Esempio n. 1
0
    def wrap(wrapper, f, g, dt, sde_type, var_type, wiener_type, show_code):
        """The base function to format a SRK method.

        Parameters
        ----------
        f : callable
            The drift function of the SDE.
        g : callable
            The diffusion function of the SDE.
        dt : float
            The numerical precision.
        sde_type : str
            "utils.ITO_SDE" : Ito's Stochastic Calculus.
            "utils.STRA_SDE" : Stratonovich's Stochastic Calculus.
        wiener_type : str
        var_type : str
            "scalar" : with the shape of ().
            "population" : with the shape of (N,) or (N1, N2) or (N1, N2, ...).
            "system": with the shape of (d, ), (d, N), or (d, N1, N2).
        show_code : bool
            Whether show the formatted code.

        Returns
        -------
        numerical_func : callable
            The numerical function.
        """

        var_type = constants.POPU_VAR if var_type is None else var_type
        sde_type = constants.ITO_SDE if sde_type is None else sde_type
        wiener_type = constants.SCALAR_WIENER if wiener_type is None else wiener_type
        if var_type not in constants.SUPPORTED_VAR_TYPE:
            raise errors.IntegratorError(f'Currently, BrainPy only supports variable types: '
                                         f'{constants.SUPPORTED_VAR_TYPE}. But we got {var_type}.')
        if sde_type != constants.ITO_SDE:
            raise errors.IntegratorError(f'SRK method for SDEs with scalar noise only supports Ito SDE type, '
                                         f'but we got {sde_type} integral.')
        if wiener_type != constants.SCALAR_WIENER:
            raise errors.IntegratorError(f'SRK method for SDEs with scalar noise only supports scalar '
                                         f'Wiener Process, but we got "{wiener_type}" noise.')

        show_code = False if show_code is None else show_code
        dt = backend.get_dt() if dt is None else dt

        if f is not None and g is not None:
            return wrapper(f=f, g=g, dt=dt, show_code=show_code, sde_type=sde_type,
                           var_type=var_type, wiener_type=wiener_type)

        elif f is not None:
            return lambda g: wrapper(f=f, g=g, dt=dt, show_code=show_code, sde_type=sde_type,
                                     var_type=var_type, wiener_type=wiener_type)

        elif g is not None:
            return lambda f: wrapper(f=f, g=g, dt=dt, show_code=show_code, sde_type=sde_type,
                                     var_type=var_type, wiener_type=wiener_type)

        else:
            raise ValueError('Must provide "f" or "g".')
Esempio n. 2
0
 def __init__(self, diff_eq):
     if not isinstance(diff_eq, ast_analysis.DiffEquation):
         if diff_eq.__class__.__name__ != 'function':
             raise errors.IntegratorError(
                 '"diff_eq" must be a function or an instance of DiffEquation .'
             )
         else:
             diff_eq = ast_analysis.DiffEquation(func=diff_eq)
     self.diff_eq = diff_eq
     self._update_code = None
     self._update_func = None
Esempio n. 3
0
    def __init__(self,
                 f,
                 var_type=None,
                 dt=None,
                 name=None,
                 adaptive=None,
                 tol=None,
                 show_code=False):
        super(AdaptiveRKIntegrator, self).__init__(f=f,
                                                   var_type=var_type,
                                                   dt=dt,
                                                   name=name,
                                                   show_code=show_code)

        # check parameters
        self.adaptive = False if (adaptive is None) else adaptive
        self.tol = 0.1 if tol is None else tol
        self.var_type = C.POP_VAR if var_type is None else var_type
        if self.var_type not in C.SUPPORTED_VAR_TYPE:
            raise errors.IntegratorError(
                f'"var_type" only supports {C.SUPPORTED_VAR_TYPE}, '
                f'not {self.var_type}.')

        # integrator keywords
        keywords = {
            C.F: 'the derivative function',
            C.DT: 'the precision of numerical integration'
        }
        for v in self.variables:
            keywords[f'{v}_new'] = 'the intermediate value'
            for i in range(1, len(self.A) + 1):
                keywords[f'd{v}_k{i}'] = 'the intermediate value'
            for i in range(2, len(self.A) + 1):
                keywords[f'k{i}_{v}_arg'] = 'the intermediate value'
                keywords[f'k{i}_t_arg'] = 'the intermediate value'
        if adaptive:
            keywords['dt_new'] = 'the new numerical precision "dt"'
            keywords['tol'] = 'the tolerance for the local truncation error'
            keywords['error'] = 'the local truncation error'
            for v in self.variables:
                keywords[f'{v}_te'] = 'the local truncation error'
            self.code_scope['tol'] = tol
            self.code_scope['math'] = bm
        utils.check_kws(self.arguments, keywords)

        # build the integrator
        self.build()
Esempio n. 4
0
def heun(f=None,
         g=None,
         dt=None,
         sde_type=None,
         var_type=None,
         wiener_type=None,
         show_code=None):
    if sde_type != constants.STRA_SDE:
        raise errors.IntegratorError(
            f'Heun method only supports Stranovich integral of SDEs, '
            f'but we got {sde_type} integral.')
    return Wrapper.wrap(Wrapper.euler_and_heun,
                        f=f,
                        g=g,
                        dt=dt,
                        sde_type=sde_type,
                        var_type=var_type,
                        wiener_type=wiener_type,
                        show_code=show_code)
Esempio n. 5
0
 def __init__(self,
              f,
              g,
              dt=None,
              name=None,
              show_code=False,
              var_type=None,
              intg_type=None,
              wiener_type=None):
     if intg_type != constants.STRA_SDE:
         raise errors.IntegratorError(
             f'Heun method only supports Stranovich integral of SDEs, '
             f'but we got {intg_type} integral.')
     super(Heun, self).__init__(f=f,
                                g=g,
                                dt=dt,
                                show_code=show_code,
                                name=name,
                                var_type=var_type,
                                intg_type=intg_type,
                                wiener_type=wiener_type)
     self.build()
Esempio n. 6
0
    def __init__(self,
                 f,
                 g,
                 dt=None,
                 name=None,
                 show_code=False,
                 var_type=None,
                 intg_type=None,
                 wiener_type=None):
        super(SDEIntegrator, self).__init__(name=name)

        # derivative functions
        self.derivative = {constants.F: f, constants.G: g}
        self.f = f
        self.g = g

        # integration function
        self.integral = None

        # essential parameters
        self.dt = math.get_dt() if dt is None else dt
        assert isinstance(
            self.dt, (int, float)), f'"dt" must be a float, but got {self.dt}'
        intg_type = constants.ITO_SDE if intg_type is None else intg_type
        var_type = constants.SCALAR_VAR if var_type is None else var_type
        wiener_type = constants.SCALAR_WIENER if wiener_type is None else wiener_type
        if intg_type not in constants.SUPPORTED_INTG_TYPE:
            raise errors.IntegratorError(
                f'Currently, BrainPy only support SDE_INT types: '
                f'{constants.SUPPORTED_INTG_TYPE}. But we got {intg_type}.')
        if var_type not in constants.SUPPORTED_VAR_TYPE:
            raise errors.IntegratorError(
                f'Currently, BrainPy only supports variable types: '
                f'{constants.SUPPORTED_VAR_TYPE}. But we got {var_type}.')
        if wiener_type not in constants.SUPPORTED_WIENER_TYPE:
            raise errors.IntegratorError(
                f'Currently, BrainPy only supports Wiener '
                f'Process types: {constants.SUPPORTED_WIENER_TYPE}. '
                f'But we got {wiener_type}.')
        self.var_type = var_type  # variable type
        self.intg_type = intg_type  # integral type
        self.wiener_type = wiener_type  # wiener process type

        # parse function arguments
        variables, parameters, arguments = utils.get_args(f)
        self.variables = variables  # variable names, (before 't')
        self.parameters = parameters  # parameter names, (after 't')
        self.arguments = list(arguments) + [f'{constants.DT}={self.dt}'
                                            ]  # function arguments

        # random seed
        self.rng = math.random.RandomState()

        # code scope
        self.code_scope = {
            constants.F: f,
            constants.G: g,
            'math': math,
            'random': self.rng
        }

        # code lines
        self.func_name = f_names(f)
        self.code_lines = [
            f'def {self.func_name}({", ".join(self.arguments)}):'
        ]

        # others
        self.show_code = show_code
Esempio n. 7
0
    def symbolic_build(self):
        if self.var_type == constants.SYSTEM_VAR:
            raise errors.IntegratorError(
                f'Exponential Euler method do not support {self.var_type} variable type.'
            )
        if self.intg_type != constants.ITO_SDE:
            raise errors.IntegratorError(
                f'Exponential Euler method only supports Ito integral, but we got {self.intg_type}.'
            )

        if sympy is None or analysis_by_sympy is None:
            raise errors.PackageMissingError('SymPy must be installed when '
                                             'using exponential integrators.')

        # check bound method
        if hasattr(self.derivative[constants.F], '__self__'):
            self.code_lines = [
                f'def {self.func_name}({", ".join(["self"] + list(self.arguments))}):'
            ]

        # 1. code scope
        closure_vars = inspect.getclosurevars(self.derivative[constants.F])
        self.code_scope.update(closure_vars.nonlocals)
        self.code_scope.update(dict(closure_vars.globals))
        self.code_scope['math'] = math

        # 2. code lines
        code_lines = self.code_lines
        # code_lines = [f'def {self.func_name}({", ".join(self.arguments)}):']
        code_lines.append(f'  {constants.DT}_sqrt = {constants.DT} ** 0.5')

        # 2.1 dg
        # dg = g(x, t, *args)
        all_dg = [f'{var}_dg' for var in self.variables]
        code_lines.append(
            f'  {", ".join(all_dg)} = g({", ".join(self.variables + self.parameters)})'
        )
        code_lines.append('  ')

        # 2.2 dW
        noise_terms(code_lines, self.variables)

        # 2.3 dgdW
        # ----
        # SCALAR_WIENER : dg * dW
        # VECTOR_WIENER : math.sum(dg * dW, axis=-1)

        if self.wiener_type == constants.SCALAR_WIENER:
            for var in self.variables:
                code_lines.append(f'  {var}_dgdW = {var}_dg * {var}_dW')
        else:
            for var in self.variables:
                code_lines.append(
                    f'  {var}_dgdW = math.sum({var}_dg * {var}_dW, axis=-1)')
        code_lines.append('  ')

        # 2.4 new var
        # ----
        analysis = separate_variables(self.derivative[constants.F])
        variables_for_returns = analysis['variables_for_returns']
        expressions_for_returns = analysis['expressions_for_returns']
        for vi, (key, vars) in enumerate(variables_for_returns.items()):
            # separate variables
            sd_variables = []
            for v in vars:
                if len(v) > 1:
                    raise ValueError(
                        'Cannot analyze multi-assignment code line.')
                sd_variables.append(v[0])
            expressions = expressions_for_returns[key]
            var_name = self.variables[vi]
            diff_eq = analysis_by_sympy.SingleDiffEq(var_name=var_name,
                                                     variables=sd_variables,
                                                     expressions=expressions,
                                                     derivative_expr=key,
                                                     scope=self.code_scope,
                                                     func_name=self.func_name)

            f_expressions = diff_eq.get_f_expressions(
                substitute_vars=diff_eq.var_name)

            # code lines
            code_lines.extend(
                [f"  {str(expr)}" for expr in f_expressions[:-1]])

            # get the linear system using sympy
            f_res = f_expressions[-1]
            df_expr = analysis_by_sympy.str2sympy(f_res.code).expr.expand()
            s_df = sympy.Symbol(f"{f_res.var_name}")
            code_lines.append(
                f'  {s_df.name} = {analysis_by_sympy.sympy2str(df_expr)}')
            var = sympy.Symbol(diff_eq.var_name, real=True)

            # get df part
            s_linear = sympy.Symbol(f'_{diff_eq.var_name}_linear')
            s_linear_exp = sympy.Symbol(f'_{diff_eq.var_name}_linear_exp')
            s_df_part = sympy.Symbol(f'_{diff_eq.var_name}_df_part')
            if df_expr.has(var):
                # linear
                linear = sympy.collect(df_expr, var, evaluate=False)[var]
                code_lines.append(
                    f'  {s_linear.name} = {analysis_by_sympy.sympy2str(linear)}'
                )
                # linear exponential
                code_lines.append(
                    f'  {s_linear_exp.name} = math.exp({analysis_by_sympy.sympy2str(linear)} * {constants.DT})'
                )
                # df part
                df_part = (s_linear_exp - 1) / s_linear * s_df
                code_lines.append(
                    f'  {s_df_part.name} = {analysis_by_sympy.sympy2str(df_part)}'
                )

            else:
                # linear exponential
                code_lines.append(
                    f'  {s_linear_exp.name} = {constants.DT}_sqrt')
                # df part
                code_lines.append(
                    f'  {s_df_part.name} = {s_df.name} * {constants.DT}')

            # update expression
            update = var + s_df_part

            # The actual update step
            code_lines.append(
                f'  {diff_eq.var_name}_new = {analysis_by_sympy.sympy2str(update)} + {var_name}_dgdW'
            )
            code_lines.append('')

        # returns
        new_vars = [f'{var}_new' for var in self.variables]
        code_lines.append(f'  return {", ".join(new_vars)}')

        # return and compile
        self.integral = utils.compile_code(
            code_scope={k: v
                        for k, v in self.code_scope.items()},
            code_lines=self.code_lines,
            show_code=self.show_code,
            func_name=self.func_name)

        if hasattr(self.derivative[constants.F], '__self__'):
            host = self.derivative[constants.F].__self__
            self.integral = self.integral.__get__(host, host.__class__)
Esempio n. 8
0
def exp_euler_wrapper(f, show_code, dt, var_type, im_return):
    try:
        import sympy
        from brainpy.integrators import sympy_analysis
    except ModuleNotFoundError:
        raise errors.PackageMissingError(
            'SymPy must be installed when using exponential euler methods.')

    if var_type == constants.SYSTEM_VAR:
        raise errors.IntegratorError(
            f'Exponential Euler method do not support {var_type} variable type.'
        )

    dt_var = 'dt'
    class_kw, variables, parameters, arguments = utils.get_args(f)
    func_name = Tools.f_names(f)

    code_lines = [f'def {func_name}({", ".join(arguments)}):']

    # code scope
    closure_vars = inspect.getclosurevars(f)
    code_scope = dict(closure_vars.nonlocals)
    code_scope.update(dict(closure_vars.globals))
    code_scope[dt_var] = dt
    code_scope['f'] = f
    code_scope['exp'] = ops.exp

    analysis = separate_variables(f)
    variables_for_returns = analysis['variables_for_returns']
    expressions_for_returns = analysis['expressions_for_returns']
    for vi, (key, vars) in enumerate(variables_for_returns.items()):
        # separate variables
        sd_variables = []
        for v in vars:
            if len(v) > 1:
                raise ValueError('Cannot analyze multi-assignment code line.')
            sd_variables.append(v[0])
        expressions = expressions_for_returns[key]
        var_name = variables[vi]
        diff_eq = sympy_analysis.SingleDiffEq(var_name=var_name,
                                              variables=sd_variables,
                                              expressions=expressions,
                                              derivative_expr=key,
                                              scope=code_scope,
                                              func_name=func_name)

        f_expressions = diff_eq.get_f_expressions(
            substitute_vars=diff_eq.var_name)

        # code lines
        code_lines.extend([f"  {str(expr)}" for expr in f_expressions[:-1]])

        # get the linear system using sympy
        f_res = f_expressions[-1]
        df_expr = sympy_analysis.str2sympy(f_res.code).expr.expand()
        s_df = sympy.Symbol(f"{f_res.var_name}")
        code_lines.append(
            f'  {s_df.name} = {sympy_analysis.sympy2str(df_expr)}')
        var = sympy.Symbol(diff_eq.var_name, real=True)

        # get df part
        s_linear = sympy.Symbol(f'_{diff_eq.var_name}_linear')
        s_linear_exp = sympy.Symbol(f'_{diff_eq.var_name}_linear_exp')
        s_df_part = sympy.Symbol(f'_{diff_eq.var_name}_df_part')
        if df_expr.has(var):
            # linear
            linear = sympy.collect(df_expr, var, evaluate=False)[var]
            code_lines.append(
                f'  {s_linear.name} = {sympy_analysis.sympy2str(linear)}')
            # linear exponential
            linear_exp = sympy.exp(linear * dt)
            code_lines.append(
                f'  {s_linear_exp.name} = {sympy_analysis.sympy2str(linear_exp)}'
            )
            # df part
            df_part = (s_linear_exp - 1) / s_linear * s_df
            code_lines.append(
                f'  {s_df_part.name} = {sympy_analysis.sympy2str(df_part)}')

        else:
            # linear exponential
            code_lines.append(f'  {s_linear_exp.name} = sqrt({dt})')
            # df part
            code_lines.append(
                f'  {s_df_part.name} = {sympy_analysis.sympy2str(dt * s_df)}')

        # update expression
        update = var + s_df_part

        # The actual update step
        code_lines.append(
            f'  {diff_eq.var_name}_new = {sympy_analysis.sympy2str(update)}')
        code_lines.append('')

    code_lines.append(f'  return {", ".join([f"{v}_new" for v in variables])}')
    return Tools.compile_and_assign_attrs(code_lines=code_lines,
                                          code_scope=code_scope,
                                          show_code=show_code,
                                          func_name=func_name,
                                          variables=variables,
                                          parameters=parameters,
                                          dt=dt,
                                          var_type=var_type)
Esempio n. 9
0
def adaptive_rk_wrapper(f, dt, A, B1, B2, C, tol, adaptive, show_code,
                        var_type, im_return):
    """Adaptive Runge-Kutta numerical method for ordinary differential equations.

    The embedded methods are designed to produce an estimate of the local
    truncation error of a single Runge-Kutta step, and as result, allow to
    control the error with adaptive stepsize. This is done by having two
    methods in the tableau, one with order p and one with order :math:`p-1`.

    The lower-order step is given by

    .. math::

        y^*_{n+1} = y_n + h\\sum_{i=1}^s b^*_i k_i,

    where the :math:`k_{i}` are the same as for the higher order method. Then the error is

    .. math::

        e_{n+1} = y_{n+1} - y^*_{n+1} = h\\sum_{i=1}^s (b_i - b^*_i) k_i,


    which is :math:`O(h^{p})`. The Butcher Tableau for this kind of method is extended to
    give the values of :math:`b_{i}^{*}`

    .. math::

        \\begin{array}{c|cccc}
            c_1    & a_{11} & a_{12}& \\dots & a_{1s}\\\\
            c_2    & a_{21} & a_{22}& \\dots & a_{2s}\\\\
            \\vdots & \\vdots & \\vdots& \\ddots& \\vdots\\\\
            c_s    & a_{s1} & a_{s2}& \\dots & a_{ss} \\\\
        \\hline & b_1    & b_2   & \\dots & b_s\\\\
               & b_1^*    & b_2^*   & \\dots & b_s^*\\\\
        \\end{array}


    Parameters
    ----------
    f : callable
        The derivative function.
    show_code : bool
        Whether show the formatted code.
    dt : float
        The numerical precision.
    A : tuple, list
        The A matrix in the Butcher tableau.
    B1 : tuple, list
        The B1 vector in the Butcher tableau.
    B2 : tuple, list
        The B2 vector in the Butcher tableau.
    C : tuple, list
        The C vector in the Butcher tableau.
    adaptive : bool
    tol : float
    var_type : str

    Returns
    -------
    integral_func : callable
        The one-step numerical integration function.
    """
    if var_type not in constants.SUPPORTED_VAR_TYPE:
        raise errors.IntegratorError(
            f'"var_type" only supports {constants.SUPPORTED_VAR_TYPE}, not {var_type}.'
        )

    class_kw, variables, parameters, arguments = utils.get_args(f)
    dt_var = 'dt'
    func_name = Tools.f_names(f)

    if adaptive:
        # code scope
        code_scope = {'f': f, 'tol': tol}
        arguments = list(arguments) + [f'dt={dt}']
    else:
        # code scope
        code_scope = {'f': f, 'dt': dt}

    # code lines
    code_lines = [f'def {func_name}({", ".join(arguments)}):']
    # stage steps
    Tools.step(variables, dt_var, A, C, code_lines, parameters)
    # variable update
    return_args = Tools.update(variables, dt_var, B1, code_lines)

    # error adaptive item
    if adaptive:
        errors_ = []
        for v in variables:
            result = []
            for i, (b1, b2) in enumerate(zip(B1, B2)):
                if isinstance(b1, str):
                    b1 = eval(b1)
                if isinstance(b2, str):
                    b2 = eval(b2)
                diff = b1 - b2
                if diff != 0.:
                    result.append(f'd{v}_k{i + 1} * {dt_var} * {diff}')
            if len(result) > 0:
                if var_type == constants.SCALAR_VAR:
                    code_lines.append(f'  {v}_te = abs({" + ".join(result)})')
                else:
                    code_lines.append(
                        f'  {v}_te = sum(abs({" + ".join(result)}))')
                errors_.append(f'{v}_te')
        if len(errors_) > 0:
            code_lines.append(f'  error = {" + ".join(errors_)}')
            code_lines.append(f'  if error > tol:')
            code_lines.append(
                f'    {dt_var}_new = 0.9 * {dt_var} * (tol / error) ** 0.2')
            code_lines.append(f'  else:')
            code_lines.append(f'    {dt_var}_new = {dt_var}')
            return_args.append(f'{dt_var}_new')

    # returns
    code_lines.append(f'  return {", ".join(return_args)}')

    # compilation
    return Tools.compile_and_assign_attrs(code_lines=code_lines,
                                          code_scope=code_scope,
                                          show_code=show_code,
                                          func_name=func_name,
                                          variables=variables,
                                          parameters=parameters,
                                          dt=dt,
                                          var_type=var_type)
Esempio n. 10
0
    def exp_euler(f, g, dt, sde_type, var_type, wiener_type, show_code):
        try:
            import sympy
            from brainpy.integrators import sympy_analysis
        except ModuleNotFoundError:
            raise errors.PackageMissingError(
                'SymPy must be installed when using exponential euler methods.'
            )

        if var_type == constants.SYSTEM_VAR:
            raise errors.IntegratorError(
                f'Exponential Euler method do not support {var_type} variable type.'
            )
        if sde_type != constants.ITO_SDE:
            raise errors.IntegratorError(
                f'Exponential Euler method only supports Ito integral, but we got {sde_type}.'
            )

        vdt, variables, parameters, arguments, func_name = common.basic_info(
            f=f, g=g)

        # 1. code scope
        closure_vars = inspect.getclosurevars(f)
        code_scope = dict(closure_vars.nonlocals)
        code_scope.update(dict(closure_vars.globals))
        code_scope['f'] = f
        code_scope['g'] = g
        code_scope[vdt] = dt
        code_scope[f'{vdt}_sqrt'] = dt**0.5
        code_scope['ops'] = ops
        code_scope['exp'] = ops.exp

        # 2. code lines
        code_lines = [f'def {func_name}({", ".join(arguments)}):']

        # 2.1 dg
        # dg = g(x, t, *args)
        all_dg = [f'{var}_dg' for var in variables]
        code_lines.append(
            f'  {", ".join(all_dg)} = g({", ".join(variables + parameters)})')
        code_lines.append('  ')

        # 2.2 dW
        Tools.noise_terms(code_lines, variables)

        # 2.3 dgdW
        # ----
        # SCALAR_WIENER : dg * dW
        # VECTOR_WIENER : ops.sum(dg * dW, axis=-1)

        if wiener_type == constants.SCALAR_WIENER:
            for var in variables:
                code_lines.append(f'  {var}_dgdW = {var}_dg * {var}_dW')
        else:
            for var in variables:
                code_lines.append(
                    f'  {var}_dgdW = ops.sum({var}_dg * {var}_dW, axis=-1)')
        code_lines.append('  ')

        # 2.4 new var
        # ----
        analysis = separate_variables(f)
        variables_for_returns = analysis['variables_for_returns']
        expressions_for_returns = analysis['expressions_for_returns']
        for vi, (key, vars) in enumerate(variables_for_returns.items()):
            # separate variables
            sd_variables = []
            for v in vars:
                if len(v) > 1:
                    raise ValueError(
                        'Cannot analyze multi-assignment code line.')
                sd_variables.append(v[0])
            expressions = expressions_for_returns[key]
            var_name = variables[vi]
            diff_eq = sympy_analysis.SingleDiffEq(var_name=var_name,
                                                  variables=sd_variables,
                                                  expressions=expressions,
                                                  derivative_expr=key,
                                                  scope=code_scope,
                                                  func_name=func_name)

            f_expressions = diff_eq.get_f_expressions(
                substitute_vars=diff_eq.var_name)

            # code lines
            code_lines.extend(
                [f"  {str(expr)}" for expr in f_expressions[:-1]])

            # get the linear system using sympy
            f_res = f_expressions[-1]
            df_expr = sympy_analysis.str2sympy(f_res.code).expr.expand()
            s_df = sympy.Symbol(f"{f_res.var_name}")
            code_lines.append(
                f'  {s_df.name} = {sympy_analysis.sympy2str(df_expr)}')
            var = sympy.Symbol(diff_eq.var_name, real=True)

            # get df part
            s_linear = sympy.Symbol(f'_{diff_eq.var_name}_linear')
            s_linear_exp = sympy.Symbol(f'_{diff_eq.var_name}_linear_exp')
            s_df_part = sympy.Symbol(f'_{diff_eq.var_name}_df_part')
            if df_expr.has(var):
                # linear
                linear = sympy.collect(df_expr, var, evaluate=False)[var]
                code_lines.append(
                    f'  {s_linear.name} = {sympy_analysis.sympy2str(linear)}')
                # linear exponential
                linear_exp = sympy.exp(linear * dt)
                code_lines.append(
                    f'  {s_linear_exp.name} = {sympy_analysis.sympy2str(linear_exp)}'
                )
                # df part
                df_part = (s_linear_exp - 1) / s_linear * s_df
                code_lines.append(
                    f'  {s_df_part.name} = {sympy_analysis.sympy2str(df_part)}'
                )

            else:
                # linear exponential
                code_lines.append(f'  {s_linear_exp.name} = sqrt({dt})')
                # df part
                code_lines.append(
                    f'  {s_df_part.name} = {sympy_analysis.sympy2str(dt * s_df)}'
                )

            # update expression
            update = var + s_df_part

            # The actual update step
            code_lines.append(
                f'  {diff_eq.var_name}_new = {sympy_analysis.sympy2str(update)} + {var_name}_dgdW'
            )
            code_lines.append('')

        # returns
        new_vars = [f'{var}_new' for var in variables]
        code_lines.append(f'  return {", ".join(new_vars)}')

        # return and compile
        return common.compile_and_assign_attrs(code_lines=code_lines,
                                               code_scope=code_scope,
                                               show_code=show_code,
                                               variables=variables,
                                               parameters=parameters,
                                               func_name=func_name,
                                               sde_type=sde_type,
                                               var_type=var_type,
                                               wiener_type=wiener_type,
                                               dt=dt)