Skip to content

API Reference

qspy.core

QSPy Core Model Extensions and Utilities

This module extends the PySB modeling framework with QSPy-specific features for quantitative systems pharmacology (QSP) workflows. It provides enhanced Model and Monomer classes, operator overloads for semantic annotation, and utilities for metadata, logging, and summary/diagram generation.

Classes:

Name Description
Model : QSPy extension of the PySB Model class with metadata, summary, and diagram support.
Monomer : QSPy extension of the PySB Monomer class with functional tag support.

Functions:

Name Description
mp_lshift : Overloads '<<' for MonomerPattern/ComplexPattern to create Initial objects.
mp_invert : Overloads '~' for MonomerPattern/ComplexPattern to create Observables with auto-naming.
mp_gt : Overloads '>' for MonomerPattern/ComplexPattern to create Observables with custom names.
_make_mono_string : Utility to generate string names for MonomerPatterns.
_make_complex_string : Utility to generate string names for ComplexPatterns.
Operator Overloads
  • MonomerPattern/ComplexPattern << value : Create Initial objects.
  • ~MonomerPattern/ComplexPattern : Create Observable objects with auto-generated names.
  • MonomerPattern/ComplexPattern > "name" : Create Observable objects with custom names.
  • Monomer @ tag : Attach a functional tag to a Monomer.

Examples:

>>> from qspy.core import Model, Monomer
>>> m = Monomer("A", ["b"])
>>> m @ PROTEIN.LIGAND
>>> model = Model()
>>> model.with_units("nM", "min", "uL")
>>> ~m(b=None)
Observable('A_u', m(b=None))
>>> m(b=None) << 100
Initial(m(b=None), 100)

Model

Bases: Model

QSPy extension of the PySB Model class.

Adds QSPy-specific utilities, metadata handling, and summary/diagram generation to the standard PySB Model. Supports custom units, logging, and functional tagging.

Methods:

Name Description
with_units

Set simulation units for concentration, time, and volume.

component_names

List of component names in the model.

qspy_metadata

Dictionary of QSPy metadata for the model.

summarize

Generate a Markdown summary of the model and optionally a diagram.

Source code in qspy\core.py
class Model(Model):
    """
    QSPy extension of the PySB Model class.

    Adds QSPy-specific utilities, metadata handling, and summary/diagram generation
    to the standard PySB Model. Supports custom units, logging, and functional tagging.

    Methods
    -------
    with_units(concentration, time, volume)
        Set simulation units for concentration, time, and volume.
    component_names
        List of component names in the model.
    qspy_metadata
        Dictionary of QSPy metadata for the model.
    summarize(path, include_diagram)
        Generate a Markdown summary of the model and optionally a diagram.
    """

    @log_event(log_args=True, static_method=True)
    @staticmethod
    def with_units(concentration: str = "mg/L", time: str = "h", volume: str = "L"):
        """
        Set simulation units for the model.

        Parameters
        ----------
        concentration : str, optional
            Concentration units (default "mg/L").
        time : str, optional
            Time units (default "h").
        volume : str, optional
            Volume units (default "L").
        """
        ensure_qspy_logging()
        SimulationUnits(concentration, time, volume)
        return

    @property
    def component_names(self):
        """
        List the names of all components in the model.

        Returns
        -------
        list of str
            Names of model components.
        """
        return [component.name for component in self.components]

    @property
    def qspy_metadata(self):
        """
        Return QSPy metadata dictionary for the model.

        Returns
        -------
        dict
            Metadata dictionary if available, else empty dict.
        """
        if hasattr(self, "qspy_metadata_tracker"):
            return self.qspy_metadata_tracker.metadata
        else:
            return {}

    @log_event(log_args=True)
    def markdown_summary(self, path=SUMMARY_DIR, include_diagram=True):
        """
        Generate a Markdown summary of the model and optionally a diagram.

        Parameters
        ----------
        path : str or Path, optional
            Output path for the summary file (default: SUMMARY_DIR).
        include_diagram : bool, optional
            Whether to include a model diagram if SBMLDiagrams is available (default: True).

        Returns
        -------
        None
        """
        lines = []
        lines.append(f"# QSPy Model Summary: `{self.name}`\n")

        metadata = self.qspy_metadata
        lines.append(f"**Model name**: `{self.name}` \n")
        lines.append(f"**Hash**: \n`{metadata.get('hash', 'N/A')}` \n")
        lines.append(f"**Version**: {metadata.get('version', 'N/A')} \n")
        lines.append(f"**Author**: {metadata.get('author', 'N/A')} \n")
        lines.append(f"**Executed by**: {metadata.get('current_user', 'N/A')} \n")
        lines.append(
            f"**Timestamp**: {metadata.get('created_at', datetime.now().isoformat())}\n"
        )

        if include_diagram and hasattr(self, "mermaid_diagram"):
            diagram_md = self.mermaid_diagram.markdown_block
            lines.append("## 🖼️ Model Diagram\n")
            lines.append(f"{diagram_md}\n")

        # Core units table
        lines.append("## Core Units\n| Quantity | Unit |")
        lines.append("|-----------|------|")
        units = getattr(self, "simulation_units", None)
        if units:
            lines.append(f"| Concentration | {units.concentration} |")
            lines.append(f"| Time         | {units.time} |")
            lines.append(f"| Volume       | {units.volume} |")
        else:
            lines.append("No core model units defined.")
            lines.append("    They can added with the `Model.with_units` method.")

        # Component counts table
        lines.append("## Numbers of Model Component\n| Component Type | Count |")
        lines.append("|---------------|-------|")
        lines += [
            f"| Monomers | {len(getattr(self, 'monomers', []))} |",
            f"| Parameters | {len(getattr(self, 'parameters', []))} |",
            f"| Expressions | {len(getattr(self, 'expressions', []))} |",
            f"| Compartments | {len(getattr(self, 'compartments', []))} |",
            f"| Rules | {len(getattr(self, 'rules', []))} |",
            f"| Initial Conditions | {len(getattr(self, 'initial_conditions', []))} |",
            f"| Observables | {len(getattr(self, 'observables', []))} |",
        ]

        # Compartments table
        lines.append("\n## Compartments\n| Name | Size |")
        lines.append("|------|------|")
        lines += [
            f"| {cpt.name} | {cpt.size.name if hasattr(cpt.size, 'name') else str(cpt.size)} |"
            for cpt in getattr(self, "compartments", [])
        ] or ["| _None_ | _N/A_ |"]

        # Monomers table
        lines.append("## Monomers\n| Name | Sites | States | Functional Tag |")
        lines.append("|------|-------|--------|---------------|")
        lines += [
            f"| {m.name} | {m.sites} | {m.site_states} | {getattr(m.functional_tag, 'value', m.functional_tag)} |"
            for m in self.monomers
        ] or ["| _None_ | _N/A_ | _N/A_ | _N/A_ |"]

        # Parameters table
        lines.append("\n## Parameters\n| Name | Value | Units |")
        lines.append("|------|--------|--------|")
        lines += [
            f"| {p.name} | {p.value} | {p.unit.to_string()} |" for p in self.parameters
        ] or ["| _None_ | _N/A_ |"]

        # Expressions table
        lines.append("\n## Expressions\n| Name | Expression |")
        lines.append("|------|------------|")
        lines += [
            f"| {e.name} | `{e.expr}` |" for e in getattr(self, "expressions", [])
        ] or ["| _None_ | _N/A_ |"]

        # Initial Conditions table
        lines.append("\n## Initial Conditions\n| Species | Value | Units |")
        lines.append("|---------|--------|--------|")
        lines += [
            f"| {str(ic[0])} | {ic[1].value if isinstance(ic[1], Parameter) else ic[1].get_value():.2f} | {ic[1].units.value}"
            for ic in self.initial_conditions
        ] or ["| _None_ | _N/A_ |"]

        def _sanitize_rule_expression(expr):
            """
            Sanitize rule expression for Markdown rendering.
            Replaces ' | ' with ' \| ' to avoid Markdown table formatting issues.
            """
            return repr(expr).replace(" | ", " \| ")
        # Rules table
        lines.append("\n## Rules\n| Name | Rule Expression | k_f | k_r | reversible |")
        lines.append("|------|-----------------|-----|-----|------------|")
        lines += [
            f"| {r.name} | `{_sanitize_rule_expression(r.rule_expression)}` | {r.rate_forward.name} | {r.rate_reverse.name if r.rate_reverse is not None else 'None'} | {r.is_reversible} |"
            for r in self.rules
        ] or ["| _None_ | _N/A_ | _N/A_ | _N/A_ | _N/A_ |"]

        # Observables table
        lines.append("\n## Observables\n| Name | Reaction Pattern |")
        lines.append("|------|------------------|")
        lines += [
            f"| {o.name} | `{o.reaction_pattern}` |" for o in self.observables
        ] or ["| _None_ | _N/A_ |"]

        path = Path(path)
        path.parent.mkdir(parents=True, exist_ok=True)
        with open(path, "w", encoding="utf-8") as f:
            f.write("\n".join(lines))

component_names property

List the names of all components in the model.

Returns:

Type Description
list of str

Names of model components.

qspy_metadata property

Return QSPy metadata dictionary for the model.

Returns:

Type Description
dict

Metadata dictionary if available, else empty dict.

markdown_summary(path=SUMMARY_DIR, include_diagram=True)

Generate a Markdown summary of the model and optionally a diagram.

Parameters:

Name Type Description Default
path str or Path

Output path for the summary file (default: SUMMARY_DIR).

SUMMARY_DIR
include_diagram bool

Whether to include a model diagram if SBMLDiagrams is available (default: True).

True

Returns:

Type Description
None
Source code in qspy\core.py
@log_event(log_args=True)
def markdown_summary(self, path=SUMMARY_DIR, include_diagram=True):
    """
    Generate a Markdown summary of the model and optionally a diagram.

    Parameters
    ----------
    path : str or Path, optional
        Output path for the summary file (default: SUMMARY_DIR).
    include_diagram : bool, optional
        Whether to include a model diagram if SBMLDiagrams is available (default: True).

    Returns
    -------
    None
    """
    lines = []
    lines.append(f"# QSPy Model Summary: `{self.name}`\n")

    metadata = self.qspy_metadata
    lines.append(f"**Model name**: `{self.name}` \n")
    lines.append(f"**Hash**: \n`{metadata.get('hash', 'N/A')}` \n")
    lines.append(f"**Version**: {metadata.get('version', 'N/A')} \n")
    lines.append(f"**Author**: {metadata.get('author', 'N/A')} \n")
    lines.append(f"**Executed by**: {metadata.get('current_user', 'N/A')} \n")
    lines.append(
        f"**Timestamp**: {metadata.get('created_at', datetime.now().isoformat())}\n"
    )

    if include_diagram and hasattr(self, "mermaid_diagram"):
        diagram_md = self.mermaid_diagram.markdown_block
        lines.append("## 🖼️ Model Diagram\n")
        lines.append(f"{diagram_md}\n")

    # Core units table
    lines.append("## Core Units\n| Quantity | Unit |")
    lines.append("|-----------|------|")
    units = getattr(self, "simulation_units", None)
    if units:
        lines.append(f"| Concentration | {units.concentration} |")
        lines.append(f"| Time         | {units.time} |")
        lines.append(f"| Volume       | {units.volume} |")
    else:
        lines.append("No core model units defined.")
        lines.append("    They can added with the `Model.with_units` method.")

    # Component counts table
    lines.append("## Numbers of Model Component\n| Component Type | Count |")
    lines.append("|---------------|-------|")
    lines += [
        f"| Monomers | {len(getattr(self, 'monomers', []))} |",
        f"| Parameters | {len(getattr(self, 'parameters', []))} |",
        f"| Expressions | {len(getattr(self, 'expressions', []))} |",
        f"| Compartments | {len(getattr(self, 'compartments', []))} |",
        f"| Rules | {len(getattr(self, 'rules', []))} |",
        f"| Initial Conditions | {len(getattr(self, 'initial_conditions', []))} |",
        f"| Observables | {len(getattr(self, 'observables', []))} |",
    ]

    # Compartments table
    lines.append("\n## Compartments\n| Name | Size |")
    lines.append("|------|------|")
    lines += [
        f"| {cpt.name} | {cpt.size.name if hasattr(cpt.size, 'name') else str(cpt.size)} |"
        for cpt in getattr(self, "compartments", [])
    ] or ["| _None_ | _N/A_ |"]

    # Monomers table
    lines.append("## Monomers\n| Name | Sites | States | Functional Tag |")
    lines.append("|------|-------|--------|---------------|")
    lines += [
        f"| {m.name} | {m.sites} | {m.site_states} | {getattr(m.functional_tag, 'value', m.functional_tag)} |"
        for m in self.monomers
    ] or ["| _None_ | _N/A_ | _N/A_ | _N/A_ |"]

    # Parameters table
    lines.append("\n## Parameters\n| Name | Value | Units |")
    lines.append("|------|--------|--------|")
    lines += [
        f"| {p.name} | {p.value} | {p.unit.to_string()} |" for p in self.parameters
    ] or ["| _None_ | _N/A_ |"]

    # Expressions table
    lines.append("\n## Expressions\n| Name | Expression |")
    lines.append("|------|------------|")
    lines += [
        f"| {e.name} | `{e.expr}` |" for e in getattr(self, "expressions", [])
    ] or ["| _None_ | _N/A_ |"]

    # Initial Conditions table
    lines.append("\n## Initial Conditions\n| Species | Value | Units |")
    lines.append("|---------|--------|--------|")
    lines += [
        f"| {str(ic[0])} | {ic[1].value if isinstance(ic[1], Parameter) else ic[1].get_value():.2f} | {ic[1].units.value}"
        for ic in self.initial_conditions
    ] or ["| _None_ | _N/A_ |"]

    def _sanitize_rule_expression(expr):
        """
        Sanitize rule expression for Markdown rendering.
        Replaces ' | ' with ' \| ' to avoid Markdown table formatting issues.
        """
        return repr(expr).replace(" | ", " \| ")
    # Rules table
    lines.append("\n## Rules\n| Name | Rule Expression | k_f | k_r | reversible |")
    lines.append("|------|-----------------|-----|-----|------------|")
    lines += [
        f"| {r.name} | `{_sanitize_rule_expression(r.rule_expression)}` | {r.rate_forward.name} | {r.rate_reverse.name if r.rate_reverse is not None else 'None'} | {r.is_reversible} |"
        for r in self.rules
    ] or ["| _None_ | _N/A_ | _N/A_ | _N/A_ | _N/A_ |"]

    # Observables table
    lines.append("\n## Observables\n| Name | Reaction Pattern |")
    lines.append("|------|------------------|")
    lines += [
        f"| {o.name} | `{o.reaction_pattern}` |" for o in self.observables
    ] or ["| _None_ | _N/A_ |"]

    path = Path(path)
    path.parent.mkdir(parents=True, exist_ok=True)
    with open(path, "w", encoding="utf-8") as f:
        f.write("\n".join(lines))

with_units(concentration='mg/L', time='h', volume='L') staticmethod

Set simulation units for the model.

Parameters:

Name Type Description Default
concentration str

Concentration units (default "mg/L").

'mg/L'
time str

Time units (default "h").

'h'
volume str

Volume units (default "L").

'L'
Source code in qspy\core.py
@log_event(log_args=True, static_method=True)
@staticmethod
def with_units(concentration: str = "mg/L", time: str = "h", volume: str = "L"):
    """
    Set simulation units for the model.

    Parameters
    ----------
    concentration : str, optional
        Concentration units (default "mg/L").
    time : str, optional
        Time units (default "h").
    volume : str, optional
        Volume units (default "L").
    """
    ensure_qspy_logging()
    SimulationUnits(concentration, time, volume)
    return

Monomer

Bases: Monomer

QSPy extension of the PySB Monomer class.

Adds support for functional tags and operator overloading for semantic annotation.

Methods:

Name Description
__matmul__

Attach a functional tag to the monomer using the '@' operator.

__imatmul__

Attach a functional tag to the monomer in-place using the '@=' operator.

__repr__

String representation including the functional tag if present.

Source code in qspy\core.py
class Monomer(Monomer):
    """
    QSPy extension of the PySB Monomer class.

    Adds support for functional tags and operator overloading for semantic annotation.

    Methods
    -------
    __matmul__(other)
        Attach a functional tag to the monomer using the '@' operator.
    __imatmul__(other)
        Attach a functional tag to the monomer in-place using the '@=' operator.
    __repr__()
        String representation including the functional tag if present.
    """

    def __init__(self, *args, **kwargs):
        """
        Initialize a Monomer with optional functional tag.

        Parameters
        ----------
        *args, **kwargs
            Arguments passed to the PySB Monomer constructor.
        """
        self.functional_tag = FunctionalTag(
            "None", "None"
        )  # Default to no functional tag
        super().__init__(*args, **kwargs)
        return

    def __matmul__(self, other: Enum):
        """
        Attach a functional tag to the monomer using the '@' operator.

        Parameters
        ----------
        other : Enum
            Enum member representing the functional tag.

        Returns
        -------
        Monomer
            The monomer instance with the functional tag set.
        """
        if isinstance(other, Enum):
            ftag_str = other.value
            ftag = FunctionalTag(*FunctionalTag.parse(ftag_str))
            setattr(self, "functional_tag", ftag)
        return self

    def __imatmul__(self, other: Enum):
        """
        Attach a functional tag to the monomer in-place using the '@=' operator.

        Parameters
        ----------
        other : Enum
            Enum member representing the functional tag.

        Returns
        -------
        Monomer
            The monomer instance with the functional tag set.
        """
        if isinstance(other, Enum):
            ftag_str = other.value
            ftag = FunctionalTag(*FunctionalTag.parse(ftag_str))
            setattr(self, "functional_tag", ftag)
        return self

    def __repr__(self):
        """
        Return a string representation of the monomer, including the functional tag if present.

        Returns
        -------
        str
            String representation of the monomer.
        """
        if self.functional_tag is None:
            return super().__repr__()
        else:
            base_repr = super().__repr__()
            return f"{base_repr} @ {self.functional_tag.value}"

    def __pow__(self, compartment: Compartment):
        """
        Overload the '**' operator to allow monomers to be passed with a compartment.

        This is a shortcut for creating a MonomerPattern with a compartment:
          i.e., `monomer ** compartment` is equivalent to `monomer() ** compartment`.


        Parameters
        ----------
        compartment : Compartment
            The compartment in which the monomer pattern resides.

        Returns
        -------
        MonomerPattern
            A MonomerPattern from calling the monomer with the compartment.
        """
        if not isinstance(compartment, Compartment):
            raise TypeError("Compartment must be an instance of Compartment")
        return self() ** compartment

__imatmul__(other)

Attach a functional tag to the monomer in-place using the '@=' operator.

Parameters:

Name Type Description Default
other Enum

Enum member representing the functional tag.

required

Returns:

Type Description
Monomer

The monomer instance with the functional tag set.

Source code in qspy\core.py
def __imatmul__(self, other: Enum):
    """
    Attach a functional tag to the monomer in-place using the '@=' operator.

    Parameters
    ----------
    other : Enum
        Enum member representing the functional tag.

    Returns
    -------
    Monomer
        The monomer instance with the functional tag set.
    """
    if isinstance(other, Enum):
        ftag_str = other.value
        ftag = FunctionalTag(*FunctionalTag.parse(ftag_str))
        setattr(self, "functional_tag", ftag)
    return self

__init__(*args, **kwargs)

Initialize a Monomer with optional functional tag.

Parameters:

Name Type Description Default
*args

Arguments passed to the PySB Monomer constructor.

()
**kwargs

Arguments passed to the PySB Monomer constructor.

()
Source code in qspy\core.py
def __init__(self, *args, **kwargs):
    """
    Initialize a Monomer with optional functional tag.

    Parameters
    ----------
    *args, **kwargs
        Arguments passed to the PySB Monomer constructor.
    """
    self.functional_tag = FunctionalTag(
        "None", "None"
    )  # Default to no functional tag
    super().__init__(*args, **kwargs)
    return

__matmul__(other)

Attach a functional tag to the monomer using the '@' operator.

Parameters:

Name Type Description Default
other Enum

Enum member representing the functional tag.

required

Returns:

Type Description
Monomer

The monomer instance with the functional tag set.

Source code in qspy\core.py
def __matmul__(self, other: Enum):
    """
    Attach a functional tag to the monomer using the '@' operator.

    Parameters
    ----------
    other : Enum
        Enum member representing the functional tag.

    Returns
    -------
    Monomer
        The monomer instance with the functional tag set.
    """
    if isinstance(other, Enum):
        ftag_str = other.value
        ftag = FunctionalTag(*FunctionalTag.parse(ftag_str))
        setattr(self, "functional_tag", ftag)
    return self

__pow__(compartment)

Overload the '**' operator to allow monomers to be passed with a compartment.

This is a shortcut for creating a MonomerPattern with a compartment: i.e., monomer ** compartment is equivalent to monomer() ** compartment.

Parameters:

Name Type Description Default
compartment Compartment

The compartment in which the monomer pattern resides.

required

Returns:

Type Description
MonomerPattern

A MonomerPattern from calling the monomer with the compartment.

Source code in qspy\core.py
def __pow__(self, compartment: Compartment):
    """
    Overload the '**' operator to allow monomers to be passed with a compartment.

    This is a shortcut for creating a MonomerPattern with a compartment:
      i.e., `monomer ** compartment` is equivalent to `monomer() ** compartment`.


    Parameters
    ----------
    compartment : Compartment
        The compartment in which the monomer pattern resides.

    Returns
    -------
    MonomerPattern
        A MonomerPattern from calling the monomer with the compartment.
    """
    if not isinstance(compartment, Compartment):
        raise TypeError("Compartment must be an instance of Compartment")
    return self() ** compartment

__repr__()

Return a string representation of the monomer, including the functional tag if present.

Returns:

Type Description
str

String representation of the monomer.

Source code in qspy\core.py
def __repr__(self):
    """
    Return a string representation of the monomer, including the functional tag if present.

    Returns
    -------
    str
        String representation of the monomer.
    """
    if self.functional_tag is None:
        return super().__repr__()
    else:
        base_repr = super().__repr__()
        return f"{base_repr} @ {self.functional_tag.value}"

mp_gt(self, other)

Overload the '>' operator for MonomerPattern and ComplexPattern.

Allows creation of Observable objects with a custom name using the syntax: pattern > "observable_name"

Parameters:

Name Type Description Default
other str

Name for the observable.

required

Returns:

Type Description
Observable

Observable object with the specified name.

Raises:

Type Description
ValueError

If the provided name is not a string.

Source code in qspy\core.py
def mp_gt(self, other):
    """
    Overload the '>' operator for MonomerPattern and ComplexPattern.

    Allows creation of Observable objects with a custom name using the syntax:
        pattern > "observable_name"

    Parameters
    ----------
    other : str
        Name for the observable.

    Returns
    -------
    Observable
        Observable object with the specified name.

    Raises
    ------
    ValueError
        If the provided name is not a string.
    """
    if not isinstance(other, str):
        raise ValueError("Observable name should be a string")
    else:
        return Observable(other, self)

mp_invert(self)

Overload the '~' operator for MonomerPattern and ComplexPattern.

Allows creation of Observable objects using the syntax: ~pattern

Returns:

Type Description
Observable

Observable object with an auto-generated name.

Source code in qspy\core.py
def mp_invert(self):
    """
    Overload the '~' operator for MonomerPattern and ComplexPattern.

    Allows creation of Observable objects using the syntax:
        ~pattern

    Returns
    -------
    Observable
        Observable object with an auto-generated name.
    """

    if isinstance(self, MonomerPattern):
        name = _make_mono_string(self)

    elif isinstance(self, ComplexPattern):
        name = _make_complex_string(self)

    # name = 'gooo'
    return Observable(name, self)

mp_lshift(self, value)

Overload the '<<' operator for MonomerPattern and ComplexPattern.

Allows creation of Initial objects using the syntax: monomer_pattern << value

Parameters:

Name Type Description Default
value float or Parameter

Initial value for the pattern.

required

Returns:

Type Description
Initial

Initial object for the pattern.

Source code in qspy\core.py
def mp_lshift(self, value):
    """
    Overload the '<<' operator for MonomerPattern and ComplexPattern.

    Allows creation of Initial objects using the syntax:
        monomer_pattern << value

    Parameters
    ----------
    value : float or Parameter
        Initial value for the pattern.

    Returns
    -------
    Initial
        Initial object for the pattern.
    """
    return Initial(self, value)

qspy.config

QSPy Configuration Module

This module centralizes configuration constants for QSPy, including logging, unit defaults, output/reporting directories, and versioning information.

Attributes:

Name Type Description
LOGGER_NAME str

Name of the logger used throughout QSPy.

LOG_PATH Path

Path to the QSPy log file.

DEFAULT_UNITS dict

Default units for concentration, time, and volume.

METADATA_DIR Path

Directory for storing model metadata files.

SUMMARY_DIR Path

Path for the model summary markdown file.

QSPY_VERSION str

The current version of QSPy.

set_log_path(path)

Set the log file path for QSPy.

Parameters:

Name Type Description Default
path Path

The new log file path.

required
Source code in qspy\config.py
def set_log_path(path: str | Path):
    """
    Set the log file path for QSPy.

    Parameters
    ----------
    path : Path
        The new log file path.
    """
    global LOG_PATH
    LOG_PATH = Path(path)
    LOG_PATH.parent.mkdir(parents=True, exist_ok=True)

set_logger_name(name)

Set the logger name for QSPy.

Parameters:

Name Type Description Default
name str

The new logger name.

required
Source code in qspy\config.py
def set_logger_name(name: str):
    """
    Set the logger name for QSPy.

    Parameters
    ----------
    name : str
        The new logger name.
    """
    global LOGGER_NAME
    LOGGER_NAME = name

set_output_dir(path)

Set the output directory for QSPy reports and metadata.

Parameters:

Name Type Description Default
path Path

The new output directory path.

required
Source code in qspy\config.py
def set_output_dir(path: str | Path):
    """
    Set the output directory for QSPy reports and metadata.

    Parameters
    ----------
    path : Path
        The new output directory path.
    """
    global OUTPUT_DIR, LOG_PATH, METADATA_DIR, SUMMARY_DIR
    OUTPUT_DIR = Path(path)
    OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
    global LOG_PATH, METADATA_DIR, SUMMARY_DIR
    LOG_PATH = OUTPUT_DIR / "logs/qspy.log"
    METADATA_DIR = OUTPUT_DIR / "metadata"
    SUMMARY_DIR = OUTPUT_DIR / "model_summary.md"

qspy.contexts.contexts

QSPy Context Managers for Model Construction

This module provides context managers and utilities for structured, validated construction of quantitative systems pharmacology (QSP) models using QSPy. Each context manager encapsulates the logic for defining a specific model component (parameters, compartments, monomers, expressions, rules, initials, observables), ensuring type safety, unit checking, and extensibility.

Classes:

Name Description
parameters : Context manager for defining model parameters
compartments : Context manager for defining model compartments.
monomers : Context manager for defining model monomers with optional functional tags.
expressions : Context manager for defining model expressions
rules : Context manager for defining model rules
pk_macros : Stub context manager for future pharmacokinetic macro support.

Functions:

Name Description
initials
observables

Examples:

>>> with parameters():
...     k1 = (1.0, "1/min")
>>> with monomers():
...     A = (["b"], {"b": ["u", "p"]})
>>> with rules():
...     bind = (A(b=None) + B(), kf, kr)

compartments

Bases: ComponentContext

Context manager for defining model compartments in a QSPy model.

Provides validation and creation logic for compartments.

Methods:

Name Description
_validate_value

Validate the size for a compartment.

create_component

Create a compartment component.

Source code in qspy\contexts\contexts.py
class compartments(ComponentContext):
    """
    Context manager for defining model compartments in a QSPy model.

    Provides validation and creation logic for compartments.

    Methods
    -------
    _validate_value(name, size)
        Validate the size for a compartment.
    create_component(name, size)
        Create a compartment component.
    """

    component_name = "compartment"

    @staticmethod
    def _validate_value(name, val):
        """
        Validate the size for a compartment.

        Parameters
        ----------
        name : str
            Name of the compartment.
        val : tuple | Parameter | Expression
            Tuple of (size, dimensions) or Parameter/Expression for size.

        Returns
        -------
        tuple
            (size, dimensions)

        Raises
        ------
        ValueError
            If the size is not a Parameter or Expression.
        """
        if not isinstance(val, (tuple, Parameter, Expression)):
            raise ValueError(
                f"Compartment '{name}' must be a tuple: (size, dimensions) or a Parameter/Expression for size"
            )
        if isinstance(val, tuple):
            if len(val) != 2:
                raise ValueError(
                    f"Compartment '{name}' tuple must be of the form (size, dimensions)"
                )
            size, dimensions = val
            if not isinstance(size, (Parameter, Expression)):
                raise ValueError(
                    f"Compartment size for '{name}' must be a Parameter or Expression"
                )
            if not isinstance(dimensions, int):
                raise ValueError(
                    f"Compartment dimensions for '{name}' must be an integer"
                )
        else:
            size = val
            dimensions = 3  # Default to 3D if not specified
        return (size, dimensions)

    @log_event(log_args=True, log_result=True, static_method=True)
    @staticmethod
    def create_component(name, size, dimensions):
        """
        Create a compartment component.

        Parameters
        ----------
        name : str
            Name of the compartment.
        size : Parameter or Expression
            Size of the compartment.

        Returns
        -------
        Compartment
            The created compartment.
        """
        compartment = Compartment(name, size=size, dimension=dimensions)
        return compartment

create_component(name, size, dimensions) staticmethod

Create a compartment component.

Parameters:

Name Type Description Default
name str

Name of the compartment.

required
size Parameter or Expression

Size of the compartment.

required

Returns:

Type Description
Compartment

The created compartment.

Source code in qspy\contexts\contexts.py
@log_event(log_args=True, log_result=True, static_method=True)
@staticmethod
def create_component(name, size, dimensions):
    """
    Create a compartment component.

    Parameters
    ----------
    name : str
        Name of the compartment.
    size : Parameter or Expression
        Size of the compartment.

    Returns
    -------
    Compartment
        The created compartment.
    """
    compartment = Compartment(name, size=size, dimension=dimensions)
    return compartment

expressions

Bases: ComponentContext

Context manager for defining model expressions in a QSPy model.

Provides validation and creation logic for expressions.

Methods:

Name Description
_validate_value

Validate the value for an expression.

create_component

Create an expression component.

Source code in qspy\contexts\contexts.py
class expressions(ComponentContext):
    """
    Context manager for defining model expressions in a QSPy model.

    Provides validation and creation logic for expressions.

    Methods
    -------
    _validate_value(name, val)
        Validate the value for an expression.
    create_component(name, expr)
        Create an expression component.
    """

    component_name = "expression"

    @staticmethod
    def _validate_value(name, val):
        """
        Validate the value for an expression.

        Parameters
        ----------
        name : str
            Name of the expression.
        val : sympy.Expr
            The sympy expression.

        Returns
        -------
        tuple
            (val,)

        Raises
        ------
        ValueError
            If the value is not a sympy.Expr.
        """
        # Only allow sympy expressions for expressions
        if not isinstance(val, sympy.Expr):
            raise ValueError(f"Expression '{name}' must be a sympy.Expr")
        return (val,)

    @log_event(log_args=True, log_result=True, static_method=True)
    @staticmethod
    def create_component(name, expr):
        """
        Create an expression component.

        Parameters
        ----------
        name : str
            Name of the expression.
        expr : sympy.Expr
            The sympy expression.

        Returns
        -------
        Expression
            The created expression.
        """
        expression = Expression(name, expr)
        return expression

create_component(name, expr) staticmethod

Create an expression component.

Parameters:

Name Type Description Default
name str

Name of the expression.

required
expr Expr

The sympy expression.

required

Returns:

Type Description
Expression

The created expression.

Source code in qspy\contexts\contexts.py
@log_event(log_args=True, log_result=True, static_method=True)
@staticmethod
def create_component(name, expr):
    """
    Create an expression component.

    Parameters
    ----------
    name : str
        Name of the expression.
    expr : sympy.Expr
        The sympy expression.

    Returns
    -------
    Expression
        The created expression.
    """
    expression = Expression(name, expr)
    return expression

monomers

Bases: ComponentContext

Context manager for defining model monomers in a QSPy model.

Provides validation and creation logic for monomers, including optional functional tags.

Methods:

Name Description
_validate_value

Validate the tuple for monomer definition.

create_component

Create a monomer component.

Source code in qspy\contexts\contexts.py
class monomers(ComponentContext):
    """
    Context manager for defining model monomers in a QSPy model.

    Provides validation and creation logic for monomers, including optional functional tags.

    Methods
    -------
    _validate_value(name, val)
        Validate the tuple for monomer definition.
    create_component(name, sites, site_states, functional_tag)
        Create a monomer component.
    """

    component_name = "monomer"

    @staticmethod
    def _validate_value(name, val):
        """
        Validate the tuple for monomer definition.

        Parameters
        ----------
        name : str
            Name of the monomer.
        val : tuple
            Tuple of (sites, site_states) or (sites, site_states, functional_tag).

        Returns
        -------
        tuple
            (sites, site_states, functional_tag)

        Raises
        ------
        ValueError
            If the tuple is not valid.
        """
        # Accept either (sites, site_states) or (sites, site_states, functional_tag)
        if not isinstance(val, tuple) or (len(val) not in [2, 3]):
            raise ValueError(
                f"Context-defined Monomer '{name}' must be a tuple: (sites, site_states) OR (sites, site_states, functional_tag)"
            )
        if len(val) == 2:
            sites, site_states = val
            functional_tag = None
        if len(val) == 3:
            sites, site_states, functional_tag = val
        # Validate types for each field
        if (sites is not None) and (not isinstance(sites, list)):
            raise ValueError(
                f"Monomer sites value for '{name}' must be a list of site names"
            )
        if (site_states is not None) and (not isinstance(site_states, dict)):
            raise ValueError(
                f"Monomer site_states for '{name}' must be a dictionary of sites and their states"
            )
        if (functional_tag is not None) and (not isinstance(functional_tag, Enum)):
            raise ValueError(
                f"Monomer functional tag for '{name} must be an Enum item'"
            )
        return (sites, site_states, functional_tag)

    @log_event(log_args=True, log_result=True, static_method=True)
    @staticmethod
    def create_component(name, sites, site_states, functional_tag):
        """
        Create a monomer component.

        Parameters
        ----------
        name : str
            Name of the monomer.
        sites : list
            List of site names.
        site_states : dict
            Dictionary of site states.
        functional_tag : Enum or None
            Functional tag for the monomer.

        Returns
        -------
        core.Monomer
            The created monomer.
        """
        # If no functional tag, create a plain Monomer
        if functional_tag is None:
            monomer = Monomer(name, sites, site_states)
        else:
            # If functional tag is provided, attach it
            monomer = Monomer(name, sites, site_states) @ functional_tag
        return monomer

create_component(name, sites, site_states, functional_tag) staticmethod

Create a monomer component.

Parameters:

Name Type Description Default
name str

Name of the monomer.

required
sites list

List of site names.

required
site_states dict

Dictionary of site states.

required
functional_tag Enum or None

Functional tag for the monomer.

required

Returns:

Type Description
Monomer

The created monomer.

Source code in qspy\contexts\contexts.py
@log_event(log_args=True, log_result=True, static_method=True)
@staticmethod
def create_component(name, sites, site_states, functional_tag):
    """
    Create a monomer component.

    Parameters
    ----------
    name : str
        Name of the monomer.
    sites : list
        List of site names.
    site_states : dict
        Dictionary of site states.
    functional_tag : Enum or None
        Functional tag for the monomer.

    Returns
    -------
    core.Monomer
        The created monomer.
    """
    # If no functional tag, create a plain Monomer
    if functional_tag is None:
        monomer = Monomer(name, sites, site_states)
    else:
        # If functional tag is provided, attach it
        monomer = Monomer(name, sites, site_states) @ functional_tag
    return monomer

parameters

Bases: ComponentContext

Context manager for defining model parameters in a QSPy model.

Provides validation and creation logic for parameters, supporting both numeric and symbolic (sympy.Expr) values.

Methods:

Name Description
_validate_value

Validate the value and unit for a parameter.

create_component

Create a parameter or expression component.

Source code in qspy\contexts\contexts.py
class parameters(ComponentContext):
    """
    Context manager for defining model parameters in a QSPy model.

    Provides validation and creation logic for parameters, supporting both numeric
    and symbolic (sympy.Expr) values.

    Methods
    -------
    _validate_value(name, val)
        Validate the value and unit for a parameter.
    create_component(name, value, unit)
        Create a parameter or expression component.
    """

    component_name = "parameter"

    @staticmethod
    def _validate_value(name, val):
        """
        Validate the value and unit for a parameter.

        Parameters
        ----------
        name : str
            Name of the parameter.
        val : tuple
            Tuple of (value, unit).

        Returns
        -------
        tuple
            (value, unit) if valid.

        Raises
        ------
        ValueError
            If the value or unit is invalid.
        """
        # Accept sympy expressions directly as expressions
        if isinstance(val, sympy.Expr):
            return (val, None)
        # Ensure tuple structure for numeric parameters
        if not isinstance(val, tuple) or len(val) != 2:
            raise ValueError(f"Parameter '{name}' must be a tuple: (value, unit)")
        value, unit = val
        if not isinstance(value, (int, float)):
            raise ValueError(f"Parameter value for '{name}' must be a number")
        if not isinstance(unit, str):
            raise ValueError(f"Unit for parameter '{name}' must be a string")
        return (value, unit)

    @log_event(log_args=True, log_result=True, static_method=True)
    @staticmethod
    def create_component(name, value, unit):
        """
        Create a parameter or expression component.

        Parameters
        ----------
        name : str
            Name of the parameter.
        value : int, float, or sympy.Expr
            Value of the parameter or a sympy expression.
        unit : str or None
            Unit for the parameter.

        Returns
        -------
        Parameter or Expression
            The created parameter or expression.
        """
        # If value is a sympy expression, create an Expression
        if isinstance(value, sympy.Expr):
            expr = Expression(name, value)
            return expr
        # Otherwise, create a Parameter
        param = Parameter(name, value, unit=unit)
        return param

    def __exit__(self, exc_type, exc_val, exc_tb):
        """
        Exit the parameter context and perform unit checking.

        Parameters
        ----------
        exc_type : type
            Exception type, if any.
        exc_val : Exception
            Exception value, if any.
        exc_tb : traceback
            Traceback, if any.

        Returns
        -------
        None
        """
        super().__exit__(exc_type, exc_val, exc_tb)
        # Check units for all parameters in the model
        units_check(self.model)
        return

__exit__(exc_type, exc_val, exc_tb)

Exit the parameter context and perform unit checking.

Parameters:

Name Type Description Default
exc_type type

Exception type, if any.

required
exc_val Exception

Exception value, if any.

required
exc_tb traceback

Traceback, if any.

required

Returns:

Type Description
None
Source code in qspy\contexts\contexts.py
def __exit__(self, exc_type, exc_val, exc_tb):
    """
    Exit the parameter context and perform unit checking.

    Parameters
    ----------
    exc_type : type
        Exception type, if any.
    exc_val : Exception
        Exception value, if any.
    exc_tb : traceback
        Traceback, if any.

    Returns
    -------
    None
    """
    super().__exit__(exc_type, exc_val, exc_tb)
    # Check units for all parameters in the model
    units_check(self.model)
    return

create_component(name, value, unit) staticmethod

Create a parameter or expression component.

Parameters:

Name Type Description Default
name str

Name of the parameter.

required
value int, float, or sympy.Expr

Value of the parameter or a sympy expression.

required
unit str or None

Unit for the parameter.

required

Returns:

Type Description
Parameter or Expression

The created parameter or expression.

Source code in qspy\contexts\contexts.py
@log_event(log_args=True, log_result=True, static_method=True)
@staticmethod
def create_component(name, value, unit):
    """
    Create a parameter or expression component.

    Parameters
    ----------
    name : str
        Name of the parameter.
    value : int, float, or sympy.Expr
        Value of the parameter or a sympy expression.
    unit : str or None
        Unit for the parameter.

    Returns
    -------
    Parameter or Expression
        The created parameter or expression.
    """
    # If value is a sympy expression, create an Expression
    if isinstance(value, sympy.Expr):
        expr = Expression(name, value)
        return expr
    # Otherwise, create a Parameter
    param = Parameter(name, value, unit=unit)
    return param

rules

Bases: ComponentContext

Context manager for defining model rules in a QSPy model.

Provides validation and creation logic for rules, supporting both reversible and irreversible forms.

Methods:

Name Description
_validate_value

Validate the tuple for rule definition.

create_component

Create a rule component.

Source code in qspy\contexts\contexts.py
class rules(ComponentContext):
    """
    Context manager for defining model rules in a QSPy model.

    Provides validation and creation logic for rules, supporting both reversible and irreversible forms.

    Methods
    -------
    _validate_value(name, val)
        Validate the tuple for rule definition.
    create_component(name, rxp, rate_forward, rate_reverse)
        Create a rule component.
    """

    component_name = "rule"

    @staticmethod
    def _validate_value(name, val):
        """
        Validate the tuple for rule definition.

        Parameters
        ----------
        name : str
            Name of the rule.
        val : tuple
            Tuple of (RuleExpression, rate_forward) or (RuleExpression, rate_forward, rate_reverse).

        Returns
        -------
        tuple
            (rxp, rate_forward, rate_reverse)

        Raises
        ------
        ValueError
            If the tuple is not valid or contains invalid types.
        """
        # Accept either (RuleExpression, rate_forward) or (RuleExpression, rate_forward, rate_reverse)
        if not isinstance(val, tuple) or (len(val) < 2 or len(val) > 3):
            raise ValueError(
                f"Rule '{name}' input must be a tuple: (RuleExpression, rate_forward) if irreversible or (RuleExpression, rate_forward, rate_reverse) if reversible"
            )
        if len(val) == 2:
            rxp, rate_forward = val
            rate_reverse = None
        elif len(val) == 3:
            rxp, rate_forward, rate_reverse = val
        # Validate types for rule components
        if not isinstance(rxp, pysb.RuleExpression):
            raise ValueError(f"Rule '{name}' must contain a valid RuleExpression")
        if not isinstance(rate_forward, (Parameter, Expression)):
            raise ValueError(
                f"rate_forward value for '{name}' must be a Parameter or Expression"
            )
        if (rate_reverse is not None) and not isinstance(
            rate_forward, (Parameter, Expression)
        ):
            raise ValueError(
                f"rate_reverse value for '{name}' must be a Parameter or Expression"
            )
        return (rxp, rate_forward, rate_reverse)

    @log_event(log_args=True, log_result=True, static_method=True)
    @staticmethod
    def create_component(name, rxp, rate_forward, rate_reverse):
        """
        Create a rule component.

        Parameters
        ----------
        name : str
            Name of the rule.
        rxp : pysb.RuleExpression
            The rule expression.
        rate_forward : Parameter or Expression
            Forward rate parameter or expression.
        rate_reverse : Parameter or Expression or None
            Reverse rate parameter or expression (if reversible).

        Returns
        -------
        Rule
            The created rule.
        """
        # Create a Rule object with the provided arguments
        rule = Rule(name, rxp, rate_forward, rate_reverse)
        return rule

create_component(name, rxp, rate_forward, rate_reverse) staticmethod

Create a rule component.

Parameters:

Name Type Description Default
name str

Name of the rule.

required
rxp RuleExpression

The rule expression.

required
rate_forward Parameter or Expression

Forward rate parameter or expression.

required
rate_reverse Parameter or Expression or None

Reverse rate parameter or expression (if reversible).

required

Returns:

Type Description
Rule

The created rule.

Source code in qspy\contexts\contexts.py
@log_event(log_args=True, log_result=True, static_method=True)
@staticmethod
def create_component(name, rxp, rate_forward, rate_reverse):
    """
    Create a rule component.

    Parameters
    ----------
    name : str
        Name of the rule.
    rxp : pysb.RuleExpression
        The rule expression.
    rate_forward : Parameter or Expression
        Forward rate parameter or expression.
    rate_reverse : Parameter or Expression or None
        Reverse rate parameter or expression (if reversible).

    Returns
    -------
    Rule
        The created rule.
    """
    # Create a Rule object with the provided arguments
    rule = Rule(name, rxp, rate_forward, rate_reverse)
    return rule

initials()

Context manager for defining initial conditions in a QSPy model.

Tracks which initials are added within the context and logs them.

Yields:

Type Description
None
Source code in qspy\contexts\contexts.py
@contextmanager
def initials():
    """
    Context manager for defining initial conditions in a QSPy model.

    Tracks which initials are added within the context and logs them.

    Yields
    ------
    None
    """
    import logging
    from pysb.core import SelfExporter
    from qspy.config import LOGGER_NAME
    from qspy.utils.logging import ensure_qspy_logging

    ensure_qspy_logging()
    logger = logging.getLogger(LOGGER_NAME)
    model = SelfExporter.default_model

    # Record the set of initial names before entering the context
    initials_before = set(str(init.pattern) for init in model.initials)
    logger.info("[QSPy] Entering initials context manager")
    try:
        yield
    finally:
        # Record the set of initial names after exiting the context
        initials_after = set(str(init.pattern) for init in model.initials)
        added = initials_after - initials_before
        if added:
            # Log the names of newly added initials
            added_initials = [
                init for init in model.initials if str(init.pattern) in added
            ]
            logger.info(f"[QSPy] Initials added in context: {added_initials}")
        else:
            logger.info("[QSPy] No new initials added")

macros()

Context manager for managing macros in a QSPy model.

Yields:

Type Description
None
Source code in qspy\contexts\contexts.py
@contextmanager
def macros():
    """
    Context manager for managing macros in a QSPy model.

    Yields
    ------
    None
    """
    import logging
    from pysb.core import SelfExporter
    from qspy.config import LOGGER_NAME
    from qspy.utils.logging import ensure_qspy_logging

    ensure_qspy_logging()
    logger = logging.getLogger(LOGGER_NAME)
    model = SelfExporter.default_model

    componenents_before = set(model.components.keys())

    logger.info("[QSPy] Entering macros context manager")
    try:
        yield
    finally:
        components_after = set(model.components.keys())
        added = components_after - componenents_before
        if added:
            # Log the names of newly added components
            added_components = [comp for comp in model.components if comp.name in added]
            logger.info(f"[QSPy] Components added in context: {added_components}")
        else:
            logger.info("[QSPy] No new components added")
    return

observables()

Context manager for defining observables in a QSPy model.

Yields:

Type Description
None
Source code in qspy\contexts\contexts.py
@contextmanager
def observables():
    """
    Context manager for defining observables in a QSPy model.

    Yields
    ------
    None
    """
    import logging
    from pysb.core import SelfExporter
    from qspy.config import LOGGER_NAME
    from qspy.utils.logging import ensure_qspy_logging

    ensure_qspy_logging()
    logger = logging.getLogger(LOGGER_NAME)
    model = SelfExporter.default_model

    # Record the set of observable names before entering the context
    observables_before = set(obs.name for obs in model.observables)
    logger.info("[QSPy] Entering observables context manager")
    try:
        yield
    finally:
        # Record the set of observable names after exiting the context
        observables_after = set(init.name for init in model.observables)
        added = observables_after - observables_before
        if added:
            # Log the names of newly added observables
            added_observables = [obs for obs in model.observables if obs.name in added]
            logger.info(f"[QSPy] Observables added in context: {added_observables}")
        else:
            logger.info("[QSPy] No new observables added")

qspy.contexts.base

QSPy Base Context Infrastructure

This module provides the abstract base class for QSPy context managers, which enable structured and validated construction of model components (parameters, monomers, compartments, etc.) in a PySB/QSPy model. The ComponentContext class handles introspection, variable tracking, and component injection for both manual and automatic (module-level) usage.

Classes:

Name Description
ComponentContext : Abstract base class for all QSPy context managers.

Examples:

>>> class MyContext(ComponentContext):
...     def create_component(self, name, *args):
...         # Custom creation logic
...         pass
...
>>> with MyContext():
...     my_param = (1.0, "1/min")

ComponentContext

Bases: ABC

Abstract base class for QSPy context managers.

Handles variable introspection, manual and automatic component addition, and provides a template for context-managed model construction.

Parameters:

Name Type Description Default
manual bool

If True, enables manual mode for explicit component addition (default: False).

False
verbose bool

If True, prints verbose output during component addition (default: False).

False

Attributes:

Name Type Description
component_name str

Name of the component type (e.g., 'parameter', 'monomer').

model Model

The active PySB model instance.

_manual_adds list

List of manually added components (used in manual mode).

_frame frame

Reference to the caller's frame for introspection.

_locals_before dict

Snapshot of local variables before entering the context.

_override bool

If True, disables module-scope enforcement.

Methods:

Name Description
__enter__

Enter the context, track local variables, and enforce module scope.

__exit__

Exit the context, detect new variables, validate, and add components.

__call__

Add a component manually (manual mode only).

_add_component

Validate and add a component to the model.

_validate_value

Validate or transform the right-hand-side value (override in subclasses).

create_component

Abstract method to create a component (must be implemented in subclasses).

add_component

Add the component to the model.

Source code in qspy\contexts\base.py
class ComponentContext(ABC):
    """
    Abstract base class for QSPy context managers.

    Handles variable introspection, manual and automatic component addition,
    and provides a template for context-managed model construction.

    Parameters
    ----------
    manual : bool, optional
        If True, enables manual mode for explicit component addition (default: False).
    verbose : bool, optional
        If True, prints verbose output during component addition (default: False).

    Attributes
    ----------
    component_name : str
        Name of the component type (e.g., 'parameter', 'monomer').
    model : pysb.Model
        The active PySB model instance.
    _manual_adds : list
        List of manually added components (used in manual mode).
    _frame : frame
        Reference to the caller's frame for introspection.
    _locals_before : dict
        Snapshot of local variables before entering the context.
    _override : bool
        If True, disables module-scope enforcement.

    Methods
    -------
    __enter__()
        Enter the context, track local variables, and enforce module scope.
    __exit__(exc_type, exc_val, exc_tb)
        Exit the context, detect new variables, validate, and add components.
    __call__(name, *args)
        Add a component manually (manual mode only).
    _add_component(name, *args)
        Validate and add a component to the model.
    _validate_value(name, val)
        Validate or transform the right-hand-side value (override in subclasses).
    create_component(name, *args)
        Abstract method to create a component (must be implemented in subclasses).
    add_component(component)
        Add the component to the model.
    """

    component_name = "component"  # e.g. 'parameter', 'monomer'

    def __init__(self, manual: bool = False, verbose: bool = False):
        """
        Initialize the context manager.

        Parameters
        ----------
        manual : bool, optional
            If True, enables manual mode for explicit component addition (default: False).
        verbose : bool, optional
            If True, prints verbose output during component addition (default: False).
        """
        self.manual = manual
        self.verbose = verbose
        self._manual_adds = []
        self._frame = None
        self._locals_before = None
        # self.components = ComponentSet()
        self.model = SelfExporter.default_model
        self._override = False

    def __enter__(self):
        """
        Enter the context manager.

        Tracks local variables for automatic detection of new assignments.
        Enforces module-level usage unless manual mode or override is enabled.

        Returns
        -------
        self or None
            Returns self in manual mode, otherwise None.

        Raises
        ------
        RuntimeError
            If no active model is found or used outside module scope.
        """
        ensure_qspy_logging()
        logger = logging.getLogger(LOGGER_NAME)
        try:
            logger.info(f"[QSPy] Entering context: {self.__class__.__name__}")
            if self.model is None:
                logger.error("No active model found. Did you instantiate a Model()?")
                raise RuntimeError(
                    "No active model found. Did you instantiate a Model()?"
                )

            if self._override:
                self._frame = inspect.currentframe().f_back.f_back
            else:
                self._frame = inspect.currentframe().f_back

            # Require module-level use for introspection mode
            if (
                (not self.manual)
                and (self._frame.f_globals is not self._frame.f_locals)
            ) and (not self._override):
                logger.error(
                    f"{self.__class__.__name__} must be used at module scope. "
                    f"Wrap model components in a module-level script."
                )
                raise RuntimeError(
                    f"{self.__class__.__name__} must be used at module scope. "
                    f"Wrap model components in a module-level script."
                )

            if not self.manual:
                # Filter out model components and context objects before deepcopy

                filtered_locals = {
                    k: v
                    for k, v in self._frame.f_locals.items()
                    if not ((hasattr(v, "__class__") and k in SKIP_TYPES) or is_module(v))
                }
                # print(filtered_locals)
                for key in filtered_locals:
                    logger.debug(f"Local variable: {key}")
                self._locals_before = copy.deepcopy(filtered_locals)

            return self if self.manual else None
        except Exception as e:
            logger.error(f"[QSPy][ERROR] Exception on entering context: {e}")
            raise

    def __exit__(self, exc_type, exc_val, exc_tb):
        """
        Exit the context manager.

        Detects new variables, validates, and adds components to the model.
        In manual mode, adds components explicitly provided via __call__.

        Parameters
        ----------
        exc_type : type
            Exception type, if any.
        exc_val : Exception
            Exception value, if any.
        exc_tb : traceback
            Traceback, if any.

        Returns
        -------
        None
        """

        logger = logging.getLogger(LOGGER_NAME)
        try:
            logger.info(f"[QSPy] Exiting context: {self.__class__.__name__}")
            if self.manual:
                for name, *args in self._manual_adds:
                    self._add_component(name, *args)
            else:
                # Filter out model components and context objects before comparison

                filtered_locals = {
                    k: v
                    for k, v in self._frame.f_locals.items()
                    if not ((hasattr(v, "__class__") and k in SKIP_TYPES) or is_module(v))
                }
                new_vars = set(filtered_locals.keys()) - set(self._locals_before.keys())

                for var_name in new_vars:
                    val = filtered_locals[var_name]
                    args = self._validate_value(var_name, val)
                    # Remove the name from the frame locals so it
                    # can be re-added as the component.
                    del self._frame.f_locals[var_name]
                    self._add_component(var_name, *args)
            # for component in self.components:
            #     if component.name in set(self._frame.f_locals.keys()):
            #         self._frame.f_locals[component.name] = component
        except Exception as e:
            logger.error(f"[QSPy][ERROR] Exception on exiting context: {e}")

    def __call__(self, name, *args):
        """
        Add a component manually (manual mode only).

        Parameters
        ----------
        name : str
            Name of the component.
        *args
            Arguments for component creation.

        Raises
        ------
        RuntimeError
            If called when manual mode is not enabled.
        """
        if not self.manual:
            raise RuntimeError(
                f"Manual mode is not enabled for this {self.__class__.__name__}"
            )
        self._manual_adds.append((name, *args))

    def _add_component(self, name, *args):
        """
        Validate and add a component to the model.

        Parameters
        ----------
        name : str
            Name of the component.
        *args
            Arguments for component creation.

        Raises
        ------
        ValueError
            If the component already exists in the model.
        """
        if name in self.model.component_names:
            raise ValueError(
                f"{self.component_name.capitalize()} '{name}' already exists in the model."
            )

        component = self.create_component(name, *args)
        self.add_component(component)

        if self.verbose:
            print(f"[{self.component_name}] Added: {name} with args: {args}")

    def _validate_value(self, name, val):
        """
        Validate or transform the right-hand-side value.

        Override in subclasses if custom validation or transformation is needed.

        Parameters
        ----------
        name : str
            Name of the variable.
        val : object
            Value assigned to the variable.

        Returns
        -------
        tuple
            Arguments to be passed to create_component.

        Raises
        ------
        ValueError
            If the value is not a tuple.
        """
        if not isinstance(val, tuple):
            raise ValueError(
                f"{self.component_name.capitalize()} '{name}' must be defined as a tuple."
            )
        return val

    @abstractmethod
    def create_component(self, name, *args):
        """
        Abstract method to create a component.

        Must be implemented in subclasses.

        Parameters
        ----------
        name : str
            Name of the component.
        *args
            Arguments for component creation.

        Raises
        ------
        NotImplementedError
            Always, unless implemented in subclass.
        """
        raise NotImplementedError

    def add_component(self, component):
        """
        Add the component to the model.

        Parameters
        ----------
        component : pysb.Component
            The component to add.
        """
        # self.components.add(component)
        self.model.add_component(component)

__call__(name, *args)

Add a component manually (manual mode only).

Parameters:

Name Type Description Default
name str

Name of the component.

required
*args

Arguments for component creation.

()

Raises:

Type Description
RuntimeError

If called when manual mode is not enabled.

Source code in qspy\contexts\base.py
def __call__(self, name, *args):
    """
    Add a component manually (manual mode only).

    Parameters
    ----------
    name : str
        Name of the component.
    *args
        Arguments for component creation.

    Raises
    ------
    RuntimeError
        If called when manual mode is not enabled.
    """
    if not self.manual:
        raise RuntimeError(
            f"Manual mode is not enabled for this {self.__class__.__name__}"
        )
    self._manual_adds.append((name, *args))

__enter__()

Enter the context manager.

Tracks local variables for automatic detection of new assignments. Enforces module-level usage unless manual mode or override is enabled.

Returns:

Type Description
self or None

Returns self in manual mode, otherwise None.

Raises:

Type Description
RuntimeError

If no active model is found or used outside module scope.

Source code in qspy\contexts\base.py
def __enter__(self):
    """
    Enter the context manager.

    Tracks local variables for automatic detection of new assignments.
    Enforces module-level usage unless manual mode or override is enabled.

    Returns
    -------
    self or None
        Returns self in manual mode, otherwise None.

    Raises
    ------
    RuntimeError
        If no active model is found or used outside module scope.
    """
    ensure_qspy_logging()
    logger = logging.getLogger(LOGGER_NAME)
    try:
        logger.info(f"[QSPy] Entering context: {self.__class__.__name__}")
        if self.model is None:
            logger.error("No active model found. Did you instantiate a Model()?")
            raise RuntimeError(
                "No active model found. Did you instantiate a Model()?"
            )

        if self._override:
            self._frame = inspect.currentframe().f_back.f_back
        else:
            self._frame = inspect.currentframe().f_back

        # Require module-level use for introspection mode
        if (
            (not self.manual)
            and (self._frame.f_globals is not self._frame.f_locals)
        ) and (not self._override):
            logger.error(
                f"{self.__class__.__name__} must be used at module scope. "
                f"Wrap model components in a module-level script."
            )
            raise RuntimeError(
                f"{self.__class__.__name__} must be used at module scope. "
                f"Wrap model components in a module-level script."
            )

        if not self.manual:
            # Filter out model components and context objects before deepcopy

            filtered_locals = {
                k: v
                for k, v in self._frame.f_locals.items()
                if not ((hasattr(v, "__class__") and k in SKIP_TYPES) or is_module(v))
            }
            # print(filtered_locals)
            for key in filtered_locals:
                logger.debug(f"Local variable: {key}")
            self._locals_before = copy.deepcopy(filtered_locals)

        return self if self.manual else None
    except Exception as e:
        logger.error(f"[QSPy][ERROR] Exception on entering context: {e}")
        raise

__exit__(exc_type, exc_val, exc_tb)

Exit the context manager.

Detects new variables, validates, and adds components to the model. In manual mode, adds components explicitly provided via call.

Parameters:

Name Type Description Default
exc_type type

Exception type, if any.

required
exc_val Exception

Exception value, if any.

required
exc_tb traceback

Traceback, if any.

required

Returns:

Type Description
None
Source code in qspy\contexts\base.py
def __exit__(self, exc_type, exc_val, exc_tb):
    """
    Exit the context manager.

    Detects new variables, validates, and adds components to the model.
    In manual mode, adds components explicitly provided via __call__.

    Parameters
    ----------
    exc_type : type
        Exception type, if any.
    exc_val : Exception
        Exception value, if any.
    exc_tb : traceback
        Traceback, if any.

    Returns
    -------
    None
    """

    logger = logging.getLogger(LOGGER_NAME)
    try:
        logger.info(f"[QSPy] Exiting context: {self.__class__.__name__}")
        if self.manual:
            for name, *args in self._manual_adds:
                self._add_component(name, *args)
        else:
            # Filter out model components and context objects before comparison

            filtered_locals = {
                k: v
                for k, v in self._frame.f_locals.items()
                if not ((hasattr(v, "__class__") and k in SKIP_TYPES) or is_module(v))
            }
            new_vars = set(filtered_locals.keys()) - set(self._locals_before.keys())

            for var_name in new_vars:
                val = filtered_locals[var_name]
                args = self._validate_value(var_name, val)
                # Remove the name from the frame locals so it
                # can be re-added as the component.
                del self._frame.f_locals[var_name]
                self._add_component(var_name, *args)
        # for component in self.components:
        #     if component.name in set(self._frame.f_locals.keys()):
        #         self._frame.f_locals[component.name] = component
    except Exception as e:
        logger.error(f"[QSPy][ERROR] Exception on exiting context: {e}")

__init__(manual=False, verbose=False)

Initialize the context manager.

Parameters:

Name Type Description Default
manual bool

If True, enables manual mode for explicit component addition (default: False).

False
verbose bool

If True, prints verbose output during component addition (default: False).

False
Source code in qspy\contexts\base.py
def __init__(self, manual: bool = False, verbose: bool = False):
    """
    Initialize the context manager.

    Parameters
    ----------
    manual : bool, optional
        If True, enables manual mode for explicit component addition (default: False).
    verbose : bool, optional
        If True, prints verbose output during component addition (default: False).
    """
    self.manual = manual
    self.verbose = verbose
    self._manual_adds = []
    self._frame = None
    self._locals_before = None
    # self.components = ComponentSet()
    self.model = SelfExporter.default_model
    self._override = False

add_component(component)

Add the component to the model.

Parameters:

Name Type Description Default
component Component

The component to add.

required
Source code in qspy\contexts\base.py
def add_component(self, component):
    """
    Add the component to the model.

    Parameters
    ----------
    component : pysb.Component
        The component to add.
    """
    # self.components.add(component)
    self.model.add_component(component)

create_component(name, *args) abstractmethod

Abstract method to create a component.

Must be implemented in subclasses.

Parameters:

Name Type Description Default
name str

Name of the component.

required
*args

Arguments for component creation.

()

Raises:

Type Description
NotImplementedError

Always, unless implemented in subclass.

Source code in qspy\contexts\base.py
@abstractmethod
def create_component(self, name, *args):
    """
    Abstract method to create a component.

    Must be implemented in subclasses.

    Parameters
    ----------
    name : str
        Name of the component.
    *args
        Arguments for component creation.

    Raises
    ------
    NotImplementedError
        Always, unless implemented in subclass.
    """
    raise NotImplementedError

is_module(obj)

Check if the object is a module.

Parameters:

Name Type Description Default
obj object

The object to check.

required

Returns:

Type Description
bool

True if the object is a module, False otherwise.

Source code in qspy\contexts\base.py
def is_module(obj):
    """
    Check if the object is a module.

    Parameters
    ----------
    obj : object
        The object to check.

    Returns
    -------
    bool
        True if the object is a module, False otherwise.
    """
    return isinstance(obj, ModuleType)

qspy.functionaltags

Functional Tagging Utilities for QSP Model Components

This module provides standardized functional tag definitions and utilities for semantic annotation of model components in quantitative systems pharmacology (QSP) models. Tags are constructed as canonical strings (e.g., "protein::ligand") that combine a molecular class and a functional subclass, enabling consistent labeling, introspection, and validation of model entities.

Classes and Enums
  • FunctionalTag : Dataclass for representing and parsing functional tags.
  • PROTEIN : Enum of common protein roles (e.g., ligand, receptor, kinase).
  • DRUG : Enum of drug roles (e.g., inhibitor, agonist, antibody).
  • RNA : Enum of RNA roles (e.g., messenger, micro, siRNA).
  • DNA : Enum of DNA roles (e.g., gene, promoter, enhancer).
  • METABOLITE : Enum of metabolite roles (e.g., substrate, product, cofactor).
  • LIPID : Enum of lipid roles (e.g., phospholipid, sterol).
  • ION : Enum of ion types (e.g., Ca2+, Na+, K+).
  • NANOPARTICLE : Enum of nanoparticle roles (e.g., drug delivery, imaging).

Functions:

Name Description
- prefixer : Utility to construct canonical tag strings from class and function labels.

Examples:

>>> from qspy.functionaltags import FunctionalTag, PROTEIN
>>> tag = FunctionalTag("protein", "ligand")
>>> tag.value
'protein::ligand'
>>> PROTEIN.KINASE.value
'protein::kinase'
>>> FunctionalTag.parse("drug::inhibitor")
('drug', 'inhibitor')

DNA

Bases: Enum

Functional tag definitions for DNA-based monomer classes.

This enum provides standardized semantic tags for common DNA roles in quantitative systems pharmacology models. Each member is constructed using the prefixer utility to combine the DNA_PREFIX with a functional subclass label, producing values like "dna::gene".

Members

GENE : str A gene region. PROMOTER : str A promoter region. ENHANCER : str An enhancer region.

Examples:

>>> DNA.GENE.value
'dna::gene'
>>> tag = FunctionalTag.parse(DNA.PROMOTER.value)
('dna', 'promoter')
Source code in qspy\functionaltags.py
class DNA(Enum):
    """
    Functional tag definitions for DNA-based monomer classes.

    This enum provides standardized semantic tags for common DNA roles
    in quantitative systems pharmacology models. Each member is constructed
    using the `prefixer` utility to combine the `DNA_PREFIX` with a
    functional subclass label, producing values like "dna::gene".

    Members
    -------
    GENE : str
        A gene region.
    PROMOTER : str
        A promoter region.
    ENHANCER : str
        An enhancer region.

    Examples
    --------
    >>> DNA.GENE.value
    'dna::gene'

    >>> tag = FunctionalTag.parse(DNA.PROMOTER.value)
    ('dna', 'promoter')
    """

    GENE = prefixer("gene", DNA_PREFIX)
    PROMOTER = prefixer("promoter", DNA_PREFIX)
    ENHANCER = prefixer("enhancer", DNA_PREFIX)

DRUG

Bases: Enum

Functional tag definitions for drug-based monomer classes.

This enum provides standardized semantic tags for common drug roles in quantitative systems pharmacology models. Each member is constructed using the prefixer utility to combine the DRUG_PREFIX with a functional subclass label, producing values like "drug::inhibitor".

These tags are used in monomer definitions to support validation, introspection, and expressive annotation of pharmacological roles.

Members

SMALL_MOLECULE : str A low molecular weight compound, typically orally bioavailable. BIOLOGIC : str A therapeutic product derived from biological sources. ANTIBODY : str An immunoglobulin-based therapeutic. MAB : str A monoclonal antibody. INHIBITOR : str A molecule that inhibits a biological process or target. AGONIST : str A molecule that activates a receptor or pathway. ANTAGONIST : str A molecule that blocks or dampens a biological response. INVERSE_AGONIST : str A molecule that induces the opposite effect of an agonist. MODULATOR : str A molecule that modulates the activity of a target. ADC : str An antibody-drug conjugate. RLT : str A radioligand therapy agent. PROTAC : str A proteolysis targeting chimera. IMUNNOTHERAPY : str An agent used in immunotherapy. CHEMOTHERAPY : str A cytotoxic agent used in chemotherapy.

Examples:

>>> DRUG.INHIBITOR.value
'drug::inhibitor'
>>> tag = FunctionalTag.parse(DRUG.ANTIBODY.value)
('drug', 'antibody')
Source code in qspy\functionaltags.py
class DRUG(Enum):
    """
    Functional tag definitions for drug-based monomer classes.

    This enum provides standardized semantic tags for common drug roles
    in quantitative systems pharmacology models. Each member is constructed
    using the `prefixer` utility to combine the `DRUG_PREFIX` with a
    functional subclass label, producing values like "drug::inhibitor".

    These tags are used in monomer definitions to support validation,
    introspection, and expressive annotation of pharmacological roles.

    Members
    -------
    SMALL_MOLECULE : str
        A low molecular weight compound, typically orally bioavailable.
    BIOLOGIC : str
        A therapeutic product derived from biological sources.
    ANTIBODY : str
        An immunoglobulin-based therapeutic.
    MAB : str
        A monoclonal antibody.
    INHIBITOR : str
        A molecule that inhibits a biological process or target.
    AGONIST : str
        A molecule that activates a receptor or pathway.
    ANTAGONIST : str
        A molecule that blocks or dampens a biological response.
    INVERSE_AGONIST : str
        A molecule that induces the opposite effect of an agonist.
    MODULATOR : str
        A molecule that modulates the activity of a target.
    ADC : str
        An antibody-drug conjugate.
    RLT : str
        A radioligand therapy agent.
    PROTAC : str
        A proteolysis targeting chimera.
    IMUNNOTHERAPY : str
        An agent used in immunotherapy.
    CHEMOTHERAPY : str
        A cytotoxic agent used in chemotherapy.

    Examples
    --------
    >>> DRUG.INHIBITOR.value
    'drug::inhibitor'

    >>> tag = FunctionalTag.parse(DRUG.ANTIBODY.value)
    ('drug', 'antibody')
    """

    SMALL_MOLECULE = prefixer("small_molecule", DRUG_PREFIX)
    BIOLOGIC = prefixer("biologic", DRUG_PREFIX)
    ANTIBODY = prefixer("antibody", DRUG_PREFIX)
    MAB = prefixer("monoclonal_antibody", DRUG_PREFIX)
    INHIBITOR = prefixer("inhibitor", DRUG_PREFIX)
    AGONIST = prefixer("agonist", DRUG_PREFIX)
    ANTAGONIST = prefixer("antagonist", DRUG_PREFIX)
    INVERSE_AGONIST = prefixer("inverse_agonist", DRUG_PREFIX)
    MODULATOR = prefixer("modulator", DRUG_PREFIX)
    ADC = prefixer("antibody_drug_conjugate", DRUG_PREFIX)
    RLT = prefixer("radioligand_therapy", DRUG_PREFIX)
    PROTAC = prefixer("protac", DRUG_PREFIX)
    IMUNNOTHERAPY = prefixer("immunotherapy", DRUG_PREFIX)
    CHEMOTHERAPY = prefixer("chemotherapy", DRUG_PREFIX)

FunctionalTag dataclass

Represents a functional tag for labeling monomers with semantic class/function metadata.

A functional tag captures both a high-level molecular class (e.g., 'protein', 'rna') and a subclass or functional role (e.g., 'ligand', 'receptor'). These tags enable semantic annotation of model components to support introspection, filtering, and validation workflows.

Parameters:

Name Type Description Default
class_ str

The molecular class label (e.g., 'protein').

required
function str

The functional or subclass label (e.g., 'receptor').

required

Attributes:

Name Type Description
value str

The canonical string representation of the tag (e.g., "protein__receptor"). This is derived by prefixing the function with its class using the defined separator.

Methods:

Name Description
__eq__

Compares functional tags by class and function. Supports comparison with other FunctionalTag instances or Enum-based tag values.

parse

Parses a canonical tag string into its (class, function) components.

Examples:

>>> tag = FunctionalTag("protein", "ligand")
>>> tag.value
'protein::ligand'
>>> FunctionalTag.parse("rna::micro")
('rna', 'micro')
Source code in qspy\functionaltags.py
@dataclass(frozen=True)
class FunctionalTag:
    """
    Represents a functional tag for labeling monomers with semantic class/function metadata.

    A functional tag captures both a high-level molecular class (e.g., 'protein', 'rna') and a
    subclass or functional role (e.g., 'ligand', 'receptor'). These tags enable semantic annotation
    of model components to support introspection, filtering, and validation workflows.

    Parameters
    ----------
    class_ : str
        The molecular class label (e.g., 'protein').
    function : str
        The functional or subclass label (e.g., 'receptor').

    Attributes
    ----------
    value : str
        The canonical string representation of the tag (e.g., "protein__receptor").
        This is derived by prefixing the function with its class using the defined separator.

    Methods
    -------
    __eq__(other)
        Compares functional tags by class and function. Supports comparison with
        other FunctionalTag instances or Enum-based tag values.
    parse(prefix_tag : str) -> Tuple[str, str]
        Parses a canonical tag string into its (class, function) components.

    Examples
    --------
    >>> tag = FunctionalTag("protein", "ligand")
    >>> tag.value
    'protein::ligand'

    >>> FunctionalTag.parse("rna::micro")
    ('rna', 'micro')
    """

    class_: str
    function: str

    def __eq__(self, other):
        if isinstance(other, FunctionalTag):
            return (self.class_, self.function) == (other.class_, other.function)
        elif isinstance(other, Enum):
            return (self.class_, self.function) == self.parse(other.value)
        else:
            return False

    @property
    def value(self):
        """
        str: The canonical string representation of the functional tag.

        Returns the tag as a string in the format "<class>::<function>", e.g., "protein::ligand".

        Examples
        --------
        >>> tag = FunctionalTag("protein", "ligand")
        >>> tag.value
        'protein::ligand'
        """
        return prefixer(self.function, self.class_)

    @staticmethod
    def parse(prefix_tag: str):
        """
        Parse a canonical tag string into its class and function components.

        Parameters
        ----------
        prefix_tag : str
            The canonical tag string (e.g., "protein::ligand").

        Returns
        -------
        tuple of (str, str)
            The class and function components as a tuple.

        Examples
        --------
        >>> FunctionalTag.parse("protein::kinase")
        ('protein', 'kinase')
        """
        class_, function = prefix_tag.split(TAG_SEP)
        return class_, function

value property

str: The canonical string representation of the functional tag.

Returns the tag as a string in the format "::", e.g., "protein::ligand".

Examples:

>>> tag = FunctionalTag("protein", "ligand")
>>> tag.value
'protein::ligand'

parse(prefix_tag) staticmethod

Parse a canonical tag string into its class and function components.

Parameters:

Name Type Description Default
prefix_tag str

The canonical tag string (e.g., "protein::ligand").

required

Returns:

Type Description
tuple of (str, str)

The class and function components as a tuple.

Examples:

>>> FunctionalTag.parse("protein::kinase")
('protein', 'kinase')
Source code in qspy\functionaltags.py
@staticmethod
def parse(prefix_tag: str):
    """
    Parse a canonical tag string into its class and function components.

    Parameters
    ----------
    prefix_tag : str
        The canonical tag string (e.g., "protein::ligand").

    Returns
    -------
    tuple of (str, str)
        The class and function components as a tuple.

    Examples
    --------
    >>> FunctionalTag.parse("protein::kinase")
    ('protein', 'kinase')
    """
    class_, function = prefix_tag.split(TAG_SEP)
    return class_, function

ION

Bases: Enum

Functional tag definitions for ion-based monomer classes.

This enum provides standardized semantic tags for common ion types in quantitative systems pharmacology models. Each member is constructed using the prefixer utility to combine the ION_PREFIX with a functional subclass label, producing values like "ion::ca2+".

Members

CALCIUM : str Calcium ion (Ca2+). POTASSIUM : str Potassium ion (K+). SODIUM : str Sodium ion (Na+). CHLORIDE : str Chloride ion (Cl-). MAGNESIUM : str Magnesium ion (Mg2+).

Examples:

>>> ION.CALCIUM.value
'ion::ca2+'
>>> tag = FunctionalTag.parse(ION.SODIUM.value)
('ion', 'na+')
Source code in qspy\functionaltags.py
class ION(Enum):
    """
    Functional tag definitions for ion-based monomer classes.

    This enum provides standardized semantic tags for common ion types
    in quantitative systems pharmacology models. Each member is constructed
    using the `prefixer` utility to combine the `ION_PREFIX` with a
    functional subclass label, producing values like "ion::ca2+".

    Members
    -------
    CALCIUM : str
        Calcium ion (Ca2+).
    POTASSIUM : str
        Potassium ion (K+).
    SODIUM : str
        Sodium ion (Na+).
    CHLORIDE : str
        Chloride ion (Cl-).
    MAGNESIUM : str
        Magnesium ion (Mg2+).

    Examples
    --------
    >>> ION.CALCIUM.value
    'ion::ca2+'

    >>> tag = FunctionalTag.parse(ION.SODIUM.value)
    ('ion', 'na+')
    """

    CALCIUM = prefixer("ca2+", ION_PREFIX)
    POTASSIUM = prefixer("k+", ION_PREFIX)
    SODIUM = prefixer("na+", ION_PREFIX)
    CHLORIDE = prefixer("cl-", ION_PREFIX)
    MAGNESIUM = prefixer("mg2+", ION_PREFIX)

LIPID

Bases: Enum

Functional tag definitions for lipid-based monomer classes.

This enum provides standardized semantic tags for common lipid roles in quantitative systems pharmacology models. Each member is constructed using the prefixer utility to combine the LIPID_PREFIX with a functional subclass label, producing values like "lipid::phospholipid".

Members

PHOSPHOLIPID : str A phospholipid molecule. GLYCOLIPID : str A glycolipid molecule. STEROL : str A sterol molecule. EICOSANOID : str An eicosanoid molecule.

Examples:

>>> LIPID.PHOSPHOLIPID.value
'lipid::phospholipid'
>>> tag = FunctionalTag.parse(LIPID.STEROL.value)
('lipid', 'sterol')
Source code in qspy\functionaltags.py
class LIPID(Enum):
    """
    Functional tag definitions for lipid-based monomer classes.

    This enum provides standardized semantic tags for common lipid roles
    in quantitative systems pharmacology models. Each member is constructed
    using the `prefixer` utility to combine the `LIPID_PREFIX` with a
    functional subclass label, producing values like "lipid::phospholipid".

    Members
    -------
    PHOSPHOLIPID : str
        A phospholipid molecule.
    GLYCOLIPID : str
        A glycolipid molecule.
    STEROL : str
        A sterol molecule.
    EICOSANOID : str
        An eicosanoid molecule.

    Examples
    --------
    >>> LIPID.PHOSPHOLIPID.value
    'lipid::phospholipid'

    >>> tag = FunctionalTag.parse(LIPID.STEROL.value)
    ('lipid', 'sterol')
    """

    PHOSPHOLIPID = prefixer("phospholipid", LIPID_PREFIX)
    GLYCOLIPID = prefixer("glycolipid", LIPID_PREFIX)
    STEROL = prefixer("sterol", LIPID_PREFIX)
    EICOSANOID = prefixer("eicosanoid", LIPID_PREFIX)

METABOLITE

Bases: Enum

Functional tag definitions for metabolite-based monomer classes.

This enum provides standardized semantic tags for common metabolite roles in quantitative systems pharmacology models. Each member is constructed using the prefixer utility to combine the METABOLITE_PREFIX with a functional subclass label, producing values like "metabolite::substrate".

Members

SUBSTRATE : str A substrate molecule in a metabolic reaction. PRODUCT : str A product molecule in a metabolic reaction. COFACTOR : str A cofactor required for enzyme activity.

Examples:

>>> METABOLITE.SUBSTRATE.value
'metabolite::substrate'
>>> tag = FunctionalTag.parse(METABOLITE.PRODUCT.value)
('metabolite', 'product')
Source code in qspy\functionaltags.py
class METABOLITE(Enum):
    """
    Functional tag definitions for metabolite-based monomer classes.

    This enum provides standardized semantic tags for common metabolite roles
    in quantitative systems pharmacology models. Each member is constructed
    using the `prefixer` utility to combine the `METABOLITE_PREFIX` with a
    functional subclass label, producing values like "metabolite::substrate".

    Members
    -------
    SUBSTRATE : str
        A substrate molecule in a metabolic reaction.
    PRODUCT : str
        A product molecule in a metabolic reaction.
    COFACTOR : str
        A cofactor required for enzyme activity.

    Examples
    --------
    >>> METABOLITE.SUBSTRATE.value
    'metabolite::substrate'

    >>> tag = FunctionalTag.parse(METABOLITE.PRODUCT.value)
    ('metabolite', 'product')
    """

    SUBSTRATE = prefixer("substrate", METABOLITE_PREFIX)
    PRODUCT = prefixer("product", METABOLITE_PREFIX)
    COFACTOR = prefixer("cofactor", METABOLITE_PREFIX)

NANOPARTICLE

Bases: Enum

Functional tag definitions for nanoparticle-based monomer classes.

This enum provides standardized semantic tags for common nanoparticle roles in quantitative systems pharmacology models. Each member is constructed using the prefixer utility to combine the NANOPARTICLE_PREFIX with a functional subclass label, producing values like "nanoparticle::imaging".

Members

DRUG_DELIVERY : str Nanoparticle for drug delivery. THERMAL : str Photothermal nanoparticle. IMAGING : str Nanoparticle for imaging. SENSING : str Nanoparticle for sensing. THERANOSTIC : str Theranostic nanoparticle.

Examples:

>>> NANOPARTICLE.DRUG_DELIVERY.value
'nanoparticle::drug_delivery'
>>> tag = FunctionalTag.parse(NANOPARTICLE.IMAGING.value)
('nanoparticle', 'imaging')
Source code in qspy\functionaltags.py
class NANOPARTICLE(Enum):
    """
    Functional tag definitions for nanoparticle-based monomer classes.

    This enum provides standardized semantic tags for common nanoparticle roles
    in quantitative systems pharmacology models. Each member is constructed
    using the `prefixer` utility to combine the `NANOPARTICLE_PREFIX` with a
    functional subclass label, producing values like "nanoparticle::imaging".

    Members
    -------
    DRUG_DELIVERY : str
        Nanoparticle for drug delivery.
    THERMAL : str
        Photothermal nanoparticle.
    IMAGING : str
        Nanoparticle for imaging.
    SENSING : str
        Nanoparticle for sensing.
    THERANOSTIC : str
        Theranostic nanoparticle.

    Examples
    --------
    >>> NANOPARTICLE.DRUG_DELIVERY.value
    'nanoparticle::drug_delivery'

    >>> tag = FunctionalTag.parse(NANOPARTICLE.IMAGING.value)
    ('nanoparticle', 'imaging')
    """

    DRUG_DELIVERY = prefixer("drug_delivery", NANOPARTICLE_PREFIX)
    THERMAL = prefixer("photothermal", NANOPARTICLE_PREFIX)
    IMAGING = prefixer("imaging", NANOPARTICLE_PREFIX)
    SENSING = prefixer("sensing", NANOPARTICLE_PREFIX)
    THERANOSTIC = prefixer("theranostic", NANOPARTICLE_PREFIX)

PROTEIN

Bases: Enum

Functional tag definitions for protein-based monomer classes.

This enum provides standardized semantic tags for common protein roles in quantitative systems pharmacology models. Each member is constructed using the prefixer utility to combine the PROTEIN_PREFIX with a functional subclass label, producing values like "protein::ligand".

These tags are used in monomer definitions to support validation, introspection, and expressive annotation of biological roles.

Members

LIGAND : str A signaling molecule that binds to a receptor. RECEPTOR : str A membrane or intracellular protein that receives ligand signals. KINASE : str An enzyme that phosphorylates other molecules. PHOSPHATASE : str An enzyme that removes phosphate groups from molecules. ADAPTOR : str A scaffold protein facilitating complex formation without enzymatic activity. TRANSCRIPTION_FACTOR : str A nuclear protein that regulates gene transcription. ENZYME : str A general-purpose catalytic protein. ANTIBODY : str An immunoglobulin capable of specific antigen binding. RECEPTOR_DECOY : str A non-signaling receptor mimic that competes with signaling receptors.

Examples:

>>> PROTEIN.LIGAND.value
'protein::ligand'
>>> tag = FunctionalTag.parse(PROTEIN.KINASE.value)
('protein', 'kinase')
Source code in qspy\functionaltags.py
class PROTEIN(Enum):
    """
    Functional tag definitions for protein-based monomer classes.

    This enum provides standardized semantic tags for common protein roles
    in quantitative systems pharmacology models. Each member is constructed
    using the `prefixer` utility to combine the `PROTEIN_PREFIX` with a
    functional subclass label, producing values like "protein::ligand".

    These tags are used in monomer definitions to support validation,
    introspection, and expressive annotation of biological roles.

    Members
    -------
    LIGAND : str
        A signaling molecule that binds to a receptor.
    RECEPTOR : str
        A membrane or intracellular protein that receives ligand signals.
    KINASE : str
        An enzyme that phosphorylates other molecules.
    PHOSPHATASE : str
        An enzyme that removes phosphate groups from molecules.
    ADAPTOR : str
        A scaffold protein facilitating complex formation without enzymatic activity.
    TRANSCRIPTION_FACTOR : str
        A nuclear protein that regulates gene transcription.
    ENZYME : str
        A general-purpose catalytic protein.
    ANTIBODY : str
        An immunoglobulin capable of specific antigen binding.
    RECEPTOR_DECOY : str
        A non-signaling receptor mimic that competes with signaling receptors.

    Examples
    --------
    >>> PROTEIN.LIGAND.value
    'protein::ligand'

    >>> tag = FunctionalTag.parse(PROTEIN.KINASE.value)
    ('protein', 'kinase')
    """

    LIGAND = prefixer("ligand", PROTEIN_PREFIX)
    RECEPTOR = prefixer("receptor", PROTEIN_PREFIX)
    KINASE = prefixer("kinase", PROTEIN_PREFIX)
    PHOSPHATASE = prefixer("phosphatase", PROTEIN_PREFIX)
    ADAPTOR = prefixer("adaptor", PROTEIN_PREFIX)
    TRANSCRIPTION_FACTOR = prefixer("transcription_factor", PROTEIN_PREFIX)
    ENZYME = prefixer("enzyme", PROTEIN_PREFIX)
    ANTIBODY = prefixer("antibody", PROTEIN_PREFIX)
    RECEPTOR_DECOY = prefixer("receptor_decoy", PROTEIN_PREFIX)

RNA

Bases: Enum

Functional tag definitions for RNA-based monomer classes.

This enum provides standardized semantic tags for common RNA roles in quantitative systems pharmacology models. Each member is constructed using the prefixer utility to combine the RNA_PREFIX with a functional subclass label, producing values like "rna::micro".

Members

MESSENGER : str Messenger RNA (mRNA). MICRO : str Micro RNA (miRNA). SMALL_INTERFERING : str Small interfering RNA (siRNA). LONG_NONCODING : str Long non-coding RNA (lncRNA).

Examples:

>>> RNA.MICRO.value
'rna::micro'
>>> tag = FunctionalTag.parse(RNA.MESSENGER.value)
('rna', 'messenger')
Source code in qspy\functionaltags.py
class RNA(Enum):
    """
    Functional tag definitions for RNA-based monomer classes.

    This enum provides standardized semantic tags for common RNA roles
    in quantitative systems pharmacology models. Each member is constructed
    using the `prefixer` utility to combine the `RNA_PREFIX` with a
    functional subclass label, producing values like "rna::micro".

    Members
    -------
    MESSENGER : str
        Messenger RNA (mRNA).
    MICRO : str
        Micro RNA (miRNA).
    SMALL_INTERFERING : str
        Small interfering RNA (siRNA).
    LONG_NONCODING : str
        Long non-coding RNA (lncRNA).

    Examples
    --------
    >>> RNA.MICRO.value
    'rna::micro'

    >>> tag = FunctionalTag.parse(RNA.MESSENGER.value)
    ('rna', 'messenger')
    """

    MESSENGER = prefixer("messenger", RNA_PREFIX)
    MICRO = prefixer("micro", RNA_PREFIX)
    SMALL_INTERFERING = prefixer("small_interfering", RNA_PREFIX)
    LONG_NONCODING = prefixer("long_noncoding", RNA_PREFIX)

prefixer(function, prefix, sep=TAG_SEP)

Constructs a canonical functional tag string by joining a class prefix and function label using the specified separator.

This function is used to generate standardized semantic tag strings (e.g., "protein::ligand") for labeling model components in a consistent, machine-readable format.

Parameters:

Name Type Description Default
function str

The functional or subclass label (e.g., "ligand").

required
prefix str

The class or category label to prefix the function with (e.g., "protein").

required
sep str

Separator string used to join the prefix and function (default is TAG_SEP, usually "::").

TAG_SEP

Returns:

Type Description
str

Combined class/function string (e.g., "protein::ligand").

Examples:

>>> prefixer("regulatory", "rna")
'rna::regulatory'
>>> prefixer("substrate", "metabolite", sep="__")
'metabolite__substrate'
Source code in qspy\functionaltags.py
def prefixer(function: str, prefix: str, sep: str = TAG_SEP):
    """
    Constructs a canonical functional tag string by joining a class prefix
    and function label using the specified separator.

    This function is used to generate standardized semantic tag strings
    (e.g., "protein::ligand") for labeling model components in a consistent,
    machine-readable format.

    Parameters
    ----------
    function : str
        The functional or subclass label (e.g., "ligand").
    prefix : str
        The class or category label to prefix the function with (e.g., "protein").
    sep : str, optional
        Separator string used to join the prefix and function
        (default is `TAG_SEP`, usually "::").

    Returns
    -------
    str
        Combined class/function string (e.g., "protein::ligand").

    Examples
    --------
    >>> prefixer("regulatory", "rna")
    'rna::regulatory'

    >>> prefixer("substrate", "metabolite", sep="__")
    'metabolite__substrate'
    """

    return "".join([prefix, sep, function])

qspy.validation.metadata

QSPy Model Metadata Tracking and Export

This module provides utilities for capturing, tracking, and exporting model metadata in QSPy workflows. It includes environment capture, model hashing, and TOML export for reproducibility and provenance tracking.

Classes:

Name Description
QSPyBench : MicroBench-based class for capturing Python and host environment info.
ModelMetadataTracker : Tracks model metadata, environment, and provides export/load utilities.

Examples:

>>> tracker = ModelMetadataTracker(version="1.0", author="Alice", export_toml=True)
>>> tracker.metadata["model_name"]
'MyModel'
>>> ModelMetadataTracker.load_metadata_toml("MyModel__Alice__abcd1234__2024-07-01.toml")

ModelMetadataTracker

Tracks and exports QSPy model metadata, including environment and hash.

On initialization, captures model version, author, user, timestamp, hash, and environment metadata. Optionally exports metadata to TOML.

Parameters:

Name Type Description Default
version str

Model version string (default "0.1.0").

'0.1.0'
author str

Author name (default: current user).

None
export_toml bool

If True, export metadata to TOML on creation (default: False).

False
capture_conda_env bool

If True, capture the active conda environment name (default: False).

False

Attributes:

Name Type Description
model Model

The active PySB model instance.

version str

Model version.

author str

Author name.

current_user str

Username of the current user.

timestamp str

ISO timestamp of metadata creation.

hash str

SHA256 hash of model rules and parameters.

env_metadata dict

Captured environment metadata.

metadata dict

Full metadata dictionary for export.

Methods:

Name Description
compute_model_hash

Compute a hash from model rules and parameters.

capture_environment

Capture execution environment metadata.

export_metadata_toml

Export metadata to a TOML file.

load_metadata_toml

Load metadata from a TOML file.

Examples:

>>> tracker = ModelMetadataTracker(version="1.0", author="Alice", export_toml=True)
>>> tracker.metadata["model_name"]
'MyModel'
Source code in qspy\validation\metadata.py
class ModelMetadataTracker:
    """
    Tracks and exports QSPy model metadata, including environment and hash.

    On initialization, captures model version, author, user, timestamp, hash, and
    environment metadata. Optionally exports metadata to TOML.

    Parameters
    ----------
    version : str, optional
        Model version string (default "0.1.0").
    author : str, optional
        Author name (default: current user).
    export_toml : bool, optional
        If True, export metadata to TOML on creation (default: False).
    capture_conda_env : bool, optional
        If True, capture the active conda environment name (default: False).

    Attributes
    ----------
    model : pysb.Model
        The active PySB model instance.
    version : str
        Model version.
    author : str
        Author name.
    current_user : str
        Username of the current user.
    timestamp : str
        ISO timestamp of metadata creation.
    hash : str
        SHA256 hash of model rules and parameters.
    env_metadata : dict
        Captured environment metadata.
    metadata : dict
        Full metadata dictionary for export.

    Methods
    -------
    compute_model_hash()
        Compute a hash from model rules and parameters.
    capture_environment()
        Capture execution environment metadata.
    export_metadata_toml(path=None, use_metadata_dir=True)
        Export metadata to a TOML file.
    load_metadata_toml(path)
        Load metadata from a TOML file.

    Examples
    --------
    >>> tracker = ModelMetadataTracker(version="1.0", author="Alice", export_toml=True)
    >>> tracker.metadata["model_name"]
    'MyModel'
    """

    def __init__(
        self, version="0.1.0", author=None, export_toml=False, capture_conda_env=False
    ):
        """
        Initialize the ModelMetadataTracker.

        Parameters
        ----------
        version : str, optional
            Model version string (default "0.1.0").
        author : str, optional
            Author name (default: current user).
        export_toml : bool, optional
            If True, export metadata to TOML on creation (default: False).
        capture_conda_env : bool, optional
            If True, capture the active conda environment name (default: False).

        Raises
        ------
        RuntimeError
            If no model is found in the current SelfExporter context.
        """
        ensure_qspy_logging()
        logger = logging.getLogger(LOGGER_NAME)
        try:
            self.model = SelfExporter.default_model
            if not self.model:
                logger.error("No model found in the current SelfExporter context")
                raise RuntimeError("No model found in the current SelfExporter context")
            self.version = version
            self.author = author or "unknown"
            self.current_user = getpass.getuser()
            self.timestamp = datetime.now().isoformat()
            self.hash = self.compute_model_hash()
            self.env_metadata = self.capture_environment()

            if capture_conda_env:
                conda_env = os.environ.get("CONDA_DEFAULT_ENV", None)
                if conda_env:
                    self.env_metadata["conda_env"] = conda_env

            self.metadata = {
                "version": self.version,
                "author": self.author,
                "current_user": self.current_user,
                "created_at": self.timestamp,
                "hash": self.hash,
                "model_name": self.model.name or "unnamed_model",
                "env": self.env_metadata,
            }

            # Attach the metadata tracker to the model
            setattr(self.model, "qspy_metadata_tracker", self)

            if export_toml:
                self.export_metadata_toml()
        except Exception as e:
            logger.error(f"[QSPy][ERROR] Exception in ModelMetadataTracker.__init__: {e}")
            raise

    def compute_model_hash(self):
        """
        Create a hash from model definition (rules + parameters).

        Returns
        -------
        str
            SHA256 hash of the model's rules and parameters.
        """
        try:
            s = repr(self.model.rules) + repr(self.model.parameters)
            return hashlib.sha256(s.encode()).hexdigest()
        except Exception as e:
            logger = logging.getLogger(LOGGER_NAME)
            logger.error(f"[QSPy][ERROR] Exception in compute_model_hash: {e}")
            raise

    def capture_environment(self):
        """
        Capture execution environment via microbench.

        Returns
        -------
        dict
            Dictionary of captured environment metadata.
        """
        try:
            bench = QSPyBench()

            @bench
            def noop():
                pass

            noop()
            bench.outfile.seek(0)
            metadata = bench.outfile.read()
            if metadata == "":
                return {"microbench": "No metadata captured."}
            else:
                return json.loads(metadata)
        except Exception as e:
            logger = logging.getLogger(LOGGER_NAME)
            logger.error(f"[QSPy][ERROR] Exception in capture_environment: {e}")
            return {"microbench": f"Error capturing metadata: {e}"}

    def export_metadata_toml(self, path=None, use_metadata_dir=True):
        """
        Export metadata to a TOML file with autogenerated filename if none is provided.

        Parameters
        ----------
        path : str or Path, optional
            Output path for the TOML file. If None, an autogenerated filename is used.
        use_metadata_dir : bool, optional
            If True, use the configured METADATA_DIR for output (default: True).

        Returns
        -------
        None
        """
        ensure_qspy_logging()
        logger = logging.getLogger(LOGGER_NAME)
        try:
            metadata_dir = Path(METADATA_DIR) if use_metadata_dir else Path(".")
            metadata_dir.mkdir(parents=True, exist_ok=True)

            if path is None:
                safe_author = self.author.replace(" ", "_")
                safe_name = (self.model.name or "model").replace(" ", "_")
                short_hash = self.hash[:8]
                safe_time = self.timestamp.replace(":", "-")
                filename = f"{safe_name}__{safe_author}__{short_hash}__{safe_time}.toml"
                path = metadata_dir / filename

            with open(path, "w") as f:
                toml.dump(self.metadata, f)
            logger.info(f"Exported model metadata to TOML: {path}")
        except Exception as e:
            logger.error(f"[QSPy][ERROR] Exception in export_metadata_toml: {e}")
            raise

    @staticmethod
    def load_metadata_toml(path):
        """
        Load model metadata from a TOML file.

        Parameters
        ----------
        path : str or Path
            Path to the TOML metadata file.

        Returns
        -------
        dict
            Loaded metadata dictionary.

        Raises
        ------
        Exception
            If loading fails.
        """
        ensure_qspy_logging()
        logger = logging.getLogger(LOGGER_NAME)
        try:
            with open(path, "r") as f:
                return toml.load(f)
        except Exception as e:
            logger.error(f"[QSPy][ERROR] Exception in load_metadata_toml: {e}")
            raise

__init__(version='0.1.0', author=None, export_toml=False, capture_conda_env=False)

Initialize the ModelMetadataTracker.

Parameters:

Name Type Description Default
version str

Model version string (default "0.1.0").

'0.1.0'
author str

Author name (default: current user).

None
export_toml bool

If True, export metadata to TOML on creation (default: False).

False
capture_conda_env bool

If True, capture the active conda environment name (default: False).

False

Raises:

Type Description
RuntimeError

If no model is found in the current SelfExporter context.

Source code in qspy\validation\metadata.py
def __init__(
    self, version="0.1.0", author=None, export_toml=False, capture_conda_env=False
):
    """
    Initialize the ModelMetadataTracker.

    Parameters
    ----------
    version : str, optional
        Model version string (default "0.1.0").
    author : str, optional
        Author name (default: current user).
    export_toml : bool, optional
        If True, export metadata to TOML on creation (default: False).
    capture_conda_env : bool, optional
        If True, capture the active conda environment name (default: False).

    Raises
    ------
    RuntimeError
        If no model is found in the current SelfExporter context.
    """
    ensure_qspy_logging()
    logger = logging.getLogger(LOGGER_NAME)
    try:
        self.model = SelfExporter.default_model
        if not self.model:
            logger.error("No model found in the current SelfExporter context")
            raise RuntimeError("No model found in the current SelfExporter context")
        self.version = version
        self.author = author or "unknown"
        self.current_user = getpass.getuser()
        self.timestamp = datetime.now().isoformat()
        self.hash = self.compute_model_hash()
        self.env_metadata = self.capture_environment()

        if capture_conda_env:
            conda_env = os.environ.get("CONDA_DEFAULT_ENV", None)
            if conda_env:
                self.env_metadata["conda_env"] = conda_env

        self.metadata = {
            "version": self.version,
            "author": self.author,
            "current_user": self.current_user,
            "created_at": self.timestamp,
            "hash": self.hash,
            "model_name": self.model.name or "unnamed_model",
            "env": self.env_metadata,
        }

        # Attach the metadata tracker to the model
        setattr(self.model, "qspy_metadata_tracker", self)

        if export_toml:
            self.export_metadata_toml()
    except Exception as e:
        logger.error(f"[QSPy][ERROR] Exception in ModelMetadataTracker.__init__: {e}")
        raise

capture_environment()

Capture execution environment via microbench.

Returns:

Type Description
dict

Dictionary of captured environment metadata.

Source code in qspy\validation\metadata.py
def capture_environment(self):
    """
    Capture execution environment via microbench.

    Returns
    -------
    dict
        Dictionary of captured environment metadata.
    """
    try:
        bench = QSPyBench()

        @bench
        def noop():
            pass

        noop()
        bench.outfile.seek(0)
        metadata = bench.outfile.read()
        if metadata == "":
            return {"microbench": "No metadata captured."}
        else:
            return json.loads(metadata)
    except Exception as e:
        logger = logging.getLogger(LOGGER_NAME)
        logger.error(f"[QSPy][ERROR] Exception in capture_environment: {e}")
        return {"microbench": f"Error capturing metadata: {e}"}

compute_model_hash()

Create a hash from model definition (rules + parameters).

Returns:

Type Description
str

SHA256 hash of the model's rules and parameters.

Source code in qspy\validation\metadata.py
def compute_model_hash(self):
    """
    Create a hash from model definition (rules + parameters).

    Returns
    -------
    str
        SHA256 hash of the model's rules and parameters.
    """
    try:
        s = repr(self.model.rules) + repr(self.model.parameters)
        return hashlib.sha256(s.encode()).hexdigest()
    except Exception as e:
        logger = logging.getLogger(LOGGER_NAME)
        logger.error(f"[QSPy][ERROR] Exception in compute_model_hash: {e}")
        raise

export_metadata_toml(path=None, use_metadata_dir=True)

Export metadata to a TOML file with autogenerated filename if none is provided.

Parameters:

Name Type Description Default
path str or Path

Output path for the TOML file. If None, an autogenerated filename is used.

None
use_metadata_dir bool

If True, use the configured METADATA_DIR for output (default: True).

True

Returns:

Type Description
None
Source code in qspy\validation\metadata.py
def export_metadata_toml(self, path=None, use_metadata_dir=True):
    """
    Export metadata to a TOML file with autogenerated filename if none is provided.

    Parameters
    ----------
    path : str or Path, optional
        Output path for the TOML file. If None, an autogenerated filename is used.
    use_metadata_dir : bool, optional
        If True, use the configured METADATA_DIR for output (default: True).

    Returns
    -------
    None
    """
    ensure_qspy_logging()
    logger = logging.getLogger(LOGGER_NAME)
    try:
        metadata_dir = Path(METADATA_DIR) if use_metadata_dir else Path(".")
        metadata_dir.mkdir(parents=True, exist_ok=True)

        if path is None:
            safe_author = self.author.replace(" ", "_")
            safe_name = (self.model.name or "model").replace(" ", "_")
            short_hash = self.hash[:8]
            safe_time = self.timestamp.replace(":", "-")
            filename = f"{safe_name}__{safe_author}__{short_hash}__{safe_time}.toml"
            path = metadata_dir / filename

        with open(path, "w") as f:
            toml.dump(self.metadata, f)
        logger.info(f"Exported model metadata to TOML: {path}")
    except Exception as e:
        logger.error(f"[QSPy][ERROR] Exception in export_metadata_toml: {e}")
        raise

load_metadata_toml(path) staticmethod

Load model metadata from a TOML file.

Parameters:

Name Type Description Default
path str or Path

Path to the TOML metadata file.

required

Returns:

Type Description
dict

Loaded metadata dictionary.

Raises:

Type Description
Exception

If loading fails.

Source code in qspy\validation\metadata.py
@staticmethod
def load_metadata_toml(path):
    """
    Load model metadata from a TOML file.

    Parameters
    ----------
    path : str or Path
        Path to the TOML metadata file.

    Returns
    -------
    dict
        Loaded metadata dictionary.

    Raises
    ------
    Exception
        If loading fails.
    """
    ensure_qspy_logging()
    logger = logging.getLogger(LOGGER_NAME)
    try:
        with open(path, "r") as f:
            return toml.load(f)
    except Exception as e:
        logger.error(f"[QSPy][ERROR] Exception in load_metadata_toml: {e}")
        raise

QSPyBench

Bases: MicroBench, MBPythonVersion, MBHostInfo

MicroBench-based class for capturing Python and host environment information.

Captures versions of key scientific libraries and host metadata for reproducibility.

Attributes:

Name Type Description
capture_versions tuple

Tuple of modules to capture version info for (qspy, numpy, scipy, sympy, pysb, pysb.pkpd, pysb.units, mergram).

Source code in qspy\validation\metadata.py
class QSPyBench(MicroBench, MBPythonVersion, MBHostInfo):
    """
    MicroBench-based class for capturing Python and host environment information.

    Captures versions of key scientific libraries and host metadata for reproducibility.

    Attributes
    ----------
    capture_versions : tuple
        Tuple of modules to capture version info for (qspy, numpy, scipy, sympy, pysb, pysb.pkpd, pysb.units, mergram).
    """
    capture_versions = (qspy, numpy, scipy, sympy, pysb, pysb.pkpd, pysb.units, mergram)

qspy.validation.modelchecker

QSPy ModelChecker Utilities

This module provides the ModelChecker class for validating PySB/QSPy models. It checks for unused or zero-valued parameters, unused monomers, missing initial conditions, dangling bonds, unit consistency, and other common modeling issues. Warnings are logged and also issued as Python warnings for user visibility.

Classes:

Name Description
ModelChecker : Performs a suite of checks on a PySB/QSPy model for common issues.

Examples:

>>> checker = ModelChecker(model)
>>> checker.check()

ModelChecker

Performs a suite of checks on a PySB/QSPy model for common modeling issues.

Checks include: - Unused monomers - Unused parameters - Zero-valued parameters - Missing initial conditions - Dangling/reused bonds - Unit consistency - (Optional) unbound sites, overdefined rules, unreferenced expressions

Parameters:

Name Type Description Default
model Model

The model to check. If None, uses the current SelfExporter.default_model.

None
logger_name str

Name of the logger to use (default: LOGGER_NAME).

LOGGER_NAME

Attributes:

Name Type Description
model Model

The model being checked.

logger Logger

Logger for outputting warnings and info.

Source code in qspy\validation\modelchecker.py
class ModelChecker:
    """
    Performs a suite of checks on a PySB/QSPy model for common modeling issues.

    Checks include:
    - Unused monomers
    - Unused parameters
    - Zero-valued parameters
    - Missing initial conditions
    - Dangling/reused bonds
    - Unit consistency
    - (Optional) unbound sites, overdefined rules, unreferenced expressions

    Parameters
    ----------
    model : pysb.Model, optional
        The model to check. If None, uses the current SelfExporter.default_model.
    logger_name : str, optional
        Name of the logger to use (default: LOGGER_NAME).

    Attributes
    ----------
    model : pysb.Model
        The model being checked.
    logger : logging.Logger
        Logger for outputting warnings and info.
    """

    def __init__(self, model=None, logger_name=LOGGER_NAME):
        """
        Initialize the ModelChecker.

        Parameters
        ----------
        model : pysb.Model, optional
            The model to check. If None, uses the current SelfExporter.default_model.
        logger_name : str, optional
            Name of the logger to use.
        """
        self.model = model
        if model is None:
            self.model = SelfExporter.default_model
        ensure_qspy_logging()
        self.logger = logging.getLogger(logger_name)
        self.check()

    @log_event()
    def check(self):
        """
        Run all model checks.

        Returns
        -------
        None
        """
        self.logger.info("🔍 Running ModelChecker...")
        self.check_unused_monomers()
        self.check_unused_parameters()
        self.check_zero_valued_parameters()
        self.check_missing_initial_conditions()
        # self.check_unconnected_species()
        # self.check_unbound_sites()
        # self.check_overdefined_rules()
        # self.check_unreferenced_expressions()
        # units_check(self.model)
        self.check_dangling_reused_bonds()
        self.check_units()
        self.check_equations_generation()
        self.logger.info("✅ ModelChecker checks completed.")

    def check_unused_monomers(self):
        """
        Check for monomers that are not used in any rules.

        Logs and warns about unused monomers.

        Returns
        -------
        None
        """
        used = set()
        for rule in self.model.rules:
            used.update(
                m.name
                for m in monomers_from_pattern(rule.rule_expression.reactant_pattern)
            )
            # monomers_from_pattern doesn't handle None, so we need to
            # check here or it will cause a problem with the set.union method.
            if rule.is_reversible:
                used.update(
                    m.name
                    for m in monomers_from_pattern(rule.rule_expression.product_pattern)
                )

        unused = [m.name for m in self.model.monomers if m.name not in used]
        if len(unused) > 0:
            msg = f"Unused Monomers (not included in any Rules): {[m for m in unused]}"
            self.logger.warning(f"⚠️ {msg}")
            warnings.warn(msg, category=UserWarning)
            print(f"⚠️ {msg}")  # Print to console for visibility

    def check_unused_parameters(self):
        """
        Check for parameters that are not used in rules, initials, or expressions.

        Logs and warns about unused parameters.

        Returns
        -------
        None
        """
        used = set()
        for rule in self.model.rules:
            if isinstance(rule.rate_forward, Parameter):
                used.add(rule.rate_forward.name)
            if rule.is_reversible:
                if isinstance(rule.rate_reverse, Parameter):
                    used.add(rule.rate_reverse.name)
        for ic in self.model.initials:
            if isinstance(ic.value, Parameter):
                used.add(ic.value.name)
        for expr in self.model.expressions:
            used.update(p.name for p in expr.expr.atoms(Parameter))
        for compartment in self.model.compartments:
            if isinstance(compartment.size, Parameter):
                used.add(compartment.size.name)

        unused = [p.name for p in self.model.parameters if p.name not in used]
        if unused:
            msg = f"Unused Parameters: {[p for p in unused]}"
            self.logger.warning(f"⚠️ {msg}")
            warnings.warn(msg, category=UserWarning)
            print(f"⚠️ {msg}")  # Print to console for visibility

    def check_zero_valued_parameters(self):
        """
        Check for parameters with a value of zero.

        Logs and warns about zero-valued parameters.

        Returns
        -------
        None
        """
        zeros = [p for p in self.model.parameters if np.isclose(p.value, 0.0)]
        if zeros:
            msg = f"Zero-valued Parameters: {[p.name for p in zeros]}"
            self.logger.warning(f"⚠️ {msg}")
            warnings.warn(msg, category=UserWarning)
            print(f"⚠️ {msg}")  # Print to console for visibility

    def check_missing_initial_conditions(self):
        """
        Check for monomers missing initial conditions.

        Logs and warns about monomers that do not have initial conditions defined.

        Returns
        -------
        None
        """
        defined = list()
        for initial in self.model.initials:
            for m in monomers_from_pattern(initial.pattern):
                defined.append(m.name)
        defined = set(defined)
        all_monomers = set(m.name for m in self.model.monomers)
        missing = all_monomers - defined
        if missing:
            msg = f"Monomers missing initial conditions: {list(missing)}"
            self.logger.warning(f"⚠️ {msg}")
            warnings.warn(msg, category=UserWarning)
            print(f"⚠️ {msg}")  # Print to console for visibility

    def check_dangling_reused_bonds(self):
        """
        Check for dangling or reused bonds in all rules.

        Returns
        -------
        None
        """
        for rule in self.model.rules:
            try:
                check_dangling_bonds(rule.rule_expression.reactant_pattern)
            except Exception as e:
                msg = f"Error checking reactant pattern in rule '{rule.name}': {e}"
                self.logger.error(msg)
                warnings.warn(msg, category=UserWarning)
                print(msg)  # Print to console for visibility
            if rule.is_reversible:
                try:
                    check_dangling_bonds(rule.rule_expression.product_pattern)
                except Exception as e:
                    msg = f"Error checking product pattern in rule '{rule.name}': {e}"
                    self.logger.error(msg)
                    warnings.warn(msg, category=UserWarning)
                    print(msg)  # Print to console for visibility

    def check_equations_generation(self):
        """
        Run the `generate_equations` function on the model and capture and report any errors.

        Returns
        -------
        None
        """
        try:
            generate_equations(self.model)
            self.logger.info("Model equations generated successfully.")
        except Exception as e:
            msg = f"Error generating model equations: {e}"
            self.logger.error(msg)
            warnings.warn(msg, category=UserWarning)
            print(msg)  # Print to console for visibility

    @log_event()
    def check_units(self):
        """
        Check for unit consistency in the model.

        Returns
        -------
        None
        """
        units_check(self.model)

    def check_unbound_sites(self):
        """
        Check for sites that never participate in bonds.

        Logs and warns about unbound sites.

        Returns
        -------
        None
        """
        bound_sites = set()
        for r in self.model.rules:
            for cp in r.rule_expression().all_complex_patterns():
                for m in cp.monomer_patterns:
                    for site, state in m.site_conditions.items():
                        if isinstance(state, tuple):  # bond tuple
                            bound_sites.add((m.monomer.name, site))

        unbound = []
        for m in self.model.monomers:
            for site in m.sites:
                if (m.name, site) not in bound_sites:
                    unbound.append(f"{m.name}.{site}")

        if unbound:
            msg = f"Unbound Sites (never participate in bonds): {unbound}"
            self.logger.warning(f"⚠️ {msg}")
            warnings.warn(msg, category=UserWarning)

    def check_overdefined_rules(self):
        """
        Check for rules that define the same reaction more than once.

        Logs and warns about overdefined rules.

        Returns
        -------
        None
        """
        seen = {}
        for r in self.model.rules:
            rxn = str(r.rule_expression())
            if rxn in seen:
                msg = f"Overdefined reaction: '{rxn}' in rules `{seen[rxn]}` and `{r.name}`"
                self.logger.warning(f"⚠️ {msg}")
                warnings.warn(msg, category=UserWarning)
            else:
                seen[rxn] = r.name

    def check_unreferenced_expressions(self):
        """
        Check for expressions that are not referenced by any rule or observable.

        Logs and warns about unreferenced expressions.

        Returns
        -------
        None
        """
        used = set()
        for rule in self.model.rules:
            if rule.rate_forward:
                used.update(str(p) for p in rule.rate_forward.parameters)
            if rule.rate_reverse:
                used.update(str(p) for p in rule.rate_reverse.parameters)
        for o in self.model.observables:
            used.update(str(p) for p in o.function.atoms(Parameter))

        exprs = [e.name for e in self.model.expressions if e.name not in used]
        if exprs:
            msg = f"Unreferenced Expressions: {exprs}"
            self.logger.warning(f"⚠️ {msg}")
            warnings.warn(msg, category=UserWarning)

__init__(model=None, logger_name=LOGGER_NAME)

Initialize the ModelChecker.

Parameters:

Name Type Description Default
model Model

The model to check. If None, uses the current SelfExporter.default_model.

None
logger_name str

Name of the logger to use.

LOGGER_NAME
Source code in qspy\validation\modelchecker.py
def __init__(self, model=None, logger_name=LOGGER_NAME):
    """
    Initialize the ModelChecker.

    Parameters
    ----------
    model : pysb.Model, optional
        The model to check. If None, uses the current SelfExporter.default_model.
    logger_name : str, optional
        Name of the logger to use.
    """
    self.model = model
    if model is None:
        self.model = SelfExporter.default_model
    ensure_qspy_logging()
    self.logger = logging.getLogger(logger_name)
    self.check()

check()

Run all model checks.

Returns:

Type Description
None
Source code in qspy\validation\modelchecker.py
@log_event()
def check(self):
    """
    Run all model checks.

    Returns
    -------
    None
    """
    self.logger.info("🔍 Running ModelChecker...")
    self.check_unused_monomers()
    self.check_unused_parameters()
    self.check_zero_valued_parameters()
    self.check_missing_initial_conditions()
    # self.check_unconnected_species()
    # self.check_unbound_sites()
    # self.check_overdefined_rules()
    # self.check_unreferenced_expressions()
    # units_check(self.model)
    self.check_dangling_reused_bonds()
    self.check_units()
    self.check_equations_generation()
    self.logger.info("✅ ModelChecker checks completed.")

check_dangling_reused_bonds()

Check for dangling or reused bonds in all rules.

Returns:

Type Description
None
Source code in qspy\validation\modelchecker.py
def check_dangling_reused_bonds(self):
    """
    Check for dangling or reused bonds in all rules.

    Returns
    -------
    None
    """
    for rule in self.model.rules:
        try:
            check_dangling_bonds(rule.rule_expression.reactant_pattern)
        except Exception as e:
            msg = f"Error checking reactant pattern in rule '{rule.name}': {e}"
            self.logger.error(msg)
            warnings.warn(msg, category=UserWarning)
            print(msg)  # Print to console for visibility
        if rule.is_reversible:
            try:
                check_dangling_bonds(rule.rule_expression.product_pattern)
            except Exception as e:
                msg = f"Error checking product pattern in rule '{rule.name}': {e}"
                self.logger.error(msg)
                warnings.warn(msg, category=UserWarning)
                print(msg)  # Print to console for visibility

check_equations_generation()

Run the generate_equations function on the model and capture and report any errors.

Returns:

Type Description
None
Source code in qspy\validation\modelchecker.py
def check_equations_generation(self):
    """
    Run the `generate_equations` function on the model and capture and report any errors.

    Returns
    -------
    None
    """
    try:
        generate_equations(self.model)
        self.logger.info("Model equations generated successfully.")
    except Exception as e:
        msg = f"Error generating model equations: {e}"
        self.logger.error(msg)
        warnings.warn(msg, category=UserWarning)
        print(msg)  # Print to console for visibility

check_missing_initial_conditions()

Check for monomers missing initial conditions.

Logs and warns about monomers that do not have initial conditions defined.

Returns:

Type Description
None
Source code in qspy\validation\modelchecker.py
def check_missing_initial_conditions(self):
    """
    Check for monomers missing initial conditions.

    Logs and warns about monomers that do not have initial conditions defined.

    Returns
    -------
    None
    """
    defined = list()
    for initial in self.model.initials:
        for m in monomers_from_pattern(initial.pattern):
            defined.append(m.name)
    defined = set(defined)
    all_monomers = set(m.name for m in self.model.monomers)
    missing = all_monomers - defined
    if missing:
        msg = f"Monomers missing initial conditions: {list(missing)}"
        self.logger.warning(f"⚠️ {msg}")
        warnings.warn(msg, category=UserWarning)
        print(f"⚠️ {msg}")  # Print to console for visibility

check_overdefined_rules()

Check for rules that define the same reaction more than once.

Logs and warns about overdefined rules.

Returns:

Type Description
None
Source code in qspy\validation\modelchecker.py
def check_overdefined_rules(self):
    """
    Check for rules that define the same reaction more than once.

    Logs and warns about overdefined rules.

    Returns
    -------
    None
    """
    seen = {}
    for r in self.model.rules:
        rxn = str(r.rule_expression())
        if rxn in seen:
            msg = f"Overdefined reaction: '{rxn}' in rules `{seen[rxn]}` and `{r.name}`"
            self.logger.warning(f"⚠️ {msg}")
            warnings.warn(msg, category=UserWarning)
        else:
            seen[rxn] = r.name

check_unbound_sites()

Check for sites that never participate in bonds.

Logs and warns about unbound sites.

Returns:

Type Description
None
Source code in qspy\validation\modelchecker.py
def check_unbound_sites(self):
    """
    Check for sites that never participate in bonds.

    Logs and warns about unbound sites.

    Returns
    -------
    None
    """
    bound_sites = set()
    for r in self.model.rules:
        for cp in r.rule_expression().all_complex_patterns():
            for m in cp.monomer_patterns:
                for site, state in m.site_conditions.items():
                    if isinstance(state, tuple):  # bond tuple
                        bound_sites.add((m.monomer.name, site))

    unbound = []
    for m in self.model.monomers:
        for site in m.sites:
            if (m.name, site) not in bound_sites:
                unbound.append(f"{m.name}.{site}")

    if unbound:
        msg = f"Unbound Sites (never participate in bonds): {unbound}"
        self.logger.warning(f"⚠️ {msg}")
        warnings.warn(msg, category=UserWarning)

check_units()

Check for unit consistency in the model.

Returns:

Type Description
None
Source code in qspy\validation\modelchecker.py
@log_event()
def check_units(self):
    """
    Check for unit consistency in the model.

    Returns
    -------
    None
    """
    units_check(self.model)

check_unreferenced_expressions()

Check for expressions that are not referenced by any rule or observable.

Logs and warns about unreferenced expressions.

Returns:

Type Description
None
Source code in qspy\validation\modelchecker.py
def check_unreferenced_expressions(self):
    """
    Check for expressions that are not referenced by any rule or observable.

    Logs and warns about unreferenced expressions.

    Returns
    -------
    None
    """
    used = set()
    for rule in self.model.rules:
        if rule.rate_forward:
            used.update(str(p) for p in rule.rate_forward.parameters)
        if rule.rate_reverse:
            used.update(str(p) for p in rule.rate_reverse.parameters)
    for o in self.model.observables:
        used.update(str(p) for p in o.function.atoms(Parameter))

    exprs = [e.name for e in self.model.expressions if e.name not in used]
    if exprs:
        msg = f"Unreferenced Expressions: {exprs}"
        self.logger.warning(f"⚠️ {msg}")
        warnings.warn(msg, category=UserWarning)

check_unused_monomers()

Check for monomers that are not used in any rules.

Logs and warns about unused monomers.

Returns:

Type Description
None
Source code in qspy\validation\modelchecker.py
def check_unused_monomers(self):
    """
    Check for monomers that are not used in any rules.

    Logs and warns about unused monomers.

    Returns
    -------
    None
    """
    used = set()
    for rule in self.model.rules:
        used.update(
            m.name
            for m in monomers_from_pattern(rule.rule_expression.reactant_pattern)
        )
        # monomers_from_pattern doesn't handle None, so we need to
        # check here or it will cause a problem with the set.union method.
        if rule.is_reversible:
            used.update(
                m.name
                for m in monomers_from_pattern(rule.rule_expression.product_pattern)
            )

    unused = [m.name for m in self.model.monomers if m.name not in used]
    if len(unused) > 0:
        msg = f"Unused Monomers (not included in any Rules): {[m for m in unused]}"
        self.logger.warning(f"⚠️ {msg}")
        warnings.warn(msg, category=UserWarning)
        print(f"⚠️ {msg}")  # Print to console for visibility

check_unused_parameters()

Check for parameters that are not used in rules, initials, or expressions.

Logs and warns about unused parameters.

Returns:

Type Description
None
Source code in qspy\validation\modelchecker.py
def check_unused_parameters(self):
    """
    Check for parameters that are not used in rules, initials, or expressions.

    Logs and warns about unused parameters.

    Returns
    -------
    None
    """
    used = set()
    for rule in self.model.rules:
        if isinstance(rule.rate_forward, Parameter):
            used.add(rule.rate_forward.name)
        if rule.is_reversible:
            if isinstance(rule.rate_reverse, Parameter):
                used.add(rule.rate_reverse.name)
    for ic in self.model.initials:
        if isinstance(ic.value, Parameter):
            used.add(ic.value.name)
    for expr in self.model.expressions:
        used.update(p.name for p in expr.expr.atoms(Parameter))
    for compartment in self.model.compartments:
        if isinstance(compartment.size, Parameter):
            used.add(compartment.size.name)

    unused = [p.name for p in self.model.parameters if p.name not in used]
    if unused:
        msg = f"Unused Parameters: {[p for p in unused]}"
        self.logger.warning(f"⚠️ {msg}")
        warnings.warn(msg, category=UserWarning)
        print(f"⚠️ {msg}")  # Print to console for visibility

check_zero_valued_parameters()

Check for parameters with a value of zero.

Logs and warns about zero-valued parameters.

Returns:

Type Description
None
Source code in qspy\validation\modelchecker.py
def check_zero_valued_parameters(self):
    """
    Check for parameters with a value of zero.

    Logs and warns about zero-valued parameters.

    Returns
    -------
    None
    """
    zeros = [p for p in self.model.parameters if np.isclose(p.value, 0.0)]
    if zeros:
        msg = f"Zero-valued Parameters: {[p.name for p in zeros]}"
        self.logger.warning(f"⚠️ {msg}")
        warnings.warn(msg, category=UserWarning)
        print(f"⚠️ {msg}")  # Print to console for visibility

qspy.utils.diagrams

QSPy Model Diagram Generation Utilities

This module provides utilities for generating flowchart-style diagrams of QSPy/PySB models. It leverages mergram and pyvipr to visualize model structure, including compartments, species, and reactions, and can export diagrams as Mermaid, Markdown, or HTML blocks.

Classes:

Name Description
ModelMermaidDiagrammer : Generates and exports flowchart diagrams for a given model.

Examples:

>>> from qspy.diagrams import ModelDiagram
>>> diagram = ModelDiagram(model)
>>> print(diagram.markdown_block)
>>> diagram.write_mermaid_file("model_flowchart.mmd")

ModelMermaidDiagrammer

Generates a Mermaid flowchart diagram of a QSPy/PySB model.

This class builds a flowchart representation of the model, including compartments, species, and reactions, and provides export options for Mermaid, Markdown, and HTML.

Parameters:

Name Type Description Default
model Model

The model to visualize. If None, uses the current SelfExporter.default_model.

None
output_dir str or Path

Directory to write diagram files (default: METADATA_DIR).

METADATA_DIR

Attributes:

Name Type Description
model Model

The model being visualized.

flowchart Flowchart

The generated flowchart object.

static_viz PysbStaticViz

Static visualization helper for the model.

has_compartments bool

Whether the model contains compartments.

output_dir Path

Directory for output files.

Methods:

Name Description
write_mermaid_file

Write the flowchart to a Mermaid file.

markdown_block

Return the flowchart as a Markdown block.

html_block

Return the flowchart as an HTML block.

Source code in qspy\utils\diagrams.py
class ModelMermaidDiagrammer:
    """
    Generates a Mermaid flowchart diagram of a QSPy/PySB model.

    This class builds a flowchart representation of the model, including compartments,
    species, and reactions, and provides export options for Mermaid, Markdown, and HTML.

    Parameters
    ----------
    model : pysb.Model, optional
        The model to visualize. If None, uses the current SelfExporter.default_model.
    output_dir : str or Path, optional
        Directory to write diagram files (default: METADATA_DIR).

    Attributes
    ----------
    model : pysb.Model
        The model being visualized.
    flowchart : Flowchart
        The generated flowchart object.
    static_viz : PysbStaticViz
        Static visualization helper for the model.
    has_compartments : bool
        Whether the model contains compartments.
    output_dir : Path
        Directory for output files.

    Methods
    -------
    write_mermaid_file(file_path)
        Write the flowchart to a Mermaid file.
    markdown_block
        Return the flowchart as a Markdown block.
    html_block
        Return the flowchart as an HTML block.
    """

    def __init__(self, model=None, output_dir=METADATA_DIR):
        """
        Initialize the ModelMermaidDiagrammer.

        Parameters
        ----------
        model : pysb.Model, optional
            The model to visualize. If None, uses the current SelfExporter.default_model.
        output_dir : str or Path, optional
            Directory to write diagram files (default: METADATA_DIR).
        """
        self.model = model
        if model is None:
            self.model = SelfExporter.default_model
        self.flowchart = Flowchart(self.model.name)
        self.static_viz = PysbStaticViz(self.model)
        self.has_compartments = len(self.model.compartments) > 0
        self._build_flowchart()
        self.output_dir = Path(output_dir)
        self.output_dir.mkdir(parents=True, exist_ok=True)
        chart_file = self.output_dir / f"{self.model.name}_flowchart.mmd"
        self.write_mermaid_file(chart_file.as_posix())
        setattr(self.model, "mermaid_diagram", self)
        return

    @staticmethod
    def _sanitize_label(label):
        """
        Sanitize a label by removing compartment information.

        Parameters
        ----------
        label : str
            The label to sanitize.

        Returns
        -------
        str
            The sanitized label without compartment information.
        """
        #print(label)
        if "** " in label:
            if "-" in label:
                # Remove compartment from labels with bound monomers
                # e.g., "molec_a() ** CENTRAL % molec_b() ** TUMOR"
                # becomes "molec_a() % molec_b()"
                parts = label.split("-")
                return parts[0].split("** ")[0] + ":" + parts[1].split("** ")[0]
            else:
                # Remove compartment info
                # e.g., "molec_a() ** CENTRAL" -> "molec_a()"
                return label.split("** ")[0]
        return label

    def _build_flowchart(self):
        """
        Build the flowchart representation of the model.

        Parses the model's compartments, species, and reactions, and adds them as nodes,
        subgraphs, and links to the flowchart.
        """
        if self.has_compartments:
            nx_graph = self.static_viz.compartments_data_graph()
        else:
            nx_graph = self.static_viz.species_graph()
        # Add nodes and edges for unidirection `>> None` reactions.
        # This is to handle cases where a reaction has no products.
        for reaction in self.model.reactions_bidirectional:
            reactants = set(reaction["reactants"])
            products = set(reaction["products"])
            rule = self.model.rules[reaction["rule"][0]]
            k_f = rule.rate_forward.name
            if len(products) < 1:
                for s in reactants:
                    s_id = f"s{s}"
                    node_comp = nx_graph.nodes[s_id]["parent"]
                    nx_graph.add_node(
                        "none",
                        NodeType="none",
                        parent=node_comp,
                        label='"fa:fa-circle-xmark"',
                        background_color="#fff",
                    )
                    nx_graph.add_edge(s_id, "none", k_f=k_f, k_r=" ")

        # Parse the networkx graph nodes into flowchart nodes.
        # Create subgraphs for compartments if they exist.
        # Otherwise, create nodes for species.
        flow_nodes = dict()
        for node_id, node_attr in nx_graph.nodes.data():
            if node_attr["NodeType"] == "compartment":
                if node_id not in self.flowchart.subgraphs:
                    self.flowchart += Subgraph(node_id)
            elif node_attr["NodeType"] == "species":
                flow_node = Node(
                    node_id,
                    label=self._sanitize_label(node_attr.get("label", node_id)),
                    fill=node_attr.get("background_color", "#fff"),
                )
                compartment = node_attr["parent"]
                if compartment not in self.flowchart.subgraphs:
                    self.flowchart += Subgraph(compartment)
                self.flowchart.subgraphs[compartment] += flow_node
                flow_nodes[node_id] = flow_node
            elif node_attr["NodeType"] == "none":
                flow_node = Node(
                    node_id,
                    label=self._sanitize_label(node_attr.get("label", node_id)),
                    fill=node_attr.get("background_color", "#fff"),
                    shape="fr-circ",
                )
                self.flowchart += flow_node
                flow_nodes[node_id] = flow_node
        # Go through the reactions and get the names of rate constants
        # and add them to the networkx edge attributes.
        for reaction in self.model.reactions_bidirectional:
            reactants = set(reaction["reactants"])
            products = set(reaction["products"])
            rule = self.model.rules[reaction["rule"][0]]
            k_f = rule.rate_forward.name
            if rule.rate_reverse is None:
                k_r = " "
            else:
                k_r = rule.rate_reverse.name

            for s in reactants:
                s_id = f"s{s}"
                for p in products:
                    p_id = f"s{p}"
                    if nx_graph.has_edge(s_id, p_id):
                        nx_graph[s_id][p_id]["k_f"] = k_f
                        nx_graph[s_id][p_id]["k_r"] = k_r

        for source, target, edge_attr in nx_graph.edges.data():
            if source in flow_nodes and target in flow_nodes:
                self.flowchart += Link(
                    flow_nodes[source],
                    flow_nodes[target],
                    text=edge_attr.get("k_f", " "),
                )
                if edge_attr.get("source_arrow_shape") == "triangle":
                    # Reaction is reversible
                    self.flowchart += Link(
                        flow_nodes[target],
                        flow_nodes[source],
                        text=edge_attr.get("k_r", " "),
                    )
        # Add colors to subgraphs for compartments
        comp_colors = sns.color_palette(
            "Set2", n_colors=len(self.flowchart.subgraphs)
        ).as_hex()
        for subgraph in self.flowchart.subgraphs.values():
            s_id = subgraph.title
            style = Style(
                s_id,
                rx="10px",
                fill=comp_colors.pop(0),
                color="#000",
            )
            self.flowchart += style

        return

    @property
    def markdown_block(self):
        """
        Return the flowchart as a Markdown block.

        Returns
        -------
        str
            Markdown representation of the flowchart.
        """
        return self.flowchart.to_markdown()

    @property
    def html_block(self):
        """
        Return the flowchart as an HTML block.

        Returns
        -------
        str
            HTML representation of the flowchart.
        """
        return self.flowchart.to_html()

    def write_mermaid_file(self, file_path):
        """
        Write the flowchart to a Mermaid file.

        Parameters
        ----------
        file_path : str or Path
            Path to the output Mermaid (.mmd) file.

        Returns
        -------
        None
        """
        self.flowchart.write(file_path)
        return

html_block property

Return the flowchart as an HTML block.

Returns:

Type Description
str

HTML representation of the flowchart.

markdown_block property

Return the flowchart as a Markdown block.

Returns:

Type Description
str

Markdown representation of the flowchart.

__init__(model=None, output_dir=METADATA_DIR)

Initialize the ModelMermaidDiagrammer.

Parameters:

Name Type Description Default
model Model

The model to visualize. If None, uses the current SelfExporter.default_model.

None
output_dir str or Path

Directory to write diagram files (default: METADATA_DIR).

METADATA_DIR
Source code in qspy\utils\diagrams.py
def __init__(self, model=None, output_dir=METADATA_DIR):
    """
    Initialize the ModelMermaidDiagrammer.

    Parameters
    ----------
    model : pysb.Model, optional
        The model to visualize. If None, uses the current SelfExporter.default_model.
    output_dir : str or Path, optional
        Directory to write diagram files (default: METADATA_DIR).
    """
    self.model = model
    if model is None:
        self.model = SelfExporter.default_model
    self.flowchart = Flowchart(self.model.name)
    self.static_viz = PysbStaticViz(self.model)
    self.has_compartments = len(self.model.compartments) > 0
    self._build_flowchart()
    self.output_dir = Path(output_dir)
    self.output_dir.mkdir(parents=True, exist_ok=True)
    chart_file = self.output_dir / f"{self.model.name}_flowchart.mmd"
    self.write_mermaid_file(chart_file.as_posix())
    setattr(self.model, "mermaid_diagram", self)
    return

write_mermaid_file(file_path)

Write the flowchart to a Mermaid file.

Parameters:

Name Type Description Default
file_path str or Path

Path to the output Mermaid (.mmd) file.

required

Returns:

Type Description
None
Source code in qspy\utils\diagrams.py
def write_mermaid_file(self, file_path):
    """
    Write the flowchart to a Mermaid file.

    Parameters
    ----------
    file_path : str or Path
        Path to the output Mermaid (.mmd) file.

    Returns
    -------
    None
    """
    self.flowchart.write(file_path)
    return

qspy.utils.logging

QSPy Logging Utilities

This module provides logging utilities for QSPy, including logger setup, event decorators, metadata redaction, and context entry/exit logging. It ensures consistent, structured, and optionally redacted logging for QSPy workflows.

Functions:

Name Description
setup_qspy_logger : Set up the QSPy logger with rotating file handler.
ensure_qspy_logging : Ensure the QSPy logger is initialized.
log_event : Decorator for logging function entry, exit, arguments, and results.
redact_sensitive : Recursively redact sensitive fields in a dictionary.
log_model_metadata : Log model metadata in a structured, optionally redacted format.
log_context_entry_exit : Decorator for logging context manager entry/exit.

Examples:

>>> @log_event(log_args=True, log_result=True)
... def foo(x): return x + 1
>>> foo(2)

ensure_qspy_logging()

Ensure the QSPy logger is initialized.

Returns:

Type Description
None
Source code in qspy\utils\logging.py
def ensure_qspy_logging():
    """
    Ensure the QSPy logger is initialized.

    Returns
    -------
    None
    """
    global _LOGGER_INITIALIZED
    if not _LOGGER_INITIALIZED:
        setup_qspy_logger()
        _LOGGER_INITIALIZED = True

log_context_entry_exit(logger_name=LOGGER_NAME, log_duration=True, track_attr=None)

Decorator for logging context manager entry and exit, with optional duration and tracking.

Parameters:

Name Type Description Default
logger_name str

Name of the logger to use (default: LOGGER_NAME).

LOGGER_NAME
log_duration bool

If True, log the duration of the context (default: True).

True
track_attr str or None

If provided, track additions to this model attribute (e.g., 'rules').

None

Returns:

Type Description
function

Decorated context manager method.

Source code in qspy\utils\logging.py
def log_context_entry_exit(logger_name=LOGGER_NAME, log_duration=True, track_attr=None):
    """
    Decorator for logging context manager entry and exit, with optional duration and tracking.

    Parameters
    ----------
    logger_name : str, optional
        Name of the logger to use (default: LOGGER_NAME).
    log_duration : bool, optional
        If True, log the duration of the context (default: True).
    track_attr : str or None, optional
        If provided, track additions to this model attribute (e.g., 'rules').

    Returns
    -------
    function
        Decorated context manager method.
    """

    def decorator(method):
        @functools.wraps(method)
        def wrapper(self, *args, **kwargs):
            logger = logging.getLogger(logger_name)
            context_name = getattr(self, "name", self.__class__.__name__)
            is_enter = method.__name__ == "__enter__"
            is_exit = method.__name__ == "__exit__"

            if is_enter:
                self._qspy_context_start = time.time()
                self._qspy_pre_ids = set()
                if track_attr:
                    tracked = getattr(self.model, track_attr, [])
                    self._qspy_pre_ids = set(id(x) for x in tracked)
                logger.info(f">>> Entering context: `{context_name}`")

            result = method(self, *args, **kwargs)

            if is_exit:
                duration = ""
                if log_duration and hasattr(self, "_qspy_context_start"):
                    elapsed = time.time() - self._qspy_context_start
                    duration = f" (duration: {elapsed:.3f}s)"
                logger.info(f"<<< Exiting context: `{context_name}`{duration}")

                if track_attr:
                    tracked = getattr(self.model, track_attr, [])
                    added = [x for x in tracked if id(x) not in self._qspy_pre_ids]
                    logger.info(f"    ↳ Added {len(added)} new `{track_attr}`:")
                    for obj in added:
                        logger.info(f"       - {getattr(obj, 'name', repr(obj))}")

            return result

        return wrapper

    return decorator

log_event(logger_name=LOGGER_NAME, log_args=False, log_result=False, static_method=False)

Decorator for logging function entry, exit, arguments, and results.

Parameters:

Name Type Description Default
logger_name str

Name of the logger to use (default: LOGGER_NAME).

LOGGER_NAME
log_args bool

If True, log function arguments (default: False).

False
log_result bool

If True, log function result (default: False).

False
static_method bool

If True, skip the first argument (for static methods).

False

Returns:

Type Description
function

Decorated function with logging.

Source code in qspy\utils\logging.py
def log_event(
    logger_name=LOGGER_NAME, log_args=False, log_result=False, static_method=False
):
    """
    Decorator for logging function entry, exit, arguments, and results.

    Parameters
    ----------
    logger_name : str, optional
        Name of the logger to use (default: LOGGER_NAME).
    log_args : bool, optional
        If True, log function arguments (default: False).
    log_result : bool, optional
        If True, log function result (default: False).
    static_method : bool, optional
        If True, skip the first argument (for static methods).

    Returns
    -------
    function
        Decorated function with logging.
    """
    ensure_qspy_logging()
    logger = logging.getLogger(logger_name)

    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            if static_method:
                args = args[1:]
            fname = func.__qualname__
            logger.info(f">>> Entering `{fname}`")
            if log_args:
                logger.info(f"    Args: {args}, Kwargs: {kwargs}")
            start = time.time()
            result = func(*args, **kwargs)
            duration = time.time() - start
            logger.info(f"<<< Exiting `{fname}` ({duration:.3f}s)")
            if log_result:
                logger.info(f"    Result: {result}")
            return result

        return wrapper

    return decorator

log_model_metadata(metadata, logger_name=LOGGER_NAME, level=logging.INFO, redact=True)

Log QSPy model metadata in structured format, with optional redaction.

Parameters:

Name Type Description Default
metadata dict

The metadata dictionary to log.

required
logger_name str

Name of the logger.

LOGGER_NAME
level int

Logging level.

INFO
redact bool

If True, redact sensitive fields (default: True).

True

Returns:

Type Description
None
Source code in qspy\utils\logging.py
def log_model_metadata(
    metadata, logger_name=LOGGER_NAME, level=logging.INFO, redact=True
):
    """
    Log QSPy model metadata in structured format, with optional redaction.

    Parameters
    ----------
    metadata : dict
        The metadata dictionary to log.
    logger_name : str, optional
        Name of the logger.
    level : int, optional
        Logging level.
    redact : bool, optional
        If True, redact sensitive fields (default: True).

    Returns
    -------
    None
    """
    logger = logging.getLogger(logger_name)
    if not metadata:
        logger.warning("No metadata provided to log.")
        return

    header = "QSPy metadata snapshot"
    if redact:
        metadata = redact_sensitive(metadata)
        header += " (sensitive fields redacted)"
    logger.log(level, header + ":")

    for line in pprint.pformat(metadata, indent=2).splitlines():
        logger.log(level, f"    {line}")

redact_sensitive(data)

Recursively redact sensitive fields in a dictionary or list.

Parameters:

Name Type Description Default
data dict or list or object

The data structure to redact.

required

Returns:

Type Description
object

The redacted data structure.

Source code in qspy\utils\logging.py
def redact_sensitive(data):
    """
    Recursively redact sensitive fields in a dictionary or list.

    Parameters
    ----------
    data : dict or list or object
        The data structure to redact.

    Returns
    -------
    object
        The redacted data structure.
    """
    if isinstance(data, dict):
        return {
            k: "[REDACTED]" if k in REDACT_KEYS else redact_sensitive(v)
            for k, v in data.items()
        }
    elif isinstance(data, list):
        return [redact_sensitive(i) for i in data]
    return data

setup_qspy_logger(log_path=LOG_PATH, max_bytes=1000000, backup_count=5, level=logging.INFO)

Set up the QSPy logger with a rotating file handler.

Parameters:

Name Type Description Default
log_path str or Path

Path to the log file (default: LOG_PATH).

LOG_PATH
max_bytes int

Maximum size of a log file before rotation (default: 1,000,000).

1000000
backup_count int

Number of backup log files to keep (default: 5).

5
level int

Logging level (default: logging.INFO).

INFO

Returns:

Type Description
Logger

The configured QSPy logger.

Source code in qspy\utils\logging.py
def setup_qspy_logger(
    log_path=LOG_PATH, max_bytes=1_000_000, backup_count=5, level=logging.INFO
):
    """
    Set up the QSPy logger with a rotating file handler.

    Parameters
    ----------
    log_path : str or Path, optional
        Path to the log file (default: LOG_PATH).
    max_bytes : int, optional
        Maximum size of a log file before rotation (default: 1,000,000).
    backup_count : int, optional
        Number of backup log files to keep (default: 5).
    level : int, optional
        Logging level (default: logging.INFO).

    Returns
    -------
    logging.Logger
        The configured QSPy logger.
    """
    log_file = Path(log_path)
    log_file.parent.mkdir(parents=True, exist_ok=True)

    handler = RotatingFileHandler(
        filename=log_file,
        maxBytes=max_bytes,
        backupCount=backup_count,
        encoding="utf-8",
    )

    formatter = logging.Formatter(
        fmt="%(asctime)s [%(levelname)s] %(message)s", datefmt="%Y-%m-%d %H:%M:%S"
    )
    handler.setFormatter(formatter)

    logger = logging.getLogger(LOGGER_NAME)
    logger.setLevel(level)

    if not logger.hasHandlers():  # Prevent duplicate handlers on reload
        logger.addHandler(handler)

    logger.info("QSPy logging initialized.")
    return logger

Experimental Features

qspy.experimental.infix_macros

QSPy experimental infix macros for expressive model syntax.

Provides infix-style macro objects for binding, elimination, and equilibrium interactions: - binds: infix macro for reversible binding reactions - eliminated: infix macro for elimination reactions - equilibrates: infix macro for reversible state transitions

These macros enable expressive model code such as: species binds target & (k_f, k_r) species eliminated compartment & k_deg state1 equilibrates state2 & (k_f, k_r)

InfixMacro

Bases: ABC

Abstract base class for infix-style macros in QSPy.

This class provides a structure for creating infix-style macros that can be used in a way that more closely resembles specifying a biological action. Adapted from Infix class example at: https://discuss.python.org/t/infix-function-in-python/41820/2

Source code in qspy\experimental\infix_macros.py
class InfixMacro(ABC):
    """
    Abstract base class for infix-style macros in QSPy.

    This class provides a structure for creating infix-style macros that can be used
    in a way that more closely resembles specifying a biological action.
    Adapted from `Infix` class example at: https://discuss.python.org/t/infix-function-in-python/41820/2
    """

    def __init__(self, lhs=None, rhs=None):
        """
        Initialize the infix macro with optional left and right sides.

        Parameters
        ----------
        lhs : any, optional
            The left-hand side of the infix operation.
        rhs : any, optional
            The right-hand side of the infix operation.
        """
        self.lhs = lhs
        self.rhs = rhs

    @abstractmethod
    def execute_macro(self, lhs, rhs, at):
        """
        Abstract method to execute the macro logic.

        Parameters
        ----------
        lhs : any
            The left-hand side operand.
        rhs : any
            The right-hand side operand.
        at : any
            Additional argument for the macro.

        Returns
        -------
        any
            The result of the macro operation.
        """
        pass

    def __rmul__(self, lhs):
        """
        Capture the left-hand side operand for the infix macro.

        Parameters
        ----------
        lhs : any
            The left-hand side operand.

        Returns
        -------
        InfixMacro
            The same instance with lhs set.
        """
        self.lhs = lhs
        return self

    def __mul__(self, rhs):
        """
        Capture the right-hand side operand for the infix macro.

        Parameters
        ----------
        rhs : any
            The right-hand side operand.

        Returns
        -------
        InfixMacro
            The same instance with rhs set.
        """
        self.rhs = rhs
        return self

    def __and__(self, at):
        """
        Execute the macro logic using the & operator.

        Parameters
        ----------
        at : any
            Additional argument for the macro.

        Returns
        -------
        any
            The result of the macro operation.
        """
        return self.execute_macro(self.lhs, self.rhs, at)

    @staticmethod
    def parse_pattern(pattern: MonomerPattern | ComplexPattern):
        """
        Parse the monomer pattern to extract relevant information.

        Parameters
        ----------
        pattern : MonomerPattern or ComplexPattern
            The pattern to parse.

        Returns
        -------
        tuple
            A tuple containing the monomer, binding site, state, and compartment.
        """
        mono = pattern.monomer
        bsite = next(
            (key for key, value in pattern.site_conditions.items() if value is None),
            None,
        )
        state = {
            key: value for key, value in pattern.site_conditions.items() if key != bsite
        }
        compartment = pattern.compartment
        return mono, bsite, state, compartment

__and__(at)

Execute the macro logic using the & operator.

Parameters:

Name Type Description Default
at any

Additional argument for the macro.

required

Returns:

Type Description
any

The result of the macro operation.

Source code in qspy\experimental\infix_macros.py
def __and__(self, at):
    """
    Execute the macro logic using the & operator.

    Parameters
    ----------
    at : any
        Additional argument for the macro.

    Returns
    -------
    any
        The result of the macro operation.
    """
    return self.execute_macro(self.lhs, self.rhs, at)

__init__(lhs=None, rhs=None)

Initialize the infix macro with optional left and right sides.

Parameters:

Name Type Description Default
lhs any

The left-hand side of the infix operation.

None
rhs any

The right-hand side of the infix operation.

None
Source code in qspy\experimental\infix_macros.py
def __init__(self, lhs=None, rhs=None):
    """
    Initialize the infix macro with optional left and right sides.

    Parameters
    ----------
    lhs : any, optional
        The left-hand side of the infix operation.
    rhs : any, optional
        The right-hand side of the infix operation.
    """
    self.lhs = lhs
    self.rhs = rhs

__mul__(rhs)

Capture the right-hand side operand for the infix macro.

Parameters:

Name Type Description Default
rhs any

The right-hand side operand.

required

Returns:

Type Description
InfixMacro

The same instance with rhs set.

Source code in qspy\experimental\infix_macros.py
def __mul__(self, rhs):
    """
    Capture the right-hand side operand for the infix macro.

    Parameters
    ----------
    rhs : any
        The right-hand side operand.

    Returns
    -------
    InfixMacro
        The same instance with rhs set.
    """
    self.rhs = rhs
    return self

__rmul__(lhs)

Capture the left-hand side operand for the infix macro.

Parameters:

Name Type Description Default
lhs any

The left-hand side operand.

required

Returns:

Type Description
InfixMacro

The same instance with lhs set.

Source code in qspy\experimental\infix_macros.py
def __rmul__(self, lhs):
    """
    Capture the left-hand side operand for the infix macro.

    Parameters
    ----------
    lhs : any
        The left-hand side operand.

    Returns
    -------
    InfixMacro
        The same instance with lhs set.
    """
    self.lhs = lhs
    return self

execute_macro(lhs, rhs, at) abstractmethod

Abstract method to execute the macro logic.

Parameters:

Name Type Description Default
lhs any

The left-hand side operand.

required
rhs any

The right-hand side operand.

required
at any

Additional argument for the macro.

required

Returns:

Type Description
any

The result of the macro operation.

Source code in qspy\experimental\infix_macros.py
@abstractmethod
def execute_macro(self, lhs, rhs, at):
    """
    Abstract method to execute the macro logic.

    Parameters
    ----------
    lhs : any
        The left-hand side operand.
    rhs : any
        The right-hand side operand.
    at : any
        Additional argument for the macro.

    Returns
    -------
    any
        The result of the macro operation.
    """
    pass

parse_pattern(pattern) staticmethod

Parse the monomer pattern to extract relevant information.

Parameters:

Name Type Description Default
pattern MonomerPattern or ComplexPattern

The pattern to parse.

required

Returns:

Type Description
tuple

A tuple containing the monomer, binding site, state, and compartment.

Source code in qspy\experimental\infix_macros.py
@staticmethod
def parse_pattern(pattern: MonomerPattern | ComplexPattern):
    """
    Parse the monomer pattern to extract relevant information.

    Parameters
    ----------
    pattern : MonomerPattern or ComplexPattern
        The pattern to parse.

    Returns
    -------
    tuple
        A tuple containing the monomer, binding site, state, and compartment.
    """
    mono = pattern.monomer
    bsite = next(
        (key for key, value in pattern.site_conditions.items() if value is None),
        None,
    )
    state = {
        key: value for key, value in pattern.site_conditions.items() if key != bsite
    }
    compartment = pattern.compartment
    return mono, bsite, state, compartment

qspy.experimental.functional_monomers

QSPy experimental functional monomers subpackage.

Provides base classes, mixins, and macros for building and manipulating functional monomers, including support for binding, synthesis, degradation, and protein-specific behaviors.

qspy.experimental.functional_monomers.protein

Functional monomer protein classes and mixins for QSPy experimental API.

Provides: - TurnoverMixin: mixin for synthesis and degradation reactions. - Ligand: class for ligand monomers with binding functionality. - Receptor: class for receptor monomers with orthosteric/allosteric binding and activation.

Ligand

Bases: BindMixin, FunctionalMonomer

Class representing a ligand monomer with binding functionality.

Source code in qspy\experimental\functional_monomers\protein.py
class Ligand(BindMixin, FunctionalMonomer):
    """
    Class representing a ligand monomer with binding functionality.
    """

    _sites = ["b"]
    _functional_tag = PROTEIN.LIGAND
    _base_state = {"b": None}

    @property
    def binding_site(self):
        """
        Return the binding site for this ligand.

        Returns
        -------
        str
            The name of the binding site.
        """
        return self._sites[0]

    def binds_to(
        self,
        receptor: "Receptor",
        r_site: str,
        k_f: float | Parameter,
        k_r: float | Parameter,
        compartment: None | Compartment = None,
    ):
        """
        Create a reversible binding reaction between this ligand and a receptor.

        Parameters
        ----------
        receptor : Receptor
            The receptor to bind.
        r_site : str
            The binding site on the receptor.
        k_f : float or Parameter
            Forward rate constant.
        k_r : float or Parameter
            Reverse rate constant.
        compartment : Compartment or None, optional
            Compartment for the reaction (default: None).

        Returns
        -------
        ComponentSet
            PySB ComponentSet for the binding reaction.
        """
        return self.binds(
            self.binding_site, receptor, r_site, k_f, k_r, compartment=compartment
        )

    def concertedly_activates(self, receptor, k_f, k_r, compartment=None):
        """
        Create a concerted activation reaction for a receptor by this ligand.

        Parameters
        ----------
        receptor : Receptor
            The receptor to activate.
        k_f : float or Parameter
            Forward rate constant.
        k_r : float or Parameter
            Reverse rate constant.
        compartment : Compartment or None, optional
            Compartment for the reaction (default: None).

        Returns
        -------
        ComponentSet
            PySB ComponentSet for the concerted activation reaction.
        """
        return receptor.concertedly_activated_by(self, k_f, k_r, compartment)

binding_site property

Return the binding site for this ligand.

Returns:

Type Description
str

The name of the binding site.

binds_to(receptor, r_site, k_f, k_r, compartment=None)

Create a reversible binding reaction between this ligand and a receptor.

Parameters:

Name Type Description Default
receptor Receptor

The receptor to bind.

required
r_site str

The binding site on the receptor.

required
k_f float or Parameter

Forward rate constant.

required
k_r float or Parameter

Reverse rate constant.

required
compartment Compartment or None

Compartment for the reaction (default: None).

None

Returns:

Type Description
ComponentSet

PySB ComponentSet for the binding reaction.

Source code in qspy\experimental\functional_monomers\protein.py
def binds_to(
    self,
    receptor: "Receptor",
    r_site: str,
    k_f: float | Parameter,
    k_r: float | Parameter,
    compartment: None | Compartment = None,
):
    """
    Create a reversible binding reaction between this ligand and a receptor.

    Parameters
    ----------
    receptor : Receptor
        The receptor to bind.
    r_site : str
        The binding site on the receptor.
    k_f : float or Parameter
        Forward rate constant.
    k_r : float or Parameter
        Reverse rate constant.
    compartment : Compartment or None, optional
        Compartment for the reaction (default: None).

    Returns
    -------
    ComponentSet
        PySB ComponentSet for the binding reaction.
    """
    return self.binds(
        self.binding_site, receptor, r_site, k_f, k_r, compartment=compartment
    )

concertedly_activates(receptor, k_f, k_r, compartment=None)

Create a concerted activation reaction for a receptor by this ligand.

Parameters:

Name Type Description Default
receptor Receptor

The receptor to activate.

required
k_f float or Parameter

Forward rate constant.

required
k_r float or Parameter

Reverse rate constant.

required
compartment Compartment or None

Compartment for the reaction (default: None).

None

Returns:

Type Description
ComponentSet

PySB ComponentSet for the concerted activation reaction.

Source code in qspy\experimental\functional_monomers\protein.py
def concertedly_activates(self, receptor, k_f, k_r, compartment=None):
    """
    Create a concerted activation reaction for a receptor by this ligand.

    Parameters
    ----------
    receptor : Receptor
        The receptor to activate.
    k_f : float or Parameter
        Forward rate constant.
    k_r : float or Parameter
        Reverse rate constant.
    compartment : Compartment or None, optional
        Compartment for the reaction (default: None).

    Returns
    -------
    ComponentSet
        PySB ComponentSet for the concerted activation reaction.
    """
    return receptor.concertedly_activated_by(self, k_f, k_r, compartment)

Receptor

Bases: BindMixin, TurnoverMixin, FunctionalMonomer

Class representing a receptor monomer with orthosteric/allosteric binding and activation.

Source code in qspy\experimental\functional_monomers\protein.py
class Receptor(BindMixin, TurnoverMixin, FunctionalMonomer):
    """
    Class representing a receptor monomer with orthosteric/allosteric binding and activation.
    """

    _sites = ["b_ortho", "b_allo", "active"]
    _site_states = {"active": [False, True]}
    _functional_tag = PROTEIN.RECEPTOR
    _base_state = {"b_ortho": None, "b_allo": None, "active": False}
    _inactive_state = {"active": False}
    _active_state = {"active": True}

    @property
    def binding_sites(self):
        """
        Return the orthosteric and allosteric binding sites.

        Returns
        -------
        list
            List of binding site names.
        """
        return self._sites[:2]

    @property
    def orthosteric_site(self):
        """
        Return the orthosteric binding site.

        Returns
        -------
        str
            Name of the orthosteric site.
        """
        return self._sites[0]

    @property
    def allosteric_site(self):
        """
        Return the allosteric binding site.

        Returns
        -------
        str
            Name of the allosteric site.
        """
        return self._sites[1]

    @property
    def inactive(self):
        """
        Return the inactive state dictionary.

        Returns
        -------
        dict
            Dictionary representing the inactive state.
        """
        return self._inactive_state

    @property
    def active(self):
        """
        Return the active state dictionary.

        Returns
        -------
        dict
            Dictionary representing the active state.
        """
        return self._active_state

    def _binds_orthosteric(
        self,
        ligand: Ligand,
        k_f: float | Parameter,
        k_r: float | Parameter,
        compartment: None | Compartment = None,
    ):
        """
        Create a reversible binding reaction at the orthosteric site.

        Parameters
        ----------
        ligand : Ligand
            The ligand to bind.
        k_f : float or Parameter
            Forward rate constant.
        k_r : float or Parameter
            Reverse rate constant.
        compartment : Compartment or None, optional
            Compartment for the reaction (default: None).

        Returns
        -------
        ComponentSet
            PySB ComponentSet for the binding reaction.
        """
        return self.binds(
            self.orthosteric_site,
            ligand,
            ligand.binding_site,
            k_f,
            k_r,
            compartment=compartment,
        )

    def _binds_allosteric(self, ligand: Ligand, k_f, k_r, compartment=None):
        """
        Create a reversible binding reaction at the allosteric site.

        Parameters
        ----------
        ligand : Ligand
            The ligand to bind.
        k_f : float or Parameter
            Forward rate constant.
        k_r : float or Parameter
            Reverse rate constant.
        compartment : Compartment or None, optional
            Compartment for the reaction (default: None).

        Returns
        -------
        ComponentSet
            PySB ComponentSet for the binding reaction.
        """
        return self.binds(
            self.allosteric_site,
            ligand,
            ligand.binding_site,
            k_f,
            k_r,
            compartment=compartment,
        )

    def bound_by(self, ligand, k_f, k_r, location="orthosteric", compartment=None):
        """
        Create a reversible binding reaction at the specified site.

        Parameters
        ----------
        ligand : Ligand
            The ligand to bind.
        k_f : float or Parameter
            Forward rate constant.
        k_r : float or Parameter
            Reverse rate constant.
        location : str, optional
            "orthosteric" or "allosteric" (default: "orthosteric").
        compartment : Compartment or None, optional
            Compartment for the reaction (default: None).

        Returns
        -------
        ComponentSet
            PySB ComponentSet for the binding reaction.
        """
        if location == "orthosteric":
            return self._binds_orthosteric(ligand, k_f, k_r, compartment=compartment)
        elif location == "allosteric":
            return self._binds_allosteric(ligand, k_f, k_r, compartment=compartment)

    def concertedly_activated_by(self, ligand: Ligand, k_f, k_r, compartment=None):
        """
        Create a concerted activation reaction for this receptor by a ligand.

        Parameters
        ----------
        ligand : Ligand
            The ligand to activate this receptor.
        k_f : float or Parameter
            Forward rate constant.
        k_r : float or Parameter
            Reverse rate constant.
        compartment : Compartment or None, optional
            Compartment for the reaction (default: None).

        Returns
        -------
        ComponentSet
            PySB ComponentSet for the concerted activation reaction.
        """
        return activate_concerted(
            ligand,
            ligand.binding_site,
            self,
            self.orthosteric_site,
            self.inactive,
            self.active,
            [k_f, k_r],
            compartment=compartment,
        )

active property

Return the active state dictionary.

Returns:

Type Description
dict

Dictionary representing the active state.

allosteric_site property

Return the allosteric binding site.

Returns:

Type Description
str

Name of the allosteric site.

binding_sites property

Return the orthosteric and allosteric binding sites.

Returns:

Type Description
list

List of binding site names.

inactive property

Return the inactive state dictionary.

Returns:

Type Description
dict

Dictionary representing the inactive state.

orthosteric_site property

Return the orthosteric binding site.

Returns:

Type Description
str

Name of the orthosteric site.

bound_by(ligand, k_f, k_r, location='orthosteric', compartment=None)

Create a reversible binding reaction at the specified site.

Parameters:

Name Type Description Default
ligand Ligand

The ligand to bind.

required
k_f float or Parameter

Forward rate constant.

required
k_r float or Parameter

Reverse rate constant.

required
location str

"orthosteric" or "allosteric" (default: "orthosteric").

'orthosteric'
compartment Compartment or None

Compartment for the reaction (default: None).

None

Returns:

Type Description
ComponentSet

PySB ComponentSet for the binding reaction.

Source code in qspy\experimental\functional_monomers\protein.py
def bound_by(self, ligand, k_f, k_r, location="orthosteric", compartment=None):
    """
    Create a reversible binding reaction at the specified site.

    Parameters
    ----------
    ligand : Ligand
        The ligand to bind.
    k_f : float or Parameter
        Forward rate constant.
    k_r : float or Parameter
        Reverse rate constant.
    location : str, optional
        "orthosteric" or "allosteric" (default: "orthosteric").
    compartment : Compartment or None, optional
        Compartment for the reaction (default: None).

    Returns
    -------
    ComponentSet
        PySB ComponentSet for the binding reaction.
    """
    if location == "orthosteric":
        return self._binds_orthosteric(ligand, k_f, k_r, compartment=compartment)
    elif location == "allosteric":
        return self._binds_allosteric(ligand, k_f, k_r, compartment=compartment)

concertedly_activated_by(ligand, k_f, k_r, compartment=None)

Create a concerted activation reaction for this receptor by a ligand.

Parameters:

Name Type Description Default
ligand Ligand

The ligand to activate this receptor.

required
k_f float or Parameter

Forward rate constant.

required
k_r float or Parameter

Reverse rate constant.

required
compartment Compartment or None

Compartment for the reaction (default: None).

None

Returns:

Type Description
ComponentSet

PySB ComponentSet for the concerted activation reaction.

Source code in qspy\experimental\functional_monomers\protein.py
def concertedly_activated_by(self, ligand: Ligand, k_f, k_r, compartment=None):
    """
    Create a concerted activation reaction for this receptor by a ligand.

    Parameters
    ----------
    ligand : Ligand
        The ligand to activate this receptor.
    k_f : float or Parameter
        Forward rate constant.
    k_r : float or Parameter
        Reverse rate constant.
    compartment : Compartment or None, optional
        Compartment for the reaction (default: None).

    Returns
    -------
    ComponentSet
        PySB ComponentSet for the concerted activation reaction.
    """
    return activate_concerted(
        ligand,
        ligand.binding_site,
        self,
        self.orthosteric_site,
        self.inactive,
        self.active,
        [k_f, k_r],
        compartment=compartment,
    )

TurnoverMixin

Bases: DegradeMixin, SynthesizeMixin

Mixin class providing a turnover() method for synthesis and degradation reactions.

Source code in qspy\experimental\functional_monomers\protein.py
class TurnoverMixin(DegradeMixin, SynthesizeMixin):
    """
    Mixin class providing a turnover() method for synthesis and degradation reactions.
    """

    def turnover(
        self,
        k_syn: float | Parameter,
        k_deg: float | Parameter,
        compartment: None | Compartment = None,
    ) -> ComponentSet:
        """
        Create synthesis and degradation reactions for this monomer.

        Parameters
        ----------
        k_syn : float or Parameter
            Synthesis rate constant.
        k_deg : float or Parameter
            Degradation rate constant.
        compartment : Compartment or None, optional
            Compartment for the reactions (default: None).

        Returns
        -------
        ComponentSet
            Combined PySB ComponentSet for synthesis and degradation.
        """
        components_syn = self.synthesized(k_syn, compartment=compartment)
        components_deg = self.degraded({}, k_deg, compartment=compartment)
        return components_syn | components_deg

turnover(k_syn, k_deg, compartment=None)

Create synthesis and degradation reactions for this monomer.

Parameters:

Name Type Description Default
k_syn float or Parameter

Synthesis rate constant.

required
k_deg float or Parameter

Degradation rate constant.

required
compartment Compartment or None

Compartment for the reactions (default: None).

None

Returns:

Type Description
ComponentSet

Combined PySB ComponentSet for synthesis and degradation.

Source code in qspy\experimental\functional_monomers\protein.py
def turnover(
    self,
    k_syn: float | Parameter,
    k_deg: float | Parameter,
    compartment: None | Compartment = None,
) -> ComponentSet:
    """
    Create synthesis and degradation reactions for this monomer.

    Parameters
    ----------
    k_syn : float or Parameter
        Synthesis rate constant.
    k_deg : float or Parameter
        Degradation rate constant.
    compartment : Compartment or None, optional
        Compartment for the reactions (default: None).

    Returns
    -------
    ComponentSet
        Combined PySB ComponentSet for synthesis and degradation.
    """
    components_syn = self.synthesized(k_syn, compartment=compartment)
    components_deg = self.degraded({}, k_deg, compartment=compartment)
    return components_syn | components_deg

qspy.experimental.functional_monomers.base

FunctionalMonomer base classes and mixins for QSPy experimental functional monomer API.

Provides: - FunctionalMonomer: base class for monomers with functional tags and base states. - BindMixin: mixin for binding macro methods. - SynthesizeMixin: mixin for synthesis macro methods. - DegradeMixin: mixin for degradation macro methods.

BindMixin

Mixin class providing a binds() method for binding reactions.

Source code in qspy\experimental\functional_monomers\base.py
class BindMixin:
    """
    Mixin class providing a binds() method for binding reactions.
    """

    def binds(
        self,
        site: str,
        other: Monomer | MonomerPattern | FunctionalMonomer,
        other_site: str,
        k_f: float | Parameter,
        k_r: float | Parameter,
        compartment: None | Compartment = None,
    ) -> ComponentSet:
        """
        Create a reversible binding reaction between this monomer and another.

        Parameters
        ----------
        site : str
            Binding site on this monomer.
        other : Monomer, MonomerPattern, or FunctionalMonomer
            The other monomer or pattern to bind.
        other_site : str
            Binding site on the other monomer.
        k_f : float or Parameter
            Forward rate constant.
        k_r : float or Parameter
            Reverse rate constant.
        compartment : Compartment or None, optional
            Compartment for the reaction (default: None).

        Returns
        -------
        ComponentSet
            PySB ComponentSet for the binding reaction.
        """
        if compartment is None:
            return bind(self, site, other, other_site, klist=[k_f, k_r])
        else:
            return bind(
                _check_for_monomer(self, compartment),
                site,
                _check_for_monomer(other, compartment),
                other_site,
                klist=[k_f, k_r],
            )

binds(site, other, other_site, k_f, k_r, compartment=None)

Create a reversible binding reaction between this monomer and another.

Parameters:

Name Type Description Default
site str

Binding site on this monomer.

required
other Monomer, MonomerPattern, or FunctionalMonomer

The other monomer or pattern to bind.

required
other_site str

Binding site on the other monomer.

required
k_f float or Parameter

Forward rate constant.

required
k_r float or Parameter

Reverse rate constant.

required
compartment Compartment or None

Compartment for the reaction (default: None).

None

Returns:

Type Description
ComponentSet

PySB ComponentSet for the binding reaction.

Source code in qspy\experimental\functional_monomers\base.py
def binds(
    self,
    site: str,
    other: Monomer | MonomerPattern | FunctionalMonomer,
    other_site: str,
    k_f: float | Parameter,
    k_r: float | Parameter,
    compartment: None | Compartment = None,
) -> ComponentSet:
    """
    Create a reversible binding reaction between this monomer and another.

    Parameters
    ----------
    site : str
        Binding site on this monomer.
    other : Monomer, MonomerPattern, or FunctionalMonomer
        The other monomer or pattern to bind.
    other_site : str
        Binding site on the other monomer.
    k_f : float or Parameter
        Forward rate constant.
    k_r : float or Parameter
        Reverse rate constant.
    compartment : Compartment or None, optional
        Compartment for the reaction (default: None).

    Returns
    -------
    ComponentSet
        PySB ComponentSet for the binding reaction.
    """
    if compartment is None:
        return bind(self, site, other, other_site, klist=[k_f, k_r])
    else:
        return bind(
            _check_for_monomer(self, compartment),
            site,
            _check_for_monomer(other, compartment),
            other_site,
            klist=[k_f, k_r],
        )

DegradeMixin

Mixin class providing a degraded() method for degradation reactions.

Source code in qspy\experimental\functional_monomers\base.py
class DegradeMixin:
    """
    Mixin class providing a degraded() method for degradation reactions.
    """

    def degraded(
        self,
        state: dict,
        k_deg: float | Parameter,
        compartment: None | Compartment = None,
    ):
        """
        Create a degradation reaction for this monomer in a given state.

        Parameters
        ----------
        state : dict
            State assignment for the monomer.
        k_deg : float or Parameter
            Degradation rate constant.
        compartment : Compartment or None, optional
            Compartment for the reaction (default: None).

        Returns
        -------
        ComponentSet
            PySB ComponentSet for the degradation reaction.
        """
        if compartment is None:
            return degrade(
                self(**state),
                k_deg,
            )
        else:
            return degrade(
                _check_for_monomer(self(**state), compartment),
                k_deg,
            )

degraded(state, k_deg, compartment=None)

Create a degradation reaction for this monomer in a given state.

Parameters:

Name Type Description Default
state dict

State assignment for the monomer.

required
k_deg float or Parameter

Degradation rate constant.

required
compartment Compartment or None

Compartment for the reaction (default: None).

None

Returns:

Type Description
ComponentSet

PySB ComponentSet for the degradation reaction.

Source code in qspy\experimental\functional_monomers\base.py
def degraded(
    self,
    state: dict,
    k_deg: float | Parameter,
    compartment: None | Compartment = None,
):
    """
    Create a degradation reaction for this monomer in a given state.

    Parameters
    ----------
    state : dict
        State assignment for the monomer.
    k_deg : float or Parameter
        Degradation rate constant.
    compartment : Compartment or None, optional
        Compartment for the reaction (default: None).

    Returns
    -------
    ComponentSet
        PySB ComponentSet for the degradation reaction.
    """
    if compartment is None:
        return degrade(
            self(**state),
            k_deg,
        )
    else:
        return degrade(
            _check_for_monomer(self(**state), compartment),
            k_deg,
        )

FunctionalMonomer

Bases: Monomer

Base class for functional monomers in QSPy.

Adds support for binding sites, site states, functional tags, and a base state.

Source code in qspy\experimental\functional_monomers\base.py
class FunctionalMonomer(Monomer):
    """
    Base class for functional monomers in QSPy.

    Adds support for binding sites, site states, functional tags, and a base state.
    """

    _sites = None
    _site_states = None
    _functional_tag = None
    _base_state = dict()

    def __init__(self, name: str):
        """
        Initialize a FunctionalMonomer.

        Parameters
        ----------
        name : str
            Name of the monomer.
        """
        super(FunctionalMonomer, self).__init__(name, self._sites, self._site_states)
        self @= self._functional_tag
        return

    @property
    def binding_sites(self) -> list:
        """
        List of binding sites for this monomer.

        Returns
        -------
        list
            List of binding site names.
        """
        return self._sites

    @property
    def states(self) -> dict:
        """
        Dictionary of site states for this monomer.

        Returns
        -------
        dict
            Dictionary mapping site names to possible states.
        """
        return self._site_states

    @property
    def base_state(self) -> dict:
        """
        Dictionary of base state values for this monomer.

        Returns
        -------
        dict
            Dictionary of base state assignments.
        """
        return self._base_state

base_state property

Dictionary of base state values for this monomer.

Returns:

Type Description
dict

Dictionary of base state assignments.

binding_sites property

List of binding sites for this monomer.

Returns:

Type Description
list

List of binding site names.

states property

Dictionary of site states for this monomer.

Returns:

Type Description
dict

Dictionary mapping site names to possible states.

__init__(name)

Initialize a FunctionalMonomer.

Parameters:

Name Type Description Default
name str

Name of the monomer.

required
Source code in qspy\experimental\functional_monomers\base.py
def __init__(self, name: str):
    """
    Initialize a FunctionalMonomer.

    Parameters
    ----------
    name : str
        Name of the monomer.
    """
    super(FunctionalMonomer, self).__init__(name, self._sites, self._site_states)
    self @= self._functional_tag
    return

SynthesizeMixin

Mixin class providing a synthesized() method for synthesis reactions.

Source code in qspy\experimental\functional_monomers\base.py
class SynthesizeMixin:
    """
    Mixin class providing a synthesized() method for synthesis reactions.
    """

    def synthesized(
        self, k_syn: float | Parameter, compartment: None | Compartment = None
    ) -> ComponentSet:
        """
        Create a synthesis reaction for this monomer.

        Parameters
        ----------
        k_syn : float or Parameter
            Synthesis rate constant.
        compartment : Compartment or None, optional
            Compartment for the reaction (default: None).

        Returns
        -------
        ComponentSet
            PySB ComponentSet for the synthesis reaction.
        """
        if compartment is None:
            return synthesize(
                self(**self.base_state),
                k_syn,
            )
        else:
            return synthesize(
                _check_for_monomer(self(**self.base_state), compartment),
                k_syn,
            )

synthesized(k_syn, compartment=None)

Create a synthesis reaction for this monomer.

Parameters:

Name Type Description Default
k_syn float or Parameter

Synthesis rate constant.

required
compartment Compartment or None

Compartment for the reaction (default: None).

None

Returns:

Type Description
ComponentSet

PySB ComponentSet for the synthesis reaction.

Source code in qspy\experimental\functional_monomers\base.py
def synthesized(
    self, k_syn: float | Parameter, compartment: None | Compartment = None
) -> ComponentSet:
    """
    Create a synthesis reaction for this monomer.

    Parameters
    ----------
    k_syn : float or Parameter
        Synthesis rate constant.
    compartment : Compartment or None, optional
        Compartment for the reaction (default: None).

    Returns
    -------
    ComponentSet
        PySB ComponentSet for the synthesis reaction.
    """
    if compartment is None:
        return synthesize(
            self(**self.base_state),
            k_syn,
        )
    else:
        return synthesize(
            _check_for_monomer(self(**self.base_state), compartment),
            k_syn,
        )

qspy.experimental.functional_monomers.macros

Functional monomer macros for QSPy experimental API.

Provides: - activate_concerted: macro for concerted activation of a receptor by a ligand.

activate_concerted(ligand, l_site, receptor, r_site, inactive_state, active_state, k_list, compartment=None)

Generate a concerted activation reaction for a receptor by a ligand.

This macro creates a reversible binding rule where a ligand binds to a receptor, and the receptor transitions from an inactive state to an active state as part of the binding event.

Parameters:

Name Type Description Default
ligand Monomer or MonomerPattern

The ligand species or pattern.

required
l_site str

The binding site on the ligand.

required
receptor Monomer or MonomerPattern

The receptor species or pattern.

required
r_site str

The binding site on the receptor.

required
inactive_state dict

Dictionary specifying the inactive state of the receptor.

required
active_state dict

Dictionary specifying the active state of the receptor.

required
k_list list

List of rate constants [k_f, k_r] for forward and reverse reactions.

required
compartment Compartment or None

The compartment in which the reaction occurs (default: None).

None

Returns:

Type Description
ComponentSet

The generated components, including the reversible activation Rule.

Examples:

Concerted activation of a receptor by a ligand::

Model()
Monomer('Ligand', ['b'])
Monomer('Receptor', ['b', 'state'], {'state': ['inactive', 'active']})
activate_concerted(
    Ligand, 'b', Receptor, 'b',
    {'state': 'inactive'}, {'state': 'active'},
    [1e-3, 1e-3]
)
Source code in qspy\experimental\functional_monomers\macros.py
def activate_concerted(
    ligand: Monomer | MonomerPattern,
    l_site: str,
    receptor: Monomer | MonomerPattern,
    r_site: str,
    inactive_state: dict,
    active_state: dict,
    k_list: list,
    compartment: None | Compartment = None,
):
    """
    Generate a concerted activation reaction for a receptor by a ligand.

    This macro creates a reversible binding rule where a ligand binds to a receptor,
    and the receptor transitions from an inactive state to an active state as part of the binding event.

    Parameters
    ----------
    ligand : Monomer or MonomerPattern
        The ligand species or pattern.
    l_site : str
        The binding site on the ligand.
    receptor : Monomer or MonomerPattern
        The receptor species or pattern.
    r_site : str
        The binding site on the receptor.
    inactive_state : dict
        Dictionary specifying the inactive state of the receptor.
    active_state : dict
        Dictionary specifying the active state of the receptor.
    k_list : list
        List of rate constants [k_f, k_r] for forward and reverse reactions.
    compartment : Compartment or None, optional
        The compartment in which the reaction occurs (default: None).

    Returns
    -------
    ComponentSet
        The generated components, including the reversible activation Rule.

    Examples
    --------
    Concerted activation of a receptor by a ligand::

        Model()
        Monomer('Ligand', ['b'])
        Monomer('Receptor', ['b', 'state'], {'state': ['inactive', 'active']})
        activate_concerted(
            Ligand, 'b', Receptor, 'b',
            {'state': 'inactive'}, {'state': 'active'},
            [1e-3, 1e-3]
        )

    """
    _verify_sites(ligand, l_site)
    _verify_sites(receptor, [r_site] + list(active_state.keys()))
    def activate_concerted_name_func(rule_expression):
        cps = rule_expression.reactant_pattern.complex_patterns
        if compartment is not None:
            comp_name = compartment.name
            return "_".join(_complex_pattern_label(cp) for cp in cps).join(
                ["_", comp_name]
            )
        else:
            return "_".join(_complex_pattern_label(cp) for cp in cps)

    s1_free = ligand(**{l_site: None})
    s1_bound = ligand(**{l_site: 1})
    s2_i = receptor(**{r_site: None}.update(inactive_state))
    s2_a = receptor(**{r_site: 1}.update(active_state))
    if compartment is not None:
        s1_free = _check_for_monomer(s1_free, compartment)
        s1_bound = _check_for_monomer(s1_bound, compartment)
        s2_i = _check_for_monomer(s2_i, compartment)
        s2_a = _check_for_monomer(s2_a, compartment)

    return _macro_rule(
        "activate_concerted",
        s1_free + s2_i | s1_bound % s2_a,
        k_list,
        ["k_f", "k_r"],
        name_func=activate_concerted_name_func,
    )