Agents kept as arrays: positions, velocities, ids, kinds and the sketch’s own fields.
Every per-agent value is a numpy array whose first axis is the agent, so pop.pos[i] is agent i’s position and pop.battery[idx] the batteries of the agents idx::
Agents are spawned at the end and killed anywhere: kill closes the gaps, keeping the survivors in order, so an agent’s index can change. Its id never does, and an id is never given to another agent, so code outside the frame loop refers to agents by id and turns ids into indices with index_of. A Tree follows pop.id by itself.
Intent fields, declared with intent=, are the ones set from outside the simulation (a mind, a network message) through write, which is safe to call from any thread. The simulation reads them and leaves them alone: it keeps its own decisions in ordinary fields.
Units are the sketch’s own: positions in world units (window points, say), velocities in world units per second, times in seconds.
Parameters
Name
Type
Description
Default
n
int
The number of agents to start with.
required
dim
int
The number of dimensions.
2
kinds
Sequence[str]
The names of the kinds of agent; pop.kind holds an index into them.
('agent',)
intent
Sequence[str]
The names of the intent fields. A field that is not given a starting value is an (n,) float array of NaN, for “no intent yet”.
()
**fields
ArrayLike
Starting values, each with a first axis of n and any shape and dtype after it (a scalar fills an (n,) array). pos and vel default to zeros of shape (n, dim). kind defaults to the first kind; it takes kind indices, a kind name for all agents, or a name per agent. add_field adds a field later.
{}
Attributes
Name
Type
Description
pos
np.ndarray
(n, dim) float64 positions.
vel
np.ndarray
(n, dim) float64 velocities.
id
np.ndarray
(n,) int64 stable ids, read-only, in increasing order.
kind
np.ndarray
(n,) int64 kind of each agent, an index into kinds.
kinds
The kind names, a tuple.
intent
The intent field names, a tuple.
dim
The number of dimensions.
senses
Senses | None
What the agents perceived at the last sense, or None before the first and after a spawn or kill.
Raises
Name
Type
Description
ValueError
If a field has the wrong length or a reserved name, kinds is empty or repeats a name, or n or dim is out of range.
Sets intent fields of some agents, from any thread, at the start of the next frame.
add_field
agents.Population.add_field(name, value)
Adds a field after the population was made, as if it had been given to it then.
Parts that keep their own per-agent state use it (a Genome adds genome), and so can a sketch that grows a field later::
pop.add_field("age", np.zeros(len(pop)))
From then on the field is like any other: spawn and kill grow and shrink it with the agents. The array is taken as it is, through np.asarray, without a copy, so value itself becomes the field until the next spawn or kill replaces it with a new array.
Parameters
Name
Type
Description
Default
name
str
The field’s name: an identifier not starting with _, and not already a field or another attribute of the population.
required
value
ArrayLike
The values, with a first axis of len(pop) and any shape and dtype after it; a scalar fills an (n,) array.
required
Raises
Name
Type
Description
ValueError
If the name is reserved or taken, or value does not have one row per agent.
has_field
agents.Population.has_field(name)
Whether name is a field: pos, vel, kind or one the sketch added.
Fields are the per-agent arrays that spawn and kill keep in step with the agents. A plain attribute set on the population is not one.
index_of
agents.Population.index_of(ids)
Turns stable ids into the agents’ current indices.
Parameters
Name
Type
Description
Default
ids
ArrayLike
One id or an array of ids.
required
Returns
Name
Type
Description
np.ndarray
An int64 array of the shape of ids: each agent’s index, or -1 for an id
np.ndarray
whose agent has died (or never existed).
intent_age
agents.Population.intent_age(field)
Seconds since each agent’s last write of an intent field, on Kiln’s clock.
A tree can fall back to its own behaviour when an agent’s intent is stale::
An (n,) float array: seconds since the write landed, inf if it never did.
Raises
Name
Type
Description
ValueError
If field is not an intent field.
kill
agents.Population.kill(mask)
Removes agents and closes the gaps, keeping the survivors in order.
Their ids are not given out again. Kill between ticks of a Tree, never from inside one of its leaves: mark the agents in a field there and kill them after the tick. The tree then drops the dead agents’ state at its next tick, without calling any halt hook.
Parameters
Name
Type
Description
Default
mask
ArrayLike
A boolean array of len(pop), True for the agents to remove, or an array of their indices (integers, so an int array of 0s and 1s is indices).
required
Raises
Name
Type
Description
ValueError
If mask is boolean but not of len(pop), or neither boolean nor integer.
IndexError
If an index is negative or not below len(pop).
sense
agents.Population.sense(radius, *, fov=None)
Finds every agent’s neighbours within radius and keeps them in pop.senses.
Call it once per simulation step, before anything that reads neighbours: tree conditions read pop.senses, and separate, align and cohere reuse its neighbours for any radius up to this one instead of searching again. The search is in plain space: neighbours don’t wrap around the edges of a world whose positions do.
Parameters
Name
Type
Description
Default
radius
float
The perception radius in world units, above 0.
required
fov
float | None
The field of view: the width in radians of a cone around each agent’s heading (its velocity), outside which neighbours are not seen. None, the default, sees all around. See Senses.
None
Returns
Name
Type
Description
Senses
The new Senses, also stored in pop.senses.
Raises
Name
Type
Description
ValueError
If radius is not above 0, fov is not above 0 and at most 2 * pi, or a position is not finite.
spawn
agents.Population.spawn(k, kind=None, **values)
Adds k agents at the end, with new ids.
Fields not given start at zero (False for booleans, “” for strings), and intent fields at NaN (float) or zero, with no write yet (intent_age is inf).
Parameters
Name
Type
Description
Default
k
int
How many agents to add.
required
kind
ArrayLike | None
Their kind, a name (or an index into kinds), or one per agent; the first kind when omitted.
None
**values
ArrayLike
Starting values of any fields, each one value for all the new agents or one per agent: pop.spawn(3, pos=(0, 0)) or pos=rng.uniform(0, 9, (3, 2)).
{}
Returns
Name
Type
Description
np.ndarray
The new agents’ indices, len(pop) - k to len(pop) - 1.
Raises
Name
Type
Description
ValueError
If k is negative, kind is unknown, values names a field the population does not have, or a value can’t be cast to its field’s dtype.
step
agents.Population.step(dt, max_speed)
Moves every agent along its velocity for dt seconds, capping its speed first.
A velocity faster than max_speed is scaled down to it, keeping its direction; then pos += vel * dt. Both arrays change in place.
Parameters
Name
Type
Description
Default
dt
float
The step length in seconds, usually from kiln.clock.fixed_steps.
required
max_speed
ArrayLike
The speed limit in world units per second: one for all agents, or an (n,) array (predators faster than prey). math.inf for none.
required
Raises
Name
Type
Description
ValueError
If a max_speed is negative.
write
agents.Population.write(ids, **intent)
Sets intent fields of some agents, from any thread, at the start of the next frame.
The values are checked and copied now, on the calling thread, and land on the main thread at the start of the next frame (through kiln.call_soon), before update: a simulation step never sees half a write. Agents that die before the write lands are skipped. Each landing records the time, on Kiln’s clock, for intent_age::
# goal is an (n, 2) intent field. In a thread that listens to a model:
pop.write([17, 42], goal=[[100, 80], [300, 220]])
Parameters
Name
Type
Description
Default
ids
ArrayLike
The agents’ stable ids (pop.id), one or an array.
required
**intent
ArrayLike
New values of intent fields, each one value for all the agents or one per agent. They are cast to the field’s dtype within their kind of number (an int into a float field, not a float into an int field).
{}
Raises
Name
Type
Description
ValueError
If a name is not an intent field of the population, no field is given, or a value does not fit the field’s shape or dtype.
agents.Senses
agents.Senses(pop, radius, *, fov=None)
The neighbours each agent perceives within a radius, at the time of sense.
Made by Population.sense, which keeps the latest in pop.senses. An agent’s neighbours are the other agents closer than radius; an agent never senses itself. The arrays hold one value per agent, in the population’s order, and its kinds, at the time of sense: they stay as they are when agents move or change kind, and stop applying when agents are spawned or killed (pop.senses is then None until the next sense). Neighbours are found in plain space: they don’t wrap around the edges of a world whose positions do.
Each array is worked out the first time it is read, so a step pays only for what it uses.
With a field of view (fov), an agent sees only the neighbours inside a cone fov radians wide, centred on the way it was heading at sense (its velocity then): fov=np.pi / 2 sees 45 degrees either side, and nothing behind. Seeing is one way, so an agent may see a neighbour that does not see it. An agent standing still has no heading and sees all around, and so does every agent with fov of 2 * pi (which is the same as None). count, nearest and nearest_of count only the neighbours seen, and so do separate, align and cohere when they reuse these senses.
Attributes
Name
Type
Description
radius
The radius the neighbours were sensed within, in world units.
fov
The width of the view cone in radians, or None for all around.
Each agent’s nearest neighbour of one kind, and the distance to it.
nearest_of
agents.Senses.nearest_of(kind)
Each agent’s nearest neighbour of one kind, and the distance to it.
Prey look for the nearest predator with pop.senses.nearest_of("predator"). Only neighbours within radius count. A predator does not find itself, but does find the other predators.
Parameters
Name
Type
Description
Default
kind
str
One of the population’s kinds.
required
Returns
Name
Type
Description
np.ndarray
(index, distance), two (n,) arrays: -1 and inf for an agent with no
np.ndarray
neighbour of that kind.
Raises
Name
Type
Description
ValueError
If kind is not one of the population’s kinds.
agents.seek
agents.seek(pop, target, max_speed)
Steers each agent toward a target at full speed.
The force is desired - vel, where the desired velocity points at the target with length max_speed. Agents overshoot and turn back; arrive slows down instead.
Parameters
Name
Type
Description
Default
pop
Population
The population.
required
target
ArrayLike
One point for all agents, shape (dim,), or one per agent, (n, dim).
required
max_speed
ArrayLike
The desired speed, world units per second: one, or (n,).
required
Returns
Name
Type
Description
np.ndarray
(n, dim) forces in world units per second squared.
Steers each agent within radius of a threat straight away from it at full speed.
The opposite of seek. Agents at radius or further get no force.
Parameters
Name
Type
Description
Default
pop
Population
The population.
required
threat
ArrayLike
One point for all agents, shape (dim,), or one per agent, (n, dim).
required
max_speed
ArrayLike
The speed to flee at, world units per second: one, or (n,).
required
radius
float
How close the threat must be to be fled, world units; no limit by default.
np.inf
Returns
Name
Type
Description
np.ndarray
(n, dim) forces in world units per second squared.
Raises
Name
Type
Description
ValueError
If a max_speed is negative.
agents.wander
agents.wander(pop, strength, rng)
Turns each agent a little at random, so it meanders.
The force pushes sideways to the agent’s velocity (a still agent, in any direction), by a random amount drawn afresh from rng at every call, so headings drift in a random walk while speeds stay as they are. The same rng state gives the same forces.
Parameters
Name
Type
Description
Default
pop
Population
The population.
required
strength
float
The size of the push, world units per second squared (the standard deviation along each axis).
required
rng
np.random.Generator
The random generator, such as np.random.default_rng(seed).
required
Returns
Name
Type
Description
np.ndarray
(n, dim) forces in world units per second squared.
agents.separate
agents.separate(pop, radius, mask=None)
Pushes neighbours apart, harder the closer they are.
Each neighbour closer than radius pushes the agent straight away from it by radius - distance: a soft contact, zero at radius, radius units per second squared when they touch. The pushes add up. Agents at the same point don’t push.
With a mask, only the agents it selects take part: a pair pushes only when both of its agents are in the mask, and an agent outside the mask gets no force and gives none. The result is the force the masked agents would get if they were a population of their own. For an agent in the mask it equals the full separate minus the pushes from neighbours outside the mask; for an agent outside it is zero.
Parameters
Name
Type
Description
Default
pop
Population
The population.
required
radius
float
The personal space, world units.
required
mask
ArrayLike | None
Which agents take part: a boolean array of len(pop). All of them by default. The neighbour search still covers the whole population, so the cost follows len(pop), not the number of agents in the mask.
None
Returns
Name
Type
Description
np.ndarray
(n, dim) forces in world units per second squared.
Raises
Name
Type
Description
ValueError
If mask is not a boolean array of len(pop).
agents.align
agents.align(pop, radius)
Steers each agent toward the mean velocity of its neighbours.
The force is mean_vel - vel over the neighbours closer than radius.
Parameters
Name
Type
Description
Default
pop
Population
The population.
required
radius
float
How far an agent sees its neighbours, world units.
required
Returns
Name
Type
Description
np.ndarray
(n, dim) forces in world units per second squared.
agents.cohere
agents.cohere(pop, radius)
Pulls each agent toward the centre of its neighbours.
The force is centre - pos over the neighbours closer than radius: a spring of one per second squared.
Parameters
Name
Type
Description
Default
pop
Population
The population.
required
radius
float
How far an agent sees its neighbours, world units.
required
Returns
Name
Type
Description
np.ndarray
(n, dim) forces in world units per second squared.
agents.follow
agents.follow(pop, flow, max_speed)
Steers each agent along a flow field, as fast as the flow is strong.
The desired velocity points along the flow where the agent is; the force is desired - vel, as in seek. A flow of length 1 or more asks for max_speed, and a weaker one for that fraction of it: where the flow is 0.5 long, agents go at half speed, and where it is zero, they slow down to a stop. After Reynolds, Steering Behaviors For Autonomous Characters (1999), flow field following.
Parameters
Name
Type
Description
Default
pop
Population
The population, in 2D.
required
flow
FlowField
The FlowField.
required
max_speed
ArrayLike
The desired speed, world units per second: one, or (n,).
required
Returns
Name
Type
Description
np.ndarray
(n, 2) forces in world units per second squared.
Raises
Name
Type
Description
ValueError
If a max_speed is negative or the population is not 2D.
Steers each agent toward where a moving target will be, at full speed.
Like seek, aimed at the target’s predicted position: target + target_vel * t, where t is the time the agent takes to cover the distance at max_speed. Far targets are led a lot and near ones hardly at all, so a pursuer cuts a corner that a seeker follows round. max_lead caps t, so a far or slow pursuer doesn’t aim at a point the target won’t reach for ages. After Reynolds, Steering Behaviors For Autonomous Characters (1999).
Parameters
Name
Type
Description
Default
pop
Population
The population.
required
target
ArrayLike
Where the target is: one point for all agents, (dim,), or one per agent, (n, dim), such as pop.pos[prey].
required
target_vel
ArrayLike
The target’s velocity, world units per second, shaped as target.
required
max_speed
ArrayLike
The desired speed, world units per second: one, or (n,).
required
max_lead
float | None
The furthest ahead to aim, in seconds of the target’s velocity; None, the default, for no limit.
None
Returns
Name
Type
Description
np.ndarray
(n, dim) forces in world units per second squared.
Steers each agent within radius of a moving threat away from where it will be.
The opposite of pursue: like flee, from the threat’s predicted position, threat + threat_vel * t, where t is the time the agent would take to cover the distance at max_speed, at most max_lead. Agents at radius or further from the threat (where it is now) get no force. After Reynolds, Steering Behaviors For Autonomous Characters (1999).
Parameters
Name
Type
Description
Default
pop
Population
The population.
required
threat
ArrayLike
Where the threat is: one point for all agents, (dim,), or one per agent, (n, dim).
required
threat_vel
ArrayLike
The threat’s velocity, world units per second, shaped as threat.
required
max_speed
ArrayLike
The speed to evade at, world units per second: one, or (n,).
required
radius
float
How close the threat must be to be evaded, world units; no limit by default.
np.inf
max_lead
float | None
The furthest ahead to look, in seconds of the threat’s velocity; None, the default, for no limit.
None
Returns
Name
Type
Description
np.ndarray
(n, dim) forces in world units per second squared.
Steers each agent along a path of line segments, keeping within radius of it.
Each agent looks where it will be in look_ahead seconds and finds the nearest point of the path to there. While that is within radius, it cruises along the path, in the order of its points, at max_speed. Once it would stray further, it steers toward a point radius further along the path from that nearest point. Past the end of an open path, agents turn back round its last point. After Reynolds, Steering Behaviors For Autonomous Characters (1999), path following.
It costs one distance per agent and segment, so keep paths to tens of points.
Parameters
Name
Type
Description
Default
pop
Population
The population.
required
path
ArrayLike
The path’s points in order, (m, dim) with m of 2 or more.
required
radius
float
The path’s half width, world units, above 0.
required
max_speed
ArrayLike
The desired speed, world units per second: one, or (n,).
required
closed
bool
Whether the path loops from its last point back to its first.
False
look_ahead
float
How far ahead agents look, in seconds of their current velocity.
0.25
Returns
Name
Type
Description
np.ndarray
(n, dim) forces in world units per second squared.
Raises
Name
Type
Description
ValueError
If the path has fewer than 2 points or another dimension than the population, radius is not above 0, or a max_speed is negative.
agents.contain
agents.contain(pop, bounds, margin, max_speed)
Turns each agent back from the walls of a box before it reaches them.
Within margin of a wall, an agent wants to move away from it, along that axis, at a speed that grows from 0 at the margin’s inner edge to max_speed at the wall: max_speed * (1 - gap / margin), where gap is its distance from the wall. It keeps any faster speed away from the wall, and its velocity along the other axes. Elsewhere it gets no force.
The walls push like springs, so other forces that push agents outward settle them inside the margin, where the two balance, rather than jittering at its edge; a target inside the margin is reached only as far as the forces balance. An agent heading straight at a wall at max_speed turns round about halfway into a margin of max_speed / weight world units, and just short of the wall in one of half that. After Reynolds, Steering Behaviors For Autonomous Characters (1999), containment.
Parameters
Name
Type
Description
Default
pop
Population
The population.
required
bounds
ArrayLike
The box, as its lowest and highest corners: ((0, 0), (width, height)).
required
margin
float
How far from a wall agents start to turn, world units: 0 or more, and at most half the box’s narrowest side. With 0, only agents outside the box turn back, at max_speed.
required
max_speed
ArrayLike
The speed to turn back at, world units per second: one, or (n,).
required
Returns
Name
Type
Description
np.ndarray
(n, dim) forces in world units per second squared.
Raises
Name
Type
Description
ValueError
If bounds is not two finite corners of the population’s dimension with the lowest below the highest, the margin is negative or more than half the box, or a max_speed is negative.
Steers each agent round the round obstacles ahead of it.
Each agent looks along its velocity as far as it will go in look_ahead seconds. If that line runs into obstacles, it turns, at its current speed, to graze the one the line enters first on the side it is already heading for: along the tangent from the agent to the obstacle’s edge. Head on, it picks a side. An agent inside an obstacle heads straight out (out of the one it is deepest in), or, at its very centre, keeps its heading. Agents standing still get no force. After Reynolds, Steering Behaviors For Autonomous Characters (1999), obstacle avoidance.
Agents graze the edge, and may cut a hair inside it while they turn: add their own size to the radii to keep them clear. Give avoiding priority over other forces (np.where(dodge.any(axis=1, keepdims=True), dodge, others)) rather than adding them up, or the others pull agents into the obstacles.
It costs one test per agent and obstacle.
Parameters
Name
Type
Description
Default
pop
Population
The population, in 2 or more dimensions.
required
centers
ArrayLike
The obstacles’ centres, (k, dim).
required
radii
ArrayLike
Their radii, world units: one for all, or (k,).
required
look_ahead
float
How far ahead agents look, in seconds of their current velocity.
1.0
Returns
Name
Type
Description
np.ndarray
(n, dim) forces in world units per second squared.
Raises
Name
Type
Description
ValueError
If centers is not (k, dim) or a radius is negative.