def __init__(self, model, dialect='kasim', _warn_no_ic=True): if model and model.compartments: raise CompartmentsNotSupported() self.model = model self.__content = None self.dialect = dialect self._warn_no_ic = _warn_no_ic self._renamed_states = collections.defaultdict(dict) self._log = pysb.logging.get_logger(__name__)
def __init__(self, model, dialect='kasim', _warn_no_ic=True, _exclude_ic_param=False): if model: if model.compartments: raise CompartmentsNotSupported() if model.tags: raise LocalFunctionsNotSupported() self.model = model self.__content = None self.dialect = dialect self._warn_no_ic = _warn_no_ic self._exclude_ic_param = _exclude_ic_param self._renamed_states = collections.defaultdict(dict) self._log = pysb.logging.get_logger(__name__)
def export(self): """Export the SBML for the PySB model associated with the exporter. Returns ------- string String containing the SBML output. """ if self.model.expressions: raise ExpressionsNotSupported() if self.model.compartments: raise CompartmentsNotSupported() output = StringIO() pysb.bng.generate_equations(self.model) output.write("""<?xml version="1.0" encoding="UTF-8"?> <sbml xmlns="http://www.sbml.org/sbml/level2" level="2" version="1"> <model name="%s">""" % self.model.name) if self.docstring: notes_str = """ <notes> <body xmlns="http://www.w3.org/1999/xhtml"> <p>%s</p> </body> </notes>""" % self.docstring.replace("\n", "<br />\n" + " " * 20) output.write(notes_str) output.write(""" <listOfCompartments> <compartment id="default" name="default" spatialDimensions="3" size="1"/> </listOfCompartments> """) # complexpattern, initial value ics = [[s, 0] for s in self.model.species] for cp, ic_param in self.model.initial_conditions: ics[self.model.get_species_index(cp)][1] = ic_param.value output.write(" <listOfSpecies>\n") for i, (cp, value) in enumerate(ics): id = '__s%d' % i metaid = 'metaid_%d' % i name = str(cp).replace( '% ', '._br_') # CellDesigner does something weird with % in names output.write( ' <species id="%s" metaid="%s" name="%s" compartment="default" initialAmount="%.17g">\n' % (id, metaid, name, value)) output.write(get_species_annotation(metaid, cp)) output.write(' </species>\n') output.write(" </listOfSpecies>\n") output.write(" <listOfParameters>\n") for i, param in enumerate(self.model.parameters_rules()): output.write( ' <parameter id="%s" metaid="metaid_%s" name="%s" value="%.17g"/>\n' % (param.name, param.name, param.name, param.value)) output.write(" </listOfParameters>\n") output.write(" <listOfReactions>\n") for i, reaction in enumerate(self.model.reactions_bidirectional): reversible = str(reaction['reversible']).lower() output.write( ' <reaction id="r%d" metaid="metaid_r%d" name="r%d" reversible="%s">\n' % (i, i, i, reversible)) output.write(' <listOfReactants>\n') for species in reaction['reactants']: output.write( ' <speciesReference species="__s%d"/>\n' % species) output.write(' </listOfReactants>\n') output.write(' <listOfProducts>\n') for species in reaction['products']: output.write( ' <speciesReference species="__s%d"/>\n' % species) output.write(' </listOfProducts>\n') mathml = '<math xmlns="http://www.w3.org/1998/Math/MathML">' \ + print_mathml(reaction['rate']) + '</math>' output.write(' <kineticLaw>' + mathml + '</kineticLaw>\n') output.write(' </reaction>\n') output.write(" </listOfReactions>\n") output.write(" </model>\n</sbml>\n") return output.getvalue()
def export(self): """Export Python code for simulation of a model without PySB. Returns ------- string String containing the standalone Python code. """ if self.model.expressions: raise ExpressionsNotSupported() if self.model.compartments: raise CompartmentsNotSupported() output = StringIO() pysb.bng.generate_equations(self.model) # Note: This has a lot of duplication from pysb.integrate. # Can that be helped? code_eqs = '\n'.join([ 'ydot[%d] = %s;' % (i, sympy.ccode(self.model.odes[i])) for i in range(len(self.model.odes)) ]) code_eqs = re.sub(r'__s(\d+)', lambda m: 'y[%s]' % (int(m.group(1))), code_eqs) for i, p in enumerate(self.model.parameters): code_eqs = re.sub(r'\b(%s)\b' % p.name, 'p[%d]' % i, code_eqs) if self.docstring: output.write('"""') output.write(self.docstring) output.write('"""\n\n') output.write("# exported from PySB model '%s'\n" % self.model.name) output.write( pad(r""" import numpy import scipy.integrate import collections import itertools import distutils.errors """)) output.write( pad(r""" _use_cython = False # try to inline a C statement to see if Cython is functional try: import Cython except ImportError: Cython = None if Cython: from Cython.Compiler.Errors import CompileError try: Cython.inline('x = 1', force=True, quiet=True) _use_cython = True except (CompileError, distutils.errors.CompileError, ValueError): pass Parameter = collections.namedtuple('Parameter', 'name value') Observable = collections.namedtuple('Observable', 'name species coefficients') Initial = collections.namedtuple('Initial', 'param_index species_index') """)) output.write("\n") output.write("class Model(object):\n") init_data = { 'num_species': len(self.model.species), 'num_params': len(self.model.parameters), 'num_observables': len(self.model.observables), 'num_ics': len(self.model.initials), } output.write( pad( r""" def __init__(self): self.y = None self.yobs = None self.y0 = numpy.empty(%(num_species)d) self.ydot = numpy.empty(%(num_species)d) self.sim_param_values = numpy.empty(%(num_params)d) self.parameters = [None] * %(num_params)d self.observables = [None] * %(num_observables)d self.initials = [None] * %(num_ics)d """, 4) % init_data) for i, p in enumerate(self.model.parameters): p_data = (i, repr(p.name), p.value) output.write(" " * 8) output.write("self.parameters[%d] = Parameter(%s, %.17g)\n" % p_data) output.write("\n") for i, obs in enumerate(self.model.observables): obs_data = (i, repr(obs.name), repr(obs.species), repr(obs.coefficients)) output.write(" " * 8) output.write("self.observables[%d] = Observable(%s, %s, %s)\n" % obs_data) output.write("\n") for i, ic in enumerate(self.model.initials): ic_data = (i, self.model.parameters.index(ic.value), self.model.get_species_index(ic.pattern)) output.write(" " * 8) output.write("self.initials[%d] = Initial(%d, %d)\n" % ic_data) output.write("\n") output.write(" " * 8) if 'math.' in code_eqs: code_eqs = 'import math\n' + code_eqs output.write('code_eqs = \'\'\'\n%s\n\'\'\'\n' % code_eqs.replace(';', '')) output.write(" " * 8) output.write("if _use_cython:\n") output.write( pad( r""" def ode_rhs(t, y, p): ydot = self.ydot Cython.inline(code_eqs, quiet=True) return ydot """, 12)) output.write(" else:\n") output.write( pad( r""" def ode_rhs(t, y, p): ydot = self.ydot exec(code_eqs) return ydot """, 12)) output.write(" " * 8) output.write('self.integrator = scipy.integrate.ode(ode_rhs)\n') output.write(" " * 8) output.write("self.integrator.set_integrator('vode', method='bdf', " "with_jacobian=True)\n") # note the simulate method is fixed, i.e. it doesn't require any templating output.write( pad( r""" def simulate(self, tspan, param_values=None, view=False): if param_values is not None: # accept vector of parameter values as an argument if len(param_values) != len(self.parameters): raise Exception("param_values must have length %d" % len(self.parameters)) self.sim_param_values[:] = param_values else: # create parameter vector from the values in the model self.sim_param_values[:] = [p.value for p in self.parameters] self.y0.fill(0) for ic in self.initials: self.y0[ic.species_index] = self.sim_param_values[ic.param_index] if self.y is None or len(tspan) != len(self.y): self.y = numpy.empty((len(tspan), len(self.y0))) if len(self.observables): self.yobs = numpy.ndarray(len(tspan), list(zip((obs.name for obs in self.observables), itertools.repeat(float)))) else: self.yobs = numpy.ndarray((len(tspan), 0)) self.yobs_view = self.yobs.view(float).reshape(len(self.yobs), -1) # perform the actual integration self.integrator.set_initial_value(self.y0, tspan[0]) self.integrator.set_f_params(self.sim_param_values) self.y[0] = self.y0 t = 1 while self.integrator.successful() and self.integrator.t < tspan[-1]: self.y[t] = self.integrator.integrate(tspan[t]) t += 1 for i, obs in enumerate(self.observables): self.yobs_view[:, i] = \ (self.y[:, obs.species] * obs.coefficients).sum(1) if view: y_out = self.y.view() yobs_out = self.yobs.view() for a in y_out, yobs_out: a.flags.writeable = False else: y_out = self.y.copy() yobs_out = self.yobs.copy() return (y_out, yobs_out) """, 4)) return output.getvalue()
def export(self): """Generate the corresponding Mathematica ODEs for the PySB model associated with the exporter. Returns ------- string String containing the Mathematica code for the model's ODEs. """ if self.model.expressions: raise ExpressionsNotSupported() if self.model.compartments: raise CompartmentsNotSupported() output = StringIO() pysb.bng.generate_equations(self.model) # Add docstring if there is one if self.docstring: output.write('(*\n' + self.docstring + '\n') else: output.write("(*\n") # Header comment output.write("Mathematica model definition file for ") output.write("model " + self.model.name + ".\n") output.write("Generated by " \ "pysb.export.mathematica.MathematicaExporter.\n") output.write("\n") output.write("Run with (for example):\n") output.write("tmax = 10\n") output.write("soln = NDSolve[Join[odes, initconds], slist, " \ "{t, 0, tmax}]\n") output.write("Plot[s0[t] /. soln, {t, 0, tmax}, PlotRange -> All]\n") output.write("*)\n\n") # PARAMETERS # Note that in Mathematica, underscores are not allowed in variable # names, so we simply strip them out here params_str = '' for i, p in enumerate(self.model.parameters): # Remove underscores pname = p.name.replace('_', '') # Convert parameter values to scientific notation # If the parameter is 0, don't take the log! if p.value == 0: params_str += '%s = %g;\n' % (pname, p.value) # Otherwise, take the log (base 10) and format accordingly else: val_str = '%.17g' % p.value if 'e' in val_str: (mantissa, exponent) = val_str.split('e') params_str += '%s = %s * 10^%s;\n' % \ (pname, mantissa, exponent) else: params_str += '%s = %s;\n' % (pname, val_str) ## ODEs ### odes_str = 'odes = {\n' # Concatenate the equations odes_str += ',\n'.join([ 's%d == %s' % (i, sympy.ccode(self.model.odes[i])) for i in range(len(self.model.odes)) ]) # Replace, e.g., s0 with s[0] odes_str = re.sub(r's(\d+)', lambda m: 's%s[t]' % (int(m.group(1))), odes_str) # Add the derivative symbol ' to the left hand sides odes_str = re.sub(r's(\d+)\[t\] ==', r"s\1'[t] ==", odes_str) # Correct the exponentiation syntax odes_str = re.sub(r'pow\(([^,]+), ([^)]+)\)', r'\1^\2', odes_str) odes_str += '\n}' #c_code = odes_str # Eliminate underscores from parameter names in equations for i, p in enumerate(self.model.parameters): odes_str = re.sub(r'\b(%s)\b' % p.name, p.name.replace('_', ''), odes_str) ## INITIAL CONDITIONS ic_values = ['0'] * len(self.model.odes) for i, ic in enumerate(self.model.initials): idx = self.model.get_species_index(ic.pattern) ic_values[idx] = ic.value.name.replace('_', '') init_conds_str = 'initconds = {\n' init_conds_str += ',\n'.join( ['s%s[0] == %s' % (i, val) for i, val in enumerate(ic_values)]) init_conds_str += '\n}' ## SOLVE LIST solvelist_str = 'solvelist = {\n' solvelist_str += ',\n'.join( ['s%s[t]' % (i) for i in range(0, len(self.model.odes))]) solvelist_str += '\n}' ## OBSERVABLES observables_str = '' for obs in self.model.observables: # Remove underscores observables_str += obs.name.replace('_', '') + ' = ' #groups = self.model.observable_groups[obs_name] observables_str += ' + '.join([ '(s%s[t] * %d)' % (s, c) for s, c in zip(obs.species, obs.coefficients) ]) observables_str += ' /. soln\n' # Add comments identifying the species species_str = '\n'.join([ '(* s%d[t] = %s *)' % (i, s) for i, s in enumerate(self.model.species) ]) output.write('(* Parameters *)\n') output.write(params_str + "\n") output.write('(* List of Species *)\n') output.write(species_str + "\n\n") output.write('(* ODEs *)\n') output.write(odes_str + "\n\n") output.write('(* Initial Conditions *)\n') output.write(init_conds_str + "\n\n") output.write('(* List of Variables (e.g., as an argument to NDSolve) ' \ '*)\n') output.write(solvelist_str + '\n\n') output.write('(* Run the simulation -- example *)\n') output.write('tmax = 100\n') output.write('soln = NDSolve[Join[odes, initconds], ' \ 'solvelist, {t, 0, tmax}]\n\n') output.write('(* Observables *)\n') output.write(observables_str + '\n') return output.getvalue()
def export(self): """Generate the PottersWheel code for the ODEs of the PySB model associated with the exporter. Returns ------- string String containing the PottersWheel code for the ODEs. """ if self.model.expressions: raise ExpressionsNotSupported() if self.model.compartments: raise CompartmentsNotSupported() output = StringIO() pysb.bng.generate_equations(self.model) model_name = self.model.name.replace('.', '_') ic_values = [0] * len(self.model.odes) for cp, ic_param in self.model.initial_conditions: ic_values[self.model.get_species_index(cp)] = ic_param.value # list of "dynamic variables" pw_x = [ "m = pwAddX(m, 's%d', %e);" % (i, ic_values[i]) for i in range(len(self.model.odes)) ] # parameters pw_k = [ "m = pwAddK(m, '%s', %e);" % (p.name, p.value) for p in self.model.parameters ] # equations (one for each dynamic variable) # Note that we just generate C code, which for basic math expressions # is identical to matlab. We just have to change 'pow' to 'power'. # Ideally there would be a matlab formatter for sympy. pw_ode = [ "m = pwAddODE(m, 's%d', '%s');" % (i, sympy.ccode(self.model.odes[i])) for i in range(len(self.model.odes)) ] pw_ode = [re.sub(r'pow(?=\()', 'power', s) for s in pw_ode] # observables pw_y = [ "m = pwAddY(m, '%s', '%s');" % (obs.name, ' + '.join('%f * s%s' % t for t in zip(obs.coefficients, obs.species))) for obs in self.model.observables ] # Add docstring, if present if self.docstring: output.write('% ' + self.docstring.replace('\n', '\n% ')) output.write('\n') output.write('% PottersWheel model definition file\n') output.write('%% save as %s.m\n' % model_name) output.write('function m = %s()\n' % model_name) output.write('\n') output.write('m = pwGetEmptyModel();\n') output.write('\n') output.write('% meta information\n') output.write("m.ID = '%s';\n" % model_name) output.write("m.name = '%s';\n" % model_name) output.write("m.description = '';\n") output.write("m.authors = {''};\n") output.write("m.dates = {''};\n") output.write("m.type = 'PW-1-5';\n") output.write('\n') output.write('% dynamic variables\n') for x in pw_x: output.write(x) output.write('\n') output.write('\n') output.write('% dynamic parameters\n') for k in pw_k: output.write(k) output.write('\n') output.write('\n') output.write('% ODEs\n') for ode in pw_ode: output.write(ode) output.write('\n') output.write('\n') output.write('% observables\n') for y in pw_y: output.write(y) output.write('\n') output.write('\n') output.write('%% end of PottersWheel model %s\n' % model_name) return output.getvalue()
def export(self): """Generate a MATLAB class definition containing the ODEs for the PySB model associated with the exporter. Returns ------- string String containing the MATLAB code for an implementation of the model's ODEs. """ if self.model.expressions: raise ExpressionsNotSupported() if self.model.compartments: raise CompartmentsNotSupported() output = StringIO() pysb.bng.generate_equations(self.model) docstring = '' if self.docstring: docstring += self.docstring.replace('\n', '\n % ') # Substitute underscores for any dots in the model name model_name = self.model.name.replace('.', '_') # -- Parameters and Initial conditions ------- # Declare the list of parameters as a struct params_str = 'self.parameters = struct( ...\n' + ' ' * 16 params_str_list = [] for i, p in enumerate(self.model.parameters): # Add parameter to struct along with nominal value cur_p_str = "'%s', %.17g" % (_fix_underscores(p.name), p.value) # Decide whether to continue or terminate the struct declaration: if i == len(self.model.parameters) - 1: cur_p_str += ');' # terminate else: cur_p_str += ', ...' # continue params_str_list.append(cur_p_str) # Format and indent the params struct declaration params_str += ('\n' + ' ' * 16).join(params_str_list) # Fill in an array of the initial conditions based on the named # parameter values initial_values_str = ('initial_values = zeros(1,%d);\n'+' '*12) % \ len(self.model.species) initial_values_str += ('\n' + ' ' * 12).join([ 'initial_values(%d) = self.parameters.%s; %% %s' % (i + 1, _fix_underscores(ic.value.name), ic.pattern) for i, ic in enumerate(self.model.initials) ]) # -- Build observables declaration -- observables_str = 'self.observables = struct( ...\n' + ' ' * 16 observables_str_list = [] for i, obs in enumerate(self.model.observables): # Associate species and coefficient lists with observable names, # changing from zero- to one-based indexing cur_obs_str = "'%s', [%s; %s]" % \ (_fix_underscores(obs.name), ' '.join([str(sp+1) for sp in obs.species]), ' '.join([str(c) for c in obs.coefficients])) # Decide whether to continue or terminate the struct declaration: if i == len(self.model.observables) - 1: cur_obs_str += ');' # terminate else: cur_obs_str += ', ...' # continue observables_str_list.append(cur_obs_str) # Format and indent the observables struct declaration observables_str += ('\n' + ' ' * 16).join(observables_str_list) # -- Build ODEs ------- # Build a stringified list of species species_list = ['%% %s;' % s for i, s in enumerate(self.model.species)] # Build the ODEs as strings from the model.odes array odes_list = [ 'y(%d,1) = %s;' % (i + 1, sympy.ccode(self.model.odes[i])) for i in range(len(self.model.odes)) ] # Zip the ODEs and species string lists and then flatten them # (results in the interleaving of the two lists) odes_species_list = [ item for sublist in zip(species_list, odes_list) for item in sublist ] # Flatten to a string and add correct indentation odes_str = ('\n' + ' ' * 12).join(odes_species_list) # Change species names from, e.g., '__s(0)' to 'y0(1)' (note change # from zero-based indexing to 1-based indexing) odes_str = re.sub(r'__s(\d+)', \ lambda m: 'y0(%s)' % (int(m.group(1))+1), odes_str) # Change C code 'pow' function to MATLAB 'power' function odes_str = re.sub(r'pow\(', 'power(', odes_str) # Prepend 'p.' to named parameters and fix any underscores for i, p in enumerate(self.model.parameters): odes_str = re.sub(r'\b(%s)\b' % p.name, 'p.%s' % _fix_underscores(p.name), odes_str) # -- Build final output -- output.write( pad( r""" classdef %(model_name)s %% %(docstring)s %% A class implementing the ordinary differential equations %% for the %(model_name)s model. %% %% Save as %(model_name)s.m. %% %% Generated by pysb.export.matlab.MatlabExporter. %% %% Properties %% ---------- %% observables : struct %% A struct containing the names of the observables from the %% PySB model as field names. Each field in the struct %% maps the observable name to a matrix with two rows: %% the first row specifies the indices of the species %% associated with the observable, and the second row %% specifies the coefficients associated with the species. %% For any given timecourse of model species resulting from %% integration, the timecourse for an observable can be %% retrieved using the get_observable method, described %% below. %% %% parameters : struct %% A struct containing the names of the parameters from the %% PySB model as field names. The nominal values are set by %% the constructor and their values can be overriden %% explicitly once an instance has been created. %% %% Methods %% ------- %% %(model_name)s.odes(tspan, y0) %% The right-hand side function for the ODEs of the model, %% for use with MATLAB ODE solvers (see Examples). %% %% %(model_name)s.get_initial_values() %% Returns a vector of initial values for all species, %% specified in the order that they occur in the original %% PySB model (i.e., in the order found in model.species). %% Non-zero initial conditions are specified using the %% named parameters included as properties of the instance. %% Hence initial conditions other than the defaults can be %% used by assigning a value to the named parameter and then %% calling this method. The vector returned by the method %% is used for integration by passing it to the MATLAB %% solver as the y0 argument. %% %% %(model_name)s.get_observables(y) %% Given a matrix of timecourses for all model species %% (i.e., resulting from an integration of the model), %% get the trajectories corresponding to the observables. %% Timecourses are returned as a struct which can be %% indexed by observable name. %% %% Examples %% -------- %% Example integration using default initial and parameter %% values: %% %% >> m = %(model_name)s(); %% >> tspan = [0 100]; %% >> [t y] = ode15s(@m.odes, tspan, m.get_initial_values()); %% %% Retrieving the observables: %% %% >> y_obs = m.get_observables(y) %% properties observables parameters end methods function self = %(model_name)s() %% Assign default parameter values %(params_str)s %% Define species indices (first row) and coefficients %% (second row) of named observables %(observables_str)s end function initial_values = get_initial_values(self) %% Return the vector of initial conditions for all %% species based on the values of the parameters %% as currently defined in the instance. %(initial_values_str)s end function y = odes(self, tspan, y0) %% Right hand side function for the ODEs %% Shorthand for the struct of model parameters p = self.parameters; %(odes_str)s end function y_obs = get_observables(self, y) %% Retrieve the trajectories for the model observables %% from a matrix of the trajectories of all model %% species. %% Initialize the struct of observable timecourses %% that we will return y_obs = struct(); %% Iterate over the observables; observable_names = fieldnames(self.observables); for i = 1:numel(observable_names) obs_matrix = self.observables.(observable_names{i}); if isempty(obs_matrix) y_obs.(observable_names{i}) = zeros(size(y, 1), 1); continue end species = obs_matrix(1, :); coefficients = obs_matrix(2, :); y_obs.(observable_names{i}) = ... y(:, species) * coefficients'; end end end end """, 0) % { 'docstring': docstring, 'model_name': model_name, 'params_str': params_str, 'initial_values_str': initial_values_str, 'observables_str': observables_str, 'params_str': params_str, 'odes_str': odes_str }) return output.getvalue()
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 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