Skip to content

:>> [[ABIO]] → ABIO DocsABIO bio

Bio Module

Core biology simulation classes.

alienbio.bio

Bio module: the chemistry substrate alienbio's instrument runs on.

Protocols (for type hints) — from alienbio.protocols.bio; the implementations conform under pyright src/ (protocols/_conformance): - Atom, Molecule, Reaction, Chemistry: the entities - Flow: transport between compartments (TransportFlux, GeneralFlow) - CompartmentTree, WorldState: topology and the dense multi-compartment store - Simulator: the reference and JAX steppers, which must agree to 1e-9

Implementations: - AtomImpl, MoleculeImpl, ReactionImpl (+ Modulation), ChemistryImpl - CompartmentImpl, CompartmentTreeImpl, WorldStateImpl - WorldImpl: the declarative world and its single resolution point - WorldSimulatorImpl / ReactionSpec, and jax_simulator.JaxWorldSimulator - TransportFlux, GeneralFlow; PerCapitaGrowth / PerCapitaDeath / CountFlow - conservation / energy canaries, the compiled rate grammar (rate_expr)

The M1 single-compartment runtime that used to live beside these (ReferenceSimulatorImpl, BioSystem, StateImpl, the agent/task layer, equilibrium/perturbation/quiescence analysis, MembraneFlow) was deleted in T056 (2026-09-10): it ran a different physics from the world simulator and nothing on the instrument's path imported it.

Atom

Bases: Protocol

Protocol for atomic elements.

Atoms are the building blocks of molecules. Each atom has: - symbol: 1-2 letter chemical notation (e.g., "C", "H", "Na") - name: Human-readable name (e.g., "Carbon", "Hydrogen") - atomic_weight: Mass in atomic mass units

Source code in src/alienbio/protocols/bio.py
@runtime_checkable
class Atom(Protocol):
    """Protocol for atomic elements.

    Atoms are the building blocks of molecules. Each atom has:
    - symbol: 1-2 letter chemical notation (e.g., "C", "H", "Na")
    - name: Human-readable name (e.g., "Carbon", "Hydrogen")
    - atomic_weight: Mass in atomic mass units
    """

    @property
    def symbol(self) -> str:
        """Chemical symbol (1-2 letters): 'C', 'H', 'O', 'Na'."""
        ...

    @property
    def name(self) -> str:
        """Human-readable name: 'Carbon', 'Hydrogen'."""
        ...

    @property
    def atomic_weight(self) -> float:
        """Atomic mass in atomic mass units."""
        ...

symbol property

Chemical symbol (1-2 letters): 'C', 'H', 'O', 'Na'.

name property

Human-readable name: 'Carbon', 'Hydrogen'.

atomic_weight property

Atomic mass in atomic mass units.

Molecule

Bases: Protocol

Protocol for molecule entities.

Molecules are composed of atoms and have: - atoms: Composition as {Atom: count} - bdepth: Biosynthetic depth (0 = primitive, higher = more complex) - name: Human-readable name (e.g., "glucose", "water") - symbol: Chemical formula derived from atoms (e.g., "C6H12O6") - molecular_weight: Computed from atom weights

Source code in src/alienbio/protocols/bio.py
@runtime_checkable
class Molecule(Protocol):
    """Protocol for molecule entities.

    Molecules are composed of atoms and have:
    - atoms: Composition as {Atom: count}
    - bdepth: Biosynthetic depth (0 = primitive, higher = more complex)
    - name: Human-readable name (e.g., "glucose", "water")
    - symbol: Chemical formula derived from atoms (e.g., "C6H12O6")
    - molecular_weight: Computed from atom weights
    """

    @property
    def local_name(self) -> str:
        """The molecule's local name within its parent entity."""
        ...

    @property
    def atoms(self) -> Mapping[Any, int]:  # keyed by Atom; Mapping keys are invariant, so the key is open
        """Atom composition: {atom: count}."""
        ...

    @property
    def bdepth(self) -> int:
        """Biosynthetic depth (0 = primitive, 4+ = complex)."""
        ...

    @property
    def name(self) -> str:
        """Human-readable name: 'glucose', 'water'."""
        ...

    @property
    def symbol(self) -> str:
        """Chemical formula derived from atoms: 'C6H12O6', 'H2O'."""
        ...

    @property
    def molecular_weight(self) -> float:
        """Molecular mass computed from atom weights."""
        ...

local_name property

The molecule's local name within its parent entity.

atoms property

Atom composition: {atom: count}.

bdepth property

Biosynthetic depth (0 = primitive, 4+ = complex).

name property

Human-readable name: 'glucose', 'water'.

symbol property

Chemical formula derived from atoms: 'C6H12O6', 'H2O'.

molecular_weight property

Molecular mass computed from atom weights.

Reaction

Bases: Protocol

Protocol for reaction entities.

Reactions define transformations within a single compartment. Each reaction has reactants, products, and a rate.

Source code in src/alienbio/protocols/bio.py
@runtime_checkable
class Reaction(Protocol):
    """Protocol for reaction entities.

    Reactions define transformations within a single compartment.
    Each reaction has reactants, products, and a rate.
    """

    @property
    def local_name(self) -> str:
        """The reaction's local name."""
        ...

    @property
    def name(self) -> str:
        """Human-readable name."""
        ...

    @property
    def symbol(self) -> str:
        """Formula string: 'A + B -> C + D'."""
        ...

    @property
    def reactants(self) -> Mapping[Any, float]:  # keyed by Molecule (see Molecule.atoms)
        """Reactant molecules and their stoichiometric coefficients."""
        ...

    @property
    def products(self) -> Mapping[Any, float]:
        """Product molecules and their stoichiometric coefficients."""
        ...

    @property
    def modifiers(self) -> Mapping[Any, Any]:
        """Catalyst/regulator molecules acting on the reaction without being
        stoichiometrically consumed, mapped to a modulation value: a bidirectional
        rate-modulation descriptor (``bio.reaction.Modulation`` — kind + params, e.g.
        an activator/inhibitor) or a bare opaque role tag ``str`` (e.g. an enzyme with
        role ``"catalyst"``), which is rate-inert. Empty for an unmodified reaction."""
        ...

    @property
    def rate(self) -> Union[float, Callable]:
        """Reaction rate (constant or function of state)."""
        ...

local_name property

The reaction's local name.

name property

Human-readable name.

symbol property

Formula string: 'A + B -> C + D'.

reactants property

Reactant molecules and their stoichiometric coefficients.

products property

Product molecules and their stoichiometric coefficients.

modifiers property

Catalyst/regulator molecules acting on the reaction without being stoichiometrically consumed, mapped to a modulation value: a bidirectional rate-modulation descriptor (bio.reaction.Modulation — kind + params, e.g. an activator/inhibitor) or a bare opaque role tag str (e.g. an enzyme with role "catalyst"), which is rate-inert. Empty for an unmodified reaction.

rate property

Reaction rate (constant or function of state).

Chemistry

Bases: Protocol

Protocol for chemistry containers.

Chemistry acts as the "world" for a chemical system, holding atoms, molecules, and reactions as public dict attributes.

Source code in src/alienbio/protocols/bio.py
@runtime_checkable
class Chemistry(Protocol):
    """Protocol for chemistry containers.

    Chemistry acts as the "world" for a chemical system,
    holding atoms, molecules, and reactions as public dict attributes.
    """

    @property
    def local_name(self) -> str:
        """The chemistry's local name."""
        ...

    @property
    def atoms(self) -> Mapping[str, Atom]:
        """All atoms in this chemistry (by symbol)."""
        ...

    @property
    def molecules(self) -> Mapping[str, Molecule]:
        """All molecules in this chemistry (by name)."""
        ...

    @property
    def reactions(self) -> Mapping[str, Reaction]:
        """All reactions in this chemistry (by name)."""
        ...

    def validate(self) -> List[str]:
        """Validate the chemistry for consistency."""
        ...

    # Reaction-network graph queries. The molecules (species nodes) and
    # reactions (reaction nodes) form a bipartite graph; node ids are their
    # ``name``s.

    def neighbors(self, node: str) -> set[str]:
        """Molecule<->reaction adjacency (bipartite), by name."""
        ...

    def paths(self, a: str, b: str, max_len: int = 8) -> List[List[str]]:
        """All simple paths (by name) from ``a`` to ``b`` within ``max_len`` edges."""
        ...

    def subgraph(self, nodes: Iterable[str]) -> "Chemistry":
        """The induced sub-chemistry over ``nodes`` (edges to dropped nodes removed)."""
        ...

    def match(self, pattern: Any) -> List[Dict[str, str]]:
        """All subgraph embeddings of ``pattern`` into this chemistry."""
        ...

local_name property

The chemistry's local name.

atoms property

All atoms in this chemistry (by symbol).

molecules property

All molecules in this chemistry (by name).

reactions property

All reactions in this chemistry (by name).

validate()

Validate the chemistry for consistency.

Source code in src/alienbio/protocols/bio.py
def validate(self) -> List[str]:
    """Validate the chemistry for consistency."""
    ...

neighbors(node)

Molecule<->reaction adjacency (bipartite), by name.

Source code in src/alienbio/protocols/bio.py
def neighbors(self, node: str) -> set[str]:
    """Molecule<->reaction adjacency (bipartite), by name."""
    ...

paths(a, b, max_len=8)

All simple paths (by name) from a to b within max_len edges.

Source code in src/alienbio/protocols/bio.py
def paths(self, a: str, b: str, max_len: int = 8) -> List[List[str]]:
    """All simple paths (by name) from ``a`` to ``b`` within ``max_len`` edges."""
    ...

subgraph(nodes)

The induced sub-chemistry over nodes (edges to dropped nodes removed).

Source code in src/alienbio/protocols/bio.py
def subgraph(self, nodes: Iterable[str]) -> "Chemistry":
    """The induced sub-chemistry over ``nodes`` (edges to dropped nodes removed)."""
    ...

match(pattern)

All subgraph embeddings of pattern into this chemistry.

Source code in src/alienbio/protocols/bio.py
def match(self, pattern: Any) -> List[Dict[str, str]]:
    """All subgraph embeddings of ``pattern`` into this chemistry."""
    ...

CompartmentTree

Bases: Protocol

Protocol for compartment topology.

Represents the hierarchical structure of compartments (organism > organ > cell). Stored separately from concentrations to allow efficient updates.

Source code in src/alienbio/protocols/bio.py
@runtime_checkable
class CompartmentTree(Protocol):
    """Protocol for compartment topology.

    Represents the hierarchical structure of compartments (organism > organ > cell).
    Stored separately from concentrations to allow efficient updates.
    """

    @property
    def num_compartments(self) -> int:
        """Total number of compartments."""
        ...

    def parent(self, child: CompartmentId) -> Optional[CompartmentId]:
        """Get parent of a compartment (None for root)."""
        ...

    def children(self, parent: CompartmentId) -> List[CompartmentId]:
        """Get children of a compartment."""
        ...

    def root(self) -> CompartmentId:
        """Get the root compartment."""
        ...

    def is_root(self, compartment: CompartmentId) -> bool:
        """Check if compartment is the root."""
        ...

num_compartments property

Total number of compartments.

parent(child)

Get parent of a compartment (None for root).

Source code in src/alienbio/protocols/bio.py
def parent(self, child: CompartmentId) -> Optional[CompartmentId]:
    """Get parent of a compartment (None for root)."""
    ...

children(parent)

Get children of a compartment.

Source code in src/alienbio/protocols/bio.py
def children(self, parent: CompartmentId) -> List[CompartmentId]:
    """Get children of a compartment."""
    ...

root()

Get the root compartment.

Source code in src/alienbio/protocols/bio.py
def root(self) -> CompartmentId:
    """Get the root compartment."""
    ...

is_root(compartment)

Check if compartment is the root.

Source code in src/alienbio/protocols/bio.py
def is_root(self, compartment: CompartmentId) -> bool:
    """Check if compartment is the root."""
    ...

WorldState

Bases: Protocol

Protocol for world concentration state.

Stores concentrations for all compartments and molecules. Dense storage: [num_compartments x num_molecules] array. Can be extended with sparse overflow for large molecule counts.

Each WorldState holds a reference to its CompartmentTree. Multiple states can share the same tree (immutable sharing). When topology changes (e.g., cell division), a new tree is created and new states point to it while historical states keep their original tree reference.

Source code in src/alienbio/protocols/bio.py
@runtime_checkable
class WorldState(Protocol):
    """Protocol for world concentration state.

    Stores concentrations for all compartments and molecules.
    Dense storage: [num_compartments x num_molecules] array.
    Can be extended with sparse overflow for large molecule counts.

    Each WorldState holds a reference to its CompartmentTree. Multiple
    states can share the same tree (immutable sharing). When topology
    changes (e.g., cell division), a new tree is created and new states
    point to it while historical states keep their original tree reference.
    """

    @property
    def tree(self) -> CompartmentTree:
        """The compartment tree this state belongs to."""
        ...

    @property
    def num_compartments(self) -> int:
        """Number of compartments."""
        ...

    @property
    def num_molecules(self) -> int:
        """Number of molecules in vocabulary."""
        ...

    def get(self, compartment: CompartmentId, molecule: MoleculeId) -> float:
        """Get concentration of molecule in compartment."""
        ...

    def set(self, compartment: CompartmentId, molecule: MoleculeId, value: float) -> None:
        """Set concentration of molecule in compartment."""
        ...

    def get_compartment(self, compartment: CompartmentId) -> List[float]:
        """Get all concentrations for a compartment."""
        ...

    # Multiplicity methods

    def get_multiplicity(self, compartment: CompartmentId) -> float:
        """Get multiplicity (instance count) for a compartment."""
        ...

    def set_multiplicity(self, compartment: CompartmentId, value: float) -> None:
        """Set multiplicity (instance count) for a compartment."""
        ...

    def total_molecules(self, compartment: CompartmentId, molecule: MoleculeId) -> float:
        """Get total molecules = multiplicity * concentration."""
        ...

    # Copy and array methods

    def copy(self) -> WorldState:
        """Create a copy of this state (shares tree reference)."""
        ...

    def as_array(self) -> Any:
        """Get concentrations as 2D array [compartments x molecules]."""
        ...

tree property

The compartment tree this state belongs to.

num_compartments property

Number of compartments.

num_molecules property

Number of molecules in vocabulary.

get(compartment, molecule)

Get concentration of molecule in compartment.

Source code in src/alienbio/protocols/bio.py
def get(self, compartment: CompartmentId, molecule: MoleculeId) -> float:
    """Get concentration of molecule in compartment."""
    ...

set(compartment, molecule, value)

Set concentration of molecule in compartment.

Source code in src/alienbio/protocols/bio.py
def set(self, compartment: CompartmentId, molecule: MoleculeId, value: float) -> None:
    """Set concentration of molecule in compartment."""
    ...

get_compartment(compartment)

Get all concentrations for a compartment.

Source code in src/alienbio/protocols/bio.py
def get_compartment(self, compartment: CompartmentId) -> List[float]:
    """Get all concentrations for a compartment."""
    ...

get_multiplicity(compartment)

Get multiplicity (instance count) for a compartment.

Source code in src/alienbio/protocols/bio.py
def get_multiplicity(self, compartment: CompartmentId) -> float:
    """Get multiplicity (instance count) for a compartment."""
    ...

set_multiplicity(compartment, value)

Set multiplicity (instance count) for a compartment.

Source code in src/alienbio/protocols/bio.py
def set_multiplicity(self, compartment: CompartmentId, value: float) -> None:
    """Set multiplicity (instance count) for a compartment."""
    ...

total_molecules(compartment, molecule)

Get total molecules = multiplicity * concentration.

Source code in src/alienbio/protocols/bio.py
def total_molecules(self, compartment: CompartmentId, molecule: MoleculeId) -> float:
    """Get total molecules = multiplicity * concentration."""
    ...

copy()

Create a copy of this state (shares tree reference).

Source code in src/alienbio/protocols/bio.py
def copy(self) -> WorldState:
    """Create a copy of this state (shares tree reference)."""
    ...

as_array()

Get concentrations as 2D array [compartments x molecules].

Source code in src/alienbio/protocols/bio.py
def as_array(self) -> Any:
    """Get concentrations as 2D array [compartments x molecules]."""
    ...

Simulator

Bases: Protocol

Protocol for simulators.

A Simulator advances the state of a chemical system over time. Applies reactions within compartments and flows across membranes.

Source code in src/alienbio/protocols/bio.py
class Simulator(Protocol):
    """Protocol for simulators.

    A Simulator advances the state of a chemical system over time.
    Applies reactions within compartments and flows across membranes.
    """

    @property
    def tree(self) -> "CompartmentTreeImpl":
        """The compartment topology."""
        ...

    @property
    def dt(self) -> float:
        """Time step size."""
        ...

    @abstractmethod
    def step(self, state: "WorldStateImpl") -> "WorldStateImpl":
        """Advance the simulation by one time step."""
        ...

    def run(
        self,
        state: "WorldStateImpl",
        steps: int,
        sample_every: Optional[int] = None,
    ) -> List["WorldStateImpl"]:
        """Run simulation for multiple steps, optionally sampling history."""
        ...

tree property

The compartment topology.

dt property

Time step size.

step(state) abstractmethod

Advance the simulation by one time step.

Source code in src/alienbio/protocols/bio.py
@abstractmethod
def step(self, state: "WorldStateImpl") -> "WorldStateImpl":
    """Advance the simulation by one time step."""
    ...

run(state, steps, sample_every=None)

Run simulation for multiple steps, optionally sampling history.

Source code in src/alienbio/protocols/bio.py
def run(
    self,
    state: "WorldStateImpl",
    steps: int,
    sample_every: Optional[int] = None,
) -> List["WorldStateImpl"]:
    """Run simulation for multiple steps, optionally sampling history."""
    ...

MockDat

Lightweight mock DAT for hydrating entities without a real DAT.

Used when creating entities from YAML specs that don't have backing DAT files. Provides the minimal interface needed by Entity.

Source code in src/alienbio/infra/entity.py
class MockDat:
    """Lightweight mock DAT for hydrating entities without a real DAT.

    Used when creating entities from YAML specs that don't have
    backing DAT files. Provides the minimal interface needed by Entity.
    """

    def __init__(self, path: str):
        self.path = path

    def get_path_name(self) -> str:
        return self.path

    def get_path(self) -> str:
        return f"/mock/{self.path}"

AtomImpl

Implementation: A chemical element.

Atoms are the building blocks of molecules. They are essentially constants representing chemical elements with their properties.

Attributes:

Name Type Description
symbol str

Chemical symbol (1-2 letters): 'C', 'H', 'O', 'Na'

name str

Human-readable name: 'Carbon', 'Hydrogen'

atomic_weight float

Atomic mass in atomic mass units

Source code in src/alienbio/bio/atom.py
class AtomImpl:
    """Implementation: A chemical element.

    Atoms are the building blocks of molecules. They are essentially constants
    representing chemical elements with their properties.

    Attributes:
        symbol: Chemical symbol (1-2 letters): 'C', 'H', 'O', 'Na'
        name: Human-readable name: 'Carbon', 'Hydrogen'
        atomic_weight: Atomic mass in atomic mass units
    """

    __slots__ = ("_symbol", "_name", "_atomic_weight")

    def __init__(
        self,
        symbol: str,
        name: str,
        atomic_weight: float,
    ) -> None:
        """Initialize an atom.

        Args:
            symbol: Chemical symbol (1-2 letters)
            name: Human-readable English name
            atomic_weight: Atomic mass in atomic mass units
        """
        if not symbol or len(symbol) > 2:
            raise ValueError(f"Symbol must be 1-2 characters, got {symbol!r}")
        self._symbol = symbol
        self._name = name
        self._atomic_weight = atomic_weight

    @property
    def symbol(self) -> str:
        """Chemical symbol (1-2 letters): 'C', 'H', 'O', 'Na'."""
        return self._symbol

    @property
    def name(self) -> str:
        """Human-readable name: 'Carbon', 'Hydrogen'."""
        return self._name

    @property
    def atomic_weight(self) -> float:
        """Atomic mass in atomic mass units."""
        return self._atomic_weight

    def __eq__(self, other: object) -> bool:
        """Atoms are equal if they have the same symbol."""
        if not isinstance(other, AtomImpl):
            return NotImplemented
        return self._symbol == other._symbol

    def __hash__(self) -> int:
        """Hash by symbol for use as dict key."""
        return hash(self._symbol)

    def __repr__(self) -> str:
        """Full representation."""
        return f"AtomImpl({self._symbol!r}, {self._name!r}, {self._atomic_weight})"

    def __str__(self) -> str:
        """Short display form."""
        return self._symbol

symbol property

Chemical symbol (1-2 letters): 'C', 'H', 'O', 'Na'.

name property

Human-readable name: 'Carbon', 'Hydrogen'.

atomic_weight property

Atomic mass in atomic mass units.

__init__(symbol, name, atomic_weight)

Initialize an atom.

Parameters:

Name Type Description Default
symbol str

Chemical symbol (1-2 letters)

required
name str

Human-readable English name

required
atomic_weight float

Atomic mass in atomic mass units

required
Source code in src/alienbio/bio/atom.py
def __init__(
    self,
    symbol: str,
    name: str,
    atomic_weight: float,
) -> None:
    """Initialize an atom.

    Args:
        symbol: Chemical symbol (1-2 letters)
        name: Human-readable English name
        atomic_weight: Atomic mass in atomic mass units
    """
    if not symbol or len(symbol) > 2:
        raise ValueError(f"Symbol must be 1-2 characters, got {symbol!r}")
    self._symbol = symbol
    self._name = name
    self._atomic_weight = atomic_weight

__eq__(other)

Atoms are equal if they have the same symbol.

Source code in src/alienbio/bio/atom.py
def __eq__(self, other: object) -> bool:
    """Atoms are equal if they have the same symbol."""
    if not isinstance(other, AtomImpl):
        return NotImplemented
    return self._symbol == other._symbol

__hash__()

Hash by symbol for use as dict key.

Source code in src/alienbio/bio/atom.py
def __hash__(self) -> int:
    """Hash by symbol for use as dict key."""
    return hash(self._symbol)

__repr__()

Full representation.

Source code in src/alienbio/bio/atom.py
def __repr__(self) -> str:
    """Full representation."""
    return f"AtomImpl({self._symbol!r}, {self._name!r}, {self._atomic_weight})"

__str__()

Short display form.

Source code in src/alienbio/bio/atom.py
def __str__(self) -> str:
    """Short display form."""
    return self._symbol

MoleculeImpl

Bases: Entity

Implementation: A molecule in the biological system.

Molecules are composed of atoms and participate in reactions.

Attributes:

Name Type Description
atoms Dict[AtomImpl, int]

Atom composition as {AtomImpl: count}

bdepth int

Biosynthetic depth (0 = primitive, higher = more complex)

name str

Human-readable name (e.g., 'glucose', 'water')

symbol str

Chemical formula derived from atoms (e.g., 'C6H12O6', 'H2O')

molecular_weight float

Computed from atom weights

formation_energy Optional[float]

Free energy of formation, an opaque assigned scalar (F018); None means energy-neutral — the molecule books nothing in the energy accounting layer (bio/energy.py). Additive/optional, like atoms.

Source code in src/alienbio/bio/molecule.py
@biotype("molecule")
class MoleculeImpl(Entity, head="Molecule"):
    """Implementation: A molecule in the biological system.

    Molecules are composed of atoms and participate in reactions.

    Attributes:
        atoms: Atom composition as {AtomImpl: count}
        bdepth: Biosynthetic depth (0 = primitive, higher = more complex)
        name: Human-readable name (e.g., 'glucose', 'water')
        symbol: Chemical formula derived from atoms (e.g., 'C6H12O6', 'H2O')
        molecular_weight: Computed from atom weights
        formation_energy: Free energy of formation, an opaque assigned scalar (F018);
            ``None`` means energy-neutral — the molecule books nothing in the energy
            accounting layer (``bio/energy.py``). Additive/optional, like ``atoms``.
    """

    __slots__ = ("_atoms", "_bdepth", "_name", "_formation_energy")

    def __init__(
        self,
        local_name: str,
        *,
        parent: Optional[Entity] = None,
        dat: Optional[DatLike] = None,
        description: str = "",
        atoms: Optional[Dict[AtomImpl, int]] = None,
        bdepth: int = 0,
        name: Optional[str] = None,
        formation_energy: Optional[float] = None,
    ) -> None:
        """Initialize a molecule.

        Args:
            local_name: Local name within parent (used as entity identifier)
            parent: Link to containing entity
            dat: DAT anchor for root molecules
            description: Human-readable description
            atoms: Atom composition as {AtomImpl: count}
            bdepth: Biosynthetic depth (0 = primitive)
            name: Human-readable name (defaults to local_name)
            formation_energy: Free energy of formation (F018), an opaque assigned
                scalar. ``None`` (default) means energy-neutral — backward-compatible
                with every existing caller that doesn't pass it.
        """
        super().__init__(local_name, parent=parent, dat=dat, description=description)
        self._atoms: Dict[AtomImpl, int] = atoms.copy() if atoms else {}
        self._bdepth = bdepth
        self._name = name if name is not None else local_name
        self._formation_energy = formation_energy

    @classmethod
    def hydrate(
        cls,
        data: dict[str, Any],
        *,
        dat: Optional[DatLike] = None,
        parent: Optional[Entity] = None,
        local_name: Optional[str] = None,
    ) -> Self:
        """Create a Molecule from a dict.

        Args:
            data: Dict with optional keys: name, bdepth, atoms, description
            dat: DAT anchor (if root entity)
            parent: Parent entity (if child)
            local_name: Override name (defaults to data["name"])

        Returns:
            New MoleculeImpl instance
        """
        from ..infra.entity import MockDat

        name = local_name or data.get("name", "molecule")

        # Create mock dat if needed
        if dat is None and parent is None:
            dat = MockDat(f"mol/{name}")

        return cls(
            name,
            parent=parent,
            dat=dat,
            description=data.get("description", ""),
            bdepth=data.get("bdepth", 0),
            formation_energy=data.get("formation_energy"),
            # atoms not hydrated here - would need atom registry
        )

    @property
    def atoms(self) -> Dict[AtomImpl, int]:
        """Atom composition: {atom: count}."""
        return self._atoms.copy()

    @property
    def bdepth(self) -> int:
        """Biosynthetic depth (0 = primitive, 4+ = complex)."""
        return self._bdepth

    @property
    def formation_energy(self) -> Optional[float]:
        """Free energy of formation (F018), or ``None`` if energy-neutral."""
        return self._formation_energy

    @property
    def name(self) -> str:
        """Human-readable name: 'glucose', 'water'."""
        return self._name

    @property
    def symbol(self) -> str:
        """Chemical formula derived from atoms: 'C6H12O6', 'H2O'.

        Atoms are ordered by Hill system: C first, then H, then alphabetically.
        """
        if not self._atoms:
            return ""

        # Hill system: C first, then H, then alphabetically
        parts = []
        symbols_counts = [(atom.symbol, count) for atom, count in self._atoms.items()]

        # Sort: C first, H second, rest alphabetically
        def sort_key(item: tuple) -> tuple:
            sym = item[0]
            if sym == "C":
                return (0, sym)
            elif sym == "H":
                return (1, sym)
            else:
                return (2, sym)

        symbols_counts.sort(key=sort_key)

        for sym, count in symbols_counts:
            if count == 1:
                parts.append(sym)
            else:
                parts.append(f"{sym}{count}")

        return "".join(parts)

    @property
    def molecular_weight(self) -> float:
        """Molecular mass computed from atom weights."""
        return sum(
            atom.atomic_weight * count
            for atom, count in self._atoms.items()
        )

    def attributes(self) -> Dict[str, Any]:
        """Semantic content of this molecule."""
        result = super().attributes()
        if self._atoms:
            # Serialize atoms as {symbol: count} for readability
            result["atoms"] = {atom.symbol: count for atom, count in self._atoms.items()}
        if self._bdepth != 0:
            result["bdepth"] = self._bdepth
        if self._formation_energy is not None:
            result["formation_energy"] = self._formation_energy
        if self._name != self._local_name:
            result["display_name"] = self._name
        return result

    def __repr__(self) -> str:
        """Full representation."""
        parts = [f"local_name={self._local_name!r}"]
        if self._name != self._local_name:
            parts.append(f"name={self._name!r}")
        if self._atoms:
            parts.append(f"symbol={self.symbol!r}")
        if self._bdepth != 0:
            parts.append(f"bdepth={self._bdepth}")
        if self._formation_energy is not None:
            parts.append(f"formation_energy={self._formation_energy}")
        if self.description:
            parts.append(f"description={self.description!r}")
        return f"MoleculeImpl({', '.join(parts)})"

atoms property

Atom composition: {atom: count}.

bdepth property

Biosynthetic depth (0 = primitive, 4+ = complex).

formation_energy property

Free energy of formation (F018), or None if energy-neutral.

name property

Human-readable name: 'glucose', 'water'.

symbol property

Chemical formula derived from atoms: 'C6H12O6', 'H2O'.

Atoms are ordered by Hill system: C first, then H, then alphabetically.

molecular_weight property

Molecular mass computed from atom weights.

__init__(local_name, *, parent=None, dat=None, description='', atoms=None, bdepth=0, name=None, formation_energy=None)

Initialize a molecule.

Parameters:

Name Type Description Default
local_name str

Local name within parent (used as entity identifier)

required
parent Optional[Entity]

Link to containing entity

None
dat Optional[DatLike]

DAT anchor for root molecules

None
description str

Human-readable description

''
atoms Optional[Dict[AtomImpl, int]]

Atom composition as {AtomImpl: count}

None
bdepth int

Biosynthetic depth (0 = primitive)

0
name Optional[str]

Human-readable name (defaults to local_name)

None
formation_energy Optional[float]

Free energy of formation (F018), an opaque assigned scalar. None (default) means energy-neutral — backward-compatible with every existing caller that doesn't pass it.

None
Source code in src/alienbio/bio/molecule.py
def __init__(
    self,
    local_name: str,
    *,
    parent: Optional[Entity] = None,
    dat: Optional[DatLike] = None,
    description: str = "",
    atoms: Optional[Dict[AtomImpl, int]] = None,
    bdepth: int = 0,
    name: Optional[str] = None,
    formation_energy: Optional[float] = None,
) -> None:
    """Initialize a molecule.

    Args:
        local_name: Local name within parent (used as entity identifier)
        parent: Link to containing entity
        dat: DAT anchor for root molecules
        description: Human-readable description
        atoms: Atom composition as {AtomImpl: count}
        bdepth: Biosynthetic depth (0 = primitive)
        name: Human-readable name (defaults to local_name)
        formation_energy: Free energy of formation (F018), an opaque assigned
            scalar. ``None`` (default) means energy-neutral — backward-compatible
            with every existing caller that doesn't pass it.
    """
    super().__init__(local_name, parent=parent, dat=dat, description=description)
    self._atoms: Dict[AtomImpl, int] = atoms.copy() if atoms else {}
    self._bdepth = bdepth
    self._name = name if name is not None else local_name
    self._formation_energy = formation_energy

hydrate(data, *, dat=None, parent=None, local_name=None) classmethod

Create a Molecule from a dict.

Parameters:

Name Type Description Default
data dict[str, Any]

Dict with optional keys: name, bdepth, atoms, description

required
dat Optional[DatLike]

DAT anchor (if root entity)

None
parent Optional[Entity]

Parent entity (if child)

None
local_name Optional[str]

Override name (defaults to data["name"])

None

Returns:

Type Description
Self

New MoleculeImpl instance

Source code in src/alienbio/bio/molecule.py
@classmethod
def hydrate(
    cls,
    data: dict[str, Any],
    *,
    dat: Optional[DatLike] = None,
    parent: Optional[Entity] = None,
    local_name: Optional[str] = None,
) -> Self:
    """Create a Molecule from a dict.

    Args:
        data: Dict with optional keys: name, bdepth, atoms, description
        dat: DAT anchor (if root entity)
        parent: Parent entity (if child)
        local_name: Override name (defaults to data["name"])

    Returns:
        New MoleculeImpl instance
    """
    from ..infra.entity import MockDat

    name = local_name or data.get("name", "molecule")

    # Create mock dat if needed
    if dat is None and parent is None:
        dat = MockDat(f"mol/{name}")

    return cls(
        name,
        parent=parent,
        dat=dat,
        description=data.get("description", ""),
        bdepth=data.get("bdepth", 0),
        formation_energy=data.get("formation_energy"),
        # atoms not hydrated here - would need atom registry
    )

attributes()

Semantic content of this molecule.

Source code in src/alienbio/bio/molecule.py
def attributes(self) -> Dict[str, Any]:
    """Semantic content of this molecule."""
    result = super().attributes()
    if self._atoms:
        # Serialize atoms as {symbol: count} for readability
        result["atoms"] = {atom.symbol: count for atom, count in self._atoms.items()}
    if self._bdepth != 0:
        result["bdepth"] = self._bdepth
    if self._formation_energy is not None:
        result["formation_energy"] = self._formation_energy
    if self._name != self._local_name:
        result["display_name"] = self._name
    return result

__repr__()

Full representation.

Source code in src/alienbio/bio/molecule.py
def __repr__(self) -> str:
    """Full representation."""
    parts = [f"local_name={self._local_name!r}"]
    if self._name != self._local_name:
        parts.append(f"name={self._name!r}")
    if self._atoms:
        parts.append(f"symbol={self.symbol!r}")
    if self._bdepth != 0:
        parts.append(f"bdepth={self._bdepth}")
    if self._formation_energy is not None:
        parts.append(f"formation_energy={self._formation_energy}")
    if self.description:
        parts.append(f"description={self.description!r}")
    return f"MoleculeImpl({', '.join(parts)})"

ReactionImpl

Bases: Entity

Implementation: A reaction transforming reactants into products.

Reactions define transformations in the biological system. Each reaction has: - reactants: molecules consumed (with stoichiometric coefficients) - products: molecules produced (with stoichiometric coefficients) - modifiers: catalysts/regulators acting on the reaction WITHOUT being consumed (enzymes, inhibitors), each mapped to a Modulation (or a bare opaque role-tag str, for backward compat — see Modulation) - rate: constant or function determining reaction speed

Example

A + 2B -> C, catalyzed by enzyme E, with rate 0.1

reaction = ReactionImpl( "r1", reactants={mol_a: 1, mol_b: 2}, products={mol_c: 1}, modifiers={enzyme_e: "catalyst"}, rate=0.1, parent=chemistry, )

Source code in src/alienbio/bio/reaction.py
class ReactionImpl(Entity, head="Reaction"):
    """Implementation: A reaction transforming reactants into products.

    Reactions define transformations in the biological system.
    Each reaction has:
    - reactants: molecules consumed (with stoichiometric coefficients)
    - products: molecules produced (with stoichiometric coefficients)
    - modifiers: catalysts/regulators acting on the reaction WITHOUT being
      consumed (enzymes, inhibitors), each mapped to a ``Modulation`` (or a
      bare opaque role-tag ``str``, for backward compat — see ``Modulation``)
    - rate: constant or function determining reaction speed

    Example:
        # A + 2B -> C, catalyzed by enzyme E, with rate 0.1
        reaction = ReactionImpl(
            "r1",
            reactants={mol_a: 1, mol_b: 2},
            products={mol_c: 1},
            modifiers={enzyme_e: "catalyst"},
            rate=0.1,
            parent=chemistry,
        )
    """

    __slots__ = ("_reactants", "_products", "_modifiers", "_rate", "_rate_law")

    def __init__(
        self,
        name: str,
        *,
        reactants: Optional[Dict[MoleculeImpl, float]] = None,
        products: Optional[Dict[MoleculeImpl, float]] = None,
        modifiers: Optional[Mapping[MoleculeImpl, ModifierValue]] = None,
        rate: RateValue = 1.0,
        rate_law: Optional[Any] = None,
        parent: Optional[Entity] = None,
        dat: Optional[DatLike] = None,
        description: str = "",
    ) -> None:
        """Initialize a reaction.

        Args:
            name: Local name within parent
            reactants: Dict mapping molecules to stoichiometric coefficients
            products: Dict mapping molecules to stoichiometric coefficients
            modifiers: Mapping catalyst/regulator molecules (not consumed) to a
                ``Modulation`` (kind + rate params) or a bare opaque role tag
                ``str`` (e.g. "catalyst") — a bare string is inert (factor 1.0)
            rate: Reaction rate (constant float or function of StateImpl)
            rate_law: Optional compiled rate expression (``bio.rate_expr``, species
                by molecule name) — the whole rate when it names a reactant, else the
                factor multiplying mass action; ``rate`` is then unused (M47.10)
            parent: Link to containing entity
            dat: DAT anchor for root reactions
            description: Human-readable description
        """
        super().__init__(name, parent=parent, dat=dat, description=description)
        self._reactants: Dict[MoleculeImpl, float] = reactants.copy() if reactants else {}
        self._products: Dict[MoleculeImpl, float] = products.copy() if products else {}
        self._modifiers: Dict[MoleculeImpl, ModifierValue] = dict(modifiers) if modifiers else {}
        self._rate: RateValue = rate
        from .rate_expr import from_json

        self._rate_law: Optional[Any] = from_json(rate_law) if rate_law is not None else None

    @classmethod
    def hydrate(  # type: ignore[override]
        cls,
        data: dict[str, Any],
        *,
        molecules: dict[str, "MoleculeImpl"],
        dat: Optional[DatLike] = None,
        parent: Optional[Entity] = None,
        local_name: Optional[str] = None,
    ) -> Self:
        """Create a Reaction from a dict.

        Args:
            data: Dict with keys: reactants, products, rate, name, description
            molecules: Dict mapping molecule names to MoleculeImpl instances
            dat: DAT anchor (if root entity)
            parent: Parent entity (if child)
            local_name: Override name (defaults to data key)

        Returns:
            New ReactionImpl instance
        """
        from ..infra.entity import MockDat

        name = local_name or data.get("name", "reaction")

        # Create mock dat if needed
        if dat is None and parent is None:
            dat = MockDat(f"rxn/{name}")

        # Build reactants dict: {MoleculeImpl: coefficient}
        reactants: Dict[MoleculeImpl, float] = {}
        for r in data.get("reactants", []):
            if isinstance(r, str):
                # Just a name, coefficient 1
                if r not in molecules:
                    raise KeyError(
                        f"Reaction {name!r}: unknown reactant molecule {r!r} "
                        f"(not in chemistry molecules)"
                    )
                reactants[molecules[r]] = 1
            elif isinstance(r, dict):
                # {name: coef} format
                for mol_name, coef in r.items():
                    if mol_name not in molecules:
                        raise KeyError(
                            f"Reaction {name!r}: unknown reactant molecule {mol_name!r} "
                            f"(not in chemistry molecules)"
                        )
                    reactants[molecules[mol_name]] = coef

        # Build products dict: {MoleculeImpl: coefficient}
        products: Dict[MoleculeImpl, float] = {}
        for p in data.get("products", []):
            if isinstance(p, str):
                # Just a name, coefficient 1
                if p not in molecules:
                    raise KeyError(
                        f"Reaction {name!r}: unknown product molecule {p!r} "
                        f"(not in chemistry molecules)"
                    )
                products[molecules[p]] = 1
            elif isinstance(p, dict):
                # {name: coef} format
                for mol_name, coef in p.items():
                    if mol_name not in molecules:
                        raise KeyError(
                            f"Reaction {name!r}: unknown product molecule {mol_name!r} "
                            f"(not in chemistry molecules)"
                        )
                    products[molecules[mol_name]] = coef

        # Build modifiers dict: {MoleculeImpl: role}.  Accepts the canonical
        # {name: role} mapping (attributes() output) or a bare list of names
        # (role defaults to "", mirroring the reactant/product list form). A
        # role that is itself a dict is a serialized Modulation (Modulation.to_dict());
        # everything else (a bare str) stays as-is, inert by default.
        modifiers: Dict[MoleculeImpl, ModifierValue] = {}
        raw_modifiers = data.get("modifiers", {})
        if isinstance(raw_modifiers, dict):
            mod_items = raw_modifiers.items()
        else:
            mod_items = (
                (m, "") if isinstance(m, str) else next(iter(m.items()))
                for m in raw_modifiers
            )
        for mol_name, role in mod_items:
            if mol_name not in molecules:
                raise KeyError(
                    f"Reaction {name!r}: unknown modifier molecule {mol_name!r} "
                    f"(not in chemistry molecules)"
                )
            modifiers[molecules[mol_name]] = Modulation(**role) if isinstance(role, dict) else role

        # Get rate (function or constant)
        rate = data.get("rate", 1.0)

        return cls(
            name,
            reactants=reactants,
            products=products,
            modifiers=modifiers,
            rate=rate,
            rate_law=data.get("rate_law"),
            parent=parent,
            dat=dat,
            description=data.get("description", ""),
        )

    @property
    def reactants(self) -> Dict[MoleculeImpl, float]:
        """Reactant molecules and their stoichiometric coefficients."""
        return self._reactants.copy()

    @property
    def products(self) -> Dict[MoleculeImpl, float]:
        """Product molecules and their stoichiometric coefficients."""
        return self._products.copy()

    @property
    def modifiers(self) -> Dict[MoleculeImpl, ModifierValue]:
        """Catalyst/regulator molecules (not consumed) mapped to a ``Modulation``
        (or a bare opaque role-tag ``str``, for backward compat — see ``Modulation``)."""
        return self._modifiers.copy()

    @property
    def rate(self) -> RateValue:
        """Reaction rate (constant or function)."""
        return self._rate

    @property
    def name(self) -> str:
        """Human-readable name (same as local_name)."""
        return self._local_name

    @property
    def symbol(self) -> str:
        """Formula string: 'glucose + ATP -> G6P + ADP'."""
        reactant_str = " + ".join(
            f"{c}{m.name}" if c != 1 else m.name
            for m, c in self._reactants.items()
        )
        product_str = " + ".join(
            f"{c}{m.name}" if c != 1 else m.name
            for m, c in self._products.items()
        )
        return f"{reactant_str} -> {product_str}"

    @property
    def rate_law(self) -> Optional[Any]:
        """The compiled rate expression (``bio.rate_expr`` tree, species by
        name), or ``None`` for plain mass action (M47.10)."""
        return self._rate_law

    def set_rate(self, rate: RateValue) -> None:
        """Set the reaction rate."""
        self._rate = rate

    def add_reactant(self, molecule: MoleculeImpl, coefficient: float = 1.0) -> None:
        """Add a reactant to this reaction."""
        self._reactants[molecule] = coefficient

    def add_product(self, molecule: MoleculeImpl, coefficient: float = 1.0) -> None:
        """Add a product to this reaction."""
        self._products[molecule] = coefficient

    def add_modifier(self, molecule: MoleculeImpl, role: ModifierValue = "") -> None:
        """Add a catalyst/regulator (not stoichiometrically consumed)."""
        self._modifiers[molecule] = role

    def attributes(self) -> Dict[str, Any]:
        """Semantic content of this reaction."""
        result = super().attributes()

        # Serialize reactants as {molecule_name: coefficient}
        if self._reactants:
            result["reactants"] = {
                mol.local_name: coef for mol, coef in self._reactants.items()
            }
        if self._products:
            result["products"] = {
                mol.local_name: coef for mol, coef in self._products.items()
            }

        # Serialize modifiers as {molecule_name: role} (catalysts/regulators). A bare
        # str role round-trips as-is; a Modulation serializes via to_dict() so hydrate()
        # can reconstruct it.
        if self._modifiers:
            result["modifiers"] = {
                mol.local_name: (role.to_dict() if isinstance(role, Modulation) else role)
                for mol, role in self._modifiers.items()
            }

        result["rate"] = self._rate
        if self._rate_law is not None:
            from .rate_expr import to_json

            result["rate_law"] = to_json(self._rate_law)

        return result

    def __repr__(self) -> str:
        """Full representation."""
        reactant_str = " + ".join(
            f"{c}{m.local_name}" if c != 1 else m.local_name
            for m, c in self._reactants.items()
        )
        product_str = " + ".join(
            f"{c}{m.local_name}" if c != 1 else m.local_name
            for m, c in self._products.items()
        )
        rate_str = str(self._rate)
        arrow = "->"
        if self._modifiers:
            mod_str = ", ".join(
                f"{m.local_name}:{role}" if role else m.local_name
                for m, role in self._modifiers.items()
            )
            arrow = f"-[{mod_str}]->"
        return f"ReactionImpl({self._local_name}: {reactant_str} {arrow} {product_str}, rate={rate_str})"

reactants property

Reactant molecules and their stoichiometric coefficients.

products property

Product molecules and their stoichiometric coefficients.

modifiers property

Catalyst/regulator molecules (not consumed) mapped to a Modulation (or a bare opaque role-tag str, for backward compat — see Modulation).

rate property

Reaction rate (constant or function).

name property

Human-readable name (same as local_name).

symbol property

Formula string: 'glucose + ATP -> G6P + ADP'.

rate_law property

The compiled rate expression (bio.rate_expr tree, species by name), or None for plain mass action (M47.10).

__init__(name, *, reactants=None, products=None, modifiers=None, rate=1.0, rate_law=None, parent=None, dat=None, description='')

Initialize a reaction.

Parameters:

Name Type Description Default
name str

Local name within parent

required
reactants Optional[Dict[MoleculeImpl, float]]

Dict mapping molecules to stoichiometric coefficients

None
products Optional[Dict[MoleculeImpl, float]]

Dict mapping molecules to stoichiometric coefficients

None
modifiers Optional[Mapping[MoleculeImpl, ModifierValue]]

Mapping catalyst/regulator molecules (not consumed) to a Modulation (kind + rate params) or a bare opaque role tag str (e.g. "catalyst") — a bare string is inert (factor 1.0)

None
rate RateValue

Reaction rate (constant float or function of StateImpl)

1.0
rate_law Optional[Any]

Optional compiled rate expression (bio.rate_expr, species by molecule name) — the whole rate when it names a reactant, else the factor multiplying mass action; rate is then unused (M47.10)

None
parent Optional[Entity]

Link to containing entity

None
dat Optional[DatLike]

DAT anchor for root reactions

None
description str

Human-readable description

''
Source code in src/alienbio/bio/reaction.py
def __init__(
    self,
    name: str,
    *,
    reactants: Optional[Dict[MoleculeImpl, float]] = None,
    products: Optional[Dict[MoleculeImpl, float]] = None,
    modifiers: Optional[Mapping[MoleculeImpl, ModifierValue]] = None,
    rate: RateValue = 1.0,
    rate_law: Optional[Any] = None,
    parent: Optional[Entity] = None,
    dat: Optional[DatLike] = None,
    description: str = "",
) -> None:
    """Initialize a reaction.

    Args:
        name: Local name within parent
        reactants: Dict mapping molecules to stoichiometric coefficients
        products: Dict mapping molecules to stoichiometric coefficients
        modifiers: Mapping catalyst/regulator molecules (not consumed) to a
            ``Modulation`` (kind + rate params) or a bare opaque role tag
            ``str`` (e.g. "catalyst") — a bare string is inert (factor 1.0)
        rate: Reaction rate (constant float or function of StateImpl)
        rate_law: Optional compiled rate expression (``bio.rate_expr``, species
            by molecule name) — the whole rate when it names a reactant, else the
            factor multiplying mass action; ``rate`` is then unused (M47.10)
        parent: Link to containing entity
        dat: DAT anchor for root reactions
        description: Human-readable description
    """
    super().__init__(name, parent=parent, dat=dat, description=description)
    self._reactants: Dict[MoleculeImpl, float] = reactants.copy() if reactants else {}
    self._products: Dict[MoleculeImpl, float] = products.copy() if products else {}
    self._modifiers: Dict[MoleculeImpl, ModifierValue] = dict(modifiers) if modifiers else {}
    self._rate: RateValue = rate
    from .rate_expr import from_json

    self._rate_law: Optional[Any] = from_json(rate_law) if rate_law is not None else None

hydrate(data, *, molecules, dat=None, parent=None, local_name=None) classmethod

Create a Reaction from a dict.

Parameters:

Name Type Description Default
data dict[str, Any]

Dict with keys: reactants, products, rate, name, description

required
molecules dict[str, 'MoleculeImpl']

Dict mapping molecule names to MoleculeImpl instances

required
dat Optional[DatLike]

DAT anchor (if root entity)

None
parent Optional[Entity]

Parent entity (if child)

None
local_name Optional[str]

Override name (defaults to data key)

None

Returns:

Type Description
Self

New ReactionImpl instance

Source code in src/alienbio/bio/reaction.py
@classmethod
def hydrate(  # type: ignore[override]
    cls,
    data: dict[str, Any],
    *,
    molecules: dict[str, "MoleculeImpl"],
    dat: Optional[DatLike] = None,
    parent: Optional[Entity] = None,
    local_name: Optional[str] = None,
) -> Self:
    """Create a Reaction from a dict.

    Args:
        data: Dict with keys: reactants, products, rate, name, description
        molecules: Dict mapping molecule names to MoleculeImpl instances
        dat: DAT anchor (if root entity)
        parent: Parent entity (if child)
        local_name: Override name (defaults to data key)

    Returns:
        New ReactionImpl instance
    """
    from ..infra.entity import MockDat

    name = local_name or data.get("name", "reaction")

    # Create mock dat if needed
    if dat is None and parent is None:
        dat = MockDat(f"rxn/{name}")

    # Build reactants dict: {MoleculeImpl: coefficient}
    reactants: Dict[MoleculeImpl, float] = {}
    for r in data.get("reactants", []):
        if isinstance(r, str):
            # Just a name, coefficient 1
            if r not in molecules:
                raise KeyError(
                    f"Reaction {name!r}: unknown reactant molecule {r!r} "
                    f"(not in chemistry molecules)"
                )
            reactants[molecules[r]] = 1
        elif isinstance(r, dict):
            # {name: coef} format
            for mol_name, coef in r.items():
                if mol_name not in molecules:
                    raise KeyError(
                        f"Reaction {name!r}: unknown reactant molecule {mol_name!r} "
                        f"(not in chemistry molecules)"
                    )
                reactants[molecules[mol_name]] = coef

    # Build products dict: {MoleculeImpl: coefficient}
    products: Dict[MoleculeImpl, float] = {}
    for p in data.get("products", []):
        if isinstance(p, str):
            # Just a name, coefficient 1
            if p not in molecules:
                raise KeyError(
                    f"Reaction {name!r}: unknown product molecule {p!r} "
                    f"(not in chemistry molecules)"
                )
            products[molecules[p]] = 1
        elif isinstance(p, dict):
            # {name: coef} format
            for mol_name, coef in p.items():
                if mol_name not in molecules:
                    raise KeyError(
                        f"Reaction {name!r}: unknown product molecule {mol_name!r} "
                        f"(not in chemistry molecules)"
                    )
                products[molecules[mol_name]] = coef

    # Build modifiers dict: {MoleculeImpl: role}.  Accepts the canonical
    # {name: role} mapping (attributes() output) or a bare list of names
    # (role defaults to "", mirroring the reactant/product list form). A
    # role that is itself a dict is a serialized Modulation (Modulation.to_dict());
    # everything else (a bare str) stays as-is, inert by default.
    modifiers: Dict[MoleculeImpl, ModifierValue] = {}
    raw_modifiers = data.get("modifiers", {})
    if isinstance(raw_modifiers, dict):
        mod_items = raw_modifiers.items()
    else:
        mod_items = (
            (m, "") if isinstance(m, str) else next(iter(m.items()))
            for m in raw_modifiers
        )
    for mol_name, role in mod_items:
        if mol_name not in molecules:
            raise KeyError(
                f"Reaction {name!r}: unknown modifier molecule {mol_name!r} "
                f"(not in chemistry molecules)"
            )
        modifiers[molecules[mol_name]] = Modulation(**role) if isinstance(role, dict) else role

    # Get rate (function or constant)
    rate = data.get("rate", 1.0)

    return cls(
        name,
        reactants=reactants,
        products=products,
        modifiers=modifiers,
        rate=rate,
        rate_law=data.get("rate_law"),
        parent=parent,
        dat=dat,
        description=data.get("description", ""),
    )

set_rate(rate)

Set the reaction rate.

Source code in src/alienbio/bio/reaction.py
def set_rate(self, rate: RateValue) -> None:
    """Set the reaction rate."""
    self._rate = rate

add_reactant(molecule, coefficient=1.0)

Add a reactant to this reaction.

Source code in src/alienbio/bio/reaction.py
def add_reactant(self, molecule: MoleculeImpl, coefficient: float = 1.0) -> None:
    """Add a reactant to this reaction."""
    self._reactants[molecule] = coefficient

add_product(molecule, coefficient=1.0)

Add a product to this reaction.

Source code in src/alienbio/bio/reaction.py
def add_product(self, molecule: MoleculeImpl, coefficient: float = 1.0) -> None:
    """Add a product to this reaction."""
    self._products[molecule] = coefficient

add_modifier(molecule, role='')

Add a catalyst/regulator (not stoichiometrically consumed).

Source code in src/alienbio/bio/reaction.py
def add_modifier(self, molecule: MoleculeImpl, role: ModifierValue = "") -> None:
    """Add a catalyst/regulator (not stoichiometrically consumed)."""
    self._modifiers[molecule] = role

attributes()

Semantic content of this reaction.

Source code in src/alienbio/bio/reaction.py
def attributes(self) -> Dict[str, Any]:
    """Semantic content of this reaction."""
    result = super().attributes()

    # Serialize reactants as {molecule_name: coefficient}
    if self._reactants:
        result["reactants"] = {
            mol.local_name: coef for mol, coef in self._reactants.items()
        }
    if self._products:
        result["products"] = {
            mol.local_name: coef for mol, coef in self._products.items()
        }

    # Serialize modifiers as {molecule_name: role} (catalysts/regulators). A bare
    # str role round-trips as-is; a Modulation serializes via to_dict() so hydrate()
    # can reconstruct it.
    if self._modifiers:
        result["modifiers"] = {
            mol.local_name: (role.to_dict() if isinstance(role, Modulation) else role)
            for mol, role in self._modifiers.items()
        }

    result["rate"] = self._rate
    if self._rate_law is not None:
        from .rate_expr import to_json

        result["rate_law"] = to_json(self._rate_law)

    return result

__repr__()

Full representation.

Source code in src/alienbio/bio/reaction.py
def __repr__(self) -> str:
    """Full representation."""
    reactant_str = " + ".join(
        f"{c}{m.local_name}" if c != 1 else m.local_name
        for m, c in self._reactants.items()
    )
    product_str = " + ".join(
        f"{c}{m.local_name}" if c != 1 else m.local_name
        for m, c in self._products.items()
    )
    rate_str = str(self._rate)
    arrow = "->"
    if self._modifiers:
        mod_str = ", ".join(
            f"{m.local_name}:{role}" if role else m.local_name
            for m, role in self._modifiers.items()
        )
        arrow = f"-[{mod_str}]->"
    return f"ReactionImpl({self._local_name}: {reactant_str} {arrow} {product_str}, rate={rate_str})"

Modulation dataclass

A modifier's effect on its reaction's rate (F015 S2 bidirectional modulation).

Ship-now form is LINEAR (F015 Q1): an "activator" (param a) scales the rate up via (1 + a * [modifier]); an "inhibitor" (param Ki) scales it down via dividing by (1 + [modifier] / Ki). Two saturable kinds (M38.3): "michaelis" (params Vmax/K) and "hill" (additionally n) — see the field docs below and WorldSimulatorImpl._modulation_factor. Any other kind (including the label-only default "") is rate-inert — a pure documentation tag, contributing a factor of exactly 1.0.

Frozen + a pure function of the frozen start-of-step state elsewhere (F015 Q4): this dataclass only carries the parameters, it has no simulation behavior of its own.

Source code in src/alienbio/bio/reaction.py
@dataclass(frozen=True)
class Modulation:
    """A modifier's effect on its reaction's rate (F015 S2 bidirectional modulation).

    Ship-now form is LINEAR (F015 Q1): an ``"activator"`` (param ``a``) scales the rate
    up via ``(1 + a * [modifier])``; an ``"inhibitor"`` (param ``Ki``) scales it down via
    dividing by ``(1 + [modifier] / Ki)``. Two saturable kinds (M38.3): ``"michaelis"``
    (params ``Vmax``/``K``) and ``"hill"`` (additionally ``n``) — see the field docs
    below and ``WorldSimulatorImpl._modulation_factor``. Any other ``kind`` (including
    the label-only default ``""``) is rate-inert — a pure documentation tag,
    contributing a factor of exactly ``1.0``.

    Frozen + a pure function of the frozen start-of-step state elsewhere (F015 Q4): this
    dataclass only carries the parameters, it has no simulation behavior of its own.
    """

    kind: str = ""
    a: Optional[float] = None
    Ki: Optional[float] = None
    #: Saturable-kind params (F015 M38.3): ``"michaelis"`` uses ``Vmax``/``K``
    #: (``Vmax * [modifier] / (K + [modifier])``); ``"hill"`` additionally uses
    #: the cooperativity exponent ``n`` (``Vmax * [modifier]**n / (K**n +
    #: [modifier]**n)``). Both are keyed off the MODIFIER's own concentration,
    #: same as the linear ``a``/``Ki`` kinds — see
    #: ``WorldSimulatorImpl._modulation_factor``.
    Vmax: Optional[float] = None
    K: Optional[float] = None
    n: Optional[float] = None

    @classmethod
    def from_value(cls, value: "Union[Modulation, str]") -> "Modulation":
        """Coerce a bare ``str`` role tag into a label-only, rate-inert ``Modulation``.

        Backward-compat (F015 Q2): every existing call site passes a bare ``str`` role
        (e.g. ``"catalyst"``); that role becomes ``Modulation(kind=<the string>)``, whose
        factor is exactly ``1.0`` — matching today, where the role never reached the
        simulator.
        """
        if isinstance(value, Modulation):
            return value
        return cls(kind=value)

    def to_dict(self) -> Dict[str, Any]:
        """Serialize non-default fields (for ``attributes()``/``hydrate()`` round-trips)."""
        result: Dict[str, Any] = {"kind": self.kind}
        if self.a is not None:
            result["a"] = self.a
        if self.Ki is not None:
            result["Ki"] = self.Ki
        if self.Vmax is not None:
            result["Vmax"] = self.Vmax
        if self.K is not None:
            result["K"] = self.K
        if self.n is not None:
            result["n"] = self.n
        return result

from_value(value) classmethod

Coerce a bare str role tag into a label-only, rate-inert Modulation.

Backward-compat (F015 Q2): every existing call site passes a bare str role (e.g. "catalyst"); that role becomes Modulation(kind=<the string>), whose factor is exactly 1.0 — matching today, where the role never reached the simulator.

Source code in src/alienbio/bio/reaction.py
@classmethod
def from_value(cls, value: "Union[Modulation, str]") -> "Modulation":
    """Coerce a bare ``str`` role tag into a label-only, rate-inert ``Modulation``.

    Backward-compat (F015 Q2): every existing call site passes a bare ``str`` role
    (e.g. ``"catalyst"``); that role becomes ``Modulation(kind=<the string>)``, whose
    factor is exactly ``1.0`` — matching today, where the role never reached the
    simulator.
    """
    if isinstance(value, Modulation):
        return value
    return cls(kind=value)

to_dict()

Serialize non-default fields (for attributes()/hydrate() round-trips).

Source code in src/alienbio/bio/reaction.py
def to_dict(self) -> Dict[str, Any]:
    """Serialize non-default fields (for ``attributes()``/``hydrate()`` round-trips)."""
    result: Dict[str, Any] = {"kind": self.kind}
    if self.a is not None:
        result["a"] = self.a
    if self.Ki is not None:
        result["Ki"] = self.Ki
    if self.Vmax is not None:
        result["Vmax"] = self.Vmax
    if self.K is not None:
        result["K"] = self.K
    if self.n is not None:
        result["n"] = self.n
    return result

Flow

Bases: ABC

Abstract base class for all flows.

Flows move molecules (or instances) between compartments. Each flow is anchored to an origin compartment.

Subclasses: - TransportFlux: amount-conserving transport between any two compartments - GeneralFlow: arbitrary state modifications (placeholder)

Common interface: - origin: the compartment where this flow is anchored - name: human-readable identifier - compute_flux(): calculate transfer rate - apply(): modify state based on flux

Source code in src/alienbio/bio/flow.py
class Flow(ABC):
    """Abstract base class for all flows.

    Flows move molecules (or instances) between compartments. Each flow is
    anchored to an origin compartment.

    Subclasses:
    - TransportFlux: amount-conserving transport between any two compartments
    - GeneralFlow: arbitrary state modifications (placeholder)

    Common interface:
    - origin: the compartment where this flow is anchored
    - name: human-readable identifier
    - compute_flux(): calculate transfer rate
    - apply(): modify state based on flux
    """

    __slots__ = ("_origin", "_name")

    def __init__(
        self,
        origin: CompartmentId,
        name: str = "",
    ) -> None:
        """Initialize base flow.

        Args:
            origin: The origin compartment (where this flow is anchored)
            name: Human-readable name for this flow
        """
        self._origin = origin
        self._name = name

    @property
    def origin(self) -> CompartmentId:
        """The origin compartment (where this flow is anchored)."""
        return self._origin

    @property
    def name(self) -> str:
        """Human-readable name."""
        return self._name

    @property
    @abstractmethod
    def is_membrane_flow(self) -> bool:
        """True if this is a membrane flow (origin ↔ parent)."""
        ...

    @property
    @abstractmethod
    def is_general_flow(self) -> bool:
        """True if this is a general flow (arbitrary edits)."""
        ...

    @abstractmethod
    def compute_flux(
        self,
        state: WorldStateImpl,
        tree: CompartmentTreeImpl,
    ) -> float:
        """Compute flux for this flow.

        Args:
            state: Current world state with concentrations
            tree: Compartment topology

        Returns:
            Flux value (positive = into origin for membrane flows)
        """
        ...

    @abstractmethod
    def apply(
        self,
        state: WorldStateImpl,
        tree: CompartmentTreeImpl,
        dt: float = 1.0,
    ) -> None:
        """Apply this flow to the state (mutates in place).

        Args:
            state: World state to modify
            tree: Compartment topology
            dt: Time step
        """
        ...

    @abstractmethod
    def attributes(self) -> Dict[str, Any]:
        """Semantic content for serialization."""
        ...

origin property

The origin compartment (where this flow is anchored).

name property

Human-readable name.

is_membrane_flow abstractmethod property

True if this is a membrane flow (origin ↔ parent).

is_general_flow abstractmethod property

True if this is a general flow (arbitrary edits).

__init__(origin, name='')

Initialize base flow.

Parameters:

Name Type Description Default
origin CompartmentId

The origin compartment (where this flow is anchored)

required
name str

Human-readable name for this flow

''
Source code in src/alienbio/bio/flow.py
def __init__(
    self,
    origin: CompartmentId,
    name: str = "",
) -> None:
    """Initialize base flow.

    Args:
        origin: The origin compartment (where this flow is anchored)
        name: Human-readable name for this flow
    """
    self._origin = origin
    self._name = name

compute_flux(state, tree) abstractmethod

Compute flux for this flow.

Parameters:

Name Type Description Default
state WorldStateImpl

Current world state with concentrations

required
tree CompartmentTreeImpl

Compartment topology

required

Returns:

Type Description
float

Flux value (positive = into origin for membrane flows)

Source code in src/alienbio/bio/flow.py
@abstractmethod
def compute_flux(
    self,
    state: WorldStateImpl,
    tree: CompartmentTreeImpl,
) -> float:
    """Compute flux for this flow.

    Args:
        state: Current world state with concentrations
        tree: Compartment topology

    Returns:
        Flux value (positive = into origin for membrane flows)
    """
    ...

apply(state, tree, dt=1.0) abstractmethod

Apply this flow to the state (mutates in place).

Parameters:

Name Type Description Default
state WorldStateImpl

World state to modify

required
tree CompartmentTreeImpl

Compartment topology

required
dt float

Time step

1.0
Source code in src/alienbio/bio/flow.py
@abstractmethod
def apply(
    self,
    state: WorldStateImpl,
    tree: CompartmentTreeImpl,
    dt: float = 1.0,
) -> None:
    """Apply this flow to the state (mutates in place).

    Args:
        state: World state to modify
        tree: Compartment topology
        dt: Time step
    """
    ...

attributes() abstractmethod

Semantic content for serialization.

Source code in src/alienbio/bio/flow.py
@abstractmethod
def attributes(self) -> Dict[str, Any]:
    """Semantic content for serialization."""
    ...

GeneralFlow

Bases: Flow

Arbitrary state modifications (placeholder).

GeneralFlow is a catch-all for flows that don't fit the TransportFlux pattern. This includes: - Lateral flows between siblings - Instance transfers (RBCs moving between compartments) - Any other arbitrary edits to the system

NOTE: This is currently a placeholder. Full implementation will require a more general interpreter to handle arbitrary state modifications specified via Expr or similar.

For now, GeneralFlow stores an apply_fn that takes state and tree and performs arbitrary modifications.

Source code in src/alienbio/bio/flow.py
class GeneralFlow(Flow):
    """Arbitrary state modifications (placeholder).

    GeneralFlow is a catch-all for flows that don't fit the TransportFlux pattern.
    This includes:
    - Lateral flows between siblings
    - Instance transfers (RBCs moving between compartments)
    - Any other arbitrary edits to the system

    NOTE: This is currently a placeholder. Full implementation will require
    a more general interpreter to handle arbitrary state modifications
    specified via Expr or similar.

    For now, GeneralFlow stores an apply_fn that takes state and tree
    and performs arbitrary modifications.
    """

    __slots__ = ("_apply_fn", "_description")

    def __init__(
        self,
        origin: CompartmentId,
        apply_fn: Optional[Callable[[WorldStateImpl, CompartmentTreeImpl, float], None]] = None,
        name: str = "",
        description: str = "",
    ) -> None:
        """Initialize a general flow.

        Args:
            origin: The compartment where this flow is conceptually anchored
            apply_fn: Function (state, tree, dt) -> None that modifies state
            name: Human-readable name for this flow
            description: Description of what this flow does

        NOTE: This is a placeholder. Full implementation will need a more
        general interpreter to support Expr-based specifications.
        """
        if not name:
            name = f"general_flow_at_{origin}"
        super().__init__(origin, name)

        self._apply_fn = apply_fn
        self._description = description

    @property
    def description(self) -> str:
        """Description of what this flow does."""
        return self._description

    @property
    def is_membrane_flow(self) -> bool:
        """False - this is not a membrane flow."""
        return False

    @property
    def is_general_flow(self) -> bool:
        """True - this is a general flow."""
        return True

    def compute_flux(
        self,
        state: WorldStateImpl,
        tree: CompartmentTreeImpl,
    ) -> float:
        """General flows don't have a simple flux concept.

        Returns 0.0 as placeholder. The actual work happens in apply().
        """
        return 0.0

    def apply(
        self,
        state: WorldStateImpl,
        tree: CompartmentTreeImpl,
        dt: float = 1.0,
    ) -> None:
        """Apply this flow to the state (mutates in place).

        Args:
            state: World state to modify
            tree: Compartment topology
            dt: Time step
        """
        if self._apply_fn is not None:
            self._apply_fn(state, tree, dt)

    def attributes(self) -> Dict[str, Any]:
        """Semantic content for serialization.

        NOTE: apply_fn cannot be serialized. Full implementation will
        need Expr-based specification that can be serialized.
        """
        return {
            "type": "general",
            "name": self._name,
            "origin": self._origin,
            "description": self._description,
        }

    def __repr__(self) -> str:
        """Full representation."""
        return f"GeneralFlow(origin={self._origin}, name={self._name!r})"

    def __str__(self) -> str:
        """Short representation."""
        return f"GeneralFlow({self._name})"

description property

Description of what this flow does.

is_membrane_flow property

False - this is not a membrane flow.

is_general_flow property

True - this is a general flow.

__init__(origin, apply_fn=None, name='', description='')

Initialize a general flow.

Parameters:

Name Type Description Default
origin CompartmentId

The compartment where this flow is conceptually anchored

required
apply_fn Optional[Callable[[WorldStateImpl, CompartmentTreeImpl, float], None]]

Function (state, tree, dt) -> None that modifies state

None
name str

Human-readable name for this flow

''
description str

Description of what this flow does

''

NOTE: This is a placeholder. Full implementation will need a more general interpreter to support Expr-based specifications.

Source code in src/alienbio/bio/flow.py
def __init__(
    self,
    origin: CompartmentId,
    apply_fn: Optional[Callable[[WorldStateImpl, CompartmentTreeImpl, float], None]] = None,
    name: str = "",
    description: str = "",
) -> None:
    """Initialize a general flow.

    Args:
        origin: The compartment where this flow is conceptually anchored
        apply_fn: Function (state, tree, dt) -> None that modifies state
        name: Human-readable name for this flow
        description: Description of what this flow does

    NOTE: This is a placeholder. Full implementation will need a more
    general interpreter to support Expr-based specifications.
    """
    if not name:
        name = f"general_flow_at_{origin}"
    super().__init__(origin, name)

    self._apply_fn = apply_fn
    self._description = description

compute_flux(state, tree)

General flows don't have a simple flux concept.

Returns 0.0 as placeholder. The actual work happens in apply().

Source code in src/alienbio/bio/flow.py
def compute_flux(
    self,
    state: WorldStateImpl,
    tree: CompartmentTreeImpl,
) -> float:
    """General flows don't have a simple flux concept.

    Returns 0.0 as placeholder. The actual work happens in apply().
    """
    return 0.0

apply(state, tree, dt=1.0)

Apply this flow to the state (mutates in place).

Parameters:

Name Type Description Default
state WorldStateImpl

World state to modify

required
tree CompartmentTreeImpl

Compartment topology

required
dt float

Time step

1.0
Source code in src/alienbio/bio/flow.py
def apply(
    self,
    state: WorldStateImpl,
    tree: CompartmentTreeImpl,
    dt: float = 1.0,
) -> None:
    """Apply this flow to the state (mutates in place).

    Args:
        state: World state to modify
        tree: Compartment topology
        dt: Time step
    """
    if self._apply_fn is not None:
        self._apply_fn(state, tree, dt)

attributes()

Semantic content for serialization.

NOTE: apply_fn cannot be serialized. Full implementation will need Expr-based specification that can be serialized.

Source code in src/alienbio/bio/flow.py
def attributes(self) -> Dict[str, Any]:
    """Semantic content for serialization.

    NOTE: apply_fn cannot be serialized. Full implementation will
    need Expr-based specification that can be serialized.
    """
    return {
        "type": "general",
        "name": self._name,
        "origin": self._origin,
        "description": self._description,
    }

__repr__()

Full representation.

Source code in src/alienbio/bio/flow.py
def __repr__(self) -> str:
    """Full representation."""
    return f"GeneralFlow(origin={self._origin}, name={self._name!r})"

__str__()

Short representation.

Source code in src/alienbio/bio/flow.py
def __str__(self) -> str:
    """Short representation."""
    return f"GeneralFlow({self._name})"

TransportFlux

Bases: Flow

Cross-compartment flux: moves conserved AMOUNT (not concentration) between two independently-addressed compartments (F016/S3, skeleton decision S3 / coverage gap G3).

Unlike the M1 MembraneFlow it replaced (anchored to a parent-child pair via the tree; deleted in T056), origin/dest here are two arbitrary compartments — no tree relationship required, which is what lets a :class:~alienbio.suite.blocks. SpatialLatticeBlock wire an arbitrary neighbor graph.

Rate law (Q1=C, gradient default) — the event rate is driven by ONE driver_molecule's concentration:

  • rate_law="gradient" (default): Fickian, rate_constant * ([X]_origin - [X]_dest) — drives the driver species toward equal concentration across the two pools (the mechanism a diffusive lattice relaxes through).
  • rate_law="first_order": rate_constant * [X]_origin — a pump/boundary-flavored unidirectional law (no dependence on dest).

Either law's raw rate is floored at 0: this ONE flux is strictly origin -> dest. A reversed local gradient (or a negative first-order rate) contributes nothing from THIS flux — wire a second, reversed TransportFlux for true bidirectional equilibration (e.g. a lattice's neighbor pair in each direction). This also protects against oscillation: once the driver species reaches equality, tiny numerical overshoot floors to 0 instead of flip-flopping sign every step.

stoichiometry ({molecule_id: count}) lets several species move together per event — active co-transport needs no new law, just an extra (possibly negative-count, counter-direction) entry co-transporting an energy carrier. Every species is rationed against the SAME shared event count (so a co-transported group moves in lockstep): for each species, the event count is clamped so its LOSING compartment's :meth:WorldStateImpl.amount never goes negative — the amount-conservation invariant (F012 count basis) this class exists to guarantee. The identical transferred amount Δn leaves the losing pool and enters the other, so Σ amount is invariant regardless of the two compartments' volumes/multiplicities.

Source code in src/alienbio/bio/flow.py
class TransportFlux(Flow):
    """Cross-compartment flux: moves conserved AMOUNT (not concentration)
    between two independently-addressed compartments (F016/S3, skeleton
    decision S3 / coverage gap G3).

    Unlike the M1 ``MembraneFlow`` it replaced (anchored to a parent-child pair
    via the tree; deleted in T056), ``origin``/``dest`` here are two arbitrary compartments — no tree
    relationship required, which is what lets a :class:`~alienbio.suite.blocks.
    SpatialLatticeBlock` wire an arbitrary neighbor graph.

    Rate law (Q1=C, gradient default) — the event rate is driven by ONE
    ``driver_molecule``'s concentration:

    - ``rate_law="gradient"`` (default): Fickian, ``rate_constant *
      ([X]_origin - [X]_dest)`` — drives the driver species toward equal
      concentration across the two pools (the mechanism a diffusive lattice
      relaxes through).
    - ``rate_law="first_order"``: ``rate_constant * [X]_origin`` — a
      pump/boundary-flavored unidirectional law (no dependence on ``dest``).

    Either law's raw rate is floored at 0: this ONE flux is strictly
    ``origin -> dest``. A reversed local gradient (or a negative first-order
    rate) contributes nothing from THIS flux — wire a second, reversed
    ``TransportFlux`` for true bidirectional equilibration (e.g. a lattice's
    neighbor pair in each direction). This also protects against oscillation:
    once the driver species reaches equality, tiny numerical overshoot floors
    to 0 instead of flip-flopping sign every step.

    ``stoichiometry`` (``{molecule_id: count}``) lets several species move together per event — active
    co-transport needs no new law, just an extra (possibly negative-count,
    counter-direction) entry co-transporting an energy carrier. Every species
    is rationed against the SAME shared event count (so a co-transported group
    moves in lockstep): for each species, the event count is clamped so its
    LOSING compartment's :meth:`WorldStateImpl.amount` never goes negative —
    the amount-conservation invariant (F012 count basis) this class exists to
    guarantee. The identical transferred amount ``Δn`` leaves the losing pool
    and enters the other, so ``Σ amount`` is invariant regardless of the two
    compartments' volumes/multiplicities.
    """

    __slots__ = ("_dest", "_stoichiometry", "_driver_molecule", "_rate_constant", "_rate_law")

    def __init__(
        self,
        origin: CompartmentId,
        dest: CompartmentId,
        stoichiometry: Dict[int, float],
        driver_molecule: int,
        rate_constant: float = 1.0,
        rate_law: str = "gradient",
        name: str = "",
    ) -> None:
        """Initialize a cross-compartment transport flux.

        Args:
            origin: The compartment this flux moves species OUT OF (the "src" pool)
            dest: The compartment this flux moves species INTO (the "dst" pool)
            stoichiometry: Molecule id -> count moved per event (all species move
                together, in lockstep, scaled by the shared event count)
            driver_molecule: Which molecule id's concentration drives the rate law
            rate_constant: ``D`` (gradient law) or ``k`` (first-order law)
            rate_law: ``"gradient"`` (Fickian, default) or ``"first_order"``
            name: Human-readable name for this flow

        Raises:
            ValueError: if ``rate_law`` is not one of the two supported laws.
        """
        if rate_law not in ("gradient", "first_order"):
            raise ValueError(
                f"TransportFlux rate_law must be 'gradient' or 'first_order', "
                f"got {rate_law!r}"
            )
        if not name:
            name = f"transport_{origin}_to_{dest}"
        super().__init__(origin, name)

        self._dest = dest
        self._stoichiometry = dict(stoichiometry)
        self._driver_molecule = driver_molecule
        self._rate_constant = rate_constant
        self._rate_law = rate_law

    @property
    def dest(self) -> CompartmentId:
        """The compartment this flux moves species INTO."""
        return self._dest

    @property
    def stoichiometry(self) -> Dict[int, float]:
        """Molecule id -> count moved per event (shared event count)."""
        return self._stoichiometry.copy()

    @property
    def driver_molecule(self) -> int:
        """Which molecule id's concentration drives the rate law."""
        return self._driver_molecule

    @property
    def rate_constant(self) -> float:
        """``D`` (gradient law) or ``k`` (first-order law)."""
        return self._rate_constant

    @property
    def rate_law(self) -> str:
        """``"gradient"`` (Fickian) or ``"first_order"``."""
        return self._rate_law

    @property
    def is_membrane_flow(self) -> bool:
        """False - this is not a parent-child membrane flow."""
        return False

    @property
    def is_general_flow(self) -> bool:
        """False - this is not an arbitrary-edit general flow."""
        return False

    def compute_flux(
        self,
        state: WorldStateImpl,
        tree: CompartmentTreeImpl,
    ) -> float:
        """Raw (unfloored, unrationed) event rate from the configured rate law.

        Args:
            state: Current world state with concentrations
            tree: Compartment topology (unused — origin/dest need no tree
                relationship)

        Returns:
            Event rate (events per unit time); may be negative (floored to 0
            in :meth:`apply`).
        """
        conc_src = state.get(self._origin, self._driver_molecule)
        if self._rate_law == "first_order":
            return self._rate_constant * conc_src
        # gradient (default): Fickian, driven toward equal concentration
        conc_dst = state.get(self._dest, self._driver_molecule)
        return self._rate_constant * (conc_src - conc_dst)

    def demand(
        self,
        frozen: WorldStateImpl,
        tree: CompartmentTreeImpl,
        dt: float = 1.0,
    ) -> tuple[float, Dict[tuple[CompartmentId, int], float]]:
        """The event count this flux wants this step, read off the FROZEN
        start-of-step state, and the AMOUNT it would draw from each losing
        ``(compartment, molecule)`` at that count (T053).

        The losing pool for a species is origin when its count is positive
        (origin -> dest), else dest (a negative count antiports that species).
        """
        event_count = max(self.compute_flux(frozen, tree) * dt, 0.0)
        draws: Dict[tuple[CompartmentId, int], float] = {}
        if event_count <= 0.0:
            return 0.0, draws
        for mol, count in self._stoichiometry.items():
            if count == 0:
                continue
            losing = self._origin if count > 0 else self._dest
            draws[(losing, mol)] = draws.get((losing, mol), 0.0) + abs(count) * event_count
        return event_count, draws

    def apply_events(
        self,
        state: WorldStateImpl,
        scales: WorldStateImpl,
        event_count: float,
    ) -> None:
        """Move ``event_count`` events' worth of every species (amounts read
        against ``scales``' multiplicity x volume), mutating ``state``."""
        if event_count <= 0.0:
            return
        origin_scale = scales.get_multiplicity(self._origin) * scales.get_volume(self._origin)
        dest_scale = scales.get_multiplicity(self._dest) * scales.get_volume(self._dest)
        for mol, count in self._stoichiometry.items():
            delta_n = event_count * count
            if origin_scale > 0:
                state.set(
                    self._origin, mol, state.get(self._origin, mol) - delta_n / origin_scale
                )
            if dest_scale > 0:
                state.set(self._dest, mol, state.get(self._dest, mol) + delta_n / dest_scale)

    def apply(
        self,
        state: WorldStateImpl,
        tree: CompartmentTreeImpl,
        dt: float = 1.0,
    ) -> None:
        """Apply this flux ALONE to ``state`` (mutates in place): the event
        count read off ``state``, rationed against every transported species'
        available AMOUNT in its losing compartment, then the same clamped
        count moved for each species. The stepper does not call this — it
        runs every flow together through :func:`apply_flows`, which rations
        the SUMMED demand of all flows on each pool; this is the one-flow
        path for callers that step a flux by hand.
        """
        event_count, draws = self.demand(state, tree, dt)
        for (comp, mol), amount in draws.items():
            available = state.amount(comp, mol)
            if amount > available:
                event_count = min(event_count, event_count * available / amount if amount > 0 else 0.0)
        self.apply_events(state, state, max(event_count, 0.0))

    def attributes(self) -> Dict[str, Any]:
        """Semantic content for serialization."""
        return {
            "type": "transport",
            "name": self._name,
            "origin": self._origin,
            "dest": self._dest,
            "stoichiometry": self._stoichiometry.copy(),
            "driver_molecule": self._driver_molecule,
            "rate_constant": self._rate_constant,
            "rate_law": self._rate_law,
        }

    def __repr__(self) -> str:
        """Full representation."""
        stoich_str = ", ".join(f"{m}:{c}" for m, c in self._stoichiometry.items())
        return (
            f"TransportFlux(origin={self._origin}, dest={self._dest}, "
            f"stoich={{{stoich_str}}}, rate={self._rate_constant}, law={self._rate_law!r})"
        )

    def __str__(self) -> str:
        """Short representation."""
        return f"TransportFlux({self._name})"

dest property

The compartment this flux moves species INTO.

stoichiometry property

Molecule id -> count moved per event (shared event count).

driver_molecule property

Which molecule id's concentration drives the rate law.

rate_constant property

D (gradient law) or k (first-order law).

rate_law property

"gradient" (Fickian) or "first_order".

is_membrane_flow property

False - this is not a parent-child membrane flow.

is_general_flow property

False - this is not an arbitrary-edit general flow.

__init__(origin, dest, stoichiometry, driver_molecule, rate_constant=1.0, rate_law='gradient', name='')

Initialize a cross-compartment transport flux.

Parameters:

Name Type Description Default
origin CompartmentId

The compartment this flux moves species OUT OF (the "src" pool)

required
dest CompartmentId

The compartment this flux moves species INTO (the "dst" pool)

required
stoichiometry Dict[int, float]

Molecule id -> count moved per event (all species move together, in lockstep, scaled by the shared event count)

required
driver_molecule int

Which molecule id's concentration drives the rate law

required
rate_constant float

D (gradient law) or k (first-order law)

1.0
rate_law str

"gradient" (Fickian, default) or "first_order"

'gradient'
name str

Human-readable name for this flow

''

Raises:

Type Description
ValueError

if rate_law is not one of the two supported laws.

Source code in src/alienbio/bio/flow.py
def __init__(
    self,
    origin: CompartmentId,
    dest: CompartmentId,
    stoichiometry: Dict[int, float],
    driver_molecule: int,
    rate_constant: float = 1.0,
    rate_law: str = "gradient",
    name: str = "",
) -> None:
    """Initialize a cross-compartment transport flux.

    Args:
        origin: The compartment this flux moves species OUT OF (the "src" pool)
        dest: The compartment this flux moves species INTO (the "dst" pool)
        stoichiometry: Molecule id -> count moved per event (all species move
            together, in lockstep, scaled by the shared event count)
        driver_molecule: Which molecule id's concentration drives the rate law
        rate_constant: ``D`` (gradient law) or ``k`` (first-order law)
        rate_law: ``"gradient"`` (Fickian, default) or ``"first_order"``
        name: Human-readable name for this flow

    Raises:
        ValueError: if ``rate_law`` is not one of the two supported laws.
    """
    if rate_law not in ("gradient", "first_order"):
        raise ValueError(
            f"TransportFlux rate_law must be 'gradient' or 'first_order', "
            f"got {rate_law!r}"
        )
    if not name:
        name = f"transport_{origin}_to_{dest}"
    super().__init__(origin, name)

    self._dest = dest
    self._stoichiometry = dict(stoichiometry)
    self._driver_molecule = driver_molecule
    self._rate_constant = rate_constant
    self._rate_law = rate_law

compute_flux(state, tree)

Raw (unfloored, unrationed) event rate from the configured rate law.

Parameters:

Name Type Description Default
state WorldStateImpl

Current world state with concentrations

required
tree CompartmentTreeImpl

Compartment topology (unused — origin/dest need no tree relationship)

required

Returns:

Name Type Description
float

Event rate (events per unit time); may be negative (floored to 0

in float

meth:apply).

Source code in src/alienbio/bio/flow.py
def compute_flux(
    self,
    state: WorldStateImpl,
    tree: CompartmentTreeImpl,
) -> float:
    """Raw (unfloored, unrationed) event rate from the configured rate law.

    Args:
        state: Current world state with concentrations
        tree: Compartment topology (unused — origin/dest need no tree
            relationship)

    Returns:
        Event rate (events per unit time); may be negative (floored to 0
        in :meth:`apply`).
    """
    conc_src = state.get(self._origin, self._driver_molecule)
    if self._rate_law == "first_order":
        return self._rate_constant * conc_src
    # gradient (default): Fickian, driven toward equal concentration
    conc_dst = state.get(self._dest, self._driver_molecule)
    return self._rate_constant * (conc_src - conc_dst)

demand(frozen, tree, dt=1.0)

The event count this flux wants this step, read off the FROZEN start-of-step state, and the AMOUNT it would draw from each losing (compartment, molecule) at that count (T053).

The losing pool for a species is origin when its count is positive (origin -> dest), else dest (a negative count antiports that species).

Source code in src/alienbio/bio/flow.py
def demand(
    self,
    frozen: WorldStateImpl,
    tree: CompartmentTreeImpl,
    dt: float = 1.0,
) -> tuple[float, Dict[tuple[CompartmentId, int], float]]:
    """The event count this flux wants this step, read off the FROZEN
    start-of-step state, and the AMOUNT it would draw from each losing
    ``(compartment, molecule)`` at that count (T053).

    The losing pool for a species is origin when its count is positive
    (origin -> dest), else dest (a negative count antiports that species).
    """
    event_count = max(self.compute_flux(frozen, tree) * dt, 0.0)
    draws: Dict[tuple[CompartmentId, int], float] = {}
    if event_count <= 0.0:
        return 0.0, draws
    for mol, count in self._stoichiometry.items():
        if count == 0:
            continue
        losing = self._origin if count > 0 else self._dest
        draws[(losing, mol)] = draws.get((losing, mol), 0.0) + abs(count) * event_count
    return event_count, draws

apply_events(state, scales, event_count)

Move event_count events' worth of every species (amounts read against scales' multiplicity x volume), mutating state.

Source code in src/alienbio/bio/flow.py
def apply_events(
    self,
    state: WorldStateImpl,
    scales: WorldStateImpl,
    event_count: float,
) -> None:
    """Move ``event_count`` events' worth of every species (amounts read
    against ``scales``' multiplicity x volume), mutating ``state``."""
    if event_count <= 0.0:
        return
    origin_scale = scales.get_multiplicity(self._origin) * scales.get_volume(self._origin)
    dest_scale = scales.get_multiplicity(self._dest) * scales.get_volume(self._dest)
    for mol, count in self._stoichiometry.items():
        delta_n = event_count * count
        if origin_scale > 0:
            state.set(
                self._origin, mol, state.get(self._origin, mol) - delta_n / origin_scale
            )
        if dest_scale > 0:
            state.set(self._dest, mol, state.get(self._dest, mol) + delta_n / dest_scale)

apply(state, tree, dt=1.0)

Apply this flux ALONE to state (mutates in place): the event count read off state, rationed against every transported species' available AMOUNT in its losing compartment, then the same clamped count moved for each species. The stepper does not call this — it runs every flow together through :func:apply_flows, which rations the SUMMED demand of all flows on each pool; this is the one-flow path for callers that step a flux by hand.

Source code in src/alienbio/bio/flow.py
def apply(
    self,
    state: WorldStateImpl,
    tree: CompartmentTreeImpl,
    dt: float = 1.0,
) -> None:
    """Apply this flux ALONE to ``state`` (mutates in place): the event
    count read off ``state``, rationed against every transported species'
    available AMOUNT in its losing compartment, then the same clamped
    count moved for each species. The stepper does not call this — it
    runs every flow together through :func:`apply_flows`, which rations
    the SUMMED demand of all flows on each pool; this is the one-flow
    path for callers that step a flux by hand.
    """
    event_count, draws = self.demand(state, tree, dt)
    for (comp, mol), amount in draws.items():
        available = state.amount(comp, mol)
        if amount > available:
            event_count = min(event_count, event_count * available / amount if amount > 0 else 0.0)
    self.apply_events(state, state, max(event_count, 0.0))

attributes()

Semantic content for serialization.

Source code in src/alienbio/bio/flow.py
def attributes(self) -> Dict[str, Any]:
    """Semantic content for serialization."""
    return {
        "type": "transport",
        "name": self._name,
        "origin": self._origin,
        "dest": self._dest,
        "stoichiometry": self._stoichiometry.copy(),
        "driver_molecule": self._driver_molecule,
        "rate_constant": self._rate_constant,
        "rate_law": self._rate_law,
    }

__repr__()

Full representation.

Source code in src/alienbio/bio/flow.py
def __repr__(self) -> str:
    """Full representation."""
    stoich_str = ", ".join(f"{m}:{c}" for m, c in self._stoichiometry.items())
    return (
        f"TransportFlux(origin={self._origin}, dest={self._dest}, "
        f"stoich={{{stoich_str}}}, rate={self._rate_constant}, law={self._rate_law!r})"
    )

__str__()

Short representation.

Source code in src/alienbio/bio/flow.py
def __str__(self) -> str:
    """Short representation."""
    return f"TransportFlux({self._name})"

ChemistryImpl

Bases: Entity

Implementation: Container for a chemical system.

Chemistry holds atoms, molecules, and reactions as public dict attributes. These are indexed by: - atoms: by symbol ("C", "H", "O") - molecules: by name ("glucose", "atp") - reactions: by name ("glycolysis_step1", "atp_synthesis")

Chemistry is conceptually immutable - built complete via constructor, though the dicts are technically mutable for flexibility.

Example

chem = ChemistryImpl( "glycolysis", atoms={"C": carbon, "H": hydrogen, "O": oxygen}, molecules={"glucose": glucose_mol, "atp": atp_mol}, reactions={"step1": reaction1, "step2": reaction2}, dat=dat, )

Direct access to contents

chem.atoms["C"] # -> carbon atom chem.molecules["glucose"] # -> glucose molecule chem.reactions["step1"] # -> reaction1

Source code in src/alienbio/bio/chemistry.py
class ChemistryImpl(Entity, head="Chemistry"):
    """Implementation: Container for a chemical system.

    Chemistry holds atoms, molecules, and reactions as public dict attributes.
    These are indexed by:
    - atoms: by symbol ("C", "H", "O")
    - molecules: by name ("glucose", "atp")
    - reactions: by name ("glycolysis_step1", "atp_synthesis")

    Chemistry is conceptually immutable - built complete via constructor,
    though the dicts are technically mutable for flexibility.

    Example:
        chem = ChemistryImpl(
            "glycolysis",
            atoms={"C": carbon, "H": hydrogen, "O": oxygen},
            molecules={"glucose": glucose_mol, "atp": atp_mol},
            reactions={"step1": reaction1, "step2": reaction2},
            dat=dat,
        )

        # Direct access to contents
        chem.atoms["C"]  # -> carbon atom
        chem.molecules["glucose"]  # -> glucose molecule
        chem.reactions["step1"]  # -> reaction1
    """

    __slots__ = ("atoms", "molecules", "reactions")

    # Public attributes - direct access, no property wrappers
    atoms: Dict[str, AtomImpl]
    molecules: Dict[str, MoleculeImpl]
    reactions: Dict[str, ReactionImpl]

    def __init__(
        self,
        name: str,
        *,
        atoms: Optional[Dict[str, AtomImpl]] = None,
        molecules: Optional[Dict[str, MoleculeImpl]] = None,
        reactions: Optional[Dict[str, ReactionImpl]] = None,
        parent: Optional[Entity] = None,
        dat: Optional[DatLike] = None,
        description: str = "",
    ) -> None:
        """Initialize a chemistry container.

        Args:
            name: Local name within parent
            atoms: Dict of atoms by symbol
            molecules: Dict of molecules by name
            reactions: Dict of reactions by name
            parent: Link to containing entity
            dat: DAT anchor for root chemistry entities
            description: Human-readable description
        """
        super().__init__(name, parent=parent, dat=dat, description=description)
        self.atoms = atoms.copy() if atoms else {}
        self.molecules = molecules.copy() if molecules else {}
        self.reactions = reactions.copy() if reactions else {}
        # One id cannot name two entities. The two dicts are independent, so a
        # shared id was accepted here and lost silently downstream: the opaque
        # name map writes molecules then reactions into one dict, so the
        # reaction won its surface name, the molecule got none, and the runner
        # then resolved that one token as the reaction for Intervene and the
        # molecule for Measure (T051 box 4 / T054 #1).
        shared = self.molecules.keys() & self.reactions.keys()
        if shared:
            raise ValueError(
                f"chemistry {name!r}: id(s) {sorted(shared)!r} name both a molecule "
                "and a reaction; ids must be unique across the whole chemistry"
            )

    @classmethod
    def hydrate(
        cls,
        data: dict[str, Any],
        *,
        dat: Optional[DatLike] = None,
        parent: Optional[Entity] = None,
        local_name: Optional[str] = None,
    ) -> Self:
        """Create a Chemistry from a dict.

        Recursively hydrates molecules and reactions from nested dicts.

        Args:
            data: Dict with keys: molecules, reactions, atoms, description
                  Each molecule/reaction can be a dict that gets hydrated.
            dat: DAT anchor (if root entity)
            parent: Parent entity (if child)
            local_name: Override name

        Returns:
            New ChemistryImpl with hydrated molecules and reactions
        """
        from ..infra.entity import MockDat

        name = local_name or data.get("name", "chemistry")

        # Create mock dat if needed
        if dat is None and parent is None:
            dat = MockDat(f"chem/{name}")

        # Extract molecules and reactions data
        molecules_data = data.get("molecules", {})
        reactions_data = data.get("reactions", {})

        # First pass: hydrate molecules
        molecules: Dict[str, MoleculeImpl] = {}
        for mol_key, mol_data in molecules_data.items():
            if isinstance(mol_data, dict):
                molecules[mol_key] = MoleculeImpl.hydrate(
                    mol_data,
                    local_name=mol_key,
                )
            else:
                # Simple name, create basic molecule
                molecules[mol_key] = MoleculeImpl.hydrate(
                    {"name": mol_key},
                    local_name=mol_key,
                )

        # Second pass: hydrate reactions (needs molecules)
        reactions: Dict[str, ReactionImpl] = {}
        for rxn_key, rxn_data in reactions_data.items():
            if isinstance(rxn_data, dict):
                reactions[rxn_key] = ReactionImpl.hydrate(
                    rxn_data,
                    molecules=molecules,
                    local_name=rxn_key,
                )
            else:
                # M8-residual: a non-dict reaction entry was previously dropped
                # silently, turning a malformed/typo'd spec into a chemistry with
                # missing reactions. Fail loudly instead.
                raise ValueError(
                    f"Reaction '{rxn_key}' must be a mapping, got "
                    f"{type(rxn_data).__name__}: {rxn_data!r}"
                )

        return cls(
            name,
            molecules=molecules,
            reactions=reactions,
            parent=parent,
            dat=dat,
            description=data.get("description", ""),
        )

    def validate(self) -> list[str]:
        """Validate the chemistry for consistency.

        Checks:
        - All molecule atoms are atoms in this chemistry
        - All reaction reactants/products are molecules in this chemistry

        Returns:
            List of error messages (empty if valid)
        """
        errors: list[str] = []
        atom_set = set(self.atoms.values())
        mol_set = set(self.molecules.values())

        # Check that all molecule atoms exist in chemistry
        for mol_name, molecule in self.molecules.items():
            for atom in molecule.atoms:
                if atom not in atom_set:
                    errors.append(
                        f"Molecule {mol_name}: atom {atom.symbol} not in chemistry"
                    )

        # Check that all reaction molecules exist in chemistry
        for rxn_name, reaction in self.reactions.items():
            for mol in reaction.reactants:
                if mol not in mol_set:
                    errors.append(
                        f"Reaction {rxn_name}: reactant {mol.name} not in chemistry"
                    )
            for mol in reaction.products:
                if mol not in mol_set:
                    errors.append(
                        f"Reaction {rxn_name}: product {mol.name} not in chemistry"
                    )

        return errors

    # ─────────────────────────────────────────────────────────────────────
    # Graph queries — the reaction network is the bipartite graph of
    # molecules (species nodes) and reactions (reaction nodes). Node ids are
    # molecule/reaction ``name``s. Delegates to the shared neutral algorithms
    # in :mod:`alienbio.infra.graph_ops` (single source of truth; originally
    # shared with the now-retired neutral ``ReactionNetwork`` view).
    # ─────────────────────────────────────────────────────────────────────

    def neighbors(self, node: str) -> set[str]:
        """Molecule<->reaction adjacency (bipartite), by name."""
        return graph_ops.neighbors(_reaction_graph_view(self), node)

    def paths(self, a: str, b: str, max_len: int = 8) -> List[List[str]]:
        """All simple paths (by name) from ``a`` to ``b`` within ``max_len`` edges."""
        return graph_ops.paths(_reaction_graph_view(self), a, b, max_len)

    def subgraph(self, nodes: Iterable[str]) -> "ChemistryImpl":
        """The induced sub-chemistry over ``nodes`` (edges to dropped nodes removed).

        Reuses the surviving molecule objects; rebuilds each surviving reaction
        with its reactant/product entries filtered to the kept molecules (rate is
        carried through by identity). All atoms are retained (they are not graph
        nodes).
        """
        node_set = set(nodes)
        kept_species, kept_reactions = graph_ops.subgraph_selection(
            _reaction_graph_view(self), node_set
        )
        name_to_mol = {mol.name: mol for mol in self.molecules.values()}
        name_to_rxn = {rxn.name: rxn for rxn in self.reactions.values()}

        new_molecules: Dict[str, MoleculeImpl] = {
            name: name_to_mol[name] for name in kept_species
        }
        new_reactions: Dict[str, ReactionImpl] = {}
        for rname in kept_reactions:
            rxn = name_to_rxn[rname]
            new_reactions[rname] = ReactionImpl(
                rname,
                reactants={
                    m: c for m, c in rxn.reactants.items() if m.name in node_set
                },
                products={
                    m: c for m, c in rxn.products.items() if m.name in node_set
                },
                rate=rxn.rate,
                dat=_mock_dat(f"rxn/{rname}"),
            )
        return ChemistryImpl(
            "subgraph",
            atoms=self.atoms.copy(),
            molecules=new_molecules,
            reactions=new_reactions,
            dat=_mock_dat("chem/subgraph"),
        )

    def match(self, pattern: "ChemistryImpl") -> List[Dict[str, str]]:
        """All subgraph embeddings of ``pattern`` into this chemistry.

        Molecules match on ``(name, symbol, bdepth, molecular_weight)`` equality,
        reactions structurally; injectivity and every pattern edge are enforced.
        Returns each embedding as ``{pattern_name: host_name}``; ``[]`` if none.
        """
        return graph_ops.match(
            _reaction_graph_view(self), _reaction_graph_view(pattern)
        )

    def attributes(self) -> Dict[str, Any]:
        """Semantic content of this chemistry."""
        result = super().attributes()

        # Serialize atoms as {symbol: {name, atomic_weight}}
        if self.atoms:
            result["atoms"] = {
                sym: {"name": atom.name, "atomic_weight": atom.atomic_weight}
                for sym, atom in self.atoms.items()
            }

        # Serialize molecules by name
        if self.molecules:
            result["molecules"] = {
                name: mol.attributes()
                for name, mol in self.molecules.items()
            }

        # Serialize reactions by name
        if self.reactions:
            result["reactions"] = {
                name: rxn.attributes()
                for name, rxn in self.reactions.items()
            }

        return result

    def __repr__(self) -> str:
        """Full representation."""
        return (
            f"ChemistryImpl({self._local_name!r}, "
            f"atoms={len(self.atoms)}, "
            f"molecules={len(self.molecules)}, "
            f"reactions={len(self.reactions)})"
        )

__init__(name, *, atoms=None, molecules=None, reactions=None, parent=None, dat=None, description='')

Initialize a chemistry container.

Parameters:

Name Type Description Default
name str

Local name within parent

required
atoms Optional[Dict[str, AtomImpl]]

Dict of atoms by symbol

None
molecules Optional[Dict[str, MoleculeImpl]]

Dict of molecules by name

None
reactions Optional[Dict[str, ReactionImpl]]

Dict of reactions by name

None
parent Optional[Entity]

Link to containing entity

None
dat Optional[DatLike]

DAT anchor for root chemistry entities

None
description str

Human-readable description

''
Source code in src/alienbio/bio/chemistry.py
def __init__(
    self,
    name: str,
    *,
    atoms: Optional[Dict[str, AtomImpl]] = None,
    molecules: Optional[Dict[str, MoleculeImpl]] = None,
    reactions: Optional[Dict[str, ReactionImpl]] = None,
    parent: Optional[Entity] = None,
    dat: Optional[DatLike] = None,
    description: str = "",
) -> None:
    """Initialize a chemistry container.

    Args:
        name: Local name within parent
        atoms: Dict of atoms by symbol
        molecules: Dict of molecules by name
        reactions: Dict of reactions by name
        parent: Link to containing entity
        dat: DAT anchor for root chemistry entities
        description: Human-readable description
    """
    super().__init__(name, parent=parent, dat=dat, description=description)
    self.atoms = atoms.copy() if atoms else {}
    self.molecules = molecules.copy() if molecules else {}
    self.reactions = reactions.copy() if reactions else {}
    # One id cannot name two entities. The two dicts are independent, so a
    # shared id was accepted here and lost silently downstream: the opaque
    # name map writes molecules then reactions into one dict, so the
    # reaction won its surface name, the molecule got none, and the runner
    # then resolved that one token as the reaction for Intervene and the
    # molecule for Measure (T051 box 4 / T054 #1).
    shared = self.molecules.keys() & self.reactions.keys()
    if shared:
        raise ValueError(
            f"chemistry {name!r}: id(s) {sorted(shared)!r} name both a molecule "
            "and a reaction; ids must be unique across the whole chemistry"
        )

hydrate(data, *, dat=None, parent=None, local_name=None) classmethod

Create a Chemistry from a dict.

Recursively hydrates molecules and reactions from nested dicts.

Parameters:

Name Type Description Default
data dict[str, Any]

Dict with keys: molecules, reactions, atoms, description Each molecule/reaction can be a dict that gets hydrated.

required
dat Optional[DatLike]

DAT anchor (if root entity)

None
parent Optional[Entity]

Parent entity (if child)

None
local_name Optional[str]

Override name

None

Returns:

Type Description
Self

New ChemistryImpl with hydrated molecules and reactions

Source code in src/alienbio/bio/chemistry.py
@classmethod
def hydrate(
    cls,
    data: dict[str, Any],
    *,
    dat: Optional[DatLike] = None,
    parent: Optional[Entity] = None,
    local_name: Optional[str] = None,
) -> Self:
    """Create a Chemistry from a dict.

    Recursively hydrates molecules and reactions from nested dicts.

    Args:
        data: Dict with keys: molecules, reactions, atoms, description
              Each molecule/reaction can be a dict that gets hydrated.
        dat: DAT anchor (if root entity)
        parent: Parent entity (if child)
        local_name: Override name

    Returns:
        New ChemistryImpl with hydrated molecules and reactions
    """
    from ..infra.entity import MockDat

    name = local_name or data.get("name", "chemistry")

    # Create mock dat if needed
    if dat is None and parent is None:
        dat = MockDat(f"chem/{name}")

    # Extract molecules and reactions data
    molecules_data = data.get("molecules", {})
    reactions_data = data.get("reactions", {})

    # First pass: hydrate molecules
    molecules: Dict[str, MoleculeImpl] = {}
    for mol_key, mol_data in molecules_data.items():
        if isinstance(mol_data, dict):
            molecules[mol_key] = MoleculeImpl.hydrate(
                mol_data,
                local_name=mol_key,
            )
        else:
            # Simple name, create basic molecule
            molecules[mol_key] = MoleculeImpl.hydrate(
                {"name": mol_key},
                local_name=mol_key,
            )

    # Second pass: hydrate reactions (needs molecules)
    reactions: Dict[str, ReactionImpl] = {}
    for rxn_key, rxn_data in reactions_data.items():
        if isinstance(rxn_data, dict):
            reactions[rxn_key] = ReactionImpl.hydrate(
                rxn_data,
                molecules=molecules,
                local_name=rxn_key,
            )
        else:
            # M8-residual: a non-dict reaction entry was previously dropped
            # silently, turning a malformed/typo'd spec into a chemistry with
            # missing reactions. Fail loudly instead.
            raise ValueError(
                f"Reaction '{rxn_key}' must be a mapping, got "
                f"{type(rxn_data).__name__}: {rxn_data!r}"
            )

    return cls(
        name,
        molecules=molecules,
        reactions=reactions,
        parent=parent,
        dat=dat,
        description=data.get("description", ""),
    )

validate()

Validate the chemistry for consistency.

Checks: - All molecule atoms are atoms in this chemistry - All reaction reactants/products are molecules in this chemistry

Returns:

Type Description
list[str]

List of error messages (empty if valid)

Source code in src/alienbio/bio/chemistry.py
def validate(self) -> list[str]:
    """Validate the chemistry for consistency.

    Checks:
    - All molecule atoms are atoms in this chemistry
    - All reaction reactants/products are molecules in this chemistry

    Returns:
        List of error messages (empty if valid)
    """
    errors: list[str] = []
    atom_set = set(self.atoms.values())
    mol_set = set(self.molecules.values())

    # Check that all molecule atoms exist in chemistry
    for mol_name, molecule in self.molecules.items():
        for atom in molecule.atoms:
            if atom not in atom_set:
                errors.append(
                    f"Molecule {mol_name}: atom {atom.symbol} not in chemistry"
                )

    # Check that all reaction molecules exist in chemistry
    for rxn_name, reaction in self.reactions.items():
        for mol in reaction.reactants:
            if mol not in mol_set:
                errors.append(
                    f"Reaction {rxn_name}: reactant {mol.name} not in chemistry"
                )
        for mol in reaction.products:
            if mol not in mol_set:
                errors.append(
                    f"Reaction {rxn_name}: product {mol.name} not in chemistry"
                )

    return errors

neighbors(node)

Molecule<->reaction adjacency (bipartite), by name.

Source code in src/alienbio/bio/chemistry.py
def neighbors(self, node: str) -> set[str]:
    """Molecule<->reaction adjacency (bipartite), by name."""
    return graph_ops.neighbors(_reaction_graph_view(self), node)

paths(a, b, max_len=8)

All simple paths (by name) from a to b within max_len edges.

Source code in src/alienbio/bio/chemistry.py
def paths(self, a: str, b: str, max_len: int = 8) -> List[List[str]]:
    """All simple paths (by name) from ``a`` to ``b`` within ``max_len`` edges."""
    return graph_ops.paths(_reaction_graph_view(self), a, b, max_len)

subgraph(nodes)

The induced sub-chemistry over nodes (edges to dropped nodes removed).

Reuses the surviving molecule objects; rebuilds each surviving reaction with its reactant/product entries filtered to the kept molecules (rate is carried through by identity). All atoms are retained (they are not graph nodes).

Source code in src/alienbio/bio/chemistry.py
def subgraph(self, nodes: Iterable[str]) -> "ChemistryImpl":
    """The induced sub-chemistry over ``nodes`` (edges to dropped nodes removed).

    Reuses the surviving molecule objects; rebuilds each surviving reaction
    with its reactant/product entries filtered to the kept molecules (rate is
    carried through by identity). All atoms are retained (they are not graph
    nodes).
    """
    node_set = set(nodes)
    kept_species, kept_reactions = graph_ops.subgraph_selection(
        _reaction_graph_view(self), node_set
    )
    name_to_mol = {mol.name: mol for mol in self.molecules.values()}
    name_to_rxn = {rxn.name: rxn for rxn in self.reactions.values()}

    new_molecules: Dict[str, MoleculeImpl] = {
        name: name_to_mol[name] for name in kept_species
    }
    new_reactions: Dict[str, ReactionImpl] = {}
    for rname in kept_reactions:
        rxn = name_to_rxn[rname]
        new_reactions[rname] = ReactionImpl(
            rname,
            reactants={
                m: c for m, c in rxn.reactants.items() if m.name in node_set
            },
            products={
                m: c for m, c in rxn.products.items() if m.name in node_set
            },
            rate=rxn.rate,
            dat=_mock_dat(f"rxn/{rname}"),
        )
    return ChemistryImpl(
        "subgraph",
        atoms=self.atoms.copy(),
        molecules=new_molecules,
        reactions=new_reactions,
        dat=_mock_dat("chem/subgraph"),
    )

match(pattern)

All subgraph embeddings of pattern into this chemistry.

Molecules match on (name, symbol, bdepth, molecular_weight) equality, reactions structurally; injectivity and every pattern edge are enforced. Returns each embedding as {pattern_name: host_name}; [] if none.

Source code in src/alienbio/bio/chemistry.py
def match(self, pattern: "ChemistryImpl") -> List[Dict[str, str]]:
    """All subgraph embeddings of ``pattern`` into this chemistry.

    Molecules match on ``(name, symbol, bdepth, molecular_weight)`` equality,
    reactions structurally; injectivity and every pattern edge are enforced.
    Returns each embedding as ``{pattern_name: host_name}``; ``[]`` if none.
    """
    return graph_ops.match(
        _reaction_graph_view(self), _reaction_graph_view(pattern)
    )

attributes()

Semantic content of this chemistry.

Source code in src/alienbio/bio/chemistry.py
def attributes(self) -> Dict[str, Any]:
    """Semantic content of this chemistry."""
    result = super().attributes()

    # Serialize atoms as {symbol: {name, atomic_weight}}
    if self.atoms:
        result["atoms"] = {
            sym: {"name": atom.name, "atomic_weight": atom.atomic_weight}
            for sym, atom in self.atoms.items()
        }

    # Serialize molecules by name
    if self.molecules:
        result["molecules"] = {
            name: mol.attributes()
            for name, mol in self.molecules.items()
        }

    # Serialize reactions by name
    if self.reactions:
        result["reactions"] = {
            name: rxn.attributes()
            for name, rxn in self.reactions.items()
        }

    return result

__repr__()

Full representation.

Source code in src/alienbio/bio/chemistry.py
def __repr__(self) -> str:
    """Full representation."""
    return (
        f"ChemistryImpl({self._local_name!r}, "
        f"atoms={len(self.atoms)}, "
        f"molecules={len(self.molecules)}, "
        f"reactions={len(self.reactions)})"
    )

CompartmentImpl

Bases: Entity

Implementation: A compartment in the biological hierarchy.

Compartments represent biological regions: organisms, organs, cells, organelles. Each compartment can contain child compartments, forming a tree structure.

The compartment entity specifies: - Structure: kind and child compartments - Initial state: multiplicity and concentrations - Behavior: membrane flows and active reactions

This entity tree serves as both the initial WorldState specification and the complete simulation configuration.

Attributes:

Name Type Description
kind str

Type of compartment ("organism", "organ", "cell", "organelle", etc.)

multiplicity float

Number of instances (default 1.0)

volume float

Volume of each instance in arbitrary units (default 1.0)

concentrations Dict[str, float]

Initial molecule concentrations {molecule_name: value}

membrane_flows List[GeneralFlow]

Flows across this compartment's membrane

active_reactions Optional[List[str]]

Reactions active here (None = all from chemistry)

children List[CompartmentImpl]

Child compartments

Example

Define an organism with cells

organism = CompartmentImpl( "body", volume=70000, # 70 liters in mL kind="organism", concentrations={"glucose": 5.0, "oxygen": 2.0}, )

liver = CompartmentImpl( "liver", volume=1500, # 1.5 liters in mL parent=organism, kind="organ", )

hepatocyte = CompartmentImpl( "hepatocyte", volume=3e-9, # ~3000 cubic microns in mL parent=liver, kind="cell", multiplicity=1e9, # 1 billion liver cells concentrations={"glucose": 1.0}, membrane_flows=[glucose_uptake_flow], active_reactions=["glycolysis", "gluconeogenesis"], )

Source code in src/alienbio/bio/compartment.py
class CompartmentImpl(Entity, head="Compartment"):
    """Implementation: A compartment in the biological hierarchy.

    Compartments represent biological regions: organisms, organs, cells, organelles.
    Each compartment can contain child compartments, forming a tree structure.

    The compartment entity specifies:
    - Structure: kind and child compartments
    - Initial state: multiplicity and concentrations
    - Behavior: membrane flows and active reactions

    This entity tree serves as both the initial WorldState specification and
    the complete simulation configuration.

    Attributes:
        kind: Type of compartment ("organism", "organ", "cell", "organelle", etc.)
        multiplicity: Number of instances (default 1.0)
        volume: Volume of each instance in arbitrary units (default 1.0)
        concentrations: Initial molecule concentrations {molecule_name: value}
        membrane_flows: Flows across this compartment's membrane
        active_reactions: Reactions active here (None = all from chemistry)
        children: Child compartments

    Example:
        # Define an organism with cells
        organism = CompartmentImpl(
            "body",
            volume=70000,  # 70 liters in mL
            kind="organism",
            concentrations={"glucose": 5.0, "oxygen": 2.0},
        )

        liver = CompartmentImpl(
            "liver",
            volume=1500,  # 1.5 liters in mL
            parent=organism,
            kind="organ",
        )

        hepatocyte = CompartmentImpl(
            "hepatocyte",
            volume=3e-9,  # ~3000 cubic microns in mL
            parent=liver,
            kind="cell",
            multiplicity=1e9,  # 1 billion liver cells
            concentrations={"glucose": 1.0},
            membrane_flows=[glucose_uptake_flow],
            active_reactions=["glycolysis", "gluconeogenesis"],
        )
    """

    __slots__ = (
        "_kind",
        "_multiplicity",
        "_volume",
        "_concentrations",
        "_membrane_flows",
        "_active_reactions",
        "_children",
    )

    def __init__(
        self,
        local_name: str,
        *,
        volume: float,
        parent: Optional[Entity] = None,
        dat: Optional[DatLike] = None,
        description: str = "",
        kind: str = "compartment",
        multiplicity: float = 1.0,
        concentrations: Optional[Dict[str, float]] = None,
        membrane_flows: Optional[List[GeneralFlow]] = None,
        active_reactions: Optional[List[str]] = None,
    ) -> None:
        """Initialize a compartment.

        Args:
            local_name: Local name within parent (used as entity identifier)
            volume: Volume of each instance (required - no default, scale depends on use case)
            parent: Parent compartment (or None for root)
            dat: DAT anchor for root compartments
            description: Human-readable description
            kind: Type of compartment ("organism", "organ", "cell", "organelle")
            multiplicity: Number of instances of this compartment (default 1.0)
            concentrations: Initial molecule concentrations {name: value}
            membrane_flows: Flows across this compartment's membrane
            active_reactions: Reaction names active here (None = all from chemistry)
        """
        super().__init__(local_name, parent=parent, dat=dat, description=description)
        self._kind = kind
        self._multiplicity = multiplicity
        self._volume = volume
        self._concentrations: Dict[str, float] = (
            concentrations.copy() if concentrations else {}
        )
        self._membrane_flows: List[GeneralFlow] = (
            list(membrane_flows) if membrane_flows else []
        )
        self._active_reactions: Optional[List[str]] = (
            list(active_reactions) if active_reactions else None
        )
        self._children: List[CompartmentImpl] = []  # type: ignore[assignment]

        # Register with parent
        if parent is not None and isinstance(parent, CompartmentImpl):
            parent._children.append(self)

    @property
    def kind(self) -> str:
        """Type of compartment: 'organism', 'organ', 'cell', 'organelle'."""
        return self._kind

    @property
    def multiplicity(self) -> float:
        """Number of instances of this compartment."""
        return self._multiplicity

    @property
    def volume(self) -> float:
        """Volume of each instance in arbitrary units."""
        return self._volume

    @property
    def concentrations(self) -> Dict[str, float]:
        """Initial molecule concentrations {name: value}."""
        return self._concentrations.copy()

    @property
    def membrane_flows(self) -> List[GeneralFlow]:
        """Flows across this compartment's membrane."""
        return list(self._membrane_flows)

    @property
    def active_reactions(self) -> Optional[List[str]]:
        """Reaction names active in this compartment (None = all)."""
        return list(self._active_reactions) if self._active_reactions else None

    @property
    def children(self) -> List[CompartmentImpl]:  # type: ignore[override]
        """Child compartments."""
        return list(self._children)

    def add_child(self, child: CompartmentImpl) -> None:
        """Add a child compartment."""
        if child not in self._children:
            self._children.append(child)

    def add_flow(self, flow: GeneralFlow) -> None:
        """Add a membrane flow."""
        self._membrane_flows.append(flow)

    def set_concentration(self, molecule: str, value: float) -> None:
        """Set initial concentration for a molecule."""
        self._concentrations[molecule] = value

    def set_multiplicity(self, value: float) -> None:
        """Set the multiplicity (instance count)."""
        self._multiplicity = value

    def set_volume(self, value: float) -> None:
        """Set the volume of each instance."""
        self._volume = value

    def set_active_reactions(self, reactions: Optional[List[str]]) -> None:
        """Set active reactions (None = all from chemistry)."""
        self._active_reactions = list(reactions) if reactions else None

    # ── Tree traversal ────────────────────────────────────────────────────────

    def all_descendants(self) -> List[CompartmentImpl]:
        """Get all descendant compartments (depth-first)."""
        result = []
        stack = list(self._children)
        while stack:
            child = stack.pop()
            result.append(child)
            stack.extend(child._children)
        return result

    def all_compartments(self) -> List[CompartmentImpl]:
        """Get self and all descendants."""
        return [self] + self.all_descendants()

    def depth(self) -> int:
        """Get depth in tree (root = 0)."""
        d = 0
        current = self._parent
        while current is not None and isinstance(current, CompartmentImpl):
            d += 1
            current = current._parent
        return d

    # ── Serialization ─────────────────────────────────────────────────────────

    def attributes(self) -> Dict[str, Any]:
        """Semantic content for serialization."""
        result: Dict[str, Any] = {
            "kind": self._kind,
            "volume": self._volume,  # Always include - required field
        }
        if self._multiplicity != 1.0:
            result["multiplicity"] = self._multiplicity
        if self._concentrations:
            result["concentrations"] = self._concentrations.copy()
        if self._active_reactions is not None:
            result["active_reactions"] = self._active_reactions.copy()
        # Note: membrane_flows and children serialized separately
        return result

    def __repr__(self) -> str:
        """Full representation."""
        return (
            f"CompartmentImpl({self._local_name!r}, kind={self._kind!r}, "
            f"multiplicity={self._multiplicity}, children={len(self._children)})"
        )

    def __str__(self) -> str:
        """Short representation."""
        mult_str = f" x{self._multiplicity:g}" if self._multiplicity != 1.0 else ""
        return f"{self._kind}:{self._local_name}{mult_str}"

kind property

Type of compartment: 'organism', 'organ', 'cell', 'organelle'.

multiplicity property

Number of instances of this compartment.

volume property

Volume of each instance in arbitrary units.

concentrations property

Initial molecule concentrations {name: value}.

membrane_flows property

Flows across this compartment's membrane.

active_reactions property

Reaction names active in this compartment (None = all).

children property

Child compartments.

__init__(local_name, *, volume, parent=None, dat=None, description='', kind='compartment', multiplicity=1.0, concentrations=None, membrane_flows=None, active_reactions=None)

Initialize a compartment.

Parameters:

Name Type Description Default
local_name str

Local name within parent (used as entity identifier)

required
volume float

Volume of each instance (required - no default, scale depends on use case)

required
parent Optional[Entity]

Parent compartment (or None for root)

None
dat Optional[DatLike]

DAT anchor for root compartments

None
description str

Human-readable description

''
kind str

Type of compartment ("organism", "organ", "cell", "organelle")

'compartment'
multiplicity float

Number of instances of this compartment (default 1.0)

1.0
concentrations Optional[Dict[str, float]]

Initial molecule concentrations {name: value}

None
membrane_flows Optional[List[GeneralFlow]]

Flows across this compartment's membrane

None
active_reactions Optional[List[str]]

Reaction names active here (None = all from chemistry)

None
Source code in src/alienbio/bio/compartment.py
def __init__(
    self,
    local_name: str,
    *,
    volume: float,
    parent: Optional[Entity] = None,
    dat: Optional[DatLike] = None,
    description: str = "",
    kind: str = "compartment",
    multiplicity: float = 1.0,
    concentrations: Optional[Dict[str, float]] = None,
    membrane_flows: Optional[List[GeneralFlow]] = None,
    active_reactions: Optional[List[str]] = None,
) -> None:
    """Initialize a compartment.

    Args:
        local_name: Local name within parent (used as entity identifier)
        volume: Volume of each instance (required - no default, scale depends on use case)
        parent: Parent compartment (or None for root)
        dat: DAT anchor for root compartments
        description: Human-readable description
        kind: Type of compartment ("organism", "organ", "cell", "organelle")
        multiplicity: Number of instances of this compartment (default 1.0)
        concentrations: Initial molecule concentrations {name: value}
        membrane_flows: Flows across this compartment's membrane
        active_reactions: Reaction names active here (None = all from chemistry)
    """
    super().__init__(local_name, parent=parent, dat=dat, description=description)
    self._kind = kind
    self._multiplicity = multiplicity
    self._volume = volume
    self._concentrations: Dict[str, float] = (
        concentrations.copy() if concentrations else {}
    )
    self._membrane_flows: List[GeneralFlow] = (
        list(membrane_flows) if membrane_flows else []
    )
    self._active_reactions: Optional[List[str]] = (
        list(active_reactions) if active_reactions else None
    )
    self._children: List[CompartmentImpl] = []  # type: ignore[assignment]

    # Register with parent
    if parent is not None and isinstance(parent, CompartmentImpl):
        parent._children.append(self)

add_child(child)

Add a child compartment.

Source code in src/alienbio/bio/compartment.py
def add_child(self, child: CompartmentImpl) -> None:
    """Add a child compartment."""
    if child not in self._children:
        self._children.append(child)

add_flow(flow)

Add a membrane flow.

Source code in src/alienbio/bio/compartment.py
def add_flow(self, flow: GeneralFlow) -> None:
    """Add a membrane flow."""
    self._membrane_flows.append(flow)

set_concentration(molecule, value)

Set initial concentration for a molecule.

Source code in src/alienbio/bio/compartment.py
def set_concentration(self, molecule: str, value: float) -> None:
    """Set initial concentration for a molecule."""
    self._concentrations[molecule] = value

set_multiplicity(value)

Set the multiplicity (instance count).

Source code in src/alienbio/bio/compartment.py
def set_multiplicity(self, value: float) -> None:
    """Set the multiplicity (instance count)."""
    self._multiplicity = value

set_volume(value)

Set the volume of each instance.

Source code in src/alienbio/bio/compartment.py
def set_volume(self, value: float) -> None:
    """Set the volume of each instance."""
    self._volume = value

set_active_reactions(reactions)

Set active reactions (None = all from chemistry).

Source code in src/alienbio/bio/compartment.py
def set_active_reactions(self, reactions: Optional[List[str]]) -> None:
    """Set active reactions (None = all from chemistry)."""
    self._active_reactions = list(reactions) if reactions else None

all_descendants()

Get all descendant compartments (depth-first).

Source code in src/alienbio/bio/compartment.py
def all_descendants(self) -> List[CompartmentImpl]:
    """Get all descendant compartments (depth-first)."""
    result = []
    stack = list(self._children)
    while stack:
        child = stack.pop()
        result.append(child)
        stack.extend(child._children)
    return result

all_compartments()

Get self and all descendants.

Source code in src/alienbio/bio/compartment.py
def all_compartments(self) -> List[CompartmentImpl]:
    """Get self and all descendants."""
    return [self] + self.all_descendants()

depth()

Get depth in tree (root = 0).

Source code in src/alienbio/bio/compartment.py
def depth(self) -> int:
    """Get depth in tree (root = 0)."""
    d = 0
    current = self._parent
    while current is not None and isinstance(current, CompartmentImpl):
        d += 1
        current = current._parent
    return d

attributes()

Semantic content for serialization.

Source code in src/alienbio/bio/compartment.py
def attributes(self) -> Dict[str, Any]:
    """Semantic content for serialization."""
    result: Dict[str, Any] = {
        "kind": self._kind,
        "volume": self._volume,  # Always include - required field
    }
    if self._multiplicity != 1.0:
        result["multiplicity"] = self._multiplicity
    if self._concentrations:
        result["concentrations"] = self._concentrations.copy()
    if self._active_reactions is not None:
        result["active_reactions"] = self._active_reactions.copy()
    # Note: membrane_flows and children serialized separately
    return result

__repr__()

Full representation.

Source code in src/alienbio/bio/compartment.py
def __repr__(self) -> str:
    """Full representation."""
    return (
        f"CompartmentImpl({self._local_name!r}, kind={self._kind!r}, "
        f"multiplicity={self._multiplicity}, children={len(self._children)})"
    )

__str__()

Short representation.

Source code in src/alienbio/bio/compartment.py
def __str__(self) -> str:
    """Short representation."""
    mult_str = f" x{self._multiplicity:g}" if self._multiplicity != 1.0 else ""
    return f"{self._kind}:{self._local_name}{mult_str}"

CompartmentTreeImpl

Implementation: Hierarchical structure of compartments.

Represents the tree topology of compartments (organism > organ > cell > organelle). Stored separately from concentrations to allow efficient structure updates.

The tree is represented with: - parents: List[Optional[CompartmentId]] - parent[child] = parent_id or None for root - children: Dict[CompartmentId, List[CompartmentId]] - children by parent

Compartments are identified by integer IDs (0, 1, 2, ...).

Example

Create tree: organism with two organs

tree = CompartmentTreeImpl() organism = tree.add_root("organism") # 0 organ_a = tree.add_child(organism, "organ_a") # 1 organ_b = tree.add_child(organism, "organ_b") # 2 cell_1 = tree.add_child(organ_a, "cell_1") # 3

print(tree.parent(cell_1)) # 1 (organ_a) print(tree.children(organism)) # [1, 2]

Source code in src/alienbio/bio/compartment_tree.py
class CompartmentTreeImpl:
    """Implementation: Hierarchical structure of compartments.

    Represents the tree topology of compartments (organism > organ > cell > organelle).
    Stored separately from concentrations to allow efficient structure updates.

    The tree is represented with:
    - parents: List[Optional[CompartmentId]] - parent[child] = parent_id or None for root
    - children: Dict[CompartmentId, List[CompartmentId]] - children by parent

    Compartments are identified by integer IDs (0, 1, 2, ...).

    Example:
        # Create tree: organism with two organs
        tree = CompartmentTreeImpl()
        organism = tree.add_root("organism")      # 0
        organ_a = tree.add_child(organism, "organ_a")  # 1
        organ_b = tree.add_child(organism, "organ_b")  # 2
        cell_1 = tree.add_child(organ_a, "cell_1")     # 3

        print(tree.parent(cell_1))    # 1 (organ_a)
        print(tree.children(organism))  # [1, 2]
    """

    __slots__ = ("_parents", "_children", "_names", "_root")

    def __init__(self) -> None:
        """Initialize empty compartment tree."""
        self._parents: List[Optional[CompartmentId]] = []
        self._children: Dict[CompartmentId, List[CompartmentId]] = {}
        self._names: List[str] = []
        self._root: Optional[CompartmentId] = None

    @property
    def num_compartments(self) -> int:
        """Total number of compartments."""
        return len(self._parents)

    def parent(self, child: CompartmentId) -> Optional[CompartmentId]:
        """Get parent of a compartment (None for root)."""
        return self._parents[child]

    def children(self, parent: CompartmentId) -> List[CompartmentId]:
        """Get children of a compartment."""
        return self._children.get(parent, [])

    def root(self) -> CompartmentId:
        """Get the root compartment."""
        if self._root is None:
            raise ValueError("Tree has no root")
        return self._root

    def is_root(self, compartment: CompartmentId) -> bool:
        """Check if compartment is the root."""
        return self._parents[compartment] is None

    def name(self, compartment: CompartmentId) -> str:
        """Get the name of a compartment."""
        return self._names[compartment]

    def add_root(self, name: str = "root") -> CompartmentId:
        """Add the root compartment.

        Args:
            name: Human-readable name for the root

        Returns:
            The root compartment ID (always 0)

        Raises:
            ValueError: If root already exists
        """
        if self._root is not None:
            raise ValueError("Root already exists")

        compartment_id = len(self._parents)
        self._parents.append(None)
        self._children[compartment_id] = []
        self._names.append(name)
        self._root = compartment_id
        return compartment_id

    def add_child(
        self, parent: CompartmentId, name: str = ""
    ) -> CompartmentId:
        """Add a child compartment.

        Args:
            parent: Parent compartment ID
            name: Human-readable name for the child

        Returns:
            The new compartment ID
        """
        if parent >= len(self._parents):
            raise ValueError(f"Parent {parent} does not exist")

        compartment_id = len(self._parents)
        if not name:
            name = f"compartment_{compartment_id}"

        self._parents.append(parent)
        self._children[compartment_id] = []
        self._children[parent].append(compartment_id)
        self._names.append(name)
        return compartment_id

    def ancestors(self, compartment: CompartmentId) -> List[CompartmentId]:
        """Get all ancestors from compartment to root (inclusive)."""
        result = []
        current: Optional[CompartmentId] = compartment
        while current is not None:
            result.append(current)
            current = self._parents[current]
        return result

    def descendants(self, compartment: CompartmentId) -> List[CompartmentId]:
        """Get all descendants of a compartment (not including self)."""
        result = []
        stack = list(self._children.get(compartment, []))
        while stack:
            child = stack.pop()
            result.append(child)
            stack.extend(self._children.get(child, []))
        return result

    def depth(self, compartment: CompartmentId) -> int:
        """Get depth of compartment (root = 0)."""
        d = 0
        current = self._parents[compartment]
        while current is not None:
            d += 1
            current = self._parents[current]
        return d

    def to_dict(self) -> Dict:
        """Serialize tree structure."""
        return {
            "parents": self._parents.copy(),
            "names": self._names.copy(),
        }

    @classmethod
    def from_dict(cls, data: Dict) -> CompartmentTreeImpl:
        """Deserialize tree structure."""
        tree = cls()
        tree._parents = data["parents"]
        tree._names = data["names"]

        # Rebuild children dict and find root
        for child, parent in enumerate(tree._parents):
            tree._children[child] = []
            if parent is None:
                tree._root = child

        for child, parent in enumerate(tree._parents):
            if parent is not None:
                tree._children[parent].append(child)

        return tree

    def __repr__(self) -> str:
        """Full representation."""
        return f"CompartmentTreeImpl(compartments={self.num_compartments})"

    def __str__(self) -> str:
        """Tree visualization."""
        if self._root is None:
            return "CompartmentTree(empty)"

        lines = []

        def _format(comp: CompartmentId, indent: str = "") -> None:
            lines.append(f"{indent}{self._names[comp]} ({comp})")
            children = self._children.get(comp, [])
            for i, child in enumerate(children):
                is_last = i == len(children) - 1
                prefix = "└── " if is_last else "├── "
                next_indent = indent + ("    " if is_last else "│   ")
                lines.append(f"{indent}{prefix}{self._names[child]} ({child})")
                for grandchild in self._children.get(child, []):
                    _format(grandchild, next_indent)

        _format(self._root)
        return "\n".join(lines)

num_compartments property

Total number of compartments.

__init__()

Initialize empty compartment tree.

Source code in src/alienbio/bio/compartment_tree.py
def __init__(self) -> None:
    """Initialize empty compartment tree."""
    self._parents: List[Optional[CompartmentId]] = []
    self._children: Dict[CompartmentId, List[CompartmentId]] = {}
    self._names: List[str] = []
    self._root: Optional[CompartmentId] = None

parent(child)

Get parent of a compartment (None for root).

Source code in src/alienbio/bio/compartment_tree.py
def parent(self, child: CompartmentId) -> Optional[CompartmentId]:
    """Get parent of a compartment (None for root)."""
    return self._parents[child]

children(parent)

Get children of a compartment.

Source code in src/alienbio/bio/compartment_tree.py
def children(self, parent: CompartmentId) -> List[CompartmentId]:
    """Get children of a compartment."""
    return self._children.get(parent, [])

root()

Get the root compartment.

Source code in src/alienbio/bio/compartment_tree.py
def root(self) -> CompartmentId:
    """Get the root compartment."""
    if self._root is None:
        raise ValueError("Tree has no root")
    return self._root

is_root(compartment)

Check if compartment is the root.

Source code in src/alienbio/bio/compartment_tree.py
def is_root(self, compartment: CompartmentId) -> bool:
    """Check if compartment is the root."""
    return self._parents[compartment] is None

name(compartment)

Get the name of a compartment.

Source code in src/alienbio/bio/compartment_tree.py
def name(self, compartment: CompartmentId) -> str:
    """Get the name of a compartment."""
    return self._names[compartment]

add_root(name='root')

Add the root compartment.

Parameters:

Name Type Description Default
name str

Human-readable name for the root

'root'

Returns:

Type Description
CompartmentId

The root compartment ID (always 0)

Raises:

Type Description
ValueError

If root already exists

Source code in src/alienbio/bio/compartment_tree.py
def add_root(self, name: str = "root") -> CompartmentId:
    """Add the root compartment.

    Args:
        name: Human-readable name for the root

    Returns:
        The root compartment ID (always 0)

    Raises:
        ValueError: If root already exists
    """
    if self._root is not None:
        raise ValueError("Root already exists")

    compartment_id = len(self._parents)
    self._parents.append(None)
    self._children[compartment_id] = []
    self._names.append(name)
    self._root = compartment_id
    return compartment_id

add_child(parent, name='')

Add a child compartment.

Parameters:

Name Type Description Default
parent CompartmentId

Parent compartment ID

required
name str

Human-readable name for the child

''

Returns:

Type Description
CompartmentId

The new compartment ID

Source code in src/alienbio/bio/compartment_tree.py
def add_child(
    self, parent: CompartmentId, name: str = ""
) -> CompartmentId:
    """Add a child compartment.

    Args:
        parent: Parent compartment ID
        name: Human-readable name for the child

    Returns:
        The new compartment ID
    """
    if parent >= len(self._parents):
        raise ValueError(f"Parent {parent} does not exist")

    compartment_id = len(self._parents)
    if not name:
        name = f"compartment_{compartment_id}"

    self._parents.append(parent)
    self._children[compartment_id] = []
    self._children[parent].append(compartment_id)
    self._names.append(name)
    return compartment_id

ancestors(compartment)

Get all ancestors from compartment to root (inclusive).

Source code in src/alienbio/bio/compartment_tree.py
def ancestors(self, compartment: CompartmentId) -> List[CompartmentId]:
    """Get all ancestors from compartment to root (inclusive)."""
    result = []
    current: Optional[CompartmentId] = compartment
    while current is not None:
        result.append(current)
        current = self._parents[current]
    return result

descendants(compartment)

Get all descendants of a compartment (not including self).

Source code in src/alienbio/bio/compartment_tree.py
def descendants(self, compartment: CompartmentId) -> List[CompartmentId]:
    """Get all descendants of a compartment (not including self)."""
    result = []
    stack = list(self._children.get(compartment, []))
    while stack:
        child = stack.pop()
        result.append(child)
        stack.extend(self._children.get(child, []))
    return result

depth(compartment)

Get depth of compartment (root = 0).

Source code in src/alienbio/bio/compartment_tree.py
def depth(self, compartment: CompartmentId) -> int:
    """Get depth of compartment (root = 0)."""
    d = 0
    current = self._parents[compartment]
    while current is not None:
        d += 1
        current = self._parents[current]
    return d

to_dict()

Serialize tree structure.

Source code in src/alienbio/bio/compartment_tree.py
def to_dict(self) -> Dict:
    """Serialize tree structure."""
    return {
        "parents": self._parents.copy(),
        "names": self._names.copy(),
    }

from_dict(data) classmethod

Deserialize tree structure.

Source code in src/alienbio/bio/compartment_tree.py
@classmethod
def from_dict(cls, data: Dict) -> CompartmentTreeImpl:
    """Deserialize tree structure."""
    tree = cls()
    tree._parents = data["parents"]
    tree._names = data["names"]

    # Rebuild children dict and find root
    for child, parent in enumerate(tree._parents):
        tree._children[child] = []
        if parent is None:
            tree._root = child

    for child, parent in enumerate(tree._parents):
        if parent is not None:
            tree._children[parent].append(child)

    return tree

__repr__()

Full representation.

Source code in src/alienbio/bio/compartment_tree.py
def __repr__(self) -> str:
    """Full representation."""
    return f"CompartmentTreeImpl(compartments={self.num_compartments})"

__str__()

Tree visualization.

Source code in src/alienbio/bio/compartment_tree.py
def __str__(self) -> str:
    """Tree visualization."""
    if self._root is None:
        return "CompartmentTree(empty)"

    lines = []

    def _format(comp: CompartmentId, indent: str = "") -> None:
        lines.append(f"{indent}{self._names[comp]} ({comp})")
        children = self._children.get(comp, [])
        for i, child in enumerate(children):
            is_last = i == len(children) - 1
            prefix = "└── " if is_last else "├── "
            next_indent = indent + ("    " if is_last else "│   ")
            lines.append(f"{indent}{prefix}{self._names[child]} ({child})")
            for grandchild in self._children.get(child, []):
                _format(grandchild, next_indent)

    _format(self._root)
    return "\n".join(lines)

WorldStateImpl

Implementation: Dense concentration storage for all compartments.

Stores concentrations as a flat array indexed by [compartment, molecule]. Also stores multiplicity (instance count) per compartment. Dense storage is efficient for small to medium molecule counts.

Each WorldState holds a reference to its CompartmentTree. Multiple states share the same tree reference (immutable sharing) until topology changes. When topology changes (e.g., cell division), a new tree is created.

Attributes:

Name Type Description
tree CompartmentTreeImpl

The CompartmentTree this state belongs to (shared reference)

num_compartments int

Number of compartments (derived from tree)

num_molecules int

Number of molecules in vocabulary

concentrations int

Flat array [num_compartments * num_molecules]

multiplicities int

Array [num_compartments] - instance count per compartment

The concentration array is row-major: concentrations[comp * num_molecules + mol]

Multiplicity represents how many instances of this compartment exist. For example, "arterial red blood cells" might have multiplicity 1e6. Concentrations are per-instance; total molecules = multiplicity * concentration.

Example

tree = CompartmentTreeImpl() root = tree.add_root("organism") cell = tree.add_child(root, "cell") state = WorldStateImpl(tree=tree, num_molecules=50)

Set concentrations

state.set(compartment=cell, molecule=5, value=1.0) print(state.get(cell, 5)) # 1.0

Set multiplicity (number of cells)

state.set_multiplicity(cell, 1000.0) print(state.get_multiplicity(cell)) # 1000.0

Source code in src/alienbio/bio/world_state.py
class WorldStateImpl:
    """Implementation: Dense concentration storage for all compartments.

    Stores concentrations as a flat array indexed by [compartment, molecule].
    Also stores multiplicity (instance count) per compartment.
    Dense storage is efficient for small to medium molecule counts.

    Each WorldState holds a reference to its CompartmentTree. Multiple states
    share the same tree reference (immutable sharing) until topology changes.
    When topology changes (e.g., cell division), a new tree is created.

    Attributes:
        tree: The CompartmentTree this state belongs to (shared reference)
        num_compartments: Number of compartments (derived from tree)
        num_molecules: Number of molecules in vocabulary
        concentrations: Flat array [num_compartments * num_molecules]
        multiplicities: Array [num_compartments] - instance count per compartment

    The concentration array is row-major: concentrations[comp * num_molecules + mol]

    Multiplicity represents how many instances of this compartment exist.
    For example, "arterial red blood cells" might have multiplicity 1e6.
    Concentrations are per-instance; total molecules = multiplicity * concentration.

    Example:
        tree = CompartmentTreeImpl()
        root = tree.add_root("organism")
        cell = tree.add_child(root, "cell")
        state = WorldStateImpl(tree=tree, num_molecules=50)

        # Set concentrations
        state.set(compartment=cell, molecule=5, value=1.0)
        print(state.get(cell, 5))  # 1.0

        # Set multiplicity (number of cells)
        state.set_multiplicity(cell, 1000.0)
        print(state.get_multiplicity(cell))  # 1000.0
    """

    __slots__ = (
        "_tree",
        "_num_molecules",
        "_concentrations",
        "_multiplicities",
        "_volumes",
        "_compartment_ids",
        "_molecule_ids",
    )

    def __init__(
        self,
        tree: CompartmentTreeImpl,
        num_molecules: int,
        initial_concentrations: Optional[List[float]] = None,
        initial_multiplicities: Optional[List[float]] = None,
        compartment_ids: Optional[List[str]] = None,
        molecule_ids: Optional[List[str]] = None,
        initial_volumes: Optional[List[float]] = None,
    ) -> None:
        """Initialize world state.

        Args:
            tree: CompartmentTree defining the topology (shared reference)
            num_molecules: Number of molecules in vocabulary
            initial_concentrations: Optional flat array of initial concentrations
            initial_multiplicities: Optional array of initial multiplicities per compartment
            compartment_ids: Optional ordered real ids for the compartment axis
                (``compartment_ids[i]`` labels flat-array compartment index ``i``).
                Supplied so a snapshot can surface real ids without fabrication
                (the unified ``WorldState`` "axis = ordered compartment ids").
            molecule_ids: Optional ordered real ids for the molecule axis
                (``molecule_ids[j]`` labels molecule index ``j``). Derived from the
                ``Chemistry`` molecule ordering. When both id lists are provided the
                state is *self-describing*; when ``None`` the state is pure-int (the
                hot-loop simulator path, unchanged).
        """
        self._tree = tree
        self._num_molecules = num_molecules
        self._compartment_ids: Optional[tuple[str, ...]] = (
            tuple(compartment_ids) if compartment_ids is not None else None
        )
        self._molecule_ids: Optional[tuple[str, ...]] = (
            tuple(molecule_ids) if molecule_ids is not None else None
        )

        num_compartments = tree.num_compartments
        size = num_compartments * num_molecules

        # Initialize concentrations
        if initial_concentrations is not None:
            if len(initial_concentrations) != size:
                raise ValueError(
                    f"Initial concentrations size {len(initial_concentrations)} != "
                    f"{num_compartments} * {num_molecules} = {size}"
                )
            self._concentrations = list(initial_concentrations)
        else:
            self._concentrations = [0.0] * size

        # Initialize multiplicities (default 1.0 for each compartment)
        if initial_multiplicities is not None:
            if len(initial_multiplicities) != num_compartments:
                raise ValueError(
                    f"Initial multiplicities size {len(initial_multiplicities)} != "
                    f"num_compartments {num_compartments}"
                )
            self._multiplicities = list(initial_multiplicities)
        else:
            self._multiplicities = [1.0] * num_compartments

        # Initialize volumes (default 1.0 for each compartment). Volume is optional
        # in the modelling sense — a container that carries no meaningful volume keeps
        # the default 1.0, so concentration == amount there and existing worlds (which
        # never set a volume) are numerically unchanged. Amount (count) is the
        # extensive source of truth: amount = multiplicity * volume * concentration.
        if initial_volumes is not None:
            if len(initial_volumes) != num_compartments:
                raise ValueError(
                    f"Initial volumes size {len(initial_volumes)} != "
                    f"num_compartments {num_compartments}"
                )
            self._volumes = list(initial_volumes)
        else:
            self._volumes = [1.0] * num_compartments

    @property
    def tree(self) -> CompartmentTreeImpl:
        """The compartment tree this state belongs to (shared reference)."""
        return self._tree

    @property
    def num_compartments(self) -> int:
        """Number of compartments (from tree)."""
        return self._tree.num_compartments

    @property
    def num_molecules(self) -> int:
        """Number of molecules in vocabulary."""
        return self._num_molecules

    # ── Real-id axes (unified WorldState: "axis = ordered compartment ids") ──

    @property
    def compartment_ids(self) -> Optional[tuple[str, ...]]:
        """Ordered real compartment ids (``[i]`` labels index ``i``), or ``None``.

        ``None`` on a pure-int state (the hot-loop simulator path). Present on
        self-describing snapshots so real ids surface without fabrication.
        """
        return self._compartment_ids

    @property
    def molecule_ids(self) -> Optional[tuple[str, ...]]:
        """Ordered real molecule ids (``[j]`` labels index ``j``), or ``None``."""
        return self._molecule_ids

    def concentration(self, compartment_id: str, molecule_id: str) -> float:
        """Get concentration by REAL ids (the unified ``WorldState.get`` surface).

        Translates ids → int indices via the ordered axes, then reads the flat
        store (int indexing is the ``*Impl`` optimization). Requires a
        self-describing state (both id axes present).

        Raises:
            ValueError: if this state has no id axes (pure-int state).
            KeyError: if either id is absent from its axis.
        """
        if self._compartment_ids is None or self._molecule_ids is None:
            raise ValueError(
                "concentration(id, id) requires a self-describing WorldState "
                "(compartment_ids and molecule_ids); this state is pure-int"
            )
        try:
            ci = self._compartment_ids.index(compartment_id)
        except ValueError:
            raise KeyError(f"unknown compartment id {compartment_id!r}") from None
        try:
            mj = self._molecule_ids.index(molecule_id)
        except ValueError:
            raise KeyError(f"unknown molecule id {molecule_id!r}") from None
        return self._concentrations[self._index(ci, mj)]

    def _index(self, compartment: CompartmentId, molecule: MoleculeId) -> int:
        """Compute flat array index."""
        return compartment * self._num_molecules + molecule

    def get(self, compartment: CompartmentId, molecule: MoleculeId) -> float:
        """Get concentration of molecule in compartment."""
        return self._concentrations[self._index(compartment, molecule)]

    def set(
        self, compartment: CompartmentId, molecule: MoleculeId, value: float
    ) -> None:
        """Set concentration of molecule in compartment."""
        self._concentrations[self._index(compartment, molecule)] = value

    def get_compartment(self, compartment: CompartmentId) -> List[float]:
        """Get all concentrations for a compartment."""
        start = compartment * self._num_molecules
        end = start + self._num_molecules
        return self._concentrations[start:end]

    def set_compartment(
        self, compartment: CompartmentId, values: List[float]
    ) -> None:
        """Set all concentrations for a compartment."""
        if len(values) != self._num_molecules:
            raise ValueError(
                f"Values length {len(values)} != num_molecules {self._num_molecules}"
            )
        start = compartment * self._num_molecules
        for i, v in enumerate(values):
            self._concentrations[start + i] = v

    # ── Multiplicity methods ──────────────────────────────────────────────────

    def get_multiplicity(self, compartment: CompartmentId) -> float:
        """Get multiplicity (instance count) for a compartment."""
        return self._multiplicities[compartment]

    def set_multiplicity(self, compartment: CompartmentId, value: float) -> None:
        """Set multiplicity (instance count) for a compartment."""
        self._multiplicities[compartment] = value

    def get_all_multiplicities(self) -> List[float]:
        """Get multiplicities for all compartments."""
        return self._multiplicities.copy()

    def total_molecules(self, compartment: CompartmentId, molecule: MoleculeId) -> float:
        """Get total molecules = multiplicity * concentration."""
        return self._multiplicities[compartment] * self.get(compartment, molecule)

    # ── Volume + amount (the extensive count basis, F012/M37.1) ───────────────

    def get_volume(self, compartment: CompartmentId) -> float:
        """Get volume for a compartment (default 1.0 when never set)."""
        return self._volumes[compartment]

    def set_volume(self, compartment: CompartmentId, value: float) -> None:
        """Set volume for a compartment."""
        self._volumes[compartment] = value

    def get_all_volumes(self) -> List[float]:
        """Get volumes for all compartments."""
        return self._volumes.copy()

    def amount(self, compartment: CompartmentId, molecule: MoleculeId) -> float:
        """Get the amount (count) = multiplicity * volume * concentration.

        The extensive source of truth for conservation: reactions and flows conserve
        *amount*, not concentration. With the default volume 1.0 this equals
        ``total_molecules`` and existing worlds are unchanged.
        """
        return (
            self._multiplicities[compartment]
            * self._volumes[compartment]
            * self.get(compartment, molecule)
        )

    # ── Copy and array methods ────────────────────────────────────────────────

    def copy(self) -> WorldStateImpl:
        """Create a copy of this state (shares tree reference; propagates id axes)."""
        return WorldStateImpl(
            self._tree,  # Shared reference - tree is immutable
            self._num_molecules,
            initial_concentrations=self._concentrations.copy(),
            initial_multiplicities=self._multiplicities.copy(),
            compartment_ids=(
                list(self._compartment_ids)
                if self._compartment_ids is not None
                else None
            ),
            molecule_ids=(
                list(self._molecule_ids) if self._molecule_ids is not None else None
            ),
            initial_volumes=self._volumes.copy(),
        )

    def as_array(self) -> Any:
        """Get concentrations as 2D numpy array [compartments x molecules].

        Returns a view if numpy is available, otherwise a list of lists.
        """
        try:
            import numpy as np

            arr = np.array(self._concentrations, dtype=np.float64)
            return arr.reshape(self.num_compartments, self._num_molecules)
        except ImportError:
            # Fallback: return list of lists
            return [
                self.get_compartment(c) for c in range(self.num_compartments)
            ]

    def from_array(self, arr: Any) -> None:
        """Set concentrations from 2D array [compartments x molecules]."""
        try:
            import numpy as np

            flat = np.asarray(arr, dtype=np.float64).flatten()
            if len(flat) != len(self._concentrations):
                raise ValueError(
                    f"Array size {len(flat)} != expected {len(self._concentrations)}"
                )
            self._concentrations = flat.tolist()
        except ImportError:
            # Fallback: assume list of lists
            idx = 0
            for row in arr:
                for val in row:
                    self._concentrations[idx] = float(val)
                    idx += 1

    def __repr__(self) -> str:
        """Full representation."""
        return (
            f"WorldStateImpl(compartments={self.num_compartments}, "
            f"molecules={self._num_molecules})"
        )

    def __str__(self) -> str:
        """Short representation with summary stats."""
        total = sum(self._concentrations)
        nonzero = sum(1 for c in self._concentrations if c > 0)
        return (
            f"WorldState({self.num_compartments}x{self._num_molecules}, "
            f"total={total:.3g}, nonzero={nonzero})"
        )

tree property

The compartment tree this state belongs to (shared reference).

num_compartments property

Number of compartments (from tree).

num_molecules property

Number of molecules in vocabulary.

compartment_ids property

Ordered real compartment ids ([i] labels index i), or None.

None on a pure-int state (the hot-loop simulator path). Present on self-describing snapshots so real ids surface without fabrication.

molecule_ids property

Ordered real molecule ids ([j] labels index j), or None.

__init__(tree, num_molecules, initial_concentrations=None, initial_multiplicities=None, compartment_ids=None, molecule_ids=None, initial_volumes=None)

Initialize world state.

Parameters:

Name Type Description Default
tree CompartmentTreeImpl

CompartmentTree defining the topology (shared reference)

required
num_molecules int

Number of molecules in vocabulary

required
initial_concentrations Optional[List[float]]

Optional flat array of initial concentrations

None
initial_multiplicities Optional[List[float]]

Optional array of initial multiplicities per compartment

None
compartment_ids Optional[List[str]]

Optional ordered real ids for the compartment axis (compartment_ids[i] labels flat-array compartment index i). Supplied so a snapshot can surface real ids without fabrication (the unified WorldState "axis = ordered compartment ids").

None
molecule_ids Optional[List[str]]

Optional ordered real ids for the molecule axis (molecule_ids[j] labels molecule index j). Derived from the Chemistry molecule ordering. When both id lists are provided the state is self-describing; when None the state is pure-int (the hot-loop simulator path, unchanged).

None
Source code in src/alienbio/bio/world_state.py
def __init__(
    self,
    tree: CompartmentTreeImpl,
    num_molecules: int,
    initial_concentrations: Optional[List[float]] = None,
    initial_multiplicities: Optional[List[float]] = None,
    compartment_ids: Optional[List[str]] = None,
    molecule_ids: Optional[List[str]] = None,
    initial_volumes: Optional[List[float]] = None,
) -> None:
    """Initialize world state.

    Args:
        tree: CompartmentTree defining the topology (shared reference)
        num_molecules: Number of molecules in vocabulary
        initial_concentrations: Optional flat array of initial concentrations
        initial_multiplicities: Optional array of initial multiplicities per compartment
        compartment_ids: Optional ordered real ids for the compartment axis
            (``compartment_ids[i]`` labels flat-array compartment index ``i``).
            Supplied so a snapshot can surface real ids without fabrication
            (the unified ``WorldState`` "axis = ordered compartment ids").
        molecule_ids: Optional ordered real ids for the molecule axis
            (``molecule_ids[j]`` labels molecule index ``j``). Derived from the
            ``Chemistry`` molecule ordering. When both id lists are provided the
            state is *self-describing*; when ``None`` the state is pure-int (the
            hot-loop simulator path, unchanged).
    """
    self._tree = tree
    self._num_molecules = num_molecules
    self._compartment_ids: Optional[tuple[str, ...]] = (
        tuple(compartment_ids) if compartment_ids is not None else None
    )
    self._molecule_ids: Optional[tuple[str, ...]] = (
        tuple(molecule_ids) if molecule_ids is not None else None
    )

    num_compartments = tree.num_compartments
    size = num_compartments * num_molecules

    # Initialize concentrations
    if initial_concentrations is not None:
        if len(initial_concentrations) != size:
            raise ValueError(
                f"Initial concentrations size {len(initial_concentrations)} != "
                f"{num_compartments} * {num_molecules} = {size}"
            )
        self._concentrations = list(initial_concentrations)
    else:
        self._concentrations = [0.0] * size

    # Initialize multiplicities (default 1.0 for each compartment)
    if initial_multiplicities is not None:
        if len(initial_multiplicities) != num_compartments:
            raise ValueError(
                f"Initial multiplicities size {len(initial_multiplicities)} != "
                f"num_compartments {num_compartments}"
            )
        self._multiplicities = list(initial_multiplicities)
    else:
        self._multiplicities = [1.0] * num_compartments

    # Initialize volumes (default 1.0 for each compartment). Volume is optional
    # in the modelling sense — a container that carries no meaningful volume keeps
    # the default 1.0, so concentration == amount there and existing worlds (which
    # never set a volume) are numerically unchanged. Amount (count) is the
    # extensive source of truth: amount = multiplicity * volume * concentration.
    if initial_volumes is not None:
        if len(initial_volumes) != num_compartments:
            raise ValueError(
                f"Initial volumes size {len(initial_volumes)} != "
                f"num_compartments {num_compartments}"
            )
        self._volumes = list(initial_volumes)
    else:
        self._volumes = [1.0] * num_compartments

concentration(compartment_id, molecule_id)

Get concentration by REAL ids (the unified WorldState.get surface).

Translates ids → int indices via the ordered axes, then reads the flat store (int indexing is the *Impl optimization). Requires a self-describing state (both id axes present).

Raises:

Type Description
ValueError

if this state has no id axes (pure-int state).

KeyError

if either id is absent from its axis.

Source code in src/alienbio/bio/world_state.py
def concentration(self, compartment_id: str, molecule_id: str) -> float:
    """Get concentration by REAL ids (the unified ``WorldState.get`` surface).

    Translates ids → int indices via the ordered axes, then reads the flat
    store (int indexing is the ``*Impl`` optimization). Requires a
    self-describing state (both id axes present).

    Raises:
        ValueError: if this state has no id axes (pure-int state).
        KeyError: if either id is absent from its axis.
    """
    if self._compartment_ids is None or self._molecule_ids is None:
        raise ValueError(
            "concentration(id, id) requires a self-describing WorldState "
            "(compartment_ids and molecule_ids); this state is pure-int"
        )
    try:
        ci = self._compartment_ids.index(compartment_id)
    except ValueError:
        raise KeyError(f"unknown compartment id {compartment_id!r}") from None
    try:
        mj = self._molecule_ids.index(molecule_id)
    except ValueError:
        raise KeyError(f"unknown molecule id {molecule_id!r}") from None
    return self._concentrations[self._index(ci, mj)]

get(compartment, molecule)

Get concentration of molecule in compartment.

Source code in src/alienbio/bio/world_state.py
def get(self, compartment: CompartmentId, molecule: MoleculeId) -> float:
    """Get concentration of molecule in compartment."""
    return self._concentrations[self._index(compartment, molecule)]

set(compartment, molecule, value)

Set concentration of molecule in compartment.

Source code in src/alienbio/bio/world_state.py
def set(
    self, compartment: CompartmentId, molecule: MoleculeId, value: float
) -> None:
    """Set concentration of molecule in compartment."""
    self._concentrations[self._index(compartment, molecule)] = value

get_compartment(compartment)

Get all concentrations for a compartment.

Source code in src/alienbio/bio/world_state.py
def get_compartment(self, compartment: CompartmentId) -> List[float]:
    """Get all concentrations for a compartment."""
    start = compartment * self._num_molecules
    end = start + self._num_molecules
    return self._concentrations[start:end]

set_compartment(compartment, values)

Set all concentrations for a compartment.

Source code in src/alienbio/bio/world_state.py
def set_compartment(
    self, compartment: CompartmentId, values: List[float]
) -> None:
    """Set all concentrations for a compartment."""
    if len(values) != self._num_molecules:
        raise ValueError(
            f"Values length {len(values)} != num_molecules {self._num_molecules}"
        )
    start = compartment * self._num_molecules
    for i, v in enumerate(values):
        self._concentrations[start + i] = v

get_multiplicity(compartment)

Get multiplicity (instance count) for a compartment.

Source code in src/alienbio/bio/world_state.py
def get_multiplicity(self, compartment: CompartmentId) -> float:
    """Get multiplicity (instance count) for a compartment."""
    return self._multiplicities[compartment]

set_multiplicity(compartment, value)

Set multiplicity (instance count) for a compartment.

Source code in src/alienbio/bio/world_state.py
def set_multiplicity(self, compartment: CompartmentId, value: float) -> None:
    """Set multiplicity (instance count) for a compartment."""
    self._multiplicities[compartment] = value

get_all_multiplicities()

Get multiplicities for all compartments.

Source code in src/alienbio/bio/world_state.py
def get_all_multiplicities(self) -> List[float]:
    """Get multiplicities for all compartments."""
    return self._multiplicities.copy()

total_molecules(compartment, molecule)

Get total molecules = multiplicity * concentration.

Source code in src/alienbio/bio/world_state.py
def total_molecules(self, compartment: CompartmentId, molecule: MoleculeId) -> float:
    """Get total molecules = multiplicity * concentration."""
    return self._multiplicities[compartment] * self.get(compartment, molecule)

get_volume(compartment)

Get volume for a compartment (default 1.0 when never set).

Source code in src/alienbio/bio/world_state.py
def get_volume(self, compartment: CompartmentId) -> float:
    """Get volume for a compartment (default 1.0 when never set)."""
    return self._volumes[compartment]

set_volume(compartment, value)

Set volume for a compartment.

Source code in src/alienbio/bio/world_state.py
def set_volume(self, compartment: CompartmentId, value: float) -> None:
    """Set volume for a compartment."""
    self._volumes[compartment] = value

get_all_volumes()

Get volumes for all compartments.

Source code in src/alienbio/bio/world_state.py
def get_all_volumes(self) -> List[float]:
    """Get volumes for all compartments."""
    return self._volumes.copy()

amount(compartment, molecule)

Get the amount (count) = multiplicity * volume * concentration.

The extensive source of truth for conservation: reactions and flows conserve amount, not concentration. With the default volume 1.0 this equals total_molecules and existing worlds are unchanged.

Source code in src/alienbio/bio/world_state.py
def amount(self, compartment: CompartmentId, molecule: MoleculeId) -> float:
    """Get the amount (count) = multiplicity * volume * concentration.

    The extensive source of truth for conservation: reactions and flows conserve
    *amount*, not concentration. With the default volume 1.0 this equals
    ``total_molecules`` and existing worlds are unchanged.
    """
    return (
        self._multiplicities[compartment]
        * self._volumes[compartment]
        * self.get(compartment, molecule)
    )

copy()

Create a copy of this state (shares tree reference; propagates id axes).

Source code in src/alienbio/bio/world_state.py
def copy(self) -> WorldStateImpl:
    """Create a copy of this state (shares tree reference; propagates id axes)."""
    return WorldStateImpl(
        self._tree,  # Shared reference - tree is immutable
        self._num_molecules,
        initial_concentrations=self._concentrations.copy(),
        initial_multiplicities=self._multiplicities.copy(),
        compartment_ids=(
            list(self._compartment_ids)
            if self._compartment_ids is not None
            else None
        ),
        molecule_ids=(
            list(self._molecule_ids) if self._molecule_ids is not None else None
        ),
        initial_volumes=self._volumes.copy(),
    )

as_array()

Get concentrations as 2D numpy array [compartments x molecules].

Returns a view if numpy is available, otherwise a list of lists.

Source code in src/alienbio/bio/world_state.py
def as_array(self) -> Any:
    """Get concentrations as 2D numpy array [compartments x molecules].

    Returns a view if numpy is available, otherwise a list of lists.
    """
    try:
        import numpy as np

        arr = np.array(self._concentrations, dtype=np.float64)
        return arr.reshape(self.num_compartments, self._num_molecules)
    except ImportError:
        # Fallback: return list of lists
        return [
            self.get_compartment(c) for c in range(self.num_compartments)
        ]

from_array(arr)

Set concentrations from 2D array [compartments x molecules].

Source code in src/alienbio/bio/world_state.py
def from_array(self, arr: Any) -> None:
    """Set concentrations from 2D array [compartments x molecules]."""
    try:
        import numpy as np

        flat = np.asarray(arr, dtype=np.float64).flatten()
        if len(flat) != len(self._concentrations):
            raise ValueError(
                f"Array size {len(flat)} != expected {len(self._concentrations)}"
            )
        self._concentrations = flat.tolist()
    except ImportError:
        # Fallback: assume list of lists
        idx = 0
        for row in arr:
            for val in row:
                self._concentrations[idx] = float(val)
                idx += 1

__repr__()

Full representation.

Source code in src/alienbio/bio/world_state.py
def __repr__(self) -> str:
    """Full representation."""
    return (
        f"WorldStateImpl(compartments={self.num_compartments}, "
        f"molecules={self._num_molecules})"
    )

__str__()

Short representation with summary stats.

Source code in src/alienbio/bio/world_state.py
def __str__(self) -> str:
    """Short representation with summary stats."""
    total = sum(self._concentrations)
    nonzero = sum(1 for c in self._concentrations if c > 0)
    return (
        f"WorldState({self.num_compartments}x{self._num_molecules}, "
        f"total={total:.3g}, nonzero={nonzero})"
    )

WorldSimulatorImpl

Implementation: Multi-compartment simulator with reactions and flows.

Simulates a world with: - Multiple compartments organized in a tree (organism > organ > cell) - Reactions that occur within compartments - Flows that transport molecules across compartment membranes

Each step: 1. Compute all reaction rates (per compartment) 2. Compute all flow fluxes (between parent-child pairs) 3. Apply reactions (modify concentrations within compartments) 4. Apply flows (transfer molecules across membranes)

Example

Build world

tree = CompartmentTreeImpl() organism = tree.add_root("organism") cell = tree.add_child(organism, "cell")

Define reactions and flows

reactions = [ReactionSpec("r1", {0: 1}, {1: 1}, rate_constant=0.1)] flows = [GeneralFlow(child=cell, molecule=0, rate_constant=0.05)]

Create simulator

sim = WorldSimulatorImpl( tree=tree, reactions=reactions, flows=flows, num_molecules=10, dt=0.1, )

Run simulation

state = WorldStateImpl(tree=tree, num_molecules=10) state.set(organism, 0, 100.0) # initial concentration history = sim.run(state, steps=1000, sample_every=100)

All states in history share the same tree reference

assert history[0].tree is history[-1].tree

Source code in src/alienbio/bio/world_simulator.py
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
class WorldSimulatorImpl:
    """Implementation: Multi-compartment simulator with reactions and flows.

    Simulates a world with:
    - Multiple compartments organized in a tree (organism > organ > cell)
    - Reactions that occur within compartments
    - Flows that transport molecules across compartment membranes

    Each step:
    1. Compute all reaction rates (per compartment)
    2. Compute all flow fluxes (between parent-child pairs)
    3. Apply reactions (modify concentrations within compartments)
    4. Apply flows (transfer molecules across membranes)

    Example:
        # Build world
        tree = CompartmentTreeImpl()
        organism = tree.add_root("organism")
        cell = tree.add_child(organism, "cell")

        # Define reactions and flows
        reactions = [ReactionSpec("r1", {0: 1}, {1: 1}, rate_constant=0.1)]
        flows = [GeneralFlow(child=cell, molecule=0, rate_constant=0.05)]

        # Create simulator
        sim = WorldSimulatorImpl(
            tree=tree,
            reactions=reactions,
            flows=flows,
            num_molecules=10,
            dt=0.1,
        )

        # Run simulation
        state = WorldStateImpl(tree=tree, num_molecules=10)
        state.set(organism, 0, 100.0)  # initial concentration
        history = sim.run(state, steps=1000, sample_every=100)

        # All states in history share the same tree reference
        assert history[0].tree is history[-1].tree
    """

    __slots__ = ("_tree", "_reactions", "_flows", "_population_laws", "_num_molecules", "_dt")

    def __init__(
        self,
        tree: CompartmentTreeImpl,
        reactions: List[ReactionSpec],
        flows: Sequence[Flow],
        num_molecules: int,
        dt: float = 1.0,
        population_laws: Optional[Sequence[PopulationLaw]] = None,
    ) -> None:
        """Initialize world simulator.

        Args:
            tree: Compartment topology
            reactions: List of reaction specifications
            flows: List of flow specifications
            num_molecules: Number of molecules in vocabulary
            dt: Time step size
            population_laws: Optional list of count-based rate-law records driving
                the multiplicity axis (F017); empty (the default) is the fast path —
                ``step`` skips the population pass entirely, so an existing world is
                byte-identical.
        """
        self._tree = tree
        self._reactions = reactions
        self._flows = flows
        self._population_laws = population_laws or []
        self._num_molecules = num_molecules
        self._dt = dt

    @property
    def tree(self) -> CompartmentTreeImpl:
        """Compartment topology."""
        return self._tree

    @property
    def reactions(self) -> List[ReactionSpec]:
        """Reaction specifications."""
        return self._reactions

    @property
    def flows(self) -> Sequence[Flow]:
        """Flow specifications."""
        return self._flows

    @property
    def population_laws(self) -> Sequence[PopulationLaw]:
        """Count-based rate-law records driving the multiplicity axis (F017)."""
        return self._population_laws

    @property
    def num_molecules(self) -> int:
        """Number of molecules in vocabulary."""
        return self._num_molecules

    @property
    def dt(self) -> float:
        """Time step size."""
        return self._dt

    def step(self, state: WorldStateImpl) -> WorldStateImpl:
        """Advance simulation by one time step.

        Args:
            state: Current world state

        Returns:
            New state after applying reactions and flows
        """
        # A subnormal concentration (below ~2.2e-308) is zero. XLA runs with
        # denormals-are-zero, so the JAX core already reads it as 0; the
        # reference must too, or a non-Lipschitz law (sqrt(x) at x -> 0) takes
        # off from a 1e-309 seed on one backend and never on the other (found
        # by the conformance property under `bio report`, 2026-08-30).
        state = _flush_subnormals(state)
        new_state = state.copy()

        # Apply reactions per compartment with order-independent simultaneous
        # extent (H4). Reactions never cross compartments, so grouping by
        # compartment is equivalent to the old global loop but lets us resolve
        # competition among all reactions active in a compartment at once.
        for comp in range(self._tree.num_compartments):
            active = [
                reaction
                for reaction in self._reactions
                if reaction.compartments is None or comp in reaction.compartments
            ]
            if active:
                self._apply_reactions(new_state, state, active, comp)

        # Apply flows between compartments — together, off the frozen state,
        # rationing the summed demand on each pool (T053; see apply_flows).
        if self._flows:
            apply_flows(self._flows, new_state, state, self._tree, self._dt)

        # Apply the population pass (F017 — the FIRST multiplicity-update path).
        # Empty is the fast path: no allocation, byte-identical to every world
        # built before this field existed.
        if self._population_laws:
            self._apply_population_laws(new_state, state)

        return new_state

    def _apply_population_laws(
        self,
        new_state: WorldStateImpl,
        frozen: WorldStateImpl,
    ) -> None:
        """Apply the population pass — one summed-demand ration shared with the
        JAX path (:func:`alienbio.bio.population.apply_population_laws`)."""
        apply_population_laws(self._population_laws, new_state, frozen, self._dt)

    def _desired_extent(
        self,
        frozen: WorldStateImpl,
        reaction: ReactionSpec,
        compartment: CompartmentId,
    ) -> float:
        """Pre-rationing reaction extent for one step — the rate-law → extent seam (F012 Q3).

        The sole rate law today is constant mass-action:
        ``rate_constant * Π conc**stoich * dt`` from the frozen start-of-step state, floored
        at 0. A future count-based (per-capita / zeroth-order, for volumeless containers) or
        catalytic law adds a branch here keyed on the reaction's rate-law kind; the
        competition / proportional-rationing machinery in ``_apply_reactions`` is unaffected,
        because it consumes only the returned extent.

        Bidirectional modulation (F015 S2) multiplies in a second, independent factor: a
        reaction with no modulators (the common case) hits the ``modulators`` emptiness
        check and skips straight past it — no added allocation, no changed result.
        """
        if reaction.rate_law is not None:
            # M47.10 — the compiled expression: the whole rate when it names a
            # reactant, else the factor multiplying mass action.
            rate = eval_rate(reaction.rate_law, lambda mol_id: frozen.get(compartment, mol_id))
            if reaction.implicit_mass_action:
                for mol_id, stoich in reaction.reactants.items():
                    rate *= frozen.get(compartment, mol_id) ** stoich
        else:
            rate = reaction.rate_constant
            for mol_id, stoich in reaction.reactants.items():
                rate *= frozen.get(compartment, mol_id) ** stoich
        if reaction.modulators:
            rate *= self._modulation_factor(frozen, reaction.modulators, compartment)
        rate *= self._dt
        # A rate law can overflow (a quotient over a subnormal) or be undefined;
        # an infinite desire is "everything available" — the rationing bounds
        # it once it is finite — and NaN is no reaction (M48.6).
        if rate != rate:
            return 0.0
        return min(max(0.0, rate), RATE_CAP)

    @staticmethod
    def _modulation_factor(
        frozen: WorldStateImpl,
        modulators: Dict[MoleculeId, Modulation],
        compartment: CompartmentId,
    ) -> float:
        """Dimensionless rate-modulation factor from non-consumed modifier species.

        LINEAR form (F015 Q1): each ``"activator"`` (param ``a``) multiplies the
        numerator by ``(1 + a * [modifier])``; each ``"inhibitor"`` (param ``Ki``)
        multiplies the denominator by ``(1 + [modifier] / Ki)`` — one modifier of each kind
        reduces to ``(1 + a*[A]) / (1 + [I]/Ki)``.

        SATURABLE forms (M38.3), each an independent multiplicative term keyed off the
        modifier's own concentration (same convention as the linear kinds above):
        ``"michaelis"`` contributes ``Vmax * [modifier] / (K + [modifier])`` (hyperbolic
        saturation, the pattern-block seam for ``EnzymeBlock``); ``"hill"`` contributes
        ``Vmax * [modifier]**n / (K**n + [modifier]**n)`` (cooperative/sigmoidal, the seam
        for ``CooperativeBindingBlock``). A zero denominator (``K == 0`` and ``[modifier]
        == 0``) contributes ``0.0`` rather than raising.

        Any other kind (including the label-only default) is inert. Pure function of the
        FROZEN start-of-step state (F015 Q4): deterministic, order-independent (H4).
        """
        numerator = 1.0
        denominator = 1.0
        saturable = 1.0
        for mol_id, modulation in modulators.items():
            conc = frozen.get(compartment, mol_id)
            if modulation.kind == "activator" and modulation.a is not None:
                numerator *= 1.0 + modulation.a * conc
            elif modulation.kind == "inhibitor" and modulation.Ki is not None:
                denominator *= 1.0 + conc / modulation.Ki
            elif (
                modulation.kind == "michaelis"
                and modulation.Vmax is not None
                and modulation.K is not None
            ):
                denom = modulation.K + conc
                saturable *= (modulation.Vmax * conc / denom) if denom > 0.0 else 0.0
            elif (
                modulation.kind == "hill"
                and modulation.Vmax is not None
                and modulation.K is not None
                and modulation.n is not None
            ):
                conc_n = conc ** modulation.n
                denom = modulation.K ** modulation.n + conc_n
                saturable *= (modulation.Vmax * conc_n / denom) if denom > 0.0 else 0.0
        return saturable * numerator / denominator

    def _apply_reactions(
        self,
        new_state: WorldStateImpl,
        frozen: WorldStateImpl,
        reactions: List[ReactionSpec],
        compartment: CompartmentId,
    ) -> None:
        """Apply all reactions active in a compartment simultaneously (H4).

        Desired extents are read from the FROZEN start-of-step state; shared
        reactants are rationed by single-pass proportional min-ratio scaling
        (see ReferenceSimulatorImpl for the non-negativity proof); the final
        extents are applied together to ``new_state``. This is order-independent
        and reduces to the C1 clamp when reactions do not compete.
        """
        # 1. Desired extent per reaction from the frozen state (via the rate-law seam).
        desired: List[float] = [
            self._desired_extent(frozen, reaction, compartment) for reaction in reactions
        ]

        # 2. Competition: demand per molecule, then per-molecule feasible ratio.
        demand: Dict[MoleculeId, float] = {}
        for reaction, ext in zip(reactions, desired):
            if ext <= 0.0:
                continue
            for mol_id, stoich in reaction.reactants.items():
                if stoich > 0:
                    demand[mol_id] = demand.get(mol_id, 0.0) + ext * stoich

        ratio: Dict[MoleculeId, float] = {}
        for mol_id, dem in demand.items():
            avail = frozen.get(compartment, mol_id)
            ratio[mol_id] = min(1.0, avail / dem) if dem > 0 else 1.0

        # Each reaction scales by the tightest ratio over its reactants.
        # 3. Apply simultaneously.
        for reaction, ext in zip(reactions, desired):
            scale = 1.0
            for mol_id, stoich in reaction.reactants.items():
                if stoich > 0:
                    scale = min(scale, ratio.get(mol_id, 1.0))
            extent = ext * scale

            for mol_id, stoich in reaction.reactants.items():
                remaining = new_state.get(compartment, mol_id) - extent * stoich
                # Exact arithmetic never goes below zero (the rationing proof);
                # float rounding can leave -1e-17. Snap that to zero (M48.6).
                if -ROUNDING_FLOOR < remaining < 0.0:
                    remaining = 0.0
                new_state.set(compartment, mol_id, remaining)
            for mol_id, stoich in reaction.products.items():
                new_state.set(
                    compartment,
                    mol_id,
                    new_state.get(compartment, mol_id) + extent * stoich,
                )

    def run(
        self,
        state: WorldStateImpl,
        steps: int,
        sample_every: Optional[int] = None,
    ) -> List[WorldStateImpl]:
        """Run simulation for multiple steps.

        Args:
            state: Initial state (not modified)
            steps: Number of steps to run
            sample_every: If set, only keep every Nth state (plus final)

        Returns:
            List of states (timeline)
        """
        if sample_every is None:
            sample_every = 1

        history: List[WorldStateImpl] = []
        current = state.copy()

        for i in range(steps):
            if i % sample_every == 0:
                history.append(current.copy())
            current = self.step(current)

        # Always include final state
        history.append(current.copy())
        return history

    @classmethod
    def from_chemistry(
        cls,
        chemistry: ChemistryImpl,
        tree: CompartmentTreeImpl,
        flows: Optional[Sequence[Flow]] = None,
        dt: float = 1.0,
        population_laws: Optional[Sequence[PopulationLaw]] = None,
    ) -> WorldSimulatorImpl:
        """Create simulator from a Chemistry and compartment tree.

        Args:
            chemistry: Chemistry containing molecules and reactions
            tree: Compartment topology
            flows: Optional list of flows (empty if not provided)
            dt: Time step
            population_laws: Optional list of count-based rate-law records (F017;
                empty if not provided — the no-op fast path)

        Returns:
            Configured WorldSimulatorImpl
        """
        # Build molecule ID mapping
        mol_names = list(chemistry.molecules.keys())
        mol_to_id = {name: i for i, name in enumerate(mol_names)}

        # Convert reactions to specs
        reaction_specs = []
        for rxn_name, reaction in chemistry.reactions.items():
            reactants = {}
            products = {}

            for mol, stoich in reaction.reactants.items():
                mol_id = mol_to_id.get(mol.name)
                if mol_id is None:
                    raise KeyError(
                        f"Reaction {rxn_name!r}: reactant {mol.name!r} not found in "
                        f"chemistry.molecules"
                    )
                reactants[mol_id] = stoich

            for mol, stoich in reaction.products.items():
                mol_id = mol_to_id.get(mol.name)
                if mol_id is None:
                    raise KeyError(
                        f"Reaction {rxn_name!r}: product {mol.name!r} not found in "
                        f"chemistry.molecules"
                    )
                products[mol_id] = stoich

            # Compile modulators (non-consumed modifier species that scale the rate —
            # F015 S2). A bare str role tag coerces to a label-only, rate-inert
            # Modulation (factor 1.0); reactions with no modifiers compile to an empty
            # dict, hitting the fast path in _desired_extent.
            modulators = {}
            for mol, mod_value in reaction.modifiers.items():
                mol_id = mol_to_id.get(mol.name)
                if mol_id is None:
                    raise KeyError(
                        f"Reaction {rxn_name!r}: modifier {mol.name!r} not found in "
                        f"chemistry.molecules"
                    )
                modulators[mol_id] = Modulation.from_value(mod_value)

            rate = reaction.rate
            if not isinstance(rate, (int, float)) or isinstance(rate, bool):
                # A callable rate was the M1 single-compartment simulator's; it
                # used to be DOWNGRADED to mass-action 1.0 with a warning, i.e. a
                # silently wrong world. Refuse instead (T056).
                raise ValueError(
                    f"reaction {reaction.local_name!r}: rate must be a number (got "
                    f"{type(rate).__name__}); a rate law is `rate_law`, compiled by rate_expr"
                )

            rate_law = None
            if reaction.rate_law is not None:
                def _column(name: Any, _rxn: str = rxn_name) -> int:
                    mol_id = mol_to_id.get(str(name))
                    if mol_id is None:
                        raise KeyError(f"Reaction {_rxn!r}: rate law names {name!r}, not in chemistry.molecules")
                    return mol_id

                rate_law = map_species(reaction.rate_law, _column)

            reaction_specs.append(ReactionSpec(
                name=rxn_name,
                reactants=reactants,
                products=products,
                rate_constant=rate,
                compartments=None,  # Apply to all compartments
                modulators=modulators,
                rate_law=rate_law,
            ))

        return cls(
            tree=tree,
            reactions=reaction_specs,
            flows=flows or [],
            num_molecules=len(mol_names),
            dt=dt,
            population_laws=population_laws or [],
        )

    def __repr__(self) -> str:
        """Full representation."""
        return (
            f"WorldSimulatorImpl(compartments={self._tree.num_compartments}, "
            f"molecules={self._num_molecules}, "
            f"reactions={len(self._reactions)}, "
            f"flows={len(self._flows)}, population_laws={len(self._population_laws)}, "
            f"dt={self._dt})"
        )

tree property

Compartment topology.

reactions property

Reaction specifications.

flows property

Flow specifications.

population_laws property

Count-based rate-law records driving the multiplicity axis (F017).

num_molecules property

Number of molecules in vocabulary.

dt property

Time step size.

__init__(tree, reactions, flows, num_molecules, dt=1.0, population_laws=None)

Initialize world simulator.

Parameters:

Name Type Description Default
tree CompartmentTreeImpl

Compartment topology

required
reactions List[ReactionSpec]

List of reaction specifications

required
flows Sequence[Flow]

List of flow specifications

required
num_molecules int

Number of molecules in vocabulary

required
dt float

Time step size

1.0
population_laws Optional[Sequence[PopulationLaw]]

Optional list of count-based rate-law records driving the multiplicity axis (F017); empty (the default) is the fast path — step skips the population pass entirely, so an existing world is byte-identical.

None
Source code in src/alienbio/bio/world_simulator.py
def __init__(
    self,
    tree: CompartmentTreeImpl,
    reactions: List[ReactionSpec],
    flows: Sequence[Flow],
    num_molecules: int,
    dt: float = 1.0,
    population_laws: Optional[Sequence[PopulationLaw]] = None,
) -> None:
    """Initialize world simulator.

    Args:
        tree: Compartment topology
        reactions: List of reaction specifications
        flows: List of flow specifications
        num_molecules: Number of molecules in vocabulary
        dt: Time step size
        population_laws: Optional list of count-based rate-law records driving
            the multiplicity axis (F017); empty (the default) is the fast path —
            ``step`` skips the population pass entirely, so an existing world is
            byte-identical.
    """
    self._tree = tree
    self._reactions = reactions
    self._flows = flows
    self._population_laws = population_laws or []
    self._num_molecules = num_molecules
    self._dt = dt

step(state)

Advance simulation by one time step.

Parameters:

Name Type Description Default
state WorldStateImpl

Current world state

required

Returns:

Type Description
WorldStateImpl

New state after applying reactions and flows

Source code in src/alienbio/bio/world_simulator.py
def step(self, state: WorldStateImpl) -> WorldStateImpl:
    """Advance simulation by one time step.

    Args:
        state: Current world state

    Returns:
        New state after applying reactions and flows
    """
    # A subnormal concentration (below ~2.2e-308) is zero. XLA runs with
    # denormals-are-zero, so the JAX core already reads it as 0; the
    # reference must too, or a non-Lipschitz law (sqrt(x) at x -> 0) takes
    # off from a 1e-309 seed on one backend and never on the other (found
    # by the conformance property under `bio report`, 2026-08-30).
    state = _flush_subnormals(state)
    new_state = state.copy()

    # Apply reactions per compartment with order-independent simultaneous
    # extent (H4). Reactions never cross compartments, so grouping by
    # compartment is equivalent to the old global loop but lets us resolve
    # competition among all reactions active in a compartment at once.
    for comp in range(self._tree.num_compartments):
        active = [
            reaction
            for reaction in self._reactions
            if reaction.compartments is None or comp in reaction.compartments
        ]
        if active:
            self._apply_reactions(new_state, state, active, comp)

    # Apply flows between compartments — together, off the frozen state,
    # rationing the summed demand on each pool (T053; see apply_flows).
    if self._flows:
        apply_flows(self._flows, new_state, state, self._tree, self._dt)

    # Apply the population pass (F017 — the FIRST multiplicity-update path).
    # Empty is the fast path: no allocation, byte-identical to every world
    # built before this field existed.
    if self._population_laws:
        self._apply_population_laws(new_state, state)

    return new_state

run(state, steps, sample_every=None)

Run simulation for multiple steps.

Parameters:

Name Type Description Default
state WorldStateImpl

Initial state (not modified)

required
steps int

Number of steps to run

required
sample_every Optional[int]

If set, only keep every Nth state (plus final)

None

Returns:

Type Description
List[WorldStateImpl]

List of states (timeline)

Source code in src/alienbio/bio/world_simulator.py
def run(
    self,
    state: WorldStateImpl,
    steps: int,
    sample_every: Optional[int] = None,
) -> List[WorldStateImpl]:
    """Run simulation for multiple steps.

    Args:
        state: Initial state (not modified)
        steps: Number of steps to run
        sample_every: If set, only keep every Nth state (plus final)

    Returns:
        List of states (timeline)
    """
    if sample_every is None:
        sample_every = 1

    history: List[WorldStateImpl] = []
    current = state.copy()

    for i in range(steps):
        if i % sample_every == 0:
            history.append(current.copy())
        current = self.step(current)

    # Always include final state
    history.append(current.copy())
    return history

from_chemistry(chemistry, tree, flows=None, dt=1.0, population_laws=None) classmethod

Create simulator from a Chemistry and compartment tree.

Parameters:

Name Type Description Default
chemistry ChemistryImpl

Chemistry containing molecules and reactions

required
tree CompartmentTreeImpl

Compartment topology

required
flows Optional[Sequence[Flow]]

Optional list of flows (empty if not provided)

None
dt float

Time step

1.0
population_laws Optional[Sequence[PopulationLaw]]

Optional list of count-based rate-law records (F017; empty if not provided — the no-op fast path)

None

Returns:

Type Description
WorldSimulatorImpl

Configured WorldSimulatorImpl

Source code in src/alienbio/bio/world_simulator.py
@classmethod
def from_chemistry(
    cls,
    chemistry: ChemistryImpl,
    tree: CompartmentTreeImpl,
    flows: Optional[Sequence[Flow]] = None,
    dt: float = 1.0,
    population_laws: Optional[Sequence[PopulationLaw]] = None,
) -> WorldSimulatorImpl:
    """Create simulator from a Chemistry and compartment tree.

    Args:
        chemistry: Chemistry containing molecules and reactions
        tree: Compartment topology
        flows: Optional list of flows (empty if not provided)
        dt: Time step
        population_laws: Optional list of count-based rate-law records (F017;
            empty if not provided — the no-op fast path)

    Returns:
        Configured WorldSimulatorImpl
    """
    # Build molecule ID mapping
    mol_names = list(chemistry.molecules.keys())
    mol_to_id = {name: i for i, name in enumerate(mol_names)}

    # Convert reactions to specs
    reaction_specs = []
    for rxn_name, reaction in chemistry.reactions.items():
        reactants = {}
        products = {}

        for mol, stoich in reaction.reactants.items():
            mol_id = mol_to_id.get(mol.name)
            if mol_id is None:
                raise KeyError(
                    f"Reaction {rxn_name!r}: reactant {mol.name!r} not found in "
                    f"chemistry.molecules"
                )
            reactants[mol_id] = stoich

        for mol, stoich in reaction.products.items():
            mol_id = mol_to_id.get(mol.name)
            if mol_id is None:
                raise KeyError(
                    f"Reaction {rxn_name!r}: product {mol.name!r} not found in "
                    f"chemistry.molecules"
                )
            products[mol_id] = stoich

        # Compile modulators (non-consumed modifier species that scale the rate —
        # F015 S2). A bare str role tag coerces to a label-only, rate-inert
        # Modulation (factor 1.0); reactions with no modifiers compile to an empty
        # dict, hitting the fast path in _desired_extent.
        modulators = {}
        for mol, mod_value in reaction.modifiers.items():
            mol_id = mol_to_id.get(mol.name)
            if mol_id is None:
                raise KeyError(
                    f"Reaction {rxn_name!r}: modifier {mol.name!r} not found in "
                    f"chemistry.molecules"
                )
            modulators[mol_id] = Modulation.from_value(mod_value)

        rate = reaction.rate
        if not isinstance(rate, (int, float)) or isinstance(rate, bool):
            # A callable rate was the M1 single-compartment simulator's; it
            # used to be DOWNGRADED to mass-action 1.0 with a warning, i.e. a
            # silently wrong world. Refuse instead (T056).
            raise ValueError(
                f"reaction {reaction.local_name!r}: rate must be a number (got "
                f"{type(rate).__name__}); a rate law is `rate_law`, compiled by rate_expr"
            )

        rate_law = None
        if reaction.rate_law is not None:
            def _column(name: Any, _rxn: str = rxn_name) -> int:
                mol_id = mol_to_id.get(str(name))
                if mol_id is None:
                    raise KeyError(f"Reaction {_rxn!r}: rate law names {name!r}, not in chemistry.molecules")
                return mol_id

            rate_law = map_species(reaction.rate_law, _column)

        reaction_specs.append(ReactionSpec(
            name=rxn_name,
            reactants=reactants,
            products=products,
            rate_constant=rate,
            compartments=None,  # Apply to all compartments
            modulators=modulators,
            rate_law=rate_law,
        ))

    return cls(
        tree=tree,
        reactions=reaction_specs,
        flows=flows or [],
        num_molecules=len(mol_names),
        dt=dt,
        population_laws=population_laws or [],
    )

__repr__()

Full representation.

Source code in src/alienbio/bio/world_simulator.py
def __repr__(self) -> str:
    """Full representation."""
    return (
        f"WorldSimulatorImpl(compartments={self._tree.num_compartments}, "
        f"molecules={self._num_molecules}, "
        f"reactions={len(self._reactions)}, "
        f"flows={len(self._flows)}, population_laws={len(self._population_laws)}, "
        f"dt={self._dt})"
    )

ReactionSpec

Specification for a reaction in the world simulator.

Reactions occur within a single compartment and transform molecules. This is a lightweight spec using molecule IDs for efficient simulation.

Attributes:

Name Type Description
name

Human-readable name

reactants

Dict[MoleculeId, stoichiometry]

products

Dict[MoleculeId, stoichiometry]

rate_constant

Base reaction rate

compartments

Which compartments this reaction occurs in (None = all)

modulators

Dict[MoleculeId, Modulation] — non-consumed modifier species that scale the rate (F015 S2); empty for an unmodified reaction (the fast path).

Source code in src/alienbio/bio/world_simulator.py
class ReactionSpec:
    """Specification for a reaction in the world simulator.

    Reactions occur within a single compartment and transform molecules.
    This is a lightweight spec using molecule IDs for efficient simulation.

    Attributes:
        name: Human-readable name
        reactants: Dict[MoleculeId, stoichiometry]
        products: Dict[MoleculeId, stoichiometry]
        rate_constant: Base reaction rate
        compartments: Which compartments this reaction occurs in (None = all)
        modulators: Dict[MoleculeId, Modulation] — non-consumed modifier species that
            scale the rate (F015 S2); empty for an unmodified reaction (the fast path).
    """

    __slots__ = ("name", "reactants", "products", "rate_constant", "compartments", "modulators", "rate_law", "implicit_mass_action")

    def __init__(
        self,
        name: str,
        reactants: Dict[MoleculeId, float],
        products: Dict[MoleculeId, float],
        rate_constant: float = 1.0,
        compartments: Optional[List[CompartmentId]] = None,
        modulators: Optional[Dict[MoleculeId, Modulation]] = None,
        rate_law: Optional[Any] = None,
    ) -> None:
        self.name = name
        self.reactants = reactants
        self.products = products
        self.rate_constant = rate_constant
        self.compartments = compartments  # None means all compartments
        self.modulators = modulators or {}  # empty by default — the no-modifier fast path
        #: M47.10 — a compiled rate expression (``bio.rate_expr`` tree, species by
        #: molecule ID). When it names a reactant it IS the rate; otherwise mass
        #: action over the reactants is implicit and the law multiplies it.
        self.rate_law = rate_law
        self.implicit_mass_action = (
            implicit_mass_action(rate_law, reactants) if rate_law is not None else True
        )

CountFlow

Bases: PopulationLaw

Size-class transition (F017 Q2=A): moves Δmultiplicity origin -> dest at rate_constant · N_origin, floored at 0 and clamped so origin never goes negative. A maturation edge — the same extent leaves origin and enters dest, so headcount is conserved exactly (parallel in shape to :class:~alienbio.bio.flow.TransportFlux, but on the multiplicity axis rather than a molecule pool).

Source code in src/alienbio/bio/population.py
class CountFlow(PopulationLaw):
    """Size-class transition (F017 Q2=A): moves Δmultiplicity ``origin -> dest`` at
    ``rate_constant · N_origin``, floored at 0 and clamped so ``origin`` never goes
    negative. A maturation edge — the same extent leaves ``origin`` and enters
    ``dest``, so headcount is conserved exactly (parallel in shape to
    :class:`~alienbio.bio.flow.TransportFlux`, but on the multiplicity axis rather
    than a molecule pool).
    """

    __slots__ = ("_origin", "_dest", "_rate_constant")

    def __init__(
        self,
        origin: CompartmentId,
        dest: CompartmentId,
        rate_constant: float = 1.0,
        name: str = "",
    ) -> None:
        """Initialize a count flow.

        Args:
            origin: the compartment this flow moves multiplicity OUT OF
            dest: the compartment this flow moves multiplicity INTO
            rate_constant: ``k``
            name: human-readable name
        """
        if not name:
            name = f"count_flow_{origin}_to_{dest}"
        super().__init__(name)
        self._origin = origin
        self._dest = dest
        self._rate_constant = rate_constant

    @property
    def origin(self) -> CompartmentId:
        """The compartment this flow moves multiplicity OUT OF."""
        return self._origin

    @property
    def dest(self) -> CompartmentId:
        """The compartment this flow moves multiplicity INTO."""
        return self._dest

    @property
    def rate_constant(self) -> float:
        """``k``."""
        return self._rate_constant

    def compute_extent(self, frozen: "WorldStateImpl") -> float:
        """Raw (unrationed) Δmultiplicity from the frozen state, floored at 0."""
        n = frozen.get_multiplicity(self._origin)
        return max(0.0, self._rate_constant * n)

    def contribute(
        self,
        frozen: "WorldStateImpl",
        dt: float,
        mult_delta: MultDelta,
        mol_delta: MolDelta,
    ) -> None:
        raw = self.compute_extent(frozen) * dt
        n = frozen.get_multiplicity(self._origin)
        raw = min(raw, n)  # never move more than exist at origin
        if raw <= 0.0:
            return

        mult_delta[self._origin] = mult_delta.get(self._origin, 0.0) - raw
        mult_delta[self._dest] = mult_delta.get(self._dest, 0.0) + raw

    def attributes(self) -> Dict[str, Any]:
        """Semantic content for serialization."""
        return {
            "type": "count_flow",
            "name": self._name,
            "origin": self._origin,
            "dest": self._dest,
            "rate_constant": self._rate_constant,
        }

    def __repr__(self) -> str:
        """Full representation."""
        return (
            f"CountFlow(origin={self._origin}, dest={self._dest}, "
            f"rate_constant={self._rate_constant})"
        )

    def __str__(self) -> str:
        """Short representation."""
        return f"CountFlow({self._name})"

origin property

The compartment this flow moves multiplicity OUT OF.

dest property

The compartment this flow moves multiplicity INTO.

rate_constant property

k.

__init__(origin, dest, rate_constant=1.0, name='')

Initialize a count flow.

Parameters:

Name Type Description Default
origin CompartmentId

the compartment this flow moves multiplicity OUT OF

required
dest CompartmentId

the compartment this flow moves multiplicity INTO

required
rate_constant float

k

1.0
name str

human-readable name

''
Source code in src/alienbio/bio/population.py
def __init__(
    self,
    origin: CompartmentId,
    dest: CompartmentId,
    rate_constant: float = 1.0,
    name: str = "",
) -> None:
    """Initialize a count flow.

    Args:
        origin: the compartment this flow moves multiplicity OUT OF
        dest: the compartment this flow moves multiplicity INTO
        rate_constant: ``k``
        name: human-readable name
    """
    if not name:
        name = f"count_flow_{origin}_to_{dest}"
    super().__init__(name)
    self._origin = origin
    self._dest = dest
    self._rate_constant = rate_constant

compute_extent(frozen)

Raw (unrationed) Δmultiplicity from the frozen state, floored at 0.

Source code in src/alienbio/bio/population.py
def compute_extent(self, frozen: "WorldStateImpl") -> float:
    """Raw (unrationed) Δmultiplicity from the frozen state, floored at 0."""
    n = frozen.get_multiplicity(self._origin)
    return max(0.0, self._rate_constant * n)

attributes()

Semantic content for serialization.

Source code in src/alienbio/bio/population.py
def attributes(self) -> Dict[str, Any]:
    """Semantic content for serialization."""
    return {
        "type": "count_flow",
        "name": self._name,
        "origin": self._origin,
        "dest": self._dest,
        "rate_constant": self._rate_constant,
    }

__repr__()

Full representation.

Source code in src/alienbio/bio/population.py
def __repr__(self) -> str:
    """Full representation."""
    return (
        f"CountFlow(origin={self._origin}, dest={self._dest}, "
        f"rate_constant={self._rate_constant})"
    )

__str__()

Short representation.

Source code in src/alienbio/bio/population.py
def __str__(self) -> str:
    """Short representation."""
    return f"CountFlow({self._name})"

PerCapitaDeath

Bases: PopulationLaw

Per-capita death: extent = rate_constant · N · dt, floored at 0 and clamped so a compartment's multiplicity never goes negative.

Optionally releases biomass back to a named pool (the reverse of :class:PerCapitaGrowth's draw) — release_stoich · extent resource AMOUNT is added to (release_compartment, release_resource) when all three are supplied; absent (the default), death is a pure multiplicity shrink with no release.

Source code in src/alienbio/bio/population.py
class PerCapitaDeath(PopulationLaw):
    """Per-capita death: ``extent = rate_constant · N · dt``, floored at 0 and clamped
    so a compartment's multiplicity never goes negative.

    Optionally releases biomass back to a named pool (the reverse of
    :class:`PerCapitaGrowth`'s draw) — ``release_stoich · extent`` resource AMOUNT is
    added to ``(release_compartment, release_resource)`` when all three are supplied;
    absent (the default), death is a pure multiplicity shrink with no release.
    """

    __slots__ = (
        "_compartment",
        "_rate_constant",
        "_release_compartment",
        "_release_resource",
        "_release_stoich",
    )

    def __init__(
        self,
        compartment: CompartmentId,
        rate_constant: float = 1.0,
        release_compartment: Optional[CompartmentId] = None,
        release_resource: Optional[MoleculeId] = None,
        release_stoich: float = 0.0,
        name: str = "",
    ) -> None:
        """Initialize a per-capita death law.

        Args:
            compartment: the population compartment whose multiplicity shrinks
            rate_constant: ``k``
            release_compartment: optional compartment receiving released biomass
            release_resource: optional molecule id receiving released biomass
            release_stoich: resource AMOUNT released per death, when both
                ``release_compartment``/``release_resource`` are set
            name: human-readable name
        """
        if not name:
            name = f"death_at_{compartment}"
        super().__init__(name)
        self._compartment = compartment
        self._rate_constant = rate_constant
        self._release_compartment = release_compartment
        self._release_resource = release_resource
        self._release_stoich = release_stoich

    @property
    def compartment(self) -> CompartmentId:
        """The population compartment whose multiplicity shrinks."""
        return self._compartment

    @property
    def rate_constant(self) -> float:
        """``k``."""
        return self._rate_constant

    @property
    def release_compartment(self) -> Optional[CompartmentId]:
        """Optional compartment receiving released biomass."""
        return self._release_compartment

    @property
    def release_resource(self) -> Optional[MoleculeId]:
        """Optional molecule id receiving released biomass."""
        return self._release_resource

    @property
    def release_stoich(self) -> float:
        """Resource AMOUNT released per death."""
        return self._release_stoich

    def compute_extent(self, frozen: "WorldStateImpl") -> float:
        """Raw (unrationed) Δmultiplicity magnitude from the frozen state, floored at 0."""
        n = frozen.get_multiplicity(self._compartment)
        return max(0.0, self._rate_constant * n)

    def contribute(
        self,
        frozen: "WorldStateImpl",
        dt: float,
        mult_delta: MultDelta,
        mol_delta: MolDelta,
    ) -> None:
        raw = self.compute_extent(frozen) * dt
        n = frozen.get_multiplicity(self._compartment)
        raw = min(raw, n)  # never kill more than exist
        if raw <= 0.0:
            return

        mult_delta[self._compartment] = mult_delta.get(self._compartment, 0.0) - raw

        if (
            self._release_compartment is not None
            and self._release_resource is not None
            and self._release_stoich > 0.0
        ):
            scale = frozen.get_multiplicity(self._release_compartment) * frozen.get_volume(
                self._release_compartment
            )
            if scale > 0.0:
                key = (self._release_compartment, self._release_resource)
                released = self._release_stoich * raw
                mol_delta[key] = mol_delta.get(key, 0.0) + released / scale

    def attributes(self) -> Dict[str, Any]:
        """Semantic content for serialization."""
        return {
            "type": "per_capita_death",
            "name": self._name,
            "compartment": self._compartment,
            "rate_constant": self._rate_constant,
            "release_compartment": self._release_compartment,
            "release_resource": self._release_resource,
            "release_stoich": self._release_stoich,
        }

    def __repr__(self) -> str:
        """Full representation."""
        return f"PerCapitaDeath(compartment={self._compartment}, rate_constant={self._rate_constant})"

    def __str__(self) -> str:
        """Short representation."""
        return f"PerCapitaDeath({self._name})"

compartment property

The population compartment whose multiplicity shrinks.

rate_constant property

k.

release_compartment property

Optional compartment receiving released biomass.

release_resource property

Optional molecule id receiving released biomass.

release_stoich property

Resource AMOUNT released per death.

__init__(compartment, rate_constant=1.0, release_compartment=None, release_resource=None, release_stoich=0.0, name='')

Initialize a per-capita death law.

Parameters:

Name Type Description Default
compartment CompartmentId

the population compartment whose multiplicity shrinks

required
rate_constant float

k

1.0
release_compartment Optional[CompartmentId]

optional compartment receiving released biomass

None
release_resource Optional[MoleculeId]

optional molecule id receiving released biomass

None
release_stoich float

resource AMOUNT released per death, when both release_compartment/release_resource are set

0.0
name str

human-readable name

''
Source code in src/alienbio/bio/population.py
def __init__(
    self,
    compartment: CompartmentId,
    rate_constant: float = 1.0,
    release_compartment: Optional[CompartmentId] = None,
    release_resource: Optional[MoleculeId] = None,
    release_stoich: float = 0.0,
    name: str = "",
) -> None:
    """Initialize a per-capita death law.

    Args:
        compartment: the population compartment whose multiplicity shrinks
        rate_constant: ``k``
        release_compartment: optional compartment receiving released biomass
        release_resource: optional molecule id receiving released biomass
        release_stoich: resource AMOUNT released per death, when both
            ``release_compartment``/``release_resource`` are set
        name: human-readable name
    """
    if not name:
        name = f"death_at_{compartment}"
    super().__init__(name)
    self._compartment = compartment
    self._rate_constant = rate_constant
    self._release_compartment = release_compartment
    self._release_resource = release_resource
    self._release_stoich = release_stoich

compute_extent(frozen)

Raw (unrationed) Δmultiplicity magnitude from the frozen state, floored at 0.

Source code in src/alienbio/bio/population.py
def compute_extent(self, frozen: "WorldStateImpl") -> float:
    """Raw (unrationed) Δmultiplicity magnitude from the frozen state, floored at 0."""
    n = frozen.get_multiplicity(self._compartment)
    return max(0.0, self._rate_constant * n)

attributes()

Semantic content for serialization.

Source code in src/alienbio/bio/population.py
def attributes(self) -> Dict[str, Any]:
    """Semantic content for serialization."""
    return {
        "type": "per_capita_death",
        "name": self._name,
        "compartment": self._compartment,
        "rate_constant": self._rate_constant,
        "release_compartment": self._release_compartment,
        "release_resource": self._release_resource,
        "release_stoich": self._release_stoich,
    }

__repr__()

Full representation.

Source code in src/alienbio/bio/population.py
def __repr__(self) -> str:
    """Full representation."""
    return f"PerCapitaDeath(compartment={self._compartment}, rate_constant={self._rate_constant})"

__str__()

Short representation.

Source code in src/alienbio/bio/population.py
def __str__(self) -> str:
    """Short representation."""
    return f"PerCapitaDeath({self._name})"

PerCapitaGrowth

Bases: PopulationLaw

Resource-coupled per-capita growth (F017 Q3=A/Q4=A).

extent = rate_constant · N_compartment · [resource]_resource_compartment · dt, floored at 0 (a growth law never itself causes shrinkage — use :class:PerCapitaDeath for that). Bilinear in population size and resource concentration: as the resource pool draws down, growth self-limits toward 0 — logistic boundedness from nutrient limitation, with no separate governor.

The resource draw is rationed exactly like a reaction's reactant (see :class:~alienbio.bio.flow.TransportFlux's amount-rationing precedent): if stoich · extent exceeds the resource pool's available AMOUNT, the extent is scaled down so the pool never goes negative. stoich == 0.0 (the uncoupled config) skips the draw entirely — growth still happens, but nothing funds the new instances' biomass, so the F012 amount-canary (:func:alienbio.bio.conservation. total_quantity) fires: matter created from nothing. This is deliberate — it is the negative half of F017's conservation red-then-green test.

Source code in src/alienbio/bio/population.py
class PerCapitaGrowth(PopulationLaw):
    """Resource-coupled per-capita growth (F017 Q3=A/Q4=A).

    ``extent = rate_constant · N_compartment · [resource]_resource_compartment · dt``,
    floored at 0 (a growth law never itself causes shrinkage — use
    :class:`PerCapitaDeath` for that). Bilinear in population size and resource
    concentration: as the resource pool draws down, growth self-limits toward 0 —
    logistic boundedness from nutrient limitation, with no separate governor.

    The resource draw is rationed exactly like a reaction's reactant (see
    :class:`~alienbio.bio.flow.TransportFlux`'s amount-rationing precedent): if
    ``stoich · extent`` exceeds the resource pool's available AMOUNT, the extent is
    scaled down so the pool never goes negative. ``stoich == 0.0`` (the uncoupled
    config) skips the draw entirely — growth still happens, but nothing funds the new
    instances' biomass, so the F012 amount-canary (:func:`alienbio.bio.conservation.
    total_quantity`) fires: matter created from nothing. This is deliberate — it is
    the negative half of F017's conservation red-then-green test.
    """

    __slots__ = ("_compartment", "_resource_compartment", "_resource", "_stoich", "_rate_constant")

    def __init__(
        self,
        compartment: CompartmentId,
        resource_compartment: CompartmentId,
        resource: MoleculeId,
        stoich: float,
        rate_constant: float = 1.0,
        name: str = "",
    ) -> None:
        """Initialize a resource-coupled per-capita growth law.

        Args:
            compartment: the population compartment whose multiplicity grows
            resource_compartment: the compartment holding the resource pool (may equal
                ``compartment``)
            resource: molecule id of the resource
            stoich: resource AMOUNT consumed per new instance (``ΔN``); ``0.0`` means
                uncoupled (no draw) — deliberately leaky, see class docstring
            rate_constant: ``k``
            name: human-readable name
        """
        if not name:
            name = f"growth_at_{compartment}"
        super().__init__(name)
        self._compartment = compartment
        self._resource_compartment = resource_compartment
        self._resource = resource
        self._stoich = stoich
        self._rate_constant = rate_constant

    @property
    def compartment(self) -> CompartmentId:
        """The population compartment whose multiplicity grows."""
        return self._compartment

    @property
    def resource_compartment(self) -> CompartmentId:
        """The compartment holding the resource pool."""
        return self._resource_compartment

    @property
    def resource(self) -> MoleculeId:
        """Molecule id of the resource."""
        return self._resource

    @property
    def stoich(self) -> float:
        """Resource AMOUNT consumed per new instance."""
        return self._stoich

    @property
    def rate_constant(self) -> float:
        """``k``."""
        return self._rate_constant

    def compute_extent(self, frozen: "WorldStateImpl") -> float:
        """Raw (unrationed) Δmultiplicity from the frozen state, floored at 0."""
        n = frozen.get_multiplicity(self._compartment)
        conc = frozen.get(self._resource_compartment, self._resource)
        return max(0.0, self._rate_constant * n * conc)

    def contribute(
        self,
        frozen: "WorldStateImpl",
        dt: float,
        mult_delta: MultDelta,
        mol_delta: MolDelta,
    ) -> None:
        raw = self.compute_extent(frozen) * dt
        if raw <= 0.0:
            return

        if self._stoich > 0.0:
            needed = self._stoich * raw
            available = frozen.amount(self._resource_compartment, self._resource)
            if needed > available:
                scale = available / needed if needed > 0.0 else 0.0
                raw *= scale
            if raw <= 0.0:
                return

        mult_delta[self._compartment] = mult_delta.get(self._compartment, 0.0) + raw

        if self._stoich > 0.0:
            drawn = self._stoich * raw
            scale = frozen.get_multiplicity(self._resource_compartment) * frozen.get_volume(
                self._resource_compartment
            )
            if scale > 0.0:
                key = (self._resource_compartment, self._resource)
                mol_delta[key] = mol_delta.get(key, 0.0) - drawn / scale

    def attributes(self) -> Dict[str, Any]:
        """Semantic content for serialization."""
        return {
            "type": "per_capita_growth",
            "name": self._name,
            "compartment": self._compartment,
            "resource_compartment": self._resource_compartment,
            "resource": self._resource,
            "stoich": self._stoich,
            "rate_constant": self._rate_constant,
        }

    def __repr__(self) -> str:
        """Full representation."""
        return (
            f"PerCapitaGrowth(compartment={self._compartment}, "
            f"resource_compartment={self._resource_compartment}, resource={self._resource}, "
            f"stoich={self._stoich}, rate_constant={self._rate_constant})"
        )

    def __str__(self) -> str:
        """Short representation."""
        return f"PerCapitaGrowth({self._name})"

compartment property

The population compartment whose multiplicity grows.

resource_compartment property

The compartment holding the resource pool.

resource property

Molecule id of the resource.

stoich property

Resource AMOUNT consumed per new instance.

rate_constant property

k.

__init__(compartment, resource_compartment, resource, stoich, rate_constant=1.0, name='')

Initialize a resource-coupled per-capita growth law.

Parameters:

Name Type Description Default
compartment CompartmentId

the population compartment whose multiplicity grows

required
resource_compartment CompartmentId

the compartment holding the resource pool (may equal compartment)

required
resource MoleculeId

molecule id of the resource

required
stoich float

resource AMOUNT consumed per new instance (ΔN); 0.0 means uncoupled (no draw) — deliberately leaky, see class docstring

required
rate_constant float

k

1.0
name str

human-readable name

''
Source code in src/alienbio/bio/population.py
def __init__(
    self,
    compartment: CompartmentId,
    resource_compartment: CompartmentId,
    resource: MoleculeId,
    stoich: float,
    rate_constant: float = 1.0,
    name: str = "",
) -> None:
    """Initialize a resource-coupled per-capita growth law.

    Args:
        compartment: the population compartment whose multiplicity grows
        resource_compartment: the compartment holding the resource pool (may equal
            ``compartment``)
        resource: molecule id of the resource
        stoich: resource AMOUNT consumed per new instance (``ΔN``); ``0.0`` means
            uncoupled (no draw) — deliberately leaky, see class docstring
        rate_constant: ``k``
        name: human-readable name
    """
    if not name:
        name = f"growth_at_{compartment}"
    super().__init__(name)
    self._compartment = compartment
    self._resource_compartment = resource_compartment
    self._resource = resource
    self._stoich = stoich
    self._rate_constant = rate_constant

compute_extent(frozen)

Raw (unrationed) Δmultiplicity from the frozen state, floored at 0.

Source code in src/alienbio/bio/population.py
def compute_extent(self, frozen: "WorldStateImpl") -> float:
    """Raw (unrationed) Δmultiplicity from the frozen state, floored at 0."""
    n = frozen.get_multiplicity(self._compartment)
    conc = frozen.get(self._resource_compartment, self._resource)
    return max(0.0, self._rate_constant * n * conc)

attributes()

Semantic content for serialization.

Source code in src/alienbio/bio/population.py
def attributes(self) -> Dict[str, Any]:
    """Semantic content for serialization."""
    return {
        "type": "per_capita_growth",
        "name": self._name,
        "compartment": self._compartment,
        "resource_compartment": self._resource_compartment,
        "resource": self._resource,
        "stoich": self._stoich,
        "rate_constant": self._rate_constant,
    }

__repr__()

Full representation.

Source code in src/alienbio/bio/population.py
def __repr__(self) -> str:
    """Full representation."""
    return (
        f"PerCapitaGrowth(compartment={self._compartment}, "
        f"resource_compartment={self._resource_compartment}, resource={self._resource}, "
        f"stoich={self._stoich}, rate_constant={self._rate_constant})"
    )

__str__()

Short representation.

Source code in src/alienbio/bio/population.py
def __str__(self) -> str:
    """Short representation."""
    return f"PerCapitaGrowth({self._name})"

PopulationLaw

Bases: ABC

Abstract base for a typed count-based rate-law record (multiplicity axis).

Subclasses: - PerCapitaGrowth: resource-coupled per-capita growth - PerCapitaDeath: per-capita death, with an optional biomass release - CountFlow: size-class maturation (Δmultiplicity origin -> dest)

Source code in src/alienbio/bio/population.py
class PopulationLaw(ABC):
    """Abstract base for a typed count-based rate-law record (multiplicity axis).

    Subclasses:
    - PerCapitaGrowth: resource-coupled per-capita growth
    - PerCapitaDeath: per-capita death, with an optional biomass release
    - CountFlow: size-class maturation (Δmultiplicity origin -> dest)
    """

    __slots__ = ("_name",)

    def __init__(self, name: str = "") -> None:
        self._name = name

    @property
    def name(self) -> str:
        """Human-readable name."""
        return self._name

    @abstractmethod
    def contribute(
        self,
        frozen: "WorldStateImpl",
        dt: float,
        mult_delta: MultDelta,
        mol_delta: MolDelta,
    ) -> None:
        """Read ``frozen`` (start-of-step state) and ACCUMULATE this law's
        Δmultiplicity / Δconcentration into the shared caller-supplied dicts.

        Never mutates ``frozen``, and never reads the dicts back — every law is a
        pure function of the frozen state, so order among laws does not matter.
        """
        ...

    @abstractmethod
    def attributes(self) -> Dict[str, Any]:
        """Semantic content for serialization."""
        ...

name property

Human-readable name.

contribute(frozen, dt, mult_delta, mol_delta) abstractmethod

Read frozen (start-of-step state) and ACCUMULATE this law's Δmultiplicity / Δconcentration into the shared caller-supplied dicts.

Never mutates frozen, and never reads the dicts back — every law is a pure function of the frozen state, so order among laws does not matter.

Source code in src/alienbio/bio/population.py
@abstractmethod
def contribute(
    self,
    frozen: "WorldStateImpl",
    dt: float,
    mult_delta: MultDelta,
    mol_delta: MolDelta,
) -> None:
    """Read ``frozen`` (start-of-step state) and ACCUMULATE this law's
    Δmultiplicity / Δconcentration into the shared caller-supplied dicts.

    Never mutates ``frozen``, and never reads the dicts back — every law is a
    pure function of the frozen state, so order among laws does not matter.
    """
    ...

attributes() abstractmethod

Semantic content for serialization.

Source code in src/alienbio/bio/population.py
@abstractmethod
def attributes(self) -> Dict[str, Any]:
    """Semantic content for serialization."""
    ...

get_atom(symbol)

Get an atom by its symbol.

Parameters:

Name Type Description Default
symbol str

Chemical symbol (e.g., 'C', 'H', 'Na')

required

Returns:

Type Description
AtomImpl

The AtomImpl for that element

Raises:

Type Description
KeyError

If the symbol is not in COMMON_ATOMS

Source code in src/alienbio/bio/atom.py
def get_atom(symbol: str) -> AtomImpl:
    """Get an atom by its symbol.

    Args:
        symbol: Chemical symbol (e.g., 'C', 'H', 'Na')

    Returns:
        The AtomImpl for that element

    Raises:
        KeyError: If the symbol is not in COMMON_ATOMS
    """
    if symbol not in COMMON_ATOMS:
        raise KeyError(f"Unknown atom symbol: {symbol!r}")
    return COMMON_ATOMS[symbol]