Esempio n. 1
0
    def __init__(self, size, monitors=None, name=None, show_code=False):
        # name
        # -----
        if name is None:
            name = ''
        else:
            name = '_' + name
        global _NeuGroup_NO
        _NeuGroup_NO += 1
        name = f'NG{_NeuGroup_NO}{name}'

        # size
        # ----
        if isinstance(size, (list, tuple)):
            if len(size) <= 0:
                raise errors.ModelDefError(
                    'size must be int, or a tuple/list of int.')
            if not isinstance(size[0], int):
                raise errors.ModelDefError(
                    'size must be int, or a tuple/list of int.')
            size = tuple(size)
        elif isinstance(size, int):
            size = (size, )
        else:
            raise errors.ModelDefError(
                'size must be int, or a tuple/list of int.')
        self.size = size

        # initialize
        # ----------
        super(NeuGroup, self).__init__(steps={'update': self.update},
                                       monitors=monitors,
                                       name=name,
                                       show_code=show_code)
Esempio n. 2
0
    def __init__(self, steps, monitors=None, name=None, host=None, show_code=False):
        # host of the data
        # ----------------
        if host is None:
            host = self
        self.host = host

        # model
        # -----
        if callable(steps):
            self.steps = OrderedDict([(steps.__name__, steps)])
        elif isinstance(steps, (list, tuple)) and callable(steps[0]):
            self.steps = OrderedDict([(step.__name__, step) for step in steps])
        elif isinstance(steps, dict):
            self.steps = steps
        else:
            raise errors.ModelDefError(f'Unknown model type: {type(steps)}. Currently, BrainPy '
                                       f'only supports: function, list/tuple/dict of functions.')

        # name
        # ----
        if name is None:
            global _DynamicSystem_NO
            name = f'DS{_DynamicSystem_NO}'
            _DynamicSystem_NO += 1
        if not name.isidentifier():
            raise errors.ModelUseError(f'"{name}" isn\'t a valid identifier according to Python '
                                       f'language definition. Please choose another name.')
        self.name = name

        # monitors
        # ---------
        if monitors is None:
            monitors = []
        self.mon = Monitor(target=self, variables=monitors)

        # runner
        # -------
        self.driver = backend.get_node_driver()(pop=self)

        # run function
        # ------------
        self.run_func = None

        # others
        # ---
        self.show_code = show_code
        if self.target_backend is None:
            raise errors.ModelDefError('Must define "target_backend".')
        if isinstance(self.target_backend, str):
            self._target_backend = (self.target_backend, )
        elif isinstance(self.target_backend, (tuple, list)):
            if not isinstance(self.target_backend[0], str):
                raise errors.ModelDefError('"target_backend" must be a list/tuple of string.')
            self._target_backend = tuple(self.target_backend)
        else:
            raise errors.ModelDefError(f'Unknown setting of "target_backend": {self.target_backend}')
Esempio n. 3
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
Esempio n. 4
0
    def build(self, inputs, inputs_is_formatted=False, return_code=True, mon_length=0, show_code=False):
        """Build the object for running.

        Parameters
        ----------
        inputs : list, tuple, optional
            The object inputs.
        inputs_is_formatted : bool
            Whether the "inputs" is formatted.
        return_code : bool
            Whether return the formatted codes.
        mon_length : int
            The monitor length.

        Returns
        -------
        calls : list, tuple
            The code lines to call step functions.
        """
        if (self._target_backend[0] != 'general') and (backend.get_backend_name() not in self._target_backend):
            raise errors.ModelDefError(f'The model {self.name} is target to run on {self._target_backend}, '
                                       f'but currently the selected backend is {backend.get_backend_name()}')
        if not inputs_is_formatted:
            inputs = utils.format_pop_level_inputs(inputs, self, mon_length)
        return self.driver.build(formatted_inputs=inputs,
                                 mon_length=mon_length,
                                 return_code=return_code,
                                 show_code=(self.show_code or show_code))
Esempio n. 5
0
 def register_constant_delay(self, key, size, delay_time):
     if not hasattr(self, 'constant_delays'):
         self.constant_delays = {}
     if key in self.constant_delays:
         raise errors.ModelDefError(
             f'"{key}" has been registered as an constant delay.')
     self.constant_delays[key] = delays.ConstantDelay(size, delay_time)
     return self.constant_delays[key]
Esempio n. 6
0
    def __init__(self, target, variables):
        self.target = target
        for mon_var in variables:
            if not hasattr(target, mon_var):
                raise errors.ModelDefError(
                    f"Item {mon_var} isn't defined in model {target}, "
                    f"so it can not be monitored.")

        item_names = []
        mon_indices = []
        item_content = {}
        if variables is not None:
            if isinstance(variables, (list, tuple)):
                for mon_var in variables:
                    if isinstance(mon_var, str):
                        var_data = getattr(target, mon_var)
                        mon_key = mon_var
                        mon_idx = None
                        mon_shape = ops.shape(var_data)
                    elif isinstance(mon_var, (tuple, list)):
                        mon_key = mon_var[0]
                        var_data = getattr(target, mon_key)
                        mon_idx = mon_var[1]
                        mon_shape = ops.shape(mon_idx)  # TODO: matrix index
                    else:
                        raise errors.ModelUseError(
                            f'Unknown monitor item: {str(mon_var)}')
                    item_names.append(mon_key)
                    mon_indices.append(mon_idx)
                    dtype = var_data.dtype if hasattr(var_data,
                                                      'dtype') else None
                    item_content[mon_var] = ops.zeros((1, ) + mon_shape,
                                                      dtype=dtype)
            elif isinstance(variables, dict):
                for k, v in variables.items():
                    item_names.append(k)
                    mon_indices.append(v)
                    if v is None:
                        shape = ops.shape(getattr(target, k))
                    else:
                        shape = ops.shape(v)
                    val_data = getattr(target, k)
                    dtype = val_data.dtype if hasattr(val_data,
                                                      'dtype') else None
                    item_content[k] = ops.zeros((1, ) + shape, dtype=dtype)
            else:
                raise errors.ModelUseError(
                    f'Unknown monitors type: {type(variables)}')

        self.ts = None
        self.item_names = item_names
        self.item_indices = mon_indices
        self.item_contents = item_content
        self.num_item = len(item_content)
Esempio n. 7
0
    def get_steps_func(self, show_code=False):
        for func_name, step in self.steps.items():
            class_args, arguments = utils.get_args(step)
            host_name = self.host.name

            calls = []
            for arg in arguments:
                if hasattr(self.host, arg):
                    calls.append(f'{host_name}.{arg}')
                elif arg in backend.SYSTEM_KEYWORDS:
                    calls.append(arg)
                else:
                    raise errors.ModelDefError(
                        f'Step function "{func_name}" of {self.host} '
                        f'define an unknown argument "{arg}" which is not '
                        f'an attribute of {self.host} nor the system keywords '
                        f'{backend.SYSTEM_KEYWORDS}.')
            self.formatted_funcs[func_name] = {
                'func': step,
                'scope': {
                    host_name: self.host
                },
                'call': [f'{host_name}.{func_name}({", ".join(calls)})']
            }
Esempio n. 8
0
def transform_integrals_to_model(integrals):
    from brainpy.integrators import sympy_analysis

    if callable(integrals):
        integrals = [integrals]

    all_scope = dict()
    all_variables = set()
    all_parameters = set()
    analyzers = []
    for integral in integrals:
        # integral function
        if Dispatcher is not None and isinstance(integral, Dispatcher):
            integral = integral.py_func
        else:
            integral = integral

        # original function
        f = integral.origin_f
        if Dispatcher is not None and isinstance(f, Dispatcher):
            f = f.py_func
        func_name = f.__name__

        # code scope
        closure_vars = inspect.getclosurevars(f)
        code_scope = dict(closure_vars.nonlocals)
        code_scope.update(dict(closure_vars.globals))

        # separate variables
        analysis = ast_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()):
            variables = []
            for v in vars:
                if len(v) > 1:
                    raise ValueError(
                        'Cannot analyze multi-assignment code line.')
                variables.append(v[0])
            expressions = expressions_for_returns[key]
            var_name = integral.variables[vi]
            DE = sympy_analysis.SingleDiffEq(var_name=var_name,
                                             variables=variables,
                                             expressions=expressions,
                                             derivative_expr=key,
                                             scope=code_scope,
                                             func_name=func_name)
            analyzers.append(DE)

        # others
        for var in integral.variables:
            if var in all_variables:
                raise errors.ModelDefError(
                    f'Variable {var} has been defined before. Cannot group '
                    f'this integral as a dynamic system.')
            all_variables.add(var)
        all_parameters.update(integral.parameters)
        all_scope.update(code_scope)

    return DynamicModel(integrals=integrals,
                        analyzers=analyzers,
                        variables=list(all_variables),
                        parameters=list(all_parameters),
                        scopes=all_scope)
Esempio n. 9
0
def class2func(cls_func, host, func_name=None, show_code=False):
    """Transform the function in a class into the ordinary function which is
    compatible with the Numba JIT compilation.

    Parameters
    ----------
    cls_func : function
        The function of the instantiated class.
    func_name : str
        The function name. If not given, it will get the function by `cls_func.__name__`.
    show_code : bool
        Whether show the code.

    Returns
    -------
    new_func : function
        The transformed function.
    """
    class_arg, arguments = utils.get_args(cls_func)
    func_name = cls_func.__name__ if func_name is None else func_name
    host_name = host.name

    # arguments 1
    calls = []
    for arg in arguments:
        if hasattr(host, arg):
            calls.append(f'{host_name}.{arg}')
        elif arg in backend.SYSTEM_KEYWORDS:
            calls.append(arg)
        else:
            raise errors.ModelDefError(
                f'Step function "{func_name}" of {host} '
                f'define an unknown argument "{arg}" which is not '
                f'an attribute of {host} nor the system keywords '
                f'{backend.SYSTEM_KEYWORDS}.')

    # analysis
    analyzed_results = analyze_step_func(host=host, f=cls_func)
    delay_call = analyzed_results['delay_call']
    # code_string = analyzed_results['code_string']
    main_code = analyzed_results['code_string']
    code_scope = analyzed_results['code_scope']
    self_data_in_right = analyzed_results['self_data_in_right']
    self_data_without_index_in_left = analyzed_results[
        'self_data_without_index_in_left']
    self_data_with_index_in_left = analyzed_results[
        'self_data_with_index_in_left']
    # main_code = get_func_body_code(code_string)
    num_indent = get_num_indent(main_code)
    data_need_pass = sorted(
        list(set(self_data_in_right + self_data_with_index_in_left)))
    data_need_return = self_data_without_index_in_left

    # check delay
    replaces_early = {}
    replaces_later = {}
    if len(delay_call) > 0:
        for delay_ in delay_call.values():
            # delay_ = dict(type=calls[-1],
            #               args=args,
            #               keywords=keywords,
            #               kws_append=kws_append,
            #               func=func,
            #               org_call=org_call,
            #               rep_call=rep_call,
            #               data_need_pass=data_need_pass)
            if delay_['type'] == 'push':
                if len(delay_['args'] + delay_['keywords']) == 2:
                    func = numba.njit(delay.push_type2)
                elif len(delay_['args'] + delay_['keywords']) == 1:
                    func = numba.njit(delay.push_type1)
                else:
                    raise ValueError(f'Unknown delay push. {delay_}')
            else:
                if len(delay_['args'] + delay_['keywords']) == 1:
                    func = numba.njit(delay.pull_type1)
                elif len(delay_['args'] + delay_['keywords']) == 0:
                    func = numba.njit(delay.pull_type0)
                else:
                    raise ValueError(f'Unknown delay pull. {delay_}')
            delay_call_name = delay_['func']
            data_need_pass.remove(delay_call_name)
            data_need_pass.extend(delay_['data_need_pass'])
            replaces_early[delay_['org_call']] = delay_['rep_call']
            replaces_later[delay_call_name] = delay_call_name.replace('.', '_')
            code_scope[delay_call_name.replace('.', '_')] = func
    for target, dest in replaces_early.items():
        main_code = main_code.replace(target, dest)
    # main_code = tools.word_replace(main_code, replaces_early)

    # arguments 2: data need pass
    new_args = arguments + []
    for data in sorted(set(data_need_pass)):
        splits = data.split('.')
        replaces_later[data] = data.replace('.', '_')
        obj = host
        for attr in splits[1:]:
            obj = getattr(obj, attr)
        if callable(obj):
            code_scope[data.replace('.', '_')] = obj
            continue
        new_args.append(data.replace('.', '_'))
        calls.append('.'.join([host_name] + splits[1:]))

    # data need return
    assigns = []
    returns = []
    for data in data_need_return:
        splits = data.split('.')
        assigns.append('.'.join([host_name] + splits[1:]))
        returns.append(data.replace('.', '_'))
        replaces_later[data] = data.replace('.', '_')

    # code scope
    code_scope[host_name] = host

    # codes
    header = f'def new_{func_name}({", ".join(new_args)}):\n'
    main_code = header + tools.indent(main_code, spaces_per_tab=2)
    if len(returns):
        main_code += f'\n{" " * num_indent + "  "}return {", ".join(returns)}'
    main_code = tools.word_replace(main_code, replaces_later)
    if show_code:
        print(main_code)
        print(code_scope)
        print()

    # recompile
    exec(compile(main_code, '', 'exec'), code_scope)
    func = code_scope[f'new_{func_name}']
    func = numba.jit(**NUMBA_PROFILE)(func)
    return func, calls, assigns