When algae is in the pond, a fish will come to eat it. When fish are in the pond, birds will come to catch them. This is an example of peer dependency: a species’ survival depends greatly on the presence of other species.
Peer dependency is one of the key principles of open-endedness, which asks, How we can build a world where innovation never ends? Open-ended worlds follow three core principles.
- Possibility Space: A world consists of a set of organisms. The space of possible organisms must be arbitrarily large.
- Peer Dependency: When new organisms appear, an environment must develop new niches.
- Search Algorithm: A procedure must find new organisms to fit new niches.
Out of these three, peer dependency is the trickiest. In living ecosystems, animals may create byproducts or serve as food, allowing new species to emerge. For example, tall trees created a niche for long-neck giraffes. Due to the complexity of physical interactions, however, it’s hard to model this cleanly.
Instead, a clean example of peer dependency is found in the realm of chemistry. In chemical systems, some reactions may create byproducts, which will be used by other reactions. By treating each reaction as a species, let’s get a look at how peer dependency affects how new species will emerge.
In chemistry, simple elements form a complex dependency tree. Reactions take in inputs, but they also create outputs. These outputs can then become inputs to other reactions, leading to a large chain.
Let’s create a simplified chemical world, and examine the dependency tree that forms.
First, let’s define a chemical as a series of four bits. This gives us a world with a total of 2^4 = 16 chemicals.
Chemical_00: 0000 Chemical_01: 0001 ... Chemical_15: 1111
Now, let’s define a reaction function that will combine two chemicals.
def React(ChemA, ChemB): # 2:0010, 3:0011 ChemNew = BitwiseXOR(ChemA, ChemB) # 0010^0011 = 0001 ChemNewA = Swap(ChemNew, ChemA) # Swap(0001, 2) = 0010 ChemNewB = Swap(ChemNew, ChemB) # Swap(0001, 3) = 0100 return ChemNewA, ChemNewB # 2:0010, 4:0100
This is a simple deterministic function that determines the output of a two-chemical reaction. For example, reacting chemicals 00+01 will always make 05+06.
You may see where this is going – our world now forms a self-contained reaction system. As chemicals react, they will form new chemicals. These new chemicals can then react with each other, and the process will continue.
We can view our system as an open-ended world where chemical reactions are the organisms.
- Possibility Space: The space of all possible chemical reactions.
- Peer Dependency: As new reactions make byproducts, they create an oppurtunity for new reactions.
- Search Algorithm: Reactions will randomly occur if their input chemicals are present.
Let’s take a look at what a typical progression looks like.
Starting from only two chemicals, a series of reactions popped up, each building on the outputs of the previous. Eventually, the world reached a stable state, where a set of reactions formed a self-sustaining loop.
One thing to note is the branching factor of our peer depencency tree. When a new reaction occurs, it will create two new outputs. This creates a niche for only a single new reaction. And if one of the chemicals was already present, we might not get any new niche at all.
Ideally, a new reaction would create opportunities for many new reactions. One change we can make is to allow reactions to compete for chemicals. Let's define a fixed energy output for every reaction.
def ReactEnergy(ChemA, ChemB): Energy = Hash(ChemA+ChemB) return Energy
In this setup, reactions can acccess chemicals in order of their energy output. For example, let's say Reaction A and Reaction B both need Chemical 01. Reaction A has an energy output of 0.5, and Reaction B has an energy output of 0.9. Reaction B will get priority over Reaction A, even if Reaction A was around longer.
If Reaction B stays around long enough, it may use up all of Chemical 01, and Reaction A will die out.
In the energy-based system, we can see a much wider area of the possibilty space is explored. Once again, the key is in the branching factor of our peer dependency. Now, when a new chemical is added, it allows for a new potential reaction with every other chemical present in the world.
One theme so far has been self-sustaining circuits. Our first system stopped progressing when it encountered a encountered a loop in the reaction tree, and the outputs matched the inputs.
In our second system, it became a lot harder to form a stable circuit, because new reactions could undermine older reactions. Once a reaction died, all other dependent reactions would also die, triggering an extinction wave.
Given any system of reactions, there is almost always some kind of reaction that will undermine the system by using up a key chemical. When a system breaks, however, it creates room for new systems to emerge. This cycle allows the world to explore a large portion of the possibile reactions.
In theory, there should still be a system of reactions that is self-sustaining and unbreakable. For example, a closed loop of high-energy reactions cannot be interrupted. But in practice, it takes a long time to discover such a system.
Or will it? Let's try a simple change -- increase the mutate rate at which we introduce new reactions.
Interestingly, when we increase the mutation rate, almost every trial results in a self-sustaining system!
The crucial component to this system is its redudancy. Instead of simply being a reaction loop, it's a reaction web -- many reactions are all feeding into one another. Even if a few of the reactions were to die out, there are still other sources of chemicals to keep the system going.
The key cause of these intertwined self-sustaining circuits is the reaction count. It's a simple cycle: the more reactions that are present, the more likely it is that a reaction has a source for its inputs. This makes the reaction more likely to survive, therefore making every other dependent reaction more stable.
This Bitwise Chemical project was an easy way to create a peer-dependent world. With a simple search algorithm, we follow the chain of dependencies as new reactions appear. The population of species sometimes resembled a wave, where some species would go extinct while new species would rise, or would resemble a stable web, where many species supported each other.
The major limitation to open-endedness here is the possiblity space of reactions, which is very small (256). In real chemical systems, open-endedness arises because elements can form complex molecules, which can do a whole bunch of functions and interact with one another.
Further work can involve:
- How can peer dependency increase in complexity while still being interesting? E.g. if we increase chemical count to 100, we would have way more reactions, but they would all qualitatively look the same.
- What types of peer-dependency lead to stable systems? What kinds lead to unstable oscillations?
"At Home in the Universe", by Stuart Kauffman. This book argues that life on Earth was inevitable, since every sufficiently complex system will self-organize. One argument he makes is that any complex chemical system will eventually develop a self-replicating compound. Our third experiment draws parallels to this, since we easily discover a self-sustaining circuit that grows indefinitely.
"What is the Adjacent Possible", by Martin Erlic. This term, adjacent possible, provides a nice framework for looking at emergent design. Ideas don't just form out of nowhere -- they are made possible because of current ideas. By following a path of adjacent possibilities, we can see the development of a world. For a design to exist, not only does it need to fulfill a minimum criteria for survival, it also needs to be discoverable by being on a branch of the "possibility tree".