Skip to content

Commit

Permalink
fixed problems with sympification
Browse files Browse the repository at this point in the history
-- it is now allowed to name species after sympy import functions,
  such as 'S' or 'sin' ...
  • Loading branch information
bad-ants-fleet committed Jul 6, 2017
1 parent 363af6a commit 5e214fb
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 34 deletions.
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -86,7 +86,7 @@ $ python setup.py install --user
```

## Version
0.1.1
0.2

## Complete list of Python dependencies:
All dependencies are available using `pip`:
Expand Down
2 changes: 1 addition & 1 deletion crnsimulator/__init__.py
Expand Up @@ -6,7 +6,7 @@
# Use at your own risk.
#
#
__version__ = "v0.1.1"
__version__ = "v0.2"

from crnsimulator.crn_parser import parse_crn_string, parse_crn_file
from crnsimulator.reactiongraph import ReactionGraph
Expand Down
38 changes: 24 additions & 14 deletions crnsimulator/reactiongraph.py
Expand Up @@ -8,7 +8,7 @@
#

import networkx as nx
import sympy
from sympy import sympify, Matrix, Symbol

from crnsimulator.solver import writeODElib

Expand All @@ -30,7 +30,7 @@ def name(self):
class ReactionGraph(object):
"""Basic Reaction Graph Object. """

def __init__(self, crn = [], nxgraph = None):
def __init__(self, crn = None, nxgraph = None):
self._RG = nx.MultiDiGraph()
if crn :
self.add_reactions(crn)
Expand Down Expand Up @@ -60,31 +60,35 @@ def products(self):
return [n for n in self._RG.nodes() if not isinstance(n, ReactionNode) \
and self._RG.in_edges(n)]

def write_ODE_lib(self, sorted_vars=[], concvect=[], jacobian=False, rate_dict=False,
def write_ODE_lib(self, sorted_vars=None, concvect=None, jacobian=False, rate_dict=False,
odename = 'odesystem', filename = './odesystem', template = None):

if concvect: assert len(concvect) == len(sorted_vars)

if 'S' in set(sorted_vars):
raise CRNSimulatorError('S must not be a species name, sorry.')
V, M, J, R = self.ode_system(sorted_vars = sorted_vars, jacobian =
jacobian, rate_dict=rate_dict)

return writeODElib(V, M, jacobian=J, rdict=R, concvect = concvect,
odename = odename, filename = filename, template = None)

def ode_system(self, sorted_vars= [], jacobian = False, rate_dict = False):
def ode_system(self, sorted_vars = None, jacobian = False, rate_dict = False):
odict, rdict = self.get_odes(rate_dict = rate_dict)


if sorted_vars :
sorted_vars = map(Symbol, sorted_vars)
assert len(sorted_vars) == len(odict.keys())
else :
sorted_vars = sorted(odict.keys())
sorted_vars = sorted(odict.keys(), key=lambda x: str(x))

# Symbol namespace dictionary, translates every variable name into a Symbol,
# even awkward names such as 'sin' or 'cos'
ns = dict(zip(map(str,sorted_vars), sorted_vars))

M = []
for dx in sorted_vars :
M.append(sympy.sympify(' + '.join(['*'.join(map(str,xp)) for xp in odict[dx]])))
M = sympy.Matrix(M)
M.append(sympify(' + '.join(['*'.join(map(str,xp)) for xp in odict[dx]]), locals=ns))
M = Matrix(M)

if jacobian :
# NOTE: The sympy version breaks regularly:
Expand All @@ -94,13 +98,19 @@ def ode_system(self, sorted_vars= [], jacobian = False, rate_dict = False):
for f in M :
for x in sorted_vars:
J.append(f.diff(x))
J = sympy.Matrix(J)
J = Matrix(J)
else :
J=None

return sorted_vars, M, J, rdict

def get_odes(self, rate_dict = False):
"""Translate the reaction graph into ODEs.
Returns:
sympy.Symbols
"""
rdict = dict()
odes = dict()
for rxn in self._RG.nodes_iter() :
Expand All @@ -114,24 +124,24 @@ def get_odes(self, rate_dict = False):
reactants = []
for reac in self._RG.predecessors_iter(rxn) :
for i in range(self._RG.number_of_edges(reac, rxn)) :
reactants.append(reac)
reactants.append(Symbol(reac))

products = []
for prod in self._RG.successors_iter(rxn) :
for i in range(self._RG.number_of_edges(rxn, prod)) :
products.append(prod)
products.append(Symbol(prod))

for x in reactants:
if x in odes :
odes[x].append(['-'+rate] + reactants)
else :
odes[x]= [['-'+rate] + reactants]
odes[x] = [['-'+rate] + reactants]

for x in products:
if x in odes :
odes[x].append([rate] + reactants)
else :
odes[x]= [[rate] + reactants]
odes[x] = [[rate] + reactants]

return odes, rdict

Expand Down
11 changes: 7 additions & 4 deletions crnsimulator/solver.py
Expand Up @@ -8,7 +8,7 @@

import crnsimulator.odelib_template

def writeODElib(svars, odeM, jacobian=None, rdict=None, concvect = [],
def writeODElib(svars, odeM, jacobian=None, rdict=None, concvect = None,
odename = 'odesystem', filename = './odesystem', template = None) :
""" Write an ODE system into an executable python script.
Expand All @@ -35,6 +35,8 @@ def writeODElib(svars, odeM, jacobian=None, rdict=None, concvect = [],
if template[-1] == 'c':
template = template[:-1]

svars = map(str, svars)

odetemp = ''
with open(template, 'r') as tfile:
odetemp = tfile.read()
Expand Down Expand Up @@ -109,9 +111,10 @@ def writeODElib(svars, odeM, jacobian=None, rdict=None, concvect = [],
# Default concentrations in integrate()
concstring = ''
#print "\n ".join([map("p0[{}] = {}".format, enumerate(concvect))])
for e, c in enumerate(concvect) :
if c :
concstring += "p0[{}] = {}\n ".format(e,c)
if concvect:
for e, c in enumerate(concvect) :
if c :
concstring += "p0[{}] = {}\n ".format(e,c)
odetemp = odetemp.replace("#<&>DEFAULTCONCENTRATIONS<&>#",concstring)

if filename[-3:] != '.py' : filename += '.py'
Expand Down
26 changes: 14 additions & 12 deletions tests/test_reactiongraph.py
Expand Up @@ -28,18 +28,20 @@ def test_init_from_crn(self):
self.assertEqual(sorted(RG.species), ['A','B','C','E'])
self.assertEqual(sorted(RG.reactants), ['A','B'])
self.assertEqual(sorted(RG.products), ['C','E'])

M, R = RG.get_odes()
rM = {'A': [['-18', 'A', 'B'], ['-99', 'A']], 'C': [['18', 'A', 'B'], ['99', 'A']], 'B': [['-18', 'A', 'B']], 'E': [['99', 'A']]}
rR = {}
self.assertItemsEqual(M, rM)
self.assertDictEqual(R, rR)

M, R = RG.get_odes(rate_dict=True)
rM = {'A': [['-k0', 'A', 'B'], ['-k1', 'A']], 'C': [['k0', 'A', 'B'], ['k1', 'A']], 'B': [['-k0', 'A', 'B']], 'E': [['k1', 'A']]}
rR = {'k1': 99, 'k0': 18}
self.assertDictEqual(M, rM)
self.assertDictEqual(R, rR)


# TODO: structure of M and R is variable, cannot check it like this
# M, R = RG.get_odes()
# rM = {'A': [['-18', 'A', 'B'], ['-99', 'A']], 'C': [['18', 'A', 'B'], ['99', 'A']], 'B': [['-18', 'A', 'B']], 'E': [['99', 'A']]}
# rR = {}
# self.assertItemsEqual(M, rM)
# self.assertDictEqual(R, rR)

# M, R = RG.get_odes(rate_dict=True)
# rM = {'A': [['-k0', 'A', 'B'], ['-k1', 'A']], 'C': [['k0', 'A', 'B'], ['k1', 'A']], 'B': [['-k0', 'A', 'B']], 'E': [['k1', 'A']]}
# rR = {'k1': 99, 'k0': 18}
# self.assertDictEqual(M, rM)
# self.assertDictEqual(R, rR)

if __name__ == '__main__':
unittest.main()
Expand Down
46 changes: 44 additions & 2 deletions tests/test_solver.py
Expand Up @@ -19,8 +19,10 @@ def setUp(self):

def tearDown(self):
ReactionNode.rid = 0
os.remove(self.filename)
os.remove(self.executable)
if os.path.exists(self.filename):
os.remove(self.filename)
if os.path.exists(self.executable):
os.remove(self.executable)

def test_crn(self):
# At some pont the simulator had troubles with CRNs that have
Expand Down Expand Up @@ -58,3 +60,43 @@ def test_crn(self):
self.assertEqual(first, (0.10000000000000001, 0.5))
self.assertEqual(last, (10.0, 0.88535344232897151))

def test_crn_sympy_imports(self):
# Test if the simulator can handle awkward species names:
# From sympy.sympify: If you want all single-letter and Greek-letter
# variables to be symbols then you can use the clashing-symbols
# dictionaries that have been defined there as private variables: _clash1
# (single-letter variables), _clash2 (the multi-letter Greek names) or
# _clash (both single and multi-letter names that are defined in abc).
crn = "2S <=> 3sin; sin + cos -> A; S -> cos [k=0.1]"
crn, _ = parse_crn_string(crn, process=True)

# Split CRN into irreversible reactions
new = []
for [r, p, k] in crn :
if None in k :
k[:] = [x if x != None else 1 for x in k]
if len(k) == 2:
new.append([r,p,k[0]])
new.append([p,r,k[1]])
else :
new.append([r,p,k[0]])
crn = new

RG = ReactionGraph(crn)

filename, odename = RG.write_ODE_lib(filename=self.filename)
_temp = imp.load_source(odename, filename)
integrate = getattr(_temp, 'integrate')

self.args.p0 = ['S=0.5', 'cos=0.2']
self.args.t_log = 10
self.args.t0 = 0.1
self.args.t8 = 10
simu = integrate(self.args)

first = simu[0]
last = simu[-1]

self.assertEqual(first, (0.10, 0.0, 0.5, 0.20, 0.0))
self.assertEqual(last, (10.0, 0.27442495177208459, 0.06646052036496547, 0.068597456334669946, 0.16135065552033576))

0 comments on commit 5e214fb

Please sign in to comment.