Skip to content

CellDesigner model

pd2af.building.celldesigner.model

Build the AF model from clingo activity / influence atoms.

make_and_add_model is the model pass: it walks the activity atoms in layer order (keptSpeciesKey → promotedSubunitKey) and populates context.model with canonical, content-deduped compartments, templates, species and modulations. When the forms of the same base entity merge (keep_species off) each activity's species is stripped of its PTM decorations (recursively, including subunits) by get_or_make_stripped_species; otherwise the input species is kept, placed in its effective compartment by get_or_make_species_in_compartment.

Which compartment a species goes in is _compartment_for_input_species: its own, its complex's when it is a promoted subunit, or -- with the drop_compartments option -- the input map's default compartment, whatever the species' own. Every compartment then merges into that one, and species and influences that become content-equal collapse through the shared cache.

The stateless get_or_make_* leaf helpers do the actual element construction. Each interns the constructed element through a shared content-keyed cache (register_or_reuse), so two content-equal elements collapse to a single Python identity end-to-end. The canonicity policy is "first-registered wins"; combined with the layer order above, a kept input-map element is always the canonical instance for its content class, never displaced by a freshly stripped one.

:mod:pd2af.core drives this pass through a :class:pd2af.building.context.BuilderContext, then the layout pass.

Functions:

Name Description
collect_ancestor_compartments

compartments plus every compartment they are nested in, transitively.

compartments_outermost_first

compartments sorted by nesting depth, so a container precedes its content.

find_default_compartment

The default compartment of input_model, or None if it has none.

get_or_make_modulation

Build a modulation and intern by content.

get_or_make_species_in_compartment

Canonical species for input_species placed in compartment.

get_or_make_stripped_species

Return a decoration-free canonical species for input_species.

get_or_make_stripped_template

Strip the PTM decorations from input_template and intern by content.

get_parent_complex_compartment

Resolve a subunit's effective compartment.

make_and_add_model

Build context.model from the clingo atoms (pass 1).

collect_ancestor_compartments

collect_ancestor_compartments(compartments: Any) -> Any

compartments plus every compartment they are nested in, transitively.

Source code in src/pd2af/building/celldesigner/model.py
def collect_ancestor_compartments(compartments: typing.Any) -> typing.Any:
    """``compartments`` plus every compartment they are nested in, transitively."""
    expanded = set(compartments)
    frontier = expanded
    while True:
        next_frontier = {
            compartment.outside
            for compartment in frontier
            if compartment.outside is not None and compartment.outside not in expanded
        }
        if not next_frontier:
            break
        expanded |= next_frontier
        frontier = next_frontier
    return expanded

compartments_outermost_first

compartments_outermost_first(compartments: Any) -> Any

compartments sorted by nesting depth, so a container precedes its content.

Source code in src/pd2af/building/celldesigner/model.py
def compartments_outermost_first(compartments: typing.Any) -> typing.Any:
    """``compartments`` sorted by nesting depth, so a container precedes its content."""

    def depth(compartment: typing.Any) -> typing.Any:
        result = 0
        seen = set()
        current = compartment.outside
        while current is not None and id(current) not in seen:
            seen.add(id(current))
            result += 1
            current = current.outside
        return result

    return sorted(compartments, key=depth)

find_default_compartment

find_default_compartment(input_model: Any) -> Any

The default compartment of input_model, or None if it has none.

CellDesigner declares a compartment with id default in every map, and it is the one the writer points a species with no compartment at. It is the compartment every activity goes in under the drop_compartments option, so the output still declares the compartment its species refer to.

Source code in src/pd2af/building/celldesigner/model.py
def find_default_compartment(input_model: typing.Any) -> typing.Any:
    """The default compartment of ``input_model``, or ``None`` if it has none.

    CellDesigner declares a compartment with id ``default`` in every map, and
    it is the one the writer points a species with no compartment at. It is the
    compartment every activity goes in under the ``drop_compartments`` option,
    so the output still declares the compartment its species refer to.
    """
    for compartment in input_model.compartments:
        if compartment.id_ == _DEFAULT_COMPARTMENT_ID:
            return compartment
    return None

get_or_make_modulation

get_or_make_modulation(modulation_class: type, source: Any, target: Any, cache: dict) -> Any

Build a modulation and intern by content.

Source code in src/pd2af/building/celldesigner/model.py
def get_or_make_modulation(
    modulation_class: type, source: typing.Any, target: typing.Any, cache: dict
) -> typing.Any:
    """Build a modulation and intern by content."""
    candidate = modulation_class(source=source, target=target)
    return register_or_reuse(candidate, cache)

get_or_make_species_in_compartment

get_or_make_species_in_compartment(input_species: Any, compartment: Any, cache: dict, input_model_element_to_canonical_model_element: dict) -> Any

Canonical species for input_species placed in compartment.

Used with keep_species on, which keeps each PD species as its own activity, for both activity-key kinds. A species already in compartment — the usual keptSpeciesKey case — is its own canonical instance, so it is registered as is and later content-equal candidates collapse onto it. Otherwise the species is rebuilt in compartment: a promoted subunit carries compartment=None (inherited from the parent complex) and needs its complex's compartment once it stands at top level, and the drop_compartments option puts every species in the default compartment so that species differing only by compartment collapse.

Two input species placed in the same compartment collapse via the cache; a rebuilt species that is content-equal to an already-cached kept one collapses onto that.

Records id(input_species) -> canonical in input_model_element_to_canonical_model_element whenever the canonical differs from the input — Pass 2's layout mapping copies use that dict to substitute stale value references that still point at the input species.

Source code in src/pd2af/building/celldesigner/model.py
def get_or_make_species_in_compartment(
    input_species: typing.Any,
    compartment: typing.Any,
    cache: dict,
    input_model_element_to_canonical_model_element: dict,
) -> typing.Any:
    """Canonical species for ``input_species`` placed in ``compartment``.

    Used with ``keep_species`` on, which keeps each PD species as its own
    activity, for both activity-key kinds. A species already in
    ``compartment`` — the usual ``keptSpeciesKey`` case — is its own canonical
    instance, so it is registered as is and later
    content-equal candidates collapse onto it. Otherwise the species is rebuilt
    in ``compartment``: a promoted subunit carries ``compartment=None``
    (inherited from the parent complex) and needs its complex's compartment
    once it stands at top level, and the ``drop_compartments`` option puts
    every species in the default compartment so that species differing only by
    compartment collapse.

    Two input species placed in the same compartment collapse via the cache; a
    rebuilt species that is content-equal to an already-cached kept one
    collapses onto that.

    Records ``id(input_species) -> canonical`` in
    ``input_model_element_to_canonical_model_element`` whenever the canonical
    differs from the input — Pass 2's layout mapping copies use that dict to
    substitute stale value references that still point at the input species.
    """
    if getattr(input_species, "compartment", None) is compartment:
        return register_or_reuse(input_species, cache)
    candidate = dataclasses.replace(input_species, compartment=compartment)
    canonical = register_or_reuse(candidate, cache)
    if canonical is not input_species:
        input_model_element_to_canonical_model_element[id(input_species)] = canonical
    return canonical

get_or_make_stripped_species

get_or_make_stripped_species(input_species: Any, compartment: Any, cache: dict, input_model_element_to_canonical_model_element: dict) -> Any

Return a decoration-free canonical species for input_species.

Used with keep_species off. Clears every post-translational decoration -- active (-> False), homomultimer (-> 1), modifications, structural_states, and, via a stripped template, modification_residues/regions -- sets the effective compartment, and strips subunits recursively (a frozenset collapses subunits that strip to equal content). Built with dataclasses.replace so the concrete class and the input's real, reader-resolvable id_ are preserved; content-equal stripped twins still intern to one canonical because id_ is compare=False. Records id(input) -> canonical when they differ so Pass 2 can substitute stale references (mirrors get_or_make_species_in_compartment).

Source code in src/pd2af/building/celldesigner/model.py
def get_or_make_stripped_species(
    input_species: typing.Any,
    compartment: typing.Any,
    cache: dict,
    input_model_element_to_canonical_model_element: dict,
) -> typing.Any:
    """Return a decoration-free canonical species for ``input_species``.

    Used with ``keep_species`` off. Clears every post-translational decoration -- ``active`` (-> False), ``homomultimer`` (-> 1),
    ``modifications``, ``structural_states``, and, via a stripped template,
    ``modification_residues``/``regions`` -- sets the effective ``compartment``,
    and strips ``subunits`` recursively (a ``frozenset`` collapses subunits that
    strip to equal content). Built with ``dataclasses.replace`` so the concrete
    class and the input's real, reader-resolvable ``id_`` are preserved;
    content-equal stripped twins still intern to one canonical because ``id_``
    is ``compare=False``. Records ``id(input) -> canonical`` when they differ so
    Pass 2 can substitute stale references (mirrors
    ``get_or_make_species_in_compartment``).
    """
    fields = {}
    if hasattr(input_species, "active"):
        fields["active"] = False
    if hasattr(input_species, "homomultimer"):
        fields["homomultimer"] = 1
    if hasattr(input_species, "modifications"):
        fields["modifications"] = frozenset()
    if hasattr(input_species, "structural_states"):
        fields["structural_states"] = frozenset()
    if hasattr(input_species, "compartment"):
        fields["compartment"] = compartment
    template = getattr(input_species, "template", None)
    if template is not None:
        fields["template"] = get_or_make_stripped_template(template, cache)
    subunits = getattr(input_species, "subunits", None)
    if subunits:
        fields["subunits"] = frozenset(
            get_or_make_stripped_species(
                subunit,
                getattr(subunit, "compartment", None),
                cache,
                input_model_element_to_canonical_model_element,
            )
            for subunit in subunits
        )
    candidate = dataclasses.replace(input_species, **fields)
    canonical = register_or_reuse(candidate, cache)
    if canonical is not input_species:
        input_model_element_to_canonical_model_element[id(input_species)] = canonical
    return canonical

get_or_make_stripped_template

get_or_make_stripped_template(input_template: Any, cache: dict) -> Any

Strip the PTM decorations from input_template and intern by content.

Two distinct input templates that strip to the same content yield a single canonical stripped template.

Kept templates must already be registered in the cache (per pipeline step (3), kept templates are collected before stripped ones are built). When a stripped candidate is content-equal to a kept template, register_or_reuse returns the kept canonical and no duplicate enters the cache.

Source code in src/pd2af/building/celldesigner/model.py
def get_or_make_stripped_template(
    input_template: typing.Any, cache: dict
) -> typing.Any:
    """Strip the PTM decorations from ``input_template`` and intern by content.

    Two distinct input templates that strip to the same content yield a single
    canonical stripped template.

    Kept templates must already be registered in the cache (per pipeline
    step (3), kept templates are collected before stripped ones are
    built). When a stripped candidate is content-equal to a kept
    template, ``register_or_reuse`` returns the kept canonical and no
    duplicate enters the cache.
    """
    fields_to_clear = {}
    if hasattr(input_template, "modification_residues"):
        fields_to_clear["modification_residues"] = frozenset()
    if hasattr(input_template, "regions"):
        fields_to_clear["regions"] = frozenset()
    candidate = dataclasses.replace(
        input_template,
        id_=f"{_STRIPPED_TEMPLATE_PREFIX}{input_template.id_}",
        **fields_to_clear,
    )
    return register_or_reuse(candidate, cache)

get_parent_complex_compartment

get_parent_complex_compartment(subunit: Any, subunit_to_top_level: dict[int, Any]) -> Any

Resolve a subunit's effective compartment.

Walks to its containing top-level species: subunits typically have no compartment attribute of their own.

Source code in src/pd2af/building/celldesigner/model.py
def get_parent_complex_compartment(
    subunit: typing.Any, subunit_to_top_level: dict[int, typing.Any]
) -> typing.Any:
    """Resolve a subunit's effective compartment.

    Walks to its containing top-level species: subunits typically have no
    ``compartment`` attribute of their own.
    """
    if getattr(subunit, "compartment", None) is not None:
        return subunit.compartment
    top_level = subunit_to_top_level.get(id(subunit), subunit)
    return getattr(top_level, "compartment", None)

make_and_add_model

make_and_add_model(context: BuilderContext, clingo_model: Any)

Build context.model from the clingo atoms (pass 1).

Source code in src/pd2af/building/celldesigner/model.py
def make_and_add_model(
    context: pd2af.building.context.BuilderContext, clingo_model: typing.Any
):
    """Build ``context.model`` from the clingo atoms (pass 1)."""
    context.model = momapy.builder.get_or_make_builder_cls(
        momapy.celldesigner.CellDesignerModel
    )()
    context.subunit_to_top_level = pd2af.building.model.build_subunit_to_top_level(
        context.input_map.model.species
    )
    if context.drop_compartments:
        context.default_compartment = find_default_compartment(context.input_map.model)
    pd2af.building.model.collect_atoms(context, clingo_model)
    _make_and_add_compartments(context)
    _make_and_add_templates(context)
    _make_and_add_species(context)
    pd2af.building.model.make_and_add_operators(
        context,
        _OPERATOR_TYPE_TO_GATE_CLASS,
        momapy.celldesigner.BooleanLogicGateInput,
        context.model.boolean_logic_gates,
    )
    _make_and_add_modulations(context)