def generate_helpers_C(self, chunk_size=100): """ translates the helpers to C code using SymEngine’s `C-code printer <https://github.com/symengine/symengine/pull/1054>`_. Parameters ---------- chunk_size : integer If the number of instructions in the final C code exceeds this number, it will be split into chunks of this size. See `large_systems` on why this is useful. If there is an obvious grouping of your helpers, the group size suggests itself for `chunk_size`. If smaller than 1, no chunking will happen. """ if self.helpers: get_helper = symengine.Function("get_general_helper") set_helper = symengine.Function("set_general_helper") for i, helper in enumerate(self.helpers): self.general_subs[helper[0]] = get_helper(i) self.render_and_write_code( (set_helper(i, helper[1].subs(self.general_subs)) for i, helper in enumerate(self.helpers)), name="general_helpers", chunk_size=chunk_size, arguments=self._default_arguments() + [("general_helper", "double *__restrict const")], omp=False, ) self._helper_C_source = True
def __init__(self, params, seed=None): """ :param params: parameters of the neural mass :type params: dict :param seed: seed for random number generator :type seed: int|None """ assert isinstance(params, dict) self.params = deepcopy(params) self.seed = seed # used in determining portion of the full system's state vector self.idx_state_var = None self.initialised = False # initialise possible helpers self.helper_symbols = { symbol: se.Symbol(symbol) for symbol in self.helper_variables } # initialise possible callback functions self.callback_functions = { function: se.Function(function) for function in self.python_callbacks } self._validate_params()
def __init__(self, f_sym=(), groups=(), simplify=None, average_dynamics=False, **kwargs): GroupHandler.__init__(self, groups) self.n = kwargs.pop("n", None) f_basic, extracted = self.extract_main(self._handle_input(f_sym)) helpers = sort_helpers(sympify_helpers(kwargs.pop("helpers", []))) if simplify is None: simplify = self.n <= 10 z = symengine.Function("z") z_vector = [z(i) for i in range(self.n)] tangent_vector = self.back_transform(z_vector) def tangent_vector_f(): for line in _jac_from_f_with_helpers(f=f_basic, helpers=helpers, simplify=False, n=self.n): yield sum(entry * tangent_vector[k] for k, entry in enumerate(line) if entry) substitutions = {y(i): z(self.map_to_main(i)) for i in range(self.n)} def finalise(entry): entry = entry.subs(substitutions) if simplify: entry = entry.simplify(ratio=1) return replace_function(entry, z, y) if average_dynamics: f_list = list(f_basic()) def f_lyap(): for entry in self.iterate(tangent_vector_f()): if type(entry) == int: # i.e., if main index if average_dynamics: group = groups[entry] yield sum(finalise(f_list[i]) for i in group) / len(group) else: yield finalise(extracted[self.main_indices[entry]]) else: yield finalise(entry[0] - entry[1]) helpers = ((helper[0], finalise(helper[1])) for helper in helpers) super(jitcode_transversal_lyap, self).__init__(f_lyap, helpers=helpers, n=self.n, **kwargs)
def test_back_transform(self): z = symengine.Function("z") z_v = [z(i) for i in range(self.n)] y_v = self.G.back_transform(z_v) transformed = [] for entry in self.G.iterate(range(self.n)): if type(entry) == int: transformed.append(sum(y_v[i] for i in self.groups[entry])) else: transformed.append(y_v[entry[0]] - y_v[entry[1]]) for i in self.G.main_indices: z_v[i] = 0 self.assertSequenceEqual(z_v, [entry.simplify() for entry in transformed])
def test_midpoint(self): f = symengine.Function("f") t = symengine.Symbol("t") control = (f(1)+f(3)+f(5)+f(7)+f(9))*2 result = quadrature(f(t),t,0,10,nsteps=5,method="midpoint") self.assertEqual(control,result)
def sympy_y(index,time=sympy_t): if time == sympy_t: return sympy_current_y(index) else: return sympy_past_y(time, index, sympy_anchors(time)) sympy_current_y = sympy.Function("current_y",real=True) sympy_past_y = sympy.Function("past_y",real=True) sympy_anchors = sympy.Function("anchors",real=True) symengine_t = symengine.Symbol("t",real=True) def symengine_y(index,time=symengine_t): if time == symengine_t: return symengine_current_y(index) else: return symengine_past_y(time, index, symengine_anchors(time)) symengine_current_y = symengine.Function("current_y",real=True) symengine_past_y = symengine.Function("past_y",real=True) symengine_anchors = symengine.Function("anchors",real=True) symengine_manually = [ symengine_t, symengine_y, symengine.cos, ] sympy_manually = [ sympy_t, sympy_y, sympy.cos, ]
def compile_C( self, simplify = None, do_cse = False, numpy_rng = False, chunk_size = 100, extra_compile_args = None, extra_link_args = None, verbose = False, modulename = None, omp = False, ): """ translates the derivative to C code using SymEngine’s `C-code printer <https://github.com/symengine/symengine/pull/1054>`_. For detailed information many of the arguments and other ways to tweak the compilation, read `these notes <jitcde-common.readthedocs.io>`_. Parameters ---------- simplify : boolean Whether the derivative should be `simplified <http://docs.sympy.org/dev/modules/simplify/simplify.html>`_ (with `ratio=1.0`) before translating to C code. The main reason why you could want to disable this is if your derivative is already optimised and so large that simplifying takes a considerable amount of time. If `None`, this will be automatically disabled for `n>10`. do_cse : boolean Whether SymPy’s `common-subexpression detection <http://docs.sympy.org/dev/modules/rewriting.html#module-sympy.simplify.cse_main>`_ should be applied before translating to C code. It is almost always better to let the compiler do this (unless you want to set the compiler optimisation to `-O2` or lower). As this requires all entries of `f` and `g` at once, it may void advantages gained from using generator functions as an input. Also, this feature uses SymPy and not SymEngine. numpy_rng : boolean Whether `numpy.random.normal` shall be explicitly employed for generating random numbers. This is less efficient and mainly exists for testing purposes to ensure that the random numbers are the same as when using the Python backend. Note that the alternative is still based on the same code as NumPy’s random-number generator (until somebody changes it) and should produce the same results. Also note that details in the arithmetic realisation may still cause tiny differences in the results from the two backends, which can then be magnified by the butterfly effect. chunk_size : integer If the number of instructions in the final C code exceeds this number, it will be split into chunks of this size. See `Handling very large differential equations <http://jitcde-common.readthedocs.io/#handling-very-large-differential-equations>`_ on why this is useful and how to best choose this value. If smaller than 1, no chunking will happen. extra_compile_args : iterable of strings extra_link_args : iterable of strings Arguments to be handed to the C compiler or linker, respectively. verbose : boolean Whether the compiler commands shall be shown. This is the same as Setuptools’ `verbose` setting. modulename : string or `None` The name used for the compiled module. omp : pair of iterables of strings or boolean What compiler arguments shall be used for multiprocessing (using OpenMP). If `True`, they will be selected automatically. If empty or `False`, no compilation for multiprocessing will happen (unless you supply the relevant compiler arguments otherwise). """ self.compile_attempt = False if simplify is None: simplify = self.n<=10 helper_lengths = dict() for sym,helpers,name,long_name in [ ( self.f_sym, self._f_helpers, "f", "drift" ), ( self.g_sym, self._g_helpers, "g", "diffusion" ) ]: setter_name = "set_" + long_name wc = sym() helpers_wc = copy_helpers(helpers) if simplify: wc = (entry.simplify(ratio=1.0) for entry in wc) if do_cse: import sympy additional_helper = sympy.Function("additional_"+name+"_helper") _cse = sympy.cse( sympy.Matrix(sympy.sympify(list(wc))), symbols = (additional_helper(i) for i in count()) ) helpers_wc.extend(symengine.sympify(_cse[0])) wc = symengine.sympify(_cse[1][0]) arguments = [ ("self", "sde_integrator * const"), ("t", "double const"), (long_name, "double", self.n), ] if name=="f" or not self.additive: arguments.append( ("Y","double",self.n) ) if name=="f": arguments.append( ("h","double") ) functions = ["y"] self.substitutions = { control_par: symengine.Symbol("self->parameter_"+control_par.name) for control_par in self.control_pars } def finalise(expression): return expression.subs(self.substitutions) if helpers_wc: converted_helpers = [] get_helper = symengine.Function("get_"+name+"_helper") set_helper = symengine.Function("set_"+name+"_helper") for i,helper in enumerate(helpers_wc): converted_helpers.append(set_helper(i, finalise(helper[1]))) self.substitutions[helper[0]] = get_helper(i) extra_arguments = [(name, "double", len(helpers_wc))] functions.extend(["get_"+name+"_helper", "set_"+name+"_helper"]) self.render_and_write_code( converted_helpers, name = name + "_helpers", chunk_size = chunk_size, arguments = arguments + extra_arguments ) setter = symengine.Function(setter_name) self.render_and_write_code( (setter(i,finalise(entry)) for i,entry in enumerate(wc)), name = name, chunk_size = chunk_size, arguments = arguments ) helper_lengths[name] = len(helpers_wc) self._process_modulename(modulename) self._render_template( n = self.n, number_of_f_helpers = helper_lengths["f"], number_of_g_helpers = helper_lengths["g"], control_pars = [ par.name for par in self.control_pars ], additive = self.additive, numpy_rng = numpy_rng, chunk_size = chunk_size, # only for OMP callbacks = [(fun.name,n_args) for fun,_,n_args in self.callback_functions], ) if not numpy_rng: rng_file = path.join(path.dirname(__file__),"random_numbers.c") shutil.copy(rng_file,self._tmpfile()) self._compile_and_load(verbose,extra_compile_args,extra_link_args,omp)
#!/usr/bin/python3 # -*- coding: utf-8 -*- from warnings import warn from itertools import count, chain from os import path as path import shutil import random import symengine import numpy as np from jitcxde_common import jitcxde, checker from jitcxde_common.helpers import sort_helpers, sympify_helpers, copy_helpers, filter_helpers, find_dependent_helpers from jitcxde_common.symbolic import collect_arguments, has_function #: the symbol for the state that must be used to define the differential equation. It is a function and the integer argument denotes the component. You may just as well define an analogous function directly with SymEngine or SymPy, but using this function is the best way to get the most of future versions of JiTCSDE, in particular avoiding incompatibilities. You can import a SymPy variant from the submodule `sympy_symbols` instead (see `SymPy vs. SymEngine`_ for details). y = symengine.Function("y") #: the symbol for time for defining the differential equation. If your differential equation has no explicit time dependency (“autonomous system”), you do not need this. You may just as well define an analogous symbol directly with SymEngine or SymPy, but using this function is the best way to get the most of future versions of JiTCSDE, in particular avoiding incompatibilities. You can import a SymPy variant from the submodule `sympy_symbols` instead (see `SymPy vs. SymEngine`_ for details). t = symengine.Symbol("t",real=True) class UnsuccessfulIntegration(Exception): """ This exception is raised when the integrator cannot meet the accuracy and step-size requirements. If you want to know the exact state of your system before the integration fails or similar, catch this exception. """ pass class jitcsde(jitcxde): """ Parameters ---------- f_sym : iterable of symbolic expressions or generator function yielding symbolic expressions or dictionary
def generate_jac_C(self, do_cse=False, chunk_size=100, sparse=True): """ translates the symbolic Jacobian to C code using SymEngine’s `C-code printer <https://github.com/symengine/symengine/pull/1054>`_. If the symbolic Jacobian has not been generated, it generates it by calling `generate_jac_sym`. Parameters ---------- do_cse : boolean Whether SymPy’s `common-subexpression detection <http://docs.sympy.org/dev/modules/rewriting.html#module-sympy.simplify.cse_main>`_ should be applied before translating to C code. It is almost always better to let the compiler do this (unless you want to set the compiler optimisation to `-O2` or lower): For simple differential equations this should not make any difference to the compiler’s optimisations. For large ones, it may make a difference but also take long. As this requires the entire Jacobian at once, it may void advantages gained from using generator functions as an input. Also, this feature uses SymPy and not SymEngine. chunk_size : integer If the number of instructions in the final C code exceeds this number, it will be split into chunks of this size. See `Handling very large differential equations <http://jitcde-common.readthedocs.io/#handling-very-large-differential-equations>`_ on why this is useful and how to best choose this value. If smaller than 1, no chunking will happen. sparse : boolean Whether a sparse Jacobian should be assumed for optimisation. Note that this does not mean that the Jacobian is stored, parsed or handled as a sparse matrix. This kind of optimisation would require `ode` or `solve_ivp` to be able to handle sparse matrices without structure in the sparseness. """ self._generate_helpers_C() # working copy jac_sym_wc = ((entry.subs(self.general_subs) for entry in line) for line in self.jac_sym) self.sparse_jac = sparse arguments = self._default_arguments() if self.helpers: arguments.append( ("general_helper", "double const *__restrict const")) if do_cse: import sympy get_helper = sympy.Function("get_jac_helper") set_helper = symengine.Function("set_jac_helper") jac_sym_wc = sympy.Matrix( [[sympy.sympify(entry) for entry in line] for line in jac_sym_wc]) _cse = sympy.cse(sympy.sympify(jac_sym_wc), symbols=(get_helper(i) for i in count())) more_helpers = symengine.sympify(_cse[0]) jac_sym_wc = symengine.sympify(_cse[1][0].tolist()) if more_helpers: arguments.append(("jac_helper", "double *__restrict const")) self.render_and_write_code( (set_helper(i, helper[1]) for i, helper in enumerate(more_helpers)), name="jac_helpers", chunk_size=chunk_size, arguments=arguments, omp=False) self._number_of_jac_helpers = len(more_helpers) set_dfdy = symengine.Function("set_dfdy") self.render_and_write_code( (set_dfdy(i, j, entry) for i, line in enumerate(jac_sym_wc) for j, entry in enumerate(line) if ((entry != 0) or not self.sparse_jac)), name="jac", chunk_size=chunk_size, arguments=arguments + [("dfdY", "PyArrayObject *__restrict const")]) self._jac_C_source = True
def generate_f_C(self, simplify=None, do_cse=False, chunk_size=100): """ translates the derivative to C code using SymEngine’s `C-code printer <https://github.com/symengine/symengine/pull/1054>`_. Parameters ---------- simplify : boolean or None Whether the derivative should be `simplified <http://docs.sympy.org/dev/modules/simplify/simplify.html>`_ (with `ratio=1.0`) before translating to C code. The main reason why you could want to enable this is if you expect your derivative not to be optimised and not be so large that simplifying takes a considerable amount of time. If `None`, this will be automatically disabled for `n>10`. do_cse : boolean Whether SymPy’s `common-subexpression detection <http://docs.sympy.org/dev/modules/rewriting.html#module-sympy.simplify.cse_main>`_ should be applied before translating to C code. It is almost always better to let the compiler do this (unless you want to set the compiler optimisation to `-O2` or lower): For simple differential equations this should not make any difference to the compiler’s optimisations. For large ones, it may make a difference but also take long. As this requires all entries of `f` at once, it may void advantages gained from using generator functions as an input. Also, this feature uses SymPy and not SymEngine. chunk_size : integer If the number of instructions in the final C code exceeds this number, it will be split into chunks of this size. See `Handling very large differential equations <http://jitcde-common.readthedocs.io/#handling-very-large-differential-equations>`_ on why this is useful and how to best choose this value. If smaller than 1, no chunking will happen. """ self._generate_helpers_C() # working copy f_sym_wc = (entry.subs(self.general_subs) for entry in self.f_sym()) if simplify is None: simplify = self.n <= 10 if simplify: f_sym_wc = (entry.simplify(ratio=1) for entry in f_sym_wc) arguments = self._default_arguments() if self.helpers: arguments.append( ("general_helper", "double const *__restrict const")) if do_cse: import sympy get_helper = sympy.Function("get_f_helper") set_helper = symengine.Function("set_f_helper") _cse = sympy.cse(sympy.Matrix(sympy.sympify(list(f_sym_wc))), symbols=(get_helper(i) for i in count())) more_helpers = symengine.sympify(_cse[0]) f_sym_wc = symengine.sympify(_cse[1][0]) if more_helpers: arguments.append(("f_helper", "double *__restrict const")) self.render_and_write_code( (set_helper(i, helper[1]) for i, helper in enumerate(more_helpers)), name="f_helpers", chunk_size=chunk_size, arguments=arguments, omp=False, ) self._number_of_f_helpers = len(more_helpers) set_dy = symengine.Function("set_dy") self.render_and_write_code( (set_dy(i, entry) for i, entry in enumerate(f_sym_wc)), name="f", chunk_size=chunk_size, arguments=arguments + [("dY", "PyArrayObject *__restrict const")]) self._f_C_source = True
""" Tests whether things works independent of where symbols are imported from. """ import jitcsde import jitcsde.sympy_symbols import sympy import symengine import random symengine_manually = [ symengine.Symbol("t", real=True), symengine.Function("y", real=True), symengine.cos, ] sympy_manually = [ sympy.Symbol("t", real=True), sympy.Function("y", real=True), sympy.cos, ] jitcsde_provisions = [ jitcsde.t, jitcsde.y, symengine.cos, ] jitcsde_sympy_provisions = [ jitcsde.sympy_symbols.t, jitcsde.sympy_symbols.y,
# {{{ pymbolic -> symengine class PymbolicToSymEngineMapper(PymbolicToSympyLikeMapper): sym = symengine def raise_conversion_error(self, expr): raise RuntimeError("do not know how to translate '%s' to symengine" % expr) # }}} CSE = symengine.Function("CSE") def make_cse(arg, prefix=None, scope=None): # SymEngine's classes can't be inherited, but there's a # mechanism to create one based on SymPy's ones which stores # the SymPy object inside the C++ object. # This SymPy object is later retrieved to get the prefix # These conversions between SymPy and SymEngine are expensive, # so use it only if necessary. if prefix is None and scope is None: return CSE(arg) from pymbolic.interop.sympy import make_cse as make_cse_sympy sympy_result = make_cse_sympy(arg, prefix=prefix, scope=scope) return symengine.sympify(sympy_result)