Genes that set each agent’s traits, and the life cycle that hands them down, so traits evolve.
agents.Genome
agents.Genome(pop, genes, *, weight_count=0)
The genes of every agent of a population, and the traits they map to.
It adds a genome field to the population (pop.add_field), an (n, gene_count) float64 array of genes in [0, 1], or adopts one the population was made with (Population(n, genome=np.zeros((n, gene_count)))). New agents spawned without genes start with all zeros; agents.reproduce gives children their parent’s genes, mutated.
Each named gene maps to a trait through its range: lo + gene * (hi - lo), so a gene of 0 gives lo and 1 gives hi (a range can run downward). genome[name] is the gene itself, a writable view of its column, and genome.trait(name) the trait. The columns after the named genes are weights, for a brain to read.
Parameters
Name
Type
Description
Default
pop
Population
The population whose agents carry the genes.
required
genes
Mapping[str, tuple[float, float]]
The named genes in column order, each with the (lo, hi) range of its trait: {"size": (2, 9), "speed": (20, 90)}.
required
weight_count
int
How many more genes to keep after the named ones, for a brain’s weights. 0 for none.
0
Attributes
Name
Type
Description
pop
The population.
Raises
Name
Type
Description
ValueError
If there are no genes at all, a range is not two finite numbers, weight_count is negative, the population has a genome field of another shape or a non-float dtype, or pop.genome is an attribute that is not a field. The population is left as it was.
Each agent the mask picks has one child, and gives it a share of its energy.
The child gets share of its parent’s energy and the parent keeps the rest, so their energies add up to the parent’s before. The energy is split as it is: a parent at 0 or below has a child at 0 or below, which the usual energy <= 0 death rule takes at once, so pick parents with energy to spare. The child is spawned at the end of the population (pop.spawn) near its parent, of its kind and moving with its velocity; its other fields start at zero, as with spawn, so set them from the returned indices. Spawning clears pop.senses.
When the population carries genes (a Genome’s genome field), the child inherits its parent’s, mutated: each gene, with odds mutation, moves by a normal step of sigma and is reflected back into [0, 1]. With a crossover, the genes are first mixed with the parent’s mate, who pays nothing. The mate is its nearest neighbour of its own kind as seen at the last pop.sense (pop.senses.nearest_of): one in its field of view, if the sense had one, and of the kind and at the place it had then. A parent with no such neighbour breeds alone.
spawn, kill and die clear pop.senses, so a crossover after any of them in the same step needs the mates chosen before. Keep their ids, and turn them back into indices once the dead are gone (a dead mate’s -1 means breeding alone)::
The parents: a boolean mask of len(pop), or their indices.
required
rng
np.random.Generator
The random generator for the positions and the genes.
required
share
float
The fraction of a parent’s energy its child gets, between 0 and 1.
0.5
mutation
float
The odds that each gene changes, 0 to 1.
0.05
sigma
float
How far a changed gene moves: the standard deviation of the step, in gene units (the whole range is 1).
0.1
crossover
Crossover | str | None
How the genes are mixed with the mate’s, a Crossover; None for no mate.
None
mates
ArrayLike | None
Each agent’s mate, as one index per agent of the population, (n,), of which the parents’ are used; -1 for none. None takes the mates from pop.senses. Needs a crossover.
None
spread
float
How far from the parent the child appears: the standard deviation of its offset on each axis, in world units. 0 puts it on its parent.
0.0
Returns
Name
Type
Description
np.ndarray
The children’s indices, at the end of the population.
Raises
Name
Type
Description
ValueError
If the population has no float energy field, its genome field is not float, share, mutation, sigma or spread is out of range, crossover is not a Crossover, mates is not one index per agent or comes without a crossover, or a crossover of genes needs mates from pop.senses and there are none. Nothing is changed then.
IndexError
If a parent’s index or a mate’s index is out of range.
agents.die
agents.die(pop, mask, *, leave=None, amount=1.0)
Kills the agents the mask picks, and leaves their bodies in a field.
Each body adds amount to leave where it died (Field.deposit), so the dead feed the living: what a body is worth is the sketch’s choice, such as its size. The agents are removed with pop.kill, which keeps the survivors in order and clears pop.senses: a crossover later in the step needs its mates chosen before (see reproduce).
Parameters
Name
Type
Description
Default
pop
Population
The population.
required
mask
ArrayLike
The agents to kill: a boolean mask of len(pop), or their indices.
required
leave
Field | None
The field their bodies go into; None to leave nothing.
None
amount
ArrayLike
What each body adds to leave: one for all, or one per agent of the population, (n,), of which the dead ones’ are used. Finite, 0 or more; checked even with no leave.
1.0
Returns
Name
Type
Description
np.ndarray
Where they died: a (k, dim) array of the dead agents’ positions.
Raises
Name
Type
Description
ValueError
If mask is neither a boolean mask of len(pop) nor indices, or an amount is negative, not finite, or not one per agent.