Skip to content

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, from Element
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