Assembly growth along the assembly index
The numbers of objects at successive assembly indices, each discovered from the level below.
- Name:
assembly_growth - Field:
biology/evolution - Formalism: ordinary differential equations
- References: Sharma et al. 2023
Select this system
from skcomplex.systems import create_simulation, create_system, describe
system = create_system('assembly_growth')
entry = describe('assembly_growth')
trajectory = create_simulation('assembly_growth').prepare().run().trajectory_
Factory
@register_system(
field='biology/evolution',
tags=('assembly theory', 'selection', 'open-ended evolution', 'ode'),
references=('Sharma et al. 2023',),
defaults={'until': 10.0, 'dt': 0.1},
)
def assembly_growth(levels: int = 10, *, k: float = 1.0, alpha: float = 1.0) -> System:
"""Return the numbers of objects at successive assembly indices, each discovered from the level below.
The objects of assembly index `a + 1` grow as `dN_{a+1}/dt = k (N_a)^alpha` from a constant pool of building
blocks. Without selection, `alpha = 1`, every object built before is reused and `N_a(t) = (k t)^a / a!`, the
exponential expansion of the assembly possible. Selection, `alpha < 1`, reuses only a subset, so the discovery of
new objects slows down and the deeper levels fill far later.
Args:
levels:
The number of assembly indices above the building blocks.
k:
The rate of discovery.
alpha:
The selectivity of reuse, 1 for no selection.
Returns:
system:
The system, with the numbers `levels.N` of the group `levels`, ordered by assembly index from 1.
Examples:
>>> from skcomplex.systems.biology.evolution import assembly_growth
>>> assembly_growth(5).elements[0].prepare().size_
5
"""
group = GroupElement('levels', element=AssemblyLevelElement, size=levels, values={'k': k, 'alpha': alpha})
return System(elements=[group])
Components
Elements:
AssemblyLevelElement, fromElement
class AssemblyLevelElement(Element):
"""Objects of one assembly index, discovered from the objects one assembly step below them.
In a group ordered by assembly index, the objects of index `a + 1` grow as `dN_{a+1}/dt = k (N_a)^alpha`, Eq. (3)
of Sharma et al. (2022), where the first member reads the constant pool `N_0` of building blocks. The selectivity
`alpha = 1` reuses every object built before, and `alpha < 1` only a subset, the signature of selection.
Args:
label:
The label of the level or of the group of levels.
visual:
How the element is drawn, or `None` for `default_visual_`.
N:
The initial number of objects.
k:
The rate of discovery.
alpha:
The selectivity of reuse.
N0:
The constant number of building blocks below the first level.
Examples:
>>> from skcomplex.systems.biology.evolution import AssemblyLevelElement
>>> AssemblyLevelElement('levels', alpha=0.5).alpha
0.5
"""
def __init__(
self,
label: str,
visual: VisualSpec | None = None,
*,
N: Annotated[float, StateSpec(domain=RealSet.closed_open(0, oo))] = 0.0,
k: Annotated[float, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 1.0,
alpha: Annotated[float, ParameterSpec(domain=RealSet.closed(0, 1))] = 1.0,
N0: float = 1.0,
) -> None:
super().__init__(label, visual=visual)
self.N = N
self.k = k
self.alpha = alpha
self.N0 = N0
def _get_laws(self, s: SimpleNamespace) -> list[BaseLaw]:
"""Return the discovery of the objects of this level from the level below.
Args:
s:
The namespace of the symbols the laws can use.
Returns:
laws:
The laws of the component.
"""
below = shift_member(s.N, -1, boundary='fixed', fill=self.N0)
return [RateLaw(s.N, s.k * below**s.alpha)]
Equations
levels.N[i]: diff(N_levels(t), t) == N_levels__shift0^alpha_levels*k_levels