Skip to content

Undirected and directed polymer assembly pool

An assembly pool of linear polymers grown from one monomer by joining pairs of its polymers.

  • Name: polymer_assembly_pool
  • Field: biology/evolution
  • Formalism: agent-based system
  • References: Sharma et al. 2023

Select this system

from skcomplex.systems import create_simulation, create_system, describe

system = create_system('polymer_assembly_pool')
entry = describe('polymer_assembly_pool')
trajectory = create_simulation('polymer_assembly_pool').prepare().run().trajectory_

Factory

@register_system(
    field='biology/evolution',
    tags=('assembly theory', 'selection', 'polymers', 'agents'),
    references=('Sharma et al. 2023',),
    defaults={'until': 200},
)
def polymer_assembly_pool(mode: str = 'undirected', *, capacity: int = 2000) -> System:
    """Return an assembly pool of linear polymers grown from one monomer by joining pairs of its polymers.

    Undirected exploration joins two random polymers of the pool, and directed exploration, a model of selection,
    joins the newest polymer with a random one. Undirected joining fills the pool with many short polymers, while
    directed joining builds ever longer chains, of a higher assembly index, about the base 2 logarithm of the length.

    Args:
        mode:
            `'undirected'` or `'directed'`.

        capacity:
            The largest number of polymers the pool holds.

    Returns:
        system:
            The system, with the lengths `pool.length` of the variable group `pool`.

    Examples:
        >>> from skcomplex.systems.biology.evolution import polymer_assembly_pool
        >>> polymer_assembly_pool('directed').interactions[0].mode
        'directed'
    """
    pool = GroupElement('pool', element=PolymerElement, size=1, variable=True, capacity=capacity)
    return System(
        elements=[pool],
        interactions=[PolymerJoiningInteraction('joining', polymers='pool', mode=mode)],
        time=DiscreteTime(),
    )

Components

Elements:

  • PolymerElement, from Element

Interactions:

  • PolymerJoiningInteraction, from Interaction
class PolymerElement(Element):
    """Linear polymer of an assembly pool, with its length and the step it was made.

    Args:
        label:
            The label of the polymer or of the group of polymers.

        visual:
            How the element is drawn, or `None` for `default_visual_`.

        length:
            The number of monomers.

        born:
            The step at which the polymer was made.

    Examples:
        >>> from skcomplex.systems.biology.evolution import PolymerElement
        >>> PolymerElement('polymer', length=3.0).length
        3.0
    """

    def __init__(
        self,
        label: str,
        visual: VisualSpec | None = None,
        *,
        length: Annotated[float, StateSpec(domain=NN)] = 1.0,
        born: Annotated[float, StateSpec(domain=NN)] = 0.0,
    ) -> None:
        super().__init__(label, visual=visual)
        self.length = length
        self.born = born

    @classmethod
    def _get_view(cls) -> type[PolymerView]:
        """Return the view of a polymer, `PolymerView`."""
        return PolymerView

class PolymerJoiningInteraction(Interaction):
    """Joining of two polymers of an assembly pool into a new one, at random or by reuse of the newest polymer.

    At every step, undirected exploration joins two polymers drawn at random from the pool, and directed exploration
    joins the polymer made most recently with one drawn at random. The joined polymer, of the summed length, is added
    to the pool, and its parts stay in it.

    Args:
        label:
            The label of the interaction.

        polymers:
            The path of the variable group of polymers.

        mode:
            `'undirected'` or `'directed'`.

    Raises:
        ValidationError:
            The mode is unknown.

    Examples:
        >>> from skcomplex.systems.biology.evolution import PolymerJoiningInteraction
        >>> PolymerJoiningInteraction('joining', polymers='pool', mode='directed').mode
        'directed'
    """

    def __init__(self, label: str, *, polymers: Annotated[str, RoleSpec()], mode: str = 'undirected') -> None:
        super().__init__(label)
        self.polymers = polymers
        self.mode = mode

    def _check(self) -> None:
        """Check the mode.

        Raises:
            ValidationError:
                The mode is unknown.
        """
        super()._check()
        if self.mode not in MODES:
            error_msg = f'The mode must be one of {", ".join(MODES)}, not {self.mode!r}.'
            raise ValidationError(error_msg)

    def _get_laws(self, s: SimpleNamespace) -> list[BaseLaw]:
        """Return the joining rule.

        Args:
            s:
                The namespace of the symbols the laws can use.

        Returns:
            laws:
                The laws of the component.
        """
        return [RuleLaw(self.join)]

    def join(self, system: SystemView) -> None:
        """Join two polymers and add the product to the pool.

        Args:
            system:
                The system.
        """
        pool = system.agents(self.polymers, PolymerView)
        partner = pool[int(system.rng.integers(len(pool)))]
        if self.mode == 'directed':
            first = max(pool, key=lambda polymer: polymer.born)
        else:
            first = pool[int(system.rng.integers(len(pool)))]
        born = max(polymer.born for polymer in pool) + 1
        system.create(self.polymers, length=first.length + partner.length, born=born)