from bionetgen.modelapi.pattern import Molecule, Pattern
from bionetgen.modelapi.rulemod import RuleMod
from bionetgen.core.utils.utils import ActionList
from bionetgen.core.exc import BNGParseError
[docs]class ModelObj:
"""
The base class for all items in a model (parameter, observable etc.).
Attributes
----------
comment : str
comment at the end of the line/object
line_label : str
line label at the beginning of the line/object
Methods
-------
print_line()
generates the actual line string with line label and comments
if applicable
gen_string()
generates the BNGL string of the object itself, separate from
line attributes
"""
def __init__(self):
self._comment = None
self._line_label = None
def __str__(self) -> str:
return self.gen_string()
def __repr__(self) -> str:
return self.gen_string()
def __contains__(self, key):
return hasattr(self, key)
def __getitem__(self, key):
return getattr(self, key)
def __setitem__(self, key, value):
setattr(self, key, value)
def __delitem__(self, key):
delattr(self, key)
@property
def comment(self) -> None:
return self._comment
@comment.setter
def comment(self, val) -> None:
# TODO: regex handling of # instead
if val.startswith("#"):
self._comment = val[1:]
else:
self._comment = val
@property
def line_label(self) -> str:
return self._line_label
@line_label.setter
def line_label(self, val) -> None:
# TODO: specific error handling
try:
ll = int(val)
self._line_label = "{} ".format(ll)
except:
self._line_label = "{}: ".format(val)
[docs] def print_line(self) -> str:
s = " "
# let's deal with line label
if self.line_label is not None:
s += self.line_label
# start building the rest of the string
s += str(self)
if self.comment is not None:
s += " #{}".format(self.comment)
return s
[docs]class Parameter(ModelObj):
"""
Class for all parameters in the model, subclass of ModelObj.
In BNGL parameters are of the form
parameter_name parameter_value/expression
or
parameter_name = parameter_value/expression
Attributes
----------
name : str
name of the parameter
value : str
value of the parameter, if loaded from XML this will always
exist since NFsim needs the value and not the expression
expr : str
this exists if the parameter is a math expression, not necerssary
write_expr : bool
this is a boolean that determines if the generated string has
is in expression form or in value form.
"""
def __init__(self, name, value, expr=None):
super().__init__()
self.name = name
self.value = value
self.expr = expr
try:
test = float(expr)
self.write_expr = False
except:
self.write_expr = True
[docs] def gen_string(self) -> str:
if self.write_expr:
return "{} {}".format(self.name, self.expr)
else:
return "{} {}".format(self.name, self.value)
[docs]class Compartment(ModelObj):
"""
Class for all compartments in the model, subclass of ModelObj.
In BNGL the compartments are of the form
compartment_name dimensions size
or
compartment_name dimensions size parent_compartment
the second form only applies when one compartment is contained in
another compartment.
Attributes
----------
name : str
name of the compartment
dim : str
dimensionality of the compartment
size : str
size/volume of the compartment
outside : str
parent compartment, if exists
write_expr : bool
boolean that describes if the size is a volume or an expression
"""
def __init__(self, name, dim, size, outside=None):
super().__init__()
self.name = name
self.dim = dim
self.size = size
try:
test = float(size)
self.write_expr = False
except:
self.write_expr = True
self.outside = outside
[docs] def gen_string(self) -> str:
s = "{} {} {}".format(self.name, self.dim, self.size)
if self.outside is not None:
s += " {}".format(self.outside)
return s
[docs]class Observable(ModelObj):
"""
Class for all observables in the model, subclass of ModelObj.
In BNGL the observables are of the form
observable_type observable_name observable_patterns
where patterns can include multiple patterns separated by commas.
Attributes
----------
name : str
name of the observable
type : str
type of the observable, Molecules or Species
patterns : list[Pattern]
list of patterns of the observable
Methods
-------
add_pattern
add a Pattern object into the list of patterns
for this observable
"""
def __init__(self, name, otype, patterns=[]):
super().__init__()
self.name = name
self.type = otype
if self.type == "Species":
for pat in patterns:
if pat.MatchOnce:
pat.MatchOnce = False
self.patterns = patterns
[docs] def gen_string(self) -> str:
s = "{} {} ".format(self.type, self.name)
for ipat, pat in enumerate(self.patterns):
if ipat > 0:
s += ","
s += str(pat)
return s
[docs] def add_pattern(self, pat) -> None:
# if type is species, set MatchOnce to false since all species automatically match once
if self.type == "Species":
pat.MatchOnce = False
self.patterns.append(pat)
[docs]class MoleculeType(ModelObj):
"""
Class for all molecule types in the model, subclass of ModelObj.
In BNGL the molecule types are of the form
molecule_type
where all possible states of each component of a molecule is
listed, e.g.
A(b, p~0~1, k~ON~OFF~NULL)
Attributes
----------
molecule : Molecule
a molecule type only contains a molecule object which
can also handle multiple component states
"""
def __init__(self, name, components):
super().__init__()
self.name = name
self.molecule = Molecule(name=name, components=components)
[docs] def gen_string(self) -> str:
return str(self.molecule)
[docs]class Species(ModelObj):
"""
Class for all species in the model, subclass of ModelObj.
In BNGL the species/seed species are of the form
species count
where species is a single pattern and count is the starting
value for that specific pattern
Attributes
----------
pattern : Pattern
pattern of the seed species
count : str
starting value of the seed species
"""
def __init__(self, pattern=Pattern(), count=0):
super().__init__()
self.pattern = pattern
self.count = count
self.name = str(self.pattern)
[docs] def gen_string(self) -> str:
s = "{} {}".format(self.pattern, self.count)
return s
[docs]class Function(ModelObj):
"""
Class for all functions in the model, subclass of ModelObj.
In BNGL functions are of the form
function_name function_expression
or
function_name = function_expression
and functions can have arguments
function_name(arg1, arg2, ..., argN)
Attributes
----------
name : str
name of the function
expr : str
function expression
args : list
optional list of arguments for the function
"""
def __init__(self, name, expr, args=None):
super().__init__()
self.name = name
self.expr = expr
self.args = args
[docs] def gen_string(self) -> str:
if self.args is None:
s = "{} = {}".format(self.name, self.expr)
else:
s = "{}({}) = {}".format(self.name, ",".join(self.args), self.expr)
return s
[docs]class Action(ModelObj):
"""
Class for all actions in the model, subclass of ModelObj.
In BNGL actions are of the form
action_type({arg1=>value1, arg2=>value2, ...})
Attributes
----------
type : str
type of action, e.g. simulate or writeFile
args : dict[arg_name] = arg_value
action arguments as keys and their values as values
"""
def __init__(self, action_type=None, action_args={}) -> None:
super().__init__()
AList = ActionList()
self.normal_types = AList.normal_types
self.no_setter_syntax = AList.no_setter_syntax
self.square_braces = AList.square_braces
self.possible_types = AList.possible_types
# Set initial values
self.name = action_type
self.type = action_type
self.args = action_args
# check type
if self.type not in self.possible_types:
raise BNGParseError(message=f"Action type {self.type} not recognized!")
seen_args = []
for arg in action_args:
arg_name, arg_value = arg, action_args[arg]
valid_arg_list = AList.arg_dict[self.type]
# TODO: actions that don't take argument names should be parsed separately to check validity of arg-val tuples
# TODO: currently not type checking arguments
if valid_arg_list is None:
raise BNGParseError(
message=f"Argument {arg_name} is given, but action {self.type} does not take arguments"
)
if len(valid_arg_list) > 0:
if arg_name not in AList.arg_dict[self.type]:
raise BNGParseError(
message=f"Action argument {arg_name} not recognized!\nCheck to make sure action is correctly formatted"
)
# TODO: If arg_value is the correct type
if arg_name in seen_args:
print(
f"Warning: argument {arg_name} already given, using latter value {arg_value}"
)
else:
seen_args.append(arg_name)
[docs] def gen_string(self) -> str:
# TODO: figure out every argument that has special
# requirements, e.g. method requires the value to
# be a string
action_str = "{}(".format(self.type)
# we can skip curly if we don't have arguments
# and we NEED to skip it for some actions
if self.type in self.normal_types and not len(self.args) == 0:
action_str += "{"
elif self.type in self.square_braces:
action_str += "["
# add arguments
for iarg, arg in enumerate(self.args):
val = self.args[arg]
if iarg > 0:
action_str += ","
# some actions need =>, some don't
if self.type in self.normal_types:
action_str += f"{arg}=>{val}"
else:
action_str += f"{arg}"
# we can skip curly if we don't have arguments
# and we NEED to skip it for some actions
if self.type in self.normal_types and not len(self.args) == 0:
action_str += "}"
elif self.type in self.square_braces:
action_str += "]"
# close up the action
action_str += ")"
return action_str
[docs] def print_line(self) -> str:
s = ""
# let's deal with line label
if self.line_label is not None:
s += self.line_label
# start building the rest of the string
s += str(self)
if self.comment is not None:
s += " #{}".format(self.comment)
return s
[docs]class Rule(ModelObj):
"""
Class for all rules in the model, subclass of ModelObj.
Attributes
----------
name : str
name of the rule, optional
reactants : list[Pattern]
list of patterns for reactants
products : list[Pattern]
list of patterns for products
rule_mod : RuleMod
modifier (moveConnected, TotalRate, etc.) used by a given rule
operations : list[Operation]
list of operations
Methods
-------
set_rate_constants((k_fwd,k_bck))
sets forward and backwards rate constants, backwards rate
constants are optional and if not given, will set the rule
to be a unidirectional rule
side_string(list[Pattern])
given a list of patterns, return a string formatted to be
on one side of a rule definition
"""
def __init__(
self,
name,
reactants=[],
products=[],
rate_constants=(),
rule_mod=RuleMod(),
operations=[],
) -> None:
super().__init__()
self.name = name
self.reactants = reactants
self.products = products
if rule_mod is None:
self.rule_mod = RuleMod()
else:
self.rule_mod = rule_mod
self.operations = operations
self.set_rate_constants(rate_constants)
[docs] def set_rate_constants(self, rate_cts):
if len(rate_cts) == 1:
self.rate_constants = [rate_cts[0]]
self.bidirectional = False
elif len(rate_cts) == 2:
self.rate_constants = [rate_cts[0], rate_cts[1]]
self.bidirectional = True
else:
print("1 or 2 rate constants allowed")
[docs] def gen_string(self):
if self.bidirectional:
return "{}: {} <-> {} {},{} {}".format(
self.name,
self.side_string(self.reactants),
self.side_string(self.products),
self.rate_constants[0],
self.rate_constants[1],
str(self.rule_mod),
)
else:
return "{}: {} -> {} {} {}".format(
self.name,
self.side_string(self.reactants),
self.side_string(self.products),
self.rate_constants[0],
str(self.rule_mod),
)
[docs] def side_string(self, patterns):
side_str = ""
for ipat, pat in enumerate(patterns):
if ipat > 0:
side_str += " + "
side_str += str(pat)
return side_str
[docs]class EnergyPattern(ModelObj):
"""
Class for all energy patterns in the model, subclass of ModelObj.
In BNGL the energy patterns are of the form
EP_pattern EP_expression
Attributes
----------
name : str
id of the energy pattern
pattern : Pattern
Pattern object representing the energy pattern
expression : str
expression used for energy pattern
"""
def __init__(self, name, pattern, expression):
super().__init__()
self.name = name
self.pattern = pattern
self.expression = expression
[docs] def gen_string(self) -> str:
s = "{} {}".format(self.pattern, self.expression)
return s
[docs]class PopulationMap(ModelObj):
"""
Class for all population maps in the model, subclass of ModelObj.
In BNGL the population maps are of the form
structured_species -> population_species lumping_parameter
Attributes
----------
name : str
id of the population map
struct_species : Pattern
Pattern object representing the species to be mapped
pop_species : Pattern
Pattern object representing the population count
rate : str
lumping parameter used in population mapping
"""
def __init__(self, name, struct_species, pop_species, rate):
super().__init__()
self.name = name
self.species = struct_species
self.population = pop_species
self.rate = rate
[docs] def gen_string(self) -> str:
s = "{} -> {} {}".format(self.species, self.population, self.rate)
return s