def test_BlockMatrix_Inverse_execution(): k, n = 2, 4 dtype = 'float32' A = sympy.MatrixSymbol('A', n, k) B = sympy.MatrixSymbol('B', n, n) inputs = A, B output = B.I*A cutsizes = {A: [(n/2, n/2), (k/2, k/2)], B: [(n/2, n/2), (n/2, n/2)]} cutinputs = [sympy.blockcut(i, *cutsizes[i]) for i in inputs] cutoutput = output.subs(dict(zip(inputs, cutinputs))) dtypes = dict(zip(inputs, [dtype]*len(inputs))) f = theano_function(inputs, [output], dtypes=dtypes) fblocked = theano_function(inputs, [sympy.block_collapse(cutoutput)], dtypes=dtypes) import numpy ninputs = [numpy.random.rand(*x.shape).astype(dtype) for x in inputs] ninputs = [numpy.arange(n*k).reshape(A.shape).astype(dtype), numpy.eye(n).astype(dtype)] ninputs[1] += numpy.ones(B.shape)*1e-5 assert numpy.allclose(f(*ninputs), fblocked(*ninputs), rtol=1e-5)
def test_theano_function_kwargs(): import numpy as np f = theano_function([x, y, z], [x + y], dim=1, on_unused_input='ignore', dtypes={ x: 'float64', y: 'float64', z: 'float64' }) assert np.linalg.norm(f([1, 2], [3, 4], [0, 0]) - np.asarray([4, 6])) < 1e-9 f = theano_function([x, y, z], [x + y], dtypes={ x: 'float64', y: 'float64', z: 'float64' }, dim=1, on_unused_input='ignore') xx = np.arange(3).astype('float64') yy = 2 * np.arange(3).astype('float64') zz = 2 * np.arange(3).astype('float64') assert np.linalg.norm(f(xx, yy, zz) - 3 * np.arange(3)) < 1e-9
def lambdify(args, expr, subs=None, simplify=True): """ call to lambdify with chosen options """ vector_expr = hasattr(expr, 'index') if subs is not None: if vector_expr: for i, e in enumerate(expr): expr[i] = e.subs(subs) else: expr = expr.subs(subs) if simplify: expr = simp(expr) expr = sympy.sympify(expr) # array2mat = [{'ImmutableMatrix': numpy.matrix}, 'numpy'] try: if vector_expr: expr_lambda = theano_function(args, expr, on_unused_input='ignore') else: expr_lambda = theano_function(args, [expr], on_unused_input='ignore') except: expr_lambda = sympy.lambdify(args, expr, dummify=False, modules='numpy') return expr_lambda
def prepareStatment(self): A = MatrixSymbol('A',*self.A.shape) H = MatrixSymbol('H',*self.H.shape) x = MatrixSymbol('x',*self.x.shape) P = MatrixSymbol('P',self.H.shape[1],self.H.shape[1]) Q = MatrixSymbol('Q',*self.Q.shape) R = MatrixSymbol('R',*self.R.shape) I = MatrixSymbol('I',max(x.shape),max(x.shape)) measurement = MatrixSymbol('measurement', *(H * x).shape) #Update y = measurement - H * x S = H * P * H.T + R K = P * H.T * S.I up_x = x + K * y up_P = (I - K * H) * P inputs = [x,P,H,R,I,measurement] outputs = [up_x, up_P] dtypes = {inp: 'float64' for inp in inputs} self.update = theano_function(inputs, outputs, dtypes=dtypes) #Predict pre_x = A * x pre_P = A * P * A.T + Q inputs = [A,x,P,Q] outputs = [pre_x, pre_P] dtypes = {inp: 'float64' for inp in inputs} self.predict = theano_function(inputs, outputs, dtypes=dtypes)
def __generate_theano_functions(self): self.rates_theano = theano_function(self.variables.reference + self.parameters.reference, [t.rate for t in self.transitions], on_unused_input='ignore') self.vector_field_theano = theano_function(self.variables.reference + self.parameters.reference, self._vector_field_sympy, on_unused_input='ignore')
def test_theano_function_numpy(): import numpy as np f = theano_function([x, y], [x+y], dim=1) assert np.linalg.norm(f([1, 2], [3, 4]) - np.asarray([4, 6])) < 1e-9 f = theano_function([x, y], [x+y], dtypes={x: 'float64', y: 'float64'}, dim=1) xx = np.arange(3).astype('float64') yy = 2*np.arange(3).astype('float64') assert np.linalg.norm(f(xx, yy) - 3*np.arange(3)) < 1e-9
def test_theano_function_kwargs(): import numpy as np f = theano_function([x, y, z], [x+y], dim=1, on_unused_input='ignore', dtypes={x: 'float64', y: 'float64', z: 'float64'}) assert np.linalg.norm(f([1, 2], [3, 4], [0, 0]) - np.asarray([4, 6])) < 1e-9 f = theano_function([x, y, z], [x+y], dtypes={x: 'float64', y: 'float64', z: 'float64'}, dim=1, on_unused_input='ignore') xx = np.arange(3).astype('float64') yy = 2*np.arange(3).astype('float64') zz = 2*np.arange(3).astype('float64') assert np.linalg.norm(f(xx, yy, zz) - 3*np.arange(3)) < 1e-9
def theano_lambdify(args, expr): """ Lambdify expression expr w.r.t arguments args using theano. """ theano_opts = {'on_unused_input': 'ignore', 'allow_input_downcast': False} # detect if expression is a vector if isinstance(expr, types.vector_types): # Below converts output of 1D vector functions with length 1... # ... back to 1D numpy array, because the default is 0D array (scalar). if len(expr) == 1: def getslice(f): return numpy.asarray(f, dtype=DTYPE)[numpy.newaxis] else: def getslice(f): return numpy.asarray(f, dtype=DTYPE) else: expr = [ expr, ] def getslice(f): return numpy.asarray(f, dtype=DTYPE) # theano does not accept sympy numbers as output... expr_lambda = theano_function(args, expr, **theano_opts) return lambda *args: getslice(expr_lambda(*args))
def sympy_theanify(sympy_expr, symbols=()): if isinstance(sympy_expr, Expr): if not symbols: symbols = sympy_expr.free_symbols return theano_function(symbols, [sympy_expr]) else: return lambda **kwargs: sympy_expr
def theano_lambdify(args, expr): """ Lambdify expression expr w.r.t arguments args using theano. """ theano_opts = {'on_unused_input': 'ignore', 'allow_input_downcast': False} # detect if expression is a vector if isinstance(expr, types.vector_types): # Below converts output of 1D vector functions with length 1... # ... back to 1D numpy array, because the default is 0D array (scalar). if len(expr) == 1: def getslice(f): return numpy.asarray(f, dtype=DTYPE)[numpy.newaxis] else: def getslice(f): return numpy.asarray(f, dtype=DTYPE) else: expr = [expr, ] def getslice(f): return numpy.asarray(f, dtype=DTYPE) # theano does not accept sympy numbers as output... expr_lambda = theano_function(args, expr, **theano_opts) return lambda *args: getslice(expr_lambda(*args))
def _theanoize(self, outputs): self.define_inputs() old_check_input = theano.config.check_input old_allow_gc = theano.config.allow_gc try: # This affects compilation and removes the input check at each step. theano.config.check_input = False # Disable Theano garbage collection to lower the number of allocations. theano.config.allow_gc = False f_imp = theano_function(self.inputs, outputs, on_unused_input='ignore', mode=theano.Mode(linker='c')) finally: theano.config.check_input = old_check_input theano.config.allow_gc = old_allow_gc # While denoting an input as trusted lowers Theano overhead: # f.trust_input = True # we can bypass additional overhead with the following function: def f(*args): for i in range(len(args)): f_imp.input_storage[i].storage[0] = args[i] f_imp.fn() return [f_imp.output_storage[i].data for i in range(len(outputs))] return f
def make_theano_fns_of_d1d2xw(self, dg_first, dg_second): args = self.normal_x_s_d.values() + self.normal_w_s_d.values() + \ self.param_sym_dict.values() args_values_x = [x.subs(self.normal_xw_s_ss_values_d) for x in self.normal_x_s_d.values()] args_values_w = [w.subs(self.normal_xw_s_ss_values_d) for w in self.normal_w_s_d.values()] args_values_p = [p.subs(self.par_to_values_dict) for p in self.param_sym_dict.values()] args_values = args_values_x + args_values_w + args_values_p dg_first_th = theano_function(args, dg_first, on_unused_input='ignore') dg_second_th = theano_function( args, dg_second, on_unused_input='ignore') return dg_first_th, dg_second_th, args_values
def _theanoize(self, outputs): self.define_inputs() f = theano_function(self.inputs, outputs, on_unused_input='ignore') # Theano will run faster if you trust the input. I'm not sure # what the implications of this are. See: # http://deeplearning.net/software/theano/tutorial/faq.html#faster-small-theano-function # Note that map(np.asarray, np.hstack(args)) is required if # trust_input is True. If it is False, then it will sanitize the # inputs. I'm not sure which one is faster. f.trust_input = True return f
def __init__(self, var_names_and_syms={}, dict_or_expr={}): if hasattr(dict_or_expr, 'keys'): for k, v in dict_or_expr.items(): dict_or_expr[k] = CompyledFunc(var_names_and_syms=var_names_and_syms, dict_or_expr=v) self.Compyled = dict_or_expr elif is_non_atomic_sympy_expr(dict_or_expr): self.Vars = tuple(var for var, symbol in var_names_and_syms.items() if symbol and not(isinstance(symbol, FLOAT_TYPES))) inputs = (var_names_and_syms[var] for var in self.Vars) if use_theano: self.Compyled = theano_function(inputs, (dict_or_expr,), allow_input_downcast=True) else: self.Compyled = ufuncify(inputs, dict_or_expr) else: self.Compyled = sympy_to_float(dict_or_expr)
def linearize_gpc_dynamics_noinit_theano(dynamics, x, u, n_uncert, p, input_states): n_states = x.shape[0] Hp = GaussHermitePC(n_uncert - n_states, p) l = Hp.shape[0] xc_vals = zeros(l, n_states) for i in range(0, n_states): xc_vals[:, i] = Matrix( [symbols('x' + str(i) + str(j)) for j in range(0, l)]) states = xc_vals.T.reshape(n_states * l, 1) dynH = Matrix(dynamics) Aj = dynH.jacobian(states) Bj = dynH.jacobian(u) Cj = dynH - Aj * states - Bj * u A = theano_function(input_states, Aj, on_unused_input='ignore') B = theano_function(input_states, Bj, on_unused_input='ignore') C = theano_function(input_states, Cj, on_unused_input='ignore') return A, B, C #theano functions for linearization
def expr_to_theano(expr, ndims, *args, **subs): from sympy.printing.theanocode import theano_function f_sym = sp.lambdify(args, subs_dict(expr, subs), modules=("sympy", )) subbed_expr = subs_dict(expr, subs) dims = {arg: ndims for i, arg in enumerate(args)} dtypes = {arg: 'float64' for arg in args} ph = sp.Symbol('PLACEHOLDER') # Compile the function with the dummpy var. f_th = theano_function( [ph, *args], [ph + subbed_expr], # If args is longer than grid, repeat last dim in grid # e.g. theta, phi share a dimension (p) # Five args, but only four dimensions dims={ ph: ndims, **dims }, dtypes={ ph: 'float64', **dtypes }, on_unused_input='ignore') # Set placeholder to zero and # broadcast before giving to theano @fu.wraps(f_sym) def f_wrap(*inner_args): # Broadcast in case inner_args # are not already broadcasted. # If they are, that's fine too. bcast_shape = np.shape(sum(inner_args)) bcast_args = [np.broadcast_to(arg, bcast_shape) for arg in inner_args] print("f_th_wrap got args:") for i, inner_arg in enumerate(inner_args): print("var {}: type={}, shape={}".format(i, type(inner_arg), np.shape(inner_arg))) print("bcast var {}: type={}, shape={}".format( i, type(bcast_args[i]), np.shape(bcast_args[i]))) phn = np.zeros_like(bcast_args[0]) return f_th(phn, *bcast_args) return f_wrap
def obs_term_gradient_optimization(obs_gradient, background_data_vector, smallest_gradient=0.001, iteration_step=0.001): # def _get_haha(x): # a = obs_gradient.subs({analysis_increment_vector_symbol: x}) # return a obs_term_gradient_value = numpy.matrix( 999.0 * numpy.ones(background_data_vector.shape)) #analysis_increment_vector = sympy.Matrix(numpy.zeros(background_data_vector.shape)) analysis_increment_vector = numpy.matrix( numpy.zeros(background_data_vector.shape)) analysis_increment_vector_symbol = sympy.MatrixSymbol( 'analysis_increment_symbol', len(background_data_vector), 1) # from scipy import optimize # optimize.minimize(_get_haha, numpy.zeros(background_data_vector.shape), method="CG") # a = _get_haha(x) dtypes = {inp: 'float8' for inp in analysis_increment_vector_symbol} print 'create func' f = theano_function([analysis_increment_vector_symbol], [obs_gradient], dtypes=dtypes) #f=sympy.lambdify(analysis_increment_vector_symbol, obs_gradient, 'numpy') print 'created func' #f=sympy.lambdify(analysis_increment_vector_symbol, obs_gradient, 'sympy') n = 0 while (obs_term_gradient_value.sum() <= -smallest_gradient or obs_term_gradient_value.sum() > smallest_gradient): # obs_term_gradient_value = obs_gradient.subs({analysis_increment_vector_symbol: analysis_increment_vector}).doit() # f=sympy.lambdify(analysis_increment_vector_symbol, obs_gradient, 'sympy') #obs_term_gradient_value = f(analysis_increment_vector) obs_term_gradient_value = f(analysis_increment_vector) analysis_increment_vector = analysis_increment_vector - iteration_step * obs_term_gradient_value print n, obs_term_gradient_value.sum() n = n + 1 if n > 2000: break # print analysis_increment_vector # print background_data_vector + analysis_increment_vector return background_data_vector + analysis_increment_vector
def prepareStatment(self): measurement = MatrixSymbol('measurement', *(self.H * self.x).shape) #Update y = measurement - self.H * self.x S = self.H * self.P * self.H.T + self.R K = self.P * self.H.T * S.I upx = self.x + K * y upP = self.I - K * self.H #Predict new_x = self.A * upx new_P = self.A * upP * self.A.T + self.Q inputs = [self.A,self.x,self.P,self.H,self.R,self.I,self.Q,measurement] outputs = [new_x, new_P] dtypes = {inp: 'float64' for inp in inputs} self.theano_update = theano_function(inputs, outputs, dtypes=dtypes)
def __init__(self, var_names_and_syms={}, dict_or_expr={}): if hasattr(dict_or_expr, 'keys'): for k, v in list(dict_or_expr.items()): dict_or_expr[k] = CompyledFunc( var_names_and_syms=var_names_and_syms, dict_or_expr=v) self.Compyled = dict_or_expr elif is_non_atomic_sympy_expr(dict_or_expr): self.Vars = tuple( var for var, symbol in list(var_names_and_syms.items()) if symbol and not (isinstance(symbol, FLOAT_TYPES))) inputs = (var_names_and_syms[var] for var in self.Vars) if use_theano: self.Compyled = theano_function(inputs, (dict_or_expr, ), allow_input_downcast=True) else: self.Compyled = ufuncify(inputs, dict_or_expr) else: self.Compyled = sympy_to_float(dict_or_expr)
def build_mixture_loss_and_grad(build_pseudo_functions=False): class GaussianPDF(sympy.Function): nargs = 3 is_commutative = False @classmethod def eval(cls, x, mu, sigma): std_x = (x - mu)/sigma return (( 1/(sigma*sympy.sqrt(sympy.pi*2)) )*sympy.exp(-(std_x**2)/2)) class GaussianMixturePDF(sympy.Function): nargs = 4 @classmethod def eval(cls, x, mu, sigma, lamda): return ( (1-lamda)*GaussianPDF(x, 0, 1) + lamda*GaussianPDF(x, mu, sigma) ) class GaussianMixtureCDF(sympy.Function): nargs = 4 @classmethod def eval(cls, x, mu, sigma, lamda): z = sympy.symbols("z", real=True, finite=True) rv = sympy.simplify(sympy.Integral( GaussianMixturePDF(z, mu, sigma, lamda), (z, -sympy.oo, x)).doit()) return rv class GaussianMixtureCDF_inverse(sympy.Function): """ @classmethod def eval(cls, r, mu, sigma, lamda): if mu == 0 and sigma == 1: return sympy.erfi(r) return sympy.Function('GaussianMixtureCDF_inverse')( r, mu, sigma, lamda) """ def _eval_is_real(self): return True def _eval_is_finite(self): return True def fdiff(self, argindex): r, mu, sigma, lamda = self.args # if mu=0 and sigma=1, then this is # just the inverse standard erf so return erfi if mu == 0 and sigma == 1: return sympy.diff(sympy.erfi(r), self.args[argindex-1]) tmp = sympy.symbols("tmp", real=True, finite=True) z_s = GaussianMixtureCDF(tmp, mu, sigma, lamda) inv_diff = sympy.diff(z_s, self.args[argindex-1]) return sympy.simplify(1/inv_diff.subs(tmp, self)) # create symbols for the modle params lamda_s, sigma_s = sympy.symbols( "lamda, sigma", positive=True, real=True, finite=True) mu_s, rho_s = sympy.symbols( "mu, rho", real=True, finite=True) # if we are building the pseudo functiosn then # the ranks are what we actually observe, so we need # to wrap them in an inverse CDF call if build_pseudo_functions: r1_s = sympy.symbols("r1_s", real=True, finite=True, positive=True) r2_s = sympy.symbols("r2_s", real=True, finite=True, positive=True) z1_s = GaussianMixtureCDF_inverse(r1_s, mu_s, sigma_s, lamda_s) z2_s = GaussianMixtureCDF_inverse(r2_s, mu_s, sigma_s, lamda_s) # otherwise we just use standard symbols for the obsreved values else: z1_s, z2_s = sympy.symbols("z1, z2", real=True, finite=True) ####### build the marginal densities std_z1_s = (z2_s - mu_s)/sigma_s std_z2_s = (z1_s - mu_s)/sigma_s ####### bivariate normal density sym_signal_density = ( 1./(2.*sympy.pi*sigma_s*sigma_s) )*( 1./sympy.sqrt(1.-rho_s**2) )*sympy.exp(-( std_z1_s**2 + std_z2_s**2 - 2*rho_s*std_z1_s*std_z2_s )/(2*(1-rho_s**2))) sym_noise_density = ( 1./(2.*sympy.pi) )*sympy.exp(-(z1_s**2 + z2_s**2)/2) sym_log_lhd = sympy.simplify(sympy.log(lamda_s*sym_signal_density + (1-lamda_s)*sym_noise_density)) # we use the following in the theano calls instead of the z's # so that theano won't choke pv_1, pv_2 = sympy.symbols('pv_1 pv_2', real=True, finite=True) # differentiate, replace the inverse micture CDF's with pv_'s, # and then build the theano functions sym_gradients = [] for sym in (mu_s, sigma_s, rho_s, lamda_s): sym_grad = sympy.diff(sym_log_lhd, sym) pv_sym_grad = sym_grad.subs({z1_s: pv_1, z2_s: pv_2}) sym_gradients.append( pv_sym_grad ) theano_gradient = theano_function( (mu_s, sigma_s, rho_s, lamda_s, pv_1, pv_2), sym_gradients, dims={mu_s:1, sigma_s:1, rho_s:1, lamda_s:1, pv_1: 1, pv_2:1}) theano_log_lhd = theano_function( (mu_s, sigma_s, rho_s, lamda_s, pv_1, pv_2), [sym_log_lhd.subs({z1_s: pv_1, z2_s: pv_2}),], dims={mu_s:1, sigma_s:1, rho_s:1, lamda_s:1, pv_1: 1, pv_2:1}) # wrap the theano functions in python functions, and return them def calc_log_lhd(theta, z1, z2): mu, sigma, rho, lamda = theta return theano_log_lhd( numpy.repeat(mu, len(z1)), numpy.repeat(sigma, len(z1)), numpy.repeat(rho, len(z1)), numpy.repeat(lamda, len(z1)), z1, z2 ).sum() def calc_log_lhd_gradient(theta, z1, z2, fix_mu, fix_sigma): mu, sigma, rho, lamda = theta res = theano_gradient( numpy.repeat(mu, len(z1)), numpy.repeat(sigma, len(z1)), numpy.repeat(rho, len(z1)), numpy.repeat(lamda, len(z1)), z1, z2 ) return numpy.array( [x.sum() for x in res] ) return calc_log_lhd, calc_log_lhd_gradient
from computations.matrices.examples import kalman from sympy.printing.theanocode import theano_function f = theano_function(kalman.inputs, kalman.outputs, {i: 'float64' for i in kalman.inputs}) import theano theano.printing.pydotprint(f, format='dot', outfile='images/theano-kalman')
def test_bad_keyword_args_raise_error(): raises(Exception, lambda: theano_function([x], [x + 1], foobar=3))
def numeric_right_hand_side(mass_matrix, forcing_vector, constants, coordinates, speeds, specified=None, generator='lambdify'): """Returns a function for the right hand side of the first order ordinary differential equations from a system described by: M(constants, coordinates) x' = F(constants, coordinates, speeds, specified) which can be evaluated numerically. Parameters ---------- mass_matrix : sympy.matrices.dense.MutableDenseMatrix, shape(n,n) The symbolic mass matrix of the system. forcing_vector : sympy.matrices.dense.MutableDenseMatrix, shape(n,1) The symbolic forcing vector of the system. constants : list of sympy.core.symbol.Symbol The constants in the equations of motion. coordinates : list of sympy.core.function.Function The generalized coordinates of the system. speeds : list of sympy.core.function.Function The generalized speeds of the system. specified : list of sympy.core.function.Function The specifed quantities of the system. generator : string, {'lambdify'|'theano'|'cython'}, optional The method used for generating the numeric right hand side. Returns ------- right_hand_side : function A function which evaluates the derivaties of the states. """ if generator == 'lambdify' or generator == 'theano': arguments = constants + coordinates + speeds if specified is not None: arguments += specified if generator == 'lambdify': mass_matrix_func = lambdify(arguments, mass_matrix) forcing_vector_func = lambdify(arguments, forcing_vector) elif generator == 'theano': mass_matrix_func = theano_function(arguments, [mass_matrix], on_unused_input='ignore') forcing_vector_func = theano_function(arguments, [forcing_vector], on_unused_input='ignore') # Theano will run faster if you trust the input. I'm not sure # what the implications of this are. See: # http://deeplearning.net/software/theano/tutorial/faq.html#faster-small-theano-function mass_matrix_func.trust_input = True forcing_vector_func.trust_input = True def mass_forcing_func(numerical_constants, numerical_coordinates, numerical_speeds, numerical_specified=None): """Returns numerical evaluations of the mass matrix and forcing vector.""" values = [numerical_constants, numerical_coordinates, numerical_speeds] if specified is not None: values.append(numerical_specified) value_array = np.hstack(tuple(values)) if generator == 'theano': value_array = [np.asarray(v) for v in value_array] return mass_matrix_func(*value_array), forcing_vector_func(*value_array) elif generator == 'cython': filename_prefix = 'multibody_system' # TODO : This is a hack to allow you to regenerate cython modules # without closing the Python session. It may be best to also force # the user to provide a module name when generating the Cython code. # Check out the Cython inline code to figure out how to do all this # better with disutils: # https://github.com/cython/cython/blob/master/Cython/Build/Inline.py exists = True while exists: try: open(filename_prefix + '.so', 'r') except IOError: exists = False else: filename_prefix += '_' + random.choice(all_letters) generate_mass_forcing_cython_code(filename_prefix, mass_matrix, forcing_vector, constants, coordinates, speeds, specified=specified) cython_module = importlib.import_module(filename_prefix) mass_forcing_func = cython_module.mass_forcing_matrices else: # TODO : add numba, fortan, parakeet, sympy.autowrap (needs matrix # support) raise NotImplementedError('The {} code generation is not implemented'.format(generator)) def right_hand_side(x, t, args): """Returns the derivatives of the states. Parameters ---------- x : ndarray, shape(n,) The current state vector. t : float The current time. args : dictionary constants : ndarray specified : ndarray num_coordinates : integer Returns ------- dx : ndarray, shape(n,) The derivative of the state. """ # TODO : Add more useful info to this doc string. Generate it # dynamically. # http://stackoverflow.com/questions/10307696/how-to-put-a-variable-into-python-docstring # TODO : Allow arg['specified'] to be a function of the states and # time, which gets evaluated at each time step. segmented = [args['constants'], x[:args['num_coordinates']], x[args['num_coordinates']:]] if specified is not None: segmented.append(args['specified']) mass_matrix_values, forcing_vector_values = \ mass_forcing_func(*segmented) # TODO: figure out how to off load solve to the various generated # code, for example for Theano: # http://deeplearning.net/software/theano/library/sandbox/linalg.html#theano.sandbox.linalg.ops.Solve dx = np.array(np.linalg.solve(mass_matrix_values, forcing_vector_values)).T[0] return dx return right_hand_side
import theano.tensor as T #from theano import function from theano import * from theano.compile import * from theano.compile.function import * from sympy.printing.theanocode import theano_function x = T.dscalar('x') y = T.dscalar('y') expr = x + y #theano.compile.function_dump("a", [x, y], expr) f = function([x, y], expr) print f(2,3) fn_theano = theano_function([x,y], [expr], dims={x: 1, y:1}, dtypes={x: 'float64'}) print fn_theano(2,3) xx = T.dvector('xx') yy = T.dvector('yy') expr_vec = xx + yy vec_dim = 1 fn_theano_vec = theano_function([xx,yy], [expr_vec]) #, dims={xx: vec_dim, yy: vec_dim}, dtypes={x: 'float64'}) print fn_theano_vec([2,2],[3,3]) NN = 100000 ts = time.time() for i in range(NN):
def __init__(self, model, tspan=None, initials=None, param_values=None, verbose=False, **kwargs): super(ScipyOdeSimulator, self).__init__(model, tspan=tspan, initials=initials, param_values=param_values, verbose=verbose, **kwargs) # We'll need to know if we're using the Jacobian when we get to run() self._use_analytic_jacobian = kwargs.get('use_analytic_jacobian', False) self.cleanup = kwargs.get('cleanup', True) integrator = kwargs.get('integrator', 'vode') compiler_mode = kwargs.get('compiler', None) # Generate the equations for the model pysb.bng.generate_equations(self._model, self.cleanup, self.verbose) # ODE RHS ----------------------------------------------- self._eqn_subs = {e: e.expand_expr(expand_observables=True) for e in self._model.expressions} ode_mat = sympy.Matrix(self.model.odes).subs(self._eqn_subs) if compiler_mode is None: self._compiler = self._autoselect_compiler() if self._compiler == 'python': self._logger.warning( "This system of ODEs will be evaluated in pure Python. " "This may be slow for large models. We recommend " "installing a package for compiling the ODEs to C code: " "'weave' (recommended for Python 2) or " "'cython' (recommended for Python 3). This warning can " "be suppressed by specifying compiler_mode='python'.") self._logger.debug('Equation mode set to "%s"' % self._compiler) else: self._compiler = compiler_mode extra_compile_args = [] # Inhibit weave C compiler warnings unless log level <= EXTENDED_DEBUG. # Note that since the output goes straight to stderr rather than via the # logging system, the threshold must be lower than DEBUG or else the # Nose logcapture plugin will cause the warnings to be shown and tests # will fail due to unexpected output. if not self._logger.isEnabledFor(EXTENDED_DEBUG): extra_compile_args.append('-w') # Use lambdarepr (Python code) with Cython, otherwise use C code eqn_repr = lambdarepr if self._compiler == 'cython' else sympy.ccode if self._compiler in ('weave', 'cython'): # Prepare the string representations of the RHS equations code_eqs = '\n'.join(['ydot[%d] = %s;' % (i, eqn_repr(o)) for i, o in enumerate(ode_mat)]) code_eqs = str(self._eqn_substitutions(code_eqs)) # Allocate ydot here, once. ydot = np.zeros(len(self.model.species)) if self._compiler == 'cython': if not cython: raise ImportError('Cython library is not installed') code_eqs = CYTHON_DECL + code_eqs def rhs(t, y, p): # note that the evaluated code sets ydot as a side effect cython.inline(code_eqs, quiet=True) return ydot with _set_cflags_no_warnings(self._logger): rhs(0.0, self.initials[0], self.param_values[0]) else: # Weave if not weave_inline: raise ImportError('Weave library is not installed') for arr_name in ('ydot', 'y', 'p'): macro = arr_name.upper() + '1' code_eqs = re.sub(r'\b%s\[(\d+)\]' % arr_name, '%s(\\1)' % macro, code_eqs) def rhs(t, y, p): # note that the evaluated code sets ydot as a side effect weave_inline(code_eqs, ['ydot', 't', 'y', 'p'], extra_compile_args=extra_compile_args) return ydot # Call rhs once just to trigger the weave C compilation step # while asserting control over distutils logging. with self._patch_distutils_logging: rhs(0.0, self.initials[0], self.param_values[0]) elif self._compiler in ('theano', 'python'): self._symbols = sympy.symbols(','.join('__s%d' % sp_id for sp_id in range(len( self.model.species))) + ',') + tuple(model.parameters) if self._compiler == 'theano': if theano is None: raise ImportError('Theano library is not installed') code_eqs_py = theano_function( self._symbols, [o if not o.is_zero else theano.tensor.zeros(1) for o in ode_mat], on_unused_input='ignore' ) else: code_eqs_py = sympy.lambdify(self._symbols, sympy.flatten(ode_mat)) def rhs(t, y, p): return code_eqs_py(*itertools.chain(y, p)) else: raise ValueError('Unknown compiler_mode: %s' % self._compiler) # JACOBIAN ----------------------------------------------- # We'll keep the code for putting together the matrix in Sympy # in case we want to do manipulations of the matrix later (e.g., to # put together the sensitivity matrix) jac_fn = None if self._use_analytic_jacobian: species_symbols = [sympy.Symbol('__s%d' % i) for i in range(len(self._model.species))] jac_matrix = ode_mat.jacobian(species_symbols) if self._compiler == 'theano': jac_eqs_py = theano_function( self._symbols, [j if not j.is_zero else theano.tensor.zeros(1) for j in jac_matrix], on_unused_input='ignore' ) def jacobian(t, y, p): jacmat = np.asarray(jac_eqs_py(*itertools.chain(y, p))) jacmat.shape = (len(self.model.odes), len(self.model.species)) return jacmat elif self._compiler in ('weave', 'cython'): # Prepare the stringified Jacobian equations. jac_eqs_list = [] for i in range(jac_matrix.shape[0]): for j in range(jac_matrix.shape[1]): entry = jac_matrix[i, j] # Skip zero entries in the Jacobian if entry == 0: continue jac_eq_str = 'jac[%d, %d] = %s;' % ( i, j, eqn_repr(entry)) jac_eqs_list.append(jac_eq_str) jac_eqs = str(self._eqn_substitutions('\n'.join(jac_eqs_list))) # Allocate jac array here, once, and initialize to zeros. jac = np.zeros( (len(self._model.odes), len(self._model.species))) if self._compiler == 'weave': # Substitute array refs with calls to the JAC1 macro jac_eqs = re.sub(r'\bjac\[(\d+), (\d+)\]', r'JAC2(\1, \2)', jac_eqs) # Substitute calls to the Y1 and P1 macros for arr_name in ('y', 'p'): macro = arr_name.upper() + '1' jac_eqs = re.sub(r'\b%s\[(\d+)\]' % arr_name, '%s(\\1)' % macro, jac_eqs) def jacobian(t, y, p): weave_inline(jac_eqs, ['jac', 't', 'y', 'p'], extra_compile_args=extra_compile_args) return jac # Manage distutils logging, as above for rhs. with self._patch_distutils_logging: jacobian(0.0, self.initials[0], self.param_values[0]) else: jac_eqs = CYTHON_DECL + jac_eqs def jacobian(t, y, p): cython.inline(jac_eqs, quiet=True) return jac with _set_cflags_no_warnings(self._logger): jacobian(0.0, self.initials[0], self.param_values[0]) else: jac_eqs_py = sympy.lambdify(self._symbols, jac_matrix, "numpy") def jacobian(t, y, p): return jac_eqs_py(*itertools.chain(y, p)) jac_fn = jacobian # build integrator options list from our defaults and any kwargs # passed to this function options = {} if self.default_integrator_options.get(integrator): options.update( self.default_integrator_options[integrator]) # default options options.update(kwargs.get('integrator_options', {})) # overwrite # defaults self.opts = options # Integrator if integrator == 'lsoda': # lsoda is accessed via scipy.integrate.odeint which, # as a function, # requires that we pass its args at the point of call. Thus we need # to stash stuff like the rhs and jacobian functions in self so we # can pass them in later. self.integrator = integrator # lsoda's rhs and jacobian function arguments are in a different # order to other integrators, so we define these shims that swizzle # the argument order appropriately. self.func = lambda t, y, p: rhs(y, t, p) if jac_fn is None: self.jac_fn = None else: self.jac_fn = lambda t, y, p: jac_fn(y, t, p) else: # The scipy.integrate.ode integrators on the other hand are object # oriented and hold the functions and such internally. Once we set # up the integrator object we only need to retain a reference to it # and can forget about the other bits. self.integrator = scipy.integrate.ode(rhs, jac=jac_fn) with warnings.catch_warnings(): warnings.filterwarnings('error', 'No integrator name match') self.integrator.set_integrator(integrator, **options)
[1.0 / factorial(j) * x**j for j in range(0, i + 1)]) f_exprs.append(expr) print expr ts = time.time() g1 = map(lambda a: function([x], a), f_exprs) te = time.time() print g1 print "theano function compile time: ", (te - ts) print "theano theano_function eval value=" for g in g1: print g([0.1]) ts = time.time() g2 = map( lambda a: theano_function([x], [a], dims={x: 1}, dtypes={x: 'float64'}), f_exprs) te = time.time() print g2 print "sympy.printing.theanocode theano_function compile time: ", (te - ts) print "sympy.printing.theanocode theano_function eval value=" for g in g2: print g([0.1]) nData = 10000 #the best one NN = 10000000 N = NN / nData print "Total loops:", N aryX = [0.1 for i in range(nData)] for i in range(len(f_exprs)):
def obs_term_gradient_optimization2(background_data_vector, observation_data_vector, actual_B_1, actual_R_1, actual_H, actual_hx, smallest_gradient=0.01, iteration_step=0.001, blockn=5, enable_parallel=False): """http://matthewrocklin.com/blog/work/2013/04/05/SymPy-Theano-part-3""" len_background_data_vector = len(background_data_vector) len_observation_data_vector = len(background_data_vector) # background_error_matrix_reverse = sympy.MatrixSymbol('B_1', len(background_data_vector), len(background_data_vector)) background_error_matrix_reverse = sympy.MatrixSymbol( 'B_1', len_background_data_vector, len_background_data_vector) observation_error_matrix_reverse = sympy.MatrixSymbol( 'R_1', len_observation_data_vector, len_observation_data_vector) linearized_observation_operator = sympy.MatrixSymbol( 'H', len_observation_data_vector, len_background_data_vector) background_derived_obs_matrix = sympy.MatrixSymbol( 'hx', len_observation_data_vector, 1) observation_data_matrix = sympy.MatrixSymbol('y', len_observation_data_vector, 1) analysis_increment_vector_symbol = sympy.MatrixSymbol( 'delta_x', len_background_data_vector, 1) obs_term_gradient_value = numpy.matrix( 999.0 * numpy.ones(background_data_vector.shape)) analysis_increment_vector = numpy.matrix( numpy.zeros(background_data_vector.shape)) inputs = [ background_error_matrix_reverse, observation_error_matrix_reverse, linearized_observation_operator, background_derived_obs_matrix, observation_data_matrix, analysis_increment_vector_symbol ] cost_function_gradient = [ 2 * background_error_matrix_reverse * analysis_increment_vector_symbol - 2 * linearized_observation_operator.transpose() * observation_error_matrix_reverse * (observation_data_matrix - background_derived_obs_matrix - linearized_observation_operator * analysis_increment_vector_symbol) ] dtypes = {inp: 'float16' for inp in inputs} print 'create func' if enable_parallel: blocksizes = { background_error_matrix_reverse: [ tuple(blockn * [len_background_data_vector / blockn]), tuple(blockn * [len_background_data_vector / blockn]) ], observation_error_matrix_reverse: [ tuple(blockn * [len_observation_data_vector / blockn]), tuple(blockn * [len_observation_data_vector / blockn]) ], linearized_observation_operator: [ tuple(blockn * [len_observation_data_vector / blockn]), tuple(blockn * [len_background_data_vector / blockn]) ], background_derived_obs_matrix: [tuple(blockn * [len_observation_data_vector / blockn]), (1, )], observation_data_matrix: [tuple(blockn * [len_observation_data_vector / blockn]), (1, )], analysis_increment_vector_symbol: [tuple(blockn * [len_background_data_vector / blockn]), (1, )], } blockinputs = [blockcut(i, *blocksizes[i]) for i in inputs] blockoutputs = [ o.subs(dict(zip(inputs, blockinputs))) for o in cost_function_gradient ] collapsed_outputs = map(block_collapse, blockoutputs) f = theano_function(inputs, collapsed_outputs, dtypes=dtypes) else: f = theano_function(inputs, cost_function_gradient, dtypes=dtypes) #f=sympy.lambdify(analysis_increment_vector_symbol, obs_gradient, 'numpy') #f=sympy.lambdify(analysis_increment_vector_symbol, obs_gradient, 'sympy') print 'created func' n = 0 while (obs_term_gradient_value.sum() <= -smallest_gradient or obs_term_gradient_value.sum() > smallest_gradient): ninputs = [ numpy.array(actual_B_1).astype(float16), numpy.array(actual_R_1).astype(float16), numpy.array(actual_H).astype(float16), numpy.array(actual_hx).astype(float16), numpy.array(observation_data_vector).astype(float16), numpy.array(analysis_increment_vector).astype(float16) ] # [2*B_1*delta_x - 2*H.T*R_1*(-hx - H*delta_x + y)] # obs_term_gradient_value = 2*actual_B_1*analysis_increment_vector - 2*actual_H.transpose()*actual_R_1*(observation_data_vector-actual_hx-actual_H*analysis_increment_vector) #obs_term_gradient_value = 2*numpy.asarray(actual_B_1)*numpy.asarray(analysis_increment_vector) - 2*actual_H.transpose()*actual_R_1*(observation_data_vector-actual_hx-actual_H*analysis_increment_vector) obs_term_gradient_value = f(*ninputs) analysis_increment_vector = analysis_increment_vector - iteration_step * obs_term_gradient_value print n, obs_term_gradient_value.sum() n = n + 1 if n > 2000: break # print analysis_increment_vector # print background_data_vector + analysis_increment_vector return background_data_vector + analysis_increment_vector
def theano_function_(inputs, outputs, **kwargs): """ Wrapper for theano_function that uses a new, empty cache by default. """ kwargs.setdefault('cache', {}) return theano_function(inputs, outputs, **kwargs)
def test_bad_keyword_args_raise_error(): raises(Exception, lambda : theano_function([x], [x+1], foobar=3))
def generate_ode_function(mass_matrix, forcing_vector, constants, coordinates, speeds, specified=None, generator='lambdify'): """Returns a numerical function which can evaluate the right hand side of the first order ordinary differential equations from a system described by: M(constants, coordinates) x' = F(constants, coordinates, speeds, specified) Parameters ---------- mass_matrix : sympy.Matrix, shape(n,n) The symbolic mass matrix of the system. forcing_vector : sympy.Matrix, shape(n,1) The symbolic forcing vector of the system. constants : list of sympy.Symbol The constants in the equations of motion. coordinates : list of sympy.Function The generalized coordinates of the system. speeds : list of sympy.Function The generalized speeds of the system. specified : list of sympy.Function The specifed quantities of the system. generator : string, {'lambdify'|'theano'|'cython'}, optional The method used for generating the numeric right hand side. Returns ------- evaluate_ode_function : function A function which evaluates the derivaties of the states. """ if generator == 'theano' and not theano_installed: raise ValueError('Theano is not installed.') if generator == 'cython' and not cython_installed: raise ValueError('Cython is not installed.') if generator == 'lambdify' or generator == 'theano': arguments = constants + coordinates + speeds if specified is not None: arguments += specified if generator == 'lambdify': mass_matrix_func = lambdify(arguments, mass_matrix) forcing_vector_func = lambdify(arguments, forcing_vector) elif generator == 'theano': mass_matrix_func = theano_function(arguments, [mass_matrix], on_unused_input='ignore') forcing_vector_func = theano_function(arguments, [forcing_vector], on_unused_input='ignore') # Theano will run faster if you trust the input. I'm not sure # what the implications of this are. See: # http://deeplearning.net/software/theano/tutorial/faq.html#faster-small-theano-function mass_matrix_func.trust_input = True forcing_vector_func.trust_input = True else: raise ImportError('Theano is not installed, choose another method.') def mass_forcing_func(numerical_constants, numerical_coordinates, numerical_speeds, numerical_specified=None): """Returns numerical evaluations of the mass matrix and forcing vector.""" values = [numerical_constants, numerical_coordinates, numerical_speeds] if specified is not None: values.append(numerical_specified) value_array = np.hstack(tuple(values)) if generator == 'theano': value_array = [np.asarray(v) for v in value_array] return (mass_matrix_func(*value_array), forcing_vector_func(*value_array)) elif generator == 'cython': filename_prefix = 'multibody_system' # TODO : This is a hack to allow you to regenerate cython modules # without closing the Python session. It may be best to also force # the user to provide a module name when generating the Cython code. # Check out the Cython inline code to figure out how to do all this # better with disutils: # https://github.com/cython/cython/blob/master/Cython/Build/Inline.py # The .pyx file has the same prefix as the Cython generated [.dll, # .so, .dylib] shared library file, so we should be able to check # all files in the directory for matches except the .pyx file. prefixes = [os.path.splitext(p)[0] for p in os.listdir('.') if not p.endswith('.pyx')] while True: if filename_prefix in prefixes: filename_prefix += '_' + random.choice(all_letters) else: break cython_generator = CythonGenerator(filename_prefix, mass_matrix, forcing_vector, constants, coordinates, speeds, specified=specified) cython_generator.generate_extension() cython_module = importlib.import_module(filename_prefix) mass_forcing_func = cython_module.mass_forcing_matrices else: # TODO : add numba, fortran, parakeet, sympy.autowrap (needs matrix # support) raise NotImplementedError('The {} code generation is not implemented'.format(generator)) def list_syms(ident, syms): identation = ' ' * ident return identation + '- ' + ('\n' + identation + '- ').join([str(s) for s in syms]) def evaluate_ode(x, t, args): """Returns the derivatives of the states, i.e. numerically evaluates the right hand side of the first order differential equation(s). x' = f(x, t) Parameters ---------- x : ndarray, shape({num_states},) The current state vector: {state_list} t : float The current time. args : dictionary constants : dictionary, len({num_constants}) A dictionary that maps the constant symbols to floats. The dictionary must contain these keys: {constant_list} specified : dictionary; ndarray, shape({num_specified},); function There are three options for this dictionary. (1) is more flexible but (2) and (3) are much more efficient. (1) A dictionary that maps the specified functions of time to floats, ndarrays, or functions that produce ndarrays. The keys can be a single specified symbolic function of time or a tuple of symbols. The total number of symbols must be equal to {num_specified}. If the value is a function it must be of the form f(x, t), where x is the current state vector ndarray and t is the current time float and it must return an ndarray of the correct shape. For example: args['specified'] = {{a: 1.0, (d, b) : np.array([1.0, 2.0]), (e, f) : lambda x, t: np.array(x[0], x[1]), c: lambda x, t: np.array(x[2])}} (2) ndarray: The specified values in the same order as the symbols given to `generate_ode_function()`, and must be the correct shape. (3) function: It must be of the form f(x, t), where x is the current state vector and t is the current time and it must return an ndarray of the correct shape (an ndarray like for (2)). The dictionary must contain these functions of time: {specified_list} Returns ------- dx : ndarray, shape({num_states},) The derivative of the state vector. """ # TODO : It would be nice if the user could pass in empty arrays to # be filled instead of creating a new array each time this function # is called. # TODO : Ideally this dictionary parsing wouldn't be inside this rhs # function. Most of it can be moved up on level. This would save # computation time. segmented = [np.array([args['constants'][c] for c in constants]), x[:len(coordinates)], x[len(coordinates):]] if specified is not None: if isinstance(args['specified'], dict): specified_values = np.zeros(len(specified)) for k, v in args['specified'].items(): # TODO : Not sure if this is the best check here. if isinstance(type(k), UndefinedFunction): k = (k,) idx = [specified.index(symmy) for symmy in k] try: specified_values[idx] = v(x, t) except TypeError: # not callable # If not callable, then it should be a float, ndarray, # or indexable. specified_values[idx] = v else: # More efficient. try: specified_values = args['specified'](x, t) except TypeError: # not callable. # If not callable, then it should be a float or ndarray. specified_values = args['specified'] # If the value is just a float, then convert to a 1D array. try: len(specified_values) except TypeError: specified_values = np.asarray([specified_values]) segmented.append(specified_values) mass_matrix_values, forcing_vector_values = \ mass_forcing_func(*segmented) # TODO: figure out how to off load solve to the various generated # code, for example for Theano: # http://deeplearning.net/software/theano/library/sandbox/linalg.html#theano.sandbox.linalg.ops.Solve # Could use scipy.linalg.solve and enable a and b overwriting to # avoid the array copying. dx = np.array(np.linalg.solve(mass_matrix_values, forcing_vector_values)).T[0] return dx template_values = {'num_states': len(coordinates + speeds), 'state_list': list_syms(16, coordinates + speeds), 'num_constants': len(constants), 'constant_list': list_syms(20, constants), 'num_specified': '0', 'specified_list': '', } if specified is not None: template_values['num_specified'] = len(specified) template_values['specified_list'] = list_syms(20, specified) evaluate_ode.__doc__ = evaluate_ode.__doc__.format(**template_values) return evaluate_ode
from numpy import array from sympy.matrices import MatrixSymbol, BlockMatrix, Matrix x = MatrixSymbol('x', 2, 2) b = BlockMatrix([[x, x]]) from numpy import array a = array([[1, 2], [3, 4]]) m = BlockMatrix([i for i in range(5)]) o = log(2) + log(3) f = theano_function([], o) from frozendict import frozendict from MathFunc import MathFunc d0 = MathFunc(dict.fromkeys(('a', 'b')), {frozendict(a=1, b=2): 3, frozendict(a=10, b=20): 30}) d = MathFunc(dict.fromkeys(('a', 'b')), {frozendict(a=1, b=2): 3, frozendict(a=10, b=20): 30}) d1 = MathFunc(dict.fromkeys(('b', 'c')), {frozendict(c=3, b=2): 30,
print('+' * 30) print('Create equations at N(0))') sy.pprint(C_eq) print() #Solve for C_eq C_sol = sy.solve(C_eq, sy.Symbol('C1')) soln = ode_sol.subs(sy.Symbol('C1'), C_sol[0]) sy.pprint(C_sol) sy.pprint(soln) print('+' * 30) ode_func = sy.lambdify(t, soln.rhs.subs({N0: 100, r: .2, k: 1000})) sy.pprint(ode_func(20)) # print('Compare to http://www.ugrad.math.ubc.ca/coursedoc/math100/notes/diffeqs/cool.html') print() print('Now use a theano function allowing broadcastable arrays') from sympy.printing.theanocode import theano_function theano_ode = theano_function([t], [soln.rhs.subs({ N0: .15, r: .2, k: .4 })], dims={t: 1}) tt = np.linspace(0, 80) theano_output = theano_ode(tt) sy.pprint(theano_output) plt.plot(tt, theano_output) plt.show() #See http://matthewrocklin.com/blog/work/2013/04/05/SymPy-Theano-part-3 #for better examples on using Theano....
def numeric_right_hand_side(mass_matrix, forcing_vector, constants, coordinates, speeds, specified=None, generator='lambdify'): """Returns a function for the right hand side of the first order ordinary differential equations from a system described by: M(constants, coordinates) x' = F(constants, coordinates, speeds, specified) which can be evaluated numerically. Parameters ---------- mass_matrix : sympy.matrices.dense.MutableDenseMatrix, shape(n,n) The symbolic mass matrix of the system. forcing_vector : sympy.matrices.dense.MutableDenseMatrix, shape(n,1) The symbolic forcing vector of the system. constants : list of sympy.core.symbol.Symbol The constants in the equations of motion. coordinates : list of sympy.core.function.Function The generalized coordinates of the system. speeds : list of sympy.core.function.Function The generalized speeds of the system. specified : list of sympy.core.function.Function The specifed quantities of the system. generator : string, {'lambdify'|'theano'|'cython'}, optional The method used for generating the numeric right hand side. Returns ------- right_hand_side : function A function which evaluates the derivaties of the states. """ if generator == 'lambdify' or generator == 'theano': arguments = constants + coordinates + speeds if specified is not None: arguments += specified if generator == 'lambdify': mass_matrix_func = lambdify(arguments, mass_matrix) forcing_vector_func = lambdify(arguments, forcing_vector) elif generator == 'theano': mass_matrix_func = theano_function(arguments, [mass_matrix], on_unused_input='ignore') forcing_vector_func = theano_function(arguments, [forcing_vector], on_unused_input='ignore') # Theano will run faster if you trust the input. I'm not sure # what the implications of this are. See: # http://deeplearning.net/software/theano/tutorial/faq.html#faster-small-theano-function mass_matrix_func.trust_input = True forcing_vector_func.trust_input = True def mass_forcing_func(numerical_constants, numerical_coordinates, numerical_speeds, numerical_specified=None): """Returns numerical evaluations of the mass matrix and forcing vector.""" values = [numerical_constants, numerical_coordinates, numerical_speeds] if specified is not None: values.append(numerical_specified) value_array = np.hstack(tuple(values)) if generator == 'theano': value_array = [np.asarray(v) for v in value_array] return mass_matrix_func(*value_array), forcing_vector_func(*value_array) elif generator == 'cython': filename_prefix = 'multibody_system' # TODO : This is a hack to allow you to regenerate cython modules # without closing the Python session. It may be best to also force # the user to provide a module name when generating the Cython code. # Check out the Cython inline code to figure out how to do all this # better with disutils: # https://github.com/cython/cython/blob/master/Cython/Build/Inline.py exists = True while exists: try: open(filename_prefix + '.so', 'r') except IOError: exists = False else: filename_prefix += '_' + random.choice(string.letters) generate_mass_forcing_cython_code(filename_prefix, mass_matrix, forcing_vector, constants, coordinates, speeds, specified=specified, time_variable='t') cython_module = importlib.import_module(filename_prefix) mass_forcing_func = cython_module.mass_forcing_matrices else: # TODO : add numba, fortan, parakeet, sympy.autowrap (needs matrix # support) raise NotImplementedError('The {} code generation is not implemented'.format(generator)) def right_hand_side(x, t, args): """Returns the derivatives of the states. Parameters ---------- x : ndarray, shape(n,) The current state vector. t : float The current time. args : dictionary constants : ndarray specified : ndarray num_coordinates : integer Returns ------- dx : ndarray, shape(n,) The derivative of the state. """ # TODO : Add more useful info to this doc string. Generate it # dynamically. # http://stackoverflow.com/questions/10307696/how-to-put-a-variable-into-python-docstring # TODO : Allow arg['specified'] to be a function of the states and # time, which gets evaluated at each time step. segmented = [args['constants'], x[:args['num_coordinates']], x[args['num_coordinates']:]] if specified is not None: segmented.append(args['specified']) mass_matrix_values, forcing_vector_values = \ mass_forcing_func(*segmented) # TODO: figure out how to off load solve to the various generated # code, for example for Theano: # http://deeplearning.net/software/theano/library/sandbox/linalg.html#theano.sandbox.linalg.ops.Solve dx = np.array(np.linalg.solve(mass_matrix_values, forcing_vector_values)).T[0] return dx return right_hand_side
def __init__(self, model, tspan=None, initials=None, param_values=None, verbose=False, **kwargs): super(ScipyOdeSimulator, self).__init__(model, tspan=tspan, initials=initials, param_values=param_values, verbose=verbose, **kwargs) # We'll need to know if we're using the Jacobian when we get to run() self._use_analytic_jacobian = kwargs.pop('use_analytic_jacobian', False) self.cleanup = kwargs.pop('cleanup', True) integrator = kwargs.pop('integrator', 'vode') compiler_mode = kwargs.pop('compiler', None) integrator_options = kwargs.pop('integrator_options', {}) if kwargs: raise ValueError('Unknown keyword argument(s): {}'.format( ', '.join(kwargs.keys()))) # Generate the equations for the model pysb.bng.generate_equations(self._model, self.cleanup, self.verbose) # ODE RHS ----------------------------------------------- self._eqn_subs = { e: e.expand_expr(expand_observables=True) for e in self._model.expressions } self._eqn_subs.update({ e: e.expand_expr(expand_observables=True) for e in self._model._derived_expressions }) ode_mat = sympy.Matrix(self.model.odes).subs(self._eqn_subs) if compiler_mode is None: self._compiler = self._autoselect_compiler() if self._compiler == 'python': self._logger.warning( "This system of ODEs will be evaluated in pure Python. " "This may be slow for large models. We recommend " "installing a package for compiling the ODEs to C code: " "'weave' (recommended for Python 2) or " "'cython' (recommended for Python 3). This warning can " "be suppressed by specifying compiler='python'.") self._logger.debug('Equation mode set to "%s"' % self._compiler) else: self._compiler = compiler_mode self._compiler_directives = None # Use lambdarepr (Python code) with Cython, otherwise use C code eqn_repr = lambdarepr if self._compiler == 'cython' else sympy.ccode if self._compiler in ('weave', 'cython'): # Prepare the string representations of the RHS equations code_eqs = '\n'.join([ 'ydot[%d] = %s;' % (i, eqn_repr(o)) for i, o in enumerate(ode_mat) ]) code_eqs = str(self._eqn_substitutions(code_eqs)) # Allocate ydot here, once. ydot = np.zeros(len(self.model.species)) if self._compiler == 'cython': self._compiler_directives = kwargs.pop( 'cython_directives', self.default_cython_directives) if not Cython: raise ImportError('Cython library is not installed') rhs = _get_rhs(self._compiler, code_eqs, ydot=ydot, compiler_directives=self._compiler_directives) with _set_cflags_no_warnings(self._logger): rhs(0.0, self.initials[0], self.param_values[0]) else: # Weave self._compiler_directives = [] # Inhibit weave C compiler warnings unless log # level <= EXTENDED_DEBUG. Note that since the output goes # straight to stderr rather than via the logging system, the # threshold must be lower than DEBUG or else the Nose # logcapture plugin will cause the warnings to be shown and # tests will fail due to unexpected output. if not self._logger.isEnabledFor(EXTENDED_DEBUG): self._compiler_directives.append('-w') if not weave_inline: raise ImportError('Weave library is not installed') for arr_name in ('ydot', 'y', 'p'): macro = arr_name.upper() + '1' code_eqs = re.sub(r'\b%s\[(\d+)\]' % arr_name, '%s(\\1)' % macro, code_eqs) rhs = _get_rhs(self._compiler, code_eqs, ydot=ydot, compiler_directives=self._compiler_directives) # Call rhs once just to trigger the weave C compilation step # while asserting control over distutils logging. with self._patch_distutils_logging: rhs(0.0, self.initials[0], self.param_values[0]) self._code_eqs = code_eqs elif self._compiler in ('theano', 'python'): self._symbols = sympy.symbols(','.join( '__s%d' % sp_id for sp_id in range(len(self.model.species))) + ',') + tuple( model.parameters) if self._compiler == 'theano': warnings.warn( "theano backend is deprecated; cython backend is " "recommended instead", category=DeprecationWarning, stacklevel=2) if theano is None: raise ImportError('Theano library is not installed') code_eqs_py = theano_function(self._symbols, [ o if not o.is_zero else theano.tensor.zeros(1) for o in ode_mat ], on_unused_input='ignore') else: code_eqs_py = sympy.lambdify(self._symbols, sympy.flatten(ode_mat)) rhs = _get_rhs(self._compiler, code_eqs_py) self._code_eqs = code_eqs_py else: raise ValueError('Unknown compiler_mode: %s' % self._compiler) # JACOBIAN ----------------------------------------------- # We'll keep the code for putting together the matrix in Sympy # in case we want to do manipulations of the matrix later (e.g., to # put together the sensitivity matrix) jac_fn = None self._jac_eqs = None if self._use_analytic_jacobian: species_symbols = [ sympy.Symbol('__s%d' % i) for i in range(len(self._model.species)) ] jac_matrix = ode_mat.jacobian(species_symbols) if self._compiler == 'theano': jac_eqs_py = theano_function(self._symbols, [ j if not j.is_zero else theano.tensor.zeros(1) for j in jac_matrix ], on_unused_input='ignore') def jac_fn(t, y, p): jacmat = np.asarray(jac_eqs_py(*itertools.chain(y, p))) jacmat.shape = (len(self.model.odes), len(self.model.species)) return jacmat elif self._compiler in ('weave', 'cython'): # Prepare the stringified Jacobian equations. jac_eqs_list = [] for i in range(jac_matrix.shape[0]): for j in range(jac_matrix.shape[1]): entry = jac_matrix[i, j] # Skip zero entries in the Jacobian if entry == 0: continue jac_eq_str = 'jac[%d, %d] = %s;' % (i, j, eqn_repr(entry)) jac_eqs_list.append(jac_eq_str) jac_eqs = str(self._eqn_substitutions('\n'.join(jac_eqs_list))) if '# Not supported in Python' in jac_eqs: raise ValueError('Analytic Jacobian calculation failed') # Allocate jac array here, once, and initialize to zeros. jac = np.zeros( (len(self._model.odes), len(self._model.species))) if self._compiler == 'weave': # Substitute array refs with calls to the JAC1 macro jac_eqs = re.sub(r'\bjac\[(\d+), (\d+)\]', r'JAC2(\1, \2)', jac_eqs) # Substitute calls to the Y1 and P1 macros for arr_name in ('y', 'p'): macro = arr_name.upper() + '1' jac_eqs = re.sub(r'\b%s\[(\d+)\]' % arr_name, '%s(\\1)' % macro, jac_eqs) jac_fn = _get_rhs( self._compiler, jac_eqs, compiler_directives=self._compiler_directives, jac=jac) # Manage distutils logging, as above for rhs. with self._patch_distutils_logging: jac_fn(0.0, self.initials[0], self.param_values[0]) else: jac_fn = _get_rhs( self._compiler, jac_eqs, compiler_directives=self._compiler_directives, jac=jac) with _set_cflags_no_warnings(self._logger): jac_fn(0.0, self.initials[0], self.param_values[0]) self._jac_eqs = jac_eqs else: jac_eqs_py = sympy.lambdify(self._symbols, jac_matrix, "numpy") jac_fn = _get_rhs(self._compiler, jac_eqs_py) self._jac_eqs = jac_eqs_py # build integrator options list from our defaults and any kwargs # passed to this function options = {} if self.default_integrator_options.get(integrator): options.update( self.default_integrator_options[integrator]) # default options options.update(integrator_options) # overwrite # defaults self.opts = options if integrator != 'lsoda': # Only used to check the user has selected a valid integrator self.integrator = scipy.integrate.ode(rhs, jac=jac_fn) with warnings.catch_warnings(): warnings.filterwarnings('error', 'No integrator name match') self.integrator.set_integrator(integrator, **options)
def __init__(self, model, tspan=None, initials=None, param_values=None, verbose=False, **kwargs): super(ScipyOdeSimulator, self).__init__(model, tspan=tspan, initials=initials, param_values=param_values, verbose=verbose, **kwargs) # We'll need to know if we're using the Jacobian when we get to run() self._use_analytic_jacobian = kwargs.get('use_analytic_jacobian', False) self.cleanup = kwargs.get('cleanup', True) integrator = kwargs.get('integrator', 'vode') use_theano = kwargs.get('use_theano', False) # Generate the equations for the model pysb.bng.generate_equations(self._model, self.cleanup, self.verbose) # ODE RHS ----------------------------------------------- self._eqn_subs = { e: e.expand_expr(expand_observables=True) for e in self._model.expressions } ode_mat = sympy.Matrix(self.model.odes).subs(self._eqn_subs) self._test_inline() if self._use_inline: # Prepare the string representations of the RHS equations code_eqs = '\n'.join([ 'ydot[%d] = %s;' % (i, sympy.ccode(o)) for i, o in enumerate(ode_mat) ]) code_eqs = self._eqn_substitutions(code_eqs) for arr_name in ('ydot', 'y', 'p'): macro = arr_name.upper() + '1' code_eqs = re.sub(r'\b%s\[(\d+)\]' % arr_name, '%s(\\1)' % macro, code_eqs) def rhs(t, y, p): ydot = self.ydot # note that the evaluated code sets ydot as a side effect weave_inline(code_eqs, ['ydot', 't', 'y', 'p'], extra_compile_args=['-w']) return ydot if use_theano or not self._use_inline: self._symbols = sympy.symbols(','.join( '__s%d' % sp_id for sp_id in range(len(self.model.species))) + ',') + tuple( model.parameters) if use_theano: if theano is None: raise ImportError('Theano library is not installed') code_eqs_py = theano_function(self._symbols, [ o if not o.is_zero else theano.tensor.zeros(1) for o in ode_mat ], on_unused_input='ignore') else: code_eqs_py = sympy.lambdify(self._symbols, sympy.flatten(ode_mat)) def rhs(t, y, p): return code_eqs_py(*itertools.chain(y, p)) # JACOBIAN ----------------------------------------------- # We'll keep the code for putting together the matrix in Sympy # in case we want to do manipulations of the matrix later (e.g., to # put together the sensitivity matrix) jac_fn = None if self._use_analytic_jacobian: species_names = [ '__s%d' % i for i in range(len(self._model.species)) ] jac_matrix = ode_mat.jacobian(species_names) if use_theano: jac_eqs_py = theano_function(self._symbols, [ j if not j.is_zero else theano.tensor.zeros(1) for j in jac_matrix ], on_unused_input='ignore') def jacobian(t, y, p): jacmat = np.asarray(jac_eqs_py(*itertools.chain(y, p))) jacmat.shape = (len(self.model.odes), len(self.model.species)) return jacmat elif self._use_inline: self.jac = np.zeros( (len(self._model.odes), len(self._model.species))) # Next, prepare the stringified Jacobian equations jac_eqs_list = [] for i in range(jac_matrix.shape[0]): for j in range(jac_matrix.shape[1]): entry = jac_matrix[i, j] # Skip zero entries in the Jacobian if entry == 0: continue jac_eq_str = 'jac[%d, %d] = %s;' % (i, j, sympy.ccode(entry)) jac_eqs_list.append(jac_eq_str) jac_eqs = self._eqn_substitutions('\n'.join(jac_eqs_list)) # Substitute array refs with calls to the JAC1 macro for inline jac_eqs = re.sub(r'\bjac\[(\d+), (\d+)\]', r'JAC2(\1, \2)', jac_eqs) # Substitute calls to the Y1 and P1 macros for arr_name in ('y', 'p'): macro = arr_name.upper() + '1' jac_eqs = re.sub(r'\b%s\[(\d+)\]' % arr_name, '%s(\\1)' % macro, jac_eqs) def jacobian(t, y, p): jac = self.jac weave_inline(jac_eqs, ['jac', 't', 'y', 'p'], extra_compile_args=['-w']) return jac else: jac_eqs_py = sympy.lambdify(self._symbols, jac_matrix, "numpy") def jacobian(t, y, p): return jac_eqs_py(*itertools.chain(y, p)) # Initialize the jacobian argument to None if we're not going to # use it # jac = self.jac as defined in jacobian() earlier # Initialization of matrix for storing the Jacobian jac_fn = jacobian # build integrator options list from our defaults and any kwargs # passed to this function options = {} if self.default_integrator_options.get(integrator): options.update( self.default_integrator_options[integrator]) # default options options.update(kwargs.get('integrator_options', {})) # overwrite # defaults self.opts = options self.ydot = np.ndarray(len(self._model.species)) # Integrator if integrator == 'lsoda': # lsoda is accessed via scipy.integrate.odeint which, # as a function, # requires that we pass its args at the point of call. Thus we need # to stash stuff like the rhs and jacobian functions in self so we # can pass them in later. self.integrator = integrator # lsoda's rhs and jacobian function arguments are in a different # order to other integrators, so we define these shims that swizzle # the argument order appropriately. self.func = lambda t, y, p: rhs(y, t, p) if jac_fn is None: self.jac_fn = None else: self.jac_fn = lambda t, y, p: jac_fn(y, t, p) else: # The scipy.integrate.ode integrators on the other hand are object # oriented and hold the functions and such internally. Once we set # up the integrator object we only need to retain a reference to it # and can forget about the other bits. self.integrator = scipy.integrate.ode(rhs, jac=jac_fn) with warnings.catch_warnings(): warnings.filterwarnings('error', 'No integrator name match') self.integrator.set_integrator(integrator, **options)
def theano_lambdify(args, expr, vector_expr): if vector_expr: expr_lambda = theano_function(args, expr, on_unused_input='ignore') else: expr_lambda = theano_function(args, [expr], on_unused_input='ignore') return expr_lambda
def test_theano_function_simple(): f = theano_function([x, y], [x+y]) assert f(2, 3) == 5
def theano_function_(inputs, outputs, **kwargs): """ Wrapper for theano_function that uses a new, empty cache by default. """ kwargs.setdefault('cache', {}) with warns_deprecated_sympy(): return theano_function(inputs, outputs, **kwargs)
def generate_ode_function(mass_matrix, forcing_vector, constants, coordinates, speeds, specified=None, generator='lambdify'): """Returns a numerical function which can evaluate the right hand side of the first order ordinary differential equations from a system described by: M(constants, coordinates) x' = F(constants, coordinates, speeds, specified) Parameters ---------- mass_matrix : sympy.Matrix, shape(n,n) The symbolic mass matrix of the system. forcing_vector : sympy.Matrix, shape(n,1) The symbolic forcing vector of the system. constants : list of sympy.Symbol The constants in the equations of motion. coordinates : list of sympy.Function The generalized coordinates of the system. speeds : list of sympy.Function The generalized speeds of the system. specified : list of sympy.Function The specifed quantities of the system. generator : string, {'lambdify'|'theano'|'cython'}, optional The method used for generating the numeric right hand side. Returns ------- evaluate_ode_function : function A function which evaluates the derivaties of the states. """ if generator == 'lambdify' or generator == 'theano': arguments = constants + coordinates + speeds if specified is not None: arguments += specified if generator == 'lambdify': mass_matrix_func = lambdify(arguments, mass_matrix) forcing_vector_func = lambdify(arguments, forcing_vector) elif generator == 'theano': mass_matrix_func = theano_function(arguments, [mass_matrix], on_unused_input='ignore') forcing_vector_func = theano_function(arguments, [forcing_vector], on_unused_input='ignore') # Theano will run faster if you trust the input. I'm not sure # what the implications of this are. See: # http://deeplearning.net/software/theano/tutorial/faq.html#faster-small-theano-function mass_matrix_func.trust_input = True forcing_vector_func.trust_input = True def mass_forcing_func(numerical_constants, numerical_coordinates, numerical_speeds, numerical_specified=None): """Returns numerical evaluations of the mass matrix and forcing vector.""" values = [numerical_constants, numerical_coordinates, numerical_speeds] if specified is not None: values.append(numerical_specified) value_array = np.hstack(tuple(values)) if generator == 'theano': value_array = [np.asarray(v) for v in value_array] return (mass_matrix_func(*value_array), forcing_vector_func(*value_array)) elif generator == 'cython': filename_prefix = 'multibody_system' # TODO : This is a hack to allow you to regenerate cython modules # without closing the Python session. It may be best to also force # the user to provide a module name when generating the Cython code. # Check out the Cython inline code to figure out how to do all this # better with disutils: # https://github.com/cython/cython/blob/master/Cython/Build/Inline.py # The .pyx file has the same prefix as the Cython generated [.dll, # .so, .dylib] shared library file, so we should be able to check # all files in the directory for matches except the .pyx file. prefixes = [os.path.splitext(p)[0] for p in os.listdir('.') if not p.endswith('.pyx')] while True: if filename_prefix in prefixes: filename_prefix += '_' + random.choice(all_letters) else: break cython_generator = CythonGenerator(filename_prefix, mass_matrix, forcing_vector, constants, coordinates, speeds, specified=specified) cython_generator.generate_extension() cython_module = importlib.import_module(filename_prefix) mass_forcing_func = cython_module.mass_forcing_matrices else: # TODO : add numba, fortran, parakeet, sympy.autowrap (needs matrix # support) raise NotImplementedError('The {} code generation is not implemented'.format(generator)) def evaluate_ode(x, t, args): """Returns the derivatives of the states, i.e. numerically evaluates the right hand side of the first order differential equation(s). x' = f(x, t) Parameters ---------- x : ndarray, shape({num_states},) The current state vector: {state_list} t : float The current time. args : dictionary constants : ndarray, shape({num_constants},) {constant_list} specified : ndarray, shape({num_specified},) or a function If this is a function it must be of the form f(x, t), where x is the current state vector and t is the current time and it must return an ndarray of the correct shape. {specified_list} Returns ------- dx : ndarray, shape({num_states},) The derivative of the state vector. """ segmented = [args['constants'], x[:len(coordinates)], x[len(coordinates):]] if specified is not None: try: sp_val = args['specified'](x, t) except TypeError: # not callable # If not callable, then it should be a float or ndarray. sp_val = args['specified'] # If the value is just a float, then convert to a 1D array. try: len(sp_val) except TypeError: sp_val = np.asarray([sp_val]) segmented.append(sp_val) mass_matrix_values, forcing_vector_values = \ mass_forcing_func(*segmented) # TODO: figure out how to off load solve to the various generated # code, for example for Theano: # http://deeplearning.net/software/theano/library/sandbox/linalg.html#theano.sandbox.linalg.ops.Solve dx = np.array(np.linalg.solve(mass_matrix_values, forcing_vector_values)).T[0] return dx template_values = {'num_states': len(coordinates + speeds), 'state_list': ', '.join([str(s) for s in coordinates + speeds]), 'num_constants': len(constants), 'constant_list': ', '.join([str(c) for c in constants]), 'num_specified': '0', 'specified_list': '', } if specified is not None: template_values['num_specified'] = len(specified) template_values['specified_list'] = ', '.join([str(s) for s in specified]) evaluate_ode.__doc__ = evaluate_ode.__doc__.format(**template_values) return evaluate_ode
def test_constantfunctions(): tf = theano_function([],[1+1j]) assert(tf()==1+1j)
def generate_ode_function(mass_matrix, forcing_vector, constants, coordinates, speeds, specified=None, generator='lambdify'): """Returns a numerical function which can evaluate the right hand side of the first order ordinary differential equations from a system described by: M(constants, coordinates) x' = F(constants, coordinates, speeds, specified) Parameters ---------- mass_matrix : sympy.Matrix, shape(n,n) The symbolic mass matrix of the system. forcing_vector : sympy.Matrix, shape(n,1) The symbolic forcing vector of the system. constants : list of sympy.Symbol The constants in the equations of motion. coordinates : list of sympy.Function The generalized coordinates of the system. speeds : list of sympy.Function The generalized speeds of the system. specified : list of sympy.Function The specifed quantities of the system. generator : string, {'lambdify'|'theano'|'cython'}, optional The method used for generating the numeric right hand side. Returns ------- evaluate_ode_function : function A function which evaluates the derivaties of the states. """ if generator == 'theano' and not theano_installed: raise ValueError('Theano is not installed.') if generator == 'cython' and not cython_installed: raise ValueError('Cython is not installed.') if generator == 'lambdify' or generator == 'theano': arguments = constants + coordinates + speeds if specified is not None: arguments += specified if generator == 'lambdify': mass_matrix_func = lambdify(arguments, mass_matrix) forcing_vector_func = lambdify(arguments, forcing_vector) elif generator == 'theano': mass_matrix_func = theano_function(arguments, [mass_matrix], on_unused_input='ignore') forcing_vector_func = theano_function(arguments, [forcing_vector], on_unused_input='ignore') # Theano will run faster if you trust the input. I'm not sure # what the implications of this are. See: # http://deeplearning.net/software/theano/tutorial/faq.html#faster-small-theano-function mass_matrix_func.trust_input = True forcing_vector_func.trust_input = True else: raise ImportError( 'Theano is not installed, choose another method.') def mass_forcing_func(numerical_constants, numerical_coordinates, numerical_speeds, numerical_specified=None): """Returns numerical evaluations of the mass matrix and forcing vector.""" values = [ numerical_constants, numerical_coordinates, numerical_speeds ] if specified is not None: values.append(numerical_specified) value_array = np.hstack(tuple(values)) if generator == 'theano': value_array = [np.asarray(v) for v in value_array] return (mass_matrix_func(*value_array), forcing_vector_func(*value_array)) elif generator == 'cython': filename_prefix = 'multibody_system' # TODO : This is a hack to allow you to regenerate cython modules # without closing the Python session. It may be best to also force # the user to provide a module name when generating the Cython code. # Check out the Cython inline code to figure out how to do all this # better with disutils: # https://github.com/cython/cython/blob/master/Cython/Build/Inline.py # The .pyx file has the same prefix as the Cython generated [.dll, # .so, .dylib] shared library file, so we should be able to check # all files in the directory for matches except the .pyx file. prefixes = [ os.path.splitext(p)[0] for p in os.listdir('.') if not p.endswith('.pyx') ] while True: if filename_prefix in prefixes: filename_prefix += '_' + random.choice(all_letters) else: break cython_generator = CythonGenerator(filename_prefix, mass_matrix, forcing_vector, constants, coordinates, speeds, specified=specified) cython_generator.generate_extension() cython_module = importlib.import_module(filename_prefix) mass_forcing_func = cython_module.mass_forcing_matrices else: # TODO : add numba, fortran, parakeet, sympy.autowrap (needs matrix # support) raise NotImplementedError( 'The {} code generation is not implemented'.format(generator)) def evaluate_ode(x, t, args): """Returns the derivatives of the states, i.e. numerically evaluates the right hand side of the first order differential equation(s). x' = f(x, t) Parameters ---------- x : ndarray, shape({num_states},) The current state vector: {state_list} t : float The current time. args : dictionary constants : ndarray, shape({num_constants},) {constant_list} specified : ndarray, shape({num_specified},) or a function If this is a function it must be of the form f(x, t), where x is the current state vector and t is the current time and it must return an ndarray of the correct shape. {specified_list} Returns ------- dx : ndarray, shape({num_states},) The derivative of the state vector. """ segmented = [ args['constants'], x[:len(coordinates)], x[len(coordinates):] ] if specified is not None: try: sp_val = args['specified'](x, t) except TypeError: # not callable # If not callable, then it should be a float or ndarray. sp_val = args['specified'] # If the value is just a float, then convert to a 1D array. try: len(sp_val) except TypeError: sp_val = np.asarray([sp_val]) segmented.append(sp_val) mass_matrix_values, forcing_vector_values = \ mass_forcing_func(*segmented) # TODO: figure out how to off load solve to the various generated # code, for example for Theano: # http://deeplearning.net/software/theano/library/sandbox/linalg.html#theano.sandbox.linalg.ops.Solve # Could use scipy.linalg.solve and enable a and b overwriting to # avoid the array copying. dx = np.array( np.linalg.solve(mass_matrix_values, forcing_vector_values)).T[0] return dx template_values = { 'num_states': len(coordinates + speeds), 'state_list': ', '.join([str(s) for s in coordinates + speeds]), 'num_constants': len(constants), 'constant_list': ', '.join([str(c) for c in constants]), 'num_specified': '0', 'specified_list': '', } if specified is not None: template_values['num_specified'] = len(specified) template_values['specified_list'] = ', '.join( [str(s) for s in specified]) evaluate_ode.__doc__ = evaluate_ode.__doc__.format(**template_values) return evaluate_ode
def __init__(self, model, tspan=None, initials=None, param_values=None, verbose=False, **kwargs): super(ScipyOdeSimulator, self).__init__(model, tspan=tspan, initials=initials, param_values=param_values, verbose=verbose, **kwargs) # We'll need to know if we're using the Jacobian when we get to run() self._use_analytic_jacobian = kwargs.pop('use_analytic_jacobian', False) self.cleanup = kwargs.pop('cleanup', True) integrator = kwargs.pop('integrator', 'vode') compiler_mode = kwargs.pop('compiler', None) integrator_options = kwargs.pop('integrator_options', {}) cython_directives = kwargs.pop('cython_directives', self.default_cython_directives) if kwargs: raise ValueError('Unknown keyword argument(s): {}'.format( ', '.join(kwargs.keys()) )) # Generate the equations for the model pysb.bng.generate_equations(self._model, self.cleanup, self.verbose) # ODE RHS ----------------------------------------------- self._eqn_subs = {e: e.expand_expr(expand_observables=True) for e in self._model.expressions} ode_mat = sympy.Matrix(self.model.odes).subs(self._eqn_subs) if compiler_mode is None: self._compiler = self._autoselect_compiler() if self._compiler == 'python': self._logger.warning( "This system of ODEs will be evaluated in pure Python. " "This may be slow for large models. We recommend " "installing a package for compiling the ODEs to C code: " "'weave' (recommended for Python 2) or " "'cython' (recommended for Python 3). This warning can " "be suppressed by specifying compiler='python'.") self._logger.debug('Equation mode set to "%s"' % self._compiler) else: self._compiler = compiler_mode extra_compile_args = [] # Inhibit weave C compiler warnings unless log level <= EXTENDED_DEBUG. # Note that since the output goes straight to stderr rather than via the # logging system, the threshold must be lower than DEBUG or else the # Nose logcapture plugin will cause the warnings to be shown and tests # will fail due to unexpected output. if not self._logger.isEnabledFor(EXTENDED_DEBUG): extra_compile_args.append('-w') # Use lambdarepr (Python code) with Cython, otherwise use C code eqn_repr = lambdarepr if self._compiler == 'cython' else sympy.ccode if self._compiler in ('weave', 'cython'): # Prepare the string representations of the RHS equations code_eqs = '\n'.join(['ydot[%d] = %s;' % (i, eqn_repr(o)) for i, o in enumerate(ode_mat)]) code_eqs = str(self._eqn_substitutions(code_eqs)) # Allocate ydot here, once. ydot = np.zeros(len(self.model.species)) if self._compiler == 'cython': if not Cython: raise ImportError('Cython library is not installed') def rhs(t, y, p): # note that the evaluated code sets ydot as a side effect Cython.inline( code_eqs, quiet=True, cython_compiler_directives=cython_directives) return ydot with _set_cflags_no_warnings(self._logger): rhs(0.0, self.initials[0], self.param_values[0]) else: # Weave if not weave_inline: raise ImportError('Weave library is not installed') for arr_name in ('ydot', 'y', 'p'): macro = arr_name.upper() + '1' code_eqs = re.sub(r'\b%s\[(\d+)\]' % arr_name, '%s(\\1)' % macro, code_eqs) def rhs(t, y, p): # note that the evaluated code sets ydot as a side effect weave_inline(code_eqs, ['ydot', 't', 'y', 'p'], extra_compile_args=extra_compile_args) return ydot # Call rhs once just to trigger the weave C compilation step # while asserting control over distutils logging. with self._patch_distutils_logging: rhs(0.0, self.initials[0], self.param_values[0]) elif self._compiler in ('theano', 'python'): self._symbols = sympy.symbols(','.join('__s%d' % sp_id for sp_id in range(len( self.model.species))) + ',') + tuple(model.parameters) if self._compiler == 'theano': if theano is None: raise ImportError('Theano library is not installed') code_eqs_py = theano_function( self._symbols, [o if not o.is_zero else theano.tensor.zeros(1) for o in ode_mat], on_unused_input='ignore' ) else: code_eqs_py = sympy.lambdify(self._symbols, sympy.flatten(ode_mat)) def rhs(t, y, p): return code_eqs_py(*itertools.chain(y, p)) else: raise ValueError('Unknown compiler_mode: %s' % self._compiler) # JACOBIAN ----------------------------------------------- # We'll keep the code for putting together the matrix in Sympy # in case we want to do manipulations of the matrix later (e.g., to # put together the sensitivity matrix) jac_fn = None if self._use_analytic_jacobian: species_symbols = [sympy.Symbol('__s%d' % i) for i in range(len(self._model.species))] jac_matrix = ode_mat.jacobian(species_symbols) if self._compiler == 'theano': jac_eqs_py = theano_function( self._symbols, [j if not j.is_zero else theano.tensor.zeros(1) for j in jac_matrix], on_unused_input='ignore' ) def jacobian(t, y, p): jacmat = np.asarray(jac_eqs_py(*itertools.chain(y, p))) jacmat.shape = (len(self.model.odes), len(self.model.species)) return jacmat elif self._compiler in ('weave', 'cython'): # Prepare the stringified Jacobian equations. jac_eqs_list = [] for i in range(jac_matrix.shape[0]): for j in range(jac_matrix.shape[1]): entry = jac_matrix[i, j] # Skip zero entries in the Jacobian if entry == 0: continue jac_eq_str = 'jac[%d, %d] = %s;' % ( i, j, eqn_repr(entry)) jac_eqs_list.append(jac_eq_str) jac_eqs = str(self._eqn_substitutions('\n'.join(jac_eqs_list))) # Allocate jac array here, once, and initialize to zeros. jac = np.zeros( (len(self._model.odes), len(self._model.species))) if self._compiler == 'weave': # Substitute array refs with calls to the JAC1 macro jac_eqs = re.sub(r'\bjac\[(\d+), (\d+)\]', r'JAC2(\1, \2)', jac_eqs) # Substitute calls to the Y1 and P1 macros for arr_name in ('y', 'p'): macro = arr_name.upper() + '1' jac_eqs = re.sub(r'\b%s\[(\d+)\]' % arr_name, '%s(\\1)' % macro, jac_eqs) def jacobian(t, y, p): weave_inline(jac_eqs, ['jac', 't', 'y', 'p'], extra_compile_args=extra_compile_args) return jac # Manage distutils logging, as above for rhs. with self._patch_distutils_logging: jacobian(0.0, self.initials[0], self.param_values[0]) else: def jacobian(t, y, p): Cython.inline( jac_eqs, quiet=True, cython_compiler_directives=cython_directives) return jac with _set_cflags_no_warnings(self._logger): jacobian(0.0, self.initials[0], self.param_values[0]) else: jac_eqs_py = sympy.lambdify(self._symbols, jac_matrix, "numpy") def jacobian(t, y, p): return jac_eqs_py(*itertools.chain(y, p)) jac_fn = jacobian # build integrator options list from our defaults and any kwargs # passed to this function options = {} if self.default_integrator_options.get(integrator): options.update( self.default_integrator_options[integrator]) # default options options.update(integrator_options) # overwrite # defaults self.opts = options # Integrator if integrator == 'lsoda': # lsoda is accessed via scipy.integrate.odeint which, # as a function, # requires that we pass its args at the point of call. Thus we need # to stash stuff like the rhs and jacobian functions in self so we # can pass them in later. self.integrator = integrator # lsoda's rhs and jacobian function arguments are in a different # order to other integrators, so we define these shims that swizzle # the argument order appropriately. self.func = lambda t, y, p: rhs(y, t, p) if jac_fn is None: self.jac_fn = None else: self.jac_fn = lambda t, y, p: jac_fn(y, t, p) else: # The scipy.integrate.ode integrators on the other hand are object # oriented and hold the functions and such internally. Once we set # up the integrator object we only need to retain a reference to it # and can forget about the other bits. self.integrator = scipy.integrate.ode(rhs, jac=jac_fn) with warnings.catch_warnings(): warnings.filterwarnings('error', 'No integrator name match') self.integrator.set_integrator(integrator, **options)
def cython_propensity_function(x, parm, prop): args = x + parm.keys() return (theano_function(args, prop), args)
def test_constantfunctions(): tf = theano_function([], [1 + 1j]) assert (tf() == 1 + 1j)
def __init__(self, venture_name='', year_0=0, nb_pro_forma_years_excl_0=1, compile=True): # set Venture Name and corresponding variable prefixes self.venture_name = venture_name if venture_name: self.venture_name_prefix = '%s___' % venture_name else: self.venture_name_prefix = '' # set pro forma period timeline self.year_0 = year_0 self.nb_pro_forma_years_excl_0 = nb_pro_forma_years_excl_0 self.nb_pro_forma_years_incl_0 = nb_pro_forma_years_excl_0 + 1 self.final_pro_forma_year = year_0 + nb_pro_forma_years_excl_0 self.index_range = range(self.nb_pro_forma_years_incl_0) self.index_range_from_1 = range(1, self.nb_pro_forma_years_incl_0) # list all Input & Output attributes & symbols, and set model structure self.input_attrs = [] self.output_attrs = [] self.set_model_structure() # gather all Input symbols and set their default values self.input_symbols = [] self.input_defaults = {} for input_attr in self.input_attrs: a = getattr(self, '%s___input' % input_attr) if isinstance(a, (list, tuple)): if (not isinstance(a[0], Symbol)) and isnan(a[0]): for i in self.index_range_from_1: self.input_symbols.append(a[i]) self.input_defaults[a[i].name] = -1. else: for i in self.index_range: self.input_symbols.append(a[i]) self.input_defaults[a[i].name] = 0. else: self.input_symbols.append(a) self.input_defaults[a.name] = 0. # compile Outputs if so required self.compile = compile if compile: def format_time_delta(time_delta): time_delta_str = str(time_delta) return time_delta_str[:time_delta_str.index('.')] print('Compiling:') tic_0 = datetime.now() for output in self.output_attrs: print(' %s... ' % output, end='') a = getattr(self, output) tic = datetime.now() if isinstance(a, (list, tuple)): if (not isinstance(a[0], Expr)) and isnan(a[0]): setattr(self, output, [nan] + [ theano_function(self.input_symbols, [a[i]]) for i in self.index_range_from_1 ]) else: setattr(self, output, [ theano_function(self.input_symbols, [a[i]]) for i in self.index_range ]) else: setattr(self, output, theano_function(self.input_symbols, [a])) toc = datetime.now() print('done after %s (%s so far)' % (format_time_delta(toc - tic), format_time_delta(toc - tic_0))) print('done after %s' % format_time_delta(toc - tic_0))