def test_none_in_rxn_pat(): Monomer('A') Monomer('B') Rule('rule1', A() + None >> None + B(), Parameter('k', 1)) Initial(A(), Parameter('A_0', 100)) Observable('B_', B()) npts = 200 kres = run_simulation(model, time=100, points=npts, seed=_KAPPA_SEED) # check that rule1's reaction pattern parses with ComplexPatterns as_complex_pattern(A()) + None >> None + as_complex_pattern(B())
def _cp_embeds_into(cp1, cp2): """Check that any state in ComplexPattern2 is matched in ComplexPattern1. """ # Check that any state in cp2 is matched in cp2 # If the thing we're matching to is just a monomer pattern, that makes # things easier--we just need to find the corresponding monomer pattern # in cp1 cp1 = as_complex_pattern(cp1) cp2 = as_complex_pattern(cp2) if len(cp2.monomer_patterns) == 1: mp2 = cp2.monomer_patterns[0] # Iterate over the monomer patterns in cp1 and see if there is one # that has the same name for mp1 in cp1.monomer_patterns: if _mp_embeds_into(mp1, mp2): return True return False
def _cp_embeds_into(cp1, cp2): """Check that any state in ComplexPattern2 is matched in ComplexPattern1. """ # Check that any state in cp2 is matched in cp1 # If the thing we're matching to is just a monomer pattern, that makes # things easier--we just need to find the corresponding monomer pattern # in cp1 if cp1 is None or cp2 is None: return False cp1 = as_complex_pattern(cp1) cp2 = as_complex_pattern(cp2) if len(cp2.monomer_patterns) == 1: mp2 = cp2.monomer_patterns[0] # Iterate over the monomer patterns in cp1 and see if there is one # that has the same name for mp1 in cp1.monomer_patterns: if _mp_embeds_into(mp1, mp2): return True return False
def __init__(self, complex_pattern, lumping_rate, counter_species=None): try: self.complex_pattern = as_complex_pattern(complex_pattern) except InvalidComplexPatternException: raise ValueError('complex_pattern must be a ComplexPattern') if not isinstance(lumping_rate, Parameter): raise ValueError('lumping_rate must be a %s' % Parameter.__class__) self.lumping_rate = lumping_rate if counter_species is None: self.counter_species = None else: self.counter_species = str(counter_species)
def test_multistate(): Monomer('A', ['a', 'a'], {'a': ['u', 'p']}) Parameter('k1', 100) Parameter('A_0', 200) Rule('r1', None >> A(a=MultiState('u', 'p')), k1) Initial(A(a=MultiState(('u', 1), 'p')) % A(a=MultiState(('u', 1), 'u')), A_0) generate_equations(model) assert model.species[0].is_equivalent_to( A(a=MultiState(('u', 1), 'p')) % A(a=MultiState(('u', 1), 'u'))) assert model.species[1].is_equivalent_to( as_complex_pattern(A(a=MultiState('u', 'p'))))
def test_stochkit_earm_multi_initials(): model = earm_1_0.model tspan = np.linspace(0, 1000, 10) sim = StochKitSimulator(model, tspan=tspan) unbound_L = model.monomers['L'](b=None) simres = sim.run(initials={unbound_L: [3000, 1500]}, n_runs=2, seed=_STOCHKIT_SEED, algorithm="ssa") df = simres.dataframe unbound_L_index = model.get_species_index(as_complex_pattern(unbound_L)) # Check we have two repeats of each initial assert np.allclose(df.loc[(slice(None), 0), '__s%d' % unbound_L_index], [3000, 3000, 1500, 1500])
def _set_initials(initials_source): for cp, value_obj in initials_source: cp = as_complex_pattern(cp) si = self._model.get_species_index(cp) if si is None: raise IndexError("Species not found in model: %s" % repr(cp)) # Loop over all simulations for sim in range(len(y0)): # If this initial condition has already been set, skip it # (i.e., an override) if not np.isnan(y0[sim][si]): continue def _get_value(sim): if isinstance(value_obj, (collections.Sequence, np.ndarray)) and \ isinstance(value_obj[sim], numbers.Number): value = value_obj[sim] elif isinstance(value_obj, Component): if value_obj in self._model.parameters: pi = self._model.parameters.index(value_obj) value = self.param_values[ sim if n_sims_params > 1 else 0][pi] elif value_obj in self._model.expressions: value = value_obj.expand_expr().evalf(subs=subs[sim]) else: raise TypeError("Unexpected initial condition " "value type: %s" % type(value_obj)) return value # initials from the model if isinstance(initials_source, np.ndarray): if len(initials_source.shape) == 1: if sim == 0: value = _get_value(0) else: # if the parameters are different for each sim, # the expressions could be different too if value_obj in self._model.expressions: value = value_obj.expand_expr().evalf( subs=subs[sim]) else: value = y0[sim-1][si] # initials from dict else: value = _get_value(sim) y0[sim][si] = value
def synthesize(species, ksynth): """Generate a reaction which synthesizes a species. Note that `species` must be "concrete", i.e. the state of all sites in all of its monomers must be specified. No site may be left unmentioned. Parameters ---------- species : Monomer, MonomerPattern or ComplexPattern The species to synthesize. If a Monomer, sites are considered as unbound and in their default state. If a pattern, must be concrete. ksynth : Parameters or number Synthesis rate. If a Parameter is passed, it will be used directly in the generated Rule. If a number is passed, a Parameter will be created with an automatically generated name based on the names and site states of the components of `species` and this parameter will be included at the end of the returned component list. Returns ------- components : ComponentSet The generated components. Contains the unidirectional synthesis Rule and optionally a Parameter if ksynth was given as a number. Examples -------- Model() Monomer('A', ['x', 'y'], {'y': ['e', 'f']}) synthesize(A(x=None, y='e'), 1e-4) """ def synthesize_name_func(rule_expression): cps = rule_expression.product_pattern.complex_patterns return '_'.join(_complex_pattern_label(cp) for cp in cps) # TODO: either the >> operator should work with a monomer, or complexpattern # shouldn't blow up if it is called if isinstance(species, Monomer): species = species() species = as_complex_pattern(species) if not species.is_concrete(): raise ValueError("species must be concrete") return _macro_rule('synthesize', None >> species, [ksynth], ['k'], name_func=synthesize_name_func)
def synthesize(species, ksynth): """Generate a reaction which synthesizes a species. Note that `species` must be "concrete", i.e. the state of all sites in all of its monomers must be specified. No site may be left unmentioned. Parameters ---------- species : Monomer, MonomerPattern or ComplexPattern The species to synthesize. If a Monomer, sites are considered as unbound and in their default state. If a pattern, must be concrete. ksynth : Parameters or number Synthesis rate. If a Parameter is passed, it will be used directly in the generated Rule. If a number is passed, a Parameter will be created with an automatically generated name based on the names and site states of the components of `species` and this parameter will be included at the end of the returned component list. Returns ------- components : ComponentSet The generated components. Contains the unidirectional synthesis Rule and optionally a Parameter if ksynth was given as a number. Examples -------- Model() Monomer('A', ['x', 'y'], {'y': ['e', 'f']}) synthesize(A(x=None, y='e'), 1e-4) """ def synthesize_name_func(rule_expression): cps = rule_expression.product_pattern.complex_patterns return '_'.join(_complex_pattern_label(cp) for cp in cps) if isinstance(species, Monomer): species = species() species = as_complex_pattern(species) if not species.is_concrete(): raise ValueError("species must be concrete") return _macro_rule('synthesize', None >> species, [ksynth], ['k'], name_func=synthesize_name_func)
def degrade(species, kdeg): """Generate a reaction which degrades a species. Note that `species` is not required to be "concrete". Parameters ---------- species : Monomer, MonomerPattern or ComplexPattern The species to synthesize. If a Monomer, sites are considered as unbound and in their default state. If a pattern, must be concrete. kdeg : Parameters or number Degradation rate. If a Parameter is passed, it will be used directly in the generated Rule. If a number is passed, a Parameter will be created with an automatically generated name based on the names and site states of the components of `species` and this parameter will be included at the end of the returned component list. Returns ------- components : ComponentSet The generated components. Contains the unidirectional degradation Rule and optionally a Parameter if ksynth was given as a number. Examples -------- Model() Monomer('B', ['x']) degrade(B(), 1e-6) # degrade all B, even bound species """ def degrade_name_func(rule_expression): cps = rule_expression.reactant_pattern.complex_patterns return '_'.join(_complex_pattern_label(cp) for cp in cps) # TODO: the >> operator should work with a monomer, or complexpattern # shouldn't blow up if it is called if isinstance(species, Monomer): species = species() species = as_complex_pattern(species) return _macro_rule('degrade', species >> None, [kdeg], ['k'], name_func=degrade_name_func)
def degrade(species, kdeg): """Generate a reaction which degrades a species. Note that `species` is not required to be "concrete". Parameters ---------- species : Monomer, MonomerPattern or ComplexPattern The species to synthesize. If a Monomer, sites are considered as unbound and in their default state. If a pattern, must be concrete. kdeg : Parameters or number Degradation rate. If a Parameter is passed, it will be used directly in the generated Rule. If a number is passed, a Parameter will be created with an automatically generated name based on the names and site states of the components of `species` and this parameter will be included at the end of the returned component list. Returns ------- components : ComponentSet The generated components. Contains the unidirectional degradation Rule and optionally a Parameter if ksynth was given as a number. Examples -------- Model() Monomer('B', ['x']) degrade(B(), 1e-6) # degrade all B, even bound species """ def degrade_name_func(rule_expression): cps = rule_expression.reactant_pattern.complex_patterns return '_'.join(_complex_pattern_label(cp) for cp in cps) if isinstance(species, Monomer): species = species() species = as_complex_pattern(species) return _macro_rule('degrade', species >> None, [kdeg], ['k'], name_func=degrade_name_func)
def _set_initials(initials_source): for cp, value_obj in initials_source: cp = as_complex_pattern(cp) si = self._model.get_species_index(cp) if si is None: raise IndexError("Species not found in model: %s" % repr(cp)) # If this initial condition has already been set, skip it if y0[si] != 0: continue if isinstance(value_obj, (int, float)): value = value_obj elif value_obj in self._model.parameters: pi = self._model.parameters.index(value_obj) value = param_vals[pi] elif value_obj in self._model.expressions: value = value_obj.expand_expr().evalf(subs=subs) else: raise ValueError("Unexpected initial condition value type") y0[si] = value
def export(self, initials=None, param_values=None): """Generate the corresponding StochKit2 XML for a PySB model Parameters ---------- initials : list of numbers List of initial species concentrations overrides (must be same length as model.species). If None, the concentrations from the model are used. param_values : list List of parameter value overrides (must be same length as model.parameters). If None, the parameter values from the model are used. Returns ------- string The model in StochKit2 XML format """ if self.model.compartments: raise CompartmentsNotSupported() generate_equations(self.model) document = etree.Element("Model") d = etree.Element('Description') d.text = 'Exported from PySB Model: %s' % self.model.name document.append(d) # Number of Reactions nr = etree.Element('NumberOfReactions') nr.text = str(len(self.model.reactions)) document.append(nr) # Number of Species ns = etree.Element('NumberOfSpecies') ns.text = str(len(self.model.species)) document.append(ns) if param_values is None: # Get parameter values from model if not supplied param_values = [p.value for p in self.model.parameters] else: # Validate length if len(param_values) != len(self.model.parameters): raise Exception('param_values must be a list of numeric ' 'parameter values the same length as ' 'model.parameters') # Get initial species concentrations from model if not supplied if initials is None: initials = np.zeros((len(self.model.species),)) subs = dict((p, param_values[i]) for i, p in enumerate(self.model.parameters)) for ic in self.model.initials: cp = as_complex_pattern(ic.pattern) si = self.model.get_species_index(cp) if si is None: raise IndexError("Species not found in model: %s" % repr(cp)) if ic.value in self.model.parameters: pi = self.model.parameters.index(ic.value) value = param_values[pi] elif ic.value in self.model.expressions: value = ic.value.expand_expr().evalf(subs=subs) else: raise ValueError( "Unexpected initial condition value type") initials[si] = value else: # Validate length if len(initials) != len(self.model.species): raise Exception('initials must be a list of numeric initial ' 'concentrations the same length as ' 'model.species') # Species spec = etree.Element('SpeciesList') for s_id in range(len(self.model.species)): spec.append(self._species_to_element('__s%d' % s_id, initials[s_id])) document.append(spec) # Parameters params = etree.Element('ParametersList') for p_id, param in enumerate(self.model.parameters): p_name = param.name if p_name == 'vol': p_name = '__vol' p_value = param.value if param_values is None else \ param_values[p_id] params.append(self._parameter_to_element(p_name, p_value)) # Default volume parameter value params.append(self._parameter_to_element('vol', 1.0)) document.append(params) # Expressions and observables expr_strings = { e.name: '(%s)' % sympy.ccode( e.expand_expr(expand_observables=True) ) for e in self.model.expressions } # Reactions reacs = etree.Element('ReactionsList') pattern = re.compile("(__s\d+)\*\*(\d+)") for rxn_id, rxn in enumerate(self.model.reactions): rxn_name = 'Rxn%d' % rxn_id rxn_desc = 'Rules: %s' % str(rxn["rule"]) reactants = defaultdict(int) products = defaultdict(int) # reactants for r in rxn["reactants"]: reactants["__s%d" % r] += 1 # products for p in rxn["products"]: products["__s%d" % p] += 1 # replace terms like __s**2 with __s*(__s-1) rate = str(rxn["rate"]) matches = pattern.findall(rate) for m in matches: repl = m[0] for i in range(1, int(m[1])): repl += "*(%s-%d)" % (m[0], i) rate = re.sub(pattern, repl, rate, count=1) # expand only expressions used in the rate eqn for e in {sym for sym in rxn["rate"].atoms() if isinstance(sym, Expression)}: rate = re.sub(r'\b%s\b' % e.name, expr_strings[e.name], rate) total_reactants = sum(reactants.values()) rxn_params = rxn["rate"].atoms(Parameter) rate = None if total_reactants <= 2 and len(rxn_params) == 1: # Try to parse as mass action to avoid compiling custom # propensity functions in StochKit (slow for big models) rxn_param = rxn_params.pop() putative_rate = sympy.Mul(*[sympy.symbols(r) ** r_stoich for r, r_stoich in reactants.items()]) * rxn_param rxn_floats = rxn["rate"].atoms(sympy.Float) rate_mul = 1.0 if len(rxn_floats) == 1: rate_mul = next(iter(rxn_floats)) putative_rate *= rate_mul if putative_rate == rxn["rate"]: # Reaction is mass-action, set rate to a Parameter or float if len(rxn_floats) == 0: rate = rxn_param elif len(rxn_floats) == 1: rate = rxn_param.value * float(rate_mul) if rate is not None and len(reactants) == 1 and \ max(reactants.values()) == 2: # Need rate * 2 in addition to any rate factor rate = (rate.value if isinstance(rate, Parameter) else rate) * 2.0 if rate is None: # Custom propensity function needed rxn_atoms = rxn["rate"].atoms() # replace terms like __s**2 with __s*(__s-1) rate = str(rxn["rate"]) matches = pattern.findall(rate) for m in matches: repl = m[0] for i in range(1, int(m[1])): repl += "*(%s-%d)" % (m[0], i) rate = re.sub(pattern, repl, rate, count=1) # expand only expressions used in the rate eqn for e in {sym for sym in rxn_atoms if isinstance(sym, Expression)}: rate = re.sub(r'\b%s\b' % e.name, expr_strings[e.name], rate) reacs.append(self._reaction_to_element(rxn_name, rxn_desc, rate, reactants, products)) document.append(reacs) if pretty_print: return etree.tostring(document, pretty_print=True).decode('utf8') else: # Hack to print pretty xml without pretty-print # (requires the lxml module). doc = etree.tostring(document) xmldoc = xml.dom.minidom.parseString(doc) uglyXml = xmldoc.toprettyxml(indent=' ') text_re = re.compile(">\n\s+([^<>\s].*?)\n\s+</", re.DOTALL) prettyXml = text_re.sub(">\g<1></", uglyXml) return prettyXml
def export(self, initials=None, param_values=None): """Generate the corresponding StochKit2 XML for a PySB model Parameters ---------- initials : list of numbers List of initial species concentrations overrides (must be same length as model.species). If None, the concentrations from the model are used. param_values : list List of parameter value overrides (must be same length as model.parameters). If None, the parameter values from the model are used. Returns ------- string The model in StochKit2 XML format """ generate_equations(self.model) document = etree.Element("Model") d = etree.Element('Description') d.text = 'Exported from PySB Model: %s' % self.model.name document.append(d) # Number of Reactions nr = etree.Element('NumberOfReactions') nr.text = str(len(self.model.reactions)) document.append(nr) # Number of Species ns = etree.Element('NumberOfSpecies') ns.text = str(len(self.model.species)) document.append(ns) if param_values is None: # Get parameter values from model if not supplied param_values = [p.value for p in self.model.parameters] else: # Validate length if len(param_values) != len(self.model.parameters): raise Exception('param_values must be a list of numeric ' 'parameter values the same length as ' 'model.parameters') # Get initial species concentrations from model if not supplied if initials is None: initials = np.zeros((len(self.model.species), )) subs = dict((p, param_values[i]) for i, p in enumerate(self.model.parameters)) for cp, value_obj in self.model.initial_conditions: cp = as_complex_pattern(cp) si = self.model.get_species_index(cp) if si is None: raise IndexError("Species not found in model: %s" % repr(cp)) if isinstance(value_obj, (int, float)): value = value_obj elif value_obj in self.model.parameters: pi = self.model.parameters.index(value_obj) value = param_values[pi] elif value_obj in self.model.expressions: value = value_obj.expand_expr().evalf(subs=subs) else: raise ValueError("Unexpected initial condition value type") initials[si] = value else: # Validate length if len(initials) != len(self.model.species): raise Exception('initials must be a list of numeric initial ' 'concentrations the same length as ' 'model.species') # Species spec = etree.Element('SpeciesList') for s_id in range(len(self.model.species)): spec.append( self._species_to_element('__s%d' % s_id, initials[s_id])) document.append(spec) # Parameters params = etree.Element('ParametersList') for p_id, param in enumerate(self.model.parameters): p_name = param.name if p_name == 'vol': p_name = '__vol' p_value = param.value if param_values is None else \ param_values[p_id] params.append(self._parameter_to_element(p_name, p_value)) # Default volume parameter value params.append(self._parameter_to_element('vol', 1.0)) document.append(params) # Expressions and observables expr_strings = { e.name: '(%s)' % sympy.ccode(e.expand_expr(expand_observables=True)) for e in self.model.expressions } # Reactions reacs = etree.Element('ReactionsList') pattern = re.compile("(__s\d+)\*\*(\d+)") for rxn_id, rxn in enumerate(self.model.reactions): rxn_name = 'Rxn%d' % rxn_id rxn_desc = 'Rules: %s' % str(rxn["rule"]) reactants = defaultdict(int) products = defaultdict(int) # reactants for r in rxn["reactants"]: reactants["__s%d" % r] += 1 # products for p in rxn["products"]: products["__s%d" % p] += 1 # replace terms like __s**2 with __s*(__s-1) rate = str(rxn["rate"]) matches = pattern.findall(rate) for m in matches: repl = m[0] for i in range(1, int(m[1])): repl += "*(%s-%d)" % (m[0], i) rate = re.sub(pattern, repl, rate, count=1) # expand only expressions used in the rate eqn for e in { sym for sym in rxn["rate"].atoms() if isinstance(sym, Expression) }: rate = re.sub(r'\b%s\b' % e.name, expr_strings[e.name], rate) total_reactants = sum(reactants.values()) rxn_params = rxn["rate"].atoms(Parameter) rate = None if total_reactants <= 2 and len(rxn_params) == 1: # Try to parse as mass action to avoid compiling custom # propensity functions in StochKit (slow for big models) rxn_param = rxn_params.pop() putative_rate = sympy.Mul(*[ sympy.symbols(r)**r_stoich for r, r_stoich in reactants.items() ]) * rxn_param rxn_floats = rxn["rate"].atoms(sympy.Float) rate_mul = 1.0 if len(rxn_floats) == 1: rate_mul = next(iter(rxn_floats)) putative_rate *= rate_mul if putative_rate == rxn["rate"]: # Reaction is mass-action, set rate to a Parameter or float if len(rxn_floats) == 0: rate = rxn_param elif len(rxn_floats) == 1: rate = rxn_param.value * float(rate_mul) if rate is not None and len(reactants) == 1 and \ max(reactants.values()) == 2: # Need rate * 2 in addition to any rate factor rate = (rate.value if isinstance(rate, Parameter) else rate) * 2.0 if rate is None: # Custom propensity function needed rxn_atoms = rxn["rate"].atoms() # replace terms like __s**2 with __s*(__s-1) rate = str(rxn["rate"]) matches = pattern.findall(rate) for m in matches: repl = m[0] for i in range(1, int(m[1])): repl += "*(%s-%d)" % (m[0], i) rate = re.sub(pattern, repl, rate, count=1) # expand only expressions used in the rate eqn for e in { sym for sym in rxn_atoms if isinstance(sym, Expression) }: rate = re.sub(r'\b%s\b' % e.name, expr_strings[e.name], rate) reacs.append( self._reaction_to_element(rxn_name, rxn_desc, rate, reactants, products)) document.append(reacs) if pretty_print: return etree.tostring(document, pretty_print=True) else: # Hack to print pretty xml without pretty-print # (requires the lxml module). doc = etree.tostring(document) xmldoc = xml.dom.minidom.parseString(doc) uglyXml = xmldoc.toprettyxml(indent=' ') text_re = re.compile(">\n\s+([^<>\s].*?)\n\s+</", re.DOTALL) prettyXml = text_re.sub(">\g<1></", uglyXml) return prettyXml