def split_stochastic(self): ''' Split the expression into a stochastic and non-stochastic part. Splits the expression into a tuple of one `Expression` objects f (the non-stochastic part) and a dictionary mapping stochastic variables to `Expression` objects. For example, an expression of the form ``f + g * xi_1 + h * xi_2`` would be returned as: ``(f, {'xi_1': g, 'xi_2': h})`` Note that the `Expression` objects for the stochastic parts do not include the stochastic variable itself. Returns ------- (f, d) : (`Expression`, dict) A tuple of an `Expression` object and a dictionary, the first expression being the non-stochastic part of the equation and the dictionary mapping stochastic variables (``xi`` or starting with ``xi_``) to `Expression` objects. If no stochastic variable is present in the code string, a tuple ``(self, None)`` will be returned with the unchanged `Expression` object. ''' stochastic_variables = [] for identifier in self.identifiers: if identifier == 'xi' or identifier.startswith('xi_'): stochastic_variables.append(identifier) # No stochastic variable if not len(stochastic_variables): return (self, None) s_expr = self.sympy_expr.expand() stochastic_symbols = [ sympy.Symbol(variable, real=True) for variable in stochastic_variables ] f = sympy.Wild('f', exclude=stochastic_symbols) # non-stochastic part match_objects = [ sympy.Wild('w_' + variable, exclude=stochastic_symbols) for variable in stochastic_variables ] match_expression = f for symbol, match_object in zip(stochastic_symbols, match_objects): match_expression += match_object * symbol matches = s_expr.match(match_expression) if matches is None: raise ValueError( ('Expression "%s" cannot be separated into stochastic ' 'and non-stochastic term') % self.code) f_expr = Expression(sympy_to_str(matches[f])) stochastic_expressions = dict( (variable, Expression(sympy_to_str(matches[match_object]))) for (variable, match_object) in zip(stochastic_variables, match_objects)) return (f_expr, stochastic_expressions)
def __call__(self, equations, variables=None): system = get_conditionally_linear_system(equations) code = [] for var, (A, B) in system.iteritems(): s_var = sp.Symbol(var) s_dt = sp.Symbol('dt') if A == 0: update_expression = s_var + s_dt * B elif B != 0: BA = B / A # Avoid calculating B/A twice BA_name = '_BA_' + var s_BA = sp.Symbol(BA_name) code += [BA_name + ' = ' + sympy_to_str(BA)] update_expression = (s_var + s_BA) * sp.exp(A * s_dt) - s_BA else: update_expression = s_var * sp.exp(A * s_dt) # The actual update step update = '_{var} = {expr}' code += [ update.format(var=var, expr=sympy_to_str(update_expression)) ] # Replace all the variables with their updated value for var in system: code += ['{var} = _{var}'.format(var=var)] return '\n'.join(code)
def __call__(self, equations, variables=None): system = get_conditionally_linear_system(equations) code = [] for var, (A, B) in system.iteritems(): s_var = sp.Symbol(var) s_dt = sp.Symbol('dt') if A == 0: update_expression = s_var + s_dt * B elif B != 0: BA = B / A # Avoid calculating B/A twice BA_name = '_BA_' + var s_BA = sp.Symbol(BA_name) code += [BA_name + ' = ' + sympy_to_str(BA)] update_expression = (s_var + s_BA)*sp.exp(A*s_dt) - s_BA else: update_expression = s_var*sp.exp(A*s_dt) # The actual update step update = '_{var} = {expr}' code += [update.format(var=var, expr=sympy_to_str(update_expression))] # Replace all the variables with their updated value for var in system: code += ['{var} = _{var}'.format(var=var)] return '\n'.join(code)
def __call__(self, equations, variables=None): if variables is None: variables = {} # Get a representation of the ODE system in the form of # dX/dt = M*X + B varnames, matrix, constants = get_linear_system(equations) # Make sure that the matrix M is constant, i.e. it only contains # external variables or constant variables symbols = set.union(*(el.atoms() for el in matrix)) non_constant = _non_constant_symbols(symbols, variables) if len(non_constant): raise ValueError(('The coefficient matrix for the equations ' 'contains the symbols %s, which are not ' 'constant.') % str(non_constant)) symbols = [Symbol(variable, real=True) for variable in varnames] solution = sp.solve_linear_system(matrix.row_join(constants), *symbols) b = sp.ImmutableMatrix([solution[symbol] for symbol in symbols]).transpose() # Solve the system dt = Symbol('dt', real=True, positive=True) A = (matrix * dt).exp() C = sp.ImmutableMatrix([A.dot(b)]) - b _S = sp.MatrixSymbol('_S', len(varnames), 1) updates = A * _S + C.transpose() try: # In sympy 0.7.3, we have to explicitly convert it to a single matrix # In sympy 0.7.2, it is already a matrix (which doesn't have an # is_explicit method) updates = updates.as_explicit() except AttributeError: pass # The solution contains _S[0, 0], _S[1, 0] etc. for the state variables, # replace them with the state variable names abstract_code = [] for idx, (variable, update) in enumerate(zip(varnames, updates)): rhs = update.subs(_S[idx, 0], variable) identifiers = get_identifiers(sympy_to_str(rhs)) for identifier in identifiers: if identifier in variables: var = variables[identifier] if var is None: print identifier, variables if var.scalar and var.constant: float_val = var.get_value() rhs = rhs.xreplace({Symbol(identifier, real=True): Float(float_val)}) # Do not overwrite the real state variables yet, the update step # of other state variables might still need the original values abstract_code.append('_' + variable + ' = ' + sympy_to_str(rhs)) # Update the state variables for variable in varnames: abstract_code.append('{variable} = _{variable}'.format(variable=variable)) return '\n'.join(abstract_code)
def split_stochastic(self): ''' Split the expression into a stochastic and non-stochastic part. Splits the expression into a tuple of one `Expression` objects f (the non-stochastic part) and a dictionary mapping stochastic variables to `Expression` objects. For example, an expression of the form ``f + g * xi_1 + h * xi_2`` would be returned as: ``(f, {'xi_1': g, 'xi_2': h})`` Note that the `Expression` objects for the stochastic parts do not include the stochastic variable itself. Returns ------- (f, d) : (`Expression`, dict) A tuple of an `Expression` object and a dictionary, the first expression being the non-stochastic part of the equation and the dictionary mapping stochastic variables (``xi`` or starting with ``xi_``) to `Expression` objects. If no stochastic variable is present in the code string, a tuple ``(self, None)`` will be returned with the unchanged `Expression` object. ''' stochastic_variables = [] for identifier in self.identifiers: if identifier == 'xi' or identifier.startswith('xi_'): stochastic_variables.append(identifier) # No stochastic variable if not len(stochastic_variables): return (self, None) s_expr = self.sympy_expr.expand() stochastic_symbols = [sympy.Symbol(variable, real=True) for variable in stochastic_variables] f = sympy.Wild('f', exclude=stochastic_symbols) # non-stochastic part match_objects = [sympy.Wild('w_'+variable, exclude=stochastic_symbols) for variable in stochastic_variables] match_expression = f for symbol, match_object in zip(stochastic_symbols, match_objects): match_expression += match_object * symbol matches = s_expr.match(match_expression) if matches is None: raise ValueError(('Expression "%s" cannot be separated into stochastic ' 'and non-stochastic term') % self.code) f_expr = Expression(sympy_to_str(matches[f])) stochastic_expressions = dict((variable, Expression(sympy_to_str(matches[match_object]))) for (variable, match_object) in zip(stochastic_variables, match_objects)) return (f_expr, stochastic_expressions)
def __str__(self): s = '%s\n' % self.__class__.__name__ if len(self.statements) > 0: s += 'Intermediate statements:\n' s += '\n'.join([(var + ' = ' + sympy_to_str(expr)) for var, expr in self.statements]) s += '\n' s += 'Output:\n' s += sympy_to_str(self.output) return s
def _get_substituted_expressions(self): ''' Return a list of ``(varname, expr)`` tuples, containing all differential equations with all the static equation variables substituted with the respective expressions. Returns ------- expr_tuples : list of (str, `CodeString`) A list of ``(varname, expr)`` tuples, where ``expr`` is a `CodeString` object with all static equation variables substituted with the respective expression. ''' subst_exprs = [] substitutions = {} for eq in self.ordered: # Skip parameters if eq.expr is None: continue new_sympy_expr = eq.expr.sympy_expr.subs(substitutions) new_str_expr = sympy_to_str(new_sympy_expr) expr = Expression(new_str_expr) if eq.type == STATIC_EQUATION: substitutions.update( {sympy.Symbol(eq.varname, real=True): expr.sympy_expr}) elif eq.type == DIFFERENTIAL_EQUATION: # a differential equation that we have to check subst_exprs.append((eq.varname, expr)) else: raise AssertionError('Unknown equation type %s' % eq.type) return subst_exprs
def _get_substituted_expressions(self): ''' Return a list of ``(varname, expr)`` tuples, containing all differential equations with all the static equation variables substituted with the respective expressions. Returns ------- expr_tuples : list of (str, `CodeString`) A list of ``(varname, expr)`` tuples, where ``expr`` is a `CodeString` object with all static equation variables substituted with the respective expression. ''' subst_exprs = [] substitutions = {} for eq in self.ordered: # Skip parameters if eq.expr is None: continue new_sympy_expr = eq.expr.sympy_expr.subs(substitutions) new_str_expr = sympy_to_str(new_sympy_expr) expr = Expression(new_str_expr) if eq.type == STATIC_EQUATION: substitutions.update({sympy.Symbol(eq.varname, real=True): expr.sympy_expr}) elif eq.type == DIFFERENTIAL_EQUATION: # a differential equation that we have to check subst_exprs.append((eq.varname, expr)) else: raise AssertionError('Unknown equation type %s' % eq.type) return subst_exprs
def __call__(self, equations, variables=None): if variables is None: variables = {} if equations.is_stochastic: raise ValueError('Cannot solve stochastic equations with this state updater') diff_eqs = equations.substituted_expressions t = Symbol('t', real=True, positive=True) dt = Symbol('dt', real=True, positive=True) t0 = Symbol('t0', real=True, positive=True) f0 = Symbol('f0', real=True) # TODO: Shortcut for simple linear equations? Is all this effort really # worth it? code = [] for name, expression in diff_eqs: rhs = expression.sympy_expr non_constant = _non_constant_symbols(rhs.atoms(), variables) - set([name]) if len(non_constant): raise ValueError(('Equation for %s referred to non-constant ' 'variables %s') % (name, str(non_constant))) # We have to be careful and use the real=True assumption as well, # otherwise sympy doesn't consider the symbol a match to the content # of the equation var = Symbol(name, real=True) f = sp.Function(name) rhs = rhs.subs(var, f(t)) derivative = sp.Derivative(f(t), t) diff_eq = sp.Eq(derivative, rhs) general_solution = sp.dsolve(diff_eq, f(t)) # Check whether this is an explicit solution if not getattr(general_solution, 'lhs', None) == f(t): raise ValueError('Cannot explicitly solve: ' + str(diff_eq)) # Solve for C1 (assuming "var" as the initial value and "t0" as time) if Symbol('C1') in general_solution: if Symbol('C2') in general_solution: raise ValueError('Too many constants in solution: %s' % str(general_solution)) constant_solution = sp.solve(general_solution, Symbol('C1')) if len(constant_solution) != 1: raise ValueError(("Couldn't solve for the constant " "C1 in : %s ") % str(general_solution)) constant = constant_solution[0].subs(t, t0).subs(f(t0), var) solution = general_solution.rhs.subs('C1', constant) else: solution = general_solution.rhs.subs(t, t0).subs(f(t0), var) # Evaluate the expression for one timestep solution = solution.subs(t, t + dt).subs(t0, t) # only try symplifying it -- it sometimes raises an error try: solution = solution.simplify() except ValueError: pass code.append(name + ' = ' + sympy_to_str(solution)) return '\n'.join(code)
def __call__(self, equations, variables=None): if variables is None: variables = {} # Get a representation of the ODE system in the form of # dX/dt = M*X + B varnames, matrix, constants = get_linear_system(equations) # Make sure that the matrix M is constant, i.e. it only contains # external variables or constant variables symbols = set.union(*(el.atoms() for el in matrix)) non_constant = _non_constant_symbols(symbols, variables) if len(non_constant): raise ValueError(('The coefficient matrix for the equations ' 'contains the symbols %s, which are not ' 'constant.') % str(non_constant)) symbols = [Symbol(variable, real=True) for variable in varnames] solution = sp.solve_linear_system(matrix.row_join(constants), *symbols) b = sp.ImmutableMatrix([solution[symbol] for symbol in symbols]).transpose() # Solve the system dt = Symbol('dt', real=True, positive=True) A = (matrix * dt).exp() C = sp.ImmutableMatrix([A.dot(b)]) - b _S = sp.MatrixSymbol('_S', len(varnames), 1) updates = A * _S + C.transpose() try: # In sympy 0.7.3, we have to explicitly convert it to a single matrix # In sympy 0.7.2, it is already a matrix (which doesn't have an # is_explicit method) updates = updates.as_explicit() except AttributeError: pass # The solution contains _S[0, 0], _S[1, 0] etc. for the state variables, # replace them with the state variable names abstract_code = [] for idx, (variable, update) in enumerate(zip(varnames, updates)): rhs = update.subs(_S[idx, 0], variable) identifiers = get_identifiers(sympy_to_str(rhs)) for identifier in identifiers: if identifier in variables: var = variables[identifier] if var is None: print identifier, variables if var.scalar and var.constant: float_val = var.get_value() rhs = rhs.xreplace( {Symbol(identifier, real=True): Float(float_val)}) # Do not overwrite the real state variables yet, the update step # of other state variables might still need the original values abstract_code.append('_' + variable + ' = ' + sympy_to_str(rhs)) # Update the state variables for variable in varnames: abstract_code.append( '{variable} = _{variable}'.format(variable=variable)) return '\n'.join(abstract_code)
def __call__(self, equations, variables=None): if variables is None: variables = {} if equations.is_stochastic: raise ValueError( 'Cannot solve stochastic equations with this state updater') diff_eqs = equations.substituted_expressions t = Symbol('t', real=True, positive=True) dt = Symbol('dt', real=True, positive=True) t0 = Symbol('t0', real=True, positive=True) f0 = Symbol('f0', real=True) # TODO: Shortcut for simple linear equations? Is all this effort really # worth it? code = [] for name, expression in diff_eqs: rhs = expression.sympy_expr non_constant = _non_constant_symbols(rhs.atoms(), variables) - set( [name]) if len(non_constant): raise ValueError(('Equation for %s referred to non-constant ' 'variables %s') % (name, str(non_constant))) # We have to be careful and use the real=True assumption as well, # otherwise sympy doesn't consider the symbol a match to the content # of the equation var = Symbol(name, real=True) f = sp.Function(name) rhs = rhs.subs(var, f(t)) derivative = sp.Derivative(f(t), t) diff_eq = sp.Eq(derivative, rhs) general_solution = sp.dsolve(diff_eq, f(t)) # Check whether this is an explicit solution if not getattr(general_solution, 'lhs', None) == f(t): raise ValueError('Cannot explicitly solve: ' + str(diff_eq)) # Solve for C1 (assuming "var" as the initial value and "t0" as time) if Symbol('C1') in general_solution: if Symbol('C2') in general_solution: raise ValueError('Too many constants in solution: %s' % str(general_solution)) constant_solution = sp.solve(general_solution, Symbol('C1')) if len(constant_solution) != 1: raise ValueError(("Couldn't solve for the constant " "C1 in : %s ") % str(general_solution)) constant = constant_solution[0].subs(t, t0).subs(f(t0), var) solution = general_solution.rhs.subs('C1', constant) else: solution = general_solution.rhs.subs(t, t0).subs(f(t0), var) # Evaluate the expression for one timestep solution = solution.subs(t, t + dt).subs(t0, t) # only try symplifying it -- it sometimes raises an error try: solution = solution.simplify() except ValueError: pass code.append(name + ' = ' + sympy_to_str(solution)) return '\n'.join(code)
def evaluator(expr, ns): expr = sympy_to_str(expr) return eval(expr, ns)
def _generate_RHS(self, eqs, var, eq_symbols, temp_vars, expr, non_stochastic_expr, stochastic_expr): ''' Helper function used in `__call__`. Generates the right hand side of an abstract code statement by appropriately replacing f, g and t. For example, given a differential equation ``dv/dt = -(v + I) / tau`` (i.e. `var` is ``v` and `expr` is ``(-v + I) / tau``) together with the `rk2` step ``return x + dt*f(x + k/2, t + dt/2)`` (i.e. `non_stochastic_expr` is ``x + dt*f(x + k/2, t + dt/2)`` and `stochastic_expr` is ``None``), produces ``v + dt*(-v - _k_v/2 + I + _k_I/2)/tau``. ''' def replace_func(x, t, expr, temp_vars): ''' Used to replace a single occurance of ``f(x, t)`` or ``g(x, t)``: `expr` is the non-stochastic (in the case of ``f``) or stochastic part (``g``) of the expression defining the right-hand-side of the differential equation describing `var`. It replaces the variable `var` with the value given as `x` and `t` by the value given for `t. Intermediate variables will be replaced with the appropriate replacements as well. For example, in the `rk2` integrator, the second step involves the calculation of ``f(k/2 + x, dt/2 + t)``. If `var` is ``v`` and `expr` is ``-v / tau``, this will result in ``-(_k_v/2 + v)/tau``. Note that this deals with only one state variable `var`, given as an argument to the surrounding `_generate_RHS` function. ''' try: s_expr = str_to_sympy(str(expr)) except SympifyError as ex: raise ValueError('Error parsing the expression "%s": %s' % (expr, str(ex))) for var in eq_symbols: # Generate specific temporary variables for the state variable, # e.g. '_k_v' for the state variable 'v' and the temporary # variable 'k'. temp_var_replacements = dict(((self.symbols[temp_var], _symbol('_'+temp_var+'_'+var)) for temp_var in temp_vars)) # In the expression given as 'x', replace 'x' by the variable # 'var' and all the temporary variables by their # variable-specific counterparts. x_replacement = x.subs(self.symbols['x'], eq_symbols[var]) x_replacement = x_replacement.subs(temp_var_replacements) # Replace the variable `var` in the expression by the new `x` # expression s_expr = s_expr.subs(eq_symbols[var], x_replacement) # Directly substitute the 't' expression for the symbol t, there # are no temporary variables to consider here. s_expr = s_expr.subs(self.symbols['t'], t) return s_expr # Note: in the following we are silently ignoring the case that a # state updater does not care about either the non-stochastic or the # stochastic part of an equation. We do trust state updaters to # correctly specify their own abilities (i.e. they do not claim to # support stochastic equations but actually just ignore the stochastic # part). We can't really check the issue here, as we are only dealing # with one line of the state updater description. It is perfectly valid # to write the euler update as: # non_stochastic = dt * f(x, t) # stochastic = dt**.5 * g(x, t) * xi # return x + non_stochastic + stochastic # # In the above case, we'll deal with lines which do not define either # the stochastic or the non-stochastic part. non_stochastic, stochastic = expr.split_stochastic() # We do have a non-stochastic part in our equation and in the state # updater description if not (non_stochastic is None or non_stochastic_expr is None): # Replace the f(x, t) part replace_f = lambda x, t:replace_func(x, t, non_stochastic, temp_vars) non_stochastic_result = non_stochastic_expr.replace(self.symbols['f'], replace_f) # Replace x by the respective variable non_stochastic_result = non_stochastic_result.subs(self.symbols['x'], eq_symbols[var]) # Replace intermediate variables temp_var_replacements = dict((self.symbols[temp_var], _symbol('_'+temp_var+'_'+var)) for temp_var in temp_vars) non_stochastic_result = non_stochastic_result.subs(temp_var_replacements) else: non_stochastic_result = None # We do have a stochastic part in our equation and in the state updater # description if not (stochastic is None or stochastic_expr is None): stochastic_results = [] # We potentially have more than one stochastic variable for xi in stochastic: # Replace the g(x, t)*xi part replace_g = lambda x, t:replace_func(x, t, stochastic[xi], temp_vars) stochastic_result = stochastic_expr.replace(self.symbols['g'], replace_g) # Replace x and xi by the respective variables stochastic_result = stochastic_result.subs(self.symbols['x'], eq_symbols[var]) stochastic_result = stochastic_result.subs(self.symbols['dW'], xi) # Replace intermediate variables temp_var_replacements = dict((self.symbols[temp_var], _symbol('_'+temp_var+'_'+var)) for temp_var in temp_vars) stochastic_result = stochastic_result.subs(temp_var_replacements) stochastic_results.append(stochastic_result) else: stochastic_results = [] RHS = [] # All the parts (one non-stochastic and potentially more than one # stochastic part) are combined with addition if non_stochastic_result is not None: RHS.append(sympy_to_str(non_stochastic_result)) for stochastic_result in stochastic_results: RHS.append(sympy_to_str(stochastic_result)) RHS = ' + '.join(RHS) return RHS