Two Parents, One Child
Recombination keeps separate discoveries
Sixty-four places in the next generation have been filled by copying a single genome. That carries a hidden rule: a genome can improve only by mutations that arrive in its own lineage, in the right order, before selection loses it. A birth takes one number to decide whether it has two parents, then one number for every gene to decide which parent supplies that gene.
The first parent is the parent of record. The child starts as its copy, and the other parent's numbers are written into the selected genes. Everything that is not a gene follows the parent of record, so ancestry stays readable.
The single-parent rule throws away discoveries that happen in different branches. One lineage might find a cheaper body while another finds a better turn. If they never share a child, selection has to wait for one of those branches to rediscover the other's answer.
Recombination makes that trade explicit without making draws outcome-dependent. A child with two parents still spends a fixed number of rolls, and a child with one parent spends the same birth decision before the per-gene draws are skipped by rule.
The chapter runs two hundred generations twice. The comparison is not a promise that two parents always win; it is a check that the operator can mix working brains and still replay from the same seed.
The crossing stream and the flat walk
A genome is four blocks read as one flat run of numbers: ten body factors, then three hundred and seventy-eight weights for the controller, then three temperament genes, then four look genes. Everything that copies or mutates a genome walks that run by index and asks it for the range of whichever gene it is standing on, so nothing in the package has to know which block it is in.
Crossing walks the same run, and the choice it has to make is what counts as the same
gene in two different genomes. There is exactly one answer available here, and it is the
position: gene 4 is Bite in every genome that has ever existed, gene 391 is
the coat hue in every one of them, and gene 71 is the sixty-second weight of a network
wired the same way in both. Matching by index is the only correspondence the flat run
supplies, and for the ten body genes and the seven at the end it is exactly right. Hold on
to the fact that the middle three hundred and seventy-eight are being matched by the same
rule, because the last section of this page is about what that assumption is worth.
// internal/gene/cross.go
// StreamCross is the stream a birth spends on where a child's genes
// come from, and the fourth and last stream this volume's arena reads.
// The opening generation comes off 15, the trials off 21, who a parent
// is off 18, the mutation pass off 16, and which of two parents each
// gene was copied out of comes off here.
//
// It is its own stream for the reason every other one is. A run that
// turns crossing off must not thereby change which genomes it started
// from, which four trials it measured them on, or which numbers its
// mutation passes read, and the only way to promise that is to spend
// the crossing somewhere nothing else is looking.
const StreamCross = 17
// Mix is what one crossing came to: the numbers it took off the
// stream, whether the birth had two parents at all, and how many genes
// the child was copied out of the second of them.
//
// Draws is here to be checked rather than read, and the thing to check
// is that it is one more than the length of the genome every single
// time, at every rate, whatever the draws said.
type Mix struct {
Draws int
Two bool
Took int
}
// Cross builds one child's genome out of two.
//
// The first number off the stream decides whether this birth has two
// parents at all. Under the rate it does; at or over it the child is
// the first parent copied, and the second parent is never read. Then
// one number for every gene, in the flat walk's own order: under a
// half the gene comes from the first parent and at or over it from the
// second.
//
// The first parent is the parent of record. The child is built by
// copying it and then writing the second parent's numbers into the
// genes the stream awarded, so everything a child carries that is not
// itself a gene (its lineage tag, and in a valley the body it is
// charged against) comes from one named parent and never from two. A
// birth with one parent runs this same procedure with every gene
// awarded to the parent of record.
//
// The per-gene draw is taken whether or not this birth has two
// parents, exactly as the mutation pass takes its second draw whether
// or not a gene moves. A crossing is 1 + G numbers on a genome of G
// genes, always, so a run at a rate of 0.75 and a run at a rate of
// nothing read the same numbers in the same order and differ only in
// the genomes they end up with.
//
// Two identical genomes cross to that genome exactly. Either branch
// writes the same number, so the child is the parent bit for bit, and
// the draws are spent all the same.
func Cross(a, b *Genome, sex float64, s *rand.Rand) (*Genome, Mix) {
var m Mix
m.Two = s.Float64() < sex
m.Draws++
g := a.Copy()
for i, n := 0, g.Len(); i < n; i++ {
from := s.Float64()
m.Draws++
if !m.Two || from < 0.5 {
continue
}
g.Put(i, b.At(i))
m.Took++
}
return g, m
}
$ go run ./cmd/cross -mode child | head -24
cross: genome 43 crossed with genome 27, seed 5, stream 17
the first number decides the birth: at a rate of 0.75 this one has two parents
block genes from 43 from 27
BODY 10 7 3
MIND 378 199 179
TEMPER 3 1 2
LOOK 4 2 2
all 395 209 186
the ten body factors, side by side
i gene 43 27 child from
0 Bulk 1.6432 2.8525 1.6432 43
1 Full 2.9017 3.1672 2.9017 43
2 Basal 1.7494 2.1980 1.7494 43
3 Work 0.7440 0.8114 0.8114 27
4 Bite 0.9573 2.0934 0.9573 43
5 Convert 2.8665 0.5840 2.8665 43
6 Reach 3.1559 3.4079 3.1559 43
7 Sight 1.7462 1.5440 1.5440 27
8 Top 0.4052 3.0604 0.4052 43
9 Swing 2.5103 1.4184 1.4184 27
Read the body table as ten separate coin tosses, because that is what it is. Seven of
the ten landed on genome 43 and three on genome 27, and the child that comes out of
them is mostly 43's animal with 27's eyes, 27's turning circle and 27's cost of
walking. Nothing in there is an average of two numbers. Every value in the child is a
value one of its parents was already holding, which is what separates crossing from
mutation as an operator: mutation invents numbers and crossing only ever moves them
about. That has a practical consequence in the code, and it is that the
Put call at the bottom of the loop can never be asked to clamp anything.
A number that was inside a gene's range in a parent is inside it in the child.
The one thing on that listing that looks like waste is the same thing that looked like waste in the copying operator. Every gene takes a number off the stream even when the birth turned out to have one parent, so a quarter of all births in this world pull three hundred and ninety-five numbers out of the generator, read none of them, and drop the lot. What they buy is the same guarantee the copying pass bought: how far along the sequence a birth leaves the stream is settled by the length of the genome and by nothing else. Turn the two-parent rate from 0.75 to nothing and the run reads the same numbers in the same order, so the difference between the two runs is the genomes and only the genomes.
// internal/gene/cross.go, continued
// Pair is the half of a breeding rule that says where a second parent
// comes from: how often a birth has one at all, and which board the
// tournament that finds it reads.
//
// Sex is the share of births with two parents. It is a dial like the
// tournament size, and the two ends of it are both real rules: at 1.00
// every child is a crossing and at 0.00 every child is a copy, which
// is what the population did before there was a second parent.
type Pair struct {
Sex float64
// Strangers is the second parent as it is tempting to take it: out
// of a population bred somewhere else, on the argument that a run
// which has come down to one lineage has nothing left to cross with
// and could use some fresh blood. It is kept behind a flag so what
// crossing two lineages that never met costs can be run instead of
// described, and it is false in every run this book ships.
//
// Guest is that population and On is what it scored on this arena.
// Both are read and neither is bred: the strangers stand still and
// the home population crosses into them.
Strangers bool
Guest []*Genome
On []float64
}
// Both is what every two-parent run on this page is made with: three
// births in four have two parents and the fourth is one parent copied.
var Both = Pair{Sex: 0.75}
// Alone is the same rule with the second parent never used. It still
// holds the second tournament and still spends the whole crossing pass,
// so it is the run to compare Both against and not the one the
// selection page made.
var Alone = Pair{Sex: 0.00}
$ go run ./cmd/cross -mode count
cross: what a generation of 64 spends, at four settings of the rate
rate two parents stream 18 stream 17 stream 16 if thrifty
0.00 0 512 25344 50560 64
0.25 13 512 25344 50560 5199
0.75 43 512 25344 50560 17049
1.00 64 512 25344 50560 25344
one place in the next generation, with a second parent to find
two tournaments of 4, one number a contestant 8 off stream 18
the birth, and then one number a gene 396 off stream 17
the mutation pass, two numbers a gene 790 off stream 16
a place costs 1194
and a generation of 64 costs 76416
the same generation when every child has one parent
one tournament a place 4 off stream 18
nothing at all 0 off stream 17
the mutation pass, two numbers a gene 790 off stream 16
a place costs 794
the last column is the crossing written the thrifty way: one number for the
birth, and the 395 gene numbers only when the birth turned out to have two
parents. It is four different totals for four rates, and it is a different
total again on any other seed, which is what a count nobody can predict means
The three middle columns of the top table are the promise, and they are the same three numbers on every row. Four rates from nothing to certainty, thirteen two-parent births on one row and sixty-four on another, and the streams cannot tell the difference. The last column is what the obvious implementation would have spent instead: sixty-four numbers when nothing crossed, twenty-five thousand three hundred and forty-four when everything did, and five thousand one hundred and ninety-nine on a rate that nobody can work out in advance because it depends on how sixty-four coin tosses landed.
Now the arithmetic of one place, because a second parent has to be found as well as used. The first parent comes out of a tournament, four genomes drawn off stream 18 with the best card winning. The second comes out of a second tournament off the same stream, held the same way over the same board. So a place costs eight numbers off 18 where it used to cost four, three hundred and ninety-six off 17 where it used to cost nothing, and the same seven hundred and ninety off 16 as always: 1,194 numbers a place and 76,416 a generation, against 794 and 50,816 for a generation whose children have one parent each.
That second tournament is why this page runs its own breeding loop rather than adding a setting to the one the selection page built. A generation here reads stream 18 twice as many times, and a stream read a different number of times is a different world. Folding crossing into the existing loop would have moved every figure the previous page measured, which is a bill no later chapter gets to send an earlier one. The consequence for a reader is plain: the one-parent column in every table below is a run of this loop with the rate set to nothing, holding its second tournament and throwing the winner away. It is the right thing to compare against, and it is not the same run as the one-parent runs on the page before.
Parent of record
A child of two parents needs somebody to be its parent. Not for its genes, which are settled gene by gene, but for everything a child carries that is not a gene. The run keeps one integer for every genome alive saying which of the opening sixty-four it descends from, and a child of two has two candidates for that integer. Something has to choose, or the count of surviving lineages stops meaning anything at exactly the moment it becomes interesting.
The choice here is the first tournament's winner, and the reason is that it makes a one-parent birth a special case of a two-parent one instead of a separate rule. The first tournament happens either way. Its winner is the genome that would have been the only parent had the crossing not fired. Making it the parent of record means the lineage bookkeeping reads the same in a run that crosses and a run that does not, so the two columns of every table below are counting the same thing. In this arena that integer is the only thing the parent of record supplies, and the page has to be plain about that: in a world where a birth costs somebody something, the parent of record is also whoever pays.
// internal/gene/cross.go, continued
// Brood is one run of the arena bred in pairs: the population, the
// three streams a generation spends, the rule that decides who makes
// the next one, where a second parent comes from, and the opening
// genome every member descends from.
//
// It is a second loop beside the one-parent Loop and not a mode on it.
// A place in this generation holds two tournaments where that one holds
// one, so a generation here takes twice as many numbers off stream 18,
// and a stream drawn a different number of times is a different world.
// Folding the two together would have moved every run the selection
// page measured, which is a price no later chapter gets to charge an
// earlier one.
type Brood struct {
Arena *Arena
Rule Rule
Pair Pair
Pop []*Genome
Root []int
Gen int
pick *rand.Rand
mix *rand.Rand
mut *rand.Rand
drew []int
kept Card
held bool
}
// internal/gene/cross.go — inside Brood.Step, the loop over places
// The board the second tournament reads, and the population it
// indexes into. They are this generation's own unless the flag says
// otherwise, in which case they are the strangers'.
board, from := on, l.Pop
if l.Pair.Strangers {
board, from = l.Pair.On, l.Pair.Guest
}
next := make([]*Genome, len(l.Pop))
root := make([]int, len(l.Pop))
for i := range next {
won := l.Rule.Tournament(on, l.pick, l.drew)
r.Picks += l.Rule.Size
mate := l.Rule.Tournament(board, l.pick, nil)
r.Picks += l.Rule.Size
if won == mate && !l.Pair.Strangers {
r.Selfed++
}
kid, x := Cross(l.Pop[won], from[mate], l.Pair.Sex, l.mix)
r.Cross += x.Draws
if x.Two {
r.Mixed++
r.Took += x.Took
}
m := l.Rule.Rates.Mutate(kid, l.mut)
r.Draws += m.Draws
if i < l.Rule.Elite {
next[i], root[i] = l.Pop[r.Champ].Copy(), l.Root[r.Champ]
continue
}
next[i], root[i] = kid, l.Root[won]
}
Two things in that loop are inherited rules rather than new ones, and both would be
easy to break. The elite place still draws its two tournaments, still crosses, still
runs a whole mutation pass, and still drops all of it on the floor before writing the
champion in, so carrying a champion costs the streams nothing. And the contestants are
still drawn with a multiply and a floor over an evenly drawn number instead of with
IntN, whose rejection loop can ask the generator twice. Two tournaments a
place means two chances a generation to spend a number nobody predicted, and a
selection rule that only usually costs what it says it costs is not a selection rule
anybody can replay.
One line in that loop counts something a reader might otherwise call a bug. Contestants are drawn with replacement, and the two tournaments read the same board, so both of them can name the same genome, and then a genome is crossed with itself. Nothing needs handling. Crossing a genome with a copy of itself hands that genome back, gene for gene, because both branches of the loop write the same number. Over the two hundred generations further down it happens 467 times out of 12,864 places, rather more often than one place in sixty-four, because two tournaments over a board where the same few genomes keep winning keep naming the same few genomes.
$ go run ./cmd/cross -mode child | tail -13
numbers off stream 17, one for the birth 1
and one for every gene of the walk 395
the crossing spent 396
how far apart the three of them are, gene by gene, as a share of each range
genome 43 from genome 27 0.3233
the child from genome 43 0.1615
the child from genome 27 0.1618
one mutated copy of genome 43 0.0047 37 genes moved
and genome 43 crossed with a copy of itself, 396 numbers later:
the largest gap anywhere in the child is 0.0000, at no gene at all
The three gaps in the middle of that block are the operator's whole character in four decimal places. Two genomes drawn out of nothing sit 0.3233 apart, averaged gene by gene as a share of each gene's own range. Their child sits 0.1615 from one of them and 0.1618 from the other. It landed almost exactly half way between its parents, and it did so without anything anywhere calculating a midpoint. Underneath that is the last line but one: a mutation pass moves the same genome 0.0047, which is a thirty-fourth of the distance. A crossing between two animals that are not related is an enormous step by the standards of anything else in this package, and a crossing between two that are closely related is a very small one.
Every gene is one toss of a fair coin, and there are 395 of them. So the count of genes a child takes from the second parent is a count of heads in 395 tosses, and the average of that is
from the mate = G ÷ 2 = 395 ÷ 2 = 197.5
with the run of tosses wobbling on both sides of it by
wobble = √G ÷ 2 = 19.87 ÷ 2 = 9.94
genes either way. The crossing at the top of the page took 186 from the mate, which is a little over one wobble low, and across the 9,640 two-parent births in the long run further down the average came to 197.58. The prediction and the measurement are the same number.
Now how far that leaves the child from each parent. Take one gene at a time. If the two parents hold the same number there, the child holds it too and the gene contributes nothing to any distance: crossing can do nothing whatever at a gene the parents agree about. If they differ by d, the child is either 0 or d away from the parent of record, and it is each of those half the time. Add that up over the whole genome and the expected distance from either parent is half the distance between them:
gap(child, parent) = gap(parent, mate) ÷ 2
The run says 0.3233 between the two parents and 0.1615 and 0.1618 from the child to each, and 0.3233 ÷ 2 = 0.16165. That is the arithmetic landing on the nose.
The consequence is the whole result of this page in advance. Nobody set crossing's step size, and nobody can: it is half of however far apart the two parents happen to be, so it is huge in a population that is still varied and it shrinks to nothing as the population converges. Mutation's step size is fixed by its rates and does not care what the population looks like. Two operators, one with a reach that depends on the population and one with a reach that does not, is a different thing from two operators with two rates.
Four properties of the operator break silently if they break at all, so they are tests and not paragraphs.
$ go test ./internal/gene/ -run 'CrossingTwoIdentical|ACrossingSpends|EveryGeneOfAChild|TheSecondParentIsDrawn' -v
=== RUN TestCrossingTwoIdenticalGenomesYieldsThatGenome
--- PASS: TestCrossingTwoIdenticalGenomesYieldsThatGenome (0.00s)
=== RUN TestACrossingSpendsOneDrawAGeneAndOneMore
--- PASS: TestACrossingSpendsOneDrawAGeneAndOneMore (0.00s)
=== RUN TestEveryGeneOfAChildCameFromAParent
--- PASS: TestEveryGeneOfAChildCameFromAParent (0.00s)
=== RUN TestTheSecondParentIsDrawnWhetherOrNotItIsUsed
--- PASS: TestTheSecondParentIsDrawnWhetherOrNotItIsUsed (0.11s)
PASS
ok theworld/internal/gene 0.119s
The first crosses a genome with a copy of itself fifty times over and demands the largest gap anywhere in the child be exactly nought, every time. That claim matters more than it looks: by the tenth generation of a real run the population is nearly all copies of one animal, and an operator that could put a number somewhere neither parent had it would be a second mutation operator with no rate and no clamp attached to it. The second holds fifty crossings at three different rates off three generators opened on one seed and demands all three end up standing at the same number, which is the draw count written as a test. The third checks every gene of every child against both parents and fails if a single value matches neither. The fourth runs two whole populations, one crossing three births in four and one crossing none, and asks all six of their generators for one more number afterwards.
Two 200-generation runs
Everything so far is a claim about one birth. The question the page is named for needs a population and a lot of generations, and it is cheap to ask: two hundred generations of sixty-four genomes on four trials of six hundred ticks, run twice off one seed, is about forty seconds of wall clock on the machine this was written on, an eight-core Ryzen 7 3700X on Linux with the generation split across eight goroutines. Yours will differ; the numbers below will not.
$ go run ./cmd/cross -mode climb
cross: 200 generations of 64, tournaments of 4, 1 champion kept, run twice off one seed
0.75 of births with two parents one parent, every birth
gen best average spread best average spread
0 179.4530 24.5807 0.2858 179.4530 24.5807 0.2858
20 996.0000 762.6483 0.0815 866.2925 644.6184 0.0571
40 1131.0000 1045.4329 0.0968 1117.0000 949.8589 0.0726
60 1153.0000 1069.2169 0.1009 1125.0000 1028.2874 0.0862
80 1154.0000 1086.9211 0.0776 1137.0000 1093.1609 0.0567
100 1154.0000 1073.5150 0.0697 1144.0000 1075.7781 0.0553
120 1154.0000 1093.1623 0.0815 1146.0000 1082.9524 0.0597
140 1154.0000 1067.1373 0.0851 1146.0000 1081.1792 0.0641
160 1154.0000 1086.4126 0.0819 1146.0000 1055.2008 0.0602
180 1154.0000 1095.9128 0.0705 1146.0000 1068.9389 0.0677
200 1154.0000 1057.6664 0.0835 1146.0000 1084.3686 0.0580
crossed alone
the best genome's score 1154.0000 1146.0000
the average of the generation 1057.6664 1084.3686
how far apart the genomes are 0.0835 0.0580
the generation it came down to one lineage 14 20
births with two parents 9640 of 12864
genes a crossed child took from the second 197.58
places whose two tournaments named one genome 467
numbers the crossed run took off stream 17 5094144
the alone run took the same 5094144 and used none of them
Crossing wins, and the size of the win is not the eight grams at the bottom of the two best columns. Look instead at where the two curves sit at the same generation. At generation 20 the crossed run is at 996.0000 against 866.2925. At 60 it is at 1153.0000 against 1125.0000, and 1153.0000 is already better than anything the one-parent run manages in the whole two hundred. The crossed run reaches its final number at generation 80; the one-parent run is still climbing at 100 and does not settle until 120. Same seed, same arena, same four trials, same opening sixty-four genomes, same tournament, same champion, same mutation rates, same numbers off all three streams in the same order. The only difference is that three births in four took some of their genes from a second animal.
The reason the finishing gap is small is a ceiling rather than a tie. A bite occupies two ticks, so six hundred ticks hold at most three hundred bites, and the largest bite this world's ranges allow is four times the founding row's quarter gram, which is exactly one gram. Four trials of three hundred bites at a gram each is 1,200.0000 grams and there is no more food to be had. The crossed run finished at 96.2 percent of that and the one-parent run at 95.5 percent, and two runs pressed up against the same wall cannot be far apart at the end however differently they got there.
Two of the summary lines cut the other way and both are real. The crossed population's average at generation 200 is 1057.6664 against 1084.3686, so the typical animal in the crossed run is the worse animal. A crossed child is about half the distance to another genome away from its parent of record, and a mutated child is a thirty-fourth of that, so the crossed population is carrying much more damage at any moment. And the crossed run came down to a single lineage at generation 14 against 20, which is the faster climb showing up in the bookkeeping: whichever line is winning takes over sooner when it is improving faster.
The line that does not cut the other way is the spread. The crossed population sits at 0.0835 and the one-parent population at 0.0580, and every reading of the crossed column after generation 20 is the larger of the two. Crossing cannot invent a number, so it cannot be making that variety; what it is doing is failing to destroy it. A one-parent population is one genome plus a mutation pass, over and over, and it collapses towards whatever the current champion is. A population where three children in four are built out of two different animals keeps mixtures alive that a chain of copies would have narrowed away.
The three crossover worlds
All of that is one seed, and the previous page has already shown what a single seed is worth when the claim is about a middle setting. So here is the shorter version of the same comparison on three different worlds: three opening generations, three sets of four trials, three histories.
$ go run ./cmd/cross -mode seeds
cross: 60 generations of 64 at three seeds, crossed against alone
seed births gen 1 gen 10 gen 30 gen 60 lineages
5 two parents 260.0000 839.5739 1103.0000 1153.0000 1
5 one parent 201.6845 578.2013 1074.0000 1125.0000 1
7 two parents 459.5906 1014.9222 1127.0000 1154.0000 1
7 one parent 460.7836 806.6432 954.6328 1070.0000 1
11 two parents 290.5054 1013.7853 1135.0000 1148.0000 1
11 one parent 320.0000 761.1379 960.1733 1092.0000 1
Crossing is ahead at generation 10, at generation 30 and at generation 60 on all three seeds. The margin at generation 10 is 261, 208 and 253 grams; by generation 60 it has closed to 28, 84 and 56, because two runs pressed against the same ceiling cannot stay far apart. At generation 1 crossing is behind on two of the three, which is what a reader can expect: one generation of crossing over a board that has not been selected yet is sixty-four large random steps taken among sixty-four mostly useless genomes, and it takes a few generations of the tournament before there is anything to recombine. Three seeds is not many, and the claim they support is the modest one: on this bench, in this arena, at these rates, a population that breeds in pairs climbs faster than the same population breeding alone, and it is not close.
Two working brains
Here is the idea that follows naturally from everything above, and it is wrong in an instructive way. Crossing works because it puts two discoveries in one body. A run that has come down to a single lineage has only one discovery left to put anywhere, and its spread has fallen to about a third of what it opened with. So find another population that solved the same arena separately, and cross into that. Two lineages, two sets of answers, and the recombination of them can beat either.
The bench holds the second population still and lets the home run take its second parent from there. Both are bred on the same plate against the same four trials, so nothing here is confounded by a different exam. The only thing that makes them strangers is that they descend from different opening genomes.
$ go run ./cmd/cross -mode strangers | head -15
cross: two lineages bred on one plate off two seeds, 60 generations each
seed 5 seed 7
the champion's score 1153.0000 1140.0000
the generation's average 1069.2169 1052.5783
lineages left of the 64 1 1
how far apart the two champions are 0.3193
how far apart the home champion and the second 0.0028
16 children of each pair, crossed on stream 17 and scored on the same four trials
the pair parent mate best kid median worst
the two champions 1153.0000 1140.0000 1092.0000 660.0000 164.0000
champion and second 1153.0000 1150.0000 1153.0000 1153.0000 1150.0000
Two animals that are as good as anything this arena has produced. One eats 1153.0000 grams of a possible 1,200 and the other 1140.0000. Cross them sixteen times and the best of the sixteen children eats 1092.0000, which is worse than both parents; the middle one eats 660.0000, which is a little over half of what either parent manages; and the worst eats 164.0000. Not one of the sixteen is as good as either animal it came from. The control below it is the same operator on the same stream over two animals from the same lineage, the champion and the runner-up, and every one of those sixteen children is a fine animal.
The number that separates the two rows is on the line above them. The two champions are 0.3193 apart, gene by gene, as a share of each gene's range. Two genomes drawn at random out of nothing at all, at the top of this page, were 0.3233 apart. Sixty generations of selection on the same bench, and from the crossing operator's point of view two finished champions of two lineages are as unrelated as two genomes nobody bred at all. The champion and the runner-up of one lineage are 0.0028 apart, which is a hundredth of that.
The reasoning from symptom to cause runs through the ten body genes first, because they
are the ones that cannot be the problem. Gene 4 is Bite in both champions.
Take one champion's bite and the other's reach and you get an animal with that bite and
that reach: the numbers mean the same thing in both genomes, so the trade is a real
trade and the child is a real animal. Every one of the seventeen genes outside the
controller works that way.
The other three hundred and seventy-eight do not, and nothing in the flat run says so. Weight 61 in one champion is the strength of the wire from a particular sensor into a particular unit of the middle row, and weight 61 in the other champion is the strength of the wire in the same position of a network wired the same way. What they are not is the same wire doing the same job, because nothing ever assigned jobs to those units. Each lineage started from noise and settled into whatever internal arrangement its own copying stumbled into. One may be using unit 3 to mean "food is to my left" and the other unit 9 for the same thing, with unit 3 doing something else entirely. Both networks work. Neither of them agrees with the other about what any position means, and the operator is matching them up by position and by nothing else. Take half the wiring of one arrangement and half of another and the result is a network whose units are being told two incompatible stories about what they are for, and none of the competence of either animal survives the telling.
That is what a competing convention is, and the flat vector cannot see one. Position
carries meaning in the body block because the block was written down by a person and
gene 4 was declared to be Bite before any animal existed. Position carries
no meaning at all in the controller, because the controller's meanings were not
declared by anybody: they were arrived at, separately, twice.
Now build a rule on it and watch what a rule built on it does.
// internal/gene/cross.go — inside type Pair struct
// Strangers is the second parent as it is tempting to take it: out
// of a population bred somewhere else, on the argument that a run
// which has come down to one lineage has nothing left to cross with
// and could use some fresh blood. It is kept behind a flag so what
// crossing two lineages that never met costs can be run instead of
// described, and it is false in every run this book ships.
Strangers bool
$ go run ./cmd/cross -mode strangers | tail -20
60 generations with the second parent taken from the strangers
gen best average spread best average spread
0 179.4530 24.5807 0.2858 179.4530 24.5807 0.2858
10 1137.0000 1053.0756 0.0941 839.5739 370.7045 0.2025
20 1138.0000 1044.6017 0.0860 996.0000 762.6483 0.0815
30 1140.0000 1051.0621 0.0907 1103.0000 1001.9389 0.0791
40 1140.0000 1058.3013 0.0934 1131.0000 1045.4329 0.0968
50 1140.0000 1058.7068 0.0917 1147.0000 1044.0960 0.0997
60 1140.0000 1078.6719 0.0855 1153.0000 1069.2169 0.1009
the left three columns cross into the strangers, the right three stay at home
the best genome's score 1140.0000 1153.0000
the average of the generation 1078.6719 1069.2169
and how far each run's champion has got from the guest champion
a drawn genome, before anything is bred 0.3399
the run that crossed into the strangers 0.0428
the run that stayed at home 0.3193
The first ten generations of that look like a triumph. The run crossing into the strangers is at 1137.0000 grams at generation 10 while the run staying at home is at 839.5739, and its population averages 1053.0756 against 370.7045. Anybody watching the first ten generations would ship it. Then it stops. It reaches 1140.0000 at generation 30 and is still at 1140.0000 at generation 60, while the home run goes past it and finishes at 1153.0000.
1140.0000 is exactly what the guest champion scored, and the three lines at the bottom say what actually happened: the champion of the run that crossed into the strangers ends up 0.0428 away from the guest champion, having started, like any drawn genome, about 0.34 away. The home run's champion is still 0.3193 away. The fresh blood did not mix into the home population. It replaced it. Three quarters of every child had half its genes from the guest board, and a few generations of that plus a tournament that kills anything not working leaves a population which is mostly guest, crossing with guests, and therefore crossing with cousins again. It inherited the strangers' answer along with the strangers' ceiling, and it stopped.
No line of that is a bug and none of it is a surprise once the gap numbers are read in the right order. Crossing moves a child half way to the other parent. Do that repeatedly against a population that never moves and the only fixed point is the population that never moves.
Why crossover can mix discoveries
Strip the creatures out and there are two mechanisms here, and they are not two settings of one thing.
Mutation makes numbers. It can put a value where no genome in the population has ever had one, its step size is whatever its rates say, and it does not care in the slightest what the rest of the population looks like. Crossing makes no numbers at all. Every value in every child was already sitting in one of its parents, so a crossing can only ever rearrange what a population is holding between them. That single fact carries almost everything on this page. It is why the operator never has to clamp. It is why crossing two identical genomes gives that genome. It is why the step it takes is half the distance between the parents and therefore huge in generation 5 and negligible in generation 150. It is why a converged population cannot be rescued by crossing it with itself, and why crossing it with something far away replaces it instead of enriching it.
The second mechanism is the matching, and it is the one that generalises furthest past creatures. Recombining two solutions requires knowing which part of one corresponds to which part of the other, and here that knowledge is supplied by nothing more than the index in a row. For any structure whose layout was decided in advance and written down, the index is a real correspondence and recombination is a real trade. For any structure whose internal organisation was arrived at rather than declared, the index is a coincidence of storage order, and recombining along it takes two working things apart along a seam that neither of them has. The controllers on this page are the second kind. Two of them can implement identical behaviour with the units in a different order, the same numbers in different places, or two different arrangements that happen to work, and nothing in a flat run of 378 numbers distinguishes any of that from a real correspondence.
Which is why the operator is at its best where it looks least ambitious. Inside one lineage, every controller descends from one controller, so the units mean the same things in all of them and position really does correspond. That is exactly the case where the parents are close together and the crossing step is small. The measured result of this page is that the small, safe, within-a-lineage version of crossing buys two hundred and sixty grams by generation 10 and forty generations at the end of the run, while the ambitious version takes a 1153-gram animal and a 1140-gram animal and makes sixteen children whose middle one manages 660.
- You can say how many numbers one place in the next generation takes off each of streams 18, 17 and 16, and why none of the three totals moves when the two-parent rate does.
- You can name the one thing the parent of record supplies to a child in this arena, and say why a birth with one parent runs the same procedure as a birth with two.
- Handed the gap between two parents, You can predict how far their child will sit from each of them, and say what happens at a gene the two parents agree about.
- You can explain why the crossing operator never needs to clamp anything, and why crossing a genome with a copy of itself has to give that genome back exactly.
- You can say why the one-parent column on this page is not the same run as a one-parent run bred without a second tournament, and what would have broken if the two loops had been folded into one.
- Shown two champions of two separate runs that both score well, You can predict what their children score and name the assumption about the middle 378 genes that fails.
Exercise 1 — the rate as a dial. The runs above cross three
births in four. Predict what happens at every birth and at every second birth, then
run go run ./cmd/cross -mode climb -sex 1 -gens 60 and
-sex 0.5 -gens 60 and compare the best score at generation 60 with the
1153.0000 the default reaches.
Crossing every birth reaches 1147.0000 at generation 60 and crossing every second one reaches 1148.0000, against 1153.0000 for three in four. All three are far ahead of the 1125.0000 the one-parent column reaches in the same sixty generations, and the six grams between them are inside the wobble of a single run. On this bench the distinction that pays is between crossing and not crossing at all; the exact rate anywhere above about a half is a detail.
One line at the bottom of each run is the check that the comparison is fair. Every one of them prints the same 1,545,984 numbers off stream 17, at every rate, which is 3,904 births at 396 apiece. Three runs that read the same amount of the same sequence are three runs that differ only in what they did with it.
Exercise 2 — the same mask twice. Run
go run ./cmd/cross -mode child -one 12 -two 19, which crosses two
genomes that never once changed cells in the opening generation. Compare its block
table with the one at the top of this page and explain what you see.
The block table is identical: 7 and 3 in BODY, 199 and 179 in MIND, 1 and 2 in TEMPER, 2 and 2 in LOOK, 209 and 186 in all. Two completely different pairs of parents, and the same genes went to the same sides. That is not a coincidence and it is not a bug. Both runs open a generator on the same seed and the same stream and take 396 numbers off the front of it, so both get the same 396 numbers, so the same genes fall on the same side of a half.
The gaps underneath differ, which is the part that is about the genomes rather than the stream: 12 and 19 sit 0.3306 apart, and their child sits 0.1536 from one and 0.1769 from the other. Both are near half of 0.3306, and they are not equal to each other because the 209 genes that went one way were not carrying exactly half of the total difference. Half is the average, not a promise about one crossing.
Exercise 3 — strangers who are not yet very good. The failure
crossed two finished champions. Predict what happens to two champions of
ten-generation runs, which score near 830 grams instead of near 1,145, then check
with go run ./cmd/cross -mode strangers -short 10.
It collapses in exactly the same way. The two champions score 839.5739 and 828.9238; their sixteen children run from 813.0969 down to 0.0000 with a median of 320.0000, and not one of them beats either parent. The gap between the two champions is 0.3294, which is the same distance apart as the finished ones.
So the failure turns on how far apart the parents are and not on how good they are, and ten generations is already long enough for two lineages to have settled into arrangements that do not correspond. The control row is the interesting part of this run: the champion and runner-up of one ten-generation lineage are only 0.0089 apart, and their children have a median of 777.5803 against parents of 839.5739 and 800.1431. Even cousins pay a small toll for being crossed. It is a toll and not a collapse.
The population in the arena now has two ways to make a genome it has never seen and a tournament deciding which genomes get to make them. Every one of those decisions was settled by comparing two numbers, and every one of those numbers came from the same place: a card with grams eaten on it, produced by a bench somebody built, running trials somebody chose, for six hundred ticks somebody picked. The animals have got very good at the thing they were asked for. What nobody in this arena has been asked yet is whether eating the most grams in four minutes of world time on a plate with no weather is the same as being an animal that lasts.