def trapezoidal_rule(f, dt=None, epsilon=1e-12): """Trapezoidal rule. The trapezoidal rule is an implicit second-order method, which can be considered as both a Runge–Kutta method and a linear multistep method. Parameters ---------- f : callable The function at the right hand of the differential equation. dt : None, float Precision of numerical integration. Returns ------- func : callable The one-step numerical integration function. """ f = autojit(f) if dt is None: dt = profile.get_dt() def int_f(y0, t, *args): dy0 = f(y0, t, *args) y1 = y0 + dt * dy0 y2 = y0 + dt / 2 * (dy0 + f(y1, t + dt, *args)) while not np.all(np.abs(y1 - y2) < epsilon): y1 = y2 y2 = y0 + dt / 2 * (dy0 + f(y1, t + dt, *args)) return y2 return autojit(int_f)
def backward_Euler(f, dt=None, epsilon=1e-12): """Backward Euler method. Also named as ``implicit_Euler``. Parameters ---------- f : callable The function at the right hand of the differential equation. dt : None, float Precision of numerical integration. Returns ------- func : callable The one-step numerical integration function. """ f = autojit(f) if dt is None: dt = profile.get_dt() def int_f(y0, t, *args): y1 = y0 + dt * f(y0, t, *args) y2 = y0 + dt * f(y1, t, *args) while not np.all(np.abs(y1 - y2) < epsilon): y1 = y2 y2 = y0 + dt * f(y1, t, *args) return y2 return autojit(int_f)
def rk4_alternative(f, dt=None): """An alternative of fourth-order Runge-Kutta method. Also named as ``RK4_alternative``. Parameters ---------- f : callable The function at the right hand of the differential equation. dt : None, float Precision of numerical integration. Returns ------- func : callable The one-step numerical integration function. """ f = autojit(f) if dt is None: dt = profile.get_dt() def int_f(y0, t, *args): k1 = f(y0, t, *args) k2 = f(y0 + dt / 3 * k1, t + dt / 3, *args) k3 = f(y0 - dt / 3 * k1 + dt * k2, t + 2 * dt / 3, *args) k4 = f(y0 + dt * k1 - dt * k2 + dt * k3, t + dt, *args) return y0 + dt / 8 * (k1 + 3 * k2 + 3 * k3 + k4) return autojit(int_f)
def rk3(f, dt=None): """Kutta's third-order method (commonly known as RK3). Also named as ``RK3``. Parameters ---------- f : callable The function at the right hand of the differential equation. dt : None, float Precision of numerical integration. Returns ------- func : callable The one-step numerical integration function. """ f = autojit(f) if dt is None: dt = profile.get_dt() def int_f(y0, t, *args): k1 = f(y0, t, *args) k2 = f(y0 + dt / 2 * k1, t + dt / 2, *args) k3 = f(y0 - dt * k1 + 2 * dt * k2, t + dt, *args) return y0 + dt / 6 * (k1 + 4 * k2 + k3) return autojit(int_f)
def rk2(f, dt=None, beta=2 / 3): """Parametric second-order Runge-Kutta (RK2). Also named as ``RK2``. Popular choices for 'beta': 1/2 : explicit midpoint method 2/3 : Ralston's method 1 : Heun's method, also known as the explicit trapezoid rule Parameters ---------- f : callable The function at the right hand of the differential equation. dt : None, float Precision of numerical integration. Returns ------- func : callable The one-step numerical integration function. """ f = autojit(f) if dt is None: dt = profile.get_dt() def int_f(y0, t, *args): k1 = f(y0, t, *args) k2 = f(y0 + beta * dt * k1, t + beta * dt, *args) return y0 + dt * ((1 - 1 / (2 * beta)) * k1 + 1 / (2 * beta) * k2) return autojit(int_f)
def forward_Euler(f, dt=None): """Forward Euler method. Also named as ``explicit_Euler``. The most unstable integrator known. Requires a very small timestep. Accuracy is O(dt). Parameters ---------- f : callable The function at the right hand of the differential equation. dt : None, float Precision of numerical integration. Returns ------- func : callable The one-step numerical integration function. """ f = autojit(f) if dt is None: dt = profile.get_dt() def int_f(y0, t, *args): return y0 + dt * f(y0, t, *args) return autojit(int_f)
def Heun_method(f, g, dt=None): """Stratonovich stochastic integral. Use the Stratonovich Heun algorithm to integrate Stratonovich equation, according to paper [2]_, [3]_. Parameters ---------- f : callable The drift coefficient, the deterministic part of the SDE. g : callable, float The diffusion coefficient, the stochastic part. dt : None, float Precision of numerical integration. Returns ------- func : callable The one-step numerical integration function. References ---------- .. [2] H. Gilsing and T. Shardlow, SDELab: A package for solving stochastic differential equations in MATLAB, Journal of Computational and Applied Mathematics 205 (2007), no. 2, 1002{1018. .. [3] P.E. Kloeden, E. Platen, and H. Schurz, Numerical solution of SDE through computer experiments, Springer, 1994. """ dt = profile.get_dt() if dt is None else dt dt_sqrt = np.sqrt(dt) f = autojit(f) if callable(g): g = autojit(g) def int_fg(y0, t, *args): dW = np.random.normal(0.0, 1.0, y0.shape) df = f(y0, t - dt, *args) * dt gn = g(y0, t - dt, *args) y_bar = y0 + gn * dW * dt_sqrt gn_bar = g(y_bar, t, *args) dg = 0.5 * (gn + gn_bar) * dW * dt_sqrt y1 = y0 + df + dg return y1 else: assert isinstance(g, (int, float, np.ndarray)) def int_fg(y0, t, *args): dW = np.random.normal(0.0, 1.0, y0.shape) df = f(y0, t - dt, *args) * dt dg = g * dW * dt_sqrt y1 = y0 + df + dg return y1 return autojit(int_fg)
def Milstein_dfree_Stra(f, g, dt=None): """Stratonovich stochastic integral. The derivative-free Milstein method is an order 1.0 strong Taylor schema. Parameters ---------- f : callable The drift coefficient, the deterministic part of the SDE. g : callable, float The diffusion coefficient, the stochastic part. dt : None, float Precision of numerical integration. Returns ------- func : callable The one-step numerical integration function. """ dt = profile.get_dt() if dt is None else dt dt_sqrt = np.sqrt(dt) f = autojit(f) if callable(g): g = autojit(g) def int_fg(y0, t, *args): dW = np.random.normal(0.0, 1.0, y0.shape) df = f(y0, t - dt, *args) * dt g_n = g(y0, t - dt, *args) dg = g_n * dW * dt_sqrt y_n_bar = y0 + df + g_n * dt_sqrt g_n_bar = g(y_n_bar, t, *args) extra_term = 0.5 * (g_n_bar - g_n) * (dW * dW * dt_sqrt) y1 = y0 + df + dg + extra_term return y1 else: assert isinstance(g, (int, float, np.ndarray)) def int_fg(y0, t, *args): dW = np.random.normal(0.0, 1.0, y0.shape) df = f(y0, t - dt, *args) * dt dg = g * dW * dt_sqrt y1 = y0 + df + dg return y1 return autojit(int_fg)
def Euler_method(f, g, dt=None): """Itô stochastic integral. The simplest stochastic numerical approximation is the Euler-Maruyama method. Its is an order 0.5 strong Taylor schema. Also named as ``EM``, ``EM_method``, ``Euler``, ``Euler_Maruyama_method``. Parameters ---------- f : callable The drift coefficient, the deterministic part of the SDE. g : callable, float The diffusion coefficient, the stochastic part. dt : None, float Precision of numerical integration. Returns ------- func : callable The one-step numerical integration function. """ dt = profile.get_dt() if dt is None else dt dt_sqrt = np.sqrt(dt) f = autojit(f) if callable(g): g = autojit(g) def int_fg(y0, t, *args): dW = np.random.normal(0.0, 1.0, y0.shape) df = f(y0, t, *args) * dt dg = dt_sqrt * g(y0, t, *args) * dW return y0 + df + dg else: assert isinstance(g, (int, float, np.ndarray)) def int_fg(y0, t, *args): dW = np.random.normal(0.0, 1.0, y0.shape) df = f(y0, t, *args) * dt dg = dt_sqrt * g * dW return y0 + df + dg return autojit(int_fg)
def __init__(self, **kwargs): if 'args' in kwargs: kwargs.pop('args') if 'kwargs' in kwargs: kwargs.pop('kwargs') for k, v in kwargs.items(): setattr(self, k, v) # define external connections self.pre_synapses = [] self.post_synapses = [] # check functions assert 'update_state' in kwargs self.update_state = helper.autojit(self.update_state) # check `geometry` assert 'geometry' in kwargs, 'Must define "geometry".' assert 'num' in kwargs, 'Must define "num".' # check `name` if 'name' not in kwargs: global _neuron_no self.name = "Neurons-{}".format(_neuron_no) _neuron_no += 1 # check `state` assert 'state' in kwargs, 'Must define "state".' # check `var2index` if 'var2index' not in kwargs: raise ValueError('Must define "var2index".') assert isinstance(self.var2index, dict), '"var2index" must be a dict.' for k, _ in self.default_variables: if k in self.var2index: if k == 'V': if self.var2index['V'] != 0: print('The position of "V" is not 0.') else: raise ValueError('"{}" is a pre-defined variable, cannot ' 'be defined in "var2index".'.format(k)) user_defined_variables = sorted(list(self.var2index.items()), key=lambda a: a[1]) neu_variables = user_defined_variables + self.default_variables var2index_array = np.zeros((len(neu_variables), ), dtype=np.int32) vars = dict() for i, (var, index) in enumerate(neu_variables): var2index_array[i] = index vars[var] = i self.var2index = vars self.var2index_array = var2index_array
def exponential_euler(f, factor_zero_order, factor_one_order, dt=None): """Order 2 Exponential Euler method. For an equation of the form .. math: y^{\\prime}=f(y), \quad y(0)=y_{0} its schema is given by .. math: y_{n+1}=y_{n}+h \\varphi(hA) f (y_{n}) where :math::`A=f^{\prime}(y_{n})` and :math::`\\varphi(z)=\\frac{e^{z}-1}{z}`. Parameters ---------- f : callable The function at the right hand of the differential equation. dt : None, float factor_zero_order : int, float The factor of the zero order function in the equation. factor_one_order : int, float The factor of the one order function in the equation. Returns ------- func : callable The one-step numerical integration function. """ a = np.exp(-factor_one_order * dt) b = factor_zero_order / factor_one_order * (1 - a) def int_f(y0, t, *args): y0 = f(y0, t, *args) return y0 * a + b return autojit(int_f)
def syn_delay(func): func = helper.autojit(func) @helper.autojit def f(syn_state, t, var2index): # get `g` g = func(syn_state, t) # get `delay_len` delay_len = var2index[-1, 0] # update `output_idx` output_idx = (var2index[-2, 1] + 1) % delay_len var2index[-2, 1] = output_idx # update `delay_idx` delay_idx = (var2index[-3, 1] + 1) % delay_len var2index[-3, 1] = delay_idx # update `conductance` syn_state[1][delay_idx] = g return f
def generate_fake_neuron(num, V=0.): """Generate the fake neuron group for testing synapse function. Parameters ---------- num : int Number of neurons in the group. V : int, float, numpy.ndarray Initial membrane potential. Returns ------- neurons : dict An instance of ``Dict`` for simulating neurons. """ var2index = dict(V=0) num, geometry = num, (num, ) state = np.zeros((5, num)) state[0] = V update_state = helper.autojit(lambda neu_state, t: 1) return Neurons(**locals())
def __init__(self, **kwargs): if 'kwargs' in kwargs: kwargs.pop('kwargs') for k, v in kwargs.items(): setattr(self, k, v) self.post.pre_synapses.append(self) self.pre.post_synapses.append(self) # check functions assert 'update_state' in kwargs, 'Must provide "update_state" function.' if 'output_synapse' not in kwargs: def f1(syn_state, var_index, neu_state): output_idx = var_index[-2] neu_state[-1] += syn_state[output_idx[0]][output_idx[1]] self.output_synapse = f1 if 'collect_spike' not in kwargs: def f2(syn_state, pre_neu_state, post_neu_state): syn_state[0][-1] = pre_neu_state[-3] self.collect_spike = f2 self.update_state = helper.autojit(self.update_state) self.output_synapse = helper.autojit(self.output_synapse) self.collect_spike = helper.autojit(self.collect_spike) # check `name` if 'name' not in kwargs: global synapse_no self.name = "Synapses-{}".format(synapse_no) synapse_no += 1 # check `num`, `num_pre` and `num_post` assert 'num' in kwargs, 'Must provide "num" attribute.' if 'num_pre' not in kwargs: self.num_pre = self.pre.num if 'num_post' not in kwargs: self.num_post = self.post.num # check `delay_len` if 'delay_len' not in kwargs: if 'delay' not in kwargs: raise ValueError('Must define "delay".') else: dt = kwargs.get('dt', profile.get_dt()) self.delay_len = format_delay(self.delay, dt) # check `var2index` if 'var2index' not in kwargs: raise ValueError('Must define "var2index".') assert isinstance(self.var2index, dict), '"var2index" must be a dict.' # "g" is the "delay_idx" # 'g_post' is the "output_idx" default_variables = [('pre_spike', (0, -1)), ('g', (1, self.delay_len - 1)), ('g_post', (1, 0)), ] self.default_variables = default_variables for k, _ in default_variables: if k in self.var2index: raise ValueError('"{}" is a pre-defined variable, ' 'cannot be defined in "var2index".'.format(k)) user_defined_variables = sorted(list(self.var2index.items()), key=lambda a: a[1]) syn_variables = user_defined_variables + default_variables var2index_array = np.zeros((len(syn_variables) + 1, 2), dtype=np.int32) var2index_array[-1, 0] = self.delay_len vars = dict(delay_len=-1) for i, (var, index) in enumerate(syn_variables): var2index_array[i] = list(index) vars[var] = i self.var2index = vars self.var2index_array = var2index_array
def __init__(self, target): self.target = target self.update_state = helper.autojit(self.update_state)