diff --git a/Makefile b/Makefile
index 52e5e79f..a26d5d9e 100644
--- a/Makefile
+++ b/Makefile
@@ -34,5 +34,5 @@ docs :
cd docs; echo "Running Sphinx docs generator"; sphinx-apidoc -o source/ ../biocrnpyler; python generate_nblinks.py; make clean && make html;
TAGS: biocrnpyler/*.py biocrnpyler/*/*.py biocrnpyler/*/*/*.py docs/*.rst \
- docs/examples/*.ipynb
+ docs/examples/*.ipynb docs/examples/*/*.ipynb
ftags $^
diff --git a/Tests/test_combinatorial_complex.py b/Tests/test_combinatorial_complex.py
index 8d582995..832c826c 100644
--- a/Tests/test_combinatorial_complex.py
+++ b/Tests/test_combinatorial_complex.py
@@ -401,3 +401,21 @@ def R(inputs, outputs):
R([CXZ, X], [CXXZ]),
]
assert all([r in r5_true for r in r5]) and all([r in r5 for r in r5_true])
+
+
+def test_singleton_arguments():
+ X, Y, Z = Species('X'), Species('Y'), Species('Z')
+
+ # Single final_state case
+ C1 = Complex([Y, Z, Z])
+ CC1 = CombinatorialComplex(final_states=C1)
+
+ # tests getters and setters
+ assert set(CC1.final_states) == set([C1])
+ assert set(CC1.sub_species) == set([Y, Z])
+ assert set(CC1.initial_states) == set([Y, Z])
+ assert CC1.intermediate_states is None
+
+ # Test with excluded states
+ C2 = Complex([X, X, Z, Z])
+ CC5 = CombinatorialComplex(final_states=[C2], excluded_states=C1)
diff --git a/biocrnpyler/components/basic.py b/biocrnpyler/components/basic.py
index 7f77d4b4..59eaa7b4 100644
--- a/biocrnpyler/components/basic.py
+++ b/biocrnpyler/components/basic.py
@@ -9,21 +9,55 @@
class DNA(Component):
- """DNA sequence that has a given length.
+ """DNA sequence component with specified length.
+
+ A `DNA` component represents a DNA sequence with a given length in base
+ pairs. This component has no associated mechanism to generate species or
+ reactions, but can be used as a building block for more complex genetic
+ constructs.
+
+ Parameters
+ ----------
+ name : str
+ Name of the DNA sequence.
+ length : int, default=0
+ Length of the DNA sequence in base pairs.
+ attributes : list of str, optional
+ List of attribute tags to associate with the DNA species.
+ **kwargs
+ Additional keyword arguments passed to the `Component` base class
+ constructor.
+
+ Attributes
+ ----------
+ species : Species
+ The DNA species object with material_type='dna'.
+
+ See Also
+ --------
+ RNA : RNA sequence component.
+ Protein : Protein sequence component.
+ Component : Base class for biomolecular components.
+
+ Examples
+ --------
+ Create a simple DNA sequence:
+
+ >>> dna = bcp.DNA(name='my_gene', length=1000)
+ >>> dna.get_species()
+ dna_my_gene
+
+ Create DNA with attributes:
+
+ >>> promoter = bcp.DNA(
+ ... name='pLac',
+ ... length=100,
+ ... attributes=['inducible', 'strong']
+ ... )
- Notes:
- -----
- Produces no reactions.
"""
def __init__(self, name, length=0, attributes=None, **kwargs):
- """Initialize a DNA object to store DNA related information.
-
- :param name: Name of the sequence (str)
- :param length: length of the basepairs (int)
- :param attributes: Species attribute
- :param kwargs: pass into the parent's (Component) initializer
- """
self.species = self.set_species(
name, material_type='dna', attributes=attributes
)
@@ -31,33 +65,91 @@ def __init__(self, name, length=0, attributes=None, **kwargs):
super().__init__(name=name, **kwargs)
def get_species(self) -> Species:
+ """Get the DNA species.
+
+ Returns
+ -------
+ Species
+ The DNA species object with material_type='dna'.
+
+ """
return self.species
def update_species(self) -> List[Species]:
+ """Generate species associated with the DNA component.
+
+ Returns
+ -------
+ list of Species
+ List containing only the DNA species itself, as DNA has no
+ associated mechanism to produce additional species.
+
+ """
species = [self.get_species()]
return species
def update_reactions(self) -> List:
+ """Generate reactions associated with the DNA component.
+
+ Returns
+ -------
+ list
+ Empty list, as DNA has no associated mechanism.
+
+ """
return []
class RNA(Component):
- """RNA sequence of a given length.
+ """RNA sequence component with specified length.
+
+ An `RNA` component represents an RNA sequence with a given length in base
+ pairs. This component has no associated mechanism to generate species or
+ reactions, but can be used to represent mRNA, tRNA, rRNA, or other RNA
+ molecules.
+
+ Parameters
+ ----------
+ name : str
+ Name of the RNA sequence.
+ length : int, default=0
+ Length of the RNA sequence in base pairs.
+ attributes : list of str, optional
+ List of attribute tags to associate with the RNA species.
+ **kwargs
+ Additional keyword arguments passed to the `Component` base class
+ constructor.
+
+ Attributes
+ ----------
+ species : Species
+ The RNA species object with material_type='rna'.
+
+ See Also
+ --------
+ DNA : DNA sequence component.
+ Protein : Protein sequence component.
+ Component : Base class for biomolecular components.
+
+ Examples
+ --------
+ Create a simple RNA sequence:
+
+ >>> rna = bcp.RNA(name='my_transcript', length=500)
+ >>> rna.get_species()
+ rna_my_transcript
+
+ Create mRNA with attributes:
+
+ >>> mrna = bcp.RNA(
+ ... name='gfp_mrna',
+ ... length=750,
+ ... attributes=['coding', 'stable']
+ ... )
- Notes:
- -----
- Produces no reactions.
"""
def __init__(self, name: str, length=0, attributes=None, **kwargs):
- """Initialize an RNA object to store RNA related information.
-
- :param name: name of the rna
- :param length: number of basepairs (int)
- :param attributes: Species attribute
- :param kwargs: pass into the parent's (Component) initializer
-
- """
self.species = self.set_species(
name, material_type='rna', attributes=attributes
)
@@ -65,34 +157,94 @@ def __init__(self, name: str, length=0, attributes=None, **kwargs):
super().__init__(name=name, **kwargs)
def get_species(self) -> Species:
+ """Get the RNA species.
+
+ Returns
+ -------
+ Species
+ The RNA species object with material_type='rna'.
+
+ """
return self.species
def update_species(self) -> List[Species]:
+ """Generate species associated with the RNA component.
+
+ Returns
+ -------
+ list of Species
+ List containing only the RNA species itself, as RNA has not
+ associated mechanism to produce additional species.
+
+ """
species = [self.get_species()]
return species
def update_reactions(self) -> List:
+ """Generate reactions associated with the RNA component.
+
+ Returns
+ -------
+ list
+ Empty list, as RNA has no associated mechanism.
+
+ """
return []
class Protein(Component):
- """Protein/peptide of a given length.
-
- Notes:
- -----
- Produces no reactions.
+ """Protein component with specified length.
+
+ A `Protein` component represents a protein or peptide with a given length
+ in amino acids. This component has no associated mechanism to generate
+ species or reactions, but can be used to represent enzymes, transcription
+ factors, structural proteins, or any other protein molecules.
+
+ Parameters
+ ----------
+ name : str
+ Name of the protein.
+ length : int, default=0
+ Length of the protein in number of amino acids.
+ attributes : list of str, optional
+ List of attribute tags to associate with the protein species. Common
+ attributes include degradation tags (e.g., 'ssrAtagged') or functional
+ properties (e.g., 'fluorescent').
+ **kwargs
+ Additional keyword arguments passed to the `Component` base class
+ constructor.
+
+ Attributes
+ ----------
+ species : Species
+ The protein species object with material_type='protein'.
+
+ See Also
+ --------
+ DNA : DNA sequence component.
+ RNA : RNA sequence component.
+ Enzyme : Enzymatic protein component.
+ Component : Base class for biomolecular components.
+
+ Examples
+ --------
+ Create a simple protein:
+
+ >>> protein = bcp.Protein(name='GFP', length=238)
+ >>> protein.get_species()
+ protein_GFP
+
+ Create a protein with degradation tag:
+
+ >>> protein = bcp.Protein(
+ ... name='LacI',
+ ... length=360,
+ ... attributes=['ssrAtagged']
+ ... )
"""
def __init__(self, name: str, length=0, attributes=None, **kwargs):
- """Initialize a Protein object to store Protein related information.
-
- :param name: name of the protein
- :param length: length of the protein in number of amino acids
- :param attributes: Species attribute
- :param kwargs: pass into the parent's (Component) initializer
-
- """
self.species = self.set_species(
name, material_type='protein', attributes=attributes
)
@@ -100,23 +252,121 @@ def __init__(self, name: str, length=0, attributes=None, **kwargs):
super().__init__(name=name, **kwargs)
def get_species(self) -> Species:
+ """Get the protein species.
+
+ Returns
+ -------
+ Species
+ The protein species object with material_type='protein'.
+
+ """
return self.species
def update_species(self) -> List[Species]:
+ """Generate species associated with the protein component.
+
+ Returns
+ -------
+ list of Species
+ List containing only the protein species itself, as Protein
+ has no associated mechanism to produce additional species.
+
+ """
species = [self.get_species()]
return species
def update_reactions(self) -> List:
+ """Generate reactions associated with the protein component.
+
+ Returns
+ -------
+ list
+ Empty list, as Protein has no associated mechanism.
+
+ """
return []
class Metabolite(Component):
- """Metabolic compounded that is produced, utilized, or degraded.
-
- Notes:
+ """Metabolic compound that can be produced, utilized, or degraded.
+
+ A `Metabolite` component represents a metabolic compound that
+ participates in biochemical pathways. It can have precursors (species
+ that are converted into this metabolite) and products (species that this
+ metabolite is converted into). The component uses a 'metabolic_pathway'
+ mechanism to generate production and degradation reactions.
+
+ Parameters
+ ----------
+ name : str
+ Name of the metabolite.
+ attributes : list of str, optional
+ List of attribute tags to associate with the metabolite species.
+ precursors : list of Species, str, Component, or None, optional
+ List of chemical species that are directly transformed into this
+ metabolite via the production mechanism. None represents
+ constitutive production (production from nothing).
+ products : list of Species, str, Component, or None, optional
+ List of chemical species produced from this metabolite via the
+ degradation mechanism. None represents total degradation
+ (degradation to nothing).
+ **kwargs
+ Additional keyword arguments passed to the `Component` base class
+ constructor.
+
+ Attributes
+ ----------
+ species : Species
+ The metabolite species object with material_type='metabolite'.
+ precursors : list of Species or None
+ List of precursor species. None values represent constitutive
+ production.
+ products : list of Species or None
+ List of product species. None values represent total degradation.
+
+ See Also
+ --------
+ Enzyme : Enzymatic component for catalysis.
+ Component : Base class for biomolecular components.
+
+ Notes
-----
- Metabolites look for 'metabolic_pathway' mechanism, but will not throw
- an error if it is not found.
+ The Metabolite component looks for a 'metabolic_pathway' mechanism but
+ will not throw an error if it is not found. If the mechanism is present:
+
+ - Production reactions are generated from precursors to the metabolite
+ - Degradation reactions are generated from the metabolite to products
+
+ None is a valid precursor/product representing constitutive
+ production/degradation.
+
+ Examples
+ --------
+ Create a metabolite with constitutive production and degradation:
+
+ >>> atp = bcp.Metabolite(
+ ... name='ATP',
+ ... precursors=[None],
+ ... products=[None]
+ ... )
+
+ Create a metabolite with specific precursor and product:
+
+ >>> adp = bcp.Metabolite(
+ ... name='ADP',
+ ... precursors=['ATP'],
+ ... products=['AMP']
+ ... )
+
+ Use with a mixture and metabolic pathway mechanism:
+
+ >>> from biocrnpyler.mechanisms import OneStepPathway
+ >>> mixture = bcp.Mixture(
+ ... components=[atp],
+ ... mechanisms={'metabolic_pathway': OneStepPathway()},
+ ... parameters={'k': 0.1}
+ ... )
+ >>> crn = mixture.compile_crn()
"""
@@ -128,17 +378,6 @@ def __init__(
products=None,
**kwargs,
):
- """Initialize and store Metabolite related information.
-
- :param name: name of the protein
- :param attributes: Species attribute
- :param precursors: list of chemical species directly transformed
- into this metabolite via the production mechanism
- :param products: list of chemical species produced from this
- metabolite via the degradation mechanism
- :param kwargs: pass into the parent's (Component) initializer
-
- """
self.species = self.set_species(
name, material_type='metabolite', attributes=attributes
)
@@ -166,9 +405,31 @@ def __init__(
Component.__init__(self=self, name=name, **kwargs)
def get_species(self) -> Species:
+ """Get the metabolite species.
+
+ Returns
+ -------
+ Species
+ The metabolite species object with material_type='metabolite'.
+
+ """
return self.species
def update_species(self) -> List[Species]:
+ """Use 'metabolic_pathway' mechanism to generate species.
+
+ Uses the 'metabolic_pathway' mechanism (if present) to generate
+ species for production reactions (from precursors to metabolite) and
+ degradation reactions (from metabolite to products).
+
+ Returns
+ -------
+ list of Species
+ List of species including the metabolite itself and any additional
+ species generated by the 'metabolic_pathway' mechanism. If no
+ mechanism is present, returns only the metabolite species.
+
+ """
species = [self.get_species()]
mech_pathway = self.get_mechanism(
'metabolic_pathway', optional_mechanism=True
@@ -192,6 +453,19 @@ def update_species(self) -> List[Species]:
return species
def update_reactions(self) -> List:
+ """Use 'metabolic_pathway' mechanism to generate reactions.
+
+ Uses the 'metabolic_pathway' mechanism (if present) to generate
+ production reactions (from precursors to metabolite) and degradation
+ reactions (from metabolite to products).
+
+ Returns
+ -------
+ list of Reaction
+ List of reactions including production and degradation pathways.
+ If no mechanism is present, returns an empty list.
+
+ """
reactions = []
mech_pathway = self.get_mechanism(
'metabolic_pathway', optional_mechanism=True
@@ -216,10 +490,81 @@ def update_reactions(self) -> List:
class ChemicalComplex(Component):
- """Two or more molecules bound together into a complex.
+ """Complex formed by binding of two or more molecular species.
+
+ A `ChemicalComplex` component represents a molecular complex formed when
+ two or more species bind together. The complex automatically inherits
+ attributes from its constituent species. The component uses a 'binding'
+ mechanism to generate binding and unbinding reactions.
+
+ Parameters
+ ----------
+ species : list of Species, str, or Component
+ List of species that form the complex. Must contain at least two
+ elements. Each element can be a `Species` object, string name, or
+ `Component` with an associated species.
+ name : str, optional
+ Name of the complex. If None, a name is automatically generated
+ from the constituent species names.
+ material_type : str, default='complex'
+ Material type identifier for the complex species. Can be customized
+ for specific complex types.
+ attributes : list of str, optional
+ List of attribute tags to associate with the complex species. The
+ complex also inherits attributes from its constituent species.
+ **kwargs
+ Additional keyword arguments passed to the `Component` base class
+ constructor.
+
+ Attributes
+ ----------
+ species : Complex
+ The complex species object created from the constituent species.
+ internal_species : list of Species
+ List of individual species that make up the complex.
+
+ See Also
+ --------
+ Component : Base class for biomolecular components.
+ Species : Chemical species representation.
+ Complex : Species subclass for molecular complexes.
+
+ Notes
+ -----
+ The ChemicalComplex component uses a 'binding' mechanism which must be
+ provided by the containing mixture. The binding mechanism generates:
+
+ - Forward binding reactions (species --> complex)
+ - Reverse unbinding reactions (complex --> species)
+
+ The first species in the list is treated as the 'bindee' and remaining
+ species are treated as 'binders' in the binding mechanism.
+
+ Examples
+ --------
+ Create a simple protein-DNA complex:
+
+ >>> complex = bcp.ChemicalComplex(
+ ... species=['TF_protein', 'DNA_promoter'],
+ ... name='TF_bound'
+ ... )
+
+ Create an enzyme-substrate complex:
- A complex forms when two or more species bind together Complexes
- inherit the attributes of their species.
+ >>> complex = bcp.ChemicalComplex(
+ ... species=['protein_E', 'S'],
+ ... name='ES_complex'
+ ... )
+
+ Use with a mixture and binding mechanism:
+
+ >>> from biocrnpyler.mechanisms import One_Step_Binding
+ >>> mixture = bcp.Mixture(
+ ... components=[complex],
+ ... mechanisms={'binding': One_Step_Binding()},
+ ... parameters={'kb': 1.0, 'ku': 0.1}
+ ... )
+ >>> crn = mixture.compile_crn()
"""
@@ -231,15 +576,6 @@ def __init__(
attributes=None,
**kwargs,
):
- """Initialize and store ChemicalComplex related information.
-
- :param species: list of species inside a complex
- :param name: name of the complex
- :param material_type: option to rename the material_type,
- default: 'complex'
- :param attributes: Species attribute
- :param kwargs: pass into the parent's (Component) initializer
- """
if not isinstance(species, list) or len(species) < 2:
raise ValueError(
f"Invalid Species {species}. Species must be a list of "
@@ -265,9 +601,31 @@ def __init__(
Component.__init__(self=self, name=name, **kwargs)
def get_species(self) -> List[Species]:
+ """Get the complex species.
+
+ Returns
+ -------
+ Complex
+ The complex species object containing all constituent species.
+
+ """
return self.species
def update_species(self) -> List[Species]:
+ """Use 'binding' mechanism to generate species for binding reactions.
+
+ Uses the 'binding' mechanism to generate all species needed for
+ binding and unbinding reactions, including the individual species
+ and the complex.
+
+ Returns
+ -------
+ list of Species
+ List of all species generated by the binding mechanism,
+ typically including the constituent species and the complex
+ species.
+
+ """
mech_b = self.get_mechanism('binding')
bindee = self.internal_species[0]
binder = self.internal_species[1:]
@@ -281,6 +639,18 @@ def update_species(self) -> List[Species]:
return species
def update_reactions(self) -> List[Reaction]:
+ """Use 'binding' mechanism to generate binding/unbinding reactions.
+
+ Uses the 'binding' mechanism to generate reactions for complex
+ formation (binding) and dissociation (unbinding).
+
+ Returns
+ -------
+ list of Reaction
+ List of reactions generated by the binding mechanism, typically
+ including forward binding and reverse unbinding reactions.
+
+ """
mech_b = self.get_mechanism('binding')
bindee = self.internal_species[0]
binder = self.internal_species[1:]
@@ -295,17 +665,96 @@ def update_reactions(self) -> List[Reaction]:
class Enzyme(Component):
- """A class to represent enzymes with multiple substrates and products.
+ """Enzyme that catalyzes conversion of substrates to products.
+
+ An `Enzyme` component represents an enzyme that catalyzes the conversion
+ of one or more substrates into one or more products. The enzyme itself
+ is not consumed in the reaction. This component uses a 'catalysis'
+ mechanism to generate the appropriate chemical reactions.
+
+ Parameters
+ ----------
+ enzyme : Species, str, or Component
+ The enzyme species that catalyzes the reaction. Can be a `Species`
+ object, a string name (creates new protein Species), or a
+ `Component` with an associated species.
+ substrates : list of Species, str, or Component
+ List of substrate species that are consumed by the enzymatic
+ reaction. Each element can be a `Species` object, string name, or
+ `Component`.
+ products : list of Species, str, or Component
+ List of product species that are produced by the enzymatic
+ reaction. Each element can be a `Species` object, string name, or
+ `Component`.
+ attributes : list of str, optional
+ List of attribute tags to associate with the enzyme species.
+ **kwargs
+ Additional keyword arguments passed to the `Component` base class
+ constructor.
+
+ Attributes
+ ----------
+ enzyme : Species
+ The enzyme species object.
+ substrates : list of Species
+ List of substrate species objects.
+ products : list of Species
+ List of product species objects.
+
+ See Also
+ --------
+ Component : Base class for biomolecular components.
+ Metabolite : Component for metabolic compounds.
+ ChemicalComplex : Component for molecular complexes.
+
+ Notes
+ -----
+ The `Enzyme` component assumes all substrates are converted to all
+ products in a single enzymatic step:
+
+ S1 + S2 + ... + SN + E --> P1 + P2 + ... + PM + E
+
+ For enzymes that catalyze multiple distinct reactions, create separate
+ `Enzyme` components with the same internal enzyme species.
+
+ The component uses a mechanism called 'catalysis' which must be
+ provided by the containing mixture. Common catalysis mechanisms include
+ Michaelis-Menten kinetics and other enzymatic rate laws.
+
+ Examples
+ --------
+ Create a simple enzyme that converts substrate S to product P:
+
+ >>> enzyme = bcp.Enzyme(
+ ... enzyme='E',
+ ... substrates=['S'],
+ ... products=['P']
+ ... )
+ >>> enzyme.get_species()
+ protein_E
+
+ Create an enzyme with multiple substrates and products:
+
+ >>> enzyme = bcp.Enzyme(
+ ... enzyme='Kinase',
+ ... substrates=['ATP', 'Protein'],
+ ... products=['ADP', 'Protein_P']
+ ... )
- Assumes the enzyme converts all substrates to a all products at once.
- For example: S1 + S2 + ... + S_N + E --> P1 + P2 + ... + P_M + E. For
- enzymes with multiple enzymatic reactions, create multiple enzyme
- components with the same internal species. Uses a mechanism called
- 'catalysis'.
+ Use with a mixture and Michaelis-Menten mechanism:
+
+ >>> from biocrnpyler.mechanisms import MichaelisMenten
+ >>> mixture = bcp.Mixture(
+ ... components=[enzyme],
+ ... mechanisms={'catalysis': MichaelisMenten()},
+ ... parameters={'kb': 0.1, 'ku': 0.01, 'kcat': 1.0}
+ ... )
+ >>> crn = mixture.compile_crn()
- TODO: implement multiple substrates and multiple products
"""
+ # TODO: implement multiple substrates and multiple products
+
def __init__(
self,
enzyme: Union[Species, str, Component],
@@ -314,18 +763,6 @@ def __init__(
attributes=None,
**kwargs,
):
- """Initialize and store enzyme related information.
-
- :param enzyme: name of the enzyme or reference to an Species
- or Component
- :param substrates: list of (name of the substrate or reference
- to a Species or Component)
- :param products: list of (name of the product or reference
- to a Species or Component)
- :param attributes: Species attribute
- :param kwargs: pass into the parent's (Component) initializer
-
- """
self.enzyme = self.set_species(
enzyme, material_type='protein', attributes=attributes
)
@@ -336,12 +773,33 @@ def __init__(
@property
def substrates(self) -> List:
+ """List of substrate species for the enzymatic reaction.
+
+ Returns
+ -------
+ list of Species
+
+ """
return self._substrates
@substrates.setter
def substrates(
self, new_substrates: List[Union[Species, str, Component]]
):
+ """Set the substrate species list.
+
+ Parameters
+ ----------
+ new_substrates : Species, str, Component, or list
+ Substrate(s) to set. Can be a single species or a list. Each
+ element is converted to a `Species` object.
+
+ Notes
+ -----
+ Automatically converts single substrate to a list and converts all
+ elements to `Species` objects using `set_species`.
+
+ """
if not isinstance(new_substrates, list):
new_substrates = [new_substrates]
# convert the new substrates to Species
@@ -349,19 +807,62 @@ def substrates(
@property
def products(self) -> List:
+ """List of product species for the enzymatic reaction.
+
+ Returns
+ -------
+ list of Species
+
+ """
return self._products
@products.setter
def products(self, new_products: List[Union[Species, str, Component]]):
+ """Set the product species list.
+
+ Parameters
+ ----------
+ new_products : Species, str, Component, or list
+ Product(s) to set. Can be a single species or a list. Each
+ element is converted to a `Species` object.
+
+ Notes
+ -----
+ Automatically converts single product to a list and converts all
+ elements to `Species` objects using `set_species`.
+
+ """
if not isinstance(new_products, list):
new_products = [new_products]
# convert the new products to Products
self._products = [self.set_species(p) for p in new_products]
def get_species(self) -> Species:
+ """Get the enzyme species.
+
+ Returns
+ -------
+ Species
+ The enzyme species object that catalyzes the reaction.
+
+ """
return self.enzyme
def update_species(self) -> List[Species]:
+ """Use 'catalysis' mechanism to generate enzymatic species.
+
+ Uses the 'catalysis' mechanism to generate all species needed for
+ the enzymatic reaction, including enzyme, substrates, products, and
+ any intermediate complexes.
+
+ Returns
+ -------
+ list of Species
+ List of all species generated by the catalysis mechanism,
+ typically including enzyme, substrates, products, and
+ enzyme-substrate complexes.
+
+ """
mech_cat = self.get_mechanism('catalysis')
return mech_cat.update_species(
enzyme=self.enzyme,
@@ -370,6 +871,19 @@ def update_species(self) -> List[Species]:
)
def update_reactions(self) -> List[Reaction]:
+ """Use 'catalysis' mechanism to generate enzymatic reactions.
+
+ Uses the 'catalysis' mechanism to generate all reactions needed for
+ the enzymatic conversion of substrates to products.
+
+ Returns
+ -------
+ list of Reaction
+ List of all reactions generated by the catalysis mechanism,
+ typically including substrate binding, catalysis, and product
+ release steps.
+
+ """
mech_cat = self.get_mechanism('catalysis')
return mech_cat.update_reactions(
enzyme=self.enzyme,
diff --git a/biocrnpyler/components/combinatorial_complex.py b/biocrnpyler/components/combinatorial_complex.py
index efc0ecac..7bb87e96 100644
--- a/biocrnpyler/components/combinatorial_complex.py
+++ b/biocrnpyler/components/combinatorial_complex.py
@@ -9,7 +9,157 @@
class CombinatorialComplex(Component):
- """Complex of many Species which bind together in many different ways."""
+ """Complex formed through combinatorial binding of multiple species.
+
+ A `CombinatorialComplex` component represents a complex that can form
+ through multiple combinatorial binding pathways. The component
+ enumerates all possible intermediate complexes and generates binding
+ reactions between initial states and final states, optionally
+ constrained by intermediate states and excluded states. Uses a
+ 'binding' mechanism to generate combinatorial binding reactions.
+
+ Parameters
+ ----------
+ final_states : ComplexSpecies or list of ComplexSpecies
+ The final complex(es) to be formed. All binding reactions
+ ultimately lead to these states.
+ initial_states : list of Species or ComplexSpecies, optional
+ Starting species that bind together to form final_states. If None,
+ defaults to all individual species contained within final_states.
+ intermediate_states : list of ComplexSpecies, optional
+ Allowed intermediate complexes formed during binding. Restricts
+ the binding pathway. If None, all possible intermediates are
+ enumerated.
+ excluded_states : list of Species or ComplexSpecies, optional
+ Species or complexes that are NOT allowed to form. If None, no
+ complexes are excluded.
+ name : str, optional
+ Name of the component. If None, automatically generated from
+ final_states names.
+ **kwargs
+ Additional keyword arguments passed to the `Component` base class
+ constructor.
+
+ Attributes
+ ----------
+ final_states : list of ComplexSpecies
+ List of final complex states.
+ initial_states : list of Species or ComplexSpecies
+ List of initial binding species.
+ intermediate_states : list of ComplexSpecies or None
+ List of allowed intermediate complexes, or None if unrestricted.
+ excluded_states : list
+ List of excluded species/complexes.
+ sub_species : list of Species
+ All individual species contained in final_states.
+ combination_dict : dict
+ Dictionary storing computed binding combinations.
+
+ See Also
+ --------
+ ChemicalComplex : Simple complex of two or more molecules.
+ Component : Base class for biomolecular components.
+ ComplexSpecies : Species subclass for molecular complexes.
+
+ Notes
+ -----
+ The combinatorial binding process generates reactions based on the
+ provided constraints:
+
+ Case 1 - only `final_states` given: all species in final_states bind
+ combinatorially:
+
+ individual_species <--> all_intermediates <--> final_states
+
+ Case 2 - `final_states` + `initial_states`: binding starts from
+ specified initial states directly to final states:
+
+ initial_states <--> final_states
+
+ Case 3 - `final_states` + `intermediate_states`: binding restricted to
+ specified intermediates:
+
+ individual_species <--> intermediate_states <--> final_states
+
+ Case 4 - `final_states` + `initial_states` + `intermediate_states`: both
+ initial and intermediate constraints applied:
+
+ initial_states <--> intermediate_states <--> final_states
+
+ The component name is automatically generated as a concatenation of
+ final_states names separated by underscores if not provided.
+
+ Examples
+ --------
+ Example 1: Full combinatorial binding
+
+ >>> A = bcp.Species('A')
+ >>> B = bcp.Species('B')
+ >>> C = bcp.Species('C')
+ >>> final = bcp.Complex([A, B, C])
+ >>> cc = bcp.CombinatorialComplex(
+ ... final_states=final,
+ ... mechanisms={'binding': bcp.One_Step_Binding()},
+ ... parameters={'kb': 1e-1, 'ku': 1e-1})
+
+ Initial states default to [A, B, C]. All intermediates
+ [A, B], [A, C], [B, C] are enumerated, resulting in 6 reversible
+ reactions:
+
+ 1. A + B <--> Complex([A, B])
+ 2. A + C <--> Complex([A, C])
+ 3. B + C <--> Complex([B, C])
+ 4. Complex([A, B]) + C <--> Complex([A, B, C])
+ 5. Complex([A, C]) + B <--> Complex([A, B, C])
+ 6. Complex([B, C]) + A <--> Complex([A, B, C])
+
+ Example 2: Constrained initial states
+
+ >>> initial = [bcp.Complex([A, B]), bcp.Complex([A, C])]
+ >>> cc = bcp.CombinatorialComplex(
+ ... final_states=final, initial_states=initial,
+ ... mechanisms={'binding': bcp.One_Step_Binding()},
+ ... parameters={'kb': 1e-1, 'ku': 1e-1})
+
+ Results in 2 reactions:
+
+ 1. Complex([A, B]) + C <--> Complex([A, B, C])
+ 2. Complex([A, C]) + B <--> Complex([A, B, C])
+
+ Example 3: Restricted intermediate states
+
+ >>> inter = [bcp.Complex([A, B]), bcp.Complex([A, C])]
+ >>> cc = bcp.CombinatorialComplex(
+ ... final_states=final, intermediate_states=inter,
+ ... mechanisms={'binding': bcp.One_Step_Binding()},
+ ... parameters={'kb': 1e-1, 'ku': 1e-1})
+
+ Results in 4 reactions:
+
+ 1. A + B <--> Complex([A, B])
+ 2. A + C <--> Complex([A, C])
+ 3. Complex([A, B]) + C <--> Complex([A, B, C])
+ 4. Complex([A, C]) + B <--> Complex([A, B, C])
+
+ Example 4: Multiple final states with homodimers
+
+ >>> final = [bcp.Complex([A, A, B]), bcp.Complex([A, B, B])]
+ >>> cc = bcp.CombinatorialComplex(
+ ... final_states=final,
+ ... mechanisms={'binding': bcp.One_Step_Binding()},
+ ... parameters={'kb': 1e-1, 'ku': 1e-1})
+
+ Results in 7 reactions including homodimer formation:
+
+ 1. A + A <--> Complex([A, A])
+ 2. Complex([A, A]) + B <--> Complex([A, A, B])
+ 3. B + B <--> Complex([B, B])
+ 4. Complex([B, B]) + A <--> Complex([A, B, B])
+ 5. A + B <--> Complex([A, B])
+ 6. Complex([A, B]) + A <--> Complex([A, A, B])
+ 7. Complex([A, B]) + B <--> Complex([A, B, B])
+
+ """
def __init__(
self,
@@ -18,108 +168,12 @@ def __init__(
intermediate_states=None,
excluded_states=None,
name=None,
- **keywords,
+ **kwargs,
):
- """Initialize combinatorial complex.
-
- Binding reactions will be generated to form all the ComplexSpecies in
- final_states from all the species in initial_states (or, if
- initial_states is None, from all the individual species inside each
- ComplexSpecies). Intermediate states restricts the binding reactions
- to only form species in this list. Excluded states are not allowed to
- be reactants or products. At a high level this generates the
- following reactions:
-
- If just final_states are given:
- final_states_internal_species
- <-[Combinatorial Binding]-> final_states
-
- if initial_states are given:
- intial_states <-[Combinatorial Binding]-> final_states
-
- if intermediate_states are given:
- final_states_internal_species
- <-[Combinatorial Binding]-> intermediate_states
- <-[Combinatorial Binding]-> final_states
-
- if initial_states and intermediate_states are given:
- intial_states <-[Combinatorial Binding]->
- intermediate_states <-[Combinatorial Binding]-> final_states
-
-
- :param final_states: a single ComplexSpecies or a list of
- ComplexSpecies.
- :param initial_states: a list of initial Species which are bound
- together to form the ComplexSpecies in final_states. If None,
- defaults to the members of the ComplexSpecies in final_states.
- :param intermediate_states: a list of intermediate ComplexSpecies
- formed when converting initial_states to final_states. If None,
- all possible intermediate ComplexSpecies are enumerated.
- :param excluded_states: a list of ComplexSpecies which are NOT
- allowed to form when converting initial states to final states.
- If None, no ComplexSpecies are excluded.
-
-
- Example 1:
- final_states=ComplexSpecies([A, B, C])
- initial_states=None
- intermediate_states=None
-
- initial_states will default to A, B, C. All intermediate states
- [A, B], [A, C], [B, C] will be enumerated.
-
- This results in the 6 reversible reactions:
- 1. A + B <--> Complex([A, B])
- 2. A + C <--> Complex([A, C])
- 3. B + C <--> Complex([B, C])
- 4. Complex([A, B]) + C <--> Complex([A, B, C])
- 5. Complex([A, C]) + B <--> Complex([A, B, C])
- 6. Complex([B, C]) + A <--> Complex([A, B, C])
-
- Example 2:
- final_states=ComplexSpecies([A, B, C])
- initial_states=[Complex([A, B]), Complex([A, C])]
- intermediate_states=None
-
- This results in the reactions:
- 1. Complex([A, B]) + C <--> Complex([A, B, C])
- 2. Complex([A, C]) + B <--> Complex([A, B, C])
-
- Example 3:
- final_states=ComplexSpecies([A, B, C])
- initial_states=None,
- intermediate_states=[Complex([A, B]), Complex([A, C])])
-
- This results in reactions:
- 1. A + B <--> Complex([A, B])
- 2. A + C <--> Complex([A, C])
- 3. Complex([A, B]) + C <--> Complex([A, B, C])
- 4. Complex([A, C]) + B <--> Complex([A, B, C])
-
- Example 4:
- final_states=[Complex([A, A, B], Complex([A, B, B]))]
- initial_states=None
- intermediate_states=None
-
- This results in the reactions:
- 1. A + A <--> Complex([A, A])
- 2. Complex([A, A]) + B <--> Complex ([A, A, B])
- 3. B + B <--> Complex([B, B])
- 4. Complex([B, B]) + A <--> Complex ([A, B, B])
- 5. A + B <--> Complex([A, B])
- 6. Complex([A, B]) + A <--> Complex ([A, A, B])
- 7. Complex([A, B]) + B <--> Complex ([A, B, B])
-
- """
- # The order these run in is important!
-
- # 1. set final_states
+ # The order these run in is important! (TODO: why?)
self.final_states = final_states
- # 2. set initial_states
self.initial_states = initial_states
- # 3. set intermidiate_states
self.intermediate_states = intermediate_states
- # 4. set excluded_states
self.excluded_states = excluded_states
# used to store combinations of species during update
@@ -131,16 +185,43 @@ def __init__(
for s in self.final_states:
name += s.name + '_'
name = name[:-1]
- super().__init__(name, **keywords)
+ super().__init__(name, **kwargs)
# Final States stores the end complexes that will be formed
@property
def final_states(self):
+ """List of final complex states to be formed.
+
+ Returns
+ -------
+ list of ComplexSpecies
+
+ """
return self._final_states
@final_states.setter
def final_states(self, final_states):
- final_states = list(self.set_species(final_states))
+ """Set the final complex states.
+
+ Parameters
+ ----------
+ final_states : ComplexSpecies or list of ComplexSpecies
+ Final complex(es) to be formed through combinatorial binding.
+
+ Raises
+ ------
+ ValueError
+ If any element in final_states is not a ComplexSpecies.
+
+ Notes
+ -----
+ Also creates a list of all sub-species (individual species
+ contained in the complexes) stored in `self.sub_species`.
+
+ """
+ final_states = self.set_species(final_states)
+ if not isinstance(final_states, list):
+ final_states = [final_states]
# all final_states must be ComplexSpecies
if not all([isinstance(s, ComplexSpecies) for s in final_states]):
@@ -160,15 +241,44 @@ def final_states(self, final_states):
# Initial states stores the starting states used in binding reactions
@property
def initial_states(self):
+ """List of initial states for binding.
+
+ Returns
+ -------
+ list of Species or ComplexSpecies
+ """
return self._initial_states
@initial_states.setter
def initial_states(self, initial_states):
+ """Set the initial binding states.
+
+ Parameters
+ ----------
+ initial_states : list of Species or ComplexSpecies, optional
+ Starting species for combinatorial binding. If None, defaults
+ to all individual species in final_states (sub_species).
+
+ Raises
+ ------
+ ValueError
+ If any initial state is not contained in sub_species or is not
+ a ComplexSpecies made from sub_species.
+
+ Notes
+ -----
+ Initial states must either be individual species from the
+ final_states or ComplexSpecies composed of those species.
+
+ """
# set initial states
if initial_states is None:
self._initial_states = self.sub_species
else:
- initial_states = list(self.set_species(initial_states))
+ initial_states = self.set_species(initial_states)
+ if not isinstance(initial_states, list):
+ initial_states = [initial_states]
+
for s in initial_states:
if not (
s in self.sub_species
@@ -190,14 +300,43 @@ def initial_states(self, initial_states):
# formed between the intial state and final state
@property
def intermediate_states(self):
+ """List of allowed intermediate complexes.
+
+ Returns
+ -------
+ list of ComplexSpecies or None
+ """
return self._intermediate_states
@intermediate_states.setter
def intermediate_states(self, intermediate_states):
+ """Set the allowed intermediate complex states.
+
+ Parameters
+ ----------
+ intermediate_states : list of ComplexSpecies, optional
+ Allowed intermediate complexes formed between initial and
+ final states. If None, all possible intermediates are
+ enumerated.
+
+ Raises
+ ------
+ ValueError
+ If any intermediate state is not a ComplexSpecies, or if any
+ contains species not in sub_species.
+
+ Notes
+ -----
+ Restricting intermediate states limits the binding pathways and
+ can reduce the number of reactions generated.
+
+ """
if intermediate_states is None:
self._intermediate_states = None
else:
- intermediate_states = list(self.set_species(intermediate_states))
+ intermediate_states = self.set_species(intermediate_states)
+ if not isinstance(intermediate_states, list):
+ intermediate_states = [intermediate_states]
# All intermediate_states must be ComplexSpecies or
# OrderdedComplexSpecies
@@ -229,18 +368,63 @@ def intermediate_states(self, intermediate_states):
# being enumerated
@property
def excluded_states(self):
+ """list: Species or complexes excluded from enumeration."""
return self._excluded_states
@excluded_states.setter
def excluded_states(self, excluded_states):
+ """Set the excluded species and complexes.
+
+ Parameters
+ ----------
+ excluded_states : list of Species or ComplexSpecies, optional
+ Species or complexes that are NOT allowed to form. If None,
+ no exclusions are applied (empty list).
+
+ Notes
+ -----
+ Excluded states will not appear as reactants or products in
+ generated reactions. Useful for preventing unwanted binding
+ pathways.
+
+ """
if excluded_states is None:
self._excluded_states = []
else:
- self._excluded_states = list(self.set_species(excluded_states))
+ excluded_states = self.set_species(excluded_states)
+ if not isinstance(excluded_states, list):
+ excluded_states = [excluded_states]
+ self._excluded_states = excluded_states
def compute_species_to_add(self, s0, sf):
- # Compute Species that need to be added to s0 to get the Complex sf
+ """Compute species needed to convert s0 into complex sf.
+
+ Parameters
+ ----------
+ s0 : Species or ComplexSpecies
+ Starting species or complex.
+ sf : ComplexSpecies
+ Target final complex.
+
+ Returns
+ -------
+ list of Species or None
+ List of species that need to be added to s0 to form sf. Returns
+ None if s0 contains species not in sf or more copies of any
+ species than sf contains.
+
+ Raises
+ ------
+ ValueError
+ If sf is not a ComplexSpecies.
+
+ Notes
+ -----
+ This method compares the stoichiometry of species in s0 and sf to
+ determine what needs to be added. If s0 contains more of any
+ species than sf, or contains species not in sf, None is returned.
+ """
if not isinstance(sf, ComplexSpecies):
raise ValueError(f"sf must be a ComplexSpecies. Recieved {sf}")
@@ -293,7 +477,48 @@ def compute_species_to_add(self, s0, sf):
return species_to_add
def get_combinations_between(self, s0, sf):
- """All combinations of Species to create the Complex sf from s0."""
+ """Get all binding combinations to form complex sf from s0.
+
+ Enumerates all possible binding orders (permutations) to construct
+ the final complex sf starting from s0, generating tuples of
+ (binder, bindee, complex_species) for each binding step.
+
+ Parameters
+ ----------
+ s0 : Species or ComplexSpecies
+ Starting species or complex.
+ sf : ComplexSpecies
+ Target final complex.
+
+ Returns
+ -------
+ list of tuple
+ List of (binder, bindee, complex_species) tuples representing
+ all possible binding combinations. Each tuple represents one
+ binding step. Returns empty list if no combinations are
+ possible.
+
+ Notes
+ -----
+ The method:
+
+ 1. Computes which species need to be added to s0 to form sf
+ 2. Generates all permutations of these species (different binding
+ orders)
+ 3. For each permutation, creates binding steps: binder + bindee -->
+ complex
+ 4. Filters out any combinations involving excluded_states
+
+ Examples
+ --------
+ If s0 = A and sf = Complex([A, B, C]), and no exclusions:
+
+ Returns combinations for different binding orders:
+
+ - Order 1: (B, A, [A,B]), (C, [A,B], [A,B,C])
+ - Order 2: (C, A, [A,C]), (B, [A,C], [A,B,C])
+
+ """
species_to_add = self.compute_species_to_add(s0, sf)
if species_to_add is None or len(species_to_add) == 0:
@@ -330,6 +555,38 @@ def get_combinations_between(self, s0, sf):
return combinations
def update_species(self):
+ """Use 'binding' mechanism to generate combinatorial species.
+
+ Uses the 'binding' mechanism to generate species for all possible
+ binding combinations between initial_states and final_states,
+ optionally constrained by intermediate_states and excluding
+ excluded_states.
+
+ Returns
+ -------
+ list of Species
+ List of all unique species generated, including initial states,
+ final states, and all intermediate complexes along binding
+ pathways.
+
+ Notes
+ -----
+ The method handles two cases:
+
+ With intermediate_states:
+
+ 1. Generate combinations: initial_states --> intermediate_states
+ 2. Generate combinations: intermediate_states --> final_states
+
+ Without intermediate_states:
+
+ Generate combinations: initial_states --> final_states directly
+
+ Duplicate species are automatically removed from the final list.
+ The combination_dict is populated during this process for use by
+ `update_reactions`.
+
+ """
mech_b = self.get_mechanism('binding')
species = []
species_added_dict = {} # save which combinations have
@@ -420,6 +677,37 @@ def update_species(self):
return list(set(species))
def update_reactions(self):
+ """Use 'binding' mechanism to generate combinatorial reactions.
+
+ Uses the 'binding' mechanism to generate reactions for all possible
+ binding combinations between initial_states and final_states,
+ optionally constrained by intermediate_states and excluding
+ excluded_states.
+
+ Returns
+ -------
+ list of Reaction
+ List of all binding reactions (forward and reverse) along all
+ enumerated pathways.
+
+ Notes
+ -----
+ The method handles two cases:
+
+ With intermediate_states:
+
+ 1. Generate reactions: initial_states <--> intermediate_states
+ 2. Generate reactions: intermediate_states <--> final_states
+
+ Without intermediate_states:
+ Generate reactions: initial_states <--> final_states directly
+
+ Duplicate reactions are automatically filtered out. The method uses
+ combination_dict computed by update_species() or computes it if
+ needed. Reactions are symmetric, so (binder, bindee, complex) and
+ (bindee, binder, complex) are treated as duplicates.
+
+ """
mech_b = self.get_mechanism('binding')
reactions = []
reactions_added_dict = {} # save which combinations have
@@ -495,7 +783,7 @@ def update_reactions(self):
)
# If there are no intermediate restrictions, compute
- # combinations in onestep
+ # combinations in one step
else:
for s0 in self.initial_states:
for sf in self.final_states:
diff --git a/biocrnpyler/components/combinatorial_conformation.py b/biocrnpyler/components/combinatorial_conformation.py
index ec44232f..ed49a49f 100644
--- a/biocrnpyler/components/combinatorial_conformation.py
+++ b/biocrnpyler/components/combinatorial_conformation.py
@@ -12,11 +12,109 @@
class CombinatorialConformation(Component):
- """Polymer made up of ordered polymer with internal binding complexes.
-
- A class to represent a PolymerConformation (made of one unique
- OrderedPolymerSpecies) with many internal Complexes which can bind and
- unbind in many different ways.
+ """Polymer conformation with combinatorial internal binding complexes.
+
+ A `CombinatorialConformation` component represents a polymer
+ conformation (made of one unique OrderedPolymerSpecies) with multiple
+ internal complexes that can bind and unbind in many different ways.
+ Unlike `CombinatorialComplex` where individual species are added one at
+ a time, this component adds groups of species in single steps to form
+ the appropriate complexes. Uses a 'conformation_change' mechanism.
+
+ Parameters
+ ----------
+ final_states : PolymerConformation or list of PolymerConformation
+ One or more final polymer conformations to be formed. All must
+ contain the same unique OrderedPolymerSpecies.
+ initial_states : list of PolymerConformation, optional
+ Initial polymer conformations that can bind/unbind to become
+ final_states. If None or empty, defaults to the bare polymer
+ without complexes.
+ intermediate_states : list of PolymerConformation, optional
+ Allowed intermediate conformations formed when converting
+ initial_states to final_states. If None, all possible
+ intermediate conformations are enumerated.
+ excluded_states : list of PolymerConformation, optional
+ Polymer conformations that will NOT be formed during enumeration.
+ If None, no conformations are excluded.
+ state_part_ids : dict, optional
+ Dictionary mapping PolymerConformation to string, used to generate
+ shorter part-ids for conformations.
+ name : str, optional
+ Name of the component. If None, uses the internal polymer name.
+ **kwargs
+ Additional keyword arguments passed to the `Component` base class
+ constructor.
+
+ Attributes
+ ----------
+ final_states : list of PolymerConformation
+ List of final conformation states.
+ initial_states : list of PolymerConformation
+ List of initial conformation states.
+ intermediate_states : list of PolymerConformation or None
+ List of allowed intermediate conformations, or None if
+ unrestricted.
+ excluded_states : list of PolymerConformation
+ List of excluded conformations.
+ internal_polymer : OrderedPolymerSpecies
+ The unique polymer species common to all conformations.
+ state_part_ids : dict
+ Dictionary for custom part-id naming.
+ combination_dict : dict
+ Dictionary storing computed conformation changes.
+
+ See Also
+ --------
+ CombinatorialComplex : Combinatorial binding of simple complexes.
+ PolymerConformation : Species subclass for polymer conformations.
+ Component : Base class for biomolecular components.
+
+ Notes
+ -----
+ Key differences from `CombinatorialComplex`:
+
+ - Operates on `PolymerConformation` objects instead of simple `Species`
+ - All conformations must share the same `OrderedPolymerSpecies`
+ - Adds groups of species simultaneously to form complexes
+ - Uses 'conformation_change' mechanism instead of 'binding'
+
+ Reaction generation: The component generates conformation change
+ reactions based on constraints:
+
+ - Without intermediate_states:
+ initial_states <--> final_states
+
+ - With intermediate_states:
+ initial_states <--> intermediate_states <--> final_states
+
+ Validation requirements: All conformations must:
+
+ 1. Be PolymerConformation objects
+ 2. Contain exactly one unique OrderedPolymerSpecies
+ 3. Have the same internal polymer
+
+ Examples
+ --------
+ Create a simple conformational change system:
+
+ >>> A, B, C, S = (bcp.Species(s) for s in ['A', 'B', 'C', 'S'])
+ >>> pc = bcp.PolymerConformation(polymer=[A, A, B, C])
+ >>> # Form a complex A:B by binding positions 0 and 2
+ >>> c1 = bcp.Complex([pc.polymers[0][0], pc.polymers[0][2]])
+ >>> pc1 = c1.parent
+ >>> # Form two complexes: A:B and A:C:S (S is external)
+ >>> c2 = bcp.Complex([pc1.polymers[0][1], pc1.polymers[0][3], S])
+ >>> pc2 = c2.parent
+ >>> # Create component to enumerate reactions
+ >>> cc = bcp.CombinatorialConformation(
+ ... final_states=pc2,
+ ... parameters={'kf': 1, 'kr': 0.01})
+
+ Using a Mixture to generate species and reactions:
+
+ >>> mixture = bcp.Mixture(components=[cc])
+ >>> crn = mixture.compile_crn()
"""
@@ -30,40 +128,6 @@ def __init__(
name=None,
**kwargs,
):
- """Initialize combinatorial conformation construct.
-
- Binding reactions will be generated to form all
- PolymerConformations in final_states from all the
- PolymerConformations in initial_states. There must be a single,
- unique, OrderedPolymerSpecies in all the conformations.
- Intermediate states restricts the binding reactions to first form
- PolymerConformations in this list. At a high level this generates
- the following reactions:
-
- initial_states <- [Combinatorial Binding] -> final_states
-
- if intermediate_states are given:
- initial_states <- [Combinatorial Binding] ->
- intermediate_states <- [Combinatorial Binding] -> final_states
-
- Unlike CombinatorialComplex where Species are added individual, in
- CombinatorialConformation, groups of Species are added in single
- steps to produce the appropriate Complexes.
-
- :param final_states: one or more PolymerConformations.
- :param initial_states: a list of initial PolymerConformations
- which can bind/unbind to become the final_state
- :param intermediate_states: a list of intermediate
- PolymerConformations formed when converting initial_states to
- final_states. If None, all possible intermediate
- PolymerConformations are enumerated.
- :param excluded_states: a list of intermediate PolymerConformations
- which will not be formed during enumeration. If None, no
- intermediates will be excluded.
- :param state_part_ids: a dictionary {PolymerConformation : str}
- used to generate shorter part-ids for this conformation
-
- """
if state_part_ids is None:
self.state_part_ids = {}
else:
@@ -81,6 +145,26 @@ def __init__(
# Helper function to assert the correct class type
def _assert_conformation(self, states, input_name='states'):
+ """Validate that states are proper PolymerConformations.
+
+ Parameters
+ ----------
+ states : list
+ List of states to validate.
+ input_name : str, default='states'
+ Name of the parameter being validated (for error messages).
+
+ Raises
+ ------
+ ValueError
+ If states are not PolymerConformations, do not contain exactly
+ one polymer, or do not share the same OrderedPolymerSpecies.
+
+ Notes
+ -----
+ Sets self.internal_polymer on first call if not already set.
+
+ """
if not all([isinstance(s, PolymerConformation) for s in states]):
raise ValueError(
f"{input_name} must be a list of PolymerConformations. "
@@ -111,17 +195,47 @@ def _assert_conformation(self, states, input_name='states'):
)
def get_species(self):
+ """Get the bare polymer conformation.
+
+ Returns
+ -------
+ PolymerConformation
+ The internal polymer without any complexes.
+
+ """
return PolymerConformation(polymer=self.internal_polymer)
# Getters and setters
# Final States stores the end complexes that will be formed
@property
def final_states(self):
+ """List of final conformation states.
+
+ Returns
+ -------
+ list of PolymerConformation
+
+ """
return self._final_states
@final_states.setter
def final_states(self, final_states):
- final_states = list(self.set_species(final_states))
+ """Set the final conformation states.
+
+ Parameters
+ ----------
+ final_states : PolymerConformation or list of PolymerConformation
+ Final conformation(s) to be formed.
+
+ Raises
+ ------
+ ValueError
+ If validation fails (see _assert_conformation).
+
+ """
+ final_states = self.set_species(final_states)
+ if not isinstance(final_states, list):
+ final_states = [final_states]
self._assert_conformation(final_states, 'final_states')
self._final_states = final_states
@@ -129,17 +243,40 @@ def final_states(self, final_states):
# Initial states stores the starting states used in binding reactions
@property
def initial_states(self):
+ """List of initial conformation states.
+
+ Returns
+ -------
+ list of PolymerConformation
+ """
return self._initial_states
@initial_states.setter
def initial_states(self, initial_states):
+ """Set the initial conformation states.
+
+ Parameters
+ ----------
+ initial_states : list of PolymerConformation, optional
+ Initial conformations. If None or empty, defaults to bare
+ polymer conformation.
+
+ Raises
+ ------
+ ValueError
+ If validation fails (see _assert_conformation).
+
+ """
# set initial states
if initial_states is None or len(initial_states) == 0:
self._initial_states = [
PolymerConformation(polymer=self.internal_polymer)
]
else:
- initial_states = list(self.set_species(initial_states))
+ initial_states = self.set_species(initial_states)
+ if not isinstance(initial_states, list):
+ initial_states = [initial_states]
+
# all initial_states must be PolymerConformation
self._assert_conformation(initial_states, 'initial_states')
@@ -149,14 +286,37 @@ def initial_states(self, initial_states):
# between the intial state and final state
@property
def intermediate_states(self):
+ """List of allowed intermediates.
+
+ Returns
+ -------
+ list of PolymerConformation or None
+
+ """
return self._intermediate_states
@intermediate_states.setter
def intermediate_states(self, intermediate_states):
+ """Set the allowed intermediate conformation states.
+
+ Parameters
+ ----------
+ intermediate_states : list of PolymerConformation, optional
+ Allowed intermediate conformations. If None, all possible
+ intermediates are enumerated.
+
+ Raises
+ ------
+ ValueError
+ If validation fails (see _assert_conformation).
+
+ """
if intermediate_states is None:
self._intermediate_states = None
else:
- intermediate_states = list(self.set_species(intermediate_states))
+ intermediate_states = self.set_species(intermediate_states)
+ if not isinstance(intermediate_states, list):
+ intermediate_states = [intermediate_states]
# All intermediate_states must be PolymerConformations
self._assert_conformation(
@@ -168,27 +328,76 @@ def intermediate_states(self, intermediate_states):
# excluded_states are PolymerConformations which are not allowed to form
@property
def excluded_states(self):
+ """List of excluded conformations.
+
+ Returns
+ -------
+ list of PolymerConformation
+
+ """
return self._excluded_states
@excluded_states.setter
def excluded_states(self, excluded_states):
+ """Set the excluded conformation states.
+
+ Parameters
+ ----------
+ excluded_states : list of PolymerConformation, optional
+ Conformations that are NOT allowed to form. If None, no
+ exclusions (empty list).
+
+ Raises
+ ------
+ ValueError
+ If validation fails (see _assert_conformation).
+
+ """
if excluded_states is None:
self._excluded_states = []
else:
# All excluded states must be PolymerConformations
- excluded_states = list(self.set_species(excluded_states))
+ excluded_states = self.set_species(excluded_states)
+ if not isinstance(excluded_states, list):
+ excluded_states = [excluded_states]
self._assert_conformation(excluded_states, 'excluded_states')
self._excluded_states = excluded_states
def compute_species_changes(self, s0, sf):
- # print("computing species changes between", s0, "and", sf)
- # Compute Species that need to be added to s0 to get the
- # PolymerConformation sf Assumes the underlying internal
- # polymer is the same Computes a list of moves to go from s0
- # --> sf. Each move produces one of the Complexes in SF
+ """Compute changes needed to convert conformation s0 into sf.
+
+ Analyzes what species need to be added and which complexes need to
+ be merged to transform the initial conformation s0 into the final
+ conformation sf. Assumes both conformations share the same
+ underlying polymer.
+
+ Parameters
+ ----------
+ s0 : PolymerConformation
+ Starting conformation.
+ sf : PolymerConformation
+ Target final conformation.
+
+ Returns
+ -------
+ tuple of (dict, dict) or False
+ Returns False if s0 cannot be additively transformed into sf.
+ Otherwise returns (species_changes, merged_complexes) where:
+
+ - species_changes: dict mapping (complex, positions) to list of
+ external species to add
+ - merged_complexes: dict mapping (complex, positions) to list of
+ complexes from s0 that merge to form sf
+
+ Notes
+ -----
+ Returns False if:
+
+ - s0 has more complexes at any position than sf
+ - Any complex in sf cannot be formed additively from s0
- # 1. if c0 contains bound locations not in cf, c0 cannot be
- # transformed additively into cf
+ """
+ # print("computing species changes between", s0, "and", sf)
if any(
[
len(s0.get_complexes_at(0, i))
@@ -283,7 +492,39 @@ def compute_species_changes(self, s0, sf):
return species_changes, merged_complexes
def get_combinations_between(self, s0, sf):
- """Returns a list of ???."""
+ """Get all conformation change combinations from s0 to sf.
+
+ Enumerates all possible orders of complex formation to transform
+ conformation s0 into sf, generating tuples representing each step.
+
+ Parameters
+ ----------
+ s0 : PolymerConformation
+ Starting conformation.
+ sf : PolymerConformation
+ Target final conformation.
+
+ Returns
+ -------
+ list of tuple
+ List of (old_state, species_to_add, new_state) tuples
+ representing all possible transformation pathways. Each tuple
+ represents one conformation change step. Returns empty list if
+ no valid pathways exist.
+
+ Notes
+ -----
+ The method:
+
+ 1. Computes which species/complexes change between s0 and sf
+ 2. Generates all permutations (different formation orders)
+ 3. For each permutation, creates conformational change steps
+ 4. Filters out any combinations involving excluded_states
+
+ Unlike `CombinatorialComplex`, this method adds groups of species
+ simultaneously to form complete complexes at polymer positions.
+
+ """
# print("geting combinations between", s0, "and", sf)
X = self.compute_species_changes(s0, sf)
@@ -378,12 +619,47 @@ def get_combinations_between(self, s0, sf):
return combinations
def _get_part_id(self, state):
+ """Get part ID for a conformation state.
+
+ Parameters
+ ----------
+ state : PolymerConformation
+ The conformation state.
+
+ Returns
+ -------
+ str
+ Custom part ID if state is in state_part_ids, otherwise string
+ representation of the state.
+
+ """
if state in self.state_part_ids:
return self.state_part_ids[state]
else:
return str(state)
def update_species(self):
+ """Use 'conformation_change' mechanism to generate species.
+
+ Uses the 'conformation_change' mechanism to generate species for
+ all possible conformation transformations between `initial_states`
+ and `final_states`, optionally constrained by `intermediate_states`
+ and excluding `excluded_states`.
+
+ Returns
+ -------
+ list of Species
+ List of all unique species generated, including
+ polymer conformations and any additional species involved in
+ conformation changes.
+
+ Notes
+ -----
+ Duplicate species are automatically removed from the final list.
+ The `combination_dict` is populated during this process for use by
+ `update_reactions`.
+
+ """
mech_c = self.get_mechanism('conformation_change')
species = []
self.combination_dict = {} # should recompute every updated species
@@ -480,6 +756,36 @@ def update_species(self):
return list(set(species))
def update_reactions(self):
+ """Use 'conformation_change' mechanism to generate reactions.
+
+ Uses the 'conformation_change' mechanism to generate reactions for
+ all possible conformation transformations between initial_states
+ and final_states, optionally constrained by intermediate_states
+ and excluding excluded_states.
+
+ Returns
+ -------
+ list of Reaction
+ List of all conformation change reactions (forward and reverse)
+ along all enumerated pathways.
+
+ Notes
+ -----
+ The method handles two cases:
+
+ With intermediate_states:
+
+ 1. Generate reactions: initial_states <--> intermediate_states
+ 2. Generate reactions: intermediate_states <--> final_states
+
+ Without intermediate_states: generate reactions:
+ initial_states <--> final_states directly.
+
+ Duplicate reactions are automatically filtered out using
+ `reactions_added_dict`. The method uses `combination_dict` computed by
+ `update_species` or computes it if needed.
+
+ """
mech_c = self.get_mechanism('conformation_change')
reactions = []
# save which combinations have already been added in order to
@@ -573,35 +879,124 @@ def update_reactions(self):
class CombinatorialConformationPromoter(CombinatorialConformation, Promoter):
- """Combinatorial promoter with expressing states.
-
- A combinatorial conformation with an additional set of states
- "expressing_states" which can transcribe/express rna/protein
- products. This class merges Promoter and
- CombinatorialConformation.
-
- :param promoter_states: one or more PolymerConformations which are used
- by the promoter class.
- :param promoter_states_on: True/False if True all promoter_states are
- transcribable. If False all states except promoter_states are
- transcribable.
- :param promoter_location: the index of the monomer in the
- PolymerConformation which represents the promoter
- :param final_states: one or more PolymerConformations.
- :param initial_states: a list of initial PolymerConformations which can
- bind/unbind to become the final_state.
- :param intermediate_states: a list of intermediate PolymerConformations
- formed when converting initial_states to final_states. If None, all
- possible intermediate PolymerConformations are enumerated.
- :param excluded_states: a list of intermediate PolymerConformations
- which will not be formed during enumeration. If None: no intermediates
- will be excluded.
- :param state_part_ids: a dictionary {PolymerConformation : str} used to
- generate shorter part-ids for this conformation
- :param activating_complexes: a list of ComplexSpecies which activate
- PolymerConformations allowing them to be transcribed.
- :param inactivating_complexes: a list of ComplexSpecies which innactive
- the PolymerConformation, preventing transcription.
+ """Combinatorial conformation with transcriptionally active states.
+
+ A `CombinatorialConformationPromoter` combines `CombinatorialConformation`
+ and `Promoter` functionality, creating a polymer with combinatorial
+ conformations where certain conformations can transcribe/express
+ RNA/protein products. Specific conformations can be designated as
+ transcriptionally active ('on') or inactive ('off').
+
+ Parameters
+ ----------
+ promoter_states : list of PolymerConformation
+ Polymer conformations used by the promoter. These states are
+ designated as either 'on' or 'off' based on promoter_states_on.
+ promoter_location : int
+ Index of the monomer in the polymer conformation that represents
+ the promoter location on the polymer.
+ promoter_states_on : bool, default=True
+ If True, all `promoter_states` are transcribable. If False, all
+ states except `promoter_states` are transcribable.
+ activating_complexes : list of ComplexSpecies, optional
+ Complexes that activate polymer conformations for transcription
+ regardless of promoter_states.
+ inactivating_complexes : list of ComplexSpecies, optional
+ Complexes that inactivate polymer conformations, preventing
+ transcription even if otherwise active.
+ intermediate_states : list of PolymerConformation, optional
+ Allowed intermediate conformations (see `CombinatorialConformation`).
+ final_states : list of PolymerConformation, optional
+ Final conformations (see `CombinatorialConformation`).
+ name : str, default='CombinatorialConformationPromoter'
+ Name of the component.
+ **kwargs
+ Additional keyword arguments passed to the parent class constructors.
+
+ Attributes
+ ----------
+ promoter_states : list of PolymerConformation
+ List of designated promoter states.
+ promoter_states_on : bool
+ Whether promoter_states are active or inactive.
+ promoter_location : int
+ Polymer position of the promoter.
+ activating_complexes : list of ComplexSpecies
+ Complexes that activate transcription.
+ inactivating_complexes : list of ComplexSpecies
+ Complexes that prevent transcription.
+ conformation_species : list
+ All conformation species (populated by update_species).
+
+ See Also
+ --------
+ CombinatorialConformation : Base class for conformational changes.
+ Promoter : Base class for transcription initiation.
+ PolymerConformation : Polymer with internal complexes.
+
+ Notes
+ -----
+ A conformation is transcriptionally active if:
+
+ 1. (conformation in promoter_states AND promoter_states_on=True) OR
+ (conformation not in promoter_states AND promoter_states_on=False)
+ 2. OR any activating_complex is present in the conformation
+ 3. AND no inactivating_complex is present
+
+ If inactivating_complex conflicts with active_state or
+ active_complex, a warning is issued and transcription is prevented.
+
+ The promoter location determines which DNA species from the polymer is
+ used for transcription initiation.
+
+ Examples
+ --------
+ Create a promoter (operon) with conformational regulation:
+
+ >>> A, B, F = (bcp.Species(s) for s in ['A', 'B', 'F'])
+ >>> op = bcp.PolymerConformation(polymer=[B, A, B])
+ >>> OF0 = bcp.Complex([op.polymers[0][0], F]).parent # F bound at pos'n 0
+ >>> OF1 = bcp.Complex([op.polymers[0][2], F]).parent # F bound at pos'n 2
+ >>> OF2 = tbp.Complex([OF1.polymers[0][2], F]).parent # F bound to both
+ >>> # Looped conformations
+ >>> L0 = Complex([op.polymers[0][0], op.polymers[0][1], F]).parent
+ >>> L1 = Complex([op.polymers[0][2], op.polymers[0][1], F]).parent
+ >>> # Define fully bound looped states
+ >>> L0F1 = bcp.Complex(
+ ... [OF1.polymers[0][0], OF1.polymers[0][1], F]).parent
+ >>> L1F0 = bcp. Complex(
+ ... [OF0.polymers[0][2], OF0.polymers[0][1], F]).parent
+ >>> # Create promoter with specific active states
+ >>> ccp = bcp.CombinatorialConformationPromoter(
+ ... name="CCP",
+ ... intermediate_states=[OF0, OF1],
+ ... final_states=[OF2, L0F1, L1F0],
+ ... promoter_states=[L0F1, L1F0, L0, L1], # transcribed states
+ ... promoter_states_on=True,
+ ... promoter_location=1
+ ... )
+
+ With repression (toggle `promoter_states_on`):
+
+ >>> # Same setup as above, but with promoter_states_on=False
+ >>> # Now only states NOT in promoter_states will transcribe
+ >>> ccp = bcp.CombinatorialConformationPromoter(
+ ... name="CCP",
+ ... intermediate_states=[OF0, OF1],
+ ... final_states=[OF2, L0F1, L1F0],
+ ... promoter_states=[L0F1, L1F0, L0, L1],
+ ... promoter_states_on=False,
+ ... promoter_location=1
+ ... )
+ >>> # Use in a DNAassembly for transcription
+ >>> assy = bcp.DNAassembly(
+ ... name="X", dna=op, promoter=ccp, rbs="rbs", protein="X")
+ >>> mixture = bcp.Mixture(
+ ... components=[assy],
+ ... mechanisms=[bcp.SimpleTranscription(), bcp.SimpleTranslation()],
+ ... parameters={'kf': 1, 'kr': 0.01, 'ktx': 1, 'ktl': 1}
+ ... )
+ >>> crn = mixture.compile_crn()
"""
@@ -661,19 +1056,68 @@ def __init__(
# Promoter class. These can be ON or OFF
@property
def promoter_states(self):
+ """List of designated promoter states.
+
+ Returns
+ -------
+ list of PolymerConformation
+
+ """
return self._promoter_states
@promoter_states.setter
def promoter_states(self, promoter_states):
+ """Set the promoter conformational states.
+
+ Parameters
+ ----------
+ promoter_states : list of PolymerConformation, optional
+ Conformations designated as promoter states. If None, empty
+ list.
+
+ Raises
+ ------
+ ValueError
+ If validation fails (see _assert_conformation).
+
+ """
if promoter_states is None:
self._promoter_states = []
else:
# All excluded states must be PolymerConformations
- promoter_states = list(self.set_species(promoter_states))
+ promoter_states = self.set_species(promoter_states)
+ if not isinstance(promoter_states, list):
+ promoter_states = [promoter_states]
self._assert_conformation(promoter_states, 'promoter_states')
self._promoter_states = promoter_states
def update_species(self):
+ """Generate species for conformation changes and transcription.
+
+ Generates species from both conformational changes (via
+ CombinatorialConformation) and transcription (via Promoter) for
+ conformations that are transcriptionally active.
+
+ Returns
+ -------
+ list of Species
+ List of all unique species including conformation states and
+ transcription-related species (RNAP complexes, transcripts,
+ etc.) for active conformations.
+
+ Notes
+ -----
+ For each conformation, determines if it is transcriptionally active
+ based on:
+
+ - Whether it is in promoter_states (and promoter_states_on setting)
+ - Presence of activating_complexes
+ - Absence of inactivating_complexes
+
+ Only active conformations generate transcription species via
+ Promoter.update_species().
+
+ """
self.conformation_species = CombinatorialConformation.update_species(
self
)
@@ -725,6 +1169,30 @@ def update_species(self):
return list(set(promoter_species + self.conformation_species))
def update_reactions(self):
+ """Generate reactions for conformation changes and transcription.
+
+ Generates reactions from both conformational changes (via
+ CombinatorialConformation) and transcription (via Promoter) for
+ conformations that are transcriptionally active.
+
+ Returns
+ -------
+ list of Reaction
+ List of all reactions including conformation change reactions
+ and transcription reactions (RNAP binding, elongation, etc.)
+ for active conformations.
+
+ Notes
+ -----
+ For each conformation, determines if it is transcriptionally active
+ using the same logic as update_species(). Only active
+ conformations generate transcription reactions via
+ `Promoter.update_reactions`.
+
+ The component name is temporarily changed to a state-specific name
+ for each conformation to ensure unique reaction identifiers.
+
+ """
if not hasattr(self, 'conformation_species'):
self.update_species
diff --git a/biocrnpyler/components/component_enumerator.py b/biocrnpyler/components/component_enumerator.py
index 868d1fb3..e7e4f209 100644
--- a/biocrnpyler/components/component_enumerator.py
+++ b/biocrnpyler/components/component_enumerator.py
@@ -6,22 +6,96 @@
class ComponentEnumerator:
- def __init__(self, name: str):
- """Class to enumerate components.
+ """Base class for enumerating new components from existing components.
+
+ A `ComponentEnumerator` creates new components in a process similar to
+ mechanisms. Component enumerators are used during CRN compilation to
+ expand or transform components, generating derived components that are
+ then compiled into species and reactions.
+
+ Parameters
+ ----------
+ name : str
+ Name identifier for the component enumerator.
+
+ Attributes
+ ----------
+ name : str
+ The name of the enumerator.
+
+ See Also
+ --------
+ LocalComponentEnumerator : Enumerator for single-component processing.
+ GlobalComponentEnumerator : Enumerator requiring all mixture components.
+ Mechanism : Base class for reaction generation.
+
+ Notes
+ -----
+ This is a base class that should be subclassed to implement specific
+ enumeration behavior. The key method to override is
+ `enumerate_components`.
+
+ Component enumerators are used during the mixture compilation process
+ to:
+
+ - Generate derived components (e.g., transcripts from DNA)
+ - Transform components based on context
+ - Create component variants or states
+
+ Examples
+ --------
+ Create a custom component enumerator:
+
+ >>> class MyEnumerator(bcp.ComponentEnumerator):
+ ... def __init__(self):
+ ... super().__init__(name='MyEnumerator')
+ ...
+ ... def enumerate_components(self, component=None):
+ ... # Custom enumeration logic
+ ... new_components = []
+ ... # ... generate new components ...
+ ... return new_components
- A component enumerator's job is to create new components in a
- process similar to mechanisms.
+ """
- """
+ def __init__(self, name: str):
self.name = name
def enumerate_components(self, component=None) -> List:
- """Method to enumerate components.
-
- This will create new components based on the input component
- somehow. The child class should implement this.
-
- :return: empty list
+ """Enumerate new components from an input component.
+
+ This method creates new components based on the input component.
+ The base implementation returns an empty list and issues a warning.
+ Subclasses should override this method to provide specific
+ enumeration behavior.
+
+ Parameters
+ ----------
+ component : Component, optional
+ The input component to enumerate from. Can be None for
+ enumerators that do not require an input component.
+
+ Returns
+ -------
+ list of Component
+ List of newly enumerated components. Base implementation
+ returns an empty list.
+
+ Warns
+ -----
+ UserWarning
+ Issues a warning when the default implementation is called,
+ indicating that a subclass should override this method.
+
+ See Also
+ --------
+ Component.enumerate_components : Component-level enumeration method.
+
+ Notes
+ -----
+ This method is called during CRN compilation as part of the
+ component enumeration phase. Subclasses should implement specific
+ logic to generate derived components.
"""
warn(
@@ -31,15 +105,72 @@ def enumerate_components(self, component=None) -> List:
return []
def __repr__(self):
+ """Return string representation of the enumerator.
+
+ Returns
+ -------
+ str
+ The name of the enumerator.
+
+ """
return self.name
class LocalComponentEnumerator(ComponentEnumerator):
- """Class to enumerate local components.
-
- A component enumerator's job is to create new components in a process
- similar to mechanisms. A local component enumerator only cares about
- the single component that is passed in
+ """Component enumerator that operates on individual components.
+
+ A `LocalComponentEnumerator` processes components independently,
+ creating new components based solely on the properties of a single
+ input component. This is the most common type of enumerator, used when
+ enumeration does not require knowledge of other components in the
+ mixture.
+
+ Parameters
+ ----------
+ name : str
+ Name identifier for the local component enumerator.
+
+ Attributes
+ ----------
+ name : str
+ The name of the enumerator (inherited from ComponentEnumerator).
+
+ See Also
+ --------
+ ComponentEnumerator : Base class for component enumerators.
+ GlobalComponentEnumerator : Enumerator requiring all mixture components.
+
+ Notes
+ -----
+ Local component enumerators are appropriate when:
+
+ - Enumeration depends only on the component being processed
+ - No cross-component interactions are needed
+ - Components can be processed independently
+
+ Common examples include:
+
+ - Generating RNA transcripts from DNA components
+ - Creating protein products from RNA components
+ - Expanding DNA assemblies into constituent parts
+
+ The `enumerate_components` method receives only the single component
+ being processed.
+
+ Examples
+ --------
+ Create a custom local enumerator:
+
+ >>> class TranscriptEnumerator(bcp.LocalComponentEnumerator):
+ ... def __init__(self):
+ ... super().__init__(name='TranscriptEnumerator')
+ ...
+ ... def enumerate_components(self, component=None):
+ ... if isinstance(component, bcp.DNA):
+ ... # Create RNA transcript from DNA
+ ... transcript = bcp.RNA(name=f'{component.name}_transcript')
+ ... return [transcript]
+ ... return []
"""
@@ -48,13 +179,74 @@ def __init__(self, name: str):
class GlobalComponentEnumerator(ComponentEnumerator):
- def __init__(self, name: str):
- """Class to enumerate global components.
+ """Component enumerator that operates on all mixture components.
+
+ A `GlobalComponentEnumerator` has access to all components in the
+ mixture, allowing for complex enumeration that depends on interactions
+ or relationships between multiple components. This is used when
+ enumeration decisions require global context.
+
+ Parameters
+ ----------
+ name : str
+ Name identifier for the global component enumerator.
+
+ Attributes
+ ----------
+ name : str
+ The name of the enumerator (inherited from ComponentEnumerator).
+
+ See Also
+ --------
+ ComponentEnumerator : Base class for component enumerators.
+ LocalComponentEnumerator : Enumerator for single-component processing.
+
+ Notes
+ -----
+ Global component enumerators are appropriate when:
+
+ - Enumeration depends on multiple components
+ - Cross-component interactions must be considered
+ - Global context or mixture-wide information is needed
+
+ The `enumerate_components` method typically receives all components
+ in the mixture, allowing the enumerator to make decisions based on the
+ complete set of components.
+
+ Common examples include:
+
+ - Generating complexes between components
+ - Creating interaction networks
+ - Enumerating components based on global constraints
+
+ Performance note: Global enumerators may be more computationally
+ expensive than local enumerators since they must consider all
+ components.
+
+ Examples
+ --------
+ Create a custom global enumerator:
+
+ >>> class ComplexEnumerator(bcp.GlobalComponentEnumerator):
+ ... def __init__(self):
+ ... super().__init__(name='ComplexEnumerator')
+ ...
+ ... def enumerate_components(self, component=None):
+ ... # Access all components (passed via mixture)
+ ... # Generate complexes between compatible components
+ ... new_complexes = []
+ ... # ... complex enumeration logic ...
+ ... return new_complexes
+
+ Use in a mixture:
+
+ >>> enumerator = ComplexEnumerator()
+ >>> mixture = bcp.Mixture(
+ ... components=[comp1, comp2, comp3],
+ ... global_component_enumerators=[enumerator]
+ ... )
- A component enumerator's job is to create new components in a
- process similar to mechanisms. A global component enumerator takes
- in every component that is in the mixture. This is for complex
- enumeration that cares about other components
+ """
- """
+ def __init__(self, name: str):
ComponentEnumerator.__init__(self, name=name)
diff --git a/biocrnpyler/components/construct_explorer.py b/biocrnpyler/components/construct_explorer.py
index d9a0c991..db41a764 100644
--- a/biocrnpyler/components/construct_explorer.py
+++ b/biocrnpyler/components/construct_explorer.py
@@ -80,7 +80,7 @@ def check_loop(self):
"""Check for loops in plasmids (?).
If we already went around the plasmid, then what we're checking for is
- continuing transcripts or proteins. We don't want to start making new
+ continuing transcripts or proteins. We do not want to start making new
transcripts because we already checked this area for promoters.
"""
@@ -200,7 +200,7 @@ def terminate_loop(self):
)
self.initialize_loop()
- # Returns a list of RNAconstructs
+ # Returns a list of RNA_constructs
def return_components(self, component, previously_enumerated=None):
return_rnas = []
for rna in self.all_rnas.values():
diff --git a/biocrnpyler/components/dna/assembly.py b/biocrnpyler/components/dna/assembly.py
index 18c99c46..8e80da43 100644
--- a/biocrnpyler/components/dna/assembly.py
+++ b/biocrnpyler/components/dna/assembly.py
@@ -15,7 +15,126 @@
class DNAassembly(DNA):
- """A class that contains a Promoter, RBS, transcript, and protein."""
+ """High-level representation of a gene expression construct.
+
+ A DNAassembly represents a complete gene expression unit combining a
+ promoter region, ribosome binding site (RBS), coding sequence, and the
+ RNA and protein products. This class provides a convenient interface for
+ modeling the central dogma pathway: DNA --> RNA --> Protein, where the
+ promoter controls transcription and the RBS controls translation.
+
+ Parameters
+ ----------
+ name : str
+ Name of the DNA assembly.
+ dna : DNA, str, or None, optional
+ The DNA species or name for the assembly. If None, a DNA species
+ with `name` is created automatically.
+ promoter : Promoter, str, or None, optional
+ The promoter component or name controlling transcription. If None,
+ no transcription occurs. If a string, a default Promoter is created.
+ transcript : RNA, str, bool, or None, optional
+ The RNA transcript produced by transcription. If None, an RNA
+ species with `name` is created. If False, no transcript is created
+ (used in expression mixtures for direct translation).
+ rbs : RBS, str, or None, optional
+ The ribosome binding site component or name controlling translation.
+ If None, no translation occurs. If a string, a default RBS is
+ created.
+ protein : Protein, str, or None, optional
+ The protein product of translation. If None, a Protein species with
+ `name` is created automatically.
+ length : int, optional
+ Length of the DNA sequence in base pairs.
+ attributes : list of str, optional
+ List of attribute tags for the assembly and its species.
+ mechanisms : dict or list, optional
+ Custom mechanisms for this assembly, overriding mixture defaults.
+ compartment : Compartment, optional
+ The compartment containing this assembly and its products.
+ parameters : dict, optional
+ Parameter values specific to this assembly.
+ initial_concentration : float, optional
+ Initial concentration of the DNA species.
+ **kwargs
+ Additional keyword arguments passed to the parent `DNA` class.
+
+ Attributes
+ ----------
+ dna : Species
+ The DNA species representing the genetic construct.
+ promoter : Promoter or None
+ The promoter component controlling transcription.
+ rbs : RBS or None
+ The ribosome binding site controlling translation.
+ transcript : Species or None
+ The RNA transcript produced by transcription.
+ protein : Species or None
+ The protein product of translation.
+
+ See Also
+ --------
+ DNA : Base class for DNA components.
+ Promoter : Component representing transcriptional control elements.
+ RBS : Component representing ribosome binding sites.
+ RNA : Base class for RNA components.
+ Protein : Base class for protein components.
+
+ Notes
+ -----
+ The DNAassembly automatically coordinates its sub-components (promoter,
+ RBS) by propagating updates to mechanisms, parameters, and mixtures. When
+ mechanisms or parameters are added to the assembly, they are also added
+ to the promoter and RBS (but never overwrite existing values in those
+ components).
+
+ The 'transcription' mechanism is used by the promoter to generate the
+ species and reactions for transcript and the 'translation' mechanism is
+ used by the RBS to generate the species and reactions for ribosome binding
+ and protein production.
+
+ For expression mixtures where transcription is bypassed, set
+ `transcript=False` to enable direct translation from DNA to protein. In
+ this case, the 'transcription' mechanism will be used to generate the
+ protein.
+
+ Examples
+ --------
+ Create a simple constitutive gene expression construct:
+
+ >>> # Basic assembly with automatic species creation
+ >>> gene = bcp.DNAassembly(
+ ... name='gene_gfp',
+ ... promoter='pconst',
+ ... rbs='rbs1'
+ ... )
+ >>> gene.dna
+ dna_gene_gfp
+ >>> gene.transcript
+ rna_gene_gfp
+ >>> gene.protein
+ protein_gene_gfp
+
+ Create an assembly with custom species names:
+
+ >>> gene = bcp.DNAassembly(
+ ... name='gene_reporter',
+ ... promoter='p_lac',
+ ... rbs='rbs_strong',
+ ... transcript='mRNA_gfp',
+ ... protein='protein_gfp'
+ ... )
+
+ Create an expression construct (no transcript):
+
+ >>> gene = bcp.DNAassembly(
+ ... name='gene_direct',
+ ... promoter='p_const',
+ ... transcript=False,
+ ... protein='protein_x'
+ ... )
+
+ """
def __init__(
self,
@@ -31,26 +150,11 @@ def __init__(
compartment=None,
parameters=None,
initial_concentration=None,
- **keywords,
+ **kwargs,
):
- """Initialize a DNA assembly.
-
- Note: If transcript is None and protein is not None, the
- DNAassembly will use its transcription mechanisms to produce the
- protein. This is used by Expression Mixtures.
-
- :param name: name of the DNA assembly
- :param dna:
- :param promoter:
- :param transcript:
- :param rbs:
- :param protein:
- :param length:
- :param attributes:
- :param mechanisms:
- :param parameters:
- :param initial_concentration:
- :param keywords: passed into the parent object (DNA)
+ """Initialize a DNAassembly.
+
+ See class docstring for parameter descriptions.
"""
self.promoter = None
@@ -68,7 +172,7 @@ def __init__(
initial_concentration=initial_concentration,
attributes=attributes,
compartment=compartment,
- **keywords,
+ **kwargs,
)
self.update_dna(dna, attributes=attributes)
@@ -80,13 +184,27 @@ def __init__(
self.update_rbs(rbs, transcript=self.transcript, protein=self.protein)
def get_species(self):
+ """Get the primary DNA species of this assembly.
+
+ Returns
+ -------
+ Species
+ The DNA species representing this genetic construct.
+
+ """
return self.dna
def set_mixture(self, mixture: Mixture) -> None:
- """Set the mixture the Component is in.
+ """Set the mixture containing this component and its sub-components.
+
+ Also propagates the mixture reference to the promoter and RBS
+ components if they exist.
+
+ Parameters
+ ----------
+ mixture : Mixture
+ The mixture object that contains this assembly.
- :param mixture: reference to a Mixture instance
- :return: None
"""
self.mixture = mixture
if self.promoter is not None:
@@ -95,11 +213,25 @@ def set_mixture(self, mixture: Mixture) -> None:
self.rbs.set_mixture(mixture)
def update_dna(self, dna: Union[None, DNA, str], attributes=None) -> None:
- """Sets up the dna attribute with a valid DNA instance.
+ """Set or update the DNA species for this assembly.
+
+ Creates a DNA species from the provided input and updates the DNA
+ references in the promoter and RBS components if they exist.
+
+ Parameters
+ ----------
+ dna : DNA, str, or None
+ The DNA component, species name, or None. If None, creates a DNA
+ species using the assembly's name. If a string, creates a new DNA
+ species with that name. If a DNA object, uses it directly.
+ attributes : list of str, optional
+ Attribute tags to add to the DNA species.
+
+ Notes
+ -----
+ This method automatically updates the `dna` attribute of the promoter
+ and RBS components to maintain consistency across the assembly.
- :param dna: name of a dna sequence or a DNA instance
- :param attributes: Species attribute
- :return: None
"""
if dna is None:
self.dna = self.set_species(
@@ -124,11 +256,32 @@ def update_dna(self, dna: Union[None, DNA, str], attributes=None) -> None:
def update_transcript(
self, transcript: Union[None, RNA, str, bool], attributes=None
) -> None:
- """Sets up the transcript attribute with a valid RNA instance.
+ """Set or update the RNA transcript for this assembly.
+
+ Creates an RNA species from the provided input and updates the
+ transcript references in the promoter and RBS components if they
+ exist.
+
+ Parameters
+ ----------
+ transcript : RNA, str, bool, or None
+ The RNA component, species name, False, or None. If None, creates
+ an RNA species using the assembly's name. If a string, creates a
+ new RNA species with that name. If an RNA object, uses it
+ directly. If False, sets transcript to None (used for expression
+ mixtures without transcription).
+ attributes : list of str, optional
+ Attribute tags to add to the RNA species.
+
+ Notes
+ -----
+ Setting `transcript=False` is used in expression mixtures where
+ translation occurs directly from DNA without an explicit RNA
+ intermediate.
+
+ This method automatically updates the `transcript` attribute of the
+ promoter and RBS components to maintain consistency.
- :param transcript: name of a RNA transcript or RNA instance
- :param attributes: Species attribute
- :return: None
"""
if transcript is None:
self.transcript = self.set_species(
@@ -157,11 +310,27 @@ def update_transcript(
def update_protein(
self, protein: Union[None, Protein, str], attributes=None
) -> None:
- """Sets up the protein attribute with a valid Protein instance.
+ """Set or update the protein product for this assembly.
+
+ Creates a Protein species from the provided input and updates the
+ protein references in the promoter and RBS components if they exist.
+
+ Parameters
+ ----------
+ protein : Protein, str, or None
+ The Protein component, species name, or None. If None, creates a
+ Protein species using the assembly's name. If a string, creates a
+ new Protein species with that name. If a Protein object, uses it
+ directly.
+ attributes : list of str, optional
+ Attribute tags to add to the Protein species.
+
+ Notes
+ -----
+ This method automatically updates the `protein` attribute of the
+ promoter and RBS components to maintain consistency across the
+ assembly.
- :param protein: name of a protein or Protein instance
- :param attributes: Species attribute
- :return: None
"""
if protein is None:
self.protein = self.set_species(
@@ -189,12 +358,34 @@ def update_promoter(
transcript: RNA = None,
protein: Protein = None,
) -> None:
- """Sets up the promoter attribute with a valid Promoter instance.
+ """Set or update the promoter component for this assembly.
+
+ Creates a Promoter component from the provided input and propagates
+ the assembly's parameters, mixture, and mechanisms to the promoter.
+
+ Parameters
+ ----------
+ promoter : Promoter, str, or None
+ The Promoter component, promoter name, or None. If None, no
+ promoter is created. If a string, creates a default Promoter with
+ that name using `Promoter.from_promoter`. If a Promoter object,
+ uses it directly.
+ transcript : RNA, optional
+ The RNA transcript to associate with the promoter. If provided,
+ updates the assembly's transcript before creating the promoter.
+ protein : Protein, optional
+ The protein product to associate with the promoter (used for some
+ regulatory mechanisms).
+
+ Notes
+ -----
+ This method automatically:
+
+ - Propagates the assembly's parameter database to the promoter
+ - Sets the promoter's mixture reference
+ - Adds the assembly's mechanisms to the promoter (without overwriting
+ existing promoter mechanisms)
- :param promoter: name of a promoter or Promoter instance
- :param transcript: reference to the RNA transcript
- :param protein:
- :return: None
"""
if transcript is not None:
self.update_transcript(transcript)
@@ -225,12 +416,33 @@ def update_rbs(
transcript: RNA = None,
protein: Protein = None,
) -> None:
- """Sets up the rbs attribute with a valid RBS instance.
+ """Set or update the ribosome binding site component.
+
+ Creates an RBS component from the provided input and propagates the
+ assembly's parameters, mixture, and mechanisms to the RBS.
+
+ Parameters
+ ----------
+ rbs : RBS, str, or None
+ The RBS component, RBS name, or None. If None, no RBS is created.
+ If a string, creates a default RBS with that name using
+ `RBS.from_rbs`. If an RBS object, uses it directly.
+ transcript : RNA, optional
+ The RNA transcript containing the RBS. If provided, updates the
+ assembly's transcript before creating the RBS.
+ protein : Protein, optional
+ The protein product of translation. If provided, updates the
+ assembly's protein before creating the RBS.
+
+ Notes
+ -----
+ This method automatically:
+
+ - Propagates the assembly's parameter database to the RBS
+ - Sets the RBS's mixture reference
+ - Adds the assembly's mechanisms to the RBS (without overwriting
+ existing RBS mechanisms)
- :param rbs: name of the ribosome binding site or RBS instance
- :param transcript: RNA that contains the ribosome binding site
- :param protein: protein that RNA contains
- :return: None
"""
if protein is not None:
self.update_protein(protein)
@@ -257,10 +469,25 @@ def update_rbs(
self.rbs.add_mechanisms(self.mechanisms, optional_mechanism=True)
def update_species(self) -> List[Species]:
- """Collects the list of Species that a DNAassemlby instance holds.
+ """Generate all species associated with this assembly.
+
+ Collects species from the DNA, promoter, and RBS components during
+ CRN compilation.
+
+ Returns
+ -------
+ list of Species
+ List containing the DNA species and all species generated by the
+ promoter and RBS components.
+
+ Notes
+ -----
+ This method is called during CRN compilation by
+ `Mixture.compile_crn` to collect all chemical species generated by
+ this assembly.
- :return: list of Species that a DNAassemlby instance holds
"""
+ # :return: list of Species that a DNAassemlby instance holds
species = []
species.append(self.dna)
if self.promoter is not None:
@@ -280,10 +507,26 @@ def update_species(self) -> List[Species]:
return species
def update_reactions(self) -> List[Reaction]:
- """Collects the list of Reactions that a DNAassemlby instance holds.
+ """Generate all reactions associated with this assembly.
+
+ Collects reactions from the promoter and RBS components during CRN
+ compilation.
+
+ Returns
+ -------
+ list of Reaction
+ List of all reactions generated by the promoter and RBS
+ components, including transcription, translation, and regulatory
+ reactions.
+
+ Notes
+ -----
+ This method is called during CRN compilation by
+ `Mixture.compile_crn` to collect all chemical reactions generated by
+ this assembly.
- :return: list of Reactions that a DNAassemlby instance holds.
"""
+ # :return: list of Reactions that a DNAassemlby instance holds.
reactions = []
if self.promoter is not None:
reactions += self.promoter.update_reactions()
@@ -307,12 +550,29 @@ def update_parameters(
parameters: ParameterDatabase = None,
overwrite_parameters: bool = True,
) -> None:
- """Updates the parameters stored in dna, promoter and rbs.
+ """Update parameters for the assembly and its sub-components.
+
+ Propagates parameter updates to the DNA assembly itself and to the
+ promoter and RBS components if they exist.
+
+ Parameters
+ ----------
+ parameter_file : str, optional
+ Path to a CSV or TSV parameter file to load.
+ parameters : ParameterDatabase, optional
+ ParameterDatabase object to merge with the assembly's parameters.
+ overwrite_parameters : bool, default=True
+ If True, new parameter values overwrite existing ones. If False,
+ existing parameters are preserved.
+
+ Notes
+ -----
+ This method calls `update_parameters` on:
+
+ 1. The parent DNA class (updating the DNA's parameters)
+ 2. The promoter component (if it exists)
+ 3. The RBS component (if it exists)
- :param parameter_file: valid parameter file
- :param parameters: a parameter database instance
- :param overwrite_parameters: whether to overwrite existing parameters
- :return: None
"""
DNA.update_parameters(
self=self,
@@ -342,16 +602,31 @@ def add_mechanism(
overwrite: bool = False,
optional_mechanism: bool = False,
) -> None:
- """Adds mechanism to the Component mechanism dictionary.
-
- DNA_assembly also adds the mechanisms to its promoter and rbs
- (but never overwrites them!).
-
- :param mechanism: reference to a Mechanism instance
- :param mech_type: type of mechanism
- :param overwrite: whether to overwrite the mechanism in Component
- :param optional_mechanism:
- :return: None
+ """Add a mechanism to the assembly and its sub-components.
+
+ Adds the mechanism to the assembly's mechanism dictionary and
+ propagates it to the promoter and RBS components without overwriting
+ their existing mechanisms.
+
+ Parameters
+ ----------
+ mechanism : Mechanism
+ The mechanism object to add.
+ mech_type : str, optional
+ The mechanism type key. If None, uses the mechanism's
+ `mechanism_type` attribute.
+ overwrite : bool, default=False
+ If True, overwrites existing mechanisms with the same type in the
+ assembly. If False, raises ValueError for duplicate types.
+ optional_mechanism : bool, default=False
+ If True, suppresses ValueError when a mechanism key conflict
+ occurs in the assembly and `overwrite` is False.
+
+ Notes
+ -----
+ The mechanism is always added to the promoter and RBS with
+ `optional_mechanism=True`, meaning it will never overwrite existing
+ mechanisms in those components even if `overwrite=True`.
"""
Component.add_mechanism(
diff --git a/biocrnpyler/components/dna/cds.py b/biocrnpyler/components/dna/cds.py
index 85041909..f75202d1 100644
--- a/biocrnpyler/components/dna/cds.py
+++ b/biocrnpyler/components/dna/cds.py
@@ -7,9 +7,76 @@
class CDS(DNA_part):
+ """Coding sequence component representing a protein-coding region.
+
+ A CDS (coding sequence) represents a contiguous DNA sequence that codes
+ for a protein product. This component stores the protein species
+ associated with the coding region but does not directly generate
+ translation reactions. Translation is typically handled by an RBS
+ component that acts on the transcript containing this CDS.
+
+ Parameters
+ ----------
+ name : str
+ Name of the coding sequence.
+ protein : Protein, str, Component, or None, optional
+ The protein product encoded by this CDS. Can be a `Species` object,
+ string name (creates a protein Species), `Component` with an
+ associated species, or None (creates protein Species using CDS name).
+ no_stop_codons : list, optional
+ List of regions without stop codons, used for sequence validation or
+ special handling of coding sequences.
+ **kwargs
+ Additional keyword arguments passed to the parent `DNA_part` class.
+
+ Attributes
+ ----------
+ name : str
+ Name of the coding sequence.
+ protein : Species
+ The protein species encoded by this CDS.
+
+ See Also
+ --------
+ RBS : Component that controls translation of proteins.
+ DNAassembly : Container for CDS and other genetic parts.
+ DNA_part : Base class for DNA component parts.
+
+ Notes
+ -----
+ The CDS component itself does not generate any reactions during CRN
+ compilation. It serves primarily as a data structure to associate a
+ coding sequence with its protein product. The actual translation
+ reactions are generated by RBS components that reference the transcript
+ containing this CDS.
+
+ Examples
+ --------
+ Create a CDS with automatic protein naming:
+
+ >>> cds = bcp.CDS(name='gfp')
+ >>> cds.protein
+ protein_gfp
+
+ Create a CDS with explicit protein name:
+
+ >>> cds = bcp.CDS(
+ ... name='cds_reporter',
+ ... protein='GFP_protein'
+ ... )
+
+ Use a CDS in a DNA assembly:
+
+ >>> assembly = bcp.DNAassembly(
+ ... name='gene_construct',
+ ... promoter='pconst',
+ ... rbs='rbs1',
+ ... protein=cds
+ ... )
+
+ """
+
def __init__(self, name, protein=None, no_stop_codons=[], **kwargs):
- """A CDS is a sequence of DNA that codes for a protein."""
- self.name = name
DNA_part.__init__(self, name, no_stop_codons=no_stop_codons, **kwargs)
# TODO use set_species()
if protein is None:
@@ -20,10 +87,35 @@ def __init__(self, name, protein=None, no_stop_codons=[], **kwargs):
self.protein = protein.get_species()
def update_species(self):
+ """Generate species associated with this CDS.
+
+ Returns
+ -------
+ list of Species
+ List containing only the protein species encoded by this CDS.
+
+ """
return [self.protein]
def update_reactions(self):
+ """Generate reactions associated with this CDS.
+
+ Returns
+ -------
+ list of Reaction
+ Empty list. CDS components have no associated mechanism.
+ Translation reactions are generated by the RBS component.
+
+ """
return []
def get_species(self):
+ """Get the protein species encoded by this CDS.
+
+ Returns
+ -------
+ Species
+ The protein species associated with this coding sequence.
+
+ """
return self.protein
diff --git a/biocrnpyler/components/dna/construct.py b/biocrnpyler/components/dna/construct.py
index 94bcd597..aaaba0a0 100644
--- a/biocrnpyler/components/dna/construct.py
+++ b/biocrnpyler/components/dna/construct.py
@@ -20,6 +20,107 @@
class Construct(Component, OrderedPolymer):
+ """Base class for ordered genetic constructs with multiple parts.
+
+ A Construct represents an ordered arrangement of genetic parts (promoters,
+ RBS, coding sequences, terminators, etc.) that form a functional unit.
+ This class provides the infrastructure for handling complex genetic
+ constructs with multiple components, their enumeration, and generation
+ of combinatorial variants. Constructs can be linear or circular and
+ support both forward and reverse orientations of their constituent parts.
+
+ Parameters
+ ----------
+ parts_list : list of list
+ List of parts in the format [[part, direction], [part, direction],
+ ...] where each part must be an OrderedMonomer and direction is
+ 'forward' or 'reverse'.
+ name : str, optional
+ Name of the construct. If None, automatically generated from parts.
+ circular : bool, default=False
+ If True, the construct is circular (e.g., plasmid). If False, linear.
+ mechanisms : dict or list, optional
+ Custom mechanisms for this construct, overriding mixture defaults.
+ parameters : dict, optional
+ Parameter values specific to this construct.
+ attributes : list of str, optional
+ List of attribute tags for the construct.
+ initial_concentration : float, optional
+ Initial concentration of the construct species.
+ component_enumerators : list, optional
+ List of enumerator objects that generate construct variants.
+ make_dirless_hash : bool, default=True
+ If True, generates direction-independent hash for construct
+ comparison.
+ **kwargs
+ Additional keyword arguments passed to Component constructor.
+
+ Attributes
+ ----------
+ parts_list : list
+ Ordered list of parts in the construct.
+ circular : bool
+ Whether the construct is circular.
+ component_enumerators : list
+ Enumerators for generating construct variants.
+ out_components : list or None
+ Cached list of output components from enumeration.
+ predicted_rnas : list or None
+ Cached list of predicted RNA species.
+ predicted_proteins : list or None
+ Cached list of predicted protein species.
+
+ See Also
+ --------
+ DNA_construct : DNA-specific construct implementation.
+ RNA_construct : RNA-specific construct implementation.
+ DNA_part : Base class for individual DNA parts.
+ OrderedPolymer : Base class for ordered polymer structures.
+
+ Notes
+ -----
+ Constructs support several advanced features:
+
+ - Part enumeration: Automatically generates all functional variants
+ based on the parts present (e.g., all promoter-RBS combinations)
+ - Combinatorial complexes: Generates all possible binding states
+ when regulatory proteins bind to different parts
+ - Direction-free comparison: Can identify equivalent constructs
+ regardless of orientation or circular permutation
+
+ The class maintains caches for enumerated components, RNA products, and
+ protein products to avoid redundant computation.
+
+ Examples
+ --------
+ Create a simple linear construct:
+
+ >>> promoter = bcp.Promoter('ptet')
+ >>> rbs = bcp.RBS('RBS_standard')
+ >>> cds = bcp.CDS('GFP')
+ >>> parts = [[promoter, 'forward'], [rbs, 'forward'], [cds, 'forward']]
+ >>> construct = bcp.Construct(
+ ... parts_list=parts,
+ ... name='gene_circuit',
+ ... circular=False
+ ... )
+
+ Create a circular plasmid:
+
+ >>> ori = bcp.DNA_part('p15A')
+ >>> terminator = bcp.Terminator('BBa_B0022')
+ >>> parts = [
+ >>> [ori, 'forward'], [promoter, 'forward'], [rbs, 'forward'],
+ >>> [cds, 'forward'], [terminator, 'forward']
+ >>> ]
+ >>> plasmid = bcp.Construct(
+ ... parts_list=parts,
+ ... name='pExpression',
+ ... circular=True
+ ... )
+
+ """
+
def __init__(
self,
parts_list,
@@ -33,12 +134,6 @@ def __init__(
make_dirless_hash=True,
**kwargs,
):
- """This represents a bunch of parts in a row.
-
- A parts list has [[part, direction], [part, direction], ...].
- Each part must be an OrderedMonomer.
-
- """
if component_enumerators is None:
component_enumerators = []
self.component_enumerators = component_enumerators
@@ -75,6 +170,48 @@ def parts_list(self):
return self.polymer
def make_name(self):
+ """Generate a systematic name for the construct based on its parts.
+
+ Creates a name by concatenating all part names with underscores.
+ Parts in reverse orientation are suffixed with '_r', and circular
+ constructs are suffixed with '_o'.
+
+ Returns
+ -------
+ str
+ The generated construct name.
+
+ Examples
+ --------
+ Linear construct with forward parts:
+
+ >>> promoter = bcp.Promoter('pLac')
+ >>> rbs = bcp.CDS('RBS1')
+ >>> cds = bcp.CDS('GFP')
+ >>> construct = bcp.DNA_construct(
+ ... [[promoter, 'forward'], [rbs, 'forward'], [cds, 'forward']]
+ ... )
+ >>> construct.make_name()
+ 'pLac_RBS1_GFP'
+
+ Linear construct with reversed part:
+
+ >>> construct = bcp.DNA_construct(
+ ... [[promoter, 'forward'], [rbs, 'reverse'], [cds, 'forward']]
+ ... )
+ >>> construct.make_name()
+ 'pLac_RBS1_r_GFP'
+
+ Circular construct:
+
+ >>> construct = bcp.DNA_construct(
+ ... [[promoter, 'forward'], [rbs, 'forward'], [cds, 'forward']],
+ ... circular=True
+ ... )
+ >>> construct.make_name()
+ 'pLac_RBS1_GFP_o'
+
+ """
output = ''
outlst = []
for part in self.parts_list:
@@ -88,19 +225,75 @@ def make_name(self):
return output
def get_part(self, part=None, part_type=None, name=None, index=None):
- """Function to get parts from Construct.parts_list.
-
- One of the 3 keywords must not be None.
-
- part: an instance of a DNA_part. Searches Construct.parts_list
- for a DNA_part with the same type and name.
- part_type: a class of DNA_part. For example, Promoter. Searches
- Construct.parts_list for a DNA_part with the same type.
- name: str. Searches Construct.parts_list for a DNA_part with
- the same name
- index: int. returns Construct.parts_list[index]
+ """Find and return a part from the construct by various criteria.
+
+ Searches the construct's parts list for a part matching the given
+ criteria. Only one search criterion should be provided at a time.
+
+ Parameters
+ ----------
+ part : DNA_part, optional
+ A specific DNA_part instance to find. Matches both type and name.
+ part_type : type, optional
+ Find all parts that are instances of this type (e.g., Promoter).
+ name : str, optional
+ Find part(s) with this exact name.
+ index : int, optional
+ Return the part at this position in parts_list.
+
+ Returns
+ -------
+ DNA_part, list of DNA_part, or None
+ Single matching part if exactly one match found. List of parts if
+ multiple matches found. None if no matches found.
+
+ Raises
+ ------
+ ValueError
+ If multiple search criteria are provided simultaneously, or if
+ invalid types are provided for parameters.
+
+ Warns
+ -----
+ UserWarning
+ If multiple matching parts are found (returns list).
+
+ Notes
+ -----
+ The search is performed with the following priority:
+
+ 1. Index (direct lookup)
+ 2. Part instance (type and name must match)
+ 3. Name (string match)
+ 4. Type (isinstance check)
+
+ Only one search criterion should be provided at a time.
+
+ Examples
+ --------
+ Find a part by name:
+
+ >>> promoter = bcp.Promoter('ptet')
+ >>> rbs = bcp.RBS('RBS_standard')
+ >>> cds = bcp.CDS('GFP')
+ >>> parts = [
+ ... [promoter, 'forward'], [rbs, 'forward'], [cds, 'forward']
+ ... ]
+ >>> construct = bcp.Construct(
+ ... parts_list=parts,
+ ... name='gene_circuit',
+ ... circular=False
+ ... )
+ >>> promoter = construct.get_part(name='ptet')
+
+ Get the third part in the construct:
+
+ >>> third_part = construct.get_part(index=2)
+
+ Find all RBS parts:
+
+ >>> all_rbs = construct.get_part(part_type=bcp.RBS)
- If nothing is found, returns None.
"""
if [part, name, index, part_type].count(None) != 3:
raise ValueError(
@@ -146,12 +339,43 @@ def get_part(self, part=None, part_type=None, name=None, index=None):
return matches
def reverse(self):
- """Reverses everything, without actually changing the DNA.
+ """Reverse the construct without modifying the underlying DNA.
+
+ Creates a reversed representation of the construct where all parts
+ are in reverse order and each part's direction is flipped. This is
+ useful for generating the reverse complement of a construct.
+
+ Returns
+ -------
+ Construct
+ Returns self after reversing the parts list and flipping
+ directions.
- Also updates the name and stuff, since this is now a different
- Construct.
+ Notes
+ -----
+ This method modifies the construct in place by:
+
+ 1. Reversing the order of parts in the parts_list
+ 2. Flipping each part's direction (forward <--> reverse)
+
+ The underlying DNA sequence is not modified, only the representation
+ changes.
+
+ Examples
+ --------
+ Reverse a simple construct:
+
+ >>> promoter = bcp.Promoter('ptet')
+ >>> gene = bcp.CDS('GFP')
+ >>> construct = bcp.Construct(
+ ... [[promoter, 'forward'], [gene, 'forward']])
+ >>> construct.reverse()
+ Construct = GFP_r_pLac_r
"""
+ # Reverses everything, without actually changing the DNA.
+ # Also updates the name and stored, since this is now a different
+ # Construct.
OrderedPolymer.reverse(self)
self.reset_stored_data()
self.name = self.make_name()
@@ -159,16 +383,81 @@ def reverse(self):
return self
def get_reversed(self):
- """Reversed version of construct without changing the construct."""
+ """Create a deep copy of the construct with reversed orientation.
+
+ Returns a new construct that is the reverse complement of this
+ construct, with all parts in reverse order and flipped directions.
+ The original construct is not modified.
+
+ Returns
+ -------
+ Construct
+ A new construct object with reversed parts and directions.
+
+ See Also
+ --------
+ reverse : Reverse the construct in place.
+
+ Examples
+ --------
+ Get reversed version without modifying original:
+
+ >>> promoter = bcp.Promoter('ptet')
+ >>> cds = bcp.CDS('GFP')
+ >>> original = bcp.Construct(
+ ... [[promoter, 'forward'], [cds, 'forward']])
+ >>> original.get_reversed()
+ Construct = GFP_r_ptet_r
+
+ """
outcon = copy.deepcopy(self)
outcon.reverse()
return outcon
def get_circularly_permuted(self, new_first_position):
- """Circularly permute a construct.
-
- Returns a new construct which has the first position changed
- to new_first_position.
+ """Create a circularly permuted version of this construct.
+
+ Returns a new construct where the circular ordering of parts starts
+ at a different position. Only valid for circular constructs.
+
+ Parameters
+ ----------
+ new_first_position : int
+ The index of the part that should become the first position in
+ the new construct.
+
+ Returns
+ -------
+ DNA_construct
+ A new circular DNA_construct with parts reordered starting from
+ the specified position.
+
+ Raises
+ ------
+ ValueError
+ If the construct is linear (circular permutation only applies
+ to circular constructs).
+
+ Notes
+ -----
+ The parts list is rotated so that `parts_list[new_first_position]`
+ becomes the new first element, maintaining the circular structure.
+
+ Examples
+ --------
+ Permute a circular plasmid:
+
+ >>> ori = bcp.DNA_part('p15A')
+ >>> promoter = bcp.Promoter('ptet')
+ >>> cds = bcp.CDS('GFP')
+ >>> plasmid = bcp.DNA_construct(
+ ... [[ori, 'forward'], [promoter, 'forward'], [cds, 'forward']],
+ ... circular=True
+ ... )
+ >>> plasmid
+ DNA_construct = p15A_ptet_GFP_o
+ >>> plasmid.get_circularly_permuted(1)
+ DNA_construct = ptet_GFP_p15A_o
"""
if not self.circular:
@@ -185,20 +474,73 @@ def get_circularly_permuted(self, new_first_position):
)
def set_mixture(self, mixture):
+ """Set the mixture containing this construct and all its parts.
+
+ Propagates the mixture reference to all parts in the construct,
+ ensuring they share access to the same parameter database and
+ mechanisms.
+
+ Parameters
+ ----------
+ mixture : Mixture
+ The mixture object that contains this construct.
+
+ Notes
+ -----
+ This method ensures that all parts in the construct have access to
+ the same mixture-level parameters and mechanisms, maintaining
+ consistency across the entire construct.
+
+ """
self.mixture = mixture
for part in self.parts_list:
part.set_mixture(mixture)
def update_permutation_hash(self):
- """Update hash for this DNA construct.
+ """Update the direction-independent hash for this construct.
+
+ Generates a unique string representation of the construct that is
+ invariant to direction (forward/reverse) and circular permutations
+ (for circular constructs). This enables comparison of functionally
+ equivalent constructs regardless of their representation.
+
+ Notes
+ -----
+ The hash is stored in the `directionless_hash` attribute and is used
+ for identifying equivalent constructs that differ only in orientation
+ or circular starting position.
+
+ The hash is computed using the `omnihash` class method, which finds
+ the most alphabetically ordered representation of the construct.
- Update the unique string generated to represent the content of
- this dna construct regardless of orientation and rotation.
+ See Also
+ --------
+ omnihash : Class method that computes the direction and rotation-free
+ hash.
"""
self.directionless_hash, _, _ = Construct.omnihash(self)
def update_base_species(self, base_name=None, attributes=None):
+ """Update the base species representation of this construct.
+
+ Sets the `base_species` attribute to a Species object representing
+ the construct's primary chemical species in the CRN.
+
+ Parameters
+ ----------
+ base_name : str, optional
+ Name for the base species. If None, uses the construct's name.
+ attributes : list of str, optional
+ Attribute tags to add to the species.
+
+ Notes
+ -----
+ The base species serves as the chemical representation of the
+ construct in CRN compilation, with material_type matching the
+ construct's material_type (e.g., 'dna' for DNA_constructs).
+
+ """
if base_name is None:
self.base_species = self.set_species(
self.name,
@@ -213,7 +555,30 @@ def update_base_species(self, base_name=None, attributes=None):
)
def update_parameters(self, overwrite_parameters=True):
- """Update parameters of all parts in the construct."""
+ """Update parameters for the construct and all its parts.
+
+ Propagates parameter updates from the construct's parameter database
+ to all parts in the parts list, ensuring consistent parameters
+ throughout the construct.
+
+ Parameters
+ ----------
+ overwrite_parameters : bool, default=True
+ If True, new parameter values overwrite existing ones in the
+ parts. If False, existing parameters in parts are preserved.
+
+ Notes
+ -----
+ This method:
+
+ 1. Updates the construct's own parameters via the parent Component
+ class
+ 2. Propagates these parameters to each part in the construct
+
+ This ensures that all parts have access to the same parameter values
+ unless explicitly overridden at the part level.
+
+ """
Component.update_parameters(
self=self, parameter_database=self.parameter_database
)
@@ -230,6 +595,36 @@ def add_mechanism(
overwrite=False,
optional_mechanism=False,
):
+ """Add a mechanism to the construct and all its parts.
+
+ Adds the mechanism to the construct's mechanism dictionary and
+ propagates it to all parts in the construct.
+
+ Parameters
+ ----------
+ mechanism : Mechanism
+ The mechanism object to add.
+ mech_type : str, optional
+ The mechanism type key. If None, uses the mechanism's
+ `mechanism_type` attribute.
+ overwrite : bool, default=False
+ If True, overwrites existing mechanisms with the same type. If
+ False, raises ValueError for duplicate types.
+ optional_mechanism : bool, default=False
+ If True, suppresses ValueError when a mechanism key conflict
+ occurs and `overwrite` is False.
+
+ Notes
+ -----
+ This method:
+
+ 1. Adds the mechanism to the construct via the parent Component
+ class
+ 2. Propagates the mechanism to each part in the construct
+
+ This ensures mechanism consistency across the entire construct.
+
+ """
Component.add_mechanism(
self,
mechanism,
@@ -246,11 +641,56 @@ def add_mechanism(
)
def __repr__(self):
- """This is just for display purposes."""
return 'Construct = ' + self.make_name()
def __contains__(self, obj2):
- """Checks if construct contains a certain part (or copy)."""
+ """Check if the construct contains a specific part.
+
+ Tests whether a DNA_part or copy of a DNA_part exists in this
+ construct's parts list.
+
+ Parameters
+ ----------
+ obj2 : DNA_part
+ The part to search for in the construct.
+
+ Returns
+ -------
+ bool
+ True if the part (or a copy with the same name and type) is in
+ the construct, False otherwise.
+
+ Notes
+ -----
+ This method supports two types of containment checks:
+
+ 1. Direct membership: The exact part object is in the construct
+ (checked via `obj2.parent == self`)
+ 2. Copy membership: A different part object with the same type and
+ name exists in the construct
+
+ Examples
+ --------
+ Check if construct contains a part:
+
+ >>> promoter = bcp.Promoter('ptet')
+ >>> rbs = bcp.RBS('RBS_standard')
+ >>> cds = bcp.CDS('GFP')
+ >>> parts = [
+ ... [promoter, 'forward'], [rbs, 'forward'], [cds, 'forward']
+ ... ]
+ >>> construct = bcp.Construct(
+ ... parts_list=parts,
+ ... name='gene_circuit'
+ ... )
+ >>> promoter in construct
+ True
+
+ >>> unknown_part = bcp.Promoter('plac')
+ >>> unknown_part in construct
+ False
+
+ """
if isinstance(obj2, DNA_part):
# if we got a DNA part it could mean one of two things:
# 1 we want to know if a dna part is anywhere
@@ -288,16 +728,45 @@ def get_species(self):
return out_species
def located_allcomb(self, spec_list):
- """Recursively trace all paths through a list.
+ """Generate all combinatorial placement dictionaries for species.
+
+ Creates all possible combinations of species placements when multiple
+ species can bind at different positions in the construct.
+
+ Parameters
+ ----------
+ spec_list : list of Species
+ List of species that have position attributes, typically
+ ComplexSpecies that can bind at specific locations.
+
+ Returns
+ -------
+ list of dict
+ List of dictionaries where each dict maps positions (int) to
+ [species, direction] pairs representing one possible combinatorial
+ binding state.
+
+ Notes
+ -----
+ This method handles the combinatorics of placing multiple binding
+ species at different positions. For example:
+
+ - Species A binds at position 0
+ - Species B binds at position 3
+ - Species C also binds at position 0
+
+ The method generates all valid combinations: `{0: [A, direction]}`,
+ `{0: [C, direction]}`, `{0: [A, direction], 3: [B, direction]}`,
+ `{0: [C, direction], 3: [B, direction]}`, etc.
+
+ The algorithm:
+
+ 1. Extracts positions from spec_list
+ 2. Groups species by position into prototype_list
+ 3. Generates all position combinations via all_comb
+ 4. For each position combination, generates all possible species
+ selections at those positions
- [[[part1,1],[part2,5]],[[part3,1]],[[part4,5],[part5,12]]]
- ====================>
- compacted_indexes = [1,5,12]
- prototype_list = [[part1,part3],[part2,part4],[part5]]
- comb_list = [[1],[5],[12],[1,5],[1,12],[5,12],[1,5,12]]
- ===========================
- then, take the lists from comb_list and create all possible lists
- out of prototype_list that includes those elements
"""
# first we have to construct the list we are tracing paths through
@@ -371,12 +840,39 @@ def recursive_path(in_list):
return outdict_list
def make_polymers(self, species_lists, backbone):
- """Makes polymers from lists of species.
+ """Create polymer species from combinatorial binding combinations.
+
+ Generates OrderedPolymerSpecies by replacing specific monomers in a
+ backbone polymer with bound versions according to the replacement
+ dictionaries.
+
+ Parameters
+ ----------
+ species_lists : list of dict
+ List of replacement dictionaries where each dict maps positions
+ (int) to [species, direction] pairs indicating which monomers
+ to replace with bound versions.
+ backbone : OrderedPolymerSpecies
+ The base polymer species serving as the template for creating
+ bound variants.
+
+ Returns
+ -------
+ list of OrderedPolymerSpecies
+ List of polymer species representing all specified combinatorial
+ binding states.
+
+ Notes
+ -----
+ This method takes a backbone polymer (typically the unbound construct)
+ and creates variants where specific positions contain bound complexes.
+
+ For example, given backbone `` and replacements:
+
+ - `{0: [A:RNAP, forward]}` creates `<[A:RNAP],B,C>`
+ - `{0: [A:RNAP, forward], 1: [B:RNAP, forward]}` creates
+ `<[A:RNAP],[B:RNAP],C>`
- inputs:
- species_lists: list of species which are to be assembled into a
- polymer
- backbone: the base_species which all these polymers should have
"""
polymers = []
for combo in species_lists:
@@ -394,25 +890,43 @@ def make_polymers(self, species_lists, backbone):
return polymers
def update_combinatorial_complexes(self, active_components):
- """Update complexes formed by combinations of components.
+ """Generate all combinatorial binding state species for the construct.
- Given an input list of components, we produce all complexes yielded
- by those components, mixed and matched to make all possible
- combinatorial complexes, where each component is assumed to only
- care about binding to one spot.
+ Given components that can bind to different positions in the
+ construct, this method generates all possible combinations of binding
+ states by mixing and matching bound species at different locations.
- First, the components are asked what species they make, then these
- species are sifted to reveal only the ones which are versions of
- the same polymer, just with different locations bound. Then,
- combinatorial combinations are made. for example:
+ Parameters
+ ----------
+ active_components : list of Component
+ Components that generate binding complexes with the construct.
+ Each is assumed to bind at only one position.
- construct:
+ Returns
+ -------
+ list of OrderedPolymerSpecies
+ All possible combinatorial binding states of the construct,
+ including the unbound state and all single and multiple binding
+ combinations.
- two new species are possible: <[A:RNAP],B,C>;
+ Notes
+ -----
+ The method:
- Combinatorial species is also possible (since A and B are assumed
- to act independantly) <[A:RNAP],[B:RNAP], C> complexes, where each
- component is assumed to only care about binding to one spot.
+ 1. Collects all binary complex species from each active component
+ 2. Identifies unique positioned complexes
+ 3. Generates all combinatorial placements using `located_allcomb`
+ 4. Creates polymer species for each combination using `make_polymers`
+
+ For example, given construct `` with two components that
+ create `<[A:RNAP],B,C>` and ``, this method generates:
+
+ - `` (unbound)
+ - `<[A:RNAP],B,C>` (A bound)
+ - `` (B bound)
+ - `<[A:RNAP],[B:RNAP],C>` (both bound)
+
+ assuming A and B act independently.
"""
species = []
@@ -446,7 +960,37 @@ def update_combinatorial_complexes(self, active_components):
# Overwrite Component.enumerate_components
def enumerate_constructs(self, previously_enumerated=None):
- """Runs all our component enumerators to generate new constructs."""
+ """Run all enumerators to generate new construct variants.
+
+ Applies all component enumerators to this construct to generate
+ derived constructs (e.g., RNA_constructs from transcription).
+
+ Parameters
+ ----------
+ previously_enumerated : set or list, optional
+ Collection of constructs that have already been enumerated, used
+ to prevent infinite recursion and duplicate enumeration.
+
+ Returns
+ -------
+ list of Construct
+ New constructs generated by all enumerators. For DNA_constructs
+ with the default TxExplorer, this includes all possible
+ RNA_construct transcripts.
+
+ Notes
+ -----
+ Each enumerator's `enumerate_components` method is called with this
+ construct and the previously enumerated set, allowing enumerators to
+ explore transcriptional units, translational products, or other
+ construct-derived components.
+
+ See Also
+ --------
+ TxExplorer : Default enumerator for DNA transcription exploration.
+ TlExplorer : Default enumerator for RNA translation exploration.
+
+ """
new_constructs = []
for enumerator in self.component_enumerators:
new_comp = enumerator.enumerate_components(
@@ -456,26 +1000,37 @@ def enumerate_constructs(self, previously_enumerated=None):
return new_constructs
def combinatorial_enumeration(self):
- """Generate combination of components.
+ """Generate components for all combinatorial binding states.
- Returns a list of new components that are copies of existing
- components, but with a different species placed inside.
+ Creates copies of parts that can react with different combinatorial
+ binding states of the construct, ensuring reactions are generated for
+ all possible binding configurations.
- This different species represents different combinatorial
- states of the polymer. for example:
+ Returns
+ -------
+ list of Component
+ Components configured to react with different combinatorial
+ binding states of the construct.
- construct:
+ Notes
+ -----
+ This method handles the generation of components that account for
+ multiple simultaneous binding events. For example, given construct
+ `` where both A and B can bind RNAP:
- two new species are possible: <[A:RNAP],B,C>;
- combinatorial species is also possible (since A and B are
- assumed to act independantly)
+ - Binary complexes: `<[A:RNAP],B,C>` and ``
+ - Combinatorial complex: `<[A:RNAP],[B:RNAP],C>`
- <[A:RNAP],[B:RNAP],C>
+ The method returns multiple versions of components A and B, each
+ configured to bind to different pre-existing binding states:
- Thus, this function returns A which binds to (creating
- <[A:RNAP],B,C>) AND also A which binds to
- (creating <[A:RNAP],[B:RNAP],C>). Likewise for B In total two
- A components are returned, and two B components are returned.
+ - A component binding to `` --> `<[A:RNAP],B,C>`
+ - A component binding to `` --> `<[A:RNAP],[B:RNAP],C>`
+ - B component binding to `` --> ``
+ - B component binding to `<[A:RNAP],B,C>` --> `<[A:RNAP],[B:RNAP],C>`
+
+ This ensures proper reaction enumeration for all binding
+ combinations.
"""
# Looks at combinatorial states of constructs to generate DNA_parts
@@ -522,29 +1077,48 @@ def combinatorial_enumeration(self):
return combinatorial_components
def enumerate_components(self, previously_enumerated=None):
- """Returns a list of new components and constructs.
+ """Generate all derived components and constructs from this construct.
+
+ Combines both construct enumeration (e.g., transcripts) and
+ combinatorial component enumeration (for binding states) to produce
+ a complete set of derived components.
+
+ Parameters
+ ----------
+ previously_enumerated : set or list, optional
+ Collection of components already enumerated, used to prevent
+ infinite recursion.
+
+ Returns
+ -------
+ list of Component
+ All new components and constructs generated, including:
+
+ - New constructs from enumerators (e.g., RNA_constructs)
+ - Components for combinatorial binding states
- New components are generated if:
- - a component creates a species which results in binding to part of
- the construct
- Example: -> <[A:RNAP],B,C>
- Then, A would be returned since a new species is created
+ Notes
+ -----
+ This method generates new components in two scenarios:
- - more than one such component exist in the same construct, for
- example: construct:
- two new species are possible: <[A:RNAP],B,C>;
+ 1. Binding-induced species: When a component creates a species
+ that binds to part of the construct. For example, ``
+ --> `<[A:RNAP],B,C>` would return component A configured
+ for this binding.
- combinatorial species is also possible (since A and B are assumed
- to act independantly)
- <[A:RNAP],[B:RNAP], C>
+ 2. Combinatorial binding states: When multiple components can
+ bind simultaneously. For construct `` where both A
+ and B can bind RNAP:
- Thus, this function returns A which binds to (creating
- <[A:RNAP], B, C>) AND also A which binds to
- (creating <[A:RNAP], [B:RNAP], C>). Likewise for B. In total two
- A components are returned, and two B components are returned.
- New constructs are generated if: self.enumerate_construcs() says
- so. For example, in , A is a promoter and makes an
- RNA_construct containing
+ - Binary species: `<[A:RNAP],B,C>` and ``
+ - Combinatorial: `<[A:RNAP],[B:RNAP],C>`
+
+ Returns components A and B configured to bind to various pre-bound
+ states, ensuring all binding combinations are enumerated.
+
+ 3. New constructs: Generated by `enumerate_constructs()`. For example,
+ a DNA_construct with promoter A generates an RNA_construct
+ containing ``.
"""
# Runs component enumerator to generate new constructs
@@ -559,10 +1133,28 @@ def enumerate_components(self, previously_enumerated=None):
@classmethod
def get_partstring(cls, part):
- """Get string name of a part (including direction).
+ """Generate a string identifier for a part including its direction.
+
+ Creates a unique string representation of a part that includes its
+ name and direction but not its position, useful for construct
+ comparison.
+
+ Parameters
+ ----------
+ part : DNA_part or OrderedMonomer
+ The part to generate a string identifier for.
+
+ Returns
+ -------
+ str
+ String combining the part's name and direction (e.g.,
+ 'promoter_pLacforward' or 'gene_GFPreverse').
- A string name of a part including its name and direction (and
- not position).
+ Notes
+ -----
+ This method creates an "orphan" copy of the part (without position
+ or direction) to get the base name, then appends the direction to
+ create a direction-aware identifier.
"""
orphan = part.get_orphan()
@@ -574,10 +1166,28 @@ def get_partstring(cls, part):
@classmethod
def get_partlist_hash(cls, partlist):
- """Get hash for a list of parts.
+ """Generate a hash string for an ordered list of parts.
- Creates a string containing the name and direction of all
- parts in a list of parts (but not their position).
+ Creates a unique string identifier for a parts list by concatenating
+ part names with position indices, capturing the order and content of
+ parts (but not their absolute positions).
+
+ Parameters
+ ----------
+ partlist : list
+ List of (part_string, part) tuples where part_string is typically
+ generated by `get_partstring`.
+
+ Returns
+ -------
+ str
+ Hash string representing the ordered parts list.
+
+ Notes
+ -----
+ The hash format concatenates each part's string representation with
+ its index in the list, separated by underscores. This creates a
+ unique identifier for the parts sequence.
"""
partlist_str = '_'.join(
@@ -590,10 +1200,32 @@ def get_partlist_hash(cls, partlist):
@classmethod
def create_hashless_reverse(cls, construct):
- """Create reverse construction without hash.
+ """Create a reversed construct without computing its hash.
+
+ Generates a reversed version of the construct with parts in reverse
+ order and flipped directions, but skips hash computation to avoid
+ infinite recursion during hash calculations.
+
+ Parameters
+ ----------
+ construct : Construct
+ The construct to reverse.
- Create a reverse construct but don't calculate its hash (because that
- would make an infinite loop).
+ Returns
+ -------
+ Construct
+ A new construct with reversed parts order and flipped directions,
+ with `make_dirless_hash=False` to prevent hash computation.
+
+ Notes
+ -----
+ This method is used internally by hash computation routines that need
+ to compare forward and reverse orientations. Setting
+ `make_dirless_hash=False` prevents infinite loops where hash
+ computation would trigger reverse computation, which would trigger
+ hash computation, etc.
+
+ The circularity status of the construct is preserved.
"""
rev_con = [a.get_orphan() for a in construct]
@@ -609,22 +1241,47 @@ def create_hashless_reverse(cls, construct):
@classmethod
def rotation_free_hash(cls, construct):
- """Compute alphabetically ordered, circular permutation.
-
- Calculates a unique circular permutation that is the most
- alphabetically ordered.
-
- Every part is considered as a potential starting point, and the most
- alphabetically ordered order is then chosen as the best permutation
-
- Returns:
- hash of the most alphabetical ordering, direction of the ordering
- (always 1), first position of the best rotation. Thus, to recreate
- the conformation of the construct used to make this hash you would
- have to
-
- 1) invert the construct (or not), then
- 2) use the indicated position as the first position
+ """Compute the most alphabetically ordered circular permutation hash.
+
+ Finds the circular permutation of the construct that produces the
+ most alphabetically ordered sequence of parts, providing a canonical
+ representation for circular constructs regardless of starting
+ position.
+
+ Parameters
+ ----------
+ construct : Construct
+ The circular construct to hash. Should have `circular=True`.
+
+ Returns
+ -------
+ hash : str
+ String hash of the most alphabetically ordered permutation.
+ direction : int
+ Always 1 (forward direction, since only rotation is considered).
+ first_position : int
+ The position that should be used as the first position to
+ recreate this canonical permutation.
+
+ Notes
+ -----
+ This method evaluates every possible starting position for a circular
+ construct and selects the permutation that produces the most
+ alphabetically ordered sequence when parts are compared
+ lexicographically.
+
+ To recreate the canonical form from the original construct:
+
+ 1. Rotate to start at `first_position`
+
+ The direction is always 1 because this method only considers
+ rotations, not reversals.
+
+ Examples
+ --------
+ For a circular construct with parts A, B, C, if starting at C gives
+ the most alphabetically ordered sequence (C, A, B), then
+ `first_position` would be 2.
"""
@@ -689,18 +1346,43 @@ def circular_next(part, construct):
@classmethod
def direction_rotation_free_hash(cls, construct):
- """Best circular permutation of a construct, forward and reverse.
+ """Compute the best hash considering both rotation and direction.
+
+ Finds the most alphabetically ordered representation of a circular
+ construct by evaluating all rotations in both forward and reverse
+ orientations.
+
+ Parameters
+ ----------
+ construct : Construct
+ The circular construct to hash.
+
+ Returns
+ -------
+ hash : str
+ String hash of the most alphabetically ordered permutation in
+ either direction.
+ direction : int
+ Direction of the best ordering: 1 for forward, -1 for reverse.
+ first_position : int
+ The position that should be used as the first position in the
+ best permutation.
- Then returns whichever one of those comes alphabetically first.
+ Notes
+ -----
+ This method:
- Returns:
- hash of the most alphabetical ordering, direction of the ordering
- (1 or -1), first position of the best rotation
+ 1. Computes the best forward rotation using `rotation_free_hash`
+ 2. Creates a reversed construct and computes its best rotation
+ 3. Returns whichever produces the more alphabetically ordered hash
- thus, to recreate the conformation of the construct used to make
- this hash you would have to
- 1) invert the construct (or not), then
- 2) use the indicated position as the first position
+ To recreate the canonical form:
+
+ 1. If direction is -1, reverse the construct
+ 2. Rotate to start at `first_position`
+
+ This provides complete normalization for circular constructs,
+ accounting for both rotation and direction symmetries.
"""
rev_con = Construct.create_hashless_reverse(construct)
@@ -713,16 +1395,39 @@ def direction_rotation_free_hash(cls, construct):
@classmethod
def linear_direction_free_hash(cls, construct):
- """String representing the construct forward or reverse.
+ """Compute the best hash for a linear construct in either direction.
+
+ Determines which orientation (forward or reverse) produces the most
+ alphabetically ordered sequence for a linear construct.
+
+ Parameters
+ ----------
+ construct : Construct
+ The linear construct to hash.
+
+ Returns
+ -------
+ hash : str
+ String hash of the most alphabetically ordered orientation.
+ direction : int
+ Direction of the best ordering: 1 for forward, -1 for reverse.
+ first_position : int
+ Always 0 for linear constructs (no circular permutation).
- Returns:
- hash of the most alphabetical ordering, direction of the ordering
- (1 or -1), first position of the best rotation (always 0)
+ Notes
+ -----
+ This method compares forward and reverse orientations part-by-part,
+ stopping as soon as one orientation is determined to be more
+ alphabetically ordered than the other.
- thus, to recreate the conformation of the construct used to make
- this hash you would have to
- 1) invert the construct (or not), then
- 2) use the indicated position as the first position
+ To recreate the canonical form:
+
+ 1. If direction is -1, reverse the construct
+ 2. Start at position 0 (always the first position for linear
+ constructs)
+
+ Unlike circular constructs, linear constructs have a defined start
+ and end, so the first_position is always 0.
"""
rev_con = Construct.create_hashless_reverse(construct)
@@ -760,25 +1465,69 @@ def linear_direction_free_hash(cls, construct):
@classmethod
def omnihash(cls, construct):
- """Construct best circullar permutation and ordering.
-
- A construct can exist forwards or backwards, and circularly permuted
- (but only if it's a circular construct).
-
- This function creates the "best" circular permutation and ordering
- of a construct. But the circular permutation is only calculated if
- the construct is circular. Best is calculated based on which
- orientation/ permutation has the most part names in alphabetical
- order.
-
- Returns:
- hash of the most alphabetical ordering (string), direction of the
- ordering (1 or -1), first position of the best rotation
-
- thus, to recreate the conformation of the construct used to make
- this hash you would have to
- 1) invert the construct (or not), then
- 2) use the indicated position as the first position
+ """Compute a canonical hash for the construct.
+
+ Creates the most alphabetically ordered representation of a construct,
+ accounting for direction (forward/reverse) and, for circular
+ constructs, rotation. This provides a unique canonical identifier
+ for functionally equivalent constructs.
+
+ Parameters
+ ----------
+ construct : Construct
+ The construct to hash (can be linear or circular).
+
+ Returns
+ -------
+ hash : str
+ Canonical hash string with 'circular' or 'linear' suffix
+ indicating construct topology.
+ direction : int
+ Direction of the canonical form: 1 for forward, -1 for reverse.
+ first_position : int
+ Starting position for the canonical form. For circular constructs,
+ this is the optimal rotation point. For linear constructs, always
+ 0.
+
+ Notes
+ -----
+ For circular constructs:
+
+ 1. Evaluates all rotations in both orientations
+ 2. Selects the most alphabetically ordered permutation
+ 3. Appends 'circular' to the hash
+
+ For linear constructs:
+
+ 1. Compares forward and reverse orientations
+ 2. Selects the most alphabetically ordered orientation
+ 3. Appends 'linear' to the hash
+
+ To recreate the canonical form:
+
+ 1. If direction is -1, reverse the construct
+ 2. For circular constructs, rotate to start at `first_position`
+ 3. For linear constructs, `first_position` is always 0
+
+ This hash enables identification of equivalent constructs that differ
+ only in representation (rotation or direction).
+
+ Examples
+ --------
+ Two circular constructs with the same parts in different rotations
+ will produce the same omnihash:
+
+ >>> A, B, C = (bcp.DNA_part(s) for s in ['A', 'B', 'C'])
+ >>> construct1 = bcp.DNA_construct(
+ ... [[A, 'forward'], [B, 'forward'], [C, 'forward']],
+ ... circular=True)
+ >>> construct2 = bcp.DNA_construct(
+ ... [[B, 'forward'], [C, 'forward'], [A, 'forward']],
+ ... circular=True)
+ >>> hash1, _, _ = bcp.Construct.omnihash(construct1)
+ >>> hash2, _, _ = bcp.Construct.omnihash(construct2)
+ >>> hash1 == hash2
+ True
"""
if construct.circular:
@@ -796,10 +1545,32 @@ def __hash__(self):
return OrderedPolymer.__hash__(self)
def __eq__(self, construct2):
- """Compare two constructs to see if they are equal.
+ """Test equality between two constructs.
+
+ Two constructs are considered equal if they have the same string
+ representation and the same name.
+
+ Parameters
+ ----------
+ construct2 : Construct
+ The other construct to compare with.
+
+ Returns
+ -------
+ bool
+ True if constructs are equal, False otherwise.
+
+ Notes
+ -----
+ This is a simple equality test based on string representation. It
+ does not use deep comparison of parts or the direction-independent
+ hash. For more sophisticated equivalence testing that accounts for
+ rotations and reversals, use the `omnihash` method.
- Equality means comparing the parts list in a way that is not too
- deep.
+ See Also
+ --------
+ omnihash : Compute canonical hash accounting for rotation and
+ direction.
"""
# TODO: make this be a python object comparison
@@ -811,23 +1582,205 @@ def __eq__(self, construct2):
return False
def update_species(self):
+ """Generate species for the construct.
+
+ Returns
+ -------
+ list of Species
+ List containing the construct's primary species representation.
+
+ Notes
+ -----
+ This method is called during CRN compilation by
+ `Mixture.compile_crn()` to generate the species associated with this
+ construct. For most constructs, this returns a single species
+ representing the entire construct.
+
+ """
species = [self.get_species()]
return species
def reset_stored_data(self):
+ """Clear all cached enumeration and prediction data.
+
+ Resets the cached results from component enumeration, RNA prediction,
+ and protein prediction, forcing these to be recomputed on the next
+ access.
+
+ Notes
+ -----
+ This method should be called whenever the construct is modified in a
+ way that would invalidate cached data, such as:
+
+ - Reversing the construct
+ - Changing parts
+ - Updating mechanisms or parameters
+
+ The cached attributes that are reset:
+
+ - `out_components`: Results from `enumerate_components`
+ - `predicted_rnas`: List of predicted RNA products
+ - `predicted_proteins`: List of predicted protein products
+
+ """
self.out_components = None
self.predicted_rnas = None
self.predicted_proteins = None
def changed(self):
+ """Handle construct changes by resetting caches and updating name.
+
+ Called when the construct has been modified, this method resets all
+ cached data and regenerates the construct's name to reflect its
+ current state.
+
+ Notes
+ -----
+ This method performs two operations:
+
+ 1. Resets all cached enumeration and prediction data via
+ `reset_stored_data`
+ 2. Regenerates the construct name via `make_name` to ensure it
+ reflects the current parts configuration
+
+ This should be called after any structural modification to the
+ construct.
+
+ """
self.reset_stored_data()
self.name = self.make_name()
def update_reactions(self, norna=False):
+ """Generate reactions for the construct.
+
+ Returns
+ -------
+ list of Reaction
+ Empty list. Base `Construct` class does not generate reactions
+ directly. Subclasses override this method to provide specific
+ reaction generation.
+
+ Parameters
+ ----------
+ norna : bool, default=False
+ If True, RNA-related reactions are excluded (used in some
+ subclass implementations).
+
+ Notes
+ -----
+ This method is called during CRN compilation by
+ `Mixture.compile_crn()`. The base implementation returns an empty
+ list because the base Construct class does not generate reactions
+ directly - reactions are generated by the parts within the construct
+ through their associated mechanisms.
+
+ Subclasses like `DNA_construct` and `RNA_construct` may override this
+ to provide construct-specific reaction generation.
+
+ """
return []
class DNA_construct(Construct, DNA):
+ """DNA construct representing a functional genetic circuit.
+
+ A DNA_construct is a specialized Construct for DNA sequences that can
+ contain promoters, RBS sites, coding sequences, terminators, and other
+ genetic elements. It supports transcription to generate RNA constructs and
+ provides DNA-specific functionality. The class uses the 'transcription'
+ mechanism to generate RNA products and related species/reactions during
+ CRN compilation.
+
+ Parameters
+ ----------
+ parts_list : list of list
+ List of parts in format [[part, direction], ...] where each part
+ must be a DNA_part or OrderedMonomer.
+ name : str, optional
+ Name of the DNA construct. If None, automatically generated.
+ circular : bool, default=False
+ If True, represents a circular DNA molecule (e.g., plasmid).
+ mechanisms : dict or list, optional
+ Custom mechanisms for this construct, overriding mixture defaults.
+ parameters : dict, optional
+ Parameter values specific to this construct.
+ attributes : list of str, optional
+ List of attribute tags for the construct.
+ initial_concentration : float, optional
+ Initial concentration of the DNA construct.
+ copy_parts : bool, default=True
+ If True, makes deep copies of parts when adding to construct.
+ component_enumerators : list, optional
+ List of enumerators for generating construct variants. Defaults to
+ [TxExplorer()] which explores transcriptional variants.
+ **kwargs
+ Additional keyword arguments passed to parent constructors.
+
+ Attributes
+ ----------
+ material_type : str
+ Always 'dna' for DNA constructs.
+ predicted_rnas : list or None
+ Cached list of RNA_construct objects that can be transcribed.
+ predicted_proteins : list or None
+ Cached list of protein species that can be produced.
+
+ See Also
+ --------
+ Construct : Base class for all constructs.
+ RNA_construct : RNA version of constructs.
+ DNA_part : Base class for DNA parts within constructs.
+ TxExplorer : Default enumerator for transcriptional exploration.
+
+ Notes
+ -----
+ DNA_constructs support several key features:
+
+ - Transcription enumeration: Automatically identifies all possible
+ transcripts based on promoter positions and orientations
+ - Protein prediction: Predicts protein products from transcripts
+ containing RBS sites
+ - Circular DNA: Special handling for plasmids and other circular
+ DNA molecules
+ - Component enumeration: Generates functional variants based on
+ the genetic parts present
+
+ The default TxExplorer enumerator automatically explores all possible
+ transcriptional units in the construct.
+
+ Examples
+ --------
+ Create a simple gene expression construct:
+
+ >>> promoter = bcp.Promoter('ptet')
+ >>> rbs = bcp.RBS('RBS_standard')
+ >>> cds = bcp.CDS('GFP')
+ >>> terminator = bcp.Terminator('BBa_B0022')
+ >>> parts = [
+ ... [promoter, 'forward'], [rbs, 'forward'],
+ ... [cds, 'forward'], [terminator, 'forward']
+ ... ]
+ >>> gene = bcp.DNA_construct(
+ ... parts_list=parts,
+ ... name='expression_cassette'
+ ... )
+
+ Create a circular plasmid:
+
+ >>> ori = bcp.DNA_part('p15A')
+ >>> plasmid_parts = [
+ ... [ori, 'forward'], [promoter, 'forward'], [rbs, 'forward'],
+ ... [gene, 'forward'], [terminator, 'forward']
+ ... ]
+ >>> plasmid = bcp.DNA_construct(
+ ... parts_list=plasmid_parts,
+ ... name='pUC19_GFP',
+ ... circular=True,
+ ... initial_concentration=10
+ ... )
+
+ """
+
def __init__(
self,
parts_list,
@@ -866,6 +1819,92 @@ def __repr__(self):
class RNA_construct(Construct, RNA):
+ """RNA construct representing a functional transcript.
+
+ An RNA construct represents an RNA molecule that can be translated into
+ proteins. Unlike DNA constructs, RNA constructs can only be linear (not
+ circular) and primarily support translation rather than transcription.
+ This class uses the 'translation' mechanism to generate protein products
+ and related species/reactions during CRN compilation.
+
+ Parameters
+ ----------
+ parts_list : list of list
+ List of parts in format [[part, direction], ...] where parts
+ represent functional RNA elements (RBS sites, coding sequences, etc.).
+ name : str, optional
+ Name of the RNA construct. If None, automatically generated.
+ promoter : Promoter, optional
+ Reference to the promoter that produced this RNA transcript.
+ component_enumerators : list, optional
+ List of enumerators for generating construct variants. Defaults to
+ [TlExplorer()] which explores translational variants.
+ length : int, default=0
+ Length of the RNA molecule in nucleotides.
+ **kwargs
+ Additional keyword arguments passed to parent constructors.
+
+ Attributes
+ ----------
+ material_type : str
+ Always 'rna' for RNA constructs.
+ promoter : Promoter or None
+ The promoter that controls transcription of this RNA.
+ length : int
+ Length of the RNA transcript.
+ circular : bool
+ Always False for RNA (RNA cannot be circular).
+
+ See Also
+ --------
+ Construct : Base class for all constructs.
+ DNA_construct : DNA version of constructs.
+ TlExplorer : Default enumerator for translational exploration.
+ RNA : Base class for RNA components.
+
+ Notes
+ -----
+ RNA_constructs have several key characteristics:
+
+ - Linear only: RNA molecules cannot be circular
+ - Translation focus: Primarily generates protein products through
+ translation mechanisms
+ - RBS enumeration: Automatically identifies all ribosome binding
+ sites and potential translation products
+ - No transcription: RNA cannot be transcribed to produce other RNA
+
+ The default TlExplorer enumerator automatically explores all possible
+ translational units in the RNA construct based on RBS positions.
+
+ Examples
+ --------
+ Create an mRNA with RBS and coding sequence:
+
+ >>> rbs1 = bcp.RBS('RBS1')
+ >>> cds1 = bcp.CDS('GFP')
+ >>> parts = [[rbs1, 'forward'], [cds1, 'forward']]
+ >>> mrna = bcp.RNA_construct(
+ ... parts_list=parts,
+ ... name='mRNA_GFP'
+ ... )
+
+ Create a polycistronic mRNA with multiple RBS-CDS pairs:
+
+ >>> rbs2 = bcp.RBS('RBS2')
+ >>> cds2 = bcp.CDS('RFP')
+ >>> strong_promoter = bcp.Promoter('pstrong')
+ >>> parts = [
+ ... [rbs1, 'forward'], [cds1, 'forward'],
+ ... [rbs2, 'forward'], [cds2, 'forward']
+ ... ]
+ >>> polycistronic = bcp.RNA_construct(
+ ... parts_list=parts,
+ ... name='mRNA_operon',
+ ... promoter=strong_promoter
+ ... )
+
+ """
+
def __init__(
self,
parts_list,
@@ -875,12 +1914,6 @@ def __init__(
length=0,
**kwargs,
):
- """Linear RNA sequence for translation.
-
- An RNA_construct is a lot like a DNA_construct except it can
- only translate, and can only be linear.
-
- """
self.material_type = 'rna'
self.promoter = promoter
self.length = length
@@ -900,19 +1933,116 @@ def __init__(
RNA.__init__(self=self, name=self.name)
def __repr__(self):
- """The name of an RNA should be different from DNA, right?"""
+ # The name of an RNA should be different from DNA, right?
return 'RNA_construct = ' + self.name
-#
# DNA_part: a component-like intermediate class necessary for DNA_construct
# Author: Andrey Shur
# Latest update: 6/4/2020
#
-#
-
-
class DNA_part(Component, OrderedMonomer):
+ """Base class for individual DNA parts in constructs.
+
+ A DNA_part represents a single functional genetic element (promoter, RBS,
+ coding sequence, terminator, etc.) that can be assembled into larger
+ DNA_constructs. Parts have position and direction within constructs and
+ serve as the modular building blocks for synthetic genetic circuits.
+ Unlike full Components, DNA_parts do not have initial concentrations -
+ these must be set on the containing construct or assembly.
+
+ Parameters
+ ----------
+ name : str
+ Name of the DNA part.
+ assembly : DNAassembly or OrderedPolymer, optional
+ The assembly or construct containing this part.
+ direction : str, optional
+ Orientation of the part: 'forward' or 'reverse'.
+ pos : int, optional
+ Position of this part within its parent construct.
+ sequence : str, optional
+ DNA sequence of the part (for future sequence-level modeling).
+ no_stop_codons : list, optional
+ List of reading frames without stop codons. Used for identifying
+ potential coding sequences. Default is empty list.
+ material_type : str, default='part'
+ Material classification for the part.
+ **kwargs
+ Additional keyword arguments passed to Component constructor.
+ Note: 'initial_concentration' is not allowed for DNA_parts.
+
+ Attributes
+ ----------
+ name : str
+ Name of the part.
+ sequence : str or None
+ DNA sequence of the part.
+ material_type : str
+ Material classification ('part').
+ no_stop_codons : list
+ Reading frames without stop codons.
+ assembly : DNAassembly or None
+ Reference to containing assembly.
+ position : int or None
+ Position within parent construct.
+ direction : str
+ Orientation ('forward' or 'reverse').
+
+ See Also
+ --------
+ Promoter : DNA_part for transcriptional control.
+ RBS : DNA_part for translational control.
+ DNA_construct : Container for multiple DNA_parts.
+ OrderedMonomer : Base class for positioned elements.
+
+ Raises
+ ------
+ AttributeError
+ If 'initial_concentration' is provided (not allowed for DNA_parts).
+
+ Notes
+ -----
+ DNA_parts are the fundamental building blocks of genetic constructs:
+
+ - Modular: Can be reused in different constructs
+ - Directional: Support forward and reverse orientations
+ - Positional: Track their location within constructs
+ - No concentration: Cannot have initial concentrations (only
+ constructs/assemblies can)
+
+ The no_stop_codons attribute is used to identify potential open reading
+ frames for translation.
+
+ Examples
+ --------
+ Create a generic DNA part:
+
+ >>> part = bcp.DNA_part(
+ ... name='regulatory_element',
+ ... direction='forward'
+ ... )
+
+ Create a part with sequence information:
+
+ >>> promoter_part = bcp.DNA_part(
+ ... name='pLac',
+ ... sequence='ATGCGATCG...',
+ ... direction='forward'
+ ... )
+
+ Use within a construct:
+
+ >>> gene_part = bcp.DNA_part(
+ ... name='GFP',
+ ... sequence='TGAGTAAAGGAGAAGAA...',
+ ... direction='forward'
+ ... )
+ >>> parts = [[promoter_part, 'forward'], [gene_part, 'forward']]
+ >>> construct = bcp.DNA_construct(parts_list=parts)
+
+ """
+
def __init__(
self,
name,
@@ -924,11 +2054,9 @@ def __init__(
material_type='part',
**kwargs,
):
- """Modular component sequence.
-
- These get compiled into working components
-
- """
+ # Modular component sequence.
+ #
+ # These get compiled into working components
if 'initial_concentration' in kwargs:
raise AttributeError(
"DNA_part should not recieve initial_concentration keyword. "
@@ -957,6 +2085,12 @@ def __init__(
@property
def dna_species(self):
+ """Species: The chemical species representation of this DNA part.
+
+ Returns a Species object with material_type='part' representing this
+ DNA_part as a chemical species in the CRN.
+
+ """
return Species(self.name, material_type='part')
def __repr__(self):
@@ -971,6 +2105,36 @@ def __hash__(self):
return OrderedMonomer.__hash__(self) + hash(self.name)
def __eq__(self, other):
+ """Test equality between two DNA_parts.
+
+ Parts are equal if they have the same type, name, parent assembly/
+ construct, direction, and position.
+
+ Parameters
+ ----------
+ other : DNA_part
+ The other part to compare with.
+
+ Returns
+ -------
+ bool
+ True if parts are equal, False otherwise.
+
+ Notes
+ -----
+ Equality requires matching:
+
+ 1. Type (both must be the same DNA_part subclass)
+ 2. Name (identical names)
+ 3. Assembly/parent (same parent construct or both have None)
+ 4. Direction (both forward or both reverse)
+ 5. Position (same position in parent construct)
+
+ Parts are considered equal even if their parent constructs are
+ different objects, as long as the string representations of the
+ parents match.
+
+ """
if type(other) is type(self):
if self.name == other.name:
if self.assembly is not None and other.assembly is not None:
@@ -995,16 +2159,75 @@ def __eq__(self, other):
return False
def clone(self, position, direction, parent_dna):
- """This defines where the part is in what piece of DNA."""
+ """Attach this part to a specific position in a DNA construct.
+
+ Parameters
+ ----------
+ position : int
+ Position in the parent DNA where this part should be placed.
+ direction : str
+ Orientation of the part: 'forward' or 'reverse'.
+ parent_dna : DNA_construct or OrderedPolymer
+ The DNA construct that will contain this part.
+
+ Returns
+ -------
+ DNA_part
+ Returns self after setting position and parent.
+
+ Notes
+ -----
+ This method establishes the relationship between a part and its
+ containing construct, setting the part's position and orientation.
+
+ """
+ # Define where the part is in what piece of DNA.
# TODO add warning if DNA_part is not cloned
self.insert(parent_dna, position, direction)
return self
def unclone(self):
- """Removes the current part from anything."""
+ """Remove this part from its parent construct.
+
+ Detaches the part from any parent construct or assembly, resetting
+ its position and parent references.
+
+ Returns
+ -------
+ DNA_part
+ Returns self after removal from parent.
+
+ Notes
+ -----
+ This method calls the `remove` method from the `OrderedMonomer` base
+ class to detach the part from its parent polymer structure.
+
+ After calling this method, the part becomes "orphaned" and can be
+ attached to a different construct using `clone`.
+
+ See Also
+ --------
+ clone : Attach the part to a construct at a specific position.
+
+ """
self.remove()
return self
def reverse(self):
+ """Reverse the orientation of this DNA part.
+
+ Flips the direction of the part between 'forward' and 'reverse'.
+
+ Returns
+ -------
+ DNA_part
+ Returns self after reversing direction.
+
+ Notes
+ -----
+ This method is typically called when a containing construct is
+ reversed, ensuring all parts maintain proper relative orientation.
+
+ """
OrderedMonomer.reverse(self)
return self
diff --git a/biocrnpyler/components/dna/misc.py b/biocrnpyler/components/dna/misc.py
index 5367d33c..e53e187f 100644
--- a/biocrnpyler/components/dna/misc.py
+++ b/biocrnpyler/components/dna/misc.py
@@ -15,10 +15,76 @@
class DNABindingSite(DNA_part):
+ """DNA binding site component for protein-DNA interactions.
+
+ A DNABindingSite represents a specific DNA sequence where proteins can
+ bind. This class models protein-DNA binding interactions using the
+ 'binding' mechanism to generate species and reactions during CRN
+ compilation. The binding site can accommodate multiple different binding
+ proteins, each creating separate binding equilibria.
+
+ Parameters
+ ----------
+ name : str
+ Name of the DNA binding site.
+ binders : Species, str, or list
+ Protein species that can bind to this site. Can be a single binder
+ or a list of multiple binders.
+ no_stop_codons : bool, optional
+ If True, indicates the sequence has no stop codons (relevant for
+ coding sequences).
+ assembly : DNAassembly, optional
+ The DNA assembly containing this binding site.
+ **kwargs
+ Additional keyword arguments passed to the parent `DNA_part` class.
+
+ Attributes
+ ----------
+ binders : list of Species
+ List of protein species that can bind to this site.
+ dna_to_bind : Species or None
+ The DNA species that contains this binding site.
+ mechanisms : dict
+ Dictionary containing the binding mechanism (defaults to
+ `One_Step_Cooperative_Binding`).
+
+ See Also
+ --------
+ IntegraseSite : Specialized binding site for integrase proteins.
+ DNA_part : Base class for DNA component parts.
+ One_Step_Cooperative_Binding : Default binding mechanism used.
+
+ Notes
+ -----
+ The DNABindingSite uses the 'binding' mechanism to generate binding
+ reactions for each protein in the `binders` list. Each binder creates
+ an independent binding equilibrium with the DNA.
+
+ The `dna_to_bind` attribute is set during component enumeration and
+ represents the actual DNA species containing this binding site.
+
+ Examples
+ --------
+ Create a binding site for a transcription factor:
+
+ >>> binding_site = bcp.DNABindingSite(
+ ... name='operator_lac',
+ ... binders='protein_LacI'
+ ... )
+
+ Create a binding site with multiple binders:
+
+ >>> binding_site = bcp.DNABindingSite(
+ ... name='enhancer',
+ ... binders=['protein_TF1', 'protein_TF2', 'protein_TF3']
+ ... )
+
+ """
+
def __init__(
- self, name, binders, no_stop_codons=None, assembly=None, **keywords
+ self, name, binders, no_stop_codons=None, assembly=None, **kwargs
):
- """An integrase attachment site binds to integrase."""
+ # An integrase attachment site binds to integrase.
if isinstance(binders, list):
self.binders = [self.set_species(a) for a in binders]
else:
@@ -30,17 +96,48 @@ def __init__(
no_stop_codons=no_stop_codons,
mechanisms=self.mechanisms,
assembly=assembly,
- **keywords,
+ **kwargs,
)
self.name = name
self.dna_to_bind = None
# self.assembly = None
def __repr__(self):
+ """Return string representation of the binding site.
+
+ Returns
+ -------
+ str
+ The name of the binding site.
+
+ """
myname = self.name
return myname
def update_species(self):
+ """Use 'binding' mechanism to generate protein-DNA species.
+
+ Uses the 'binding' mechanism to generate species for each protein
+ binder and their DNA-protein complexes when bound to the DNA
+ containing this binding site.
+
+ Returns
+ -------
+ list of Species
+ List containing all binder proteins and DNA-protein complexes
+ generated by the binding mechanism. Returns only the binders
+ if `dna_to_bind` is None.
+
+ Notes
+ -----
+ This method is called during CRN compilation by
+ `Mixture.compile_crn`. If `dna_to_bind` is not set (None), only
+ the binder species are returned without generating complexes.
+
+ Each binder generates its own set of binding species with unique
+ part_id identifiers based on the binder's name.
+
+ """
spec = []
spec += self.binders
if self.dna_to_bind is not None:
@@ -57,6 +154,25 @@ def update_species(self):
return spec
def update_reactions(self):
+ """Use 'binding' mechanism to generate protein-DNA reactions.
+
+ Uses the 'binding' mechanism to generate binding and unbinding
+ reactions for each protein binder with the DNA containing this binding
+ site.
+
+ Returns
+ -------
+ list of Reaction
+ List of binding/unbinding reactions for all binders. Returns
+ empty list if `dna_to_bind` is None.
+
+ Notes
+ -----
+ This method is called during CRN compilation by
+ `Mixture.compile_crn`. Each binder generates its own binding
+ equilibrium with unique kinetic parameters identified by part_id.
+
+ """
rxns = []
if self.dna_to_bind is not None:
mech_b = self.mechanisms['binding']
@@ -69,8 +185,32 @@ def update_reactions(self):
)
return rxns
- def update_component(self, internal_species=None, **keywords):
- """Copy of component, except with the proper fields updated."""
+ def update_component(self, internal_species=None, **kwargs):
+ """Create a copy of the binding site with updated DNA reference.
+
+ Used for component enumeration when binding sites are part of larger
+ DNA constructs that need to be duplicated with different species.
+
+ Parameters
+ ----------
+ internal_species : Species, optional
+ The new DNA species to bind to.
+ **kwargs
+ Additional keyword arguments (currently unused).
+
+ Returns
+ -------
+ DNABindingSite or None
+ A shallow copy of this binding site with updated `dna_to_bind`
+ attribute if parent is DNA. Returns None if parent is not DNA.
+
+ Notes
+ -----
+ This method is called during component enumeration to create copies
+ of binding sites with updated DNA references when DNA constructs
+ are enumerated into their constituent parts.
+
+ """
if isinstance(self.parent, DNA):
out_component = copy.copy(self)
out_component.dna_to_bind = internal_species
@@ -80,6 +220,102 @@ def update_component(self, internal_species=None, **keywords):
class IntegraseSite(DNABindingSite):
+ """Integrase attachment site for site-specific recombination.
+
+ An IntegraseSite represents a specialized DNA binding site where integrase
+ proteins can bind and catalyze site-specific recombination. This
+ component uses the 'binding' mechanism to model integrase-DNA binding and
+ the 'integration' mechanism to generate recombination reactions. The class
+ handles both intramolecular (same DNA molecule) and intermolecular
+ (different DNA molecules) recombination events between compatible
+ attachment sites (attB/attP producing attL/attR, or similar).
+
+ Parameters
+ ----------
+ name : str
+ Name of the integrase site.
+ site_type : str, default='attB'
+ Type of attachment site. Common types include 'attB', 'attP',
+ 'attL', 'attR', 'FLP', 'CRE'. Determines recombination compatibility.
+ integrase : str or Species, default='int1'
+ The integrase protein that recognizes this site. Can be a string
+ name or Species object.
+ dinucleotide : int, default=1
+ Specific dinucleotide variant of the attachment site. Different
+ dinucleotides allow orthogonal recombination systems.
+ no_stop_codons : bool, optional
+ If True, indicates the sequence has no stop codons.
+ integrase_binding : bool, default=True
+ If True, integrase must bind before recombination. If False,
+ recombination occurs without explicit binding (simplified model).
+ **kwargs
+ Additional keyword arguments passed to parent class.
+
+ Attributes
+ ----------
+ integrase : Species
+ The integrase protein species that catalyzes recombination.
+ dinucleotide : int
+ The dinucleotide variant identifier.
+ site_type : str
+ The type of attachment site (attB, attP, etc.).
+ other_dna : Species or None
+ Reference to DNA from another molecule (for intermolecular events).
+ linked_sites : dict
+ Dictionary tracking connected recombination partner sites.
+ complexed_version : Species or None
+ The integrase-bound version of this site.
+ integrase_binding : bool
+ Whether explicit integrase binding is modeled.
+
+ See Also
+ --------
+ DNABindingSite : Parent class for general DNA binding sites.
+ BasicIntegration : Mechanism for integrase-mediated recombination.
+ One_Step_Cooperative_Binding : Mechanism for integrase binding.
+
+ Notes
+ -----
+ Integrase sites follow specific recombination rules:
+
+ - attB + attP --> attL + attR (integration)
+ - attL + attR --> attB + attP (excision)
+ - Compatible sites must have matching integrases and dinucleotides
+
+ The `linked_sites` dictionary maintains connections between compatible
+ recombination partners, enabling the generation of appropriate
+ recombination reactions during CRN compilation.
+
+ Recombination can be intramolecular (creating loops or deletions) or
+ intermolecular (joining or exchanging DNA segments between molecules).
+
+ Examples
+ --------
+ Create a basic attB site for phage integration:
+
+ >>> attB = bcp.IntegraseSite(
+ ... name='attB',
+ ... site_type='attB',
+ ... integrase='int_phiC31'
+ ... )
+
+ Create orthogonal integration sites with different dinucleotides:
+
+ >>> site1 = bcp.IntegraseSite(
+ ... name='att1',
+ ... site_type='attB',
+ ... dinucleotide=1,
+ ... integrase='int1'
+ ... )
+ >>> site2 = bcp.IntegraseSite(
+ ... name='att2',
+ ... site_type='attB',
+ ... dinucleotide=2,
+ ... integrase='int1'
+ ... )
+
+ """
+
def __init__(
self,
name,
@@ -88,7 +324,7 @@ def __init__(
dinucleotide=1,
no_stop_codons=None,
integrase_binding=True,
- **keywords,
+ **kwargs,
):
self.update_integrase(integrase)
# self.integrase = integrase
@@ -103,11 +339,31 @@ def __init__(
name,
self.integrase,
no_stop_codons=no_stop_codons,
- **keywords,
+ **kwargs,
)
self.add_mechanism(BasicIntegration(self.integrase.name))
def __repr__(self):
+ """Return detailed string representation of the integrase site.
+
+ Returns
+ -------
+ str
+ Formatted string including site name, integrase name,
+ dinucleotide (if not 1), position, and direction.
+
+ Warns
+ -----
+ UserWarning
+ If site_type is not in the recognized list of integrase sites.
+
+ Notes
+ -----
+ The representation format is:
+ 'name_integrase[_dinucleotide][_position][_direction]'
+ where optional components are included only if set.
+
+ """
myname = self.name
if self.site_type in integrase_sites:
myname += '_' + self.integrase.name
@@ -125,20 +381,108 @@ def __repr__(self):
return myname
def update_integrase(self, int_name):
+ """Set or update the integrase protein for this site.
+
+ Parameters
+ ----------
+ int_name : str or Species
+ Name of the integrase protein or a Species object.
+
+ Notes
+ -----
+ Converts the input to a protein Species object and stores it in
+ the `integrase` attribute.
+
+ """
self.integrase = Component.set_species(
int_name, material_type='protein'
)
def __hash__(self):
+ """Return hash value for the integrase site.
+
+ Returns
+ -------
+ int
+ Combined hash of the parent DNABindingSite and the dna_to_bind
+ species.
+
+ Notes
+ -----
+ The hash combines the parent class hash with the dna_to_bind hash
+ to ensure unique identification of sites bound to specific DNA.
+
+ """
sumhash = DNABindingSite.__hash__(self) + self.dna_to_bind.__hash__()
return sumhash
def get_complexed_species(self, dna):
+ """Create the integrase-bound complex for this site.
+
+ Parameters
+ ----------
+ dna : Species
+ The DNA species containing this integrase site.
+
+ Returns
+ -------
+ Complex
+ A complex containing the DNA bound by two integrase molecules
+ (dimeric binding typical of integrases).
+
+ Notes
+ -----
+ Most integrases bind as dimers to catalyze recombination, hence
+ the complex contains two integrase molecules.
+
+ """
recomp = Complex([dna, self.integrase, self.integrase])
return recomp
- def update_component(self, internal_species=None, **keywords):
- """Copy of component, except with the proper fields updated."""
+ def update_component(self, internal_species=None, **kwargs):
+ """Create a copy of the integrase site with updated DNA reference.
+
+ This method handles the complex task of copying integrase sites
+ during component enumeration, maintaining proper linkages between
+ recombination partner sites.
+
+ Parameters
+ ----------
+ internal_species : Species, optional
+ The new DNA species containing this integrase site.
+ **kwargs
+ Additional keyword arguments. If 'practice_run' is True, performs
+ special handling to preserve initial site linkage configuration.
+
+ Returns
+ -------
+ IntegraseSite or None
+ A copy of this integrase site with updated `dna_to_bind` and
+ properly managed linked site references. Returns None if the
+ parent DNABindingSite.update_component returns None.
+
+ Notes
+ -----
+ This method manages the complex bookkeeping required for integrase
+ site recombination:
+
+ Practice Run Mode ('practice_run'=True): During combinatorial
+ enumeration's practice run, the method preserves the initial
+ configuration of linked sites by updating references in
+ partner sites to point to the newly created copy.
+
+ Normal Mode ('practice_run'=False or not specified):
+
+ - For intramolecular reactions: Only populates linked sites if both
+ recombination partners are bound by integrase (or both unbound if
+ integrase_binding is False)
+ - For intermolecular reactions: Always populates linked sites to
+ enable proper reaction generation
+
+ The method ensures that only one site of a recombination pair
+ generates the reaction, preventing duplicate reactions in the CRN.
+
+ """
newcomp = DNABindingSite.update_component(
self, internal_species=internal_species
)
@@ -149,7 +493,7 @@ def update_component(self, internal_species=None, **keywords):
# integrase must be bound in order for integrase sites to
# do anything
return None
- elif 'practice_run' in keywords and keywords['practice_run']:
+ elif 'practice_run' in kwargs and kwargs['practice_run']:
# combinatorial enumeration calls update_component twice. an
# integrase site must inform all the sites it is linked to that it
# has been updated. In certain cases the status of whether a site
@@ -222,12 +566,79 @@ def update_component(self, internal_species=None, **keywords):
return newcomp
def update_species(self):
+ """Generate species associated with binding and integration.
+
+ Generate the list of species associated with the binding site,
+ including integrase-DNA complexes generated by the parent DNA binding
+ site if `integrase_binding` is True.
+
+ Returns
+ -------
+ list of Species
+ If `integrase_binding` is True, returns species from parent
+ DNABindingSite including integrase-DNA complexes. If False,
+ returns only the binder proteins without generating complexes.
+
+ Notes
+ -----
+ When `integrase_binding` is False, the model assumes simplified
+ recombination without explicit binding steps, useful for simplified
+ models where binding kinetics are not important.
+
+ """
if self.integrase_binding:
return DNABindingSite.update_species(self)
else:
return self.binders
def update_reactions(self):
+ """Use 'binding' and 'integration' mechanisms to generate reactions.
+
+ Creates binding reactions (if `integrase_binding` is True) and
+ recombination reactions with linked partner sites. Handles both
+ intramolecular (same DNA) and intermolecular (different DNA)
+ recombination events.
+
+ Returns
+ -------
+ list of Reaction
+ List containing:
+
+ - Integrase binding reactions (if `integrase_binding` is True)
+ - Recombination reactions with each linked partner site
+
+ Returns empty list if no linked sites exist.
+
+ Notes
+ -----
+ For each linked partner site, the method determines whether to
+ generate a recombination reaction based on:
+
+ 1. Intramolecular reactions (same DNA molecule):
+
+ - Only generates reaction if both sites are properly bound (or
+ unbound if integrase_binding is False)
+ - Prevents duplicate reactions by checking if complex_parent is
+ already in the linked sites data
+ - Creates DNA loops, deletions, or inversions
+
+ 2. Intermolecular reactions (different DNA molecules):
+
+ - Generates reactions for all DNA molecules listed in
+ linked_sites
+ - These DNAs are added by partner sites that were processed
+ earlier
+ - Creates DNA joining, exchange, or integration events
+
+ The method uses the 'integration' mechanism to generate the actual
+ recombination reactions with appropriate kinetic parameters
+ identified by the integrase name as part_id.
+
+ Each site pair generates only one reaction (not two) to avoid
+ duplicates, with the reaction generation responsibility determined
+ by the update order and population status of linked sites.
+
+ """
if self.integrase_binding:
reactions = DNABindingSite.update_reactions(self)
else:
@@ -325,49 +736,303 @@ def update_reactions(self):
class UserDefined(DNA_part):
- def __init__(self, name, dpl_type=None, **keywords):
- """User-defined part.
+ """User-defined DNA part with no intrinsic functionality.
- A user defined part is a part that doesn't do anything, just exists as
- a label basically.
+ A UserDefined part serves as a placeholder or label in DNA constructs.
+ It represents a DNA sequence that exists in the construct but does not
+ use any mechanisms and therefore does not generate any species or
+ reactions during CRN compilation. This is useful for marking regions of
+ DNA that are important for visualization, documentation, or future
+ extension but do not participate in the modeled biochemical processes.
- """
- DNA_part.__init__(self, name, **keywords)
+ Parameters
+ ----------
+ name : str
+ Name of the user-defined part.
+ dpl_type : str, optional
+ Type identifier for DNA Parts Library compatibility. Can be used
+ to specify the category or function of this part for external
+ tools or databases.
+ **kwargs
+ Additional keyword arguments passed to the parent `DNA_part` class.
+
+ Attributes
+ ----------
+ name : str
+ Name of the part.
+ dpl_type : str or None
+ DNA Parts Library type identifier.
+
+ See Also
+ --------
+ DNA_part : Base class for DNA component parts.
+ Origin : Specialized placeholder for origins of replication.
+ Operator : Specialized placeholder for operator sequences.
+
+ Notes
+ -----
+ UserDefined parts do not use any mechanisms and do not generate any
+ species or reactions during CRN compilation - both `update_species`
+ and `update_reactions` return empty lists.
+
+ This component is particularly useful for:
+
+ - Marking spacer sequences or linkers
+ - Placeholder for parts not yet modeled
+ - Annotation regions for construct visualization
+ - Future extension points in genetic designs
+
+ Examples
+ --------
+ Create a spacer sequence:
+
+ >>> spacer = bcp.UserDefined(
+ ... name='spacer_50bp',
+ ... dpl_type='spacer'
+ ... )
+
+ Create a placeholder for an unmodeled part:
+
+ >>> unknown = bcp.UserDefined(
+ ... name='unknown_region',
+ ... dpl_type='uncharacterized'
+ ... )
+
+ """
+
+ def __init__(self, name, dpl_type=None, **kwargs):
+ # User-defined part.
+ #
+ # A user defined part is a part that doesn't do anything, just exists
+ # as a label basically.
+ DNA_part.__init__(self, name, **kwargs)
self.dpl_type = dpl_type
self.name = name
def update_species(self):
+ """Generate species for the user-defined part.
+
+ Returns
+ -------
+ list
+ Empty list, as user-defined parts have no associated mechanism.
+
+ """
return []
def update_reactions(self):
+ """Generate reactions for the user-defined part.
+
+ Returns
+ -------
+ list
+ Empty list, as user-defined parts have no associated mechanism.
+
+ """
return []
class Origin(DNA_part):
- def __init__(self, name, **keywords):
- """An origin does nothing except look right when plotted."""
- DNA_part.__init__(self, name, **keywords)
+ """Origin of replication component for visualization.
+
+ An Origin represents an origin of replication (ORI) in a DNA construct.
+ Like UserDefined parts, it serves primarily as a visual marker and does
+ not use any mechanisms, therefore it does not generate any species or
+ reactions during CRN compilation. This component is useful for marking
+ replication origins in plasmids or other DNA constructs for documentation
+ and visualization purposes.
+
+ Parameters
+ ----------
+ name : str
+ Name of the origin of replication.
+ **kwargs
+ Additional keyword arguments passed to the parent `DNA_part` class.
+
+ Attributes
+ ----------
+ name : str
+ Name of the origin.
+
+ See Also
+ --------
+ DNA_part : Base class for DNA component parts.
+ UserDefined : General placeholder for non-functional parts.
+ Operator : Placeholder for operator sequences.
+
+ Notes
+ -----
+ Origins do not use any mechanisms and do not generate any species or
+ reactions - both `update_species` and `update_reactions` return
+ empty lists.
+
+ While real origins of replication are essential for plasmid maintenance,
+ their function is typically not modeled in gene expression CRNs, hence
+ this component serves as a placeholder for construct annotation.
+
+ Examples
+ --------
+ Create a standard E. coli origin:
+
+ >>> ori = bcp.Origin(name='pUC_ori')
+
+ Create a low-copy origin:
+
+ >>> p15a_ori = bcp.Origin(name='p15A_ori')
+
+ """
+
+ def __init__(self, name, **kwargs):
+ # An origin does nothing except look right when plotted.
+ DNA_part.__init__(self, name, **kwargs)
self.name = name
def update_species(self):
+ """Generate species for the origin of replication.
+
+ Returns
+ -------
+ list
+ Empty list, as origins have no associated mechanism.
+
+ """
return []
def update_reactions(self):
+ """Generate reactions for the origin of replication.
+
+ Returns
+ -------
+ list
+ Empty list, as origins have no associated mechanism.
+
+ """
return []
class Operator(DNA_part):
- def __init__(self, name, binder=None, **keywords):
- """An operator does nothing except look right when plotted."""
- DNA_part.__init__(self, name, **keywords)
- self.binder = []
- if binder is not None:
- for bind in binder:
- self.binder += [Component.set_species(bind)]
+ """Operator sequence component for visualization.
+
+ An Operator represents an operator DNA sequence (a regulatory element
+ where repressor proteins typically bind) in a genetic construct. Like
+ Origin and UserDefined parts, it primarily serves as a visual marker
+ and does not use any mechanisms, therefore it does not generate species
+ or reactions during CRN compilation. While the component can store
+ references to binding proteins, it does not model the actual binding
+ interactions.
+
+ Parameters
+ ----------
+ name : str
+ Name of the operator sequence.
+ binders : Species, str, list, or None, optional
+ Protein(s) that bind to this operator. Can be a single binder,
+ a list of binders, or None. This is stored for reference but
+ does not generate binding reactions.
+ **kwargs
+ Additional keyword arguments passed to the parent `DNA_part` class.
+
+ Attributes
+ ----------
+ name : str
+ Name of the operator.
+ binders : list of Species
+ List of protein species that can bind this operator (for reference
+ only).
+
+ See Also
+ --------
+ DNA_part : Base class for DNA component parts.
+ DNABindingSite : Functional binding site that generates reactions.
+ RegulatedPromoter : Promoter with functional operator-like behavior.
+
+ Notes
+ -----
+ Operators do not use any mechanisms and do not generate any species or
+ reactions - both `update_species` and `update_reactions` return
+ empty lists.
+
+ For functional operator behavior with actual binding reactions, use
+ `DNABindingSite` or include operators within `RegulatedPromoter`
+ components, which do use binding mechanisms to generate reactions.
+
+ The `binder` attribute stores potential binding proteins for
+ documentation purposes but does not create binding interactions in
+ the model.
+
+ Examples
+ --------
+ Create a lac operator:
+
+ >>> lac_op = bcp.Operator(
+ ... name='lacO',
+ ... binders='protein_LacI'
+ ... )
+
+ Create an operator with multiple potential binders:
+
+ >>> multi_op = bcp.Operator(
+ ... name='operator_1',
+ ... binders=['protein_RepA', 'protein_RepB']
+ ... )
+
+ Create an operator without specifying binders:
+
+ >>> generic_op = bcp.Operator(name='op1')
+
+ """
+
+ def __init__(self, name, binders=None, **kwargs):
+ # Legacy keyword processing
+ if kwargs.get('binder', None):
+ warn("'binder' is deprecated; use 'binders'")
+ if binders is not None:
+ raise TypeError("'binder' and 'binders' specified; pick one")
+ binders = kwargs.pop('binder')
+
+ # An operator does nothing except look right when plotted.
+ DNA_part.__init__(self, name, **kwargs)
+ self.binders = []
+ if binders is not None:
+ if not isinstance(binders, list):
+ binders = [binders]
+ for bind in binders:
+ self.binders += [Component.set_species(bind)]
self.name = name
def update_species(self):
+ """Generate species for the operator.
+
+ Returns
+ -------
+ list
+ Empty list, as operators have no associated mechanism.
+
+ Notes
+ -----
+ For functional operator behavior with species generation, use
+ `DNABindingSite` instead.
+
+ """
return []
def update_reactions(self):
+ """Generate reactions for the operator.
+
+ Returns
+ -------
+ list
+ Empty list, as operators have no associated mechanism.
+
+ Notes
+ -----
+ For functional operator behavior with binding reactions, use
+ `DNABindingSite` or `RegulatedPromoter` instead.
+
+ """
return []
+
+ @property
+ def binder(self):
+ # Legacy attribute
+ return self.binders
diff --git a/biocrnpyler/components/dna/promoter.py b/biocrnpyler/components/dna/promoter.py
index 4dad4b47..5393917e 100644
--- a/biocrnpyler/components/dna/promoter.py
+++ b/biocrnpyler/components/dna/promoter.py
@@ -19,9 +19,81 @@
class Promoter(DNA_part):
- """A basic Promoter class with no regulation.
-
- Needs to be included in a DNAassembly or DNAconstruct to function.
+ """Basic promoter component for constitutive transcription.
+
+ A promoter represents a DNA regulatory element that controls transcription
+ of an RNA transcript. This base class implements constitutive
+ (unregulated) transcription. The component uses the 'transcription'
+ mechanism to generate species and reactions during CRN compilation. The
+ promoter must be included in a `DNAassembly` or `DNA_construct` to
+ function properly.
+
+ Parameters
+ ----------
+ name : str
+ Name of the promoter.
+ assembly : DNAassembly, optional
+ The DNA assembly containing this promoter. If provided, the assembly's
+ name is used to create the default transcript.
+ transcript : RNA, str, or None, optional
+ The RNA transcript produced by this promoter. If None and `assembly`
+ is provided, creates an RNA species using the assembly's name. Can be
+ a list of transcripts for multi-cistronic operons.
+ length : int, default=0
+ Length of the promoter sequence in base pairs.
+ mechanisms : dict or list, optional
+ Custom mechanisms for this promoter, overriding mixture defaults.
+ parameters : dict, optional
+ Parameter values specific to this promoter.
+ protein : Protein, str, list, or None, optional
+ Protein product(s) for expression mixtures where transcription is
+ bypassed. Can be a single protein, list of proteins, or None.
+ dna_to_bind : DNA or Species, optional
+ The DNA species that serves as the transcription template. If None,
+ uses the assembly's DNA when available.
+ **kwargs
+ Additional keyword arguments passed to the parent `DNA_part` class.
+
+ Attributes
+ ----------
+ transcript : Species, list of Species, or None
+ The RNA transcript(s) produced by transcription.
+ protein : list of Species or None
+ Protein product(s) for expression systems.
+ length : int
+ Length of the promoter in base pairs.
+ dna_to_bind : Species or None
+ The DNA species used as transcription template.
+
+ See Also
+ --------
+ RegulatedPromoter : Promoter with independent regulator binding.
+ ActivatablePromoter : Promoter with Hill function activation.
+ RepressiblePromoter : Promoter with Hill function repression.
+ CombinatorialPromoter : Promoter with combinatorial regulation.
+ DNAassembly : Container for promoters and genetic constructs.
+
+ Notes
+ -----
+ Promoters cannot have initial concentrations set directly. Initial
+ conditions must be set on the containing `DNAassembly` or `DNA_construct`.
+
+ Examples
+ --------
+ Create a basic constitutive promoter:
+
+ >>> promoter = bcp.Promoter(
+ ... name='pconst',
+ ... transcript='mRNA_gfp'
+ ... )
+
+ Create a promoter within an assembly:
+
+ >>> assembly = bcp.DNAassembly(name='gene_x')
+ >>> promoter = bcp.Promoter(
+ ... name='p_lac',
+ ... assembly=assembly
+ ... )
"""
@@ -35,7 +107,7 @@ def __init__(
parameters=None,
protein=None,
dna_to_bind=None,
- **keywords,
+ **kwargs,
):
self._dna_bind = dna_to_bind
self.length = length
@@ -64,21 +136,21 @@ def __init__(
# Promoter should not have initial conditions. These need to
# be in DNAAssembly or DNAConstruct
if (
- 'initial_concentration' in keywords.values()
- and keywords['initial_concentration'] is not None
+ 'initial_concentration' in kwargs.values()
+ and kwargs['initial_concentration'] is not None
):
raise AttributeError(
"Cannot set initial_concentration of a Promoter. Must set "
"initial_concentration for the DNAassembly or DNAConstruct"
)
if (
- 'initial_condition_dictionary' in keywords.values()
- and keywords['initial_condition_dictionary'] is not None
+ 'initial_condition_dictionary' in kwargs.values()
+ and kwargs['initial_condition_dictionary'] is not None
):
raise AttributeError(
"Cannot set initial_condition_dictionary of a Promoter. Must "
"set initial_condition_dictionary for the DNAassembly or "
- "DNAconstruct."
+ "DNA_construct."
)
DNA_part.__init__(
@@ -87,10 +159,22 @@ def __init__(
mechanisms=mechanisms,
parameters=parameters,
assembly=assembly,
- **keywords,
+ **kwargs,
)
def update_species(self):
+ """Generate species associated with this promoter.
+
+ Calls the transcription mechanism to generate species for constitutive
+ transcription from the DNA template.
+
+ Returns
+ -------
+ list of Species
+ List of species generated by the transcription mechanism,
+ including RNA polymerase-DNA complexes and transcripts.
+
+ """
mech_tx = self.get_mechanism('transcription')
species = []
@@ -106,6 +190,11 @@ def update_species(self):
@property
def dna_to_bind(self):
+ """Species or None: DNA species used as transcription template.
+
+ If not explicitly set, defaults to the assembly's DNA species.
+
+ """
if self._dna_bind is None:
if self.assembly is None:
return None
@@ -115,12 +204,42 @@ def dna_to_bind(self):
@dna_to_bind.setter
def dna_to_bind(self, value):
+ """Set the DNA species used as transcription template.
+
+ Parameters
+ ----------
+ value : Species or None
+ The DNA species to use for transcription.
+
+ """
self._dna_bind = value
def get_species(self):
+ """Get the primary species associated with this promoter.
+
+ Returns
+ -------
+ None
+ Promoters do not have a primary species; they are part of a DNA
+ assembly.
+
+ """
+ # TODO: OK to return None vs empty list?
return None
def update_reactions(self):
+ """Generate reactions associated with this promoter.
+
+ Calls the 'transcription' mechanism to generate reactions for
+ constitutive transcription from the DNA template.
+
+ Returns
+ -------
+ list of Reaction
+ List of reactions generated by the transcription mechanism,
+ including RNA polymerase binding and transcript production.
+
+ """
mech_tx = self.get_mechanism('transcription')
reactions = []
@@ -134,8 +253,31 @@ def update_reactions(self):
)
return reactions
- def update_component(self, internal_species=None, **keywords):
- """Copy of component, except with the proper fields updated."""
+ def update_component(self, internal_species=None, **kwargs):
+ """Create a copy of the promoter with updated DNA binding target.
+
+ Used for component enumeration when promoters are part of larger
+ constructs that need to be duplicated with different species.
+
+ Parameters
+ ----------
+ internal_species : Species, optional
+ The new DNA species to use as the binding target.
+ **kwargs
+ Additional keyword arguments (currently unused).
+
+ Returns
+ -------
+ Promoter or None
+ A shallow copy of this promoter with the updated `dna_to_bind`
+ attribute. Returns None if parent is RNA.
+
+ Raises
+ ------
+ TypeError
+ If parent is neither DNA nor RNA construct.
+
+ """
if isinstance(self.parent, RNA):
return None
elif isinstance(self.parent, DNA):
@@ -150,20 +292,55 @@ def update_component(self, internal_species=None, **keywords):
# Used for expression mixtures where transcripts are replaced by proteins
def get_protein_for_expression(self):
- if self.transcript is None:
- return self.protein
- else:
- return None
+ """Get protein product for expression mixtures.
+
+ In expression mixtures, transcription may be bypassed and translation
+ may occur directly from DNA. This method returns the protein product
+ when the gene is expressed.
+
+ Returns
+ -------
+ list of Species or None
+ The protein product(s) if transcript is None, otherwise None.
+
+ Notes
+ -----
+ This is used by expression mixtures where the transcript species is
+ omitted and translation occurs directly.
+
+ """
+ return self.protein
@classmethod
def from_promoter(cls, name, assembly, transcript, protein):
- """Initialize a promoter instance from another promoter or str.
+ """Create a promoter instance from another promoter or string.
+
+ Factory method for creating promoters from various input types.
+
+ Parameters
+ ----------
+ name : Promoter or str
+ Either a string name for a new promoter, or an existing Promoter
+ object to copy.
+ assembly : DNAassembly
+ The assembly containing this promoter.
+ transcript : RNA or str
+ The RNA transcript produced by this promoter.
+ protein : Protein, str, or list
+ The protein product(s) for expression mixtures.
+
+ Returns
+ -------
+ Promoter
+ A new Promoter instance. If `name` is a Promoter, returns a
+ deep copy with updated assembly, transcript, and protein
+ attributes.
+
+ Raises
+ ------
+ TypeError
+ If `name` is neither a string nor a Promoter.
- :param name: either string or an other promoter instance
- :param assembly:
- :param transcript:
- :param protein:
- :return: Promoter instance
"""
if isinstance(name, Promoter):
promoter_instance = copy.deepcopy(name)
@@ -186,10 +363,82 @@ def from_promoter(cls, name, assembly, transcript, protein):
class RegulatedPromoter(Promoter):
- """A Promoter class with simple regulation.
+ """Promoter with simple independent regulatory binding.
+
+ A regulated promoter allows multiple regulatory proteins (activators or
+ repressors) to bind independently to the promoter DNA. Each regulator
+ binds independently, and transcription can occur from both bound and
+ unbound states with different rates. The component uses the 'binding'
+ mechanism (`One_Step_Cooperative_Binding` by default) to generate
+ DNA-regulator complexes and the 'transcription' mechanism to generate
+ transcription reactions from each regulatory state.
+
+ Parameters
+ ----------
+ name : str
+ Name of the promoter.
+ regulators : Species, str, or list
+ Regulator species that bind to the promoter. Can be a single
+ regulator or a list. Each regulator binds independently.
+ leak : bool, default=True
+ If True, allows transcription from the unbound promoter state (leak
+ expression). If False, only bound states transcribe.
+ assembly : DNAassembly, optional
+ The assembly containing this promoter.
+ transcript : RNA or str, optional
+ The RNA transcript produced by this promoter.
+ length : int, default=0
+ Length of the promoter in base pairs.
+ mechanisms : dict or list, optional
+ Custom mechanisms for this promoter.
+ parameters : dict, optional
+ Parameter values specific to this promoter.
+ **kwargs
+ Additional keyword arguments passed to parent class.
+
+ Attributes
+ ----------
+ regulators : list of Species
+ List of protein species that regulate this promoter.
+ leak : bool
+ Whether leak transcription (from unbound state) is allowed.
+ complexes : list of Species
+ List of DNA-regulator complexes generated during compilation.
+
+ See Also
+ --------
+ Promoter : Base promoter class.
+ ActivatablePromoter : Hill function-based activation.
+ RepressiblePromoter : Hill function-based repression.
+ CombinatorialPromoter : Combinatorial regulation logic.
+
+ Notes
+ -----
+ Each regulator binds independently, creating multiple DNA-protein
+ complexes. Transcription can occur from each complex with different
+ parameters identified by part_id.
+
+ The leak behavior allows modeling of constitutive expression that occurs
+ even without regulator binding.
+
+ Examples
+ --------
+ Create a promoter with a single regulator:
+
+ >>> promoter = bcp.RegulatedPromoter(
+ ... name='p_reg',
+ ... regulators='protein_TF',
+ ... leak=True
+ ... )
+
+ Create a promoter with multiple independent regulators:
+
+ >>> promoter = bcp.RegulatedPromoter(
+ ... name='p_multi',
+ ... regulators=['protein_TF1', 'protein_TF2'],
+ ... leak=False
+ ... )
- regulators = [list of species]
- Each regulator binds independently to the Promoter to regulate it.
"""
def __init__(
@@ -202,7 +451,7 @@ def __init__(
length=0,
mechanisms=None,
parameters=None,
- **keywords,
+ **kwargs,
):
Promoter.__init__(
self,
@@ -212,7 +461,7 @@ def __init__(
length=length,
mechanisms=mechanisms,
parameters=parameters,
- **keywords,
+ **kwargs,
)
if not isinstance(regulators, list):
@@ -230,6 +479,31 @@ def __init__(
self.complexes = []
def update_species(self):
+ """Generate species for regulated transcription.
+
+ Uses the 'transcription' and 'binding' mechanisms to generate species
+ for regulator-DNA binding and transcription from each regulatory
+ state. Generates DNA-regulator complexes and identifies which
+ complexes can transcribe.
+
+ Returns
+ -------
+ list of Species
+ List containing all DNA-regulator complexes and species generated
+ by the transcription mechanism for each regulatory state.
+
+ Notes
+ -----
+ The method generates:
+
+ - DNA-regulator binding complexes for each regulator
+ - Transcription-related species for unbound DNA (if leak is True)
+ - Transcription-related species for each DNA-regulator complex
+
+ Complexes are stored in `self.complexes` for use in
+ `update_reactions`.
+
+ """
mech_tx = self.get_mechanism('transcription')
mech_b = self.get_mechanism('binding')
species = []
@@ -276,6 +550,27 @@ def update_species(self):
return species
def update_reactions(self):
+ """Generate reactions for regulated transcription.
+
+ Uses the 'binding' mechanism to generate binding reactions for each
+ regulator and the 'transcription' mechanism to generate transcription
+ reactions for each regulatory state (bound and unbound).
+
+ Returns
+ -------
+ list of Reaction
+ List containing all binding reactions for regulators and
+ transcription reactions for each DNA state.
+
+ Notes
+ -----
+ Reactions are generated for:
+
+ - Regulator binding and unbinding to DNA
+ - Transcription from unbound DNA (if leak is True)
+ - Transcription from each DNA-regulator complex
+
+ """
reactions = []
mech_tx = self.get_mechanism('transcription')
mech_b = self.get_mechanism('binding')
@@ -313,13 +608,70 @@ def update_reactions(self):
class ActivatablePromoter(Promoter):
- """Promoter activated by single species, modeled as Hill function."""
+ r"""Promoter with Hill function-based activation.
+
+ An activatable promoter models transcriptional activation by a single
+ regulator species using Hill function kinetics. The component uses a
+ 'transcription' mechanism (`PositiveHillTranscription`) to generate
+ species and reactions where the transcription rate increases with
+ activator concentration following cooperative binding dynamics.
+
+ Parameters
+ ----------
+ name : str
+ Name of the promoter.
+ activator : Species or str
+ The activator protein species that enhances transcription.
+ transcript : RNA or str, optional
+ The RNA transcript produced by this promoter.
+ leak : bool, default=False
+ If True, allows basal transcription without activator. If False, no
+ transcription occurs without activator binding.
+ **kwargs
+ Additional keyword arguments passed to parent `Promoter` class.
+
+ Attributes
+ ----------
+ activator : Species
+ The activator protein that enhances transcription.
+ leak : bool
+ Whether basal (leak) transcription is allowed.
+
+ See Also
+ --------
+ RepressiblePromoter : Hill function-based repression.
+ RegulatedPromoter : Independent regulator binding.
+ PositiveHillTranscription : Mechanism for Hill activation.
+
+ Notes
+ -----
+ The activation follows a Hill function:
+
+ .. math::
+
+ \text{rate} = k_{\text{max}} \frac{[A]^n}{K_d^n + [A]^n}
+ + k_{\text{leak}}
+
+ where [A] is activator concentration, n is the Hill coefficient, and
+ :math:`K_d` is the dissociation constant.
+
+ Examples
+ --------
+ Create an activatable promoter:
+
+ >>> promoter = bcp.ActivatablePromoter(
+ ... name='p_ara',
+ ... activator='protein_AraC',
+ ... leak=True
+ ... )
+
+ """
def __init__(
- self, name, activator, transcript=None, leak=False, **keywords
+ self, name, activator, transcript=None, leak=False, **kwargs
):
- # Always call the superclass __init__() with **keywords passed through
- Promoter.__init__(self, name=name, transcript=transcript, **keywords)
+ # Always call the superclass __init__() with **kwargs passed through
+ Promoter.__init__(self, name=name, transcript=transcript, **kwargs)
# Set the Regulator
# Component.set_species(
@@ -336,7 +688,22 @@ def __init__(
PositiveHillTranscription(), 'transcription', overwrite=True
)
- def update_species(self, **keywords):
+ def update_species(self, **kwargs):
+ """Use 'transcription' mechanism to generate activation species.
+
+ Parameters
+ ----------
+ **kwargs
+ Additional keyword arguments passed to the transcription
+ mechanism.
+
+ Returns
+ -------
+ list of Species
+ List of species generated by the transcription mechanism for
+ Hill-based activation.
+
+ """
# Mechanisms are stored in an automatically created
# dictionary: mechanism_type --> Mechanism Instance.
mech_tx = self.get_mechanism('transcription')
@@ -354,7 +721,21 @@ def update_species(self, **keywords):
return species
- def update_reactions(self, **keywords):
+ def update_reactions(self, **kwargs):
+ """Use 'transcription' mechanism to generate activation reactions.
+
+ Parameters
+ ----------
+ **kwargs
+ Additional keyword arguments passed to the transcription
+ mechanism.
+
+ Returns
+ -------
+ list of Reaction
+ List of reactions for Hill-based transcriptional activation.
+
+ """
mech_tx = self.get_mechanism('transcription')
reactions = [] # a list of reactions must be returned
@@ -373,13 +754,70 @@ def update_reactions(self, **keywords):
class RepressiblePromoter(Promoter):
- """Promoter repressed by single species, modeled Hill function."""
+ r"""Promoter with Hill function-based repression.
+
+ A repressible promoter models transcriptional repression by a single
+ regulator species using Hill function kinetics. The component uses a
+ 'transcription' mechanism (`NegativeHillTranscription`) to generate
+ species and reactions where the transcription rate decreases with
+ repressor concentration following cooperative binding dynamics.
+
+ Parameters
+ ----------
+ name : str
+ Name of the promoter.
+ repressor : Species or str
+ The repressor protein species that inhibits transcription.
+ transcript : RNA or str, optional
+ The RNA transcript produced by this promoter.
+ leak : bool, default=False
+ If True, allows residual transcription even at high repressor
+ concentrations. If False, transcription is fully repressed.
+ **kwargs
+ Additional keyword arguments passed to parent `Promoter` class.
+
+ Attributes
+ ----------
+ repressor : Species
+ The repressor protein that inhibits transcription.
+ leak : bool
+ Whether leak transcription at high repressor is allowed.
+
+ See Also
+ --------
+ ActivatablePromoter : Hill function-based activation.
+ RegulatedPromoter : Independent regulator binding.
+ NegativeHillTranscription : Mechanism for Hill repression.
+
+ Notes
+ -----
+ The repression follows a Hill function:
+
+ .. math::
+
+ \text{rate} = k_{\text{max}} \frac{K_d^n}{K_d^n + [R]^n}
+ + k_{\text{leak}}
+
+ where [R] is repressor concentration, n is the Hill coefficient, and
+ :math:`K_d` is the dissociation constant.
+
+ Examples
+ --------
+ Create a repressible promoter:
+
+ >>> promoter = bcp.RepressiblePromoter(
+ ... name='p_lac',
+ ... repressor='protein_LacI',
+ ... leak=False
+ ... )
+
+ """
def __init__(
- self, name, repressor, transcript=None, leak=False, **keywords
+ self, name, repressor, transcript=None, leak=False, **kwargs
):
- # Always call the superclass __init__() with **keywords passed through
- Promoter.__init__(self, name=name, transcript=transcript, **keywords)
+ # Always call the superclass __init__() with **kwargs passed through
+ Promoter.__init__(self, name=name, transcript=transcript, **kwargs)
# Set the Regulator
# Component.set_species(
@@ -396,7 +834,22 @@ def __init__(
NegativeHillTranscription(), 'transcription', overwrite=True
)
- def update_species(self, **keywords):
+ def update_species(self, **kwargs):
+ """Use 'transcription' mechanism to generate repression species.
+
+ Parameters
+ ----------
+ **kwargs
+ Additional keyword arguments passed to the transcription
+ mechanism.
+
+ Returns
+ -------
+ list of Species
+ List of species generated by the 'transcription' mechanism for
+ Hill-based repression.
+
+ """
# Mechanisms are stored in an automatically created
# dictionary: mechanism_type --> Mechanism Instance.
mech_tx = self.get_mechanism('transcription')
@@ -410,12 +863,26 @@ def update_species(self, **keywords):
part_id=self.name + '_' + self.repressor.name,
leak=self.leak,
protein=self.get_protein_for_expression(),
- **keywords,
+ **kwargs,
)
return species
- def update_reactions(self, **keywords):
+ def update_reactions(self, **kwargs):
+ """Use 'transcription' mechanism to generate repression reactions.
+
+ Parameters
+ ----------
+ **kwargs
+ Additional keyword arguments passed to the 'transcription'
+ mechanism.
+
+ Returns
+ -------
+ list of Reaction
+ List of reactions for Hill-based transcriptional repression.
+
+ """
mech_tx = self.get_mechanism('transcription')
reactions = [] # a list of reactions must be returned
@@ -428,12 +895,118 @@ def update_reactions(self, **keywords):
part_id=self.name + '_' + self.repressor.name,
leak=self.leak,
protein=self.get_protein_for_expression(),
- **keywords,
+ **kwargs,
)
return reactions
class CombinatorialPromoter(Promoter):
+ """Promoter with combinatorial regulatory logic.
+
+ A combinatorial promoter allows multiple regulators to bind cooperatively,
+ where transcription behavior depends on the specific combination of bound
+ regulators. The component uses the 'binding' mechanism
+ (`Combinatorial_Cooperative_Binding`) to generate all possible
+ DNA-regulator complexes and the 'transcription' mechanism to generate
+ reactions for each regulatory state. This enables complex logic gates
+ (AND, OR, NOR, etc.) and multi-input regulatory functions.
+
+ Parameters
+ ----------
+ name : str
+ Name of the promoter.
+ regulators : Species, str, or list
+ List of regulator species that can bind to the promoter. Regulators
+ can bind in various combinations.
+ leak : bool, default=False
+ If True, allows transcription from promoter states not in
+ `tx_capable_list` (including unbound state). If False, only states in
+ `tx_capable_list` transcribe.
+ assembly : DNAassembly, optional
+ The assembly containing this promoter.
+ transcript : RNA or str, optional
+ The RNA transcript produced by this promoter.
+ length : int, default=0
+ Length of the promoter in base pairs.
+ mechanisms : dict or list, optional
+ Custom mechanisms for this promoter.
+ parameters : dict, optional
+ Parameter values specific to this promoter.
+ protein : Protein, str, list, or None, optional
+ Protein product(s) for expression mixtures.
+ tx_capable_list : list of list, optional
+ List specifying which combinations of bound regulators enable
+ transcription. Each element is a list of regulator names (strings or
+ Species). If None, all combinations enable transcription.
+ cooperativity : dict, optional
+ Dictionary mapping regulator names to their cooperativity values
+ (Hill coefficients) for binding, e.g., {'regulator1': 2,
+ 'regulator2': 1}.
+ **kwargs
+ Additional keyword arguments passed to parent class.
+
+ Attributes
+ ----------
+ regulators : list of Species
+ Sorted list of protein regulators (sorted for consistency).
+ cooperativity : dict or None
+ Cooperativity values for each regulator.
+ tx_capable_list : list of set
+ List of regulator combinations (as sets) that enable transcription.
+ leak : bool
+ Whether leak transcription is allowed for non-capable states.
+ complex_combinations : dict
+ Dictionary mapping combinations to their DNA-regulator complexes.
+ tx_capable_complexes : list of Species
+ List of DNA complexes that can transcribe.
+ leak_complexes : list of Species
+ List of DNA complexes that transcribe with leak parameters.
+
+ See Also
+ --------
+ RegulatedPromoter : Independent regulatory binding.
+ Combinatorial_Cooperative_Binding : Binding mechanism used by this
+ promoter.
+
+ Notes
+ -----
+ Only combinations in `tx_capable_list` transcribe with full rate;
+ others transcribe with leak rate (if leak is True) or not at all.
+
+ Regulators are automatically sorted alphabetically to ensure consistent
+ ordering when checking combinations.
+
+ Examples
+ --------
+ Create an AND gate promoter (transcribes only when both bound):
+
+ >>> promoter = bcp.CombinatorialPromoter(
+ ... name='p_and',
+ ... regulators=['TF1', 'TF2'],
+ ... tx_capable_list=[['TF1', 'TF2']],
+ ... leak=False
+ ... )
+
+ Create an OR gate promoter (transcribes when either is bound):
+
+ >>> promoter = bcp.CombinatorialPromoter(
+ ... name='p_or',
+ ... regulators=['TF1', 'TF2'],
+ ... tx_capable_list=[['TF1'], ['TF2'], ['TF1', 'TF2']],
+ ... leak=False
+ ... )
+
+ Create a promoter with cooperative binding:
+
+ >>> promoter = bcp.CombinatorialPromoter(
+ ... name='p_coop',
+ ... regulators=['TF1', 'TF2'],
+ ... cooperativity={'TF1': 2, 'TF2': 1},
+ ... tx_capable_list=[['TF1', 'TF2']]
+ ... )
+
+ """
+
def __init__(
self,
name,
@@ -447,46 +1020,8 @@ def __init__(
protein=None,
tx_capable_list=None,
cooperativity=None,
- **keywords,
+ **kwargs,
):
- """Combinatorial promoter.
-
- Binding multiple regulators results in qualitatively different
- transcription behavior. For example, maybe it's an AND gate
- promoter where it only transcribes if two regulators are
- bound, but not if either one is bound.
-
- =============
- inputs
- =============
- name: the name of the promoter
- regulators: a list of strings or species indicating all
- the possible regualtors that can bind
-
- leak: if true, then a promoter with nothing bound will transcribe
-
- assembly: a DNA_assembly object that contains this promoter
-
- transcript: the transcript that this promoter makes
-
- length: the length in nt? I don't think this is used for anything at
- the moment
-
- mechanisms: additional mechanisms. formatted with
- {"mechanism_type":mechanismObject(),...}
-
- parameters: promoter-specific parameters. Formatted as
- {("identifier1","identifier2"):value,...}
-
- tx_capable_list: list of which combination of regulators bound will
- lead to transcription. Formatted as
- [["regulator1","regulator2"],["regulator1"],...] regulators can be
- strings or Species
-
- cooperativity: a dictionary of cooperativity values. For example,
- {"regulator":2,"regulator2":1,....}
-
- """
Promoter.__init__(
self,
name=name,
@@ -496,7 +1031,7 @@ def __init__(
mechanisms=mechanisms,
parameters=parameters,
protein=protein,
- **keywords,
+ **kwargs,
)
if not isinstance(regulators, list):
@@ -548,6 +1083,38 @@ def __init__(
)
def update_species(self):
+ """Generate species for combinatorial regulatory logic.
+
+ Uses the 'transcription' and 'binding' mechanisms to generate all
+ possible DNA-regulator complexes through cooperative binding and
+ identifies which complexes enable transcription based on
+ `tx_capable_list`.
+
+ Returns
+ -------
+ list of Species
+ List containing:
+
+ - The unbound DNA
+ - All regulator species
+ - All DNA-regulator binding complexes
+ - Transcription-related species for capable complexes
+ - Transcription-related species for leak complexes (if leak is
+ True)
+
+ Notes
+ -----
+ The method classifies DNA-regulator complexes into two categories:
+
+ - `tx_capable_complexes`: Complexes that match combinations in
+ `tx_capable_list` and transcribe with full rate parameters
+ - `leak_complexes`: Complexes not in `tx_capable_list` that
+ transcribe with leak parameters (if leak is True)
+
+ Complexes are stored in these attributes for use by
+ `update_reactions`.
+
+ """
mech_tx = self.get_mechanism('transcription')
mech_b = self.get_mechanism('binding')
# set the tx_capable_complexes to nothing because we havent updated
@@ -620,6 +1187,38 @@ def update_species(self):
return species
def update_reactions(self):
+ """Generate reactions for combinatorial regulatory logic.
+
+ Uses the 'transcription' and 'binding' mechanisms to generate binding
+ reactions for all regulator combinations and transcription reactions
+ for capable and leak complexes.
+
+ Returns
+ -------
+ list of Reaction
+ List containing:
+
+ - Cooperative binding reactions for all regulator combinations
+ - Transcription reactions from unbound DNA (if leak is True)
+ - Transcription reactions from capable complexes
+ - Transcription reactions from leak complexes (if leak is True)
+
+ Warns
+ -----
+ UserWarning
+ If no complexes can transcribe after calling `update_species`.
+
+ Notes
+ -----
+ This method automatically calls `update_species` if the complex
+ lists are empty, ensuring that species generation occurs before
+ reaction generation.
+
+ Each transcription reaction uses a unique part_id that identifies the
+ regulatory state, constructed from the promoter name and bound
+ regulators (e.g., 'p_name_TF1_TF2_RNAP').
+
+ """
reactions = []
mech_tx = self.get_mechanism('transcription')
mech_b = self.get_mechanism('binding')
diff --git a/biocrnpyler/components/dna/rbs.py b/biocrnpyler/components/dna/rbs.py
index 90eb5c7c..b507d835 100644
--- a/biocrnpyler/components/dna/rbs.py
+++ b/biocrnpyler/components/dna/rbs.py
@@ -9,9 +9,79 @@
class RBS(DNA_part):
- """A simple RBS class with no regulation.
+ """Ribosome binding site component for translation control.
- Must be included in a DNAconstruct or DNAassembly to do anything.
+ An RBS (ribosome binding site) represents a regulatory element that
+ controls translation of a protein from an RNA transcript. The component
+ uses the 'translation' mechanism to generate species and reactions for
+ ribosome binding and protein production. The RBS must be included in a
+ `DNAassembly` or `DNA_construct` to function properly during CRN
+ compilation.
+
+ Parameters
+ ----------
+ name : str
+ Name of the RBS.
+ assembly : DNAassembly, optional
+ The DNA assembly containing this RBS. If provided, the assembly's
+ name is used to generate default transcript and protein species.
+ transcript : RNA, str, or None, optional
+ The RNA transcript containing this RBS. If None and `assembly` is
+ provided, creates an RNA species using the assembly's name.
+ protein : Protein, str, or None, optional
+ The protein product of translation. If None and `assembly` is
+ provided, creates a Protein species using the assembly's name.
+ length : int, default=0
+ Length of the RBS sequence in base pairs.
+ mechanisms : dict or list, optional
+ Custom mechanisms for this RBS, overriding mixture defaults.
+ parameters : dict, optional
+ Parameter values specific to this RBS.
+ **kwargs
+ Additional keyword arguments passed to the parent `DNA_part` class.
+
+ Attributes
+ ----------
+ transcript : Species or None
+ The RNA transcript containing the RBS.
+ protein : Species or None
+ The protein product of translation.
+ assembly : DNAassembly or None
+ The DNA assembly containing this RBS.
+ length : int
+ Length of the RBS in base pairs.
+
+ See Also
+ --------
+ Promoter : Component for transcription control.
+ DNAassembly : Container for RBS and genetic constructs.
+ DNA_part : Base class for DNA component parts.
+
+ Notes
+ -----
+ The RBS cannot have initial concentrations set directly. Initial
+ conditions must be set on the containing `DNAassembly` or `DNA_construct`.
+
+ The translation mechanism generates reactions for ribosome binding to
+ the transcript and subsequent protein production.
+
+ Examples
+ --------
+ Create a basic RBS:
+
+ >>> rbs = bcp.RBS(
+ ... name='rbs1',
+ ... transcript='mRNA_gfp',
+ ... protein='protein_gfp'
+ ... )
+
+ Create an RBS within an assembly:
+
+ >>> assembly = bcp.DNAassembly(name='gene_x')
+ >>> rbs = bcp.RBS(
+ ... name='rbs_strong',
+ ... assembly=assembly
+ ... )
"""
@@ -24,17 +94,16 @@ def __init__(
length=0,
mechanisms=None,
parameters=None,
- **keywords,
+ **kwargs,
):
- self.assembly = assembly
self.length = length
-
DNA_part.__init__(
self,
name=name,
mechanisms=mechanisms,
parameters=parameters,
- **keywords,
+ assembly=assembly,
+ **kwargs,
)
if transcript is None and assembly is None:
@@ -54,6 +123,18 @@ def __init__(
self.protein = self.set_species(protein, material_type='protein')
def update_species(self):
+ """Use the 'translation' mechanism to generate translation species.
+
+ Uses the 'translation' mechanism to generate species for ribosome
+ binding and protein production from the RNA transcript.
+
+ Returns
+ -------
+ list of Species
+ List of species generated by the translation mechanism,
+ including ribosome-RNA complexes and protein products.
+
+ """
mech_tl = self.get_mechanism('translation')
species = []
species += mech_tl.update_species(
@@ -65,6 +146,18 @@ def update_species(self):
return species
def update_reactions(self):
+ """Use the 'translation' mechanism to generate translation reactions.
+
+ Uses the 'translation' mechanism to generate reactions for ribosome
+ binding to the transcript and protein production.
+
+ Returns
+ -------
+ list of Reaction
+ List of translation reactions including ribosome binding and
+ protein synthesis. Returns empty list if protein is None.
+
+ """
mech_tl = self.get_mechanism('translation')
reactions = []
@@ -77,8 +170,32 @@ def update_reactions(self):
)
return reactions
- def update_component(self, internal_species=None, **keywords):
- """Copy of component, except with the proper fields updated."""
+ def update_component(self, internal_species=None, **kwargs):
+ """Create a copy of the RBS with updated transcript reference.
+
+ Used for component enumeration when RBS is part of larger constructs
+ that need to be duplicated with different species.
+
+ Parameters
+ ----------
+ internal_species : Species, optional
+ The new transcript species to use for this RBS copy.
+ **kwargs
+ Additional keyword arguments (currently unused).
+
+ Returns
+ -------
+ RBS or None
+ A shallow copy of this RBS with the updated `transcript`
+ attribute if parent is RNA and direction is 'forward'. Returns
+ None otherwise.
+
+ Raises
+ ------
+ AttributeError
+ If direction attribute has an unknown value.
+
+ """
if isinstance(self.parent, DNA):
return None
elif isinstance(self.parent, RNA):
@@ -99,13 +216,33 @@ def update_component(self, internal_species=None, **keywords):
@classmethod
def from_rbs(cls, name, assembly, transcript, protein):
- """Initialize an RBS instance from another RBS or str.
+ """Create an RBS instance from another RBS or string.
+
+ Factory method for creating RBS objects from various input types.
+
+ Parameters
+ ----------
+ name : RBS or str
+ Either a string name for a new RBS, or an existing RBS object
+ to copy.
+ assembly : DNAassembly
+ The assembly containing this RBS.
+ transcript : RNA or str
+ The RNA transcript containing the RBS.
+ protein : Protein or str
+ The protein product of translation.
+
+ Returns
+ -------
+ RBS
+ A new RBS instance. If `name` is an RBS, returns a deep copy
+ with updated assembly, transcript, and protein attributes.
+
+ Raises
+ ------
+ TypeError
+ If `name` is neither a string nor an RBS.
- :param name: either string or an other rbs instance
- :param assembly:
- :param transcript:
- :param protein:
- :return: RBS instance
"""
if isinstance(name, RBS):
rbs_instance = copy.deepcopy(name)
diff --git a/biocrnpyler/components/dna/terminator.py b/biocrnpyler/components/dna/terminator.py
index dc35e334..2b4efacb 100644
--- a/biocrnpyler/components/dna/terminator.py
+++ b/biocrnpyler/components/dna/terminator.py
@@ -5,12 +5,96 @@
class Terminator(DNA_part):
- def __init__(self, name, **keywords):
- DNA_part.__init__(self, name, **keywords)
+ """Transcriptional terminator component for ending transcription.
+
+ A Terminator represents a DNA sequence that signals the end of
+ transcription, causing RNA polymerase to dissociate from the DNA
+ template and release the newly synthesized RNA transcript. This
+ component serves as a structural annotation within genetic constructs
+ but does not directly generate species or reactions during CRN
+ compilation.
+
+ Parameters
+ ----------
+ name : str
+ Name of the terminator.
+ **kwargs
+ Additional keyword arguments passed to the parent `DNA_part` class.
+
+ Attributes
+ ----------
+ name : str
+ Name of the terminator.
+
+ See Also
+ --------
+ Promoter : Component that initiates transcription.
+ DNAassembly : Container for terminators and other genetic parts.
+ DNA_part : Base class for DNA component parts.
+
+ Notes
+ -----
+ The Terminator component itself does not generate any species or
+ reactions during CRN compilation. It serves primarily as a structural
+ element to mark the end of transcription units in genetic constructs.
+
+ Termination behavior, if modeled, would typically be implemented through
+ termination efficiency parameters in the transcription mechanism rather
+ than through the terminator component itself.
+
+ Examples
+ --------
+ Create a basic terminator:
+
+ >>> terminator = bcp.Terminator(name='T7_terminator')
+
+ Use a terminator in a DNA construct:
+
+ >>> promoter = bcp.Promoter('ptet')
+ >>> rbs = bcp.RBS('RBS_standard')
+ >>> cds = bcp.CDS('GFP')
+ >>> terminator = bcp.Terminator('BBa_B0022')
+ >>> construct = bcp.DNA_construct(
+ ... [promoter, rbs, cds, terminator],
+ ... name='complete_gene'
+ ... )
+
+ Create a terminator with custom attributes:
+
+ >>> terminator = bcp.Terminator(
+ ... name='BBa_B0015',
+ ... attributes=['double_terminator']
+ ... )
+
+ """
+
+ def __init__(self, name, **kwargs):
+ DNA_part.__init__(self, name, **kwargs)
self.name = name
def update_species(self):
+ """Generate species associated with this terminator.
+
+ Returns
+ -------
+ list of Species
+ Empty list as terminator components have no associated mechanism.
+
+ """
return []
def update_reactions(self):
+ """Generate reactions associated with this terminator.
+
+ Returns
+ -------
+ list of Reaction
+ Empty list as terminator components have no associated mechanism.
+
+ Notes
+ -----
+ Termination behavior is typically modeled through
+ transcription mechanism parameters.
+
+ """
return []
diff --git a/biocrnpyler/components/integrase_enumerator.py b/biocrnpyler/components/integrase_enumerator.py
index 0cb14d3d..2b47c6a8 100644
--- a/biocrnpyler/components/integrase_enumerator.py
+++ b/biocrnpyler/components/integrase_enumerator.py
@@ -12,43 +12,104 @@
from .dna.misc import IntegraseSite
-class Polymer_transformation: # TODO: rename using standard conventions
+class Polymer_transformation:
+ """Template for transforming polymer sequences through recombination.
+
+ A `Polymer_transformation` defines a template for creating new polymers
+ from existing ones through recombination reactions. The template
+ specifies a parts list containing placeholders (monomers from input
+ polymers) and new parts (with no parent). This enables complex DNA
+ rearrangements like integration, deletion, and inversion.
+
+ Parameters
+ ----------
+ partslist : list
+ List of parts defining the output polymer. Can contain:
+
+ - OrderedMonomers from existing polymers (placeholders)
+ - Parts with parent=None (inserted into new polymer)
+ - Tuples of (part, direction)
+
+ circular : bool, default=False
+ Whether the output polymer should be circular.
+ parentsdict : dict, optional
+ Dictionary mapping parent polymers to input names ('input1',
+ 'input2', etc.). If None, automatically generated.
+ material_type : str, default='dna'
+ Material type for the created polymer.
+
+ Attributes
+ ----------
+ number_of_inputs : int
+ Number of distinct input polymers required.
+ parentsdict : dict
+ Mapping from parent polymers to generic input names.
+ partslist : list
+ Template parts list with dummy placeholders.
+ circular : bool
+ Whether output is circular.
+ material_type : str
+ Material type of output polymer.
+
+ See Also
+ --------
+ IntegraseRule : Defines integrase recombination rules.
+ Integrase_Enumerator : Enumerates integrase products.
+ OrderedMonomer : Monomer in ordered polymer.
+
+ Notes
+ -----
+ The transformation works by:
+
+ 1. Analyzing partslist to identify parent polymers
+ 2. Creating generic 'input#' placeholders for each parent
+ 3. Storing template with dummy placeholders
+ 4. When applied via `create_polymer`, replacing placeholders with
+ actual parts from input polymers
+
+ Placeholder system:
+
+ - Parts from polymers become placeholders referencing position in
+ 'input#'
+ - Parts with parent=None are copied directly into output
+ - Complexes bound to parts are transferred to new positions
+
+ Examples
+ --------
+ Create a simple transformation template:
+
+ >>> # Define template: take element 0 from input1, element 2 from
+ >>> # input2 (reversed), and insert a promoter
+ >>> template = Polymer_transformation(
+ ... partslist=[
+ ... polymer1[0], # Placeholder for position 0
+ ... [polymer2[2], 'reverse'], # Position 2, reversed
+ ... promoter # New part (parent=None)
+ ... ],
+ ... circular=False
+ ... )
+ >>> # Apply template to create new polymer
+ >>> new_polymer = template.create_polymer([polymer1, polymer2])
+
+ Integration reaction template:
+
+ >>> # Combine two plasmids at cut sites
+ >>> template = Polymer_transformation(
+ ... partslist=(
+ ... plasmid1[:cut1] +
+ ... [[prod_site1, 'forward']] +
+ ... plasmid2[cut2+1:] +
+ ... [[prod_site2, 'forward']] +
+ ... plasmid1[cut1+1:]
+ ... ),
+ ... circular=True
+ ... )
+
+ """
+
def __init__(
self, partslist, circular=False, parentsdict=None, material_type='dna'
):
- """Initalized a polymer transformation.
-
- A Polymer transformation is like a generic transformation of a polymer
- sequence. You specify a parts list that would make up the output
- polymer. This list can contain:
-
- parts from ordered polymers
- parts that aren't in any polymers (have parent = None)
- parts from ordered polymers are considered as "placeholders"
- parts with parent = None are inserted into the new polymer.
-
- Also you can specify if the output should be circular or not.
-
- Example:
- valid partslist:
- partslist = [
- Monomer(forward,3,"input1"),
- Monomer(reverse,1,"input2"),
- Promoter(forward,None,None)
- ]
-
- then, self.create_polymer([polymer1,polymer2]) takes element 3 from
- polymer1 and puts it forward, element 1 from polymer 2, and a Promoter
- object and creates a new polymer by feeding these three monomers into
- a polymer constructor.
-
- new_polymer.parts_list = [
- polymer1[3].setdir("forward"),
- polymer2[1].setdir("reverse"),
- [Promoter,"forward"]
- ]
-
- """
if parentsdict is None:
# the input to this function is a list of monomers that belong to
# various parents. Each different parent that is represented in
@@ -131,10 +192,22 @@ def __init__(
self.material_type = material_type
def renumber_output(self, output_renumbering_function):
- """Change the ordering of the output list.
+ """Change the ordering of the output parts list.
+
+ Applies a renumbering function to rearrange the parts in the
+ template, useful for handling circular permutations or reversals.
+
+ Parameters
+ ----------
+ output_renumbering_function : callable
+ Function that takes an index (int) and returns tuple of
+ (new_index, direction), where direction is 'f' (forward) or
+ 'r' (reverse).
- Use the output_renumbering_function, which takes in an int and
- returns an int which is the new index of the part.
+ Notes
+ -----
+ Modifies self.partslist in place. Used to adjust transformations
+ when matching against existing constructs.
"""
new_partslist = []
@@ -150,17 +223,40 @@ def renumber_output(self, output_renumbering_function):
self.partslist = new_partslist
def get_renumbered(self, output_renumbering_function):
- """Return copy of transformation with output indexes renumbered."""
+ """Return copy of transformation with output indexes renumbered.
+
+ Parameters
+ ----------
+ output_renumbering_function : callable
+ Function mapping old indexes to (new_index, direction) tuples.
+
+ Returns
+ -------
+ Polymer_transformation
+ Copy of this transformation with renumbered output.
+
+ """
rxn_copied = copy.copy(self)
rxn_copied.renumber_output(output_renumbering_function)
return rxn_copied
def reversed(self):
- """Return a circularly permuted version of self.
+ """Return circularly permuted version with rotated inputs.
+
+ Creates a new transformation where inputs are shuffled: input1
+ becomes input2, input2 becomes input3, ..., last input becomes
+ input1. Used for handling symmetric integrase reactions.
- That means the inputs are shuffled around For example, we had input1,
- input2, input3. Now we will have input1=input2, input2=input3,
- input3=input1.
+ Returns
+ -------
+ Polymer_transformation
+ New transformation with rotated input assignments.
+
+ Notes
+ -----
+ For single-input transformations, returns self unchanged. Essential
+ for bidirectional integrase reactions where site1/site2 roles can
+ be swapped.
"""
new_parentsdict = {}
@@ -192,13 +288,42 @@ def reversed(self):
)
def create_polymer(self, polymer_list, **kwargs):
- """Create a new polymer from the template saved inside this class.
+ """Create a new polymer from the template using input polymers.
+
+ Applies the transformation template to concrete input polymers,
+ replacing placeholders with actual parts and inserting new parts.
+
+ Parameters
+ ----------
+ polymer_list : list of Polymer
+ List of input polymers. Must have at least number_of_inputs
+ polymers. Order matters: first polymer is 'input1', second is
+ 'input2', etc.
+ **kwargs
+ Additional keyword arguments (currently unused).
+
+ Returns
+ -------
+ Polymer
+ New polymer created by applying the transformation. Type
+ matches the input polymers' class.
+
+ Notes
+ -----
+ Transformation process:
- A polymer_list is a list of polymers from which the resulting polymer
- is made. Some of the parts which compose the output polymer don't have
- a parent, and therefore are new parts. In these cases anything bound
- to the previous location of these parts will be bound to the new ones
- as well.
+ 1. Map polymer_list to 'input#' names
+ 2. For each template part:
+
+ - If placeholder: grab part from appropriate input polymer at
+ specified position
+ - If new part: insert the part or its dna_species
+ - Handle complex species bound to parts
+
+ 3. Create output polymer with specified circularity
+
+ When transforming parts with bound complexes, the method attempts
+ to preserve bindings by replacing core parts within complexes.
"""
polymer_dict = {
@@ -293,11 +418,30 @@ def create_polymer(self, polymer_list, **kwargs):
@classmethod
def dummify(cls, in_polymer, name):
- """Create a simplified, disconnected polymer.
-
- The polymer that has the same number of monomers, direction of
- monomers, and name as the input polymer, but is otherwise
- disconnected.
+ """Create a simplified placeholder polymer.
+
+ Generates a generic polymer with the same structure (length,
+ directions, circularity) as the input but without specific parts.
+ Used for creating template placeholders.
+
+ Parameters
+ ----------
+ in_polymer : Polymer
+ The polymer to simplify.
+ name : str
+ Name for the dummy polymer (e.g., 'input1').
+
+ Returns
+ -------
+ NamedPolymer
+ Simplified polymer with generic OrderedMonomers at each
+ position.
+
+ Notes
+ -----
+ The dummy polymer preserves only structural information (number of
+ monomers, their directions, and circularity) while removing
+ specific part identities.
"""
# this is used specifically with polymerTransformation. Dummified
@@ -312,6 +456,15 @@ def dummify(cls, in_polymer, name):
return NamedPolymer(out_list, name, circular=circular)
def __repr__(self):
+ """Return string representation of the transformation.
+
+ Returns
+ -------
+ str
+ Human-readable string showing the transformation template with
+ part names, positions, and directions.
+
+ """
part_texts = []
for plist in self.partslist:
part = plist[0]
@@ -339,6 +492,122 @@ def __repr__(self):
class IntegraseRule:
+ """Rules defining integrase recombination reactions and products.
+
+ An `IntegraseRule` specifies how an integrase enzyme acts on
+ attachment sites to generate recombined DNA products. Defines which
+ site pairs can react, what products they form, and which reaction
+ types (deletion, integration, inversion) are allowed.
+
+ Parameters
+ ----------
+ name : str, optional
+ Name of the integrase (default='int1').
+ reactions : dict, optional
+ Dictionary mapping (site1_type, site2_type) tuples to product
+ site type. Default: {('attB', 'attP'): 'attL',
+ ('attP', 'attB'): 'attR'}
+ allow_deletion : bool, default=True
+ Whether to allow deletion reactions (intramolecular, same
+ direction).
+ allow_integration : bool, default=True
+ Whether to allow integration reactions (intermolecular).
+ allow_inversion : bool, default=True
+ Whether to allow inversion reactions (intramolecular, opposite
+ directions).
+
+ Attributes
+ ----------
+ name : str
+ Integrase name.
+ integrase_species : Species
+ The integrase protein species.
+ reactions : dict
+ Reaction rules mapping site pairs to products.
+ attsites : list
+ All attachment site types involved in reactions.
+ allow_deletion : bool
+ Whether deletions are allowed.
+ allow_integration : bool
+ Whether integrations are allowed.
+ allow_inversion : bool
+ Whether inversions are allowed.
+ integrations_to_do : list
+ List of integrations to perform during compilation.
+
+ See Also
+ --------
+ IntegraseSite : DNA part representing attachment sites.
+ Integrase_Enumerator : Enumerator using integrase rules.
+ Polymer_transformation : Template for DNA transformations.
+
+ Notes
+ -----
+ Integrase mechanism types:
+
+ 1. Serine Integrases:
+
+ - Recombine attB + attP --> attL + attR
+ - Require matching dinucleotides
+ - With directionality factors: attL + attR --> attB + attP
+
+ 2. Tyrosine Recombinases (Cre, Flp):
+
+ - Homotypic sites: loxP + loxP --> loxP + loxP
+ - Can be palindromic (bidirectional)
+
+ 3. Invertases:
+
+ - Only perform inversion reactions
+ - Set allow_deletion=False, allow_integration=False
+
+ 4. Resolvases:
+
+ - Only perform deletion reactions
+ - Set allow_inversion=False, allow_integration=False
+
+ Reaction Types:
+
+ - Inversion: Two sites on same DNA, opposite directions -->
+ region between sites flips
+ - Deletion: Two sites on same DNA, same direction --> region
+ between sites excised (forms circular product)
+ - Integration: Sites on different DNAs --> DNAs join
+ - Recombination: Two linear DNAs --> two recombinant linear DNAs
+
+ Examples
+ --------
+ Define a standard serine integrase:
+
+ >>> int_rule = bcp.IntegraseRule(
+ ... name='PhiC31',
+ ... reactions={
+ ... ('attB', 'attP'): 'attL',
+ ... ('attP', 'attB'): 'attR'
+ ... }
+ ... )
+
+ Define a Cre recombinase (homotypic):
+
+ >>> cre_rule = bcp.IntegraseRule(
+ ... name='Cre',
+ ... reactions={
+ ... ('loxP', 'loxP'): 'loxP',
+ ... ('loxP', 'loxP'): 'loxP' # Symmetric
+ ... }
+ ... )
+
+ Define an invertase (inversion only):
+
+ >>> inv_rule = bcp.IntegraseRule(
+ ... name='Hin',
+ ... reactions={('hixL', 'hixR'): 'hixL', ('hixR', 'hixL'): 'hixR'},
+ ... allow_deletion=False,
+ ... allow_integration=False
+ ... )
+
+ """
+
def __init__(
self,
name=None,
@@ -347,14 +616,6 @@ def __init__(
allow_integration=True,
allow_inversion=True,
):
- """The integrase mechanism is a mechanism at the level of DNA.
-
- It creates DNA species which the integrase manipulations would lead
- to. This mechanism does not create any reaction rates. We need to
- figure out how integrase binding will work before being able to create
- reactions and their corresponding rates
-
- """
if reactions is None:
reactions = {('attB', 'attP'): 'attL', ('attP', 'attB'): 'attR'}
if name is None:
@@ -374,9 +635,38 @@ def __init__(
self.integrations_to_do = []
def binds_to(self):
+ """Get all attachment site types this integrase binds to.
+
+ Returns
+ -------
+ list of str
+ List of all site types involved in integrase reactions.
+
+ """
return self.attsites
def reaction_allowed(self, site1, site2):
+ """Check if two sites can undergo integrase recombination.
+
+ Parameters
+ ----------
+ site1 : IntegraseSite
+ First attachment site.
+ site2 : IntegraseSite
+ Second attachment site.
+
+ Returns
+ -------
+ bool
+ True if sites can react according to reaction rules.
+
+ Raises
+ ------
+ AssertionError
+ If sites have different integrases or do not match this
+ integrase.
+
+ """
assert isinstance(site1, IntegraseSite)
assert isinstance(site2, IntegraseSite)
assert site1.integrase == site2.integrase
@@ -386,7 +676,14 @@ def reaction_allowed(self, site1, site2):
return False
def reactive_sites(self):
- """Attachment sites that participate in integrase reactions."""
+ """Get attachment site types that participate in reactions.
+
+ Returns
+ -------
+ list of str
+ List of site types that can be reactants (not just products).
+
+ """
attsites = []
for reaction in self.reactions:
attsites += list(reaction)
@@ -394,7 +691,41 @@ def reactive_sites(self):
return attsites
def generate_products(self, site1, site2, site2_parent=None):
- """DNA_part objects corresponding to the products of recombination."""
+ """Generate product sites from recombination of two sites.
+
+ Creates IntegraseSite objects for the products of site1 + site2
+ recombination according to the reaction rules.
+
+ Parameters
+ ----------
+ site1 : IntegraseSite
+ First attachment site (determines product ordering).
+ site2 : IntegraseSite
+ Second attachment site.
+ site2_parent : Polymer, optional
+ Parent polymer for site2 (used in intermolecular reactions).
+
+ Returns
+ -------
+ tuple of (IntegraseSite, IntegraseSite)
+ Product sites at positions corresponding to site1 and site2.
+
+ Raises
+ ------
+ AssertionError
+ If sites have mismatched integrases or dinucleotides.
+ KeyError
+ If site pair is not in reaction rules.
+
+ Notes
+ -----
+ Product sites inherit dinucleotides and integrase from reactants.
+ Product order depends on site1 direction:
+
+ - site1 forward: return (prod1, prod2)
+ - site1 reverse: return (prod2, prod1) with swapped directions
+
+ """
# the sites should have the same integrase and dinucleotide, otherwise
# it won't work
assert site1.integrase == site2.integrase
@@ -471,39 +802,93 @@ def integrate(
force_inter=False,
existing_dna_constructs=None,
):
- """Perform an integration reaction between the chosen sites.
-
- Make new DNA_constructs site1 and site2 are integrase site dna_parts
- which have parents that are DNA_constructs.
-
- There are four possible reactions:
- 1) inversion
- two sites are part of the same dna construct
- the result is another dna construct with the same circularity and
- the region in between the sites flipped
- 2) deletion
- two sites are part of the same dna construct
- the result is two dna constructs: one with the same circularity
- but the region between the sites deleted, and another
- circular dna construct that contains the deleted portion
- 3) integration
- the sites are on two different dna constructs
- the result is a single dna construct
- 4) recombination
- the sites are on two different dna constructs
- the results are two different dna constructs with the proper
- portions swapped after the correct dna constructs are generated,
- the reactions which were done to produce them are encoded into
- polymer_transformations and "baked into" the integrase sites
- themselves. So, each integrase site knows which specific integrase
- reactions it should produce when it comes time to update_reactions.
-
- also_inter controls whether intramolecular reactions should also
- generate intermolecular reactions that occur between two copies of the
- same plasmid.
-
- force_inter forces a reaction to be intermolecular even if the two
- sites are on the same plasmid.
+ """Perform integrase recombination between two attachment sites.
+
+ Executes an integration reaction between site1 and site2, creating
+ new DNA constructs based on the reaction type (inversion, deletion,
+ integration, or recombination). Stores transformation templates in
+ the sites' linked_sites attribute.
+
+ Parameters
+ ----------
+ site1 : IntegraseSite
+ First attachment site (must have Construct parent).
+ site2 : IntegraseSite
+ Second attachment site (must have Construct parent).
+ also_inter : bool, default=True
+ If True and reaction is intramolecular, also generate
+ intermolecular version (between two copies of same plasmid).
+ force_inter : bool, default=False
+ Force reaction to be treated as intermolecular even if sites
+ are on same construct.
+ existing_dna_constructs : list of Construct, optional
+ List of previously generated constructs to check for
+ duplicates.
+
+ Returns
+ -------
+ list of Construct
+ List of newly created DNA constructs from the integration.
+
+ Raises
+ ------
+ ValueError
+ If either site is not part of a Construct.
+
+ Notes
+ -----
+ Four reaction types:
+
+ 1. Inversion (intramolecular, opposite directions):
+
+ - Same construct, sites point opposite directions
+ - Result: Region between sites is flipped
+ - Circularity preserved
+
+ 2. Deletion (intramolecular, same direction):
+
+ - Same construct, sites point same direction
+ - Result: Two constructs - one with deleted region, one
+ circular excised fragment
+
+ 3. Integration (intermolecular, one circular):
+
+ - Sites on different constructs, one circular
+ - Result: Single construct (circular if both were circular)
+
+ 4. Recombination (intermolecular, both linear):
+
+ - Sites on two linear constructs
+ - Result: Two recombinant linear constructs
+
+ Polymer_transformation templates are stored in:
+
+ - site1.linked_sites[(site2, intermolecular)]
+ - site2.linked_sites[(site1, intermolecular)]
+
+ These templates are used during CRN compilation to generate
+ reactions and species.
+
+ Existing_dna_constructs are checked for matches (including circular
+ permutations and reversals) to avoid creating duplicates.
+
+ Examples
+ --------
+ Inversion reaction:
+
+ >>> # Two sites on same plasmid, opposite directions
+ >>> int_rule.integrate(attB_site, attP_site_reversed)
+ # Creates inverted plasmid
+
+ Integration reaction:
+
+ >>> # Sites on different plasmids
+ >>> int_rule.integrate(
+ ... plasmid1_attB,
+ ... plasmid2_attP,
+ ... existing_dna_constructs=prev_constructs
+ ... )
+ # Creates integrated plasmid
"""
intermolecular = True # by default, the reaction is intermolecular
@@ -751,6 +1136,97 @@ def integrate(
class Integrase_Enumerator(GlobalComponentEnumerator):
+ """Global enumerator for integrase-mediated DNA recombination products.
+
+ An `Integrase_Enumerator` systematically enumerates all possible DNA
+ constructs that can result from integrase-mediated recombination
+ reactions. Examines all components for integrase attachment sites and
+ generates products for all allowed site pairs.
+
+ Parameters
+ ----------
+ name : str
+ Name identifier for the enumerator.
+ int_mechanisms : dict, optional
+ Dictionary mapping integrase names (str) to IntegraseRule objects.
+ Default: {'int1': IntegraseRule()}
+
+ Attributes
+ ----------
+ int_mechanisms : dict
+ Dictionary of integrase rules.
+
+ See Also
+ --------
+ GlobalComponentEnumerator : Base class for global enumeration.
+ IntegraseRule : Defines integrase recombination rules.
+ IntegraseSite : DNA part for attachment sites.
+ Polymer_transformation : Template for DNA rearrangements.
+
+ Notes
+ -----
+ Enumeration process:
+
+ 1. Identify all integrase attachment sites in components
+ 2. Group sites by integrase type
+ 3. For each integrase:
+
+ a. Find all valid site pairs (from reactive_sites)
+ b. Check if pair can react (reaction_allowed)
+ c. Perform integration to generate products
+ d. Store transformation templates in sites
+
+ 4. Return list of new DNA constructs
+
+ This is a global enumerator because integrase reactions can occur
+ between sites on different constructs (intermolecular reactions).
+ Access to all components is necessary.
+
+ Integrase Types Supported:
+
+ - Serine integrases (attB/attP --> attL/attR)
+ - Tyrosine recombinases (Cre, Flp with homotypic sites)
+ - Invertases (inversion only)
+ - Resolvases (deletion only)
+ - Custom integrase rules
+
+ The `find_dna_construct` method is used to detect duplicates including
+ circular permutations and reversals, preventing redundant construct
+ generation.
+
+ Examples
+ --------
+ Create an integrase enumerator:
+
+ >>> phi_c31 = bcp.IntegraseRule(
+ ... name='PhiC31',
+ ... reactions={
+ ... ('attB', 'attP'): 'attL',
+ ... ('attP', 'attB'): 'attR'
+ ... }
+ ... )
+ >>> enumerator = bcp.Integrase_Enumerator(
+ ... name='integrase_enum',
+ ... int_mechanisms={'PhiC31': phi_c31}
+ ... )
+
+ Use in a mixture:
+
+ >>> mixture = bcp.Mixture(
+ ... components=[plasmid1, plasmid2],
+ ... global_component_enumerators=[enumerator]
+ ... )
+ >>> # Enumerator automatically called during compilation
+ >>> crn = mixture.compile_crn()
+
+ Manual enumeration:
+
+ >>> constructs = [plasmid_with_attB, plasmid_with_attP]
+ >>> new_constructs = enumerator.enumerate_components(constructs)
+ >>> # new_constructs contains integrated plasmids
+
+ """
+
def __init__(self, name: str, int_mechanisms=None):
if int_mechanisms is None:
int_mechanisms = {'int1': IntegraseRule()}
@@ -758,7 +1234,20 @@ def __init__(self, name: str, int_mechanisms=None):
GlobalComponentEnumerator.__init__(self, name=name)
def list_integrase(self, construct):
- """Lists all the parts that can be acted on by integrases."""
+ """List all integrase attachment sites in a construct.
+
+ Parameters
+ ----------
+ construct : Construct
+ DNA construct to examine.
+
+ Returns
+ -------
+ dict
+ Dictionary mapping integrase names (str) to lists of
+ IntegraseSite objects.
+
+ """
int_dict = {}
for part in construct.parts_list:
if isinstance(part, IntegraseSite) and part.integrase is not None:
@@ -772,7 +1261,24 @@ def list_integrase(self, construct):
return int_dict
def reset(self, components=None, **kwargs):
- """This resets the linked_sites member in any attachment sites."""
+ """Reset linked_sites attribute in all attachment sites.
+
+ Clears stored integration reactions from all integrase sites in
+ components, preparing for fresh enumeration.
+
+ Parameters
+ ----------
+ components : list of Component
+ Components containing integrase sites to reset.
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Notes
+ -----
+ Called at the start of enumeration to clear previous integration
+ data.
+
+ """
for component in components:
if hasattr(component, 'parts_list'):
for part in component:
@@ -783,13 +1289,46 @@ def reset(self, components=None, **kwargs):
def find_dna_construct(
cls, construct: Construct, conlist: List[Construct]
):
- """Find a construct that matches the input 'construct'.
+ """Find matching construct in list (handles permutations/reversals).
+
+ Searches for a construct equivalent to the input, accounting for
+ circular permutations and reversals.
+
+ Parameters
+ ----------
+ construct : Construct
+ Construct to find.
+ conlist : list of Construct
+ List of constructs to search.
+
+ Returns
+ -------
+ tuple of (Construct, callable) or None
+ If found: (matched_construct, index_function), where
+ index_function maps old indexes to (new_index, direction).
+ If not found: None.
+
+ Raises
+ ------
+ KeyError
+ If construct matches multiple constructs in list (should not
+ happen with proper generation order).
+
+ Notes
+ -----
+ For circular constructs, the following matching logic is used:
+
+ - Try all circular permutations
+ - For each permutation, try forward and reverse orientations
+
+ For linear constructs, the following matching logic is used:
+
+ - Try forward orientation
+ - Try reverse orientation
- Can be reverse or circularly permuted.
+ Uses `directionless_hash` for fast initial filtering before detailed
+ species comparison.
- returns: found_construct, index_function(index) =>
- (new_index,"f" or "r" if it must be reversed)
- or, None, if no matching construct is found
"""
matched_construct = None
for other_construct in conlist:
@@ -851,23 +1390,71 @@ def find_dna_construct(
def enumerate_components(
self, components=None, previously_enumerated=None, **kwargs
):
- """Explort all the possible integrase-motivated DNA configurations.
-
- If some integrases aren't present, then define intnames to be a list
- of names of the integrases which are present.
-
- An integrase can act in different ways:
- * serine integrases recombine B and P sites that turn into
- L and R sites, and only sites with the same dinucleotide can
- be recombined.
- * serine integrases with directionality factors recombine L and R
- sites with the same dinucleotide
- * Invertases only do flipping reactions
- * resolvases only do deletion reactions
- * FLP or CRE react with homotypic sites, so site1+site1 = site1+site1.
- But there are still different types of sites which are orthogonal.
- For example, a CRE type 1 or a CRE type 2 site. The sites can also
- be palindromic, which means that they can react in either direction.
+ """Enumerate all possible integrase-mediated DNA configurations.
+
+ Systematically generates all DNA constructs that can result from
+ integrase recombination between attachment sites in the input
+ components.
+
+ Parameters
+ ----------
+ components : list of Component, optional
+ List of components to enumerate. Only DNA_construct objects
+ are processed.
+ previously_enumerated : list of Component, optional
+ List of components already enumerated (used for duplicate
+ detection).
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Construct
+ List of newly created DNA constructs from all allowed
+ integrase reactions.
+
+ Notes
+ -----
+ Enumeration algorithm:
+
+ 1. Extract all DNA_construct components
+ 2. List all integrase sites by integrase type
+ 3. For each integrase in int_mechanisms:
+
+ a. Get all attachment sites for that integrase
+ b. Find reactive site types from IntegraseRule
+ c. Generate all site pairs (combinations)
+ d. For each valid pair:
+
+ - Check if reaction_allowed
+ - Perform `integrate` to generate products
+ - Add new constructs to output list
+
+ 4. Return all newly generated constructs
+
+ Depending on the `IntegraseRule` settings, the following reaction
+ types are generated:
+
+ - Inversions (same construct, opposite directions)
+ - Deletions (same construct, same direction)
+ - Integrations (different constructs, at least one circular)
+ - Recombinations (two linear constructs)
+
+ The `integrate` method checks existing_dna_constructs (includes
+ both previously_enumerated and newly created constructs) to avoid
+ generating duplicates.
+
+ Examples
+ --------
+ Enumerate integration products:
+
+ >>> enumerator = bcp.Integrase_Enumerator(
+ ... name='enum',
+ ... int_mechanisms={'PhiC31': phi_c31_rule}
+ ... )
+ >>> plasmids = [donor_plasmid, target_plasmid]
+ >>> products = enumerator.enumerate_components(plasmids)
+ >>> # products contains integrated plasmids
"""
if previously_enumerated is None:
diff --git a/biocrnpyler/components/membrane.py b/biocrnpyler/components/membrane.py
index 8fefc4b5..000d1a00 100644
--- a/biocrnpyler/components/membrane.py
+++ b/biocrnpyler/components/membrane.py
@@ -10,11 +10,73 @@
class DiffusibleMolecule(Component):
- """A class to represent passive diffusion.
-
- This class is to classify a molecule that will diffuse passively
- through the membrane. By default, a DiffusibleMolecule uses a
- mechanism called 'diffusion'.
+ """Molecule that diffuses passively through a membrane.
+
+ A `DiffusibleMolecule` component represents a molecule that undergoes
+ passive diffusion across a membrane between two compartments. The
+ component uses a 'diffusion' mechanism to generate bidirectional
+ diffusion reactions based on concentration gradients.
+
+ Parameters
+ ----------
+ substrate : Species, str, or Component
+ The diffusible molecule species. Can be a `Species` object, string
+ name, or `Component` with an associated species.
+ internal_compartment : str or Compartment, default='Internal'
+ The internal compartment. Can be a string name (creates new
+ Compartment) or an existing `Compartment` object.
+ external_compartment : str or Compartment, default='External'
+ The external compartment. Can be a string name (creates new
+ Compartment) or an existing `Compartment` object.
+ attributes : list of str, optional
+ List of attribute tags to associate with the substrate species.
+ **kwargs
+ Additional keyword arguments passed to the `Component` base class
+ constructor.
+
+ Attributes
+ ----------
+ substrate : Species
+ The substrate species in the internal compartment.
+ product : Species
+ The same substrate species in the external compartment (diffusion
+ product).
+
+ See Also
+ --------
+ MembraneChannel : Active transport through membrane channels.
+ MembranePump : ATP-dependent active transport.
+ Component : Base class for biomolecular components.
+
+ Notes
+ -----
+ Passive diffusion follows concentration gradients and does not require
+ energy. The diffusion mechanism generates bidirectional reactions:
+
+ - Forward: substrate_internal --> substrate_external
+ - Reverse: substrate_external --> substrate_internal
+
+ If not specified using the `name` keyword, the component name is
+ automatically generated as: '_'
+
+ Examples
+ --------
+ Create a simple diffusible molecule:
+
+ >>> glucose = bcp.DiffusibleMolecule(
+ ... substrate='Glucose',
+ ... internal_compartment='Cytoplasm',
+ ... external_compartment='Extracellular'
+ ... )
+
+ Use with a mixture and diffusion mechanism:
+
+ >>> mixture = bcp.Mixture(
+ ... components=[glucose],
+ ... mechanisms={'diffusion': bcp.Simple_Diffusion()},
+ ... parameters={'k_diff': 0.01}
+ ... )
+ >>> crn = mixture.compile_crn()
"""
@@ -24,17 +86,8 @@ def __init__(
internal_compartment: Union[str, Compartment] = 'Internal',
external_compartment: Union[str, Compartment] = 'External',
attributes=None,
- **keywords,
+ **kwargs,
):
- """Initialize a DiffusibleMolecule object.
-
- :param substrate: name of the diffusible substrate, reference to
- an Species or Component
- :param internal_compartment: name of internal compartment
- :param external_compartment: name of external compartment
- :param attributes: Species attribute, passed to Component
- :param keywords: pass into the parent's (Component) initializer
- """
# Creates compartment object if compartment is a str
if isinstance(internal_compartment, str):
internal_compartment = Compartment(name=internal_compartment)
@@ -50,20 +103,53 @@ def __init__(
)
# Name the component
- name = self.substrate.name + '_' + self.substrate.compartment.name
+ if (name := kwargs.pop('name', None)) is None:
+ name = self.substrate.name + '_' + self.substrate.compartment.name
Component.__init__(
- self=self, name=name, attributes=attributes, **keywords
+ self=self, name=name, attributes=attributes, **kwargs
)
def get_species(self):
+ """Get the substrate species in the internal compartment.
+
+ Returns
+ -------
+ Species
+ The substrate species in the internal compartment.
+
+ """
return self.substrate
def update_species(self):
+ """Use 'diffusion' mechanism to generate diffusion species.
+
+ Uses the 'diffusion' mechanism to generate species in both
+ compartments.
+
+ Returns
+ -------
+ list of Species
+ List of species in internal and external compartments generated
+ by the diffusion mechanism.
+
+ """
mech_diff = self.get_mechanism('diffusion')
return mech_diff.update_species(self.substrate, self.product)
def update_reactions(self):
+ """Use 'diffusion' mechanism to generate diffusion reactions.
+
+ Uses the 'diffusion' mechanism to generate reactions for passive
+ diffusion between compartments.
+
+ Returns
+ -------
+ list of Reaction
+ List of diffusion reactions (forward and reverse) between
+ internal and external compartments.
+
+ """
mech_diff = self.get_mechanism('diffusion')
return mech_diff.update_reactions(
self.substrate, self.product, component=self, part_id=self.name
@@ -71,12 +157,86 @@ def update_reactions(self):
class IntegralMembraneProtein(Component):
- """Transmembrane proteins or integral membrane proteins.
-
- This membrane class is to classify a membrane channel that will intergrate
- into the membrane. Uses a mechanism called "membrane_insertion". Size is
- used to indicate number of repeating components to create oligomer. Dimer
- = 2, Trimers = 3, etc.
+ """Transmembrane protein that integrates into the membrane.
+
+ An `IntegralMembraneProtein` component represents a membrane protein
+ that integrates into a membrane compartment. The component uses a
+ 'membrane_insertion' mechanism to generate reactions for protein
+ insertion into the membrane. The size parameter allows modeling of
+ oligomeric channels (dimers, trimers, etc.).
+
+ Parameters
+ ----------
+ membrane_protein : Species, str, or Component
+ The membrane protein species before insertion. Can be a `Species`
+ object, string name, or `Component` with an associated species.
+ product : Species, str, or Component
+ The integrated membrane protein species. Can be a `Species` object,
+ string name, or `Component`.
+ direction : str, optional
+ Transport direction attribute for the integrated protein.
+ Default is 'Passive'. Common values: 'Passive', 'Importer',
+ 'Exporter'.
+ size : int, optional
+ Number of monomers needed to form the functional channel. Used to
+ model oligomeric channels (e.g., size=2 for dimers, size=3 for
+ trimers). Default is 1.
+ compartment : str or Compartment, default='Internal'
+ The compartment containing the membrane protein before insertion.
+ Can be a string name or `Compartment` object.
+ membrane_compartment : str or Compartment, default='Membrane'
+ The membrane compartment where the protein integrates. Can be a
+ string name or `Compartment` object.
+ attributes : list of str, optional
+ List of attribute tags to associate with the membrane protein.
+ **kwargs
+ Additional keyword arguments passed to the `Component` base class
+ constructor.
+
+ Attributes
+ ----------
+ membrane_protein : Species
+ The membrane protein species before insertion.
+ product : Species
+ The integrated transmembrane protein species in the membrane
+ compartment.
+
+ See Also
+ --------
+ MembraneChannel : Membrane channel for substrate transport.
+ Component : Base class for biomolecular components.
+
+ Notes
+ -----
+ The membrane_insertion mechanism generates reactions for protein
+ integration into the membrane. For oligomeric channels, the size
+ parameter determines the stoichiometry:
+
+ - size=1: Monomer insertion
+ - size=2: Dimer formation (2 proteins --> 1 channel)
+ - size=3: Trimer formation (3 proteins --> 1 channel)
+
+ The component name is automatically generated as:
+ '_'
+
+ Examples
+ --------
+ Create a simple membrane protein:
+
+ >>> channel = bcp.IntegralMembraneProtein(
+ ... membrane_protein='ChannelProtein',
+ ... product='ChannelProtein_membrane',
+ ... direction='Passive'
+ ... )
+
+ Create a dimeric channel protein:
+
+ >>> dimer = bcp.IntegralMembraneProtein(
+ ... membrane_protein='Aquaporin',
+ ... product='Aquaporin_channel',
+ ... size=2,
+ ... direction='Passive'
+ ... )
"""
@@ -89,22 +249,8 @@ def __init__(
compartment: Union[str, Compartment] = 'Internal',
membrane_compartment: Union[str, Compartment] = 'Membrane',
attributes=None,
- **keywords,
+ **kwargs,
):
- """Initialize a IntegralMembraneProtein object.
-
- :param product: name of the membrane channel, reference to an
- Species or Component
- :param direction: transport direction (str), set to "Passive" by
- default, undirectional unless specified
- :param size: number of monomers needed for channel used in
- Membrane_Protein_Integration(Mechanism)
- :param internal_compartment: name of internal compartment
- :param membrane_compartment: name of membrane compartment
- :param attributes: Species attribute.
- :param keywords: pass into the parent's (Component) initializer
-
- """
# Creates compartment object if compartment is a str
if isinstance(compartment, str):
compartment = Compartment(name=compartment)
@@ -187,16 +333,48 @@ def __init__(
+ self.membrane_protein.compartment.name
)
- Component.__init__(self=self, name=name, **keywords)
+ Component.__init__(self=self, name=name, **kwargs)
def get_species(self):
+ """Get the membrane protein species before insertion.
+
+ Returns
+ -------
+ Species
+ The membrane protein species in the compartment before
+ integration into the membrane.
+
+ """
return self.membrane_protein
def update_species(self):
+ """Use 'membrane_insertion' to generate membrane insertion species.
+
+ Uses the 'membrane_insertion' mechanism to generate species for
+ the protein before and after insertion.
+
+ Returns
+ -------
+ list of Species
+ List of species generated by the membrane_insertion mechanism,
+ including the protein and integrated product.
+
+ """
mech_ins = self.get_mechanism('membrane_insertion')
return mech_ins.update_species(self.membrane_protein, self.product)
def update_reactions(self):
+ """Use 'membrane_insertion' to generate membrane insertion reactions.
+
+ Uses the 'membrane_insertion' mechanism to generate reactions for
+ protein integration into the membrane.
+
+ Returns
+ -------
+ list of Reaction
+ List of reactions for protein insertion into the membrane.
+
+ """
mech_ins = self.get_mechanism('membrane_insertion')
return mech_ins.update_reactions(
self.membrane_protein,
@@ -207,13 +385,97 @@ def update_reactions(self):
class MembraneChannel(Component):
- """A class to represent membrane channels.
-
- The membrane channel transports substrates across the membrane
- following the concentration gradient. Direction and mechanism will be
- based on the specific transporter.
-
- Uses a mechanism called "transport".
+ """Membrane channel for facilitated transport across membranes.
+
+ A `MembraneChannel` component represents a membrane channel or
+ transporter that facilitates substrate movement across a membrane
+ following concentration gradients. The direction of transport depends
+ on the specific transporter type. The component uses a 'transport'
+ mechanism to generate transport reactions.
+
+ Parameters
+ ----------
+ integral_membrane_protein : Species, str, or Component
+ The integral membrane protein that forms the channel. Can be a
+ `Species` object, string name, or `Component`. If a string,
+ automatically creates a protein species with appropriate direction
+ attribute.
+ substrate : Species, str, or Component
+ The substrate to be transported through the channel. Can be a
+ `Species` object, string name, or `Component`.
+ direction : str, optional
+ Direction of transport. If None, extracted from
+ integral_membrane_protein attributes. Common values: 'Importer'
+ (external --> internal), 'Exporter' (internal --> external),
+ 'Passive' (bidirectional).
+ internal_compartment : str or Compartment, default='Internal'
+ The internal compartment. Can be a string name (creates new
+ Compartment) or an existing `Compartment` object.
+ external_compartment : str or Compartment, default='External'
+ The external compartment. Can be a string name (creates new
+ Compartment) or an existing `Compartment` object.
+ attributes : list of str, optional
+ List of attribute tags to associate with substrate species.
+ **kwargs
+ Additional keyword arguments passed to the `Component` base class
+ constructor.
+
+ Attributes
+ ----------
+ integral_membrane_protein : Species
+ The membrane channel protein species.
+ substrate : Species
+ The substrate species in the source compartment (depends on
+ direction).
+ product : Species
+ The same substrate in the destination compartment.
+
+ See Also
+ --------
+ IntegralMembraneProtein : Protein insertion into membranes.
+ MembranePump : ATP-dependent active transport.
+ DiffusibleMolecule : Passive diffusion without channels.
+ Component : Base class for biomolecular components.
+
+ Notes
+ -----
+ The transport mechanism generates reactions based on the direction:
+
+ - 'Importer': substrate_external + channel
+ --> substrate_internal + channel
+ - 'Exporter': substrate_internal + channel
+ --> substrate_external + channel
+ - 'Passive': bidirectional transport following gradients
+
+ The component name is automatically generated as:
+ '_'
+
+ Examples
+ --------
+ Create a glucose importer:
+
+ >>> importer = bcp.MembraneChannel(
+ ... integral_membrane_protein='GlucoseTransporter',
+ ... substrate='Glucose',
+ ... direction='Importer'
+ ... )
+
+ Create a passive channel:
+
+ >>> channel = bcp.MembraneChannel(
+ ... integral_membrane_protein='WaterChannel',
+ ... substrate='Water',
+ ... direction='Passive'
+ ... )
+
+ Use with a mixture:
+
+ >>> mixture = bcp.Mixture(
+ ... components=[importer],
+ ... mechanisms={'transport': bcp.Facilitated_Transport_MM()},
+ ... parameter_file='mechanisms/transport_parameters.tsv'
+ ... )
+ >>> crn = mixture.compile_crn()
"""
@@ -225,19 +487,8 @@ def __init__(
internal_compartment: Union[str, Compartment] = 'Internal',
external_compartment: Union[str, Compartment] = 'External',
attributes=None,
- **keywords,
+ **kwargs,
):
- """Initialize a MembraneChannel object.
-
- :param substrate: substrate to be transported (str, Species,
- Component)
- :param direction: direction of transport based on transporter action
- :param internal_compartment: name of internal compartment
- :param external_compartment: name of external compartment
- :param attributes: Species attribute
- :param keywords: pass into the parent's (Component) initializer
-
- """
# Creates compartment object if compartment is a str
if isinstance(internal_compartment, str):
internal_compartment = Compartment(name=internal_compartment)
@@ -245,6 +496,7 @@ def __init__(
external_compartment = Compartment(name=external_compartment)
# Set up the integral membrane protein
+ # TODO: allow integral_membrane_protein to be a Component
if isinstance(integral_membrane_protein, str):
integral_membrane_protein = self.set_species(
integral_membrane_protein,
@@ -313,18 +565,48 @@ def __init__(
+ self.integral_membrane_protein.compartment.name
)
- Component.__init__(self=self, name=name, **keywords)
+ Component.__init__(self=self, name=name, **kwargs)
def get_species(self):
+ """Get the integral membrane protein species.
+
+ Returns
+ -------
+ Species
+ The integral membrane protein species that forms the channel.
+
+ """
return self.integral_membrane_protein
def update_species(self):
+ """Use 'transport' mechanism to generate channel-mediated species.
+
+ Uses the 'transport' mechanism to generate species including the
+ channel protein, substrate, and product.
+
+ Returns
+ -------
+ list of Species
+ List of species generated by the transport mechanism.
+
+ """
mech_tra = self.get_mechanism('transport')
return mech_tra.update_species(
self.integral_membrane_protein, self.substrate, self.product
)
def update_reactions(self):
+ """Use 'transport' mechanism to generate channel-mediated reactions.
+
+ Uses the 'transport' mechanism to generate reactions for substrate
+ transport through the channel.
+
+ Returns
+ -------
+ list of Reaction
+ List of transport reactions through the membrane channel.
+
+ """
mech_tra = self.get_mechanism('transport')
return mech_tra.update_reactions(
self.integral_membrane_protein,
@@ -336,11 +618,105 @@ def update_reactions(self):
class MembranePump(Component):
- """A class to represent membrane pumps or transporters that require ATP.
-
- The membrane pump transports substrates unidirectionally across the
- membrane, independent of the concentration gradient. Uses a mechanism
- called 'transport'.
+ """ATP-dependent membrane pump for active transport.
+
+ A `MembranePump` component represents an active transporter or pump
+ that uses ATP to transport substrates across membranes against
+ concentration gradients. The pump operates unidirectionally and requires
+ energy in the form of ATP. The component uses a 'transport' mechanism
+ to generate ATP-dependent transport reactions.
+
+ Parameters
+ ----------
+ membrane_pump : Species, str, or Component
+ The membrane pump protein species. Can be a `Species` object,
+ string name, or `Component`. If a string, automatically creates a
+ protein species with appropriate direction attribute.
+ substrate : Species, str, or Component
+ The substrate to be transported by the pump. Can be a `Species`
+ object, string name, or `Component`.
+ direction : str, optional
+ Direction of active transport. Common values: 'Importer'
+ (external --> internal), 'Exporter' (internal --> external),
+ 'Passive' (default). Affects substrate and ATP compartment
+ placement.
+ internal_compartment : str or Compartment, default='Internal'
+ The internal compartment. Can be a string name (creates new
+ Compartment) or an existing `Compartment` object.
+ external_compartment : str or Compartment, default='External'
+ The external compartment. Can be a string name (creates new
+ Compartment) or an existing `Compartment` object.
+ ATP : int, optional
+ Number of ATP molecules required per transport cycle. Default is 1.
+ attributes : list of str, optional
+ List of attribute tags to associate with substrate species.
+ **kwargs
+ Additional keyword arguments passed to the `Component` base class
+ constructor.
+
+ Attributes
+ ----------
+ membrane_pump : Species
+ The membrane pump protein species.
+ substrate : Species
+ The substrate species in the source compartment.
+ product : Species
+ The same substrate in the destination compartment.
+ energy : Species
+ ATP species used for energy (compartment depends on direction).
+ waste : Species
+ ADP species produced (compartment depends on direction).
+
+ See Also
+ --------
+ MembraneChannel : Facilitated transport without ATP.
+ DiffusibleMolecule : Passive diffusion.
+ Component : Base class for biomolecular components.
+
+ Notes
+ -----
+ Active transport requires ATP hydrolysis and can move substrates
+ against concentration gradients. The typical reaction scheme is:
+
+ - Exporter: substrate_internal + ATP + pump -->
+ substrate_external + ADP + pump
+ - Importer: substrate_external + ATP + pump -->
+ substrate_internal + ADP + pump
+
+ The ATP parameter controls the stoichiometry of ATP consumption per
+ transport event.
+
+ The component name is automatically generated as:
+ '_'
+
+ Examples
+ --------
+ Create a simple ATP-dependent exporter:
+
+ >>> pump = bcp.MembranePump(
+ ... membrane_pump='CalciumPump',
+ ... substrate='Calcium',
+ ... direction='Exporter',
+ ... ATP=2
+ ... )
+
+ Create an ABC transporter (importer):
+
+ >>> abc = bcp.MembranePump(
+ ... membrane_pump='ABC_Transporter',
+ ... substrate='Maltose',
+ ... direction='Importer',
+ ... ATP=1
+ ... )
+
+ Use with a mixture:
+
+ >>> mixture = bcp.Mixture(
+ ... components=[pump],
+ ... mechanisms={'transport': bcp.Primary_Active_Transport_MM()},
+ ... parameter_file='mechanisms/transport_parameters.tsv'
+ ... )
+ >>> crn = mixture.compile_crn()
"""
@@ -353,19 +729,8 @@ def __init__(
external_compartment: Union[str, Compartment] = 'External',
ATP: int = None,
attributes=None,
- **keywords,
+ **kwargs,
):
- """Initialize a MembranePump object.
-
- :param substrate: name of the substrate, reference to a Species
- or Component
- :param direction: give direction of transport ref to vesicle
- :param internal_compartment: name of internal compartment
- :param external_compartment: name of external compartment
- :param ATP: indicates the number of ATP required for transport
- :param attributes: Species attribute
- :param keywords: pass into the parent's (Component) initializer
- """
# Creates compartment object if compartment is a str
if isinstance(internal_compartment, str):
internal_compartment = Compartment(name=internal_compartment)
@@ -498,12 +863,32 @@ def __init__(
+ self.membrane_pump.compartment.name
)
- Component.__init__(self=self, name=name, **keywords)
+ Component.__init__(self=self, name=name, **kwargs)
def get_species(self):
+ """Get the membrane pump protein species.
+
+ Returns
+ -------
+ Species
+ The membrane pump protein species.
+
+ """
return self.membrane_pump
def update_species(self):
+ """Use 'trasnport' mechanism to generate ATP-dependent species.
+
+ Uses the 'transport' mechanism to generate species including the
+ pump protein, substrate, product, ATP, and ADP.
+
+ Returns
+ -------
+ list of Species
+ List of species generated by the transport mechanism,
+ including pump, substrate, product, energy, and waste.
+
+ """
mech_cat = self.get_mechanism('transport')
return mech_cat.update_species(
self.membrane_pump,
@@ -514,6 +899,17 @@ def update_species(self):
)
def update_reactions(self):
+ """Use 'trasnport' mechanism to generate ATP-dependent reactions.
+
+ Uses the 'transport' mechanism to generate reactions for active
+ transport coupled to ATP hydrolysis.
+
+ Returns
+ -------
+ list of Reaction
+ List of ATP-dependent transport reactions.
+
+ """
mech_cat = self.get_mechanism('transport')
return mech_cat.update_reactions(
self.membrane_pump,
@@ -527,11 +923,116 @@ def update_reactions(self):
class MembraneSensor(Component):
- """A class to represent a two-component system (TCS) membrane sensor.
-
- The membrane sensor protein senses the signal substrate and added the
- assigned substrate to the response protein. Uses a mechanism called
- 'membrane_sensor'.
+ """Two-component system (TCS) membrane sensor protein.
+
+ A `MembraneSensor` component represents a membrane sensor protein in a
+ two-component signaling system. The sensor detects external signal
+ substrates and catalyzes the transfer of a chemical group (typically
+ phosphate) to a response protein, activating it. The component uses a
+ 'membrane_sensor' mechanism to generate signal transduction reactions.
+
+ Parameters
+ ----------
+ membrane_sensor_protein : Species, str, or Component
+ The membrane sensor protein (histidine kinase) that detects the
+ signal. Can be a `Species` object, string name, or `Component`.
+ response_protein : Species, str, or Component
+ The cytoplasmic response regulator protein that receives the
+ signal. Can be a `Species` object, string name, or `Component`.
+ assigned_substrate : Species, str, or Component
+ The chemical group to be transferred (typically phosphate). Can be
+ a `Species` object, string name, or `Component`.
+ signal_substrate : Species, str, or Component
+ The external signal molecule that activates the sensor. Can be a
+ `Species` object, string name, or `Component`.
+ product : Species, str, or Component, optional
+ The activated response protein product. If None, automatically
+ named as 'active'.
+ internal_compartment : str or Compartment, default='Internal'
+ The internal compartment containing response protein. Can be a
+ string name (creates new Compartment) or an existing `Compartment`
+ object.
+ external_compartment : str or Compartment, default='External'
+ The external compartment containing signal. Can be a string name
+ (creates new Compartment) or an existing `Compartment` object.
+ ATP : int, default=2
+ Number of ATP molecules required for the signaling process.
+ attributes : list of str, optional
+ List of attribute tags to associate with species.
+ **kwargs
+ Additional keyword arguments passed to the `Component` base class
+ constructor.
+
+ Attributes
+ ----------
+ membrane_sensor_protein : Species
+ The membrane sensor protein species.
+ response_protein : Species
+ The response regulator protein species.
+ assigned_substrate : Species
+ The substrate to be transferred (e.g., phosphate).
+ signal_substrate : Species
+ The external signal molecule species.
+ product : Species
+ The activated response protein species.
+ energy : Species
+ ATP species used for energy.
+ waste : Species
+ ADP species produced.
+
+ See Also
+ --------
+ Component : Base class for biomolecular components.
+
+ Notes
+ -----
+ Two-component systems (TCS) are common bacterial signal transduction
+ pathways. The typical mechanism involves:
+
+ 1. Signal detection by membrane sensor (histidine kinase)
+ 2. Autophosphorylation of sensor using ATP
+ 3. Phosphotransfer to response regulator
+ 4. Activated response regulator regulates gene expression
+
+ The general reaction scheme:
+
+ signal + sensor + ATP + response_protein -->
+ signal + sensor + ADP + response_protein-P
+
+ The component name is automatically generated as:
+ '_'
+
+ Examples
+ --------
+ Create a simple two-component system:
+
+ >>> tcs = bcp.MembraneSensor(
+ ... membrane_sensor_protein='EnvZ',
+ ... response_protein='OmpR',
+ ... assigned_substrate='Phosphate',
+ ... signal_substrate='Osmolarity',
+ ... ATP=2
+ ... )
+
+ Create a chemotaxis receptor:
+
+ >>> chemoreceptor = bcp.MembraneSensor(
+ ... membrane_sensor_protein='CheA',
+ ... response_protein='CheY',
+ ... assigned_substrate='Phosphate',
+ ... signal_substrate='Aspartate',
+ ... product='CheY_P'
+ ... )
+
+ Use with a mixture:
+
+ >>> mixture = bcp.Mixture(
+ ... components=[tcs],
+ ... mechanisms={
+ ... 'membrane_sensor': bcp.Membrane_Signaling_Pathway_MM()},
+ ... parameter_file='mechanisms/transport_parameters.tsv'
+ ... )
+ >>> crn = mixture.compile_crn()
"""
@@ -546,26 +1047,8 @@ def __init__(
external_compartment: Union[str, Compartment] = 'External',
ATP: int = 2,
attributes=None,
- **keywords,
+ **kwargs,
):
- """Initialize a MembraneSensor object.
-
- :param membrane_sensor_protein: name of the membrane protein in
- the TCS, reference to an Species or Component
- :param response_protein: name of the response protein in the TCS,
- reference to an Species or Component
- :param assigned_substrate: name of the assigned substrate in the TCS,
- reference to an Species or Component
- :param signal_substrate: name of the signal substrate in the TCS,
- reference to an Species or Component
- :param product: name of the product in the TCS, reference to an
- Species or Component
- :param internal_compartment: name of internal compartment
- :param external_compartment: name of external compartment
- :param ATP: indicates the number of ATP required for transport
- :param attributes: Species attribute
- :param keywords: pass into the parent's (Component) initializer
- """
# Creates compartment object if compartment is a str
if isinstance(internal_compartment, str):
internal_compartment = Compartment(name=internal_compartment)
@@ -649,12 +1132,32 @@ def __init__(
+ self.membrane_sensor_protein.compartment.name
)
- Component.__init__(self=self, name=name, **keywords)
+ Component.__init__(self=self, name=name, **kwargs)
def get_species(self):
+ """Get the membrane sensor protein species.
+
+ Returns
+ -------
+ Species
+ The membrane sensor protein (histidine kinase) species.
+
+ """
return self.membrane_sensor_protein
def update_species(self):
+ """Use 'membrane_sensor' to generate species signaling species.
+
+ Uses the 'membrane_sensor' mechanism to generate all species
+ involved in the signaling pathway including sensor, response
+ protein, substrates, signal, product, ATP, and ADP.
+
+ Returns
+ -------
+ list of Species
+ List of species generated by the membrane_sensor mechanism.
+
+ """
mech_sen = self.get_mechanism('membrane_sensor')
return mech_sen.update_species(
self.membrane_sensor_protein,
@@ -667,6 +1170,19 @@ def update_species(self):
)
def update_reactions(self):
+ """Use 'membrane_sensor' to generate species signaling reactions.
+
+ Uses the 'membrane_sensor' mechanism to generate reactions for
+ signal detection, ATP-dependent phosphorylation, and
+ phosphotransfer to the response regulator.
+
+ Returns
+ -------
+ list of Reaction
+ List of signal transduction reactions including sensing,
+ autophosphorylation, and phosphotransfer.
+
+ """
mech_sen = self.get_mechanism('membrane_sensor')
return mech_sen.update_reactions(
self.membrane_sensor_protein,
diff --git a/biocrnpyler/core/chemical_reaction_network.py b/biocrnpyler/core/chemical_reaction_network.py
index 47c0c5e6..9e2e9f06 100644
--- a/biocrnpyler/core/chemical_reaction_network.py
+++ b/biocrnpyler/core/chemical_reaction_network.py
@@ -26,20 +26,105 @@
class ChemicalReactionNetwork(object):
- r"""Network of reactions between a set of species.
-
- A chemical reaction network is a container of species and reactions
- chemical reaction networks can be compiled into SBML.
-
- reaction types:
- mass action: standard mass action semantics where the propensity of a
- reaction is given by deterministic propensity =
- .. math::
- k \Prod_{inputs i} [S_i]^a_i
- stochastic propensity =
- .. math::
- k \Prod_{inputs i} (S_i)!/(S_i - a_i)!
- where a_i is the spectrometric coefficient of species i
+ r"""Container for chemical species and their reactions.
+
+ A ChemicalReactionNetwork (CRN) represents a biochemical system as a set
+ of species and reactions between them. CRNs can be compiled to SBML format
+ for simulation with various tools, or simulated directly with bioscrape or
+ roadrunner.
+
+ Parameters
+ ----------
+ species : list of Species
+ List of chemical species in the network.
+ reactions : list of Reaction
+ List of reactions between species. Each reaction specifies inputs,
+ outputs, and rate parameters.
+ initial_concentration_dict : dict, optional
+ Dictionary mapping Species to their initial concentrations. Values can
+ be numbers or `Parameter` objects. If None, an empty dictionary is
+ created.
+ show_warnings : bool, default=False
+ If True, shows warnings about duplicate species/reactions or
+ inconsistencies during CRN validation.
+
+ Attributes
+ ----------
+ species : list of Species
+ Deep copy of the species list (use `add_species` to modify).
+ reactions : list of Reaction
+ Deep copy of the reactions list (use `add_reactions` to modify).
+ initial_concentration_dict : dict
+ Dictionary of initial concentrations for species in the CRN.
+
+ See Also
+ --------
+ Species : Chemical species in a CRN.
+ Reaction : Chemical reaction between species.
+ Mixture : High-level interface for building CRNs from components.
+
+ Notes
+ -----
+ Mass action reactions follow standard mass action kinetics:
+
+ - Deterministic propensity: :math:`k \prod_{i} [S_i]^{a_i}`
+ - Stochastic propensity: :math:`k \prod_{i} \frac{S_i!}{(S_i - a_i)!}`
+
+ where :math:`a_i` is the stoichiometric coefficient of species :math:`i`.
+
+ A valid CRN requires:
+
+ - All species in reactions must be in the species list
+ - All species and reactions must be unique (duplicates trigger warnings)
+ - Initial concentrations must be non-negative
+
+ Once created, species and reactions cannot be removed, only added. This
+ ensures CRN validity is maintained throughout its lifetime.
+
+ Chemical reaction networks can be simulated by writing the output
+ as SMBL using `write_sbml_file` and then loading into an external
+ simulator, or by using the bioscrape package, which can be called
+ directly using `simulate_with_bioscrape_via_sbml`.
+
+ Examples
+ --------
+ Create a simple CRN manually:
+
+ >>> # Define species
+ >>> S = bcp.Species('S')
+ >>> P = bcp.Species('P')
+ >>> E = bcp.Species('protein_E')
+ >>> C = bcp.Species('C')
+ >>> # Define reactions
+ >>> rxn1 = bcp.Reaction.from_massaction(
+ ... [S, E], [C], k_forward=0.1, k_reverse=1e-4)
+ >>> rxn2 = bcp.Reaction.from_massaction([C], [E, P], k_forward=0.01)
+ >>> # Create CRN
+ >>> crn = bcp.ChemicalReactionNetwork(
+ ... species=[S, E, C, P],
+ ... reactions=[rxn1, rxn2]
+ ... )
+
+ Compile a CRN from a mixture:
+
+ >>> enzyme = bcp.Enzyme('E', 'S', 'P')
+ >>> mixture = bcp.Mixture(
+ ... components=[enzyme],
+ ... mechanisms={'catalysis': bcp.MichaelisMenten()},
+ ... parameters={'kb': 0.1, 'ku': 1e-4, 'kcat': 0.01})
+ >>> crn = mixture.compile_crn()
+ >>> print(
+ ... f"CRN has {len(crn.species)} species and "
+ ... f"{len(crn.reactions)} reactions")
+ CRN has 4 species and 2 reactions
+
+ Export to SBML and simulate:
+
+ >>> crn.write_sbml_file('model.xml')
+ >>> result = crn.simulate_with_bioscrape_via_sbml(
+ ... initial_condition_dict={S: 100, 'protein_E': 50, P: 0},
+ ... timepoints=np.linspace(0, 5))
+
"""
def __init__(
@@ -66,25 +151,30 @@ def __init__(
@property
def species(self):
+ """list: List of Species : Deep copy of all species in the CRN."""
return copy.deepcopy(self._species)
@species.setter
def species(self, species):
- """Sets the species of the CRN object.
-
- If the species is not set, it initializes an empty list and adds
- the species to it. If the species is already set, it raises an
- AttributeError. A _species_set is used to ensure that no duplicate
- species are added to the CRN. In earlier BioCRNPyler versions, a
- _species_dict was used. This dictionary had key as species, and
- value as "True", which is unintuitive. Since, sets are designed to
- keep unique elements, so we use a set to keep track of species.
-
- Args:
- species (_type_): _description_
-
- Raises:
- AttributeError: _description_
+ """Set the initial species list for the CRN.
+
+ Parameters
+ ----------
+ species : list of Species
+ Initial species to add to the CRN. Additional species can be added
+ later using `add_species`.
+
+ Raises
+ ------
+ AttributeError
+ If species list is already set. Species cannot be removed once
+ added, only new species can be added with `add_species`.
+
+ Notes
+ -----
+ This setter can only be called once during CRN initialization. A
+ `_species_set` is maintained internally to ensure no duplicate species
+ are added to the CRN.
"""
if not hasattr(self, '_species'):
@@ -99,10 +189,26 @@ def species(self, species):
@property
def reactions(self):
+ """List of Reaction: Deep copy of all reactions in the CRN."""
return copy.deepcopy(self._reactions)
@reactions.setter
def reactions(self, reactions):
+ """Set the initial reactions list for the CRN.
+
+ Parameters
+ ----------
+ reactions : list of Reaction
+ Initial reactions to add to the CRN. Additional reactions can be
+ added later using `add_reactions`.
+
+ Raises
+ ------
+ AttributeError
+ If reactions list is already set. Reactions cannot be removed once
+ added, only new reactions can be added with `add_reactions`.
+
+ """
if not hasattr(self, '_reactions'):
self._reactions = []
self.add_reactions(reactions)
@@ -113,11 +219,30 @@ def reactions(self, reactions):
)
def add_species(self, species, copy_species=True, compartment=None):
- """Adds a Species or a list of Species to the CRN object.
-
- :param species: Species instance or list of Species instances
- :param copy_species: whether to deep copy Species added to the CRN.
- Protects CRN validity at teh expense of speed.
+ """Add species to the CRN.
+
+ Parameters
+ ----------
+ species : Species or list of Species
+ Species object(s) to add to the CRN. Lists are automatically
+ flattened and binding locations are removed.
+ copy_species : bool, default=True
+ If True, deep-copies species before adding them to the CRN.
+ Protects CRN validity at the expense of speed.
+ compartment : Compartment, optional
+ If provided, assigns this compartment to any species with default
+ compartments.
+
+ Raises
+ ------
+ ValueError
+ If any element is not a Species object.
+
+ Notes
+ -----
+ Duplicate species (based on equality) are automatically filtered out.
+ Species are stored in both a list (`_species`) and a set
+ (`_species_set`) for efficient duplicate checking.
"""
if not isinstance(species, list):
@@ -152,15 +277,34 @@ def add_reactions(
add_species=True,
compartment=None,
) -> None:
- """Adds a reaction or a list of reactions to the CRN object.
-
- :param reactions: Reaction instance or list of Reaction instances
- :param copy_reactions: whether to deep copy reactions before adding
- them to the CRN. Protects CRN validity at the
- expense of speed.
- :param add_species: whether to add species in reactions to the CRN.
- Prevents errors at the expense of speed.
- :return: None
+ """Add reactions to the CRN.
+
+ Parameters
+ ----------
+ reactions : Reaction or list of Reaction
+ Reaction object(s) to add to the CRN.
+ copy_reactions : bool, default=True
+ If True, deep-copies reactions before adding them to the CRN.
+ Protects CRN validity at the expense of speed.
+ add_species : bool, default=True
+ If True, automatically adds any species appearing in the reactions
+ to the CRN. Prevents missing species errors at the expense of
+ speed.
+ compartment : Compartment, optional
+ If provided, assigns this compartment to any species with default
+ compartments found in the reactions.
+
+ Raises
+ ------
+ ValueError
+ If any element is not a Reaction object.
+
+ Notes
+ -----
+ Unlike species, reactions are not checked for duplicates when added.
+ It is recommended to keep `copy_reactions=True` to protect the CRN
+ from external modifications.
+
"""
if not isinstance(reactions, list):
reactions = [reactions]
@@ -197,10 +341,27 @@ def add_reactions(
@property
def initial_concentration_dict(self):
+ """dict: Dictionary mapping Species to initial concentrations."""
return self._initial_concentration_dict
@initial_concentration_dict.setter
def initial_concentration_dict(self, initial_concentration_dict):
+ """Set initial concentrations for species in the CRN.
+
+ Parameters
+ ----------
+ initial_concentration_dict : dict or None
+ Dictionary mapping Species objects to their initial
+ concentrations. Values can be numbers or `Parameter` objects. If
+ None, an empty dictionary is created.
+
+ Raises
+ ------
+ ValueError
+ If a species in the dictionary is not in the CRN, or if any
+ concentration is negative.
+
+ """
if initial_concentration_dict is None:
self._initial_concentration_dict = {}
elif isinstance(initial_concentration_dict, dict):
@@ -224,13 +385,38 @@ def initial_concentration_dict(self, initial_concentration_dict):
def check_crn_validity(
reactions: List[Reaction], species: List[Species], show_warnings=True
) -> Tuple[List[Reaction], List[Species]]:
- """Checks that lists of reactions of species can form a valid CRN.
+ """Validate that reactions and species can form a valid CRN.
+
+ Checks for duplicate species/reactions and verifies that all species
+ in reactions are present in the species list.
+
+ Parameters
+ ----------
+ reactions : list of Reaction
+ List of reactions to validate.
+ species : list of Species
+ List of species to validate.
+ show_warnings : bool, default=True
+ If True, issues warnings for duplicates or inconsistencies.
+
+ Returns
+ -------
+ tuple of (list of Reaction, list of Species)
+ The input reactions and species lists, unchanged.
+
+ Raises
+ ------
+ ValueError
+ If any reaction is not a Reaction object, or any species is not a
+ Species object.
+
+ Warns
+ -----
+ UserWarning
+ - Duplicate reactions or species are found
+ - Species exist without reactions
+ - Reactions contain unlisted species
- :param reactions: list of reaction
- :param species: list of species
- :param show_warnings: whether to show warning when duplicated
- reactions/species was found
- :return: tuple(reaction,species)
"""
if not all(isinstance(r, Reaction) for r in reactions):
raise ValueError("A non-reaction object was used as a reaction!")
@@ -298,13 +484,40 @@ def pretty_print(
show_compartment=False,
**kwargs,
):
- """A more powerful printing function.
+ """Generate detailed, human-readable string representation of the CRN.
+
+ Parameters
+ ----------
+ show_rates : bool, default=True
+ If True, displays reaction rate functions and parameters.
+ show_material : bool, default=True
+ If True, displays species material types (e.g., 'dna', 'protein').
+ show_attributes : bool, default=True
+ If True, displays species attributes.
+ show_initial_concentration : bool, default=True
+ If True, displays initial concentrations for each species.
+ show_keys : bool, default=True
+ If True, shows parameter database keys for initial concentrations
+ (useful for debugging parameter lookup).
+ show_compartment : bool, default=False
+ If True, displays compartment information for each species.
+ **kwargs
+ Additional keyword arguments passed to species and reaction
+ `pretty_print` methods.
+
+ Returns
+ -------
+ str
+ Formatted string with species (sorted by initial concentration)
+ and reactions with detailed information.
+
+ Notes
+ -----
+ This method provides much more detailed output than `__repr__`,
+ making it useful for debugging and understanding CRN structure.
+ Species are sorted by initial concentration (highest first) for easier
+ analysis.
- Useful for understanding CRNs but does not return string identifiers.
- `show_material` toggles whether species.material is printed.
- `show_attributes` toggles whether species.attributes is printed
- `show_rates` toggles whether reaction rate functions are printed
- `show_compartment` toggles whether species.compartment is printed
"""
txt = 'Species' + f"(N = {len(self._species)}) = " + '{\n'
@@ -369,6 +582,21 @@ def ics(s):
def initial_condition_vector(
self, init_cond_dict: Union[Dict[str, float], Dict[Species, float]]
):
+ """Generate an initial condition vector for simulations.
+
+ Parameters
+ ----------
+ init_cond_dict : dict
+ Dictionary mapping species (or species names as strings) to their
+ initial concentrations.
+
+ Returns
+ -------
+ list of float
+ Vector of initial concentrations matching the order of species in
+ `self._species`. Species not in `init_cond_dict` are set to 0.0.
+
+ """
x0 = [0.0] * len(self._species)
for idx, s in enumerate(self._species):
if s in init_cond_dict:
@@ -378,7 +606,44 @@ def initial_condition_vector(
def get_all_species_containing(
self, species: Species, return_as_strings=False
):
- """Return all species (complexes) containing given species."""
+ """Find all species (complexes) that contain a given species.
+
+ Searches recursively through all species in the CRN to find those that
+ contain the target species as a component.
+
+ Parameters
+ ----------
+ species : Species
+ The species to search for within other species.
+ return_as_strings : bool, default=False
+ If True, returns species as string representations. If False,
+ returns actual Species objects.
+
+ Returns
+ -------
+ list
+ List of Species objects (or strings if `return_as_strings=True`)
+ that contain the target species.
+
+ Raises
+ ------
+ ValueError
+ If `species` is not a Species object.
+
+ Examples
+ --------
+ >>> substrate = bcp.Species('S')
+ >>> enzyme = bcp.Enzyme('E', substrate, 'P')
+ >>> mixture = bcp.Mixture(
+ ... components=[enzyme],
+ ... mechanisms={'catalysis': bcp.MichaelisMenten()},
+ ... parameters={'kb': 0.1, 'ku': 1e-4, 'kcat': 0.01})
+ >>> crn = mixture.compile_crn()
+ >>> # Find all complexes containing S
+ >>> crn.get_all_species_containing(substrate)
+ [S, complex_S_protein_E_]
+
+ """
return_list = []
if not isinstance(species, Species):
raise ValueError(
@@ -394,9 +659,34 @@ def get_all_species_containing(
return return_list
def replace_species(self, species: Species, new_species: Species):
- """Replaces species with new_species in the entire CRN.
+ """Replace a species with another throughout the CRN.
+
+ Creates a new CRN where all occurrences of a target species are
+ replaced with a new species. The original CRN is not modified.
+
+ Parameters
+ ----------
+ species : Species
+ The species to be replaced.
+ new_species : Species
+ The species to replace with.
+
+ Returns
+ -------
+ ChemicalReactionNetwork
+ New CRN with the species replacement applied.
+
+ Raises
+ ------
+ ValueError
+ If either argument is not a Species object.
+
+ Notes
+ -----
+ This method does not modify the original CRN. It creates and returns
+ a new CRN with the replacement applied throughout all species and
+ reactions.
- Does not act in place: returns a new CRN.
"""
if not isinstance(species, Species):
raise ValueError(
@@ -425,28 +715,54 @@ def generate_sbml_model(
stochastic_model=False,
show_warnings=False,
check_validity=True,
- **keywords,
+ **kwargs,
):
- """Create new SBML model and populate with CRN species and reactions.
+ """Generate an SBML model from the CRN.
+
+ Creates SBML document and model objects containing all species,
+ reactions, compartments, and parameters from the CRN.
+
+ Parameters
+ ----------
+ stochastic_model : bool, default=False
+ If True, generates an SBML model configured for stochastic
+ simulation.
+ show_warnings : bool, default=False
+ If True, shows warnings from CRN validity checking.
+ check_validity : bool, default=True
+ If True, validates the CRN before generating SBML.
+ **kwargs
+ Additional keyword arguments passed to `create_sbml_model` and
+ `add_all_reactions`.
+
+ Returns
+ -------
+ tuple of (libsbml.SBMLDocument, libsbml.Model)
+ The SBML document and model objects. The document can be written
+ to a file or further manipulated.
+
+ Warns
+ -----
+ UserWarning
+ Issues a warning if the generated SBML model contains errors.
+
+ See Also
+ --------
+ write_sbml_file : Write the SBML model directly to a file.
- :param stochastic_model: whether the model is stochastic
- :param show_warnings: of from check crn validity
- :param keywords: extra keywords pass onto create_sbml_model() and
- add_all_reactions()
- :return: tuple: (document,model) SBML objects
"""
if check_validity:
ChemicalReactionNetwork.check_crn_validity(
self._reactions, self._species, show_warnings=show_warnings
)
- document, model = create_sbml_model(**keywords)
+ document, model = create_sbml_model(**kwargs)
all_compartments = []
for species in self._species:
if species.compartment not in all_compartments:
all_compartments.append(species.compartment)
add_all_compartments(
- model=model, compartments=all_compartments, **keywords
+ model=model, compartments=all_compartments, **kwargs
)
add_all_species(
@@ -458,7 +774,7 @@ def generate_sbml_model(
model=model,
reactions=self._reactions,
stochastic_model=stochastic_model,
- **keywords,
+ **kwargs,
)
if document.getNumErrors():
@@ -473,20 +789,44 @@ def write_sbml_file(
file_name=None,
stochastic_model=False,
check_validity=True,
- **keywords,
+ **kwargs,
) -> bool:
- """Writes CRN object to a SBML file.
+ """Write the CRN to an SBML file.
+
+ Generates an SBML model from the CRN and writes it to a file for use
+ with simulators like COPASI, VCell, or bioscrape.
+
+ Parameters
+ ----------
+ file_name : str
+ Path where the SBML file will be written.
+ stochastic_model : bool, default=False
+ If True, exports an SBML file configured for stochastic
+ simulations.
+ check_validity : bool, default=True
+ If True, validates the CRN before generating SBML.
+ **kwargs
+ Additional keyword arguments passed to `generate_sbml_model`.
+
+ Returns
+ -------
+ bool
+ True if the file was written successfully.
+
+ See Also
+ --------
+ generate_sbml_model : Generate SBML objects without writing to file.
+
+ Examples
+ --------
+ >>> crn.write_sbml_file('my_model.xml')
+ >>> crn.write_sbml_file('stochastic_model.xml', stochastic_model=True)
- :param file_name: name of the file where the SBML model gets written
- :param stochastic_model: export an SBML file which ready for
- stochastic simulations
- :param keywords: keywords that passed into generate_sbml_model()
- :return: bool, show whether the writing process was successful
"""
document, _ = self.generate_sbml_model(
stochastic_model=stochastic_model,
check_validity=check_validity,
- **keywords,
+ **kwargs,
)
sbml_string = libsbml.writeSBMLToString(document)
with open(file_name, 'w') as f:
@@ -501,11 +841,34 @@ def simulate_with_bioscrape(
return_dataframe=True,
safe=False,
):
- """Simulate CRN model with bioscrape.
-
- [Bioscrape on GitHub](https://github.com/biocircuits/bioscrape).
+ """Simulate CRN with bioscrape.
+
+ .. deprecated:: 1.0.0
+ This method is deprecated. Use
+ `simulate_with_bioscrape_via_sbml` instead.
+
+ Parameters
+ ----------
+ timepoints : array-like
+ Time points for simulation.
+ initial_condition_dict : dict, optional
+ Dictionary of initial concentrations.
+ stochastic : bool, default=False
+ If True, runs stochastic simulation.
+ return_dataframe : bool, default=True
+ If True, returns results as pandas DataFrame.
+ safe : bool, default=False
+ Safe mode for bioscrape simulation.
+
+ Returns
+ -------
+ DataFrame or array
+ Simulation results.
+
+ See Also
+ --------
+ simulate_with_bioscrape_via_sbml : Recommended simulation method.
- Returns the data for all species as Pandas dataframe.
"""
result = None
warnings.warn(
@@ -537,11 +900,67 @@ def simulate_with_bioscrape_via_sbml(
check_validity=True,
**kwargs,
):
- """Simulate CRN model with bioscrape via temporary SBML file.
+ """Simulate CRN with bioscrape via SBML export.
+
+ Exports the CRN to an SBML file and simulates it using the bioscrape
+ simulator. Bioscrape is a stochastic and deterministic simulator for
+ biological circuits.
+
+ Parameters
+ ----------
+ timepoints : array-like
+ Array of time points at which to record simulation results.
+ filename : str, optional
+ Path to save the SBML file. If None, creates a temporary file
+ 'temp_sbml_file.xml'.
+ initial_condition_dict : dict, optional
+ Dictionary mapping species to initial concentrations. Overrides
+ the CRN's `initial_concentration_dict`.
+ return_dataframe : bool, default=True
+ If True, returns results as a pandas DataFrame. If False, returns
+ a numpy array.
+ stochastic : bool, default=False
+ If True, runs stochastic (Gillespie) simulation. If False, runs
+ deterministic (ODE) simulation.
+ safe : bool, default=False
+ If True, uses bioscrape's safe mode which checks for errors.
+ return_model : bool, default=False
+ If True, returns a tuple of (results, bioscrape_model). If False,
+ returns only results.
+ check_validity : bool, default=True
+ If True, validates the CRN before generating SBML.
+ **kwargs
+ Additional keyword arguments. 'sbml_warnings' can be set to True
+ to show SBML parsing warnings.
+
+ Returns
+ -------
+ DataFrame or array, or tuple
+ Simulation results as DataFrame or array. If `return_model=True`,
+ returns tuple of (results, bioscrape Model object).
+
+ Warns
+ -----
+ UserWarning
+ Issues a warning if bioscrape is not installed.
+
+ Notes
+ -----
+ Requires bioscrape to be installed: `pip install bioscrape`
+
+ Bioscrape GitHub: https://github.com/biocircuits/bioscrape
+
+ Examples
+ --------
+ >>> result = crn.simulate_with_bioscrape_via_sbml(
+ ... timepoints=np.linspace(0, 10, 100)
+ ... )
+ >>> # Stochastic simulation
+ >>> result = crn.simulate_with_bioscrape_via_sbml(
+ ... timepoints=np.linspace(0, 10, 100),
+ ... stochastic=True
+ ... )
- [Bioscrape on GitHub](https://github.com/biocircuits/bioscrape).
-
- Returns the data for all species as Pandas dataframe.
"""
result = None
m = None
@@ -599,19 +1018,55 @@ def simulate_with_roadrunner(
return_roadrunner=False,
check_validity=True,
):
- """To simulate using roadrunner.
-
- Arguments:
- timepoints: The array of time points to run the simulation for.
- initial_condition_dict:
-
- Returns the results array as returned by RoadRunner OR a
- Roadrunner model object.
-
- Refer to the libRoadRunner simulator library documentation for
- details on simulation results: https://libroadrunner.org/.
-
- NOTE : Needs roadrunner package installed to simulate.
+ """Simulate CRN with libRoadRunner.
+
+ Converts the CRN to SBML in memory and simulates it using the
+ libRoadRunner deterministic simulator. libRoadRunner is a fast
+ SBML simulator for deterministic (ODE) simulation.
+
+ Parameters
+ ----------
+ timepoints : list of float
+ Array of time points at which to record simulation results.
+ initial_condition_dict : dict, optional
+ Dictionary mapping species names (strings) to initial
+ concentrations. Overrides the CRN's `initial_concentration_dict`.
+ return_roadrunner : bool, default=False
+ If True, returns the RoadRunner model object instead of simulation
+ results. Useful for advanced control and analysis.
+ check_validity : bool, default=True
+ If True, validates the CRN before generating SBML.
+
+ Returns
+ -------
+ array or RoadRunner
+ If `return_roadrunner=False`, returns simulation results as a
+ numpy array. If `return_roadrunner=True`, returns the RoadRunner
+ model object.
+
+ Warns
+ -----
+ UserWarning
+ Issues a warning if libroadrunner is not installed.
+
+ Notes
+ -----
+ Requires libroadrunner to be installed: `pip install libroadrunner`
+
+ libRoadRunner documentation: https://libroadrunner.org/
+
+ Examples
+ --------
+ >>> result = crn.simulate_with_roadrunner(
+ ... timepoints=np.linspace(0, 10, 100)
+ ... )
+ >>> # Get RoadRunner object for advanced control
+ >>> rr = crn.simulate_with_roadrunner(
+ ... timepoints=np.linspace(0, 10, 100),
+ ... return_roadrunner=True
+ ... )
+ >>> # Run parameter scan with RoadRunner
+ >>> result = rr.simulate(0, 10, 100)
"""
res_ar = None
diff --git a/biocrnpyler/core/compartment.py b/biocrnpyler/core/compartment.py
index de8674d7..39fbf23f 100644
--- a/biocrnpyler/core/compartment.py
+++ b/biocrnpyler/core/compartment.py
@@ -3,20 +3,91 @@
class Compartment:
- """A formal Compartment object for a Species in a CRN.
+ """Spatial compartment for organizing species in a CRN model.
- A Compartment must have a name. They may also have a spatial dimension
- (such as 2 for two-dimensional, or 3 for three-dimensional) and the
- size in litres.
+ Compartments represent physically distinct regions where chemical species
+ can exist, such as the cytoplasm, nucleus, extracellular space, or
+ organelles. Each compartment has a name, size, and spatial dimensionality.
+ Species in different compartments are treated as distinct, even if they
+ have the same molecular identity.
- Note: The "default" keyword is reserved for BioCRNpyler allotting a
- default compartment. Users must choose a different string.
+ Parameters
+ ----------
+ name : str
+ Name of the compartment. Must consist of letters, numbers, or
+ underscores. Cannot contain double underscores, and cannot begin or
+ end with special characters. Must start with a letter. The name
+ 'default' is reserved by BioCRNpyler.
+ size : float or int, default=1e-6
+ Size of the compartment in the units specified by `unit`. Default is
+ 1 microliter (1e-6 liters).
+ spatial_dimensions : int, default=3
+ Number of spatial dimensions (0 for point, 1 for line, 2 for surface,
+ 3 for volume). Must be non-negative.
+ unit : str, optional
+ Unit identifier for the compartment size (e.g., 'L', 'mL', 'µL').
+ Must be a supported unit in BioCRNpyler. See documentation for
+ supported units or add custom units in 'core/units.py'.
- The unit attribute for Compartment can be used to set unit for
- the Compartment size. Make sure that the string identifier used
- for the unit is a supported unit in BioCRNpyler. Check the
- documentation to find a list of supported units. Add your own
- custom units in units.py if needed.
+ Attributes
+ ----------
+ name : str
+ Name of the compartment.
+ size : float
+ Size of the compartment.
+ spatial_dimensions : int
+ Number of spatial dimensions.
+ unit : str or None
+ Unit identifier for the compartment size.
+
+ Raises
+ ------
+ TypeError
+ If `name` is None.
+ ValueError
+ If `name` is not a string, contains invalid characters, or if `size`
+ or `spatial_dimensions` are invalid.
+
+ See Also
+ --------
+ Species : Chemical species that can be assigned to compartments.
+ Mixture : Container that can have a default compartment.
+
+ Notes
+ -----
+ The reserved name 'default' is used internally by BioCRNpyler for species
+ that have not been explicitly assigned to a compartment. User-defined
+ compartments should use other names.
+
+ Two compartments are considered equal if they have the same name. If two
+ compartments have the same name but different sizes or spatial dimensions,
+ a ValueError is raised to prevent inconsistencies.
+
+ Examples
+ --------
+ Create a cytoplasm compartment:
+
+ >>> cytoplasm = bcp.Compartment(
+ ... name="cytoplasm",
+ ... size=1e-15, # 1 femtoliter (bacterial cell volume)
+ ... spatial_dimensions=3,
+ ... unit="L"
+ ... )
+
+ Create a membrane compartment (2D):
+
+ >>> membrane = bcp.Compartment(
+ ... name="membrane",
+ ... size=1e-12, # 1 square micrometer
+ ... spatial_dimensions=2,
+ ... unit="m^2"
+ ... )
+
+ Use compartments with species:
+
+ >>> species_cyto = bcp.Species("Protein_X", compartment=cytoplasm)
+ >>> species_mem = bcp.Species("Protein_X", compartment=membrane)
+ >>> species_cyto == species_mem # False - different compartments
"""
@@ -28,10 +99,28 @@ def __init__(self, name: str, size=1e-6, spatial_dimensions=3, unit=None):
@property
def name(self):
+ """str: Name of the compartment."""
return self._name
@name.setter
def name(self, name: str):
+ """Set the compartment name with validation.
+
+ Parameters
+ ----------
+ name : str
+ Name for the compartment. Must consist of letters, numbers, or
+ underscores. Cannot contain double underscores ('__'), and cannot
+ begin or end with underscores. Must start with a letter.
+
+ Raises
+ ------
+ TypeError
+ If `name` is None.
+ ValueError
+ If `name` is not a string or contains invalid characters.
+
+ """
if name is None:
raise TypeError("Compartment name must be a string.")
elif isinstance(name, str):
@@ -54,10 +143,29 @@ def name(self, name: str):
@property
def spatial_dimensions(self):
+ """int: Number of spatial dimensions.
+
+ 0 for point, 1 for line, 2 for surface, 3 for volume.
+
+ """
return self._spatial_dimensions
@spatial_dimensions.setter
def spatial_dimensions(self, spatial_dimensions: int):
+ """Set the spatial dimensions with validation.
+
+ Parameters
+ ----------
+ spatial_dimensions : int
+ Number of spatial dimensions. Must be a non-negative integer.
+ Common values: 0 (point), 1 (line), 2 (surface), 3 (volume).
+
+ Raises
+ ------
+ ValueError
+ If `spatial_dimensions` is not an integer or is negative.
+
+ """
if not isinstance(spatial_dimensions, int):
raise ValueError(
"Compartment spatial dimension must be an integer."
@@ -71,10 +179,25 @@ def spatial_dimensions(self, spatial_dimensions: int):
@property
def size(self):
+ """float: Size of compartment in units specified by unit attribute."""
return self._size
@size.setter
def size(self, size: float):
+ """Set the compartment size with validation.
+
+ Parameters
+ ----------
+ size : float or int
+ Size of the compartment. Must be non-negative. Units are specified
+ by the `unit` attribute.
+
+ Raises
+ ------
+ ValueError
+ If `size` is not a float or int, or is negative.
+
+ """
if not isinstance(size, (float, int)):
raise ValueError("Compartment size must be a float or int.")
elif size < 0:
@@ -84,10 +207,26 @@ def size(self, size: float):
@property
def unit(self):
+ """str: Unit identifier for compartment size (e.g., 'mL', 'uL')."""
return self._unit
@unit.setter
def unit(self, unit: str):
+ """Set the unit identifier with validation.
+
+ Parameters
+ ----------
+ unit : str or None
+ Unit identifier for the compartment size. Must be a supported unit
+ in BioCRNpyler (e.g., 'L', 'mL', 'µL' for volumes). See
+ documentation for supported units. Can be None for unitless sizes.
+
+ Raises
+ ------
+ ValueError
+ If `unit` is not a string (when not None).
+
+ """
if unit is not None:
if not isinstance(unit, str):
raise ValueError(
@@ -99,14 +238,35 @@ def unit(self, unit: str):
self._unit = None
def __eq__(self, other):
- """Check for equality of compartments.
+ """Check equality of compartments by name.
+
+ Two compartments are considered equal if they have the same name.
+ If two compartments have the same name but different sizes or spatial
+ dimensions, a ValueError is raised to prevent inconsistencies.
+
+ Parameters
+ ----------
+ other : Compartment
+ Another compartment to compare with.
- Overrides the default implementation. Two compartments are
- equivalent if they have the same name, spatial dimension, and size
+ Returns
+ -------
+ bool
+ True if compartments have the same name (and consistent
+ attributes), False otherwise.
- :param other: Compartment instance
+ Raises
+ ------
+ ValueError
+ If compartments have the same name but different sizes or spatial
+ dimensions.
- :return: boolean
+ Notes
+ -----
+ This comparison is based solely on the compartment name. If two
+ compartments share a name, they must also share the same physical
+ properties (size and spatial dimensions) to maintain model
+ consistency.
"""
if isinstance(other, Compartment) and self.name == other.name:
diff --git a/biocrnpyler/core/component.py b/biocrnpyler/core/component.py
index e5f25006..7033071a 100644
--- a/biocrnpyler/core/component.py
+++ b/biocrnpyler/core/component.py
@@ -14,12 +14,96 @@
class Component:
- """Component class for core components.
-
- These subclasses of Component represent different kinds of biomolecules.
-
- This class must be Subclassed to provide functionality with the
- functions get_species and get_reactions overwritten.
+ """Base class for biomolecular components in BioCRNpyler.
+
+ Component subclasses represent different kinds of biomolecules such as
+ DNA, RNA, proteins, and complexes. Components interact with mechanism
+ objects to generate chemical reaction network (CRN) species and reactions
+ during compilation. This class must be subclassed to provide functionality
+ by overriding the `update_species` and `update_reactions` methods.
+
+ Parameters
+ ----------
+ name : str or Species
+ Name of the component. If a `Species` object is provided, its name
+ attribute will be used.
+ mechanisms : dict or list, optional
+ Custom mechanisms to override default mechanisms from the mixture.
+ Can be a dict with mechanism types (str) as keys and mechanism
+ objects as values, or a list of mechanism objects.
+ parameters : dict, optional
+ Dictionary of parameter values to add to the component's parameter
+ database. Keys follow the format (mechanism, part_id, param_name).
+ parameter_file : str, optional
+ Path to a parameter file (CSV or TSV format) to load into the
+ component's parameter database.
+ mixture : Mixture, optional
+ Reference to the `Mixture` object containing this component. The
+ mixture provides default mechanisms and parameters.
+ compartment : Compartment, optional
+ The `Compartment` object representing the physical location of this
+ component.
+ attributes : list of str, optional
+ List of attribute strings to tag the component and its associated
+ species. Attributes can be used for mechanism selection and species
+ filtering.
+ initial_concentration : float, optional
+ Initial concentration of the component's primary species. Must be
+ non-negative. This value is added to the parameter database with key
+ ('initial concentration', None, component.name).
+ initial_condition_dictionary : dict, optional
+ Dictionary mapping species (or species names) to initial concentration
+ values for components with multiple species.
+
+ Attributes
+ ----------
+ name : str
+ The name of the component.
+ mechanisms : dict
+ Dictionary of mechanisms specific to this component, keyed by
+ mechanism type (str).
+ mixture : Mixture or None
+ Reference to the mixture containing this component.
+ compartment : Compartment or None
+ The compartment containing this component.
+ attributes : list of str
+ List of attribute tags associated with this component.
+ parameter_database : ParameterDatabase
+ Database storing all parameters associated with this component.
+ initial_concentration : float or None
+ Initial concentration value for the component's primary species.
+ initial_condition_dictionary : dict
+ Dictionary of initial conditions for species generated by this
+ component.
+
+ See Also
+ --------
+ Mechanism : Base class for reaction generation schemas.
+ Mixture : Container for components, mechanisms, and global parameters.
+ Species : Represents chemical species in a CRN.
+
+ Notes
+ -----
+ This is an abstract base class. Direct instantiation is possible but
+ subclasses like `DNA`, `RNA`, `Protein`, or `DNAassembly` should be
+ used for specific biomolecular functionality.
+
+ The parameter lookup hierarchy is:
+
+ 1. Component.parameter_database
+ 2. Component.mixture.parameter_database (if mixture is set)
+
+ Examples
+ --------
+ Create a basic component with custom parameters:
+
+ >>> comp = bcp.Component(
+ ... name='MyComponent',
+ ... parameters={'kb': 100, 'ku': 10},
+ ... initial_concentration=50.0
+ ... )
+ >>> comp.name
+ 'MyComponent'
"""
@@ -37,18 +121,6 @@ def __init__(
initial_concentration=None,
initial_condition_dictionary=None,
):
- """Initializes a Component object.
-
- :param name:
- :param mechanisms:
- :param parameters:
- :param parameter_file:
- :param mixture:
- :param compartment:
- :param attributes:
- :param initial_concentration:
- :param initial_condition_dictionary:
- """
if mechanisms is None:
self.mechanisms = {}
else:
@@ -117,18 +189,19 @@ def initial_concentration(self, initial_concentration):
@property
def compartment(self):
- """The compartment of the Component.
-
- :return: Compartment
- """
+ """Compartment or None: The compartment containing this component."""
return self._compartment
@compartment.setter
def compartment(self, compartment: Compartment) -> None:
- """Set the compartment of the Component.
+ """Set the compartment of the component.
+
+ Parameters
+ ----------
+ compartment : Compartment
+ The compartment object to assign to this component. Also updates
+ any internal species with default compartments.
- :param compartment:
- :return: None
"""
if compartment is not None and not isinstance(
compartment, Compartment
@@ -151,18 +224,31 @@ def compartment(self, compartment: Compartment) -> None:
raise TypeError("The compartment must be a Compartment object")
def set_mixture(self, mixture) -> None:
- """Set the mixture the Component is in.
+ """Set the mixture containing this component.
+
+ Parameters
+ ----------
+ mixture : Mixture or None
+ The mixture object that contains this component and provides
+ default mechanisms and parameters.
- :param mixture:
- :return: None
"""
self.mixture = mixture
# TODO implement as an abstractmethod
def get_species(self) -> None:
- """The subclasses should implement this method!
+ """Get the primary species associated with this component.
+
+ Returns
+ -------
+ None
+ Subclasses should override this method to return their primary
+ `Species` object.
+
+ Notes
+ -----
+ This is a placeholder that should be implemented by subclasses.
- :return: None
"""
return None
@@ -174,13 +260,35 @@ def set_species(
compartment=None,
attributes=None,
) -> Species:
- """Set species from strings, species, or Components.
+ """Convert various inputs into Species objects.
+
+ Parameters
+ ----------
+ species : Species, str, Component, or list
+ The species to convert. Can be a `Species` object (returned
+ as-is), a string (creates new Species), a `Component` (extracts
+ its species), or a list of any of these types.
+ material_type : str, optional
+ Material type for the species (e.g., 'dna', 'rna', 'protein').
+ Only used when creating new Species from strings.
+ compartment : Compartment, optional
+ Compartment to assign to the species. Only used when creating
+ new Species from strings.
+ attributes : list of str, optional
+ Attributes to assign to the species. Only used when creating
+ new Species from strings.
+
+ Returns
+ -------
+ Species or list of Species
+ The converted Species object(s). Returns a list if input was a
+ list.
+
+ Raises
+ ------
+ ValueError
+ If the input cannot be converted to a valid Species.
- :param species: Species, str, Component
- :param material_type:
- :param compartment:
- :param attributes:
- :return: Species
"""
if isinstance(species, Species):
return species
@@ -217,11 +325,75 @@ def __hash__(self):
return str.__hash__(repr(self.get_species()))
def set_attributes(self, attributes: List[str]):
+ """Set multiple attributes for the component.
+
+ Adds a list of attribute tags to the component and its associated
+ species by calling `add_attribute` for each attribute in the list.
+
+ Parameters
+ ----------
+ attributes : list of str or None
+ List of attribute strings to add to the component. If None, no
+ action is taken.
+
+ See Also
+ --------
+ add_attribute : Add a single attribute to the component.
+
+ Examples
+ --------
+ >>> comp = bcp.Protein(name="MyProtein")
+ >>> comp.set_attributes(["degtagged", "fluorescent"])
+ >>> comp.attributes
+ ['degtagged', 'fluorescent']
+
+ """
if attributes is not None:
for attribute in attributes:
self.add_attribute(attribute)
def add_attribute(self, attribute: str):
+ """Add a single attribute to the component.
+
+ Adds an attribute tag to the component's attribute list and to its
+ associated species object, if one exists. Attributes can be used for
+ mechanism selection, species filtering, and tracking special
+ properties.
+
+ Parameters
+ ----------
+ attribute : str
+ Attribute string to add to the component. Must be a non-None
+ string value.
+
+ Raises
+ ------
+ AssertionError
+ If `attribute` is not a string or is None.
+ Warning
+ If the component has no internal species to which the attribute
+ can be added.
+
+ Notes
+ -----
+ Attributes are commonly used to tag components with properties such
+ as:
+
+ - Degradation tags (e.g., 'degtagged', 'ssrAtagged', )
+ - Functional properties (e.g., 'fluorescent', 'membranebound')
+ - Regulatory elements (e.g., 'inducible', 'repressible')
+
+ Examples
+ --------
+ Add attributes to tag a protein with special properties:
+
+ >>> protein = bcp.Protein('GFP')
+ >>> protein.add_attribute('fluorescent')
+ >>> protein.add_attribute('ssrAtagged')
+ >>> protein.attributes
+ ['fluorescent', 'ssrAtagged']
+
+ """
assert (
isinstance(attribute, str) and attribute is not None
), f"Attribute: {attribute} must be a str"
@@ -242,12 +414,20 @@ def update_parameters(
parameter_database=None,
overwrite_parameters=True,
):
- """Updates the ParameterDatabase inside a Component.
-
- Possible inputs:
- parameter_file (string)
- parameters (dict)
- parameter_database (ParameterDatabase)
+ """Update the parameter database with new parameters.
+
+ Parameters
+ ----------
+ parameter_file : str, optional
+ Path to a CSV or TSV file containing parameters to load.
+ parameters : dict, optional
+ Dictionary of parameters to add. Keys follow the format
+ (mechanism, part_id, param_name).
+ parameter_database : ParameterDatabase, optional
+ Another parameter database to merge into component's database.
+ overwrite_parameters : bool, default=True
+ If True, new parameter values overwrite existing ones. If False,
+ existing parameters are preserved.
"""
if parameter_file is not None:
@@ -266,15 +446,32 @@ def update_parameters(
)
def get_mechanism(self, mechanism_type, optional_mechanism=False):
- """Searches the Component for a Mechanism of the correct type.
-
- If the Component does not have the mechanism, searches the
- Components' Mixture for the Mechanism.
-
- :param mechanism_type:
- :param optional_mechanism: toggles whether an error is thrown if
- no mechanism is found
- :return:
+ """Retrieve a mechanism by type from the component or its mixture.
+
+ Searches first in the component's mechanism dictionary, then falls
+ back to the mixture's mechanisms if not found.
+
+ Parameters
+ ----------
+ mechanism_type : str
+ The type identifier of the mechanism to retrieve (e.g.,
+ 'transcription', 'translation', 'binding').
+ optional_mechanism : bool, default=False
+ If True, returns None when mechanism not found. If False, raises
+ KeyError when mechanism not found.
+
+ Returns
+ -------
+ Mechanism or None
+ The requested mechanism object, or None if not found and
+ `optional_mechanism` is True.
+
+ Raises
+ ------
+ TypeError
+ If `mechanism_type` is not a string.
+ KeyError
+ If mechanism not found and `optional_mechanism` is False.
"""
if not isinstance(mechanism_type, str):
@@ -318,18 +515,30 @@ def add_mechanism(
overwrite=False,
optional_mechanism=False,
):
- """Add a mechanism to the component mechanism dictionary.
-
- Adds a mechanism of type mech_type to the Component Mechanism
- dictionary.
-
- :param mechanism:
- :param mech_type:
- :param overwrite: toggles whether the mechanism is added overwriting
- any mechanism with the same key.
- :param optional_mechanism: toggles whether an error is thrown if a
- Mechanism is added that conflicts with an exising Mechanism
- :return:
+ """Add a mechanism to this component's mechanism dictionary.
+
+ Parameters
+ ----------
+ mechanism : Mechanism
+ The mechanism object to add.
+ mech_type : str, optional
+ The type key under which to store the mechanism. If None, uses
+ the mechanism's `mechanism_type` attribute.
+ overwrite : bool, default=False
+ If True, replaces any existing mechanism with the same key.
+ If False, raises ValueError when key already exists.
+ optional_mechanism : bool, default=False
+ If True, suppresses the ValueError when a mechanism key conflict
+ occurs and `overwrite` is False.
+
+ Raises
+ ------
+ TypeError
+ If `mechanism` is not a Mechanism object, or if `mech_type` is
+ not a string.
+ ValueError
+ If mechanism key already exists, `overwrite` is False, and
+ `optional_mechanism` is False.
"""
if not hasattr(self, '_mechanisms'):
@@ -361,14 +570,30 @@ def add_mechanisms(
overwrite=False,
optional_mechanism=False,
):
- """Add a list or dictionary of mechanisms to the mixture.
-
- :param mechanisms: Can take both GlobalMechanisms and Mechanisms
- :param overwrite: toggles whether the mechanism is added overwriting
- any mechanism with the same key.
- :param optional_mechanism: toggles whether an error is thrown if a
- Mechanism is added that conflicts with an exising Mechanism
- :return:
+ """Add multiple mechanisms to this component.
+
+ Accepts mechanisms as a single object, list, or dictionary and adds
+ them to the component's mechanism dictionary.
+
+ Parameters
+ ----------
+ mechanisms : Mechanism, GlobalMechanism, dict, or list
+ The mechanism(s) to add. Can be a single mechanism, a dict with
+ mechanism types as keys and mechanisms as values, or a list of
+ mechanisms.
+ overwrite : bool, default=False
+ If True, replaces any existing mechanisms with the same keys.
+ If False, raises ValueError when keys already exist.
+ optional_mechanism : bool, default=False
+ If True, suppresses ValueError when mechanism key conflicts occur
+ and `overwrite` is False.
+
+ Raises
+ ------
+ ValueError
+ If `mechanisms` is not a valid type, or if mechanism key conflicts
+ occur with `overwrite=False` and `optional_mechanism=False`.
+
"""
if isinstance(mechanisms, Mechanism):
self.add_mechanism(
@@ -406,22 +631,47 @@ def get_parameter(
return_none=False,
check_mixture=True,
) -> Union[Parameter, Real]:
- """Get a parameter from different objects that hold parameters.
-
- Hierarchy:
- 1. searches for the Parameter in Component.parameter_database
- 2. searches for the parameter in Component.mixture.parameter_database
-
- :param param_name:
- :param part_id:
- :param mechanism:
- :param return_numerical: numerical value or the parameter object is
- returned
- :param return_none: returns None instead of throwing an error if a
- parameter isn't found
- :param check_mixture: toggle whether or not to check the Component's
- Mixture as well
- :return: Parameter object or a Real number
+ """Retrieve parameter from component or mixture parameter database.
+
+ Searches first in the component's parameter database, then falls back
+ to the mixture's parameter database if not found.
+
+ Parameters
+ ----------
+ param_name : str
+ Name of the parameter to retrieve.
+ part_id : str, optional
+ Part identifier for the parameter lookup key.
+ mechanism : str, optional
+ Mechanism identifier for the parameter lookup key.
+ return_numerical : bool, default=False
+ If True, returns the numerical value. If False, returns the
+ `Parameter` object.
+ return_none : bool, default=False
+ If True, returns None when parameter not found. If False, raises
+ ValueError when parameter not found.
+ check_mixture : bool, default=True
+ If True, searches the mixture's parameter database if not found
+ in the component's database.
+
+ Returns
+ -------
+ Parameter, Real, or None
+ The parameter object or its numerical value, or None if not found
+ and `return_none` is True.
+
+ Raises
+ ------
+ ValueError
+ If parameter not found and `return_none` is False.
+
+ Notes
+ -----
+ Parameter lookup follows the hierarchy:
+
+ 1. Component.parameter_database
+ 2. Component.mixture.parameter_database (if `check_mixture` is True)
+
"""
# Try the Component ParameterDatabase
param = self.parameter_database.find_parameter(
@@ -446,9 +696,19 @@ def get_parameter(
# TODO implement abstractmethod
def update_species(self) -> List[Species]:
- """The subclasses should implement this method!
+ """Generate and return species associated with this component.
+
+ Returns
+ -------
+ list of Species
+ List of species objects generated by this component. This base
+ implementation returns an empty list.
+
+ Notes
+ -----
+ This method should be overridden by subclasses to return the actual
+ species generated by the component during CRN compilation.
- :return: empty list
"""
species = []
warn("Unsubclassed update_species called for " + repr(self))
@@ -456,25 +716,52 @@ def update_species(self) -> List[Species]:
# TODO implement abstractmethod
def update_reactions(self) -> List[Species]:
- """The subclasses should implement this method!
+ """Generate and return reactions associated with this component.
+
+ Returns
+ -------
+ list of Reaction
+ List of reaction objects generated by this component. This base
+ implementation returns an empty list.
+
+ Notes
+ -----
+ This method should be overridden by subclasses to return the actual
+ reactions generated by the component during CRN compilation.
- :return: empty list
"""
reactions = []
warn("Unsubclassed update_reactions called for " + repr(self))
return reactions
def enumerate_components(self, previously_enumerated=None) -> List:
- """Enumerate components created by this component.
-
- This method is used for component enumeration. Usually you
- will return a list of components that are copies of existing
- ones (first list) and new components (second list). For
- example, A DNA_construct makes a list of copies of its parts
- as the first output, and a list of RNA_constructs as the
- second output. An RNA_construct will make a list of copies of
- its parts as the first output, and a list of Protein
- components as its second output (if it makes any proteins)
+ """Enumerate derived components created from this component.
+
+ This method generates new components based on the current component,
+ typically used during CRN compilation to expand higher-level
+ components into their constituent parts and products.
+
+ Parameters
+ ----------
+ previously_enumerated : set or list, optional
+ Collection of components that have already been enumerated, used
+ to prevent infinite recursion in component enumeration.
+
+ Returns
+ -------
+ list
+ List of new components created from this component. This base
+ implementation returns an empty list.
+
+ Notes
+ -----
+ Subclasses override this method to implement specific enumeration
+ behavior. For example:
+
+ - A `DNA_construct` returns copies of its parts and `RNA_construct`
+ objects representing transcripts.
+ - An `RNA_construct` returns copies of its parts and `Protein`
+ components representing translation products.
"""
return []
diff --git a/biocrnpyler/core/mechanism.py b/biocrnpyler/core/mechanism.py
index b805a4ba..7b2792c9 100644
--- a/biocrnpyler/core/mechanism.py
+++ b/biocrnpyler/core/mechanism.py
@@ -6,22 +6,69 @@
class Mechanism(object):
- """Mechanism class for core mechanisms.
-
- Core mechanisms within a mixture (transcription, translation, etc)
-
- The Mechanism class is used to implement different core
- mechanisms in TX-TL. All specific core mechanisms should be
- derived from this class.
+ """Base class for mechanisms that generate species and reactions.
+
+ Mechanisms are reaction schemas that define how components interact and
+ transform during CRN compilation. They represent molecular processes such
+ as transcription, translation, binding, catalysis, and degradation. Each
+ mechanism is called by components during compilation to generate the
+ appropriate species and reactions.
+
+ Parameters
+ ----------
+ name : str
+ Name of the mechanism instance for identification and debugging.
+ mechanism_type : str, default=''
+ Type identifier for the mechanism (e.g., 'transcription',
+ 'translation', 'binding'). Used for mechanism lookup in components
+ and mixtures.
+
+ Attributes
+ ----------
+ name : str
+ Name of the mechanism.
+ mechanism_type : str
+ Type identifier for the mechanism.
+
+ See Also
+ --------
+ Component : Base class that calls mechanisms during compilation.
+ Mixture : Container that provides default mechanisms to components.
+
+ Notes
+ -----
+ Subclasses must override `update_species` and `update_reactions` to
+ implement specific mechanism behavior. The base class implementations
+ return empty lists and issue warnings.
+
+ If `mechanism_type` is empty or None, a warning is issued as this may
+ prevent proper mechanism inheritance and lookup.
+
+ Examples
+ --------
+ Create a custom mechanism by subclassing:
+
+ >>> class CustomTranscription(bcp.Mechanism):
+ ... def __init__(self, name="custom_tx"):
+ ... super().__init__(name=name, mechanism_type="transcription")
+ ...
+ ... def update_species(self, dna, transcript, **kwargs):
+ ... # Generate RNA species
+ ... return [transcript]
+ ...
+ ... def update_reactions(self, dna, transcript, **kwargs):
+ ... # Generate transcription reaction
+ ... return [Reaction([dna], [dna, transcript], k=0.01)]
+
+ Use a mechanism in a mixture:
+
+ >>> mixture = bcp.Mixture(
+ ... mechanisms={'transcription': CustomTranscription()}
+ ... )
"""
def __init__(self, name: str, mechanism_type=''):
- """Initializes a Mechanism instance.
-
- :param name: name of the Mechanism
- :param mechanism_type: mechanism_type in string
- """
self.name = name
self.mechanism_type = mechanism_type
if mechanism_type == '' or mechanism_type is None:
@@ -31,17 +78,67 @@ def __init__(self, name: str, mechanism_type=''):
)
def update_species(self, component=None, part_id=None) -> List:
- """The child class should implement this method.
+ """Generate species for this mechanism.
+
+ Parameters
+ ----------
+ component : Component, optional
+ The component calling this mechanism. May be used to access
+ component-specific parameters or attributes.
+ part_id : str, optional
+ Part identifier for parameter lookup. Used to retrieve
+ part-specific parameters from the parameter database.
+
+ Returns
+ -------
+ list of Species
+ List of species objects generated by this mechanism. This base
+ implementation returns an empty list.
+
+ Warns
+ -----
+ UserWarning
+ Issues a warning when the base class method is called, indicating
+ that subclasses should override this method.
+
+ Notes
+ -----
+ Subclasses must override this method to implement mechanism-specific
+ species generation logic.
- :return: empty list
"""
warn(f"Default update_species called for mechanism {self.name}")
return []
def update_reactions(self, component=None, part_id=None) -> List:
- """The child class should implement this method.
+ """Generate reactions for this mechanism.
+
+ Parameters
+ ----------
+ component : Component, optional
+ The component calling this mechanism. May be used to access
+ component-specific parameters or attributes.
+ part_id : str, optional
+ Part identifier for parameter lookup. Used to retrieve
+ part-specific parameters from the parameter database.
+
+ Returns
+ -------
+ list of Reaction
+ List of reaction objects generated by this mechanism. This base
+ implementation returns an empty list.
+
+ Warns
+ -----
+ UserWarning
+ Issues a warning when the base class method is called, indicating
+ that subclasses should override this method.
+
+ Notes
+ -----
+ Subclasses must override this method to implement mechanism-specific
+ reaction generation logic.
- :return: empty list
"""
warn(f"Default update_reactions called for mechanism {self.name}")
return []
@@ -51,23 +148,94 @@ def __repr__(self):
class EmptyMechanism(Mechanism):
- """Mechanism that does nothing.
-
- For use when one needs a Mechanism to do nothing, such as
- translation in Expression Mixtures.
+ """Mechanism that generates no species or reactions.
+
+ A placeholder mechanism used when a mechanism type is required but no
+ actual reactions should be generated. Commonly used in Expression Mixtures
+ where translation is disabled, or to temporarily disable specific
+ mechanisms without removing them from the code.
+
+ Parameters
+ ----------
+ name : str
+ Name of the mechanism instance.
+ mechanism_type : str
+ Type identifier for the mechanism (e.g., 'translation').
+
+ See Also
+ --------
+ Mechanism : Base class for all mechanisms.
+
+ Notes
+ -----
+ Both `update_species` and `update_reactions` return empty lists,
+ ensuring no CRN elements are generated when this mechanism is called.
+
+ Examples
+ --------
+ Use EmptyMechanism to disable translation in a mixture:
+
+ >>> rnap = bcp.Species('RNAP')
+ >>> mixture = bcp.Mixture(
+ ... mechanisms={
+ ... 'transcription': bcp.Transcription_MM(rnap),
+ ... 'translation': bcp.EmptyMechanism(
+ ... 'no_translation', 'translation')
+ ... }
+ ... )
+
+ Create a DNA assembly that transcribes but doesn't translate:
+
+ >>> promoter = bcp.Promoter('pconst')
+ >>> assembly = bcp.DNAassembly(
+ ... name='tx_only',
+ ... promoter=promoter,
+ ... mechanisms={
+ ... 'translation': bcp.EmptyMechanism('empty', 'translation')
+ ... }
+ ... )
"""
def __init__(self, name, mechanism_type):
- """Initializes an EmptyMechanism instance.
-
- :param name: name of the Mechanism
- :param mechanism_type: mechanism_type in string
- """
Mechanism.__init__(self, name=name, mechanism_type=mechanism_type)
def update_species(self, component=None, part_id=None, **kwargs):
+ """Generate empty list of species.
+
+ Parameters
+ ----------
+ component : Component, optional
+ The component calling this mechanism (unused).
+ part_id : str, optional
+ Part identifier (unused).
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list
+ Empty list.
+
+ """
return []
def update_reactions(self, component=None, part_id=None, **kwargs):
+ """Generate empty list of reactions.
+
+ Parameters
+ ----------
+ component : Component, optional
+ The component calling this mechanism (unused).
+ part_id : str, optional
+ Part identifier (unused).
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list
+ Empty list.
+
+ """
return []
diff --git a/biocrnpyler/core/mixture.py b/biocrnpyler/core/mixture.py
index 3450b78e..598592f0 100644
--- a/biocrnpyler/core/mixture.py
+++ b/biocrnpyler/core/mixture.py
@@ -16,6 +16,131 @@
class Mixture(object):
+ """Container for components, mechanisms, and parameters in a CRN model.
+
+ A Mixture holds together all components (DNA, RNA, Protein, etc.),
+ mechanisms (transcription, translation, binding, etc.), and parameters
+ needed to compile a chemical reaction network (CRN). Mixtures can include
+ default mechanisms that apply to all components, as well as global
+ mechanisms that affect all species (e.g., dilution, degradation).
+
+ Parameters
+ ----------
+ name : str, default=''
+ Name of the mixture for identification and parameter lookup.
+ mechanisms : dict, list, or Mechanism, optional
+ Default mechanisms for components in this mixture. Can be a dict with
+ mechanism types (str) as keys and mechanism objects as values, a
+ list of mechanism objects, or a single `Mechanism`.
+ components : list of Component or Component, optional
+ Components to include in the mixture. Components are deep-copied when
+ added to prevent modification of original objects.
+ parameters : dict, optional
+ Dictionary of parameter values. Keys follow the format
+ (mechanism, part_id, param_name).
+ compartment : Compartment, optional
+ Default compartment for all components and species in this mixture.
+ parameter_file : str, optional
+ Path to a CSV or TSV file containing parameters to load.
+ overwrite_parameters : bool, default=False
+ If True, parameters from file/dict overwrite existing parameters.
+ If False, existing parameters are preserved.
+ global_mechanisms : dict, list, or GlobalMechanism, optional
+ Global mechanisms that apply to all species after component
+ compilation (e.g., dilution, global degradation). Can be a dict,
+ list, or single `GlobalMechanism`.
+ species : list of Species or Species, optional
+ Additional species to add directly to the CRN without going through
+ component compilation.
+ initial_condition_dictionary : dict, optional
+ Dictionary mapping species to initial concentration values. Deprecated
+ in favor of using parameters with mechanism='initial concentration'.
+ global_component_enumerators : list, optional
+ List of global component enumerators for advanced component generation
+ patterns (e.g., creating all pairwise interactions).
+ global_recursion_depth : int, default=4
+ Maximum recursion depth for global component enumeration during
+ compilation.
+ local_recursion_depth : int, optional
+ Maximum recursion depth for local component enumeration. If None,
+ defaults to `global_recursion_depth + 2`.
+
+ Attributes
+ ----------
+ name : str
+ Name of the mixture.
+ compartment : Compartment or None
+ Default compartment for the mixture.
+ components : list of Component
+ List of components in the mixture (deep copies of added components).
+ mechanisms : dict
+ Dictionary of default mechanisms, keyed by mechanism type (str).
+ global_mechanisms : dict
+ Dictionary of global mechanisms, keyed by mechanism type (str).
+ parameter_database : ParameterDatabase
+ Database storing all parameters for this mixture.
+ added_species : list of Species
+ List of species added directly to the mixture.
+ global_component_enumerators : list
+ List of global component enumerators.
+ global_recursion_depth : int
+ Recursion depth for global component enumeration.
+ local_recursion_depth : int
+ Recursion depth for local component enumeration.
+ crn : ChemicalReactionNetwork or None
+ The compiled CRN, created by calling `compile_crn`.
+
+ See Also
+ --------
+ Component : Base class for biomolecular components.
+ Mechanism : Base class for reaction generation schemas.
+ GlobalMechanism : Mechanisms that apply to all species.
+ ChemicalReactionNetwork : Container for species and reactions.
+
+ Notes
+ -----
+ Components added to a Mixture are deep-copied to prevent unintended
+ modifications. Each component's `mixture` attribute is set to reference
+ this Mixture, allowing components to access default mechanisms and
+ parameters.
+
+ The compilation process follows these steps:
+
+ 1. Global component enumeration (e.g., generating all interactions)
+ 2. Local component enumeration (e.g., DNA --> RNA --> Protein)
+ 3. Species generation from all enumerated components
+ 4. Reaction generation from all enumerated components
+ 5. Application of global mechanisms to all species
+
+ Examples
+ --------
+ Create a basic mixture with components and mechanisms:
+
+ >>> # Create a simple transcription-translation mixture
+ >>> mixture = bcp.Mixture(
+ ... name="txtl_extract",
+ ... mechanisms={
+ ... 'transcription': bcp.SimpleTranscription(),
+ ... 'translation': bcp.SimpleTranslation()
+ ... },
+ ... parameters={'ktx': 0.05, 'ktl': 0.01}
+ ... )
+
+ Add components to the mixture:
+
+ >>> promoter = bcp.Promoter("pconst")
+ >>> gene = bcp.DNA_construct([promoter, bcp.RBS('rbs'), bcp.CDS('gfp')])
+ >>> mixture.add_component(gene)
+
+ Compile the CRN:
+
+ >>> crn = mixture.compile_crn()
+ >>> print(
+ ... f"CRN has {len(crn.species)} species "
+ ... f"and {len(crn.reactions)} reactions")
+
+ """
+
def __init__(
self,
name='',
@@ -32,27 +157,6 @@ def __init__(
global_recursion_depth=4,
local_recursion_depth=None,
):
- """Mixture object holding components, mechanisms, and parameters.
-
- A Mixture object holds together all the components
- (DNA,Protein, etc), mechanisms (Transcription, Translation),
- and parameters related to the mixture itself
- (e.g. Transcription rate). Default components and mechanisms
- can be added as well as global mechanisms that impacts all
- species (e.g. cell growth).
-
- :param name: Name of the mixture
- :param mechanisms: Dictionary of mechanisms
- :param components: List of components in the mixture (list of
- Components)
- :param parameters: Dictionary of parameters (check parameters
- documentation for the keys)
- :param parameter_file: Parameters can be loaded from a parameter file
- :param default_mechanisms:
- :param global_mechanisms: dict of global mechanisms that impacts
- all species (e.g. cell growth)
-
- """
# Initialize instance variables
self.name = name # Save the name of the mixture
self.compartment = compartment
@@ -73,7 +177,7 @@ def __init__(
if mechanisms is None and not hasattr(self, '_mechanisms'):
self.mechanisms = {}
else:
- self.add_mechanisms(mechanisms)
+ self.add_mechanisms(mechanisms, overwrite=True)
# process global_mechanisms:
# Global mechanisms are applied just once ALL species generated from
@@ -107,6 +211,25 @@ def __init__(
self.crn = None
def add_species(self, species: Union[List[Species], Species]):
+ """Add species directly to the mixture without component compilation.
+
+ Parameters
+ ----------
+ species : Species or list of Species
+ Species object(s) to add directly to the mixture. These species
+ will be included in the CRN during compilation.
+
+ Raises
+ ------
+ AssertionError
+ If any element in the list is not a Species object.
+
+ Notes
+ -----
+ Species added this way bypass component enumeration and are added
+ directly to the CRN during `compile_crn`.
+
+ """
if not hasattr(self, 'added_species'):
self.added_species = []
@@ -128,12 +251,31 @@ def set_species(
material_type=None,
attributes=None,
):
- """Used to set internal species from strings, Species or Components.
+ """Convert various inputs into Species objects.
+
+ Parameters
+ ----------
+ species : Species, str, or Component
+ The species to convert. Can be a `Species` object (returned
+ as-is), a string (creates new Species), or a `Component` (extracts
+ its species).
+ material_type : str, optional
+ Material type for the species (e.g., 'dna', 'rna', 'protein').
+ Only used when creating new Species from strings.
+ attributes : list of str, optional
+ Attributes to assign to the species. Only used when creating
+ new Species from strings.
+
+ Returns
+ -------
+ Species
+ The converted Species object.
+
+ Raises
+ ------
+ ValueError
+ If the input cannot be converted to a valid Species.
- :param species: name of a species or a species instance
- :param material_type: material type of a species as a string
- :param attributes: Species attribute
- :return: Species in the mixture
"""
if isinstance(species, Species):
return species
@@ -165,7 +307,31 @@ def components(self, components):
self.add_components(components)
def add_component(self, component):
- """This function adds a single component to the mixture."""
+ """Add a single component to the mixture.
+
+ Parameters
+ ----------
+ component : Component or list of Component
+ Component object to add to the mixture. If a list is provided,
+ calls `add_components` instead. The component is deep-copied
+ before being added.
+
+ Raises
+ ------
+ AssertionError
+ If the component is not a Component object.
+ ValueError
+ If a component with the same type and name already exists in
+ the mixture.
+
+ Notes
+ -----
+ Components are deep-copied when added to prevent modification of the
+ original component. The copied component's `mixture` attribute is set
+ to this Mixture, and its `compartment` is set to the mixture's
+ compartment.
+
+ """
if not hasattr(self, '_components'):
self.components = []
@@ -194,9 +360,24 @@ def add_component(self, component):
self.components.append(component_copy)
def get_mechanism(self, mechanism_type):
- """Searches the Mixture for a Mechanism of the correct type.
+ """Retrieve a mechanism by type from the mixture.
+
+ Parameters
+ ----------
+ mechanism_type : str
+ The type identifier of the mechanism to retrieve (e.g.,
+ 'transcription', 'translation', 'binding').
+
+ Returns
+ -------
+ Mechanism or None
+ The requested mechanism object, or None if not found.
+
+ Raises
+ ------
+ TypeError
+ If `mechanism_type` is not a string.
- If no Mechanism is found, None is returned.
"""
if not isinstance(mechanism_type, str):
raise TypeError(
@@ -209,7 +390,25 @@ def get_mechanism(self, mechanism_type):
return None
def add_components(self, components: Union[List[Component], Component]):
- """This function adds a list of components to the mixture."""
+ """Add multiple components to the mixture.
+
+ Parameters
+ ----------
+ components : Component or list of Component
+ Component object(s) to add to the mixture. Each component is
+ deep-copied before being added.
+
+ Raises
+ ------
+ ValueError
+ If `components` is not a Component, list of Components, or if
+ any duplicate components are detected.
+
+ See Also
+ --------
+ add_component : Add a single component to the mixture.
+
+ """
if isinstance(components, Component):
self.add_component(components)
elif isinstance(components, List):
@@ -222,16 +421,34 @@ def add_components(self, components: Union[List[Component], Component]):
)
def get_component(self, component=None, name=None, index=None):
- """Function to get components from Mixture._components.
-
- One of the 3 keywords must not be None.
+ """Retrieve components from the mixture by various criteria.
+
+ Exactly one of the three parameters must be provided.
+
+ Parameters
+ ----------
+ component : Component, optional
+ A component instance to search for. Returns components with
+ matching type and name.
+ name : str, optional
+ Name of the component to search for. Returns all components
+ with this name.
+ index : int, optional
+ Index of the component in the mixture's component list.
+
+ Returns
+ -------
+ Component, list of Component, or None
+ - Single Component if exactly one match is found or index is used
+ - List of Components if multiple matches are found
+ - None if no matches are found
+
+ Raises
+ ------
+ ValueError
+ If zero or more than one parameter is provided, or if parameters
+ are of incorrect types.
- :param component: an instance of a component. Searches
- Mixture._components for a Component with the same type and name.
- :param name: str. Searches Mixture._components for a Component
- with the same name
- :param index: int. returns Mixture._components[index]
- :return: if nothing is found, returns None.
"""
if [component, name, index].count(None) != 2:
raise ValueError(
@@ -274,7 +491,7 @@ def get_component(self, component=None, name=None, index=None):
@property
def mechanisms(self):
- """Mechanisms stores Mixture Mechanisms."""
+ """Mechanism: Stores mixture mechanisms."""
return self._mechanisms
@mechanisms.setter
@@ -283,17 +500,32 @@ def mechanisms(self, mechanisms):
self.add_mechanisms(mechanisms, overwrite=True)
def add_mechanism(self, mechanism, mech_type=None, overwrite=False):
- """Add mechanism to mixture dictionary.
-
- Adds a mechanism of type mech_type to the Mixture
- mechanism_dictionary.
-
- :param mechanism: a Mechanism instance
- :param mech_type: the type of mechanism. defaults to
- mechanism.mech_type if None
- :param overwrite: whether to overwrite existing mechanisms of
- the same type (default False)
- :return:
+ """Add a mechanism to the mixture's mechanism dictionary.
+
+ Parameters
+ ----------
+ mechanism : Mechanism or GlobalMechanism
+ The mechanism object to add. If a `GlobalMechanism` is provided,
+ it is automatically added to the global mechanisms dictionary.
+ mech_type : str, optional
+ The type key under which to store the mechanism. If None, uses
+ the mechanism's `mechanism_type` attribute.
+ overwrite : bool, default=False
+ If True, replaces any existing mechanisms with the same keys. If
+ False, raises ValueError when keys already exist. If None, ignores
+ mechanisms that already exist.
+
+ Raises
+ ------
+ TypeError
+ If `mechanism` is not a Mechanism object, or if `mech_type` is
+ not a string.
+ ValueError
+ If mechanism key already exists and `overwrite` is None.
+
+ See Also
+ --------
+ add_global_mechanism : Add a global mechanism specifically.
"""
if not hasattr(self, '_mechanisms'):
@@ -315,23 +547,41 @@ def add_mechanism(self, mechanism, mech_type=None, overwrite=False):
self.add_global_mechanism(mechanism, mech_type, overwrite)
elif isinstance(mechanism, Mechanism):
if mech_type in self._mechanisms and not overwrite:
- raise ValueError(
- f"mech_type {mech_type} already in Mixture {self}. "
- f"To overwrite, use keyword overwrite = True."
- )
+ if overwrite is False:
+ raise ValueError(
+ f"mech_type {mech_type} already in Mixture {self}. "
+ f"To overwrite, use keyword overwrite=True."
+ )
else:
self._mechanisms[mech_type] = copy.deepcopy(mechanism)
def add_mechanisms(self, mechanisms, overwrite=False):
- """Add mechanism to mixture dictionary.
-
- This function adds a list or dictionary of mechanisms to the
- mixture. Can take both GlobalMechanisms and Mechanisms.
-
- :param mechanisms: a Mechanism instance
- :param overwrite: whether to overwrite existing mechanisms of
- the same type (default False)
- :return:
+ """Add multiple mechanisms to the mixture.
+
+ Accepts mechanisms as a single object, list, or dictionary and adds
+ them to the mixture's mechanism dictionary. Can handle both regular
+ `Mechanism` and `GlobalMechanism` objects.
+
+ Parameters
+ ----------
+ mechanisms : Mechanism, GlobalMechanism, dict, or list
+ The mechanism(s) to add. Can be a single mechanism, a dict with
+ mechanism types as keys and mechanisms as values, or a list of
+ mechanisms.
+ overwrite : bool, default=False
+ If True, replaces any existing mechanisms with the same keys. If
+ False, raises ValueError when keys already exist. If None, ignores
+ mechanisms that already exist.
+
+ Raises
+ ------
+ ValueError
+ If `mechanisms` is not a valid type, or if mechanism key conflicts
+ occur with `overwrite=False`.
+
+ See Also
+ --------
+ add_mechanism : Add a single mechanism to the mixture.
"""
if isinstance(mechanisms, Mechanism):
@@ -352,7 +602,7 @@ def add_mechanisms(self, mechanisms, overwrite=False):
@property
def global_mechanisms(self):
- """Stores global Mechanisms in the Mixture."""
+ """Mechanism: Stores global mechanisms in the mixture."""
return self._global_mechanisms
@global_mechanisms.setter
@@ -372,15 +622,33 @@ def add_global_mechanism(
):
"""Add a global mechanism to the mixture.
- Adds a mechanism of type mech_type to the Mixture
- global_mechanism dictonary.
-
- keywords:
- mechanism: a Mechanism instance
- mech_type: the type of mechanism. defaults to mechanism.mech_type
- if None
- overwrite: whether to overwrite existing mechanisms of the same \
- type (default False)
+ Global mechanisms are applied to all species after component
+ compilation, enabling operations like dilution or global degradation.
+
+ Parameters
+ ----------
+ mechanism : GlobalMechanism
+ The global mechanism object to add. Must be a `GlobalMechanism`
+ instance.
+ mech_type : str, optional
+ The type key under which to store the mechanism. If None, uses
+ the mechanism's `mechanism_type` attribute.
+ overwrite : bool, default=False
+ If True, replaces any existing global mechanism with the same key.
+ If False, raises ValueError when key already exists.
+
+ Raises
+ ------
+ TypeError
+ If `mechanism` is not a GlobalMechanism object, or if `mech_type`
+ is not a string.
+ ValueError
+ If mechanism key already exists and `overwrite` is False.
+
+ Notes
+ -----
+ Global mechanisms are applied during `compile_crn` after all
+ component reactions have been generated.
"""
if not hasattr(self, '_global_mechanisms'):
@@ -409,6 +677,20 @@ def add_global_mechanism(
def update_parameters(
self, parameter_file=None, parameters=None, overwrite_parameters=True
):
+ """Update the parameter database with new parameters.
+
+ Parameters
+ ----------
+ parameter_file : str, optional
+ Path to a CSV or TSV file containing parameters to load.
+ parameters : dict, optional
+ Dictionary of parameters to add. Keys follow the format
+ (mechanism, part_id, param_name).
+ overwrite_parameters : bool, default=True
+ If True, new parameter values overwrite existing ones. If False,
+ existing parameters are preserved.
+
+ """
if parameter_file is not None:
self.parameter_database.load_parameters_from_file(
parameter_file, overwrite_parameters=overwrite_parameters
@@ -420,6 +702,23 @@ def update_parameters(
)
def get_parameter(self, mechanism, part_id, param_name):
+ """Retrieve a parameter from the mixture's parameter database.
+
+ Parameters
+ ----------
+ mechanism : str
+ Mechanism identifier for the parameter lookup key.
+ part_id : str
+ Part identifier for the parameter lookup key.
+ param_name : str
+ Name of the parameter to retrieve.
+
+ Returns
+ -------
+ Parameter or None
+ The parameter object, or None if not found.
+
+ """
param = self.parameter_database.find_parameter(
mechanism, part_id, param_name
)
@@ -428,28 +727,43 @@ def get_parameter(self, mechanism, part_id, param_name):
def get_initial_concentration(
self, S: Union[List, Species], component=None
):
- """Get initial concentration.
+ """Determine initial concentrations using parameter hierarchy.
+
+ Searches for initial concentration parameters for species following a
+ hierarchical lookup strategy, defaulting to 0 if not found.
+
+ Parameters
+ ----------
+ S : Species or list of Species
+ Species object(s) for which to find initial concentrations. Lists
+ are automatically flattened.
+ component : Component, optional
+ The component that generated the species. Used for
+ component-specific parameter lookup.
- Tries to find an initial condition of species s using the
- parameter hierarchy using the key:
+ Returns
+ -------
+ dict
+ Dictionary mapping each Species object to its initial
+ concentration value (float).
- 1. Searches Component's ParameterDatabase using the key:
- mechanisms = "initial concentration"
- part_id = mixture.name
- parameter_name = str(s)
+ Raises
+ ------
+ ValueError
+ If any element in `S` is not a Species object.
- if s == component.get_species, also checks with
- parameter_name=component.name
+ Notes
+ -----
+ The parameter lookup hierarchy is:
- 2. Searches the Mixture's ParameterDatabase using the key:
- mechanisms = "initial concentration"
- part_id = mixture.name
- parameter_name = str(s)
+ 1. Component's `ParameterDatabase` with `mechanism='initial
+ concentration'`, `part_id=mixture.name`, and parameter names:
+ `str(s)`, `s.name`, or `component.name` (where `s` is the
+ component's primary species)
- if s == component.get_species, also checks with
- parameter_name=component.name
+ 2. Mixture's `ParameterDatabase` with the same keys
- 3. Defaults to 0
+ 3. Defaults to 0 if not found
"""
if isinstance(S, Species):
@@ -531,6 +845,39 @@ def add_species_to_crn(
copy_species=True,
compartment=None,
):
+ """Add species to the CRN with initial concentrations.
+
+ Helper method that adds species to the CRN and automatically looks up
+ and assigns their initial concentrations.
+
+ Parameters
+ ----------
+ new_species : Species or list of Species
+ Species to add to the CRN.
+ component : Component, optional
+ The component that generated these species. Used for
+ component-specific initial concentration lookup.
+ no_initial_concentrations : bool, default=False
+ If True, skips initial concentration lookup and assignment.
+ copy_species : bool, default=True
+ If True, deep-copies species before adding them to the CRN.
+ compartment : Compartment, optional
+ Compartment to assign to the species. Overrides species'
+ existing compartments.
+
+ Returns
+ -------
+ list of Species
+ All species in the CRN after addition (may include pre-existing
+ species).
+
+ Notes
+ -----
+ This method tracks which species are newly added and only assigns
+ initial concentrations to those new species, preventing overwriting
+ of previously set initial concentrations.
+
+ """
if self.crn is None:
self.crn = ChemicalReactionNetwork(species=[], reactions=[])
@@ -572,8 +919,31 @@ def add_species_to_crn(
def apply_global_mechanisms(
self, species, compartment=None
) -> Tuple[List[Species], List[Reaction]]:
- # update with global mechanisms
+ """Apply all global mechanisms to a set of species.
+
+ Calls each global mechanism's `update_species_global` and
+ `update_reactions_global` methods, then adds the resulting species
+ and reactions to the CRN.
+
+ Parameters
+ ----------
+ species : list of Species
+ Species to which global mechanisms should be applied.
+ compartment : Compartment, optional
+ Compartment for newly generated species and reactions.
+
+ Returns
+ -------
+ tuple of (list of Species, list of Reaction)
+ New species and reactions generated by global mechanisms.
+
+ Notes
+ -----
+ Global mechanisms are typically used for operations that affect all
+ species uniformly, such as dilution, global degradation, or
+ compartment transport.
+ """
global_mech_species = []
global_mech_reactions = []
if self.global_mechanisms:
@@ -595,8 +965,34 @@ def apply_global_mechanisms(
def component_enumeration(
self, comps_to_enumerate=None, recursion_depth=10
) -> List[Component]:
- # Components that produce components through Component Enumeration
+ """Recursively enumerate components to generate derived components.
+
+ Calls each component's `enumerate_components` method repeatedly to
+ expand high-level components into their constituent parts (e.g.,
+ DNA_construct --> RNA_construct --> Protein).
+
+ Parameters
+ ----------
+ comps_to_enumerate : list of Component, optional
+ Initial components to enumerate. If None, uses all components in
+ the mixture.
+ recursion_depth : int, default=10
+ Maximum number of enumeration iterations. Prevents infinite
+ recursion.
+
+ Returns
+ -------
+ list of Component
+ All components including the original components and all derived
+ components generated through enumeration.
+
+ Warns
+ -----
+ UserWarning
+ Warns if unenumerated components remain after reaching the
+ recursion depth limit.
+ """
all_components = []
new_components = []
if comps_to_enumerate is None:
@@ -629,7 +1025,34 @@ def component_enumeration(
def global_component_enumeration(
self, comps_to_enumerate=None, recursion_depth=None
) -> List[Component]:
- """Components that produce other components infinitely."""
+ """Apply global component enumerators to generate new components.
+
+ Global component enumerators create new components based on patterns
+ across all components (e.g., generating all pairwise binding
+ interactions between proteins).
+
+ Parameters
+ ----------
+ comps_to_enumerate : list of Component, optional
+ Initial components to pass to enumerators. If None, uses all
+ components in the mixture.
+ recursion_depth : int, optional
+ Maximum number of enumeration iterations. If None, uses
+ `self.global_recursion_depth`.
+
+ Returns
+ -------
+ list of Component
+ All components including original and newly generated components
+ from global enumeration.
+
+ Notes
+ -----
+ This method is called during `compile_crn` before local component
+ enumeration. Global enumerators are useful for creating complex
+ interaction networks without manually specifying every interaction.
+
+ """
if recursion_depth is None:
recursion_depth = self.global_recursion_depth
@@ -683,24 +1106,87 @@ def compile_crn(
add_reaction_species: bool = True,
compartment: Compartment = None,
) -> ChemicalReactionNetwork:
- """Create chemical reaction network from mixture species, reactions.
-
- :param initial_concentration_dict: a dictionary to overwrite initial
- concentrations at the end of compile time
- :param recursion_depth: how deep to run the Local and
- Global Component Enumeration
- :param return_enumerated_components: returns a list of all enumerated
- components along with the CRN
- :param initial_concentrations_at_end: if True does not look in
- Components for Species' initial concentrations and only checks
- the Mixture database at the end.
- :param copy_objects: Species and Reactions will be copied when placed
- into the CRN. Protects CRN validity at the expense of compilation
+ """Compile a chemical reaction network from the mixture.
+
+ Enumerates components, generates species and reactions from each
+ component, applies global mechanisms, and returns a complete CRN.
+
+ Parameters
+ ----------
+ recursion_depth : int, optional
+ Maximum recursion depth for both local and global component
+ enumeration. If None, uses `self.global_recursion_depth`.
+ initial_concentration_dict : dict, optional
+ Dictionary mapping species to initial concentrations. This
+ overrides all other initial concentration settings and is applied
+ at the very end of compilation.
+ return_enumerated_components : bool, default=False
+ If True, returns a tuple of (CRN, enumerated_components) instead
+ of just the CRN.
+ initial_concentrations_at_end : bool, default=False
+ If True, initial concentrations are only set at the end using the
+ mixture's parameter database, ignoring component-specific initial
+ concentrations during compilation.
+ copy_objects : bool, default=True
+ If True, species and reactions are deep-copied when added to the
+ CRN. Protects CRN validity at the expense of compilation speed.
+ add_reaction_species : bool, default=True
+ If True, species appearing in reactions are automatically added to
+ the CRN. Ensures no missing species at the expense of compilation
speed.
- :param add_reaction_species: Species inside reactions will be
- added to the CRN. Ensures no missing species at the expense
- of compilation speed.
- :return: ChemicalReactionNetwork
+ compartment : Compartment, optional
+ Compartment to assign to all species and reactions that are not
+ already assigned to a compartment. If None, uses
+ `self.compartment`.
+
+ Returns
+ -------
+ ChemicalReactionNetwork or tuple
+ If `return_enumerated_components` is False, returns the compiled
+ `ChemicalReactionNetwork`. If True, returns a tuple of
+ (ChemicalReactionNetwork, list of enumerated Components).
+
+ Notes
+ -----
+ The compilation process follows these steps:
+
+ 1. Add any directly-added species to the CRN
+ 2. Global component enumeration (generates component interactions)
+ 3. Local component enumeration (e.g., DNA --> RNA --> Protein)
+ 4. Generate species from all enumerated components
+ 5. Generate reactions from all enumerated components
+ 6. Apply global mechanisms to all species
+ 7. Set initial concentrations
+
+ Examples
+ --------
+ Basic compilation:
+
+ >>> gene = bcp.DNAassembly(
+ ... 'GFP', promoter='pconst', rbs='RBS', protein='GFP')
+ >>> mixture = bcp.Mixture(
+ ... name="txtl_extract",
+ ... components=[gene],
+ ... mechanisms={
+ ... 'transcription': bcp.SimpleTranscription(),
+ ... 'translation': bcp.SimpleTranslation()
+ ... },
+ ... parameters={'ktx': 0.05, 'ktl': 0.01}
+ ... )
+ >>> crn = mixture.compile_crn()
+
+ Compilation with custom initial concentrations:
+
+ >>> crn = mixture.compile_crn(
+ ... initial_concentration_dict={gene.dna: 1, gene.transcript: 50}
+ ... )
+
+ Get both CRN and enumerated components:
+
+ >>> crn, components = mixture.compile_crn(
+ ... return_enumerated_components=True
+ ... )
+
"""
resetwarnings()
@@ -710,7 +1196,7 @@ def compile_crn(
self.crn = ChemicalReactionNetwork([], [])
# helper to flatten & drop duplicates via ==, retagging
- # default→compartment
+ # default -> compartment
def _filter_new_by_eq(cands, existing_list):
# flatten nested lists
flat = []
diff --git a/biocrnpyler/core/parameter.py b/biocrnpyler/core/parameter.py
index 407981c4..88363897 100644
--- a/biocrnpyler/core/parameter.py
+++ b/biocrnpyler/core/parameter.py
@@ -10,38 +10,39 @@
"""Parameter processing module.
-#### Parameter Value Defaulting
-
-Not all parameters need to have the required headings. The only two
-required columns are "param_val" and "param_name". BioCRNpyler uses a form
-of parameter name defaulting discussed below to find default parameters if
-no exact match is in the config file. This makes it easy to set default
-parameters for things like "ku" and "ktx" to quickly build models.
-
-#### Parameters inside BioCRNpyler:
-
-Inside of bioCRNpyler, parameters are stored as a dictionary key value
-pair: (mechanism_name, part_id, param_name) --> param_val. If that
-particular parameter key cannot be found, the software will default to the
-following keys: (mechanism_type, part_id, param_name) >> (part_id,
-param_name) >> (mechanism_name, param_name) >> (mechanism_type, param_name)
->> (param_name) and give a warning. As a note, mechanism_name refers to
-the .name variable of a Mechanism. mechanism_type refers to the .type
-variable of a Mechanism. Either of these can be used as a
-mechanism_id. This allows for models to be constructed easily using default
-parameter values and for parameters to be shared between different
-Mechanisms and/or Components.
-
-#### Initial Conditions are also Parameters
-
-The initial condition of any Species (or Component) will also be looked up
+*Parameter Value Defaulting*
+
+Not all parameters need to have the required headings. The only two required
+columns are 'param_val' and 'param_name'. BioCRNpyler uses a form of
+parameter name defaulting discussed below to find default parameters if no
+exact match is in the config file. This makes it easy to set default
+parameters for things like 'ku' and 'ktx' to quickly build models.
+
+*Parameters inside BioCRNpyler*
+
+Inside of bioCRNpyler, parameters are stored as a dictionary key value pair:
+`(mechanism_name, part_id, param_name) --> param_val`. If that particular
+parameter key cannot be found, the software will default to the following
+keys: `(mechanism_type, part_id, param_name)` >> `(part_id, param_name)` >>
+`(mechanism_name, param_name)` >> `(mechanism_type, param_name)` >>
+`(param_name)` and give a warning. As a note, `mechanism_name` refers to the
+`.name` variable of a `Mechanism`, and `mechanism_type` refers to the `.type`
+variable of a `Mechanism`. Either of these can be used as a
+`mechanism_id`. This allows for models to be constructed easily using default
+parameter values and for parameters to be shared between different mechanisms
+and/or components.
+
+Units are read directly read from the column labeled "units" in the
+parameter file.
+
+*Initial Conditions are also Parameters*
+
+The initial condition of any `Species` (or `Component`) will also be looked up
as a parameters automatically. Initial conditions can be customized in
-through the custom_initial_conditions keyword in the Mixture constructor.
-custom_initial_conditions will take precedent to parameter initial
-conditions.
+through the `custom_initial_conditions` keyword in the `Mixture` constructor.
+The `custom_initial_conditions` keyword will take precedent over parameter
+initial conditions.
-#### Units are read directly read from the column labeled "units" in the
-#### parameter file.
"""
import csv
@@ -53,22 +54,103 @@
# This could later be extended
ParameterKey = namedtuple('ParameterKey', 'mechanism part_id name')
+"""Named tuple defining a parameter key.
+
+ Parameters
+ ----------
+ mechanism : str, Mechanism, or None
+ Mechanism identifier. Can be a string (used as both name and type), a
+ Mechanism object (uses .name and .mechanism_type), or None.
+ part_id : str or None
+ Part/component identifier for the parameter.
+ name : str
+ Name of the parameter. Must start with a letter and contain at
+ least one character.
+
+"""
class Parameter(object):
+ """Base class for representing parameters in BioCRNpyler.
+
+ Parameters represent kinetic constants, initial concentrations, and
+ other numerical values used in chemical reaction networks. This class
+ provides validation for parameter names, values, and units.
+
+ Parameters
+ ----------
+ parameter_name : str
+ Name of the parameter. Must start with a letter and contain at
+ least one character.
+ parameter_value : float or str
+ Value of the parameter. Can be a number or a string in formats:
+ '1.00', '1e4', or '2/5' (rational). Strings are automatically
+ converted to numerical values.
+ unit : str, optional
+ Unit of the parameter (e.g., '1/s', 'nM', 'molecules'). If None,
+ defaults to empty string.
+
+ Attributes
+ ----------
+ parameter_name : str
+ The validated name of the parameter.
+ value : float
+ The numerical value of the parameter.
+ unit : str
+ The unit string associated with the parameter.
+
+ See Also
+ --------
+ ParameterEntry : Parameter with database lookup keys.
+ ModelParameter : Parameter with search and found keys for defaulting.
+ ParameterDatabase : Database for storing and retrieving parameters.
+
+ Notes
+ -----
+ This is the base class for all parameter types in BioCRNpyler. In
+ practice, subclasses `ParameterEntry` and `ModelParameter` are more
+ commonly used as they support the parameter lookup and defaulting
+ system.
+
+ Parameter values provided as strings are automatically converted:
+
+ - '1e4' --> 10000.0
+ - '2/5' --> 0.4
+ - '1.23' --> 1.23
+
+ Examples
+ --------
+ Create a basic parameter:
+
+ >>> param = bcp.Parameter('kb', 100.0, unit='1/s')
+ >>> param.parameter_name
+ 'kb'
+ >>> param.value
+ 100.0
+
+ Create a parameter from a string value:
+
+ >>> param = bcp.Parameter('ku', '1e-4', unit='1/s')
+ >>> param.value
+ 0.0001
+
+ Create a parameter from a rational string:
+
+ >>> param = bcp.Parameter('ratio', '3/4')
+ >>> param.value
+ 0.75
+
+ """
+
def __init__(
self,
parameter_name: str,
parameter_value: Union[str, numbers.Real],
unit=None,
):
- """A class for representing parameters in general.
-
- Only the below subclasses are ever used.
+ """Initialize a Parameter object.
- :param parameter_name: is the name of the parameter
- :param parameter_value: is the value of the parameter
- :param unit: is the unit of the parameter or a species
+ See class docstring for parameter descriptions.
"""
self.parameter_name = parameter_name
@@ -77,10 +159,26 @@ def __init__(
@property
def parameter_name(self) -> str:
+ """str: The name of the parameter."""
return self._parameter_name
@parameter_name.setter
def parameter_name(self, new_parameter_name: str):
+ """Set the parameter name with validation.
+
+ Parameters
+ ----------
+ new_parameter_name : str
+ New name for the parameter. Must be a string starting with a
+ letter (not a number).
+
+ Raises
+ ------
+ ValueError
+ If `new_parameter_name` is not a string, or if it doesn't start
+ with a letter.
+
+ """
if not isinstance(new_parameter_name, str):
raise ValueError(
f"parameter_name must be a string: "
@@ -96,10 +194,26 @@ def parameter_name(self, new_parameter_name: str):
@property
def value(self) -> numbers.Real:
+ """float: The numerical value of the parameter."""
return self._value
@value.setter
def value(self, new_parameter_value: Union[str, numbers.Real]):
+ """Set the parameter value with validation and conversion.
+
+ Parameters
+ ----------
+ new_parameter_value : float or str
+ New value for the parameter. If a string, must be in format
+ '1.00', '1e4', or '2/5'. Strings are automatically converted
+ to float.
+
+ Raises
+ ------
+ ValueError
+ If `new_parameter_value` is not a number or valid string format.
+
+ """
if not (
isinstance(new_parameter_value, numbers.Real)
or isinstance(new_parameter_value, str)
@@ -129,10 +243,24 @@ def value(self, new_parameter_value: Union[str, numbers.Real]):
@property
def unit(self) -> str:
+ """str: The unit string for the parameter."""
return self._unit
@unit.setter
def unit(self, new_unit: str):
+ """Set the parameter unit.
+
+ Parameters
+ ----------
+ new_unit : str or None
+ Unit string for the parameter. If None, sets to empty string.
+
+ Raises
+ ------
+ ValueError
+ If `new_unit` is not a string or None.
+
+ """
if new_unit is None:
self._unit = ''
elif not isinstance(new_unit, str):
@@ -144,6 +272,23 @@ def unit(self, new_unit: str):
@staticmethod
def _convert_rational(p_value: str) -> numbers.Real:
+ """Convert a string parameter value to a numerical value.
+
+ Handles rational fractions (e.g., '2/5') and standard float
+ strings (e.g., '1.23' or '1e4').
+
+ Parameters
+ ----------
+ p_value : str
+ String representation of parameter value. Can be a fraction
+ ('2/5') or standard float format ('1.23', '1e4').
+
+ Returns
+ -------
+ float
+ Numerical value of the parameter.
+
+ """
if '/' in p_value:
nom, denom = p_value.split('/')
return float(nom) / float(denom)
@@ -151,6 +296,25 @@ def _convert_rational(p_value: str) -> numbers.Real:
return float(p_value)
def __eq__(self, other):
+ """Test equality between parameters or parameter and number.
+
+ Parameters
+ ----------
+ other : Parameter or float
+ Object to compare with. Can be another Parameter object or a
+ numerical value.
+
+ Returns
+ -------
+ bool
+ True if values are equal, False otherwise.
+
+ Raises
+ ------
+ TypeError
+ If `other` cannot be compared (not a Parameter or number).
+
+ """
if isinstance(other, Parameter):
return self.value == other.value
else:
@@ -162,25 +326,100 @@ def __eq__(self, other):
)
def __str__(self):
+ """Return string representation of the parameter.
+
+ Returns
+ -------
+ str
+ String in format 'Parameter = '.
+
+ """
return f"Parameter {self.parameter_name} = {self.value}"
def __hash__(self):
+ """Return hash value for the parameter.
+
+ Returns
+ -------
+ int
+ Hash value based on parameter name, value, and unit.
+
+ """
return (
hash(self._parameter_name) + hash(self._value) + hash(self._unit)
)
class ParameterEntry(Parameter):
- """Parameter stored the ParameterDatabase.
-
- parameter_keys is a dictionary {key:value} or named_tuple (type
- ParameterKey) of keys for looking up the parameter.
-
- parameter_info is a dictionary {key:value} of additional
- information about the parameter.
-
- For example: additional columns in the parameter file or the
- parameter file name.
+ """Parameter with database lookup key and metadata.
+
+ A `ParameterEntry` extends `Parameter` with a lookup key for database
+ storage and retrieval, plus additional metadata about the parameter's
+ origin and context.
+
+ Parameters
+ ----------
+ parameter_name : str
+ Name of the parameter.
+ parameter_value : float or str
+ Value of the parameter.
+ parameter_key : dict, ParameterKey, str, or None, optional
+ Lookup key for the parameter database.
+ parameter_info : dict, optional
+ Additional metadata about the parameter (e.g., source file,
+ comments). If dict contains 'unit' key, it will update the
+ parameter's unit.
+ **kwargs
+ Additional keyword arguments passed to Parameter constructor,
+ including 'unit'.
+
+ Attributes
+ ----------
+ parameter_key : ParameterKey
+ The lookup key as a named tuple (mechanism, part_id, name).
+ parameter_info : dict
+ Dictionary of additional parameter metadata.
+
+ See Also
+ --------
+ Parameter : Base parameter class.
+ ModelParameter : Parameter with search and found keys.
+ ParameterDatabase : Database for storing parameter entries.
+
+ Notes
+ -----
+ The `parameter_key` value can be any of the following:
+
+ - dict: {'mechanism': ..., 'part_id': ..., 'name': ...}
+ - ParameterKey namedtuple: (mechanism, part_id, name)
+ - str: parameter name (other fields set to None)
+ - None: creates key with all fields None except name
+
+ using the following conventions:
+
+ - mechanism: str or None (mechanism name or type)
+ - part_id: str or None (component/part identifier)
+ - name: str (parameter name)
+
+ These keys enable flexible parameter lookup with defaulting behavior
+ in the ParameterDatabase.
+
+ Examples
+ --------
+ Create a parameter entry with full key:
+
+ >>> entry = bcp.ParameterEntry(
+ ... 'kb',
+ ... 100.0,
+ ... parameter_key={'mechanism': 'binding', 'part_id': 'promoter1'},
+ ... unit='1/s'
+ ... )
+
+ Create a parameter entry with just a name:
+
+ >>> entry = bcp.ParameterEntry('ku', 0.01, parameter_key='ku')
+ >>> entry.parameter_key
+ ParameterKey(mechanism=None, part_id=None, name='ku')
"""
@@ -192,6 +431,11 @@ def __init__(
parameter_info=None,
**kwargs,
):
+ """Initialize a ParameterEntry object.
+
+ See class docstring for parameter descriptions.
+
+ """
Parameter.__init__(self, parameter_name, parameter_value, **kwargs)
self.parameter_key = parameter_key
@@ -202,6 +446,47 @@ def __init__(
def create_parameter_key(
new_key: Union[Dict, ParameterKey, str], parameter_name=None
) -> ParameterKey:
+ """Convert various input types to a ParameterKey namedtuple.
+
+ Parameters
+ ----------
+ new_key : dict, ParameterKey, tuple, str, or None
+ Input to convert to ParameterKey:
+
+ - dict: Must have keys matching ParameterKey fields
+ - ParameterKey: Returned as-is
+ - 3-tuple: Converted to ParameterKey with proper field mapping
+ - str: Used as 'name', other fields set to None
+ - None: All fields set to None (requires parameter_name)
+
+ parameter_name : str, optional
+ Parameter name to use if not specified in new_key. Overrides
+ `name` field if provided in dict.
+
+ Returns
+ -------
+ ParameterKey
+ Named tuple with fields (mechanism, part_id, name).
+
+ Raises
+ ------
+ ValueError
+ If `new_key` is not a valid type or format.
+
+ Examples
+ --------
+ >>> key = bcp.ParameterEntry.create_parameter_key('kb')
+ >>> key
+ ParameterKey(mechanism=None, part_id=None, name='kb')
+
+ >>> key = bcp.ParameterEntry.create_parameter_key(
+ ... {'mechanism': 'transcription', 'part_id': 'prom1'},
+ ... parameter_name='ktx'
+ ... )
+ >>> key
+ ParameterKey(mechanism='transcription', part_id='prom1', name='ktx')
+
+ """
# New Key can be a named_tuple
if isinstance(new_key, dict):
new_key = dict(new_key)
@@ -240,20 +525,46 @@ def create_parameter_key(
@property
def parameter_key(self) -> ParameterKey:
+ """ParameterKey: The database lookup key for this parameter."""
return self._parameter_key
@parameter_key.setter
def parameter_key(self, parameter_key: Union[Dict, ParameterKey, str]):
+ """Set the parameter lookup key.
+
+ Parameters
+ ----------
+ parameter_key : dict, ParameterKey, str, or None
+ New parameter key. Automatically converted to ParameterKey
+ namedtuple using `create_parameter_key`.
+
+ """
self._parameter_key = self.create_parameter_key(
parameter_key, self.parameter_name
)
@property
def parameter_info(self) -> Dict:
+ """dict: Additional metadata about the parameter."""
return self._parameter_info
@parameter_info.setter
def parameter_info(self, parameter_info: Dict):
+ """Set parameter metadata.
+
+ Parameters
+ ----------
+ parameter_info : dict or None
+ Dictionary of additional parameter information. If dict
+ contains 'unit' key, updates the parameter's unit attribute.
+
+ Raises
+ ------
+ ValueError
+ If `parameter_info` is not None or a dict, or if 'unit' in
+ dict conflicts with existing unit.
+
+ """
if parameter_info is None:
self._parameter_info = {}
elif isinstance(parameter_info, dict):
@@ -280,6 +591,17 @@ def parameter_info(self, parameter_info: Dict):
)
def get_sbml_id(self):
+ """Generate SBML-compatible identifier for the parameter.
+
+ Constructs an identifier string from the parameter key fields,
+ formatted as: '__'.
+
+ Returns
+ -------
+ str
+ SBML-compatible identifier string.
+
+ """
sbml_id = self.parameter_key.name + '_'
if self.parameter_key.part_id is not None:
sbml_id += self.parameter_key.part_id
@@ -289,17 +611,86 @@ def get_sbml_id(self):
return sbml_id
def __str__(self):
- return f"ParameterEntry({self.parameter_key}) = {self.value}"
+ """Return string representation of the parameter entry.
+ Returns
+ -------
+ str
+ String in format 'ParameterEntry() = '.
-class ModelParameter(ParameterEntry):
- """A class for representing parameters used in the Model.
+ """
+ return f"ParameterEntry({self.parameter_key}) = {self.value}"
- search_key is a tuple searched for to find the parameter, eg
- (mech_id, part_id, param_name),
- found_key is the tuple used after defaulting to find the
- parameter eg (param_name)
+class ModelParameter(ParameterEntry):
+ """Parameter with search and found keys for defaulting behavior.
+
+ A `ModelParameter` extends `ParameterEntry` with information about how
+ the parameter was looked up in the database. It tracks both the
+ original search key and the actual key where the parameter was found,
+ enabling parameter defaulting and debugging.
+
+ Parameters
+ ----------
+ parameter_name : str
+ Name of the parameter.
+ parameter_value : float or str
+ Value of the parameter.
+ search_key : dict, ParameterKey, tuple, or str
+ The key originally searched for in the database. Usually includes
+ mechanism, part_id, and name.
+ found_key : dict, ParameterKey, tuple, or str
+ The key where the parameter was actually found after defaulting.
+ May have fewer fields than search_key.
+ unit : str, optional
+ Unit of the parameter.
+ parameter_key : dict, ParameterKey, str, or None, optional
+ Database lookup key (inherited from ParameterEntry).
+ parameter_info : dict, optional
+ Additional metadata (inherited from ParameterEntry).
+ **kwargs
+ Additional keyword arguments passed to ParameterEntry constructor.
+
+ Attributes
+ ----------
+ search_key : ParameterKey
+ The original lookup key as a named tuple.
+ found_key : ParameterKey
+ The key where parameter was found as a named tuple.
+
+ See Also
+ --------
+ Parameter : Base parameter class.
+ ParameterEntry : Parameter with database key.
+ ParameterDatabase : Database with parameter defaulting.
+
+ Notes
+ -----
+ The parameter defaulting hierarchy is:
+
+ 1. (mechanism_name, part_id, param_name)
+ 2. (mechanism_type, part_id, param_name)
+ 3. (None, part_id, param_name)
+ 4. (mechanism_name, None, param_name)
+ 5. (mechanism_type, None, param_name)
+ 6. (None, None, param_name)
+
+ The `search_key` shows what was requested, while `found_key` shows
+ which level of defaulting was used. This information is useful for
+ debugging parameter lookups.
+
+ Examples
+ --------
+ Create a model parameter showing search and found keys:
+
+ >>> model_param = bcp.ModelParameter(
+ ... 'kb',
+ ... 100.0,
+ ... search_key={'mechanism': 'binding', 'part_id': 'prom1'},
+ ... found_key={'mechanism': 'binding', 'part_id': None},
+ ... unit='1/s'
+ ... )
+ >>> # Shows parameter was found using mechanism-level default
"""
@@ -314,6 +705,11 @@ def __init__(
parameter_info=None,
**kwargs,
):
+ """Initialize a ModelParameter object.
+
+ See class docstring for parameter descriptions.
+
+ """
ParameterEntry.__init__(
self,
parameter_name,
@@ -328,44 +724,167 @@ def __init__(
@property
def search_key(self):
+ """ParameterKey: The key originally searched for in database."""
return self._search_key
@search_key.setter
def search_key(self, search_key):
+ """Set the search key.
+
+ Parameters
+ ----------
+ search_key : dict, ParameterKey, tuple, or str
+ The key that was searched for. Automatically converted to
+ ParameterKey namedtuple.
+
+ """
self._search_key = self.create_parameter_key(
search_key, self.parameter_name
)
@property
def found_key(self):
+ """ParameterKey: The key where parameter was actually found."""
return self._found_key
@found_key.setter
def found_key(self, found_key):
+ """Set the found key.
+
+ Parameters
+ ----------
+ found_key : dict, ParameterKey, tuple, or str
+ The key where the parameter was found after defaulting.
+ Automatically converted to ParameterKey namedtuple.
+
+ """
self._found_key = self.create_parameter_key(
found_key, self.parameter_name
)
def __str__(self):
+ """Return string representation of the model parameter.
+
+ Returns
+ -------
+ str
+ String showing parameter key, value, and search key in format
+ 'ModelParameter() = search_key='.
+
+ """
return (
f"ModelParameter({self.parameter_key}) = "
- + f"{self.value}\tsearch_key={self.search_key}"
+ + f"{self.value}\n search_key={self.search_key}"
)
class ParameterDatabase(object):
+ """Database for storing and retrieving parameters with defaulting.
+
+ A `ParameterDatabase` stores parameters with flexible lookup keys that
+ enable parameter defaulting based on mechanism, part_id, and parameter
+ name. Parameters can be loaded from dictionaries, files, or other
+ databases.
+
+ Parameters
+ ----------
+ parameter_dictionary : dict, optional
+ Dictionary of parameters to load. Keys should be ParameterKey-like
+ (dict, tuple, or str) and values should be numerical or Parameter
+ objects.
+ parameter_file : str or list of str, optional
+ Path(s) to parameter file(s) to load. Files must be tab-separated
+ (.tsv, .txt) or comma-separated (.csv).
+ overwrite_parameters : bool, default=False
+ If True, allows overwriting existing parameters when loading. If
+ False, raises ValueError if duplicate keys are encountered.
+
+ Attributes
+ ----------
+ parameters : dict
+ Internal dictionary mapping ParameterKey to ParameterEntry objects.
+ Access via indexing or iteration rather than directly.
+
+ See Also
+ --------
+ Parameter : Base parameter class.
+ ParameterEntry : Parameter with database key.
+ ModelParameter : Parameter with search and found keys.
+
+ Notes
+ -----
+ When searching for a parameter with `find_parameter(mechanism,
+ part_id, param_name)`, the database searches in this order:
+
+ 1. (mechanism.name, part_id, param_name)
+ 2. (mechanism.type, part_id, param_name)
+ 3. (None, part_id, param_name)
+ 4. (mechanism.name, None, param_name)
+ 5. (mechanism.type, None, param_name)
+ 6. (None, None, param_name)
+
+ This enables flexible parameter specification where specific parameters
+ override more general ones.
+
+ Parameter files should have these columns (column names are flexible):
+
+ - 'param_name' or 'parameter_name' (required)
+ - 'param_val' or 'value' (required)
+ - 'mechanism_id' or 'mechanism' (optional)
+ - 'part_id' or 'part' (optional)
+ - 'units' or 'unit' (optional)
+
+ Additional columns are stored in parameter_info.
+
+ Parameter files are searched for in the following directories:
+
+ 1. The current directory
+ 2. All directories listed in the 'BCP_PATH' environment variable
+ 3. The BioCRNpyler source code directory
+
+ The directories are search in this order and the first parameter file that
+ is found is returned. For files in the BioCRNpyler source code directory,
+ common filename patterns are of the form '/_parameters.tsv'
+ where is 'components', 'mechanisms', or 'mixtures'.
+
+ Examples
+ --------
+ Create a parameter database from a dictionary:
+
+ >>> params = {
+ ... 'kb': 100.0,
+ ... 'ku': 0.01,
+ ... ('transcription', None, 'ktx'): 0.05
+ ... }
+ >>> db = bcp.ParameterDatabase(parameter_dictionary=params)
+
+ Load parameters from a file:
+
+ >>> db = bcp.ParameterDatabase(
+ ... parameter_file='mixtures/pure_parameters.tsv')
+
+ Look up a parameter with defaulting:
+
+ >>> param = db.find_parameter('transcription', 'promoter1', 'ktx')
+ >>> param.value
+ 0.05
+
+ Add a new parameter:
+
+ >>> db.add_parameter('kcat', 10.0,
+ ... parameter_key={'mechanism': 'catalysis', 'part_id': 'enzyme1'})
+
+ """
+
def __init__(
self,
parameter_dictionary=None,
parameter_file=None,
overwrite_parameters=False,
):
- """A class for storing parameters in Components and Mixtures.
+ """Initialize a ParameterDatabase object.
- :param parameter_dictionary:
- :param parameter_file:
- :param overwrite_parameters: whether to overwrite existing entries
- in the parameter database
+ See class docstring for parameter descriptions.
"""
self.parameters = {} # create an emtpy dictionary to get parameters.
@@ -403,6 +922,21 @@ def __init__(
# To check if a key or ParameterEntry is in a the ParameterDatabase
def __contains__(self, val):
+ """Check if a key or ParameterEntry is in the database.
+
+ Parameters
+ ----------
+ val : ParameterEntry, dict, ParameterKey, tuple, or str
+ Value to check. Can be a ParameterEntry object or any valid
+ parameter key format.
+
+ Returns
+ -------
+ bool
+ True if the key exists in the database (and ParameterEntry
+ values match if val is a ParameterEntry), False otherwise.
+
+ """
if isinstance(val, ParameterEntry):
key = val.parameter_key
if key in self.parameters and self.parameters[key] == val:
@@ -419,11 +953,32 @@ def __contains__(self, val):
# Ability to loop through parameters eg
# for entry in ParameterDatabase: ...
def __iter__(self):
+ """Initialize iterator over parameter entries.
+
+ Returns
+ -------
+ ParameterDatabase
+ Self with iterator state initialized.
+
+ """
self.keys = list(self.parameters.keys())
self.current_key_ind = 0
return self
def __next__(self):
+ """Get next parameter entry in iteration.
+
+ Returns
+ -------
+ ParameterEntry
+ Next parameter entry in the database.
+
+ Raises
+ ------
+ StopIteration
+ When all parameters have been iterated.
+
+ """
if self.current_key_ind < len(self.keys):
key = self.keys[self.current_key_ind]
entry = self.parameters[key]
@@ -434,17 +989,64 @@ def __next__(self):
# Length method
def __len__(self):
+ """Return number of parameters in the database.
+
+ Returns
+ -------
+ int
+ Number of parameter entries stored.
+
+ """
return len(self.parameters)
# Gets a parameter from the database
# Only returns exact matches.
def __getitem__(self, key):
+ """Get a parameter by exact key match.
+
+ Parameters
+ ----------
+ key : dict, ParameterKey, tuple, or str
+ Parameter key to look up. No defaulting is performed.
+
+ Returns
+ -------
+ ParameterEntry
+ The parameter entry with the exact matching key.
+
+ Raises
+ ------
+ KeyError
+ If the exact key is not found in the database.
+
+ """
param_key = ParameterEntry.create_parameter_key(key)
return self.parameters[param_key]
# Sets a parameter in the databases - useful for quickly changing
# parameters, but add_parameter is recommended.
def __setitem__(self, parameter_key, value):
+ """Set a parameter value by key.
+
+ Parameters
+ ----------
+ parameter_key : dict, ParameterKey, tuple, or str
+ Key for the parameter.
+ value : float, str, or ParameterEntry
+ New value or ParameterEntry object. If ParameterEntry, its key
+ must match parameter_key.
+
+ Raises
+ ------
+ ValueError
+ If value is ParameterEntry with mismatched key.
+
+ Notes
+ -----
+ This method automatically overwrites existing parameters.
+ For more control, use `add_parameter` instead.
+
+ """
key = ParameterEntry.create_parameter_key(parameter_key)
if isinstance(value, ParameterEntry):
@@ -465,6 +1067,14 @@ def __setitem__(self, parameter_key, value):
)
def __str__(self):
+ """Return string representation of the parameter database.
+
+ Returns
+ -------
+ str
+ String listing all parameters in the database.
+
+ """
txt = 'ParameterDatabase:'
param_txt = '\n'.join([repr(p) for p in self.parameters])
return txt + param_txt
@@ -478,16 +1088,39 @@ def add_parameter(
parameter_info=None,
overwrite_parameters=False,
):
- """Adds a parameter to the database with appropriate metadata.
-
- :param parameter_name: the name of the parameter
- :param parameter_value: the value of the parameter
- :param parameter_origin:
- :param parameter_key:
- :param parameter_info:
- :param overwrite_parameters: whether to overwrite existing entries
- in the parameter database
- :return:
+ """Add a parameter to the database.
+
+ Parameters
+ ----------
+ parameter_name : str
+ Name of the parameter.
+ parameter_value : float or str
+ Value of the parameter. Strings are converted to float.
+ parameter_origin : str, optional
+ Description of where the parameter came from (e.g., filename).
+ Stored in `parameter_info`.
+ parameter_key : dict, ParameterKey, str, or None, optional
+ Lookup key for the parameter. If None, creates key with only
+ the parameter name.
+ parameter_info : dict, optional
+ Additional metadata about the parameter. `parameter_origin` is
+ added to this dict if provided.
+ overwrite_parameters : bool, default=False
+ If True, allows overwriting existing parameters. If False,
+ raises ValueError if key already exists.
+
+ Raises
+ ------
+ ValueError
+ If key already exists in database and
+ `overwrite_parameters=False`.
+
+ Examples
+ --------
+ >>> db = bcp.ParameterDatabase()
+ >>> db.add_parameter('kb', 100.0)
+ >>> db.add_parameter('ku', 0.01,
+ ... parameter_key={'mechanism': 'binding'})
"""
# Put parameter origin into parameter_info
@@ -520,12 +1153,30 @@ def load_parameters_from_dictionary(
parameter_dictionary: Dict[ParameterKey, Union[str, numbers.Real]],
overwrite_parameters=False,
) -> None:
- """Loads Parameters from a parameter dictionary.
-
- :param parameter_dictionary: Dictionary with keys ParameterKey types
- and values with real numbers
- :param overwrite_parameters: whether to overwrite existing entries
- in the parameter database
+ """Load parameters from a dictionary.
+
+ Parameters
+ ----------
+ parameter_dictionary : dict
+ Dictionary with keys as ParameterKey-like objects (dict, tuple,
+ or str) and values as numerical values or strings.
+ overwrite_parameters : bool, default=False
+ If True, allows overwriting existing parameters. If False,
+ raises ValueError if duplicate keys are encountered.
+
+ Raises
+ ------
+ ValueError
+ If duplicate keys exist and `overwrite_parameters=False`.
+
+ Examples
+ --------
+ >>> db = bcp.ParameterDatabase()
+ >>> params = {
+ ... 'kb': 100.0,
+ ... ('binding', None, 'ku'): 0.01,
+ ... }
+ >>> db.load_parameters_from_dictionary(params)
"""
for k in parameter_dictionary:
@@ -544,11 +1195,30 @@ def load_parameters_from_dictionary(
def load_parameters_from_database(
self, parameter_database, overwrite_parameters=False
) -> None:
- """Loads parameters from another ParameterDatabase.
-
- :param parameter_database: instance of another ParameterDatabase
- :param overwrite_parameters: whether to overwrite existing entries
- in the parameter database.
+ """Load parameters from another `ParameterDatabase`.
+
+ Parameters
+ ----------
+ parameter_database : ParameterDatabase
+ Another ParameterDatabase instance to copy parameters from.
+ overwrite_parameters : bool, default=False
+ If True, allows overwriting existing parameters. If False,
+ raises ValueError if duplicate keys are encountered.
+
+ Raises
+ ------
+ TypeError
+ If `parameter_database` is not a ParameterDatabase instance.
+ ValueError
+ If duplicate keys exist and `overwrite_parameters=False`.
+
+ Examples
+ --------
+ >>> db1 = bcp.ParameterDatabase(parameter_dictionary={'kb': 100.0})
+ >>> db2 = bcp.ParameterDatabase()
+ >>> db2.load_parameters_from_database(db1)
+ >>> db2['kb'].value
+ 100.0
"""
if not isinstance(parameter_database, ParameterDatabase):
@@ -572,14 +1242,60 @@ def load_parameters_from_database(
def load_parameters_from_file(
self, filename: str, overwrite_parameters=False
) -> None:
- """Loads parameters from a file to the ParameterDatabase.
+ """Load parameters from a file.
- Parameter files must be tab-separated (.tsv or .txt) or
- comma-separated (.csv) files!
+ Reads parameters from a CSV or TSV file and adds them to the
+ database. The file must have 'param_name' and 'param_val' columns.
+ Optional columns include 'mechanism', 'part_id', and 'units'.
- :param filename: name of the file (with valid file path)
- :param overwrite_parameters: whether to overwrite existing entries
- in the parameter database
+ Parameters
+ ----------
+ filename : str
+ Path to parameter file. Must be tab-separated (.tsv, .txt) or
+ comma-separated (.csv). File is searched in current directory
+ and BioCRNpyler package paths.
+ overwrite_parameters : bool, default=False
+ If True, allows overwriting existing parameters. If False,
+ raises ValueError if duplicate keys are encountered.
+
+ Raises
+ ------
+ ValueError
+ If file cannot be found, has invalid format, or contains
+ duplicate keys when `overwrite_parameters=False`.
+
+ Notes
+ -----
+ Accepted column names (case-sensitive, first match used):
+
+ - param_name: 'parameter_name', 'parameter', 'param', 'param_name'
+ - param_val: 'val', 'value', 'param_val', 'parameter_value'
+ - mechanism: 'mechanism', 'mechanism_id'
+ - part_id: 'part_id', 'part'
+ - unit: 'units', 'unit'
+
+ Additional columns are stored in parameter_info dictionary.
+
+ File Format Example (CSV)::
+
+ .. code::
+
+ mechanism,part_id,param_name,param_val,unit
+ binding,,kb,100,1/s
+ binding,,ku,0.01,1/s
+ transcription,prom1,ktx,0.05,1/s
+
+ Examples
+ --------
+ >>> db = bcp.ParameterDatabase(
+ ... parameter_file='mixtures/pure_parameters.tsv')
+
+ Load multiple files:
+
+ >>> db = bcp.ParameterDatabase(
+ ... parameter_file=[
+ ... 'mixtures/pure_parameters.tsv',
+ ... 'components/tetr_parameters.tsv'])
"""
from ..utils.fileutil import find_file_in_bcp_path
@@ -770,15 +1486,35 @@ def load_parameters_from_file(
def _get_field_names(
field_names: List[str], accepted_field_names: Dict[str, List[str]]
) -> Dict[str, str]:
- """Searches through valid field names and finds currently used one.
-
- It builds a dictionary of currently used field names.
-
- :param field_names: list of field names (columns) found in the
- csv file
- :param accepted_field_names: dictionary of possible field names and
- their valid aliases
- :return: dictionary of currently used field names (aliases)
+ """Map parameter file column names to standard field names.
+
+ Searches through column names in a parameter file to find which
+ valid aliases are being used, and creates a mapping dictionary.
+
+ Parameters
+ ----------
+ field_names : list of str
+ List of column names found in the parameter file.
+ accepted_field_names : dict
+ Dictionary mapping standard field names to lists of valid
+ aliases. Format: {'field': ['alias1', 'alias2', ...]}.
+
+ Returns
+ -------
+ dict
+ Dictionary mapping standard field names to the actual column
+ names used in the file. Fields not found are set to None.
+
+ Raises
+ ------
+ ValueError
+ If `field_names` or `accepted_field_names` are invalid types or
+ empty.
+
+ Warns
+ -----
+ UserWarning
+ If a standard field has no matching column in the file.
"""
if not isinstance(field_names, list):
@@ -824,24 +1560,84 @@ def _get_field_names(
return return_field_names
def find_parameter(self, mechanism, part_id, param_name):
- """Searches the database for the best matching parameter.
-
- Parameter defaulting hierarchy:
- (mechanism_name, part_id, param_name) --> param_val.
- If that particular parameter key cannot be found,
- the software will default to the following keys:
- (mechanism_type, part_id, param_name) >> (part_id, param_name) >>
- (mechanism_name, param_name) >> (mechanism_type, param_name) >>
- (param_name) and give a warning.
-
- As a note, mechanism_name refers to the .name variable of a
- Mechanism. mechanism_type refers to the .type variable of a
- Mechanism. Either of these can be used as a mechanism_id. This
- allows for models to be constructed easily using default parameter
- values and for parameters to be shared between different Mechanisms
- and/or Components.
+ """Search for a parameter with automatic defaulting.
+
+ Searches the database for the best matching parameter using a
+ hierarchical defaulting system. If an exact match is not found,
+ progressively more general keys are tried.
+
+ Parameters
+ ----------
+ mechanism : str, Mechanism, or None
+ Mechanism identifier. Can be a string (used as both name and
+ type), a Mechanism object (uses .name and .mechanism_type), or
+ None.
+ part_id : str or None
+ Part/component identifier for the parameter.
+ param_name : str
+ Name of the parameter to find.
+
+ Returns
+ -------
+ ModelParameter or None
+ ModelParameter object with search_key and found_key attributes
+ showing how the parameter was found. Returns None if no match
+ found at any defaulting level.
+
+ Raises
+ ------
+ ValueError
+ If `mechanism` is not a string, Mechanism object, or None.
+
+ Notes
+ -----
+ The method searches for parameters in this order:
+
+ 1. (mechanism.name, part_id, param_name)
+ 2. (mechanism.type, part_id, param_name)
+ 3. (None, part_id, param_name)
+ 4. (mechanism.name, None, param_name)
+ 5. (mechanism.type, None, param_name)
+ 6. (None, None, param_name)
+
+ This allows setting default parameters at various levels of
+ specificity. For example, a general 'kb' parameter can be
+ overridden for specific mechanisms or parts.
+
+ Examples
+ --------
+ >>> db = bcp.ParameterDatabase()
+ >>> db.add_parameter('kb', 100.0)
+ >>> db.add_parameter('kb', 200.0,
+ ... parameter_key={'mechanism': 'binding'})
+
+ General lookup finds the general parameter
+
+ >>> param = db.find_parameter(None, None, 'kb')
+ >>> param.value
+ 100.0
+
+ Mechanism-specific lookup finds the specific parameter
+
+ >>> param = db.find_parameter('binding', None, 'kb')
+ >>> param.value
+ 200.0
"""
+ # Parameter defaulting hierarchy:
+ # (mechanism_name, part_id, param_name) --> param_val.
+ # If that particular parameter key cannot be found,
+ # the software will default to the following keys:
+ # (mechanism_type, part_id, param_name) >> (part_id, param_name) >>
+ # (mechanism_name, param_name) >> (mechanism_type, param_name) >>
+ # (param_name) and give a warning.
+ #
+ # As a note, mechanism_name refers to the .name variable of a
+ # Mechanism. mechanism_type refers to the .type variable of a
+ # Mechanism. Either of these can be used as a mechanism_id. This
+ # allows for models to be constructed easily using default parameter
+ # values and for parameters to be shared between different Mechanisms
+ # and/or Components.
# this is imported here because otherwise there are import loops
from .mechanism import Mechanism
diff --git a/biocrnpyler/core/polymer.py b/biocrnpyler/core/polymer.py
index 62a49a53..958f96b1 100644
--- a/biocrnpyler/core/polymer.py
+++ b/biocrnpyler/core/polymer.py
@@ -1,10 +1,10 @@
"""Polymer support module.
-The classes OrderedPolymer and OrderedMonomer are datastructures used
-to represent Polymers and their associatd components.
+The classes `OrderedPolymer` and `OrderedMonomer` are data structures used
+to represent Polymers and their associated components.
-These classes are used by Chemical Reaction Network Species as well as certain
-Components such as DNA_construct.
+These classes are used by `ChemicalReactionNetwork` species as well as certain
+components such as DNA_construct.
"""
@@ -13,7 +13,47 @@
class MonomerCollection:
- """Collection of OrderedMonomers without any particular structure."""
+ """Collection of ordered monomers without any particular structure.
+
+ A base container class that holds a collection of `OrderedMonomer`
+ objects without imposing any ordering or structural constraints. This
+ class serves as a parent class for more structured polymer
+ representations like `OrderedPolymer`.
+
+ Parameters
+ ----------
+ monomers : list of OrderedMonomer
+ List of `OrderedMonomer` objects to include in the collection. Each
+ monomer is copied and linked to this collection as its parent.
+
+ Attributes
+ ----------
+ monomers : tuple of OrderedMonomer
+ Tuple containing copies of the input monomers, with each monomer's
+ parent set to this collection.
+
+ See Also
+ --------
+ OrderedPolymer : A polymer with ordered monomers and directionality.
+ OrderedMonomer : A unit that can belong to a MonomerCollection.
+
+ Notes
+ -----
+ Monomers are stored as a tuple (immutable) to prevent direct
+ modification. Each monomer is copied during initialization to ensure
+ the collection maintains its own references.
+
+ Examples
+ --------
+ Create a collection of monomers:
+
+ >>> mon1 = bcp.OrderedMonomer()
+ >>> mon2 = bcp.OrderedMonomer()
+ >>> collection = bcp.MonomerCollection([mon1, mon2])
+ >>> len(collection.monomers)
+ 2
+
+ """
def __init__(self, monomers):
self.monomers = monomers
@@ -24,6 +64,21 @@ def monomers(self):
@monomers.setter
def monomers(self, monomers):
+ """Set the monomers in the collection.
+
+ Parameters
+ ----------
+ monomers : list of OrderedMonomer
+ List of `OrderedMonomer` objects to store in the collection.
+ Each monomer is copied and has its parent set to this
+ collection.
+
+ Raises
+ ------
+ AssertionError
+ If any element in `monomers` is not an `OrderedMonomer`.
+
+ """
mon_list = []
for monomer in monomers:
assert isinstance(monomer, OrderedMonomer)
@@ -34,16 +89,82 @@ def monomers(self, monomers):
class OrderedPolymer(MonomerCollection):
- """A polymer made up of OrderedMonomers that has a specific order."""
+ """A polymer made up of OrderedMonomers with a specific order.
+
+ Represents a linear sequence of monomers where each monomer has a
+ defined position and direction. This class extends `MonomerCollection`
+ to provide ordered, directional polymer structures commonly used to
+ represent DNA constructs, RNA sequences, and protein chains.
+
+ Parameters
+ ----------
+ parts : list or tuple
+ Sequence of parts to add to the polymer. Each element can be:
+
+ - An `OrderedMonomer` object (uses existing direction)
+ - A list/tuple `[OrderedMonomer, direction]` specifying monomer
+ and its direction explicitly
+
+ default_direction : str or int, optional
+ Default direction for monomers when not explicitly specified.
+ Common values include 'forward', 'reverse', 0, 1, or None.
+
+ Attributes
+ ----------
+ polymer : tuple of OrderedMonomer
+ Ordered tuple of monomers in this polymer. Alias for `monomers`
+ property inherited from `MonomerCollection`.
+ default_direction : str, int, or None
+ Default direction assigned to monomers lacking explicit direction.
+
+ See Also
+ --------
+ NamedPolymer : An OrderedPolymer with an associated name.
+ OrderedMonomer : A unit that belongs to an OrderedPolymer.
+ MonomerCollection : Base class for monomer collections.
+
+ Notes
+ -----
+ Directions indicate the orientation of monomers in the polymer:
+
+ - 'forward' or 0: Standard/positive orientation
+ - 'reverse' or 1: Inverted/negative orientation
+ - None: No specified orientation
+
+ The polymer tuple is immutable, but monomers can be added via
+ `insert`, `append`, or `replace` methods. Direct assignment to
+ positions uses `__setitem__` which calls `replace`.
+
+ All monomers are deep-copied when added to ensure the polymer
+ maintains independent references. This prevents external modifications
+ from affecting the polymer structure.
+
+ Examples
+ --------
+ Create a polymer from monomers:
+
+ >>> mon1 = bcp.OrderedMonomer()
+ >>> mon2 = bcp.OrderedMonomer()
+ >>> polymer = bcp.OrderedPolymer(
+ ... parts=[mon1, mon2],
+ ... default_direction='forward'
+ ... )
+ >>> len(polymer)
+ 2
+
+ Create a polymer with explicit directions:
+
+ >>> polymer = bcp.OrderedPolymer(
+ ... parts=[[mon1, 'forward'], [mon2, 'reverse']]
+ ... )
+ >>> polymer[0].direction
+ 'forward'
+ >>> polymer[1].direction
+ 'reverse'
- def __init__(self, parts, default_direction=None):
- """Initialize an ordered polymer.
+ """
- Parts can be a list of lists containing
- [[OrderedMonomer,direction],[OrderedMonomer,direction],...]
- alternatively, you can have a regular list, and the direcitons
- will end up being None
- """
+ def __init__(self, parts, default_direction=None):
self.default_direction = default_direction
self.polymer = parts
@@ -53,6 +174,25 @@ def polymer(self):
@polymer.setter
def polymer(self, parts):
+ """Set the polymer sequence from a list of parts.
+
+ Parameters
+ ----------
+ parts : list or tuple
+ Sequence of parts to add to the polymer. Each element can be:
+
+ - An `OrderedMonomer` object
+ - A list/tuple `[OrderedMonomer, direction]`
+
+ Raises
+ ------
+ AssertionError
+ If `parts` is not a list or tuple.
+ ValueError
+ If any element is not an `OrderedMonomer` or valid part
+ specification.
+
+ """
polymer = []
assert isinstance(
parts, (list, tuple)
@@ -96,10 +236,51 @@ def __hash__(self):
return hval
def changed(self):
+ """Callback method invoked whenever the polymer structure changes.
+
+ This method is called after operations that modify the polymer,
+ such as `insert`, `replace`, `delpart`, or `reverse`.
+ Subclasses can override this to implement custom behavior when
+ the polymer is modified.
+
+ Notes
+ -----
+ The base implementation does nothing. Override in subclasses to add
+ functionality like name regeneration, validation, or notifications.
+
+ """
# runs whenever anything changed
pass
def insert(self, position, part, direction=None):
+ """Insert a monomer at a specific position in the polymer.
+
+ Inserts a copy of the given monomer at the specified position,
+ shifting all subsequent monomers to higher positions. Calls the
+ `changed` callback after insertion.
+
+ Parameters
+ ----------
+ position : int
+ Index at which to insert the monomer. Must be between 0 and
+ `len(polymer)` (inclusive).
+ part : OrderedMonomer
+ The monomer to insert. A copy of this monomer will be added.
+ direction : str, int, or None, optional
+ Direction for the inserted monomer. If None, uses the monomer's
+ existing direction.
+
+ See Also
+ --------
+ append : Add a monomer to the end of the polymer.
+ replace : Replace a monomer at a specific position.
+
+ Notes
+ -----
+ The monomer is deep-copied before insertion to maintain
+ independence from the original object.
+
+ """
# OrderedMonomers are always copied when inserted into an
# OrderedPolymer
part_copy = copy.copy(part)
@@ -116,6 +297,34 @@ def insert(self, position, part, direction=None):
self.changed()
def replace(self, position, part, direction=None):
+ """Replace a monomer at a specific position in the polymer.
+
+ Removes the monomer at the given position and inserts a copy of
+ the new monomer in its place. Calls the `changed` callback after
+ replacement.
+
+ Parameters
+ ----------
+ position : int
+ Index of the monomer to replace. Must be a valid position in
+ the polymer.
+ part : OrderedMonomer
+ The monomer to insert. A copy of this monomer will be added.
+ direction : str, int, or None, optional
+ Direction for the new monomer. If None, uses the monomer's
+ existing direction.
+
+ See Also
+ --------
+ insert : Insert a monomer at a specific position.
+ delpart : Remove a monomer from the polymer.
+
+ Notes
+ -----
+ The removed monomer's `remove` method is called to clear its
+ parent, position, and direction attributes.
+
+ """
# OrderedMonomers are always copied when inserted into an
# OrderedPolymer
part_copy = copy.copy(part)
@@ -133,6 +342,32 @@ def replace(self, position, part, direction=None):
self.changed()
def append(self, part, direction=None):
+ """Add a monomer to the end of the polymer.
+
+ Appends a copy of the given monomer to the end of the polymer
+ sequence by calling `insert` at the final position.
+
+ Parameters
+ ----------
+ part : OrderedMonomer
+ The monomer to append. A copy of this monomer will be added.
+ direction : str, int, or None, optional
+ Direction for the appended monomer. If None, uses the monomer's
+ existing direction if available.
+
+ See Also
+ --------
+ insert : Insert a monomer at a specific position.
+
+ Examples
+ --------
+ >>> polymer = bcp.OrderedPolymer(parts=[])
+ >>> mon = bcp.OrderedMonomer()
+ >>> polymer.append(mon, direction='forward')
+ >>> len(polymer)
+ 1
+
+ """
# OrderedMonomers are always copied when inserted into an
# OrderedPolymer
part_copy = copy.copy(part)
@@ -155,6 +390,40 @@ def __repr__(self):
return outstr
def direction_invert(self, dirname):
+ """Invert a direction value.
+
+ Converts a direction to its opposite orientation. Used during
+ polymer reversal operations.
+
+ Parameters
+ ----------
+ dirname : str, int, or None
+ The direction to invert. Supported values:
+
+ - 'forward' <--> 'reverse'
+ - 0 <--> 1
+ - None -> None
+
+ Returns
+ -------
+ str, int, or None
+ The inverted direction. Returns the input unchanged if it
+ cannot be inverted.
+
+ Warns
+ -----
+ UserWarning
+ If the direction value is not recognized.
+
+ Examples
+ --------
+ >>> polymer = bcp.OrderedPolymer(parts=[])
+ >>> polymer.direction_invert('forward')
+ 'reverse'
+ >>> polymer.direction_invert(0)
+ 1
+
+ """
if dirname == 'forward':
return 'reverse'
elif dirname == 'reverse':
@@ -170,15 +439,68 @@ def direction_invert(self, dirname):
return dirname
def __len__(self):
+ """Return the number of monomers in the polymer.
+
+ Returns
+ -------
+ int
+ The number of monomers in the polymer sequence.
+
+ """
return len(self.polymer)
def __getitem__(self, ii):
+ """Get a monomer or slice of monomers from the polymer.
+
+ Parameters
+ ----------
+ ii : int or slice
+ Index or slice to retrieve from the polymer.
+
+ Returns
+ -------
+ OrderedMonomer or tuple
+ The monomer at the given index, or a tuple of monomers for a
+ slice.
+
+ """
return self.polymer[ii]
def __setitem__(self, ii, val):
+ """Replace a monomer at a specific position.
+
+ Parameters
+ ----------
+ ii : int
+ Index at which to replace the monomer.
+ val : OrderedMonomer
+ The new monomer to insert at the position.
+
+ Notes
+ -----
+ Internally calls `replace` with the monomer's existing direction.
+
+ """
self.replace(ii, val, val.direction)
def __eq__(self, other):
+ """Check equality with another OrderedPolymer.
+
+ Two polymers are equal if they have the same length and each
+ corresponding pair of monomers has the same direction, position,
+ and type.
+
+ Parameters
+ ----------
+ other : OrderedPolymer
+ The polymer to compare with.
+
+ Returns
+ -------
+ bool
+ True if polymers are equal, False otherwise.
+
+ """
if isinstance(other, OrderedPolymer):
for item1, item2 in zip(self.polymer, other.polymer):
if (
@@ -194,12 +516,49 @@ def __eq__(self, other):
return False
def __contains__(self, item):
+ """Check if a monomer is in the polymer.
+
+ Parameters
+ ----------
+ item : OrderedMonomer
+ The monomer to search for.
+
+ Returns
+ -------
+ bool
+ True if the monomer is in the polymer, False otherwise.
+
+ """
if item in self.polymer:
return True
else:
return False
def delpart(self, position):
+ """Remove a monomer from the polymer at a specific position.
+
+ Removes the monomer at the given position, shifts all subsequent
+ monomers to lower positions, and calls the `changed` callback.
+
+ Parameters
+ ----------
+ position : int
+ Index of the monomer to remove. Must be a valid position in
+ the polymer.
+
+ See Also
+ --------
+ replace : Replace a monomer at a specific position.
+ insert : Insert a monomer at a specific position.
+
+ Notes
+ -----
+ The removed monomer's `remove` method is called to clear its
+ parent, position, and direction. If the polymer has a `name`
+ attribute and a `make_name` method, the name is regenerated
+ after deletion.
+
+ """
part = self.polymer[position]
part.remove()
for subsequent_part in self.polymer[position + 1 :]:
@@ -210,6 +569,32 @@ def delpart(self, position):
self.name = self.make_name()
def reverse(self):
+ """Reverse the order and directions of all monomers in the polymer.
+
+ Reverses the polymer sequence and inverts the direction of each
+ monomer. Updates all monomer positions to reflect their new
+ locations. Calls the `changed` callback after reversal.
+
+ Notes
+ -----
+ This operation modifies the polymer in place. All monomers have
+ their directions inverted using `direction_invert` and their
+ positions updated to match the reversed sequence.
+
+ Examples
+ --------
+ >>> mon1 = bcp.OrderedMonomer()
+ >>> mon2 = bcp.OrderedMonomer()
+ >>> polymer = bcp.OrderedPolymer(
+ ... parts=[[mon1, 'forward'], [mon2, 'reverse']]
+ ... )
+ >>> polymer.reverse()
+ >>> polymer[0].direction
+ 'forward'
+ >>> polymer[1].direction
+ 'reverse'
+
+ """
self.polymer = self.polymer[::-1]
for ind, part in enumerate(self.polymer):
part.position = ind
@@ -218,7 +603,75 @@ def reverse(self):
class NamedPolymer(OrderedPolymer):
- """The same as an OrderedPolymer but it has a name."""
+ """An OrderedPolymer with an associated name and circularity flag.
+
+ Extends `OrderedPolymer` to include a name identifier and optional
+ circular topology flag. Commonly used to represent named biological
+ constructs like plasmids, chromosomes, or specific DNA/RNA sequences.
+
+ Parameters
+ ----------
+ parts : list or tuple
+ Sequence of parts to add to the polymer. See `OrderedPolymer` for
+ format details.
+ name : str
+ Name identifier for the polymer.
+ default_direction : str, int, or None, optional
+ Default direction for monomers when not explicitly specified.
+ circular : bool, default=False
+ If True, indicates the polymer has circular topology (e.g., a
+ plasmid). If False, the polymer is linear.
+
+ Attributes
+ ----------
+ name : str
+ Name identifier for the polymer.
+ circular : bool
+ Flag indicating circular (True) or linear (False) topology.
+ polymer : tuple of OrderedMonomer
+ Ordered tuple of monomers in this polymer (inherited).
+ default_direction : str, int, or None
+ Default direction for monomers (inherited).
+
+ See Also
+ --------
+ OrderedPolymer : Base class for ordered polymer structures.
+ OrderedMonomer : A unit that belongs to an OrderedPolymer.
+
+ Notes
+ -----
+ The `circular` attribute is primarily informational and does not
+ automatically enforce circular topology constraints in polymer
+ operations. Subclasses or external code must handle circular semantics
+ as needed.
+
+ Examples
+ --------
+ Create a linear named polymer:
+
+ >>> mon1 = bcp.OrderedMonomer()
+ >>> mon2 = bcp.OrderedMonomer()
+ >>> polymer = bcp.NamedPolymer(
+ ... parts=[mon1, mon2],
+ ... name='my_construct',
+ ... default_direction='forward'
+ ... )
+ >>> polymer.name
+ 'my_construct'
+ >>> polymer.circular
+ False
+
+ Create a circular polymer (plasmid):
+
+ >>> plasmid = bcp.NamedPolymer(
+ ... parts=[mon1, mon2],
+ ... name='pUC19',
+ ... circular=True
+ ... )
+ >>> plasmid.circular
+ True
+
+ """
def __init__(self, parts, name, default_direction=None, circular=False):
self.name = name
@@ -231,12 +684,87 @@ def __init__(self, parts, name, default_direction=None, circular=False):
class OrderedMonomer:
"""A unit that belongs to an OrderedPolymer.
- Each unit has a direction, a location, and a link back to its parent.
+ Represents a single monomer unit within a polymer structure. Each
+ monomer tracks its position in the polymer, its directional
+ orientation, and maintains a reference to its parent polymer. This
+ class is used as a base for representing DNA parts, RNA components,
+ amino acids, and other polymer building blocks.
+
+ Parameters
+ ----------
+ direction : str, int, or None, optional
+ Directional orientation of the monomer in the polymer. Common
+ values include 'forward', 'reverse', 0, 1, or None. Default is
+ None.
+ position : int or None, optional
+ Index position of the monomer within its parent polymer. Must be
+ non-None if the monomer belongs to a polymer. Default is None.
+ parent : MonomerCollection or None, optional
+ Reference to the parent `MonomerCollection` or `OrderedPolymer`
+ containing this monomer. Default is None.
+
+ Attributes
+ ----------
+ direction : str, int, or None
+ Directional orientation of the monomer.
+ position : int or None
+ Position index within the parent polymer.
+ parent : MonomerCollection or None
+ Reference to the parent collection or polymer.
+ is_polymer_component : bool
+ Flag indicating whether this monomer is part of a polymer
+ structure. Set to True when inserted into a polymer.
+
+ See Also
+ --------
+ OrderedPolymer : A polymer made up of OrderedMonomers.
+ MonomerCollection : Base class for monomer collections.
+
+ Notes
+ -----
+ OrderedMonomers are deep-copied when inserted into polymers to ensure
+ independence. Use `get_orphan` to create a copy without a parent
+ reference, or `get_removed` to create a fully detached copy.
+
+ The `is_polymer_component` flag and `find_polymer_component` method
+ support scenarios where monomers may be nested within complex species.
+
+ Examples
+ --------
+ Create a standalone monomer:
+
+ >>> monomer = bcp.OrderedMonomer(direction='forward')
+ >>> monomer.direction
+ 'forward'
+ >>> monomer.parent is None
+ True
+
+ Add a monomer to a polymer:
+
+ >>> polymer = bcp.OrderedPolymer(parts=[])
+ >>> monomer = bcp.OrderedMonomer()
+ >>> polymer.append(monomer, direction='forward')
+ >>> polymer[0].position
+ 0
+ >>> polymer[0].parent is polymer
+ True
"""
def __init__(self, direction=None, position=None, parent=None):
- """The default is that the monomer is not part of a polymer."""
+ """Initialize an OrderedMonomer.
+
+ Parameters
+ ----------
+ direction : str, int, or None, optional
+ Directional orientation of the monomer. Default is None.
+ position : int or None, optional
+ Position index within the parent polymer. Default is None.
+ parent : MonomerCollection or None, optional
+ Reference to the parent collection. Default is None.
+
+ """
+ # The default is that the monomer is not part of a polymer.
self.parent = None
self.direction = None
# Set position to prevent weird testing errors of not having
@@ -256,6 +784,19 @@ def parent(self):
@parent.setter
def parent(self, parent):
+ """Set the parent collection for this monomer.
+
+ Parameters
+ ----------
+ parent : MonomerCollection or None
+ The parent collection or polymer to assign to this monomer.
+
+ Raises
+ ------
+ ValueError
+ If `parent` is not None and not a `MonomerCollection` instance.
+
+ """
if parent is None or isinstance(parent, MonomerCollection):
self._parent = parent
else:
@@ -269,6 +810,15 @@ def direction(self):
@direction.setter
def direction(self, direction):
+ """Set the directional orientation of the monomer.
+
+ Parameters
+ ----------
+ direction : str, int, or None
+ The direction to assign. Common values include 'forward',
+ 'reverse', 0, 1, or None.
+
+ """
self._direction = direction
@property
@@ -277,12 +827,50 @@ def position(self):
@position.setter
def position(self, position):
+ """Set the position index of the monomer.
+
+ Parameters
+ ----------
+ position : int or None
+ The position index to assign. Must be non-None if the monomer
+ has a parent polymer.
+
+ Raises
+ ------
+ ValueError
+ If the monomer has a parent but position is None.
+
+ """
if self.parent is not None and position is None:
raise ValueError(f"{self} is part of a polymer with no position!")
else:
self._position = position
def find_polymer_component(self):
+ """Find the polymer component within this monomer or its species.
+
+ Searches this monomer and, if it is a `ComplexSpecies`, its
+ constituent species to find which one is marked as a polymer
+ component.
+
+ Returns
+ -------
+ OrderedMonomer or None
+ The monomer that is part of a polymer structure, or None if no
+ polymer component is found.
+
+ Raises
+ ------
+ ValueError
+ If multiple species are marked as polymer components in the
+ same location.
+
+ Notes
+ -----
+ This method is primarily used internally to handle complex species
+ that may contain monomers as part of larger structures.
+
+ """
from .species import ComplexSpecies
outpolymer = None
@@ -309,6 +897,29 @@ def find_polymer_component(self):
def monomer_insert(
self, parent: OrderedPolymer, position: int, direction=None
):
+ """Insert this monomer into a polymer at a specific position.
+
+ Sets the monomer's parent, position, and direction attributes to
+ reflect its insertion into the polymer. Marks the monomer (or its
+ polymer component if it is a complex species) as a polymer
+ component.
+
+ Parameters
+ ----------
+ parent : OrderedPolymer
+ The polymer to insert this monomer into.
+ position : int
+ The position index where this monomer is being inserted.
+ direction : str, int, or None, optional
+ The direction for this monomer. If None, uses the monomer's
+ existing direction.
+
+ Raises
+ ------
+ ValueError
+ If position is None, or if parent is None.
+
+ """
if position is None:
raise ValueError(f"{self} has no position to be inserted at!")
if direction is None:
@@ -324,10 +935,49 @@ def monomer_insert(
self.direction = direction
def set_dir(self, direction):
+ """Set the direction of the monomer and return self.
+
+ Convenience method for setting direction in a fluent interface
+ style.
+
+ Parameters
+ ----------
+ direction : str, int, or None
+ The direction to assign to this monomer.
+
+ Returns
+ -------
+ OrderedMonomer
+ Returns self for method chaining.
+
+ Examples
+ --------
+ >>> monomer = bcp.OrderedMonomer().set_dir('forward')
+ >>> monomer.direction
+ 'forward'
+
+ """
self.direction = direction
return self
def remove(self):
+ """Remove this monomer from its parent polymer.
+
+ Clears the monomer's parent, position, and direction attributes,
+ effectively detaching it from any polymer structure.
+
+ Returns
+ -------
+ OrderedMonomer
+ Returns self for method chaining.
+
+ See Also
+ --------
+ get_removed : Create a fully detached copy of the monomer.
+ get_orphan : Create a copy with parent removed but position and
+ direction preserved.
+
+ """
self.parent = None
self.position = None
self.direction = None
@@ -335,16 +985,61 @@ def remove(self):
return self
def get_orphan(self):
- """Returns a copy of this monomer, except with no parent.
+ """Create a copy of this monomer without a parent reference.
+
+ Returns a copy that retains position and direction but has no
+ parent polymer. Useful for temporarily working with monomers
+ outside their polymer context.
+
+ Returns
+ -------
+ OrderedMonomer
+ A copy of this monomer with parent set to None but position
+ and direction preserved.
- But it still has a position and direction.
+ See Also
+ --------
+ get_removed : Create a fully detached copy.
+ remove : Remove this monomer from its parent in place.
+
+ Notes
+ -----
+ This is a shallow copy of the monomer object itself, though the
+ parent reference is explicitly cleared.
"""
+ # Returns a copy of this monomer, except with no parent.
+ # But it still has a position and direction.
copied_monomer = copy.copy(self)
copied_monomer.parent = None
return copied_monomer
def get_removed(self):
+ """Create a fully detached copy of this monomer.
+
+ Returns a copy with all polymer-related attributes (parent,
+ position, direction) cleared. Also removes 'forward' and 'reverse'
+ attributes if present.
+
+ Returns
+ -------
+ OrderedMonomer
+ A copy of this monomer with no parent, position, or direction,
+ and with directional attributes removed.
+
+ See Also
+ --------
+ get_orphan : Create a copy without parent but with position and
+ direction.
+ remove : Remove this monomer from its parent in place.
+
+ Notes
+ -----
+ This method is useful for creating completely independent copies of
+ monomers that can be reused in different contexts without any
+ polymer associations.
+
+ """
copied_part = copy.copy(self)
copied_part.parent = None
copied_part.direction = None
@@ -365,6 +1060,22 @@ def __repr__(self):
return txt
def __eq__(self, other):
+ """Check equality with another OrderedMonomer.
+
+ Two monomers are equal if they have the same direction, position,
+ and parent.
+
+ Parameters
+ ----------
+ other : OrderedMonomer
+ The monomer to compare with.
+
+ Returns
+ -------
+ bool
+ True if monomers are equal, False otherwise.
+
+ """
if isinstance(other, OrderedMonomer):
if (
self.direction == other.direction
@@ -375,6 +1086,15 @@ def __eq__(self, other):
return False
def __hash__(self):
+ """Compute hash value for this monomer.
+
+ Returns
+ -------
+ int
+ Hash value based on position, direction, name (if present),
+ and parent.
+
+ """
hval = 0
hval += self.subhash()
@@ -384,6 +1104,23 @@ def __hash__(self):
return hval
def subhash(self):
+ """Compute hash contribution from monomer properties.
+
+ Computes a hash value based on the monomer's position, direction,
+ and name (if present), excluding the parent reference.
+
+ Returns
+ -------
+ int
+ Hash value based on monomer-specific properties.
+
+ Notes
+ -----
+ This method is used by `__hash__` to compute the monomer's hash
+ contribution. It excludes the parent to avoid circular dependencies
+ in hash computation.
+
+ """
hval = 0
hval += hash(self.position)
hval += hash(self.direction)
diff --git a/biocrnpyler/core/propensities.py b/biocrnpyler/core/propensities.py
index 8c8f2b20..f45e0076 100644
--- a/biocrnpyler/core/propensities.py
+++ b/biocrnpyler/core/propensities.py
@@ -14,19 +14,78 @@
class Propensity(object):
+ """Base class for reaction propensity functions in BioCRNpyler.
+
+ Propensities define the rate laws for chemical reactions in a CRN.
+ Different propensity types implement different kinetic models such as
+ mass action, Hill functions, and custom formulas. Propensities can be
+ deterministic (ODE) or stochastic (Gillespie).
+
+ Attributes
+ ----------
+ propensity_dict : dict
+ Dictionary with 'species' and 'parameters' keys storing the
+ species and parameters used in the propensity function.
+ name : str or None
+ Name identifier for the propensity type.
+
+ See Also
+ --------
+ MassAction : Mass action kinetics propensity.
+ GeneralPropensity : Custom formula propensity.
+ Hill : Base class for Hill-type propensities.
+
+ Notes
+ -----
+ This is an abstract base class that should be subclassed to implement
+ specific propensity types. Subclasses must implement:
+
+ - `create_kinetic_law`: Generate SBML kinetic law
+ - `pretty_print_rate`: Human-readable rate formula
+
+ The `propensity_dict` structure:
+
+ - 'species': {: Species object, ...}
+ - 'parameters': {: Parameter or number, ...}
+
+ """
+
def __init__(self):
+ """Initialize a Propensity object.
+
+ Creates an empty propensity dictionary with 'species' and
+ 'parameters' keys.
+
+ """
self.propensity_dict = {'species': {}, 'parameters': {}}
self.name = None
@staticmethod
def is_valid_propensity(propensity_type) -> bool:
- """Checks whether the given propensity_type is valid propensity.
+ """Check if an object is a valid Propensity subclass instance.
+
+ Recursively checks all subclasses of Propensity to determine if
+ the given object is a valid propensity type.
+
+ Parameters
+ ----------
+ propensity_type : object
+ Object to check for Propensity validity.
+
+ Returns
+ -------
+ bool
+ True if `propensity_type` is an instance of Propensity or any
+ of its subclasses, False otherwise.
+
+ Examples
+ --------
+ >>> prop = bcp.MassAction(k_forward=100.0)
+ >>> bcp.Propensity.is_valid_propensity(prop)
+ True
+ >>> bcp.Propensity.is_valid_propensity("not a propensity")
+ False
- It recursively checks all subclasses of Propensity until it finds the
- propensity type. Otherwise raise a Type error
-
- :param propensity_type: Propensity
- :returns bool
"""
for propensity in Propensity.get_available_propensities():
if isinstance(propensity_type, propensity):
@@ -35,14 +94,23 @@ def is_valid_propensity(propensity_type) -> bool:
@staticmethod
def _all_subclasses(cls):
- """Returns a set of all subclasses of cls (recursively calculated).
+ """Recursively find all subclasses of a class.
+
+ Parameters
+ ----------
+ cls : type
+ A class to find subclasses of.
+
+ Returns
+ -------
+ set
+ Set of all subclasses of `cls`, including nested subclasses.
- Source:
- https://stackoverflow.com/questions/3862310/
- how-to-find-all-the-subclasses-of-a-class-given-its-name
+ Notes
+ -----
+ Source: https://stackoverflow.com/questions/3862310/
+ how-to-find-all-the-subclasses-of-a-class-given-its-name
- :param cls: A class in the codebase, for example Propensity
- :return: set of all subclasses from cls
"""
return set(cls.__subclasses__()).union(
[
@@ -54,24 +122,57 @@ def _all_subclasses(cls):
@staticmethod
def get_available_propensities() -> Set:
+ """Get all available propensity subclasses.
+
+ Returns
+ -------
+ set
+ Set of all Propensity subclass types available in BioCRNpyler.
+
+ Examples
+ --------
+ >>> propensities = bcp.Propensity.get_available_propensities()
+ >>> bcp.MassAction in propensities
+ True
+
+ """
return Propensity._all_subclasses(Propensity)
def _create_sbml_parameter(
self, parameter_name, sbml_model, ratelaw, rename_dict=None
):
- """Creates an sbml parameter for a parameter of the given name.
-
- if self.propensity_dict["parameter"]["parameter_name"] is a
- Parameter, creates a global parameter
- "Parameter.name_Parameter.part_id_Parameter.mechanism" where
- part_id and mechanism can be empty (but _ will always be
- incldued for uniqueness).
-
- if self.propensity_dict["parameter"]["parameter_name"] is a
- Number, creates a local parameter "parameter_name".
-
- rename_dict allows for param.name to be changed to
- rename_dict[param.name]
+ """Create an SBML parameter for the propensity.
+
+ Creates either a global or local SBML parameter depending on
+ whether the parameter is a ParameterEntry or a number.
+
+ Parameters
+ ----------
+ parameter_name : str
+ Name of the parameter in propensity_dict['parameters'].
+ sbml_model : libsbml.Model
+ SBML model object to add global parameters to.
+ ratelaw : libsbml.KineticLaw
+ SBML kinetic law object to add local parameters to.
+ rename_dict : dict, optional
+ Dictionary to rename parameters. Maps original parameter names
+ to new names.
+
+ Returns
+ -------
+ libsbml.Parameter or libsbml.LocalParameter
+ Created SBML parameter object.
+
+ Raises
+ ------
+ TypeError
+ If parameter is not a ParameterEntry, int, or float.
+
+ Notes
+ -----
+ If parameter is a ParameterEntry, creates a global parameter named
+ '__'. If parameter is a number,
+ creates a local parameter with the given name.
"""
p = self.propensity_dict['parameters'][parameter_name]
@@ -112,7 +213,28 @@ def _create_sbml_parameter(
)
def _check_parameter(self, parameter, allow_None=False, positive=True):
- """Helper function to set parameters and do type checking."""
+ """Validate parameter type and value.
+
+ Parameters
+ ----------
+ parameter : Parameter, float, or None
+ Parameter to validate.
+ allow_None : bool, default=False
+ If True, allows None as a valid value.
+ positive : bool, default=True
+ If True, requires parameter value to be positive.
+
+ Returns
+ -------
+ Parameter, float, or None
+ The validated parameter.
+
+ Raises
+ ------
+ ValueError
+ If parameter is invalid type or has invalid value.
+
+ """
if isinstance(parameter, Parameter) and (
parameter.value > 0 or not positive
):
@@ -136,7 +258,26 @@ def _check_parameter(self, parameter, allow_None=False, positive=True):
)
def _check_species(self, species, allow_None=False):
- """Helper function to set species and do type checking."""
+ """Validate species type.
+
+ Parameters
+ ----------
+ species : Species or None
+ Species to validate.
+ allow_None : bool, default=False
+ If True, allows None as a valid value.
+
+ Returns
+ -------
+ Species or None
+ The validated species.
+
+ Raises
+ ------
+ TypeError
+ If species is not a Species object and not None when allowed.
+
+ """
if isinstance(species, Species):
return species
elif species is None and allow_None:
@@ -147,17 +288,67 @@ def _check_species(self, species, allow_None=False):
)
def pretty_print(self, show_parameters=True, **kwargs):
+ """Generate human-readable string representation of propensity.
+
+ Parameters
+ ----------
+ show_parameters : bool, default=True
+ If True, includes parameter values in output.
+ **kwargs
+ Additional keyword arguments passed to formatting methods.
+
+ Returns
+ -------
+ str
+ Formatted string showing rate formula and optionally
+ parameters.
+
+ """
txt = self.pretty_print_rate(**kwargs)
if show_parameters:
txt += '\n' + self.pretty_print_parameters(**kwargs)
return txt
def pretty_print_rate(self, **kwargs):
+ """Generate human-readable rate formula string.
+
+ Parameters
+ ----------
+ **kwargs
+ Formatting keyword arguments (e.g., reaction, stochastic).
+
+ Returns
+ -------
+ str
+ Formatted rate formula string.
+
+ Raises
+ ------
+ NotImplementedError
+ Must be implemented by subclasses.
+
+ """
raise NotImplementedError(
"class Propensity is meant to be subclassed!"
)
def pretty_print_parameters(self, show_keys=True, **kwargs):
+ """Generate formatted string of all propensity parameters.
+
+ Parameters
+ ----------
+ show_keys : bool, default=True
+ If True, shows search and found keys for ModelParameter
+ objects (useful for debugging parameter lookup).
+ **kwargs
+ Additional formatting keyword arguments.
+
+ Returns
+ -------
+ str
+ Formatted string listing all parameters and their values.
+
+ """
txt = ''
for k in self.propensity_dict['parameters']:
p = self.propensity_dict['parameters'][k]
@@ -179,44 +370,130 @@ def pretty_print_parameters(self, show_keys=True, **kwargs):
@property
def is_reversible(self):
- """By default, Propensities are assumed to NOT be reversible."""
+ """bool: Whether the propensity represents a reversible reaction.
+
+ Default is False. Subclasses override this for reversible kinetics.
+
+ """
return False
@property
def k_forward(self):
+ """Float : Forward rate constant.
+
+ Raises
+ ------
+ NotImplementedError
+ Must be implemented by subclasses that use rate constants.
+
+ """
raise NotImplementedError
@property
def k_reverse(self):
+ """Float or None: Reverse rate constant for reversible reactions.
+
+ Raises
+ ------
+ NotImplementedError
+ Must be implemented by subclasses that use rate constants.
+
+ """
raise NotImplementedError
def __eq__(self, other):
+ """Test equality between propensities.
+
+ Parameters
+ ----------
+ other : Propensity
+ Other propensity to compare with.
+
+ Returns
+ -------
+ bool
+ True if propensities have the same class and propensity_dict.
+
+ """
if other.__class__ == self.__class__:
return other.propensity_dict == self.propensity_dict
@property
def species(self) -> List:
- """Returns the instance variables that are species type."""
+ """List of Species : All species used in the propensity function."""
return list(self.propensity_dict['species'].values())
def create_kinetic_law(
self, reaction, reverse_reaction, stochastic, **kwargs
):
+ """Create SBML kinetic law for a reaction.
+
+ Parameters
+ ----------
+ reaction : libsbml.Reaction
+ SBML reaction object to add kinetic law to.
+ reverse_reaction : bool
+ If True, creates kinetic law for reverse direction.
+ stochastic : bool
+ If True, uses stochastic propensity formulation.
+ **kwargs
+ Additional arguments (e.g., crn_reaction, model).
+
+ Returns
+ -------
+ libsbml.KineticLaw
+ Created SBML kinetic law object.
+
+ Raises
+ ------
+ NotImplementedError
+ Must be implemented by subclasses.
+
+ """
raise NotImplementedError(
"class Propensity is meant to be subclassed!"
)
@classmethod
def from_dict(cls, propensity_dict):
+ """Create a propensity from a dictionary.
+
+ Parameters
+ ----------
+ propensity_dict : dict
+ Dictionary with 'parameters' and 'species' keys containing
+ parameter and species values.
+
+ Returns
+ -------
+ Propensity
+ New instance of the propensity class.
+
+ """
merged = propensity_dict['parameters']
merged.update(propensity_dict['species'])
return cls(**merged)
def _create_annotation(self, model, propensity_dict_in_sbml, **kwargs):
- """Create simulator specific annotations to part of SBML model object.
-
- Annotations are used to take advantage of a simulator specific
- need/feature.
+ """Create simulator-specific annotations for SBML model.
+
+ Annotations enable simulator-specific features and optimizations.
+ Currently supports bioscrape annotations.
+
+ Parameters
+ ----------
+ model : libsbml.Model
+ SBML model object.
+ propensity_dict_in_sbml : dict
+ Propensity dictionary with SBML identifiers.
+ **kwargs
+ Additional arguments. If 'for_bioscrape' is True, creates
+ bioscrape annotations.
+
+ Returns
+ -------
+ str
+ XML annotation string to append to SBML reaction.
"""
annotation_string = ''
@@ -237,10 +514,27 @@ def _create_annotation(self, model, propensity_dict_in_sbml, **kwargs):
return annotation_string
def _create_bioscrape_annotation(self, propensity_dict_in_sbml):
- """Add BioSCRAPE annotation.
+ """Generate bioscrape-specific propensity type annotation.
+
+ Creates XML annotation that bioscrape uses to optimize simulation
+ by identifying propensity types.
- Propensity Annotations are Used to take advantage of Bioscrape
- Propensity types for faster simulation.
+ Parameters
+ ----------
+ propensity_dict_in_sbml : dict
+ Propensity dictionary with SBML identifiers for species and
+ parameters.
+
+ Returns
+ -------
+ str
+ XML annotation string with bioscrape PropensityType tag.
+
+ Notes
+ -----
+ Bioscrape uses propensity type annotations for faster simulation.
+ The annotation includes parameter and species names with their
+ SBML identifiers, plus the propensity type name.
"""
annotation_dict = defaultdict()
@@ -270,6 +564,25 @@ def _create_bioscrape_annotation(self, propensity_dict_in_sbml):
return annotation_string
def _translate_propensity_dict_to_sbml(self, model, ratelaw):
+ """Translate internal propensity representation to SBML format.
+
+ Converts propensity_dict with Species objects and Parameters to
+ SBML identifiers that can be used in SBML rate formulas.
+
+ Parameters
+ ----------
+ model : libsbml.Model
+ SBML model object to add global parameters to.
+ ratelaw : libsbml.KineticLaw
+ SBML kinetic law object to add local parameters to.
+
+ Returns
+ -------
+ dict
+ Copy of propensity_dict with species and parameters replaced
+ by their SBML identifier strings.
+
+ """
# get copy of the propensity_dict and fill with sbml names
propensity_dict_in_sbml = copy.deepcopy(self.propensity_dict)
for param_name in propensity_dict_in_sbml['parameters'].keys():
@@ -289,22 +602,88 @@ def _translate_propensity_dict_to_sbml(self, model, ratelaw):
class GeneralPropensity(Propensity):
+ """Propensity with user-defined formula string.
+
+ A `GeneralPropensity` allows specification of arbitrary kinetic rate
+ laws using a formula string. The formula can reference species and
+ parameters that are validated and tracked.
+
+ Parameters
+ ----------
+ propensity_function : str
+ Valid mathematical formula as a string (e.g., 'k*S1*S2'). Must
+ contain all referenced species and parameters.
+ propensity_species : list of Species
+ List of Species objects used in the formula. Each species must
+ appear in the propensity_function string.
+ propensity_parameters : list of ParameterEntry
+ List of ParameterEntry objects used in the formula. Each
+ parameter name must appear in the propensity_function string.
+
+ Attributes
+ ----------
+ propensity_function : str
+ The mathematical formula defining the rate law.
+ name : str
+ Set to 'general' for this propensity type.
+
+ Raises
+ ------
+ TypeError
+ If propensity_species or propensity_parameters contain invalid
+ types.
+ ValueError
+ If species or parameters in lists do not appear in the formula.
+
+ See Also
+ --------
+ MassAction : Mass action kinetics propensity.
+ Hill : Hill-type propensities.
+
+ Notes
+ -----
+ The propensity_function string must be a valid mathematical expression
+ that can be parsed by libsbml.parseL3Formula(). It can include:
+
+ - Arithmetic operators: `+, -, *, /, ^`
+ - Mathematical functions: exp, log, sin, cos, etc.
+ - Species names (as strings matching their representation)
+ - Parameter names (matching ParameterEntry.parameter_name)
+
+ Examples
+ --------
+ Create a custom Michaelis-Menten propensity:
+
+ >>> S = bcp.Species('S')
+ >>> E = bcp.Species('E')
+ >>> kcat = bcp.ParameterEntry('kcat', 0.1)
+ >>> Km = bcp.ParameterEntry('Km', 10.0)
+ >>> prop = bcp.GeneralPropensity(
+ ... propensity_function='kcat*E*S/(Km + S)',
+ ... propensity_species=[S, E],
+ ... propensity_parameters=[kcat, Km]
+ ... )
+
+ Create a custom regulatory function:
+
+ >>> X = bcp.Species('X')
+ >>> Y = bcp.Species('Y')
+ >>> k1 = bcp.ParameterEntry('k1', 1.0)
+ >>> k2 = bcp.ParameterEntry('k2', 0.5)
+ >>> prop = bcp.GeneralPropensity(
+ ... propensity_function='k1*X + k2*Y^2',
+ ... propensity_species=[X, Y],
+ ... propensity_parameters=[k1, k2]
+ ... )
+
+ """
+
def __init__(
self,
propensity_function: str,
propensity_species: List[Species],
propensity_parameters: List[ParameterEntry],
):
- """A class to define a general propensity.
-
- :param propensity_function: valid propensity formula defined as a
- string
- :param propensity_species: list of species that are part of
- the propensity_function
- :param propensity_parameters: list of parameters that are part of
- the propensity_function
-
- """
super(GeneralPropensity, self).__init__()
self.propensity_function = propensity_function
@@ -343,10 +722,43 @@ def __init__(
self.name = 'general'
def pretty_print_rate(self, **kwargs):
+ """Return the propensity function formula string.
+
+ Returns
+ -------
+ str
+ The propensity_function formula.
+
+ """
return self.propensity_function
def create_kinetic_law(self, model, sbml_reaction, **kwargs):
- """Creates SBML KineticLaw using the propensity_function string."""
+ """Create SBML kinetic law using the propensity_function string.
+
+ Translates species and parameter names to SBML identifiers and
+ creates the kinetic law from the formula string.
+
+ Parameters
+ ----------
+ model : libsbml.Model
+ SBML model object.
+ sbml_reaction : libsbml.Reaction
+ SBML reaction to add kinetic law to.
+ **kwargs
+ Additional keyword arguments (unused for GeneralPropensity).
+
+ Returns
+ -------
+ libsbml.KineticLaw
+ Created SBML kinetic law object.
+
+ Raises
+ ------
+ ValueError
+ If the propensity_function cannot be parsed as valid SBML
+ formula.
+
+ """
ratelaw = sbml_reaction.createKineticLaw()
propensity_dict_in_sbml = self._translate_propensity_dict_to_sbml(
@@ -380,6 +792,83 @@ def create_kinetic_law(self, model, sbml_reaction, **kwargs):
class MassAction(Propensity):
+ r"""Mass action kinetics propensity.
+
+ Implements mass action rate laws for chemical reactions. Supports both
+ irreversible and reversible reactions. Propensities are computed
+ differently for deterministic (ODE) and stochastic (Gillespie)
+ simulations.
+
+ Parameters
+ ----------
+ k_forward : float or ParameterEntry
+ Forward reaction rate constant. Must be positive.
+ k_reverse : float, ParameterEntry, or None, optional
+ Reverse reaction rate constant. If None, reaction is irreversible.
+ If provided, must be positive.
+
+ Attributes
+ ----------
+ name : str
+ Set to 'massaction' for this propensity type.
+
+ See Also
+ --------
+ GeneralPropensity : Custom formula propensity.
+ Hill : Hill-type propensities.
+
+ Notes
+ -----
+ Deterministic (ODE) propensity: For reaction A + B --> C with rate
+ constant k:
+
+ .. math::
+
+ \text{rate} = k [A] [B]
+
+ Stochastic (Gillespie) propensity: For reaction A + B --> C with
+ rate constant k:
+
+ .. math::
+
+ \text{propensity} &= k \cdot A \cdot (B-1) \text{ if } A=B \\
+ \text{propensity} &= k \cdot A \cdot B \text{ otherwise}
+
+ The stochastic formulation accounts for combinatorics of molecule
+ selection. For stoichiometric coefficient n > 1:
+
+ .. math::
+
+ \text{factor} = S \cdot (S-1) \cdot ... \cdot (S-n+1)
+
+ If `k_reverse` is provided, the reaction is reversible:
+
+ .. math::
+
+ A + B \rightleftharpoons C
+
+ Two kinetic laws are created: one for forward, one for reverse.
+
+ Examples
+ --------
+ Create an irreversible mass action propensity:
+
+ >>> prop = bcp.MassAction(k_forward=100.0)
+
+ Create a reversible mass action propensity:
+
+ >>> prop = bcp.MassAction(k_forward=100.0, k_reverse=0.01)
+ >>> prop.is_reversible
+ True
+
+ Use with ParameterEntry objects:
+
+ >>> kb = bcp.ParameterEntry('kb', 100.0, unit='1/(nM*s)')
+ >>> ku = bcp.ParameterEntry('ku', 0.01, unit='1/s')
+ >>> prop = bcp.MassAction(k_forward=kb, k_reverse=ku)
+
+ """
+
def __init__(
self,
k_forward: Union[float, ParameterEntry],
@@ -392,6 +881,7 @@ def __init__(
@property
def k_forward(self):
+ """float: Forward rate constant value."""
if isinstance(self._k_forward, Parameter):
return self._k_forward.value
else:
@@ -399,11 +889,20 @@ def k_forward(self):
@k_forward.setter
def k_forward(self, new_k_forward):
+ """Set the forward rate constant.
+
+ Parameters
+ ----------
+ new_k_forward : float or ParameterEntry
+ New forward rate constant. Must be positive.
+
+ """
self._k_forward = self._check_parameter(new_k_forward)
self.propensity_dict['parameters']['k_forward'] = self._k_forward
@property
def k_reverse(self):
+ """float: Reverse rate constant value, None if irreversible."""
if isinstance(self._k_reverse, Parameter):
return self._k_reverse.value
else:
@@ -411,6 +910,15 @@ def k_reverse(self):
@k_reverse.setter
def k_reverse(self, new_k_reverse):
+ """Set the reverse rate constant.
+
+ Parameters
+ ----------
+ new_k_reverse : float, ParameterEntry, or None
+ New reverse rate constant. If None, reaction is irreversible.
+ If provided, must be positive.
+
+ """
self._k_reverse = self._check_parameter(
new_k_reverse, allow_None=True
)
@@ -419,12 +927,28 @@ def k_reverse(self, new_k_reverse):
@property
def is_reversible(self):
+ """bool: True if k_reverse is not None, False otherwise."""
if self.k_reverse is None:
return False
else:
return True
def pretty_print_rate(self, **kwargs):
+ """Generate human-readable rate formula string.
+
+ Parameters
+ ----------
+ **kwargs
+ Must include 'reaction' (CRN Reaction object) and 'stochastic'
+ (bool) keys.
+
+ Returns
+ -------
+ str
+ Formatted rate formula showing forward rate and optionally
+ reverse rate.
+
+ """
crn_reaction = kwargs['reaction']
reactant_species = {}
for w_species in crn_reaction.inputs:
@@ -446,10 +970,43 @@ def create_kinetic_law(
model,
sbml_reaction,
stochastic,
+ crn_reaction=None,
reverse_reaction=False,
**kwargs,
):
- if (crn_reaction := kwargs.pop('crn_reaction', None)) is None:
+ """Create SBML kinetic law for mass action reaction.
+
+ Generates SBML kinetic law with proper mass action formula for
+ either forward or reverse direction.
+
+ Parameters
+ ----------
+ model : libsbml.Model
+ SBML model object.
+ sbml_reaction : libsbml.Reaction
+ SBML reaction to add kinetic law to.
+ stochastic : bool
+ If True, uses stochastic mass action formula accounting for
+ combinatorics.
+ crn_reaction : Reaction
+ Mass action reaction to use for the kinetic law.
+ reverse_reaction : bool, default=False
+ If True, creates kinetic law for reverse reaction.
+ **kwargs
+ Must include 'crn_reaction' (CRN Reaction object).
+
+ Returns
+ -------
+ libsbml.KineticLaw
+ Created SBML kinetic law object.
+
+ Raises
+ ------
+ ValueError
+ If crn_reaction not provided or if rate formula is invalid.
+
+ """
+ if crn_reaction is None:
raise ValueError(
"crn_reaction reference is needed for Massaction kinetics!"
)
@@ -505,6 +1062,34 @@ def create_kinetic_law(
def _get_rate_formula(
self, rate_coeff_name, stochastic, reactant_species
) -> str:
+ """Generate mass action rate formula string.
+
+ Creates the mathematical formula for mass action kinetics,
+ accounting for stoichiometry and stochastic vs deterministic
+ simulation.
+
+ Parameters
+ ----------
+ rate_coeff_name : str
+ Name of the rate constant parameter (SBML identifier).
+ stochastic : bool
+ If True, uses stochastic formula with combinatorics. If False,
+ uses deterministic ODE formula.
+ reactant_species : dict
+ Dictionary mapping species_id (str) to WeightedSpecies objects
+ with stoichiometry information.
+
+ Returns
+ -------
+ str
+ Rate formula string suitable for SBML parseL3Formula().
+
+ Notes
+ -----
+ For deterministic: rate = k * [A]^n * [B]^m
+ For stochastic: rate = k * A * (A-1) * ... * (A-n+1) * B * ...
+
+ """
# Create Rate-strings for massaction propensities
ratestring = rate_coeff_name
@@ -533,6 +1118,60 @@ def _get_rate_formula(
class Hill(Propensity):
+ """Base class for Hill-type propensities.
+
+ Hill propensities implement cooperative binding kinetics with
+ sigmoidal response curves. This base class provides common
+ functionality for positive and negative Hill functions.
+
+ Parameters
+ ----------
+ k : float or ParameterEntry
+ Maximum rate constant. Must be positive.
+ s1 : Species
+ Input species that drives the Hill function.
+ K : float or ParameterEntry
+ Half-saturation (dissociation) constant. Must be positive.
+ n : float or ParameterEntry
+ Hill coefficient (cooperativity). Must be positive. Values > 1
+ indicate positive cooperativity, < 1 negative cooperativity.
+ d : Species or None
+ Optional species for proportional Hill functions. If provided,
+ rate is proportional to this species concentration.
+
+ Attributes
+ ----------
+ k : float
+ Maximum rate constant value.
+ K : float
+ Half-saturation constant value.
+ n : float
+ Hill coefficient value.
+ s1 : Species
+ Input species.
+ d : Species or None
+ Proportional species (None for non-proportional Hill).
+
+ See Also
+ --------
+ HillPositive : Positive Hill function.
+ HillNegative : Negative Hill function (repression).
+ ProportionalHillPositive : Proportional positive Hill.
+ ProportionalHillNegative : Proportional negative Hill.
+
+ Notes
+ -----
+ This is an abstract base class. Use the specific subclasses:
+
+ - `HillPositive`: Activation, :math:`k s_1^n / (K^n + s_1^n)`
+ - `HillNegative`: Repression, :math:`k / (1 + (s_1/K)^n)`
+ - `ProportionalHillPositive`: :math:`k d s_1^n / (K^n + s_1^n)`
+ - `ProportionalHillNegative`: :math:`k d / (1 + (s_1/K)^n)`
+
+ Hill functions are not reversible - k_reverse is not supported.
+
+ """
+
def __init__(self, k: float, s1: Species, K: float, n: float, d: Species):
Propensity.__init__(self)
self.k = k
@@ -544,6 +1183,7 @@ def __init__(self, k: float, s1: Species, K: float, n: float, d: Species):
@property
def k(self):
+ """float: Maximum rate constant value."""
if isinstance(self._k, Parameter):
return self._k.value
else:
@@ -551,11 +1191,20 @@ def k(self):
@k.setter
def k(self, new_k):
+ """Set the maximum rate constant.
+
+ Parameters
+ ----------
+ new_k : float or ParameterEntry
+ New maximum rate constant. Must be positive.
+
+ """
self._k = self._check_parameter(new_k)
self.propensity_dict['parameters']['k'] = self._k
@property
def K(self):
+ """float: Half-saturation (dissociation) constant value."""
if isinstance(self._K, Parameter):
return self._K.value
else:
@@ -563,11 +1212,20 @@ def K(self):
@K.setter
def K(self, new_K):
+ """Set the half-saturation constant.
+
+ Parameters
+ ----------
+ new_K : float or ParameterEntry
+ New half-saturation constant. Must be positive.
+
+ """
self._K = self._check_parameter(new_K)
self.propensity_dict['parameters']['K'] = self._K
@property
def n(self):
+ """float: Hill coefficient (cooperativity) value."""
if isinstance(self._n, Parameter):
return self._n.value
else:
@@ -575,28 +1233,64 @@ def n(self):
@n.setter
def n(self, new_n):
+ """Set the Hill coefficient.
+
+ Parameters
+ ----------
+ new_n : float or ParameterEntry
+ New Hill coefficient. Must be positive.
+
+ """
self._n = self._check_parameter(new_n)
self.propensity_dict['parameters']['n'] = self._n
@property
def s1(self):
+ """Species: Input species driving the Hill function."""
return self._s1
@s1.setter
def s1(self, new_s1):
+ """Set the input species.
+
+ Parameters
+ ----------
+ new_s1 : Species
+ New input species.
+
+ """
self._s1 = self._check_species(new_s1)
self.propensity_dict['species']['s1'] = self.s1
@property
def d(self):
+ """Species: Proportional species (None if not proportional)."""
return self._d
@d.setter
def d(self, new_d):
+ """Set the proportional species.
+
+ Parameters
+ ----------
+ new_d : Species or None
+ New proportional species. None for non-proportional Hill.
+
+ """
self._d = self._check_species(new_d, allow_None=True)
self.propensity_dict['species']['d'] = self._d
def pretty_print_rate(self, show_parameters=True, **kwargs):
+ """Generate human-readable rate formula string.
+
+ Raises
+ ------
+ NotImplementedError
+ Hill base class doesn't have a rate formula. Use subclasses
+ HillPositive, HillNegative, ProportionalHillPositive, or
+ ProportionalHillNegative.
+
+ """
raise NotImplementedError(
"Propensity class Hill is meant to be subclassed: "
"try HillPositive, HillNegative, ProportionalHillPositive, "
@@ -604,7 +1298,35 @@ def pretty_print_rate(self, show_parameters=True, **kwargs):
)
def create_kinetic_law(self, model, sbml_reaction, stochastic, **kwargs):
- """This code is reused in all Hill Propensity subclasses."""
+ """Create SBML kinetic law for Hill propensity.
+
+ This method is shared by all Hill subclasses.
+
+ Parameters
+ ----------
+ model : libsbml.Model
+ SBML model object.
+ sbml_reaction : libsbml.Reaction
+ SBML reaction to add kinetic law to.
+ stochastic : bool
+ If True, uses stochastic formulation (same as deterministic
+ for Hill functions).
+ **kwargs
+ Additional arguments. 'reverse_reaction' is not supported.
+
+ Returns
+ -------
+ libsbml.KineticLaw
+ Created SBML kinetic law object.
+
+ Raises
+ ------
+ ValueError
+ If reverse_reaction=True (Hill propensities cannot be
+ reversible) or if rate formula is invalid.
+
+ """
+ # This code is reused in all Hill Propensity subclasses.
if (
'reverse_reaction' in kwargs
and kwargs['reverse_reaction'] is True
@@ -640,35 +1362,121 @@ def create_kinetic_law(self, model, sbml_reaction, stochastic, **kwargs):
return ratelaw
def _get_rate_formula(self, propensity_dict):
+ """Generate Hill rate formula string.
+
+ Parameters
+ ----------
+ propensity_dict : dict
+ Propensity dictionary with SBML identifiers.
+
+ Returns
+ -------
+ str
+ Rate formula string.
+
+ Raises
+ ------
+ NotImplementedError
+ Hill base class doesn't have a rate formula. Use subclasses.
+
+ """
raise NotImplementedError(
"Hill does not have a rate formula! Check out the subclasses."
)
class HillPositive(Hill):
- def __init__(self, k: float, s1: Species, K: float, n: float):
- """Positive propensity function for a Hill rate law.
+ r"""Positive Hill function propensity (activation).
- Hill positive propensity is a nonlinear propensity with the
- following formula.
+ Implements an activating Hill function with sigmoidal dose-response
+ curve. As s1 increases, the rate approaches k.
- p(s1; k, K, n) = k*s1^n/(s1^n + K)
+ Parameters
+ ----------
+ k : float or ParameterEntry
+ Maximum rate constant. Must be positive.
+ s1 : Species
+ Input species that activates the reaction.
+ K : float or ParameterEntry
+ Half-saturation constant (concentration at half-maximal rate).
+ Must be positive.
+ n : float or ParameterEntry
+ Hill coefficient (cooperativity). Must be positive. Values > 1
+ indicate ultrasensitivity.
- :param k: rate constant (float)
- :param s1: species (chemical_reaction_network.species)
- :param K: dissociation constant (float)
+ Attributes
+ ----------
+ name : str
+ Set to 'hillpositive' for this propensity type.
- """
+ See Also
+ --------
+ HillNegative : Repressive Hill function.
+ ProportionalHillPositive : Proportional positive Hill.
+
+ Notes
+ -----
+ The following formula is implemented:
+
+ .. math::
+
+ p(s_1; k, K, n) = \frac{k s_1^n}{K^n + s_1^n},
+
+ leading to the following behaviors:
+
+ - When s1 = 0: rate ≈ 0
+ - When s1 = K: rate = k/2
+ - When s1 >> K: rate -> k
+ - Larger n gives sharper (more switch-like) response
+
+ Examples
+ --------
+ Create a Hill activation propensity:
+
+ >>> X = bcp.Species('X')
+ >>> prop = bcp.HillPositive(k=10.0, s1=X, K=50.0, n=2.0)
+
+ Use with parameter objects:
+
+ >>> kmax = bcp.ParameterEntry('kmax', 10.0)
+ >>> Kd = bcp.ParameterEntry('Kd', 50.0)
+ >>> hill_n = bcp.ParameterEntry('n', 2.0)
+ >>> prop = bcp.HillPositive(k=kmax, s1=X, K=Kd, n=hill_n)
+
+ """
+
+ def __init__(self, k: float, s1: Species, K: float, n: float):
Hill.__init__(self=self, k=k, s1=s1, K=K, n=n, d=None)
self.name = 'hillpositive'
def pretty_print_rate(self, show_parameters=True, **kwargs):
+ """Generate human-readable rate formula string.
+
+ Returns
+ -------
+ str
+ Formatted Hill positive formula.
+
+ """
return (
f" Kf = k {self.s1.pretty_print(**kwargs)}^n / "
+ f"( K^n + {self.s1.pretty_print(**kwargs)}^n )"
)
def _get_rate_formula(self, propensity_dict):
+ """Generate SBML-compatible Hill positive rate formula.
+
+ Parameters
+ ----------
+ propensity_dict : dict
+ Propensity dictionary with SBML identifiers.
+
+ Returns
+ -------
+ str
+ SBML rate formula string.
+
+ """
k = propensity_dict['parameters']['k']
n = propensity_dict['parameters']['n']
K = propensity_dict['parameters']['K']
@@ -678,27 +1486,94 @@ def _get_rate_formula(self, propensity_dict):
class HillNegative(Hill):
- def __init__(self, k: float, s1: Species, K: float, n: float):
- """Propensity function for a negative Hill rate law.
+ r"""Negative Hill function propensity (repression).
- Hill negative propensity is a nonlinear propensity with the
- following formula:
+ Implements a repressive Hill function. As s1 increases, the rate
+ decreases from k toward zero.
- p(s1; k, K, n) = k*1/(s1^n + K)
+ Parameters
+ ----------
+ k : float or ParameterEntry
+ Maximum rate constant (when s1=0). Must be positive.
+ s1 : Species
+ Input species that represses the reaction.
+ K : float or ParameterEntry
+ Half-saturation constant (concentration at half-maximal
+ repression). Must be positive.
+ n : float or ParameterEntry
+ Hill coefficient (cooperativity). Must be positive. Values > 1
+ indicate ultrasensitive repression.
- :param k: rate constant (float)
- :param s1: species (chemical_reaction_network.species)
- :param K: dissociation constant (float)
- :param n: cooperativity (float)
+ Attributes
+ ----------
+ name : str
+ Set to 'hillnegative' for this propensity type.
- """
+ See Also
+ --------
+ HillPositive : Activating Hill function.
+ ProportionalHillNegative : Proportional negative Hill.
+
+ Notes
+ -----
+ The following mathematical formula is implemented:
+
+ .. math::
+
+ p(s_1; k, K, n) = \frac{k}{1 + (s_1/K)^n}
+
+ leading to the following behavior:
+
+ - When s1 = 0: rate = k
+ - When s1 = K: rate = k/2
+ - When s1 >> K: rate -> 0
+ - Larger n gives sharper (more switch-like) repression
+
+ Examples
+ --------
+ Create a Hill repression propensity:
+
+ >>> R = bcp.Species('R') # Repressor
+ >>> prop = bcp.HillNegative(k=10.0, s1=R, K=50.0, n=2.0)
+
+ Model transcriptional repression:
+
+ >>> repressor = bcp.Species('repressor')
+ >>> kmax = bcp.ParameterEntry('kmax', 1.0)
+ >>> Ki = bcp.ParameterEntry('Ki', 100.0)
+ >>> prop = bcp.HillNegative(k=kmax, s1=repressor, K=Ki, n=2.0)
+
+ """
+
+ def __init__(self, k: float, s1: Species, K: float, n: float):
Hill.__init__(self=self, k=k, s1=s1, K=K, n=n, d=None)
self.name = 'hillnegative'
def pretty_print_rate(self, show_parameters=True, **kwargs):
+ """Generate human-readable rate formula string.
+
+ Returns
+ -------
+ str
+ Formatted Hill negative formula.
+
+ """
return f" Kf = k / ( 1 + ({self.s1.pretty_print(**kwargs)}/K)^n )"
def _get_rate_formula(self, propensity_dict):
+ """Generate SBML-compatible Hill negative rate formula.
+
+ Parameters
+ ----------
+ propensity_dict : dict
+ Propensity dictionary with SBML identifiers.
+
+ Returns
+ -------
+ str
+ SBML rate formula string.
+
+ """
k = propensity_dict['parameters']['k']
n = propensity_dict['parameters']['n']
K = propensity_dict['parameters']['K']
@@ -708,23 +1583,90 @@ def _get_rate_formula(self, propensity_dict):
class ProportionalHillPositive(HillPositive):
- def __init__(self, k: float, s1: Species, K: float, n: float, d: Species):
- """Propensity function for a proportional Hill runciton rate law.
+ r"""Proportional positive Hill function propensity.
- Proportional Hill positive propensity with the following formula:
+ Implements a positive Hill function with rate proportional to a
+ species concentration. Commonly used for regulated production where
+ the rate depends on both an activator and a template/enzyme.
- p(s1, d; k, K, n) = k*d*s1^n/(s1^n + K)
+ Parameters
+ ----------
+ k : float or ParameterEntry
+ Maximum rate constant per unit of d. Must be positive.
+ s1 : Species
+ Input species that activates the reaction (e.g., transcription
+ factor).
+ K : float or ParameterEntry
+ Half-saturation constant for s1. Must be positive.
+ n : float or ParameterEntry
+ Hill coefficient (cooperativity). Must be positive.
+ d : Species
+ Proportional species (e.g., DNA template, enzyme). Rate scales
+ linearly with this species concentration.
- :param k: rate constant (float)
- :param s1: species (chemical_reaction_network.species)
- :param K: dissociation constant (float)
- :param n: cooperativity (float)
- :param d: species (chemical_reaction_network.species)
- """
+ Attributes
+ ----------
+ name : str
+ Set to 'proportionalhillpositive' for this propensity type.
+
+ See Also
+ --------
+ HillPositive : Non-proportional positive Hill.
+ ProportionalHillNegative : Proportional negative Hill.
+
+ Notes
+ -----
+ The following mathematical formula: is used for the popensity:
+
+ .. math::
+
+ p(s_1, d; k, K, n) = \frac{k d s_1^n}{K^n + s_1^n}
+
+ This is commonly used for transcription, where
+
+ - d = DNA template concentration
+ - s1 = transcription factor concentration
+ - Rate is proportional to both template and TF activation
+
+ This results in the following behaviors:
+
+ - When d = 0: rate = 0 (no template/enzyme)
+ - When s1 = 0: rate ≈ 0 (no activation)
+ - When s1 >> K: rate -> k*d (fully activated, proportional to d)
+
+ Examples
+ --------
+ Model regulated transcription:
+
+ >>> TF = bcp.Species('TF') # Transcription factor
+ >>> DNA = bcp.Species('DNA') # DNA template
+ >>> prop = bcp.ProportionalHillPositive(
+ ... k=0.1, s1=TF, K=50.0, n=2.0, d=DNA)
+
+ Model enzyme with allosteric activator:
+
+ >>> activator = bcp.Species('activator')
+ >>> enzyme = bcp.Species('enzyme')
+ >>> kcat = bcp.ParameterEntry('kcat', 10.0)
+ >>> Ka = bcp.ParameterEntry('Ka', 100.0)
+ >>> prop = bcp.ProportionalHillPositive(
+ ... k=kcat, s1=activator, K=Ka, n=2.0, d=enzyme)
+
+ """
+
+ def __init__(self, k: float, s1: Species, K: float, n: float, d: Species):
Hill.__init__(self=self, k=k, s1=s1, K=K, n=n, d=d)
self.name = 'proportionalhillpositive'
def pretty_print_rate(self, show_parameters=True, **kwargs):
+ """Generate human-readable rate formula string.
+
+ Returns
+ -------
+ str
+ Formatted proportional Hill positive formula.
+
+ """
return (
f" Kf = k {self.d.pretty_print(**kwargs)} "
+ f"{self.s1.pretty_print(**kwargs)}^n / "
@@ -732,6 +1674,19 @@ def pretty_print_rate(self, show_parameters=True, **kwargs):
)
def _get_rate_formula(self, propensity_dict):
+ """Generate SBML-compatible proportional Hill positive formula.
+
+ Parameters
+ ----------
+ propensity_dict : dict
+ Propensity dictionary with SBML identifiers.
+
+ Returns
+ -------
+ str
+ SBML rate formula string.
+
+ """
k = propensity_dict['parameters']['k']
n = propensity_dict['parameters']['n']
K = propensity_dict['parameters']['K']
@@ -741,30 +1696,108 @@ def _get_rate_formula(self, propensity_dict):
class ProportionalHillNegative(HillNegative):
- def __init__(self, k: float, s1: Species, K: float, n: float, d: Species):
- """Proportional Hill negative propensity.
+ r"""Proportional negative Hill function propensity.
- Satisfies the following formula:
+ Implements a repressive Hill function with rate proportional to a
+ species concentration. Commonly used for regulated production where
+ a repressor inhibits production from a template/enzyme.
- p(s1, d; k, K, n) = k*d/(s1^n + K)
+ Parameters
+ ----------
+ k : float or ParameterEntry
+ Maximum rate constant per unit of d (when s1=0). Must be positive.
+ s1 : Species
+ Input species that represses the reaction (e.g., repressor).
+ K : float or ParameterEntry
+ Half-saturation constant for repression by s1. Must be positive.
+ n : float or ParameterEntry
+ Hill coefficient. Must be positive.
+ d : Species
+ Proportional species (e.g., DNA template, enzyme). Rate scales
+ linearly with this species concentration.
- :param k: rate constant (float)
- :param s1: species (chemical_reaction_network.species)
- :param K: dissociation constant (float)
- :param n: cooperativity (float)
- :param d: species (chemical_reaction_network.species)
+ Attributes
+ ----------
+ name : str
+ Set to 'proportionalhillnegative' for this propensity type.
- """
+ See Also
+ --------
+ HillNegative : Non-proportional negative Hill.
+ ProportionalHillPositive : Proportional positive Hill.
+
+ Notes
+ -----
+ The following mathematical formula: is used:
+
+ .. math::
+
+ p(s_1, d; k, K, n) = \frac{k d}{1 + (s_1/K)^n}
+
+ This is commonly used for repressed transcription where
+
+ - d = DNA template concentration
+ - s1 = repressor concentration
+ - Rate is proportional to template but repressed by s1
+
+ and resulting in the following behaviors:
+
+ - When d = 0: rate = 0 (no template/enzyme)
+ - When s1 = 0: rate = k*d (fully derepressed)
+ - When s1 >> K: rate -> 0 (fully repressed)
+
+ Examples
+ --------
+ Model repressed transcription:
+
+ >>> repressor = bcp.Species('repressor')
+ >>> DNA = bcp.Species('DNA')
+ >>> prop = bcp.ProportionalHillNegative(
+ ... k=0.1, s1=repressor, K=50.0, n=2.0, d=DNA)
+
+ Model enzyme with allosteric inhibitor:
+
+ >>> inhibitor = bcp.Species('inhibitor')
+ >>> enzyme = bcp.Species('enzyme')
+ >>> kcat = bcp.ParameterEntry('kcat', 10.0)
+ >>> Ki = bcp.ParameterEntry('Ki', 100.0)
+ >>> prop = bcp.ProportionalHillNegative(
+ ... k=kcat, s1=inhibitor, K=Ki, n=2.0, d=enzyme)
+
+ """
+
+ def __init__(self, k: float, s1: Species, K: float, n: float, d: Species):
Hill.__init__(self=self, k=k, s1=s1, K=K, n=n, d=d)
self.name = 'proportionalhillnegative'
def pretty_print_rate(self, show_parameters=True, **kwargs):
+ """Generate human-readable rate formula string.
+
+ Returns
+ -------
+ str
+ Formatted proportional Hill negative formula.
+
+ """
return (
f" Kf = k {self.d.pretty_print(**kwargs)} / "
+ f"( 1 + ({self.s1.pretty_print(**kwargs)}/K)^{self.n} )"
)
def _get_rate_formula(self, propensity_dict):
+ """Generate SBML-compatible proportional Hill negative formula.
+
+ Parameters
+ ----------
+ propensity_dict : dict
+ Propensity dictionary with SBML identifiers.
+
+ Returns
+ -------
+ str
+ SBML rate formula string.
+
+ """
k = propensity_dict['parameters']['k']
n = propensity_dict['parameters']['n']
K = propensity_dict['parameters']['K']
diff --git a/biocrnpyler/core/reaction.py b/biocrnpyler/core/reaction.py
index b96c0f3e..1389bfd2 100644
--- a/biocrnpyler/core/reaction.py
+++ b/biocrnpyler/core/reaction.py
@@ -13,17 +13,98 @@
class Reaction(object):
- r"""An abstract representation of a chemical reaction in a CRN.
-
+ r"""Chemical reaction in a CRN with species and rate law.
+
+ A `Reaction` represents a chemical transformation between species with
+ an associated propensity (rate law). Reactions can be irreversible or
+ reversible, and support various kinetic models through different
+ propensity types.
+
+ Parameters
+ ----------
+ inputs : list of Species or list of WeightedSpecies
+ Reactant species. Can be Species objects (stoichiometry=1) or
+ WeightedSpecies objects (with custom stoichiometry). Duplicates
+ are automatically combined.
+ outputs : list of Species or list of WeightedSpecies
+ Product species. Can be Species objects (stoichiometry=1) or
+ WeightedSpecies objects (with custom stoichiometry). Duplicates
+ are automatically combined.
+ propensity_type : Propensity
+ Propensity object defining the rate law (e.g., MassAction, Hill).
+
+ Attributes
+ ----------
+ inputs : list of WeightedSpecies
+ Reactant species with stoichiometry.
+ outputs : list of WeightedSpecies
+ Product species with stoichiometry.
+ propensity_type : Propensity
+ The rate law for this reaction.
+ is_reversible : bool
+ True if the propensity supports reversible kinetics.
+ species : list of Species
+ All species involved in the reaction (inputs, outputs, and
+ propensity species).
+
+ See Also
+ --------
+ Species : Chemical species in a CRN.
+ WeightedSpecies : Species with stoichiometric coefficient.
+ Propensity : Base class for rate laws.
+ MassAction : Mass action kinetics propensity.
+
+ Notes
+ -----
A reaction has the form:
- .. math:: \sum_i n_i I_i --> \sum_i m_i O_i @ rate = k
+ .. math::
+ \sum_i n_i I_i \rightarrow \sum_i m_i O_i
+
+ where :math:`n_i` is the stoichiometry of reactant :math:`I_i` and
+ :math:`m_i` is the stoichiometry of product :math:`O_i`.
+
+ For reversible reactions:
+
+ .. math::
+ \sum_i n_i I_i \rightleftharpoons \sum_i m_i O_i
+
+ Stoichiometry is handled as follows:
+
+ - Species lists automatically combine duplicates
+ - A + A --> B becomes 2A --> B
+ - Stoichiometry affects rate calculations in mass action kinetics
+
+ Different propensity types implement different rate laws:
+
+ - MassAction: Standard mass action kinetics
+ - Hill functions: Cooperative binding kinetics
+ - GeneralPropensity: Custom formula
+
+ Examples
+ --------
+ Create a simple irreversible reaction:
+
+ >>> A = bcp.Species('A')
+ >>> B = bcp.Species('B')
+ >>> prop = bcp.MassAction(k_forward=0.1)
+ >>> rxn = bcp.Reaction([A], [B], prop)
+
+ Create a reversible reaction:
+
+ >>> C = bcp.Species('C')
+ >>> prop = bcp.MassAction(k_forward=100.0, k_reverse=0.01)
+ >>> rxn = bcp.Reaction([A, B], [C], prop)
+ >>> rxn.is_reversible
+ True
+
+ Create a reaction with stoichiometry:
+
+ >>> rxn = bcp.Reaction([A, A], [B], prop) # 2A <--> B
- where n_i is the count of the ith input, I_i, and m_i is the count
- of the ith output, O_i. If the reaction is reversible, the
- reverse reaction is also included:
+ Use the from_massaction class method:
- .. math:: \sum_i m_i O_i --> \sum_i n_i I_i @ rate = k_rev
+ >>> rxn = bcp.Reaction.from_massaction([A, B], [C], k_forward=100.0)
"""
@@ -42,13 +123,24 @@ def __init__(
@property
def propensity_type(self) -> Propensity:
+ """Propensity: The rate law for this reaction."""
return self._propensity_type
@propensity_type.setter
def propensity_type(self, new_propensity_type: Propensity):
- """Replace the propensity type associated with the reaction object.
+ """Set the propensity type for the reaction.
+
+ Parameters
+ ----------
+ new_propensity_type : Propensity
+ New propensity object. Must be a valid Propensity subclass
+ instance.
+
+ Raises
+ ------
+ ValueError
+ If `new_propensity_type` is not a valid Propensity instance.
- :param new_propensity_type: Valid propensity type
"""
if not Propensity.is_valid_propensity(new_propensity_type):
raise ValueError(
@@ -66,13 +158,41 @@ def from_massaction(
k_forward: float,
k_reverse: float = None,
):
- """Initialize a Reaction object with mass action kinetics.
+ """Create a Reaction with mass action kinetics.
+
+ Convenience constructor for creating reactions with MassAction
+ propensity.
+
+ Parameters
+ ----------
+ inputs : list of Species or list of WeightedSpecies
+ Reactant species.
+ outputs : list of Species or list of WeightedSpecies
+ Product species.
+ k_forward : float
+ Forward reaction rate constant. Must be positive.
+ k_reverse : float, optional
+ Reverse reaction rate constant. If None, reaction is
+ irreversible. If provided, must be positive.
+
+ Returns
+ -------
+ Reaction
+ New Reaction object with MassAction propensity.
+
+ Examples
+ --------
+ Create an irreversible reaction:
+
+ >>> A = bcp.Species('A')
+ >>> B = bcp.Species('B')
+ >>> rxn = bcp.Reaction.from_massaction([A], [B], k_forward=0.1)
+
+ Create a reversible reaction:
+
+ >>> rxn = bcp.Reaction.from_massaction(
+ ... [A, B], [C], k_forward=100.0, k_reverse=0.01)
- :param inputs:
- :param outputs:
- :param k_forward:
- :param k_reverse:
- :return: Reaction object
"""
mak = MassAction(k_forward=k_forward, k_reverse=k_reverse)
@@ -80,24 +200,51 @@ def from_massaction(
@property
def is_reversible(self) -> bool:
+ """bool: True if the reaction has reversible kinetics.
+
+ Determined by the propensity type's is_reversible property.
+
+ """
return self.propensity_type.is_reversible
@property
def inputs(self) -> List[WeightedSpecies]:
+ """List of WeightedSpecies: Reactant species with stoichiometry."""
return self._input_complexes
@inputs.setter
def inputs(self, new_input_complexes: List[WeightedSpecies]):
+ """Set the reaction inputs.
+
+ Parameters
+ ----------
+ new_input_complexes : list of Species or list of WeightedSpecies
+ New reactant species. Species are automatically converted to
+ WeightedSpecies. Duplicates are combined with adjusted
+ stoichiometry.
+
+ """
self._input_complexes = Reaction._check_and_convert_complex_list(
complexes=new_input_complexes
)
@property
def outputs(self) -> List[WeightedSpecies]:
+ """List of WeightedSpecies: Product species with stoichiometry."""
return self._output_complexes
@outputs.setter
def outputs(self, new_output_complexes: List[WeightedSpecies]):
+ """Set the reaction outputs.
+
+ Parameters
+ ----------
+ new_output_complexes : list of Species or list of WeightedSpecies
+ New product species. Species are automatically converted to
+ WeightedSpecies. Duplicates are combined with adjusted
+ stoichiometry.
+
+ """
self._output_complexes = Reaction._check_and_convert_complex_list(
complexes=new_output_complexes
)
@@ -106,6 +253,37 @@ def outputs(self, new_output_complexes: List[WeightedSpecies]):
def _check_and_convert_complex_list(
complexes: Union[List[Species], List[WeightedSpecies]],
) -> List[WeightedSpecies]:
+ """Convert and validate species list to WeightedSpecies list.
+
+ Converts Species to WeightedSpecies, validates all elements are
+ proper types, and combines duplicate species by summing
+ stoichiometry.
+
+ Parameters
+ ----------
+ complexes : list of Species or list of WeightedSpecies
+ Input species list to convert and validate.
+
+ Returns
+ -------
+ list of WeightedSpecies
+ Converted list with duplicates combined. Each unique species
+ appears once with total stoichiometry.
+
+ Raises
+ ------
+ TypeError
+ If `complexes` contains elements that are neither Species nor
+ WeightedSpecies.
+
+ Notes
+ -----
+ Duplicate handling examples:
+
+ - [A, A, B] --> [2A, B]
+ - [WeightedSpecies(A, 2), A] --> [WeightedSpecies(A, 3)]
+
+ """
if all(
[isinstance(one_complex, Species) for one_complex in complexes]
):
@@ -148,11 +326,37 @@ def _check_and_convert_complex_list(
# return self.propensity_type.k_reverse
def replace_species(self, species: Species, new_species: Species):
- """Replaces species with new_species in the reaction.
-
- :param species: species to be replaced
- :param new_species: the new species the old species is replaced with
- :return: a new Reaction instance
+ """Create new reaction with a species replaced.
+
+ Replaces all occurrences of a species throughout the reaction
+ (inputs, outputs, and propensity species) with a new species.
+
+ Parameters
+ ----------
+ species : Species
+ Species to be replaced.
+ new_species : Species
+ Species to replace with.
+
+ Returns
+ -------
+ Reaction
+ New Reaction object with species replaced. The original
+ reaction is not modified.
+
+ Raises
+ ------
+ ValueError
+ If either argument is not a Species object.
+
+ Examples
+ --------
+ >>> A = bcp.Species('A')
+ >>> B = bcp.Species('B')
+ >>> C = bcp.Species('C')
+ >>> rxn = bcp.Reaction.from_massaction([A, B], [C], k_forward=0.1)
+ >>> D = bcp.Species('D')
+ >>> rxn2 = rxn.replace_species(A, D) # D + B --> C
"""
if not isinstance(species, Species) or not isinstance(
@@ -193,7 +397,16 @@ def replace_species(self, species: Species, new_species: Species):
return new_r
def __repr__(self):
- """Helper function to print the text of a rate function."""
+ """Return string representation of the reaction.
+
+ Returns
+ -------
+ str
+ Reaction equation showing inputs, outputs, material types, and
+ attributes, but not rates or parameters.
+
+ """
+ # Helper function to print the text of a rate function.
return self.pretty_print(
show_rates=False,
show_material=True,
@@ -209,6 +422,44 @@ def pretty_print(
show_parameters=True,
**kwargs,
):
+ """Generate detailed, formatted string representation of reaction.
+
+ Parameters
+ ----------
+ show_rates : bool, default=True
+ If True, includes rate law formula below reaction equation.
+ show_material : bool, default=True
+ If True, shows species material types (e.g., 'dna', 'protein').
+ show_attributes : bool, default=True
+ If True, shows species attributes.
+ show_parameters : bool, default=True
+ If True, shows parameter values in rate law.
+ **kwargs
+ Additional keyword arguments passed to species and propensity
+ pretty_print methods. Can include 'stochastic' (bool) for
+ stochastic vs deterministic rate display.
+
+ Returns
+ -------
+ str
+ Formatted reaction string. Format:
+ 'inputs --> outputs' or 'inputs <--> outputs' (reversible)
+ Optionally followed by rate law and parameters.
+
+ Examples
+ --------
+ >>> A = bcp.Species('A')
+ >>> B = bcp.Species('B')
+ >>> rxn = bcp.Reaction.from_massaction([A], [B], k_forward=0.1)
+ >>> print(rxn.pretty_print())
+ A --> B
+ Kf=k_forward * A
+ k_forward=0.1 Kf = 0.1 * A
+
+ >>> print(rxn.pretty_print(show_rates=False))
+ A --> B
+
+ """
kwargs['show_rates'] = show_rates
kwargs['show_material'] = show_material
kwargs['show_attributes'] = show_attributes
@@ -232,12 +483,39 @@ def pretty_print(
return txt
def __eq__(self, other):
- """Check if reactions are equivalent.
+ """Test equality between reactions.
+
+ Two reactions are equal if they have the same inputs, outputs,
+ and propensity (in any order).
+
+ Parameters
+ ----------
+ other : Reaction
+ Another reaction to compare with.
+
+ Returns
+ -------
+ bool
+ True if reactions have identical inputs, outputs, and
+ propensity.
- Two reactions are equivalent if they have the same inputs,
- outputs, and propensity.
+ Raises
+ ------
+ TypeError
+ If `other` is not a Reaction object.
+
+ Notes
+ -----
+ Order of species in inputs/outputs doesn't matter:
+
+ - A + B --> C equals B + A --> C
+ - Species are compared using sets
"""
+ # Check if reactions are equivalent.
+ #
+ # Two reactions are equivalent if they have the same inputs,
+ # outputs, and propensity.
if not isinstance(other, Reaction):
raise TypeError(
f"Only reactions can be compared with reaction! "
@@ -256,14 +534,37 @@ def __eq__(self, other):
) == (set(other.inputs), set(other.outputs), other.propensity_type)
def __contains__(self, item: Species):
- """It checks whether a species is part of a reaction.
-
- Checks the input and output lists as well as the propensity
- type for the species.
-
- :param item: a Species instance
- :return: bool
- :exception NotImplementedError for non-Species objects
+ """Check if a species is involved in the reaction.
+
+ Checks whether a species appears in the reaction's inputs,
+ outputs, or propensity (e.g., Hill kinetics with regulatory
+ species).
+
+ Parameters
+ ----------
+ item : Species
+ Species to check for.
+
+ Returns
+ -------
+ bool
+ True if species is in inputs, outputs, or propensity species.
+
+ Raises
+ ------
+ NotImplementedError
+ If `item` is not a Species object.
+
+ Examples
+ --------
+ >>> A = bcp.Species('A')
+ >>> B = bcp.Species('B')
+ >>> C = bcp.Species('C')
+ >>> rxn = bcp.Reaction.from_massaction([A], [B], k_forward=0.1)
+ >>> A in rxn
+ True
+ >>> C in rxn
+ False
"""
if isinstance(item, Species):
@@ -279,12 +580,32 @@ def __contains__(self, item: Species):
@property
def species(self) -> List[Species]:
- """List of species in the reactions.
-
- Returns a list of species in the reactions collected from the inputs
- and outputs and the propensity (e.g. Hill kinetics has species in it).
-
- :return: list of species in the reactions
+ """List of Species: All species involved in the reaction.
+
+ Returns a flattened list of all species from inputs, outputs, and
+ the propensity (e.g., Hill functions have regulatory species).
+
+ Returns
+ -------
+ list of Species
+ All species in the reaction. May contain duplicates if a
+ species appears in multiple roles.
+
+ Notes
+ -----
+ This property collects species from three sources:
+
+ 1. Input species (reactants)
+ 2. Output species (products)
+ 3. Propensity species (e.g., activators/repressors in Hill)
+
+ Examples
+ --------
+ >>> A = bcp.Species('A')
+ >>> B = bcp.Species('B')
+ >>> rxn = bcp.Reaction.from_massaction([A], [B], k_forward=0.1)
+ >>> rxn.species
+ [A, B]
"""
in_part = []
diff --git a/biocrnpyler/core/species.py b/biocrnpyler/core/species.py
index f21e12b9..4dd32847 100644
--- a/biocrnpyler/core/species.py
+++ b/biocrnpyler/core/species.py
@@ -12,8 +12,95 @@
class Species(OrderedMonomer):
"""A formal species object for a chemical reaction network (CRN).
- A Species must have a name. They may also have a material_type (such as
- DNA, RNA, Protein), and a list of attributes.
+ Represents a chemical species in a CRN with a name, material type,
+ attributes, and compartment. Species inherits from `OrderedMonomer`,
+ allowing it to be part of polymer structures while also functioning as
+ an independent chemical entity in reactions.
+
+ Parameters
+ ----------
+ name : str
+ Name of the species. Must consist of letters, numbers, or
+ underscores, cannot contain double underscores, and cannot
+ begin/end with special characters.
+ material_type : str, default=''
+ Type of material (e.g., 'dna', 'rna', 'protein', 'complex').
+ Required if name starts with a number. Must start with a letter.
+ attributes : list of str or None, optional
+ List of attribute tags for the species (e.g., 'degraded',
+ 'phosphorylated'). Each attribute must be alphanumeric.
+ compartment : Compartment, str, or None, optional
+ The compartment containing this species. If None, uses default
+ compartment. If str, creates a new Compartment with that name.
+ **kwargs
+ Additional keyword arguments passed to `OrderedMonomer`.
+
+ Attributes
+ ----------
+ name : str
+ The name of the species.
+ material_type : str
+ The material type of the species.
+ attributes : list of str
+ List of attribute tags associated with the species.
+ compartment : Compartment
+ The compartment containing this species.
+ direction : str, int, or None
+ Directional orientation (inherited from `OrderedMonomer`). When
+ set, the direction is also added as an attribute.
+
+ See Also
+ --------
+ ComplexSpecies : Species formed from multiple bound species.
+ OrderedPolymerSpecies : Polymer species for chemical reactions.
+ WeightedSpecies : Species with stoichiometry coefficient.
+
+ Notes
+ -----
+ Species names must:
+
+ - Contain only letters, numbers, and underscores
+ - Not contain double underscores ('__')
+ - Not end with an underscore
+ - Start with a letter or number (if starting with number, requires
+ material_type)
+
+ Species are represented as strings in the format:
+
+ `material_type_name_attribute1_attribute2_compartment`
+
+ Components are omitted if empty or default values.
+
+ Two species
+ are equal if they have the same name, material_type, attributes,
+ compartment, parent, and position.
+
+ Examples
+ --------
+ Create a simple species:
+
+ >>> S = bcp.Species('S')
+ >>> S.name
+ 'S'
+
+ Create a protein with attributes:
+
+ >>> GFP = bcp.Species(
+ ... name='GFP',
+ ... material_type='protein',
+ ... attributes=['fluorescent', 'degraded']
+ ... )
+ >>> repr(GFP)
+ 'protein_GFP_fluorescent_degraded'
+
+ Create a species in a compartment:
+
+ >>> cytoplasm = bcp.Compartment('cytoplasm')
+ >>> enzyme = bcp.Species(
+ ... name='enzyme',
+ ... material_type='protein',
+ ... compartment=cytoplasm
+ ... )
"""
@@ -52,7 +139,20 @@ def attributes(self, attributes):
self._attributes = []
def remove_attribute(self, attribute: str):
- """Remove an attribute from a Species."""
+ """Remove an attribute from the species.
+
+ Parameters
+ ----------
+ attribute : str
+ The attribute to remove. Must be an alphanumeric string.
+
+ Notes
+ -----
+ If the attribute is not present or is None, no action is taken.
+ All occurrences of the attribute are removed from the attributes
+ list.
+
+ """
if not hasattr(self, '_attributes') or attribute is None:
return
else:
@@ -62,7 +162,32 @@ def remove_attribute(self, attribute: str):
self._attributes = [a for a in self.attributes if a != attribute]
def add_attribute(self, attribute: str):
- """Adds attribute to a Species."""
+ """Add an attribute to the species.
+
+ Parameters
+ ----------
+ attribute : str
+ The attribute to add. Must be an alphanumeric string and
+ non-None.
+
+ Raises
+ ------
+ AssertionError
+ If attribute is not an alphanumeric string or is None.
+
+ Notes
+ -----
+ Duplicate attributes are not added - each attribute appears only
+ once in the attributes list.
+
+ Examples
+ --------
+ >>> species = bcp.Species('MyProtein')
+ >>> species.add_attribute('degraded')
+ >>> species.attributes
+ ['degraded']
+
+ """
if not hasattr(self, '_attributes'):
self._attributes = []
assert (
@@ -112,10 +237,22 @@ def direction(self):
@direction.setter
def direction(self, direction):
- """Direction attribute.
-
- This is inheritted from OrderedMonomer. A species with direction
- will use it as an attribute as well. This is overwritten to make
+ """Set the directional orientation of the species.
+
+ Overrides `OrderedMonomer.direction` to automatically add the
+ direction as an attribute and remove the old direction attribute.
+
+ Parameters
+ ----------
+ direction : str, int, or None
+ The direction to assign. Common values include 'forward',
+ 'reverse', 0, 1, or None. When set, the direction is added as
+ an attribute.
+
+ Notes
+ -----
+ This is inherited from `OrderedMonomer`. A species with direction
+ will use it as an attribute as well. This is overwritten to make
direction an attribute.
"""
@@ -128,7 +265,17 @@ def direction(self, direction):
self.add_attribute(direction)
def remove(self):
- """Remove direction as an attribute."""
+ """Remove the species from its parent polymer.
+
+ Overrides `OrderedMonomer.remove` to also remove the direction
+ attribute if present.
+
+ Returns
+ -------
+ Species
+ Returns self after removal for method chaining.
+
+ """
if self.direction is not None:
self.remove_attribute(self.direction)
return OrderedMonomer.remove(self) # call the OrderedMonomer function
@@ -136,11 +283,33 @@ def remove(self):
# Note: this is used because properties can't be overwritten without
# setters being overwritten in subclasses.
def _check_name(self, name):
- """Check that name is in proper format.
-
- Check that the string contains only underscores and alpha-numeric
- characters or is None. Additionally cannot end in "_" or contain
- double "__", also cannot start with a number.
+ """Validate that name follows proper formatting rules.
+
+ Parameters
+ ----------
+ name : str or None
+ The name to validate.
+
+ Returns
+ -------
+ str or None
+ The validated name, unchanged if valid.
+
+ Raises
+ ------
+ ValueError
+ If name violates formatting rules.
+ TypeError
+ If name is not a string or None.
+
+ Notes
+ -----
+ Valid names must:
+
+ - Contain only underscores and alphanumeric characters
+ - Not contain double underscores ('__')
+ - Not end with an underscore
+ - Start with an alphanumeric character
"""
if name is None:
@@ -207,7 +376,10 @@ def __repr__(self):
for i in self.attributes:
if i is not None:
txt += '_' + str(i)
- if self.compartment.name != 'default':
+ if (
+ self.compartment is not None
+ and self.compartment.name != 'default'
+ ):
# Only add a compartment name if it is not the default one. if
# compartment name is already there with an underscore remove it
# from the string first to not repeat the compartment tag
@@ -217,6 +389,30 @@ def __repr__(self):
return txt
def replace_species(self, species, new_species):
+ """Replace a species with another species.
+
+ For a simple Species, returns `new_species` if this species equals
+ `species`, otherwise returns self. For complex species, acts
+ recursively.
+
+ Parameters
+ ----------
+ species : Species
+ The species to search for and replace.
+ new_species : Species
+ The species to replace with.
+
+ Returns
+ -------
+ Species
+ Either `new_species` (if self == species) or self.
+
+ Raises
+ ------
+ ValueError
+ If either argument is not a Species instance.
+
+ """
if not isinstance(species, Species):
raise ValueError(
"species argument must be an instance of Species!"
@@ -233,10 +429,19 @@ def replace_species(self, species, new_species):
return self
def get_species(self, recursive=None):
- """Get species list.
+ """Get a list containing this species.
+
+ Returns
+ -------
+ list of Species
+ A list containing only this species: [self].
- Used in some recursive calls where ComplexSpecies returns a list
- and Species will return just themselves (in a list).
+ Notes
+ -----
+ This method is used in recursive calls where `ComplexSpecies`
+ returns a list of constituent species while `Species` returns just
+ itself in a list. The `recursive` parameter is accepted for
+ compatibility but not used in the base Species class.
"""
return [self]
@@ -249,12 +454,40 @@ def pretty_print(
show_initial_condition=False,
**kwargs, # TODO: allows spurious keywords; fix...
):
- """A more powerful printing function.
-
- Useful for understanding CRNs but does not return string
- identifiers. show_material toggles whether species.material is
- printed. show_attributes toggles whether species.attributes is
- printed
+ """Generate a human-readable string representation of the species.
+
+ Parameters
+ ----------
+ show_material : bool, default=True
+ If True, includes material_type in brackets around the species.
+ show_compartment : bool, default=False
+ If True, shows the compartment name in the representation.
+ show_attributes : bool, default=True
+ If True, includes attributes in parentheses after the name.
+ show_initial_condition : bool, default=False
+ Placeholder for compatibility with CRN printing.
+ **kwargs
+ Additional keyword arguments (currently unused).
+
+ Returns
+ -------
+ str
+ Formatted string representation of the species.
+
+ Notes
+ -----
+ This method provides more detailed output than `__repr__`,
+ useful for understanding CRNs but does not return string
+ identifiers compatible with parsers.
+
+ Format: `material_type[name(attr1, attr2)-direction]`
+
+ Examples
+ --------
+ >>> S = bcp.Species('S', material_type='protein',
+ ... attributes=['active'])
+ >>> S.pretty_print()
+ 'protein[S(active)]'
"""
txt = ''
@@ -288,16 +521,25 @@ def pretty_print(
def __eq__(self, other):
"""Check if two species are equivalent.
- Overrides the default implementation. Two species are equivalent
- if they have the same name, type, and attributes.
+ Two species are equal if they have the same name, material_type,
+ attributes (as sets), parent, compartment, and position.
+
+ Parameters
+ ----------
+ other : Species
+ The species to compare with.
- :param other: Species instance
+ Returns
+ -------
+ bool
+ True if species are equivalent, False otherwise.
- :return: boolean
+ Notes
+ -----
+ Equality between parents and children can result in loops, so
+ string equality is used for parent comparison.
"""
- # Note: "==" equality between parents and children can result in
- # loops, so string equality is used
if (
isinstance(other, Species)
and self.material_type == other.material_type
@@ -312,10 +554,24 @@ def __eq__(self, other):
return False
def monomer_eq(self, other):
- """Check if two monomers are equal.
+ """Check if two monomers are equal, ignoring parent and position.
- Same as normal equality, but does not check for parents or
- positions.
+ Parameters
+ ----------
+ other : Species
+ The species to compare with.
+
+ Returns
+ -------
+ bool
+ True if species have the same name, material_type, attributes,
+ and compartment, regardless of parent or position.
+
+ Notes
+ -----
+ This is the same as normal equality but does not check for parents
+ or positions. Useful for comparing species that may be in different
+ polymer contexts.
"""
if (
@@ -342,10 +598,25 @@ def __contains__(self, item):
return item in self.get_species()
def contains_species_monomer(self, s):
- """Checks if the Species has a monomer (Species) inside of it.
-
- Checks without checking Species.parent, Species.position, or
- direction. In effect, a less stringent version of __contains__.
+ """Check if the species contains a monomer, ignoring context.
+
+ Parameters
+ ----------
+ s : Species
+ The species monomer to search for.
+
+ Returns
+ -------
+ bool
+ True if the species contains a monomer equal to `s` (ignoring
+ parent, position, and direction), False otherwise.
+
+ Notes
+ -----
+ This is a less stringent version of `__contains__` that checks
+ without considering Species.parent, Species.position, or direction.
+ Useful for determining if a species is present regardless of its
+ polymer context.
"""
s_copy = copy.deepcopy(s)
@@ -359,7 +630,28 @@ def contains_species_monomer(self, s):
@staticmethod
def flatten_list(in_list) -> List:
- """Helper function to flatten lists."""
+ """Recursively flatten a nested list of species.
+
+ Parameters
+ ----------
+ in_list : list or Species
+ A potentially nested list of species, or a single species.
+
+ Returns
+ -------
+ list
+ Flattened list containing all species. None elements are
+ filtered out.
+
+ Examples
+ --------
+ >>> S1 = bcp.Species('S1')
+ >>> S2 = bcp.Species('S2')
+ >>> nested = [S1, [S2, None]]
+ >>> bcp.Species.flatten_list(nested)
+ [S1, S2]
+
+ """
out_list = []
if not isinstance(in_list, list):
out_list.append(in_list)
@@ -375,8 +667,43 @@ def flatten_list(in_list) -> List:
class WeightedSpecies:
+ """Container for a species with stoichiometric coefficient.
+
+ Wraps a `Species` object together with its stoichiometry for use in
+ reactions. This class is primarily used internally by the Reaction
+ class to represent reactants and products with their coefficients.
+
+ Parameters
+ ----------
+ species : Species
+ The species object.
+ stoichiometry : int, default=1
+ The stoichiometric coefficient. Must be a positive integer.
+
+ Attributes
+ ----------
+ species : Species
+ The wrapped species object.
+ stoichiometry : int
+ The stoichiometric coefficient (positive integer).
+
+ See Also
+ --------
+ Species : Base class for chemical species.
+ Reaction : Chemical reaction containing weighted species.
+
+ Examples
+ --------
+ Create a weighted species:
+
+ >>> S = bcp.Species('S')
+ >>> ws = bcp.WeightedSpecies(species=S, stoichiometry=2)
+ >>> ws.stoichiometry
+ 2
+
+ """
+
def __init__(self, species: Species, stoichiometry: int = 1):
- """Container object for all types of species and its stoichiometry."""
self.species: Species = species
self.stoichiometry: int = stoichiometry
@@ -404,19 +731,32 @@ def replace_species(self, *args, **kwargs):
@staticmethod
def _count_weighted_species(weighted_species: List):
- """Merge the same species in a list with different stoichiometry.
-
- >>> s1 = Species(name='a')
- >>> ws1 = WeightedSpecies(species=s1, stoichiometry=2)
- >>> ws2 = WeightedSpecies(species=s1, stoichiometry=5)
+ """Merge species in a list with different stoichiometry.
+
+ Combines `WeightedSpecies` objects with the same species by summing
+ their stoichiometric coefficients.
+
+ Parameters
+ ----------
+ weighted_species : list of WeightedSpecies
+ List of weighted species to merge.
+
+ Returns
+ -------
+ dict
+ Dictionary mapping unique `WeightedSpecies` to their total
+ stoichiometry.
+
+ Examples
+ --------
+ >>> s1 = bcp.Species(name='a')
+ >>> ws1 = bcp.WeightedSpecies(species=s1, stoichiometry=2)
+ >>> ws2 = bcp.WeightedSpecies(species=s1, stoichiometry=5)
>>> ws_list = [ws1, ws2]
- >>> freq_dict = WeightedSpecies._count_weighted_species(ws_list)
+ >>> freq_dict = bcp.WeightedSpecies._count_weighted_species(ws_list)
>>> len(freq_dict)
1
- :param weighted_species: list of weighted_species
- :return: unique list of weighted_species, i.e. set(weighted_species)
-
"""
# convert to set doesn't work because we need only species equality
unique_species = []
@@ -447,18 +787,78 @@ def __hash__(self):
class ComplexSpecies(Species):
- """Internal representation of a complex species.
-
- ComplexSpecies and OrderedComplexSpecies should ALWAYS be created with
- the Complex function.
-
- A special kind of species which is formed as a complex of two or more
- species. Used for attribute inheritance and storing groups of bounds
- Species. Note that in a ComplexSpecies, the order of the species list
- does not matter. This means that ComplexSpecies([s1, s2]) =
- ComplexSpecies([s2, s1]). This is good for modelling order-indpendent
- binding complexes. For a case where species order matters
- (e.g. polymers) use OrderedComplexSpecies
+ """Species formed from multiple bound species.
+
+ A special kind of species representing a complex of two or more bound
+ species. ComplexSpecies should always be created using the `Complex`
+ function, not directly. Order of species in the list does not matter:
+ ComplexSpecies([s1, s2]) == ComplexSpecies([s2, s1]).
+
+ Parameters
+ ----------
+ species : list of Species or str
+ List of species forming the complex. Must contain at least 2
+ species.
+ name : str or None, optional
+ Custom name for the complex. If None, generates a name from
+ constituent species.
+ material_type : str, default='complex'
+ Material type identifier for the complex.
+ attributes : list of str or None, optional
+ Attributes for the complex species.
+ compartment : Compartment, str, or None, optional
+ Compartment containing the complex.
+ called_from_complex : bool, default=False
+ Internal flag to enforce use of `Complex` function.
+
+ Attributes
+ ----------
+ species : list of Species
+ Sorted list of constituent species in the complex.
+ species_set : list of Species
+ Unique species in the complex, sorted by string representation.
+ name : str
+ Name of the complex (auto-generated if not provided).
+
+ See Also
+ --------
+ Complex : Metaclass for creating ComplexSpecies.
+ OrderedComplexSpecies : Complex where species order matters.
+ Species : Base class for chemical species.
+
+ Notes
+ -----
+ ComplexSpecies add an additional '_' at the end of their string
+ representation to differentiate edge cases.
+
+ Species order does not affect equality: ComplexSpecies([s1, s2])
+ == ComplexSpecies([s2, s1])
+
+ For ordered complexes, use `OrderedComplexSpecies`.
+
+ If no name is provided, the complex is named by concatenating all
+ constituent species names with counts for duplicates.
+
+ Always use the `Complex` function to create `ComplexSpecies`:
+
+ >>> # Correct
+ >>> complex_species = bcp.Complex([S1, S2])
+
+ >>> # Incorrect (will raise warning)
+ >>> complex_species = bcp.ComplexSpecies([S1, S2])
+
+ Examples
+ --------
+ Create a complex (using Complex function):
+
+ >>> S1 = bcp.Species('S1')
+ >>> S2 = bcp.Species('S2')
+ >>> complex_species = bcp.Complex([S1, S2])
+
+ Check if a species is in a complex:
+
+ >>> S1 in complex_species
+ True
"""
@@ -496,10 +896,18 @@ def __init__(
)
def __repr__(self):
- """String representation of ComplexSpecies.
-
- ComplexSpecies add an additional "_" onto the end of their string
- representation. This ensures that some edge cases are
+ """Generate string representation of ComplexSpecies.
+
+ Returns
+ -------
+ str
+ String representation with an additional '_' at the end to
+ differentiate edge cases.
+
+ Notes
+ -----
+ ComplexSpecies add an additional '_' onto the end of their string
+ representation. This ensures that some edge cases are
differentiated.
"""
@@ -526,7 +934,30 @@ def name(self, name: str):
self._name = self._check_name(name)
def __contains__(self, item):
- """Returns a list of species inside the ComplexSpecies."""
+ """Check if a species is contained in the complex.
+
+ Parameters
+ ----------
+ item : Species
+ The species to search for.
+
+ Returns
+ -------
+ bool
+ True if the species is found in the complex or any nested
+ complexes, False otherwise.
+
+ Raises
+ ------
+ ValueError
+ If `item` is not a Species instance.
+
+ Notes
+ -----
+ This method searches recursively through nested ComplexSpecies to
+ find the target species.
+
+ """
if not isinstance(item, Species):
raise ValueError(
"Operator 'in' requires chemical_reaction_network.Species "
@@ -570,10 +1001,27 @@ def species(self, species):
self._species = species
def replace_species(self, species: Species, new_species: Species):
- """Replace species with new_species in the entire Complex Species.
+ """Replace a species throughout the entire complex.
+
+ Acts recursively on nested ComplexSpecies. Does not modify in
+ place - returns a new ComplexSpecies.
+
+ Parameters
+ ----------
+ species : Species
+ The species to replace.
+ new_species : Species
+ The species to replace with.
- Acts recursively on nested ComplexSpecies Does not act in place -
- returns a new ComplexSpecies.
+ Returns
+ -------
+ ComplexSpecies
+ A new ComplexSpecies with the replacement applied.
+
+ Raises
+ ------
+ ValueError
+ If either argument is not a Species instance.
"""
if not isinstance(species, Species):
@@ -603,10 +1051,19 @@ def replace_species(self, species: Species, new_species: Species):
)
def get_species(self, recursive=False):
- """Returns all species in the ComplexSpecies.
+ """Get all species in the complex.
+
+ Parameters
+ ----------
+ recursive : bool, default=False
+ If True, returns species inside nested ComplexSpecies
+ recursively. If False, returns only this ComplexSpecies.
- If recursive = True, returns species inside internal ComplexSpecies
- recursively as well.
+ Returns
+ -------
+ list of Species
+ List of species. If recursive=False, returns [self]. If
+ recursive=True, returns [self] plus all constituent species.
"""
if not recursive:
@@ -671,21 +1128,92 @@ def pretty_print(
return txt
def monomer_count(self, m):
- """Effectively self.species.count(m) using monomer_eq for equality."""
- return sum([s.monomer_eq(m) for s in self.species])
+ """Count occurrences of a monomer in the complex.
+ Parameters
+ ----------
+ m : Species
+ The monomer to count.
-class OrderedComplexSpecies(ComplexSpecies):
- """Create an ordered complex species.
+ Returns
+ -------
+ int
+ Number of times the monomer appears in the complex, using
+ `monomer_eq` for equality comparison.
- ComplexSpecies and OrderedComplexSpecies should ALWAYS be created with
- the Complex function.
+ Notes
+ -----
+ This effectively implements `self.species.count(m)` but uses
+ `monomer_eq` for equality, which ignores parent and position.
+
+ """
+ return sum([s.monomer_eq(m) for s in self.species])
- A special kind of species which is formed as a complex of two or more
- species. In OrderedComplexSpecies the order in which the complex
- subspecies are is defined denote different species, eg [s1, s2, s3] !=
- [s1, s3, s2]. Used for attribute inheritance and storing groups of
- bounds Species.
+
+class OrderedComplexSpecies(ComplexSpecies):
+ """Complex species where species order is significant.
+
+ A special kind of species formed from a complex of two or more species
+ where the order matters. OrderedComplexSpecies should always be created
+ using the `Complex` function with `ordered=True`, not directly.
+ Unlike ComplexSpecies, [s1, s2, s3] != [s1, s3, s2].
+
+ Parameters
+ ----------
+ species : list of Species or str
+ Ordered list of species forming the complex. Must contain at least
+ 2 species.
+ name : str or None, optional
+ Custom name for the complex. If None, generates a name from
+ constituent species in order.
+ material_type : str, default='ordered_complex'
+ Material type identifier for the ordered complex.
+ attributes : list of str or None, optional
+ Attributes for the complex species.
+ compartment : Compartment, str, or None, optional
+ Compartment containing the complex.
+ called_from_complex : bool, default=False
+ Internal flag to enforce use of `Complex` function.
+
+ Attributes
+ ----------
+ species : list of Species
+ Ordered list of constituent species (NOT sorted).
+ name : str
+ Name of the complex (auto-generated if not provided).
+
+ See Also
+ --------
+ Complex : Metaclass for creating ordered complexes.
+ ComplexSpecies : Complex where species order doesn't matter.
+ OrderedPolymerSpecies : Ordered polymer for chemical reactions.
+
+ Notes
+ -----
+ Unlike `ComplexSpecies`, the order of species matters:
+ OrderedComplexSpecies([s1, s2]) != OrderedComplexSpecies([s2, s1])
+
+ Similar to ComplexSpecies, OrderedComplexSpecies add an additional
+ '_' at the end.
+
+ Always use `Complex` with `ordered=True`:
+
+ >>> # Correct
+ >>> complex_species = bcp.Complex([S1, S2], ordered=True)
+
+ >>> # Incorrect (will raise warning)
+ >>> complex_species = bcp.OrderedComplexSpecies([S1, S2])
+
+ Examples
+ --------
+ Create an ordered complex:
+
+ >>> S1 = bcp.Species('S1')
+ >>> S2 = bcp.Species('S2')
+ >>> ordered = bcp.Complex([S1, S2], ordered=True)
+ >>> reversed = bcp.Complex([S2, S1], ordered=True)
+ >>> ordered == reversed
+ False
"""
@@ -844,23 +1372,80 @@ def pretty_print(
class OrderedPolymerSpecies(OrderedComplexSpecies, OrderedPolymer):
- """OrderedPolymers which can also participate in chemical reactions.
-
- OrderedPolymerSpecies is made up of Species (which are also
- OrderedMonomers).
-
- The Species inside an OrderedPolymerSpecies are meant to model multiple
- binding sites and/or functional regions. ComplexSpecies can be formed
- inside an OrderedPolymer by passing the internal Species at a specific
- location.
-
- When used as an input to a reaction, OrderedPolymerSpecies can be
- passed or one if its internal Species (eg a Species with Species.parent
- = OrderedPolymerSpecies) can also be used to produce the same
- reaction. This allows flexibility in the arguments to different
- Mechanisms. Sometimes, it is convenient to pass in the
- OrderedPolymerSpecies, sometimes it is convenient to pass an internal
- Species. Both will work from the point of view of any Mechanism.
+ """Ordered polymer that can participate in chemical reactions.
+
+ A polymer composed of Species (which are also OrderedMonomers) that can
+ act as a reactant or product in chemical reactions. The internal
+ species represent multiple binding sites and/or functional regions.
+
+ Parameters
+ ----------
+ species : list of Species or list of [Species, direction]
+ List of species monomers to form the polymer. Each element can be
+ a Species or a [Species, direction] pair.
+ name : str or None, optional
+ Custom name for the polymer. If None, auto-generated from
+ constituent species.
+ material_type : str, default='ordered_polymer'
+ Material type identifier for the polymer.
+ compartment : Compartment, str, or None, optional
+ Compartment containing the polymer.
+ attributes : list of str or None, optional
+ Attributes for the polymer species.
+ circular : bool, default=False
+ If True, the polymer has circular topology (e.g., plasmid).
+
+ Attributes
+ ----------
+ polymer : tuple of Species
+ Ordered tuple of species monomers in the polymer.
+ species : tuple of Species
+ Alias for `polymer` (inherited from OrderedPolymer).
+ circular : bool
+ Flag indicating circular topology.
+ default_material : str
+ Class attribute defining default material type.
+
+ See Also
+ --------
+ OrderedPolymer : Base class for ordered polymers.
+ OrderedComplexSpecies : Ordered complex base class.
+ PolymerConformation : Set of polymers with connections.
+
+ Notes
+ -----
+ When used as a reaction input, either the entire
+ OrderedPolymerSpecies or one of its internal Species (with
+ Species.parent = OrderedPolymerSpecies) can be passed to mechanisms.
+
+ Species inside an `OrderedPolymerSpecies` model multiple binding
+ sites and/or functional regions. `ComplexSpecies` can be formed at
+ specific locations by passing the internal Species.
+
+ The `circular` attribute indicates circular topology but does not
+ automatically enforce circular constraints in operations.
+
+ Examples
+ --------
+ Create a linear polymer:
+
+ >>> S1 = bcp.Species('S1')
+ >>> S2 = bcp.Species('S2')
+ >>> polymer = bcp.OrderedPolymerSpecies(
+ ... species=[S1, S2],
+ ... name='my_polymer'
+ ... )
+ >>> len(polymer)
+ 2
+
+ Create a circular polymer (plasmid):
+
+ >>> plasmid = bcp.OrderedPolymerSpecies(
+ ... species=[S1, S2],
+ ... circular=True
+ ... )
+ >>> plasmid.circular
+ True
"""
@@ -922,10 +1507,39 @@ def __init__(
@classmethod
def from_polymer_species(cls, ops, replace_dict, **kwargs):
- """OrderedPolymerSpecies with certain monomers replaced.
-
- inputs: replace_dict {monomer index --> new Species}
- outputs: OrderedPolymerSpecies
+ """Create OrderedPolymerSpecies with specific monomers replaced.
+
+ Parameters
+ ----------
+ ops : OrderedPolymerSpecies
+ The original polymer species to modify.
+ replace_dict : dict
+ Dictionary mapping monomer indices (int) to new Species to
+ insert at those positions.
+ **kwargs
+ Additional keyword arguments for the new OrderedPolymerSpecies.
+ Defaults are inherited from `ops` if not specified.
+
+ Returns
+ -------
+ OrderedPolymerSpecies
+ New polymer with specified monomers replaced.
+
+ Notes
+ -----
+ If `replace_dict` is empty, returns a deep copy of `ops`.
+
+ Examples
+ --------
+ Replace monomer at position 1:
+
+ >>> S1 = bcp.Species('S1')
+ >>> S2 = bcp.Species('S2')
+ >>> S3 = bcp.Species('S3')
+ >>> polymer = bcp.OrderedPolymerSpecies([S1, S2])
+ >>> new_polymer = bcp.OrderedPolymerSpecies.from_polymer_species(
+ ... polymer, {1: S3}
+ ... )
"""
if replace_dict == {}:
@@ -1034,33 +1648,70 @@ def __contains__(self, item):
class PolymerConformation(Species, MonomerCollection):
- """Set of polymers and connections in the form of ComplexSpecies.
-
- This class stores a set of PolymerSpecies and a set of connections
- between them in the form of ComplexSpecies containing Monomers inside
- the PolymerSpecies.
-
- The main function of this class is to provide a unique name to each
- conformation. The name is given by:
-
- conformation__[PolymerSpecies 1]_..._[PolymerSpecies N]
- _[ComplexSpecies_1 parent Polymer indices]
- _[ComplexSpecies_1]..._[ComplexSpecies_M]__
-
- where the list of PolymerSpecies and ComplexSpecies are in alphabetical
- order. The ComplexSpecies parent Polymer indices notes which Polymers
- each Species in the ComplexSpecies comes from, with 'n' used for None.
-
- In general, users should not produce PolymerConformations directly. The
- Complex function will automatically produce these when a complex is
- formed involving Multiple OrderedMonomers contained within one or more
- PolymerSpecies.
-
- In effect, this can be thought of as a data structure for a
- hypergraph. The monomers of the PolymerSpecies are vertices and
- ComplexSpecies form edges that connect an arbitrary number of vertices
- (potentially including other Species as well). Note that this class
- allows for multiple edges between the same sets of vertices.
+ """Set of polymers and their connections via ComplexSpecies.
+
+ Represents a conformation of one or more PolymerSpecies connected by
+ ComplexSpecies containing monomers from the polymers. This class
+ provides unique naming for conformations and serves as a data structure
+ for polymer hypergraphs.
+
+ Parameters
+ ----------
+ complexes : list of ComplexSpecies, optional
+ List of ComplexSpecies connecting monomers from
+ OrderedPolymerSpecies. Must contain monomers from the polymers.
+ polymer : OrderedPolymerSpecies or list of Species, optional
+ Single polymer or list of species to form a polymer. Exactly one
+ of `complexes` or `polymer` must be provided.
+ material_type : str, default='conformation'
+ Material type identifier.
+ name : str or None, optional
+ Custom name for the conformation. If None, auto-generated.
+ **kwargs
+ Additional keyword arguments passed to Species constructor.
+
+ Attributes
+ ----------
+ polymers : list of OrderedPolymerSpecies
+ List of polymers in this conformation.
+ complexes : list of ComplexSpecies
+ List of complexes connecting monomers in the polymers.
+ name : str
+ Auto-generated name encoding polymer and complex structure.
+
+ See Also
+ --------
+ OrderedPolymerSpecies : Polymer species for chemical reactions.
+ ComplexSpecies : Complex of multiple bound species.
+ Complex : Metaclass for creating complexes.
+
+ Notes
+ -----
+ Auto-generated names follow the format:
+ `conformation__[Polymer1]_[Polymer2]_[indices]_[Complex1]_[Complex2]__`
+
+ where indices encode which polymers each complex binds to and the list of
+ `PolymerSpecies` and `ComplexSpecies` are in alphabetical order.
+
+ A `PolymerConformation` represents a hypergraph where:
+
+ - Monomers are vertices
+ - `ComplexSpecies` are hyperedges connecting arbitrary numbers of
+ vertices
+ - Multiple edges between the same vertices are allowed
+
+ Users typically do not create PolymerConformations directly. The
+ `Complex` function automatically creates them when complexing
+ monomers from OrderedPolymerSpecies.
+
+ Examples
+ --------
+ Create from a single polymer:
+
+ >>> S1 = bcp.Species('S1')
+ >>> S2 = bcp.Species('S2')
+ >>> polymer = bcp.OrderedPolymerSpecies([S1, S2])
+ >>> conformation = bcp.PolymerConformation(polymer=polymer)
"""
@@ -1072,12 +1723,6 @@ def __init__(
name=None,
**kwargs,
):
- """Initialize PolymerConformation class.
-
- complexes: a list of ComplexSpecies each of which must contain
- Monomers from the OrderedPolymerSpecies in the conformation
-
- """
Species.__init__(
self, name=name, material_type=material_type, **kwargs
)
@@ -1115,13 +1760,33 @@ def __init__(
def from_polymer_conformation(
cls, pcs, complexes=None, complexes_to_remove=None, **kwargs
):
- """New PolymerConformation from prevous conformaiton plus complexes.
-
- This function produces a new PolymerConformation from previously
- existing PolymerConformations and new Complexes.
-
- pcs: a list of PolymerConformations
- complexes: a list of complexes to add to the polymer conformation
+ """Create PolymerConformation from existing conformations.
+
+ Produces a new PolymerConformation by merging complexes from
+ previous PolymerConformations and adding new complexes.
+
+ Parameters
+ ----------
+ pcs : list of PolymerConformation
+ List of existing PolymerConformations to merge.
+ complexes : list of ComplexSpecies, optional
+ Additional complexes to add to the conformation. Default is an
+ empty list.
+ complexes_to_remove : list of ComplexSpecies, optional
+ Complexes to exclude from the merged conformation. Default is
+ an empty list.
+ **kwargs
+ Additional keyword arguments for the new PolymerConformation.
+
+ Returns
+ -------
+ PolymerConformation
+ New conformation merging all input conformations and complexes.
+
+ Raises
+ ------
+ TypeError
+ If `pcs` is not a list of PolymerConformations.
"""
if not isinstance(pcs, list) or not any(
@@ -1148,17 +1813,43 @@ def from_polymer_conformation(
def from_polymer_replacement(
cls, pc, old_polymers, new_polymers, **kwargs
):
- """Replace old polymers with new polymers.
-
- This function produces a PolymerConformation from a previously
- existing PolymerConformation by replacing old_polymers with
- new_polymers
-
- pc: the PolymerConformation to replace polymers from.
- old_polymers: a list of PolymerSpecies instances. These must be
- the same instances stored inside pc or an error is thrown.
- new_polymers: a list of new PolymerSpecies instances to replace
- each of the old_polymers. Must be the same length as old_polymers.
+ """Create PolymerConformation by replacing polymers.
+
+ Produces a PolymerConformation from an existing one by replacing
+ specified polymers with new ones, updating all complexes
+ accordingly.
+
+ Parameters
+ ----------
+ pc : PolymerConformation
+ The conformation to modify.
+ old_polymers : list of OrderedPolymerSpecies
+ Polymers to replace. Must be the same instances (not just
+ equal) as those in `pc.polymers`.
+ new_polymers : list of OrderedPolymerSpecies
+ New polymers to use as replacements. Must be the same length
+ as `old_polymers`.
+ **kwargs
+ Additional keyword arguments for the new PolymerConformation.
+ Defaults are inherited from `pc` if not specified.
+
+ Returns
+ -------
+ PolymerConformation
+ New conformation with polymers replaced.
+
+ Raises
+ ------
+ TypeError
+ If arguments are not the correct types.
+ ValueError
+ If `old_polymers` are not instances in `pc.polymers`, or if
+ lists have different lengths.
+
+ Notes
+ -----
+ This method updates all complexes to reference monomers from the
+ new polymers at the same positions as in the old polymers.
"""
if not isinstance(pc, PolymerConformation):
@@ -1537,49 +2228,125 @@ def __repr__(self):
class Complex:
"""Metaclass for creating chemical complexes.
- Complex is not a class that gets instantiated - it creates
- ComplexSpecies and OrderedComplexSpecies. The Logic encoded in
- the __new__ function is used to insert these classes into the
- binding sites of OrderedPolymerSpecies.
+ `Complex` is not a class that gets instantiated directly - it creates
+ instances of `ComplexSpecies`, `OrderedComplexSpecies`,
+ `OrderedPolymerSpecies`, or `PolymerConformation` based on the input
+ species and their parent relationships.
- Arguments:
- species: a list of species to put into ComplexSpecies or
- OrderedComplexSpecies
-
- kwargs:
- ordered: whether to produce an OrderedComplexSpecies (default = False)
+ Parameters
+ ----------
+ species : list of Species
+ List of species to combine into a complex. Can include standalone
+ Species, Species with parents (monomers in polymers), or entire
+ OrderedPolymerSpecies.
+ ordered : bool, default=False
+ If True, creates OrderedComplexSpecies where species order
+ matters. If False, creates ComplexSpecies where order is
+ irrelevant.
+ **kwargs
+ Additional keyword arguments passed to the created species class.
+
+ Returns
+ -------
+ ComplexSpecies, OrderedComplexSpecies, OrderedPolymerSpecies, or
+ PolymerConformation
+ The type of species returned depends on the input structure:
+
+ - Simple species list -> ComplexSpecies or OrderedComplexSpecies
+ - Monomers from one polymer -> OrderedPolymerSpecies
+ - Monomers from multiple polymers/conformations ->
+ PolymerConformation
+
+ See Also
+ --------
+ ComplexSpecies : Unordered complex of multiple species.
+ OrderedComplexSpecies : Ordered complex of multiple species.
+ OrderedPolymerSpecies : Polymer species for reactions.
+ PolymerConformation : Multiple polymers with connections.
+
+ Notes
+ -----
+ The `__new__` method implements logic for different scenarios:
+
+ 1. No parents: Creates ComplexSpecies or OrderedComplexSpecies
+ 2. Single polymer parent: Creates OrderedPolymerSpecies with
+ complex at binding site
+ 3. Multiple polymer parents or conformations: Creates
+ PolymerConformation merging all complexes
+ 4. Error cases: Raises exceptions for invalid combinations
+
+ The correct species type is automatically determined from the input,
+ allowing flexible complex formation without explicit type selection.
+
+ Examples
+ --------
+ Create a simple complex:
+
+ >>> S1 = bcp.Species('S1')
+ >>> S2 = bcp.Species('S2')
+ >>> complex = bcp.Complex([S1, S2])
+ >>> type(complex)
+ biocrnpyler.core.species.ComplexSpecies
+
+ Create an ordered complex:
+
+ >>> ordered = bcp.Complex([S1, S2], ordered=True)
+ >>> type(ordered)
+ biocrnpyler.core.species.OrderedComplexSpecies
+
+ Create a complex at a polymer binding site:
+
+ >>> S3 = bcp.Species('S3')
+ >>> polymer = bcp.OrderedPolymerSpecies([S1, S2])
+ >>> # S1 is now inside the polymer at position 0
+ >>> complex = bcp.Complex([polymer[0], S3])
+ >>> type(complex.parent)
+ biocrnpyler.core.species.OrderedPolymerSpecies
"""
def __new__(cls, *args, **kwargs):
- """Produce an instace of the correct species type.
-
- This function effectively produces the instance of the correct
- Species Class based upon the arguments passed in.
-
- Cases: Here species refer to the Species in the Species list passed
- into the construct.
-
- 1. No Species have parents.
- Produces: an ComplexSpecies or an OrderedComplexSepcies
-
- 2. A single Species S has a parent which is an
- OrderedPolymerSpecies with no parent. Produces: an
- OrderedPolymerSpecies with a ComplexSpecies or
- OrderedComplexSpecies containing S in S's location in the
- OrderedPolymerSpecies.
-
- 3. [Error Case] Multiple Species S have parents which are
- OrderedPolymerSpecies without parents.
-
- 4. [Error Case] Entire OrderedPolymerSpecies inside
- PolymerConformations are being Complexed Together.
-
- 5. One or More Species S have parents which are
- OrderedPolymerSpecies with parents and/or PolymerConformations.
-
- Produces: a (Ordered)ComplexSpecies containing all S inside a
- PolymerConformation which merges all PolymerComformation Complexes.
+ """Create an instance of the appropriate species type.
+
+ This method analyzes the input species and their parent
+ relationships to determine which type of complex to create.
+
+ Parameters
+ ----------
+ *args
+ Positional arguments, first should be the species list.
+ **kwargs
+ Keyword arguments including 'species' and 'ordered'.
+
+ Returns
+ -------
+ ComplexSpecies, OrderedComplexSpecies, OrderedPolymerSpecies, or
+ PolymerConformation
+ The appropriate species type based on input structure.
+
+ Raises
+ ------
+ TypeError
+ If species argument is not a list, or if trying to complex
+ entire OrderedPolymerSpecies that are already in
+ PolymerConformations, or if invalid parent types are found.
+ ValueError
+ If trying to form complexes between monomers from multiple
+ OrderedPolymerSpecies without PolymerConformations.
+
+ Notes
+ -----
+ Cases handled:
+
+ 1. No Species have parents -> `ComplexSpecies` or
+ 1OrderedComplexSpecies`
+ 2. Single Species has parent `OrderedPolymerSpecies` (no parent) ->
+ `OrderedPolymerSpecies` with complex at binding site
+ 3. Multiple Species with OrderedPolymerSpecies1` parents (no
+ parents) -> Error (must use PolymerConformations)
+ 4. Entire OrderedPolymerSpecies in PolymerConformations -> Error
+ 5. One or more `Species` from polymer Conformations ->
+ `PolymerConformation` merging all complexes
"""
species = []
diff --git a/biocrnpyler/mechanisms/__init__.py b/biocrnpyler/mechanisms/__init__.py
index bc712136..41ae4ae0 100644
--- a/biocrnpyler/mechanisms/__init__.py
+++ b/biocrnpyler/mechanisms/__init__.py
@@ -9,6 +9,7 @@
"""
from .binding import *
+from .conformation import *
from .enzyme import *
from .global_mechanisms import *
from .integrase import *
diff --git a/biocrnpyler/mechanisms/binding.py b/biocrnpyler/mechanisms/binding.py
index c7489960..21175de2 100644
--- a/biocrnpyler/mechanisms/binding.py
+++ b/biocrnpyler/mechanisms/binding.py
@@ -9,9 +9,76 @@
class One_Step_Cooperative_Binding(Mechanism):
- """A reaction where n binders (A) bind to 1 bindee (B) in one step.
+ """Cooperative binding mechanism for single-step multi-ligand binding.
+
+ A 'binding' mechanism where multiple copies of a binder molecule (A) bind
+ cooperatively to a single bindee molecule (B) in one concerted step. This
+ models cooperative binding events where all ligands bind simultaneously
+ rather than sequentially.
+
+ The binding reaction is given by
+
+ n*A + B <--> A_n:B
+
+ where n is the cooperativity (number of binders).
+
+ Parameters
+ ----------
+ name : str, default='one_step_cooperative_binding'
+ Name identifier for this mechanism instance.
+ mechanism_type : str, default='cooperative_binding'
+ Type classification of this mechanism.
+
+ Attributes
+ ----------
+ name : str
+ Name of the mechanism instance.
+ mechanism_type : str
+ Type classification ('cooperative_binding').
+
+ See Also
+ --------
+ Two_Step_Cooperative_Binding : Sequential cooperative binding mechanism.
+ Combinatorial_Cooperative_Binding : Multiple distinct binders binding.
+ One_Step_Binding : Simple binding without cooperativity.
+ Mechanism : Base class for all mechanisms.
+
+ Notes
+ -----
+ This mechanism is used to model cooperative binding where multiple
+ identical ligands bind simultaneously to a receptor. Common examples
+ include:
+
+ - Oxygen binding to hemoglobin
+ - Transcription factor dimers binding to DNA
+ - Cooperative enzyme-substrate interactions
+
+ The mechanism generates a single reversible mass-action reaction with
+ forward rate constant 'kb' and reverse rate constant 'ku'. The
+ 'cooperativity' parameter determines the stoichiometry of the binding
+ reaction. A cooperativity of 2 models dimeric binding, 3 for trimeric,
+ etc.
+
+ Required parameters for this mechanism:
+
+ - 'kb' : Forward binding rate constant
+ - 'ku' : Reverse unbinding rate constant
+ - 'cooperativity' : Number of binder molecules that bind simultaneously
+
+ Examples
+ --------
+ Create a mechanism for dimeric transcription factor binding:
+
+ >>> promoter = bcp.RegulatedPromoter(
+ ... name='p_dimer',
+ ... regulators='TF_dimer',
+ ... )
+ >>> mixture = bcp.Mixture(
+ ... components=[promoter],
+ ... mechanisms={'binding': bcp.One_Step_Cooperative_Binding()},
+ ... parameters={'cooperativity': 2, 'kb': 0.1, 'ku': 0.01}
+ ... )
- n A + B <--> nA:B
"""
def __init__(
@@ -29,8 +96,56 @@ def update_species(
cooperativity=None,
component=None,
part_id=None,
- **kwords,
+ **kwargs,
):
+ """Generate species for cooperative binding.
+
+ Creates the species involved in cooperative binding: the binder,
+ bindee, and the resulting complex containing multiple binders bound
+ to the bindee.
+
+ Parameters
+ ----------
+ binder : Species
+ The ligand species that binds cooperatively.
+ bindee : Species
+ The target species being bound to.
+ complex_species : Species, optional
+ Pre-specified complex species. If None, automatically creates a
+ Complex containing n binders and 1 bindee, where n is the
+ cooperativity.
+ cooperativity : int or float, optional
+ Number of binder molecules that bind simultaneously. If None,
+ retrieved from component parameters using part_id.
+ component : Component, optional
+ Component containing parameter values. Required if cooperativity
+ is not provided directly.
+ part_id : str, optional
+ Identifier for parameter lookup. If None, defaults to
+ 'repr(binder)-repr(bindee)'.
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Species
+ List containing [binder, bindee, complex] where complex is the
+ cooperative binding product.
+
+ Raises
+ ------
+ ValueError
+ If neither component nor cooperativity is provided.
+ TypeError
+ If complex_species is not a Species or None.
+
+ Notes
+ -----
+ The `cooperativity` parameter determines how many binder molecules
+ are incorporated into the complex. For example, cooperativity=2
+ creates a complex with 2 binders and 1 bindee.
+
+ """
if part_id is None:
part_id = repr(binder) + '-' + repr(bindee)
@@ -69,9 +184,66 @@ def update_reactions(
ku=None,
part_id=None,
cooperativity=None,
- **kwords,
+ **kwargs,
):
- # Get Parameters
+ """Generate reactions for cooperative binding.
+
+ Creates a single reversible mass-action reaction for the cooperative
+ binding of multiple binder molecules to a bindee.
+
+ Parameters
+ ----------
+ binder : Species
+ The ligand species that binds cooperatively.
+ bindee : Species
+ The target species being bound to.
+ complex_species : Species, optional
+ Pre-specified complex species. If None, automatically creates a
+ Complex containing n binders and 1 bindee.
+ component : Component, optional
+ Component containing parameter values. Required if kb, ku, or
+ cooperativity are not provided directly.
+ kb : Parameter or float, optional
+ Forward binding rate constant. If None, retrieved from component
+ parameters.
+ ku : Parameter or float, optional
+ Reverse unbinding rate constant. If None, retrieved from
+ component parameters.
+ part_id : str, optional
+ Identifier for parameter lookup. If None, defaults to
+ 'repr(binder)-repr(bindee)'.
+ cooperativity : int or float, optional
+ Number of binder molecules that bind simultaneously. If None,
+ retrieved from component parameters.
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Reaction
+ List containing a single reversible mass-action reaction for
+ cooperative binding.
+
+ Raises
+ ------
+ ValueError
+ If component is None and any of kb, ku, or cooperativity is not
+ provided.
+ TypeError
+ If complex_species is not a Species or None.
+
+ Notes
+ -----
+ The reaction stoichiometry is determined by the `cooperativity`
+ parameter:
+
+ - cooperativity * binder + bindee <--> complex
+
+ The forward rate is `kb` and reverse rate is `ku`. The reaction
+ follows mass-action kinetics with the appropriate stoichiometric
+ coefficients.
+
+ """
if part_id is None:
part_id = repr(binder) + '-' + repr(bindee)
if kb is None and component is not None:
@@ -126,10 +298,83 @@ def update_reactions(
class Two_Step_Cooperative_Binding(Mechanism):
- """A reaction where n binders (s1) bind to 1 bindee (s2) in two steps.
+ """Sequential cooperative binding mechanism with oligomerization.
+
+ A 'binding' mechanism where multiple binder molecules first oligomerize,
+ then the oligomer binds to the bindee in a two-step process. This models
+ cooperative binding where ligands must first form a multimeric complex
+ before binding to their target.
+
+ The binding process follows two sequential reactions:
+
+ 1. n*A <--> A_n (oligomerization)
+ 2. A_n + B <--> A_n:B (binding)
+
+ where n is the cooperativity.
+
+ Parameters
+ ----------
+ name : str, default='two_step_cooperative_binding'
+ Name identifier for this mechanism instance.
+ mechanism_type : str, default='cooperative_binding'
+ Type classification of this mechanism.
+
+ Attributes
+ ----------
+ name : str
+ Name of the mechanism instance.
+ mechanism_type : str
+ Type classification ('cooperative_binding').
+
+ See Also
+ --------
+ One_Step_Cooperative_Binding : Single-step cooperative binding.
+ Combinatorial_Cooperative_Binding : Multiple distinct binders.
+ Mechanism : Base class for all mechanisms.
+
+ Notes
+ -----
+ This mechanism models cooperative binding as a two-step process:
+
+ 1. Oligomerization: Binder molecules first associate to form an
+ oligomer (dimer, trimer, etc.)
+ 2. Binding: The oligomer then binds to the target
+
+ This is useful for modeling:
+
+ - Protein dimerization followed by DNA binding
+ - Receptor oligomerization before ligand binding
+ - Sequential assembly and binding processes
+
+ Required parameters for this mechanism:
+
+ - 'kb1' : Forward rate constant for oligomerization
+ - 'ku1' : Reverse rate constant for oligomerization
+ - 'kb2' : Forward rate constant for oligomer-bindee binding
+ - 'ku2' : Reverse rate constant for oligomer-bindee binding
+ - 'cooperativity' : Number of binder molecules in the oligomer
+
+ Examples
+ --------
+ Model transcription factor dimerization followed by DNA binding:
+
+ >>> mech = bcp.Two_Step_Cooperative_Binding()
+ >>> # TF dimerizes (2*TF <-> TF2), then binds DNA (TF2 + DNA <-> TF2:DNA)
+ >>> params = {
+ ... 'cooperativity': 2,
+ ... 'kb1': 0.1, 'ku1': 0.01, # Dimerization rates
+ ... 'kb2': 1.0, 'ku2': 0.001 # DNA binding rates
+ ... }
+
+ Model trimeric receptor assembly and activation:
+
+ >>> mech = bcp.Two_Step_Cooperative_Binding()
+ >>> params = {
+ ... 'cooperativity': 3, # Trimeric receptor
+ ... 'kb1': 0.05, 'ku1': 0.1, # Trimerization
+ ... 'kb2': 10.0, 'ku2': 0.01 # Ligand binding
+ ... }
- n A <--> nx_A
- nx_A <--> nx_A:B
"""
def __init__(
@@ -148,8 +393,57 @@ def update_species(
n_mer_species=None,
cooperativity=None,
part_id=None,
- **keywords,
+ **kwargs,
):
+ """Generate species for two-step cooperative binding.
+
+ Creates the species involved in sequential cooperative binding:
+ binder, bindee, oligomer (n-mer), and final complex.
+
+ Parameters
+ ----------
+ binder : Species
+ The ligand species that oligomerizes then binds.
+ bindee : Species
+ The target species that the oligomer binds to.
+ component : Component, optional
+ Component containing parameter values. Required if cooperativity
+ is not provided directly.
+ complex_species : Species, optional
+ Pre-specified final complex species. If None, automatically
+ creates a Complex containing the n-mer and bindee.
+ n_mer_species : Species, optional
+ Pre-specified oligomer species. If None, automatically creates a
+ Complex containing n binders.
+ cooperativity : int or float, optional
+ Number of binders in the oligomer. If None, retrieved from
+ component parameters.
+ part_id : str, optional
+ Identifier for parameter lookup. If None, defaults to
+ 'repr(binder)-repr(bindee)'.
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Species
+ List containing [binder, bindee, complex, n_mer] where n_mer is
+ the oligomer and complex is the final bound product.
+
+ Raises
+ ------
+ ValueError
+ If neither component nor cooperativity is provided.
+ TypeError
+ If n_mer_species or complex_species is not a Species or None.
+
+ Notes
+ -----
+ The n_mer represents the oligomerized form of the binder (e.g., a
+ dimer for cooperativity=2). The complex represents the n_mer bound
+ to the bindee.
+
+ """
if part_id is None:
part_id = repr(binder) + '-' + repr(bindee)
@@ -201,20 +495,66 @@ def update_reactions(
cooperativity=None,
complex_species=None,
n_mer_species=None,
- **keywords,
+ **kwargs,
):
- """Update reactions.
-
- Returns reactions:
- cooperativity binder <--> n_mer, kf = kb1, kr = ku1
- n_mer + bindee <--> complex, kf = kb2, kr = ku2
- :param s1:
- :param s2:
- :param kb:
- :param ku:
- :param cooperativity:
- :param keywords:
- :return:
+ """Generate reactions for two-step cooperative binding.
+
+ Creates two sequential reactions: oligomerization of binders followed
+ by oligomer binding to the bindee.
+
+ Parameters
+ ----------
+ binder : Species
+ The ligand species that oligomerizes then binds.
+ bindee : Species
+ The target species that the oligomer binds to.
+ kb : list of float or Parameter, optional
+ Forward rate constants [kb1, kb2] for oligomerization and binding.
+ If None, retrieved from component parameters.
+ ku : list of float or Parameter, optional
+ Reverse rate constants [ku1, ku2] for oligomerization and binding.
+ If None, retrieved from component parameters.
+ component : Component, optional
+ Component containing parameter values. Required if kb, ku, or
+ cooperativity are not provided.
+ part_id : str, optional
+ Identifier for parameter lookup. If None, defaults to
+ 'repr(binder)-repr(bindee)'.
+ cooperativity : int or float, optional
+ Number of binders in the oligomer. If None, retrieved from
+ component parameters.
+ complex_species : Species, optional
+ Pre-specified final complex species.
+ n_mer_species : Species, optional
+ Pre-specified oligomer species.
+ **kwargs
+ Additional keyword arguments passed to update_species.
+
+ Returns
+ -------
+ list of Reaction
+ List containing two reactions:
+
+ 1. Oligomerization: cooperativity*binder <--> n_mer
+ 2. Binding: n_mer + bindee <--> complex
+
+ Raises
+ ------
+ ValueError
+ If component is None and `kb`, `ku`, or `cooperativity` is not
+ provided, or if `kb` and `ku` do not contain exactly 2 values
+ each.
+
+ Notes
+ -----
+ The two-step process uses separate rate constants:
+
+ - 'kb1', 'ku1': Control oligomerization kinetics
+ - 'kb2', 'ku2': Control oligomer-bindee binding kinetics
+
+ This separation allows modeling of processes where oligomerization
+ and binding have different kinetic properties.
+
"""
if part_id is None:
repr(binder) + '-' + repr(bindee)
@@ -262,7 +602,7 @@ def update_reactions(
n_mer_species=n_mer_species,
cooperativity=cooperativity,
part_id=part_id,
- **keywords,
+ **kwargs,
)
inputs_for_rxn1 = [
@@ -287,28 +627,122 @@ def update_reactions(
class Combinatorial_Cooperative_Binding(Mechanism):
- """Reaction where some number binders bind combinatorially to bindee."""
+ """Combinatorial binding mechanism for multiple distinct ligands.
+
+ A 'binding' mechanism where different types of binder molecules can bind
+ to a bindee in various combinations, each with its own cooperativity. This
+ models complex regulatory scenarios where multiple transcription factors
+ or ligands can bind to the same target in different combinations, each
+ producing a distinct complex.
+
+ The mechanism generates all possible binding combinations and the
+ reactions between them, considering individual binding affinities and
+ cooperativities for each binder type.
+
+ Parameters
+ ----------
+ name : str, default='Combinatorial_Cooperative_binding'
+ Name identifier for this mechanism instance.
+ mechanism_type : str, default='cooperative_binding'
+ Type classification of this mechanism.
+
+ Attributes
+ ----------
+ name : str
+ Name of the mechanism instance.
+ mechanism_type : str
+ Type classification ('cooperative_binding').
+
+ See Also
+ --------
+ One_Step_Cooperative_Binding : Single binder type cooperative binding.
+ CombinatorialPromoter : Component that uses this mechanism.
+ Mechanism : Base class for all mechanisms.
+
+ Notes
+ -----
+ This mechanism is designed for modeling complex regulatory logic where:
+
+ - Multiple different regulators can bind to the same target
+ - Each regulator can have its own cooperativity (e.g., some bind as
+ dimers, others as monomers)
+ - All possible combinations of bound states are generated
+ - Each transition between states has specific rate constants
+
+ The mechanism generates a complete reaction network connecting all
+ possible bound states. For n different binders, this creates 2^n - 1
+ different complexes (excluding the unbound state).
+
+ Required parameters for this mechanism (per binder):
+
+ - 'kb': Forward binding rate
+ - 'ku': Reverse unbinding rate
+ - 'cooperativity': Number of molecules binding together
+
+ This is commonly used for:
+
+ - Complex promoter regulation with multiple transcription factors
+ - Multi-ligand receptor systems
+ - Combinatorial protein complex assembly
+
+ Examples
+ --------
+ Model a promoter with two different transcription factors:
+
+ >>> A, B = bcp.Species('A'), bcp.Species('B')
+ >>> AND_promoter = bcp.CombinatorialPromoter(
+ ... 'AND_promoter', [A, B], tx_capable_list=[[A, B]], leak=False)
+ ... AND_assembly = bcp.DNAassembly(
+ ... 'AND', promoter=AND_promoter, rbs='medium', protein='GFP')
+ ... mixture = bcp.ExpressionExtract(
+ ... name='AND_mixture', components=[AND_assembly],
+ ... parameter_file=[
+ ... 'mechanisms/binding_parameters.tsv',
+ ... 'mixtures/extract_parameters.tsv',
+ ... ]
+ ... )
+ ... crn = mixture.compile_crn()
+
+ """
def __init__(
self,
name='Combinatorial_Cooperative_binding',
mechanism_type='cooperative_binding',
):
- """Initializes a Combinatorial_Cooperative_Binding instance.
-
- :param name: name of the Mechanism, default: \
- Combinatorial_Cooperative_binding
- :param mechanism_type: type of the Mechanism, default: \
- cooperative_binding
- """
Mechanism.__init__(self, name, mechanism_type)
def make_cooperative_complex(self, combo, bindee, cooperativity):
- """Make complex containing multipple binders.
+ """Create a complex with multiple cooperative binders.
- Given a list of binders and their cooperativities, make a complex that
- contains the binders present N number of times where N is each one's
- cooperativity.
+ Constructs a complex species containing the specified combination of
+ binders (each repeated according to its cooperativity) bound to the
+ bindee.
+
+ Parameters
+ ----------
+ combo : tuple or list of Species
+ Combination of binder species to include in the complex.
+ bindee : Species
+ The target species being bound to.
+ cooperativity : dict
+ Dictionary mapping binder names (str) to their cooperativity
+ values (int). Determines how many copies of each binder are
+ included.
+
+ Returns
+ -------
+ Species or Complex
+ If only bindee is present (empty combo), returns bindee alone.
+ Otherwise returns a Complex containing all binders (repeated per
+ cooperativity) and the bindee.
+
+ Notes
+ -----
+ For each binder in combo, the method adds cooperativity[binder.name]
+ copies to the complex. For example, if binder A has cooperativity 2
+ and binder B has cooperativity 1, the complex for combo=[A, B] would
+ contain [A, A, B, bindee].
"""
complexed_species_list = []
@@ -333,12 +767,59 @@ def update_species(
cooperativity=None,
component=None,
part_id=None,
- **kwords,
+ **kwargs,
):
+ """Generate all species for combinatorial binding.
+
+ Creates all possible complexes from combinations of binders bound to
+ the bindee, considering each binder's cooperativity.
+
+ Parameters
+ ----------
+ binders : list of Species
+ List of different binder species that can bind in combinations.
+ bindee : Species
+ The target species being bound to.
+ cooperativity : dict, optional
+ Dictionary mapping binder names to cooperativity values. If None
+ for any binder, retrieved from component parameters.
+ component : Component, optional
+ Component containing parameter values. Required if cooperativity
+ values are not provided.
+ part_id : str, optional
+ Base identifier for parameter lookup. Individual binder parameters
+ are looked up as 'part_id_bindername'.
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Species
+ List of all possible complexes from binding combinations. For n
+ binders, returns 2^n - 1 complexes (all combinations except
+ unbound bindee).
+
+ Raises
+ ------
+ ValueError
+ If component is None and cooperativity is not provided for all
+ binders.
+
+ Notes
+ -----
+ This method generates all possible combinations of binders:
+ - Single binders: A:bindee, B:bindee, etc.
+ - Pairs: A:B:bindee, A:C:bindee, etc.
+ - Higher combinations up to all binders bound simultaneously
+
+ Each complex respects the individual cooperativity of its binders.
+
+ """
cooperativity_dict = {}
out_species = []
+ prefix = "" if part_id is None else part_id + '_'
for binder in binders:
- binder_partid = part_id + '_' + binder.name
+ binder_partid = prefix + binder.name
if (
(cooperativity is None)
or (
@@ -388,11 +869,76 @@ def update_reactions(
kus=None,
part_id=None,
cooperativity=None,
- **kwords,
+ **kwargs,
):
+ """Generate reactions for all combinatorial binding transitions.
+
+ Creates reactions connecting all possible binding states, where each
+ reaction represents one binder type associating or dissociating while
+ other binders remain bound.
+
+ Parameters
+ ----------
+ binders : list of Species
+ List of different binder species that can bind in combinations.
+ bindee : Species
+ The target species being bound to.
+ component : Component, optional
+ Component containing parameter values. Required if rate constants
+ or cooperativities are not provided.
+ kbs : dict, optional
+ Dictionary mapping binder names to forward rate constants (kb).
+ If None for any binder, retrieved from component parameters.
+ kus : dict, optional
+ Dictionary mapping binder names to reverse rate constants (ku).
+ If None for any binder, retrieved from component parameters.
+ part_id : str, optional
+ Base identifier for parameter lookup. Individual binder parameters
+ are looked up as 'part_id_bindername'.
+ cooperativity : dict, optional
+ Dictionary mapping binder names to cooperativity values. If None
+ for any binder, retrieved from component parameters.
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Reaction
+ List of all reactions connecting binding states. Each reaction
+ represents adding or removing one binder type to/from an existing
+ complex.
+
+ Raises
+ ------
+ ValueError
+ If component is None and any required parameters (kb, ku,
+ cooperativity) are not provided.
+
+ Notes
+ -----
+ The reaction network connects all possible binding states such that:
+
+ - Each reaction adds or removes exactly one binder type
+ - Rate constants are specific to each binder
+ - Cooperativity determines the stoichiometry of each binder
+
+ For n binders, this generates approximately n * 2^(n-1) reactions,
+ connecting the 2^n possible states (including unbound).
+
+ Each binder requires three parameters:
+
+ - 'kb': Forward binding rate constant
+ - 'ku': Reverse unbinding rate constant
+ - 'cooperativity': Stoichiometry of that binder
+
+ The algorithm avoids generating duplicate reactions by tracking which
+ transitions have been created.
+
+ """
binder_params = {}
+ prefix = "" if part_id is None else part_id + '_'
for binder in binders:
- binder_partid = part_id + '_' + binder.name
+ binder_partid = prefix + binder.name
if (isinstance(kbs, dict) and binder not in kbs) or (
not isinstance(kbs, dict) and component is not None
):
@@ -504,9 +1050,86 @@ def update_reactions(
class One_Step_Binding(Mechanism):
- """A mechanism to model the binding of a list of species.
+ """Simple binding mechanism for multiple species without cooperativity.
+
+ A 'binding' mechanism to model the simultaneous binding of multiple
+ species into a single complex in one concerted step. Unlike cooperative
+ binding mechanisms, this treats all species equally without cooperativity
+ factors - each species contributes exactly one molecule to the complex.
+
+ The binding reaction follows:
+ S1 + S2 + ... + Sn <--> S1:S2:...:Sn
+
+ Parameters
+ ----------
+ name : str, default='one_step_binding'
+ Name identifier for this mechanism instance.
+ mechanism_type : str, default='binding'
+ Type classification of this mechanism.
+
+ Attributes
+ ----------
+ name : str
+ Name of the mechanism instance.
+ mechanism_type : str
+ Type classification ('binding').
+
+ See Also
+ --------
+ One_Step_Cooperative_Binding : Binding with cooperativity.
+ Mechanism : Base class for all mechanisms.
+
+ Notes
+ -----
+ This mechanism is the simplest binding model where multiple distinct
+ species come together to form a complex. Each species contributes
+ exactly one molecule (stoichiometry of 1) to the complex.
+
+ Common applications include:
+
+ - Protein complex formation from distinct subunits
+ - Multi-component enzyme assembly
+ - Simple receptor-ligand binding
+ - Formation of heterodimers or heterotrimers
+
+ The mechanism generates a single reversible mass-action reaction with
+ rate constants 'kb' (forward) and 'ku' (reverse).
+
+ Key differences from cooperative binding:
+
+ - No 'cooperativity' parameter - all stoichiometries are 1
+ - Can handle arbitrary lists of different species
+ - Simpler parameter structure (single 'kb', 'ku' for the entire reaction)
+
+ Required parameters for this mechanism:
+
+ - 'kb' : Forward binding rate constant
+ - 'ku' : Reverse unbinding rate constant
+
+ Examples
+ --------
+ Model receptor-ligand binding:
+
+ >>> mech = bcp.One_Step_Binding()
+ >>> ligand, receptor = bcp.Species('L'), bcp.Species('R')
+ >>> mech.update_species(
+ ... binder=ligand,
+ ... bindee=receptor,
+ ... kb=1.0, ku=0.001
+ ... )
+ [L, R, complex_L_R_]
+
+ Model formation of a three-protein complex:
+
+ >>> proteins = [
+ ... bcp.Species(s, material_type='protein') for s in ['A', 'B', 'C']]
+ >>> rxns = mech.update_reactions(
+ ... binder=proteins[0],
+ ... bindee=proteins[1:],
+ ... kb=0.1, ku=0.01
+ ... )
+ >>> # Generates: A + B + C <--> A:B:C
- S1 + S2 ... SN <--> S1:S2:...:SN
"""
def __init__(self, name='one_step_binding', mechanism_type='binding'):
@@ -519,8 +1142,49 @@ def update_species(
component=None,
complex_species=None,
part_id=None,
- **keywords,
+ **kwargs,
):
+ """Generate species for simple multi-species binding.
+
+ Creates the list of species involved in the binding reaction: all
+ input species plus the resulting complex.
+
+ Parameters
+ ----------
+ binder : Species or list of Species
+ The first species or list of species to bind. Automatically
+ converted to list if single species provided.
+ bindee : Species or list of Species
+ The second species or list of species to bind. Automatically
+ converted to list if single species provided.
+ component : Component, optional
+ Component containing this mechanism (unused but kept for API
+ consistency).
+ complex_species : Species, optional
+ Pre-specified complex species. If None, automatically creates a
+ Complex containing all binders and bindees.
+ part_id : str, optional
+ Identifier for parameter lookup. If None, automatically generated
+ from species names as `name1_name2_..._nameN`.
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Species
+ List containing all input species plus the complex. Format:
+ [binder1, ..., bindee1, ..., complex].
+
+ Notes
+ -----
+ The binder/bindee distinction is primarily for API consistency with
+ other binding mechanisms. Functionally, all species are treated
+ equally in the binding reaction.
+
+ The complex is created as a Complex object containing all input
+ species in the order: binders + bindees.
+
+ """
if not isinstance(binder, list):
binder = [binder]
if not isinstance(bindee, list):
@@ -546,8 +1210,59 @@ def update_reactions(
part_id=None,
kb=None,
ku=None,
- **keywords,
+ **kwargs,
):
+ """Generate reaction for simple multi-species binding.
+
+ Creates a single reversible mass-action reaction for the binding of
+ all species into a complex.
+
+ Parameters
+ ----------
+ binder : Species or list of Species
+ The first species or list of species to bind. Automatically
+ converted to list if single species provided.
+ bindee : Species or list of Species
+ The second species or list of species to bind. Automatically
+ converted to list if single species provided.
+ component : Component, optional
+ Component containing parameter values. Required if kb or ku are
+ not provided directly.
+ complex_species : Species, optional
+ Pre-specified complex species. If None, automatically creates a
+ Complex containing all binders and bindees.
+ part_id : str, optional
+ Identifier for parameter lookup. If None, automatically generated
+ from species names as `name1_name2_..._nameN`.
+ kb : Parameter or float, optional
+ Forward binding rate constant. If None, retrieved from component
+ parameters.
+ ku : Parameter or float, optional
+ Reverse unbinding rate constant. If None, retrieved from
+ component parameters.
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Reaction
+ List containing a single reversible mass-action reaction for the
+ binding of all species.
+
+ Raises
+ ------
+ ValueError
+ If component is None and kb or ku is not provided.
+
+ Notes
+ -----
+ The reaction has equal stoichiometry (1) for all species:
+ species1 + species2 + ... + speciesN <--> complex
+
+ This is simpler than cooperative binding mechanisms which can have
+ varying stoichiometries for different species.
+
+ """
if not isinstance(binder, list):
binder = [binder]
if not isinstance(bindee, list):
diff --git a/biocrnpyler/mechanisms/binding_parameters.tsv b/biocrnpyler/mechanisms/binding_parameters.tsv
new file mode 100644
index 00000000..726967ba
--- /dev/null
+++ b/biocrnpyler/mechanisms/binding_parameters.tsv
@@ -0,0 +1,7 @@
+mechanism_id part_id param_name param_val units comments
+ ktx 0.05 transcripts/sec/polymerase Assuming 50nt/s and transcript length of 1000
+ ktl 0.05 proteins/sec/nM Assuming 15aa/s and protein length of 300
+ cooperativity 2
+ kb 100 assuming 10ms to diffuse across 1um (characteristic cell size)
+ ku 10 90% binding
+ kdil 0.001 assuming half life of ~20 minutes for everything (e coli doubling time)
diff --git a/biocrnpyler/mechanisms/conformation.py b/biocrnpyler/mechanisms/conformation.py
index 77e5c73e..da7ab284 100644
--- a/biocrnpyler/mechanisms/conformation.py
+++ b/biocrnpyler/mechanisms/conformation.py
@@ -6,9 +6,57 @@
class One_Step_Reversible_Conformation_Change(Mechanism):
- """A reaction where n binders (A) bind to 1 bindee (B) in one step.
+ """Reversible conformational change mechanism.
+
+ A mechanism that models the reversible conformational change of a species
+ from one state to another. This can represent protein folding/unfolding,
+ DNA structural changes, allosteric transitions, or any other molecular
+ conformational switch. Additional species (cofactors, ions, etc.) can be
+ required for the conformational change.
+
+ The reaction follows:
+
+ s0 [+ additional species] <--> sf [+ additional species]
+
+ where s0 is the initial conformation and sf is the final conformation.
+
+ Parameters
+ ----------
+ name : str, default='one_step_conformation_change'
+ Name identifier for this mechanism instance.
+ mechanism_type : str, default='conformation_change'
+ Type classification of this mechanism.
+
+ Attributes
+ ----------
+ name : str
+ Name of the mechanism instance.
+ mechanism_type : str
+ Type classification ('conformation_change').
+
+ See Also
+ --------
+ Mechanism : Base class for all mechanisms.
+
+ Notes
+ -----
+ This mechanism is used to model conformational changes including:
+
+ - Protein folding: Native <--> denatured states
+ - Allosteric transitions: Inactive <--> active enzyme forms
+ - DNA structural changes: B-DNA <--> Z-DNA transitions
+ - Receptor activation: Closed <--> open channel states
+ - Molecular switches: Any two-state molecular system
+
+ The mechanism requires two rate constants:
+
+ - 'kf': Forward rate constant (s0 -> sf)
+ - 'kr': Reverse rate constant (sf -> s0)
+
+ Additional species can participate in the conformational change without
+ being modified (e.g., ATP required for a conformational switch but not
+ consumed).
- n A + B <--> nA:B
"""
def __init__(
@@ -16,6 +64,11 @@ def __init__(
name='one_step_conformation_change',
mechanism_type='conformation_change',
):
+ """Initialize a One_Step_Reversible_Conformation_Change mechanism.
+
+ See class docstring for parameter descriptions.
+
+ """
Mechanism.__init__(self, name, mechanism_type)
def update_species(
@@ -25,8 +78,46 @@ def update_species(
additional_species=None,
component=None,
part_id=None,
- **kwords,
+ **kwargs,
):
+ """Generate species for conformational change.
+
+ Creates the list of species involved in the conformational change,
+ including initial and final conformations plus any additional species
+ required for the transition.
+
+ Parameters
+ ----------
+ s0 : Species
+ The initial conformation state of the molecule.
+ sf : Species
+ The final conformation state of the molecule.
+ additional_species : list of Species, optional
+ Additional species required for the conformational change (e.g.,
+ cofactors, ions) but not consumed. Default is empty list.
+ component : Component, optional
+ Component containing this mechanism (for context, not used here).
+ part_id : str, optional
+ Identifier for parameter lookup (not used in species generation).
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Species
+ List containing [s0, sf] plus any additional species.
+
+ Notes
+ -----
+ The additional species participate in the conformational change but
+ are not modified. They appear on both sides of the reaction. This is
+ useful for modeling:
+
+ - Cofactor-dependent conformational changes
+ - Ion-induced structural transitions
+ - Ligand-stabilized conformations
+
+ """
if additional_species is None:
additional_species = []
@@ -39,10 +130,63 @@ def update_reactions(
additional_species=None,
component=None,
part_id=None,
- **kwords,
+ **kwargs,
):
+ """Generate reactions for conformational change.
+
+ Creates a reversible mass-action reaction for the conformational
+ transition between two states of a molecule.
+
+ Parameters
+ ----------
+ s0 : Species
+ The initial conformation state of the molecule.
+ sf : Species
+ The final conformation state of the molecule.
+ additional_species : list of Species, optional
+ Additional species required for the conformational change (e.g.,
+ cofactors, ions) but not consumed. These appear on both sides of
+ the reaction. Default is empty list.
+ component : Component
+ Component containing parameter values. Required for retrieving
+ 'kf' and 'kr' rate constants.
+ part_id : str, optional
+ Identifier for parameter lookup. If None, defaults to
+ 'repr(s0)-repr(sf)'.
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Reaction
+ List containing a single reversible mass-action reaction for the
+ conformational change.
+
+ Raises
+ ------
+ ValueError
+ If component is None (implicitly, when parameters cannot be
+ retrieved).
+
+ Notes
+ -----
+ The reaction equation depends on additional_species:
+
+ - Without additional species: s0 <--> sf
+ - With additional species: s0 + additionals <--> sf + additionals
+
+ The mechanism requires two parameters:
+
+ - 'kf': Forward rate constant (s0 -> sf transition)
+ - 'kr': Reverse rate constant (sf -> s0 transition)
+
+ Additional species are not consumed or produced; they facilitate the
+ conformational change. This models situations where cofactors or ions
+ must be present for the transition but are not modified.
+
+ """
if part_id is None:
- repr(s0) + '-' + repr(sf)
+ part_id = repr(s0) + '-' + repr(sf)
if additional_species is None:
additional_species = []
diff --git a/biocrnpyler/mechanisms/enzyme.py b/biocrnpyler/mechanisms/enzyme.py
index 994084d1..4099dbf3 100644
--- a/biocrnpyler/mechanisms/enzyme.py
+++ b/biocrnpyler/mechanisms/enzyme.py
@@ -7,19 +7,100 @@
class BasicCatalysis(Mechanism):
- """Mechanism for the schema S + C --> P + C."""
+ """Basic catalytic mechanism for irreversible substrate conversion.
+
+ A 'catalysis' mechanism where a catalyst (enzyme) converts a substrate
+ into a product in a single irreversible step. The catalyst is not
+ consumed in the reaction and can continue to catalyze additional
+ conversions.
+
+ The catalytic reaction is given by
+
+ S + C --> P + C
+
+ where S is the substrate, C is the catalyst (enzyme), and P is the
+ product.
+
+ Parameters
+ ----------
+ name : str, default='basic_catalysis'
+ Name identifier for this mechanism instance.
+ mechanism_type : str, default='catalysis'
+ Type classification of this mechanism.
+
+ Attributes
+ ----------
+ name : str
+ Name of the mechanism instance.
+ mechanism_type : str
+ Type classification ('catalysis').
+
+ See Also
+ --------
+ BasicProduction : Catalytic production without substrate consumption.
+ MichaelisMenten : Two-step enzyme kinetics with complex formation.
+ Mechanism : Base class for all mechanisms.
+
+ Notes
+ -----
+ This mechanism generates a single irreversible mass-action reaction
+ with rate constant 'kcat'. Unlike Michaelis-Menten kinetics, there is
+ no explicit enzyme-substrate complex formation; the reaction proceeds
+ in a single catalytic step.
+
+ Common applications include:
+
+ - Simplified enzyme kinetics models
+ - Catalytic degradation reactions
+ - Rate-limiting steps in metabolic pathways
+
+ Required parameters for this mechanism:
+
+ - 'kcat' : Catalytic rate constant for substrate conversion
+
+ Examples
+ --------
+ Model enzymatic degradation of a substrate:
+
+ >>> enzyme = bcp.Enzyme('E', substrates=['S'], products=['P'])
+ >>> mixture = bcp.Mixture(
+ ... components=[enzyme],
+ ... mechanisms={'catalysis': bcp.BasicCatalysis()},
+ ... parameters={'kcat': 1.0}
+ ... )
+ >>> mixture.compile_crn()
+
+ """
def __init__(
self, name: str = 'basic_catalysis', mechanism_type: str = 'catalysis'
):
- """Initializes a BasicCatalysis instance.
-
- :param name: name of the Mechanism, default: 'basic_catalysis'
- :param mechanism_type: type of the Mechanism, default: 'catalysis'
- """
Mechanism.__init__(self, name, mechanism_type)
def update_species(self, enzyme, substrate, product=None):
+ """Generate species for basic catalysis.
+
+ Creates the list of species involved in the catalytic reaction:
+ enzyme, substrate, and optionally the product.
+
+ Parameters
+ ----------
+ enzyme : Species
+ The catalyst species that facilitates the reaction.
+ substrate : Species
+ The substrate species to be converted.
+ product : Species, optional
+ The product species. If None, only enzyme and substrate are
+ returned (useful for degradation reactions where no explicit
+ product is tracked).
+
+ Returns
+ -------
+ list of Species
+ List containing [enzyme, substrate] if product is None, or
+ [enzyme, substrate, product] otherwise.
+
+ """
if product is None:
return [enzyme, substrate]
else:
@@ -34,6 +115,47 @@ def update_reactions(
part_id=None,
kcat=None,
):
+ """Generate reactions for basic catalysis.
+
+ Creates a single irreversible mass-action reaction for catalytic
+ conversion of substrate to product.
+
+ Parameters
+ ----------
+ enzyme : Species
+ The catalyst species that facilitates the reaction.
+ substrate : Species
+ The substrate species to be converted.
+ product : Species
+ The product species. Can be None for degradation reactions.
+ component : Component, optional
+ Component containing parameter values. Required if kcat is not
+ provided directly.
+ part_id : str, optional
+ Identifier for parameter lookup. If None, defaults to
+ component.name.
+ kcat : Parameter or float, optional
+ Catalytic rate constant. If None, retrieved from component
+ parameters.
+
+ Returns
+ -------
+ list of Reaction
+ List containing a single irreversible mass-action reaction:
+ enzyme + substrate --> enzyme + product.
+
+ Raises
+ ------
+ ValueError
+ If component is None and kcat is not provided.
+
+ Notes
+ -----
+ The reaction follows mass-action kinetics with rate constant 'kcat'.
+ The enzyme appears on both sides of the reaction as it acts as a
+ catalyst and is not consumed.
+
+ """
if part_id is None and component is not None:
part_id = component.name
@@ -57,18 +179,113 @@ def update_reactions(
class BasicProduction(Mechanism):
- """Mechanism for the schema C --> P + C."""
+ """Basic catalytic production mechanism with optional substrate.
- def __init__(self, name='basic_production', mechanism_type='catalysis'):
- """Initializes a BasicProduction instance.
+ A 'catalysis' mechanism where a catalyst (enzyme) produces a product.
+ Optionally, a substrate can be consumed during production, allowing for
+ both pure production (C --> P + C) and production with substrate
+ consumption (S + C --> P + C).
- :param name: name of the Mechanism, default: basic_production
- :param mechanism_type: type of the Mechanism, default: catalysis
- :param keywords:
- """
+ The production reaction can be either:
+
+ C --> P + C (pure production, no substrate)
+
+ or
+
+ S + C --> P + C (production with substrate consumption)
+
+ where S is the substrate, C is the catalyst (enzyme), and P is the
+ product.
+
+ Parameters
+ ----------
+ name : str, default='basic_production'
+ Name identifier for this mechanism instance.
+ mechanism_type : str, default='catalysis'
+ Type classification of this mechanism.
+
+ Attributes
+ ----------
+ name : str
+ Name of the mechanism instance.
+ mechanism_type : str
+ Type classification ('catalysis').
+
+ See Also
+ --------
+ BasicCatalysis : Catalytic conversion requiring a substrate.
+ MichaelisMentenCopy : Two-step kinetics preserving the substrate.
+ Mechanism : Base class for all mechanisms.
+
+ Notes
+ -----
+ This mechanism generates a single irreversible mass-action reaction
+ with rate constant 'kcat'. The catalyst is not consumed and appears on
+ both sides of the reaction.
+
+ Common applications include:
+
+ - Constitutive gene expression (transcription/translation)
+ - Enzymatic synthesis reactions
+ - Autocatalytic production processes
+
+ Required parameters for this mechanism:
+
+ - 'kcat' : Catalytic rate constant for product formation
+
+ The flexibility to include or exclude substrates makes this mechanism
+ useful for modeling both simple production (e.g., constitutive protein
+ expression) and production coupled with substrate consumption (e.g.,
+ enzymatic synthesis from precursors).
+
+ Examples
+ --------
+ Model constitutive protein production from a gene:
+
+ >>> gene = bcp.DNA('gfp')
+ >>> protein = bcp.Protein('GFP')
+ >>> expression = bcp.Enzyme(gene, substrates=[], products=[protein])
+ >>> mixture = bcp.Mixture(
+ ... components=[expression],
+ ... mechanisms={'catalysis': bcp.BasicProduction()},
+ ... parameters={'kcat': 0.01}
+ ... )
+ >>> mixture.compile_crn()
+ Species = dna_gfp, protein_GFP
+ Reactions = [
+ dna[gene] --> dna[gene]+protein[protein]
+ ]
+
+ """
+
+ def __init__(self, name='basic_production', mechanism_type='catalysis'):
Mechanism.__init__(self, name, mechanism_type)
def update_species(self, enzyme, substrate=None, product=None):
+ """Generate species for basic production.
+
+ Creates the list of species involved in the production reaction:
+ enzyme, and optionally substrate and product.
+
+ Parameters
+ ----------
+ enzyme : Species
+ The catalyst species that facilitates production.
+ substrate : Species, optional
+ The substrate species to be consumed. If None, production
+ occurs without substrate consumption.
+ product : Species, optional
+ The product species. If None, only enzyme (and substrate if
+ provided) are returned.
+
+ Returns
+ -------
+ list of Species
+ List containing enzyme and any non-None substrate and product
+ species. Order is [enzyme, product, substrate] if all are
+ provided.
+
+ """
species = [enzyme]
if product is not None:
species += [product]
@@ -86,6 +303,51 @@ def update_reactions(
part_id=None,
kcat=None,
):
+ """Generate reactions for basic production.
+
+ Creates a single irreversible mass-action reaction for catalytic
+ production, with or without substrate consumption.
+
+ Parameters
+ ----------
+ enzyme : Species
+ The catalyst species that facilitates production.
+ substrate : Species
+ The substrate species. Can be None for pure production without
+ substrate consumption.
+ product : Species
+ The product species. Can be None if no explicit product is
+ tracked.
+ component : Component, optional
+ Component containing parameter values. Required if kcat is not
+ provided directly.
+ part_id : str, optional
+ Identifier for parameter lookup. If None, defaults to
+ component.name.
+ kcat : Parameter or float, optional
+ Catalytic rate constant. If None, retrieved from component
+ parameters.
+
+ Returns
+ -------
+ list of Reaction
+ List containing a single irreversible mass-action reaction.
+ If substrate is None: enzyme --> enzyme + product.
+ If substrate is provided: enzyme + substrate --> enzyme +
+ product.
+
+ Raises
+ ------
+ ValueError
+ If component is None and kcat is not provided.
+
+ Notes
+ -----
+ The enzyme appears on both sides of the reaction as it acts as a
+ catalyst and is not consumed. The substrate, if provided, is
+ consumed in the reaction.
+
+ """
if part_id is None and component is not None:
part_id = component.name
@@ -111,22 +373,131 @@ def update_reactions(
class MichaelisMenten(Mechanism):
- """Mechanism to automatically generate Michaelis-Menten Type Reactions.
+ """Standard Michaelis-Menten enzyme kinetics mechanism.
+
+ A 'catalysis' mechanism implementing classical Michaelis-Menten enzyme
+ kinetics with explicit enzyme-substrate complex formation. The substrate
+ binds reversibly to the enzyme to form a complex, which then
+ irreversibly converts to product and releases the enzyme.
+
+ The reaction scheme is
+
+ S + E <--> S:E --> E + P
+
+ where S is the substrate, E is the enzyme, S:E is the enzyme-substrate
+ complex, and P is the product.
+
+ Parameters
+ ----------
+ name : str, default='michaelis_menten'
+ Name identifier for this mechanism instance.
+ mechanism_type : str, default='catalysis'
+ Type classification of this mechanism.
+
+ Attributes
+ ----------
+ name : str
+ Name of the mechanism instance.
+ mechanism_type : str
+ Type classification ('catalysis').
+
+ See Also
+ --------
+ BasicCatalysis : Single-step catalysis without complex formation.
+ MichaelisMentenCopy : Michaelis-Menten preserving substrate.
+ MichaelisMentenReversible : Michaelis-Menten with product binding.
+ Mechanism : Base class for all mechanisms.
+
+ Notes
+ -----
+ This mechanism generates two mass-action reactions:
+
+ 1. Reversible binding: S + E <--> S:E (rates 'kb' and 'ku')
+ 2. Irreversible catalysis: S:E --> E + P (rate 'kcat')
+
+ Common applications include:
+
+ - Enzyme-catalyzed reactions in metabolic pathways
+ - Protein degradation by proteases
+ - Drug metabolism by cytochrome P450 enzymes
+ - Any enzymatic process following Michaelis-Menten kinetics
+
+ Required parameters for this mechanism:
+
+ - 'kb' : Binding rate constant for enzyme-substrate association
+ - 'ku' : Unbinding rate constant for enzyme-substrate dissociation
+ - 'kcat' : Catalytic rate constant for product formation
+
+ The mechanism can also model degradation reactions by setting product
+ to None, resulting in: S + E <--> S:E --> E.
+
+ Examples
+ --------
+ Model enzyme-catalyzed substrate conversion:
+
+ >>> substrate = bcp.Species('S')
+ >>> product = bcp.Species('P')
+ >>> enzyme = bcp.Enzyme('E', substrates=[substrate], products=[product])
+ >>> mixture = bcp.Mixture(
+ ... components=[enzyme],
+ ... mechanisms={'catalysis': bcp.MichaelisMenten()},
+ ... parameters={'kb': 1.0, 'ku': 0.1, 'kcat': 0.5}
+ ... )
+ >>> mixture.compile_crn()
+ Species = protein_E, S, P, complex_S_protein_E_
+ Reactions = [
+ S+protein[E] <--> complex[S:protein[E]]
+ complex[S:protein[E]] --> P+protein[E]
+ ]
+
+ Model enzymatic degradation:
+
+ >>> degradase = bcp.Protein('degradase')
+ >>> target = bcp.Protein('target')
+ >>> degrader = bcp.Enzyme(degradase, substrates=[target], products=[])
+ >>> mixture = bcp.Mixture(
+ ... components=[degrader],
+ ... mechanisms={'catalysis': bcp.MichaelisMenten()},
+ ... parameters={'kb': 1.0, 'ku': 0.1, 'kcat': 0.2}
+ ... )
- In the Copy RXN version, the substrates is not Consumed:
- sub + enz <--> sub:enz --> enz + prod
"""
def __init__(self, name='michaelis_menten', mechanism_type='catalysis'):
- """Initializes a MichaelisMenten instance.
-
- :param name: name of the Mechanism, default: 'michaelis_menten'
- :param mechanism_type: type of the Mechanism, default: 'catalysis'
- :param keywords:
- """
Mechanism.__init__(self, name, mechanism_type)
def update_species(self, enzyme, substrate, product=None, complex=None):
+ """Generate species for Michaelis-Menten kinetics.
+
+ Creates the species involved in Michaelis-Menten enzyme kinetics:
+ enzyme, substrate, enzyme-substrate complex, and optionally the
+ product.
+
+ Parameters
+ ----------
+ enzyme : Species
+ The enzyme species that catalyzes the reaction.
+ substrate : Species
+ The substrate species to be converted.
+ product : Species, optional
+ The product species. If None, only enzyme, substrate, and
+ complex are returned (useful for degradation reactions).
+ complex : Species, optional
+ Pre-specified enzyme-substrate complex. If None, automatically
+ creates a Complex([substrate, enzyme]).
+
+ Returns
+ -------
+ list of Species
+ List containing [enzyme, substrate, complex] if product is
+ None, or [enzyme, substrate, product, complex] otherwise.
+
+ Notes
+ -----
+ The complex is automatically generated as a Complex object
+ containing the substrate and enzyme if not explicitly provided.
+
+ """
if complex is None:
complexS = Complex([substrate, enzyme])
else:
@@ -148,6 +519,63 @@ def update_reactions(
ku=None,
kcat=None,
):
+ """Generate reactions for Michaelis-Menten kinetics.
+
+ Creates two mass-action reactions implementing Michaelis-Menten
+ enzyme kinetics: reversible enzyme-substrate binding and
+ irreversible catalytic conversion.
+
+ Parameters
+ ----------
+ enzyme : Species
+ The enzyme species that catalyzes the reaction.
+ substrate : Species
+ The substrate species to be converted.
+ product : Species
+ The product species. Can be None for degradation reactions.
+ component : Component, optional
+ Component containing parameter values. Required if kb, ku, or
+ kcat are not provided directly.
+ part_id : str, optional
+ Identifier for parameter lookup. If None, defaults to
+ component.name.
+ complex : Species, optional
+ Pre-specified enzyme-substrate complex. If None, automatically
+ creates a Complex([substrate, enzyme]).
+ kb : Parameter or float, optional
+ Forward binding rate constant. If None, retrieved from
+ component parameters.
+ ku : Parameter or float, optional
+ Reverse unbinding rate constant. If None, retrieved from
+ component parameters.
+ kcat : Parameter or float, optional
+ Catalytic rate constant. If None, retrieved from component
+ parameters.
+
+ Returns
+ -------
+ list of Reaction
+ List containing two reactions:
+ [binding_reaction, catalysis_reaction].
+
+ Raises
+ ------
+ ValueError
+ If component is None and any of kb, ku, or kcat is not
+ provided.
+
+ Notes
+ -----
+ The mechanism generates the following reactions:
+
+ 1. S + E <--> S:E (binding, rates 'kb' and 'ku')
+ 2. S:E --> E + P (catalysis, rate 'kcat')
+
+ For degradation (product is None):
+
+ 2. S:E --> E (degradation, rate 'kcat')
+
+ """
# Get parameters
if part_id is None and component is not None:
part_id = component.name
@@ -195,10 +623,93 @@ def update_reactions(
class MichaelisMentenReversible(Mechanism):
- """Michaelis-Menten reactions with product that can bind to enzymes.
+ """Reversible Michaelis-Menten kinetics with product binding.
+
+ A 'catalysis' mechanism implementing Michaelis-Menten enzyme kinetics
+ where the product can also bind reversibly to the enzyme. Both the
+ substrate and product form distinct enzyme complexes, and the catalytic
+ step itself is reversible.
+
+ The reaction scheme is
+
+ S + E <--> S:E <--> E:P <--> E + P
+
+ where S is the substrate, E is the enzyme, S:E is the enzyme-substrate
+ complex, E:P is the enzyme-product complex, and P is the product.
+
+ Parameters
+ ----------
+ name : str, default='michaelis_menten_reverse_binding'
+ Name identifier for this mechanism instance.
+ mechanism_type : str, default='catalysis'
+ Type classification of this mechanism.
+
+ Attributes
+ ----------
+ name : str
+ Name of the mechanism instance.
+ mechanism_type : str
+ Type classification ('catalysis').
+
+ See Also
+ --------
+ MichaelisMenten : Standard Michaelis-Menten with irreversible catalysis.
+ MichaelisMentenCopy : Michaelis-Menten preserving substrate.
+ Mechanism : Base class for all mechanisms.
+
+ Notes
+ -----
+ This mechanism generates three mass-action reactions:
+
+ 1. Reversible substrate binding: S + E <--> S:E (rates 'kb1' and 'ku1')
+ 2. Reversible product binding: P + E <--> E:P (rates 'kb2' and 'ku2')
+ 3. Reversible catalysis: S:E <--> E:P (rates 'kcat' and 'kcat_rev')
+
+ Common applications include:
+
+ - Reversible enzymatic reactions near equilibrium
+ - Bidirectional metabolic pathways
+ - Reactions where product inhibition is significant
+ - Detailed kinetic models requiring thermodynamic consistency
+
+ Required parameters for this mechanism:
+
+ - 'kb1' : Forward binding rate for substrate-enzyme association
+ - 'ku1' : Reverse unbinding rate for substrate-enzyme dissociation
+ - 'kb2' : Forward binding rate for product-enzyme association
+ - 'ku2' : Reverse unbinding rate for product-enzyme dissociation
+ - 'kcat' : Forward catalytic rate constant (S:E --> E:P)
+ - 'kcat_rev' : Reverse catalytic rate constant (E:P --> S:E)
+
+ This mechanism is particularly useful when modeling reactions close to
+ equilibrium where the reverse reaction and product binding cannot be
+ neglected.
+
+ Examples
+ --------
+ Model a reversible enzymatic conversion:
+
+ >>> enzyme = bcp.Species('E', material_type='protein')
+ >>> substrate = bcp.Species('S')
+ >>> product = bcp.Species('P')
+ >>> comp = bcp.Enzyme(
+ ... enzyme, substrates=[substrate], products=[product],
+ ... mechanisms={'catalysis': bcp.MichaelisMentenReversible()},
+ ... parameters={
+ ... 'kb1': 2.0, 'ku1': 0.5,
+ ... 'kb2': 1.5, 'ku2': 0.3,
+ ... 'kcat': 1.0, 'kcat_rev': 0.4
+ ... }
+ ... )
+ >>> mixture = bcp.Mixture(components=[comp])
+ >>> mixture.compile_crn()
+ Species = protein_E, S, P, complex_S_protein_E_, complex_P_protein_E_
+ Reactions = [
+ S+protein[E] <--> complex[S:protein[E]]
+ P+protein[E] <--> complex[P:protein[E]]
+ complex[S:protein[E]] <--> complex[P:protein[E]]
+ ]
- In the Copy RXN version, the substrate is not Consumed
- sub + enz <--> sub:enz <--> enz:prod <--> enz + prod
"""
def __init__(
@@ -206,18 +717,45 @@ def __init__(
name='michaelis_menten_reverse_binding',
mechanism_type='catalysis',
):
- """Initializes a MichaelisMentenReversible instance.
-
- :param name: name of the Mechanism, default:
- 'michaelis_menten_reverse_binding'
- :param mechanism_type: type of the Mechanism, default: 'catalysis'
- :param keywords:
- """
Mechanism.__init__(self, name, mechanism_type)
def update_species(
self, enzyme, substrate, product, complex=None, complex2=None
):
+ """Generate species for reversible Michaelis-Menten kinetics.
+
+ Creates the species involved in reversible Michaelis-Menten enzyme
+ kinetics: enzyme, substrate, product, enzyme-substrate complex, and
+ enzyme-product complex.
+
+ Parameters
+ ----------
+ enzyme : Species
+ The enzyme species that catalyzes the reaction.
+ substrate : Species
+ The substrate species.
+ product : Species
+ The product species.
+ complex : Species, optional
+ Pre-specified enzyme-substrate complex. If None, automatically
+ creates a Complex([substrate, enzyme]).
+ complex2 : Species, optional
+ Pre-specified enzyme-product complex. If None, automatically
+ creates a Complex([product, enzyme]).
+
+ Returns
+ -------
+ list of Species
+ List containing [enzyme, substrate, product, complex1,
+ complex2] where complex1 is S:E and complex2 is E:P.
+
+ Notes
+ -----
+ Both complexes are automatically generated if not explicitly
+ provided. The enzyme-substrate complex contains [substrate, enzyme]
+ and the enzyme-product complex contains [product, enzyme].
+
+ """
if complex is None:
complex1 = Complex([substrate, enzyme])
else:
@@ -241,6 +779,67 @@ def update_reactions(
ku=None,
kcat=None,
):
+ """Generate reactions for reversible Michaelis-Menten kinetics.
+
+ Creates three mass-action reactions implementing reversible
+ Michaelis-Menten enzyme kinetics with product binding: substrate
+ binding, product binding, and reversible catalysis.
+
+ Parameters
+ ----------
+ enzyme : Species
+ The enzyme species that catalyzes the reaction.
+ substrate : Species
+ The substrate species.
+ product : Species
+ The product species.
+ component : Component, optional
+ Component containing parameter values. Required if kb, ku, or
+ kcat are not provided directly.
+ part_id : str, optional
+ Identifier for parameter lookup. If None, defaults to
+ component.name.
+ complex : Species, optional
+ Pre-specified enzyme-substrate complex. If None, automatically
+ creates a Complex([substrate, enzyme]).
+ complex2 : Species, optional
+ Pre-specified enzyme-product complex. If None, automatically
+ creates a Complex([product, enzyme]).
+ kb : tuple of (float or Parameter), optional
+ Tuple of (kb1, kb2) binding rate constants. If None, kb1 and
+ kb2 retrieved separately from component parameters.
+ ku : tuple of (float or Parameter), optional
+ Tuple of (ku1, ku2) unbinding rate constants. If None, ku1 and
+ ku2 retrieved separately from component parameters.
+ kcat : tuple of (float or Parameter), optional
+ Tuple of (kcat, kcat_rev) catalytic rate constants. If None,
+ kcat and kcat_rev retrieved separately from component
+ parameters.
+
+ Returns
+ -------
+ list of Reaction
+ List containing three reactions: [substrate_binding_reaction,
+ product_binding_reaction, catalysis_reaction].
+
+ Raises
+ ------
+ ValueError
+ If component is None and any of kb, ku, or kcat is not
+ provided.
+
+ Notes
+ -----
+ The mechanism generates the following reactions:
+
+ 1. S + E <--> S:E (binding, rates 'kb1' and 'ku1')
+ 2. P + E <--> E:P (binding, rates 'kb2' and 'ku2')
+ 3. S:E <--> E:P (catalysis, rates 'kcat' and 'kcat_rev')
+
+ When providing parameters directly (not via component), kb, ku, and
+ kcat should be tuples of two values each.
+
+ """
# Get parameters
if part_id is None and component is not None:
part_id = component.name
@@ -311,21 +910,127 @@ def update_reactions(
class MichaelisMentenCopy(Mechanism):
- """In the Copy RXN version, the substrate is not consumed.
+ """Michaelis-Menten kinetics with substrate preservation.
+
+ A 'copy' mechanism implementing Michaelis-Menten enzyme kinetics where
+ the substrate is not consumed during the reaction. Instead, the
+ substrate acts as a template that is copied or read, producing a
+ product while preserving the original substrate.
+
+ The reaction scheme is
+
+ S + E <--> S:E --> S + E + P
+
+ where S is the substrate (template), E is the enzyme, S:E is the
+ enzyme-substrate complex, and P is the product.
+
+ Parameters
+ ----------
+ name : str, default='michaelis_menten_copy'
+ Name identifier for this mechanism instance.
+ mechanism_type : str, default='copy'
+ Type classification of this mechanism.
+
+ Attributes
+ ----------
+ name : str
+ Name of the mechanism instance.
+ mechanism_type : str
+ Type classification ('copy').
+
+ See Also
+ --------
+ MichaelisMenten : Standard Michaelis-Menten consuming substrate.
+ BasicProduction : Simpler production without complex formation.
+ Mechanism : Base class for all mechanisms.
+
+ Notes
+ -----
+ This mechanism generates two mass-action reactions:
+
+ 1. Reversible binding: S + E <--> S:E (rates 'kb' and 'ku')
+ 2. Catalytic copying: S:E --> S + E + P (rate 'kcat')
+
+ Common applications include:
+
+ - Gene transcription (DNA template produces RNA)
+ - Translation (mRNA template produces protein)
+ - DNA replication
+ - Any process where a template is read without being consumed
+
+ Required parameters for this mechanism:
+
+ - 'kb' : Binding rate constant for enzyme-substrate association
+ - 'ku' : Unbinding rate constant for enzyme-substrate dissociation
+ - 'kcat' : Catalytic rate constant for product formation
+
+ The key difference from standard Michaelis-Menten is that the substrate
+ appears on both sides of the catalytic step, making it a true copying
+ or templating mechanism rather than a conversion.
+
+ Examples
+ --------
+ Model translation with component:
+
+ >>> mrna = bcp.Species('mRNA')
+ >>> ribosome = bcp.Species('Ribo')
+ >>> protein = bcp.species('GFP')
+ >>> comp = bcp.Enzyme(
+ ... ribosome, substrates=[mrna], products=[protein],
+ ... parameters={'kb': 2.0, 'ku': 0.2, 'kcat': 0.1}
+ ... )
+ >>> mixture = bcp.Mixture(
+ ... components=[comp],
+ ... mechanisms={'catalysis': bcp.MichaelisMentenCopy()},
+ ... )
+ >>> mixture.compile_crn()
+ Species = Ribo, mRNA, complex_Ribo_mRNA_, GFP
+ Reactions = [
+ mRNA+Ribo <--> complex[Ribo:mRNA]
+ complex[Ribo:mRNA] --> mRNA+GFP+Ribo
+ ]
- substrate + Enz <--> substrate:Enz --> substrate + Enz + product
"""
def __init__(self, name='michaelis_menten_copy', mechanism_type='copy'):
- """Initializes a MichaelisMentenCopy instance.
-
- :param name: name of the Mechanism, default: 'michaelis_menten_copy'
- :param mechanism_type: type of the Mechanism, default: 'copy'
- :param keywords:
- """
Mechanism.__init__(self, name, mechanism_type)
def update_species(self, enzyme, substrate, complex=None, product=None):
+ """Generate species for copy-type Michaelis-Menten kinetics.
+
+ Creates the species involved in copy-type Michaelis-Menten enzyme
+ kinetics: enzyme, substrate (template), enzyme-substrate complex,
+ and optionally the product(s).
+
+ Parameters
+ ----------
+ enzyme : Species
+ The enzyme species that catalyzes the copying reaction.
+ substrate : Species
+ The substrate (template) species that is copied but not
+ consumed.
+ complex : Species, optional
+ Pre-specified enzyme-substrate complex. If None, automatically
+ creates a Complex([substrate, enzyme]).
+ product : Species or list of Species, optional
+ The product species or list of products. If None, only enzyme,
+ substrate, and complex are returned.
+
+ Returns
+ -------
+ list of Species
+ List containing [enzyme, substrate, complex] if product is
+ None. If product is provided, returns [enzyme, substrate,
+ complex, product] for single product or [enzyme, substrate,
+ complex] + product for list of products.
+
+ Notes
+ -----
+ This method can handle multiple products by accepting product as a
+ list. This is useful for modeling processes like transcription
+ where multiple transcript copies may be produced.
+
+ """
if complex is None:
complexS = Complex([substrate, enzyme])
else:
@@ -350,6 +1055,64 @@ def update_reactions(
ku=None,
kcat=None,
):
+ """Generate reactions for copy-type Michaelis-Menten kinetics.
+
+ Creates two mass-action reactions implementing copy-type
+ Michaelis-Menten enzyme kinetics: reversible enzyme-substrate
+ binding and catalytic copying that preserves the substrate.
+
+ Parameters
+ ----------
+ enzyme : Species
+ The enzyme species that catalyzes the copying reaction.
+ substrate : Species
+ The substrate (template) species that is copied but not
+ consumed.
+ product : Species
+ The product species.
+ component : Component, optional
+ Component containing parameter values. Required if kb, ku, or
+ kcat are not provided directly.
+ part_id : str, optional
+ Identifier for parameter lookup. If None, defaults to
+ component.name.
+ complex : Species, optional
+ Pre-specified enzyme-substrate complex. If None, automatically
+ creates a Complex([substrate, enzyme]).
+ kb : Parameter or float, optional
+ Forward binding rate constant. If None, retrieved from
+ component parameters.
+ ku : Parameter or float, optional
+ Reverse unbinding rate constant. If None, retrieved from
+ component parameters.
+ kcat : Parameter or float, optional
+ Catalytic rate constant. If None, retrieved from component
+ parameters.
+
+ Returns
+ -------
+ list of Reaction
+ List containing two reactions:
+ [binding_reaction, catalysis_reaction].
+
+ Raises
+ ------
+ ValueError
+ If component is None and any of kb, ku, or kcat is not
+ provided.
+
+ Notes
+ -----
+ The mechanism generates the following reactions:
+
+ 1. S + E <--> S:E (binding, rates 'kb' and 'ku')
+ 2. S:E --> S + E + P (copying, rate 'kcat')
+
+ The key feature is that the substrate appears on both sides of the
+ catalytic reaction, ensuring it is not consumed. This makes the
+ reaction a true template-based copying mechanism.
+
+ """
if complex is None:
complexS = Complex([substrate, enzyme])
else:
diff --git a/biocrnpyler/mechanisms/global_mechanisms.py b/biocrnpyler/mechanisms/global_mechanisms.py
index 8fba4e81..f4396497 100644
--- a/biocrnpyler/mechanisms/global_mechanisms.py
+++ b/biocrnpyler/mechanisms/global_mechanisms.py
@@ -44,23 +44,103 @@
class GlobalMechanism(Mechanism):
- """Global mechanisms are a lot like mechanisms.
-
- They are called only by mixtures on a list of all species have been
- generated by components. Global mechanisms are meant to work as universal
- mechanisms that function on each species or all species of some material
- type or with some attribute. Global mechanisms may only act on one species
- at a time.
-
- In order to decide which species a global mechanism acts upon, the
- filter_dict is used.
-
- An example of a global mechanism is degradation via dilution which is
- demonstrated in the Tests folder.
-
- GlobalMechanisms should be used cautiously or avoided alltogether - the
- order in which they are called may have to be carefully user-defined in
- the subclasses of Mixture in order to ensure expected behavior.
+ """Base class for global mechanisms that act on all species in a mixture.
+
+ Global mechanisms are applied by mixtures to all species after components
+ have generated their species. Unlike regular mechanisms that act on
+ specific component interactions, global mechanisms function universally on
+ species based on their properties (material type, attributes, or name).
+
+ Global mechanisms use a filter dictionary to determine which species they
+ act upon. For each species, the mechanism checks the species's material
+ type, attributes, and name against the filter dictionary. If a match
+ returns True, the mechanism acts on that species; if False, it does not.
+
+ Parameters
+ ----------
+ name : str
+ Name identifier for this mechanism instance.
+ mechanism_type : str, default=''
+ Type classification of this mechanism.
+ filter_dict : dict, optional
+ Dictionary mapping species properties (material_type, attributes,
+ or name) to boolean values. True means the mechanism acts on
+ species with that property, False means it does not. If None,
+ an empty dictionary is used.
+ default_on : bool, default=False
+ Determines behavior when a species is not found in filter_dict.
+ If True, the mechanism acts on unfiltered species. If False, it
+ does not. Also used as the default when filter conflicts occur.
+ recursive_species_filtering : bool, default=False
+ Determines how ComplexSpecies are filtered. If True, filtering
+ is based on all subspecies recursively. If False, filtering acts
+ only on the ComplexSpecies itself.
+
+ Attributes
+ ----------
+ name : str
+ Name of the mechanism instance.
+ mechanism_type : str
+ Type classification of this mechanism.
+ filter_dict : dict
+ Dictionary for filtering species.
+ default_on : bool
+ Default behavior for unfiltered species.
+ recursive_species_filtering : bool
+ Whether to filter ComplexSpecies recursively.
+
+ See Also
+ --------
+ Dilution : Global mechanism for species dilution.
+ Degradation_mRNA_MM : Global mRNA degradation mechanism.
+ Mechanism : Base class for all mechanisms.
+
+ Notes
+ -----
+ GlobalMechanisms should be used cautiously as the order in which they
+ are called may affect the final CRN. The calling order may need to be
+ carefully defined in Mixture subclasses to ensure expected behavior.
+
+ Filter dictionary usage:
+
+ - Keys can be material_type, attribute names, or species names
+ - Values are boolean (True = apply mechanism, False = do not apply)
+ - When conflicts occur, a warning is issued and default_on is used
+ - Attributes take precedence over material_type when both are present
+
+ All parameters required by the global mechanism must be present in the
+ Mixture's parameter dictionary. Global mechanisms are assumed to take
+ a single species as input.
+
+ Examples
+ --------
+ Create a custom global degradation mechanism:
+
+ >>> class CustomDegradation(bcp.GlobalMechanism):
+ ... def update_reactions(self, s, mixture):
+ ... kdeg = self.get_parameter(s, 'kdeg', mixture)
+ ... return [bcp.Reaction.from_massaction(
+ ... inputs=[s], outputs=[], k_forward=kdeg
+ ... )]
+
+ Use with a filter to degrade only proteins:
+
+ >>> mech = CustomDegradation(
+ ... name='protein_degradation',
+ ... mechanism_type='degradation',
+ ... filter_dict={'protein': True},
+ ... default_on=False
+ ... )
+ >>> mixture = bcp.Mixture(
+ ... components=[bcp.Protein('A'), bcp.RNA('B')],
+ ... mechanisms={'degradation': mech},
+ ... parameters={'kdeg': 0.01}
+ ... )
+ >>> mixture.compile_crn()
+ Species = protein_A, rna_B
+ Reactions = [
+ protein[A] -->
+ ]
"""
@@ -72,36 +152,6 @@ def __init__(
default_on: bool = False,
recursive_species_filtering: bool = False,
):
- """Creates a GlobalMechanisms instance.
-
- If the species's name, material type, and attributes are all not in
- the filter_dict, the GlobalMechanism will be called if default_on ==
- True and not called if default_on == False.
-
- :param name: name of the GlobalMechanism
- :param mechanism_type:
- :param filter_dict: filter_dict[species.material_type /
- species.attributes] = True / False. For each species, its
- material type or attributes are sent through the filter_dict. If
- True is returned, the GlobalMechanism will act on the species. If
- False is returned, the the GlobalMechanism will not be called. If
- filter_dict[attribute] is different from
- filter_dict[material_type], filter_dict[attribute] takes
- precedent. If multiple filter_dict[attribute] contradict for
- different attributes, an error is raised. Note that the above
- filtering is done automatically. Any parameters needed by the
- global mechanism must be in the Mixture's parameter
- dictionary. These methods are assumed to take a single species as
- input. :param default_on: what to do if a species doesn't come up
- in the filter dict. Also used for as the default if there is a
- filterdict conflict :param recursive_species_filtering: keyword
- determines how the material_type and name of ComplexSpecies is
- defined. If True: the filter based upon all subspecies.type and
- name recursively going through all ComplexSpecies. If False: the
- filter dict will act only on the ComplexSpecies. By default, this
- is False.
-
- """
if filter_dict is None:
self.filter_dict = {}
else:
@@ -112,10 +162,33 @@ def __init__(
Mechanism.__init__(self, name=name, mechanism_type=mechanism_type)
def apply_filter(self, s: Species):
- """Determine if global mechanism acts on a species.
+ """Determine if the global mechanism should act on a species.
+
+ Checks the species's material_type, attributes, and name against the
+ filter dictionary to decide if the mechanism should be applied.
+
+ Parameters
+ ----------
+ s : Species
+ The species to check against the filter dictionary.
+
+ Returns
+ -------
+ bool
+ True if the mechanism should act on this species, False
+ otherwise.
+
+ Notes
+ -----
+ The filtering logic follows this hierarchy:
+
+ 1. Checks all attributes and material_type of the species (and
+ subspecies if recursive_species_filtering is True)
+ 2. If any match is found in filter_dict, uses that boolean value
+ 3. If conflicts occur (different attributes give different results),
+ issues a warning and uses default_on
+ 4. If no match is found, uses default_on
- :param s: Species
- :return:
"""
fd = self.filter_dict
use_mechanism = None
@@ -143,6 +216,28 @@ def apply_filter(self, s: Species):
def update_species_global(
self, species_list: List[Species], mixture, compartment=None
):
+ """Apply mechanism's update_species to filtered species in a list.
+
+ Iterates through all species in the list, applies the filter to
+ determine which species the mechanism acts upon, and calls
+ update_species for each applicable species.
+
+ Parameters
+ ----------
+ species_list : list of Species
+ List of all species to potentially act upon.
+ mixture : Mixture
+ The mixture containing parameters and other context.
+ compartment : Compartment, optional
+ If provided, assigns this compartment to any new species that
+ have the default compartment.
+
+ Returns
+ -------
+ list of Species
+ List of all new species generated by the mechanism.
+
+ """
new_species = []
for s in species_list:
use_mechanism = self.apply_filter(s)
@@ -160,6 +255,28 @@ def update_species_global(
def update_reactions_global(
self, species_list: List[Species], mixture, compartment=None
):
+ """Apply mechanism's `update_reactions` to filtered species in a list.
+
+ Iterates through all species in the list, applies the filter to
+ determine which species the mechanism acts upon, and calls
+ `update_reactions` for each applicable species.
+
+ Parameters
+ ----------
+ species_list : list of Species
+ List of all species to potentially act upon.
+ mixture : Mixture
+ The mixture containing parameters and other context.
+ compartment : Compartment, optional
+ If provided, assigns this compartment to species that have the
+ default compartment before generating reactions.
+
+ Returns
+ -------
+ list of Reaction
+ List of all new reactions generated by the mechanism.
+
+ """
new_reactions = []
for s in species_list:
use_mechanism = self.apply_filter(s)
@@ -170,6 +287,30 @@ def update_reactions_global(
return new_reactions
def get_parameter(self, species, param_name, mixture):
+ """Retrieve a parameter value from the mixture for a given species.
+
+ Parameters
+ ----------
+ species : Species
+ The species for which to retrieve the parameter. Used as the
+ part_id for parameter lookup.
+ param_name : str
+ Name of the parameter to retrieve.
+ mixture : Mixture
+ The mixture containing the parameters.
+
+ Returns
+ -------
+ Parameter or float
+ The parameter value retrieved from the mixture.
+
+ Raises
+ ------
+ ValueError
+ If no parameter matching the (mechanism, species, param_name)
+ combination can be found.
+
+ """
param = mixture.get_parameter(
mechanism=self, part_id=repr(species), param_name=param_name
)
@@ -184,33 +325,158 @@ def get_parameter(self, species, param_name, mixture):
return param
def update_species(self, s: Species, mixture):
- """Updat specifies for a global mechanism.
-
- All global mechanisms must use update_species functions with these
- inputs.
-
- :param s: Species instance
- :return:
+ """Generate new species for a global mechanism acting on one species.
+
+ This is a template method that should be overridden by subclasses
+ to define the species generated by the mechanism.
+
+ Parameters
+ ----------
+ s : Species
+ The species that the mechanism is acting upon.
+ mixture : Mixture
+ The mixture containing parameters and other context.
+
+ Returns
+ -------
+ list of Species
+ List of new species generated by the mechanism. Default
+ implementation returns an empty list.
+
+ Notes
+ -----
+ All GlobalMechanism subclasses should implement this method if they
+ need to generate new species (e.g., enzyme-substrate complexes for
+ degradation mechanisms).
"""
return []
def update_reactions(self, s, mixture):
- """Updat reactions for a global mechanism.
-
- All global mechanisms must use update_reactions functions with these
- inputs.
-
- :param s:
- :param mixture:
- :return:
+ """Generate reactions for a global mechanism acting on one species.
+
+ This is a template method that should be overridden by subclasses
+ to define the reactions generated by the mechanism.
+
+ Parameters
+ ----------
+ s : Species
+ The species that the mechanism is acting upon.
+ mixture : Mixture
+ The mixture containing parameters and other context.
+
+ Returns
+ -------
+ list of Reaction
+ List of reactions generated by the mechanism. Default
+ implementation returns an empty list.
+
+ Notes
+ -----
+ All GlobalMechanism subclasses should implement this method to
+ define the reactions the mechanism produces (e.g., degradation
+ reactions, dilution reactions, etc.).
"""
return []
class Dilution(GlobalMechanism):
- """A global mechanism to represent dilution."""
+ """Global mechanism for species dilution or degradation.
+
+ A 'dilution' mechanism that removes species from the system at a rate
+ proportional to their concentration. This models dilution due to cell
+ growth, continuous flow in a bioreactor, or general degradation
+ processes.
+
+ The dilution reaction for each species is
+
+ S --> ∅
+
+ where S is any species and the rate is determined by 'kdil'.
+
+ Parameters
+ ----------
+ name : str, default='global_degradation_via_dilution'
+ Name identifier for this mechanism instance.
+ mechanism_type : str, default='dilution'
+ Type classification of this mechanism.
+ filter_dict : dict, optional
+ Dictionary for filtering which species undergo dilution. If None,
+ all species are affected based on default_on.
+ default_on : bool, default=True
+ If True, dilution applies to all species not explicitly filtered
+ out. If False, dilution applies only to explicitly filtered species.
+ recursive_species_filtering : bool, default=True
+ If True, filters based on all subspecies within ComplexSpecies. If
+ False, filters only the ComplexSpecies itself.
+
+ Attributes
+ ----------
+ name : str
+ Name of the mechanism instance.
+ mechanism_type : str
+ Type classification ('dilution').
+ filter_dict : dict
+ Dictionary for filtering species.
+ default_on : bool
+ Default behavior for unfiltered species.
+ recursive_species_filtering : bool
+ Whether to filter ComplexSpecies recursively.
+
+ See Also
+ --------
+ AntiDilutionConstitutiveCreation : Counter-mechanism for constant
+ concentration.
+ GlobalMechanism : Base class for global mechanisms.
+
+ Notes
+ -----
+ This mechanism generates a single irreversible mass-action reaction for
+ each species that passes the filter, with rate constant 'kdil'. By
+ default, it applies to all species (default_on=True) unless specific
+ species are excluded via the filter_dict.
+
+ Common applications include:
+
+ - Modeling cell growth and dilution in batch cultures
+ - Continuous flow bioreactor systems
+ - Simplified degradation for all cellular components
+ - Washout effects in chemostats
+
+ Required parameters for this mechanism:
+
+ - 'kdil' : Dilution rate constant (per species if needed)
+
+ The mechanism can be selectively applied using filter_dict. For
+ example, to exclude specific species from dilution, set
+ filter_dict={'notdiluted': False} and tag those species with the
+ 'notdiluted' attribute.
+
+ Examples
+ --------
+ Apply dilution to all species in a mixture:
+
+ >>> dilution_mech = bcp.Dilution(default_on=True)
+ >>> mixture = bcp.Mixture(
+ ... components=[bcp.Protein('A'), bcp.Protein('B')],
+ ... mechanisms={'dilution': dilution_mech},
+ ... parameters={'kdil': 0.01}
+ ... )
+
+ Apply dilution only to proteins, excluding DNA:
+
+ >>> dilution_mech = bcp.Dilution(
+ ... filter_dict={'protein': True, 'dna': False},
+ ... default_on=False
+ ... )
+ >>> mixture = bcp.Mixture(
+ ... components=[bcp.Protein('P'), bcp.DNA('gene')],
+ ... mechanisms={'dilution': dilution_mech},
+ ... parameters={'kdil': 0.01}
+ ... )
+
+ """
def __init__(
self,
@@ -230,6 +496,24 @@ def __init__(
)
def update_reactions(self, s: Species, mixture):
+ """Generate dilution reaction for a single species.
+
+ Creates an irreversible mass-action reaction that removes the
+ species from the system at rate 'kdil'.
+
+ Parameters
+ ----------
+ s : Species
+ The species undergoing dilution.
+ mixture : Mixture
+ The mixture containing the 'kdil' parameter.
+
+ Returns
+ -------
+ list of Reaction
+ List containing a single reaction: S --> ∅ with rate 'kdil'.
+
+ """
k_dil = self.get_parameter(s, 'kdil', mixture)
rxn = Reaction.from_massaction(
inputs=[s], outputs=[], k_forward=k_dil
@@ -237,11 +521,103 @@ def update_reactions(self, s: Species, mixture):
return [rxn]
-class AnitDilutionConstiutiveCreation(GlobalMechanism):
- """Constitutively create certain species at the rate of dilution.
-
- Useful for keeping machinery species like ribosomes and polymerases at a
- constant concentration.
+class AntiDilutionConstitutiveCreation(GlobalMechanism):
+ """Global mechanism for constitutive species creation to counter dilution.
+
+ A 'dilution' mechanism that constitutively creates species at a constant
+ rate to maintain their concentration despite dilution. This is useful for
+ modeling cellular machinery (ribosomes, polymerases, etc.) that is
+ maintained at approximately constant levels through homeostatic
+ mechanisms.
+
+ The production reaction for each species is
+
+ ∅ --> S
+
+ where S is any species and the rate is determined by 'kdil' (matching
+ the dilution rate to maintain steady state).
+
+ Parameters
+ ----------
+ name : str, default='anti_dilution_constiuitive_creation'
+ Name identifier for this mechanism instance.
+ material_type : str, default='dilution'
+ Type classification of this mechanism (used as mechanism_type).
+ filter_dict : dict, optional
+ Dictionary for filtering which species are constitutively created.
+ If None, all species are affected based on default_on.
+ default_on : bool, default=True
+ If True, creation applies to all species not explicitly filtered
+ out. If False, creation applies only to explicitly filtered species.
+ recursive_species_filtering : bool, default=True
+ If True, filters based on all subspecies within ComplexSpecies. If
+ False, filters only the ComplexSpecies itself.
+
+ Attributes
+ ----------
+ name : str
+ Name of the mechanism instance.
+ mechanism_type : str
+ Type classification ('dilution').
+ filter_dict : dict
+ Dictionary for filtering species.
+ default_on : bool
+ Default behavior for unfiltered species.
+ recursive_species_filtering : bool
+ Whether to filter ComplexSpecies recursively.
+
+ See Also
+ --------
+ Dilution : Dilution mechanism this counteracts.
+ GlobalMechanism : Base class for global mechanisms.
+
+ Notes
+ -----
+ This mechanism generates a single irreversible mass-action reaction for
+ each species that passes the filter, with rate constant 'kdil'. It is
+ typically used in conjunction with the Dilution mechanism to maintain
+ steady-state concentrations of cellular machinery.
+
+ Common applications include:
+
+ - Maintaining ribosome and polymerase concentrations in models
+ - Homeostatic regulation of protein levels
+ - Buffered species in cell-free systems
+ - Representing constitutive expression of essential genes
+
+ Required parameters for this mechanism:
+
+ - 'kdil' : Creation rate constant (typically equal to dilution rate)
+
+ When used with Dilution on the same species with the same 'kdil' value,
+ the species concentration remains constant (steady state).
+
+ Examples
+ --------
+ Maintain ribosome concentration despite dilution:
+
+ >>> ribosome = bcp.Protein('ribosome')
+ >>> dilution = bcp.Dilution(default_on=True)
+ >>> creation = bcp.AntiDilutionConstitutiveCreation(
+ ... filter_dict={'ribosome': True},
+ ... default_on=False
+ ... )
+ >>> mixture = bcp.Mixture(
+ ... components=[ribosome],
+ ... mechanisms={'dilution': dilution, 'creation': creation},
+ ... parameters={'kdil': 0.01}
+ ... )
+
+ Maintain all machinery species at constant levels:
+
+ >>> machinery_species = [
+ ... bcp.Protein('ribosome', attributes=['machinery']),
+ ... bcp.Protein('RNAP', attributes=['machinery'])
+ ... ]
+ >>> creation = bcp.AntiDilutionConstitutiveCreation(
+ ... filter_dict={'machinery': True},
+ ... default_on=False
+ ... )
"""
@@ -263,6 +639,24 @@ def __init__(
)
def update_reactions(self, s, mixture):
+ """Generate constitutive creation reaction for a single species.
+
+ Creates an irreversible mass-action reaction that produces the
+ species at rate 'kdil' to counteract dilution.
+
+ Parameters
+ ----------
+ s : Species
+ The species being constitutively created.
+ mixture : Mixture
+ The mixture containing the 'kdil' parameter.
+
+ Returns
+ -------
+ list of Reaction
+ List containing a single reaction: ∅ --> S with rate 'kdil'.
+
+ """
k_dil = self.get_parameter(s, 'kdil', mixture)
rxn = Reaction.from_massaction(
inputs=[], outputs=[s], k_forward=k_dil
@@ -271,13 +665,109 @@ def update_reactions(self, s, mixture):
class Degradation_mRNA_MM(GlobalMechanism, MichaelisMenten):
- """Michaelis Menten mRNA Degradation by Endonucleases.
+ """Michaelis-Menten mRNA degradation by endonucleases.
+
+ A 'rna_degradation' mechanism that uses Michaelis-Menten kinetics to
+ model the enzymatic degradation of mRNA by endonucleases. All species
+ with material_type 'rna' are degraded, including those within
+ ComplexSpecies.
+
+ The degradation reaction scheme is
mRNA + Endo <--> mRNA:Endo --> Endo
- All species of type "rna" are degraded by this mechanisms, including those
- inside of a ComplexSpecies. ComplexSpecies are seperated by this process,
- including embedded ComplexSpecies. OrderedPolymerSpecies are ignored.
+ where mRNA is any RNA species and Endo is the endonuclease.
+
+ Parameters
+ ----------
+ nuclease : Species
+ The endonuclease species that degrades mRNA.
+ name : str, default='rna_degradation_mm'
+ Name identifier for this mechanism instance.
+ mechanism_type : str, default='rna_degradation'
+ Type classification of this mechanism.
+ default_on : bool, default=False
+ If True, mechanism acts on all species not filtered. If False, only
+ acts on filtered species.
+ recursive_species_filtering : bool, default=True
+ If True, searches for RNA within ComplexSpecies recursively. If
+ False, only acts on top-level species.
+ filter_dict : dict, optional
+ Dictionary for filtering species. Default is {'rna': True,
+ 'notdegradable': False} to degrade RNA but not species marked as
+ not degradable.
+ **kwargs
+ Additional keyword arguments passed to parent classes.
+
+ Attributes
+ ----------
+ nuclease : Species
+ The endonuclease species.
+ name : str
+ Name of the mechanism instance.
+ mechanism_type : str
+ Type classification ('rna_degradation').
+ filter_dict : dict
+ Dictionary for filtering species.
+ default_on : bool
+ Default behavior for unfiltered species.
+ recursive_species_filtering : bool
+ Whether to filter ComplexSpecies recursively.
+
+ See Also
+ --------
+ MichaelisMenten : Base enzyme kinetics mechanism.
+ Deg_Tagged_Degradation : Targeted protein degradation.
+ GlobalMechanism : Base class for global mechanisms.
+
+ Notes
+ -----
+ This mechanism handles three cases:
+
+ 1. Pure RNA species: Degraded completely (RNA --> ∅)
+ 2. RNA in ComplexSpecies: Complex is broken apart, RNA is degraded,
+ non-RNA components are released
+ 3. OrderedPolymerSpecies: Not affected by this mechanism
+
+ The mechanism generates Michaelis-Menten reactions with three rate
+ constants per species. ComplexSpecies containing RNA are separated
+ during degradation, including embedded ComplexSpecies. However,
+ OrderedPolymerSpecies (like DNA or assembled proteins) are ignored.
+
+ Required parameters for this mechanism:
+
+ - 'kdeg' : Catalytic rate constant for RNA degradation
+ - 'kb' : Forward binding rate for nuclease-RNA association
+ - 'ku' : Reverse unbinding rate for nuclease-RNA dissociation
+
+ The default filter_dict applies degradation to all 'rna' species but
+ excludes any species with the 'notdegradable' attribute, allowing
+ fine-grained control over which RNA species are degraded.
+
+ Examples
+ --------
+ Model global mRNA degradation in a cell-free system:
+
+ >>> rnase = bcp.Protein('RNase')
+ >>> mrna = bcp.RNA('mRNA')
+ >>> degradation = bcp.Degradation_mRNA_MM(nuclease=rnase.species)
+ >>> mixture = bcp.Mixture(
+ ... components=[rnase, mrna],
+ ... mechanisms={'rna_degradation': degradation},
+ ... parameters={'kdeg': 0.1, 'kb': 1.0, 'ku': 0.5}
+ ... )
+
+ Protect specific RNAs from degradation:
+
+ >>> rnase = bcp.Protein('RNase')
+ >>> stable_rna = bcp.RNA('stable', attributes=['notdegradable'])
+ >>> unstable_rna = bcp.RNA('unstable')
+ >>> degradation = bcp.Degradation_mRNA_MM(nuclease=rnase.species)
+ >>> mixture = bcp.Mixture(
+ ... components=[rnase, stable_rna, unstable_rna],
+ ... mechanisms={'rna_degradation': degradation},
+ ... parameters={'kdeg': 0.1, 'kb': 1.0, 'ku': 0.5}
+ ... )
"""
@@ -289,7 +779,7 @@ def __init__(
default_on=False,
recursive_species_filtering=True,
filter_dict=None,
- **keywords,
+ **kwargs,
):
if isinstance(nuclease, Species):
self.nuclease = nuclease
@@ -312,6 +802,36 @@ def __init__(
)
def update_species(self, s, mixture):
+ """Generate species for mRNA degradation reactions.
+
+ Creates enzyme-substrate complexes needed for Michaelis-Menten
+ degradation kinetics. Handles RNA in ComplexSpecies by identifying
+ non-RNA components that will be released.
+
+ Parameters
+ ----------
+ s : Species
+ The species to check for RNA degradation.
+ mixture : Mixture
+ The mixture containing parameters.
+
+ Returns
+ -------
+ list of Species
+ List of new species (enzyme-substrate complexes) generated for
+ degradation reactions. Empty list if species should not be
+ degraded.
+
+ Notes
+ -----
+ Behavior depends on species type:
+
+ - Pure RNA species: Creates nuclease:RNA complex
+ - ComplexSpecies containing RNA: Creates nuclease:complex complex,
+ identifies non-RNA products to be released
+ - OrderedPolymerSpecies: Returns empty list (not degraded)
+
+ """
species = []
# Check if rna species are inside a ComplexSpecies.
@@ -348,6 +868,36 @@ def update_species(self, s, mixture):
return species
def update_reactions(self, s, mixture):
+ """Generate Michaelis-Menten degradation reactions for mRNA.
+
+ Creates two mass-action reactions implementing Michaelis-Menten
+ kinetics for RNA degradation: reversible binding and irreversible
+ catalysis.
+
+ Parameters
+ ----------
+ s : Species
+ The species to check for RNA degradation.
+ mixture : Mixture
+ The mixture containing parameters 'kdeg', 'kb', and 'ku'.
+
+ Returns
+ -------
+ list of Reaction
+ List of reactions for RNA degradation. Empty list if species
+ should not be degraded.
+
+ Notes
+ -----
+ Generates standard Michaelis-Menten reactions:
+
+ 1. RNA + nuclease <--> RNA:nuclease (rates 'kb' and 'ku')
+ 2. RNA:nuclease --> nuclease (rate 'kdeg')
+
+ For ComplexSpecies containing RNA, non-RNA components are released
+ in the catalytic step instead of being degraded.
+
+ """
reactions = []
# Check if rna species are inside a ComplexSpecies.
@@ -406,31 +956,135 @@ def update_reactions(self, s, mixture):
class Deg_Tagged_Degradation(GlobalMechanism, MichaelisMenten):
- """Michaelis Menten Degradation of deg-tagged proteins by degredase.
-
- Species_degtagged + degredase <--> Species_degtagged:degredase
- --> degredase
-
- All species with the attribute degtagged and material_type protein are
- degraded. The method is not recursive.
+ """Michaelis-Menten degradation of deg-tagged proteins by degradase.
+
+ A 'degradation' mechanism that uses Michaelis-Menten kinetics to model
+ the targeted enzymatic degradation of proteins tagged for degradation
+ (e.g., via degron sequences). Only species with a specific degradation
+ tag attribute and material_type 'protein' are degraded.
+
+ The degradation reaction scheme is
+
+ Protein_degtagged + degradase <--> Protein_degtagged:degradase
+ --> degradase
+
+ where Protein_degtagged is any protein with the degradation tag.
+
+ Parameters
+ ----------
+ degradase : Species
+ The degradase enzyme species that degrades tagged proteins.
+ deg_tag : str, default='degtagged'
+ The attribute name used to identify proteins tagged for
+ degradation.
+ name : str, default='deg_tagged_degradation'
+ Name identifier for this mechanism instance.
+ mechanism_type : str, default='degradation'
+ Type classification of this mechanism.
+ filter_dict : dict, optional
+ Dictionary for filtering species. Default is {deg_tag: True} to
+ degrade only species with the degradation tag.
+ recursive_species_filtering : bool, default=False
+ If True, searches within ComplexSpecies recursively. If False,
+ only acts on top-level species.
+ default_on : bool, default=False
+ If True, mechanism acts on all species not filtered. If False, only
+ acts on filtered species.
+ **kwargs
+ Additional keyword arguments passed to parent classes.
+
+ Attributes
+ ----------
+ degradase : Species
+ The degradase enzyme species.
+ name : str
+ Name of the mechanism instance.
+ mechanism_type : str
+ Type classification ('degradation').
+ filter_dict : dict
+ Dictionary for filtering species.
+ default_on : bool
+ Default behavior for unfiltered species.
+ recursive_species_filtering : bool
+ Whether to filter ComplexSpecies recursively.
+
+ See Also
+ --------
+ MichaelisMenten : Base enzyme kinetics mechanism.
+ Degradation_mRNA_MM : Global mRNA degradation mechanism.
+ GlobalMechanism : Base class for global mechanisms.
+
+ Notes
+ -----
+ This mechanism implements targeted protein degradation similar to
+ biological systems like the ubiquitin-proteasome system or degron-mediated
+ degradation. Unlike global degradation mechanisms, it only affects
+ proteins explicitly tagged with the specified attribute.
+
+ The mechanism is not recursive by default, meaning it only degrades
+ proteins directly tagged with the `deg_tag` attribute, not proteins
+ within ComplexSpecies unless the complex itself is tagged.
+
+ Common applications include:
+
+ - Modeling ssrA-tagged protein degradation
+ - Implementing synthetic degron systems
+ - Targeted protein knockdown experiments
+ - Conditional protein stability control
+
+ Required parameters for this mechanism:
+
+ - 'kdeg' : Catalytic rate constant for protein degradation
+ - 'kb' : Forward binding rate for degradase-protein association
+ - 'ku' : Reverse unbinding rate for degradase-protein dissociation
+
+ The `deg_tag` attribute must be added to protein species that should be
+ degraded. By default, the mechanism looks for the 'degtagged'
+ attribute but this can be customized via the `deg_tag` parameter.
+
+ Examples
+ --------
+ Model ssrA-tagged protein degradation:
+
+ >>> clpxp = bcp.Protein('ClpXP')
+ >>> stable_protein = bcp.Protein('stable')
+ >>> tagged_protein = bcp.Protein('tagged', attributes=['degtagged'])
+ >>> degradation = bcp.Deg_Tagged_Degradation(
+ ... degradase=clpxp.species,
+ ... deg_tag='degtagged'
+ ... )
+ >>> mixture = bcp.Mixture(
+ ... components=[clpxp, stable_protein, tagged_protein],
+ ... mechanisms={'degradation': degradation},
+ ... parameters={'kdeg': 0.5, 'kb': 1.0, 'ku': 0.1}
+ ... )
+
+ Use custom degradation tags:
+
+ >>> proteasome = bcp.Protein('proteasome')
+ >>> ubiquitinated = bcp.Protein('target', attributes=['ubiquitinated'])
+ >>> degradation = bcp.Deg_Tagged_Degradation(
+ ... degradase=proteasome.species,
+ ... deg_tag='ubiquitinated'
+ ... )
"""
def __init__(
self,
- degredase,
+ degradase,
deg_tag='degtagged',
name='deg_tagged_degradation',
mechanism_type='degradation',
filter_dict=None,
recursive_species_filtering=False,
default_on=False,
- **keywords,
+ **kwargs,
):
- if isinstance(degredase, Species):
- self.degredase = degredase
+ if isinstance(degradase, Species):
+ self.degradase = degradase
else:
- raise ValueError("'degredase' must be a Species.")
+ raise ValueError("'degradase' must be a Species.")
MichaelisMenten.__init__(
self=self, name=name, mechanism_type=mechanism_type
)
@@ -448,13 +1102,58 @@ def __init__(
)
def update_species(self, s, mixture):
+ """Generate species for deg-tagged protein degradation reactions.
+
+ Creates enzyme-substrate complexes needed for Michaelis-Menten
+ degradation kinetics of tagged proteins.
+
+ Parameters
+ ----------
+ s : Species
+ The species to check for degradation tagging.
+ mixture : Mixture
+ The mixture containing parameters.
+
+ Returns
+ -------
+ list of Species
+ List containing the degradase:protein complex species.
+
+ """
species = []
species += MichaelisMenten.update_species(
- self, enzyme=self.degredase, substrate=s, product=None
+ self, enzyme=self.degradase, substrate=s, product=None
)
return species
def update_reactions(self, s, mixture):
+ """Generate reactions for deg-tagged protein degradation reactions.
+
+ Creates two mass-action reactions implementing Michaelis-Menten
+ kinetics for targeted protein degradation: reversible binding and
+ irreversible catalysis.
+
+ Parameters
+ ----------
+ s : Species
+ The tagged protein species to be degraded.
+ mixture : Mixture
+ The mixture containing parameters 'kdeg', 'kb', and 'ku'.
+
+ Returns
+ -------
+ list of Reaction
+ List of two reactions for tagged protein degradation:
+ [binding_reaction, catalysis_reaction].
+
+ Notes
+ -----
+ Generates standard Michaelis-Menten reactions:
+
+ 1. Protein + degradase <--> Protein:degradase (rates 'kb' and 'ku')
+ 2. Protein:degradase --> degradase (rate 'kdeg')
+
+ """
kdeg = self.get_parameter(s, 'kdeg', mixture)
kb = self.get_parameter(s, 'kb', mixture)
ku = self.get_parameter(s, 'ku', mixture)
@@ -462,7 +1161,7 @@ def update_reactions(self, s, mixture):
rxns = []
rxns += MichaelisMenten.update_reactions(
self,
- enzyme=self.degredase,
+ enzyme=self.degradase,
substrate=s,
product=None,
kb=kb,
diff --git a/biocrnpyler/mechanisms/integrase.py b/biocrnpyler/mechanisms/integrase.py
index 64ad72f5..393d059c 100644
--- a/biocrnpyler/mechanisms/integrase.py
+++ b/biocrnpyler/mechanisms/integrase.py
@@ -10,23 +10,108 @@
class BasicIntegration(Mechanism):
- """Mechanism for the schema DNA1 + DNA2 --> DNA3 + DNA4."""
+ """Basic DNA integration mechanism without enzyme involvement.
+
+ An 'integration' mechanism that models the direct recombination or
+ integration of two DNA molecules to form two new DNA products. This
+ represents a simplified integration process without explicit enzyme
+ involvement, useful for modeling the overall effect of site-specific
+ recombination.
+
+ The integration reaction is given by
+
+ DNA1 + DNA2 --> DNA3 + DNA4
+
+ where DNA1 and DNA2 are input DNA molecules, and DNA3 and DNA4 are
+ the recombined product DNA molecules.
+
+ Parameters
+ ----------
+ name : str, default='basic_integration'
+ Name identifier for this mechanism instance.
+ mechanism_type : str, default='integration'
+ Type classification of this mechanism.
+ **kwargs
+ Additional keyword arguments (unused, maintained for API
+ compatibility).
+
+ Attributes
+ ----------
+ name : str
+ Name of the mechanism instance.
+ mechanism_type : str
+ Type classification ('integration').
+
+ See Also
+ --------
+ EnzymeIntegration : Integration with explicit integrase enzyme.
+ Mechanism : Base class for all mechanisms.
+
+ Notes
+ -----
+ This mechanism generates a single irreversible mass-action reaction
+ representing the overall integration process. It does not model the
+ detailed molecular mechanism involving integrase binding or
+ intermediate complexes.
+
+ Common applications include:
+
+ - Simplified models of site-specific recombination
+ - DNA rearrangement in synthetic biology circuits
+ - Cassette exchange systems
+ - Genome editing with minimal mechanistic detail
+
+ Required parameters for this mechanism:
+
+ - 'kint' : Integration rate constant
+
+ The mechanism does not generate new species (returns empty list from
+ update_species) as it models the direct conversion without
+ intermediate complexes.
+
+ Examples
+ --------
+ See 'Specialized_Tutorials/Integrase_Examples'.
+
+ """
def __init__(
self,
name: str = 'basic_integration',
mechanism_type: str = 'integration',
- **keywords,
+ **kwargs,
):
- """Initializes a BasicIntegration instance.
-
- :param name: name of the Mechanism, default: basic_integration
- :param mechanism_type: type of the Mechanism, default: integration
- :param keywords:
- """
Mechanism.__init__(self, name, mechanism_type)
- def update_species(self, DNA_inputs, DNA_outputs=None, **keywords):
+ def update_species(self, DNA_inputs, DNA_outputs=None, **kwargs):
+ """Generate species for basic integration.
+
+ This method returns an empty list as basic integration does not
+ create intermediate species or complexes.
+
+ Parameters
+ ----------
+ DNA_inputs : list of Species
+ List of input DNA species to be integrated.
+ DNA_outputs : list of Species, optional
+ List of output DNA species after integration. Not used in
+ species generation.
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list
+ Empty list (no new species generated).
+
+ Notes
+ -----
+ This mechanism does not generate intermediate complexes. If
+ modeling with explicit integrase-DNA complexes is needed, consider
+ using a binding mechanism in combination with integration, or use
+ the `EnzymeIntegration` mechanism.
+
+ """
# this doesn't make any species because I use a Binding
# mechanism for that maybe if we do the tetramerization
# mechanism then this would do something
@@ -39,8 +124,49 @@ def update_reactions(
component=None,
part_id=None,
kint=None,
- **keywords,
+ **kwargs,
):
+ """Generate integration reaction for basic integration.
+
+ Creates a single irreversible mass-action reaction for DNA
+ integration.
+
+ Parameters
+ ----------
+ DNA_inputs : list of Species
+ List of input DNA species to be integrated (typically 2).
+ DNA_outputs : list of Species
+ List of output DNA species after integration (typically 2).
+ component : Component, optional
+ Component containing parameter values. Required if kint is not
+ provided directly.
+ part_id : str, optional
+ Identifier for parameter lookup. If None, defaults to
+ component.name.
+ kint : Parameter or float, optional
+ Integration rate constant. If None, retrieved from component
+ parameters.
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Reaction
+ List containing a single irreversible mass-action reaction:
+ DNA_inputs --> DNA_outputs.
+
+ Raises
+ ------
+ ValueError
+ If component is None and kint is not provided.
+
+ Notes
+ -----
+ The reaction follows simple mass-action kinetics with rate
+ constant 'kint'. All input DNA species are consumed and all output
+ DNA species are produced simultaneously.
+
+ """
if part_id is None and component is not None:
part_id = component.name
@@ -59,11 +185,79 @@ def update_reactions(
class EnzymeIntegration(Mechanism):
- """Enzymatic integrase mechanism.
+ """Enzyme-catalyzed DNA integration mechanism with integrase.
- Mechanism for the schema:
+ An 'integration' mechanism that models site-specific recombination
+ catalyzed by integrase enzymes. The mechanism explicitly includes the
+ integrase as a catalyst that facilitates DNA recombination but is not
+ consumed in the reaction. This mechanism uses tetrameric integrase
+ complexes (4 integrase molecules) to catalyze the integration.
- integrase + DNA1 + DNA2 --> integrase + DNA3 + DNA4.
+ The integration reaction is given by
+
+ 4*Int + DNA1 + DNA2 --> 4*Int + DNA3 + DNA4
+
+ where Int is the integrase enzyme, DNA1 and DNA2 are input DNA
+ molecules, and DNA3 and DNA4 are the recombined product DNA molecules.
+
+ Parameters
+ ----------
+ name : str, default='enzyme_integration'
+ Name identifier for this mechanism instance.
+ mechanism_type : str, default='integration'
+ Type classification of this mechanism.
+ integrase : str, default='Int1'
+ Name of the integrase enzyme. A Species with this name and
+ material_type 'protein' is created automatically.
+ **kwargs
+ Additional keyword arguments (unused, maintained for API
+ compatibility).
+
+ Attributes
+ ----------
+ integrase : Species
+ The integrase enzyme species with material_type 'protein'.
+ name : str
+ Name of the mechanism instance.
+ mechanism_type : str
+ Type classification ('integration').
+
+ See Also
+ --------
+ BasicIntegration : Integration without explicit enzyme.
+ Mechanism : Base class for all mechanisms.
+
+ Notes
+ -----
+ This mechanism models site-specific recombination with explicit
+ integrase involvement. The integrase acts as a true catalyst,
+ appearing on both sides of the reaction and not being consumed.
+
+ The mechanism uses 4 integrase molecules to model the tetrameric
+ integrase complex typically formed during site-specific recombination
+ (e.g., lambda phage integration, Cre-loxP recombination). This
+ reflects the biological reality where integrase monomers form
+ tetramers to catalyze strand exchange.
+
+ Common applications include:
+
+ - Lambda phage integration/excision systems
+ - Cre-loxP recombination
+ - FLP-FRT site-specific recombination
+ - Detailed models of integrase-mediated genome editing
+
+ Required parameters for this mechanism:
+
+ - 'kint' : Integration rate constant
+
+ The stoichiometry of 4 integrase molecules reflects biological
+ integrase mechanisms where a tetramer is the active form. The
+ mechanism does not generate intermediate complexes (returns empty list
+ from update_species).
+
+ Examples
+ --------
+ See 'Specialized_Tutorials/Integrase_Examples'.
"""
@@ -72,19 +266,42 @@ def __init__(
name: str = 'enzyme_integration',
mechanism_type: str = 'integration',
integrase='Int1',
- **keywords,
+ **kwargs,
):
- """Initializes a BasicIntegration instance.
-
- :param name: name of the Mechanism, default: basic_integration
- :param mechanism_type: type of the Mechanism, default: integration
- :param keywords:
- """
# TODO ZAT: remove unused keywords argument
self.integrase = Species(name=integrase, material_type='protein')
Mechanism.__init__(self, name, mechanism_type)
- def update_species(self, DNA_inputs, DNA_outputs=None, **keywords):
+ def update_species(self, DNA_inputs, DNA_outputs=None, **kwargs):
+ """Generate species for enzyme integration.
+
+ This method returns an empty list as enzyme integration does not
+ create intermediate species or complexes in this implementation.
+
+ Parameters
+ ----------
+ DNA_inputs : list of Species
+ List of input DNA species to be integrated.
+ DNA_outputs : list of Species, optional
+ List of output DNA species after integration. Not used in
+ species generation.
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list
+ Empty list (no new species generated).
+
+ Notes
+ -----
+ This mechanism does not generate intermediate integrase-DNA
+ complexes. The integrase-DNA binding and tetramerization steps are
+ implicitly captured in the overall rate constant 'kint'. For more
+ detailed mechanistic models, consider using explicit binding
+ mechanisms before integration.
+
+ """
# this doesn't make any species because I use a Binding
# mechanism for that maybe if we do the tetramerization
# mechanism then this would do something
@@ -97,8 +314,56 @@ def update_reactions(
component=None,
part_id=None,
kint=None,
- **keywords,
+ **kwargs,
):
+ """Generate integration reaction with integrase enzyme.
+
+ Creates a single irreversible mass-action reaction for enzymatic
+ DNA integration with tetrameric integrase complex.
+
+ Parameters
+ ----------
+ DNA_inputs : list of Species
+ List of input DNA species to be integrated (typically 2).
+ DNA_outputs : list of Species
+ List of output DNA species after integration (typically 2).
+ component : Component, optional
+ Component containing parameter values. Required if kint is not
+ provided directly.
+ part_id : str, optional
+ Identifier for parameter lookup. If None, defaults to
+ component.name.
+ kint : Parameter or float, optional
+ Integration rate constant. If None, retrieved from component
+ parameters.
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Reaction
+ List containing a single irreversible mass-action reaction:
+ 4*integrase + DNA_inputs --> 4*integrase + DNA_outputs.
+
+ Raises
+ ------
+ ValueError
+ If component is None and kint is not provided.
+
+ Notes
+ -----
+ The reaction includes 4 integrase molecules on both sides,
+ modeling the tetrameric integrase complex required for
+ site-specific recombination. The integrase is not consumed (acts
+ as a true catalyst).
+
+ The stoichiometry reflects biological mechanisms like:
+
+ - Lambda integrase (Int) tetramer formation
+ - Cre recombinase synaptic complex
+ - Other integrase family enzymes requiring multimers
+
+ """
if part_id is None and component is not None:
part_id = component.name
diff --git a/biocrnpyler/mechanisms/metabolite.py b/biocrnpyler/mechanisms/metabolite.py
index 18f51cff..4312aec9 100644
--- a/biocrnpyler/mechanisms/metabolite.py
+++ b/biocrnpyler/mechanisms/metabolite.py
@@ -2,14 +2,150 @@
from ..core.reaction import Reaction
-# [precursors] --> [products] using Massaction (None OK)
class OneStepPathway(Mechanism):
+ """Simple one-step metabolic pathway mechanism.
+
+ A 'metabolic_pathway' mechanism that models the conversion of precursor
+ metabolites to product metabolites in a single irreversible step. This
+ mechanism can represent metabolic reactions, spontaneous conversions, or
+ simplified enzymatic pathways where enzyme dynamics are not explicitly
+ modeled.
+
+ The pathway reaction can be any of the following forms:
+
+ - Precursors --> Products (standard conversion)
+ - --> Products (creation from nothing)
+ - Precursors --> (degradation to nothing)
+
+ where precursors and products can be single species or lists of species.
+
+ Parameters
+ ----------
+ name : str, default='one_step_pathway'
+ Name identifier for this mechanism instance.
+ mechanism_type : str, default='metabolic_pathway'
+ Type classification of this mechanism.
+
+ Attributes
+ ----------
+ name : str
+ Name of the mechanism instance.
+ mechanism_type : str
+ Type classification ('metabolic_pathway').
+
+ See Also
+ --------
+ BasicCatalysis : Enzymatic catalysis with explicit enzyme.
+ BasicProduction : Catalytic production mechanism.
+ Mechanism : Base class for all mechanisms.
+
+ Notes
+ -----
+ This mechanism generates a single irreversible mass-action reaction
+ with rate constant 'k'. It provides flexibility to model various types
+ of metabolic processes:
+
+ - Standard conversion: Multiple precursors convert to multiple
+ products
+ - Creation: Products appear spontaneously (precursor=None)
+ - Degradation: Precursors disappear (product=None)
+
+ The mechanism does not explicitly model enzymes or intermediate
+ complexes, making it suitable for:
+
+ - Lumped metabolic pathways
+ - Spontaneous chemical reactions
+ - Simplified models where enzyme dynamics are negligible
+ - Material flow in metabolic networks
+ - Constitutive processes
+
+ Common applications include:
+
+ - Metabolic flux balance models
+ - Simplified biosynthetic pathways
+ - Nutrient uptake and consumption
+ - Waste product formation
+ - Simple chemical transformations
+
+ Required parameters for this mechanism:
+
+ - 'k' : Forward rate constant for the conversion
+
+ The mechanism supports arbitrary stoichiometries through the use of
+ lists for precursors and products. Stoichiometry is determined by the
+ number of times a species appears in the list.
+
+ Examples
+ --------
+ Model a simple metabolic conversion:
+
+ >>> g6p = bcp.Metabolite('g6p', precursors=['glucose'], products=[])
+ >>> mixture = bcp.Mixture(
+ ... components=[g6p],
+ ... mechanisms={'metabolic_pathway': bcp.OneStepPathway()},
+ ... parameters={'k': 0.1}
+ ... )
+ >>> mixture.compile_crn()
+
+ Model constitutive metabolite production:
+
+ >>> metabolite = bcp.Metabolite(
+ ... 'metabolite', precursors=[None], products=[])
+ >>> mixture = bcp.Mixture(
+ ... components=[metabolite],
+ ... mechanisms={'metabolic_pathway': bcp.OneStepPathway()},
+ ... parameters={'k': 1.0}
+ ... )
+ >>> mixture.compile_crn()
+
+ Model metabolite degradation:
+
+ >>> waste = bcp.Metabolite(
+ ... 'waste_product', precursors=[], products=[None])
+ >>> mixture = bcp.Mixture(
+ ... components=[waste],
+ ... mechanisms={'metabolic_pathway': bcp.OneStepPathway()},
+ ... parameters={'k': 0.2}
+ ... )
+ >>> mixture.compile_crn()
+
+ """
+
def __init__(
self, name='one_step_pathway', mechanism_type='metabolic_pathway'
):
Mechanism.__init__(self, name=name, mechanism_type=mechanism_type)
- def update_species(self, precursor, product, **keywords):
+ def update_species(self, precursor, product, **kwargs):
+ """Generate species list for metabolic pathway.
+
+ Collects all species involved in the pathway (precursors and
+ products) into a single list.
+
+ Parameters
+ ----------
+ precursor : Species, list of Species, or None
+ Precursor species or list of precursor species. If None, the
+ pathway represents creation from nothing.
+ product : Species, list of Species, or None
+ Product species or list of product species. If None, the
+ pathway represents degradation to nothing.
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Species
+ Combined list of all precursor and product species. Returns
+ empty list if both are None.
+
+ Notes
+ -----
+ This method simply aggregates the input species without creating
+ new species or complexes. The species should already exist in the
+ system before this mechanism is applied.
+
+ """
species = []
if precursor is not None:
species += precursor
@@ -24,8 +160,57 @@ def update_reactions(
component=None,
part_id=None,
k=None,
- **keywords,
+ **kwargs,
):
+ """Generate metabolic pathway reactions.
+
+ Creates a single irreversible mass-action reaction for the
+ metabolic conversion of precursors to products.
+
+ Parameters
+ ----------
+ precursor : Species, list of Species, or None
+ Precursor species or list of precursor species. If None, the
+ reaction represents creation (no inputs).
+ product : Species, list of Species, or None
+ Product species or list of product species. If None, the
+ reaction represents degradation (no outputs).
+ component : Component, optional
+ Component containing parameter values. Required if k is not
+ provided directly.
+ part_id : str, optional
+ Identifier for parameter lookup. If None, defaults to using
+ component's parameter lookup without specific part_id.
+ k : Parameter or float, optional
+ Forward rate constant for the pathway. If None, retrieved from
+ component parameters.
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Reaction
+ List containing a single irreversible mass-action reaction:
+ precursors --> products.
+
+ Raises
+ ------
+ ValueError
+ If component is None and k is not provided.
+
+ Notes
+ -----
+ The reaction follows mass-action kinetics with rate constant 'k'.
+ The mechanism supports three modes:
+
+ - Standard: precursors --> products
+ - Creation: [] --> products (when precursor is None)
+ - Degradation: precursors --> [] (when product is None)
+
+ Multiple species of the same type in the precursor or product list
+ determine the stoichiometry of that species in the reaction.
+
+ """
if precursor is None:
inputs = []
else:
diff --git a/biocrnpyler/mechanisms/signaling.py b/biocrnpyler/mechanisms/signaling.py
index fb33d2fd..75f00058 100644
--- a/biocrnpyler/mechanisms/signaling.py
+++ b/biocrnpyler/mechanisms/signaling.py
@@ -7,23 +7,111 @@
class Membrane_Signaling_Pathway_MM(Mechanism):
- """A mechanism to model a two-component system (TCS) membrane sensor.
+ """Two-component system membrane sensor with Michaelis-Menten kinetics.
- Includes the sensing of signal substrate (SigSub) and the phosphorylation
- of the response protein (RP) but not include the reporter circuit.
- Mechanism follows Michaelis-Menten type reactions.
+ A 'membrane_sensor' mechanism that models a two-component system (TCS)
+ for signal transduction across cellular membranes. This mechanism
+ includes signal substrate sensing, membrane sensor protein activation,
+ auto-phosphorylation via ATP, and phosphorylation of response proteins,
+ but does not include downstream reporter circuits.
- Mechanism for the activation of the membrane sensor protein (MSP):
- SP + SigSub <--> SP:SigSub --> SP*
+ The mechanism follows a multi-step Michaelis-Menten kinetic scheme with
+ the following reaction pathway:
- Mechanism for the auto-phosphorylation:
- SP* + nATP <--> SP*:nATP --> SP**:nADP --> SP** + nADP
+ 1. Activation of membrane sensor protein (MSP):
- Mechanism for the phosphorylation of response protein:
- SP** + RP <--> SP**:RP --> SP*:RP* --> SP*+RP*
+ SP + SigSub <--> SP:SigSub --> SP*
- Mechanism for the dephosphorylation of phosphoryled response protein:
- RP* --> RP + Pi
+ 2. Auto-phosphorylation via ATP:
+
+ SP* + nATP <--> SP*:nATP --> SP**:nADP --> SP** + nADP
+
+ 3. Phosphorylation of response protein (RP):
+
+ SP** + RP <--> SP**:RP --> SP*:RP* --> SP* + RP*
+
+ 4. Dephosphorylation of phosphorylated response protein:
+
+ RP* --> RP + Pi
+
+ Parameters
+ ----------
+ name : str, default='two_component_membrane_signaling'
+ Name identifier for this mechanism instance.
+ mechanism_type : str, default='membrane_sensor'
+ Type classification of this mechanism.
+
+ Attributes
+ ----------
+ name : str
+ Name of the mechanism instance.
+ mechanism_type : str
+ Type classification ('membrane_sensor').
+
+ See Also
+ --------
+ Mechanism : Base class for all mechanisms.
+ MichaelisMenten : Enzyme-substrate mechanism with MM kinetics.
+
+ Notes
+ -----
+ This mechanism models bacterial two-component signaling systems, which
+ are common environmental sensing pathways. The sensor protein spans the
+ membrane and undergoes conformational changes upon binding external
+ signals, leading to autophosphorylation and subsequent phosphotransfer
+ to response proteins that regulate gene expression.
+
+ The mechanism requires the membrane sensor protein to have an ATP
+ attribute (membrane_sensor_protein.ATP) that specifies the number of
+ ATP molecules required for autophosphorylation.
+
+ Required parameters for this mechanism:
+
+ - 'kb_sigMS' : Forward binding rate for signal substrate to membrane
+ sensor protein
+ - 'ku_sigMS' : Reverse unbinding rate for signal substrate from
+ membrane sensor protein
+ - 'kb_autoPhos' : Forward binding rate for ATP to activated membrane
+ sensor protein
+ - 'ku_autoPhos' : Reverse unbinding rate for ATP from activated
+ membrane sensor protein
+ - 'k_hydro' : ATP hydrolysis rate constant
+ - 'ku_waste' : Unbinding rate for ADP waste products
+ - 'kb_phosRP' : Forward binding rate for response protein to
+ phosphorylated membrane sensor
+ - 'ku_phosRP' : Reverse unbinding rate for response protein from
+ phosphorylated membrane sensor
+ - 'k_phosph' : Phosphotransfer rate constant to response protein
+ - 'ku_activeRP' : Unbinding rate for activated response protein
+ - 'ku_dephos' : Dephosphorylation rate constant for phosphorylated
+ response protein
+
+ Examples
+ --------
+ Create a two-component signaling system with default parameters:
+
+ >>> response = bcp.Protein(name='OmpR')
+ >>> sensor = bcp.MembraneSensor(
+ ... membrane_sensor_protein='EnvZ',
+ ... response_protein=response.species,
+ ... assigned_substrate='Phosphate',
+ ... signal_substrate='Osmolarity',
+ ... ATP=2
+ ... )
+ >>> mechanism = bcp.Membrane_Signaling_Pathway_MM()
+ >>> mixture = bcp.Mixture(
+ ... components=[sensor, response],
+ ... mechanisms={'membrane_sensor': mechanism},
+ ... parameters={
+ ... 'kb_sigMS': 1.0, 'ku_sigMS': 0.1,
+ ... 'kb_autoPhos': 1.0, 'ku_autoPhos': 0.1,
+ ... 'k_hydro': 0.5, 'ku_waste': 1.0,
+ ... 'kb_phosRP': 1.0, 'ku_phosRP': 0.1,
+ ... 'k_phosph': 0.5, 'ku_activeRP': 1.0,
+ ... 'ku_dephos': 0.01
+ ... }
+ ... )
+ ... mixture.compile_crn()
"""
@@ -31,7 +119,7 @@ def __init__(
self,
name='two_component_membrane_signaling',
mechanism_type='membrane_sensor',
- **keywords,
+ **kwargs,
):
Mechanism.__init__(self, name, mechanism_type)
@@ -45,8 +133,70 @@ def update_species(
energy,
waste,
complex_dict=None,
- **keywords,
+ **kwargs,
):
+ """Generate species for two-component membrane signaling pathway.
+
+ Creates all species involved in the signaling cascade, including the
+ membrane sensor protein, response protein, signal substrate, ATP/ADP
+ energy species, and all intermediate complexes formed during signal
+ transduction and phosphotransfer.
+
+ Parameters
+ ----------
+ membrane_sensor_protein : Species
+ The membrane sensor protein that detects the signal. Must have
+ an ATP attribute specifying the number of ATP molecules required
+ for autophosphorylation.
+ response_protein : Species
+ The response protein that receives the phosphate group and
+ becomes activated.
+ assigned_substrate : Species
+ The phosphate substrate (Pi) that is transferred during the
+ signaling cascade.
+ signal_substrate : Species
+ The external signal molecule that activates the membrane sensor
+ protein.
+ product : Species
+ The phosphorylated response protein product (RP*).
+ energy : Species
+ ATP species used for autophosphorylation.
+ waste : Species
+ ADP species produced after ATP hydrolysis.
+ complex_dict : dict, optional
+ Pre-defined dictionary of complex species with keys
+ 'Activated_MSP', 'ATP:Activated_MSP', 'ADP:Activated_MSP:Sub',
+ 'Activated_MSP:Sub', 'Activated_MSP:Sub:RP', and
+ 'Activated_MSP:RP:Sub'. If None, complexes are automatically
+ created.
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list
+ List containing individual species and complex array:
+ [membrane_sensor_protein, response_protein,
+ assigned_substrate, signal_substrate, energy, waste,
+ complex_array] where complex_array is a list of all Complex
+ species generated.
+
+ Notes
+ -----
+ The method creates six different complex species representing the
+ intermediate states of the signaling cascade:
+
+ 1. Activated_MSP : signal_substrate:membrane_sensor_protein
+ 2. ATP:Activated_MSP : nATP:Activated_MSP
+ 3. ADP:Activated_MSP:Sub : Activated_MSP:nADP:Pi
+ 4. Activated_MSP:Sub : Activated_MSP:Pi (phosphorylated sensor)
+ 5. Activated_MSP:Sub:RP : (Activated_MSP:Pi):response_protein
+ 6. Activated_MSP:RP:Sub : Activated_MSP:(response_protein:Pi)
+
+ The number of ATP/ADP molecules (nATP) is determined by the
+ membrane_sensor_protein.ATP attribute.
+
+ """
nATP = membrane_sensor_protein.ATP
if complex_dict is None:
@@ -116,12 +266,86 @@ def update_reactions(
complex_dict=None,
component=None,
part_id=None,
- **keywords,
+ **kwargs,
):
- """Update reactions for membrane signaling pathway.
+ """Generate reactions for two-component membrane signaling pathway.
+
+ Creates all eight reactions comprising the complete signaling
+ cascade from signal detection through response protein activation
+ and dephosphorylation. Reactions follow Michaelis-Menten kinetics
+ with reversible binding steps and irreversible catalytic steps.
+
+ Parameters
+ ----------
+ membrane_sensor_protein : Species
+ The membrane sensor protein that detects the signal. Must have
+ an ATP attribute specifying the number of ATP molecules required
+ for autophosphorylation.
+ response_protein : Species
+ The response protein that receives the phosphate group and
+ becomes activated.
+ assigned_substrate : Species
+ The phosphate substrate (Pi) that is transferred during the
+ signaling cascade.
+ signal_substrate : Species
+ The external signal molecule that activates the membrane sensor
+ protein.
+ product : Species
+ The phosphorylated response protein product (RP*).
+ energy : Species
+ ATP species used for autophosphorylation.
+ waste : Species
+ ADP species produced after ATP hydrolysis.
+ complex_dict : dict, optional
+ Pre-defined dictionary of complex species. If None, complexes
+ are automatically created using the same logic as in
+ update_species.
+ component : Component
+ Component containing parameter values. Required for parameter
+ lookup.
+ part_id : str
+ Identifier for parameter lookup in the component's parameter
+ database. Required for parameter lookup.
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Reaction
+ List of eight reactions representing the complete signaling
+ cascade:
+
+ 1. Signal binding (reversible)
+ 2. ATP binding (reversible)
+ 3. ATP hydrolysis (irreversible)
+ 4. ADP release (irreversible)
+ 5. Response protein binding (reversible)
+ 6. Phosphotransfer (irreversible)
+ 7. Activated response protein release (irreversible)
+ 8. Response protein dephosphorylation (irreversible)
+
+ Raises
+ ------
+ AttributeError
+ If component or part_id is None (required for parameter lookup).
+
+ Notes
+ -----
+ The reaction scheme follows this pathway:
+
+ 1. SP + SigSub <--> SP:SigSub (rates: 'kb_sigMS', 'ku_sigMS')
+ 2. SP:SigSub + nATP <--> SP:SigSub:nATP
+ (rates: 'kb_autoPhos', 'ku_autoPhos')
+ 3. SP:SigSub:nATP --> SP:SigSub:Pi:nADP (rate: 'k_hydro')
+ 4. SP:SigSub:Pi:nADP --> SP:SigSub:Pi + nADP (rate: 'ku_waste')
+ 5. SP:SigSub:Pi + RP <--> SP:SigSub:Pi:RP
+ (rates: 'kb_phosRP', 'ku_phosRP')
+ 6. SP:SigSub:Pi:RP --> SP:SigSub:RP:Pi (rate: 'k_phosph')
+ 7. SP:SigSub:RP:Pi --> SP:SigSub + RP:Pi (rate: 'ku_activeRP')
+ 8. RP:Pi --> RP + Pi (rate: 'ku_dephos')
- This always requires the inputs component and part_id to find
- the relevant parameters.
+ This method requires both component and part_id parameters to
+ retrieve rate constants from the component's parameter database.
"""
# Get Parameters
diff --git a/biocrnpyler/mechanisms/transport.py b/biocrnpyler/mechanisms/transport.py
index bfb6affe..5a2206bc 100644
--- a/biocrnpyler/mechanisms/transport.py
+++ b/biocrnpyler/mechanisms/transport.py
@@ -8,18 +8,105 @@
class Simple_Diffusion(Mechanism):
- """Diffusion of a substrate through a membrane channel.
+ """Passive diffusion mechanism for substrate transport across membranes.
+
+ A 'diffusion' mechanism that models simple passive diffusion of
+ substrates through a membrane without requiring membrane proteins or
+ energy. The transport is bidirectional and follows Fick's law of
+ diffusion with equal forward and reverse rate constants.
+
+ The reaction follows the schema:
+
+ substrate <--> product
+
+ where substrate and product represent the same species on opposite sides
+ of the membrane.
+
+ Parameters
+ ----------
+ name : str, default='simple_diffusion'
+ Name identifier for this mechanism instance.
+ mechanism_type : str, default='diffusion'
+ Type classification of this mechanism.
+
+ Attributes
+ ----------
+ name : str
+ Name of the mechanism instance.
+ mechanism_type : str
+ Type classification ('diffusion').
+
+ See Also
+ --------
+ Simple_Transport : Passive transport through membrane channels.
+ Facilitated_Transport_MM : Facilitated diffusion with carriers.
+ Mechanism : Base class for all mechanisms.
+
+ Notes
+ -----
+ Simple diffusion models the movement of small, lipophilic molecules
+ across lipid bilayers without the assistance of membrane proteins. This
+ process is driven purely by concentration gradients and does not require
+ cellular energy.
+
+ Common examples include:
+
+ - Diffusion of gases (O2, CO2) across cell membranes
+ - Transport of small nonpolar molecules
+ - Movement of lipid-soluble substances
+
+ The mechanism generates a single reversible mass-action reaction with
+ equal forward and reverse rate constants, reflecting the thermodynamic
+ equilibrium of passive diffusion.
+
+ Required parameters for this mechanism:
+
+ - 'k_diff' : Diffusion rate constant (same for both directions)
+
+ Examples
+ --------
+ Model oxygen diffusion across a membrane:
+
+ >>> O2 = bcp.DiffusibleMolecule('O2')
+ >>> mechanism = bcp.Simple_Diffusion()
+ >>> mixture = bcp.Mixture(
+ ... components=[O2],
+ ... mechanisms={'diffusion': mechanism},
+ ... parameters={'k_diff': 0.1}
+ ... )
+ >>> mixture.compile_crn()
- Does not require energy and follows diffusion rules.
- Reaction schema: substrate <-> product
"""
def __init__(
- self, name='simple_diffusion', mechanism_type='diffusion', **keywords
+ self, name='simple_diffusion', mechanism_type='diffusion', **kwargs
):
Mechanism.__init__(self, name, mechanism_type)
- def update_species(self, substrate, product, **keywords):
+ def update_species(self, substrate, product, **kwargs):
+ """Generate species for simple diffusion.
+
+ Returns the substrate and product species involved in the diffusion
+ reaction.
+
+ Parameters
+ ----------
+ substrate : Species
+ The substrate species on one side of the membrane (typically
+ the intracellular side).
+ product : Species
+ The product species on the other side of the membrane (typically
+ the extracellular side). Usually the same molecular species as
+ substrate but in a different compartment.
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Species
+ List containing [substrate, product].
+
+ """
return [substrate, product]
def update_reactions(
@@ -29,8 +116,51 @@ def update_reactions(
component=None,
part_id=None,
k_diff=None,
- **keywords,
+ **kwargs,
):
+ """Generate reactions for simple diffusion.
+
+ Creates a single reversible mass-action reaction representing
+ passive diffusion across a membrane with equal forward and reverse
+ rate constants.
+
+ Parameters
+ ----------
+ substrate : Species
+ The substrate species on one side of the membrane.
+ product : Species
+ The product species on the other side of the membrane.
+ component : Component, optional
+ Component containing parameter values. Required if k_diff is not
+ provided directly.
+ part_id : str, optional
+ Identifier for parameter lookup. If None and component is
+ provided, defaults to component.name.
+ k_diff : Parameter or float, optional
+ Diffusion rate constant. If None, retrieved from component
+ parameters. Used as both forward and reverse rate constant.
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Reaction
+ List containing a single reversible mass-action reaction for
+ diffusion.
+
+ Raises
+ ------
+ ValueError
+ If component is None and k_diff is not provided.
+
+ Notes
+ -----
+ The reaction has equal forward and reverse rate constants, reflecting
+ the thermodynamic equilibrium of passive diffusion:
+
+ substrate <--> product (rates: 'k_diff', 'k_diff')
+
+ """
# Get Parameters
if part_id is None and component is not None:
part_id = component.name
@@ -56,24 +186,133 @@ def update_reactions(
class Membrane_Protein_Integration(Mechanism):
- """Integrate into the membrane protein in the membrane.
+ """Membrane protein integration mechanism for protein insertion.
+
+ A 'membrane_insertion' mechanism that models the integration of newly
+ synthesized proteins into cellular membranes. Supports both monomeric
+ and oligomeric membrane proteins, handling oligomerization before
+ membrane insertion when required.
+
+ The reaction schema depends on protein oligomeric state:
+
+ For monomers (size = 1):
+ monomer --> integral membrane protein
+
+ For oligomers (size > 1):
+ monomer * size <--> oligomer --> integral membrane protein
+
+ Parameters
+ ----------
+ name : str, default='membrane_protein_integration'
+ Name identifier for this mechanism instance.
+ mechanism_type : str, default='membrane_insertion'
+ Type classification of this mechanism.
+
+ Attributes
+ ----------
+ name : str
+ Name of the mechanism instance.
+ mechanism_type : str
+ Type classification ('membrane_insertion').
+
+ See Also
+ --------
+ Mechanism : Base class for all mechanisms.
+
+ Notes
+ -----
+ This mechanism models the process by which proteins become embedded in
+ cellular membranes. For oligomeric proteins, multiple monomers must
+ first associate into a complex before integration can occur. The
+ integration step uses a `ProportionalHillNegative` propensity function to
+ model saturation kinetics and product inhibition.
+
+ The mechanism requires the integral membrane protein to have a size
+ attribute (integral_membrane_protein.size) that specifies the number of
+ monomers in the functional unit.
+
+ Common examples include:
+
+ - Integration of ion channels (often oligomeric)
+ - Insertion of receptor proteins (can be monomeric or oligomeric)
+ - Assembly and insertion of transporter complexes
+
+ Required parameters for this mechanism:
+
+ - 'kb_oligomer' : Forward oligomerization rate constant (for size > 1)
+ - 'ku_oligomer' : Reverse oligomerization rate constant (for size > 1)
+ - 'kex' : Maximum integration rate constant
+ - 'kcat' : Michaelis constant for integration
+
+ Examples
+ --------
+ Model integration of a tetrameric channel:
+
+ >>> channel = bcp.IntegralMembraneProtein(
+ ... membrane_protein='Aquaporin',
+ ... product='Aquaporin_channel',
+ ... size=2,
+ ... direction='Passive'
+ ... )
+ >>> mechanism = bcp.Membrane_Protein_Integration()
+ >>> mixture = bcp.Mixture(
+ ... components=[channel],
+ ... mechanisms={'membrane_insertion': mechanism},
+ ... parameters={
+ ... 'kb_oligomer': 1.0, 'ku_oligomer': 0.1,
+ ... 'kex': 0.5, 'kcat': 10.0
+ ... }
+ ... )
+ >>> mixture.compile_crn()
- Reaction schema for monomers: monomer -> intergral membrane protein
- Reaction schema for oligomer: monomer*[size] -> oligomer
- -> intergral membrane protein
"""
def __init__(
self,
name='membrane_protein_integration',
mechanism_type='membrane_insertion',
- **keywords,
+ **kwargs,
):
Mechanism.__init__(self, name, mechanism_type)
def update_species(
- self, integral_membrane_protein, product, complex=None, **keywords
+ self, integral_membrane_protein, product, complex=None, **kwargs
):
+ """Generate species for membrane protein integration.
+
+ Creates species for monomers, oligomeric complexes (if needed), and
+ the integrated membrane protein product.
+
+ Parameters
+ ----------
+ integral_membrane_protein : Species
+ The membrane protein monomer that will be integrated. Must have
+ a size attribute specifying oligomeric state.
+ product : Species
+ The integrated membrane protein product after insertion.
+ complex : Species, optional
+ Pre-specified oligomeric complex. If None and size > 1,
+ automatically creates a Complex of size monomers. Ignored for
+ monomeric proteins (size = 1).
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list
+ List containing [integral_membrane_protein, product, complex]
+ where complex is None for monomers or a Complex species for
+ oligomers.
+
+ Notes
+ -----
+ For monomeric proteins (size = 1), no oligomeric complex is formed
+ and the complex element in the return list is None.
+
+ For oligomeric proteins (size > 1), a complex containing 'size'
+ copies of the monomer is created or used if provided.
+
+ """
if complex is None:
size = integral_membrane_protein.size
if size > 1:
@@ -95,12 +334,62 @@ def update_reactions(
complex=None,
component=None,
part_id=None,
- **keywords,
+ **kwargs,
):
- """Update reactions for membrane integration.
-
- This always requires the inputs component and part_id to find
- the relevant parameters.
+ """Generate reactions for membrane protein integration.
+
+ Creates reactions for oligomerization (if needed) and membrane
+ integration. For oligomeric proteins, generates both oligomerization
+ and integration reactions. For monomers, generates only the
+ integration reaction.
+
+ Parameters
+ ----------
+ integral_membrane_protein : Species
+ The membrane protein monomer. Must have a size attribute.
+ product : Species
+ The integrated membrane protein product.
+ complex : Species, optional
+ Pre-specified oligomeric complex. If None and size > 1,
+ automatically created.
+ component : Component
+ Component containing parameter values. Required for parameter
+ lookup.
+ part_id : str
+ Identifier for parameter lookup in the component's parameter
+ database. Required for parameter lookup.
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Reaction
+ For oligomers (size > 1): List of two reactions
+ [oligomerization, integration].
+ For monomers (size = 1): List of one reaction [integration].
+
+ Raises
+ ------
+ AttributeError
+ If component or part_id is None (required for parameter lookup).
+
+ Notes
+ -----
+ The reaction scheme depends on oligomeric state:
+
+ For oligomers (size > 1):
+
+ 1. size * monomer <--> oligomer (rates: 'kb_oligomer',
+ 'ku_oligomer')
+ 2. oligomer --> product (ProportionalHillNegative with 'kex',
+ 'kcat')
+
+ For monomers (size = 1):
+
+ 1. monomer --> product (ProportionalHillNegative with 'kex', 'kcat')
+
+ The integration reaction uses `ProportionalHillNegative` kinetics with
+ Hill coefficient n=4 to model saturation and product inhibition.
"""
# Get Parameters
@@ -164,11 +453,93 @@ def update_reactions(
class Simple_Transport(Mechanism):
- """Transport of a substrate through a membrane channel.
-
- Does not require energy and has unidirectional transport,
- following diffusion rules. Reaction schema: membrane_channel +
- substrate <-> membrane_channel + product
+ """Passive transport mechanism through membrane channel proteins.
+
+ A 'transport' mechanism that models passive, bidirectional transport of
+ substrates through membrane channel proteins. Unlike simple diffusion,
+ this mechanism requires a membrane channel protein but does not consume
+ energy. The channel acts catalytically, binding substrate and product
+ but not being consumed.
+
+ The reaction follows the schema:
+
+ membrane_channel + substrate <--> membrane_channel + product
+
+ Parameters
+ ----------
+ name : str, default='simple_membrane_protein_transport'
+ Name identifier for this mechanism instance.
+ mechanism_type : str, default='transport'
+ Type classification of this mechanism.
+
+ Attributes
+ ----------
+ name : str
+ Name of the mechanism instance.
+ mechanism_type : str
+ Type classification ('transport').
+
+ See Also
+ --------
+ Simple_Diffusion : Passive diffusion without proteins.
+ Facilitated_Transport_MM : Transport with MM kinetics.
+ Primary_Active_Transport_MM : Energy-dependent active transport.
+ Mechanism : Base class for all mechanisms.
+
+ Notes
+ -----
+ This mechanism models passive transport through channel proteins such as
+ ion channels, aquaporins, and other pore-forming proteins. The channel
+ facilitates movement down concentration gradients without conformational
+ changes or energy expenditure.
+
+ The mechanism requires the membrane channel to have the 'Passive'
+ attribute, distinguishing it from active transporters and carriers that
+ require different mechanisms.
+
+ Common examples include:
+
+ - Ion channels (K+, Na+, Ca2+ channels)
+ - Aquaporins for water transport
+ - Gap junctions between cells
+ - Porins in bacterial outer membranes
+
+ The transport is bidirectional with equal forward and reverse rate
+ constants, reflecting passive equilibration across the membrane.
+
+ Required parameters for this mechanism:
+
+ - 'k_trnsp' : Transport rate constant (same for both directions)
+
+ Examples
+ --------
+ Model potassium transport through an ion channel:
+
+ >>> protein = bcp.IntegralMembraneProtein(
+ ... membrane_protein='Knck1',
+ ... product='K_channel',
+ ... direction='Passive',
+ ... compartment='cytoplasm',
+ ... membrane_compartment='membrane',
+ ... attributes=['Passive']
+ ... )
+ >>> channel = bcp.MembraneChannel(
+ ... integral_membrane_protein=protein.membrane_protein,
+ ... substrate='K',
+ ... direction='Passive',
+ ... internal_compartment='cytoplasm',
+ ... external_compartment='external'
+ ... )
+ >>> mixture = bcp.Mixture(
+ ... components=[protein, channel],
+ ... mechanisms={
+ ... 'membrane_insertion': bcp.Membrane_Protein_Integration(),
+ ... 'transport': bcp.Simple_Transport(),
+ ... },
+ ... parameters={'k_trnsp': 1.0},
+ ... parameter_file='mechanisms/transport_parameters.tsv',
+ ... )
+ >>> mixture.compile_crn()
"""
@@ -176,18 +547,49 @@ def __init__(
self,
name='simple_membrane_protein_transport',
mechanism_type='transport',
- **keywords,
+ **kwargs,
):
Mechanism.__init__(self, name, mechanism_type)
- def update_species(
- self, membrane_channel, substrate, product, **keywords
- ):
+ def update_species(self, membrane_channel, substrate, product, **kwargs):
+ """Generate species for simple transport.
+
+ Returns the membrane channel, substrate, and product species
+ involved in the transport reaction. Validates that the channel has
+ the 'Passive' attribute.
+
+ Parameters
+ ----------
+ membrane_channel : Species
+ The membrane channel protein through which transport occurs.
+ Must have 'Passive' as its first attribute.
+ substrate : Species
+ The substrate species being transported (typically intracellular
+ side).
+ product : Species
+ The product species after transport (typically extracellular
+ side).
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Species
+ List containing [membrane_channel, substrate, product].
+
+ Raises
+ ------
+ ValueError
+ If membrane_channel does not have 'Passive' as its first
+ attribute, indicating it should use Facilitated_Transport_MM
+ instead.
+
+ """
if membrane_channel.attributes[0] != 'Passive':
raise ValueError(
"Protein is not classified as a channel with passive "
"transport of small molecules. Use mechanism "
- "Facilitated_Passive_Transport instead"
+ "Facilitated_Transport_MM instead"
)
return [membrane_channel, substrate, product]
@@ -200,8 +602,57 @@ def update_reactions(
component=None,
part_id=None,
k_trnsp=None,
- **keywords,
+ **kwargs,
):
+ """Generate reactions for simple membrane protein transport.
+
+ Creates a single reversible mass-action reaction representing
+ passive transport through a membrane channel with equal forward and
+ reverse rate constants. The channel acts catalytically and is not
+ consumed.
+
+ Parameters
+ ----------
+ membrane_channel : Species
+ The membrane channel protein facilitating transport.
+ substrate : Species
+ The substrate species being transported.
+ product : Species
+ The product species after transport.
+ component : Component, optional
+ Component containing parameter values. Required if k_trnsp is
+ not provided directly.
+ part_id : str, optional
+ Identifier for parameter lookup. If None and component is
+ provided, defaults to component.name.
+ k_trnsp : Parameter or float, optional
+ Transport rate constant. If None, retrieved from component
+ parameters. Used as both forward and reverse rate constant.
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Reaction
+ List containing a single reversible mass-action reaction for
+ transport.
+
+ Raises
+ ------
+ ValueError
+ If component is None and k_trnsp is not provided.
+
+ Notes
+ -----
+ The reaction has equal forward and reverse rate constants:
+
+ membrane_channel + substrate <--> membrane_channel + product
+ (rates: 'k_trnsp', 'k_trnsp')
+
+ The membrane channel appears on both sides of the reaction,
+ indicating it acts catalytically and is recycled.
+
+ """
# Get Parameters
if part_id is None and component is not None:
part_id = component.name
@@ -219,22 +670,107 @@ def update_reactions(
# Simple membrane protein transport
# Sub (Internal) <--> Product (External)
- SimpleTransport_rxn = Reaction.from_massaction(
+ Simple_Transport_rxn = Reaction.from_massaction(
inputs=[substrate, membrane_channel],
outputs=[product, membrane_channel],
k_forward=k_trnsp,
k_reverse=k_trnsp,
)
- return [SimpleTransport_rxn]
+ return [Simple_Transport_rxn]
class Facilitated_Transport_MM(Mechanism):
- """Michaelis-Menten transport of a substrate through a membrane carrier.
-
- Mechanism follows Michaelis-Menten Type Reactions with products
- that can bind to membrane carriers. Mechanism for the schema:
-
- Sub+MC <--> Sub:MC --> Prod:MC --> Prod + MC
+ """Facilitated diffusion mechanism with Michaelis-Menten kinetics.
+
+ A 'transport' mechanism that models facilitated diffusion of substrates
+ through membrane carrier proteins. Unlike simple channels, carriers
+ undergo conformational changes to transport substrates across membranes.
+ The mechanism follows Michaelis-Menten kinetics with explicit substrate
+ and product binding steps.
+
+ The reaction follows the schema:
+
+ Sub + MC <--> Sub:MC --> Prod:MC --> Prod + MC
+
+ where MC is the membrane carrier protein.
+
+ Parameters
+ ----------
+ name : str, default='facilitated_membrane_protein_transport'
+ Name identifier for this mechanism instance.
+ mechanism_type : str, default='transport'
+ Type classification of this mechanism.
+
+ Attributes
+ ----------
+ name : str
+ Name of the mechanism instance.
+ mechanism_type : str
+ Type classification ('transport').
+
+ See Also
+ --------
+ Simple_Transport : Passive transport through channels.
+ Primary_Active_Transport_MM : Energy-dependent active transport.
+ MichaelisMenten : Enzyme mechanism with similar kinetics.
+ Mechanism : Base class for all mechanisms.
+
+ Notes
+ -----
+ This mechanism models facilitated diffusion by carrier proteins that
+ alternate between substrate-bound and product-bound conformations. The
+ carrier binds substrate on one side of the membrane, undergoes a
+ conformational change to transport it across, releases it as product,
+ and returns to the original conformation.
+
+ Key characteristics:
+
+ - Does not require ATP or other energy sources
+ - Transport is driven by concentration gradients
+ - Carrier proteins alternate between conformational states
+ - Follows Michaelis-Menten saturation kinetics
+
+ Common examples include:
+
+ - GLUT transporters for glucose
+ - Amino acid carriers
+ - Nucleoside transporters
+ - Urea transporters
+
+ The mechanism uses a GeneralPropensity with a Heaviside function for
+ the initial binding step to enforce directionality based on
+ concentration gradients.
+
+ Required parameters for this mechanism:
+
+ - 'kb_subMC' : Forward binding rate for substrate to membrane carrier
+ - 'ku_subMC' : Unbinding rate for substrate from carrier
+ - 'k_trnspMC' : Conformational change rate (transport step)
+ - 'ku_prodMC' : Unbinding rate for product from carrier
+
+ Examples
+ --------
+ Model glucose transport through a GLUT transporter:
+
+ >>> glc_in = bcp.Species('glucose', compartment='cytoplasm')
+ >>> glc_out = bcp.Species('glucose', compartment='external')
+ >>> carrier = bcp.MembraneChannel(
+ ... integral_membrane_protein='GlucoseTransporter',
+ ... substrate=glc_out,
+ ... external_compartment='external',
+ ... internal_compartment='cytoplasm',
+ ... direction='Importer'
+ ... )
+ >>> mechanism = bcp.Facilitated_Transport_MM()
+ >>> mixture = bcp.Mixture(
+ ... components=[carrier],
+ ... mechanisms={'transport': mechanism},
+ ... parameters={
+ ... 'kb_subMC': 1.0, 'ku_subMC': 0.5,
+ ... 'k_trnspMC': 0.8, 'ku_prodMC': 0.5
+ ... }
+ ... )
+ >>> mixture.compile_crn()
"""
@@ -242,7 +778,7 @@ def __init__(
self,
name='facilitated_membrane_protein_transport',
mechanism_type='transport',
- **keywords,
+ **kwargs,
):
Mechanism.__init__(self, name, mechanism_type)
@@ -252,8 +788,46 @@ def update_species(
substrate,
product,
complex_dict=None,
- **keywords,
+ **kwargs,
):
+ """Generate species for facilitated transport.
+
+ Creates species for the membrane carrier, substrate, product, and
+ the two intermediate complexes formed during the transport cycle.
+
+ Parameters
+ ----------
+ membrane_carrier : Species
+ The membrane carrier protein that facilitates transport.
+ substrate : Species
+ The substrate species being transported (typically intracellular
+ side).
+ product : Species
+ The product species after transport (typically extracellular
+ side). Usually the same molecular species as substrate but in a
+ different compartment.
+ complex_dict : dict, optional
+ Pre-defined dictionary of complex species with keys 'sub:MC' and
+ 'prod:MC'. If None, complexes are automatically created.
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list
+ List containing [membrane_carrier, substrate, product,
+ complex_array] where complex_array is a list of two Complex
+ species: [substrate:carrier, product:carrier].
+
+ Notes
+ -----
+ The method creates two complex species representing intermediates in
+ the transport cycle:
+
+ 1. sub:MC : substrate:membrane_carrier complex
+ 2. prod:MC : product:membrane_carrier complex
+
+ """
if complex_dict is None:
# Create empty dictionary for complexes
complex_dict = {}
@@ -281,12 +855,60 @@ def update_reactions(
complex_dict=None,
component=None,
part_id=None,
- **keywords,
+ **kwargs,
):
- """Update reactions for facilitated transport mechanism.
-
- This always requires the inputs component and part_id to find
- the relevant parameters.
+ """Generate reactions for facilitated transport.
+
+ Creates four reactions representing the complete transport cycle:
+ substrate binding, substrate unbinding, conformational change
+ (transport), and product release.
+
+ Parameters
+ ----------
+ membrane_carrier : Species
+ The membrane carrier protein facilitating transport.
+ substrate : Species
+ The substrate species being transported.
+ product : Species
+ The product species after transport.
+ complex_dict : dict, optional
+ Pre-defined dictionary of complex species. If None, complexes
+ are automatically created using the same logic as in
+ update_species.
+ component : Component
+ Component containing parameter values. Required for parameter
+ lookup.
+ part_id : str
+ Identifier for parameter lookup in the component's parameter
+ database. Required for parameter lookup.
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Reaction
+ List of four reactions: [substrate_binding, substrate_unbinding,
+ transport_step, product_release].
+
+ Raises
+ ------
+ AttributeError
+ If component or part_id is None (required for parameter lookup).
+
+ Notes
+ -----
+ The reaction scheme follows this pathway:
+
+ 1. MC + Sub <--> MC:Sub (GeneralPropensity with Heaviside function
+ using 'kb_subMC')
+ 2. MC:Sub --> MC + Sub (irreversible, rate: 'ku_subMC')
+ 3. MC:Sub --> MC:Prod (conformational change, rate: 'k_trnspMC')
+ 4. MC:Prod --> MC + Prod (irreversible, rate: 'ku_prodMC')
+
+ The initial binding step uses a GeneralPropensity with a Heaviside
+ function to enforce concentration gradient-driven directionality.
+ The Heaviside function ensures transport only occurs when substrate
+ concentration exceeds product concentration.
"""
# Get Parameters
@@ -358,13 +980,103 @@ def update_reactions(
class Primary_Active_Transport_MM(Mechanism):
- """Transport of a substrate through a membrane carrier.
-
- Mechanism follows Michaelis-Menten Type Reactions with products
- that can bind to membrane carriers. Mechanism for the schema:
-
- Sub+MP <--> Sub:MP + E --> Sub:MP:E --> MP:Prod:E
- --> Prod + MP:W --> Prod + MP+ W
+ """Primary active transport mechanism with ATP-dependent pumping.
+
+ A 'transport' mechanism that models primary active transport where
+ substrates are moved against their concentration gradients using energy
+ from ATP hydrolysis. The mechanism follows Michaelis-Menten kinetics
+ with explicit binding, ATP hydrolysis, conformational change, and
+ product release steps.
+
+ The reaction follows the schema:
+
+ Sub + MP <--> Sub:MP + E --> Sub:MP:E --> MP:Prod:E
+ --> Prod + MP:W --> Prod + MP + W
+
+ where MP is the membrane pump, E is ATP (energy), and W is ADP (waste).
+
+ Parameters
+ ----------
+ name : str, default='active_membrane_protein_transport'
+ Name identifier for this mechanism instance.
+ mechanism_type : str, default='transport'
+ Type classification of this mechanism.
+
+ Attributes
+ ----------
+ name : str
+ Name of the mechanism instance.
+ mechanism_type : str
+ Type classification ('transport').
+
+ See Also
+ --------
+ Facilitated_Transport_MM : Passive facilitated diffusion.
+ Simple_Transport : Passive channel transport.
+ Mechanism : Base class for all mechanisms.
+
+ Notes
+ -----
+ This mechanism models primary active transporters such as P-type ATPases
+ (e.g., Na+/K+-ATPase, Ca2+-ATPase), ABC transporters, and other pumps
+ that directly couple ATP hydrolysis to substrate transport. The pump
+ undergoes conformational changes driven by ATP binding and hydrolysis to
+ move substrates against concentration gradients.
+
+ Key characteristics:
+
+ - Requires ATP or other energy source
+ - Can transport substrates against concentration gradients
+ - Undergoes ATP-dependent conformational changes
+ - Follows Michaelis-Menten saturation kinetics
+
+ Common examples include:
+
+ - Na+/K+-ATPase (maintains ion gradients in animal cells)
+ - Ca2+-ATPase (SERCA pump in muscle cells)
+ - H+-ATPases (proton pumps in various organisms)
+ - ABC transporters (drug efflux pumps)
+
+ The mechanism requires the membrane pump to have an ATP attribute
+ (membrane_pump.ATP) that specifies the number of ATP molecules required
+ per transport cycle.
+
+ The binding steps use GeneralPropensity with Heaviside functions to
+ ensure proper directionality based on species concentrations.
+
+ Required parameters for this mechanism:
+
+ - 'kb_subMP' : Forward binding rate for substrate to membrane pump
+ - 'ku_subMP' : Unbinding rate for substrate from pump
+ - 'kb_subMPnATP' : Forward binding rate for ATP to substrate:pump
+ complex
+ - 'ku_subMPnATP' : Unbinding rate for ATP from substrate:pump complex
+ - 'k_trnspMP' : Conformational change rate (transport step)
+ - 'ku_prodMP' : Unbinding rate for product from pump
+ - 'ku_MP' : Unbinding rate for ADP from pump
+
+ Examples
+ --------
+ Model active sodium transport by Na+/K+-ATPase:
+
+ >>> pump = bcp.MembranePump(
+ ... membrane_pump='NaK_ATPase',
+ ... substrate='Na',
+ ... direction='Exporter',
+ ... ATP=1
+ ... )
+ >>> mechanism = bcp.Primary_Active_Transport_MM()
+ >>> mixture = bcp.Mixture(
+ ... components=[pump],
+ ... mechanisms={'transport': mechanism},
+ ... parameters={
+ ... 'kb_subMP': 1.0, 'ku_subMP': 0.1,
+ ... 'kb_subMPnATP': 1.0, 'ku_subMPnATP': 0.1,
+ ... 'k_trnspMP': 0.5, 'ku_prodMP': 1.0,
+ ... 'ku_MP': 1.0
+ ... }
+ ... )
+ >>> mixture.compile_crn()
"""
@@ -372,7 +1084,7 @@ def __init__(
self,
name='active_membrane_protein_transport',
mechanism_type='transport',
- **keywords,
+ **kwargs,
):
Mechanism.__init__(self, name, mechanism_type)
@@ -384,8 +1096,59 @@ def update_species(
energy,
waste,
complex_dict=None,
- **keywords,
+ **kwargs,
):
+ """Generate species for primary active transport.
+
+ Creates species for the membrane pump, substrate, product, ATP/ADP
+ energy species, and all intermediate complexes formed during the
+ ATP-driven transport cycle.
+
+ Parameters
+ ----------
+ membrane_pump : Species
+ The membrane pump protein that transports substrates using ATP.
+ Must have an ATP attribute specifying the number of ATP
+ molecules required per transport cycle.
+ substrate : Species
+ The substrate species being transported (typically intracellular
+ side).
+ product : Species
+ The product species after transport (typically extracellular
+ side). Usually the same molecular species as substrate but in a
+ different compartment.
+ energy : Species
+ ATP species used to drive active transport.
+ waste : Species
+ ADP species produced after ATP hydrolysis.
+ complex_dict : dict, optional
+ Pre-defined dictionary of complex species with keys 'Pump:Sub',
+ 'Pump:Sub:ATP', 'Pump:Prod:ATP', and 'Pump:ADP'. If None,
+ complexes are automatically created.
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list
+ List containing [membrane_pump, substrate, product, energy,
+ waste, complex_array] where complex_array is a list of four
+ Complex species generated.
+
+ Notes
+ -----
+ The method creates four complex species representing intermediates
+ in the active transport cycle:
+
+ 1. Pump:Sub : membrane_pump:substrate complex
+ 2. Pump:Sub:ATP : membrane_pump:substrate:nATP complex
+ 3. Pump:Prod:ATP : membrane_pump:product:nATP complex
+ 4. Pump:ADP : membrane_pump:nADP complex
+
+ The number of ATP/ADP molecules (nATP) is determined by the
+ membrane_pump.ATP attribute.
+
+ """
nATP = membrane_pump.ATP
if complex_dict is None:
@@ -434,12 +1197,73 @@ def update_reactions(
complex_dict=None,
component=None,
part_id=None,
- **keywords,
+ **kwargs,
):
- """Update reactions for primary active transport mechanism.
-
- This always requires the inputs component and part_id to find
- the relevant parameters.
+ """Generate reactions for primary active transport.
+
+ Creates seven reactions representing the complete ATP-driven
+ transport cycle: substrate binding/unbinding, ATP
+ binding/unbinding, conformational change (transport), product
+ release, and ADP release.
+
+ Parameters
+ ----------
+ membrane_pump : Species
+ The membrane pump protein. Must have an ATP attribute.
+ substrate : Species
+ The substrate species being transported.
+ product : Species
+ The product species after transport.
+ energy : Species
+ ATP species used for active transport.
+ waste : Species
+ ADP species produced after ATP hydrolysis.
+ complex_dict : dict, optional
+ Pre-defined dictionary of complex species. If None, complexes
+ are automatically created using the same logic as in
+ update_species.
+ component : Component
+ Component containing parameter values. Required for parameter
+ lookup.
+ part_id : str
+ Identifier for parameter lookup in the component's parameter
+ database. Required for parameter lookup.
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Reaction
+ List of seven reactions: [substrate_binding,
+ substrate_unbinding, ATP_binding, ATP_unbinding, transport_step,
+ product_release, ADP_release].
+
+ Raises
+ ------
+ AttributeError
+ If `component` or `part_id` is None (required for parameter
+ lookup).
+
+ Notes
+ -----
+ The reaction scheme follows this pathway:
+
+ 1. MP + Sub <--> MP:Sub (`GeneralPropensity` with Heaviside using
+ 'kb_subMP', reverse rate: 'ku_subMP')
+ 2. MP:Sub + nATP <--> MP:Sub:nATP (`GeneralPropensity` with Heaviside
+ using 'kb_subMPnATP', reverse rate: 'ku_subMPnATP')
+ 3. MP:Sub:nATP --> MP:Prod:nATP (conformational change, rate:
+ 'k_trnspMP')
+ 4. MP:Prod:nATP --> MP:nADP + Prod (product release, rate:
+ 'ku_prodMP')
+ 5. MP:nADP --> MP + nADP (ADP release, rate: 'ku_MP')
+
+ The binding steps use `GeneralPropensity` with Heaviside functions to
+ enforce proper directionality. The Heaviside functions ensure that
+ reactions only proceed when the required species are present.
+
+ The number of ATP/ADP molecules (nATP) is determined by
+ membrane_pump.ATP attribute.
"""
# Get Parameters
diff --git a/biocrnpyler/mechanisms/txtl.py b/biocrnpyler/mechanisms/txtl.py
index a0711526..c40b38f9 100644
--- a/biocrnpyler/mechanisms/txtl.py
+++ b/biocrnpyler/mechanisms/txtl.py
@@ -12,23 +12,113 @@
class OneStepGeneExpression(Mechanism):
- """Gene expression without transcription or translation.
+ """Single-step gene expression mechanism without explicit TX-TL steps.
+
+ A 'transcription' mechanism that models gene expression as a single
+ direct reaction from DNA to protein, without explicitly modeling
+ transcription and translation as separate steps. This simplified
+ mechanism is useful for models where the intermediate mRNA dynamics are
+ not important.
+
+ The reaction follows the schema:
G --> G + P
+
+ where G is the gene (DNA) and P is the protein product.
+
+ Parameters
+ ----------
+ name : str, default='gene_expression'
+ Name identifier for this mechanism instance.
+ mechanism_type : str, default='transcription'
+ Type classification of this mechanism.
+
+ Attributes
+ ----------
+ name : str
+ Name of the mechanism instance.
+ mechanism_type : str
+ Type classification ('transcription').
+
+ See Also
+ --------
+ SimpleTranscription : Explicit transcription mechanism.
+ SimpleTranslation : Explicit translation mechanism.
+ Mechanism : Base class for all mechanisms.
+
+ Notes
+ -----
+ This mechanism is appropriate for modeling scenarios where:
+
+ - mRNA dynamics are much faster than protein dynamics
+ - The model focuses on protein-level behavior
+ - Computational efficiency is prioritized over mechanistic detail
+
+ The single-step abstraction combines transcription and translation into
+ a single effective rate constant. This is often used in coarse-grained
+ models of gene regulatory networks.
+
+ Common applications include:
+
+ - Simplified gene regulatory network models
+ - Steady-state or quasi-steady-state analyses
+ - Systems where mRNA lifetime is negligible
+ - High-level circuit design and prototyping
+
+ Required parameters for this mechanism:
+
+ - 'kexpress' : Combined expression rate constant
+
+ Examples
+ --------
+ Model simple gene expression in a minimal system:
+
+ >>> gene = bcp.DNAassembly(
+ ... name='gfp', promoter='pconst', protein='GFP',
+ ... )
+ >>> mechanism = bcp.OneStepGeneExpression()
+ >>> mixture = bcp.Mixture(
+ ... components=[gene],
+ ... mechanisms={
+ ... 'transcription': mechanism,
+ ... },
+ ... parameters={'kexpress': 0.1}
+ ... )
+ >>> mixture.compile_crn()
+
"""
def __init__(
self, name='gene_expression', mechanism_type='transcription'
):
- """Initializes a OneStepGeneExpression instance.
+ Mechanism.__init__(self, name=name, mechanism_type=mechanism_type)
- :param name: name of the Mechanism, default: gene_expression
- :param mechanism_type: type of the Mechanism, default: transcription
+ def update_species(self, dna, transcript=None, protein=None, **kwargs):
+ """Generate species for one-step gene expression.
+
+ Returns the DNA and protein species involved in the expression
+ reaction.
+
+ Parameters
+ ----------
+ dna : Species
+ The DNA species (gene) that expresses the protein.
+ transcript : Species, optional
+ Transcript species (unused in this mechanism, accepted for API
+ consistency).
+ protein : Species
+ The protein species produced by expression. If None, no species
+ are returned.
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Species
+ List containing [dna, protein] if protein is not None, otherwise
+ [dna].
"""
- Mechanism.__init__(self, name=name, mechanism_type=mechanism_type)
-
- def update_species(self, dna, protein, transcript=None, **keywords):
species = [dna]
if protein is not None:
species += [protein]
@@ -43,8 +133,55 @@ def update_reactions(
protein=None,
transcript=None,
part_id=None,
- **keywords,
+ **kwargs,
):
+ """Generate reactions for one-step gene expression.
+
+ Creates a single mass-action reaction representing direct gene
+ expression from DNA to protein without intermediate transcript.
+
+ Parameters
+ ----------
+ dna : Species
+ The DNA species (gene) that expresses the protein.
+ component : Component, optional
+ Component containing parameter values. Required if kexpress is
+ not provided directly.
+ kexpress : Parameter or float, optional
+ Expression rate constant. If None, retrieved from component
+ parameters.
+ protein : Species, optional
+ The protein species produced. If None, no reactions are
+ generated.
+ transcript : Species, optional
+ Transcript species (unused in this mechanism, accepted for API
+ consistency).
+ part_id : str, optional
+ Identifier for parameter lookup in the component's parameter
+ database.
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Reaction
+ List containing a single mass-action reaction for gene
+ expression if protein is not None, otherwise empty list.
+
+ Raises
+ ------
+ ValueError
+ If component is None and kexpress is not provided.
+
+ Notes
+ -----
+ The reaction has the form:
+
+ DNA --> DNA + Protein (rate: 'kexpress')
+
+ The DNA is catalytic and appears on both sides of the reaction.
+
+ """
if kexpress is None and component is not None:
kexpress = component.get_parameter(
'kexpress', part_id=part_id, mechanism=self
@@ -63,23 +200,114 @@ def update_reactions(
class SimpleTranscription(Mechanism):
- """A Mechanism to model simple catalytic transcription.
+ """Simple catalytic transcription mechanism.
+
+ A 'transcription' mechanism that models transcription as a single
+ catalytic reaction where DNA directly produces mRNA without explicitly
+ modeling RNA polymerase binding and unbinding. This simplified mechanism
+ is appropriate when polymerase dynamics are fast compared to other
+ processes.
+
+ The reaction follows the schema:
G --> G + T
+
+ where G is the gene (DNA) and T is the transcript (mRNA).
+
+ Parameters
+ ----------
+ name : str, default='simple_transcription'
+ Name identifier for this mechanism instance.
+ mechanism_type : str, default='transcription'
+ Type classification of this mechanism.
+
+ Attributes
+ ----------
+ name : str
+ Name of the mechanism instance.
+ mechanism_type : str
+ Type classification ('transcription').
+
+ See Also
+ --------
+ Transcription_MM : Explicit Michaelis-Menten transcription.
+ OneStepGeneExpression : Combined transcription-translation.
+ Mechanism : Base class for all mechanisms.
+
+ Notes
+ -----
+ This mechanism is appropriate for modeling scenarios where:
+
+ - RNA polymerase dynamics are fast and at quasi-steady-state
+ - The focus is on transcript-level regulation
+ - Computational efficiency is prioritized
+
+ The catalytic abstraction treats the DNA as a template that is not
+ consumed in the reaction. In mixtures without explicit transcription
+ (e.g., expression mixtures), this mechanism can combine with translation
+ rates to produce protein directly.
+
+ Common applications include:
+
+ - Basic transcription models
+ - Models where RNAP is not limiting
+ - Constitutive or weakly regulated promoters
+
+ Required parameters for this mechanism:
+
+ - 'ktx' : Transcription rate constant
+ - 'ktl' : Translation rate constant (when used in expression mixtures
+ without explicit transcripts)
+
+ Examples
+ --------
+ Model constitutive transcription:
+
+ >>> gene = bcp.DNAassembly(
+ ... name='constitutive_GFP',
+ ... promoter='pconst', protein='GFP')
+ >>> tx_mechanism = bcp.SimpleTranscription()
+ >>> mixture = bcp.Mixture(
+ ... components=[gene],
+ ... mechanisms={
+ ... 'transcription': tx_mechanism,
+ ... },
+ ... parameters={'ktx': 0.05}
+ ... )
+ >>> mixture.compile_crn()
+
"""
def __init__(
self, name='simple_transcription', mechanism_type='transcription'
):
- """Initializes a SimpleTranscription instance.
+ Mechanism.__init__(self, name=name, mechanism_type=mechanism_type)
- :param name: name of the Mechanism, default: simple_transcription
- :param mechanism_type: type of the Mechanism, default: transcription
+ def update_species(self, dna, transcript=None, protein=None, **kwargs):
+ """Generate species for simple transcription.
+
+ Returns the DNA and transcript species (and optionally protein)
+ involved in the transcription reaction.
+
+ Parameters
+ ----------
+ dna : Species
+ The DNA species (gene) that produces the transcript.
+ transcript : Species, optional
+ The mRNA transcript species produced. If None, only DNA is
+ returned.
+ protein : Species, optional
+ Protein species (included for API consistency with expression
+ mixtures).
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Species
+ List containing DNA and any non-None transcript/protein species.
"""
- Mechanism.__init__(self, name=name, mechanism_type=mechanism_type)
-
- def update_species(self, dna, transcript=None, protein=None, **keywords):
species = [dna]
if transcript is not None:
species += [transcript]
@@ -96,8 +324,58 @@ def update_reactions(
part_id=None,
transcript=None,
protein=None,
- **keywords,
+ **kwargs,
):
+ """Generate reactions for simple transcription.
+
+ Creates a single mass-action reaction representing transcription from
+ DNA to mRNA. In expression mixtures without explicit transcription,
+ combines transcription and translation rates to produce protein
+ directly.
+
+ Parameters
+ ----------
+ dna : Species
+ The DNA species (gene) that produces the transcript.
+ component : Component, optional
+ Component containing parameter values. Required if ktx is not
+ provided directly.
+ ktx : Parameter or float, optional
+ Transcription rate constant. If None, retrieved from component
+ parameters.
+ part_id : str, optional
+ Identifier for parameter lookup in the component's parameter
+ database.
+ transcript : Species, optional
+ The mRNA transcript species produced. If None and protein is
+ not None, produces protein directly.
+ protein : Species, optional
+ Protein species for expression mixtures without explicit
+ transcription.
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Reaction
+ List containing a single mass-action reaction for transcription.
+
+ Raises
+ ------
+ ValueError
+ If component is None and ktx is not provided.
+
+ Notes
+ -----
+ The reaction has two possible forms:
+
+ 1. With transcript: DNA --> DNA + mRNA (rate: 'ktx')
+ 2. Without transcript: DNA --> DNA + Protein (rate: 'ktx' * 'ktl')
+
+ The second form is used in expression mixtures that skip explicit
+ transcript modeling.
+
+ """
if ktx is None and component is not None:
ktx = component.get_parameter(
'ktx', part_id=part_id, mechanism=self
@@ -127,23 +405,111 @@ def update_reactions(
class SimpleTranslation(Mechanism):
- """A mechanism to model simple catalytic translation.
+ """Simple catalytic translation mechanism.
+
+ A 'translation' mechanism that models translation as a single catalytic
+ reaction where mRNA directly produces protein without explicitly modeling
+ ribosome binding and unbinding. This simplified mechanism is appropriate
+ when ribosome dynamics are fast compared to other processes.
+
+ The reaction follows the schema:
T --> T + P
+
+ where T is the transcript (mRNA) and P is the protein.
+
+ Parameters
+ ----------
+ name : str, default='simple_translation'
+ Name identifier for this mechanism instance.
+ mechanism_type : str, default='translation'
+ Type classification of this mechanism.
+
+ Attributes
+ ----------
+ name : str
+ Name of the mechanism instance.
+ mechanism_type : str
+ Type classification ('translation').
+
+ See Also
+ --------
+ Translation_MM : Explicit Michaelis-Menten translation.
+ SimpleTranscription : Simple transcription mechanism.
+ Mechanism : Base class for all mechanisms.
+
+ Notes
+ -----
+ This mechanism is appropriate for modeling scenarios where:
+
+ - Ribosome dynamics are fast and at quasi-steady-state
+ - The focus is on protein-level regulation
+ - Computational efficiency is prioritized
+
+ The catalytic abstraction treats the mRNA as a template that is not
+ consumed in the reaction. The mRNA persists and can produce multiple
+ protein copies.
+
+ Common applications include:
+
+ - Basic translation models
+ - Models where ribosomes are not limiting
+ - Constitutive protein expression
+
+ Required parameters for this mechanism:
+
+ - 'ktl' : Translation rate constant
+
+ Examples
+ --------
+ Model simple translation:
+
+ >>> gene = bcp.DNAassembly(
+ ... name='dna_assembly',
+ ... promoter='pconst', rbs='RBS_medium', protein='GFP')
+ >>> tl_mechanism = bcp.SimpleTranslation()
+ >>> mixture = bcp.Mixture(
+ ... components=[gene],
+ ... mechanisms={
+ ... 'transcription': bcp.SimpleTranscription(),
+ ... 'translation': tl_mechanism
+ ... },
+ ... parameter_file='mixtures/pure_parameters.tsv'
+ ... )
+ >>> mixture.compile_crn()
+
"""
def __init__(
self, name='simple_translation', mechanism_type='translation'
):
- """Initializes a SimpleTranslation instance.
+ Mechanism.__init__(self, name=name, mechanism_type=mechanism_type)
- :param name: name of the Mechanism, default: simple_translation
- :param mechanism_type: type of the Mechanism, default: translation
+ def update_species(self, transcript, protein=None, **kwargs):
+ """Generate species for simple translation.
+
+ Returns the transcript and protein species involved in the
+ translation reaction. If protein is None, creates a default protein
+ species based on the transcript name.
+
+ Parameters
+ ----------
+ transcript : Species
+ The mRNA transcript species that produces protein.
+ protein : Species or list of Species, optional
+ The protein species produced. If None, a default protein with
+ the same name as the transcript is created. Can be a single
+ Species or a list.
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Species
+ List containing [transcript] plus protein species (single or
+ list).
"""
- Mechanism.__init__(self, name=name, mechanism_type=mechanism_type)
-
- def update_species(self, transcript, protein=None, **keywords):
if protein is None:
protein = Species(transcript.name, material_type='protein')
outlst = [transcript]
@@ -160,8 +526,53 @@ def update_reactions(
ktl=None,
part_id=None,
protein=None,
- **keywords,
+ **kwargs,
):
+ """Generate reactions for simple translation.
+
+ Creates a single mass-action reaction representing translation from
+ mRNA to protein.
+
+ Parameters
+ ----------
+ transcript : Species
+ The mRNA transcript species that produces protein.
+ component : Component, optional
+ Component containing parameter values. Required if ktl is not
+ provided directly.
+ ktl : Parameter or float, optional
+ Translation rate constant. If None, retrieved from component
+ parameters.
+ part_id : str, optional
+ Identifier for parameter lookup in the component's parameter
+ database.
+ protein : Species, optional
+ The protein species produced.
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Reaction
+ List containing a single mass-action reaction for translation if
+ transcript is not None, otherwise empty list.
+
+ Raises
+ ------
+ ValueError
+ If component is None and ktl is not provided.
+
+ Notes
+ -----
+ The reaction has the form:
+
+ mRNA --> mRNA + Protein (rate: 'ktl')
+
+ The mRNA is catalytic and appears on both sides of the reaction. If
+ transcript is None (only occurs in mixtures without transcription),
+ no reactions are generated.
+
+ """
if ktl is None and component is not None:
ktl = component.get_parameter(
'ktl', part_id=part_id, mechanism=self
@@ -186,14 +597,96 @@ def update_reactions(
class PositiveHillTranscription(Mechanism):
- """Model transcription as a proprotional positive Hill function.
+ """Transcription regulated by positive Hill function (activation).
- G --> G + P
+ A 'transcription' mechanism that models transcriptional activation using
+ a proportional positive Hill function. The transcription rate increases
+ with regulator (activator) concentration according to Hill kinetics,
+ capturing cooperative binding and activation.
+
+ The reaction follows the schema:
+
+ G --> G + T
+
+ with rate:
+
+ rate = k * G * (R^n) / (K + R^n)
+
+ where R is the regulator (activator), n is the Hill coefficient, and K
+ is the activation constant. Optionally includes a basal leak reaction at
+ rate 'kleak'.
+
+ Parameters
+ ----------
+ name : str, default='positivehill_transcription'
+ Name identifier for this mechanism instance.
+ mechanism_type : str, default='transcription'
+ Type classification of this mechanism.
+
+ Attributes
+ ----------
+ name : str
+ Name of the mechanism instance.
+ mechanism_type : str
+ Type classification ('transcription').
+
+ See Also
+ --------
+ NegativeHillTranscription : Transcriptional repression with Hill
+ function.
+ SimpleTranscription : Simple transcription without regulation.
+ Mechanism : Base class for all mechanisms.
+
+ Notes
+ -----
+ This mechanism models transcriptional activation by regulatory proteins
+ such as transcription factors. The Hill function captures the sigmoidal
+ response typical of cooperative binding, where multiple regulator
+ molecules bind to the promoter to activate transcription.
+
+ Key features:
+
+ - Sigmoidal activation response
+ - Cooperative binding through Hill coefficient
+ - Optional basal transcription (leak)
+ - Saturation at high regulator concentrations
+
+ Common applications include:
+
+ - Activatable promoters
+ - Positive feedback loops
+ - Transcriptional cascades
+ - Genetic switches and toggles
+
+ Required parameters for this mechanism:
+
+ - 'k' : Maximum transcription rate constant
+ - 'K' : Activation constant (regulator concentration for half-maximal
+ activation)
+ - 'n' : Hill coefficient (cooperativity)
+ - 'kleak' : Basal transcription rate (optional, for leak reaction)
+
+ Examples
+ --------
+ Model transcriptional activation by an inducer:
+
+ >>> LacI = bcp.Protein('AraC')
+ >>> plac = bcp.ActivatablePromoter('pBAD', LacI)
+ >>> gene = bcp.DNAassembly(
+ ... name='activiated_GFP',
+ ... promoter=plac, rbs='RBS_medium', protein='GFP')
+ >>> tx_mechanism = bcp.PositiveHillTranscription()
+ >>> mixture = bcp.Mixture(
+ ... components=[LacI, gene],
+ ... mechanisms={
+ ... 'transcription': tx_mechanism,
+ ... 'translation': bcp.SimpleTranslation(),
+ ... },
+ ... parameters={'k': 1.0, 'K': 10.0, 'n': 2, 'kleak': 0.01},
+ ... parameter_file='mixtures/pure_parameters.tsv',
+ ... )
+ >>> mixture.compile_crn()
- rate = k*G*(R^n)/(K+R^n)
- where R is a regulator (activator).
- Optionally includes a leak reaction
- G --> G + P @ rate kleak.
"""
def __init__(
@@ -201,13 +694,6 @@ def __init__(
name='positivehill_transcription',
mechanism_type='transcription',
):
- """Initializes a PositiveHillTranscription instance.
-
- :param name: name of the Mechanism, default:
- positivehill_transcription
- :param mechanism_type: type of the Mechanism, default: transcription
-
- """
Mechanism.__init__(self, name=name, mechanism_type=mechanism_type)
def update_species(
@@ -217,8 +703,37 @@ def update_species(
transcript=None,
leak=False,
protein=None,
- **keywords,
+ **kwargs,
):
+ """Generate species for positive Hill transcription.
+
+ Returns all species involved in the regulated transcription
+ reaction.
+
+ Parameters
+ ----------
+ dna : Species
+ The DNA species (promoter) being transcribed.
+ regulator : Species
+ The activator species that regulates transcription.
+ transcript : Species, optional
+ The mRNA transcript species produced. If None and protein is
+ provided, protein is produced directly.
+ leak : bool, default=False
+ If True, includes a leak reaction for basal transcription.
+ protein : Species, optional
+ Protein species for expression mixtures without explicit
+ transcription.
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Species
+ List containing [dna, regulator] plus any non-None
+ transcript/protein species.
+
+ """
species = [dna, regulator]
if transcript is not None:
species += [transcript]
@@ -236,22 +751,59 @@ def update_reactions(
transcript=None,
leak=False,
protein=None,
- **keywords,
+ **kwargs,
):
- """Update reactions for positive Hill transcription.
-
- This always requires the inputs component and part_id to find
- the relevant parameters.
-
- :param dna:
- :param regulator:
- :param component:
- :param part_id:
- :param transcript:
- :param leak:
- :param protein:
- :param keywords:
- :return:
+ """Generate reactions for positive Hill transcription.
+
+ Creates regulated transcription reaction(s) using a proportional
+ positive Hill function, with optional basal leak reaction.
+
+ Parameters
+ ----------
+ dna : Species
+ The DNA species (promoter) being transcribed.
+ regulator : Species
+ The activator species that regulates transcription.
+ component : Component
+ Component containing parameter values. Required for parameter
+ lookup.
+ part_id : str
+ Identifier for parameter lookup in the component's parameter
+ database. Required for parameter lookup.
+ transcript : Species, optional
+ The mRNA transcript species produced. If None and protein is
+ provided, protein is produced directly.
+ leak : bool, default=False
+ If True, includes a basal leak reaction.
+ protein : Species, optional
+ Protein species for expression mixtures.
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Reaction
+ List containing one or two reactions:
+
+ - Regulated transcription with ProportionalHillPositive
+ propensity
+ - Optional leak reaction (if leak=True)
+
+ Raises
+ ------
+ AttributeError
+ If component or part_id is None (required for parameter lookup).
+
+ Notes
+ -----
+ The regulated reaction uses ProportionalHillPositive propensity:
+
+ DNA --> DNA + Product (rate: k * G * R^n / (K + R^n))
+
+ Where Product is either transcript or protein depending on mixture
+ type. If leak=True, adds:
+
+ DNA --> DNA + Product (rate: 'kleak')
"""
ktx = component.get_parameter('k', part_id=part_id, mechanism=self)
@@ -294,14 +846,96 @@ def update_reactions(
class NegativeHillTranscription(Mechanism):
- """Model transcription as a proportional negative Hill function.
+ """Transcription regulated by negative Hill function (repression).
- G --> G + P
+ A 'transcription' mechanism that models transcriptional repression using
+ a proportional negative Hill function. The transcription rate decreases
+ with regulator (repressor) concentration according to Hill kinetics,
+ capturing cooperative binding and repression.
+
+ The reaction follows the schema:
+
+ G --> G + T
+
+ with rate:
+
+ rate = k * G * 1 / (K + R^n)
+
+ where R is the regulator (repressor), n is the Hill coefficient, and K
+ is the repression constant. Optionally includes a basal leak reaction at
+ rate 'kleak'.
+
+ Parameters
+ ----------
+ name : str, default='negativehill_transcription'
+ Name identifier for this mechanism instance.
+ mechanism_type : str, default='transcription'
+ Type classification of this mechanism.
+
+ Attributes
+ ----------
+ name : str
+ Name of the mechanism instance.
+ mechanism_type : str
+ Type classification ('transcription').
+
+ See Also
+ --------
+ PositiveHillTranscription : Transcriptional activation with Hill
+ function.
+ SimpleTranscription : Simple transcription without regulation.
+ Mechanism : Base class for all mechanisms.
+
+ Notes
+ -----
+ This mechanism models transcriptional repression by regulatory proteins
+ such as repressor proteins. The Hill function captures the response
+ where increasing repressor concentration decreases transcription rate,
+ with cooperativity determined by the Hill coefficient.
+
+ Key features:
+
+ - Decreasing transcription with increasing repressor
+ - Cooperative repressor binding through Hill coefficient
+ - Optional basal transcription (leak)
+ - Saturation at high repressor concentrations
+
+ Common applications include:
+
+ - Repressible promoters
+ - Negative feedback loops
+ - Transcriptional repression cascades
+ - Genetic switches and oscillators
+
+ Required parameters for this mechanism:
+
+ - 'k' : Maximum transcription rate constant (when repressor is absent)
+ - 'K' : Repression constant (repressor concentration for half-maximal
+ repression)
+ - 'n' : Hill coefficient (cooperativity)
+ - 'kleak' : Basal transcription rate (optional, for leak reaction)
+
+ Examples
+ --------
+ Model transcriptional repression:
+
+ >>> LacI = bcp.Protein('LacI')
+ >>> plac = bcp.RepressiblePromoter('plac', LacI)
+ >>> gene = bcp.DNAassembly(
+ ... name='repressed_GFP',
+ ... promoter=plac, rbs='RBS_medium', protein='GFP')
+ >>> tx_mechanism = bcp.NegativeHillTranscription()
+ >>> mixture = bcp.Mixture(
+ ... components=[LacI, gene],
+ ... mechanisms={
+ ... 'transcription': tx_mechanism,
+ ... 'translation': bcp.SimpleTranslation(),
+ ... },
+ ... parameters={'k': 1.0, 'K': 10.0, 'n': 2, 'kleak': 0.01},
+ ... parameter_file='mixtures/pure_parameters.tsv',
+ ... )
+ >>> mixture.compile_crn()
- rate = k*G*(1)/(K+R^n)
- where R is a regulator (repressor).
- Optionally includes a leak reaction
- G --> G + P @ rate kleak.
"""
def __init__(
@@ -309,13 +943,6 @@ def __init__(
name='negativehill_transcription',
mechanism_type='transcription',
):
- """Initializes a NegativeHillTranscription instance.
-
- :param name: name of the Mechanism, default:
- negativehill_transcription
- :param mechanism_type: type of the Mechanism, default: transcription
-
- """
Mechanism.__init__(self, name=name, mechanism_type=mechanism_type)
def update_species(
@@ -325,8 +952,35 @@ def update_species(
transcript=None,
leak=False,
protein=None,
- **keywords,
+ **kwargs,
):
+ """Generate species for negative Hill transcription.
+
+ Returns all species involved in the regulated transcription
+ reaction.
+
+ Parameters
+ ----------
+ dna : Species
+ The DNA species (promoter) being transcribed.
+ regulator : Species
+ The repressor species that regulates transcription.
+ transcript : Species, optional
+ The mRNA transcript species produced.
+ leak : bool, default=False
+ If True, includes a leak reaction for basal transcription.
+ protein : Species, optional
+ Protein species for expression mixtures.
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Species
+ List containing [dna, regulator] plus any non-None
+ transcript/protein species.
+
+ """
species = [dna, regulator]
if transcript is not None:
species += [transcript]
@@ -344,22 +998,46 @@ def update_reactions(
transcript=None,
leak=False,
protein=None,
- **keywords,
+ **kwargs,
):
- """Update reactions for negative Hill transcription.
-
- This always requires the inputs component and part_id to find
- the relevant parameters.
-
- :param dna:
- :param regulator:
- :param component:
- :param part_id:
- :param transcript:
- :param leak:
- :param protein:
- :param keywords:
- :return:
+ """Generate reactions for negative Hill transcription.
+
+ Creates regulated transcription reaction(s) using a proportional
+ negative Hill function, with optional basal leak reaction.
+
+ Parameters
+ ----------
+ dna : Species
+ The DNA species (promoter) being transcribed.
+ regulator : Species
+ The repressor species that regulates transcription.
+ component : Component
+ Component containing parameter values. Required.
+ part_id : str
+ Identifier for parameter lookup. Required.
+ transcript : Species, optional
+ The mRNA transcript species produced.
+ leak : bool, default=False
+ If True, includes a basal leak reaction.
+ protein : Species, optional
+ Protein species for expression mixtures.
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Reaction
+ List containing one or two reactions: regulated transcription
+ with ProportionalHillNegative propensity, and optional leak
+ reaction.
+
+ Notes
+ -----
+ The regulated reaction uses ProportionalHillNegative propensity:
+
+ DNA --> DNA + Product (rate: k * G / (K + R^n))
+
+ If leak=True, adds: DNA --> DNA + Product (rate: 'kleak')
"""
ktx = component.get_parameter('k', part_id=part_id, mechanism=self)
@@ -402,17 +1080,88 @@ def update_reactions(
class Transcription_MM(MichaelisMentenCopy):
- """Michaelis Menten Transcription.
+ """Michaelis-Menten transcription with explicit RNA polymerase.
+
+ A 'transcription' mechanism that explicitly models RNA polymerase (RNAP)
+ binding to DNA, followed by transcription and release. This mechanism
+ follows Michaelis-Menten kinetics and is appropriate when RNAP is
+ limiting or when explicit modeling of polymerase-DNA interactions is
+ needed.
+
+ The reaction follows the schema:
+
+ G + RNAP <--> G:RNAP --> G + RNAP + mRNA
+
+ Parameters
+ ----------
+ rnap : Species
+ RNA polymerase species that catalyzes transcription.
+ name : str, default='transcription_mm'
+ Name identifier for this mechanism instance.
+
+ Attributes
+ ----------
+ rnap : Species
+ The RNA polymerase species.
+ name : str
+ Name of the mechanism instance.
+ mechanism_type : str
+ Type classification ('transcription').
+
+ See Also
+ --------
+ SimpleTranscription : Simple transcription without explicit RNAP.
+ Translation_MM : Michaelis-Menten translation.
+ MichaelisMentenCopy : Base class for MM copy mechanisms.
+ Mechanism : Base class for all mechanisms.
+
+ Notes
+ -----
+ This mechanism models transcription using standard Michaelis-Menten
+ kinetics where RNA polymerase acts as an enzyme that binds DNA,
+ catalyzes mRNA synthesis, and is released unchanged. This is appropriate
+ when:
+
+ - RNAP concentration affects transcription rate
+ - Explicit modeling of RNAP-DNA binding is important
+ - RNAP sequestration or competition effects are relevant
+
+ The mechanism uses the MichaelisMentenCopy base class which generates:
+
+ 1. Reversible binding: DNA + RNAP <--> DNA:RNAP (rates: 'kb', 'ku')
+ 2. Catalysis: DNA:RNAP --> DNA + RNAP + mRNA (rate: 'ktx')
+
+ Common applications include:
+
+ - TX-TL systems with limited RNAP
+ - Models of transcriptional resource allocation
+ - Competition between promoters for RNAP
+ - Detailed mechanistic models of gene expression
+
+ Required parameters for this mechanism:
+
+ - 'ktx' : Transcription/catalysis rate constant
+ - 'kb' : Forward binding rate for RNAP to DNA
+ - 'ku' : Reverse unbinding rate for RNAP from DNA
+
+ Examples
+ --------
+ Model transcription with explicit RNA polymerase:
+
+ >>> gene = bcp.DNAassembly(
+ ... name='gene_assembly',
+ ... promoter='pconst', transcript='mRNA')
+ >>> mechanism = bcp.Transcription_MM(rnap=rnap)
+ >>> mixture = bcp.Mixture(
+ ... components=[gene],
+ ... mechanisms={'transcription': mechanism},
+ ... parameters={'ktx': 0.05, 'kb': 1.0, 'ku': 0.1}
+ ... )
+ >>> mixture.compile_crn()
- G + RNAP <--> G:RNAP --> G+RNAP+mRNA
"""
- def __init__(self, rnap: Species, name='transcription_mm', **keywords):
- """Initializes a Transcription_MM instance.
-
- :param rnap: Species instance that is representing an RNA polymerase
- :param name: name of the Mechanism, default: transcription_mm
- """
+ def __init__(self, rnap: Species, name='transcription_mm', **kwargs):
if isinstance(rnap, Species):
self.rnap = rnap
else:
@@ -422,7 +1171,32 @@ def __init__(self, rnap: Species, name='transcription_mm', **keywords):
self=self, name=name, mechanism_type='transcription'
)
- def update_species(self, dna, transcript=None, protein=None, **keywords):
+ def update_species(self, dna, transcript=None, protein=None, **kwargs):
+ """Generate species for Michaelis-Menten transcription.
+
+ Creates species involved in RNAP-mediated transcription including DNA,
+ RNAP, the DNA:RNAP complex, and transcript or protein product.
+
+ Parameters
+ ----------
+ dna : Species
+ The DNA species (gene) being transcribed.
+ transcript : Species, optional
+ The mRNA transcript species produced. If None and protein is
+ provided, protein is produced directly (for expression mixtures).
+ protein : Species, optional
+ Protein species for expression mixtures without explicit
+ transcription.
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Species
+ List containing [dna, rnap, dna:rnap complex, product] where
+ product is either transcript or protein.
+
+ """
species = [dna]
if transcript is None and protein is not None:
@@ -444,8 +1218,53 @@ def update_reactions(
complex=None,
transcript=None,
protein=None,
- **keywords,
+ **kwargs,
):
+ """Generate reactions for Michaelis-Menten transcription.
+
+ Creates Michaelis-Menten transcription reactions including reversible
+ RNAP-DNA binding and catalytic transcript production.
+
+ Parameters
+ ----------
+ dna : Species
+ The DNA species (gene) being transcribed.
+ component : Component
+ Component containing parameter values. Required for parameter
+ lookup.
+ part_id : str, optional
+ Identifier for parameter lookup. If None, defaults to
+ component.name.
+ complex : Complex, optional
+ Pre-specified DNA:RNAP complex species. If None, automatically
+ created.
+ transcript : Species, optional
+ The mRNA transcript species produced. If None and protein is
+ provided, protein is produced directly.
+ protein : Species, optional
+ Protein species for expression mixtures.
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Reaction
+ List of two reactions:
+
+ - Reversible RNAP-DNA binding (rates: 'kb', 'ku')
+ - Catalytic transcription (rate: 'ktx')
+
+ Notes
+ -----
+ The reactions follow the Michaelis-Menten scheme:
+
+ 1. DNA + RNAP <--> DNA:RNAP (rates: 'kb' and 'ku')
+ 2. DNA:RNAP --> DNA + RNAP + Product (rate: 'ktx')
+
+ Where Product is either transcript or protein depending on mixture
+ type.
+
+ """
# Get Parameters
if part_id is None and component is not None:
part_id = component.name
@@ -476,17 +1295,90 @@ def update_reactions(
class Translation_MM(MichaelisMentenCopy):
- """Michaelis Menten Translation.
+ """Michaelis-Menten translation with explicit ribosome.
+
+ A 'translation' mechanism that explicitly models ribosome binding to
+ mRNA, followed by translation and release. This mechanism follows
+ Michaelis-Menten kinetics and is appropriate when ribosomes are limiting
+ or when explicit modeling of ribosome-mRNA interactions is needed.
+
+ The reaction follows the schema:
+
+ mRNA + Rib <--> mRNA:Rib --> mRNA + Rib + Protein
+
+ Parameters
+ ----------
+ ribosome : Species
+ Ribosome species that catalyzes translation.
+ name : str, default='translation_mm'
+ Name identifier for this mechanism instance.
+
+ Attributes
+ ----------
+ ribosome : Species
+ The ribosome species.
+ name : str
+ Name of the mechanism instance.
+ mechanism_type : str
+ Type classification ('translation').
+
+ See Also
+ --------
+ SimpleTranslation : Simple translation without explicit ribosomes.
+ Transcription_MM : Michaelis-Menten transcription.
+ MichaelisMentenCopy : Base class for MM copy mechanisms.
+ Mechanism : Base class for all mechanisms.
+
+ Notes
+ -----
+ This mechanism models translation using standard Michaelis-Menten
+ kinetics where ribosomes act as enzymes that bind mRNA, catalyze
+ protein synthesis, and are released unchanged. This is appropriate when:
+
+ - Ribosome concentration affects translation rate
+ - Explicit modeling of ribosome-mRNA binding is important
+ - Ribosome sequestration or competition effects are relevant
+
+ The mechanism uses the MichaelisMentenCopy base class which generates:
+
+ 1. Reversible binding: mRNA + Rib <--> mRNA:Rib (rates: 'kb', 'ku')
+ 2. Catalysis: mRNA:Rib --> mRNA + Rib + Protein (rate: 'ktl')
+
+ Common applications include:
+
+ - TX-TL systems with limited ribosomes
+ - Models of translational resource allocation
+ - Competition between mRNAs for ribosomes
+ - Detailed mechanistic models of protein expression
+
+ Required parameters for this mechanism:
+
+ - 'ktl' : Translation/catalysis rate constant
+ - 'kb' : Forward binding rate for ribosome to mRNA
+ - 'ku' : Reverse unbinding rate for ribosome from mRNA
+
+ Examples
+ --------
+ Model translation with explicit ribosomes:
+
+ >>> gene = bcp.DNAassembly(
+ ... name='gene_assembly',
+ ... promoter='pconst', transcript='mRNA',
+ ... rbs='RBS_medium', protein='GFP')
+ >>> ribosome = bcp.Species('Ribosome', material_type='protein')
+ >>> tl_mechanism = bcp.Translation_MM(ribosome=ribosome)
+ >>> mixture = bcp.Mixture(
+ ... components=[gene],
+ ... mechanisms={
+ ... 'transcription': bcp.SimpleTranscription(),
+ ... 'translation': mechanism},
+ ... parameters={'ktx': 0.05, 'ktl': 0.1, 'kb': 1.0, 'ku': 0.1}
+ ... )
+ >>> mixture.compile_crn()
- mRNA + Rib <--> mRNA:Rib --> mRNA + Rib + Protein
"""
- def __init__(self, ribosome: Species, name='translation_mm', **keywords):
- """Initializes a Translation_MM instance.
-
- :param ribosome: Species instance that is representing a ribosome
- :param name: name of the Mechanism, default: translation_mm
- """
+ def __init__(self, ribosome: Species, name='translation_mm', **kwargs):
if isinstance(ribosome, Species):
self.ribosome = ribosome
else:
@@ -495,7 +1387,31 @@ def __init__(self, ribosome: Species, name='translation_mm', **keywords):
self=self, name=name, mechanism_type='translation'
)
- def update_species(self, transcript, protein, **keywords):
+ def update_species(self, transcript, protein, **kwargs):
+ """Generate species for Michaelis-Menten translation.
+
+ Creates species involved in ribosome-mediated translation including
+ mRNA, ribosome, the mRNA:ribosome complex, and protein product.
+
+ Parameters
+ ----------
+ transcript : Species
+ The mRNA transcript species being translated. Can be None in
+ expression mixtures without explicit transcription.
+ protein : Species
+ The protein species produced by translation.
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Species
+ List containing translation species. In expression mixtures
+ without transcription (transcript is None), returns [protein].
+ Otherwise returns [ribosome, transcript, mRNA:ribosome complex,
+ protein].
+
+ """
species = []
# This can only occur in expression mixtures
@@ -518,8 +1434,53 @@ def update_reactions(
component,
part_id=None,
complex=None,
- **keywords,
+ **kwargs,
):
+ """Generate reactions for Michaelis-Menten translation.
+
+ Creates Michaelis-Menten translation reactions including reversible
+ ribosome-mRNA binding and catalytic protein production.
+
+ Parameters
+ ----------
+ transcript : Species
+ The mRNA transcript species being translated. Can be None in
+ expression mixtures.
+ protein : Species
+ The protein species produced by translation.
+ component : Component
+ Component containing parameter values. Required for parameter
+ lookup.
+ part_id : str, optional
+ Identifier for parameter lookup. If None, defaults to
+ component.name.
+ complex : Complex, optional
+ Pre-specified mRNA:ribosome complex species. If None,
+ automatically created.
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Reaction
+ List of two reactions for translation if transcript is not None:
+
+ - Reversible ribosome-mRNA binding (rates: 'kb', 'ku')
+ - Catalytic translation (rate: 'ktl')
+
+ Returns empty list if transcript is None (expression mixtures).
+
+ Notes
+ -----
+ The reactions follow the Michaelis-Menten scheme:
+
+ 1. mRNA + Ribosome <--> mRNA:Ribosome (rates: 'kb' and 'ku')
+ 2. mRNA:Ribosome --> mRNA + Ribosome + Protein (rate: 'ktl')
+
+ In expression mixtures without explicit transcription, no translation
+ reactions are generated (empty list returned).
+
+ """
rxns = []
# Get Parameters
@@ -548,18 +1509,107 @@ def update_reactions(
class Energy_Transcription_MM(Mechanism):
- """Michaelis Menten Transcription that consumed energy.
+ """Michaelis-Menten transcription with explicit energy consumption.
- G + RNAP <--> G:RNAP
- Fuel + G:RNAP --> G + RNAP + T + Fuel
+ A 'transcription' mechanism that models transcription with explicit
+ consumption of energy sources (fuel species like NTPs) and production of
+ waste products. This mechanism couples RNAP-DNA binding with
+ length-dependent fuel consumption to model realistic transcription
+ energetics.
- Transcription can only happen when there is fuel, at rate ktx/L
- (length dependent transcription rate)
+ The reaction follows the schema:
+ G + RNAP <--> G:RNAP
+ Fuel + G:RNAP --> G + RNAP + T
Fuel + G:RNAP --> G:RNAP + wastes
- Fuel consumption treated faster at rate ktx. This occurs L times
- faster than the above, resulting in the correct fuel use.
+ Transcription occurs at rate 'ktx' / L (length-dependent), while fuel
+ consumption occurs at rate 'ktx', resulting in L times more fuel
+ consumption than transcripts produced.
+
+ Parameters
+ ----------
+ rnap : Species
+ RNA polymerase species that catalyzes transcription.
+ fuels : list of Species
+ List of fuel species (e.g., NTPs) consumed during transcription.
+ wastes : list of Species
+ List of waste species (e.g., pyrophosphate) produced during
+ transcription.
+ name : str, default='energy_transcription_mm'
+ Name identifier for this mechanism instance.
+
+ Attributes
+ ----------
+ rnap : Species
+ The RNA polymerase species.
+ fuels : list of Species
+ Fuel species consumed during transcription.
+ wastes : list of Species
+ Waste species produced during transcription.
+ name : str
+ Name of the mechanism instance.
+ mechanism_type : str
+ Type classification ('transcription').
+
+ See Also
+ --------
+ Transcription_MM : MM transcription without explicit energy.
+ MichaelisMentenCopy : Base class for MM copy mechanisms.
+ Mechanism : Base class for all mechanisms.
+
+ Notes
+ -----
+ This mechanism provides a more realistic model of transcription by
+ explicitly tracking energy consumption. Key features:
+
+ - Length-dependent transcription rate ('ktx' / L)
+ - Explicit fuel (NTP) consumption
+ - Waste product generation
+ - RNAP-DNA binding dynamics
+
+ The length parameter L represents the gene length in appropriate units,
+ and the mechanism automatically scales fuel consumption to match the
+ energetic requirements of synthesizing an mRNA of length L.
+
+ Common applications include:
+
+ - Detailed TX-TL models with explicit resources
+ - Models of transcriptional burden and resource depletion
+ - Systems where NTP availability affects gene expression
+
+ Required parameters for this mechanism:
+
+ - 'ktx' : Base transcription rate constant
+ - 'kb' : Forward binding rate for RNAP to DNA
+ - 'ku' : Reverse unbinding rate for RNAP from DNA
+ - 'length' : Gene length (for length-dependent transcription)
+
+ Examples
+ --------
+ Model transcription with explicit NTP consumption:
+
+ >>> gene = bcp.DNAassembly(
+ ... name='constitutive_promoter',
+ ... promoter='pconst', rbs='RBS_medium', protein='GFP')
+ >>> rnap = bcp.Species('RNAP')
+ >>> ntp = bcp.Species('NTP')
+ >>> ppi = bcp.Species('PPi')
+ >>> mechanism = bcp.Energy_Transcription_MM(
+ ... rnap=rnap,
+ ... fuels=[ntp],
+ ... wastes=[ppi]
+ ... )
+ >>> mixture = bcp.Mixture(
+ ... components=[gene],
+ ... mechanisms={
+ ... 'transcription': mechanism,
+ ... 'translation': bcp.SimpleTranslation()
+ ... },
+ ... parameters={'ktx': 0.05, 'kb': 1.0, 'ku': 0.1, 'length': 1000},
+ ... parameter_file='mixtures/pure_parameters.tsv'
+ ... )
+ >>> mixture.compile_crn()
"""
@@ -567,17 +1617,10 @@ def __init__(
self,
rnap: Species,
fuels: List[Species],
- wastes=List[Species],
+ wastes: List[Species],
name='energy_transcription_mm',
- **keywords,
+ **kwargs,
):
- """Initializes a Transcription_MM instance.
-
- :param fuels: List of Species consumed during transcription
- :param wastes: List of Species consumed during transcription
- :param rnap: Species instance that is representing an RNA polymerase
- :param name: name of the Mechanism, default: transcription_mm
- """
if isinstance(rnap, Species):
self.rnap = rnap
else:
@@ -599,7 +1642,32 @@ def __init__(
self=self, name=name, mechanism_type='transcription'
)
- def update_species(self, dna, transcript=None, protein=None, **keywords):
+ def update_species(self, dna, transcript=None, protein=None, **kwargs):
+ """Generate species for energy-consuming transcription.
+
+ Creates species involved in transcription with explicit energy
+ consumption including DNA, RNAP, the DNA:RNAP complex, transcript,
+ and fuel species.
+
+ Parameters
+ ----------
+ dna : Species
+ The DNA species (gene) being transcribed.
+ transcript : Species, optional
+ The mRNA transcript species produced.
+ protein : Species, optional
+ Protein species (unused in this mechanism, accepted for API
+ consistency).
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Species
+ List containing [dna, rnap, transcript, fuels..., dna:rnap
+ complex].
+
+ """
species = [dna, self.rnap, transcript] + self.fuels
bound_complex = Complex([dna, self.rnap])
species += [bound_complex]
@@ -614,8 +1682,56 @@ def update_reactions(
complex=None,
transcript=None,
protein=None,
- **keywords,
+ **kwargs,
):
+ """Generate reactions for energy-consuming transcription.
+
+ Creates three reactions modeling transcription with explicit fuel
+ consumption and waste production: RNAP-DNA binding, length-dependent
+ transcription, and fuel consumption.
+
+ Parameters
+ ----------
+ dna : Species
+ The DNA species (gene) being transcribed.
+ component : Component
+ Component containing parameter values. Required for parameter
+ lookup.
+ part_id : str, optional
+ Identifier for parameter lookup. If None, defaults to
+ component.name.
+ complex : Complex, optional
+ Pre-specified DNA:RNAP complex species (unused, complex is
+ created internally).
+ transcript : Species, optional
+ The mRNA transcript species produced.
+ protein : Species, optional
+ Protein species (unused, accepted for API consistency).
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Reaction
+ List of three reactions:
+
+ - RNAP-DNA binding (rates: 'kb', 'ku')
+ - Transcription with fuel (rate: 'ktx' / 'length')
+ - Fuel consumption producing wastes (rate: 'ktx')
+
+ Notes
+ -----
+ The reactions model transcription energetics:
+
+ 1. DNA + RNAP <--> DNA:RNAP (rates: 'kb' and 'ku')
+ 2. Fuel + DNA:RNAP --> Fuel + DNA + RNAP + mRNA (rate: 'ktx' / L)
+ 3. Fuel + DNA:RNAP --> DNA:RNAP + Wastes (rate: 'ktx')
+
+ The length-dependent transcription rate ('ktx' / L) ensures that L
+ times more fuel is consumed than transcripts produced, reflecting the
+ energetic cost of synthesizing a transcript of length L.
+
+ """
# Get Parameters
if part_id is None and component is not None:
part_id = component.name
@@ -648,11 +1764,109 @@ def update_reactions(
class Energy_Translation_MM(Mechanism):
- """Michaelis Menten Translation that consumes energy species.
+ """Michaelis-Menten translation with explicit energy consumption.
+
+ A 'translation' mechanism that models translation with explicit
+ consumption of energy sources (fuel species like amino acids/NTPs) and
+ production of waste products. This mechanism couples ribosome-mRNA binding
+ with length-dependent fuel consumption to model realistic translation
+ energetics.
+
+ The reaction follows the schema:
+
+ mRNA + Rib <--> mRNA:Rib
+ Fuel + mRNA:Rib --> mRNA + Rib + Protein + Fuel
+ Fuel + mRNA:Rib --> mRNA:Rib + wastes
+
+ Translation occurs at rate 'ktl' / L (length-dependent), while fuel
+ consumption occurs at rate 'ktl', resulting in L times more fuel
+ consumption than proteins produced.
+
+ Parameters
+ ----------
+ ribosome : Species
+ Ribosome species that catalyzes translation.
+ fuels : list of Species
+ List of fuel species (e.g., amino acids, GTP) consumed during
+ translation.
+ wastes : list of Species
+ List of waste species produced during translation.
+ name : str, default='energy_translation_mm'
+ Name identifier for this mechanism instance.
+
+ Attributes
+ ----------
+ ribosome : Species
+ The ribosome species.
+ fuels : list of Species
+ Fuel species consumed during translation.
+ wastes : list of Species
+ Waste species produced during translation.
+ name : str
+ Name of the mechanism instance.
+ mechanism_type : str
+ Type classification ('translation').
+
+ See Also
+ --------
+ Translation_MM : MM translation without explicit energy.
+ Energy_Transcription_MM : MM transcription with explicit energy.
+ MichaelisMentenCopy : Base class for MM copy mechanisms.
+ Mechanism : Base class for all mechanisms.
+
+ Notes
+ -----
+ This mechanism provides a more realistic model of translation by
+ explicitly tracking energy consumption. Key features:
+
+ - Length-dependent translation rate ('ktl' / L)
+ - Explicit fuel (amino acid/NTP) consumption
+ - Waste product generation
+ - Ribosome-mRNA binding dynamics
+
+ The length parameter L represents the gene/protein length in appropriate
+ units, and the mechanism automatically scales fuel consumption to match
+ the energetic requirements of synthesizing a protein of length L.
+
+ Common applications include:
+
+ - Detailed TX-TL models with explicit resources
+ - Models of translational burden and resource depletion
+ - Systems where amino acid availability affects protein expression
+
+ Required parameters for this mechanism:
+
+ - 'ktl' : Base translation rate constant
+ - 'kb' : Forward binding rate for ribosome to mRNA
+ - 'ku' : Reverse unbinding rate for ribosome from mRNA
+ - 'length' : Gene/protein length (for length-dependent translation)
+
+ Examples
+ --------
+ Model translation with explicit amino acid consumption:
+
+ >>> gene = bcp.DNAassembly(
+ ... name='constitutive_promoter',
+ ... promoter='pconst', rbs='RBS_medium', protein='GFP')
+ >>> ribosome = bcp.Species('Ribosome')
+ >>> amino_acids = bcp.Species('AA')
+ >>> waste = bcp.Species('waste')
+ >>> mechanism = bcp.Energy_Translation_MM(
+ ... ribosome=ribosome,
+ ... fuels=[amino_acids],
+ ... wastes=[waste]
+ ... )
+ >>> mixture = bcp.Mixture(
+ ... components=[gene],
+ ... mechanisms={
+ ... 'transcription': bcp.SimpleTranscription(),
+ ... 'translation': mechanism,
+ ... },
+ ... parameters={'ktl': 0.1, 'kb': 1.0, 'ku': 0.1, 'length': 300},
+ ... parameter_file='mixtures/pure_parameters.tsv'
+ ... )
+ >>> mixture.compile_crn()
- mRNA + Rib <--> mRNA:Rib (binding)
- fuels + mRNA:Rib --> mRNA + Rib + Protein + fuels (translation)
- fuels + mRNA:Rib --> mRNA:Rib +wastes (fuel consumption)
"""
def __init__(
@@ -661,16 +1875,8 @@ def __init__(
fuels: List[Species],
wastes=List[Species],
name='energy_translation_mm',
- **keywords,
+ **kwargs,
):
- """Initializes a Translation_MM instance.
-
- :param ribosome: Species instance that is representing a ribosome
- :param fuels: List of fuel Species that are consumed during
- translation
- :param wastes: List of Species consumed during translation
- :param name: name of the Mechanism, default: energy_translation_mm
- """
if isinstance(ribosome, Species):
self.ribosome = ribosome
else:
@@ -688,7 +1894,29 @@ def __init__(
Mechanism.__init__(self=self, name=name, mechanism_type='translation')
- def update_species(self, transcript, protein, **keywords):
+ def update_species(self, transcript, protein, **kwargs):
+ """Generate species for energy-consuming translation.
+
+ Creates species involved in translation with explicit energy
+ consumption including mRNA, ribosome, the mRNA:ribosome complex,
+ protein, and fuel species.
+
+ Parameters
+ ----------
+ transcript : Species
+ The mRNA transcript species being translated.
+ protein : Species
+ The protein species produced by translation.
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Species
+ List containing [fuels..., ribosome, protein, mRNA:ribosome
+ complex].
+
+ """
species = self.fuels + [self.ribosome, protein]
bound_complex = Complex([transcript, self.ribosome])
species += [bound_complex]
@@ -702,8 +1930,55 @@ def update_reactions(
component,
part_id=None,
complex=None,
- **keywords,
+ **kwargs,
):
+ """Generate reactions for energy-consuming translation.
+
+ Creates three reactions modeling translation with explicit fuel
+ consumption and waste production: ribosome-mRNA binding,
+ length-dependent translation, and fuel consumption.
+
+ Parameters
+ ----------
+ transcript : Species
+ The mRNA transcript species being translated.
+ protein : Species
+ The protein species produced by translation.
+ component : Component
+ Component containing parameter values. Required for parameter
+ lookup.
+ part_id : str, optional
+ Identifier for parameter lookup. If None, defaults to
+ component.name.
+ complex : Complex, optional
+ Pre-specified mRNA:ribosome complex species (unused, complex is
+ created internally).
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Reaction
+ List of three reactions:
+
+ - Ribosome-mRNA binding (rates: 'kb', 'ku')
+ - Translation with fuel (rate: 'ktl' / 'length')
+ - Fuel consumption producing wastes (rate: 'ktl')
+
+ Notes
+ -----
+ The reactions model translation energetics:
+
+ 1. mRNA + Ribosome <--> mRNA:Ribosome (rates: 'kb' and 'ku')
+ 2. Fuel + mRNA:Ribosome --> Fuel + mRNA + Ribosome + Protein (rate:
+ 'ktl' / L)
+ 3. Fuel + mRNA:Ribosome --> mRNA:Ribosome + Wastes (rate: 'ktl')
+
+ The length-dependent translation rate ('ktl' / L) ensures that L
+ times more fuel is consumed than proteins produced, reflecting the
+ energetic cost of synthesizing a protein of length L.
+
+ """
# Get Parameters
if part_id is None and component is not None:
part_id = component.name
@@ -739,24 +2014,102 @@ def update_reactions(
class multi_tx(Mechanism):
- """Multi-RNAp Transcription w/ Isomerization.
+ """Multi-polymerase transcription with isomerization and occupancy.
- Detailed transcription mechanism accounting for each individual
- RNAp occupancy states of gene.
+ A 'transcription' mechanism that explicitly models multiple RNA
+ polymerases (RNAPs) binding to a single gene simultaneously, accounting
+ for polymerase spacing, isomerization between open and closed
+ configurations, and competitive binding. This detailed mechanism captures
+ transcriptional queueing and interference effects.
- n ={0, max_occ}
- DNA:RNAp_n + RNAp <--> DNA:RNAp_n_c --> DNA:RNAp_n+1
- DNA:RNAp_n --> DNA:RNAp_0 + n RNAp + n mRNA
- DNA:RNAp_n_c --> DNA:RNAp_0_c + n RNAp + n mRNA
+ The reaction scheme follows:
+ DNA:RNAp_n + RNAp <--> DNA:RNAp_n_c --> DNA:RNAp_n+1
+ DNA:RNAp_n --> DNA:RNAp_0 + n RNAp + n mRNA
+ DNA:RNAp_n_c --> DNA:RNAp_0_c + n RNAp + n mRNA
- n --> number of open configuration RNAp on DNA
- max_occ --> Physical maximum number of RNAp on DNA (based on
- RNAp and DNA dimensions)
- DNA:RNAp_n --> DNA with n open configuration RNAp on it
- DNA:RNAp_n_c --> DNA with n open configuration RNAp and 1 closed
- configuration RNAp on it
+ where:
+
+ - n = {0, 1, ..., max_occ} is the number of polymerases
+ - max_occ is the physical maximum based on RNAP and DNA dimensions
+ - DNA:RNAp_n represents DNA with n open configuration RNAPs
+ - DNA:RNAp_n_c represents DNA with n open RNAPs and 1 closed RNAP
+
+ Parameters
+ ----------
+ pol : Species
+ RNA polymerase species.
+ name : str, default='multi_tx'
+ Name identifier for this mechanism instance.
+ mechanism_type : str, default='transcription'
+ Type classification of this mechanism.
+
+ Attributes
+ ----------
+ pol : Species
+ The RNA polymerase species.
+ name : str
+ Name of the mechanism instance.
+ mechanism_type : str
+ Type classification ('transcription').
+
+ See Also
+ --------
+ Transcription_MM : Simple Michaelis-Menten transcription.
+ multi_tl : Multi-ribosome translation mechanism.
+ Mechanism : Base class for all mechanisms.
+
+ Notes
+ -----
+ This mechanism provides a detailed, spatially-aware model of
+ transcription that captures:
+
+ - Multiple polymerases on a single gene
+ - Polymerase queueing and spacing constraints
+ - Open/closed conformational states (isomerization)
+ - Coordinated transcript release from multiple polymerases
+
+ The model is appropriate for detailed mechanistic studies where:
+
+ - Polymerase density and spacing matter
+ - Transcriptional queueing affects expression
+ - Multiple concurrent transcription events are important
+
+ The mechanism generates many species (2 * max_occ complexes) and
+ reactions (O(max_occ)), so it should be used with caution for
+ computational efficiency.
+
+ Required parameters for this mechanism:
+
+ - 'ktx' : Transcription/release rate constant
+ - 'kb' : Forward binding rate for polymerase to DNA
+ - 'ku' : Reverse unbinding rate for polymerase from DNA
+ - 'k_iso' : Isomerization rate from closed to open configuration
+ - 'max_occ' : Maximum polymerase occupancy (integer)
+
+ Examples
+ --------
+ Model multi-polymerase transcription:
+
+ >>> gene = bcp.DNAassembly(
+ ... name='dna_assembly',
+ ... promoter='pconst', rbs='RBS_medium', protein='GFP')
+ >>> rnap = bcp.Species('RNAP')
+ >>> tx_mechanism = bcp.multi_tx(pol=rnap)
+ >>> mixture = bcp.Mixture(
+ ... components=[gene],
+ ... mechanisms={
+ ... 'transcription': tx_mechanism,
+ ... 'translation': bcp.SimpleTranslation()
+ ... },
+ ... parameters={
+ ... 'ktx': 0.05, 'ktl': 0.1, 'kb': 1.0, 'ku': 0.1,
+ ... 'k_iso': 0.5, 'max_occ': 5
+ ... }
+ ... )
+ >>> mixture.compile_crn()
+
+ For more details, see examples/MultiTX_Demo.ipynb.
- For more details, see examples/MultiTX_Demo.ipynb
"""
def __init__(
@@ -764,16 +2117,8 @@ def __init__(
pol: Species,
name: str = 'multi_tx',
mechanism_type: str = 'transcription',
- **keywords,
+ **kwargs,
):
- """Initializes a multi_tx instance.
-
- :param pol: reference to a species instance that represents a
- polymerase
- :param name: name of the Mechanism, default: multi_tx
- :param mechanism_type: type of the mechanism, default: transcription
- :param keywords:
- """
if isinstance(pol, Species):
self.pol = pol
else:
@@ -783,8 +2128,54 @@ def __init__(
# species update
def update_species(
- self, dna, transcript, component, part_id, protein=None, **keywords
+ self, dna, transcript, component, part_id, protein=None, **kwargs
):
+ """Generate species for multi-polymerase transcription.
+
+ Creates all species and complexes involved in multi-polymerase
+ transcription including DNA with varying numbers of bound polymerases
+ in both open and closed configurations.
+
+ Parameters
+ ----------
+ dna : Species
+ The DNA species (gene) being transcribed.
+ transcript : Species
+ The mRNA transcript species produced.
+ component : Component
+ Component containing parameter values. Required for parameter
+ lookup.
+ part_id : str
+ Identifier for parameter lookup. Required to retrieve 'max_occ'
+ parameter.
+ protein : Species, optional
+ Protein species (unused, accepted for API consistency).
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Species
+ List containing:
+
+ - DNA:RNAP_n complexes in open configuration (n = 0 to max_occ-1)
+ - DNA:RNAP_n complexes in closed configuration (n = 0 to
+ max_occ-1)
+ - Individual species [polymerase, dna, transcript]
+
+ Notes
+ -----
+ For each occupancy level n from 0 to max_occ-1, two complex species
+ are created:
+
+ - Open configuration: DNA with n+1 polymerases, all in open state
+ - Closed configuration: DNA with n+1 polymerases (n open, 1 closed)
+
+ The 'max_occ' parameter determines the maximum number of polymerases
+ that can occupy the gene simultaneously, typically based on physical
+ spacing constraints.
+
+ """
max_occ = int(
component.get_parameter(
'max_occ',
@@ -814,18 +2205,56 @@ def update_species(
return cp_open + cp_closed + cp_misc
def update_reactions(
- self, dna, transcript, component, part_id, protein=None, **keywords
+ self, dna, transcript, component, part_id, protein=None, **kwargs
):
- """It sets up the following reactions.
+ """Generate reactions for multi-polymerase transcription.
+
+ Creates reactions modeling multiple polymerases binding to DNA,
+ isomerization between configurations, and transcript release with
+ coordination among multiple polymerases.
+
+ Parameters
+ ----------
+ dna : Species
+ The DNA species (gene) being transcribed.
+ transcript : Species
+ The mRNA transcript species produced.
+ component : Component
+ Component containing parameter values. Required for parameter
+ lookup.
+ part_id : str
+ Identifier for parameter lookup. Required to retrieve parameters.
+ protein : Species, optional
+ Protein species (unused, accepted for API consistency).
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Reaction
+ List of reactions including:
+
+ - Polymerase binding reactions (DNA:RNAP_n + RNAP <-->
+ DNA:RNAP_n_c)
+ - Isomerization reactions (DNA:RNAP_n_c --> DNA:RNAP_n)
+ - Release reactions from open states (DNA:RNAP_n --> DNA + n*RNAP
+ + n*mRNA)
+ - Release reactions from closed states (DNA:RNAP_n_c -->
+ DNA:RNAP_0_c + n*RNAP + n*mRNA)
+ - Base binding reaction (DNA + RNAP <--> DNA:RNAP_0_c)
+
+ Notes
+ -----
+ The reaction scheme captures:
+
+ 1. DNA:RNAP_n + RNAP <--> DNA:RNAP_(n+1)_c (rates: 'kb', 'ku')
+ 2. DNA:RNAP_n_c --> DNA:RNAP_n (rate: 'k_iso')
+ 3. DNA:RNAP_n --> DNA + n*RNAP + n*mRNA (rate: 'ktx')
+ 4. DNA:RNAP_n_c --> DNA:RNAP_0_c + n*RNAP + n*mRNA (rate: 'ktx')
+
+ Where n ranges from 0 to max_occ-1. The 'max_occ' parameter
+ represents the physical maximum occupancy of polymerases on the gene.
- DNA:RNAp_n + RNAp <--> DNA:RNAp_n_c --> DNA:RNAp_n+1
- kf1 = k1, kr1 = k2, kf2 = k_iso
- DNA:RNAp_n --> DNA:RNAp_0 + n RNAp + n mRNA
- kf = ktx_solo
- DNA:RNAp_n_c --> DNA:RNAp_0_c + n RNAp + n mRNA
- kf = ktx_solo
-
- max_occ = maximum occupancy of gene (physical limit)
"""
# parameter loading
kb = component.get_parameter('kb', part_id=part_id, mechanism=self)
@@ -932,25 +2361,106 @@ def update_reactions(
class multi_tl(Mechanism):
- """Multi-RBZ Translation w/ Isomerization.
+ """Multi-ribosome translation with isomerization and occupancy.
- Detailed translation mechanism accounting for each individual
- RBZ occupancy states of mRNA. Still needs some work, so use with caution,
- read all warnings and consult the example notebook.
+ A 'translation' mechanism that explicitly models multiple ribosomes
+ binding to a single mRNA simultaneously, accounting for ribosome spacing,
+ isomerization between open and closed configurations, and competitive
+ binding. This detailed mechanism captures translational queueing and
+ ribosome traffic effects.
- n ={0, max_occ}
- mRNA:RBZ_n + RBZ <--> mRNA:RBZ_n_c --> mRNA:RBZ_n+1
- mRNA:RBZ_n --> mRNA:RBZ_0 + n RBZ + n Protein
- mRNA:RBZ_n_c --> mRNA:RBZ_0_c + n RBZ + n Protein
+ The reaction scheme follows:
+ mRNA:RBZ_n + RBZ <--> mRNA:RBZ_n_c --> mRNA:RBZ_n+1
+ mRNA:RBZ_n --> mRNA:RBZ_0 + n RBZ + n Protein
+ mRNA:RBZ_n_c --> mRNA:RBZ_0_c + n RBZ + n Protein
- n --> number of open configuration RBZ on mRNA
- max_occ --> Physical maximum number of RBZ on mRNA
- (based on RBZ and mRNA dimensions)
- mRNA:RBZ_n --> mRNA with n open configuration RBZ on it
- mRNA:RBZ_n_c --> mRNA with n open configuration RBZ and 1
- closed configuration RBZ on it
+ where:
+
+ - n = {0, 1, ..., max_occ} is the number of ribosomes
+ - max_occ is the physical maximum based on ribosome and mRNA dimensions
+ - mRNA:RBZ_n represents mRNA with n open configuration ribosomes
+ - mRNA:RBZ_n_c represents mRNA with n open and 1 closed ribosome
+
+ Parameters
+ ----------
+ ribosome : Species
+ Ribosome species.
+ name : str, default='multi_tl'
+ Name identifier for this mechanism instance.
+ mechanism_type : str, default='translation'
+ Type classification of this mechanism.
+
+ Attributes
+ ----------
+ ribosome : Species
+ The ribosome species.
+ name : str
+ Name of the mechanism instance.
+ mechanism_type : str
+ Type classification ('translation').
+
+ See Also
+ --------
+ Translation_MM : Simple Michaelis-Menten translation.
+ multi_tx : Multi-polymerase transcription mechanism.
+ Mechanism : Base class for all mechanisms.
+
+ Notes
+ -----
+ This mechanism provides a detailed, spatially-aware model of translation
+ that captures:
+
+ - Multiple ribosomes on a single mRNA (polysome formation)
+ - Ribosome queueing and spacing constraints
+ - Open/closed conformational states (isomerization)
+ - Coordinated protein release from multiple ribosomes
+
+ The model is appropriate for detailed mechanistic studies where:
+
+ - Ribosome density and spacing matter
+ - Translational queueing affects expression
+ - Multiple concurrent translation events are important
+ - Polysome dynamics are relevant
+
+ The mechanism generates many species (2 * max_occ complexes) and
+ reactions (O(max_occ)), so it should be used with caution for
+ computational efficiency.
+
+ CAUTION: This mechanism is still under development. Use with care, read
+ all warnings, and consult the example notebook before use.
+
+ Required parameters for this mechanism:
+
+ - 'ktl' : Translation/release rate constant
+ - 'kb' : Forward binding rate for ribosome to mRNA
+ - 'ku' : Reverse unbinding rate for ribosome from mRNA
+ - 'k_iso' : Isomerization rate from closed to open configuration
+ - 'max_occ' : Maximum ribosome occupancy (integer)
+
+ Examples
+ --------
+ Model multi-ribosome translation:
+
+ >>> gene = bcp.DNAassembly(
+ ... name='gene_assembly',
+ ... promoter='pconst', transcript='mRNA',
+ ... rbs='RBS_medium', protein='GFP')
+ >>> ribosome = bcp.Species('Ribosome')
+ >>> tl_mechanism = bcp.multi_tl(ribosome=ribosome)
+ >>> mixture = bcp.Mixture(
+ ... components=[gene],
+ ... mechanisms={
+ ... 'transcription': bcp.SimpleTranscription(),
+ ... 'translation': tl_mechanism},
+ ... parameters={
+ ... 'ktx': 0.05, 'ktl': 0.1, 'kb': 1.0, 'ku': 0.1,
+ ... 'k_iso': 0.5, 'max_occ': 5
+ ... }
+ ... )
+ >>> mixture.compile_crn()
+
+ For more details, see examples/MultiTX_Demo.ipynb.
- For more details, see examples/MultiTX_Demo.ipynb
"""
def __init__(
@@ -958,15 +2468,8 @@ def __init__(
ribosome: Species,
name: str = 'multi_tl',
mechanism_type: str = 'translation',
- **keywords,
+ **kwargs,
):
- """Initializes a multi_tl instance.
-
- :param ribosome: a Species instance that represents a ribosome
- :param name: name of the Mechanism, default: multi_tl
- :param mechanism_type: type of the Mechanism, default: translation
-
- """
if isinstance(ribosome, Species):
self.ribosome = ribosome
else:
@@ -976,8 +2479,53 @@ def __init__(
# species update
def update_species(
- self, transcript, protein, component, part_id, **keywords
+ self, transcript, protein, component, part_id, **kwargs
):
+ """Generate species for multi-ribosome translation.
+
+ Creates all species and complexes involved in multi-ribosome
+ translation including mRNA with varying numbers of bound ribosomes in
+ both open and closed configurations.
+
+ Parameters
+ ----------
+ transcript : Species
+ The mRNA transcript species being translated.
+ protein : Species
+ The protein species produced by translation.
+ component : Component
+ Component containing parameter values. Required for parameter
+ lookup.
+ part_id : str
+ Identifier for parameter lookup. Required to retrieve 'max_occ'
+ parameter.
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Species
+ List containing:
+
+ - mRNA:Ribosome_n complexes in open configuration (n = 0 to
+ max_occ-1)
+ - mRNA:Ribosome_n complexes in closed configuration (n = 0 to
+ max_occ-1)
+ - Individual species [ribosome, transcript, protein]
+
+ Notes
+ -----
+ For each occupancy level n from 0 to max_occ-1, two complex species
+ are created:
+
+ - Open configuration: mRNA with n+1 ribosomes, all in open state
+ - Closed configuration: mRNA with n+1 ribosomes (n open, 1 closed)
+
+ The 'max_occ' parameter determines the maximum number of ribosomes
+ that can occupy the mRNA simultaneously, typically based on physical
+ spacing constraints and mRNA length.
+
+ """
max_occ = int(
component.get_parameter(
'max_occ',
@@ -1007,16 +2555,55 @@ def update_species(
return cp_open + cp_closed + cp_misc
def update_reactions(
- self, transcript, protein, component, part_id, **keywords
+ self, transcript, protein, component, part_id, **kwargs
):
- """It sets up the following reactions.
+ """Generate reactions for multi-ribosome translation.
+
+ Creates reactions modeling multiple ribosomes binding to mRNA,
+ isomerization between configurations, and protein release with
+ coordination among multiple ribosomes.
+
+ Parameters
+ ----------
+ transcript : Species
+ The mRNA transcript species being translated.
+ protein : Species
+ The protein species produced by translation.
+ component : Component
+ Component containing parameter values. Required for parameter
+ lookup.
+ part_id : str
+ Identifier for parameter lookup. Required to retrieve parameters.
+ **kwargs
+ Additional keyword arguments (unused).
+
+ Returns
+ -------
+ list of Reaction
+ List of reactions including:
+
+ - Ribosome binding reactions (mRNA:RBZ_n + RBZ <-->
+ mRNA:RBZ_n_c)
+ - Isomerization reactions (mRNA:RBZ_n_c --> mRNA:RBZ_n)
+ - Release reactions from open states (mRNA:RBZ_n --> mRNA + n*RBZ
+ + n*Protein)
+ - Release reactions from closed states (mRNA:RBZ_n_c -->
+ mRNA:RBZ_0_c + n*RBZ + n*Protein)
+ - Base binding reaction (mRNA + RBZ <--> mRNA:RBZ_0_c)
+
+ Notes
+ -----
+ The reaction scheme captures:
+
+ 1. mRNA:RBZ_n + RBZ <--> mRNA:RBZ_(n+1)_c (rates: 'kb', 'ku')
+ 2. mRNA:RBZ_n_c --> mRNA:RBZ_n (rate: 'k_iso')
+ 3. mRNA:RBZ_n --> mRNA + n*RBZ + n*Protein (rate: 'ktl')
+ 4. mRNA:RBZ_n_c --> mRNA:RBZ_0_c + n*RBZ + n*Protein (rate: 'ktl')
+
+ Where n ranges from 0 to max_occ-1. The 'max_occ' parameter
+ represents the physical maximum occupancy of ribosomes on the mRNA,
+ determined by ribosome spacing and mRNA length.
- mRNA:RBZ_n + RBZ <--> mRNA:RBZ_n_c --> mRNA:RBZ_n+1
- kf1 = kbr, kr1 = kur, kf2 = k_iso_r
- mRNA:RBZ_n --> mRNA:RBZ_0 + n RBZ + n Protein
- kf = ktl_solo
- mRNA:RBZ_n_c --> mRNA:RBZ_0_c + n RBZ + n Protein
- kf = ktl_solo
"""
# parameter loading
kb = component.get_parameter('kb', part_id=part_id, mechanism=self)
diff --git a/biocrnpyler/mixtures/__init__.py b/biocrnpyler/mixtures/__init__.py
index 16e30875..61d83034 100644
--- a/biocrnpyler/mixtures/__init__.py
+++ b/biocrnpyler/mixtures/__init__.py
@@ -1,9 +1,9 @@
"""BioCRNpyler mixture library.
-A Mixture in BioCRNpyler defines the *context* in which Components are
+A mixture in BioCRNpyler defines the *context* in which components are
compiled into a chemical reaction network (CRN). A mixture ties
together components, mechanisms, and parameters by specifying *which*
-Mechanisms are available, *which* Components are present, and *what*
+Mechanisms are available, *which* components are present, and *what*
parameters to use.
"""
diff --git a/biocrnpyler/mixtures/cell.py b/biocrnpyler/mixtures/cell.py
index 1b88c71c..07484166 100644
--- a/biocrnpyler/mixtures/cell.py
+++ b/biocrnpyler/mixtures/cell.py
@@ -19,20 +19,146 @@
class ExpressionDilutionMixture(Mixture):
- """In vivo gene expression without any machinery.
-
- Here transcription and translation are lumped into one reaction:
- expression, without RNA polymerase or ribosomes. A global mechanism is
- used to dilute all non-DNA species.
+ """In vivo gene expression with dilution but without cellular machinery.
+
+ A simplified mixture that models gene expression as a single direct
+ reaction from DNA to protein, without explicitly representing
+ transcription and translation as separate processes or cellular machinery
+ (ribosomes, polymerases). This mixture lumps transcription and
+ translation into a single 'expression' reaction and includes global
+ dilution to model cell growth and division effects on all non-DNA
+ species.
+
+ This mixture is appropriate for coarse-grained models of in vivo gene
+ expression where mRNA dynamics are negligible and growth dilution is
+ important.
+
+ Parameters
+ ----------
+ name : str, default=''
+ Name of the mixture for identification and parameter lookup.
+ mechanisms : dict, list, or Mechanism, optional
+ Default mechanisms for components in this mixture. Can be a dict with
+ mechanism types (str) as keys and mechanism objects as values, a
+ list of mechanism objects, or a single `Mechanism`.
+ components : list of Component or Component, optional
+ Components to include in the mixture. Components are deep-copied when
+ added to prevent modification of original objects.
+ parameters : dict, optional
+ Dictionary of parameter values. Keys follow the format
+ (mechanism, part_id, param_name).
+ compartment : Compartment, optional
+ Default compartment for all components and species in this mixture.
+ parameter_file : str, optional
+ Path to a CSV or TSV file containing parameters to load.
+ overwrite_parameters : bool, default=False
+ If True, parameters from file/dict overwrite existing parameters.
+ If False, existing parameters are preserved.
+ global_mechanisms : dict, list, or GlobalMechanism, optional
+ Global mechanisms that apply to all species after component
+ compilation (e.g., dilution, global degradation). Can be a dict,
+ list, or single `GlobalMechanism`.
+ species : list of Species or Species, optional
+ Additional species to add directly to the CRN without going through
+ component compilation.
+ initial_condition_dictionary : dict, optional
+ Dictionary mapping species to initial concentration values. Deprecated
+ in favor of using parameters with mechanism='initial concentration'.
+ global_component_enumerators : list, optional
+ List of global component enumerators for advanced component generation
+ patterns (e.g., creating all pairwise interactions).
+ global_recursion_depth : int, default=4
+ Maximum recursion depth for global component enumeration during
+ compilation.
+ local_recursion_depth : int, optional
+ Maximum recursion depth for local component enumeration. If None,
+ defaults to `global_recursion_depth + 2`.
+
+ Attributes
+ ----------
+ name : str
+ Name of the mixture.
+ compartment : Compartment or None
+ Default compartment for the mixture.
+ components : list of Component
+ List of components in the mixture (deep copies of added components).
+ mechanisms : dict
+ Dictionary of default mechanisms, keyed by mechanism type (str).
+ global_mechanisms : dict
+ Dictionary of global mechanisms, keyed by mechanism type (str).
+ parameter_database : ParameterDatabase
+ Database storing all parameters for this mixture.
+ added_species : list of Species
+ List of species added directly to the mixture.
+ global_component_enumerators : list
+ List of global component enumerators.
+ global_recursion_depth : int
+ Recursion depth for global component enumeration.
+ local_recursion_depth : int
+ Recursion depth for local component enumeration.
+ crn : ChemicalReactionNetwork or None
+ The compiled CRN, created by calling `compile_crn`.
+
+ See Also
+ --------
+ ExpressionExtract : Expression without dilution.
+ SimpleTxTlDilutionMixture : TX-TL with dilution.
+ TxTlDilutionMixture : TX-TL with machinery and dilution.
+ Mixture : Base class for all mixtures.
+
+ Notes
+ -----
+ Default mechanisms included:
+
+ - 'transcription' : `OneStepGeneExpression` - Single-step gene
+ expression (DNA --> DNA + Protein) without intermediate mRNA
+ - 'translation' : `EmptyMechanism` - Dummy mechanism that generates no
+ reactions (translation is disabled)
+ - 'catalysis' : `BasicCatalysis` - Simple catalytic reactions without
+ explicit enzyme binding
+ - 'binding' : `One_Step_Binding` - Simple multi-species binding
+ - 'dilution' : `Dilution` - Global dilution mechanism (Species --> ∅)
+ applied to all non-DNA species to model growth/division
+
+ Key features of this mixture:
+
+ - No explicit transcription or translation steps
+ - No cellular machinery (RNAP, ribosomes, RNases)
+ - No intermediate mRNA species
+ - Global dilution of all species except DNA
+ - Models growth dilution effects in vivo
+ - Simplified parameter space
+
+ When compiled, this mixture automatically disables transcript generation
+ in DNAassemblies that produce proteins, routing expression directly from
+ DNA to protein.
+
+ Common applications include:
+
+ - In vivo gene circuit modeling with growth effects
+ - Steady-state gene expression in growing cells
+ - Models where mRNA dynamics are negligible
+ - High-level circuit design with dilution
+
+ Examples
+ --------
+ Create an in vivo expression mixture with dilution for GFP:
+
+ >>> gfp_gene = bcp.DNAassembly(
+ ... name='gfp_construct',
+ ... promoter='pconst',
+ ... protein='GFP'
+ ... )
+ >>> mixture = bcp.ExpressionDilutionMixture(
+ ... name='cell_mixture',
+ ... components=[gfp_gene],
+ ... parameter_file='mixtures/cell_parameters.tsv'
+ ... )
+ >>> crn = mixture.compile_crn()
"""
def __init__(self, name='', **kwargs):
- """Initializes an ExpressionDilutionMixture instance.
-
- :param name: name of the mixture
- :param kwargs: keywords passed into the parent Class (Mixture)
- """
Mixture.__init__(self, name=name, **kwargs)
# Create default mechanisms for Gene Expression
@@ -49,22 +175,50 @@ def __init__(self, name='', **kwargs):
mech_cat.mechanism_type: mech_cat,
mech_bind.mechanism_type: mech_bind,
}
- self.add_mechanisms(default_mechanisms)
+ self.add_mechanisms(default_mechanisms, overwrite=None)
# Create global mechanism for dilution
dilution_mechanism = Dilution(
name='dilution', filter_dict={'dna': False}, default_on=True
)
global_mechanisms = {'dilution': dilution_mechanism}
- self.add_mechanisms(global_mechanisms)
+ self.add_mechanisms(global_mechanisms, overwrite=None)
+
+ def compile_crn(self, **kwargs) -> ChemicalReactionNetwork:
+ """Compile CRN with transcript generation disabled in gene expression.
+
+ Overrides the parent `compile_crn` method to automatically disable
+ transcript generation in DNAassemblies that produce proteins. This
+ ensures that gene expression proceeds directly from DNA to protein
+ without intermediate mRNA species.
+
+ Parameters
+ ----------
+ **kwargs
+ Additional keyword arguments passed to the parent Mixture
+ `compile_crn` method.
+
+ Returns
+ -------
+ ChemicalReactionNetwork
+ Compiled chemical reaction network with expression and dilution
+ reactions.
+
+ Notes
+ -----
+ This method automatically modifies DNAassemblies before compilation:
- def compile_crn(self, **keywords) -> ChemicalReactionNetwork:
- """Compile CRN, replacing transcripts with proteins.
+ - For assemblies with a protein product, sets transcript to False
+ - RNA-only assemblies (no protein) are not affected
+ - Mechanisms receive protein instead of transcript when transcript
+ is disabled
- Overwriting compile_crn to turn off transcription in all
- DNAassemblies.
+ This behavior enables the single-step expression mechanism to route
+ production directly to protein.
- :return: compiled CRN instance
+ See `Mixture.compile_crn
+ ` for a more detailed
+ description of the parent method behavior.
"""
for component in self.components:
@@ -78,26 +232,153 @@ def compile_crn(self, **keywords) -> ChemicalReactionNetwork:
component.update_transcript(False)
# Call the superclass function
- return Mixture.compile_crn(self, **keywords)
+ return Mixture.compile_crn(self, **kwargs)
class SimpleTxTlDilutionMixture(Mixture):
- """Mixture with continuous dilution for non-DNA species.
-
- Transcription and Translation are both modeled as catalytic with no
- cellular machinery. mRNA is also degraded via a separate reaction to
- represent endonucleases.
+ """In vivo TX-TL with simple mechanisms and continuous dilution.
+
+ A mixture that models transcription and translation as separate catalytic
+ reactions without explicitly representing cellular machinery (RNAP,
+ ribosomes). This mixture uses simple mass-action kinetics where DNA and
+ mRNA act as catalysts for transcript and protein production,
+ respectively. Includes global dilution to model cell growth and division
+ effects, plus separate RNA degradation to model endonuclease activity.
+
+ This mixture is appropriate for in vivo gene expression models where
+ machinery is not limiting but explicit TX-TL steps and growth dilution
+ are important.
+
+ Parameters
+ ----------
+ name : str, default=''
+ Name of the mixture for identification and parameter lookup.
+ mechanisms : dict, list, or Mechanism, optional
+ Default mechanisms for components in this mixture. Can be a dict with
+ mechanism types (str) as keys and mechanism objects as values, a
+ list of mechanism objects, or a single `Mechanism`.
+ components : list of Component or Component, optional
+ Components to include in the mixture. Components are deep-copied when
+ added to prevent modification of original objects.
+ parameters : dict, optional
+ Dictionary of parameter values. Keys follow the format
+ (mechanism, part_id, param_name).
+ compartment : Compartment, optional
+ Default compartment for all components and species in this mixture.
+ parameter_file : str, optional
+ Path to a CSV or TSV file containing parameters to load.
+ overwrite_parameters : bool, default=False
+ If True, parameters from file/dict overwrite existing parameters.
+ If False, existing parameters are preserved.
+ global_mechanisms : dict, list, or GlobalMechanism, optional
+ Global mechanisms that apply to all species after component
+ compilation (e.g., dilution, global degradation). Can be a dict,
+ list, or single `GlobalMechanism`.
+ species : list of Species or Species, optional
+ Additional species to add directly to the CRN without going through
+ component compilation.
+ initial_condition_dictionary : dict, optional
+ Dictionary mapping species to initial concentration values. Deprecated
+ in favor of using parameters with mechanism='initial concentration'.
+ global_component_enumerators : list, optional
+ List of global component enumerators for advanced component generation
+ patterns (e.g., creating all pairwise interactions).
+ global_recursion_depth : int, default=4
+ Maximum recursion depth for global component enumeration during
+ compilation.
+ local_recursion_depth : int, optional
+ Maximum recursion depth for local component enumeration. If None,
+ defaults to `global_recursion_depth + 2`.
+
+ Attributes
+ ----------
+ name : str
+ Name of the mixture.
+ compartment : Compartment or None
+ Default compartment for the mixture.
+ components : list of Component
+ List of components in the mixture (deep copies of added components).
+ mechanisms : dict
+ Dictionary of default mechanisms, keyed by mechanism type (str).
+ global_mechanisms : dict
+ Dictionary of global mechanisms, keyed by mechanism type (str).
+ parameter_database : ParameterDatabase
+ Database storing all parameters for this mixture.
+ added_species : list of Species
+ List of species added directly to the mixture.
+ global_component_enumerators : list
+ List of global component enumerators.
+ global_recursion_depth : int
+ Recursion depth for global component enumeration.
+ local_recursion_depth : int
+ Recursion depth for local component enumeration.
+ crn : ChemicalReactionNetwork or None
+ The compiled CRN, created by calling `compile_crn`.
+
+ See Also
+ --------
+ ExpressionDilutionMixture : Single-step expression with dilution.
+ TxTlDilutionMixture : TX-TL with machinery and dilution.
+ SimpleTxTlExtract : TX-TL without dilution.
+ Mixture : Base class for all mixtures.
+
+ Notes
+ -----
+ Default mechanisms included:
+
+ - 'transcription' : `SimpleTranscription` - Simple catalytic
+ transcription (DNA --> DNA + mRNA) without explicit RNAP binding
+ - 'translation' : `SimpleTranslation` - Simple catalytic translation
+ (mRNA --> mRNA + Protein) without explicit ribosome binding
+ - 'catalysis' : `BasicCatalysis` - Simple catalytic reactions without
+ explicit enzyme binding
+ - 'binding' : `One_Step_Binding` - Simple multi-species binding
+ - 'dilution' : `Dilution` - Global dilution mechanism (Species --> ∅)
+ applied to all non-DNA species to model growth/division
+ - 'rna_degradation' : `Dilution` - Separate RNA degradation mechanism
+ (mRNA --> ∅) applied to all RNA species to model endonuclease
+ activity
+
+ Key features of this mixture:
+
+ - Explicit transcription and translation steps
+ - Intermediate mRNA species
+ - Simple mass-action kinetics (no enzyme binding)
+ - No cellular machinery (RNAP, ribosomes)
+ - Global dilution of all non-DNA species
+ - Separate RNA degradation (faster than dilution)
+ - Models growth effects in vivo
+
+ Common applications include:
+
+ - In vivo gene circuit modeling with growth
+ - Models where machinery is not limiting
+ - Constitutive or weakly regulated promoters in growing cells
+ - mRNA dynamics with degradation and dilution
+
+ Examples
+ --------
+ Create a simple in vivo TX-TL mixture with dilution for GFP:
+
+ >>> gfp_gene = bcp.DNAassembly(
+ ... name='gfp_construct',
+ ... promoter='pconst',
+ ... rbs='bcd2',
+ ... transcript='gfp_mrna',
+ ... protein='GFP'
+ ... )
+ >>> mixture = bcp.SimpleTxTlDilutionMixture(
+ ... name='cell_mixture',
+ ... components=[gfp_gene],
+ ... parameter_file='mixtures/cell_parameters.tsv'
+ ... )
+ >>> crn = mixture.compile_crn()
"""
- def __init__(self, name='', **keywords):
- """Initializes a SimpleTxTlDilutionMixture instance.
-
- :param name: name of the mixture
- :param kwargs: keywords passed into the parent Class (Mixture)
- """
- # Always call the superclass __init__ with **keywords
- Mixture.__init__(self, name=name, **keywords)
+ def __init__(self, name='', **kwargs):
+ # Always call the superclass __init__ with **kwargs
+ Mixture.__init__(self, name=name, **kwargs)
# Create TxTl Mechanisms
# Transcription will not involve machinery
@@ -112,7 +393,7 @@ def __init__(self, name='', **keywords):
mech_cat.mechanism_type: mech_cat,
mech_bind.mechanism_type: mech_bind,
}
- self.add_mechanisms(default_mechanisms)
+ self.add_mechanisms(default_mechanisms, overwrite=None)
# Global Dilution Mechanisms
# By Default Species are diluted S-->0 Unless:
@@ -131,31 +412,186 @@ def __init__(self, name='', **keywords):
'dilution': dilution_mechanism,
'rna_degradation': deg_mrna,
}
- self.add_mechanisms(global_mechanisms)
+ self.add_mechanisms(global_mechanisms, overwrite=None)
class TxTlDilutionMixture(Mixture):
- """Transcription and translation with expression machinery.
-
- This model includes a background load "cellular processes" which
- represents innate loading effects in the cell. Effects of loading
- on cell growth are not modelled. Unlike TxTlExtract, has global
- dilution for non-DNA and non-Machinery This model does not include
- any energy.
+ """In vivo TX-TL with explicit machinery, dilution, and background load.
+
+ A mixture that models transcription and translation with explicit
+ representation of RNA polymerase (RNAP), ribosomes, and RNases for in
+ vivo contexts. This mixture uses Michaelis-Menten kinetics for TX-TL,
+ explicitly tracking enzyme-substrate binding and catalysis. Includes
+ global dilution to model cell growth effects and a background load
+ component representing endogenous cellular processes that compete for
+ shared machinery.
+
+ Unlike `TxTlExtract`, this mixture includes dilution for non-DNA and
+ non-machinery species. Machinery components (RNAP, ribosomes, RNases) are
+ protected from dilution via the 'machinery' attribute. This model does
+ not include explicit energy species.
+
+ Parameters
+ ----------
+ name : str, default=''
+ Name of the mixture for identification and parameter lookup.
+ rnap : str, default='RNAP'
+ Name for the RNA polymerase protein species.
+ ribosome : str, default='Ribo'
+ Name for the ribosome protein species.
+ rnaase : str, default='RNAase'
+ Name for the ribonuclease protein species.
+ mechanisms : dict, list, or Mechanism, optional
+ Default mechanisms for components in this mixture. Can be a dict with
+ mechanism types (str) as keys and mechanism objects as values, a
+ list of mechanism objects, or a single `Mechanism`.
+ components : list of Component or Component, optional
+ Components to include in the mixture. Components are deep-copied when
+ added to prevent modification of original objects.
+ parameters : dict, optional
+ Dictionary of parameter values. Keys follow the format
+ (mechanism, part_id, param_name).
+ compartment : Compartment, optional
+ Default compartment for all components and species in this mixture.
+ parameter_file : str, optional
+ Path to a CSV or TSV file containing parameters to load.
+ overwrite_parameters : bool, default=False
+ If True, parameters from file/dict overwrite existing parameters.
+ If False, existing parameters are preserved.
+ global_mechanisms : dict, list, or GlobalMechanism, optional
+ Global mechanisms that apply to all species after component
+ compilation (e.g., dilution, global degradation). Can be a dict,
+ list, or single `GlobalMechanism`.
+ species : list of Species or Species, optional
+ Additional species to add directly to the CRN without going through
+ component compilation.
+ initial_condition_dictionary : dict, optional
+ Dictionary mapping species to initial concentration values. Deprecated
+ in favor of using parameters with mechanism='initial concentration'.
+ global_component_enumerators : list, optional
+ List of global component enumerators for advanced component generation
+ patterns (e.g., creating all pairwise interactions).
+ global_recursion_depth : int, default=4
+ Maximum recursion depth for global component enumeration during
+ compilation.
+ local_recursion_depth : int, optional
+ Maximum recursion depth for local component enumeration. If None,
+ defaults to `global_recursion_depth + 2`.
+
+ Attributes
+ ----------
+ name : str
+ Name of the mixture.
+ rnap : Protein
+ RNA polymerase component with 'machinery' attribute.
+ ribosome : Protein
+ Ribosome component with 'machinery' attribute.
+ rnaase : Protein
+ Ribonuclease component with 'machinery' attribute.
+ compartment : Compartment or None
+ Default compartment for the mixture.
+ components : list of Component
+ List of components in the mixture (deep copies of added components).
+ mechanisms : dict
+ Dictionary of default mechanisms, keyed by mechanism type (str).
+ global_mechanisms : dict
+ Dictionary of global mechanisms, keyed by mechanism type (str).
+ parameter_database : ParameterDatabase
+ Database storing all parameters for this mixture.
+ added_species : list of Species
+ List of species added directly to the mixture.
+ global_component_enumerators : list
+ List of global component enumerators.
+ global_recursion_depth : int
+ Recursion depth for global component enumeration.
+ local_recursion_depth : int
+ Recursion depth for local component enumeration.
+ crn : ChemicalReactionNetwork or None
+ The compiled CRN, created by calling `compile_crn`.
+
+ See Also
+ --------
+ SimpleTxTlDilutionMixture : TX-TL without machinery, with dilution.
+ TxTlExtract : TX-TL with machinery, without dilution.
+ ExpressionDilutionMixture : Single-step expression with dilution.
+ Mixture : Base class for all mixtures.
+
+ Notes
+ -----
+ This mixture automatically adds the following components:
+
+ - RNA polymerase (RNAP) with 'machinery' attribute
+ - Ribosome with 'machinery' attribute
+ - Ribonuclease (RNase) with 'machinery' attribute
+ - Background processes DNAassembly representing cellular load
+
+ Default mechanisms included:
+
+ - 'transcription' : `Transcription_MM` - Michaelis-Menten transcription
+ with explicit RNAP binding (DNA + RNAP <--> DNA:RNAP --> DNA + RNAP +
+ mRNA)
+ - 'translation' : `Translation_MM` - Michaelis-Menten translation with
+ explicit ribosome binding (mRNA + Rib <--> mRNA:Rib --> mRNA + Rib +
+ Protein)
+ - 'rna_degradation' : `Degradation_mRNA_MM` - Global RNA degradation by
+ RNase using Michaelis-Menten kinetics
+ - 'catalysis' : `MichaelisMenten` - General Michaelis-Menten enzyme
+ catalysis
+ - 'binding' : `One_Step_Binding` - Simple multi-species binding
+ - 'dilution' : `Dilution` - Global dilution mechanism (Species --> ∅)
+ applied to all species except DNA and machinery
+
+ Key features of this mixture:
+
+ - Explicit modeling of transcription and translation machinery
+ - Resource competition (genes and background processes compete for
+ machinery)
+ - Enzyme sequestration in complexes
+ - RNA degradation dynamics
+ - Global dilution modeling cell growth
+ - Machinery protected from dilution
+ - Background load representing cellular processes
+ - Suitable for modeling in vivo gene expression with resource limits
+
+ Background processes:
+
+ - Implemented as a DNAassembly component ('cellular_processes')
+ - Represents endogenous genes competing for machinery
+ - Uses average promoter and RBS parameters
+ - Creates realistic loading effects on available machinery
+ - Does not model effects of loading on cell growth rate
+
+ Common applications include:
+
+ - In vivo gene circuit modeling with growth
+ - Resource allocation in growing cells
+ - Gene expression burden studies
+ - Competition between heterologous and endogenous genes
+ - Synthetic biology in cellular contexts
+
+ Examples
+ --------
+ Create an in vivo TX-TL mixture with machinery and dilution for GFP:
+
+ >>> gfp_gene = bcp.DNAassembly(
+ ... name='gfp_construct',
+ ... promoter='pconst',
+ ... rbs='bcd2',
+ ... transcript='gfp_mrna',
+ ... protein='GFP'
+ ... )
+ >>> mixture = bcp.TxTlDilutionMixture(
+ ... name='cell_mixture',
+ ... components=[gfp_gene],
+ ... parameter_file='mixtures/cell_parameters.tsv'
+ ... )
+ >>> crn = mixture.compile_crn()
"""
def __init__(
self, name='', rnap='RNAP', ribosome='Ribo', rnaase='RNAase', **kwargs
):
- """Initializes a TxTlDilutionMixture instance.
-
- :param name: name of the mixture
- :param rnap: name of the RNA polymerase, default: RNAP
- :param ribosome: name of the ribosome, default: Ribo
- :param rnaase: name of the Ribonuclease, default: RNAase
- :param kwargs: keywords passed into the parent Class (Mixture)
- """
Mixture.__init__(self, name=name, **kwargs)
# Create Components for TxTl machinery
@@ -215,4 +651,4 @@ def __init__(
'dilution': dilution_mechanism,
}
- self.add_mechanisms(default_mechanisms)
+ self.add_mechanisms(default_mechanisms, overwrite=None)
diff --git a/biocrnpyler/mixtures/cell_parameters.tsv b/biocrnpyler/mixtures/cell_parameters.tsv
new file mode 100644
index 00000000..139bd141
--- /dev/null
+++ b/biocrnpyler/mixtures/cell_parameters.tsv
@@ -0,0 +1,21 @@
+mechanism part_id param_name value units comments
+gene_expression kexpress 0.28125 The product of the above two rates
+ e coli Ribo 150 uM assuming ~100000 Ribosomes / e. coli with a volume 1 um^3
+ e coli RNAP 15 uM assuming ~10000 RNAP molecules / e. coli with a volume 1 um^3
+ e coli RNAase 45 uM assuming ~30000 RNAP molecules / e. coli with a volume 1 um^4
+ e coli cellular_processes 5 somewhat arbitary concentration for ~3000 genes in e. coli assuming weak loading on all of them
+ e coli extract protein_Ribo 24 1/5 th the Ribosome concentration of E. Coli
+ e coli extract protein_RNAP 3 1/5 th the rnap concentration of E. Coli
+ e coli extract protein_RNAase 6 1/5 th the rnaase concentration of E. Coli
+ e coli extract 2 protein_Ribo 12
+ e coli extract 2 protein_RNAP 6
+ e coli extract 2 protein_RNAase 3
+ ktx 0.05 transcripts / second per polymerase assuming 50nt/s and transcript length of 1000
+ ktl 0.05 proteins / second per ribosome assuming 15aa/s and protein length of 300
+ cooperativity 2 Seems like a good default
+ kb 100 assuming 10ms to diffuse across 1um (characteristic cell size)
+ ku 10 """90% binding"""
+ kdil 0.001 assuming half life of ~20 minutes for everything (e coli doubling time)
+rna_degradation_mm kdeg 1.01 The values from Singhal et al Supplemental Table S2
+rna_degradation_mm kb 1 The values from Singhal et al Supplemental Table S2
+rna_degradation_mm ku 1.26582E-06 The values from Singhal et al Supplemental Table S2
diff --git a/biocrnpyler/mixtures/extract.py b/biocrnpyler/mixtures/extract.py
index 31df2d40..4a54d7a4 100644
--- a/biocrnpyler/mixtures/extract.py
+++ b/biocrnpyler/mixtures/extract.py
@@ -22,19 +22,142 @@
class ExpressionExtract(Mixture):
- """Gene expression without any machinery (ribosomes, polymerases, etc.).
-
- Here transcription and Translation are lumped into one reaction:
- expression.
+ """Gene expression extract without explicit TX-TL machinery.
+
+ A simplified mixture that models gene expression as a single direct
+ reaction from DNA to protein, without explicitly representing
+ transcription and translation as separate processes. This extract lumps
+ transcription and translation into a single 'expression' reaction,
+ eliminating intermediate mRNA species and cellular machinery (ribosomes,
+ polymerases, etc.).
+
+ This extract is appropriate for coarse-grained models where mRNA dynamics
+ are negligible and computational efficiency is prioritized over
+ mechanistic detail.
+
+ Parameters
+ ----------
+ name : str, default=''
+ Name of the mixture for identification and parameter lookup.
+ mechanisms : dict, list, or Mechanism, optional
+ Default mechanisms for components in this mixture. Can be a dict with
+ mechanism types (str) as keys and mechanism objects as values, a
+ list of mechanism objects, or a single `Mechanism`.
+ components : list of Component or Component, optional
+ Components to include in the mixture. Components are deep-copied when
+ added to prevent modification of original objects.
+ parameters : dict, optional
+ Dictionary of parameter values. Keys follow the format
+ (mechanism, part_id, param_name).
+ compartment : Compartment, optional
+ Default compartment for all components and species in this mixture.
+ parameter_file : str, optional
+ Path to a CSV or TSV file containing parameters to load.
+ overwrite_parameters : bool, default=False
+ If True, parameters from file/dict overwrite existing parameters.
+ If False, existing parameters are preserved.
+ global_mechanisms : dict, list, or GlobalMechanism, optional
+ Global mechanisms that apply to all species after component
+ compilation (e.g., dilution, global degradation). Can be a dict,
+ list, or single `GlobalMechanism`.
+ species : list of Species or Species, optional
+ Additional species to add directly to the CRN without going through
+ component compilation.
+ initial_condition_dictionary : dict, optional
+ Dictionary mapping species to initial concentration values. Deprecated
+ in favor of using parameters with mechanism='initial concentration'.
+ global_component_enumerators : list, optional
+ List of global component enumerators for advanced component generation
+ patterns (e.g., creating all pairwise interactions).
+ global_recursion_depth : int, default=4
+ Maximum recursion depth for global component enumeration during
+ compilation.
+ local_recursion_depth : int, optional
+ Maximum recursion depth for local component enumeration. If None,
+ defaults to `global_recursion_depth + 2`.
+
+ Attributes
+ ----------
+ name : str
+ Name of the mixture.
+ compartment : Compartment or None
+ Default compartment for the mixture.
+ components : list of Component
+ List of components in the mixture (deep copies of added components).
+ mechanisms : dict
+ Dictionary of default mechanisms, keyed by mechanism type (str).
+ global_mechanisms : dict
+ Dictionary of global mechanisms, keyed by mechanism type (str).
+ parameter_database : ParameterDatabase
+ Database storing all parameters for this mixture.
+ added_species : list of Species
+ List of species added directly to the mixture.
+ global_component_enumerators : list
+ List of global component enumerators.
+ global_recursion_depth : int
+ Recursion depth for global component enumeration.
+ local_recursion_depth : int
+ Recursion depth for local component enumeration.
+ crn : ChemicalReactionNetwork or None
+ The compiled CRN, created by calling `compile_crn`.
+
+ See Also
+ --------
+ SimpleTxTlExtract : TX-TL with separate transcription and translation.
+ TxTlExtract : TX-TL with explicit machinery.
+ OneStepGeneExpression : Mechanism used for expression.
+ Mixture : Base class for all mixtures.
+
+ Notes
+ -----
+ Default mechanisms included:
+
+ - 'transcription' : `OneStepGeneExpression` - Single-step gene
+ expression (DNA --> DNA + Protein) without intermediate mRNA
+ - 'translation' : `EmptyMechanism` - Dummy mechanism that generates no
+ reactions (translation is disabled)
+ - 'catalysis' : `BasicCatalysis` - Simple catalytic reactions without
+ explicit enzyme binding
+ - 'binding' : `One_Step_Binding` - Simple multi-species binding
+
+ Key features of this extract:
+
+ - No explicit transcription or translation steps
+ - No cellular machinery (RNAP, ribosomes, RNases)
+ - No intermediate mRNA species
+ - Simplified parameter space (single 'kexpress' rate)
+ - Fast compilation and simulation
+
+ When compiled, this extract automatically disables transcript generation
+ in DNA assemblies that produce proteins, routing expression directly from
+ DNA to protein.
+
+ Common applications include:
+
+ - High-level gene circuit modeling
+ - Steady-state or quasi-steady-state analyses
+ - Rapid prototyping of genetic designs
+ - Models where mRNA dynamics are negligible
+
+ Examples
+ --------
+ Create an expression mixture for GFP production:
+
+ >>> gfp_gene = bcp.DNAassembly(
+ ... name='gfp_construct',
+ ... promoter='pconst',
+ ... protein='GFP'
+ ... )
+ >>> mixture = bcp.ExpressionExtract(
+ ... name='expression_mixture',
+ ... components=[gfp_gene],
+ ... parameter_file='mixtures/extract_parameters.tsv'
+ ... )
+ >>> crn = mixture.compile_crn()
"""
def __init__(self, name='', **kwargs):
- """Initializes an ExpressionExtract instance.
-
- :param name: name of the mixture
- :param kwargs: keywords passed into the parent Class (Mixture)
- """
# always call the superlcass Mixture.__init__(...)
Mixture.__init__(self, name=name, **kwargs)
@@ -53,15 +176,43 @@ def __init__(self, name='', **kwargs):
mech_bind.mechanism_type: mech_bind,
}
- self.add_mechanisms(default_mechanisms)
+ self.add_mechanisms(default_mechanisms, overwrite=None)
+
+ def compile_crn(self, **kwargs) -> ChemicalReactionNetwork:
+ """Compile CRN with transcript generation disabled in gene expression.
+
+ Overrides the parent `compile_crn` method to automatically disable
+ transcript generation in DNA assemblies that produce proteins. This
+ ensures that gene expression proceeds directly from DNA to protein
+ without intermediate mRNA species.
- def compile_crn(self, **keywords) -> ChemicalReactionNetwork:
- """Compile CRN, turning off transcription.
+ Parameters
+ ----------
+ **kwargs
+ Additional keyword arguments passed to the parent Mixture
+ `compile_crn `
+ method.
- Overwriting compile_crn to turn off transcription in all
- DNAassemblies.
+ Returns
+ -------
+ ChemicalReactionNetwork
+ Compiled chemical reaction network with expression reactions.
- :return: compiled CRN instance
+ Notes
+ -----
+ This method automatically modifies DNA assemblies before compilation:
+
+ - For assemblies with a protein product, sets transcript to False
+ - RNA-only assemblies (no protein) are not affected
+ - Mechanisms receive protein instead of transcript when transcript
+ is disabled
+
+ This behavior enables the single-step expression mechanism to route
+ production directly to protein.
+
+ See `Mixture.compile_crn
+ ` for a more detailed
+ description of the parent method behavior.
"""
for component in self.components:
@@ -77,23 +228,147 @@ def compile_crn(self, **keywords) -> ChemicalReactionNetwork:
component.update_transcript(False)
# Call the superclass function
- return Mixture.compile_crn(self, **keywords)
+ return Mixture.compile_crn(self, **kwargs)
class SimpleTxTlExtract(Mixture):
- """Transcription and translation in extract w/out any machinery.
-
- Transcriptoin and translation without ribosomes, polymerases,
- etc. RNA is degraded via a global mechanism.
+ """TX-TL extract with simple transcription and translation mechanisms.
+
+ A mixture that models transcription and translation as separate catalytic
+ reactions without explicitly representing cellular machinery (RNAP,
+ ribosomes, RNases). This extract uses simple mass-action kinetics where
+ DNA and mRNA act as catalysts for transcript and protein production,
+ respectively. Unlike `ExpressionExtract`, this mixture includes explicit
+ mRNA species and separate TX-TL steps. Unlike `TxTlExtract`, it does not
+ model enzyme binding or resource competition.
+
+ This extract includes global RNA degradation via dilution.
+
+ Parameters
+ ----------
+ name : str, default=''
+ Name of the mixture for identification and parameter lookup.
+ mechanisms : dict, list, or Mechanism, optional
+ Default mechanisms for components in this mixture. Can be a dict with
+ mechanism types (str) as keys and mechanism objects as values, a
+ list of mechanism objects, or a single `Mechanism`.
+ components : list of Component or Component, optional
+ Components to include in the mixture. Components are deep-copied when
+ added to prevent modification of original objects.
+ parameters : dict, optional
+ Dictionary of parameter values. Keys follow the format
+ (mechanism, part_id, param_name).
+ compartment : Compartment, optional
+ Default compartment for all components and species in this mixture.
+ parameter_file : str, optional
+ Path to a CSV or TSV file containing parameters to load.
+ overwrite_parameters : bool, default=False
+ If True, parameters from file/dict overwrite existing parameters.
+ If False, existing parameters are preserved.
+ global_mechanisms : dict, list, or GlobalMechanism, optional
+ Global mechanisms that apply to all species after component
+ compilation (e.g., dilution, global degradation). Can be a dict,
+ list, or single `GlobalMechanism`.
+ species : list of Species or Species, optional
+ Additional species to add directly to the CRN without going through
+ component compilation.
+ initial_condition_dictionary : dict, optional
+ Dictionary mapping species to initial concentration values. Deprecated
+ in favor of using parameters with mechanism='initial concentration'.
+ global_component_enumerators : list, optional
+ List of global component enumerators for advanced component generation
+ patterns (e.g., creating all pairwise interactions).
+ global_recursion_depth : int, default=4
+ Maximum recursion depth for global component enumeration during
+ compilation.
+ local_recursion_depth : int, optional
+ Maximum recursion depth for local component enumeration. If None,
+ defaults to `global_recursion_depth + 2`.
+
+ Attributes
+ ----------
+ name : str
+ Name of the mixture.
+ compartment : Compartment or None
+ Default compartment for the mixture.
+ components : list of Component
+ List of components in the mixture (deep copies of added components).
+ mechanisms : dict
+ Dictionary of default mechanisms, keyed by mechanism type (str).
+ global_mechanisms : dict
+ Dictionary of global mechanisms, keyed by mechanism type (str).
+ parameter_database : ParameterDatabase
+ Database storing all parameters for this mixture.
+ added_species : list of Species
+ List of species added directly to the mixture.
+ global_component_enumerators : list
+ List of global component enumerators.
+ global_recursion_depth : int
+ Recursion depth for global component enumeration.
+ local_recursion_depth : int
+ Recursion depth for local component enumeration.
+ crn : ChemicalReactionNetwork or None
+ The compiled CRN, created by calling `compile_crn`.
+
+ See Also
+ --------
+ ExpressionExtract : Single-step expression without transcripts.
+ TxTlExtract : TX-TL with explicit machinery.
+ SimpleTranscription : Mechanism used for transcription.
+ SimpleTranslation : Mechanism used for translation.
+ Mixture : Base class for all mixtures.
+
+ Notes
+ -----
+ Default mechanisms included:
+
+ - 'transcription' : `SimpleTranscription` - Simple catalytic
+ transcription (DNA --> DNA + mRNA) without explicit RNAP binding
+ - 'translation' : `SimpleTranslation` - Simple catalytic
+ translation (mRNA --> mRNA + Protein) without explicit ribosome binding
+ - 'rna_degradation' : `Dilution` - Global RNA degradation mechanism
+ (mRNA --> ∅) applied to all RNA species
+ - 'catalysis' : `BasicCatalysis` - Simple catalytic reactions without
+ explicit enzyme binding
+ - 'binding' : `One_Step_Binding` - Simple multi-species binding
+
+ Key features of this extract:
+
+ - Explicit transcription and translation steps
+ - Intermediate mRNA species
+ - Simple mass-action kinetics (no enzyme binding)
+ - No cellular machinery (RNAP, ribosomes)
+ - Global RNA degradation
+ - Faster simulation than Michaelis-Menten models
+
+ Common applications include:
+
+ - Gene circuit modeling with explicit TX-TL
+ - Models where machinery is not limiting
+ - Constitutive or weakly regulated promoters
+ - Rapid prototyping with mRNA dynamics
+
+ Examples
+ --------
+ Create a simple TX-TL mixture for GFP expression:
+
+ >>> gfp_gene = bcp.DNAassembly(
+ ... name='gfp_construct',
+ ... promoter='pconst',
+ ... rbs='bcd2',
+ ... transcript='gfp_mrna',
+ ... protein='GFP'
+ ... )
+ >>> mixture = bcp.SimpleTxTlExtract(
+ ... name='simple_txtl_mixture',
+ ... components=[gfp_gene],
+ ... parameter_file='mixtures/extract_parameters.tsv'
+ ... )
+ >>> crn = mixture.compile_crn()
"""
def __init__(self, name='', **kwargs):
- """Initializes a SimpleTxTlExtract instance.
-
- :param name: name of the mixture
- :param kwargs: keywords passed into the parent Class (Mixture)
- """
# Always call the superlcass Mixture.__init__(...)
Mixture.__init__(self, name=name, **kwargs)
@@ -109,7 +384,7 @@ def __init__(self, name='', **kwargs):
mech_cat.mechanism_type: mech_cat,
mech_bind.mechanism_type: mech_bind,
}
- self.add_mechanisms(default_mechanisms)
+ self.add_mechanisms(default_mechanisms, overwrite=False)
# global mechanisms for dilution and rna degradation
mech_rna_deg_global = Dilution(
@@ -118,31 +393,167 @@ def __init__(self, name='', **kwargs):
default_on=False,
)
global_mechanisms = {'rna_degradation': mech_rna_deg_global}
- self.add_mechanisms(global_mechanisms)
+ self.add_mechanisms(global_mechanisms, overwrite=None)
class TxTlExtract(Mixture):
- """Transcription and translation with expression machinery.
-
- A Model for Transcription and Translation in Cell Extract with
- Ribosomes, Polymerases, and Endonucleases.
-
- This model does not include any energy.
+ """TX-TL extract with explicit transcription and translation machinery.
+
+ A mixture that models transcription and translation with explicit
+ representation of RNA polymerase (RNAP), ribosomes, and RNases. This
+ extract uses Michaelis-Menten kinetics for transcription and translation,
+ explicitly tracking enzyme-substrate binding and catalysis. Unlike
+ `SimpleTxTlExtract`, this mixture models resource competition and enzyme
+ sequestration effects.
+
+ This model does not include explicit energy species. For energy-aware
+ modeling, use `EnergyTxTlExtract`.
+
+ Parameters
+ ----------
+ name : str, default=''
+ Name of the mixture for identification and parameter lookup.
+ rnap : str, default='RNAP'
+ Name for the RNA polymerase protein species.
+ ribosome : str, default='Ribo'
+ Name for the ribosome protein species.
+ rnaase : str, default='RNAase'
+ Name for the ribonuclease protein species.
+ mechanisms : dict, list, or Mechanism, optional
+ Default mechanisms for components in this mixture. Can be a dict with
+ mechanism types (str) as keys and mechanism objects as values, a
+ list of mechanism objects, or a single `Mechanism`.
+ components : list of Component or Component, optional
+ Components to include in the mixture. Components are deep-copied when
+ added to prevent modification of original objects.
+ parameters : dict, optional
+ Dictionary of parameter values. Keys follow the format
+ (mechanism, part_id, param_name).
+ compartment : Compartment, optional
+ Default compartment for all components and species in this mixture.
+ parameter_file : str, optional
+ Path to a CSV or TSV file containing parameters to load.
+ overwrite_parameters : bool, default=False
+ If True, parameters from file/dict overwrite existing parameters.
+ If False, existing parameters are preserved.
+ global_mechanisms : dict, list, or GlobalMechanism, optional
+ Global mechanisms that apply to all species after component
+ compilation (e.g., dilution, global degradation). Can be a dict,
+ list, or single `GlobalMechanism`.
+ species : list of Species or Species, optional
+ Additional species to add directly to the CRN without going through
+ component compilation.
+ initial_condition_dictionary : dict, optional
+ Dictionary mapping species to initial concentration values. Deprecated
+ in favor of using parameters with mechanism='initial concentration'.
+ global_component_enumerators : list, optional
+ List of global component enumerators for advanced component generation
+ patterns (e.g., creating all pairwise interactions).
+ global_recursion_depth : int, default=4
+ Maximum recursion depth for global component enumeration during
+ compilation.
+ local_recursion_depth : int, optional
+ Maximum recursion depth for local component enumeration. If None,
+ defaults to `global_recursion_depth + 2`.
+
+ Attributes
+ ----------
+ name : str
+ Name of the mixture.
+ compartment : Compartment or None
+ Default compartment for the mixture.
+ components : list of Component
+ List of components in the mixture (deep copies of added components).
+ crn : ChemicalReactionNetwork or None
+ The compiled CRN, created by calling `compile_crn`.
+ mechanisms : dict
+ Dictionary of default mechanisms, keyed by mechanism type (str).
+ global_mechanisms : dict
+ Dictionary of global mechanisms, keyed by mechanism type (str).
+ parameter_database : ParameterDatabase
+ Database storing all parameters for this mixture.
+ added_species : list of Species
+ List of species added directly to the mixture.
+ global_component_enumerators : list
+ List of global component enumerators.
+ global_recursion_depth : int
+ Recursion depth for global component enumeration.
+ local_recursion_depth : int
+ Recursion depth for local component enumeration.
+ rnap : Protein
+ RNA polymerase component.
+ ribosome : Protein
+ Ribosome component.
+ rnaase : Protein
+ Ribonuclease component.
+
+ See Also
+ --------
+ SimpleTxTlExtract : TX-TL without explicit machinery.
+ EnergyTxTlExtract : TX-TL with explicit energy consumption.
+ ExpressionExtract : Combined TX-TL without transcripts.
+ Mixture : Base class for all mixtures.
+
+ Notes
+ -----
+ This mixture automatically adds the following components:
+
+ - RNA polymerase (RNAP)
+ - Ribosome
+ - Ribonuclease (RNase)
+
+ Default mechanisms included:
+
+ - 'transcription' : `Transcription_MM` - Michaelis-Menten transcription
+ with explicit RNAP binding (DNA + RNAP <--> DNA:RNAP --> DNA + RNAP +
+ mRNA)
+ - 'translation' : `Translation_MM` - Michaelis-Menten translation with
+ explicit ribosome binding (mRNA + Rib <--> mRNA:Rib --> mRNA + Rib +
+ Protein)
+ - 'rna_degradation' : `Degradation_mRNA_MM` - Global RNA degradation by
+ RNase using Michaelis-Menten kinetics
+ - 'catalysis' : `MichaelisMenten` - General Michaelis-Menten enzyme
+ catalysis
+ - 'binding' : `One_Step_Binding` - Simple multi-species binding
+
+ Key features of this mixture:
+
+ - Explicit modeling of transcription and translation machinery
+ - Resource competition effects (multiple genes compete for RNAP)
+ - Enzyme sequestration in complexes
+ - RNA degradation dynamics
+ - Suitable for modeling TX-TL systems with limited machinery
+
+ Common applications include:
+
+ - Cell-free TX-TL systems
+ - Resource allocation in gene circuits
+ - Gene expression burden studies
+ - Synthetic biology prototyping
+
+ Examples
+ --------
+ Create a TX-TL mixture for GFP expression:
+
+ >>> gfp_gene = bcp.DNAassembly(
+ ... name='gfp_construct',
+ ... promoter='pconst',
+ ... rbs='bcd2',
+ ... transcript='gfp_mrna',
+ ... protein='GFP'
+ ... )
+ >>> mixture = bcp.TxTlExtract(
+ ... name='txtl_mixture',
+ ... components=[gfp_gene],
+ ... parameter_file='mixtures/extract_parameters.tsv'
+ ... )
+ >>> crn = mixture.compile_crn()
"""
def __init__(
self, name='', rnap='RNAP', ribosome='Ribo', rnaase='RNAase', **kwargs
):
- """Initializes a TxTlExtract instance.
-
- :param name: name of the mixture
- :param rnap: name of the RNA polymerase, default: RNAP
- :param ribosome: name of the ribosome, default: Ribo
- :param rnaase: name of the Ribonuclease, default: RNAase
- :param kwargs: keywords passed into the parent Class (Mixture)
-
- """
# Always call the superlcass Mixture.__init__(...)
Mixture.__init__(self, name=name, **kwargs)
@@ -168,19 +579,195 @@ def __init__(
mech_cat.mechanism_type: mech_cat,
mech_bind.mechanism_type: mech_bind,
}
- self.add_mechanisms(default_mechanisms)
+ self.add_mechanisms(default_mechanisms, overwrite=None)
class EnergyTxTlExtract(Mixture):
- """Transcription and translation in extract with machinery, energy.
-
- This model include energy carrier molcules in the form of NTPs,
- Amino Acids, and a Fuel Species (such as 3PGA) used for NTP
- regeneration. This model is equivalent to TxTl extract, but with
- limited fuel. Note that different amino acids and nucleotides are
- lumped together.
-
- Energy usage for transcription and translation is length dependent.
+ """TX-TL cell extract with explicit machinery and energy consumption.
+
+ A mixture that models transcription and translation with explicit
+ representation of RNA polymerase (RNAP), ribosomes, RNases, and energy
+ carrier molecules. This extract uses Michaelis-Menten kinetics with
+ length-dependent fuel consumption to model realistic TX-TL energetics.
+ Unlike `TxTlExtract`, this mixture explicitly tracks NTPs, amino acids,
+ and fuel species (e.g., 3PGA for NTP regeneration).
+
+ Energy usage for transcription and translation is length-dependent,
+ reflecting the stoichiometric consumption of NTPs and amino acids during
+ biopolymer synthesis.
+
+ Parameters
+ ----------
+ name : str, default=''
+ Name of the mixture for identification and parameter lookup.
+ rnap : str, default='RNAP'
+ Name for the RNA polymerase protein species.
+ ribosome : str, default='Ribo'
+ Name for the ribosome protein species.
+ rnaase : str, default='RNAase'
+ Name for the ribonuclease protein species.
+ ntps : str, default='NTPs'
+ Name for the nucleotide triphosphate species (lumped NTPs).
+ ndps : str, default='NDPs'
+ Name for the nucleotide diphosphate species (lumped NDPs).
+ amino_acids : str, default='amino_acids'
+ Name for the amino acid species (lumped amino acids).
+ fuel : str, default='Fuel_3PGA'
+ Name for the fuel species used for NTP regeneration (e.g., 3PGA).
+ mechanisms : dict, list, or Mechanism, optional
+ Default mechanisms for components in this mixture. Can be a dict with
+ mechanism types (str) as keys and mechanism objects as values, a
+ list of mechanism objects, or a single `Mechanism`.
+ components : list of Component or Component, optional
+ Components to include in the mixture. Components are deep-copied when
+ added to prevent modification of original objects.
+ parameters : dict, optional
+ Dictionary of parameter values. Keys follow the format
+ (mechanism, part_id, param_name).
+ compartment : Compartment, optional
+ Default compartment for all components and species in this mixture.
+ parameter_file : str, optional
+ Path to a CSV or TSV file containing parameters to load.
+ overwrite_parameters : bool, default=False
+ If True, parameters from file/dict overwrite existing parameters.
+ If False, existing parameters are preserved.
+ global_mechanisms : dict, list, or GlobalMechanism, optional
+ Global mechanisms that apply to all species after component
+ compilation (e.g., dilution, global degradation). Can be a dict,
+ list, or single `GlobalMechanism`.
+ species : list of Species or Species, optional
+ Additional species to add directly to the CRN without going through
+ component compilation.
+ initial_condition_dictionary : dict, optional
+ Dictionary mapping species to initial concentration values. Deprecated
+ in favor of using parameters with mechanism='initial concentration'.
+ global_component_enumerators : list, optional
+ List of global component enumerators for advanced component generation
+ patterns (e.g., creating all pairwise interactions).
+ global_recursion_depth : int, default=4
+ Maximum recursion depth for global component enumeration during
+ compilation.
+ local_recursion_depth : int, optional
+ Maximum recursion depth for local component enumeration. If None,
+ defaults to `global_recursion_depth + 2`.
+
+ Attributes
+ ----------
+ name : str
+ Name of the mixture.
+ rnap : Protein
+ RNA polymerase component.
+ ribosome : Protein
+ Ribosome component.
+ rnaase : Protein
+ Ribonuclease component.
+ amino_acids : Metabolite
+ Amino acid metabolite component.
+ fuel : Metabolite
+ Fuel metabolite component for ATP regeneration.
+ ndps : Metabolite
+ Nucleotide diphosphate metabolite component.
+ ntps : Metabolite
+ Nucleotide triphosphate metabolite component with fuel-dependent
+ regeneration.
+ compartment : Compartment or None
+ Default compartment for the mixture.
+ components : list of Component
+ List of components in the mixture (deep copies of added components).
+ mechanisms : dict
+ Dictionary of default mechanisms, keyed by mechanism type (str).
+ global_mechanisms : dict
+ Dictionary of global mechanisms, keyed by mechanism type (str).
+ parameter_database : ParameterDatabase
+ Database storing all parameters for this mixture.
+ added_species : list of Species
+ List of species added directly to the mixture.
+ global_component_enumerators : list
+ List of global component enumerators.
+ global_recursion_depth : int
+ Recursion depth for global component enumeration.
+ local_recursion_depth : int
+ Recursion depth for local component enumeration.
+ crn : ChemicalReactionNetwork or None
+ The compiled CRN, created by calling `compile_crn`.
+
+ See Also
+ --------
+ TxTlExtract : TX-TL with machinery but no energy.
+ SimpleTxTlExtract : TX-TL without machinery or energy.
+ Energy_Transcription_MM : Mechanism for energy-consuming transcription.
+ Energy_Translation_MM : Mechanism for energy-consuming translation.
+ Mixture : Base class for all mixtures.
+
+ Notes
+ -----
+ This mixture automatically adds the following components:
+
+ - RNA polymerase (RNAP)
+ - Ribosome
+ - Ribonuclease (RNase)
+ - Amino acids (lumped)
+ - NTPs (nucleotide triphosphates, lumped)
+ - NDPs (nucleotide diphosphates, lumped)
+ - Fuel (e.g., 3PGA for ATP regeneration)
+
+ Default mechanisms included:
+
+ - 'transcription' : `Energy_Transcription_MM` - Michaelis-Menten
+ transcription with length-dependent NTP consumption (DNA + RNAP <-->
+ DNA:RNAP; NTP + DNA:RNAP --> DNA + RNAP + mRNA + NDP)
+ - 'translation' : `Energy_Translation_MM` - Michaelis-Menten translation
+ with length-dependent amino acid and NTP consumption (mRNA + Rib <-->
+ mRNA:Rib; AA + NTP + mRNA:Rib --> mRNA + Rib + Protein + NDP)
+ - 'rna_degradation' : `Degradation_mRNA_MM` - Global RNA degradation by
+ RNase using Michaelis-Menten kinetics
+ - 'catalysis' : `MichaelisMenten` - General Michaelis-Menten enzyme
+ catalysis
+ - 'binding' : `One_Step_Binding` - Simple multi-species binding
+ - 'pathway' : `OneStepPathway` - Metabolite conversion (added to NTPs
+ and fuel components)
+
+ Key features of this mixture:
+
+ - Explicit modeling of transcription and translation machinery
+ - Length-dependent energy consumption
+ - NTP regeneration from fuel species
+ - Resource competition and depletion effects
+ - Realistic modeling of TX-TL resource limits
+ - Energy-dependent expression dynamics
+
+ Energy model details:
+
+ - Transcription consumes L NTPs per mRNA of length L
+ - Translation consumes L amino acids and 4L NTPs per protein of length L
+ - Fuel species regenerates NTPs from NDPs
+ - Different nucleotides and amino acids are lumped together
+
+ Common applications include:
+
+ - Cell-free TX-TL systems with limited resources
+ - Models of energy-limited gene expression
+ - Resource allocation and burden studies
+ - TX-TL system optimization
+ - Metabolic coupling with gene expression
+
+ Examples
+ --------
+ Create an energy-aware TX-TL mixture for GFP expression:
+
+ >>> gfp_gene = bcp.DNAassembly(
+ ... name='gfp_construct',
+ ... promoter='pconst',
+ ... rbs='bcd2',
+ ... transcript='gfp_mrna',
+ ... protein='GFP'
+ ... )
+ >>> mixture = bcp.EnergyTxTlExtract(
+ ... name='energy_txtl_mixture',
+ ... components=[gfp_gene],
+ ... parameter_file='mixtures/extract_parameters.tsv'
+ ... )
+ >>> crn = mixture.compile_crn()
"""
@@ -196,20 +783,6 @@ def __init__(
fuel='Fuel_3PGA',
**kwargs,
):
- """Initailize the TX-TL mixture.
-
- :param name: name of the mixture
- :param rnap: name of the RNA polymerase, default: RNAP
- :param ribosome: name of the ribosome, default: Ribo
- :param rnaase: name of the Ribonuclease, default: RNAase
- :param ntps: name of the nucleotide fuel source (eg ATP + GTP etc),
- default: NTP
- :param amino_acids: name of the amino acids species, default:
- amino_acids
- :param fuel: name of the fuel species that regenerates ATP
- :param kwargs: keywords passed into the parent Class (Mixture)
-
- """
Mixture.__init__(self, name=name, **kwargs)
# create default Components to represent cellular machinery
@@ -227,8 +800,8 @@ def __init__(
# These mechanisms are Component specific and only added to
# the NTPs metabolite
mech_pathway = OneStepPathway()
- self.ntps.add_mechanisms(mech_pathway)
- self.fuel.add_mechanisms(mech_pathway)
+ self.ntps.add_mechanisms(mech_pathway, overwrite=None)
+ self.fuel.add_mechanisms(mech_pathway, overwrite=None)
default_components = [
self.rnap,
@@ -263,4 +836,4 @@ def __init__(
mech_cat.mechanism_type: mech_cat,
mech_bind.mechanism_type: mech_bind,
}
- self.add_mechanisms(default_mechanisms)
+ self.add_mechanisms(default_mechanisms, overwrite=None)
diff --git a/biocrnpyler/mixtures/extract_parameters.tsv b/biocrnpyler/mixtures/extract_parameters.tsv
index 28cc4b07..9609316c 100644
--- a/biocrnpyler/mixtures/extract_parameters.tsv
+++ b/biocrnpyler/mixtures/extract_parameters.tsv
@@ -1,20 +1,26 @@
-mechanism part_id param_name value units comments
-energy_transcription_mm ktx 3.25 Tx_cat from Singhal et al.
-energy_transcription_mm kb 4.48 For the promoter tested in Singhal et al Supplemental Table S2
-energy_transcription_mm ku 2.48889E-06 For the promoter tested in Singhal et al Supplemental Table S2
-energy_transcription_mm length 300 "This is a default length, gene specific ones should be set"
-energy_translation_mm ktl 19.2 TL_cat from Singhal et al.
-energy_translation_mm kb 0.819 For the RBS tested in Singhal et al Supplemental Table S2
-energy_translation_mm ku 0.002853659 For the RBS tested in Singhal et al Supplemental Table S2
-energy_translation_mm length 100 "This is a default length, transcript specific ones should be set"
-rna_degradation_mm kdeg 1.01 The values from Singhal et al Supplemental Table S2
-rna_degradation_mm kb 1 The values from Singhal et al Supplemental Table S2
-rna_degradation_mm ku 1.26582E-06 The values from Singhal et al Supplemental Table S2
-one_step_pathway NTPs_production k 0.02 alpha_atp fron Singhal et al.
-one_step_pathway NTPs_degradation k 0.0000177 Delta_ATP from Singhal et a.
-initial concentration NTPs 5 mM (total NTPs in standard energy buffer)
-initial concentration amino_acids 30 mM mM (total amino acids in standard energy buffer)
-initial concentration Fuel_3PGA 30 mM mM (standard energy buffer amount)
-initial concentration RNAase 20.2 The values from Singhal et al Supplemental Table S2
-initial concentration protein_Ribo 0.0273 The values from Singhal et al Supplemental Table S2
-initial concentration RNAP 0.00933 The values from Singhal et al Supplemental Table S2
+mechanism part_id param_name value units comments
+gene_expression kexpress 0.28125 The product of the above two rates
+ ktx 0.05 transcripts / second per polymerase assuming 50nt/s and transcript length of 1000
+ ktl 0.05 proteins / second per ribosome assuming 15aa/s and protein length of 300
+ kb 100 assuming 10ms to diffuse across 1um (characteristic cell size)
+ ku 10 90% binding
+ kdil 0.001 assuming half life of ~20 minutes for everything (e coli doubling time)
+energy_transcription_mm ktx 3.25 Tx_cat from Singhal et al.
+energy_transcription_mm kb 4.48 For the promoter tested in Singhal et al Supplemental Table S2
+energy_transcription_mm ku 2.48889E-06 For the promoter tested in Singhal et al Supplemental Table S2
+energy_transcription_mm length 300 "This is a default length, gene specific ones should be set"
+energy_translation_mm ktl 19.2 TL_cat from Singhal et al.
+energy_translation_mm kb 0.819 For the RBS tested in Singhal et al Supplemental Table S2
+energy_translation_mm ku 0.002853659 For the RBS tested in Singhal et al Supplemental Table S2
+energy_translation_mm length 100 "This is a default length, transcript specific ones should be set"
+rna_degradation_mm kdeg 1.01 The values from Singhal et al Supplemental Table S2
+rna_degradation_mm kb 1 The values from Singhal et al Supplemental Table S2
+rna_degradation_mm ku 1.26582E-06 The values from Singhal et al Supplemental Table S2
+one_step_pathway NTPs_production k 0.02 alpha_atp fron Singhal et al.
+one_step_pathway NTPs_degradation k 0.0000177 Delta_ATP from Singhal et a.
+initial concentration NTPs 5 mM (total NTPs in standard energy buffer)
+initial concentration amino_acids 30 mM mM (total amino acids in standard energy buffer)
+initial concentration Fuel_3PGA 30 mM mM (standard energy buffer amount)
+initial concentration RNAase 20.2 The values from Singhal et al Supplemental Table S2
+initial concentration protein_Ribo 0.0273 The values from Singhal et al Supplemental Table S2
+initial concentration RNAP 0.00933 The values from Singhal et al Supplemental Table S2
diff --git a/biocrnpyler/mixtures/pure.py b/biocrnpyler/mixtures/pure.py
index 3d57924b..fc666e25 100644
--- a/biocrnpyler/mixtures/pure.py
+++ b/biocrnpyler/mixtures/pure.py
@@ -10,18 +10,166 @@
class BasicPURE(Mixture):
- """Reconstituted protein synthesis system with resource limits.
+ """PURE cell-free protein synthesis system with energy consumption.
- This model includes energy carrier molecules in the form of NTPs, amino
- acids, and a fuel species (such as ATP) used for transcription,
- translation, and other core mechanisms. This model is equivalent to
- `EnergyTxTlExtract`, but without a fuel generation mechanism. Amino
- acids and nucleotides are lumped together into a single meta-species.
+ A mixture that models the PURE (Protein synthesis Using Recombinant
+ Elements) reconstituted cell-free transcription-translation system with
+ explicit representation of RNA polymerase (RNAP), ribosomes, RNases, and
+ energy carrier molecules. This extract uses Michaelis-Menten kinetics
+ with length-dependent fuel consumption to model realistic TX-TL
+ energetics.
- Note that fuel is modeled as a separate molecule so if the default
- 'ATP' is used, it is separate from the other nucleotides ('NTPs').
+ Unlike `EnergyTxTlExtract`, this mixture does not include fuel
+ regeneration mechanisms. Energy carriers (ATP, NTPs, amino acids) are
+ consumed but not regenerated, making this suitable for modeling
+ resource-limited PURE systems. Different amino acids and nucleotides are
+ lumped into single meta-species for simplicity.
- Energy usage for transcription and translation is length dependent.
+ Note that fuel (default 'ATP') is modeled as a separate molecule from
+ other nucleotides ('NTPs'), allowing independent tracking of energy
+ consumption.
+
+ Energy usage for transcription and translation is length-dependent,
+ reflecting stoichiometric consumption during biopolymer synthesis.
+
+ Parameters
+ ----------
+ name : str, default='PURE'
+ Name identifier for the mixture.
+ rnap : str, default='RNAP'
+ Name for the RNA polymerase protein species.
+ ribosome : str, default='Ribo'
+ Name for the ribosome protein species.
+ rnaase : str, default='RNAase'
+ Name for the ribonuclease protein species.
+ ntps : str, default='NTPs'
+ Name for the nucleotide triphosphate species (lumped NTPs excluding
+ ATP).
+ ndps : str, default='NDPs'
+ Name for the nucleotide diphosphate species (lumped NDPs).
+ amino_acids : str, default='AAs'
+ Name for the amino acid species (lumped amino acids).
+ fuel : str, default='ATP'
+ Name for the primary energy carrier species (ATP).
+ parameter_file : str, default='mixtures/pure_parameters.tsv'
+ Path to file containing default parameter values for the PURE
+ system.
+ **kwargs
+ Additional keyword arguments passed to the parent Mixture class.
+
+ Attributes
+ ----------
+ rnap : Protein
+ RNA polymerase component.
+ ribosome : Protein
+ Ribosome component.
+ rnaase : Protein
+ Ribonuclease component.
+ ntps : Metabolite
+ Nucleotide triphosphate metabolite component (excluding ATP).
+ amino_acids : Metabolite
+ Amino acid metabolite component.
+ fuel : Metabolite
+ Fuel metabolite component (ATP).
+ name : str
+ Name of the mixture.
+
+ See Also
+ --------
+ EnergyTxTlExtract : TX-TL with fuel regeneration.
+ TxTlExtract : TX-TL with machinery but no energy.
+ Energy_Transcription_MM : Mechanism for energy-consuming transcription.
+ Energy_Translation_MM : Mechanism for energy-consuming translation.
+ Mixture : Base class for all mixtures.
+
+ Notes
+ -----
+ This mixture automatically adds the following components:
+
+ - RNA polymerase (RNAP)
+ - Ribosome
+ - Ribonuclease (RNase)
+ - Amino acids (lumped)
+ - NTPs (nucleotide triphosphates excluding ATP, lumped)
+ - NDPs (nucleotide diphosphates, lumped)
+ - Fuel (ATP for energy)
+
+ Default mechanisms included:
+
+ - 'transcription' : `Energy_Transcription_MM` - Michaelis-Menten
+ transcription with length-dependent ATP and NTP consumption
+ - 'translation' : `Energy_Translation_MM` - Michaelis-Menten translation
+ with length-dependent amino acid and ATP consumption
+ - 'rna_degradation' : `Degradation_mRNA_MM` - Global RNA degradation by
+ RNase using Michaelis-Menten kinetics
+ - 'catalysis' : `MichaelisMenten` - General Michaelis-Menten enzyme
+ catalysis for user-defined enzymatic reactions
+ - 'binding' : `One_Step_Binding` - Simple multi-species binding for
+ forming complexes
+
+ Key features of this mixture:
+
+ - Explicit modeling of PURE system components
+ - Length-dependent energy consumption (realistic stoichiometry)
+ - No fuel regeneration mechanisms (finite resource pool)
+ - Resource competition effects (genes compete for RNAP and ribosomes)
+ - Resource depletion dynamics (ATP, NTPs, amino acids deplete)
+ - Enzyme sequestration in complexes
+ - RNA degradation by RNase
+ - Separate tracking of ATP vs other NTPs
+ - Suitable for modeling batch-mode PURE reactions
+
+ Energy model details:
+
+ - Transcription: Consumes L NTPs and L ATPs per mRNA of length L
+ - Translation: Consumes L amino acids and 4L ATPs per protein of length
+ L (4 ATPs per amino acid reflect GTP hydrolysis during elongation)
+ - No regeneration: ATP, NTPs, and amino acids are consumed but not
+ regenerated
+ - Energy depletion: Expression stops when resources are exhausted
+ - Length parameter L: Represents gene/protein length in appropriate
+ units
+ - Lumped species: Different nucleotides lumped into NTPs, different
+ amino acids lumped into single species
+ - Separate ATP: ATP tracked separately from other NTPs for independent
+ energy accounting
+
+ Differences from `EnergyTxTlExtract`:
+
+ - No fuel regeneration pathway (no NTP regeneration from 3PGA or other
+ fuel sources)
+ - ATP modeled as separate fuel species rather than included in NTPs
+ - Default parameter file points to PURE-specific parameters
+ - Intended for modeling finite-resource batch reactions
+ - More realistic for in vitro PURE systems
+
+ Common applications include:
+
+ - PURE cell-free TX-TL systems
+ - Resource-limited gene expression modeling
+ - TX-TL system optimization with fixed resource budgets
+ - Batch mode TX-TL reactions
+ - Energy budget and resource allocation studies
+ - Multi-gene expression burden analysis
+ - In vitro synthetic biology applications
+
+ Examples
+ --------
+ Create a PURE mixture for GFP expression:
+
+ >>> gfp_gene = bcp.DNAassembly(
+ ... name='gfp_construct',
+ ... promoter='pconst',
+ ... rbs='bcd2',
+ ... transcript='gfp_mrna',
+ ... protein='GFP'
+ ... )
+ >>> mixture = bcp.BasicPURE(
+ ... name='pure_mixture',
+ ... components=[gfp_gene],
+ ... parameter_file='mixtures/pure_parameters.tsv'
+ ... )
+ >>> crn = mixture.compile_crn()
"""
@@ -38,22 +186,6 @@ def __init__(
parameter_file='mixtures/pure_parameters.tsv',
**kwargs,
):
- """Initialize the PURE mixture.
-
- :param name: name of the mixture
- :param rnap: name of the RNA polymerase, default: RNAP
- :param ribosome: name of the ribosome, default: Ribo
- :param rnaase: name of the Ribonuclease, default: RNAase
- :param ntps: name of the nucleotide fuel source (eg ATP + GTP etc),
- default: NTP
- :param amino_acids: name of the amino acids species, default:
- amino_acids
- :param fuel: name of the energy carrier species
- :param parameter_file: file containing default parameter values
- :param parameter: dictionary with parameter values
- :param kwargs: keywords passed into the parent Class (Mixture)
-
- """
Mixture.__init__(
self, name=name, parameter_file=parameter_file, **kwargs
)
@@ -102,4 +234,4 @@ def __init__(
mech_cat.mechanism_type: mech_cat,
mech_bind.mechanism_type: mech_bind,
}
- self.add_mechanisms(default_mechanisms)
+ self.add_mechanisms(default_mechanisms, overwrite=None)
diff --git a/biocrnpyler/utils/fileutil.py b/biocrnpyler/utils/fileutil.py
index 0530495d..d873c1c9 100644
--- a/biocrnpyler/utils/fileutil.py
+++ b/biocrnpyler/utils/fileutil.py
@@ -1,5 +1,7 @@
-# findfile.py - find biocrnypyler file
+# fileutil.py - file utilities for biocrnpyler
# RMM (via Claude), 19 Sep 2025
+#
+# Utilities for finding and managing files in the biocrnpyler package.
import os
from pathlib import Path
@@ -11,28 +13,45 @@ def find_file_in_bcp_path(
) -> Optional[str]:
"""Find a file by searching through biocrnpyler paths.
- Search order:
- 1. Current directory
- 2. Directories specified in environment variable (default: BCP_PATH)
- 3. biocrnpyler package's 'defaults' subdirectory
-
- Args:
- filename (str): Name of file to find
- env_var_name (str): Environment variable containing search paths
-
- Returns:
- str: Full path to file if found, None if not found
-
- Example:
- >>> # Set environment variable (in shell or programmatically)
- >>> os.environ['BCP_PATH'] = '/path/to/models:/path/to/configs'
- >>>
- >>> # Find a file
- >>> filepath = find_file_in_bcp_path('model.xml')
- >>> if filepath:
- >>> print(f"Found file at: {filepath}")
- >>> else:
- >>> print("File not found")
+ Searches for a file in multiple locations in the following order:
+
+ 1. Current working directory
+ 2. Directories specified in environment variable (default: 'BCP_PATH')
+ 3. biocrnpyler package directory
+
+ The environment variable should contain a colon-separated (Unix) or
+ semicolon-separated (Windows) list of directory paths.
+
+ Parameters
+ ----------
+ filename : str
+ Name of file to find.
+ env_var_name : str, default='BCP_PATH'
+ Name of environment variable containing search paths.
+
+ Returns
+ -------
+ str or None
+ Full path to file if found, None otherwise.
+
+ Examples
+ --------
+ Set environment variable and find a file:
+
+ >>> import os
+ >>> os.environ['BCP_PATH'] = '/path/to/models:/path/to/configs'
+ >>> filepath = find_file_in_bcp_path('model.xml')
+ >>> if filepath:
+ ... print(f'Found file at: {filepath}')
+ ... else:
+ ... print('File not found')
+ Found file at: /path/to/models/model.xml
+
+ Search with a custom environment variable:
+
+ >>> filepath = find_file_in_bcp_path(
+ ... 'parameters.csv', env_var_name='MY_PARAMS_PATH')
+
"""
search_paths = []
diff --git a/biocrnpyler/utils/general.py b/biocrnpyler/utils/general.py
index ee364fb5..9b8eda4a 100644
--- a/biocrnpyler/utils/general.py
+++ b/biocrnpyler/utils/general.py
@@ -34,7 +34,7 @@ def recursive_parent(s):
def remove_bindloc(spec_list):
- """Go through every species on a list and remove any "bindloc" attributes.
+ """Go through every species on a list and remove any 'bindloc' attributes.
This is used to convert monomers with a parent polymer into the
correct species after combinatorial binding in things like
@@ -82,6 +82,7 @@ def combine_dictionaries(dict1, dict2):
"""Append lists that share the same key, and add new keys.
WARNING: this only works if the dictionaries have values that are lists.
+
"""
outdict = dict1
for key in dict2:
@@ -97,7 +98,10 @@ def combine_dictionaries(dict1, dict2):
def member_dictionary_search(member, dictionary):
"""Searches dictionary for keys relevant to the given data member.
+ Notes
+ -----
Order of returning:
+
repr
name
material_type
diff --git a/biocrnpyler/utils/plotting.py b/biocrnpyler/utils/plotting.py
index 6d12a703..64d5f4ac 100644
--- a/biocrnpyler/utils/plotting.py
+++ b/biocrnpyler/utils/plotting.py
@@ -90,7 +90,7 @@ def makeArrows2(
headangle=math.pi / 6,
make_arrows=True,
):
- """This function draws an arrow shape at the end of graph lines."""
+ """Draw an arrow shape at the end of graph lines."""
xs, ys = [], []
xbounds = [0, 0]
ybounds = [0, 0]
@@ -158,25 +158,32 @@ def graphPlot(
rseed=30,
show_species_images=False,
):
- """Given a directed graph, plot it!
-
- Inputs:
- DG: a directed graph of type DiGraph
- DGspecies: a directed graph which only contains the species nodes
- DGreactions: a directed graph which only contains the reaction nodes
- plot: a bokeh plot object
- layout: graph layout function.
- 'force' uses fa2 to push nodes apart
+ """Given a directed graph, plot it.
+
+ Parameters
+ ----------
+ DG : DiGraph
+ A directed graph of type DiGraph.
+ DGspecies : DiGraph
+ A directed graph which only contains the species nodes.
+ DGreactions : DiGraph
+ A directed graph which only contains the reaction nodes.
+ plot : bokeh plot object
+ A bokeh plot object.
+ layout : str
+ Graph layout function. 'force' uses fa2 to push nodes apart.
'circle' plots the nodes and reactions in two overlapping
- circles, with the reactions on the inside of the circle
- 'custom' allows user input "layoutfunc". Internally, layoutfunc
- is passed the three inputs (DG, DGspecies, DGreactions) and
- should output a position dictionary with node
- {:(x,y)}
- positions: a dictionary of node names and x,y positions. this gets
- passed into the layout function
- posscale: multiply the scaling of the plot. This only affects the arrows
- because the arrows are a hack :(.
+ circles, with the reactions on the inside of the circle.
+ 'custom' allows user input 'layoutfunc'. Internally,
+ 'layoutfunc' is passed the three inputs (DG, DGspecies,
+ DGreactions) and should output a position dictionary with node
+ {:(x,y)}.
+ positions : dict
+ A dictionary of node names and x,y positions. This gets passed
+ into the layout function.
+ posscale : float
+ Multiply the scaling of the plot. This only affects the arrows
+ because the arrows are a hack :(.
"""
random.seed(rseed)
@@ -361,19 +368,25 @@ def generate_networkx_graph(
):
"""Generates a networkx DiGraph object that represents the CRN.
- input:
- ==========================
- CRN: a CRN from mixture.get_model() for example
- useweights: this will attempt to represent the reaction rates by the
- length of edges. short edges are fast rates. It doesn't look
- very good usually.
- use_pretty_print: this uses the "pretty print" function to represent
- reactions and nodes a bit cleaner
- pp_show_material: default false because this is listed in "type"
- pp_show_rates: default true because this is useful information
- pp_show_attributes
- colordict: a dictionary containing which node types are what color
- based upon the following keywords:
+ Parameters
+ ----------
+ CRN : ChemicalReactionNetwork
+ A CRN from mixture.get_model() for example.
+ useweights : bool
+ This will attempt to represent the reaction rates by the length
+ of edges. Short edges are fast rates. It doesn't look very good
+ usually.
+ use_pretty_print : bool
+ This uses the "pretty print" function to represent reactions
+ and nodes a bit cleaner.
+ pp_show_material : bool
+ Default false because this is listed in "type".
+ pp_show_rates : bool
+ Default true because this is useful information.
+ pp_show_attributes : bool
+ colordict : dict
+ A dictionary containing which node types are what color based
+ upon the following keywords:
Keywords are chosen to match species.material_type
{"complex": "cyan",
@@ -384,24 +397,27 @@ def generate_networkx_graph(
"phosphate": "yellow",
"nothing":"purple"}
- When using a custom colordict, the following attributes will be checked to
- find colors with the first keys taking precedence:
+ When using a custom colordict, the following attributes will be
+ checked to find colors with the first keys taking precedence:
repr(species): "color"
species.name: "color"
(species.material_type, tuple(species.attributes)): "color"
species.material_type: "color"
tuple(species.attributes): "color"
-
- imagedict is a dictionary which contains species and their corresponding
+ imagedict : dict
+ A dictionary which contains species and their corresponding
image representations. This is the output generated by
- CRNPlotter.renderMixture()
+ CRNPlotter.renderMixture().
- output:
- ==================
- CRNgraph: the DiGraph object containing all nodes and edges
- CRNspeciesonly: a DiGraph object with only species
- CRNreactionsonly: a DiGraph object with only reactions
+ Returns
+ -------
+ CRNgraph : DiGraph
+ The DiGraph object containing all nodes and edges.
+ CRNspeciesonly : DiGraph
+ A DiGraph object with only species.
+ CRNreactionsonly : DiGraph
+ A DiGraph object with only reactions.
"""
if not PLOT_NETWORK:
@@ -598,8 +614,8 @@ def get_directed(self, direction, bound=None, non_binder=None):
"""Copy of self with direction changed.
In the case of MultiPart it also means reversing the order of the
- subparts. A MultiPart binds to things differently from a normal
- part. the binding is distributed among the subparts. "non_binder"
+ subparts. A MultiPart binds to things differently from a normal
+ part. The binding is distributed among the subparts. 'non_binder'
indicates a dpl_type which should not be shown binding to things.
"""
@@ -675,7 +691,7 @@ def __init__(
label_size=13,
added_opts=None,
):
- """Simplified DNAconstruct with only plotting information."""
+ """Simplified DNA_construct with only plotting information."""
self.name = name
self.parts_list = parts_list
self.circular = circular
@@ -695,9 +711,9 @@ def get_dpl(self):
return outlist
def get_dpl_binders(self):
- """Output a dnaplotlib dictionary list to represent the "binders".
+ """Output a dnaplotlib dictionary list to represent 'binders'.
- Binders are "regulation" arcs modified to draw a SBOL glyph
+ Binders are 'regulation' arcs modified to draw a SBOL glyph
instead of a line.
"""
@@ -1297,10 +1313,10 @@ def render_network_bokeh(
species_glyph_size=12,
reaction_glyph_size=8,
export_name=None,
- **keywords,
+ **kwargs,
):
DG, DGspec, DGrxn = generate_networkx_graph(
- CRN, **keywords
+ CRN, **kwargs
) # this creates the networkx objects
plot = Plot(
width=500,
@@ -1309,7 +1325,7 @@ def render_network_bokeh(
y_range=Range1d(-500, 500),
) # this generates a
show_im = False
- if 'imagedict' in keywords and keywords['imagedict'] is not None:
+ if 'imagedict' in kwargs and kwargs['imagedict'] is not None:
show_im = True
if export:
plot.output_backend = 'svg'
diff --git a/biocrnpyler/utils/sbmlutil.py b/biocrnpyler/utils/sbmlutil.py
index 06a6b16b..d53fe198 100644
--- a/biocrnpyler/utils/sbmlutil.py
+++ b/biocrnpyler/utils/sbmlutil.py
@@ -35,16 +35,24 @@ def create_sbml_model(
"""Creates SBML Level 3 Version 2 model with some fixed standard settings.
Refer to python-libsbml for more information on SBML API.
- :param compartment_id:
- :param time_units:
- :param extent_units:
- :param substance_units:
- :param length_units:
- :param area_units:
- :param volume_units:
- :param volume:
- :param model_id:
- :return: the SBMLDocument and the Model object as a tuple
+
+ Parameters
+ ----------
+ compartment_id : str
+ time_units : str
+ extent_units : str
+ substance_units : str
+ length_units : str
+ area_units : str
+ volume_units : str
+ volume : float
+ model_id : str
+
+ Returns
+ -------
+ tuple
+ The SBMLDocument and the Model object as a tuple.
+
"""
document = libsbml.SBMLDocument(3, 2)
model = document.createModel()
@@ -92,13 +100,17 @@ def add_all_species(
):
"""Adds a list of Species to the SBML model.
- :param model: valid SBML model
- :param species: list of species to be added to the SBML model
- :param compartment: compartment id, if empty species go to the first
- compartment
- :param initial_concentration_dict: a dictionary s -->
- initial_concentration
- :return: None
+ Parameters
+ ----------
+ model : libsbml.Model
+ Valid SBML model.
+ species : list
+ List of species to be added to the SBML model.
+ initial_condition_dictionary : dict
+ A dictionary s --> initial_concentration.
+ compartment : str, optional
+ Compartment id, if empty species go to the first compartment.
+
"""
elementlist = model.getSBMLDocument().getListOfAllElements()
@@ -144,12 +156,21 @@ def add_species(
):
"""Helper function to add a species to the sbml model.
- :param model:
- :param compartment: a compartment in the SBML model
- :param species: must be chemical_reaction_network.species objects
- :param initial_concentration: initial concentration of the species
- in the SBML model
- :return: SBML species object
+ Parameters
+ ----------
+ model : libsbml.Model
+ compartment : libsbml.Compartment
+ A compartment in the SBML model.
+ species_name : str
+ species_id : str
+ initial_concentration : float, optional
+ Initial concentration of the species in the SBML model.
+
+ Returns
+ -------
+ libsbml.Species
+ SBML species object.
+
"""
# Construct the species ID
@@ -169,23 +190,36 @@ def add_species(
return sbml_species
-def add_all_compartments(model, compartments: List, **keywords):
+def add_all_compartments(model, compartments: List, **kwargs):
"""Adds the list of Compartment objects to the SBML model.
- :param model: valid SBML model
- :param compartments: list of compartments to be added to the SBML model
- :return: None
+ Parameters
+ ----------
+ model : libsbml.Model
+ Valid SBML model.
+ compartments : list
+ List of compartments to be added to the SBML model.
+
"""
for compartment in compartments:
- add_compartment(model=model, compartment=compartment, **keywords)
+ add_compartment(model=model, compartment=compartment, **kwargs)
-def add_compartment(model, compartment, **keywords):
+def add_compartment(model, compartment, **kwargs):
"""Helper function to add a compartment to the SBML model.
- :param model: a valid SBML model
- :param compartment: a Compartment object
- :return: SBML compartment object
+ Parameters
+ ----------
+ model : libsbml.Model
+ A valid SBML model.
+ compartment : bcp.Compartment
+ A Compartment object.
+
+ Returns
+ -------
+ libsbml.Compartment
+ SBML compartment object.
+
"""
sbml_compartment = model.createCompartment()
compartment_id = compartment.name
@@ -235,10 +269,15 @@ def find_parameter(mixture, id):
def add_all_reactions(model, reactions: List, stochastic=False, **kwargs):
"""Adds a list of reactions to the SBML model.
- :param model: an sbml model created by create_sbml_model()
- :param reactions: list of Reactions
- :param stochastic: binary flag for stochastic models
- :return: None
+ Parameters
+ ----------
+ model : libsbml.Model
+ An sbml model created by create_sbml_model().
+ reactions : list
+ List of Reactions.
+ stochastic : bool
+ Binary flag for stochastic models.
+
"""
elementlist = model.getSBMLDocument().getListOfAllElements()
@@ -286,12 +325,23 @@ def add_reaction(
):
"""Adds a sbml_reaction to an sbml model.
- :param model: an sbml model created by create_sbml_model()
- :param crn_reaction: must be a chemical_reaction_network.reaction object
- :param reaction_id: unique id of the reaction
- :param stochastic: stochastic model flag
- :param reverse_reaction:
- :return: SBML Reaction object
+ Parameters
+ ----------
+ model : libsbml.Model
+ An sbml model created by create_sbml_model().
+ crn_reaction : bcp.Reaction
+ Must be a chemical_reaction_network.reaction object.
+ reaction_id : str
+ Unique id of the reaction.
+ stochastic : bool
+ Stochastic model flag.
+ reverse_reaction : bool
+
+ Returns
+ -------
+ libsbml.Reaction
+ SBML Reaction object.
+
"""
# Create the sbml_reaction in SBML
sbml_reaction = model.createReaction()
@@ -568,8 +618,11 @@ def getAllIds(allElements):
def getSpeciesByName(model, name, compartment=''):
"""Returns a list of species in the Model with the given name.
- compartment : (Optional) argument to specify the compartment name in which
- to look for the species.
+ Parameters
+ ----------
+ compartment : str, optional
+ Compartment name in which to look for the species.
+
"""
if not isinstance(name, str):
raise ValueError(f"'name' must be a string. Received {name}.")
@@ -598,25 +651,23 @@ def getSpeciesByName(model, name, compartment=''):
# Validate SBML
-
-
class validateSBML(object):
- """libSBML class to validate the generated SBML models.
-
- ## @brief Validates SBMLDocument
- ## @author Akiya Jouraku (translated from libSBML C++ examples)
- ## @author Ben Bornstein
- ## @author Michael Hucka
- """
+ """Class to validate the generated SBML models."""
def __init__(self, ucheck):
self.reader = libsbml.SBMLReader()
self.ucheck = ucheck
def validate(self, sbml_document, print_results=False):
- """sbml_document: libSBML SBMLDocument object.
+ """Validate an SBML document.
+
+ Parameters
+ ----------
+ sbml_document : libsbml.SBMLDocument
+ libSBML SBMLDocument object.
+ print_results : bool
+ Print toggle for validation warnings.
- print_results: Print toggle for validation warnings.
"""
sbmlDoc = sbml_document
errors = sbmlDoc.getNumErrors()
diff --git a/biocrnpyler/utils/units.py b/biocrnpyler/utils/units.py
index 6cdebccd..43dcbe68 100644
--- a/biocrnpyler/utils/units.py
+++ b/biocrnpyler/utils/units.py
@@ -128,9 +128,19 @@ def biocrnpyler_supported_units():
def create_new_unit_definition(model, unit_id):
- """Creates UnitDefinition inside SBML Model object.
+ """
+ Creates UnitDefinition inside SBML Model object.
+
+ Parameters
+ ----------
+ model : libsbml.Model
+ unit_id : str
+
+ Returns
+ -------
+ libsbml.UnitDefinition
+ A pointer to the new libSBML object created for the unit type.
- Returns a pointer to the new libSBML object created for the unit type.
"""
supported_units = biocrnpyler_supported_units()
if not isinstance(unit_id, str):
diff --git a/docs/conf.py b/docs/conf.py
index bfbecc1f..e0be1ec6 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -54,6 +54,7 @@
'nbsphinx',
'nbsphinx_link',
'recommonmark',
+ 'numpydoc',
]
source_suffix = ['.rst']
@@ -66,11 +67,12 @@
autodoc_default_options = {
'members': True,
'inherited-members': True,
+ 'special-members': True,
'exclude-members': '__init__, __weakref__, __repr__, __str__',
}
# For classes, include both the class docstring and the init docstring
-autoclass_content = 'both'
+autoclass_content = 'class'
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
@@ -80,6 +82,11 @@
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ['_build']
+# Don't automatically show all members of class in Methods & Attributes section
+numpydoc_show_class_members = False
+
+# Don't create a Sphinx TOC for the lists of class methods and attributes
+numpydoc_class_members_toctree = False
# -- Options for HTML output -------------------------------------------------
@@ -172,13 +179,14 @@ def linkcode_resolve(domain, info):
linespec = ''
base_url = "https://github.com/BuildACell/BioCRNPyler/blob/"
- if release != version: # development release
+ if release != version: # development release
# TODO: replace 'refactor-modules' with 'master' -> replaced with main
# print(" --> ", base_url + "refactor-modules/control/%s%s" % (fn, linespec))
return base_url + 'main/biocrnpyler/%s%s' % (fn, linespec)
- else: # specific version
+ else: # specific version
return base_url + '%s/biocrnpyler/%s%s' % (version, fn, linespec)
+
# -- Options for doctest ----------------------------------------------
# Import biocrnpyler as bcp
diff --git a/docs/examples/1. Combinatorial Promoters.ipynb b/docs/examples/1. Combinatorial Promoters.ipynb
new file mode 120000
index 00000000..8c00cb64
--- /dev/null
+++ b/docs/examples/1. Combinatorial Promoters.ipynb
@@ -0,0 +1 @@
+../../examples/Specialized Tutorials/1. Combinatorial Promoters.ipynb
\ No newline at end of file
diff --git a/docs/examples/2. Membrane Models.ipynb b/docs/examples/2. Membrane Models.ipynb
new file mode 120000
index 00000000..5e31db24
--- /dev/null
+++ b/docs/examples/2. Membrane Models.ipynb
@@ -0,0 +1 @@
+../../examples/Specialized Tutorials/2. Membrane Models.ipynb
\ No newline at end of file
diff --git a/docs/examples/3. Multiple Occupancy in TX-TL.ipynb b/docs/examples/3. Multiple Occupancy in TX-TL.ipynb
new file mode 120000
index 00000000..19847749
--- /dev/null
+++ b/docs/examples/3. Multiple Occupancy in TX-TL.ipynb
@@ -0,0 +1 @@
+../../examples/Specialized Tutorials/3. Multiple Occupancy in TX-TL.ipynb
\ No newline at end of file
diff --git a/docs/examples/4. Combinatorial Conformation Modeling.ipynb b/docs/examples/4. Combinatorial Conformation Modeling.ipynb
new file mode 120000
index 00000000..535de5ec
--- /dev/null
+++ b/docs/examples/4. Combinatorial Conformation Modeling.ipynb
@@ -0,0 +1 @@
+../../examples/Specialized Tutorials/4. Combinatorial Conformation Modeling.ipynb
\ No newline at end of file
diff --git a/docs/examples/5. TX-TL Toolbox.ipynb b/docs/examples/5. TX-TL Toolbox.ipynb
new file mode 120000
index 00000000..6f984be1
--- /dev/null
+++ b/docs/examples/5. TX-TL Toolbox.ipynb
@@ -0,0 +1 @@
+../../examples/Specialized Tutorials/5. TX-TL Toolbox.ipynb
\ No newline at end of file
diff --git a/docs/examples/6. Integrase Examples.ipynb b/docs/examples/6. Integrase Examples.ipynb
new file mode 120000
index 00000000..66962463
--- /dev/null
+++ b/docs/examples/6. Integrase Examples.ipynb
@@ -0,0 +1 @@
+../../examples/Specialized Tutorials/6. Integrase Examples.ipynb
\ No newline at end of file
diff --git a/docs/examples/7. Transport_Models.ipynb b/docs/examples/7. Transport_Models.ipynb
new file mode 120000
index 00000000..c12f4e25
--- /dev/null
+++ b/docs/examples/7. Transport_Models.ipynb
@@ -0,0 +1 @@
+../../examples/Specialized Tutorials/7. Transport_Models.ipynb
\ No newline at end of file
diff --git a/docs/examples/8. Multicellular_Transport.ipynb b/docs/examples/8. Multicellular_Transport.ipynb
new file mode 120000
index 00000000..99508721
--- /dev/null
+++ b/docs/examples/8. Multicellular_Transport.ipynb
@@ -0,0 +1 @@
+../../examples/Specialized Tutorials/8. Multicellular_Transport.ipynb
\ No newline at end of file
diff --git a/examples/6. Global Mechanisms.ipynb b/examples/6. Global Mechanisms.ipynb
index 04032e86..b3eeb94e 100644
--- a/examples/6. Global Mechanisms.ipynb
+++ b/examples/6. Global Mechanisms.ipynb
@@ -539,7 +539,7 @@
"gamS = Species(\"GamS\")\n",
"\n",
"linear_dna_degradation = Deg_Tagged_Degradation(\n",
- " degredase=recBCD, filter_dict={\"dna\":True, \"circular\":False}, default_on=False)\n",
+ " degradase=recBCD, filter_dict={\"dna\":True, \"circular\":False}, default_on=False)\n",
"\n",
"inhibited_recBCD = ChemicalComplex([recBCD]+2*[gamS])\n",
"\n",
diff --git a/pyproject.toml b/pyproject.toml
index 1f0465d0..b30ba757 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -74,12 +74,13 @@ ignore = [
'D100', 'D101', 'D102', # ignore missing docstrings for now
'D103', 'D104', 'D105',
'D105', 'D106', 'D107',
+ 'D401', # ignore imperative docstring checking for now
'D417', # ignore missing argument descriptions for now
]
[tool.ruff.lint.per-file-ignores]
# Ignore 'D' and 'E' rules everywhere except for the `biocrnpyler/` directory.
-'!biocrnpyler/**.py' = ['D', 'E']
+'!biocrnpyler/**.py' = ['D', 'E', 'I']
'Tests/**.py' = [
'E', # ignore code style
'F841', # unused variables OK
@@ -95,7 +96,7 @@ ignore = [
quote-style = 'preserve'
[tool.ruff.lint.pydocstyle]
-convention = "google"
+convention = "numpy"
[tool.isort]
profile = 'black'