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)
def ij2mat(i, j, num_pre=None, num_post=None): """Convert i-j connection to matrix connection. Parameters ---------- i : list, np.ndarray Pre-synaptic neuron index. j : list, np.ndarray Post-synaptic neuron index. num_pre : int The number of the pre-synaptic neurons. num_post : int The number of the post-synaptic neurons. Returns ------- conn_mat : np.ndarray A 2D ndarray connectivity matrix. """ if len(i) != len(j): raise errors.ModelUseError('"i" and "j" must be the equal length.') if num_pre is None: print( 'WARNING: "num_pre" is not provided, the result may not be accurate.' ) num_pre = i.max() if num_post is None: print( 'WARNING: "num_post" is not provided, the result may not be accurate.' ) num_post = j.max() conn_mat = ops.zeros((num_pre, num_post)) conn_mat[i, j] = 1. return conn_mat
def ramp_input(c_start, c_end, duration, t_start=0, t_end=None, dt=None): """Get the gradually changed input current. Parameters ---------- c_start : float The minimum (or maximum) current size. c_end : float The maximum (or minimum) current size. duration : int, float The total duration. t_start : float The ramped current start time-point. t_end : float The ramped current end time-point. Default is the None. dt : float, int, optional The numerical precision. Returns ------- current_and_duration : tuple (The formatted current, total duration) """ dt = backend.get_dt() if dt is None else dt t_end = duration if t_end is None else t_end current = ops.zeros(int(math.ceil(duration / dt))) p1 = int(math.ceil(t_start / dt)) p2 = int(math.ceil(t_end / dt)) current[p1: p2] = ops.as_tensor(np.linspace(c_start, c_end, p2 - p1)) return current
def __init__(self, v0, delay_len, before_t0=0., t0=0., dt=None): # size self.size = ops.shape(v0) # delay_len self.delay_len = delay_len self.dt = backend.get_dt() if dt is None else dt self.num_delay = int(math.ceil(delay_len / self.dt)) # other variables self._delay_in = self.num_delay - 1 self._delay_out = 0 self.current_time = t0 # before_t0 self.before_t0 = before_t0 # delay data self.data = ops.zeros((self.num_delay + 1,) + self.size) if callable(before_t0): for i in range(self.num_delay): self.data[i] = before_t0(t0 + (i - self.num_delay) * self.dt) else: self.data[:-1] = before_t0 self.data[-1] = v0
def __init__(self, size, delay_time): if isinstance(size, int): size = (size, ) self.size = tuple(size) self.delay_time = delay_time if isinstance(delay_time, (int, float)): self.uniform_delay = True self.delay_num_step = int(math.ceil( delay_time / backend.get_dt())) + 1 self.delay_data = ops.zeros((self.delay_num_step, ) + self.size) else: if not len(self.size) == 1: raise NotImplementedError( f'Currently, BrainPy only supports 1D heterogeneous delays, while does ' f'not implement the heterogeneous delay with {len(self.size)}-dimensions.' ) self.num = size2len(size) if isinstance(delay_time, type(ops.as_tensor([1]))): assert ops.shape(delay_time) == self.size elif callable(delay_time): delay_time2 = ops.zeros(size) for i in range(size[0]): delay_time2[i] = delay_time() delay_time = delay_time2 else: raise NotImplementedError( f'Currently, BrainPy does not support delay type ' f'of {type(delay_time)}: {delay_time}') self.uniform_delay = False delay = delay_time / backend.get_dt() dint = ops.as_tensor(delay_time / backend.get_dt(), dtype=int) ddiff = (delay - dint) >= 0.5 self.delay_num_step = ops.as_tensor(delay + ddiff, dtype=int) + 1 self.delay_data = ops.zeros((max(self.delay_num_step), ) + size) self.diag = ops.arange(self.num) self.delay_in_idx = self.delay_num_step - 1 if self.uniform_delay: self.delay_out_idx = 0 else: self.delay_out_idx = ops.zeros(self.num, dtype=int) self.name = None
def __init__(self, size, freqs, **kwargs): self.dt = backend.get_dt() / 1000. self.freqs = freqs self.size = (size,) if isinstance(size, int) else tuple(size) self.num = size2len(size) self.spike = ops.zeros(self.num, dtype=bool) self.t_last_spike = -1e7 * ops.ones(self.num) if backend.get_backend_name() == 'numba-cuda': super(PoissonInput, self).__init__(steps={'update': self.numba_cuda_update}, **kwargs) else: super(PoissonInput, self).__init__(steps={'update': self.non_numba_cuda_update}, **kwargs)
def period_input(values, durations, dt=None, return_length=False): """Format an input current with different periods. For example: If you want to get an input where the size is 0 bwteen 0-100 ms, and the size is 1. between 100-200 ms. >>> import numpy as np >>> period_input(values=[0, 1], >>> durations=[100, 100]) Parameters ---------- values : list, np.ndarray The current values for each period duration. durations : list, np.ndarray The duration for each period. dt : float Default is None. return_length : bool Return the final duration length. Returns ------- current_and_duration : tuple (The formatted current, total duration) """ assert len(durations) == len(values), f'"values" and "durations" must be the same length, while ' \ f'we got {len(values)} != {len(durations)}.' dt = backend.get_dt() if dt is None else dt # get input current shape, and duration I_duration = sum(durations) I_shape = () for val in values: shape = ops.shape(val) if len(shape) > len(I_shape): I_shape = shape # get the current start = 0 I_current = ops.zeros((int(math.ceil(I_duration / dt)),) + I_shape) for c_size, duration in zip(values, durations): length = int(duration / dt) I_current[start: start + length] = c_size start += length if return_length: return I_current, I_duration else: return I_current
def __init__(self, size, times, indices, need_sort=True, **kwargs): if len(indices) != len(times): raise errors.ModelUseError(f'The length of "indices" and "times" must be the same. ' f'However, we got {len(indices)} != {len(times)}.') # data about times and indices self.idx = 0 self.times = np.ascontiguousarray(times, dtype=float) self.indices = np.ascontiguousarray(indices, dtype=int) self.num_times = len(times) if need_sort: sort_idx = np.argsort(times) self.indices = self.indices[sort_idx] self.spike = ops.zeros(size2len(size), dtype=bool) super(SpikeTimeInput, self).__init__(size=size, **kwargs)
def spike_input(points, lengths, sizes, duration, dt=None): """Format current input like a series of short-time spikes. For example: If you want to generate a spike train at 10 ms, 20 ms, 30 ms, 200 ms, 300 ms, and each spike lasts 1 ms and the spike current is 0.5, then you can use the following funtions: >>> spike_input(points=[10, 20, 30, 200, 300], >>> lengths=1., # can be a list to specify the spike length at each point >>> sizes=0.5, # can be a list to specify the current size at each point >>> duration=400.) Parameters ---------- points : list, tuple The spike time-points. Must be an iterable object. lengths : int, float, list, tuple The length of each point-current, mimicking the spike durations. sizes : int, float, list, tuple The current sizes. duration : int, float The total current duration. dt : float The default is None. Returns ------- current_and_duration : tuple (The formatted current, total duration) """ dt = backend.get_dt() if dt is None else dt assert isinstance(points, (list, tuple)) if isinstance(lengths, (float, int)): lengths = [lengths] * len(points) if isinstance(sizes, (float, int)): sizes = [sizes] * len(points) current = ops.zeros(int(math.ceil(duration / dt))) for time, dur, size in zip(points, lengths, sizes): pp = int(time / dt) p_len = int(dur / dt) current[pp: pp + p_len] = size return current
def constant_input(I_and_duration, dt=None): """Format constant input in durations. For example: If you want to get an input where the size is 0 bwteen 0-100 ms, and the size is 1. between 100-200 ms. >>> import numpy as np >>> constant_input([(0, 100), (1, 100)]) >>> constant_input([(np.zeros(100), 100), (np.random.rand(100), 100)]) Parameters ---------- I_and_duration : list This parameter receives the current size and the current duration pairs, like `[(Isize1, duration1), (Isize2, duration2)]`. dt : float Default is None. Returns ------- current_and_duration : tuple (The formatted current, total duration) """ dt = backend.get_dt() if dt is None else dt # get input current dimension, shape, and duration I_duration = 0. I_dim = 0 I_shape = () for I in I_and_duration: I_duration += I[1] shape = ops.shape(I[0]) if len(shape) > len(I_shape): I_shape = shape # get the current I_current = ops.zeros((int(math.ceil(I_duration / dt)),) + I_shape) start = 0 for c_size, duration in I_and_duration: length = int(duration / dt) I_current[start: start + length] = c_size start += length return I_current, I_duration
def run(self, duration, report=False, report_percent=0.1): if isinstance(duration, (int, float)): duration = [0, duration] elif isinstance(duration, (tuple, list)): assert len(duration) == 2 duration = tuple(duration) else: raise ValueError # get the times times = ops.arange(duration[0], duration[1], backend.get_dt()) # reshape the monitor for key in self.mon.keys(): self.mon[key] = ops.zeros((len(times), ) + ops.shape(self.mon[key])[1:]) # run the model run_model(run_func=self.run_func, times=times, report=report, report_percent=report_percent)
def __init__(self, size, integrals, target_vars, fixed_vars, pars_update, scope, show_code=False): """Trajectory Class. Parameters ---------- size : int, tuple, list The network size. integrals : list of functions, function The integral functions. target_vars : dict The target variables, with the format of "{key: initial_v}". fixed_vars : dict The fixed variables, with the format of "{key: fixed_v}". pars_update : dict The parameters to update. scope : """ if callable(integrals): integrals = (integrals, ) elif isinstance(integrals, (list, tuple)) and callable(integrals[0]): integrals = tuple(integrals) else: raise ValueError self.integrals = integrals self.target_vars = target_vars self.fixed_vars = fixed_vars self.pars_update = pars_update self.scope = {key: val for key, val in scope.items()} self.show_code = show_code # check network size if isinstance(size, int): size = (size, ) elif isinstance(size, (tuple, list)): assert isinstance(size[0], int) size = tuple(size) else: raise ValueError # monitors, variables, parameters self.mon = DictPlus() self.vars_and_pars = DictPlus() for key, val in target_vars.items(): self.vars_and_pars[key] = ops.ones(size) * val self.mon[key] = ops.zeros((1, ) + size) for key, val in fixed_vars.items(): self.vars_and_pars[key] = ops.ones(size) * val for key, val in pars_update.items(): self.vars_and_pars[key] = val self.scope['VP'] = self.vars_and_pars self.scope['MON'] = self.mon self.scope['_fixed_vars'] = fixed_vars code_lines = ['def run_func(_t, _i, _dt):'] for integral in integrals: func_name = integral.__name__ self.scope[func_name] = integral # update the step function assigns = [f'VP["{var}"]' for var in integral.variables] calls = [f'VP["{var}"]' for var in integral.variables] calls.append('_t') calls.extend([f'VP["{var}"]' for var in integral.parameters[1:]]) code_lines.append( f' {", ".join(assigns)} = {func_name}({", ".join(calls)})') # reassign the fixed variables for key, val in fixed_vars.items(): code_lines.append(f' VP["{key}"][:] = _fixed_vars["{key}"]') # monitor the target variables for key in target_vars.keys(): code_lines.append(f' MON["{key}"][_i] = VP["{key}"]') # compile code = '\n'.join(code_lines) if show_code: print(code) print(self.scope) print() # recompile exec(compile(code, '', 'exec'), self.scope) self.run_func = self.scope['run_func']