Skip to content

Modes

pd2af.modes

Everything a user chooses from: the vocabularies, the modes and the options.

A mode decides what counts as an activity; an option decides which activities are treated as the same thing. Deciding what is an activity is the solver's job, so a mode is a rule group list and a choice that only groups the results afterwards is an option: :data:TRANSFORMATION_OPTIONS holds each option's flag, default and description, and a mode names in default_options the value it starts from, which an explicit flag always overrules.

:class:Language, :class:LayoutMode and :class:InfluencePairingMode are the three vocabularies a transformation mode is defined against, so they live here with it. They are string enumerations: a member's value is the token the outside world sees, on the command line, in the --json output and in the calls a library user writes. :data:LANGUAGES carries everything pd2af knows about each input language and the order the CLI and the docs list them in, :data:LAYOUT_MODE_DESCRIPTIONS the prose the CLI lists each layout mode with, and :data:LAYOUT_MODES_BY_LANGUAGE is the join of the first two vocabularies. A language token is also the wire format: it is the aspcompose variant key resolved by :func:pd2af.asp.rules.build_program, a segment of every language-specific rule identifier, and a member of a mode's compatible_languages. The output language is deduced from the input: celldesigner input -> CellDesigner output, sbgn_pd input -> SBGN-AF output.

A :class:TransformationMode carries everything pd2af knows about a mode: its name, the prose the CLI lists it with, the rule groups its ASP program is made of, the input languages it accepts, and the options it starts from.

Modes are contributed from outside the package through the pd2af.modes entry-point group. Each entry point resolves to a :class:TransformationMode object::

# pyproject.toml of the contributing distribution
[project.entry-points."pd2af.modes"]
my-mode = "my_package:MY_MODE"

# my_package/__init__.py
import pd2af

MY_MODE = pd2af.TransformationMode(
    name="my-mode",
    docs="what this mode does, in one clause",
    rule_group_references=("influences:kind", "influences:output"),
    rule_group_definitions=MY_RULE_GROUPS,
)

rule_group_references names groups pd2af already registers; rule_group_definitions holds RuleGroup objects the mode brings with it, which :func:pd2af.asp.rules.build_registry registers alongside the built-in ones. A contributed mode may not take the name of an existing mode, and a mode that fails to load — a bad import, a wrong type, a name collision, a group that fails registry validation, an unknown default_options key — takes down every pd2af entry point rather than just its own: a mode that half-loads is worse than one that refuses to.

Classes:

Name Description
InfluencePairingMode

How to draw an influence whose source or target maps to several glyphs.

Language

An input language pd2af accepts, in the order the CLI lists them.

LayoutMode

A concrete way of laying the output map out, in display order.

TransformationMode

One transformation mode: its metadata and the groups it is made of.

Functions:

Name Description
get_compatible_layout_modes

The concrete layout modes valid for these options on the given language.

get_language_from_map_or_model

Infer the input language token from an input map's or model's type.

get_transformation_mode

Return the :class:TransformationMode called name.

get_transformation_modes

Return {name: TransformationMode}, read-only and ordered.

make_map_from_model

Wrap a bare model in a layout-less map of its language.

InfluencePairingMode

Bases: StrEnum

How to draw an influence whose source or target maps to several glyphs.

Language

Bases: StrEnum

An input language pd2af accepts, in the order the CLI lists them.

LayoutMode

Bases: StrEnum

A concrete way of laying the output map out, in display order.

TransformationMode dataclass

TransformationMode(name: str, docs: str, rule_group_references: tuple[str, ...] = (), rule_group_definitions: tuple[RuleGroup, ...] = (), compatible_languages: frozenset[Language] = frozenset(LANGUAGES), default_options: MappingProxyType[str, bool] = MappingProxyType({}))

One transformation mode: its metadata and the groups it is made of.

Attributes:

Name Type Description

Parameters:

Name Type Description Default
name str
required
docs str
required
rule_group_references tuple[str, ...]
()
rule_group_definitions tuple[RuleGroup, ...]
()
compatible_languages frozenset[Language]
frozenset({<Language.CELLDESIGNER: 'celldesigner'>, <Language.SBGN_PD: 'sbgn_pd'>})
default_options mappingproxy[str, bool]
mappingproxy({})

rule_group_ids property

rule_group_ids: tuple[str, ...]

Every group this mode is made of, defined ones included.

get_compatible_layout_modes

get_compatible_layout_modes(language: Language, keep_species: bool = False, drop_compartments: bool = False) -> tuple[LayoutMode, ...]

The concrete layout modes valid for these options on the given language.

Merged activities are synthesized from several input species, so they have no original geometry for plain/overlay to reuse. Activities merge unless the species are kept apart, and drop_compartments makes them merge whatever keep_species says. The transformation mode does not bear on the answer.

Source code in src/pd2af/modes.py
def get_compatible_layout_modes(
    language: Language,
    keep_species: bool = False,
    drop_compartments: bool = False,
) -> tuple[LayoutMode, ...]:
    """The concrete layout modes valid for these options on the given language.

    Merged activities are synthesized from several input species, so they have
    no original geometry for `plain`/`overlay` to reuse. Activities merge
    unless the species are kept apart, and `drop_compartments` makes them merge
    whatever `keep_species` says. The transformation mode does not bear on the
    answer.
    """
    available_layout_modes = LAYOUT_MODES_BY_LANGUAGE[language]
    activities_merge = not keep_species or drop_compartments
    if not activities_merge:
        return available_layout_modes
    if LayoutMode.DOT in available_layout_modes:
        return (LayoutMode.DOT,)
    return ()

get_language_from_map_or_model

get_language_from_map_or_model(map_or_model: Map | Model) -> Language

Infer the input language token from an input map's or model's type.

Matching is by isinstance rather than exact type: momapy's builder classes are subclasses of the map classes, so a builder is accepted too.

Source code in src/pd2af/modes.py
def get_language_from_map_or_model(
    map_or_model: momapy.core.map.Map | momapy.core.model.Model,
) -> Language:
    """Infer the input language token from an input map's or model's type.

    Matching is by ``isinstance`` rather than exact type: momapy's builder
    classes are subclasses of the map classes, so a builder is accepted too.
    """
    for language, properties in LANGUAGES.items():
        if isinstance(
            map_or_model, (properties["map_class"], properties["model_class"])
        ):
            return language
    raise ValueError(
        f"unsupported input type {type(map_or_model).__name__!r}; expected "
        + " or ".join(
            momapy_class.__name__
            for properties in LANGUAGES.values()
            for momapy_class in (
                properties["map_class"],
                properties["model_class"],
            )
        )
    )

get_transformation_mode

get_transformation_mode(name: str) -> TransformationMode

Return the :class:TransformationMode called name.

Raises ValueError, listing the available names, if there is none.

Source code in src/pd2af/modes.py
def get_transformation_mode(name: str) -> "TransformationMode":
    """Return the :class:`TransformationMode` called ``name``.

    Raises ``ValueError``, listing the available names, if there is none.
    """
    modes = get_transformation_modes()
    mode = modes.get(name)
    if mode is None:
        raise ValueError(
            f"unknown transformation mode {name!r}; available modes: "
            + ", ".join(modes)
        )
    return mode

get_transformation_modes cached

get_transformation_modes() -> MappingProxyType[str, TransformationMode]

Return {name: TransformationMode}, read-only and ordered.

Built-in modes come first in declaration order, then the modes contributed through the pd2af.modes entry-point group, so list-modes and the --transformation-mode choices read canonically.

Source code in src/pd2af/modes.py
@functools.cache
def get_transformation_modes() -> types.MappingProxyType[str, "TransformationMode"]:
    """Return ``{name: TransformationMode}``, read-only and ordered.

    Built-in modes come first in declaration order, then the modes contributed
    through the ``pd2af.modes`` entry-point group, so ``list-modes`` and the
    ``--transformation-mode`` choices read canonically.
    """
    modes = {}
    for mode in _BUILTIN_TRANSFORMATION_MODES:
        _check_default_options_are_known(mode)
        modes[mode.name] = mode
    for entry_point in importlib.metadata.entry_points(group=ENTRY_POINT_GROUP):
        mode = entry_point.load()
        if not isinstance(mode, TransformationMode):
            raise TypeError(
                f"entry point {entry_point.name!r} of group "
                f"{ENTRY_POINT_GROUP!r} ({entry_point.value}) resolved to "
                f"{type(mode).__name__}, expected a TransformationMode"
            )
        if mode.name in modes:
            raise ValueError(
                f"entry point {entry_point.name!r} of group "
                f"{ENTRY_POINT_GROUP!r} contributes transformation mode "
                f"{mode.name!r}, which already exists"
            )
        _check_default_options_are_known(mode)
        modes[mode.name] = mode
    return types.MappingProxyType(modes)

make_map_from_model

make_map_from_model(model: Model, language: Language) -> Map

Wrap a bare model in a layout-less map of its language.

A Map is a keyword-only frozen dataclass whose layout and layout_model_mapping default to None, so a model-only map is a direct construction of the language's map_class. A language token that is not in :data:LANGUAGES raises KeyError.

Source code in src/pd2af/modes.py
def make_map_from_model(
    model: momapy.core.model.Model, language: Language
) -> momapy.core.map.Map:
    """Wrap a bare model in a layout-less map of its language.

    A ``Map`` is a keyword-only frozen dataclass whose ``layout`` and
    ``layout_model_mapping`` default to ``None``, so a model-only map is a
    direct construction of the language's ``map_class``. A language token that
    is not in :data:`LANGUAGES` raises ``KeyError``.
    """
    return LANGUAGES[language]["map_class"](model=model)