Beispiel #1
0
 def visit_Return(self, node):
     if isinstance(node.value, ast.Name):
         self.returns.append(node.value.id)
     elif isinstance(node.value, (ast.Tuple, ast.List)):
         for var in node.value.elts:
             if not isinstance(var, ast.Name):
                 raise errors.DiffEqError(self.return_error_msg)
             self.returns.append(var.id)
     else:
         raise errors.DiffEqError(self.return_error_msg)
     return node
Beispiel #2
0
 def visit_Return(self, node):
     if isinstance(node.value, ast.Name):
         self.returns.append(node.value.id)
     elif isinstance(node.value, (ast.Tuple, ast.List)):
         for var in node.value.elts:
             if not (var, ast.Name):
                 raise errors.DiffEqError(f'Unknown return type: {node}')
             self.returns.append(var.id)
     else:
         raise errors.DiffEqError(f'Unknown return type: {node}')
     return node
Beispiel #3
0
def _get_args(f):
    # 1. get the function arguments
    original_args = []
    args = []
    kwargs = []

    for name, par in inspect.signature(f).parameters.items():
        if par.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD:
            args.append(par.name)
        elif par.kind is inspect.Parameter.VAR_POSITIONAL:
            args.append(par.name)
        elif par.kind is inspect.Parameter.KEYWORD_ONLY:
            args.append(par.name)
        elif par.kind is inspect.Parameter.POSITIONAL_ONLY:
            raise errors.BrainPyError(
                'Don not support positional only parameters, e.g., /')
        elif par.kind is inspect.Parameter.VAR_KEYWORD:
            kwargs.append(par.name)
        else:
            raise errors.BrainPyError(f'Unknown argument type: {par.kind}')

        original_args.append(str(par))

    # 2. analyze the function arguments
    #   2.1 class keywords
    class_kw = []
    if original_args[0] in CLASS_KEYWORDS:
        class_kw.append(original_args[0])
        original_args = original_args[1:]
        args = args[1:]
    for a in original_args:
        if a.split('=')[0].strip() in CLASS_KEYWORDS:
            raise errors.DiffEqError(f'Class keywords "{a}" must be defined '
                                     f'as the first argument.')
    return class_kw, args, kwargs, original_args
Beispiel #4
0
def _get_args(f):
    """Get the function arguments"""
    args = []
    kwargs = {}
    for name, par in inspect.signature(f).parameters.items():
        if par.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD:
            if par.default is inspect._empty:
                args.append(par.name)
            else:
                kwargs[par.name] = par.default
        elif par.kind is inspect.Parameter.VAR_POSITIONAL:
            raise errors.DiffEqError(
                f'{JointEq.__name__} does not support VAR_POSITIONAL parameters '
                f'*{par.name} (error in {f}).')
        elif par.kind is inspect.Parameter.KEYWORD_ONLY:
            raise errors.DiffEqError(
                f'{JointEq.__name__} does not support KEYWORD_ONLY parameters, '
                f'e.g., *  (error in {f}).')
        elif par.kind is inspect.Parameter.POSITIONAL_ONLY:
            raise errors.DiffEqError(
                f'{JointEq.__name__} does not support POSITIONAL_ONLY parameters, '
                'e.g., /  (error in {f}).')
        elif par.kind is inspect.Parameter.VAR_KEYWORD:
            raise errors.DiffEqError(
                f'{JointEq.__name__} does not support VAR_KEYWORD '
                f'arguments **{par.name} (error in {f}).')
        else:
            raise errors.DiffEqError(f'Unknown argument type: {par.kind}')

    # variables
    vars = []
    for a in args:
        if a == 't':
            break
        vars.append(a)
    else:
        raise ValueError('Do not find time variable "t".')

    return vars, args, kwargs
Beispiel #5
0
def get_args(f):
    """Get the function arguments.

    Parameters
    ----------
    f : callable
        The function.

    Returns
    -------
    args : tuple
        The variable names, the other arguments, and the original args.
    """

    # 1. get the function arguments
    parameters = inspect.signature(f).parameters

    arguments = []
    for name, par in parameters.items():
        if par.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD:
            arguments.append(par.name)

        elif par.kind is inspect.Parameter.KEYWORD_ONLY:
            arguments.append(par.name)

        elif par.kind is inspect.Parameter.VAR_POSITIONAL:
            raise errors.ModelDefError(
                'Step function do not support positional parameters, e.g., *args'
            )
        elif par.kind is inspect.Parameter.POSITIONAL_ONLY:
            raise errors.ModelDefError(
                'Step function do not support positional only parameters, e.g., /'
            )
        elif par.kind is inspect.Parameter.VAR_KEYWORD:
            raise errors.ModelDefError(
                f'Step function do not support dict of keyword arguments: {str(par)}'
            )
        else:
            raise errors.ModelDefError(f'Unknown argument type: {par.kind}')

    # 2. check the function arguments
    class_kw = None
    if len(arguments) > 0 and arguments[0] in backend.CLASS_KEYWORDS:
        class_kw = arguments[0]
        arguments = arguments[1:]
    for a in arguments:
        if a in backend.CLASS_KEYWORDS:
            raise errors.DiffEqError(f'Class keywords "{a}" must be defined '
                                     f'as the first argument.')
    return class_kw, arguments
Beispiel #6
0
    def solve(self, diff_eq, var):
        if analysis_by_sympy is None or sympy is None:
            raise errors.PackageMissingError(
                f'Package "sympy" must be installed when the users '
                f'want to utilize {ExponentialEuler.__name__}. ')

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

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

        # get the linear system using sympy
        f_res = f_expressions[-1]
        if len(f_res.code) > 500:
            raise errors.DiffEqError(
                f'Too complex differential equation:\n\n'
                f'{f_res.code}\n\n'
                f'SymPy cannot analyze. Please use {ExpEulerAuto} to '
                f'make Exponential Euler integration due to it is capable of '
                f'performing automatic differentiation.')
        df_expr = analysis_by_sympy.str2sympy(f_res.code).expr.expand()
        s_df = sympy.Symbol(f"{f_res.var_name}")
        self.code_lines.append(
            f'  {s_df.name} = {analysis_by_sympy.sympy2str(df_expr)}')

        # 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.diff(df_expr, var, evaluate=True)
            # TODO: linear has unknown symbol
            self.code_lines.append(
                f'  {s_linear.name} = {analysis_by_sympy.sympy2str(linear)}')
            # linear exponential
            self.code_lines.append(
                f'  {s_linear_exp.name} = math.exp({s_linear.name} * {C.DT})')
            # df part
            df_part = (s_linear_exp - 1) / s_linear * s_df
            self.code_lines.append(
                f'  {s_df_part.name} = {analysis_by_sympy.sympy2str(df_part)}')
        else:
            # df part
            self.code_lines.append(
                f'  {s_df_part.name} = {s_df.name} * {C.DT}')
        return s_df_part
Beispiel #7
0
 def call(t, *vars, **args_and_kwargs):
     params = dict(t=t)
     for var in f_vars:
         params[var] = vars[all_vars.index(var)]
     for par in f_args[len(f_vars) + 1:]:
         if par in args_and_kwargs:
             params[par] = args_and_kwargs[par]
         else:
             if par not in all_vars:
                 raise errors.DiffEqError(
                     f'Missing {par} during the functional call of {f}.')
             params[par] = vars[all_vars.index(par)]
     for par, value in f_kwargs.items():
         if par in args_and_kwargs:
             params[par] = args_and_kwargs[par]
     return f(**params)
Beispiel #8
0
    def _build_integrator(self, eq):
        if isinstance(eq, joint_eq.JointEq):
            results = []
            for sub_eq in eq.eqs:
                results.extend(self._build_integrator(sub_eq))
            return results

        else:
            vars, pars, _ = utils.get_args(eq)

            # checking
            if len(vars) != 1:
                raise errors.DiffEqError(
                    f'{self.__class__} only supports numerical integration '
                    f'for one variable once, while we got {vars} in {eq}. '
                    f'Please split your multiple variables into multiple '
                    f'derivative functions.')

            # gradient function
            value_and_grad = math.vector_grad(eq,
                                              argnums=0,
                                              dyn_vars=self.dyn_var,
                                              return_value=True)

            # integration function
            def integral(*args, **kwargs):
                assert len(args) > 0
                dt = kwargs.pop('dt', math.get_dt())
                linear, derivative = value_and_grad(*args, **kwargs)
                phi = math.where(linear == 0., math.ones_like(linear),
                                 (math.exp(dt * linear) - 1) / (dt * linear))
                return args[0] + dt * phi * derivative

            return [
                (integral, vars, pars),
            ]
Beispiel #9
0
 def visit_Delete(self, node):
     raise errors.DiffEqError(f'Currently, {self.__class__.__name__} do not support to '
                              f'analyze "del" operation in differential equation.')
Beispiel #10
0
 def visit_Raise(self, node):
     raise errors.DiffEqError(f'Currently, {self.__class__.__name__} do not support to '
                              f'analyze "raise" statement in differential equation.')
Beispiel #11
0
 def visit_With(self, node):
     raise errors.DiffEqError(f'Currently, {self.__class__.__name__} do not support to '
                              f'analyze "with" block in differential equation.')
Beispiel #12
0
 def visit_Try(self, node):
     raise errors.DiffEqError(f'Currently, {self.__class__.__name__} do not support to '
                              f'analyze "try" handler in differential equation.')
Beispiel #13
0
 def visit_While(self, node):
     raise errors.DiffEqError(f'Currently, {self.__class__.__name__} do not support to '
                              f'analyze "while" loops in differential equation.')
Beispiel #14
0
def get_args(f):
    """Get the function arguments.

    >>> def f1(a, b, t, *args, c=1): pass
    >>> get_args(f1)
    (['a', 'b'], ['t', '*args', 'c'], ['a', 'b', 't', '*args', 'c=1'])

    >>> def f2(a, b, *args, c=1, **kwargs): pass
    >>> get_args(f2)
    ValueError: Don not support dict of keyword arguments: **kwargs

    >>> def f3(a, b, t, c=1, d=2): pass
    >>> get_args(f4)
    (['a', 'b'], ['t', 'c', 'd'], ['a', 'b', 't', 'c=1', 'd=2'])

    >>> def f4(a, b, t, *args): pass
    >>> get_args(f4)
    (['a', 'b'], ['t', '*args'], ['a', 'b', 't', '*args'])

    >>> scope = {}
    >>> exec(compile('def f5(a, b, t, *args): pass', '', 'exec'), scope)
    >>> get_args(scope['f5'])
    (['a', 'b'], ['t', '*args'], ['a', 'b', 't', '*args'])

    Parameters
    ----------
    f : callable
        The function.

    Returns
    -------
    args : tuple
        The variable names, the other arguments, and the original args.
    """

    # 1. get the function arguments
    reduced_args = []
    original_args = []

    for name, par in inspect.signature(f).parameters.items():
        if par.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD:
            reduced_args.append(par.name)

        elif par.kind is inspect.Parameter.VAR_POSITIONAL:
            reduced_args.append(f'*{par.name}')

        elif par.kind is inspect.Parameter.KEYWORD_ONLY:
            reduced_args.append(par.name)

        elif par.kind is inspect.Parameter.POSITIONAL_ONLY:
            raise errors.DiffEqError(
                'Don not support positional only parameters, e.g., /')
        elif par.kind is inspect.Parameter.VAR_KEYWORD:
            raise errors.DiffEqError(
                f'Don not support dict of keyword arguments: {str(par)}')
        else:
            raise errors.DiffEqError(f'Unknown argument type: {par.kind}')

        original_args.append(str(par))

    # 2. analyze the function arguments
    #   2.1 class keywords
    class_kw = []
    if reduced_args[0] in backend.CLASS_KEYWORDS:
        class_kw.append(reduced_args[0])
        reduced_args = reduced_args[1:]
    for a in reduced_args:
        if a in backend.CLASS_KEYWORDS:
            raise errors.DiffEqError(f'Class keywords "{a}" must be defined '
                                     f'as the first argument.')
    #  2.2 variable names
    var_names = []
    for a in reduced_args:
        if a == 't':
            break
        var_names.append(a)
    else:
        raise ValueError('Do not find time variable "t".')
    other_args = reduced_args[len(var_names):]
    return class_kw, var_names, other_args, original_args
Beispiel #15
0
 def visit_AnnAssign(self, node):
     raise errors.DiffEqError(f'Currently, {self.__class__.__name__} do not support an '
                              f'assignment with a type annotation.')
Beispiel #16
0
    def __init__(self, eqs):
        # equations
        if not isinstance(eqs, (tuple, list)):
            raise errors.DiffEqError(f'"eqs" only supports list/tuple of '
                                     f'derivative functions, but got {eqs}.')
        for eq in eqs:
            if not callable(eq):
                raise errors.DiffEqError(
                    f'"eqs" only supports list/tuple of '
                    f'derivative functions, but got {eq}.')

        # variables in equations
        self.vars_in_eqs = []
        vars_in_eqs = []
        for eq in eqs:
            vars, _, _ = _get_args(eq)
            for var in vars:
                if var in vars_in_eqs:
                    raise errors.DiffEqError(
                        f'Variable "{var}" has been used, however we got a same '
                        f'variable name in {eq}. Please change another name.')
            vars_in_eqs.extend(vars)
            self.vars_in_eqs.append(vars)

        # arguments in equations
        self.args_in_eqs = []
        all_arg_pars = []
        all_kwarg_pars = dict()
        for eq in eqs:
            vars, args, kwargs = _get_args(eq)
            self.args_in_eqs.append(args + list(kwargs.keys()))
            for par in args[len(vars) + 1:]:
                if (par not in vars_in_eqs) and (par not in all_arg_pars) and (
                        par not in all_kwarg_pars):
                    all_arg_pars.append(par)
            for key, value in kwargs.values():
                if key in all_kwarg_pars and value != all_kwarg_pars[key]:
                    raise errors.DiffEqError(
                        f'We got two different default value of "{key}": '
                        f'{all_kwarg_pars[key]} != {value}')
                elif (key not in vars_in_eqs) and (key not in all_arg_pars):
                    all_kwarg_pars[key] = value
                else:
                    raise errors.DiffEqError

        # # variable names provided
        # if not isinstance(variables, (tuple, list)):
        #   raise errors.DiffEqError(f'"variables" must be a list/tuple of str, but we got {variables}')
        # for v in variables:
        #   if not isinstance(v, str):
        #     raise errors.DiffEqError(f'"variables" must be a list/tuple of str, but we got {v} in "variables"')
        # if len(vars_in_eqs) != len(variables):
        #   raise errors.DiffEqError(f'We detect {len(vars_in_eqs)} variables "{vars_in_eqs}" '
        #                            f'in the provided equations. However, the used provided '
        #                            f'"variables" have {len(variables)} variables '
        #                            f'"{variables}".')
        # if len(set(vars_in_eqs) - set(variables)) != 0:
        #   raise errors.DiffEqError(f'We detect there are variable "{vars_in_eqs}" in the provided '
        #                            f'equations, while the user provided variables "{variables}" '
        #                            f'is not the same.')

        # finally
        self.eqs = eqs
        # self.variables = variables
        self.arg_keys = vars_in_eqs + ['t'] + all_arg_pars
        self.kwarg_keys = list(all_kwarg_pars.keys())
        self.kwargs = all_kwarg_pars
        parameters = [
            inspect.Parameter(vp, inspect.Parameter.POSITIONAL_OR_KEYWORD)
            for vp in self.arg_keys
        ]
        parameters.extend([
            inspect.Parameter(k,
                              kind=inspect.Parameter.POSITIONAL_OR_KEYWORD,
                              default=all_kwarg_pars[k])
            for k in self.kwarg_keys
        ])
        signature = inspect.signature(eqs[0])
        self.__signature__ = signature.replace(parameters=parameters)
        self.__name__ = 'joint_eq'
Beispiel #17
0
def get_args(f):
  """Get the function arguments.

  >>> def f1(a, b, t, *args, c=1): pass
  >>> get_args(f1)
  (['a', 'b'], ['t', '*args', 'c'], ['a', 'b', 't', '*args', 'c=1'])

  >>> def f2(a, b, *args, c=1, **kwargs): pass
  >>> get_args(f2)
  ValueError: Do not support dict of keyword arguments: **kwargs

  >>> def f3(a, b, t, c=1, d=2): pass
  >>> get_args(f4)
  (['a', 'b'], ['t', 'c', 'd'], ['a', 'b', 't', 'c=1', 'd=2'])

  >>> def f4(a, b, t, *args): pass
  >>> get_args(f4)
  (['a', 'b'], ['t', '*args'], ['a', 'b', 't', '*args'])

  >>> scope = {}
  >>> exec(compile('def f5(a, b, t, *args): pass', '', 'exec'), scope)
  >>> get_args(scope['f5'])
  (['a', 'b'], ['t', '*args'], ['a', 'b', 't', '*args'])

  Parameters
  ----------
  f : callable
      The function.

  Returns
  -------
  args : tuple
      The variable names, the other arguments, and the original args.
  """

  # get the function arguments
  reduced_args = []
  args = []

  for name, par in inspect.signature(f).parameters.items():
    if par.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD:
      reduced_args.append(par.name)

    elif par.kind is inspect.Parameter.VAR_POSITIONAL:
      reduced_args.append(f'*{par.name}')

    elif par.kind is inspect.Parameter.KEYWORD_ONLY:
      raise errors.DiffEqError(f'In BrainPy, numerical integrators do not support KEYWORD_ONLY '
                               f'parameters, e.g., * (error in {f}).')
    elif par.kind is inspect.Parameter.POSITIONAL_ONLY:
      raise errors.DiffEqError(f'In BrainPy, numerical integrators do not support POSITIONAL_ONLY '
                               f'parameters, e.g., / (error in {f}).')
    elif par.kind is inspect.Parameter.VAR_KEYWORD:  # TODO
      raise errors.DiffEqError(f'In BrainPy, numerical integrators do not support VAR_KEYWORD '
                               f'arguments: {str(par)} (error in {f}).')
    else:
      raise errors.DiffEqError(f'Unknown argument type: {par.kind} (error in {f}).')

    args.append(str(par))

  #  variable names
  vars = []
  for a in reduced_args:
    if a == 't':
      break
    vars.append(a)
  else:
    raise ValueError('Do not find time variable "t".')
  pars = reduced_args[len(vars):]
  return vars, pars, args
Beispiel #18
0
 def visit_IfExp(self, node):
     raise errors.DiffEqError(f'Currently, {self.__class__.__name__} do not support to '
                              f'analyze "if-else" conditions in differential equation.')
Beispiel #19
0
    def build(self):
        if analysis_by_sympy is None or sympy is None:
            raise errors.PackageMissingError(
                f'Package "sympy" must be installed when the users '
                f'want to utilize {ExponentialEuler.__name__}. ')

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

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

        analysis = separate_variables(self.f)
        variables_for_returns = analysis['variables_for_returns']
        expressions_for_returns = analysis['expressions_for_returns']
        for vi, (key, all_var) in enumerate(variables_for_returns.items()):
            # separate variables
            sd_variables = []
            for v in all_var:
                if len(v) > 1:
                    raise ValueError(
                        f'Cannot analyze multi-assignment code line: {v}.')
                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)
            var = sympy.Symbol(diff_eq.var_name, real=True)
            try:
                s_df_part = tools.timeout(self.timeout)(self.solve)(diff_eq,
                                                                    var)
            except KeyboardInterrupt:
                raise errors.DiffEqError(
                    f'{self.__class__} solve {self.f} failed, because '
                    f'symbolic differentiation of SymPy timeout due to {self.timeout} s limit. '
                    f'Instead, you can use {ExpEulerAuto} to make Exponential Euler '
                    f'integration due to due to it is capable of '
                    f'performing automatic differentiation.')
            # update expression
            update = var + s_df_part

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

        self.code_lines.append(
            f'  return {", ".join([f"{v}_new" for v in self.variables])}')
        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.f, '__self__'):
            host = self.f.__self__
            self.integral = self.integral.__get__(host, host.__class__)