-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTheanoCodeGenerationForODEsWithSymPy.py
More file actions
158 lines (104 loc) · 4.12 KB
/
Copy pathTheanoCodeGenerationForODEsWithSymPy.py
File metadata and controls
158 lines (104 loc) · 4.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# -*- coding: utf-8 -*-
# Generating theano for integrating differential equation systems
from pylab import *
from sympy import *
from sympy.tensor import IndexedBase, Idx
symbols('dt')==symbols('dt')
# Start with a simple oscillator model
x, y, dt, tau, a = symbols('x y dt tau a')
ddt = {
x: tau*(x - x**3/3 + y),
y: (a - x)/tau
}
# Form the "half" step, and then the full step for the Heun method
half = {u: u + dt*du for u, du in ddt.iteritems()}
heun = [u + (du + du.subs(half))*dt/2 for u, du in half.iteritems()]
# Then, ask sympy to perform common subexpression elimination, or `cse`, so that we don't compute the same thing multiple times
aux, exs = cse(heun)
for l, r in aux:
print l, '<-', r
for u, ex in zip(ddt.keys(), exs):
print u, '<-', ex
# Here we want to change the kernel
template = '''
function([X, DX, dt, Param(a, default=, tau], [X])
{{
int step;
{decl}
theano.scan()(step=0; step<{nstep}; step++)
{{
{loop}
}}
}}
'''
decl = 'float x, y, ' + ', '.join([ccode(au) for au, _ in aux]) + ';'
nstep = str(100)
loop = ('; '.join(['%s=X[%d]' % (ccode(u), i) for i, u in enumerate(ddt.keys())]) + ';\n\n'
+ ''.join(['%s = %s;\n' % (l, ccode(r)) for l, r in aux]) + '\n'
+ ''.join(['DX[%d] = %s;\n' % (i, ccode(ex)) for i, ex in enumerate(exs)])
)
kernel = template.format(
decl=decl,
nstep=nstep,
loop=loop
)
print kernel
# Of course this is only for one node, no delays or noise or connectivity. Also can do this just with strings:
system = '''
dx = tau*(x - x**3/3 + y)
dy = (a - x)/tau
'''
ddt2 = {}
for lhs, rhs in [line.split('=') for line in system.split('\n') if line]:
svar = parse_expr(lhs.replace('d', ''))
vf = parse_expr(rhs)
ddt2[svar] = vf
ddt2
# Then we can have just a translating function
def translate(system, nstep=100):
"""
Input `system` is either a string with lines containing dx = x - x**3/3 + y
or a dictionary like {'x': 'x - x**3/3 + y'}.
"""
# parse system definition
if type(system) in (str, unicode):
ddt = {}
for lhs, rhs in [line.split('=') for line in system.split('\n') if line]:
svar = parse_expr(lhs.replace('d', ''))
vf = parse_expr(rhs)
ddt[svar] = vf
elif isinstance(system, dict):
ddt = {symbols(k): parse_expr(v) for k, v in system.iteritems()}
else:
raise NotImplementedError("don't know what to do with %r" % (system,))
# create Heun discretization
half = {u: u + dt*du for u, du in ddt.iteritems()}
heun = [u + (du + du.subs(half))*dt/2 for u, du in half.iteritems()]
# move common parts to aux variables
aux, exs = cse(heun, optimizations='basic', order='none')
# compile to kernel source
decl = 'float ' + ', '.join(map(ccode, ddt.keys())) + ', ' + ', '.join([ccode(au) for au, _ in aux]) + ';'
nstep = str(nstep)
loop = ('; '.join(['%s=X[%d]' % (ccode(u), i) for i, u in enumerate(ddt.keys())]) + ';\n\n'
+ ''.join(['%s = %s;\n' % (l, ccode(r)) for l, r in aux]) + '\n'
+ ''.join(['DX[%d] = %s;\n' % (i, ccode(ex)) for i, ex in enumerate(exs)])
)
return template.format(
decl=decl,
nstep=nstep,
loop=loop
)
# et voila
print translate('''
dx = tau*(x - x**3/3 + y)
dy = (a - x + b*y + c*y**2)/tau
dz = x + y + z + x*y*z
''')
# Hm, it automatically changed the order of `x, z, y` but that's because `ddt` is a `dict` which doesn't keep track of order.
# `Integrator` can inspect the `Model` to find out state variable names, parameter names, and equations.
#
# This is sufficient to write a general numexpr evaluator for the dfun (which should be `Model.dfun`), and as done here, sufficient to generate an OpenCL kernel specific to this integration scheme, if so desired.
#
# However, perhaps the set up is right, if we just take the type(integrator) to indicate which scheme to use. The integrator could be responsible for either evaluating with numexpr the model as would be done now, or providing the correct discretization scheme. After, the simulator is responsible for applying the kernel, etc.
#
# OK Not bad.