From a28f1c17632d55178fa0aa767d6eb4bef634fc49 Mon Sep 17 00:00:00 2001 From: chaoming Date: Mon, 6 Sep 2021 20:27:27 +0800 Subject: [PATCH 01/30] update README --- README.md | 44 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 0cf4129c..e9fab3b0 100644 --- a/README.md +++ b/README.md @@ -18,10 +18,10 @@ `BrainPy` is designed to effectively satisfy your basic requirements: -- *Easy to learn and use*: BrainPy is only based on Python language and has little dependency requirements. -- *Flexible and transparent*: BrainPy endows the users with the fully data/logic flow control. Users can code any logic they want with BrainPy. -- *Extensible*: BrainPy allow users to extend new functionality just based on Python coding. For example, we extend the numerical integration with the ability to do numerical analysis. In such a way, the same code in BrainPy can not only be used for simulation, but also for dynamics analysis. -- *Efficient running speed*: All codes in BrainPy can be just-in-time compiled (based on [JAX](https://github.com/google/jax) and [Numba](https://github.com/numba/)) to run on CPU or GPU devices, thus guaranteeing its running efficiency. +- **Easy to learn and use**: BrainPy is only based on Python language and has little dependency requirements. +- **Flexible and transparent**: BrainPy endows the users with the fully data/logic flow control. Users can code any logic they want with BrainPy. +- **Extensible**: BrainPy allow users to extend new functionality just based on Python coding. For example, we extend the numerical integration with the ability to do numerical analysis. In such a way, the same code in BrainPy can not only be used for simulation, but also for dynamics analysis. +- **Efficient**: All codes in BrainPy can be just-in-time compiled (based on [JAX](https://github.com/google/jax) and [Numba](https://github.com/numba/)) to run on CPU or GPU devices, thus guaranteeing its running efficiency. @@ -41,7 +41,6 @@ **Method 1**: install ``BrainPy`` by using ``pip``: ```python -# > pip install -U brain-py ``` @@ -93,21 +92,46 @@ Here list several examples of BrainPy. More detailed examples and tutorials plea ### Neuron models -- [Hodgkin–Huxley neuron model](https://github.com/PKU-NIP-Lab/BrainModels/blob/main/brainmodels/tensor_backend/neurons/HodgkinHuxley_model.py) +- [Leaky integrate-and-fire neuron model](https://brainmodels.readthedocs.io/en/latest/apis/generated/brainmodels.neurons.LIF.html), [source code](https://github.com/PKU-NIP-Lab/BrainModels/blob/main/brainmodels/neurons/LIF.py) +- [Exponential integrate-and-fire neuron model](https://brainmodels.readthedocs.io/en/latest/apis/generated/brainmodels.neurons.ExpIF.html), [source code](https://github.com/PKU-NIP-Lab/BrainModels/blob/main/brainmodels/neurons/ExpIF.py) +- [Quadratic integrate-and-fire neuron model](https://brainmodels.readthedocs.io/en/latest/apis/generated/brainmodels.neurons.QuaIF.html), [source code](https://github.com/PKU-NIP-Lab/BrainModels/blob/main/brainmodels/neurons/QuaIF.py) +- [Adaptive Quadratic integrate-and-fire model](https://brainmodels.readthedocs.io/en/latest/apis/generated/brainmodels.neurons.AdQuaIF.html), [source code](https://github.com/PKU-NIP-Lab/BrainModels/blob/main/brainmodels/neurons/AdQuaIF.py) +- [Adaptive Exponential integrate-and-fire model](https://brainmodels.readthedocs.io/en/latest/apis/generated/brainmodels.neurons.AdExIF.html), [source code](https://github.com/PKU-NIP-Lab/BrainModels/blob/main/brainmodels/neurons/AdExIF.py) +- [Generalized integrate-and-fire model](https://brainmodels.readthedocs.io/en/latest/apis/generated/brainmodels.neurons.GIF.html), [source code](https://github.com/PKU-NIP-Lab/BrainModels/blob/main/brainmodels/neurons/GIF.py) +- [Hodgkin–Huxley neuron model](https://brainmodels.readthedocs.io/en/latest/apis/generated/brainmodels.neurons.HH.html), [source code](https://github.com/PKU-NIP-Lab/BrainModels/blob/main/brainmodels/tensor_backend/neurons/HodgkinHuxley_model.py) +- [Izhikevich neuron model](https://brainmodels.readthedocs.io/en/latest/apis/generated/brainmodels.neurons.Izhikevich.html), [source code](https://github.com/PKU-NIP-Lab/BrainModels/blob/main/brainmodels/neurons/Izhikevich.py) +- [Morris-Lecar neuron model](https://brainmodels.readthedocs.io/en/latest/apis/generated/brainmodels.neurons.MorrisLecar.html), [source code](https://github.com/PKU-NIP-Lab/BrainModels/blob/main/brainmodels/neurons/MorrisLecar.py) +- [Hindmarsh-Rose bursting neuron model](https://brainmodels.readthedocs.io/en/latest/apis/generated/brainmodels.neurons.HindmarshRose.html), [source code](https://github.com/PKU-NIP-Lab/BrainModels/blob/main/brainmodels/neurons/HindmarshRose.py) + +See [brainmodels.neurons](https://brainmodels.readthedocs.io/en/latest/apis/neurons.html) to find more. ### Synapse models -- [AMPA synapse model](https://github.com/PKU-NIP-Lab/BrainModels/blob/main/brainmodels/tensor_backend/synapses/AMPA_synapse.py) +- [Voltage jump synapse model](https://brainmodels.readthedocs.io/en/latest/apis/generated/brainmodels.synapses.VoltageJump.html), [source code](https://github.com/PKU-NIP-Lab/BrainModels/blob/main/brainmodels/synapses/voltage_jump.py) +- [Exponential synapse model](https://brainmodels.readthedocs.io/en/latest/apis/generated/brainmodels.synapses.ExponentialCUBA.html), [source code](https://github.com/PKU-NIP-Lab/BrainModels/blob/main/brainmodels/synapses/exponential.py) +- [Alpha synapse model](https://brainmodels.readthedocs.io/en/latest/apis/generated/brainmodels.synapses.AlphaCUBA.html), [source code](https://github.com/PKU-NIP-Lab/BrainModels/blob/main/brainmodels/synapses/alpha.py) +- [Dual exponential synapse model](https://brainmodels.readthedocs.io/en/latest/apis/generated/brainmodels.synapses.DualExpCUBA.html), [source code](https://github.com/PKU-NIP-Lab/BrainModels/blob/main/brainmodels/synapses/dual_exp.py) +- [AMPA synapse model](https://brainmodels.readthedocs.io/en/latest/apis/generated/brainmodels.synapses.AMPA.html), [source code](https://github.com/PKU-NIP-Lab/BrainModels/blob/main/brainmodels/synapses/AMPA.py) +- [GABAA synapse model](https://brainmodels.readthedocs.io/en/latest/apis/generated/brainmodels.synapses.GABAa.html), [source code](https://github.com/PKU-NIP-Lab/BrainModels/blob/main/brainmodels/synapses/GABAa.py) +- [NMDA synapse model](https://brainmodels.readthedocs.io/en/latest/apis/generated/brainmodels.synapses.NMDA.html), [source code](https://github.com/PKU-NIP-Lab/BrainModels/blob/main/brainmodels/synapses/NMDA.py) +- [Short-term plasticity model](https://brainmodels.readthedocs.io/en/latest/apis/generated/brainmodels.synapses.STP.html), [source code](https://github.com/PKU-NIP-Lab/BrainModels/blob/main/brainmodels/synapses/STP.py) + +See [brainmodels.synapses](https://brainmodels.readthedocs.io/en/latest/apis/synapses.html) to find more. ### Network models -- [Gamma oscillation network model](https://brainmodels.readthedocs.io/en/latest/from_papers/Wang_1996_gamma_oscillation.html) -- [E/I balanced network model](https://brainmodels.readthedocs.io/en/latest/from_papers/Vreeswijk_1996_EI_net.html) -- [Continuous attractor network model](https://brainmodels.readthedocs.io/en/latest/from_papers/Wu_2008_CANN.html) +- **[CANN]** [*(Si Wu, 2008)* Continuous-attractor Neural Network](https://brainmodels.readthedocs.io/en/latest/examples/CANN/Wu_2008_CANN.html) +- [*(Vreeswijk & Sompolinsky, 1996)* E/I balanced network](https://brainmodels.readthedocs.io/en/latest/examples/EI_nets/Vreeswijk_1996_EI_net.html) +- [*(Sherman & Rinzel, 1992)* Gap junction leads to anti-synchronization](https://brainmodels.readthedocs.io/en/latest/examples/gj_nets/Sherman_1992_gj_antisynchrony.html) +- [*(Wang & Buzsáki, 1996)* Gamma Oscillation](https://brainmodels.readthedocs.io/en/latest/examples/oscillation_synchronization/Wang_1996_gamma_oscillation.html) +- [*(Brunel & Hakim, 1999)* Fast Global Oscillation](https://brainmodels.readthedocs.io/en/latest/examples/oscillation_synchronization/Brunel_Hakim_1999_fast_oscillation.html) +- [*(Diesmann, et, al., 1999)* Synfire Chains](https://brainmodels.readthedocs.io/en/latest/examples/oscillation_synchronization/Diesmann_1999_synfire_chains.html) +- **[Working Memory Model]** [*(Mi, et. al., 2017)* STP for Working Memory Capacity](https://brainmodels.readthedocs.io/en/latest/examples/working_memory/Mi_2017_working_memory_capacity.html) +- **[Working Memory Model]** [*(Bouchacourt & Buschman, 2019)* Flexible Working Memory Model](https://brainmodels.readthedocs.io/en/latest/examples/working_memory/Bouchacourt_2019_Flexible_working_memory.html) -- 2.34.1 From 7b8ee0e0df1f0b7d53561c7cf680127ef98e66b1 Mon Sep 17 00:00:00 2001 From: chaoming Date: Mon, 6 Sep 2021 20:36:37 +0800 Subject: [PATCH 02/30] update examples and tests --- examples/deep_neural_network/mlp_mnist.py | 70 --- .../networks/Wang_1996_gamma_oscillation.py | 151 ----- .../networks/Wang_2002_decision_making.py | 538 ---------------- examples/networks/Wu_2008_CANN.py | 136 ---- examples/neurons/FitzHugh_Nagumo.py | 63 -- examples/neurons/HH_model.py | 85 --- examples/neurons/HindmarshRose_model.py | 37 -- examples/neurons/LIF_model.py | 51 -- examples/neurons/LIF_model2.py | 143 ----- .../recurrent_neural_network/img/Slide4.jpg | Bin 40909 -> 0 bytes .../recurrent_neural_network/img/Slide5.jpg | Bin 59899 -> 0 bytes .../recurrent_neural_network/rnn_demo.ipynb | 580 ------------------ examples/recurrent_neural_network/rnn_demo.py | 328 ---------- examples/synapses/AMPA_synapse.py | 145 ----- examples/synapses/gap_junction.py | 140 ----- tests/math/numpy/test_ast2numba.py | 2 +- .../connectivity/test_random_conn.py | 82 +++ tests/simulation/test_monitors_on_numpy.py | 85 +++ 18 files changed, 168 insertions(+), 2468 deletions(-) delete mode 100644 examples/deep_neural_network/mlp_mnist.py delete mode 100644 examples/networks/Wang_1996_gamma_oscillation.py delete mode 100644 examples/networks/Wang_2002_decision_making.py delete mode 100644 examples/networks/Wu_2008_CANN.py delete mode 100644 examples/neurons/FitzHugh_Nagumo.py delete mode 100644 examples/neurons/HH_model.py delete mode 100644 examples/neurons/HindmarshRose_model.py delete mode 100644 examples/neurons/LIF_model.py delete mode 100644 examples/neurons/LIF_model2.py delete mode 100644 examples/recurrent_neural_network/img/Slide4.jpg delete mode 100644 examples/recurrent_neural_network/img/Slide5.jpg delete mode 100644 examples/recurrent_neural_network/rnn_demo.ipynb delete mode 100644 examples/recurrent_neural_network/rnn_demo.py delete mode 100644 examples/synapses/AMPA_synapse.py delete mode 100644 examples/synapses/gap_junction.py create mode 100644 tests/simulation/connectivity/test_random_conn.py create mode 100644 tests/simulation/test_monitors_on_numpy.py diff --git a/examples/deep_neural_network/mlp_mnist.py b/examples/deep_neural_network/mlp_mnist.py deleted file mode 100644 index 01b5b1f6..00000000 --- a/examples/deep_neural_network/mlp_mnist.py +++ /dev/null @@ -1,70 +0,0 @@ -# -*- coding: utf-8 -*- - - -import numpy as np -import tensorflow as tf - -import brainpy as bp - -bp.math.use_backend('jax') - -# Data -(X_train, Y_train), (X_test, Y_test) = tf.keras.datasets.mnist.load_data() -num_train, num_test = X_train.shape[0], X_test.shape[0] -num_dim = bp.tools.size2num(X_train.shape[1:]) -X_train = X_train.reshape((num_train, num_dim)) / 255.0 -X_test = X_test.reshape((num_test, num_dim)) / 255.0 -Y_train = Y_train.flatten() -Y_test = Y_test.flatten() - -# Model -model = bp.dnn.MLP(layer_sizes=(num_dim, 256, 256, 10)) -opt = bp.dnn.Adam(lr=0.0001, train_vars=model.train_vars()) - - -# loss - -@bp.math.function(nodes=model) -def loss_func(x, label): - logit = model(x, config={'train': True}) - return bp.dnn.cross_entropy_loss(logit, label).mean() - - -vg = bp.math.value_and_grad(loss_func) - - -# functions - -@bp.math.jit -@bp.math.function(nodes=(model, opt)) -def train(x, y): - v, g = vg(x, y) - opt(grads=g) - return v - - -@bp.math.jit -@bp.math.function(nodes=model) -def predict(x): - logit = model(x, config={'train': False}) - return bp.dnn.softmax(logit) - - -num_batch = 128 - -# Training -for epoch in range(30): - # Train - loss = [] - sel = np.arange(len(X_train)) - np.random.shuffle(sel) - for it in range(0, X_train.shape[0], num_batch): - l = train(X_train[sel[it:it + num_batch]], Y_train[sel[it:it + num_batch]]) - loss.append(l) - - # Eval - test_predictions = [predict(x_batch).argmax(1) for x_batch in X_test.reshape((50, -1, num_dim))] - accuracy = np.array(test_predictions).flatten() == Y_test.flatten() - print(f'Epoch {epoch + 1:4d} ' - f'Loss {np.mean(loss):.3f} ' - f'Accuracy {100 * np.mean(accuracy):.3f}') diff --git a/examples/networks/Wang_1996_gamma_oscillation.py b/examples/networks/Wang_1996_gamma_oscillation.py deleted file mode 100644 index 54129ab7..00000000 --- a/examples/networks/Wang_1996_gamma_oscillation.py +++ /dev/null @@ -1,151 +0,0 @@ -# -*- coding: utf-8 -*- - -import brainpy as bp - -# bp.math.use_backend('jax') -bp.integrators.set_default_odeint('rk4') - - -class GABAa(bp.TwoEndConn): - def __init__(self, pre, post, conn, delay=0., g_max=0.1, E=-75., - alpha=12., beta=0.1, T=1.0, T_duration=1.0, **kwargs): - super(GABAa, self).__init__(pre=pre, post=post, **kwargs) - - # parameters - self.g_max = g_max - self.E = E - self.alpha = alpha - self.beta = beta - self.T = T - self.T_duration = T_duration - self.delay = delay - - # connections - self.conn = conn(pre.size, post.size) - self.conn_mat = self.conn.requires('conn_mat') - self.size = bp.math.shape(self.conn_mat) - - # variables - self.t_last_pre_spike = bp.math.ones(self.size) * -1e7 - self.s = bp.math.zeros(self.size) - self.g = self.register_constant_delay('g', size=self.size, delay=delay) - - @bp.odeint - def int_s(self, s, t, TT): - return self.alpha * TT * (1 - s) - self.beta * s - - def update(self, _t, _i): - spike = bp.math.reshape(self.pre.spikes, (self.pre.num, 1)) * self.conn_mat - self.t_last_pre_spike[:] = bp.math.where(spike, _t, self.t_last_pre_spike) - TT = ((_t - self.t_last_pre_spike) < self.T_duration) * self.T - self.s[:] = self.int_s(self.s, _t, TT) - self.g.push(self.g_max * self.s) - g = self.g.pull() - self.post.inputs -= bp.math.sum(g, axis=0) * (self.post.V - self.E) - - -class HH(bp.NeuGroup): - def __init__(self, size, ENa=55., EK=-90., EL=-65, C=1.0, - gNa=35., gK=9., gL=0.1, V_th=20., phi=5.0, **kwargs): - super(HH, self).__init__(size=size, **kwargs) - - # parameters - self.ENa = ENa - self.EK = EK - self.EL = EL - self.C = C - self.gNa = gNa - self.gK = gK - self.gL = gL - self.V_th = V_th - self.phi = phi - - # variables - self.V = bp.math.ones(self.num) * -65. - self.h = bp.math.ones(self.num) * 0.6 - self.n = bp.math.ones(self.num) * 0.32 - self.inputs = bp.math.zeros(self.num) - self.spikes = bp.math.zeros(self.num, dtype=bp.math.bool_) - - @bp.odeint - def integral(self, V, h, n, t, Iext): - alpha = 0.07 * bp.math.exp(-(V + 58) / 20) - beta = 1 / (bp.math.exp(-0.1 * (V + 28)) + 1) - dhdt = self.phi * (alpha * (1 - h) - beta * h) - - alpha = -0.01 * (V + 34) / (bp.math.exp(-0.1 * (V + 34)) - 1) - beta = 0.125 * bp.math.exp(-(V + 44) / 80) - dndt = self.phi * (alpha * (1 - n) - beta * n) - - m_alpha = -0.1 * (V + 35) / (bp.math.exp(-0.1 * (V + 35)) - 1) - m_beta = 4 * bp.math.exp(-(V + 60) / 18) - m = m_alpha / (m_alpha + m_beta) - INa = self.gNa * m ** 3 * h * (V - self.ENa) - IK = self.gK * n ** 4 * (V - self.EK) - IL = self.gL * (V - self.EL) - dVdt = (- INa - IK - IL + Iext) / self.C - - return dVdt, dhdt, dndt - - def update(self, _t, _i): - V, h, n = self.integral(self.V, self.h, self.n, _t, self.inputs) - self.spikes[:] = bp.math.logical_and(self.V < self.V_th, V >= self.V_th) - self.V[:] = V - self.h[:] = h - self.n[:] = n - self.inputs[:] = 0. - - -def try1(): - num = 100 - neu = HH(num, monitors=['spikes', 'V'], name='X') - neu.V = -70. + bp.math.random.normal(size=num) * 20 - neu2 = HH(num, monitors=['spikes', 'V'], name='Y') - neu2.V = -70. + bp.math.random.normal(size=num) * 20 - neu3 = HH(num, monitors=['spikes', 'V'], name='Z') - neu3.V = -70. + bp.math.random.normal(size=num) * 20 - neu4 = HH(num, monitors=['spikes', 'V'],) - neu5 = HH(num, monitors=['spikes', 'V'], ) - - syn = GABAa(pre=neu, post=neu, conn=bp.connect.All2All(include_self=False)) - syn.g_max = 0.1 / num - - net = bp.math.jit(bp.Network(neu3, neu4, neu5, neu=neu, syn=syn, neu2=neu2)) - net.run(duration=500., inputs=[('X.inputs', 1.), - ('Y.inputs', 1.)], report=0.2) - - fig, gs = bp.visualize.get_figure(2, 1, 3, 8) - xlim = (-0.1, 500.1) - - fig.add_subplot(gs[0, 0]) - bp.visualize.line_plot(neu.mon.ts, neu.mon.V, xlim=xlim, - ylabel='Membrane potential (N0)') - - fig.add_subplot(gs[1, 0]) - bp.visualize.line_plot(neu2.mon.ts, neu2.mon.V, xlim=xlim, - ylabel='Membrane potential (N0)', show=True) - # bp.visualize.raster_plot(neu.mon.ts, neu.mon.spikes, xlim=xlim, show=True) - - -def try2(): - num = 100 - neu = HH(num, monitors=['spikes', 'V']) - neu.V = -70. + bp.math.random.normal(size=num) * 20 - syn = GABAa(pre=neu, post=neu, conn=bp.connect.All2All(include_self=False)) - syn.g_max = 0.1 / num - - net = bp.math.jit(bp.Network(neu=neu, syn=syn)) - net.run(duration=500., inputs=[('neu.inputs', 1.)], report=0.2) - - fig, gs = bp.visualize.get_figure(2, 1, 3, 8) - xlim = (-0.1, 500.1) - - fig.add_subplot(gs[0, 0]) - bp.visualize.line_plot(neu.mon.ts, neu.mon.V, xlim=xlim, ylabel='Membrane potential (N0)') - fig.add_subplot(gs[1, 0]) - bp.visualize.raster_plot(neu.mon.ts, neu.mon.spikes, xlim=xlim, show=True) - - -if __name__ == '__main__': - try1() - # try2() diff --git a/examples/networks/Wang_2002_decision_making.py b/examples/networks/Wang_2002_decision_making.py deleted file mode 100644 index 32ef3f45..00000000 --- a/examples/networks/Wang_2002_decision_making.py +++ /dev/null @@ -1,538 +0,0 @@ -# -*- coding: utf-8 -*- -# --- -# jupyter: -# jupytext: -# formats: ipynb,py:percent -# text_representation: -# extension: .py -# format_name: percent -# format_version: '1.3' -# jupytext_version: 1.11.4 -# kernelspec: -# display_name: brainpy -# language: python -# name: brainpy -# --- - -# %% [markdown] -# # *(Wang, 2002)*: Decision making spiking model - -# %% [markdown] -# Implementation of the paper: *Wang, Xiao-Jing. "Probabilistic decision making by slow reverberation in cortical circuits." Neuron 36.5 (2002): 955-968.* -# -# - Author : Chaoming Wang (chao.brain@qq.com), Xinyu Liu (adaliu1998@163.com) - -# %% -import sys -sys.path.append('/mnt/d/codes/Projects/BrainPy') -import matplotlib.pyplot as plt -import jax -import brainpy as bp - -# %% -bp.math.use_backend('jax') -bp.math.set_dt(0.1) - - -# %% [markdown] -# ## Neuron model - -# %% [markdown] -# ### LIF neurons - -# %% [markdown] -# Both pyramidal cells and interneurons are described by leaky integrate-and-fire neurons. -# -# $$ -# C_{m} \frac{d V(t)}{d t}=-g_{L}\left(V(t)-V_{L}\right)-I_{s y n}(t) -# $$ -# where -# - $I_{syn}(t)$ represents the total synaptic current flowing into the cell -# - resting potential $V_L$ = -70 mV -# - firing threshold $V_{th}$ = -50 mV -# - reset potential $V_{rest}$ = -55 mV -# - membrane capacitance $C_m$ = 0.5 nF for pyramidal cells and 0.2 nF for interneurons -# - membrane leak conductance $g_L$ = 25 nS for pyramidal cells and 20 nS for interneurons -# - refractory period $\tau_{ref}$ = 2 ms for pyramidal cells and 1 ms for interneurons - -# %% -class LIF(bp.NeuGroup): - def __init__(self, size, V_L=-70., V_reset=-55., V_th=-50., - Cm=0.5, gL=0.025, t_refractory=2., **kwargs): - super(LIF, self).__init__(size=size, **kwargs) - - self.V_L = V_L - self.V_reset = V_reset - self.V_th = V_th - self.Cm = Cm - self.gL = gL - self.t_refractory = t_refractory - - self.V = bp.math.Variable(bp.math.ones(self.num) * V_L) - self.input = bp.math.Variable(bp.math.zeros(self.num)) - self.spike = bp.math.Variable(bp.math.zeros(self.num, dtype=bool)) - self.refractory = bp.math.Variable(bp.math.zeros(self.num, dtype=bool)) - self.t_last_spike = bp.math.Variable(bp.math.ones(self.num) * -1e7) - - @bp.odeint - def integral(self, V, t, Iext): - dVdt = (- self.gL * (V - self.V_L) - Iext) / self.Cm - return dVdt - - def update(self, _t, _i): - ref = (_t - self.t_last_spike) <= self.t_refractory - V = self.integral(self.V, _t, self.input) - V = bp.math.where(ref, self.V, V) - spike = (V >= self.V_th) - self.V[:] = bp.math.where(spike, self.V_reset, V) - self.spike[:] = spike - self.t_last_spike[:] = bp.math.where(spike, _t, self.t_last_spike) - self.refractory[:] = bp.math.logical_or(spike, ref) - self.input[:] = 0. - - -# %% [markdown] -# ### Poisson neurons - - -# %% -class PoissonNoise(bp.NeuGroup): - def __init__(self, size, freqs, **kwargs): - super(PoissonNoise, self).__init__(size=size, **kwargs) - - self.freqs = freqs - self.dt = bp.math.get_dt() / 1000. - self.spike = bp.math.Variable(bp.math.zeros(self.num, dtype=bool)) - self.rng = bp.math.random.RandomState() - - def update(self, _t, _i): - self.spike[:] = self.rng.random(self.num) < self.freqs * self.dt - - -# %% -class PoissonStimulus(bp.NeuGroup): - def __init__(self, size, t_start=0., t_end=0., t_interval=0., - freq_mean=0., freq_var=20., **kwargs): - super(PoissonStimulus, self).__init__(size=size, **kwargs) - - self.dt = bp.math.get_dt() / 1000 - self.t_start = t_start - self.t_end = t_end - self.t_interval = t_interval - self.freq_mean = freq_mean - self.freq_var = freq_var - self.freqs = bp.math.Variable(bp.math.array([0.])) - self.t_last_change = bp.math.Variable(bp.math.array([-1e7])) - self.spike = bp.math.Variable(bp.math.zeros(size, dtype=bool)) - self.rand_state = bp.math.random.RandomState() - - def update(self, _t, _i): - def true_f2(_V): - _V['freqs'][0] = _V['rand_state'].normal(self.freq_mean, self.freq_var) - _V['t_last_change'][0] = _V['_t'] - return _V - - def true_f1(_V): - _V = jax.lax.cond((_V['_t'] - _V['t_last_change'][0]) >= self.t_interval, - true_f2, lambda _V: _V, _V) - _V['spike'][:] = _V['rand_state'].random(self.num) < (_V['freqs'][0] * self.dt) - return _V - - def false_f1(_V): - _V['freqs'][0] = 0. - _V['spike'][:] = False - return _V - - V = dict(freqs=self.freqs, - spike=self.spike, - _t=_t, - rand_state=self.rand_state, - t_last_change=self.t_last_change) - V = jax.lax.cond((self.t_start < _t), true_f1, false_f1, V) - self.freqs.value = V['freqs'] - self.spike.value = V['spike'] - self.rand_state.value = V['rand_state'] - self.t_last_change.value = V['t_last_change'] - - # if self.t_start < _t < self.t_end: - # if (_t - self.t_last_change[0]) >= self.t_interval: - # self.freqs[0] = self.rand_state.normal(self.freq_mean, self.freq_var) - # self.t_last_change[0] = _t - # self.spike[:] = self.rand_state.random(self.num) < (self.freqs[0] * self.dt) - # else: - # self.freqs[0] = 0. - # self.spike[:] = False - - -# %% [markdown] -# ## Synapse models - -# %% [markdown] -# The total synaptic currents are given by -# -# $$ -# I_{s y n}(t)=I_{e x t, A M P A}(t)+I_{r e c, A M P A}(t)+I_{r e c, N M D A}(t)+I_{r e c, G A B A}(t) -# $$ -# -# in which -# -# $$ -# \begin{gathered} -# I_{\mathrm{ext}, \mathrm{AMPA}}(t)=g_{\mathrm{ext}, \mathrm{AMPA}}\left(V(t)-V_{E}\right) \mathrm{s}^{\mathrm{ext}, \mathrm{AMPA}}(t) \\ -# I_{\mathrm{rec}, \mathrm{AMPA}}(t)=g_{\mathrm{rec}, \mathrm{AMPA}}\left(V(t)-V_{E}\right) \sum_{\mathrm{j}=1}^{\mathrm{C}_{\mathrm{E}}} W_{j} S_{j}^{\mathrm{AMPA}}(t) \\ -# I_{\mathrm{rec}, \mathrm{NMDA}}(t)=\frac{g_{\mathrm{NMDA}}\left(V(t)-V_{E}\right)}{\left(1+\left[\mathrm{Mg}^{2+}\right] \exp (-0.062 V(t)) / 3.57\right)} \sum_{j=1}^{\mathrm{c}_{E}} w_{j} \mathrm{~s}_{j}^{\mathrm{NMDA}}(t) \\ -# I_{\mathrm{rec}, \mathrm{GABA}}(t)=g_{\mathrm{GABA}}\left(V(t)-V_{l}\right) \sum_{j=1}^{c_{1}} s_{j}^{\mathrm{GABA}}(t) -# \end{gathered} -# $$ - -# %% [markdown] -# where -# -# - $V_E$ = 0 mV -# - $V_I$ = -70 mV -# - $\left[\mathrm{Mg}^{2+}\right]$ = 1 mM -# - The dimensionless weights $w_j$ represent the structured excitatory recurrent connections -# - the sum over $j$ represents a sum over the synapses formed by presynaptic neurons $j$ - -# %% [markdown] -# ### AMPA -# -# The AMPA (external and recurrent) channels are described by -# -# $$ -# \frac{d s_{j}^{A M P A}(t)}{d t}=-\frac{s_{j}^{A M P A}(t)}{\tau_{A M P A}}+\sum_{k} \delta\left(t-t_{j}^{k}\right) -# $$ -# -# where -# -# - the decay time of AMPA currents $\tau_{A M P A}$ = 2 ms -# - for the external AMPA currents, the spikes are emitted according to a Poisson process with rate $V_{ext}$ = 2400 Hz independently from cell to cell - -# %% -class AMPA_One(bp.TwoEndConn): - def __init__(self, pre, post, delay=0.5, g_max=0.10, E=0., tau=2.0, **kwargs): - super(AMPA_One, self).__init__(pre=pre, post=post, **kwargs) - - # parameters - self.g_max = g_max - self.E = E - self.tau = tau - self.delay = delay - - # variables - self.pre_spike = self.register_constant_delay('ps', size=self.pre.num, delay=delay) - self.s = bp.math.Variable(bp.math.zeros(self.pre.num)) - - @bp.odeint - def int_s(self, s, t): - ds = - s / self.tau - return ds - - def update(self, _t, _i): - self.pre_spike.push(self.pre.spike) - pre_spike = self.pre_spike.pull() - self.s[:] = self.int_s(self.s, _t) - self.s += pre_spike * self.g_max - self.post.input += self.s * (self.post.V - self.E) - - -# %% -class AMPA(bp.TwoEndConn): - def __init__(self, pre, post, delay=0.5, g_max=0.10, E=0., tau=2.0, **kwargs): - super(AMPA, self).__init__(pre=pre, post=post, **kwargs) - - # parameters - self.g_max = g_max - self.E = E - self.tau = tau - self.delay = delay - self.size = (self.pre.num, self.post.num) - - # variables - self.pre_spike = self.register_constant_delay('ps', size=self.pre.num, delay=delay) - self.pre_one = bp.math.Variable(bp.math.ones(self.pre.num)) - self.s = bp.math.Variable(bp.math.zeros(self.size)) - - @bp.odeint - def int_s(self, s, t): - ds = - s / self.tau - return ds - - def update(self, _t, _i): - self.pre_spike.push(self.pre.spike) - pre_spike = self.pre_spike.pull() - self.s[:] = self.int_s(self.s, _t) - self.s += (pre_spike * self.g_max).reshape((-1, 1)) - self.post.input += bp.math.dot(self.pre_one, self.s) * (self.post.V - self.E) - - -# %% [markdown] -# ### NMDA - -# %% [markdown] -# NMDA channels are described by: -# -# $$ -# \begin{gathered} -# \frac{d s_{j}^{\mathrm{NMDA}}(t)}{d t}=-\frac{s_{j}^{\mathrm{NMDA}}(t)}{\tau_{\mathrm{NMDA}, \text { decay }}}+\alpha x_{j}(t)\left(1-s_{j}^{\mathrm{NMDA}}(t)\right) \\ -# \frac{d x_{j}(t)}{d t}=-\frac{x_{j}(t)}{\tau_{\mathrm{NMDA}, \text { rise }}}+\sum_{k} \delta\left(t-t_{j}^{k}\right) -# \end{gathered} -# $$ -# -# where -# -# - the decay time $\tau_{\mathrm{NMDA}, \text { decay }}$ = 100 ms -# - $\alpha$ = 0.5 $\mathrm{ms}^{-1}$ -# - the rise time $\tau_{\mathrm{NMDA}, \text { rise }}$ = 2 ms - -# %% -class NMDA(bp.TwoEndConn): - def __init__(self, pre, post, delay=0.5, tau_decay=100, tau_rise=2., - g_max=0.15, E=0., cc_Mg=1., alpha=0.5, **kwargs): - super(NMDA, self).__init__(pre=pre, post=post, **kwargs) - - # parameters - self.g_max = g_max - self.E = E - self.cc_Mg = cc_Mg - self.alpha = alpha - self.tau_decay = tau_decay - self.tau_rise = tau_rise - self.delay = delay - self.size = (self.pre.num, self.post.num) - - # variables - self.pre_spike = self.register_constant_delay('ps', size=self.pre.num, delay=delay) - self.pre_one = bp.math.Variable(bp.math.ones(self.pre.num)) - self.s = bp.math.Variable(bp.math.zeros(self.size)) - self.x = bp.math.Variable(bp.math.zeros(self.size)) - - @bp.odeint - def integral(self, s, x, t): - dsdt = -s / self.tau_decay + self.alpha * x * (1 - s) - dxdt = -x / self.tau_rise - return dsdt, dxdt - - def update(self, _t, _i): - self.pre_spike.push(self.pre.spike) - pre_spike = self.pre_spike.pull() - self.s[:], self.x[:] = self.integral(self.s, self.x, _t) - self.x += pre_spike.reshape((-1, 1)) - - g_inf = 1 / (1 + self.cc_Mg * bp.math.exp(-0.062 * self.post.V) / 3.57) - Iext = bp.math.dot(self.pre_one, self.s) * (self.post.V - self.E) * g_inf - self.post.input += Iext * self.g_max - - -# %% [markdown] -# ### GABAA -# -# The GABA synaptic variable obeys -# -# $$ -# \frac{d s_{j}^{G A B A}(t)}{d t}=-\frac{s_{j}^{G A B A}(t)}{\tau_{G A B A}}+\sum_{k} \delta\left(t-t_{j}^{k}\right) -# $$ -# -# where -# - the decay time of AMPA currents $\tau_{GABA}$ = 5 ms - -# %% -class GABAa(AMPA): - def __init__(self, pre, post, delay=0.5, g_max=0.10, E=-70., tau=5.0, **kwargs): - super(GABAa, self).__init__(pre=pre, post=post, E=E, tau=tau, delay=delay, g_max=g_max, **kwargs) - - -# %% [markdown] -# ## Parameters - -# %% -scale = 1. -num_exc = int(1600 * scale) -num_inh = int(400 * scale) -f = 0.15 -num_A = int(f * num_exc) -num_B = int(f * num_exc) -num_N = num_exc - num_A - num_B -print(f"N_E = {num_exc} = {num_A} + {num_B} + {num_N}, N_I = {num_inh}") - -# %% -mu0 = 40. -coherence = 25.6 - -# %% -# times -pre_period = 100. -stim_period = 1000. -delay_period = 500. -total_period = pre_period + stim_period + delay_period - -# %% -poisson_freq = 2400. # Hz -w_pos = 1.7 -w_neg = 1. - f * (w_pos - 1.) / (1. - f) -g_max_ext2E_AMPA = 2.1 * 1e-3 # uS -g_max_ext2I_AMPA = 1.62 * 1e-3 # uS -g_max_E2E_AMPA = 0.05 * 1e-3 / scale # uS -g_max_E2E_NMDA = 0.165 * 1e-3 / scale # uS -g_max_E2I_AMPA = 0.04 * 1e-3 / scale # uS -g_max_E2I_NMDA = 0.13 * 1e-3 / scale # uS -g_max_I2E_GABAa = 1.3 * 1e-3 / scale # uS -g_max_I2I_GABAa = 1.0 * 1e-3 / scale # uS - -# %% [markdown] -# ## Build the network - - -# %% -# E neurons/pyramid neurons -A = LIF(num_A, Cm=0.5, gL=0.025, t_refractory=2.) -B = LIF(num_B, Cm=0.5, gL=0.025, t_refractory=2.) -N = LIF(num_N, Cm=0.5, gL=0.025, t_refractory=2.) -# I neurons/interneurons -I = LIF(num_inh, Cm=0.2, gL=0.020, t_refractory=1.) - - -# %% -# IA = PoissonStimulus(num_A, t_start=pre_period, t_end=pre_period + stim_period, t_interval=50., -# freq_mean=mu0 + mu0 / 100. * coherence, freq_var=10., monitors=['freqs']) -# IB = PoissonStimulus(num_B, t_start=pre_period, t_end=pre_period + stim_period, t_interval=50., -# freq_mean=mu0 - mu0 / 100. * coherence, freq_var=10., monitors=['freqs']) -IA = PoissonNoise(num_A, freqs=mu0 + mu0 / 100. * coherence) -IB = PoissonNoise(num_B, freqs=mu0 - mu0 / 100. * coherence) - - -# %% -noise_A = PoissonNoise(num_A, freqs=poisson_freq) -noise_B = PoissonNoise(num_B, freqs=poisson_freq) -noise_N = PoissonNoise(num_N, freqs=poisson_freq) -noise_I = PoissonNoise(num_inh, freqs=poisson_freq) - - -# %% -IA2A = AMPA_One(pre=IA, post=A, g_max=g_max_ext2E_AMPA) -IB2B = AMPA_One(pre=IB, post=B, g_max=g_max_ext2E_AMPA) - - -# %% -## define E2E conn -A2A_AMPA = AMPA(pre=A, post=A, g_max=g_max_E2E_AMPA * w_pos) -A2A_NMDA = NMDA(pre=A, post=A, g_max=g_max_E2E_NMDA * w_pos) - -A2B_AMPA = AMPA(pre=A, post=B, g_max=g_max_E2E_AMPA * w_neg) -A2B_NMDA = NMDA(pre=A, post=B, g_max=g_max_E2E_NMDA * w_neg) - -A2N_AMPA = AMPA(pre=A, post=N, g_max=g_max_E2E_AMPA) -A2N_NMDA = NMDA(pre=A, post=N, g_max=g_max_E2E_NMDA) - -B2A_AMPA = AMPA(pre=B, post=A, g_max=g_max_E2E_AMPA * w_neg) -B2A_NMDA = NMDA(pre=B, post=A, g_max=g_max_E2E_NMDA * w_neg) - -B2B_AMPA = AMPA(pre=B, post=B, g_max=g_max_E2E_AMPA * w_pos) -B2B_NMDA = NMDA(pre=B, post=B, g_max=g_max_E2E_NMDA * w_pos) - -B2N_AMPA = AMPA(pre=B, post=N, g_max=g_max_E2E_AMPA) -B2N_NMDA = NMDA(pre=B, post=N, g_max=g_max_E2E_NMDA) - -N2A_AMPA = AMPA(pre=N, post=A, g_max=g_max_E2E_AMPA * w_neg) -N2A_NMDA = NMDA(pre=N, post=A, g_max=g_max_E2E_NMDA * w_neg) - -N2B_AMPA = AMPA(pre=N, post=B, g_max=g_max_E2E_AMPA * w_neg) -N2B_NMDA = NMDA(pre=N, post=B, g_max=g_max_E2E_NMDA * w_neg) - -N2N_AMPA = AMPA(pre=N, post=N, g_max=g_max_E2E_AMPA) -N2N_NMDA = NMDA(pre=N, post=N, g_max=g_max_E2E_NMDA) - -## define E2I conn -A2I_AMPA = AMPA(pre=A, post=I, g_max=g_max_E2I_AMPA) -A2I_NMDA = NMDA(pre=A, post=I, g_max=g_max_E2I_NMDA) - -B2I_AMPA = AMPA(pre=B, post=I, g_max=g_max_E2I_AMPA) -B2I_NMDA = NMDA(pre=B, post=I, g_max=g_max_E2I_NMDA) - -N2I_AMPA = AMPA(pre=N, post=I, g_max=g_max_E2I_AMPA) -N2I_NMDA = NMDA(pre=N, post=I, g_max=g_max_E2I_NMDA) - -I2A_GABAa = GABAa(pre=I, post=A, g_max=g_max_I2E_GABAa) -I2B_GABAa = GABAa(pre=I, post=B, g_max=g_max_I2E_GABAa) -I2N_GABAa = GABAa(pre=I, post=N, g_max=g_max_I2E_GABAa) - -## define I2I conn -I2I_GABAa = GABAa(pre=I, post=I, g_max=g_max_I2I_GABAa) - -## define external projections -noise2A = AMPA_One(pre=noise_A, post=A, g_max=g_max_ext2E_AMPA) -noise2B = AMPA_One(pre=noise_B, post=B, g_max=g_max_ext2E_AMPA) -noise2N = AMPA_One(pre=noise_N, post=N, g_max=g_max_ext2E_AMPA) -noise2I = AMPA_One(pre=noise_I, post=I, g_max=g_max_ext2I_AMPA) - -# %% -# build & simulate network -net = bp.Network( - # Synaptic Connections - noise2A, noise2B, noise2N, noise2I, IA2A, IB2B, - A2A_AMPA, A2A_NMDA, A2B_AMPA, A2B_NMDA, A2N_AMPA, A2N_NMDA, B2A_AMPA, B2A_NMDA, - B2B_AMPA, B2B_NMDA, B2N_AMPA, B2N_NMDA, N2A_AMPA, N2A_NMDA, N2B_AMPA, N2B_NMDA, - A2I_AMPA, A2I_NMDA, B2I_AMPA, B2I_NMDA, N2I_AMPA, N2I_NMDA, N2N_AMPA, N2N_NMDA, - I2A_GABAa, I2B_GABAa, I2N_GABAa, I2I_GABAa, - # Neuron Groups - noise_A, noise_B, noise_N, noise_I, N, I, A=A, B=B, IA=IA, IB=IB, - monitors=['A.spike', 'B.spike', 'IA.freqs', 'IB.freqs'] -) -net = bp.math.jit(net) - - -net.run(duration=total_period, report=0.1) - - -# %% [markdown] -# ## Visualization - -# %% -t_start = 0. - -fig, gs = bp.visualize.get_figure(4, 1, 3, 10) - -fig.add_subplot(gs[0, 0]) -bp.visualize.raster_plot(net.mon.ts, net.mon['A.spike'], markersize=1) -plt.title("Spiking activity of group A") -plt.ylabel("Neuron Index") -plt.xlim(t_start, total_period + 1) -plt.axvline(pre_period, linestyle='dashed') -plt.axvline(pre_period + stim_period, linestyle='dashed') -plt.axvline(pre_period + stim_period + delay_period, linestyle='dashed') - -fig.add_subplot(gs[1, 0]) -bp.visualize.raster_plot(net.mon.ts, net.mon['B.spike'], markersize=1) -plt.title("Spiking activity of group B") -plt.ylabel("Neuron Index") -plt.xlim(t_start, total_period + 1) -plt.axvline(pre_period, linestyle='dashed') -plt.axvline(pre_period + stim_period, linestyle='dashed') -plt.axvline(pre_period + stim_period + delay_period, linestyle='dashed') - -fig.add_subplot(gs[2, 0]) -plt.plot(net.mon.ts, net.mon['IA.freqs'], label="group A") -plt.plot(net.mon.ts, net.mon['IB.freqs'], label="group B") -plt.title("Input activity") -plt.ylabel("Firing rate [Hz]") -plt.xlim(t_start, total_period + 1) -plt.axvline(pre_period, linestyle='dashed') -plt.axvline(pre_period + stim_period, linestyle='dashed') -plt.axvline(pre_period + stim_period + delay_period, linestyle='dashed') -plt.legend() - -fig.add_subplot(gs[3, 0]) -rateA = brainpy.simulation.measure.firing_rate(net.mon['A.spike'], width=10.) -rateB = brainpy.simulation.measure.firing_rate(net.mon['B.spike'], width=10.) -plt.plot(net.mon.ts, rateA, label="Group A") -plt.plot(net.mon.ts, rateB, label="Group B") -plt.ylabel('Firing rate [Hz]') -plt.title("Population activity") -plt.xlim(t_start, total_period + 1) -plt.axvline(pre_period, linestyle='dashed') -plt.axvline(pre_period + stim_period, linestyle='dashed') -plt.axvline(pre_period + stim_period + delay_period, linestyle='dashed') -plt.legend() - -plt.xlabel("Time [ms]") -plt.show() diff --git a/examples/networks/Wu_2008_CANN.py b/examples/networks/Wu_2008_CANN.py deleted file mode 100644 index 03eb7b80..00000000 --- a/examples/networks/Wu_2008_CANN.py +++ /dev/null @@ -1,136 +0,0 @@ -# -*- coding: utf-8 -*- - -import brainpy as bp - - -# bp.math.use_backend('jax') - - -class CANN1D(bp.NeuGroup): - def __init__(self, num, tau=1., k=8.1, a=0.5, A=10., J0=4., - z_min=-bp.math.pi, z_max=bp.math.pi, **kwargs): - super(CANN1D, self).__init__(size=num, **kwargs) - - # parameters - self.tau = tau # The synaptic time constant - self.k = k # Degree of the rescaled inhibition - self.a = a # Half-width of the range of excitatory connections - self.A = A # Magnitude of the external input - self.J0 = J0 # maximum connection value - - # feature space - self.z_min = z_min - self.z_max = z_max - self.z_range = z_max - z_min - self.x = bp.math.linspace(z_min, z_max, num) # The encoded feature values - self.rho = num / self.z_range # The neural density - self.dx = self.z_range / num # The stimulus density - - # variables - self.u = bp.math.Variable(bp.math.zeros(num)) - self.input = bp.math.Variable(bp.math.zeros(num)) - - # The connection matrix - self.conn_mat = self.make_conn(self.x) - - def dist(self, d): - d = bp.math.remainder(d, self.z_range) - d = bp.math.where(d > 0.5 * self.z_range, d - self.z_range, d) - return d - - def make_conn(self, x): - assert bp.math.ndim(x) == 1 - x_left = bp.math.reshape(x, (-1, 1)) - x_right = bp.math.repeat(x.reshape((1, -1)), len(x), axis=0) - d = self.dist(x_left - x_right) - Jxx = self.J0 * bp.math.exp(-0.5 * bp.math.square(d / self.a)) / \ - (bp.math.sqrt(2 * bp.math.pi) * self.a) - return Jxx - - def get_stimulus_by_pos(self, pos): - return self.A * bp.math.exp(-0.25 * bp.math.square(self.dist(self.x - pos) / self.a)) - - @staticmethod - @bp.odeint(method='rk4', dt=0.05) - def int_u(u, t, conn, k, tau, Iext): - r1 = bp.math.square(u) - r2 = 1.0 + k * bp.math.sum(r1) - r = r1 / r2 - Irec = bp.math.dot(conn, r) - du = (-u + Irec + Iext) / tau - return du - - def update(self, _t, _i): - self.u[:] = self.int_u(self.u, _t, self.conn_mat, self.k, self.tau, self.input) - self.input[:] = 0. - - -def task1_population_coding(): - cann = bp.math.jit(CANN1D(num=512, k=0.1, monitors=['u'])) - - I1 = cann.get_stimulus_by_pos(0.) - Iext, duration = brainpy.simulation.inputs.section_input(values=[0., I1, 0.], - durations=[1., 8., 8.], - return_length=True) - cann.run(duration=duration, inputs=('input', Iext, 'iter')) - - bp.visualize.animate_1D( - dynamical_vars=[{'ys': cann.mon.u, 'xs': cann.x, 'legend': 'u'}, - {'ys': Iext, 'xs': cann.x, 'legend': 'Iext'}], - frame_step=1, - frame_delay=100, - show=True, - # save_path='../../images/CANN-encoding.gif' - ) - - -def task2_template_matching(): - cann = bp.math.jit(CANN1D(num=512, k=8.1, monitors=['u'])) - - dur1, dur2, dur3 = 10., 30., 0. - num1 = int(dur1 / bp.math.get_dt()) - num2 = int(dur2 / bp.math.get_dt()) - num3 = int(dur3 / bp.math.get_dt()) - Iext = bp.math.zeros((num1 + num2 + num3,) + cann.size) - Iext[:num1] = cann.get_stimulus_by_pos(0.5) - Iext[num1:num1 + num2] = cann.get_stimulus_by_pos(0.) - Iext[num1:num1 + num2] += 0.1 * cann.A * bp.math.random.randn(num2, *cann.size) - cann.run(duration=dur1 + dur2 + dur3, inputs=('input', Iext, 'iter')) - - bp.visualize.animate_1D( - dynamical_vars=[{'ys': cann.mon.u, 'xs': cann.x, 'legend': 'u'}, - {'ys': Iext, 'xs': cann.x, 'legend': 'Iext'}], - frame_step=5, - frame_delay=50, - show=True, - # save_path='../../images/CANN-decoding.gif' - ) - - -def task3_smooth_tracking(): - cann = bp.math.jit(CANN1D(num=512, k=8.1, monitors=['u'])) - - dur1, dur2, dur3 = 20., 20., 20. - num1 = int(dur1 / bp.math.get_dt()) - num2 = int(dur2 / bp.math.get_dt()) - num3 = int(dur3 / bp.math.get_dt()) - position = bp.math.zeros(num1 + num2 + num3) - position[num1: num1 + num2] = bp.math.linspace(0., 12., num2) - position[num1 + num2:] = 12. - position = position.reshape((-1, 1)) - Iext = cann.get_stimulus_by_pos(position) - cann.run(duration=dur1 + dur2 + dur3, inputs=('input', Iext, 'iter')) - - bp.visualize.animate_1D( - dynamical_vars=[{'ys': cann.mon.u, 'xs': cann.x, 'legend': 'u'}, - {'ys': Iext, 'xs': cann.x, 'legend': 'Iext'}], - frame_step=5, - frame_delay=50, - show=True, - # save_path='../../images/CANN-tracking.gif' - ) - - -task1_population_coding() -task2_template_matching() -task3_smooth_tracking() diff --git a/examples/neurons/FitzHugh_Nagumo.py b/examples/neurons/FitzHugh_Nagumo.py deleted file mode 100644 index 11767703..00000000 --- a/examples/neurons/FitzHugh_Nagumo.py +++ /dev/null @@ -1,63 +0,0 @@ -# -*- coding: utf-8 -*- - -import brainpy as bp - -bp.math.set_dt(0.02) - - -class FitzHughNagumo(bp.NeuGroup): - def __init__(self, size, a=0.7, b=0.8, tau=12.5, Vth=1.9, **kwargs): - super(FitzHughNagumo, self).__init__(size=size, **kwargs) - - self.a = a - self.b = b - self.tau = tau - self.Vth = Vth - - self.V = bp.math.zeros(size) - self.w = bp.math.zeros(size) - self.spike = bp.math.zeros(size) - self.input = bp.math.zeros(size) - - @bp.odeint(method='rk4') - def integral(self, V, w, t, Iext): - dw = (V + self.a - self.b * w) / self.tau - dV = V - V * V * V / 3 - w + Iext - return dV, dw - - def update(self, _t, _i): - V, self.w[:] = self.integral(self.V, self.w, _t, self.input) - self.spike[:] = (V >= self.Vth) * (self.V < self.Vth) - self.V[:] = V - self.input[:] = 0. - - -if __name__ == '__main__': - FNs = FitzHughNagumo(100, monitors=['V']) - - # simulation - FNs.run(duration=300., inputs=('input', 1.), report=True) - bp.visualize.line_plot(FNs.mon.ts, FNs.mon.V, show=True) - - FNs.run(duration=(300., 600.), inputs=('input', 0.6), report=True) - bp.visualize.line_plot(FNs.mon.ts, FNs.mon.V, show=True) - - # phase plane analysis - phase = bp.analysis.PhasePlane(FNs.integral, - target_vars={'V': [-3, 2], 'w': [-2, 2]}, - fixed_vars=None, - pars_update={'Iext': 1., "a": 0.7, 'b': 0.8, 'tau': 12.5}) - phase.plot_nullcline() - phase.plot_fixed_point() - # phase.plot_trajectory(initials={'V': -1, 'w': 1}, duration=100.) - phase.plot_limit_cycle_by_sim(initials={'V': -1, 'w': 1}, duration=100.) - phase.plot_vector_field(show=True) - - # bifurcation analysis - bifurcation = bp.analysis.Bifurcation(FNs.integral, - target_pars=dict(Iext=[-1, 1], a=[0.3, 0.8]), - target_vars={'V': [-3, 2], 'w': [-2, 2]}, - fixed_vars=None, - pars_update={'b': 0.8, 'tau': 12.5}, - numerical_resolution=0.01) - bifurcation.plot_bifurcation(show=True) diff --git a/examples/neurons/HH_model.py b/examples/neurons/HH_model.py deleted file mode 100644 index e0386eeb..00000000 --- a/examples/neurons/HH_model.py +++ /dev/null @@ -1,85 +0,0 @@ -# -*- coding: utf-8 -*- - - -import brainpy as bp - -bp.math.use_backend('numpy') -bp.math.set_dt(dt=0.02) - - -class HH(bp.NeuGroup): - def __init__(self, size, ENa=50., EK=-77., EL=-54.387, C=1.0, - gNa=120., gK=36., gL=0.03, V_th=20., **kwargs): - super(HH, self).__init__(size=size, **kwargs) - - # parameters - self.ENa = ENa - self.EK = EK - self.EL = EL - self.C = C - self.gNa = gNa - self.gK = gK - self.gL = gL - self.V_th = V_th - - # variables - self.V = bp.math.Variable(bp.math.ones(self.num) * -65.) - self.m = bp.math.Variable(bp.math.ones(self.num) * 0.5) - self.h = bp.math.Variable(bp.math.ones(self.num) * 0.6) - self.n = bp.math.Variable(bp.math.ones(self.num) * 0.32) - self.spike = bp.math.Variable(bp.math.zeros(self.num, dtype=bool)) - self.input = bp.math.Variable(bp.math.zeros(self.num)) - - @bp.odeint(method='exponential_euler') - # @bp.odeint(method='rk4') - def integral(self, V, m, h, n, t, Iext): - alpha = 0.1 * (V + 40) / (1 - bp.math.exp(-(V + 40) / 10)) - beta = 4.0 * bp.math.exp(-(V + 65) / 18) - dmdt = alpha * (1 - m) - beta * m - - alpha = 0.07 * bp.math.exp(-(V + 65) / 20.) - beta = 1 / (1 + bp.math.exp(-(V + 35) / 10)) - dhdt = alpha * (1 - h) - beta * h - - alpha = 0.01 * (V + 55) / (1 - bp.math.exp(-(V + 55) / 10)) - beta = 0.125 * bp.math.exp(-(V + 65) / 80) - dndt = alpha * (1 - n) - beta * n - - I_Na = (self.gNa * m ** 3.0 * h) * (V - self.ENa) - I_K = (self.gK * n ** 4.0) * (V - self.EK) - I_leak = self.gL * (V - self.EL) - dVdt = (- I_Na - I_K - I_leak + Iext) / self.C - - return dVdt, dmdt, dhdt, dndt - - def update(self, _t, _i): - V, m, h, n = self.integral(self.V, self.m, self.h, self.n, _t, self.input) - self.spike[:] = (self.V < self.V_th) * (V >= self.V_th) - self.V[:] = V - self.m[:] = m - self.h[:] = h - self.n[:] = n - self.input[:] = 0. - - -def run_hh1(): - group = bp.math.jit(HH(int(1e4), monitors=['V'])) - - group.run(200., inputs=('input', 10.), report=0.1) - bp.visualize.line_plot(group.mon.ts, group.mon.V, show=True) - - group.run(200., report=0.1) - bp.visualize.line_plot(group.mon.ts, group.mon.V, show=True) - - -def run_hh2(): - group = HH(100, monitors=bp.Monitor(variables=['V'], intervals=[1.])) - - group.run(200. * 50, inputs=('input', 10.), report=True) - bp.visualize.line_plot(group.mon.ts, group.mon.V, show=True) - - -if __name__ == '__main__': - # run_hh2() - run_hh1() - # run_hh_with_interval_monitor() diff --git a/examples/neurons/HindmarshRose_model.py b/examples/neurons/HindmarshRose_model.py deleted file mode 100644 index bb191dd9..00000000 --- a/examples/neurons/HindmarshRose_model.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- - -import brainpy as bp - - -class HRNeuron(bp.NeuGroup): - def __init__(self, num, a=1., b=3., c=1., d=5., s=4., x_r=-1.6, r=0.001, - **kwargs): - - def dev_hr(x, y, z, t, Isyn): - dx = y - a * x ** 3 + b * x * x - z + Isyn - dy = c - d * x * x - y - dz = r * (s * (x - x_r) - z) - return dx, dy, dz - - self.int_hr = bp.odeint(f=dev_hr, method='rk4', dt=0.02) - - super(HRNeuron, self).__init__(size=num, **kwargs) - - def update(self, _t, _i): - pass - - -hr = HRNeuron(1) - -analyzer = bp.analysis.FastSlowBifurcation( - integrals=hr.int_hr, - fast_vars={'x': [-3, 3], 'y': [-10., 5.]}, - slow_vars={'z': [-5., 5.]}, - pars_update={'Isyn': 0.5}, - numerical_resolution=0.001 -) -analyzer.plot_bifurcation() -analyzer.plot_trajectory([{'x': 1., 'y': 0., 'z': -0.0}], - duration=300., - show=True) - diff --git a/examples/neurons/LIF_model.py b/examples/neurons/LIF_model.py deleted file mode 100644 index 1bd71a38..00000000 --- a/examples/neurons/LIF_model.py +++ /dev/null @@ -1,51 +0,0 @@ -# -*- coding: utf-8 -*- - - -import brainpy as bp - -# bp.math.use_backend('jax') - - -class LIF(bp.NeuGroup): - def __init__(self, size, V_L=-70., V_reset=-70., V_th=-50., - Cm=0.5, gL=0.025, t_refractory=2., **kwargs): - super(LIF, self).__init__(size=size, **kwargs) - - self.V_L = V_L - self.V_reset = V_reset - self.V_th = V_th - self.Cm = Cm - self.gL = gL - self.t_refractory = t_refractory - - self.V = bp.math.Variable(bp.math.ones(self.num) * V_L) - self.input = bp.math.Variable(bp.math.zeros(self.num)) - self.spike = bp.math.Variable(bp.math.zeros(self.num, dtype=bool)) - self.refractory = bp.math.Variable(bp.math.zeros(self.num, dtype=bool)) - self.t_last_spike = bp.math.Variable(bp.math.ones(self.num) * -1e7) - - @bp.odeint - def integral(self, V, t, Iext): - dVdt = (- self.gL * (V - self.V_L) - Iext) / self.Cm - return dVdt - - def update(self, _t, _i): - ref = (_t - self.t_last_spike) <= self.t_refractory - V = self.integral(self.V, _t, self.input) - V = bp.math.where(ref, self.V, V) - spike = (V >= self.V_th) - self.V[:] = bp.math.where(spike, self.V_reset, V) - self.spike[:] = spike - self.t_last_spike[:] = bp.math.where(spike, _t, self.t_last_spike) - self.refractory[:] = bp.math.logical_or(spike, ref) - self.input[:] = 0. - - -if __name__ == '__main__': - group = bp.math.jit(LIF(100, monitors=['V'])) - - group.run(duration=200., inputs=('input', -1.), report=0.1) - bp.visualize.line_plot(group.mon.ts, group.mon.V, show=True) - - group.run(duration=(200, 400.), report=0.1) - bp.visualize.line_plot(group.mon.ts, group.mon.V, show=True) diff --git a/examples/neurons/LIF_model2.py b/examples/neurons/LIF_model2.py deleted file mode 100644 index e957be55..00000000 --- a/examples/neurons/LIF_model2.py +++ /dev/null @@ -1,143 +0,0 @@ -# -*- coding: utf-8 -*- -import jax.lax - -import brainpy as bp - -bp.math.use_backend('jax') - - -class LIF(bp.NeuGroup): - def __init__(self, size, t_refractory=1., V_rest=0., - V_reset=-5., V_th=20., R=1., tau=10., **kwargs): - # parameters - self.V_rest = V_rest - self.V_reset = V_reset - self.V_th = V_th - self.R = R - self.tau = tau - self.t_refractory = t_refractory - - # variables - self.V = bp.math.ones(size) * V_reset - self.input = bp.math.zeros(size) - self.t_last_spike = bp.math.ones(size) * -1e7 - self.spike = bp.math.zeros(size, dtype=bool) - self.refractory = bp.math.zeros(size, dtype=bool) - - super(LIF, self).__init__(size=size, **kwargs) - - @bp.odeint - def int_V(self, V, t, Iext): - return (- (V - self.V_rest) + self.R * Iext) / self.tau - - @bp.math.control_transform - def update(self, _t, _i): - for i in range(self.num): - if _t - self.t_last_spike[i] <= self.t_refractory: - self.refractory[i] = True - else: - V = self.int_V(self.V[i], _t, self.input[i]) - if V >= self.V_th: - self.V[i] = self.V_reset - self.spike[i] = 1. - self.t_last_spike[i] = _t - self.refractory[i] = True - else: - self.spike[i] = 0. - self.V[i] = V - self.refractory[i] = False - self.input[i] = 0. - - -class LIF2(bp.NeuGroup): - def __init__(self, size, t_refractory=1., V_rest=0., - V_reset=-5., V_th=20., R=1., tau=10., **kwargs): - # parameters - self.V_rest = V_rest - self.V_reset = V_reset - self.V_th = V_th - self.R = R - self.tau = tau - self.t_refractory = t_refractory - - # variables - self.V = bp.math.ones(size) * V_reset - self.input = bp.math.zeros(size) - self.t_last_spike = bp.math.ones(size) * -1e7 - self.spike = bp.math.zeros(size, dtype=bool) - self.refractory = bp.math.zeros(size, dtype=bool) - - super(LIF2, self).__init__(size=size, **kwargs) - - @bp.odeint - def int_V(self, V, t, Iext): - return (- (V - self.V_rest) + self.R * Iext) / self.tau - - @bp.math.control_transform - def update(self, _t, _i): - - def true_func2(val2): - i, V, _V = val2 - _V['V'][i] = _V['V_reset'] - _V['spike'][i] = True - _V['t_last_spike'][i] = _V['_t'] - _V['refractory'][i] = True - return _V - - def false_func2(val2): - i, V, _V = val2 - _V['spike'][i] = False - _V['V'][i] = V - _V['refractory'][i] = False - return _V - - def false_func1(val2): - i, _V = val2 - V = self.int_V(_V['V'][i], _V['_t'], _V['input'][i]) - _V = jax.lax.cond(V >= _V['V_th'], true_func2, false_func2, (i, V, _V)) - return _V - - def true_fun1(val2): - i, _V = val2 - _V['refractory'][i] = True - return _V - - def loop_func(i, val): - val = jax.lax.cond(val['_t'] - val['t_last_spike'][i] <= val['t_refractory'], - true_fun1, - false_func1, - (i, val)) - val['input'][i] = 0. - return val - - loop_val = dict(t_last_spike=self.t_last_spike, - t_refractory=self.t_refractory, - refractory=self.refractory, - V_th=self.V_th, - V=self.V, - input=self.input, - V_reset=self.V_reset, - spike=self.spike, - _t=_t, - _i=_i) - loop_val = jax.lax.fori_loop(0, self.num, loop_func, loop_val) - self.t_last_spike.value = loop_val['t_last_spike'] - self.refractory.value = loop_val['refractory'] - self.V.value = loop_val['V'] - self.input.value = loop_val['input'] - self.spike.value = loop_val['spike'] - # Problems - # 1. where value is the ndarray - # 2. - - -if __name__ == '__main__': - # group = LIF(100, monitors=['V']) - group = LIF2(100, monitors=['V']) - group = bp.math.jit(group) - - group.run(duration=200., inputs=('input', 26.), report=0.1) - bp.visualize.line_plot(group.mon.ts, group.mon.V, show=True) - - group.run(duration=(200, 400.), report=0.1) - bp.visualize.line_plot(group.mon.ts, group.mon.V, show=True) diff --git a/examples/recurrent_neural_network/img/Slide4.jpg b/examples/recurrent_neural_network/img/Slide4.jpg deleted file mode 100644 index 7918bd4830ec9c3a3ed4bcf908dc77142c138706..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40909 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 K53d*gG4f9Y0%Sk{ diff --git a/examples/recurrent_neural_network/img/Slide5.jpg b/examples/recurrent_neural_network/img/Slide5.jpg deleted file mode 100644 index d8de76cd0ad17e083e5de473994635abbabf68ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59899 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 (Batch Size, Sequence Length, One-Hot Encoding Size)\n" - ] - } - ], - "source": [ - "input_seq = one_hot_encode(input_seq, dict_size, seq_len, batch_size)\n", - "print(\"Input shape: {} --> (Batch Size, Sequence Length, One-Hot Encoding Size)\".format(input_seq.shape))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Since we're done with all the data pre-processing, we can now move the data from numpy arrays to PyTorch's very own data structure - **Torch Tensors**" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [], - "source": [ - "input_seq = torch.from_numpy(input_seq)\n", - "target_seq = torch.Tensor(target_seq)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now we've reached the fun part of this project! We'll be defining the model using the Torch library, and this is where you can add or remove layers, be it fully connected layers, convolutational layers, vanilla RNN layers, LSTM layers, and many more! In this post, we'll be using the basic nn.rnn to demonstrate a simple example of how RNNs can be used.\n", - "\n", - "Before we start building the model, let's use a build in feature in PyTorch to check the device we're running on (CPU or GPU). This implementation will not require GPU as the training is really simple. However, as you progress on to large datasets and models with millions of trainable parameters, using the GPU will be very important to speed up your training." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "GPU is available\n" - ] - } - ], - "source": [ - "# torch.cuda.is_available() checks and returns a Boolean True if a GPU is available, else it'll return False\n", - "is_cuda = torch.cuda.is_available()\n", - "\n", - "# If we have a GPU available, we'll set our device to GPU. We'll use this device variable later in our code.\n", - "if is_cuda:\n", - " device = torch.device(\"cuda\")\n", - " print(\"GPU is available\")\n", - "else:\n", - " device = torch.device(\"cpu\")\n", - " print(\"GPU not available, CPU used\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To start building our own neural network model, we can define a class that inherits PyTorch’s base class (nn.module) for all neural network modules. After doing so, we can start defining some variables and also the layers for our model under the constructor. For this model, we’ll only be using 1 layer of RNN followed by a fully connected layer. The fully connected layer will be in-charge of converting the RNN output to our desired output shape.\n", - "\n", - "We’ll also have to define the forward pass function under forward() as a class method. The order the forward function is sequentially executed, therefore we’ll have to pass the inputs and the zero-initialized hidden state through the RNN layer first, before passing the RNN outputs to the fully-connected layer. Note that we are using the layers that we defined in the constructor.\n", - "\n", - "The last method that we have to define is the method that we called earlier to initialize the hidden state - init_hidden(). This basically creates a tensor of zeros in the shape of our hidden states." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [], - "source": [ - "class Model(nn.Module):\n", - " def __init__(self, input_size, output_size, hidden_dim, n_layers):\n", - " super(Model, self).__init__()\n", - "\n", - " # Defining some parameters\n", - " self.hidden_dim = hidden_dim\n", - " self.n_layers = n_layers\n", - "\n", - " # Defining the layers\n", - " # RNN Layer\n", - " self.rnn = nn.RNN(input_size, hidden_dim, n_layers, batch_first=True)\n", - " # Fully connected layer\n", - " self.fc = nn.Linear(hidden_dim, output_size)\n", - "\n", - " def forward(self, x):\n", - " batch_size = x.size(0)\n", - "\n", - " # Initializing hidden state for first input using method defined below\n", - " hidden = self.init_hidden(batch_size)\n", - "\n", - " # Passing in the input and hidden state into the model and obtaining outputs\n", - " out, hidden = self.rnn(x, hidden)\n", - "\n", - " # Reshaping the outputs such that it can be fit into the fully connected layer\n", - " out = out.contiguous().view(-1, self.hidden_dim)\n", - " out = self.fc(out)\n", - "\n", - " return out, hidden\n", - "\n", - " def init_hidden(self, batch_size):\n", - " # This method generates the first hidden state of zeros which we'll use in the forward pass\n", - " hidden = torch.zeros(self.n_layers, batch_size, self.hidden_dim).to(device)\n", - " # We'll send the tensor holding the hidden state to the device we specified earlier as well\n", - " return hidden" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "After defining the model above, we'll have to instantiate the model with the relevant parameters and define our hyperparamters as well. The hyperparameters we're defining below are:\n", - "\n", - "- *n_epochs*: Number of Epochs --> This refers to the number of times our model will go through the entire training dataset\n", - "- *lr*: Learning Rate --> This affects the rate at which our model updates the weights in the cells each time backpropogation is done\n", - " - A smaller learning rate means that the model changes the values of the weight with a smaller magnitude\n", - " - A larger learning rate means that the weights are updated to a larger extent for each time step\n", - "\n", - "Similar to other neural networks, we have to define the optimizer and loss function as well. We’ll be using CrossEntropyLoss as the final output is basically a classification task." - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": {}, - "outputs": [], - "source": [ - "# Instantiate the model with hyperparameters\n", - "model = Model(input_size=dict_size, output_size=dict_size, hidden_dim=12, n_layers=1)\n", - "# We'll also set the model to the device that we defined earlier (default is CPU)\n", - "model = model.to(device)\n", - "\n", - "# Define hyperparameters\n", - "n_epochs = 100\n", - "lr = 0.01\n", - "\n", - "# Define Loss, Optimizer\n", - "criterion = nn.CrossEntropyLoss()\n", - "optimizer = torch.optim.Adam(model.parameters(), lr=lr)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now we can begin our training! As we only have a few sentences, this training process is very fast. However, as we progress, larger datasets and deeper models mean that the input data is much larger and the number of parameters within the model that we have to compute is much more." - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": { - "scrolled": true - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Epoch: 10/100............. Loss: 2.4076\n", - "Epoch: 20/100............. Loss: 2.1100\n", - "Epoch: 30/100............. Loss: 1.6700\n", - "Epoch: 40/100............. Loss: 1.2411\n", - "Epoch: 50/100............. Loss: 0.8873\n", - "Epoch: 60/100............. Loss: 0.6129\n", - "Epoch: 70/100............. Loss: 0.4116\n", - "Epoch: 80/100............. Loss: 0.2827\n", - "Epoch: 90/100............. Loss: 0.2038\n", - "Epoch: 100/100............. Loss: 0.1561\n" - ] - } - ], - "source": [ - "# Training Run\n", - "input_seq = input_seq.to(device)\n", - "for epoch in range(1, n_epochs + 1):\n", - " optimizer.zero_grad() # Clears existing gradients from previous epoch\n", - " # input_seq = input_seq.to(device)\n", - " output, hidden = model(input_seq)\n", - " output = output.to(device)\n", - " target_seq = target_seq.to(device)\n", - " loss = criterion(output, target_seq.view(-1).long())\n", - " loss.backward() # Does backpropagation and calculates gradients\n", - " optimizer.step() # Updates the weights accordingly\n", - "\n", - " if epoch % 10 == 0:\n", - " print('Epoch: {}/{}.............'.format(epoch, n_epochs), end=' ')\n", - " print(\"Loss: {:.4f}\".format(loss.item()))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let’s test our model now and see what kind of output we will get. Before that, let’s define some helper function to convert our model output back to text." - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "metadata": {}, - "outputs": [], - "source": [ - "def predict(model, character):\n", - " # One-hot encoding our input to fit into the model\n", - " character = np.array([[char2int[c] for c in character]])\n", - " character = one_hot_encode(character, dict_size, character.shape[1], 1)\n", - " character = torch.from_numpy(character)\n", - " character = character.to(device)\n", - "\n", - " out, hidden = model(character)\n", - "\n", - " prob = nn.functional.softmax(out[-1], dim=0).data\n", - " # Taking the class with the highest probability score from the output\n", - " char_ind = torch.max(prob, dim=0)[1].item()\n", - "\n", - " return int2char[char_ind], hidden" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "metadata": {}, - "outputs": [], - "source": [ - "def sample(model, out_len, start='hey'):\n", - " model.eval() # eval mode\n", - " start = start.lower()\n", - " # First off, run through the starting characters\n", - " chars = [ch for ch in start]\n", - " size = out_len - len(chars)\n", - " # Now pass in the previous characters and get a new one\n", - " for ii in range(size):\n", - " char, h = predict(model, chars)\n", - " chars.append(char)\n", - "\n", - " return ''.join(chars)" - ] - }, - { - "cell_type": "code", - "execution_count": 30, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'good i am fine '" - ] - }, - "execution_count": 30, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "sample(model, 15, 'good')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As we can see, the model is able to come up with the sentence ‘good i am fine ‘ if we feed it with the words ‘good’, achieving what we intended for it to do!" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "jupytext": { - "encoding": "# -*- coding: utf-8 -*-", - "formats": "ipynb,py:percent" - }, - "kernelspec": { - "display_name": "brainpy", - "language": "python", - "name": "brainpy" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.11" - }, - "latex_envs": { - "LaTeX_envs_menu_present": true, - "autoclose": false, - "autocomplete": true, - "bibliofile": "biblio.bib", - "cite_by": "apalike", - "current_citInitial": 1, - "eqLabelWithNumbers": true, - "eqNumInitial": 1, - "hotkeys": { - "equation": "Ctrl-E", - "itemize": "Ctrl-I" - }, - "labels_anchors": false, - "latex_user_defs": false, - "report_style_numbering": false, - "user_envs_cfg": false - }, - "toc": { - "base_numbering": 1, - "nav_menu": {}, - "number_sections": true, - "sideBar": true, - "skip_h1_title": false, - "title_cell": "Table of Contents", - "title_sidebar": "Contents", - "toc_cell": false, - "toc_position": {}, - "toc_section_display": true, - "toc_window_display": true - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/examples/recurrent_neural_network/rnn_demo.py b/examples/recurrent_neural_network/rnn_demo.py deleted file mode 100644 index 3a748054..00000000 --- a/examples/recurrent_neural_network/rnn_demo.py +++ /dev/null @@ -1,328 +0,0 @@ -# -*- coding: utf-8 -*- -# --- -# jupyter: -# jupytext: -# formats: ipynb,py:percent -# text_representation: -# extension: .py -# format_name: percent -# format_version: '1.3' -# jupytext_version: 1.11.4 -# kernelspec: -# display_name: brainpy -# language: python -# name: brainpy -# --- - -# %% [markdown] -# # Recurrent Neural Network Demo - -# %% [markdown] -# Thanks to: https://github.com/gabrielloye/RNN-walkthrough - -# %% [markdown] -# In this implementation, we'll be building a model that can complete your sentence based on a few characters or a word used as input. -# -# ![Example](img/Slide4.jpg) -# -# To keep this short and simple, we won't be using any large or external datasets. Instead, we'll just be defining a few sentences to see how the model learns from these sentences. The process that this implementation will take is as follows: -# -# ![Overview](img/Slide5.jpg) - -# %% -import sys - -import jax.lax - -sys.path.append('../../') - -import brainpy as bp -import numpy as np - -# %% [markdown] -# First, we'll define the sentences that we want our model to output when fed with the first word or the first few characters. -# -# Then we'll create a dictionary out of all the characters that we have in the sentences and map them to an integer. This will allow us to convert our input characters to their respective integers (*char2int*) and vice versa (*int2char*). - -# %% -text = ['hey how are you good i am fine', - 'good i am fine have a nice day', - 'The cell then uses gates to regulate the information to be kept or discarded at ' - 'each time step before passing on the long-term and short-term information to the ' - 'next cell'.lower(), - 'This repo holds the code for the implementation in my FloydHub article on RNNs'.lower(), - 'The secret sauce to the LSTM lies in its gating mechanism within each LSTM cell'.lower(), - 'the input at a time-step and the hidden state from the previous time step is ' - 'passed through a tanh activation function'.lower()] -text = ['hey how are you', - 'good i am fine', - 'have a nice day'] - -# Join all the sentences together and extract the unique characters from the combined sentences -chars = set(''.join(text)) - -# Creating a dictionary that maps integers to the characters -int2char = dict(enumerate(chars)) - -# Creating another dictionary that maps characters to integers -char2int = {char: ind for ind, char in int2char.items()} - -# %% -print(char2int) - -# %% [markdown] -# Next, we'll be padding our input sentences to ensure that all the sentences are of the sample length. While RNNs are typically able to take in variably sized inputs, we will usually want to feed training data in batches to speed up the training process. In order to used batches to train on our data, we'll need to ensure that each sequence within the input data are of equal size. -# -# Therefore, in most cases, padding can be done by filling up sequences that are too short with **0** values and trimming sequences that are too long. In our case, we'll be finding the length of the longest sequence and padding the rest of the sentences with blank spaces to match that length. - -# %% -maxlen = len(max(text, key=len)) -print("The longest string has {} characters".format(maxlen)) - -# %% -# Padding - -# A simple loop that loops through the list of sentences and adds -# a ' ' whitespace until the length of the sentence matches the -# length of the longest sentence -for i in range(len(text)): - while len(text[i]) < maxlen: - text[i] += ' ' - -# %% [markdown] -# As we're going to predict the next character in the sequence at each time step, we'll have to divide each sentence into -# -# - Input data -# - The last input character should be excluded as it does not need to be fed into the model -# - Target/Ground Truth Label -# - One time-step ahead of the Input data as this will be the "correct answer" for the model at each time step corresponding to the input data - -# %% -# Creating lists that will hold our input and target sequences -input_seq = [] -target_seq = [] - -for i in range(len(text)): - # Remove last character for input sequence - input_seq.append(text[i][:-1]) - - # Remove firsts character for target sequence - target_seq.append(text[i][1:]) - print("Input Sequence: {}\nTarget Sequence: {}".format(input_seq[i], target_seq[i])) - -# %% [markdown] -# Now we can convert our input and target sequences to sequences of integers instead of characters by mapping them using the dictionaries we created above. This will allow us to one-hot-encode our input sequence subsequently. - -# %% -for i in range(len(text)): - input_seq[i] = [char2int[character] for character in input_seq[i]] - target_seq[i] = [char2int[character] for character in target_seq[i]] - -# %% [markdown] -# Before encoding our input sequence into one-hot vectors, we'll define 3 key variables: -# -# - *dict_size*: The number of unique characters that we have in our text -# - This will determine the one-hot vector size as each character will have an assigned index in that vector -# - *seq_len*: The length of the sequences that we're feeding into the model -# - As we standardised the length of all our sentences to be equal to the longest sentences, this value will be the max length - 1 as we removed the last character input as well -# - *batch_size*: The number of sentences that we defined and are going to feed into the model as a batch - -# %% -dict_size = len(char2int) -seq_len = maxlen - 1 -batch_size = len(text) - - -def one_hot_encode(sequence, dict_size, seq_len, batch_size): - # Creating a multi-dimensional array of zeros with the desired output shape - features = np.zeros((batch_size, seq_len, dict_size), dtype=np.float32) - - # Replacing the 0 at the relevant character index with a 1 to represent that character - for i in range(batch_size): - for u in range(seq_len): - features[i, u, sequence[i][u]] = 1 - return features - - -# %% [markdown] -# We also defined a helper function that creates arrays of zeros for each character -# and replaces the corresponding character index with a **1**. - -# %% -input_seq = one_hot_encode(input_seq, dict_size, seq_len, batch_size) -print("Input shape: {} --> (Batch Size, Sequence Length, One-Hot Encoding Size)".format(input_seq.shape)) - -# %% [markdown] -# Since we're done with all the data pre-processing, we can now move the data from -# numpy arrays to PyTorch's very own data structure - **Torch Tensors** - -# %% -input_seq = bp.math.array(input_seq) -target_seq = bp.math.array(target_seq) - - -# %% [markdown] -# Now we've reached the fun part of this project! We'll be defining the model using the -# Torch library, and this is where you can add or remove layers, be it fully connected -# layers, convolutational layers, vanilla RNN layers, LSTM layers, and many more! In -# this post, we'll be using the basic nn.rnn to demonstrate a simple example of how RNNs -# can be used. -# -# Before we start building the model, let's use a build in feature in PyTorch to check -# the device we're running on (CPU or GPU). This implementation will not require GPU as -# the training is really simple. However, as you progress on to large datasets and models -# with millions of trainable parameters, using the GPU will be very important to speed up -# your training. - - -# %% [markdown] -# To start building our own neural network model, we can define a class that inherits -# PyTorch’s base class (nn.module) for all neural network modules. After doing so, we -# can start defining some variables and also the layers for our model under the constructor. -# For this model, we’ll only be using 1 layer of RNN followed by a fully connected layer. -# The fully connected layer will be in-charge of converting the RNN output to our desired -# output shape. -# -# We’ll also have to define the forward pass function under forward() as a class method. -# The order the forward function is sequentially executed, therefore we’ll have to pass -# the inputs and the zero-initialized hidden state through the RNN layer first, before -# passing the RNN outputs to the fully-connected layer. Note that we are using the layers -# that we defined in the constructor. -# -# The last method that we have to define is the method that we called earlier to initialize -# the hidden state - init_hidden(). This basically creates a tensor of zeros in the shape -# of our hidden states. - -# %% -class Model(bp.dnn.Module): - def __init__(self, input_size, output_size, hidden_dim, n_layers): - super(Model, self).__init__() - - # Defining some parameters - self.hidden_dim = hidden_dim - self.n_layers = n_layers - - # Defining the layers - # RNN Layer - self.w_ir = bp.math.TrainVar(bp.math.random.random((input_size, hidden_dim))) - self.w_rr = bp.math.TrainVar(bp.math.random.random((hidden_dim, hidden_dim))) - self.b_rr = bp.math.TrainVar(bp.math.zeros((hidden_dim,))) - # Fully connected layer - self.w_ro = bp.math.TrainVar(bp.math.random.random((hidden_dim, output_size))) - self.b_ro = bp.math.TrainVar(bp.math.zeros((output_size,))) - - def __call__(self, x): - def scan_fun(hidden, x): - hidden = bp.dnn.relu(x @ self.w_ir + hidden @ self.w_rr + self.b_rr) - return hidden, hidden - - @jax.partial(jax.vmap, in_axes=(None, 0)) - def readout(params, hidden): - return hidden @ params['w_ro'] + params['b_ro'] - - init_hidden = bp.math.zeros((x.shape[0], self.hidden_dim)) - _, hist_hidden = jax.lax.scan(scan_fun, init_hidden, x.transpose(1, 0, 2)) - outputs = readout(dict(w_ro=self.w_ro, b_ro=self.b_ro), hist_hidden) - return outputs.transpose(1, 0, 2) - - -# %% [markdown] -# After defining the model above, we'll have to instantiate the model with the -# relevant parameters and define our hyperparamters as well. The hyperparameters -# we're defining below are: -# -# - *n_epochs*: Number of Epochs --> This refers to the number of times our model will go through the entire training dataset -# - *lr*: Learning Rate --> This affects the rate at which our model updates the weights in the cells each time backpropogation is done -# - A smaller learning rate means that the model changes the values of the weight with a smaller magnitude -# - A larger learning rate means that the weights are updated to a larger extent for each time step -# -# Similar to other neural networks, we have to define the optimizer and loss function -# as well. We’ll be using CrossEntropyLoss as the final output is basically a classification task. - -# %% -# Instantiate the model with hyperparameters -model = Model(input_size=dict_size, output_size=dict_size, hidden_dim=12, n_layers=1) - -# Define hyperparameters -n_epochs = 100 -lr = 0.01 - -# Define Loss, Optimizer -optimizer = bp.dnn.Adam(lr=lr, train_vars=model.train_vars()) - - -# %% -@bp.math.function(nodes=(model, optimizer)) -def loss(x, y): - outputs = model(x) - loss = bp.dnn.cross_entropy_loss(outputs, y) - return loss - - -vg = bp.math.value_and_grad(loss) - - -@bp.math.jit -@bp.math.function(nodes=(model, optimizer)) -def train(x, y): - loss, grads = vg(x, y) - optimizer(grads) - return loss - - -# %% [markdown] -# Now we can begin our training! As we only have a few sentences, this training -# process is very fast. However, as we progress, larger datasets and deeper -# models mean that the input data is much larger and the number of parameters -# within the model that we have to compute is much more. - -# %% -# Training Run -for epoch in range(1, n_epochs + 1): - loss = train(input_seq, target_seq) - - if epoch % 10 == 0: - print('Epoch: {}/{}.............'.format(epoch, n_epochs), end=' ') - print("Loss: {:.4f}".format(loss)) - - -# %% [markdown] -# Let’s test our model now and see what kind of output we will get. -# Before that, let’s define some helper function to convert our model -# output back to text. - -# %% -def predict(model, character): - # One-hot encoding our input to fit into the model - character = np.array([[char2int[c] for c in character]]) - character = one_hot_encode(character, dict_size, character.shape[1], 1) - character = bp.math.array(character) - - out = model(character) - - prob = bp.dnn.softmax(out[-1], axis=0) - # Taking the class with the highest probability score from the output - char_ind = bp.math.max(prob, axis=0)[1] - return int2char[int(char_ind)] - - -# %% -def sample(model, out_len, start='hey'): - start = start.lower() - # First off, run through the starting characters - chars = [ch for ch in start] - size = out_len - len(chars) - # Now pass in the previous characters and get a new one - for ii in range(size): - char = predict(model, chars) - chars.append(char) - - return ''.join(chars) - - -# %% -print(sample(model, 15, 'good')) - -# %% [markdown] -# As we can see, the model is able to come up with the sentence ‘good i am fine ‘ -# if we feed it with the words ‘good’, achieving what we intended for it to do! diff --git a/examples/synapses/AMPA_synapse.py b/examples/synapses/AMPA_synapse.py deleted file mode 100644 index cb19af25..00000000 --- a/examples/synapses/AMPA_synapse.py +++ /dev/null @@ -1,145 +0,0 @@ -# -*- coding: utf-8 -*- - -import brainpy as bp - -bp.math.use_backend('numpy') -bp.math.set_dt(0.01) -bp.integrators.set_default_odeint('rk4') - - -class HH(bp.NeuGroup): - def __init__(self, size, ENa=50., EK=-77., EL=-54.387, - C=1.0, gNa=120., gK=36., gL=0.03, V_th=20., - **kwargs): - super(HH, self).__init__(size=size, **kwargs) - - # parameters - self.ENa = ENa - self.EK = EK - self.EL = EL - self.C = C - self.gNa = gNa - self.gK = gK - self.gL = gL - self.V_th = V_th - - # variables - self.V = bp.math.Variable(bp.math.ones(self.num) * -65.) - self.m = bp.math.Variable(bp.math.ones(self.num) * 0.5) - self.h = bp.math.Variable(bp.math.ones(self.num) * 0.6) - self.n = bp.math.Variable(bp.math.ones(self.num) * 0.32) - self.spike = bp.math.Variable(bp.math.zeros(self.num, dtype=bool)) - self.input = bp.math.Variable(bp.math.zeros(self.num)) - - # @bp.odeint(method='exponential_euler') - @bp.odeint(method='rk4') - def integral(self, V, m, h, n, t, Iext): - alpha = 0.1 * (V + 40) / (1 - bp.math.exp(-(V + 40) / 10)) - beta = 4.0 * bp.math.exp(-(V + 65) / 18) - dmdt = alpha * (1 - m) - beta * m - - alpha = 0.07 * bp.math.exp(-(V + 65) / 20.) - beta = 1 / (1 + bp.math.exp(-(V + 35) / 10)) - dhdt = alpha * (1 - h) - beta * h - - alpha = 0.01 * (V + 55) / (1 - bp.math.exp(-(V + 55) / 10)) - beta = 0.125 * bp.math.exp(-(V + 65) / 80) - dndt = alpha * (1 - n) - beta * n - - I_Na = (self.gNa * m ** 3.0 * h) * (V - self.ENa) - I_K = (self.gK * n ** 4.0) * (V - self.EK) - I_leak = self.gL * (V - self.EL) - dVdt = (- I_Na - I_K - I_leak + Iext) / self.C - - return dVdt, dmdt, dhdt, dndt - - def update(self, _t, _i): - V, m, h, n = self.integral(self.V, self.m, self.h, self.n, _t, self.input) - self.spike[:] = bp.math.logical_and(self.V < self.V_th, V >= self.V_th) - self.V[:] = V - self.m[:] = m - self.h[:] = h - self.n[:] = n - self.input[:] = 0. - - -class AMPA_vec(bp.TwoEndConn): - def __init__(self, pre, post, conn, delay=0., g_max=0.10, E=0., tau=2.0, **kwargs): - super(AMPA_vec, self).__init__(pre=pre, post=post, **kwargs) - - # parameters - self.g_max = g_max - self.E = E - self.tau = tau - self.delay = delay - - # connections - self.conn = conn(pre.size, post.size) - self.pre_ids, self.post_ids = conn.requires('pre_ids', 'post_ids') - self.size = len(self.pre_ids) - - # data - self.s = bp.math.Variable(bp.math.zeros(self.size)) - self.g = self.register_constant_delay('g', size=self.size, delay=delay) - - @bp.odeint(method='euler') - def int_s(self, s, t): - return - s / self.tau - - def update(self, _t, _i): - g = self.g.pull() - for i in range(self.size): - pre_id = self.pre_ids[i] - self.s[i] = self.int_s(self.s[i], _t) - self.s[i] += self.pre.spike[pre_id] - post_id = self.post_ids[i] - self.post.input[post_id] -= g[i] * (self.post.V[post_id] - self.E) - self.g.push(self.g_max * self.s) - - -class AMPA_mat(bp.TwoEndConn): - def __init__(self, pre, post, conn, delay=0., g_max=0.10, E=0., tau=2.0, **kwargs): - super(AMPA_mat, self).__init__(pre=pre, post=post, **kwargs) - - # parameters - self.g_max = g_max - self.E = E - self.tau = tau - self.delay = delay - - # connections - self.conn = conn(pre.size, post.size) - self.conn_mat = conn.requires('conn_mat') - self.size = bp.math.shape(self.conn_mat) - - # variables - self.s = bp.math.Variable(bp.math.zeros(self.size)) - self.g = self.register_constant_delay('g', size=self.size, delay=delay) - - @bp.odeint - def int_s(self, s, t): - return - s / self.tau - - def update(self, _t, _i): - self.s[:] = self.int_s(self.s, _t) - for i in range(self.pre.size[0]): - if self.pre.spike[i] > 0: - self.s[i] += self.conn_mat[i] - self.g.push(self.g_max * self.s) - g = self.g.pull() - self.post.input[:] -= bp.math.sum(g, axis=0) * (self.post.V - self.E) - - -if __name__ == '__main__': - hh = HH(100, monitors=['V'], name='X') - ampa = AMPA_vec(pre=hh, post=hh, conn=bp.connect.All2All(), delay=10., monitors=['s']) - # ampa = AMPA_mat(pre=hh, post=hh, conn=bp.connect.All2All(), delay=10., monitors=['s']) - net = bp.Network(hh, ampa) - net = bp.math.jit(net, show_code=True) - net.run(100., inputs=('X.input', 10.), report=0.1) - - fig, gs = bp.visualize.get_figure(row_num=2, col_num=1, ) - fig.add_subplot(gs[0, 0]) - bp.visualize.line_plot(hh.mon.ts, hh.mon.V) - fig.add_subplot(gs[1, 0]) - bp.visualize.line_plot(ampa.mon.ts, ampa.mon.s, show=True) diff --git a/examples/synapses/gap_junction.py b/examples/synapses/gap_junction.py deleted file mode 100644 index 111a993d..00000000 --- a/examples/synapses/gap_junction.py +++ /dev/null @@ -1,140 +0,0 @@ -# -*- coding: utf-8 -*- - -import jax - -import brainpy as bp - -bp.math.use_backend('jax') - - -class GapJunction(bp.TwoEndConn): - def __init__(self, pre, post, conn, g_max=1., **kwargs): - super(GapJunction, self).__init__(pre=pre, post=post, conn=conn, **kwargs) - - # connections - self.pre_ids, self.post_ids = conn.requires('pre_ids', 'post_ids') - self.num = len(self.pre_ids) - - # parameters - self.g_max = bp.math.ones(self.num) * g_max - - # checking - assert hasattr(pre, 'V'), 'Pre-synaptic group must has "V" variable.' - assert hasattr(post, 'V'), 'Post-synaptic group must has "V" variable.' - assert hasattr(post, 'input'), 'Post-synaptic group must has "input" variable.' - - def update(self, _t, _i): - def loop_body(i, val): - pre_id = val['pre_ids'][i] - post_id = val['post_ids'][i] - diff = (val['pre_V'][pre_id] - val['post_V'][post_id]) - val['post_input'][post_id] += val['g_max'][i] * diff - return val - - val = jax.lax.fori_loop(0, self.num, loop_body, - dict(pre_ids=self.pre_ids, - post_ids=self.post_ids, - post_input=self.post.input, - pre_V=self.pre.V, - post_V=self.post.V, - g_max=self.g_max)) - self.post.input.value = val['post_input'] - - -class LifGapJunction(bp.TwoEndConn): - def __init__(self, pre, post, conn, g_max=0.1, k_spikelet=0.1, **kwargs): - super(LifGapJunction, self).__init__(pre=pre, post=post, conn=conn, **kwargs) - - # parameters - self.k_spikelet = k_spikelet - - # connections - self.conn = conn(pre.size, post.size) - self.pre_ids, self.post_ids = conn.requires('pre_ids', 'post_ids') - self.num = len(self.pre_ids) - - # variables - self.g_max = bp.math.ones(self.num) * g_max - - # checking - assert hasattr(pre, 'V'), 'Pre-synaptic group must has "V" variable.' - assert hasattr(post, 'V'), 'Post-synaptic group must has "V" variable.' - assert hasattr(post, 'input'), 'Post-synaptic group must has "input" variable.' - - def update(self, _t, _i): - def loop_body(i, val): - pre_id = val['pre_ids'][i] - post_id = val['post_ids'][i] - spikelet = val['g_max'][i] * val['k_spikelet'] * val['pre_spike'][pre_id] - diff = (val['pre_V'][pre_id] - val['post_V'][post_id]) - val['post_input'][post_id] += val['g_max'][i] * diff - val['post_V'][post_id] += spikelet * (1. - val['post_ref'][post_id]) - return val - - val = jax.lax.fori_loop(0, self.num, loop_body, - dict(pre_ids=self.pre_ids, - pre_spike=self.pre.spike, - post_input=self.post.input, - pre_V=self.pre.V, - post_ids=self.post_ids, - post_V=self.post.V, - post_ref=self.post.refractory, - g_max=self.g_max, - k_spikelet=self.k_spikelet)) - self.post.input.value = val['post_input'] - self.post.V.value = val['post_V'] - - -class LIF(bp.NeuGroup): - target_backend = 'general' - - @staticmethod - def derivative(V, t, Iext, V_rest, R, tau): - dvdt = (-V + V_rest + R * Iext) / tau - return dvdt - - def __init__(self, size, t_refractory=1., V_rest=0., - V_reset=-5., V_th=20., R=1., tau=10., **kwargs): - super(LIF, self).__init__(size=size, **kwargs) - - # parameters - self.V_rest = V_rest - self.V_reset = V_reset - self.V_th = V_th - self.R = R - self.tau = tau - self.t_refractory = t_refractory - - # variables - self.t_last_spike = bp.math.ones(self.num) * -1e7 - self.input = bp.math.zeros(self.num) - self.V = bp.math.ones(self.num) * V_rest - self.refractory = bp.math.zeros(self.num, dtype=bool) - self.spike = bp.math.zeros(self.num, dtype=bool) - - # integrator - self.integral = bp.odeint(self.derivative) - - def update(self, _t, _i): - refractory = (_t - self.t_last_spike) <= self.t_refractory - V = self.integral(self.V, _t, self.input, self.V_rest, self.R, self.tau) - V = bp.math.where(refractory, self.V, V) - spike = self.V_th <= V - self.t_last_spike[:] = bp.math.where(spike, _t, self.t_last_spike) - self.V[:] = bp.math.where(spike, self.V_reset, V) - self.refractory[:] = refractory | spike - self.input[:] = 0. - self.spike[:] = spike - - -def example(): - lif = LIF(100, monitors=['V'], name='X') - gj = LifGapJunction(lif, lif, bp.connect.FixedProb(0.2)) - net = bp.Network(lif=lif, gj=gj) - net = bp.math.jit(net) - - net.run(100., inputs=('X.input', 20.), report=0.2) - - -if __name__ == '__main__': - example() diff --git a/tests/math/numpy/test_ast2numba.py b/tests/math/numpy/test_ast2numba.py index 61b7d486..953388a5 100644 --- a/tests/math/numpy/test_ast2numba.py +++ b/tests/math/numpy/test_ast2numba.py @@ -40,7 +40,7 @@ def test_find_self_data1(): def test_transformer(): code = ''' -def update(self, _t, _i): +def update(self, _t, _dt): V, m, h, n = self.integral(self.V, self.m, self.h, self.n, _t, self.input) self.spike[:] = (self.V < self.V_th) * (V >= self.V_th) self.V[:] = V diff --git a/tests/simulation/connectivity/test_random_conn.py b/tests/simulation/connectivity/test_random_conn.py new file mode 100644 index 00000000..886604de --- /dev/null +++ b/tests/simulation/connectivity/test_random_conn.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- + +import brainpy as bp + + +def test_random_prob_numpy(): + bp.math.use_backend('numpy') + + conn1 = bp.connect.FixedProb(prob=0.1, method='iter', seed=123) + conn1(pre_size=(10, 20), post_size=(10, 20)) + + conn2 = bp.connect.FixedProb(prob=0.1, method='matrix', seed=123) + conn2(pre_size=(10, 20), post_size=(10, 20)) + + assert (conn2.pre_ids == conn1.pre_ids).all() + assert (conn2.post_ids == conn1.post_ids).all() + + +def test_random_prob_jax(): + bp.math.use_backend('jax') + + conn1 = bp.connect.FixedProb(prob=0.1, method='iter', seed=123) + conn1(pre_size=(10, 20), post_size=(10, 20)) + + conn1 = bp.connect.FixedProb(prob=0.1, method='matrix', seed=123) + conn1(pre_size=(10, 20), post_size=(10, 20)) + + +def test_random_fix_pre_jax(): + bp.math.use_backend('jax') + + for num in [0.4, 20]: + conn1 = bp.connect.FixedPreNum(num, method='iter', seed=1234) + conn1(pre_size=(10, 15), post_size=(10, 20)) + + conn2 = bp.connect.FixedPreNum(num, method='matrix', seed=1234) + conn2(pre_size=(10, 15), post_size=(10, 20)) + + +def test_random_fix_pre_numpy(): + bp.math.use_backend('numpy') + + for num in [0.4, 20]: + conn1 = bp.connect.FixedPreNum(num, method='iter', seed=1234) + conn1(pre_size=(10, 15), post_size=(10, 20)) + + conn2 = bp.connect.FixedPreNum(num, method='matrix', seed=1234) + conn2(pre_size=(10, 15), post_size=(10, 20)) + + assert (conn2.pre_ids == conn1.pre_ids).all() + assert (conn2.post_ids == conn1.post_ids).all() + + +def test_random_fix_post_jax(): + bp.math.use_backend('jax') + + for num in [0.4, 20]: + conn1 = bp.connect.FixedPostNum(num, method='iter', seed=1234) + conn1(pre_size=(10, 15), post_size=(10, 20)) + + conn2 = bp.connect.FixedPostNum(num, method='matrix', seed=1234) + conn2(pre_size=(10, 15), post_size=(10, 20)) + + +def test_random_fix_post_numpy(): + bp.math.use_backend('numpy') + + for num in [0.4, 20]: + conn1 = bp.connect.FixedPostNum(num, method='iter', seed=1234) + conn1(pre_size=(10, 15), post_size=(10, 20)) + + conn2 = bp.connect.FixedPostNum(num, method='matrix', seed=1234) + conn2(pre_size=(10, 15), post_size=(10, 20)) + + # assert (bp.math.sort(conn2.pre_ids) == bp.math.sort(conn1.pre_ids)).all() + assert (conn2.pre_ids == conn1.pre_ids).all() + assert (conn2.post_ids == conn1.post_ids).all() + + + + + diff --git a/tests/simulation/test_monitors_on_numpy.py b/tests/simulation/test_monitors_on_numpy.py new file mode 100644 index 00000000..8f1e9460 --- /dev/null +++ b/tests/simulation/test_monitors_on_numpy.py @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- + + +import numpy as np + +from brainpy.simulation.brainobjects import NeuGroup +from brainpy.simulation.monitors import Monitor + + +class TryGroup(NeuGroup): + target_backend = 'general' + + def __init__(self, **kwargs): + self.a = np.ones((2, 2)) + super(TryGroup, self).__init__(size=1, **kwargs) + + def update(self, _t): + self.a += 1 + + +def test_non_array(): + set(dt=0.1) + try1 = TryGroup(monitors=['a']) + try1.a = 1. + try1.run(100.) + + assert np.ndim(try1.mon.a) == 2 and np.shape(try1.mon.a)[1] == 1 + assert np.allclose(np.arange(2, 1002).reshape((-1, 1)), try1.mon.a) + + +def test_1d_array(): + set(dt=0.1) + try1 = TryGroup(monitors=['a']) + try1.a = np.ones(1) + try1.run(100.) + + assert np.ndim(try1.mon.a) == 2 and np.shape(try1.mon.a)[1] == 1 + assert np.allclose(np.arange(2, 1002).reshape((-1, 1)), try1.mon.a) + + +def test_2d_array(): + set(dt=0.1) + try1 = TryGroup(monitors=['a']) + try1.a = np.ones((2, 2)) + try1.run(100.) + + assert np.ndim(try1.mon.a) == 2 and np.shape(try1.mon.a)[1] == 4 + series = np.arange(2, 1002).reshape((-1, 1)) + series = np.repeat(series, 4, axis=1) + assert np.allclose(series, try1.mon.a) + + +def test_monitor_with_every(): + set(dt=0.1) + + # try1: 2d array + try1 = TryGroup(monitors=Monitor(variables=['a'], every=[1.])) + try1.run(100.) + assert np.ndim(try1.mon.a) == 2 and np.shape(try1.mon.a)[1] == 4 + series = np.arange(2, 1002, 1. / 0.1).reshape((-1, 1)) + series = np.repeat(series, 4, axis=1) + assert np.allclose(series, try1.mon.a) + + # try2: 1d array + try2 = TryGroup(monitors=Monitor(variables=['a'], every=[1.])) + try2.a = np.array([1., 1.]) + try2.run(100.) + assert np.ndim(try2.mon.a) == 2 and np.shape(try2.mon.a)[1] == 2 + series = np.arange(2, 1002, 1. / 0.1).reshape((-1, 1)) + series = np.repeat(series, 2, axis=1) + assert np.allclose(series, try2.mon.a) + + # try2: scalar + try3 = TryGroup(monitors=Monitor(variables=['a'], every=[1.])) + try3.a = 1. + try3.run(100.) + assert np.ndim(try3.mon.a) == 2 and np.shape(try3.mon.a)[1] == 1 + series = np.arange(2, 1002, 1. / 0.1).reshape((-1, 1)) + assert np.allclose(series, try3.mon.a) + + +# test_non_array() +# test_1d_array() +# test_2d_array() +# test_monitor_with_every() -- 2.34.1 From 01a51645ff9195aa070435f429ab0c97ab0e5f92 Mon Sep 17 00:00:00 2001 From: chaoming Date: Mon, 6 Sep 2021 21:26:12 +0800 Subject: [PATCH 03/30] update README --- README.md | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e9fab3b0..22cc16f3 100644 --- a/README.md +++ b/README.md @@ -40,17 +40,25 @@ **Method 1**: install ``BrainPy`` by using ``pip``: +To install the stable release of BrainPy (V1.0.3), please use + ```python > pip install -U brain-py ``` +To install the latest pre-release version of BrainPy (V1.1.0), please use + +```bash +> pip install -U brain-py --pre +``` + +If you have installed the previous version of BrainPy, please uninstall the older one first + ```bash -# If you have installed the previous version of BrainPy, -# please uninstall the old one first > pip uninstall brainpy-simulator # Then install the latest version of BrainPy -> pip install -U brain-py +> pip install -U brain-py --pre ``` **Method 2**: install ``BrainPy`` from source: @@ -84,7 +92,7 @@ -## Step 3: comprehensive examples +## Step 3: inspirational examples Here list several examples of BrainPy. More detailed examples and tutorials please see [**BrainModels**](https://brainmodels.readthedocs.io). @@ -148,3 +156,4 @@ See [brainmodels.synapses](https://brainmodels.readthedocs.io/en/latest/apis/syn + -- 2.34.1 From 15ab5bfb29f3bf03d42911c5fe678830a50db33f Mon Sep 17 00:00:00 2001 From: chaoming Date: Tue, 7 Sep 2021 20:40:28 +0800 Subject: [PATCH 04/30] support jit Base class in numpy backend --- brainpy/math/numpy/ast2numba.py | 364 ++++++++++++++++++++--------- brainpy/math/numpy/compilation.py | 167 ++++++++++--- tests/math/numpy/test_ast2numba.py | 8 +- 3 files changed, 388 insertions(+), 151 deletions(-) diff --git a/brainpy/math/numpy/ast2numba.py b/brainpy/math/numpy/ast2numba.py index 16073079..fffbf8a5 100644 --- a/brainpy/math/numpy/ast2numba.py +++ b/brainpy/math/numpy/ast2numba.py @@ -2,10 +2,7 @@ """ - -TODO: support Base function -TODO: enable code debug and error report - +TODO: enable code debug and error report; See https://github.com/numba/numba/issues/7370 """ import ast @@ -21,89 +18,193 @@ from numba.core.dispatcher import Dispatcher from brainpy import errors, math, tools from brainpy.base.base import Base from brainpy.base.collector import Collector +from brainpy.base.function import Function from brainpy.math import profile DE_INT = DynamicSystem = Container = None __all__ = [ - 'jit_cls', - 'jit_integrator', + 'jit', ] -def jit_cls(ds, nopython=True, fastmath=True, parallel=False, nogil=False, show_code=False): - global DynamicSystem, Container +def jit(obj_or_fun, show_code=False, **jit_setting): + global DE_INT + if DE_INT is None: + from brainpy.integrators.constants import DE_INT + + if callable(obj_or_fun): + # Function + if isinstance(obj_or_fun, Function): + return jit_Func(obj_or_fun, show_code=show_code, **jit_setting) + + # Base + elif isinstance(obj_or_fun, Base): + return jit_Base(func=obj_or_fun.__call__, host=obj_or_fun, + show_code=show_code, **jit_setting) + + # integrator + elif hasattr(obj_or_fun, '__name__') and obj_or_fun.__name__.startswith(DE_INT): + return jit_integrator(intg=obj_or_fun, show_code=show_code, **jit_setting) + + # bounded method + elif hasattr(obj_or_fun, '__self__') and isinstance(obj_or_fun.__self__, Base): + return jit_Base(func=obj_or_fun, host=obj_or_fun.__self__, + show_code=show_code, **jit_setting) + + else: + # native function + if not isinstance(obj_or_fun, Dispatcher): + return numba.jit(obj_or_fun, **jit_setting) + else: + # numba function + return obj_or_fun + + else: + return jit_DS(obj_or_fun, show_code=show_code, **jit_setting) + + +def jit_DS(obj_or_fun, show_code=False, **jit_setting): + global DynamicSystem if DynamicSystem is None: from brainpy.simulation.brainobjects.base import DynamicSystem - if Container is None: - from brainpy.simulation.brainobjects.base import Container - assert isinstance(ds, DynamicSystem) + if not isinstance(obj_or_fun, DynamicSystem): + raise errors.UnsupportedError(f'JIT compilation in numpy backend only ' + f'supports {Base.__name__}, but we got ' + f'{type(obj_or_fun)}.') # function analysis - for key, step in list(ds.steps.items()): + for key, step in list(obj_or_fun.steps.items()): key = key.replace(".", "_") - r = analyze_func(step, nopython=nopython, fastmath=fastmath, - parallel=parallel, nogil=nogil, show_code=show_code) + r = _jit_func(obj_or_fun=step, show_code=show_code, **jit_setting) + if r['func'] != step: + func = _form_final_call(f_org=step, f_rep=r['func'], arg2call=r['arg2call'], + arguments=r['arguments'], nodes=r['nodes'], + show_code=show_code, name=step.__name__) + obj_or_fun.steps.replace(key, func) + + # dynamic system + return obj_or_fun + + +def jit_integrator(intg, show_code=False, **jit_setting): + r = _jit_intg_func(intg, show_code=show_code, **jit_setting) + if len(r['arguments']): + intg = _form_final_call(f_org=intg, f_rep=r['func'], arg2call=r['arg2call'], + arguments=r['arguments'], nodes=r['nodes'], + show_code=show_code, name=intg.__name__) + else: + intg = r['func'] + return intg - if len(r): - arguments = sorted(r['arguments']) - code_scope = {key: node for key, node in r['nodes'].items()} - code_scope[key] = r['func'] - called_args = _items2lines([f"{a}={r['arg2call'][a]}" for a in arguments]).strip() - code_lines = [f'def new_{key}(_t, _dt):', - f' {key}(_t=_t, _dt=_dt, {called_args.strip()})'] - - # compile new function - code = '\n'.join(code_lines) - # code, _scope = _add_try_except(code) - # code_scope.update(_scope) - if show_code: - print(code) - print() - pprint(code_scope) - print() - exec(compile(code, '', 'exec'), code_scope) - func = code_scope[f'new_{key}'] - ds.steps.replace(key, func) - return ds - - -def jit_integrator(integrator, nopython=True, fastmath=True, parallel=False, nogil=False, show_code=False): - return analyze_intg_func(f=integrator, nopython=nopython, fastmath=fastmath, - parallel=parallel, nogil=nogil, show_code=show_code) - - -def analyze_func(f, nopython=True, fastmath=True, parallel=False, nogil=False, show_code=False): - global DE_INT, DynamicSystem + +def jit_Func(func, show_code=False, **jit_setting): + assert isinstance(func, Function) + + r = _jit_Function(func=func, show_code=show_code, **jit_setting) + if len(r['arguments']): + func = _form_final_call(f_org=func._f, f_rep=r['func'], arg2call=r['arg2call'], + arguments=r['arguments'], nodes=r['nodes'], + show_code=show_code, name=func.name + '_call') + else: + func = r['func'] + + return func + + +def jit_Base(func, host, show_code=False, **jit_setting): + r = _jit_cls_func(func, host=host, show_code=show_code, **jit_setting) + if len(r['arguments']): + func = _form_final_call(f_org=func, f_rep=r['func'], arg2call=r['arg2call'], + arguments=r['arguments'], nodes=r['nodes'], + show_code=show_code, name=host.name + '_call') + else: + func = r['func'] + return func + + +def _jit_func(obj_or_fun, show_code=False, **jit_setting): + global DE_INT if DE_INT is None: from brainpy.integrators.constants import DE_INT - if DynamicSystem is None: - from brainpy.simulation.brainobjects.base import DynamicSystem - if hasattr(f, '__name__') and f.__name__.startswith(DE_INT): - return analyze_intg_func(f, nopython=nopython, fastmath=fastmath, parallel=parallel, - nogil=nogil, show_code=show_code) + if callable(obj_or_fun): + # integrator + if hasattr(obj_or_fun, '__name__') and obj_or_fun.__name__.startswith(DE_INT): + return _jit_intg_func(obj_or_fun, show_code=show_code, **jit_setting) + + # bounded method + elif hasattr(obj_or_fun, '__self__') and isinstance(obj_or_fun.__self__, Base): + return _jit_cls_func(obj_or_fun, host=obj_or_fun.__self__, + show_code=show_code, **jit_setting) + + # wrapped function + elif isinstance(obj_or_fun, Function): + return _jit_Function(obj_or_fun, show_code=show_code, **jit_setting) + + # base class function + elif isinstance(obj_or_fun, Base): + return _jit_cls_func(obj_or_fun.__call__, host=obj_or_fun, + show_code=show_code, **jit_setting) + + else: + # native function + if not isinstance(obj_or_fun, Dispatcher): + if inspector.inspect_function(obj_or_fun)['numba_type'] is None: + f = numba.jit(obj_or_fun, **jit_setting) + return dict(func=f, arguments=set(), arg2call=Collector(), nodes=Collector()) + # numba function or innate supported function + return dict(func=obj_or_fun, arguments=set(), arg2call=Collector(), nodes=Collector()) + + else: + raise ValueError + + +def _jit_Function(func, show_code=False, **jit_setting): + assert isinstance(func, Function) + + # code_scope + closure_vars = inspect.getclosurevars(func._f) + code_scope = dict(closure_vars.nonlocals) + code_scope.update(closure_vars.globals) + # code + code = tools.deindent(inspect.getsource(func._f)).strip() + # arguments + arguments = set() + # nodes + nodes = {v.name: v for v in func._nodes.values()} + # arg2call + arg2call = dict() - elif hasattr(f, '__self__'): - if isinstance(f.__self__, DynamicSystem): - return analyze_cls_func(f, nopython=nopython, fastmath=fastmath, parallel=parallel, - nogil=nogil, show_code=show_code) + for key, node in func._nodes.items(): + code, _arguments, _arg2call, _nodes, code_scope = _analyze_cls_func( + host=node, code=code, show_code=show_code, code_scope=code_scope, + self_name=key, pop_self=True, **jit_setting) + arguments.update(_arguments) + arg2call.update(_arg2call) + nodes.update(_nodes) - if not isinstance(f, Dispatcher): - if inspector.inspect_function(f)['numba_type'] is None: - f = numba.jit(f, nopython=nopython, fastmath=fastmath, parallel=parallel, nogil=nogil) - return dict(func=f, arguments=set(), arg2call=Collector(), nodes=Collector()) + # compile new function + # code, _scope = _add_try_except(code) + # code_scope.update(_scope) + if show_code: + print(code) + print() + pprint(code_scope) + print() + exec(compile(code, '', 'exec'), code_scope) + func = code_scope[func._f.__name__] + func = numba.jit(func, **jit_setting) - return {} + # returns + return dict(func=func, arguments=arguments, arg2call=arg2call, nodes=nodes) -def analyze_cls_func(f, code=None, host=None, show_code=False, **jit_setting): - global Container, DynamicSystem +def _jit_cls_func(f, code=None, host=None, show_code=False, **jit_setting): + global Container if Container is None: from brainpy.simulation.brainobjects.base import Container - if DynamicSystem is None: - from brainpy.simulation.brainobjects.base import DynamicSystem host = (host or f.__self__) @@ -115,29 +216,28 @@ def analyze_cls_func(f, code=None, host=None, show_code=False, **jit_setting): # step function of Container if isinstance(host, Container): - if f.__name__ != 'update': - raise errors.UnsupportedError(f'Currently, BrainPy only supports compile "update" step ' - f'function, while we got {f.__name__}: {f}') + # if f.__name__ != 'update': + # raise errors.UnsupportedError(f'Currently, BrainPy only supports compile "update" step ' + # f'function, while we got {f.__name__}: {f}') code_lines = [] code_scope = {} for key, step in host.child_steps.items(): - r = analyze_func(f=step, show_code=show_code, **jit_setting) - if len(r): - arguments.update(r['arguments']) - arg2call.update(r['arg2call']) - nodes.update(r['nodes']) - code_scope[key.replace('.', '_')] = r['func'] - call_args = [f'{arg}={arg}' for arg in sorted(r['arguments'])] - code_lines.append("{call}(_t, _dt, {args})".format( - call=key.replace('.', '_'), - args=", ".join(call_args))) - # args=_items2lines(call_args, line_break='\n\t\t\t'))) + r = _jit_func(obj_or_fun=step, show_code=show_code, **jit_setting) + # if r['func'] != step: + arguments.update(r['arguments']) + arg2call.update(r['arg2call']) + nodes.update(r['nodes']) + code_scope[key.replace('.', '_')] = r['func'] + call_args = [f'{arg}={arg}' for arg in sorted(r['arguments'])] + code_lines.append("{call}(_t, _dt, {args})".format(call=key.replace('.', '_'), + args=", ".join(call_args))) + # args=_items2lines(call_args, line_break='\n\t\t\t'))) code_lines = [' ' + line for line in code_lines] # code_lines.insert(0, f'def {host.name}_update(_t, _dt, {_items2lines(sorted(arguments))}):') code_lines.insert(0, f'def {host.name}_update(_t, _dt, {", ".join(sorted(arguments))}):') code = '\n'.join(code_lines) # code_scope.update(nodes) - func_name = f'{host.name}_update' + func_name = f'{host.name}_{f.__name__}' # step function of normal DynamicSystem else: @@ -170,20 +270,16 @@ def analyze_cls_func(f, code=None, host=None, show_code=False, **jit_setting): return dict(func=func, arguments=arguments, arg2call=arg2call, nodes=nodes) -def analyze_intg_func(f, nopython=True, fastmath=True, parallel=False, nogil=False, show_code=False): - if f.brainpy_data['method'].startswith('exponential'): - return analyze_cls_func(f=f, - code="\n".join(f.brainpy_data['code_lines']), - nopython=nopython, - fastmath=fastmath, - parallel=parallel, - nogil=nogil, - show_code=show_code) - +def _jit_intg_func(f, show_code=False, **jit_setting): global DynamicSystem if DynamicSystem is None: from brainpy.simulation.brainobjects.base import DynamicSystem + # exponential euler methods + if f.brainpy_data['method'].startswith('exponential'): + return _jit_cls_func(f=f, code="\n".join(f.brainpy_data['code_lines']), + show_code=show_code, **jit_setting) + # information in the integrator func_name = f.brainpy_data['func_name'] raw_func = f.brainpy_data['raw_func'] @@ -218,13 +314,7 @@ def analyze_intg_func(f, nopython=True, fastmath=True, parallel=False, nogil=Fal continue elif func_node: need_recompile = True - r = analyze_cls_func(f=func, - host=func_node, - nopython=nopython, - fastmath=fastmath, - parallel=parallel, - nogil=nogil, - show_code=show_code) + r = _jit_cls_func(f=func, host=func_node, show_code=show_code, **jit_setting) if len(r['arguments']) or remove_self: tree = _replace_func(tree, func_call=key, arg_to_append=r['arguments'], @@ -236,11 +326,7 @@ def analyze_intg_func(f, nopython=True, fastmath=True, parallel=False, nogil=Fal nodes[func_node.name] = func_node # update nodes else: need_recompile = True - code_scope[key] = numba.jit(func, - nopython=nopython, - fastmath=fastmath, - parallel=parallel, - nogil=nogil) + code_scope[key] = numba.jit(func, **jit_setting) if need_recompile: tree.body[0].decorator_list.clear() @@ -261,7 +347,7 @@ def analyze_intg_func(f, nopython=True, fastmath=True, parallel=False, nogil=Fal new_f.brainpy_data = {key: val for key, val in f.brainpy_data.items()} new_f.brainpy_data['code_lines'] = code.strip().split('\n') new_f.brainpy_data['code_scope'] = code_scope_backup - jit_f = numba.jit(new_f, nopython=nopython, fastmath=fastmath, parallel=parallel, nogil=nogil) + jit_f = numba.jit(new_f, **jit_setting) return dict(func=jit_f, arguments=arguments, arg2call=arg2call, nodes=nodes) else: return dict(func=f, arguments=arguments, arg2call=arg2call, nodes=nodes) @@ -342,7 +428,7 @@ def _replace_func(code_or_tree, func_call, arg_to_append, remove_self=None): return new_tree -def _analyze_cls_func(host, code, show_code, code_scope, cls_name=None, **jit_setting): +def _analyze_cls_func(host, code, show_code, code_scope, self_name=None, pop_self=True, **jit_setting): """ Parameters @@ -351,7 +437,7 @@ def _analyze_cls_func(host, code, show_code, code_scope, cls_name=None, **jit_se The data host. code : str The function source code. - cls_name : optional, str + self_name : optional, str The class name, like "self", "cls". show_code : bool @@ -364,14 +450,15 @@ def _analyze_cls_func(host, code, show_code, code_scope, cls_name=None, **jit_se # arguments tree = ast.parse(code) - if cls_name is None: - cls_name = tree.body[0].args.args[0].arg + if self_name is None: + self_name = tree.body[0].args.args[0].arg # data assigned by self.xx in line right - if cls_name not in profile.CLASS_KEYWORDS: + if self_name not in profile.CLASS_KEYWORDS: raise errors.CodeError(f'BrainPy only support class keyword ' - f'{profile.CLASS_KEYWORDS}, but we got {cls_name}.') - tree.body[0].args.args.pop(0) # remove "self" etc. class argument - self_data = re.findall('\\b' + cls_name + '\\.[A-Za-z_][A-Za-z0-9_.]*\\b', code) + f'{profile.CLASS_KEYWORDS}, but we got {self_name}.') + if pop_self: + tree.body[0].args.args.pop(0) # remove "self" etc. class argument + self_data = re.findall('\\b' + self_name + '\\.[A-Za-z_][A-Za-z0-9_.]*\\b', code) self_data = list(set(self_data)) # analyze variables and functions accessed by the self.xx @@ -405,7 +492,7 @@ def _analyze_cls_func(host, code, show_code, code_scope, cls_name=None, **jit_se code_scope[f'{target.name}_{split_keys[i]}'] = np.random # replace RandomState elif callable(data): assert len(split_keys) == i + 1 - r = analyze_func(f=data, show_code=show_code, **jit_setting) + r = _jit_func(obj_or_fun=data, show_code=show_code, **jit_setting) if len(r): tree = _replace_func(tree, func_call=key, arg_to_append=r['arguments']) arguments.update(r['arguments']) @@ -456,3 +543,62 @@ def _items2lines(items, num_each_line=5, separator=', ', line_break='\n\t\t'): for item in items[i: i + num_each_line]: res += item + separator return res + + +def _form_final_call(f_org, f_rep, arg2call, arguments, nodes, show_code=False, name=None): + cls_kw, reduce_args, org_args = _get_args(f_org) + + name = (name or f_org.__name__) + code_scope = {key: node for key, node in nodes.items()} + code_scope[name] = f_rep + called_args = _items2lines(reduce_args + [f"{a}={arg2call[a]}" for a in sorted(arguments)]).strip() + code_lines = [f'def new_{name}({", ".join(org_args)}):', + f' {name}({called_args.strip()})'] + + # compile new function + code = '\n'.join(code_lines) + # code, _scope = _add_try_except(code) + # code_scope.update(_scope) + if show_code: + print(code) + print() + pprint(code_scope) + print() + exec(compile(code, '', 'exec'), code_scope) + func = code_scope[f'new_{name}'] + return func + + +def _get_args(f): + # 1. get the function arguments + original_args = [] + reduced_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(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 original_args[0] in profile.CLASS_KEYWORDS: + class_kw.append(original_args[0]) + original_args = original_args[1:] + reduced_args = reduced_args[1:] + for a in original_args: + if a.split('=')[0].strip() in profile.CLASS_KEYWORDS: + raise errors.DiffEqError(f'Class keywords "{a}" must be defined ' + f'as the first argument.') + return class_kw, reduced_args, original_args diff --git a/brainpy/math/numpy/compilation.py b/brainpy/math/numpy/compilation.py index 2805f050..54bf0568 100644 --- a/brainpy/math/numpy/compilation.py +++ b/brainpy/math/numpy/compilation.py @@ -11,8 +11,7 @@ except ModuleNotFoundError: ast2numba = None numba = None -DE_INT = None -DynamicSystem = None +DE_INT = DynamicSystem = None __all__ = [ 'jit', @@ -23,7 +22,98 @@ __all__ = [ logger = logging.getLogger('brainpy.math.numpy.compilation') -def jit(obj_or_func, nopython=True, fastmath=True, parallel=False, nogil=False, show_code=False, **kwargs): +def jit(obj_or_fun, nopython=True, fastmath=True, parallel=False, nogil=False, + forceobj=False, looplift=True, error_model='python', inline='never', + boundscheck=None, show_code=False, **kwargs): + """Just-In-Time (JIT) Compilation in NumPy backend. + + JIT compilation in NumPy backend relies on `Numba `_. However, + in BrainPy, `bp.math.numpy.jit()` can apply to class objects, especially the instance + of :py:class:`brainpy.DynamicSystem`. + + If you are using JAX backend, please refer to the JIT compilation in + JAX backend `bp.math.jax.jit() `_. + + Parameters + ---------- + obj_or_fun : callable, Base + The function or the base model to jit compile. + + nopython : bool + Set to True to disable the use of PyObjects and Python API + calls. Default value is True. + + fastmath : bool + In certain classes of applications strict IEEE 754 compliance + is less important. As a result it is possible to relax some + numerical rigour with view of gaining additional performance. + The way to achieve this behaviour in Numba is through the use + of the ``fastmath`` keyword argument. + + parallel : bool + Enables automatic parallelization (and related optimizations) for + those operations in the function known to have parallel semantics. + + nogil : bool + Whenever Numba optimizes Python code to native code that only + works on native types and variables (rather than Python objects), + it is not necessary anymore to hold Python’s global interpreter + lock (GIL). Numba will release the GIL when entering such a + compiled function if you passed ``nogil=True``. + + forceobj: bool + Set to True to force the use of PyObjects for every value. + Default value is False. + + looplift: bool + Set to True to enable jitting loops in nopython mode while + leaving surrounding code in object mode. This allows functions + to allocate NumPy arrays and use Python objects, while the + tight loops in the function can still be compiled in nopython + mode. Any arrays that the tight loop uses should be created + before the loop is entered. Default value is True. + + error_model: str + The error-model affects divide-by-zero behavior. + Valid values are 'python' and 'numpy'. The 'python' model + raises exception. The 'numpy' model sets the result to + *+/-inf* or *nan*. Default value is 'python'. + + inline: str or callable + The inline option will determine whether a function is inlined + at into its caller if called. String options are 'never' + (default) which will never inline, and 'always', which will + always inline. If a callable is provided it will be called with + the call expression node that is requesting inlining, the + caller's IR and callee's IR as arguments, it is expected to + return Truthy as to whether to inline. + NOTE: This inlining is performed at the Numba IR level and is in + no way related to LLVM inlining. + + boundscheck: bool or None + Set to True to enable bounds checking for array indices. Out + of bounds accesses will raise IndexError. The default is to + not do bounds checking. If False, bounds checking is disabled, + out of bounds accesses can produce garbage results or segfaults. + However, enabling bounds checking will slow down typical + functions, so it is recommended to only use this flag for + debugging. You can also set the NUMBA_BOUNDSCHECK environment + variable to 0 or 1 to globally override this flag. The default + value is None, which under normal execution equates to False, + but if debug is set to True then bounds checking will be + enabled. + + show_code : bool + Debugging. + + kwargs + + Returns + ------- + res : callable, Base + The jitted objects. + """ + # checking if ast2numba is None or numba is None: raise errors.PackageMissingError('JIT compilation in numpy backend need Numba. ' @@ -31,44 +121,29 @@ def jit(obj_or_func, nopython=True, fastmath=True, parallel=False, nogil=False, '>>> pip install numba\n' '>>> # or \n' '>>> conda install numba') - global DE_INT, DynamicSystem - if DE_INT is None: - from brainpy.integrators.constants import DE_INT - if DynamicSystem is None: - from brainpy.simulation.brainobjects.base import DynamicSystem - - # JIT compilation - if callable(obj_or_func): - # integrator - if hasattr(obj_or_func, '__name__') and obj_or_func.__name__.startswith(DE_INT): - return ast2numba.jit_integrator(obj_or_func, - nopython=nopython, - fastmath=fastmath, - parallel=parallel, - nogil=nogil, - show_code=show_code) - else: - # native function - return numba.jit(obj_or_func, - nopython=nopython, - fastmath=fastmath, - parallel=parallel, - nogil=nogil) - - else: - # dynamic system - if not isinstance(obj_or_func, DynamicSystem): - raise errors.UnsupportedError(f'JIT compilation in numpy backend only supports ' - f'{DynamicSystem.__name__}, but we got {type(obj_or_func)}.') - return ast2numba.jit_cls(obj_or_func, - nopython=nopython, - fastmath=fastmath, - parallel=parallel, - nogil=nogil, - show_code=show_code) + return ast2numba.jit(obj_or_fun, show_code=show_code, + nopython=nopython, fastmath=fastmath, parallel=parallel, nogil=nogil, + forceobj=forceobj, looplift=looplift, error_model=error_model, + inline=inline, boundscheck=boundscheck) def vmap(obj_or_func, *args, **kwargs): + """Vectorization Compilation in NumPy backend. + + Vectorization compilation is not implemented in NumPy backend. + Please refer to the vectorization compilation in JAX backend + `bp.math.jax.vmap() `_. + + Parameters + ---------- + obj_or_func + args + kwargs + + Returns + ------- + + """ _msg = 'Vectorize compilation is only supported in JAX backend, not available in numpy backend. \n' \ 'You can switch to JAX backend by `brainpy.math.use_backend("jax")`' logger.error(_msg) @@ -76,6 +151,22 @@ def vmap(obj_or_func, *args, **kwargs): def pmap(obj_or_func, *args, **kwargs): + """Parallel Compilation in NumPy backend. + + Parallel compilation is not implemented in NumPy backend. + Please refer to the parallel compilation in JAX backend + `bp.math.jax.pmap() `_. + + Parameters + ---------- + obj_or_func + args + kwargs + + Returns + ------- + + """ _msg = 'Parallel compilation is only supported in JAX backend, not available in numpy backend. \n' \ 'You can switch to JAX backend by `brainpy.math.use_backend("jax")`' logger.error(_msg) diff --git a/tests/math/numpy/test_ast2numba.py b/tests/math/numpy/test_ast2numba.py index 953388a5..dd62f4d1 100644 --- a/tests/math/numpy/test_ast2numba.py +++ b/tests/math/numpy/test_ast2numba.py @@ -6,7 +6,7 @@ from pprint import pprint import brainpy as bp from brainpy.math.numpy.ast2numba import FuncTransformer -from brainpy.math.numpy.ast2numba import analyze_cls_func +from brainpy.math.numpy.ast2numba import _jit_cls_func from brainpy.tools import ast2code bp.math.use_backend('numpy') @@ -117,7 +117,7 @@ def test_cls_func_hh1(): hh = HH(10) - r = analyze_cls_func(hh.update, show_code=True) + r = _jit_cls_func(hh.update, show_code=True) pprint(r['func']) pprint('arguments:') pprint(r['arguments']) @@ -184,7 +184,7 @@ def test_cls_func_hh2(): hh = HH(10) - r = analyze_cls_func(hh.update, show_code=True) + r = _jit_cls_func(hh.update, show_code=True) pprint(r['func']) pprint('arguments:') pprint(r['arguments']) @@ -284,7 +284,7 @@ def test_cls_func_ampa1(): hh = HH(10) ampa = AMPA_vec(pre=hh, post=hh, conn=bp.connect.All2All(), delay=10.) - r = analyze_cls_func(ampa.update, show_code=True) + r = _jit_cls_func(ampa.update, show_code=True) pprint(r['func']) pprint('arguments:') pprint(r['arguments']) -- 2.34.1 From e73ddb64e25a19df8347f6ee6b9e8a0658be3ebc Mon Sep 17 00:00:00 2001 From: chaoming Date: Tue, 7 Sep 2021 20:40:36 +0800 Subject: [PATCH 05/30] support jit Base class in numpy backend --- tests/math/numpy/test_jit.py | 157 +++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 tests/math/numpy/test_jit.py diff --git a/tests/math/numpy/test_jit.py b/tests/math/numpy/test_jit.py new file mode 100644 index 00000000..a6d7e8b9 --- /dev/null +++ b/tests/math/numpy/test_jit.py @@ -0,0 +1,157 @@ +# -*- coding: utf-8 -*- + + +import brainpy as bp + +bp.math.use_backend('numpy') +bp.integrators.set_default_odeint(method='rk4') + + +class LIF(bp.NeuGroup): + def __init__(self, size, t_refractory=1., V_rest=0., V_reset=-5., + V_th=20., R=1., tau=10., **kwargs): + super(LIF, self).__init__(size=size, **kwargs) + + # parameters + self.V_rest = V_rest + self.V_reset = V_reset + self.V_th = V_th + self.R = R + self.tau = tau + self.t_refractory = t_refractory + + # variables + self.V = bp.math.Variable(bp.math.ones(self.num) * V_rest) + self.input = bp.math.Variable(bp.math.zeros(self.num)) + self.refractory = bp.math.Variable(bp.math.zeros(self.num, dtype=bool)) + self.spike = bp.math.Variable(bp.math.zeros(self.num, dtype=bool)) + self.t_last_spike = bp.math.Variable(bp.math.ones(self.num) * -1e7) + + @bp.odeint + def integral(self, V, t, Iext): + dvdt = (-V + self.V_rest + self.R * Iext) / self.tau + return dvdt + + def update(self, _t, _dt): + refractory = (_t - self.t_last_spike) <= self.t_refractory + V = self.integral(self.V, _t, self.input, dt=_dt) + V = bp.math.where(refractory, self.V, V) + spike = self.V_th <= V + self.t_last_spike[:] = bp.math.where(spike, _t, self.t_last_spike) + self.V[:] = bp.math.where(spike, self.V_reset, V) + self.refractory[:] = bp.math.logical_or(refractory, spike) + self.input[:] = 0. + self.spike[:] = spike + + +class HH(bp.NeuGroup): + def __init__(self, size, ENa=50., gNa=120., EK=-77., gK=36., EL=-54.387, + gL=0.03, V_th=20., C=1.0, **kwargs): + super(HH, self).__init__(size=size, **kwargs) + + # parameters + self.ENa = ENa + self.EK = EK + self.EL = EL + self.gNa = gNa + self.gK = gK + self.gL = gL + self.C = C + self.V_th = V_th + + # variables + self.V = bp.math.Variable(-65. * bp.math.ones(self.num)) + self.m = bp.math.Variable(0.5 * bp.math.ones(self.num)) + self.h = bp.math.Variable(0.6 * bp.math.ones(self.num)) + self.n = bp.math.Variable(0.32 * bp.math.ones(self.num)) + self.input = bp.math.Variable(bp.math.zeros(self.num)) + self.spike = bp.math.Variable(bp.math.zeros(self.num, dtype=bool)) + self.t_last_spike = bp.math.Variable(bp.math.ones(self.num) * -1e7) + + @bp.odeint + def integral(self, V, m, h, n, t, Iext): + alpha = 0.1 * (V + 40) / (1 - bp.math.exp(-(V + 40) / 10)) + beta = 4.0 * bp.math.exp(-(V + 65) / 18) + dmdt = alpha * (1 - m) - beta * m + + alpha = 0.07 * bp.math.exp(-(V + 65) / 20.) + beta = 1 / (1 + bp.math.exp(-(V + 35) / 10)) + dhdt = alpha * (1 - h) - beta * h + + alpha = 0.01 * (V + 55) / (1 - bp.math.exp(-(V + 55) / 10)) + beta = 0.125 * bp.math.exp(-(V + 65) / 80) + dndt = alpha * (1 - n) - beta * n + + I_Na = (self.gNa * m ** 3.0 * h) * (V - self.ENa) + I_K = (self.gK * n ** 4.0) * (V - self.EK) + I_leak = self.gL * (V - self.EL) + dVdt = (- I_Na - I_K - I_leak + Iext) / self.C + + return dVdt, dmdt, dhdt, dndt + + def update(self, _t, _dt): + V, m, h, n = self.integral(self.V, self.m, self.h, self.n, _t, self.input, dt=_dt) + self.spike[:] = bp.math.logical_and(self.V < self.V_th, V >= self.V_th) + self.V[:] = V + self.m[:] = m + self.h[:] = h + self.n[:] = n + self.input[:] = 0. + + +def test_jit_neugroup_lif(): + bp.math.jit(LIF(10), show_code=True).run(1.) + + +def test_jit_neugroup_lif_bounded_func(): + bp.integrators.set_default_odeint(method='exponential_euler') + bp.math.jit(LIF(10).update, show_code=True) + + +def test_jit_neugroup_lif_integrator(): + bp.integrators.set_default_odeint(method='exponential_euler') + bp.math.jit(LIF(10).integral, show_code=True) + + +def test_jit_neugroup_hh(): + bp.math.jit(HH(10), show_code=True).run(1.) + + +def test_jit_neugroup_hh_bounded_func(): + bp.integrators.set_default_odeint(method='exponential_euler') + bp.math.jit(HH(10).update, show_code=True) + + +def test_jit_neugroup_hh_integrator(): + bp.integrators.set_default_odeint(method='exponential_euler') + bp.math.jit(HH(10).integral, show_code=True) + + +class LogisticRegression(bp.Base): + def __init__(self, dimension): + super(LogisticRegression, self).__init__() + + self.dimension = dimension + self.w = bp.math.Variable(2.0 * bp.math.ones(dimension) - 1.3) + + def __call__(self, X, Y): + u = bp.math.dot(((1.0 / (1.0 + bp.math.exp(-Y * bp.math.dot(X, self.w))) - 1.0) * Y), X) + self.w[:] -= u + + +def test1(): + num_dim = 10 + num_points = 200 + # num_points = 20000000 + points = bp.math.random.random((num_points, num_dim)) + labels = bp.math.random.random(num_points) + lr = LogisticRegression(num_dim) + jit_lr = bp.math.jit(lr, show_code=True) + print(lr.w) + + num_iter = 20 + for i in range(num_iter): + jit_lr(points, labels) + print(lr.w) + + -- 2.34.1 From d5da27d7d967a18aae78290a3c16a92d384fd2ba Mon Sep 17 00:00:00 2001 From: chaoming Date: Tue, 7 Sep 2021 23:21:35 +0800 Subject: [PATCH 06/30] fix bugs --- brainpy/math/numpy/ast2numba.py | 37 +++++++++++++++++---------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/brainpy/math/numpy/ast2numba.py b/brainpy/math/numpy/ast2numba.py index fffbf8a5..a63ee421 100644 --- a/brainpy/math/numpy/ast2numba.py +++ b/brainpy/math/numpy/ast2numba.py @@ -41,6 +41,7 @@ def jit(obj_or_fun, show_code=False, **jit_setting): # Base elif isinstance(obj_or_fun, Base): return jit_Base(func=obj_or_fun.__call__, host=obj_or_fun, + name=obj_or_fun.name + '_call', show_code=show_code, **jit_setting) # integrator @@ -113,12 +114,13 @@ def jit_Func(func, show_code=False, **jit_setting): return func -def jit_Base(func, host, show_code=False, **jit_setting): +def jit_Base(func, host, name=None, show_code=False, **jit_setting): r = _jit_cls_func(func, host=host, show_code=show_code, **jit_setting) if len(r['arguments']): + name = func.__name__ if name is None else name func = _form_final_call(f_org=func, f_rep=r['func'], arg2call=r['arg2call'], arguments=r['arguments'], nodes=r['nodes'], - show_code=show_code, name=host.name + '_call') + show_code=show_code, name=name) else: func = r['func'] return func @@ -189,10 +191,7 @@ def _jit_Function(func, show_code=False, **jit_setting): # code, _scope = _add_try_except(code) # code_scope.update(_scope) if show_code: - print(code) - print() - pprint(code_scope) - print() + output_compiled_codes(code, code_scope) exec(compile(code, '', 'exec'), code_scope) func = code_scope[func._f.__name__] func = numba.jit(func, **jit_setting) @@ -258,10 +257,7 @@ def _jit_cls_func(f, code=None, host=None, show_code=False, **jit_setting): # code, _scope = _add_try_except(code) # code_scope.update(_scope) if show_code: - print(code) - print() - pprint(code_scope) - print() + output_compiled_codes(code, code_scope) exec(compile(code, '', 'exec'), code_scope) func = code_scope[func_name] func = numba.jit(func, **jit_setting) @@ -338,10 +334,7 @@ def _jit_intg_func(f, show_code=False, **jit_setting): code_scope_backup = {k: v for k, v in code_scope.items()} # compile functions if show_code: - print(code) - print() - pprint(code_scope) - print() + output_compiled_codes(code, code_scope) exec(compile(code, '', 'exec'), code_scope) new_f = code_scope[func_name] new_f.brainpy_data = {key: val for key, val in f.brainpy_data.items()} @@ -560,10 +553,7 @@ def _form_final_call(f_org, f_rep, arg2call, arguments, nodes, show_code=False, # code, _scope = _add_try_except(code) # code_scope.update(_scope) if show_code: - print(code) - print() - pprint(code_scope) - print() + output_compiled_codes(code, code_scope) exec(compile(code, '', 'exec'), code_scope) func = code_scope[f'new_{name}'] return func @@ -602,3 +592,14 @@ def _get_args(f): raise errors.DiffEqError(f'Class keywords "{a}" must be defined ' f'as the first argument.') return class_kw, reduced_args, original_args + + +def output_compiled_codes(code, scope): + print('The recompiled function:') + print('-------------------------') + print(code) + print() + print('The namespace of the above function:') + print('------------------------------------') + pprint(scope) + print() -- 2.34.1 From 6bd89b76215986a74770e18533c8827c09f08ae7 Mon Sep 17 00:00:00 2001 From: chaoming Date: Tue, 7 Sep 2021 23:22:49 +0800 Subject: [PATCH 07/30] update doc string of default type setting --- brainpy/math/numpy/ops.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/brainpy/math/numpy/ops.py b/brainpy/math/numpy/ops.py index 5c08d1ba..b9d9df48 100644 --- a/brainpy/math/numpy/ops.py +++ b/brainpy/math/numpy/ops.py @@ -383,18 +383,36 @@ complex128 = numpy.complex128 def set_int_(int_type): + """Set the default ``int`` type. + + Parameters + ---------- + int_type : type + """ global int_ assert isinstance(int_type, type) int_ = int_type def set_float_(float_type): + """Set the default ``float`` type. + + Parameters + ---------- + float_type : type + """ global float_ assert isinstance(float_type, type) float_ = float_type def set_complex_(complex_type): + """Set the default ``complex`` type. + + Parameters + ---------- + complex_type : type + """ global complex_ assert isinstance(complex_type, type) complex_ = complex_type -- 2.34.1 From 7549ae498a9add477764a019c9c12d2721e99982 Mon Sep 17 00:00:00 2001 From: chaoming Date: Tue, 7 Sep 2021 23:23:04 +0800 Subject: [PATCH 08/30] update doc string --- brainpy/math/jax/compilation.py | 32 ++++++++++++++++++++++---------- brainpy/math/jax/gradient.py | 8 ++++++-- brainpy/math/jax/jaxarray.py | 2 +- brainpy/math/numpy/ndarray.py | 9 +++++++++ 4 files changed, 38 insertions(+), 13 deletions(-) diff --git a/brainpy/math/jax/compilation.py b/brainpy/math/jax/compilation.py index 6f7d277c..30eceaaf 100644 --- a/brainpy/math/jax/compilation.py +++ b/brainpy/math/jax/compilation.py @@ -58,17 +58,21 @@ def _make_jit(func, vars_to_change, vars_needed, def jit(obj_or_func, vars_to_change=None, vars_needed=None, static_argnums=None, static_argnames=None, device=None, - backend=None, donate_argnums=(), inline=False): - """JIT (Just-In-Time) Compilation. + backend=None, donate_argnums=(), inline=False, **kwargs): + """JIT (Just-In-Time) Compilation for JAX backend. - This function has the same ability to Just-In-Time transform a pure function, - but it can also JIT transform a :py:class:`DynamicSystem`, or a :py:class:`Base` object, - or a bounded method of a :py:class:`Base` object. + This function has the same ability to Just-In-Time compile a pure function, + but it can also JIT compile a :py:class:`brainpy.DynamicSystem`, or a + :py:class:`brainpy.Base` object, or a bounded method of a + :py:class:`brainpy.Base` object. + + If you are using "numpy", please refer to the JIT compilation + in NumPy backend `bp.math.numpy.jit() `_. Examples -------- - You can JIT a :py:class:`DynamicSystem` + You can JIT a :py:class:`brainpy.DynamicSystem` >>> import brainpy as bp >>> @@ -76,12 +80,12 @@ def jit(obj_or_func, vars_to_change=None, vars_needed=None, >>> pass >>> lif = bp.math.jit(LIF(10)) - You can JIT a :py:class:`Base` object with ``__call__()`` implementation. + You can JIT a :py:class:`brainpy.Base` object with ``__call__()`` implementation. >>> mlp = bp.dnn.MLP((10, 100, 10)) >>> jit_mlp = bp.math.jit(mlp) - You can also JIT a bounded method of a :py:class:`Base` object. + You can also JIT a bounded method of a :py:class:`brainpy.Base` object. >>> class Hello(bp.dnn.Module): >>> def __init__(self): @@ -96,6 +100,10 @@ def jit(obj_or_func, vars_to_change=None, vars_needed=None, Further, you can JIT a normal function, just used like in JAX. + >>> @bp.math.jit + >>> def selu(x, alpha=1.67, lmbda=1.05): + >>> return lmbda * bp.math.where(x > 0, x, alpha * bp.math.exp(x) - alpha) + Parameters ---------- obj_or_func : Base, function @@ -267,7 +275,9 @@ def _make_vmap(func, dyn_vars, rand_vars, in_axes, out_axes, def vmap(obj_or_func, vars=None, vars_batched=None, in_axes=0, out_axes=0, axis_name=None, reduce_func=None): - """Vectorized compile a function or a module to run in parallel on a single device. + """Vectorization compilation in JAX backend. + + Vectorized compile a function or a module to run in parallel on a single device. Examples -------- @@ -517,7 +527,9 @@ def _make_pmap(func, dyn_vars, rand_vars, reduce_func, axis_name=None, in_axes=0 def pmap(obj_or_func, vars=None, axis_name=None, in_axes=0, out_axes=0, static_broadcasted_argnums=(), devices=None, backend=None, axis_size=None, donate_argnums=(), global_arg_shapes=None, reduce_func=None): - """Parallel compile a function or a module to run on multiple devices in parallel. + """Parallel compilation in JAX backend. + + Parallel compile a function or a module to run on multiple devices in parallel. Parameters ---------- diff --git a/brainpy/math/jax/gradient.py b/brainpy/math/jax/gradient.py index cb4b6ab1..aca37277 100644 --- a/brainpy/math/jax/gradient.py +++ b/brainpy/math/jax/gradient.py @@ -16,7 +16,9 @@ __all__ = [ def grad(func, vars=None, argnums=None, has_aux=None, holomorphic=False, allow_int=False, reduce_axes=()): - """Creates a function which evaluates the gradient of ``fun``. + """Automatic Gradient Computation in JAX backend. + + Creates a function which evaluates the gradient of ``fun``. Parameters ---------- @@ -97,7 +99,9 @@ def grad(func, vars=None, argnums=None, has_aux=None, def value_and_grad(func, vars=None, argnums=None, has_aux=None, holomorphic=False, allow_int=False, reduce_axes=()): - """Create a function which evaluates both ``fun`` and the gradient of ``fun``. + """Automatic Gradient Computation in JAX backend. + + Create a function which evaluates both ``fun`` and the gradient of ``fun``. Parameters ---------- diff --git a/brainpy/math/jax/jaxarray.py b/brainpy/math/jax/jaxarray.py index 04a93314..0c39f1c3 100644 --- a/brainpy/math/jax/jaxarray.py +++ b/brainpy/math/jax/jaxarray.py @@ -257,7 +257,7 @@ class JaxArray(object): def __ipow__(self, oc): # a **= b - self._value = self._value.__pow__(oc._value if isinstance(oc, JaxArray) else oc) + self._value = self._value ** (oc._value if isinstance(oc, JaxArray) else oc) return self def __matmul__(self, oc): diff --git a/brainpy/math/numpy/ndarray.py b/brainpy/math/numpy/ndarray.py index 102b26d6..7cec2735 100644 --- a/brainpy/math/numpy/ndarray.py +++ b/brainpy/math/numpy/ndarray.py @@ -15,6 +15,9 @@ ndarray = np.ndarray class Variable(np.ndarray): + """Variable. + + """ def __new__(cls, value, type='', replicate=None): value2 = np.asarray(value) obj = value2.view(cls) @@ -40,6 +43,9 @@ class Variable(np.ndarray): class TrainVar(Variable): + """Trainable Variable. + + """ __slots__ = () def __new__(cls, value, replicate=None): @@ -47,6 +53,9 @@ class TrainVar(Variable): class Parameter(Variable): + """Parameter. + + """ __slots__ = () def __new__(cls, value, replicate=None): -- 2.34.1 From 9f3cced3641e38d54ccf697c3ab2515e417bc1f8 Mon Sep 17 00:00:00 2001 From: chaoming Date: Tue, 7 Sep 2021 23:23:26 +0800 Subject: [PATCH 09/30] update JIT compilation tutorial --- docs/quickstart/jit_compilation.ipynb | 752 +++++++++++++++++++++++--- 1 file changed, 673 insertions(+), 79 deletions(-) diff --git a/docs/quickstart/jit_compilation.ipynb b/docs/quickstart/jit_compilation.ipynb index b45d6ba5..38b3da31 100644 --- a/docs/quickstart/jit_compilation.ipynb +++ b/docs/quickstart/jit_compilation.ipynb @@ -15,9 +15,9 @@ "source": [ "The core idea behind BrainPy is the Just-In-Time (JIT) compilation. JIT compilation enables your Python code to be compiled into machine code \"just-in-time\" for execution. Subsequently, such transformed code can run at native machine code speed!\n", "\n", - "Excellent JIT compilers such as [JAX](https://github.com/google/jax) and [Numba](https://github.com/numba/numba) are provided in Python. However, they are designed to work only on pure Python functions: all the input data is passed through the function parameters, all the results are output through the function results. While, the essence of Python is object-oriented programming (OOP) based on ``class``. OOP makes the programming more flexible, modular, and re-usable. In BrainPy, we relieve these constraints and enables users to JIT a class object without the preformance loss. \n", + "Excellent JIT compilers such as [JAX](https://github.com/google/jax) and [Numba](https://github.com/numba/numba) are provided in Python. However, they are designed to work only on pure Python functions. While, in computational neuroscience, most models have too many parameters and variables, it's hard to manage and control model logic by only using functions. On the contrary, object-oriented programming (OOP) based on ``class`` in Python will make your coding more readable, controlable, flexible and modular. Therefore, it is necessary to support JIT compilation on class objects for programming in brain modeling. \n", "\n", - "In this section, we will talk about the JIT compilation in BrainPy. " + "Here, in BrainPy, we provide JIT compilation interface for class objects, built on the top of JAX and Numba. In this section, we will talk about this. " ] }, { @@ -34,9 +34,6 @@ } ], "source": [ - "import sys\n", - "sys.path.append('../../')\n", - "\n", "import brainpy as bp" ] }, @@ -51,7 +48,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "[Numba](https://github.com/numba/numba) is specialized to optimize your native Python codes such like functions, NumPy arrays, loops and condition controls. It is a cross-platform library which can run on Windows, Linux, macOS, etc. The most wonderful thing is that numba can JIT compile your native Python loops (``for`` or ``while`` syntaxs) and condition controls (``if ... else ...``). This means that it supports your intutive Python programming. \n", + "[Numba](https://github.com/numba/numba) is specialized to optimize your native NumPy codes, including NumPy arrays, loops and condition controls, etc. It is a cross-platform library which can run on Windows, Linux, macOS, etc. The most wonderful thing is that numba can just-in-time compile your native Python loops (``for`` or ``while`` syntaxs) and condition controls (``if ... else ...``). This means that it supports your intutive Python programming. \n", "\n", "However, Numba is a lightweight JIT compiler, and is just suitable for small network models. For large networks, the parallel performance is poor. Futhermore, numba doesn't support `one code runs on multiple devices`. Same code cannot run on GPU targets." ] @@ -98,16 +95,18 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## BrainPy `math` module" + "## JIT compilation in BrainPy" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "In order to obtain an *intutive*, *flexible* and *high-performance* framework for brain modeling, in [BrainPy](https://github.com/PKU-NIP-Lab/BrainPy), we want to combine the advantages of both compilers together, and try to overcome the gotchas of each framework as much as possible (although we have not finished it). Specifically, we provide [BrainPy math module](../apis/math.rst) for:\n", + "In order to obtain an *intutive*, *flexible* and *high-performance* framework for brain modeling, in [BrainPy](https://github.com/PKU-NIP-Lab/BrainPy), we want to combine the advantages of both compilers together, and try to overcome the gotchas of each framework as much as possible (although we have not finished it). \n", "\n", - "- flexible switch between NumPy and JAX backends\n", + "Specifically, we provide [BrainPy math module](../apis/math.rst) for \n", + "\n", + "- flexible switch between NumPy/Numba and JAX backends\n", "- unified numpy-like array operations \n", "- unified ``ndarray`` data structure which supports in-place update\n", "- unified ``random`` APIs\n", @@ -118,7 +117,21 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Users can switch to different backends by using ``brainpy.math.use_backend``:" + "### Backend Switch" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The advantages and disadvantages of Numba and JAX are listed in above. We support them both for different models. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you are coding a small network model, NumPy/Numba backend may be very suitable for you. You can switch to this backend by:" ] }, { @@ -144,6 +157,13 @@ "bp.math.get_backend_name()" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Actually, \"numpy\" is the default backend used in BrainPy. However, if you are coding a large-scale network model, or try to run on GPUs or TPUs, please switch to JAX backend by:" + ] + }, { "cell_type": "code", "execution_count": 3, @@ -171,9 +191,28 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "After the backend switch, the APIs in ``brainpy.math`` is much similar to APIs in original ``numpy``. The detailed comparison please see the [Comparison Table](../apis/math/comparison.rst). \n", - "\n", - "For example, the **array creation** APIs," + "In BrainPy, \"numpy\" and \"jax\" backends are interchangeable. Both backends have the same APIs, and same codes can run on both backends (except ``for ...`` and ``if ... else ...`` in JAX backend, we are trying to solve this problem)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Math Operations" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The APIs in ``brainpy.math`` module in each backend is much similar to APIs in original ``numpy``. The detailed comparison please see the [Comparison Table](../apis/math/comparison.rst). " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For example, the **array creation** functions," ] }, { @@ -208,7 +247,9 @@ { "cell_type": "code", "execution_count": 5, - "metadata": {}, + "metadata": { + "scrolled": true + }, "outputs": [ { "data": { @@ -252,7 +293,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The **array manipulation** APIs:" + "The **array manipulation** functions:" ] }, { @@ -278,7 +319,9 @@ { "cell_type": "code", "execution_count": 8, - "metadata": {}, + "metadata": { + "scrolled": true + }, "outputs": [ { "data": { @@ -331,9 +374,9 @@ { "data": { "text/plain": [ - "JaxArray(DeviceArray([[0.15193117, 0.7006848 , 0.75320196, 0.29045963, 0.7425157 ],\n", - " [0.18510342, 0.9365095 , 0.02459204, 0.2899201 , 0.1062901 ],\n", - " [0.31897604, 0.14713216, 0.0075345 , 0.60187805, 0.293056 ]], dtype=float32))" + "JaxArray(DeviceArray([[0.84606385, 0.14539516, 0.98411 , 0.5173148 , 0.9132446 ],\n", + " [0.39373338, 0.70007217, 0.524508 , 0.25626922, 0.9771589 ],\n", + " [0.21962452, 0.14170194, 0.87090707, 0.31382847, 0.44447434]], dtype=float32))" ] }, "execution_count": 10, @@ -353,10 +396,10 @@ { "data": { "text/plain": [ - "JaxArray(DeviceArray([[-2.5946703 , -0.44657612, 1.4826825 , -3.1162384 ,\n", - " 0.60915095],\n", - " [-0.6821795 , -0.7344547 , 0.24855301, -1.627654 ,\n", - " 1.7101754 ]], dtype=float32))" + "JaxArray(DeviceArray([[ 2.7446158 , -0.86415875, 2.2743175 , 0.87442636,\n", + " 3.4002311 ],\n", + " [ 2.7979205 , -1.6518768 , -0.7373221 , 0.7196598 ,\n", + " -0.12697993]], dtype=float32))" ] }, "execution_count": 11, @@ -385,10 +428,8 @@ { "data": { "text/plain": [ - "JaxArray(DeviceArray([[ -3.9590292, -1.9154855, 1.9797885, -6.3715463,\n", - " 4.029502 ],\n", - " [-10.512729 , -4.277547 , 5.44226 , -15.859331 ,\n", - " 8.668155 ]], dtype=float32))" + "JaxArray(DeviceArray([[ 8.340457 , -4.1679125, 0.7996733, 2.313746 , 3.1462712],\n", + " [19.42553 , -9.199984 , 3.873664 , 5.5019183, 9.692774 ]], dtype=float32))" ] }, "execution_count": 12, @@ -426,7 +467,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The **Discrete Fourier Transform** functions:" + "The **discrete fourier transform** functions:" ] }, { @@ -483,70 +524,204 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## JIT compilation in BrainPy" + "### JIT for Functions" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Same with Numba and JAX, BrainPy supports JIT compilation for **functions**. " + "To take advantage of the JIT compilation, users just need to wrap their customized *functions* or *objects* into **[bp.math.jit()](../apis/math/generated/brainpy.math.numpy.jit.rst)** to instruct BrainPy to transform your codes into machine codes. \n", + "\n", + "\n", + "Take the **pure functions** as the example. Here we try to implement a function of Gaussian Error Linear Unit," ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": 16, "metadata": {}, + "outputs": [], "source": [ - "For example, in JAX backend, we implementat a ``selu`` function: " + "def gelu(x):\n", + " sqrt = bp.math.sqrt(2 / bp.math.pi)\n", + " cdf = 0.5 * (1.0 + bp.math.tanh(sqrt * (x + 0.044715 * (x ** 3))))\n", + " y = x * cdf\n", + " return y" ] }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "279 µs ± 1.36 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)\n" + ] + } + ], + "source": [ + "# jax backend, without JIT\n", + "\n", + "x = bp.math.random.random(100000)\n", + "%timeit gelu(x)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "68.4 µs ± 901 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)\n" + ] + } + ], + "source": [ + "# jax backend, with JIT\n", + "\n", + "gelu_jit = bp.math.jit(gelu)\n", + "%timeit gelu_jit(x)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, "metadata": {}, "outputs": [], "source": [ - "bp.math.use_backend('jax')\n", + "bp.math.use_backend('numpy')" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3.48 ms ± 17.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" + ] + } + ], + "source": [ + "# numpy backend, without JIT\n", "\n", - "def selu(x, alpha=1.67, lmbda=1.05):\n", - " return lmbda * bp.math.where(x > 0, x, alpha * bp.math.exp(x) - alpha)" + "x = bp.math.random.random(100000)\n", + "%timeit gelu(x)" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1.88 ms ± 68.6 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + ] + } + ], + "source": [ + "# numpy backend, with JIT\n", + "\n", + "gelu_jit = bp.math.jit(gelu)\n", + "%timeit gelu_jit(x)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "What need you to do is to simply pass the function into the [bp.math.jit()](../apis/math/generated/brainpy.math.jax.jit.rst):" + "### JIT for Objects" ] }, { - "cell_type": "code", - "execution_count": 17, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "selu_jit = bp.math.jit(selu)" + "Moreover, in BrainPy, JIT compilation can be carried on the **class objects**. Specifically, any instance of [brainpy.Base](../apis/generated/brainpy.base.Base.rst) object can be just-in-time compiled into machine codes. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Then, let's compare them:" + "Let's try a simple example, which trains a Logistic regression classifier. " ] }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 22, "metadata": {}, "outputs": [], "source": [ - "x = bp.math.random.random((1000000,))" + "class LogisticRegression(bp.Base):\n", + " def __init__(self, dimension):\n", + " super(LogisticRegression, self).__init__()\n", + "\n", + " # parameters \n", + " self.dimension = dimension\n", + " \n", + " # variables\n", + " self.w = bp.math.Variable(2.0 * bp.math.ones(dimension) - 1.3)\n", + "\n", + " def __call__(self, X, Y):\n", + " u = bp.math.dot(((1.0 / (1.0 + bp.math.exp(-Y * bp.math.dot(X, self.w))) - 1.0) * Y), X)\n", + " self.w[:] = self.w - u\n", + " \n", + "\n", + "num_dim, num_points = 10, 20000000\n", + "num_iter = 30\n", + "\n", + "points = bp.math.random.random((num_points, num_dim))\n", + "labels = bp.math.random.random(num_points)" ] }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Logistic Regression model without jit used time 18.506070852279663 s\n" + ] + } + ], + "source": [ + "# numpy backend, without JIT\n", + "\n", + "lr1 = LogisticRegression(num_dim)\n", + "lr1(points, labels)\n", + "\n", + "import time\n", + "t0 = time.time()\n", + "for i in range(num_iter):\n", + " lr1(points, labels)\n", + "\n", + "print(f'Logistic Regression model without jit used time {time.time() - t0} s')" + ] + }, + { + "cell_type": "code", + "execution_count": 24, "metadata": { "scrolled": true }, @@ -555,117 +730,531 @@ "name": "stdout", "output_type": "stream", "text": [ - "2.69 ms ± 103 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" + "Logistic Regression model with jit used time 11.58539867401123 s\n" ] } ], "source": [ - "%timeit selu(x)" + "# numpy backend, with JIT\n", + "\n", + "lr2 = LogisticRegression(num_dim)\n", + "jit_lr2 = bp.math.jit(lr2)\n", + "jit_lr2(points, labels) # first call is the compiling\n", + "\n", + "t0 = time.time()\n", + "for i in range(num_iter):\n", + " jit_lr2(points, labels)\n", + "\n", + "print(f'Logistic Regression model with jit used time {time.time() - t0} s')" ] }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 25, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "335 µs ± 13.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)\n" + "Logistic Regression model with jit+parallel used time 7.264842987060547 s\n" ] } ], "source": [ - "%timeit selu_jit(x)" + "# numpy backend, with JIT + parallel\n", + "\n", + "lr3 = LogisticRegression(num_dim)\n", + "jit_lr3 = bp.math.jit(lr3, parallel=True)\n", + "jit_lr3(points, labels) # first call is the compiling\n", + "\n", + "t0 = time.time()\n", + "for i in range(num_iter):\n", + " jit_lr3(points, labels)\n", + "\n", + "print(f'Logistic Regression model with jit+parallel used time {time.time() - t0} s')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Similarly, in NumPy backend, we can also compare the non-jitted ``selu`` function and the jitted one which wrapped by [bp.math.jit()](../apis/math/generated/brainpy.math.numpy.jit.rst):" + "What's worth noting here is that:\n", + "\n", + "1. The dynamically changed variable (weight ``w``) is marked as a **[bp.math.Variable](../apis/math/generated/brainpy.math.numpy.Variable.rst)** in `__init__()` function. \n", + "2. The variable ``w`` is in-place updated with ``[:]`` indexing in `__call__()` function." ] }, { - "cell_type": "code", - "execution_count": 36, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "bp.math.use_backend('numpy')\n", - "\n", - "def selu(x, alpha=1.67, lmbda=1.05):\n", - " return lmbda * bp.math.where(x > 0, x, alpha * bp.math.exp(x) - alpha)\n", - "\n", - "selu_jit = bp.math.jit(selu)\n", + "The above two things are all things that are *special* in the JIT compilation of class objects. Other operations and coding styles are the same with class objects without JIT acceleration. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Mechanism of JIT in NumPy backend" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "So, **why must we in-place update the dynamically changed variables?**\n", "\n", - "x = bp.math.random.random((1000000,))" + "- First of all, in the compilation phase, a ``self.`` accessed variable which is not an instance of ``bp.math.Variable`` will be compiled as a static constant. For example, ``self.a = 1.`` will be compiled as a constant ``1.``. If you try to change the value of ``self.a``, it will not work." ] }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 26, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "9.26 ms ± 173 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" + "1.0\n" ] } ], "source": [ - "%timeit selu(x)" + "class Demo1(bp.Base):\n", + " def __init__(self):\n", + " super(Demo1, self).__init__()\n", + " \n", + " self.a = 1.\n", + " \n", + " def update(self, b):\n", + " self.a = b\n", + " \n", + "\n", + "d1 = Demo1()\n", + "bp.math.jit(d1.update)(2.)\n", + "print(d1.a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- Second, all the variables you want to change during the function call must be labeled as ``bp.math.Variable``. Then during the JIT compilation period, these variables will be recompiled as arguments of the jitted functions. " ] }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 27, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "8.64 ms ± 117 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" + "The recompiled function:\n", + "-------------------------\n", + "\n", + "def update(b, Demo20_a=None):\n", + " Demo20_a = b\n", + "\n", + "\n", + "The namespace of the above function:\n", + "------------------------------------\n", + "{}\n", + "\n", + "The recompiled function:\n", + "-------------------------\n", + "def new_update(b):\n", + " update(b, Demo20_a=Demo20.a.value,)\n", + "\n", + "The namespace of the above function:\n", + "------------------------------------\n", + "{'Demo20': <__main__.Demo2 object at 0x7ff00c1cba30>,\n", + " 'update': CPUDispatcher()}\n", + "\n" ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ - "selu_jit(x)\n", + "class Demo2(bp.Base):\n", + " def __init__(self):\n", + " super(Demo2, self).__init__()\n", + " \n", + " self.a = bp.math.Variable(1.)\n", + " \n", + " def update(self, b):\n", + " self.a = b\n", + " \n", "\n", - "%timeit selu_jit(x)" + "bp.math.jit(Demo2().update, show_code=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "However, in BrainPy, JIT compilation can be carried on the objects. " + "The original ``Demo2.update`` function is recompiled as ``update()`` function, with the dynamical variable ``a`` compiled as an argument ``Demo20_a``. Then, during the functional call (in the ``new_update()`` function), ``Demo20.a.value`` is passed to ``Demo20_a`` for the jitted ``update()`` function. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- Third, as you can notice in the above source code of the recompiled function, the recompiled variable ``Demo20_a`` does not return. This means once the function finished running, the computed value will disappear. Therefore, the dynamically changed variables must be in-place updated to hold their updated values. " ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 28, "metadata": {}, - "outputs": [], - "source": [] + "outputs": [ + { + "data": { + "text/plain": [ + "Variable(2.)" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "class Demo3(bp.Base):\n", + " def __init__(self):\n", + " super(Demo3, self).__init__()\n", + " \n", + " self.a = bp.math.Variable(1.)\n", + " \n", + " def update(self, b):\n", + " self.a[...] = b\n", + " \n", + "\n", + "d3 = Demo3()\n", + "bp.math.jit(d3.update)(2.)\n", + "d3.a" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The above simple demonstrations illustrate the core mechanism of the JIT compilation in NumPy backend. [bp.math.jit()](../apis/math/generated/brainpy.math.numpy.jit.rst) in NumPy backend can recursively compile your class objects. So, please try you models, and run it under the JIT accelerations. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The mechanism of JIT compilation of JAX backend is quite different. We will detail this in th upcoming tutorials. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### In-place operators" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the next, what's the most important question is: **what are in-place operators?**" + ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 29, "metadata": {}, - "outputs": [], - "source": [] + "outputs": [ + { + "data": { + "text/plain": [ + "140668971726128" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "v = bp.math.arange(10)\n", + "\n", + "id(v)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Actually, in-place operators include the following operations:\n", + "\n", + "1. **Indexing and slicing**. Like (More details please refer to [Array Objects Indexing](https://numpy.org/doc/stable/reference/arrays.indexing.html))\n", + " - Index: ``v[i] = a``\n", + " - Slice: ``v[i:j] = b``\n", + " - Slice the specific values: ``v[[1, 3]] = c``\n", + " - Slice all values, ``v[:] = d``, ``v[...] = e``" + ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 30, "metadata": {}, - "outputs": [], - "source": [] + "outputs": [ + { + "data": { + "text/plain": [ + "140668971726128" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "v[0] = 1\n", + "\n", + "id(v)" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "140668971726128" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "v[1: 2] = 1\n", + "\n", + "id(v)" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "140668971726128" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "v[[1, 3]] = 2\n", + "\n", + "id(v)" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "140668971726128" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "v[:] = 0\n", + "\n", + "id(v)" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "140668971726128" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "v[...] = bp.math.arange(10)\n", + "\n", + "id(v)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "2. **Augmented assignment**. All augmented assignment are in-place operations, which include \n", + " - ``+=`` (add)\n", + " - ``-=`` (subtract)\n", + " - ``/=`` (divide)\n", + " - ``*=`` (multiply)\n", + " - ``//=`` (floor divide)\n", + " - ``%=`` (modulo)\n", + " - ``**=`` (power)\n", + " - ``&=`` (and)\n", + " - ``|=`` (or)\n", + " - ``^=`` (xor)\n", + " - ``<<=`` (left shift)\n", + " - ``>>=`` (right shift)" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "140668971726128" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "v += 1\n", + "\n", + "id(v)" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "140668971726128" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "v *= 2\n", + "\n", + "id(v)" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "140668971726128" + ] + }, + "execution_count": 37, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "v |= bp.math.random.randint(0, 2, 10)\n", + "\n", + "id (v)" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "140668971726128" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "v **= 2.\n", + "\n", + "id(v)" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "140668971726128" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "v >>= 2\n", + "\n", + "id(v)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "More advanced usage please see our forthcoming tutorials. " + ] } ], "metadata": { @@ -713,7 +1302,12 @@ "title_cell": "Table of Contents", "title_sidebar": "Contents", "toc_cell": false, - "toc_position": {}, + "toc_position": { + "height": "calc(100% - 180px)", + "left": "10px", + "top": "150px", + "width": "245.76px" + }, "toc_section_display": true, "toc_window_display": true } -- 2.34.1 From af66f1d5e6b8de54c3a7d3946dcc3963ccfd5b57 Mon Sep 17 00:00:00 2001 From: chaoming Date: Tue, 7 Sep 2021 23:23:38 +0800 Subject: [PATCH 10/30] update docs --- changelog.rst | 21 ++++++++++-- docs/apis/math/general.rst | 4 +++ docs/apis/math/jax.rst | 4 ++- docs/apis/math/numpy.rst | 4 ++- ...ainpy.simulation.brainobjects.Dendrite.rst | 34 ------------------- ....simulation.brainobjects.DynamicSystem.rst | 34 ------------------- ...inpy.simulation.brainobjects.Molecular.rst | 34 ------------------- .../brainpy.simulation.brainobjects.Soma.rst | 34 ------------------- docs/index.rst | 2 +- 9 files changed, 29 insertions(+), 142 deletions(-) delete mode 100644 docs/apis/simulation/generated/brainpy.simulation.brainobjects.Dendrite.rst delete mode 100644 docs/apis/simulation/generated/brainpy.simulation.brainobjects.DynamicSystem.rst delete mode 100644 docs/apis/simulation/generated/brainpy.simulation.brainobjects.Molecular.rst delete mode 100644 docs/apis/simulation/generated/brainpy.simulation.brainobjects.Soma.rst diff --git a/changelog.rst b/changelog.rst index 5f5ff57d..311cc6a4 100644 --- a/changelog.rst +++ b/changelog.rst @@ -17,6 +17,7 @@ Highlights of core changes: - support numba ``jit`` on class objects - unified numpy-like API + ``base`` module ~~~~~~~~~~~~~~~ @@ -24,11 +25,13 @@ Highlights of core changes: - ``Function`` to wrap functions - ``Collector`` and ``ArrayCollector`` to collect variables, integrators, nodes and others + ``integrators`` module ~~~~~~~~~~~~~~~~~~~~~~ - detailed documentation for ODE numerical methods + ``simulation`` module ~~~~~~~~~~~~~~~~~~~~~ @@ -36,6 +39,13 @@ Highlights of core changes: - support multi-scale modeling - support large-scale modeling - support simulation on GPUs +- fix bugs on ``firing_rate()`` +- remove ``_i`` in ``update()`` function, replace ``_i`` with ``_dt``, + meaning the dynamic system has the canonic equation form + of :math:`dx/dt = f(x, t, dt)` +- reimplement the ``input_step`` and ``monitor_step`` in a more intuitive way +- support to set `dt` in the single object level (i.e., single instance of DynamicSystem) + ``dnn`` module ~~~~~~~~~~~~~~ @@ -47,10 +57,15 @@ Highlights of core changes: - initializations - common used layers -``measure`` module -~~~~~~~~~~~~~~~~~~ -- fix bugs on ``firing_rate()`` +documentation +~~~~~~~~~~~~~ + +- documentation for ``base`` module +- documentation for ``math`` module +- documentation for ``integrators`` module +- documentation for ``dnn`` module + diff --git a/docs/apis/math/general.rst b/docs/apis/math/general.rst index 0a91cd2e..4ea13832 100644 --- a/docs/apis/math/general.rst +++ b/docs/apis/math/general.rst @@ -12,4 +12,8 @@ General Functions get_backend_name set_dt get_dt + set_int_ + set_float_ + set_complex_ + diff --git a/docs/apis/math/jax.rst b/docs/apis/math/jax.rst index cc5b695c..622e416c 100644 --- a/docs/apis/math/jax.rst +++ b/docs/apis/math/jax.rst @@ -13,4 +13,6 @@ Core Functions in JAX backend pmap grad value_and_grad - + Variable + TrainVar + Parameter diff --git a/docs/apis/math/numpy.rst b/docs/apis/math/numpy.rst index 3286093f..fecc42f4 100644 --- a/docs/apis/math/numpy.rst +++ b/docs/apis/math/numpy.rst @@ -13,4 +13,6 @@ Core Functions in NumPy backend pmap grad value_and_grad - + Variable + TrainVar + Parameter diff --git a/docs/apis/simulation/generated/brainpy.simulation.brainobjects.Dendrite.rst b/docs/apis/simulation/generated/brainpy.simulation.brainobjects.Dendrite.rst deleted file mode 100644 index f1992f01..00000000 --- a/docs/apis/simulation/generated/brainpy.simulation.brainobjects.Dendrite.rst +++ /dev/null @@ -1,34 +0,0 @@ -brainpy.simulation.brainobjects.Dendrite -======================================== - -.. currentmodule:: brainpy.simulation.brainobjects - -.. autoclass:: Dendrite - - - .. automethod:: __init__ - - - .. rubric:: Methods - - .. autosummary:: - - ~Dendrite.__init__ - ~Dendrite.ints - ~Dendrite.nodes - ~Dendrite.run - ~Dendrite.train_vars - ~Dendrite.unique_name - ~Dendrite.vars - - - - - - .. rubric:: Attributes - - .. autosummary:: - - ~Dendrite.target_backend - - \ No newline at end of file diff --git a/docs/apis/simulation/generated/brainpy.simulation.brainobjects.DynamicSystem.rst b/docs/apis/simulation/generated/brainpy.simulation.brainobjects.DynamicSystem.rst deleted file mode 100644 index 2b63e600..00000000 --- a/docs/apis/simulation/generated/brainpy.simulation.brainobjects.DynamicSystem.rst +++ /dev/null @@ -1,34 +0,0 @@ -brainpy.simulation.brainobjects.DynamicSystem -============================================= - -.. currentmodule:: brainpy.simulation.brainobjects - -.. autoclass:: DynamicSystem - - - .. automethod:: __init__ - - - .. rubric:: Methods - - .. autosummary:: - - ~DynamicSystem.__init__ - ~DynamicSystem.ints - ~DynamicSystem.nodes - ~DynamicSystem.run - ~DynamicSystem.train_vars - ~DynamicSystem.unique_name - ~DynamicSystem.vars - - - - - - .. rubric:: Attributes - - .. autosummary:: - - ~DynamicSystem.target_backend - - \ No newline at end of file diff --git a/docs/apis/simulation/generated/brainpy.simulation.brainobjects.Molecular.rst b/docs/apis/simulation/generated/brainpy.simulation.brainobjects.Molecular.rst deleted file mode 100644 index 63a314a9..00000000 --- a/docs/apis/simulation/generated/brainpy.simulation.brainobjects.Molecular.rst +++ /dev/null @@ -1,34 +0,0 @@ -brainpy.simulation.brainobjects.Molecular -========================================= - -.. currentmodule:: brainpy.simulation.brainobjects - -.. autoclass:: Molecular - - - .. automethod:: __init__ - - - .. rubric:: Methods - - .. autosummary:: - - ~Molecular.__init__ - ~Molecular.ints - ~Molecular.nodes - ~Molecular.run - ~Molecular.train_vars - ~Molecular.unique_name - ~Molecular.vars - - - - - - .. rubric:: Attributes - - .. autosummary:: - - ~Molecular.target_backend - - \ No newline at end of file diff --git a/docs/apis/simulation/generated/brainpy.simulation.brainobjects.Soma.rst b/docs/apis/simulation/generated/brainpy.simulation.brainobjects.Soma.rst deleted file mode 100644 index eac90a24..00000000 --- a/docs/apis/simulation/generated/brainpy.simulation.brainobjects.Soma.rst +++ /dev/null @@ -1,34 +0,0 @@ -brainpy.simulation.brainobjects.Soma -==================================== - -.. currentmodule:: brainpy.simulation.brainobjects - -.. autoclass:: Soma - - - .. automethod:: __init__ - - - .. rubric:: Methods - - .. autosummary:: - - ~Soma.__init__ - ~Soma.ints - ~Soma.nodes - ~Soma.run - ~Soma.train_vars - ~Soma.unique_name - ~Soma.vars - - - - - - .. rubric:: Attributes - - .. autosummary:: - - ~Soma.target_backend - - \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index 39f96e6c..c621b582 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -16,7 +16,7 @@ high-performance brain modeling. Among its key ingredients, BrainPy supports: 6. And more ...... -.. _BrainPy:: https://github.com/PKU-NIP-Lab/BrainPy +.. _BrainPy: https://github.com/PKU-NIP-Lab/BrainPy .. note:: -- 2.34.1 From d7f8ddb049848a2465ab43d5fe114935640bcb2e Mon Sep 17 00:00:00 2001 From: chaoming Date: Tue, 7 Sep 2021 23:24:12 +0800 Subject: [PATCH 11/30] update TODO --- .gitignore | 1 + TODO.md | 16 +++++++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 70213cc3..75139841 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ docs/images/connection_methods.pptx */generated docs/apis/math/generated +docs/apis/simulation/generated docs/apis/dnn/_autosummary docs/quickstart/_autosummary docs/apis/dnn/generated diff --git a/TODO.md b/TODO.md index d7da38e9..8d4f4af8 100644 --- a/TODO.md +++ b/TODO.md @@ -8,7 +8,7 @@ -# Version 1.1.0-alpha +# Version 1.1.0 @@ -26,14 +26,16 @@ ## Math +- [ ] **[Numpy]**: JIT compilation in numpy backend supports ``Base`` and ``Function`` objects +- [ ] **[Numpy]**: support JIT in `fft` sub-module - [x] **[Numpy]:** support JIT compilation in numpy backend (done @ 2021.09.01 by @chaoming) - [x] **[Numpy]:** support 'fft' (done @ 2021.09.03 by @chaoming) -- [ ] support to set `dt` in the single object level (i.e., single instance of DynamicSystem) - [ ] **[JAX]:** "random" module - [ ] **[JAX]:** math operations - [ ] **[JAX]:** vmap - [ ] **[JAX]:** pmap - [ ] **[JAX]:** control conditions: +- [ ] static_argnums - [x] **[JAX]:** support 'fft' (done @ 2021.09.03 by @chaoming) - [x] **[JAX]:** **IMPORTANT!!!** Change API of `grad()` and `value_and_grad()`: There are bugs in the gradient functions. Gradient computation also needs to inspect the variable types. Moreover, it is independent from the JIT function. Therefore, we should pass dynamical variables into the gradient functions too. (done @ 2021.08.26 by @chaoming) - [x] **[JAX]:** **IMPORTANT!!!** change API of `vars()`: we should refer Dynamical Variables as `Variable`; We can not retrieve every "JaxArray" from `vars()`, otherwise the whole system will waste a lot of time on useless assignments. (done @ 2021.08.25 by @chaoming) @@ -42,7 +44,7 @@ - [x] register pytree (done @ 2021.06.15 by @chaoming) - [x] support `ndarray` intrinsic methods: - [x] functions in NumPy ndarray: any(), all() .... view() (done @ 2021.06.30 by @chaoming) - - [ ] functions in JAX DeviceArray: + - [x] functions in JAX DeviceArray: - [x] numpy methods in JaxArray (done @ 2021.08.25, @2021.08.28 by @chaoming) - [x] documentation for JaxArray methods (done @ 2021.08.25 by @chaoming) - [ ] test for ndarray wrapper @@ -74,6 +76,9 @@ - [ ] Allow defining the `Soma` object - [ ] Allow defining the `Dendrite` object +- [x] support to set `dt` in the single object level (i.e., single instance of DynamicSystem) (done @2021.09.05 @chaoming) +- [x] reimplement the ``input_step`` and ``monitor_step`` in a more intuitive way (done @2021.09 by chaoming) +- [x] remove ``_i`` in ``update()`` function, replace ``_i`` with ``_dt``, meaning the dynamic system has the canonic equation form of $dx/dt = f(x, t, dt)$ (done @2021.09 by chaoming) @@ -102,8 +107,9 @@ ## Documentation +- [x] documentation for ``math`` module (done @2021.09) - [ ] detailed documentation for numerical solvers of SDEs -- [ ] doc comments for ODEs, like Euler, RK2, etc. We should provide the detailed mathematical equations, and the corresponding suggestions for the corresponding algorithm. +- [x] doc comments for ODEs, like Euler, RK2, etc. We should provide the detailed mathematical equations, and the corresponding suggestions for the corresponding algorithm. (done @2021.09.01 @chaoming) - [x] APIs for integrators (done @2021/08/23 by @chaoming) - [x] installation instruction, especially package dependency (done @2021/08/23 by @chaoming) @@ -125,7 +131,7 @@ ## Others -- [ ] publish `BrainPy` on `"conda-forge"`: https://conda-forge.org/docs/maintainer/adding_pkgs.html# +- [x] publish `BrainPy` on `"conda-forge"`: https://conda-forge.org/docs/maintainer/adding_pkgs.html# (cancelled) -- 2.34.1 From e6eacbfff8538f6f356125278f7f5a72811add9b Mon Sep 17 00:00:00 2001 From: chaoming Date: Wed, 8 Sep 2021 10:13:51 +0800 Subject: [PATCH 12/30] update quickstart --- docs/quickstart/dynamics_simulation.ipynb | 11 ++++++----- docs/quickstart/jit_compilation.ipynb | 8 +++++--- docs/quickstart/numerical_solvers.ipynb | 2 +- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/docs/quickstart/dynamics_simulation.ipynb b/docs/quickstart/dynamics_simulation.ipynb index 73427bee..59d80923 100644 --- a/docs/quickstart/dynamics_simulation.ipynb +++ b/docs/quickstart/dynamics_simulation.ipynb @@ -39,7 +39,9 @@ "import sys\n", "sys.path.append('../../')\n", "\n", - "import brainpy as bp" + "import brainpy as bp\n", + "\n", + "bp.__version__" ] }, { @@ -385,7 +387,7 @@ "\n", " def update(self, _t, _i):\n", " V, m, h, n = self.integral(self.V, self.m, self.h, self.n, _t, self.input)\n", - " self.spike = bp.math.logical_and(self.V < self.V_th, V >= self.V_th)\n", + " self.spike[:] = bp.math.logical_and(self.V < self.V_th, V >= self.V_th)\n", " self.V[:] = V\n", " self.m[:] = m\n", " self.h[:] = h\n", @@ -711,8 +713,7 @@ "metadata": { "hide_input": false, "jupytext": { - "encoding": "# -*- coding: utf-8 -*-", - "formats": "ipynb,py:percent" + "encoding": "# -*- coding: utf-8 -*-" }, "kernelspec": { "display_name": "brainpy", @@ -729,7 +730,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.11" + "version": "3.8.10" }, "latex_envs": { "LaTeX_envs_menu_present": true, diff --git a/docs/quickstart/jit_compilation.ipynb b/docs/quickstart/jit_compilation.ipynb index 38b3da31..bf0421ff 100644 --- a/docs/quickstart/jit_compilation.ipynb +++ b/docs/quickstart/jit_compilation.ipynb @@ -34,7 +34,9 @@ } ], "source": [ - "import brainpy as bp" + "import brainpy as bp\n", + "\n", + "bp.__version__" ] }, { @@ -531,7 +533,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "To take advantage of the JIT compilation, users just need to wrap their customized *functions* or *objects* into **[bp.math.jit()](../apis/math/generated/brainpy.math.numpy.jit.rst)** to instruct BrainPy to transform your codes into machine codes. \n", + "To take advantage of the JIT compilation, users just need to wrap their customized *functions* or *objects* into [bp.math.jit()](../apis/math/generated/brainpy.math.numpy.jit.rst) to instruct BrainPy to transform your codes into machine codes. \n", "\n", "\n", "Take the **pure functions** as the example. Here we try to implement a function of Gaussian Error Linear Unit," @@ -781,7 +783,7 @@ "source": [ "What's worth noting here is that:\n", "\n", - "1. The dynamically changed variable (weight ``w``) is marked as a **[bp.math.Variable](../apis/math/generated/brainpy.math.numpy.Variable.rst)** in `__init__()` function. \n", + "1. The dynamically changed variable (weight ``w``) is marked as a [bp.math.Variable](../apis/math/generated/brainpy.math.numpy.Variable.rst) in `__init__()` function. \n", "2. The variable ``w`` is in-place updated with ``[:]`` indexing in `__call__()` function." ] }, diff --git a/docs/quickstart/numerical_solvers.ipynb b/docs/quickstart/numerical_solvers.ipynb index 4a7ee47c..cfd63ed2 100644 --- a/docs/quickstart/numerical_solvers.ipynb +++ b/docs/quickstart/numerical_solvers.ipynb @@ -850,7 +850,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.11" + "version": "3.8.10" }, "latex_envs": { "LaTeX_envs_menu_present": true, -- 2.34.1 From 794ef2c21a5e65e6eb735c2c9c9f96c745e218f8 Mon Sep 17 00:00:00 2001 From: chaoming Date: Wed, 8 Sep 2021 17:18:46 +0800 Subject: [PATCH 13/30] delete develop folder --- develop/benchmark/COBA/COBA-ANNarchy.py | 60 --- develop/benchmark/COBA/COBA-Nest.py | 69 ---- develop/benchmark/COBA/COBA.py | 355 ------------------ develop/benchmark/COBA/COBA_brainpy.py | 116 ------ develop/benchmark/COBA/COBA_brian2.py | 52 --- develop/benchmark/COBA/pynn.py | 275 -------------- develop/benchmark/COBAHH/COBAHH_brainpy.py | 149 -------- develop/benchmark/COBAHH/COBAHH_brian2.py | 85 ----- develop/benchmark/COBAHH/COBAHH_nest.sli | 174 --------- .../COBAHH/COBAHH_neuron/COBAHH_neuron.hoc | 52 --- .../benchmark/COBAHH/COBAHH_neuron/hhcell.hoc | 89 ----- .../benchmark/COBAHH/COBAHH_neuron/hhchan.ses | 101 ----- .../benchmark/COBAHH/COBAHH_neuron/init.hoc | 138 ------- .../COBAHH/COBAHH_neuron/intrinsic.hoc | 6 - .../COBAHH/COBAHH_neuron/intrinsic.ses | 69 ---- .../benchmark/COBAHH/COBAHH_neuron/net.hoc | 74 ---- .../COBAHH/COBAHH_neuron/netstim.hoc | 74 ---- .../COBAHH/COBAHH_neuron/perfrun.hoc | 175 --------- .../COBAHH/COBAHH_neuron/ranstream.hoc | 18 - .../benchmark/COBAHH/COBAHH_neuron/spkplt.hoc | 14 - develop/benchmark/COBAHH/how_to_run.md | 18 - develop/benchmark/COBAHH/param_nest.sli | 80 ---- develop/benchmark/CUBA/CUBA_brainpy.py | 113 ------ develop/benchmark/CUBA/CUBA_brian2.py | 41 -- develop/benchmark/scaling_test.py | 141 ------- develop/conda-recipe/meta.yaml | 42 --- 26 files changed, 2580 deletions(-) delete mode 100644 develop/benchmark/COBA/COBA-ANNarchy.py delete mode 100644 develop/benchmark/COBA/COBA-Nest.py delete mode 100644 develop/benchmark/COBA/COBA.py delete mode 100644 develop/benchmark/COBA/COBA_brainpy.py delete mode 100644 develop/benchmark/COBA/COBA_brian2.py delete mode 100644 develop/benchmark/COBA/pynn.py delete mode 100644 develop/benchmark/COBAHH/COBAHH_brainpy.py delete mode 100644 develop/benchmark/COBAHH/COBAHH_brian2.py delete mode 100644 develop/benchmark/COBAHH/COBAHH_nest.sli delete mode 100644 develop/benchmark/COBAHH/COBAHH_neuron/COBAHH_neuron.hoc delete mode 100644 develop/benchmark/COBAHH/COBAHH_neuron/hhcell.hoc delete mode 100644 develop/benchmark/COBAHH/COBAHH_neuron/hhchan.ses delete mode 100644 develop/benchmark/COBAHH/COBAHH_neuron/init.hoc delete mode 100644 develop/benchmark/COBAHH/COBAHH_neuron/intrinsic.hoc delete mode 100644 develop/benchmark/COBAHH/COBAHH_neuron/intrinsic.ses delete mode 100644 develop/benchmark/COBAHH/COBAHH_neuron/net.hoc delete mode 100644 develop/benchmark/COBAHH/COBAHH_neuron/netstim.hoc delete mode 100644 develop/benchmark/COBAHH/COBAHH_neuron/perfrun.hoc delete mode 100644 develop/benchmark/COBAHH/COBAHH_neuron/ranstream.hoc delete mode 100644 develop/benchmark/COBAHH/COBAHH_neuron/spkplt.hoc delete mode 100644 develop/benchmark/COBAHH/how_to_run.md delete mode 100644 develop/benchmark/COBAHH/param_nest.sli delete mode 100644 develop/benchmark/CUBA/CUBA_brainpy.py delete mode 100644 develop/benchmark/CUBA/CUBA_brian2.py delete mode 100644 develop/benchmark/scaling_test.py delete mode 100644 develop/conda-recipe/meta.yaml diff --git a/develop/benchmark/COBA/COBA-ANNarchy.py b/develop/benchmark/COBA/COBA-ANNarchy.py deleted file mode 100644 index 340777fe..00000000 --- a/develop/benchmark/COBA/COBA-ANNarchy.py +++ /dev/null @@ -1,60 +0,0 @@ -from ANNarchy import * - -setup(dt=0.05) - -NE = 3200 # Number of excitatory cells -NI = 800 # Number of inhibitory cells -duration = 5.0 * 1000.0 # Total time of the simulation - -COBA = Neuron( - parameters=""" - El = -60.0 : population - Vr = -60.0 : population - Erev_exc = 0.0 : population - Erev_inh = -80.0 : population - Vt = -50.0 : population - tau = 20.0 : population - tau_exc = 5.0 : population - tau_inh = 10.0 : population - I = 20.0 : population - """, - equations=""" - tau * dv/dt = (El - v) + g_exc * (Erev_exc - v) + g_inh * (Erev_inh - v ) + I - tau_exc * dg_exc/dt = - g_exc - tau_inh * dg_inh/dt = - g_inh - """, - spike=""" - v > Vt - """, - reset=""" - v = Vr - """, - refractory=5.0 -) - -P = Population(geometry=NE + NI, neuron=COBA) -Pe = P[:NE] -Pi = P[NE:] -P.v = Normal(-55.0, 5.0) - -we = 6. / 10. # excitatory synaptic weight (voltage) -wi = 67. / 10. # inhibitory synaptic weight - -Ce = Projection(pre=Pe, post=P, target='exc') -Ce.connect_fixed_probability(weights=we, probability=0.02) -Ci = Projection(pre=Pi, post=P, target='inh') -Ci.connect_fixed_probability(weights=wi, probability=0.02) - -compile() - -m = Monitor(P, 'spike') -simulate(duration) - -t, n = m.raster_plot() -print('Number of spikes:', len(t)) - -from pylab import * -plot(t, n, '.') -xlabel('Time (ms)') -ylabel('Neuron index') -show() diff --git a/develop/benchmark/COBA/COBA-Nest.py b/develop/benchmark/COBA/COBA-Nest.py deleted file mode 100644 index e45aa8fd..00000000 --- a/develop/benchmark/COBA/COBA-Nest.py +++ /dev/null @@ -1,69 +0,0 @@ -import time - -import numpy -from nest import * - -numpy.random.seed(98765) -SetKernelStatus({"resolution": 0.1}) -# nb_threads = 4 -# SetKernelStatus({"local_num_threads": int(nb_threads)}) - -simtime = 5 * 1000.0 # [ms] Simulation time -NE = 3200 # number of exc. neurons -NI = 800 # number of inh. neurons - -SetDefaults("iaf_cond_exp", { - "C_m": 200., - "g_L": 10., - "tau_syn_ex": 5., - "tau_syn_in": 10., - "E_ex": 0., - "E_in": -80., - "t_ref": 5., - "E_L": -60., - "V_th": -50., - "I_e": 200., - "V_reset": -60., - "V_m": -60. -}) - -nodes_ex = Create("iaf_cond_exp", NE) -nodes_in = Create("iaf_cond_exp", NI) -nodes = nodes_ex + nodes_in - -# Initialize the membrane potentials -v = -55.0 + 5.0 * numpy.random.normal(size=NE + NI) -for i, node in enumerate(nodes): - SetStatus([node], {"V_m": v[i]}) - -# Create the synapses -w_exc = 6. -w_inh = -67. -SetDefaults("static_synapse", {"delay": 0.1}) -CopyModel("static_synapse", "excitatory", {"weight": w_exc}) -CopyModel("static_synapse", "inhibitory", {"weight": w_inh}) - - - -Connect(nodes_ex, nodes,{'rule': 'pairwise_bernoulli', 'p': 0.02}, syn_spec="excitatory") -Connect(nodes_in, nodes,{'rule': 'pairwise_bernoulli', 'p': 0.02}, syn_spec="inhibitory") - -# Spike detectors -SetDefaults("spike_detector", {"withtime": True, - "withgid": True, - "to_file": False}) -espikes = Create("spike_detector") -ispikes = Create("spike_detector") -Connect(nodes_ex, espikes, 'all_to_all') -Connect(nodes_in, ispikes, 'all_to_all') - -tstart = time.time() -Simulate(simtime) -print('Done in', time.time() - tstart) - -events_ex = GetStatus(espikes, "n_events")[0] -events_in = GetStatus(ispikes, "n_events")[0] -print('Total spikes:', events_ex + events_in) - -nest.raster_plot.from_device(espikes, hist=True) -nest.raster_plot.show() diff --git a/develop/benchmark/COBA/COBA.py b/develop/benchmark/COBA/COBA.py deleted file mode 100644 index 60774636..00000000 --- a/develop/benchmark/COBA/COBA.py +++ /dev/null @@ -1,355 +0,0 @@ -# -*- coding: utf-8 -*- - -from ANNarchy import * -from brian2 import * -from nest import * -import brainpy as bp - -import os -import json -import time -import numpy as np -import matplotlib.pyplot as plt - -dt = 0.05 -setup(dt=dt) - - -def run_brianpy(num_neu, duration, device='cpu'): - bp.backend.set('numba', dt=dt) - - # Parameters - num_inh = int(num_neu / 5) - num_exc = num_neu - num_inh - taum = 20 - taue = 5 - taui = 10 - Vt = -50 - Vr = -60 - El = -60 - Erev_exc = 0. - Erev_inh = -80. - I = 20. - we = 0.6 # excitatory synaptic weight (voltage) - wi = 6.7 # inhibitory synaptic weight - ref = 5.0 - - class LIF(bp.NeuGroup): - target_backend = ['numpy', 'numba'] - - def __init__(self, size, **kwargs): - super(LIF, self).__init__(size=size, **kwargs) - # variables - self.V = bp.ops.zeros(size) - self.spike = bp.ops.zeros(size) - self.ge = bp.ops.zeros(size) - self.gi = bp.ops.zeros(size) - self.input = bp.ops.zeros(size) - self.t_last_spike = bp.ops.ones(size) * -1e7 - - @staticmethod - @bp.odeint - def int_g(ge, gi, t): - dge = - ge / taue - dgi = - gi / taui - return dge, dgi - - @staticmethod - @bp.odeint - def int_V(V, t, ge, gi): - dV = (ge * (Erev_exc - V) + gi * (Erev_inh - V) + El - V + I) / taum - return dV - - def update(self, _t, _i): - self.ge, self.gi = self.int_g(self.ge, self.gi, _t) - for i in range(self.size[0]): - self.spike[i] = 0. - if (_t - self.t_last_spike[i]) > ref: - V = self.int_V(self.V[i], _t, self.ge[i], self.gi[i]) - if V >= Vt: - self.V[i] = Vr - self.spike[i] = 1. - self.t_last_spike[i] = _t - else: - self.V[i] = V - self.input[i] = I - - class ExcSyn(bp.TwoEndConn): - target_backend = ['numpy', 'numba'] - - def __init__(self, pre, post, conn, **kwargs): - self.conn = conn(pre.size, post.size) - self.pre2post = self.conn.requires('pre2post') - super(ExcSyn, self).__init__(pre=pre, post=post, **kwargs) - - def update(self, _t, _i): - for pre_id, spike in enumerate(self.pre.spike): - if spike > 0: - for post_i in self.pre2post[pre_id]: - self.post.ge[post_i] += we - - class InhSyn(bp.TwoEndConn): - target_backend = ['numpy', 'numba'] - - def __init__(self, pre, post, conn, **kwargs): - self.conn = conn(pre.size, post.size) - self.pre2post = self.conn.requires('pre2post') - super(InhSyn, self).__init__(pre=pre, post=post, **kwargs) - - def update(self, _t, _i): - for pre_id, spike in enumerate(self.pre.spike): - if spike > 0: - for post_i in self.pre2post[pre_id]: - self.post.gi[post_i] += wi - - E_group = LIF(num_exc, monitors=['spike']) - E_group.V = np.random.randn(num_exc) * 5. - 55. - I_group = LIF(num_inh, monitors=['spike']) - I_group.V = np.random.randn(num_inh) * 5. - 55. - E2E = ExcSyn(pre=E_group, post=E_group, conn=bp.connect.FixedProb(0.02)) - E2I = ExcSyn(pre=E_group, post=I_group, conn=bp.connect.FixedProb(0.02)) - I2E = InhSyn(pre=I_group, post=E_group, conn=bp.connect.FixedProb(0.02)) - I2I = InhSyn(pre=I_group, post=I_group, conn=bp.connect.FixedProb(0.02)) - - net = bp.Network(E_group, I_group, E2E, E2I, I2E, I2I) - - t0 = time.time() - net.run(duration) - t = time.time() - t0 - print(f'BrainPy ({device}) used time {t} s.') - return t - - -def run_annarchy(num_neu, duration, device='cpu'): - NI = int(num_neu / 5) - NE = num_neu - NI - - clear() - - COBA = Neuron( - parameters=""" - El = -60.0 : population - Vr = -60.0 : population - Erev_exc = 0.0 : population - Erev_inh = -80.0 : population - Vt = -50.0 : population - tau = 20.0 : population - tau_exc = 5.0 : population - tau_inh = 10.0 : population - I = 20.0 : population - """, - equations=""" - tau * dv/dt = (El - v) + g_exc * (Erev_exc - v) + g_inh * (Erev_inh - v ) + I - tau_exc * dg_exc/dt = - g_exc - tau_inh * dg_inh/dt = - g_inh - """, - spike=""" - v > Vt - """, - reset=""" - v = Vr - """, - refractory=5.0 - ) - - # ########################################### - # Population - # ########################################### - P = Population(geometry=NE + NI, neuron=COBA) - Pe = P[:NE] - Pi = P[NE:] - P.v = Normal(-55.0, 5.0) - - # ########################################### - # Projections - # ########################################### - we = 6. / 10. # excitatory synaptic weight (voltage) - wi = 67. / 10. # inhibitory synaptic weight - - Ce = Projection(pre=Pe, post=P, target='exc') - Ci = Projection(pre=Pi, post=P, target='inh') - Ce.connect_fixed_probability(weights=we, probability=0.02) - Ci.connect_fixed_probability(weights=wi, probability=0.02) - - t0 = time.time() - compile() - simulate(duration) - t = time.time() - t0 - print(f'ANNarchy ({device}) used time {t} s.') - return t - - -def run_brian2(num_neu, duration): - num_inh = int(num_neu / 5) - num_exc = num_neu - num_inh - - start_scope() - device.reinit() - device.activate() - - defaultclock.dt = dt * ms - set_device('cpp_standalone', directory='brian2_COBA') - # device.build() - # prefs.codegen.target = "cython" - - taum = 20 * ms - taue = 5 * ms - taui = 10 * ms - Vt = -50 * mV - Vr = -60 * mV - El = -60 * mV - Erev_exc = 0. * mV - Erev_inh = -80. * mV - I = 20. * mvolt - - eqs = ''' - dv/dt = (ge*(Erev_exc-v)+gi*(Erev_inh-v)-(v-El) + I)*(1./taum) : volt (unless refractory) - dge/dt = -ge/taue : 1 - dgi/dt = -gi/taui : 1 - ''' - net = Network() - - # ########################################### - # Population - # ########################################### - P = NeuronGroup(num_exc + num_inh, - model=eqs, - threshold='v>Vt', reset='v = Vr', - refractory=5 * ms, method='euler') - net.add(P) - - # ########################################### - # Projections - # ########################################### - - we = 0.6 # excitatory synaptic weight (voltage) - wi = 6.7 # inhibitory synaptic weight - Ce = Synapses(P[:num_exc], P, on_pre='ge += we') - Ci = Synapses(P[num_exc:], P, on_pre='gi += wi') - net.add(Ce, Ci) - - P.v = (np.random.randn(num_exc + num_inh) * 5. - 55.) * mvolt - Ce.connect(p=0.02) - Ci.connect(p=0.02) - - t1 = time.time() - net.run(duration * ms) - t = time.time() - t1 - print(f'Brian2 used {t} s') - return t - - -def run_pynest(num_neu, duration): - NI = int(num_neu / 5) - NE = num_neu - NI - - ResetKernel() - SetKernelStatus({"resolution": dt}) - # nb_threads = 4 - # SetKernelStatus({"local_num_threads": int(nb_threads)}) - - SetDefaults("iaf_cond_exp", { - "C_m": 200., - "g_L": 10., - "tau_syn_ex": 5., - "tau_syn_in": 10., - "E_ex": 0., - "E_in": -80., - "t_ref": 5., - "E_L": -60., - "V_th": -50., - "I_e": 200., - "V_reset": -60., - "V_m": -60. - }) - - # ########################################### - # Population - # ########################################### - nodes_ex = Create("iaf_cond_exp", NE) - nodes_in = Create("iaf_cond_exp", NI) - nodes = nodes_ex + nodes_in - - # Initialize the membrane potentials - v = -55.0 + 5.0 * np.random.normal(size=NE + NI) - for i, node in enumerate(nodes): - SetStatus([node], {"V_m": v[i]}) - - # Create the synapses - w_exc = 6. - w_inh = -67. - SetDefaults("static_synapse", {"delay": 0.1}) - CopyModel("static_synapse", "excitatory", {"weight": w_exc}) - CopyModel("static_synapse", "inhibitory", {"weight": w_inh}) - - conn_dict = {'rule': 'pairwise_bernoulli', 'p': 0.02} - Connect(nodes_ex, nodes, conn_dict, syn_spec="excitatory") - Connect(nodes_in, nodes, conn_dict, syn_spec="inhibitory") - - # Spike detectors - SetDefaults("spike_detector", {"withtime": True, - "withgid": True, - "to_file": False}) - espikes = Create("spike_detector") - ispikes = Create("spike_detector") - Connect(nodes_ex, espikes, 'all_to_all') - Connect(nodes_in, ispikes, 'all_to_all') - - t0 = time.time() - Simulate(duration) - t = time.time() - t0 - print(f'PyNest used {t} s') - return t - - -def main(num_neurons, duration=1000, fn_output=None): - final_results = {'setting': dict(num_neurons=num_neurons, - duration=duration, - dt=dt), - "BRIAN2": [], - "PyNEST": [], - "ANNarchy_cpu": [], - 'BrainPy_cpu': []} - - for num_neu in num_neurons: - print(f"Running benchmark with {num_neu} neurons.") - - if num_neu > 2500: - final_results['PyNEST'].append(np.nan) - else: - t = run_pynest(num_neu, duration) - final_results['PyNEST'].append(t) - - t = run_brianpy(num_neu, duration, device='cpu') - final_results['BrainPy_cpu'].append(t) - - t = run_annarchy(num_neu, duration, device='cpu') - final_results['ANNarchy_cpu'].append(t) - - t = run_brian2(num_neu, duration) - final_results['BRIAN2'].append(t) - - if fn_output is not None: - if not os.path.exists(os.path.dirname(fn_output)): - os.makedirs(os.path.dirname(fn_output)) - with open(fn_output, 'w') as fout: - json.dump(final_results, fout, indent=2) - - plt.plot(num_neurons, final_results["BRIAN2"], label="BRIAN2", linestyle="--", color="r") - plt.plot(num_neurons, final_results["PyNEST"], label="PyNEST", linestyle="--", color="y") - plt.plot(num_neurons, final_results["ANNarchy_cpu"], label="ANNarchy", linestyle="--", color="m") - plt.plot(num_neurons, final_results["BrainPy_cpu"], label="BrainPy", linestyle="--", color="g") - - plt.title("Benchmark comparison of neural simulators") - plt.xlabel("Number of input / output neurons") - plt.ylabel("Simulation time (seconds)") - plt.legend(loc=1, prop={"size": 5}) - xticks = [num_neurons[0], num_neurons[len(num_neurons) // 2], num_neurons[-1]] - plt.xticks(xticks) - plt.yscale("log") - plt.legend() - plt.show() - - -if __name__ == "__main__": - main(list(range(500, 9001, 500)), 5000, 'results/COBA.json') diff --git a/develop/benchmark/COBA/COBA_brainpy.py b/develop/benchmark/COBA/COBA_brainpy.py deleted file mode 100644 index 5922a866..00000000 --- a/develop/benchmark/COBA/COBA_brainpy.py +++ /dev/null @@ -1,116 +0,0 @@ -# -*- coding: utf-8 -*- - - -import time -import numpy as np -import brainpy as bp - -np.random.seed(1234) -dt = 0.05 -bp.backend.set('numba', dt=dt) - -# Parameters -num_exc = 3200 * 10 -num_inh = 800 * 1 -taum = 20 -taue = 5 -taui = 10 -Vt = -50 -Vr = -60 -El = -60 -Erev_exc = 0. -Erev_inh = -80. -I = 20. -we = 0.6 # excitatory synaptic weight (voltage) -wi = 6.7 # inhibitory synaptic weight -ref = 5.0 - - -class LIF(bp.NeuGroup): - target_backend = ['numpy', 'numba'] - - def __init__(self, size, **kwargs): - # variables - self.V = bp.ops.zeros(size) - self.spike = bp.ops.zeros(size) - self.ge = bp.ops.zeros(size) - self.gi = bp.ops.zeros(size) - self.input = bp.ops.zeros(size) - self.t_last_spike = bp.ops.ones(size) * -1e7 - - super(LIF, self).__init__(size=size, **kwargs) - - @staticmethod - @bp.odeint - def int_g(ge, gi, t): - dge = - ge / taue - dgi = - gi / taui - return dge, dgi - - @staticmethod - @bp.odeint - def int_V(V, t, ge, gi): - dV = (ge * (Erev_exc - V) + gi * (Erev_inh - V) + El - V + I) / taum - return dV - - def update(self, _t, _i): - self.ge, self.gi = self.int_g(self.ge, self.gi, _t) - for i in range(self.size[0]): - self.spike[i] = 0. - if (_t - self.t_last_spike[i]) > ref: - V = self.int_V(self.V[i], _t, self.ge[i], self.gi[i]) - if V >= Vt: - self.V[i] = Vr - self.spike[i] = 1. - self.t_last_spike[i] = _t - else: - self.V[i] = V - self.input[i] = I - - -class ExcSyn(bp.TwoEndConn): - target_backend = ['numpy', 'numba'] - - def __init__(self, pre, post, conn, **kwargs): - self.conn = conn(pre.size, post.size) - self.pre2post = self.conn.requires('pre2post') - super(ExcSyn, self).__init__(pre=pre, post=post, **kwargs) - - def update(self, _t, _i): - for pre_id, spike in enumerate(self.pre.spike): - if spike > 0: - for post_i in self.pre2post[pre_id]: - self.post.ge[post_i] += we - - -class InhSyn(bp.TwoEndConn): - target_backend = ['numpy', 'numba'] - - def __init__(self, pre, post, conn, **kwargs): - self.conn = conn(pre.size, post.size) - self.pre2post = self.conn.requires('pre2post') - super(InhSyn, self).__init__(pre=pre, post=post, **kwargs) - - def update(self, _t, _i): - for pre_id, spike in enumerate(self.pre.spike): - if spike > 0: - for post_i in self.pre2post[pre_id]: - self.post.gi[post_i] += wi - - -E_group = LIF(num_exc, monitors=['spike']) -E_group.V = np.random.randn(num_exc) * 5. - 55. -I_group = LIF(num_inh, monitors=['spike']) -I_group.V = np.random.randn(num_inh) * 5. - 55. -E2E = ExcSyn(pre=E_group, post=E_group, conn=bp.connect.FixedProb(0.02)) -E2I = ExcSyn(pre=E_group, post=I_group, conn=bp.connect.FixedProb(0.02)) -I2E = InhSyn(pre=I_group, post=E_group, conn=bp.connect.FixedProb(0.02)) -I2I = InhSyn(pre=I_group, post=I_group, conn=bp.connect.FixedProb(0.02)) - -net = bp.Network(E_group, I_group, E2E, E2I, I2E, I2I) -t0 = time.time() - -net.run(5000., report=True) -print('Used time {} s.'.format(time.time() - t0)) - -bp.visualize.raster_plot(net.ts, E_group.mon.spike, show=True) diff --git a/develop/benchmark/COBA/COBA_brian2.py b/develop/benchmark/COBA/COBA_brian2.py deleted file mode 100644 index 027de1f7..00000000 --- a/develop/benchmark/COBA/COBA_brian2.py +++ /dev/null @@ -1,52 +0,0 @@ -from brian2 import * - -defaultclock.dt = 0.05 * ms -set_device('cpp_standalone', directory='brian2_COBA') -# prefs.codegen.target = "cython" - -taum = 20 * ms -taue = 5 * ms -taui = 10 * ms -Vt = -50 * mV -Vr = -60 * mV -El = -60 * mV -Erev_exc = 0. * mV -Erev_inh = -80. * mV -I = 20. * mvolt -num_exc = 3200 -num_inh = 800 - -eqs = ''' -dv/dt = (ge*(Erev_exc-v)+gi*(Erev_inh-v)-(v-El) + I)*(1./taum) : volt (unless refractory) -dge/dt = -ge/taue : 1 -dgi/dt = -gi/taui : 1 -''' -net = Network() - -P = NeuronGroup(num_exc + num_inh, eqs, threshold='v>Vt', reset='v = Vr', - refractory=5 * ms, method='euler') - - -we = 0.6 # excitatory synaptic weight (voltage) -wi = 6.7 # inhibitory synaptic weight -Ce = Synapses(P[:3200], P, on_pre='ge += we') -Ci = Synapses(P[3200:], P, on_pre='gi += wi') - - -P.v = (np.random.randn(num_exc + num_inh) * 5. - 55.) * mvolt -Ce.connect(p=0.02) -Ci.connect(p=0.02) - -s_mon = SpikeMonitor(P) - - -# Run for 0 second in order to measure compilation time -t1 = time.time() -run(5. * second, report='text') -t2 = time.time() -print('Done in', t2 - t1) - -plot(s_mon.t / ms, s_mon.i, '.k') -xlabel('Time (ms)') -ylabel('Neuron index') -show() diff --git a/develop/benchmark/COBA/pynn.py b/develop/benchmark/COBA/pynn.py deleted file mode 100644 index d1ef0468..00000000 --- a/develop/benchmark/COBA/pynn.py +++ /dev/null @@ -1,275 +0,0 @@ -# coding: utf-8 -""" -Balanced network of excitatory and inhibitory neurons. - -An implementation of benchmarks 1 and 2 from - - Brette et al. (2007) Journal of Computational Neuroscience 23: 349-398 - -The network is based on the CUBA and COBA models of Vogels & Abbott -(J. Neurosci, 2005). The model consists of a network of excitatory and -inhibitory neurons, connected via current-based "exponential" -synapses (instantaneous rise, exponential decay). - - -Usage: python VAbenchmarks.py [-h] [--plot-figure] [--use-views] [--use-assembly] - [--use-csa] [--debug DEBUG] - simulator benchmark - -positional arguments: - simulator neuron, nest, brian or another backend simulator - benchmark either CUBA or COBA - -optional arguments: - -h, --help show this help message and exit - --plot-figure plot the simulation results to a file - --use-views use population views in creating the network - --use-assembly use assemblies in creating the network - --use-csa use the Connection Set Algebra to define the connectivity - --debug DEBUG print debugging information - - -Andrew Davison, UNIC, CNRS -August 2006 - -""" - -import socket -from math import * -from pyNN.utility import get_simulator, Timer, ProgressBar, init_logging, normalized_filename -from pyNN.random import NumpyRNG, RandomDistribution - - -# === Configure the simulator ================================================ - -sim, options = get_simulator( - ("benchmark", "either CUBA or COBA"), - ("--plot-figure", "plot the simulation results to a file", {"action": "store_true"}), - ("--use-views", "use population views in creating the network", {"action": "store_true"}), - ("--use-assembly", "use assemblies in creating the network", {"action": "store_true"}), - ("--use-csa", "use the Connection Set Algebra to define the connectivity", {"action": "store_true"}), - ("--debug", "print debugging information")) - -if options.use_csa: - import csa - -if options.debug: - init_logging(None, debug=True) - -timer = Timer() - -# === Define parameters ======================================================== - -threads = 1 -rngseed = 98765 -parallel_safe = True - -n = 4000 # number of cells -r_ei = 4.0 # number of excitatory cells:number of inhibitory cells -pconn = 0.02 # connection probability -stim_dur = 50. # (ms) duration of random stimulation -rate = 100. # (Hz) frequency of the random stimulation - -dt = 0.1 # (ms) simulation timestep -tstop = 1000 # (ms) simulaton duration -delay = 0.2 - -# Cell parameters -area = 20000. # (µm²) -tau_m = 20. # (ms) -cm = 1. # (µF/cm²) -g_leak = 5e-5 # (S/cm²) -if options.benchmark == "COBA": - E_leak = -60. # (mV) -elif options.benchmark == "CUBA": - E_leak = -49. # (mV) -v_thresh = -50. # (mV) -v_reset = -60. # (mV) -t_refrac = 5. # (ms) (clamped at v_reset) -v_mean = -60. # (mV) 'mean' membrane potential, for calculating CUBA weights -tau_exc = 5. # (ms) -tau_inh = 10. # (ms) - -# Synapse parameters -if options.benchmark == "COBA": - Gexc = 4. # (nS) - Ginh = 51. # (nS) -elif options.benchmark == "CUBA": - Gexc = 0.27 # (nS) #Those weights should be similar to the COBA weights - Ginh = 4.5 # (nS) # but the delpolarising drift should be taken into account -Erev_exc = 0. # (mV) -Erev_inh = -80. # (mV) - -### what is the synaptic delay??? - -# === Calculate derived parameters ============================================= - -area = area*1e-8 # convert to cm² -cm = cm*area*1000 # convert to nF -Rm = 1e-6/(g_leak*area) # membrane resistance in MΩ -assert tau_m == cm*Rm # just to check -n_exc = int(round((n*r_ei/(1+r_ei)))) # number of excitatory cells -n_inh = n - n_exc # number of inhibitory cells -if options.benchmark == "COBA": - celltype = sim.IF_cond_exp - w_exc = Gexc*1e-3 # We convert conductances to uS - w_inh = Ginh*1e-3 -elif options.benchmark == "CUBA": - celltype = sim.IF_curr_exp - w_exc = 1e-3*Gexc*(Erev_exc - v_mean) # (nA) weight of excitatory synapses - w_inh = 1e-3*Ginh*(Erev_inh - v_mean) # (nA) - assert w_exc > 0; assert w_inh < 0 - -# === Build the network ======================================================== - -extra = {'threads' : threads, - 'filename': "va_%s.xml" % options.benchmark, - 'label': 'VA'} -if options.simulator == "neuroml": - extra["file"] = "VAbenchmarks.xml" - -node_id = sim.setup(timestep=dt, min_delay=delay, max_delay=1.0, **extra) -np = sim.num_processes() - -host_name = socket.gethostname() -print("Host #%d is on %s" % (node_id + 1, host_name)) - -print("%s Initialising the simulator with %d thread(s)..." % (node_id, extra['threads'])) - -cell_params = { - 'tau_m' : tau_m, 'tau_syn_E' : tau_exc, 'tau_syn_I' : tau_inh, - 'v_rest' : E_leak, 'v_reset' : v_reset, 'v_thresh' : v_thresh, - 'cm' : cm, 'tau_refrac' : t_refrac} - -if (options.benchmark == "COBA"): - cell_params['e_rev_E'] = Erev_exc - cell_params['e_rev_I'] = Erev_inh - -timer.start() - -print("%s Creating cell populations..." % node_id) -if options.use_views: - # create a single population of neurons, and then use population views to define - # excitatory and inhibitory sub-populations - all_cells = sim.Population(n_exc + n_inh, celltype(**cell_params), label="All Cells") - exc_cells = all_cells[:n_exc] - exc_cells.label = "Excitatory cells" - inh_cells = all_cells[n_exc:] - inh_cells.label = "Inhibitory cells" -else: - # create separate populations for excitatory and inhibitory neurons - exc_cells = sim.Population(n_exc, celltype(**cell_params), label="Excitatory_Cells") - inh_cells = sim.Population(n_inh, celltype(**cell_params), label="Inhibitory_Cells") - if options.use_assembly: - # group the populations into an assembly - all_cells = exc_cells + inh_cells - -if options.benchmark == "COBA": - ext_stim = sim.Population(20, sim.SpikeSourcePoisson(rate=rate, duration=stim_dur), label="expoisson") - rconn = 0.01 - ext_conn = sim.FixedProbabilityConnector(rconn) - ext_syn = sim.StaticSynapse(weight=0.1) - -print("%s Initialising membrane potential to random values..." % node_id) -rng = NumpyRNG(seed=rngseed, parallel_safe=parallel_safe) -uniformDistr = RandomDistribution('uniform', low=v_reset, high=v_thresh, rng=rng) -if options.use_views: - all_cells.initialize(v=uniformDistr) -else: - exc_cells.initialize(v=uniformDistr) - inh_cells.initialize(v=uniformDistr) - -print("%s Connecting populations..." % node_id) -progress_bar = ProgressBar(width=20) -if options.use_csa: - connector = sim.CSAConnector(csa.cset(csa.random(pconn))) -else: - connector = sim.FixedProbabilityConnector(pconn, rng=rng, callback=progress_bar) -exc_syn = sim.StaticSynapse(weight=w_exc, delay=delay) -inh_syn = sim.StaticSynapse(weight=w_inh, delay=delay) - -connections = {} -if options.use_views or options.use_assembly: - connections['exc'] = sim.Projection(exc_cells, all_cells, connector, exc_syn, receptor_type='excitatory') - connections['inh'] = sim.Projection(inh_cells, all_cells, connector, inh_syn, receptor_type='inhibitory') - if (options.benchmark == "COBA"): - connections['ext'] = sim.Projection(ext_stim, all_cells, ext_conn, ext_syn, receptor_type='excitatory') -else: - connections['e2e'] = sim.Projection(exc_cells, exc_cells, connector, exc_syn, receptor_type='excitatory') - connections['e2i'] = sim.Projection(exc_cells, inh_cells, connector, exc_syn, receptor_type='excitatory') - connections['i2e'] = sim.Projection(inh_cells, exc_cells, connector, inh_syn, receptor_type='inhibitory') - connections['i2i'] = sim.Projection(inh_cells, inh_cells, connector, inh_syn, receptor_type='inhibitory') - if (options.benchmark == "COBA"): - connections['ext2e'] = sim.Projection(ext_stim, exc_cells, ext_conn, ext_syn, receptor_type='excitatory') - connections['ext2i'] = sim.Projection(ext_stim, inh_cells, ext_conn, ext_syn, receptor_type='excitatory') - -# === Setup recording ========================================================== -print("%s Setting up recording..." % node_id) -if options.use_views or options.use_assembly: - all_cells.record('spikes') - exc_cells[[0, 1]].record('v') -else: - exc_cells.record('spikes') - inh_cells.record('spikes') - exc_cells[0, 1].record('v') - -buildCPUTime = timer.diff() - -# === Save connections to file ================================================= - -#for prj in connections.keys(): - #connections[prj].saveConnections('Results/VAbenchmark_%s_%s_%s_np%d.conn' % (benchmark, prj, options.simulator, np)) -saveCPUTime = timer.diff() - -# === Run simulation =========================================================== - -print("%d Running simulation..." % node_id) - -sim.run(tstop) - -simCPUTime = timer.diff() - -E_count = exc_cells.mean_spike_count() -I_count = inh_cells.mean_spike_count() - -# === Print results to file ==================================================== - -print("%d Writing data to file..." % node_id) - -filename = normalized_filename("Results", "VAbenchmarks_%s_exc" % options.benchmark, "pkl", - options.simulator, np) -exc_cells.write_data(filename, - annotations={'script_name': __file__}) -inh_cells.write_data(filename.replace("exc", "inh"), - annotations={'script_name': __file__}) - -writeCPUTime = timer.diff() - -if options.use_views or options.use_assembly: - connections = "%d e→e,i %d i→e,i" % (connections['exc'].size(), - connections['inh'].size()) -else: - connections = u"%d e→e %d e→i %d i→e %d i→i" % (connections['e2e'].size(), - connections['e2i'].size(), - connections['i2e'].size(), - connections['i2i'].size()) - -if node_id == 0: - print("\n--- Vogels-Abbott Network Simulation ---") - print("Nodes : %d" % np) - print("Simulation type : %s" % options.benchmark) - print("Number of Neurons : %d" % n) - print("Number of Synapses : %s" % connections) - print("Excitatory conductance : %g nS" % Gexc) - print("Inhibitory conductance : %g nS" % Ginh) - print("Excitatory rate : %g Hz" % (E_count * 1000.0 / tstop,)) - print("Inhibitory rate : %g Hz" % (I_count * 1000.0 / tstop,)) - print("Build time : %g s" % buildCPUTime) - #print("Save connections time : %g s" % saveCPUTime) - print("Simulation time : %g s" % simCPUTime) - print("Writing time : %g s" % writeCPUTime) - - -# === Finished with simulator ================================================== - -sim.end() diff --git a/develop/benchmark/COBAHH/COBAHH_brainpy.py b/develop/benchmark/COBAHH/COBAHH_brainpy.py deleted file mode 100644 index 5514872c..00000000 --- a/develop/benchmark/COBAHH/COBAHH_brainpy.py +++ /dev/null @@ -1,149 +0,0 @@ -# -*- coding: utf-8 -*- - -import time - -import numpy as np -import brainpy as bp - -bp.profile.set(jit=True, dt=0.1, numerical_method='exponential') - -duration = 10 * 1000. # ms -num_exc = 3200 -num_inh = 800 -Cm = 200 # Membrane Capacitance [pF] - -gl = 10. # Leak Conductance [nS] -El = -60. # Resting Potential [mV] -g_Na = 20. * 1000 -ENa = 50. # reversal potential (Sodium) [mV] -g_Kd = 6. * 1000 # K Conductance [nS] -EK = -90. # reversal potential (Potassium) [mV] -VT = -63. -Vt = -20. -# Time constants -taue = 5. # Excitatory synaptic time constant [ms] -taui = 10. # Inhibitory synaptic time constant [ms] -# Reversal potentials -Ee = 0. # Excitatory reversal potential (mV) -Ei = -80. # Inhibitory reversal potential (Potassium) [mV] -# excitatory synaptic weight -we = 6. # excitatory synaptic conductance [nS] -# inhibitory synaptic weight -wi = 67. # inhibitory synaptic conductance [nS] - - -class Hh(bp.NeuGroup): - def __init__(self, size, **kwargs): - super(Hh, self).__init__(size=size, **kwargs) - self.V = bp.ops.zeros(self.num) - self.m = bp.ops.zeros(self.num) - self.n = bp.ops.zeros(self.num) - self.h = bp.ops.zeros(self.num) - self.ge = bp.ops.zeros(self.num) - self.gi = bp.ops.zeros(self.num) - self.spike = bp.ops.zeros(self.num, dtype=bool) - - def integral(): - pass - - -@bp.integrate -def int_ge(ge, t): - return - ge / taue - - -@bp.integrate -def int_gi(gi, t): - return - gi / taui - - -@bp.integrate -def int_m(m, t, V): - a = 13 - V + VT - b = V - VT - 40 - m_alpha = 0.32 * a / (np.exp(a / 4) - 1.) - m_beta = 0.28 * b / (np.exp(b / 5) - 1) - dmdt = (m_alpha * (1 - m) - m_beta * m) - return dmdt - - -@bp.integrate -def int_h(h, t, V): - h_alpha = 0.128 * np.exp((17 - V + VT) / 18) - h_beta = 4. / (1 + np.exp(-(V - VT - 40) / 5)) - dhdt = (h_alpha * (1 - h) - h_beta * h) - return dhdt - - -@bp.integrate -def int_n(n, t, V): - c = 15 - V + VT - n_alpha = 0.032 * c / (np.exp(c / 5) - 1.) - n_beta = .5 * np.exp((10 - V + VT) / 40) - dndt = (n_alpha * (1 - n) - n_beta * n) - return dndt - - -@bp.integrate -def int_V(V, t, m, h, n, ge, gi): - g_na_ = g_Na * (m * m * m) * h - g_kd_ = g_Kd * (n * n * n * n) - dvdt = (gl * (El - V) + ge * (Ee - V) + gi * (Ei - V) - - g_na_ * (V - ENa) - g_kd_ * (V - EK)) / Cm - return dvdt - - -def neu_update(ST, _t): - ST['ge'] = int_ge(ST['ge'], _t) - ST['gi'] = int_gi(ST['gi'], _t) - ST['m'] = int_m(ST['m'], _t, ST['V']) - ST['h'] = int_h(ST['h'], _t, ST['V']) - ST['n'] = int_n(ST['n'], _t, ST['V']) - V = int_V(ST['V'], _t, ST['m'], ST['h'], ST['n'], ST['ge'], ST['gi']) - sp = np.logical_and(ST['V'] < Vt, V >= Vt) - ST['sp'] = sp - ST['V'] = V - - -neuron = bp.NeuType(name='CUBA-HH', ST=neu_ST, steps=neu_update, mode='vector') - - -def exc_update(pre, post, pre2post): - for pre_id in range(len(pre2post)): - if pre['sp'][pre_id] > 0.: - post_ids = pre2post[pre_id] - # post['ge'][post_ids] += we - for p_id in post_ids: - post['ge'][p_id] += we - - -def inh_update(pre, post, pre2post): - for pre_id in range(len(pre2post)): - if pre['sp'][pre_id] > 0.: - post_ids = pre2post[pre_id] - # post['gi'][post_ids] += wi - for p_id in post_ids: - post['gi'][p_id] += wi - - -exc_syn = bp.SynType('exc_syn', steps=exc_update, ST=bp.types.SynState()) - -inh_syn = bp.SynType('inh_syn', steps=inh_update, ST=bp.types.SynState()) - -group = bp.NeuGroup(neuron, size=num_exc + num_inh, monitors=['sp']) -group.ST['V'] = El + (np.random.randn(num_exc + num_inh) * 5 - 5) -group.ST['ge'] = (np.random.randn(num_exc + num_inh) * 1.5 + 4) * 10. -group.ST['gi'] = (np.random.randn(num_exc + num_inh) * 12 + 20) * 10. - -exc_conn = bp.TwoEndConn(exc_syn, pre=group[:num_exc], post=group, - conn=bp.connect.FixedProb(prob=0.02)) - -inh_conn = bp.TwoEndConn(inh_syn, pre=group[num_exc:], post=group, - conn=bp.connect.FixedProb(prob=0.02)) - -net = bp.Network(group, exc_conn, inh_conn) -t0 = time.time() -net.run(duration, report=True) -print('Used time {} s.'.format(time.time() - t0)) - -bp.visualize.raster_plot(net.ts, group.mon.sp, show=True) diff --git a/develop/benchmark/COBAHH/COBAHH_brian2.py b/develop/benchmark/COBAHH/COBAHH_brian2.py deleted file mode 100644 index a73b0667..00000000 --- a/develop/benchmark/COBAHH/COBAHH_brian2.py +++ /dev/null @@ -1,85 +0,0 @@ -from brian2 import * - -set_device('cpp_standalone', directory='brian2_COBAHH') - -defaultclock.dt = 0.1 * ms - - -duration = 10 * second -monitor = 'spike' -area = 0.02 -Cm = 200 -gl = 10. -g_na = 20 * 1000 -g_kd = 6. * 1000 - -time_unit = 1 * ms -El = -60 -EK = -90 -ENa = 50 -VT = -63 -# Time constants -taue = 5 * ms -taui = 10 * ms -# Reversal potentials -Ee = 0 -Ei = -80 -# excitatory synaptic weight -we = 6 -# inhibitory synaptic weight -wi = 67 - -# The model -eqs = Equations(''' - dv/dt = (gl*(El-v) + ge*(Ee-v) + gi*(Ei-v)- - g_na*(m*m*m)*h*(v-ENa)- - g_kd*(n*n*n*n)*(v-EK))/Cm/time_unit : 1 - dm/dt = (alpha_m*(1-m)-beta_m*m)/time_unit : 1 - dn/dt = (alpha_n*(1-n)-beta_n*n)/time_unit : 1 - dh/dt = (alpha_h*(1-h)-beta_h*h)/time_unit : 1 - dge/dt = -ge/taue : 1 - dgi/dt = -gi/taui : 1 - alpha_m = 0.32*(13-v+VT)/(exp((13-v+VT)/4)-1.) : 1 - beta_m = 0.28*(v-VT-40)/(exp((v-VT-40)/5)-1) : 1 - alpha_h = 0.128*exp((17-v+VT)/18) : 1 - beta_h = 4./(1+exp((40-v+VT)/5)) : 1 - alpha_n = 0.032*(15-v+VT)/(exp((15-v+VT)/5)-1.) : 1 - beta_n = .5*exp((10-v+VT)/40) : 1 -''') - -P = NeuronGroup(4000, model=eqs, threshold='v>-20', method='exponential_euler') -Pe = P[:3200] -Pi = P[3200:] -Ce = Synapses(Pe, P, on_pre='ge+=we') -Ci = Synapses(Pi, P, on_pre='gi+=wi') -Ce.connect(p=0.02) -Ci.connect(p=0.02) - -# Initialization -P.v = 'El + (randn() * 5 - 5)' -P.g = '(randn() * 1.5 + 4) * 10.' -P.gi = '(randn() * 12 + 20) * 10.' - -# monitor -if monitor == 'V': - trace = StateMonitor(P, 'v', record=True) -else: - s_mon = SpikeMonitor(P) - -# Record a few traces -t0 = time.time() -run(duration, report='text') -print('{}. Used time {} s.'.format(prefs.codegen.target, time.time() - t0)) - -if monitor == 'V': - for i in [1, 10, 100]: - plot(trace.t / ms, trace.v[i], label='N-{}'.format(i)) - xlabel('t (ms)') - ylabel('v (mV)') - legend() - show() -else: - plot(s_mon.t / ms, s_mon.i, ',k') - xlabel('Time (ms)') - ylabel('Neuron index') - show() diff --git a/develop/benchmark/COBAHH/COBAHH_nest.sli b/develop/benchmark/COBAHH/COBAHH_nest.sli deleted file mode 100644 index 613b0e33..00000000 --- a/develop/benchmark/COBAHH/COBAHH_nest.sli +++ /dev/null @@ -1,174 +0,0 @@ -%%% FUNCTION SECTION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -% Take spike detector, find total number of spikes registered, -% return average rate per neuron in Hz. -% NOTE: If you are running with several MPI processes, this -% function only gives an approximation to the true rate. -% -% spike_det ComputeRate -> rate -/ComputeRate -{ - GetStatus /n_events get /nspikes Set - % We need to guess how many neurons we record from. - % This assumes an even distribution of nodes across - % processes, which is ok for the Brette_et_al_2007 - % benchmarks, but should not be considered a general - % solution. - Nrec cvd NumProcesses div - /nnrn Set - - nspikes nnrn simtime mul div - 1000 mul % convert from mHz to Hz, leave on stack - end -} bind % optional, improves performance -def - - -%%% CONSTRUCTION SECTION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -/BuildNetwork -{ - % set global kernel parameters - 0 - << - /resolution dt - /total_num_virtual_procs virtual_processes - /overwrite_files true - >> SetStatus - - tic % start timer on construction - - % Set initial parameters for all new neurons and devices - - model model_params SetDefaults - - (Creating excitatory population.) = % show message - /E_net model [ NE ] LayoutNetwork def - - (Creating inhibitory population.) = % show message - /I_net model [ NI ] LayoutNetwork def - - (Creating excitatory stimulus generator.) = - /E_stimulus stimulus Create def - E_stimulus stimulus_params SetStatus - - % one detector would in principle be enough, - % but by recording the populations separately, - % we save ourselves a lot of sorting work later - - (Creating excitatory spike detector.) = - /E_detector detector Create def - E_detector detector_params SetStatus - - (Creating inhibitory spike detector.) = - /I_detector detector Create def - I_detector detector_params SetStatus - - % some connecting functions need lists (arrays) over all - % neurons they should work on, so we need to extract these - % lists from the subnetworks - - % obtain array with GIDs of all excitatory neurons - /E_neurons E_net GetLocalNodes def - - % obtain array with GIDs of all inhibitory neurons - /I_neurons I_net GetLocalNodes def - - % all neurons - /allNeurons E_neurons I_neurons join def - - /N allNeurons length def - - /CE NE epsilon mul iround def %number of incoming excitatory connections - /CI NI epsilon mul iround def %number of incomining inhibitory connections - - % number of synapses---just so we know - /Nsyn - CE CI add % internal synapses - N mul - Nrec 2 mul % "synapses" to spike detectors - add - Nstim add % "synapses" from poisson generator - def - - % Create custom synapse types with appropriate values for - % our excitatory and inhibitory connections - /static_synapse << /delay delay >> SetDefaults - /static_synapse /syn_ex E_synapse_params CopyModel - /static_synapse /syn_in I_synapse_params CopyModel - - (Connecting excitatory population.) = - - % E -> E connections - E_neurons % source population [we pick from this] - E_neurons % target neurons [for each we pick CE sources] - << /rule /fixed_indegree /indegree CE >> % number of source neurons to pick - /syn_ex % synapse model - Connect - - % I -> E connections - % as above, but on a single line - I_neurons E_neurons << /rule /fixed_indegree /indegree CI >> /syn_in Connect - - - (Connecting inhibitory population.) = - - % ... as above, just written more compactly - % E -> I - E_neurons I_neurons << /rule /fixed_indegree /indegree CE >> /syn_ex Connect - % I -> I - I_neurons I_neurons << /rule /fixed_indegree /indegree CI >> /syn_in Connect - - %Add external stimulus - - (Connecting Poisson stimulus.) = - [E_stimulus] - E_neurons Nstim Take % pick the first Nstim neurons - /all_to_all - /syn_ex - Connect - - - % Spike detectors are connected to the first Nrec neurons in each - % population. Since neurons are equal and connectivity is homogeneously - % randomized, this is equivalent to picking Nrec neurons at random - % from each population - - (Connecting spike detectors.) = - - E_neurons Nrec Take % pick the first Nrec neurons - [E_detector] Connect - - I_neurons Nrec Take % pick the first Nrec neurons - [I_detector] Connect - - % read out time used for building - - toc /BuildCPUTime Set -} def - -/RunSimulation -{ - BuildNetwork - - % run, measure computer time with tic-toc - tic - (Simulating...) = - simtime Simulate - toc /SimCPUTime Set - - % write a little report - (Simulation summary) = - (Number of Neurons : ) =only N = - (Number of Synapses: ) =only Nsyn = - (Excitatory rate : ) =only E_detector ComputeRate =only ( Hz) = - (Inhibitory rate : ) =only I_detector ComputeRate =only ( Hz) = - (Building time : ) =only BuildCPUTime =only ( s) = - (Simulation time : ) =only SimCPUTime =only ( s\n) = -} def - -/parameters_set lookup { - RunSimulation -} { - (Parameters are not set. Please call one of coba.sli, cuba_ps.sli, cuba.sli, cuba_stdp.sli, or hh_coba.sli.) M_ERROR message -} ifelse diff --git a/develop/benchmark/COBAHH/COBAHH_neuron/COBAHH_neuron.hoc b/develop/benchmark/COBAHH/COBAHH_neuron/COBAHH_neuron.hoc deleted file mode 100644 index f6458628..00000000 --- a/develop/benchmark/COBAHH/COBAHH_neuron/COBAHH_neuron.hoc +++ /dev/null @@ -1,52 +0,0 @@ -// Main file for cobahh network (Hodgkin-Huxley model cells with COnductance BAsed synapses). - -{load_file("nrngui.hoc")} // GUI and runtime libraries -{load_file("hhcell.hoc")} // defines CobaHHCell class - -// Procedures that set up network architecture and performance reporting. -{load_file("init.hoc")} - -// Called by create_cells() in net.hoc -obfunc newcell() { - return new CobaHHCell() -} - -// Create the cells, then connect them. -create_net() // in net.hoc - -STOPSTIM=10000 // duration of stimulation (ms) - -// Randomized spike trains driving excitatory synapses. -create_stim(run_random_low_start_, AMPA_GMAX) // in netstim.hoc - -// A few last items for performance reports, e.g. set up spike time recording, and, -// if in "demo" mode, create graph for raster plots, and panel with Stop button. -finish_setup() // in init.hoc - -// Parallel run to tstop. -prun() // in perfrun.hoc - -// Only the "master" cpu does this. -if (pc.id == 0) {print "RunTime: ", runtime} - -// Up to this point, all CPUs have executed the same code, -// except for taking different branches depending on their value of pc.id, -// which ranges from 0 to pc.nhost-1. - -// Gather performance statistics from each CPU. - -// Only the master (pc.id == 0) returns from pc.runworker(). -// All other CPUs ("workers") now wait for messages. -{pc.runworker()} - -// Send requests to the workers and handle the results they send back. -collect_results() // in init.hoc - -// Send all workers a QUIT message; those NEURON processes exit. -// The master waits until all worker output has been transferred to it. -{pc.done()} - -// Only the master executes code beyond this point; all others have exited. - -// Times of all spikes, and consolidated performance report. -output_results() // in perfrun.hoc diff --git a/develop/benchmark/COBAHH/COBAHH_neuron/hhcell.hoc b/develop/benchmark/COBAHH/COBAHH_neuron/hhcell.hoc deleted file mode 100644 index d66c1a59..00000000 --- a/develop/benchmark/COBAHH/COBAHH_neuron/hhcell.hoc +++ /dev/null @@ -1,89 +0,0 @@ -// Mostly constructed by cell builder. Lines marked with //* added manually -{load_file("hhchan.ses")} //* - -//Network cell templates -// CobaHHCell - -AMPA_INDEX = 0 //* synlist synapse indices -GABA_INDEX = 1 //* - -begintemplate CobaHHCell -public is_art -public init, topol, basic_shape, subsets, geom, biophys, geom_nseg, biophys_inhomo -public synlist, x, y, z, position, connect2target - -public soma -public all - -objref synlist - -proc init() { - topol() - subsets() - geom() - biophys() - geom_nseg() - synlist = new List() - synapses() - x = y = z = 0 // only change via position -} - -create soma - -proc topol() { local i - basic_shape() -} -proc basic_shape() { - soma {pt3dclear() pt3dadd(0, 0, 0, 1) pt3dadd(15, 0, 0, 1)} -} - -objref all -proc subsets() { local i - objref all - all = new SectionList() - soma all.append() - -} -proc geom() { - forsec all { /*area = 20000 */ L = diam = 79.7885 } -} -external lambda_f -proc geom_nseg() { -//* performance killer: soma area(.5) // make sure diam reflects 3d points -} -proc biophys() { - forsec all { - cm = 1 - insert pas - g_pas = 5e-05 - e_pas = -65 - insert nahh - gmax_nahh = 0.1 - insert khh - gmax_khh = 0.03 - ena = 50 //* - ek = -90 //* - } -} -proc biophys_inhomo(){} -proc position() { local i - soma for i = 0, n3d()-1 { - pt3dchange(i, $1-x+x3d(i), $2-y+y3d(i), $3-z+z3d(i), diam3d(i)) - } - x = $1 y = $2 z = $3 -} -proc connect2target() { //$o1 target point process, $o2 returned NetCon - soma $o2 = new NetCon(&v(1), $o1) - $o2.threshold = -10 //* -} -objref syn_ -proc synapses() { - /* E0 */ soma syn_ = new ExpSyn(0.5) synlist.append(syn_) - syn_.tau = 5 - /* I1 */ soma syn_ = new ExpSyn(0.5) synlist.append(syn_) - syn_.tau = 10 - syn_.e = -80 -} -func is_art() { return 0 } - -endtemplate CobaHHCell diff --git a/develop/benchmark/COBAHH/COBAHH_neuron/hhchan.ses b/develop/benchmark/COBAHH/COBAHH_neuron/hhchan.ses deleted file mode 100644 index 9fb662cf..00000000 --- a/develop/benchmark/COBAHH/COBAHH_neuron/hhchan.ses +++ /dev/null @@ -1,101 +0,0 @@ -objectvar save_window_, rvp_ -objectvar scene_vector_[2] -objectvar ocbox_, ocbox_list_, scene_, scene_list_ -{ocbox_list_ = new List() scene_list_ = new List()} - -//Begin ChannelBuild[0] managed KSChan[0] -{ -load_file("chanbild.hoc", "ChannelBuild") -} -{ion_register("na", 1)} -{ocbox_ = new ChannelBuild(1)} -{object_push(ocbox_)} -{genprop.set_data("nahh", 1, 1, 5, "na")} -{genprop.set_defstr(0.1, 0)} -tobj = new ChannelBuildKSGate(this) -{gatelist.append(tobj)} -{tobj.begin_restore(3)} -{tobj.set_state("m", 1, 140, 140)} -{tobj.set_trans(0, 0, 0)} -{tobj.transitions.object(0).settype(0, "")} -{tobj1 = new Vector(3) for (i=0; i < 3; i += 1) tobj1.x[i] = fscan() } -1.28 -0.25 --50 -{tobj.transitions.object(0).set_f(0, 3, tobj1)} -{tobj1 = new Vector(3) for (i=0; i < 3; i += 1) tobj1.x[i] = fscan() } -1.4 --0.2 --23 -{tobj.transitions.object(0).set_f(1, 3, tobj1)} -{tobj.end_restore()} -tobj = new ChannelBuildKSGate(this) -{gatelist.append(tobj)} -{tobj.begin_restore(1)} -{tobj.set_state("h", 1, 140, 110)} -{tobj.set_trans(0, 0, 0)} -{tobj.transitions.object(0).settype(0, "")} -{tobj1 = new Vector(3) for (i=0; i < 3; i += 1) tobj1.x[i] = fscan() } -0.128 --0.055556 --46 -{tobj.transitions.object(0).set_f(0, 2, tobj1)} -{tobj1 = new Vector(3) for (i=0; i < 3; i += 1) tobj1.x[i] = fscan() } -4 --0.2 --23 -{tobj.transitions.object(0).set_f(1, 4, tobj1)} -{tobj.end_restore()} -end_restore() -{genprop.set_single(0)} -{set_alias(1)} -{usetable(1)} -{object_pop()} -{ -ocbox_.map("ChannelBuild[0] managed KSChan[0]", 0, 0, 360, 225.6) -} -objref ocbox_ -//End ChannelBuild[0] managed KSChan[0] - -{WindowMenu[0].ses_gid(1, 0, 0, "channels")} - -//Begin ChannelBuild[1] managed KSChan[1] -{ -load_file("chanbild.hoc", "ChannelBuild") -} -{ion_register("k", 1)} -{ocbox_ = new ChannelBuild(1)} -{object_push(ocbox_)} -{genprop.set_data("khh", 1, 1, 6, "k")} -{genprop.set_defstr(0.03, 0)} -tobj = new ChannelBuildKSGate(this) -{gatelist.append(tobj)} -{tobj.begin_restore(4)} -{tobj.set_state("n", 1, 140, 140)} -{tobj.set_trans(0, 0, 0)} -{tobj.transitions.object(0).settype(0, "")} -{tobj1 = new Vector(3) for (i=0; i < 3; i += 1) tobj1.x[i] = fscan() } -0.16 -0.2 --48 -{tobj.transitions.object(0).set_f(0, 3, tobj1)} -{tobj1 = new Vector(3) for (i=0; i < 3; i += 1) tobj1.x[i] = fscan() } -0.5 --0.025 --53 -{tobj.transitions.object(0).set_f(1, 2, tobj1)} -{tobj.end_restore()} -end_restore() -{genprop.set_single(0)} -{set_alias(1)} -{usetable(1)} -{object_pop()} -{ -ocbox_.map("ChannelBuild[1] managed KSChan[1]", 0, 0, 365.76, 210.24) -} -objref ocbox_ -//End ChannelBuild[1] managed KSChan[1] - -{WindowMenu[0].ses_gid(0, 0, 0, "channels")} -objectvar scene_vector_[1] -{doNotify()} diff --git a/develop/benchmark/COBAHH/COBAHH_neuron/init.hoc b/develop/benchmark/COBAHH/COBAHH_neuron/init.hoc deleted file mode 100644 index dad940c1..00000000 --- a/develop/benchmark/COBAHH/COBAHH_neuron/init.hoc +++ /dev/null @@ -1,138 +0,0 @@ -setuptime = startsw() -{load_file("nrngui.hoc")} - -// Defines ParallelNetManager class. -{load_file("netparmpi.hoc")} // in nrn/lib/hoc - -// Defines RandomStream class, used to create and manage randomized spike streams. -{load_file("ranstream.hoc")} // in common - -// Will become instances of ParallelNetManager, ParallelContext, -// List of RandomStream objects, an FInitializeHandler (to report progress), -// and a Graph for the raster plot. -objref pnm, pc, ranlist, fih, grspk - -// If program was launched by executing mosinit.hoc, -// there is already a variable called mosinit with value 1. -// If this variable does not exist, create it and set its value to 0. -// mosinit controls reporting of results--see -// procs finish_setup(), collect_results(), and output_results() below. -if (!name_declared("mosinit")) {mosinit = 0} - -ncell = 4000 -ranlist = new List() -random_stream_offset_ = 500 // Adjacent streams will be correlated by this offset. -// Seeds for network architecture and stimulus trains. -connect_random_low_start_ = 1 // Used in net.hoc. -run_random_low_start_ = 2 // Used in coba|cobahh|cuba|cubadv/init.hoc - -// The ParallelNetManager class provides routines and variables -// for managing distributed network simulations -// in a parallel computing environment. -pnm = new ParallelNetManager(ncell) -// One of the ParallelNetManager's public members is a ParallelContext object. -pc = pnm.pc - -// iterator pcitr() manages round robin style distribution of cells on CPUs. -// There are pc.nhost CPUs, numbered 0 to pc.nhost-1, where 0 is the "master" CPU -// and ncell cells, numbered 0 to ncell-1 -// CPU i creates cells i+j*pc.nhost where j=0,1,2,... and i+j*pc.nhost < ncell -// pcitr is called four times with different iterator_statements: -// twice in common/net.hoc, to distribute cells and set up synapses -// once in netstim.hoc, to set up stimulation of a specified number of cells -// once in perfrun.hoc, to set up spike recording from all cells -// Note that the outer loop of pcitr is over the the target cells, and, -// when setting up synaptic connections, the inner loop will be over the source cells. -// This minimizes setup time, which is an issue if the number of possible connections -// is ~ 10^4 x 10^4 or greater -iterator pcitr() {local i1, i2 - i1 = 0 - for (i2=pc.id; i2 < ncell; i2 += pc.nhost) { - $&1 = i1 - $&2 = i2 - iterator_statement - i1 += 1 - } -} - -// Create the model. -{load_file("net.hoc")} // in common -// Set up the stimulus sources (streams of afferent spike events). -{load_file("netstim.hoc")} // in common - -// Simulation control parameters. -tstop = 10000 //5000 //(ms) -dt = 0.1 //(ms) time step -steps_per_ms = 1/dt -v_init = -60 -celsius = 36 - -// Variables and procedures used for -// performance and statistics measurement and reporting. -{load_file("perfrun.hoc")} // in common - -proc finish_setup() { - // Record all spikes in the net. - want_all_spikes() // in common/perfrun.hoc - // Keep track of spike exchanges. - mkhist(100) // in common/perfrun.hoc - - setuptime = startsw() - setuptime - if (pc.id == 0) {print "SetupTime: ", setuptime} - // mosinit==1 means "demo mode," i.e. show results during the simulation. - if (mosinit == 1) { - mosrt = startsw() - // Make proc mosprogress() be an event handler - // that will be responsible for progress reports and raster plot updates, - // and send the event that will trigger the first report and update - fih = new FInitializeHandler("cvode.event(0, \"mosprogress()\")") - grspk = new Graph(0) // Graph of spike rasters. - grspk.size(0,tstop,0,pnm.ncell) - grspk.view(0, 0, 1000, 4000, 50, 100, 750, 500) - plt_so_far = 0 - xpanel("Stop simulation") - xbutton("Stop", "stoprun = 1") - xpanel(380, 10) - } -} - -// This event handler reports progress, updates the raster plot, -// and sends another event that will trigger the next progress report and plot update. -proc mosprogress() {local i - if (t == 0) { grspk.erase_all } - printf("runtime=%g\t t=%g\t nspike=%d\n", startsw() - mosrt, t, pnm.spikevec.size) - cvode.event(t+mosinvl, "mosprogress()") - for i=plt_so_far, pnm.spikevec.size-1 { - grspk.mark(pnm.spikevec.x[i], pnm.idvec.x[i], "|", 5,1,1) - } - plt_so_far = pnm.spikevec.size - grspk.flush() - doNotify() -} - -proc collect_results() { - // "demo mode" so show final progress report. - if (mosinit == 1) { - mosprogress() - } - pnm.prstat(1) - // Get each cpu's performance stats. - getstat() // in common/perfrun.hoc - // Get the spike times. - pnm.gatherspikes() - // Display histogram of # spikes vs # exchanges, and print stats. - prhist() // in common/perfrun.hoc - print_spike_stat_info() // in common/perfrun.hoc -} - -// output_results() is the last statement in the coba|cobahh|cuba|cubadv/init.hoc files. -// Since only the pc.id==0 process returns from pc.runworker, -// only the master will call output_results(). -proc output_results() { - // If "demo mode," don't bother writing performance and spike data - if (mosinit == 1) {return} - - perf2file() // in common/perfun.hoc - spike2file() // in common/perfun.hoc - quit() -} diff --git a/develop/benchmark/COBAHH/COBAHH_neuron/intrinsic.hoc b/develop/benchmark/COBAHH/COBAHH_neuron/intrinsic.hoc deleted file mode 100644 index a41e41c3..00000000 --- a/develop/benchmark/COBAHH/COBAHH_neuron/intrinsic.hoc +++ /dev/null @@ -1,6 +0,0 @@ -load_file("nrngui.hoc") -load_file("hhcell.hoc") -objref cell -cell = new CobaHHCell(0) -access cell.soma -load_file("intrinsic.ses") diff --git a/develop/benchmark/COBAHH/COBAHH_neuron/intrinsic.ses b/develop/benchmark/COBAHH/COBAHH_neuron/intrinsic.ses deleted file mode 100644 index b767b886..00000000 --- a/develop/benchmark/COBAHH/COBAHH_neuron/intrinsic.ses +++ /dev/null @@ -1,69 +0,0 @@ -{load_file("nrngui.hoc")} -objectvar save_window_, rvp_ -objectvar scene_vector_[4] -objectvar ocbox_, ocbox_list_, scene_, scene_list_ -{ocbox_list_ = new List() scene_list_ = new List()} -{pwman_place(0,0,0)} - -//Begin PointProcessManager -{ -load_file("pointman.hoc") -} -{ -CobaHHCell[0].soma ocbox_ = new PointProcessManager(0) -} -{object_push(ocbox_)} -{ -mt.select("IClamp") i = mt.selected() -ms[i] = new MechanismStandard("IClamp") -ms[i].set("del", 50, 0) -ms[i].set("dur", 750, 0) -ms[i].set("amp", 0.02, 0) -mt.select("IClamp") i = mt.selected() maction(i) -hoc_ac_ = 0.5 -sec.sec move() d1.flip_to(0) -} -{object_pop() doNotify()} -{ -ocbox_ = ocbox_.v1 -ocbox_.map("PointProcessManager", 393, 446, 208.32, 326.4) -} -objref ocbox_ -//End PointProcessManager - -{ -xpanel("RunControl", 0) -v_init = -65 -xvalue("Init","v_init", 1,"stdinit()", 1, 1 ) -xbutton("Init & Run","run()") -xbutton("Stop","stoprun=1") -runStopAt = 5 -xvalue("Continue til","runStopAt", 1,"{continuerun(runStopAt) stoprun=1}", 1, 1 ) -runStopIn = 1 -xvalue("Continue for","runStopIn", 1,"{continuerun(t + runStopIn) stoprun=1}", 1, 1 ) -xbutton("Single Step","steprun()") -t = 1000 -xvalue("t","t", 2 ) -tstop = 1000 -xvalue("Tstop","tstop", 1,"tstop_changed()", 0, 1 ) -dt = 0.025 -xvalue("dt","dt", 1,"setdt()", 0, 1 ) -steps_per_ms = 40 -xvalue("Points plotted/ms","steps_per_ms", 1,"setdt()", 0, 1 ) -screen_update_invl = 0.05 -xvalue("Scrn update invl","screen_update_invl", 1,"", 0, 1 ) -realtime = 0.81 -xvalue("Real Time","realtime", 0,"", 0, 1 ) -xpanel(98,168) -} -{ -save_window_ = new Graph(0) -save_window_.size(0,1000,-80,40) -scene_vector_[3] = save_window_ -{save_window_.view(0, -80, 1000, 120, 388, 170, 300.48, 200.32)} -graphList[0].append(save_window_) -save_window_.save_name("graphList[0].") -save_window_.addexpr("v(.5)", 1, 1, 0.8, 0.9, 2) -} -objectvar scene_vector_[1] -{doNotify()} diff --git a/develop/benchmark/COBAHH/COBAHH_neuron/net.hoc b/develop/benchmark/COBAHH/COBAHH_neuron/net.hoc deleted file mode 100644 index 2db7d803..00000000 --- a/develop/benchmark/COBAHH/COBAHH_neuron/net.hoc +++ /dev/null @@ -1,74 +0,0 @@ -objref nil - -proc netparam() { -N_I = int(ncell/5.0) // Number of inibitory cells -N_E = ncell - N_I // Number of excitatory cells -CONNECTIVITY = 0.02 // Connection probability -C_I = int(N_I*CONNECTIVITY) // nb inh synapses per neuron -C_E = int(N_E*CONNECTIVITY) // nb exc synapses per neuron -AMPA_GMAX = 0.006 // (uS) -GABA_GMAX = 0.067 // (uS) -DELAY = 0 // (ms) -} -netparam() - -proc create_cells() { local i, gid localobj cell, nc - // Make each CPU create just the cells that "belong" to it. - for pcitr(&i, &gid) { // in common/init.hoc - // i ranges from 0 to "number of cells on this cpu". - // gid is globally unique and is in the range from 0 to ncell, - // where ncell is the total number of cells on all machines. - cell = newcell(gid) - // Associate this cell and gid with each other. - pnm.register_cell(gid, cell) - // Create a corresponding new instance of the RandomStream class. - ranlist.append(new RandomStream(gid)) // Notice it is the ith RandomStream. - } -} - -// Pick exactly C_E and C_I unique non-self random connections for each target. -// Assume many more sources than target connections. -proc connect_cells() { local i, j, gid, r, d localobj cell, u, rs - // In the paper, delay is 0. If it is nonzero, we can run this model - // on a parallel machine, i.e a machine with pc.nhost > 1. - d = DELAY - // Initialize the pseudorandom number generator. - mcell_ran4_init(connect_random_low_start_) - u = new Vector(ncell) // for sampling without replacement - for pcitr(&i, &gid) { // For each target cell . . . - u.fill(0) // u.x[i]==1 will mean that i has already been chosen. - cell = pnm.cells.object(i) - rs = ranlist.object(i) // . . . identify the corresponding RandomStream . . . - rs.start() - // . . . and make it return pseudorandom integers in the range 0..N_E-1. - rs.r.discunif(0, N_E-1) - j=0 while(j < C_E) { // Excitatory sources - r = rs.repick() - // No self connection, and no more than one connection from any source. - if (r != gid) if (u.x[r] == 0) { - // Set up a connection from source to target. - pnm.nc_append(r, gid, AMPA_INDEX, AMPA_GMAX, d) - // Mark this source index as "used" and prepare to choose another. - u.x[r] = 1 - j += 1 - } - } - // Now make the RandomStream return pseudorandom integers in the range N_E..ncell-1. - rs.r.discunif(N_E, ncell-1) - j=0 while(j < C_I) { // Inhibitory sources - r = rs.repick() - if (r != gid) if (u.x[r] == 0) { - pnm.nc_append(r, gid, GABA_INDEX, GABA_GMAX, d) - u.x[r] = 1 - j += 1 - } - } - } -} - -proc create_net() { - create_cells() - if (pc.id == 0) printf("Created %d cells; %d on host 0\n", pnm.ncell, pnm.cells.count) - connect_cells() - if (pc.id == 0) printf("Created %d connections to targets on host 0\n", pnm.nclist.count) -} diff --git a/develop/benchmark/COBAHH/COBAHH_neuron/netstim.hoc b/develop/benchmark/COBAHH/COBAHH_neuron/netstim.hoc deleted file mode 100644 index 872c8826..00000000 --- a/develop/benchmark/COBAHH/COBAHH_neuron/netstim.hoc +++ /dev/null @@ -1,74 +0,0 @@ -// random external input for first 50 ms -// do not consider part of net so keep out of the pnm data structures - -// Stimulation parameters - -N_STIM = ncell/50 // number of neurons stimulated -STOPSTIM = 50 // duration of stimulation (ms) -NSYN_STIM = 20 // nb of stim (exc) synapses per neuron -STIM_INTERVAL = 70 // mean interval between stims (ms) - -objref svec, stvec -svec = new Vector() -stvec = new Vector() -objref stimlist, ncstimlist -proc create_stim() {local i, gid localobj stim, cell, nc, rs - mcell_ran4_init($1) - stimlist = new List() - ncstimlist = new List() - - // The first N_STIM (excitatory) cells are stimulated. - // Each CPU creates NetStims and NetCons that target only its own cells, - // i.e. no NetStim's spike train is sent to a cell on a different CPU. - // Thus the delay can be 0 and we can still run in parallel. - // HOWEVER when the "use_self_queue" performance optimization is requested - // from cvode.queue_mode, that optimization will be refused, - // even when running serially, unless ALL NetCon.delay > 0. - // This is why nc.delay is set to 1 several lines below. - for pcitr(&i, &gid) { // in common/init.hoc - if (gid >= N_STIM) { break } - - // The ith cell and its corresponding RandomStream. - cell = pc.gid2cell(gid) - rs = ranlist.object(i) - - stim = new NetStim() - stim.interval = STIM_INTERVAL - stim.number = 1000 // but will shut off after STOPSTIM - stim.noise = 1 - stim.start = 0 - // Use the gid-specific random generator so random streams are - // independent of where and how many stims there are. - stim.noiseFromRandom(rs.r) - rs.r.negexp(1) - rs.start() - - if (hoc_sf_.is_artificial(cell)) { - nc = new NetCon(stim, cell) - }else{ - nc = new NetCon(stim, cell.synlist.object(0)) - } - // Set all NetCon.delay > 0 so that "use_self_queue" optimization - // will not be refused due to 0 delay between NetStim and its target - // (see above comment re: "use_self_queue" performance optimization). - // There is no loss of generality, since the NetStim can be set to fire 1 ms - // before you want the targets to get the spike. - nc.delay = 1 - nc.weight = $2 - nc.record(stvec, svec, ncstimlist.count) - - ncstimlist.append(nc) - stimlist.append(stim) - } - - stim = new NetStim() // will turn off all the others - stim.number = 1 - stim.start = STOPSTIM - for i=0, stimlist.count-1 { - nc = new NetCon(stim, stimlist.object(i)) - nc.delay = 1 - nc.weight = -1 - ncstimlist.append(nc) - } - stimlist.append(stim) -} diff --git a/develop/benchmark/COBAHH/COBAHH_neuron/perfrun.hoc b/develop/benchmark/COBAHH/COBAHH_neuron/perfrun.hoc deleted file mode 100644 index fc5368c9..00000000 --- a/develop/benchmark/COBAHH/COBAHH_neuron/perfrun.hoc +++ /dev/null @@ -1,175 +0,0 @@ -proc want_all_spikes() { local i, gid - for pcitr(&i, &gid) { - pnm.spike_record(gid) - } -} - -objref mxhist_ -proc mkhist() { - if (pnm.myid == 0) { - mxhist_ = new Vector($1) - pc.max_histogram(mxhist_) - } -} - -proc prhist() {local i, j - if (pnm.myid == 0 && object_id(mxhist_)) { - printf("histogram of #spikes vs #exchanges\n") - j = 0 - for i=0, mxhist_.size-1 { - if (mxhist_.x[i] != 0) { j = i } - } - for i = 0, j { - printf("%d\t %d\n", i, mxhist_.x[i]) - } - printf("end of histogram\n") - } -} - -objref tdat_ -tdat_ = new Vector(3) -proc prun() { - pnm.pc.set_maxstep(10) - runtime=startsw() - tdat_.x[0] = pnm.pc.wait_time - stdinit() - pnm.psolve(tstop) - tdat_.x[0] = pnm.pc.wait_time - tdat_.x[0] - runtime = startsw() - runtime - tdat_.x[1] = pnm.pc.step_time - tdat_.x[2] = pnm.pc.send_time -// printf("%d wtime %g\n", pnm.myid, waittime) -} - -proc poststat() { - pnm.pc.post("poststat", pnm.myid, tdat_) -} - -objref spstat_ -proc postspstat() {local i - spstat_ = new Vector() - cvode.spike_stat(spstat_) - i = spstat_.size - spstat_.resize(spstat_.size + 4) - spstat_.x[i] = pc.spike_statistics(&spstat_.x[i+1], &spstat_.x[i+2],\ - &spstat_.x[i+3]) - pnm.pc.post("postspstat", pnm.myid, spstat_) -} - -objref tavg_stat, tmin_stat, tmax_stat, idmin_stat, idmax_stat - -proc getstat() {local i, j, id localobj tdat - tdat = tdat_.c tavg_stat = tdat_.c tmin_stat = tdat_.c tmax_stat = tdat_.c - idmin_stat = tdat_.c.fill(0) idmax_stat = tdat_.c.fill(0) - if (pnm.nwork > 1) { - pnm.pc.context("poststat()\n") - for i=0, pnm.nwork-2 { - pnm.pc.take("poststat", &id, tdat) - tavg_stat.add(tdat) - for j = 0, tdat_.size-1 { - if (tdat.x[j] > tmax_stat.x[j]) { - idmax_stat.x[j] = id - tmax_stat.x[j] = tdat.x[j] - } - if (tdat.x[j] < tmin_stat.x[j]) { - idmin_stat.x[j] = id - tmin_stat.x[j] = tdat.x[j] - } - } - } - } - tavg_stat.div(pnm.nhost) -} - -proc print_spike_stat_info() {local i, j, id localobj spstat, sum, min, max, idmin, idmax, label - spstat = new Vector() - spstat_ = new Vector() - cvode.spike_stat(spstat_) - i = spstat_.size - spstat_.resize(spstat_.size + 4) - spstat_.x[i] = pc.spike_statistics(&spstat_.x[i+1], &spstat_.x[i+2],\ - &spstat_.x[i+3]) - sum = spstat_.c - min = spstat_.c - max = spstat_.c - idmin = spstat_.c.fill(0) - idmax = spstat_.c.fill(0) - if (pnm.nwork > 1) { - pnm.pc.context("postspstat()\n") - for i=0, pnm.nwork-2 { - pnm.pc.take("postspstat", &id, spstat) - sum.add(spstat) - for j=0, spstat.size-1 { - if (spstat.x[j] > max.x[j]) { - idmax.x[j] = id - max.x[j] = spstat.x[j] - } - if (spstat.x[j] < min.x[j]) { - idmin.x[j] = id - min.x[j] = spstat.x[j] - } - } - } - } - label = new List() - label.append(new String("eqn")) - label.append(new String("NetCon")) - label.append(new String("deliver")) - label.append(new String("NC deliv")) - label.append(new String("PS send")) - label.append(new String("S deliv")) - label.append(new String("S send")) - label.append(new String("S move")) - label.append(new String("Q insert")) - label.append(new String("Q move")) - label.append(new String("Q remove")) - label.append(new String("max sent")) - label.append(new String("sent")) - label.append(new String("received")) - label.append(new String("used")) - - printf("%10s %13s %10s %10s %5s %5s\n",\ - "", "total", "min", "max", "idmin", "idmax") - for i=0, spstat_.size-1 { - printf("%-10s %13.0lf %10d %10d %5d %5d\n",\ -label.object(i).s, sum.x[i], min.x[i], max.x[i], idmin.x[i], idmax.x[i]) - } - - printf("\n%-12s %-12s %-12s %-12s %-12s %-12s\n",\ - "setup", "run", "avgspkxfr", "avgcomp", "avgx2q", "avgvxfr") - printf("%-12.4g %-12.4g", setuptime, runtime) - for i=0, tdat_.size-1 { printf(" %-12.4g", tavg_stat.x[i]) } - - printf("\n\n%5s %-15s %-15s %-15s %-15s\n", \ - "", "id spkxfr", "id comp", "id x2q", "id vxfr") - printf("%-5s", "min") - for i=0, tdat_.size-1 { printf(" %-4d %-10.4g", idmin_stat.x[i], tmin_stat.x[i]) } - printf("\n%-5s", "max") - for i=0, tdat_.size-1 { printf(" %-4d %-10.4g", idmax_stat.x[i], tmax_stat.x[i]) } - printf("\n") -} - -proc perf2file() { local i localobj perf - perf = new File() - perf.aopen("perf.dat") - perf.printf("%d %d %g %g ",pnm.nhost, pnm.ncell, setuptime, runtime) - for i=0, tdat_.size-1 { perf.printf(" %g", tavg_stat.x[i]) } - perf.printf(" ") - for i=0, tdat_.size-1 { perf.printf(" %d %g ", idmin_stat.x[i], tmin_stat.x[i]) } - perf.printf(" ") - for i=0, tdat_.size-1 { perf.printf(" %d %g ", idmax_stat.x[i], tmax_stat.x[i]) } - perf.printf("\n") - - perf.close -} - -proc spike2file() { localobj outf - outf = new File() - outf.wopen("out.dat") - for i=0, pnm.idvec.size-1 { - outf.printf("%g\t%d\n", pnm.spikevec.x[i], pnm.idvec.x[i]) - } - outf.close -} - - diff --git a/develop/benchmark/COBAHH/COBAHH_neuron/ranstream.hoc b/develop/benchmark/COBAHH/COBAHH_neuron/ranstream.hoc deleted file mode 100644 index da2e0834..00000000 --- a/develop/benchmark/COBAHH/COBAHH_neuron/ranstream.hoc +++ /dev/null @@ -1,18 +0,0 @@ -random_stream_offset_ = 1000 - -begintemplate RandomStream -public r, repick, start, stream -external random_stream_offset_ -objref r -proc init() { - stream = $1 - r = new Random() - start() -} -func start() { - return r.MCellRan4(stream*random_stream_offset_ + 1) -} -func repick() { - return r.repick() -} -endtemplate RandomStream diff --git a/develop/benchmark/COBAHH/COBAHH_neuron/spkplt.hoc b/develop/benchmark/COBAHH/COBAHH_neuron/spkplt.hoc deleted file mode 100644 index aceab007..00000000 --- a/develop/benchmark/COBAHH/COBAHH_neuron/spkplt.hoc +++ /dev/null @@ -1,14 +0,0 @@ -load_file("nrngui.hoc") - -objref g -g = new Graph() -proc spkplt() {localobj x, y - clipboard_retrieve($s1) - printf("read %d spikes\n", hoc_obj_[1].size) - x = hoc_obj_[1] - y = hoc_obj_[0] - g.size(x.min, x.max, y.min, y.max) - y.mark(g, x, "|", 5, $2, 1) -} - -spkplt("out.dat", 1) diff --git a/develop/benchmark/COBAHH/how_to_run.md b/develop/benchmark/COBAHH/how_to_run.md deleted file mode 100644 index 1af7ea71..00000000 --- a/develop/benchmark/COBAHH/how_to_run.md +++ /dev/null @@ -1,18 +0,0 @@ -Run -=== - -brainpy: -> python COBAHH_brainpy.py - -brian2: -> python COBAHH_brian2.py - -NEST simulator: -> nest param_nest.sli COBAHH_nest.sli - -NEURON: -> cd COBAHH_neuron -> nrngui COBAHH_neuron.hoc - -References: -[1] Brette, R., Rudolph, M., Carnevale, T. et al. Simulation of networks of spiking neurons: A review of tools and strategies. J Comput Neurosci 23, 349–398 (2007). https://doi.org/10.1007/s10827-007-0038-6 diff --git a/develop/benchmark/COBAHH/param_nest.sli b/develop/benchmark/COBAHH/param_nest.sli deleted file mode 100644 index 4b6ff7c3..00000000 --- a/develop/benchmark/COBAHH/param_nest.sli +++ /dev/null @@ -1,80 +0,0 @@ -%%% PARAMETER SECTION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -% define all relevant parameters: changes should be made here -% all data is place in the userdict dictionary - -% A dictionary is a list of name value pairs, enclosed in << and >> -% Here we use dictionaries to encapsulate the parameters for the different -% benchmarks - -/hh_coba_params -<< - /model /hh_cond_exp_traub % the neuron model to use - - /model_params - << - /g_Na 20000.0 nS % Sodium conductance [nS] - /g_K 6000.0 nS % K Conductance [nS] - /g_L 10.0 nS % Leak Conductance [nS] - /C_m 200.0 pF % Membrane Capacitance [pF] - /E_Na 50.0 mV % reversal potential (Sodium) [mV] - /E_K -90.0 mV % reversal potential (Potassium) [mV] - /E_L -60.0 mV % Resting Potential [mV] - /E_ex 0.0 mV % Excitatory reversal potential (mV) - /E_in -80.0 mV % Inhibitory reversal potential (Potassium) [mV] - /tau_syn_ex 5.0 ms % Excitatory synaptic time constant [ms] - /tau_syn_in 10.0 ms % Inhibitory synaptic time constant [ms] - >> - - /delay 0.1 ms % synaptic delay, all connections [ms] - - % synaptic strengths, here peak conductance - /E_synapse_params - << - /weight 6.0 nS % excitatory synaptic conductance - >> - - /I_synapse_params - << - /weight -67.0 nS % inhibitory synaptic conductance - >> - - /stimulus /poisson_generator - /stimulus_params - << - /rate 300.0 Hz % rate of inital poisson stimulus - /start 1.0 ms % start of Poisson_generator [ms] - /stop 51.0 ms % stop of Poisson_generator [ms] - /origin 0.0 ms % origin of time, to calculate start_time [ms] - >> - - /detector /spike_detector - /detector_params - << - /withtime true - /withgid true - /to_file true - /label (hh_coba) - >> - - % number of neurons per population to record from - /Nrec 500 - - %number of neurons to stimulate - /Nstim 50 - /simtime 10000.0 ms % simulated time - /dt 0.1 ms % simulation step - - /NE 3200 % number of excitatory neurons - /NI 800 % number of inhibitory neurons - /epsilon 0.02 % Connection probability - - /virtual_processes 1 % number of virtual processes to use - ->> def - -hh_coba_params using % here we activate the definitions in the dictionary - -/parameters_set true def -statusdict/argv :: size 1 gt { 1 get dirname (/) join } { () } ifelse -(COBAHH_nest.sli) join run diff --git a/develop/benchmark/CUBA/CUBA_brainpy.py b/develop/benchmark/CUBA/CUBA_brainpy.py deleted file mode 100644 index 56183c60..00000000 --- a/develop/benchmark/CUBA/CUBA_brainpy.py +++ /dev/null @@ -1,113 +0,0 @@ -# -*- coding: utf-8 -*- - -import time - -import numpy as np - -import brainpy as bp - -dt = 0.1 -bp.profile.set(jit=True, dt=dt) - -num_exc = 3200 -num_inh = 800 -taum = 20 -taue = 5 -taui = 10 -Vt = -50 -Vr = -60 -El = -49 -we = 60 * 0.27 / 10 # excitatory synaptic weight (voltage) -wi = -20 * 4.5 / 10 # inhibitory synaptic weight -ref = 5.0 - -neu_ST = bp.types.NeuState( - {'sp_t': -1e7, - 'V': 0., - 'sp': 0., - 'ge': 0., - 'gi': 0.} -) - - -@bp.integrate -def int_ge(ge, t): - return -ge / taue - - -@bp.integrate -def int_gi(gi, t): - return -gi / taui - - -@bp.integrate -def int_V(V, t, ge, gi): - return (ge + gi - (V - El)) / taum - - -def neu_update(ST, _t): - ST['ge'] = int_ge(ST['ge'], _t) - ST['gi'] = int_gi(ST['gi'], _t) - - if _t - ST['sp_t'] > ref: - V = int_V(ST['V'], _t, ST['ge'], ST['gi']) - if V >= Vt: - ST['V'] = Vr - ST['sp'] = 1. - ST['sp_t'] = _t - else: - ST['V'] = V - ST['sp'] = 0. - else: - ST['sp'] = 0. - - -neuron = bp.NeuType(name='CUBA', ST=neu_ST, steps=neu_update, mode='scalar') - - -def update1(pre, post, pre2post): - for pre_id in range(len(pre2post)): - if pre['sp'][pre_id] > 0.: - post_ids = pre2post[pre_id] - for i in post_ids: - post['ge'][i] += we - - -exc_syn = bp.SynType('exc_syn', steps=update1, ST=bp.types.SynState()) - - -def update2(pre, post, pre2post): - for pre_id in range(len(pre2post)): - if pre['sp'][pre_id] > 0.: - post_ids = pre2post[pre_id] - for i in post_ids: - post['gi'][i] += wi - - -inh_syn = bp.SynType('inh_syn', steps=update2, ST=bp.types.SynState()) - -group = bp.NeuGroup(neuron, - size=num_exc + num_inh, - monitors=['sp']) -group.ST['V'] = Vr + np.random.rand(num_exc + num_inh) * (Vt - Vr) - -exc_conn = bp.TwoEndConn(exc_syn, - pre=group[:num_exc], - post=group, - conn=bp.connect.FixedProb(prob=0.02)) - -inh_conn = bp.TwoEndConn(inh_syn, - pre=group[num_exc:], - post=group, - conn=bp.connect.FixedProb(prob=0.02)) - -net = bp.Network(group, exc_conn, inh_conn, mode='repeat') -t0 = time.time() -# net.run(5 * 1000., report_percent=1., report=True) -net.run(1250., report=True) -net.run((1250., 2500.), report=True) -net.run((2500., 3750.), report=True) -net.run((3750., 5000.), report=True) -print('Used time {} s.'.format(time.time() - t0)) - -bp.visualize.raster_plot(net.ts, group.mon.sp, show=True) diff --git a/develop/benchmark/CUBA/CUBA_brian2.py b/develop/benchmark/CUBA/CUBA_brian2.py deleted file mode 100644 index 200c1d51..00000000 --- a/develop/benchmark/CUBA/CUBA_brian2.py +++ /dev/null @@ -1,41 +0,0 @@ -from brian2 import * - -set_device('cpp_standalone', directory='brian2_CUBA') -# prefs.codegen.target = "cython" - -taum = 20 * ms -taue = 5 * ms -taui = 10 * ms -Vt = -50 * mV -Vr = -60 * mV -El = -49 * mV - -eqs = ''' -dv/dt = (ge+gi-(v-El))/taum : volt (unless refractory) -dge/dt = -ge/taue : volt -dgi/dt = -gi/taui : volt -''' - -P = NeuronGroup(4000, eqs, threshold='v>Vt', reset='v = Vr', - refractory=5 * ms, method='euler') -P.v = 'Vr + rand() * (Vt - Vr)' -P.g = 0 * mV -P.gi = 0 * mV - -we = (60 * 0.27 / 10) * mV # excitatory synaptic weight (voltage) -Ce = Synapses(P, P, on_pre='ge += we') -Ce.connect('i<3200', p=0.02) -wi = (-20 * 4.5 / 10) * mV # inhibitory synaptic weight -Ci = Synapses(P, P, on_pre='gi += wi') -Ci.connect('i>=3200', p=0.02) - -s_mon = SpikeMonitor(P) - -t0 = time.time() -run(5 * second, report='text') -print('{}. Used time {} s.'.format(prefs.codegen.target, time.time() - t0)) - -plot(s_mon.t / ms, s_mon.i, ',k') -xlabel('Time (ms)') -ylabel('Neuron index') -show() diff --git a/develop/benchmark/scaling_test.py b/develop/benchmark/scaling_test.py deleted file mode 100644 index 1561ba04..00000000 --- a/develop/benchmark/scaling_test.py +++ /dev/null @@ -1,141 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -Test the network scaling ability. -""" - - -import time -import brainpy as bp -import numpy as np -import math - - -def define_hh(E_Na=50., g_Na=120., E_K=-77., g_K=36., E_Leak=-54.387, - g_Leak=0.03, C=1.0, Vth=20., Iext=10.): - ST = bp.types.NeuState( - {'V': -65., 'm': 0., 'h': 0., 'n': 0., 'sp': 0., 'inp': 0.}, - help='Hodgkin–Huxley neuron state.\n' - '"V" denotes membrane potential.\n' - '"n" denotes potassium channel activation probability.\n' - '"m" denotes sodium channel activation probability.\n' - '"h" denotes sodium channel inactivation probability.\n' - '"sp" denotes spiking state.\n' - '"inp" denotes synaptic input.\n' - ) - - @bp.integrate - def int_m(m, t, V): - alpha = 0.1 * (V + 40) / (1 - math.exp(-(V + 40) / 10)) - beta = 4.0 * math.exp(-(V + 65) / 18) - return alpha * (1 - m) - beta * m - - @bp.integrate - def int_h(h, t, V): - alpha = 0.07 * math.exp(-(V + 65) / 20.) - beta = 1 / (1 + math.exp(-(V + 35) / 10)) - return alpha * (1 - h) - beta * h - - @bp.integrate - def int_n(n, t, V): - alpha = 0.01 * (V + 55) / (1 - math.exp(-(V + 55) / 10)) - beta = 0.125 * math.exp(-(V + 65) / 80) - return alpha * (1 - n) - beta * n - - @bp.integrate - def int_V(V, t, m, h, n, Isyn): - INa = g_Na * m ** 3 * h * (V - E_Na) - IK = g_K * n ** 4 * (V - E_K) - IL = g_Leak * (V - E_Leak) - dvdt = (- INa - IK - IL + Isyn) / C - return dvdt - - def update(ST, _t): - m = int_m(ST['m'], _t, ST['V']) - h = int_h(ST['h'], _t, ST['V']) - n = int_n(ST['n'], _t, ST['V']) - V = int_V(ST['V'], _t, m, h, n, ST['inp']) - sp = (ST['V'] < Vth) and (V >= Vth) - ST['sp'] = sp - ST['V'] = V - ST['m'] = m - ST['h'] = h - ST['n'] = n - ST['inp'] = Iext - - return bp.NeuType(name='HH_neuron', - ST=ST, - steps=update, - mode='scalar') - - -bp.profile.set(dt=0.1, numerical_method='exponential') - - -def hh_compare_cpu_and_multi_cpu(num=1000, vector=True): - print(f'HH, vector_based={vector}, device=cpu', end=', ') - bp.profile.set(jit=True, device='cpu') - - HH = define_hh() - HH.mode = 'vector' if vector else 'scalar' - neu = bp.NeuGroup(HH, size=num) - - t0 = time.time() - neu.run(duration=1000., report=True) - t_cpu = time.time() - t0 - print('used {:.3f} ms'.format(t_cpu)) - - print(f'HH, vector_based={vector}, device=multi-cpu', end=', ') - bp.profile.set(jit=True, device='multi-cpu') - neu = bp.NeuGroup(HH, size=num) - t0 = time.time() - neu.run(duration=1000., report=True) - t_multi_cpu = time.time() - t0 - print('used {:.3f} ms'.format(t_multi_cpu)) - - print(f"HH model with multi-cpu speeds up {t_cpu / t_multi_cpu}") - print() - - -def hh_compare_cpu_and_gpu(num=1000): - print(f'HH, device=cpu', end=', ') - bp.profile.set(jit=True, device='cpu', show_code=False) - - HH = define_hh() - HH.mode = 'scalar' - # neu = bp.NeuGroup(HH, geometry=num) - # - # t0 = time.time() - # neu.run(duration=1000., report=True) - # t_cpu = time.time() - t0 - # print('used {:.3f} ms'.format(t_cpu)) - - print(f'HH, device=gpu', end=', ') - bp.profile.set(jit=True, device='gpu') - neu = bp.NeuGroup(HH, size=num) - t0 = time.time() - neu.run(duration=1000., report=True) - t_multi_cpu = time.time() - t0 - print('used {:.3f} ms'.format(t_multi_cpu)) - - # print(f"HH model with multi-cpu speeds up {t_cpu / t_multi_cpu}") - # print() - - -if __name__ == '__main__': - pass - - # hh_compare_cpu_and_multi_cpu(int(1e4)) - # hh_compare_cpu_and_multi_cpu(int(1e5)) - # hh_compare_cpu_and_multi_cpu(int(1e6)) - - # hh_compare_cpu_and_gpu(int(1e2)) - # hh_compare_cpu_and_gpu(int(1e3)) - # hh_compare_cpu_and_gpu(int(1e4)) - hh_compare_cpu_and_gpu(int(1e5)) - hh_compare_cpu_and_gpu(int(1e6)) - # hh_compare_cpu_and_gpu(int(1e7)) - - - - diff --git a/develop/conda-recipe/meta.yaml b/develop/conda-recipe/meta.yaml deleted file mode 100644 index c11d4966..00000000 --- a/develop/conda-recipe/meta.yaml +++ /dev/null @@ -1,42 +0,0 @@ -package: - name: brain-py - version: "1.0.3" - -source: - path: ../../ - -build: - noarch: python - number: 0 - script: python -m pip install --no-deps --ignore-installed . - -requirements: - host: - - python - - pip - run: - - python - - numpy>=1.13 - - sympy>=1.2 - - numba>=0.50 - - matplotlib>=3.0 - - setuptools>=40.0.0 - -test: - imports: - - brainpy - -about: - home: https://github.com/PKU-NIP-Lab/BrainPy - license: GPL-3.0 - summary: 'A simulation toolbox for researches in computational neuroscience and brain-inspired computation.' - description: | - BrainPy is a lightweight framework based on the latest Just-In-Time (JIT) - compilers (especially [Numba](https://numba.pydata.org/)). The goal of - BrainPy is to provide a unified simulation and analysis framework for neuronal - dynamics with the feature of high flexibility and efficiency. BrainPy is - flexible because it endows the users with the fully data/logic flow control. - BrainPy is efficient because it supports JIT acceleration on CPUs and GPUs. - dev_url: https://github.com/PKU-NIP-Lab/BrainPy - doc_url: https://brainpy.readthedocs.io/en/latest/ - doc_source_url: https://github.com/PKU-NIP-Lab/BrainPy/blob/master/README.md -- 2.34.1 From 63bd3d9868086b54b8869d69da3b3d99b05ee9b6 Mon Sep 17 00:00:00 2001 From: chaoming Date: Wed, 8 Sep 2021 19:19:01 +0800 Subject: [PATCH 14/30] delete extra docs --- ...rainpy.simulation.brainobjects.Channel.rst | 36 ------------------- ...y.simulation.brainobjects.CondNeuGroup.rst | 35 ------------------ ....simulation.brainobjects.ConstantDelay.rst | 36 ------------------- ...inpy.simulation.brainobjects.Container.rst | 35 ------------------ .../brainpy.simulation.brainobjects.Delay.rst | 35 ------------------ ...rainpy.simulation.brainobjects.Network.rst | 35 ------------------ ...ainpy.simulation.brainobjects.NeuGroup.rst | 35 ------------------ ...npy.simulation.brainobjects.TwoEndConn.rst | 36 ------------------- ...rainpy.simulation.connectivity.All2All.rst | 31 ---------------- .../brainpy.simulation.connectivity.DOG.rst | 31 ---------------- ...y.simulation.connectivity.FixedPostNum.rst | 31 ---------------- ...py.simulation.connectivity.FixedPreNum.rst | 31 ---------------- ...inpy.simulation.connectivity.FixedProb.rst | 31 ---------------- ...y.simulation.connectivity.GaussianProb.rst | 31 ---------------- ...simulation.connectivity.GaussianWeight.rst | 31 ---------------- ...inpy.simulation.connectivity.GridEight.rst | 31 ---------------- ...ainpy.simulation.connectivity.GridFour.rst | 31 ---------------- .../brainpy.simulation.connectivity.GridN.rst | 31 ---------------- ...rainpy.simulation.connectivity.One2One.rst | 31 ---------------- ...ainpy.simulation.connectivity.PowerLaw.rst | 31 ---------------- ...py.simulation.connectivity.ScaleFreeBA.rst | 31 ---------------- ...imulation.connectivity.ScaleFreeBADual.rst | 31 ---------------- ...npy.simulation.connectivity.SmallWorld.rst | 31 ---------------- ...brainpy.simulation.connectivity.ij2mat.rst | 6 ---- ...brainpy.simulation.connectivity.mat2ij.rst | 6 ---- ...ainpy.simulation.connectivity.post2pre.rst | 6 ---- ...ainpy.simulation.connectivity.post2syn.rst | 6 ---- ...npy.simulation.connectivity.post_slice.rst | 6 ---- ...ainpy.simulation.connectivity.pre2post.rst | 6 ---- ...rainpy.simulation.connectivity.pre2syn.rst | 6 ---- ...inpy.simulation.connectivity.pre_slice.rst | 6 ---- .../brainpy.simulation.monitor.Monitor.rst | 25 ------------- docs/quickstart/numerical_solvers.ipynb | 11 +++--- 33 files changed, 5 insertions(+), 827 deletions(-) delete mode 100644 docs/apis/simulation/generated/brainpy.simulation.brainobjects.Channel.rst delete mode 100644 docs/apis/simulation/generated/brainpy.simulation.brainobjects.CondNeuGroup.rst delete mode 100644 docs/apis/simulation/generated/brainpy.simulation.brainobjects.ConstantDelay.rst delete mode 100644 docs/apis/simulation/generated/brainpy.simulation.brainobjects.Container.rst delete mode 100644 docs/apis/simulation/generated/brainpy.simulation.brainobjects.Delay.rst delete mode 100644 docs/apis/simulation/generated/brainpy.simulation.brainobjects.Network.rst delete mode 100644 docs/apis/simulation/generated/brainpy.simulation.brainobjects.NeuGroup.rst delete mode 100644 docs/apis/simulation/generated/brainpy.simulation.brainobjects.TwoEndConn.rst delete mode 100644 docs/apis/simulation/generated/brainpy.simulation.connectivity.All2All.rst delete mode 100644 docs/apis/simulation/generated/brainpy.simulation.connectivity.DOG.rst delete mode 100644 docs/apis/simulation/generated/brainpy.simulation.connectivity.FixedPostNum.rst delete mode 100644 docs/apis/simulation/generated/brainpy.simulation.connectivity.FixedPreNum.rst delete mode 100644 docs/apis/simulation/generated/brainpy.simulation.connectivity.FixedProb.rst delete mode 100644 docs/apis/simulation/generated/brainpy.simulation.connectivity.GaussianProb.rst delete mode 100644 docs/apis/simulation/generated/brainpy.simulation.connectivity.GaussianWeight.rst delete mode 100644 docs/apis/simulation/generated/brainpy.simulation.connectivity.GridEight.rst delete mode 100644 docs/apis/simulation/generated/brainpy.simulation.connectivity.GridFour.rst delete mode 100644 docs/apis/simulation/generated/brainpy.simulation.connectivity.GridN.rst delete mode 100644 docs/apis/simulation/generated/brainpy.simulation.connectivity.One2One.rst delete mode 100644 docs/apis/simulation/generated/brainpy.simulation.connectivity.PowerLaw.rst delete mode 100644 docs/apis/simulation/generated/brainpy.simulation.connectivity.ScaleFreeBA.rst delete mode 100644 docs/apis/simulation/generated/brainpy.simulation.connectivity.ScaleFreeBADual.rst delete mode 100644 docs/apis/simulation/generated/brainpy.simulation.connectivity.SmallWorld.rst delete mode 100644 docs/apis/simulation/generated/brainpy.simulation.connectivity.ij2mat.rst delete mode 100644 docs/apis/simulation/generated/brainpy.simulation.connectivity.mat2ij.rst delete mode 100644 docs/apis/simulation/generated/brainpy.simulation.connectivity.post2pre.rst delete mode 100644 docs/apis/simulation/generated/brainpy.simulation.connectivity.post2syn.rst delete mode 100644 docs/apis/simulation/generated/brainpy.simulation.connectivity.post_slice.rst delete mode 100644 docs/apis/simulation/generated/brainpy.simulation.connectivity.pre2post.rst delete mode 100644 docs/apis/simulation/generated/brainpy.simulation.connectivity.pre2syn.rst delete mode 100644 docs/apis/simulation/generated/brainpy.simulation.connectivity.pre_slice.rst delete mode 100644 docs/apis/simulation/generated/brainpy.simulation.monitor.Monitor.rst diff --git a/docs/apis/simulation/generated/brainpy.simulation.brainobjects.Channel.rst b/docs/apis/simulation/generated/brainpy.simulation.brainobjects.Channel.rst deleted file mode 100644 index 18b6a039..00000000 --- a/docs/apis/simulation/generated/brainpy.simulation.brainobjects.Channel.rst +++ /dev/null @@ -1,36 +0,0 @@ -brainpy.simulation.brainobjects.Channel -======================================= - -.. currentmodule:: brainpy.simulation.brainobjects - -.. autoclass:: Channel - - - .. automethod:: __init__ - - - .. rubric:: Methods - - .. autosummary:: - - ~Channel.__init__ - ~Channel.current - ~Channel.ints - ~Channel.nodes - ~Channel.run - ~Channel.train_vars - ~Channel.unique_name - ~Channel.update - ~Channel.vars - - - - - - .. rubric:: Attributes - - .. autosummary:: - - ~Channel.target_backend - - \ No newline at end of file diff --git a/docs/apis/simulation/generated/brainpy.simulation.brainobjects.CondNeuGroup.rst b/docs/apis/simulation/generated/brainpy.simulation.brainobjects.CondNeuGroup.rst deleted file mode 100644 index 8bd7dd1c..00000000 --- a/docs/apis/simulation/generated/brainpy.simulation.brainobjects.CondNeuGroup.rst +++ /dev/null @@ -1,35 +0,0 @@ -brainpy.simulation.brainobjects.CondNeuGroup -============================================ - -.. currentmodule:: brainpy.simulation.brainobjects - -.. autoclass:: CondNeuGroup - - - .. automethod:: __init__ - - - .. rubric:: Methods - - .. autosummary:: - - ~CondNeuGroup.__init__ - ~CondNeuGroup.ints - ~CondNeuGroup.nodes - ~CondNeuGroup.run - ~CondNeuGroup.train_vars - ~CondNeuGroup.unique_name - ~CondNeuGroup.update - ~CondNeuGroup.vars - - - - - - .. rubric:: Attributes - - .. autosummary:: - - ~CondNeuGroup.target_backend - - \ No newline at end of file diff --git a/docs/apis/simulation/generated/brainpy.simulation.brainobjects.ConstantDelay.rst b/docs/apis/simulation/generated/brainpy.simulation.brainobjects.ConstantDelay.rst deleted file mode 100644 index e6e1e090..00000000 --- a/docs/apis/simulation/generated/brainpy.simulation.brainobjects.ConstantDelay.rst +++ /dev/null @@ -1,36 +0,0 @@ -brainpy.simulation.brainobjects.ConstantDelay -============================================= - -.. currentmodule:: brainpy.simulation.brainobjects - -.. autoclass:: ConstantDelay - - - .. automethod:: __init__ - - - .. rubric:: Methods - - .. autosummary:: - - ~ConstantDelay.__init__ - ~ConstantDelay.ints - ~ConstantDelay.nodes - ~ConstantDelay.reset - ~ConstantDelay.run - ~ConstantDelay.train_vars - ~ConstantDelay.unique_name - ~ConstantDelay.update - ~ConstantDelay.vars - - - - - - .. rubric:: Attributes - - .. autosummary:: - - ~ConstantDelay.target_backend - - \ No newline at end of file diff --git a/docs/apis/simulation/generated/brainpy.simulation.brainobjects.Container.rst b/docs/apis/simulation/generated/brainpy.simulation.brainobjects.Container.rst deleted file mode 100644 index 2fe82208..00000000 --- a/docs/apis/simulation/generated/brainpy.simulation.brainobjects.Container.rst +++ /dev/null @@ -1,35 +0,0 @@ -brainpy.simulation.brainobjects.Container -========================================= - -.. currentmodule:: brainpy.simulation.brainobjects - -.. autoclass:: Container - - - .. automethod:: __init__ - - - .. rubric:: Methods - - .. autosummary:: - - ~Container.__init__ - ~Container.ints - ~Container.nodes - ~Container.run - ~Container.train_vars - ~Container.unique_name - ~Container.update - ~Container.vars - - - - - - .. rubric:: Attributes - - .. autosummary:: - - ~Container.target_backend - - \ No newline at end of file diff --git a/docs/apis/simulation/generated/brainpy.simulation.brainobjects.Delay.rst b/docs/apis/simulation/generated/brainpy.simulation.brainobjects.Delay.rst deleted file mode 100644 index b8f69456..00000000 --- a/docs/apis/simulation/generated/brainpy.simulation.brainobjects.Delay.rst +++ /dev/null @@ -1,35 +0,0 @@ -brainpy.simulation.brainobjects.Delay -===================================== - -.. currentmodule:: brainpy.simulation.brainobjects - -.. autoclass:: Delay - - - .. automethod:: __init__ - - - .. rubric:: Methods - - .. autosummary:: - - ~Delay.__init__ - ~Delay.ints - ~Delay.nodes - ~Delay.run - ~Delay.train_vars - ~Delay.unique_name - ~Delay.update - ~Delay.vars - - - - - - .. rubric:: Attributes - - .. autosummary:: - - ~Delay.target_backend - - \ No newline at end of file diff --git a/docs/apis/simulation/generated/brainpy.simulation.brainobjects.Network.rst b/docs/apis/simulation/generated/brainpy.simulation.brainobjects.Network.rst deleted file mode 100644 index 7ba4080b..00000000 --- a/docs/apis/simulation/generated/brainpy.simulation.brainobjects.Network.rst +++ /dev/null @@ -1,35 +0,0 @@ -brainpy.simulation.brainobjects.Network -======================================= - -.. currentmodule:: brainpy.simulation.brainobjects - -.. autoclass:: Network - - - .. automethod:: __init__ - - - .. rubric:: Methods - - .. autosummary:: - - ~Network.__init__ - ~Network.ints - ~Network.nodes - ~Network.run - ~Network.train_vars - ~Network.unique_name - ~Network.update - ~Network.vars - - - - - - .. rubric:: Attributes - - .. autosummary:: - - ~Network.target_backend - - \ No newline at end of file diff --git a/docs/apis/simulation/generated/brainpy.simulation.brainobjects.NeuGroup.rst b/docs/apis/simulation/generated/brainpy.simulation.brainobjects.NeuGroup.rst deleted file mode 100644 index ecb3ecb6..00000000 --- a/docs/apis/simulation/generated/brainpy.simulation.brainobjects.NeuGroup.rst +++ /dev/null @@ -1,35 +0,0 @@ -brainpy.simulation.brainobjects.NeuGroup -======================================== - -.. currentmodule:: brainpy.simulation.brainobjects - -.. autoclass:: NeuGroup - - - .. automethod:: __init__ - - - .. rubric:: Methods - - .. autosummary:: - - ~NeuGroup.__init__ - ~NeuGroup.ints - ~NeuGroup.nodes - ~NeuGroup.run - ~NeuGroup.train_vars - ~NeuGroup.unique_name - ~NeuGroup.update - ~NeuGroup.vars - - - - - - .. rubric:: Attributes - - .. autosummary:: - - ~NeuGroup.target_backend - - \ No newline at end of file diff --git a/docs/apis/simulation/generated/brainpy.simulation.brainobjects.TwoEndConn.rst b/docs/apis/simulation/generated/brainpy.simulation.brainobjects.TwoEndConn.rst deleted file mode 100644 index a78154ef..00000000 --- a/docs/apis/simulation/generated/brainpy.simulation.brainobjects.TwoEndConn.rst +++ /dev/null @@ -1,36 +0,0 @@ -brainpy.simulation.brainobjects.TwoEndConn -========================================== - -.. currentmodule:: brainpy.simulation.brainobjects - -.. autoclass:: TwoEndConn - - - .. automethod:: __init__ - - - .. rubric:: Methods - - .. autosummary:: - - ~TwoEndConn.__init__ - ~TwoEndConn.ints - ~TwoEndConn.nodes - ~TwoEndConn.register_constant_delay - ~TwoEndConn.run - ~TwoEndConn.train_vars - ~TwoEndConn.unique_name - ~TwoEndConn.update - ~TwoEndConn.vars - - - - - - .. rubric:: Attributes - - .. autosummary:: - - ~TwoEndConn.target_backend - - \ No newline at end of file diff --git a/docs/apis/simulation/generated/brainpy.simulation.connectivity.All2All.rst b/docs/apis/simulation/generated/brainpy.simulation.connectivity.All2All.rst deleted file mode 100644 index 2d132532..00000000 --- a/docs/apis/simulation/generated/brainpy.simulation.connectivity.All2All.rst +++ /dev/null @@ -1,31 +0,0 @@ -brainpy.simulation.connectivity.All2All -======================================= - -.. currentmodule:: brainpy.simulation.connectivity - -.. autoclass:: All2All - - - .. automethod:: __init__ - - - .. rubric:: Methods - - .. autosummary:: - - ~All2All.__init__ - ~All2All.make_conn_mat - ~All2All.make_mat2ij - ~All2All.make_post2pre - ~All2All.make_post2syn - ~All2All.make_post_slice - ~All2All.make_pre2post - ~All2All.make_pre2syn - ~All2All.make_pre_slice - ~All2All.requires - - - - - - \ No newline at end of file diff --git a/docs/apis/simulation/generated/brainpy.simulation.connectivity.DOG.rst b/docs/apis/simulation/generated/brainpy.simulation.connectivity.DOG.rst deleted file mode 100644 index 1e2db903..00000000 --- a/docs/apis/simulation/generated/brainpy.simulation.connectivity.DOG.rst +++ /dev/null @@ -1,31 +0,0 @@ -brainpy.simulation.connectivity.DOG -=================================== - -.. currentmodule:: brainpy.simulation.connectivity - -.. autoclass:: DOG - - - .. automethod:: __init__ - - - .. rubric:: Methods - - .. autosummary:: - - ~DOG.__init__ - ~DOG.make_conn_mat - ~DOG.make_mat2ij - ~DOG.make_post2pre - ~DOG.make_post2syn - ~DOG.make_post_slice - ~DOG.make_pre2post - ~DOG.make_pre2syn - ~DOG.make_pre_slice - ~DOG.requires - - - - - - \ No newline at end of file diff --git a/docs/apis/simulation/generated/brainpy.simulation.connectivity.FixedPostNum.rst b/docs/apis/simulation/generated/brainpy.simulation.connectivity.FixedPostNum.rst deleted file mode 100644 index 9f0c9565..00000000 --- a/docs/apis/simulation/generated/brainpy.simulation.connectivity.FixedPostNum.rst +++ /dev/null @@ -1,31 +0,0 @@ -brainpy.simulation.connectivity.FixedPostNum -============================================ - -.. currentmodule:: brainpy.simulation.connectivity - -.. autoclass:: FixedPostNum - - - .. automethod:: __init__ - - - .. rubric:: Methods - - .. autosummary:: - - ~FixedPostNum.__init__ - ~FixedPostNum.make_conn_mat - ~FixedPostNum.make_mat2ij - ~FixedPostNum.make_post2pre - ~FixedPostNum.make_post2syn - ~FixedPostNum.make_post_slice - ~FixedPostNum.make_pre2post - ~FixedPostNum.make_pre2syn - ~FixedPostNum.make_pre_slice - ~FixedPostNum.requires - - - - - - \ No newline at end of file diff --git a/docs/apis/simulation/generated/brainpy.simulation.connectivity.FixedPreNum.rst b/docs/apis/simulation/generated/brainpy.simulation.connectivity.FixedPreNum.rst deleted file mode 100644 index 16a40093..00000000 --- a/docs/apis/simulation/generated/brainpy.simulation.connectivity.FixedPreNum.rst +++ /dev/null @@ -1,31 +0,0 @@ -brainpy.simulation.connectivity.FixedPreNum -=========================================== - -.. currentmodule:: brainpy.simulation.connectivity - -.. autoclass:: FixedPreNum - - - .. automethod:: __init__ - - - .. rubric:: Methods - - .. autosummary:: - - ~FixedPreNum.__init__ - ~FixedPreNum.make_conn_mat - ~FixedPreNum.make_mat2ij - ~FixedPreNum.make_post2pre - ~FixedPreNum.make_post2syn - ~FixedPreNum.make_post_slice - ~FixedPreNum.make_pre2post - ~FixedPreNum.make_pre2syn - ~FixedPreNum.make_pre_slice - ~FixedPreNum.requires - - - - - - \ No newline at end of file diff --git a/docs/apis/simulation/generated/brainpy.simulation.connectivity.FixedProb.rst b/docs/apis/simulation/generated/brainpy.simulation.connectivity.FixedProb.rst deleted file mode 100644 index 0e063137..00000000 --- a/docs/apis/simulation/generated/brainpy.simulation.connectivity.FixedProb.rst +++ /dev/null @@ -1,31 +0,0 @@ -brainpy.simulation.connectivity.FixedProb -========================================= - -.. currentmodule:: brainpy.simulation.connectivity - -.. autoclass:: FixedProb - - - .. automethod:: __init__ - - - .. rubric:: Methods - - .. autosummary:: - - ~FixedProb.__init__ - ~FixedProb.make_conn_mat - ~FixedProb.make_mat2ij - ~FixedProb.make_post2pre - ~FixedProb.make_post2syn - ~FixedProb.make_post_slice - ~FixedProb.make_pre2post - ~FixedProb.make_pre2syn - ~FixedProb.make_pre_slice - ~FixedProb.requires - - - - - - \ No newline at end of file diff --git a/docs/apis/simulation/generated/brainpy.simulation.connectivity.GaussianProb.rst b/docs/apis/simulation/generated/brainpy.simulation.connectivity.GaussianProb.rst deleted file mode 100644 index 4715e3c8..00000000 --- a/docs/apis/simulation/generated/brainpy.simulation.connectivity.GaussianProb.rst +++ /dev/null @@ -1,31 +0,0 @@ -brainpy.simulation.connectivity.GaussianProb -============================================ - -.. currentmodule:: brainpy.simulation.connectivity - -.. autoclass:: GaussianProb - - - .. automethod:: __init__ - - - .. rubric:: Methods - - .. autosummary:: - - ~GaussianProb.__init__ - ~GaussianProb.make_conn_mat - ~GaussianProb.make_mat2ij - ~GaussianProb.make_post2pre - ~GaussianProb.make_post2syn - ~GaussianProb.make_post_slice - ~GaussianProb.make_pre2post - ~GaussianProb.make_pre2syn - ~GaussianProb.make_pre_slice - ~GaussianProb.requires - - - - - - \ No newline at end of file diff --git a/docs/apis/simulation/generated/brainpy.simulation.connectivity.GaussianWeight.rst b/docs/apis/simulation/generated/brainpy.simulation.connectivity.GaussianWeight.rst deleted file mode 100644 index dcf747ff..00000000 --- a/docs/apis/simulation/generated/brainpy.simulation.connectivity.GaussianWeight.rst +++ /dev/null @@ -1,31 +0,0 @@ -brainpy.simulation.connectivity.GaussianWeight -============================================== - -.. currentmodule:: brainpy.simulation.connectivity - -.. autoclass:: GaussianWeight - - - .. automethod:: __init__ - - - .. rubric:: Methods - - .. autosummary:: - - ~GaussianWeight.__init__ - ~GaussianWeight.make_conn_mat - ~GaussianWeight.make_mat2ij - ~GaussianWeight.make_post2pre - ~GaussianWeight.make_post2syn - ~GaussianWeight.make_post_slice - ~GaussianWeight.make_pre2post - ~GaussianWeight.make_pre2syn - ~GaussianWeight.make_pre_slice - ~GaussianWeight.requires - - - - - - \ No newline at end of file diff --git a/docs/apis/simulation/generated/brainpy.simulation.connectivity.GridEight.rst b/docs/apis/simulation/generated/brainpy.simulation.connectivity.GridEight.rst deleted file mode 100644 index 3b1adcc2..00000000 --- a/docs/apis/simulation/generated/brainpy.simulation.connectivity.GridEight.rst +++ /dev/null @@ -1,31 +0,0 @@ -brainpy.simulation.connectivity.GridEight -========================================= - -.. currentmodule:: brainpy.simulation.connectivity - -.. autoclass:: GridEight - - - .. automethod:: __init__ - - - .. rubric:: Methods - - .. autosummary:: - - ~GridEight.__init__ - ~GridEight.make_conn_mat - ~GridEight.make_mat2ij - ~GridEight.make_post2pre - ~GridEight.make_post2syn - ~GridEight.make_post_slice - ~GridEight.make_pre2post - ~GridEight.make_pre2syn - ~GridEight.make_pre_slice - ~GridEight.requires - - - - - - \ No newline at end of file diff --git a/docs/apis/simulation/generated/brainpy.simulation.connectivity.GridFour.rst b/docs/apis/simulation/generated/brainpy.simulation.connectivity.GridFour.rst deleted file mode 100644 index 0227e5e2..00000000 --- a/docs/apis/simulation/generated/brainpy.simulation.connectivity.GridFour.rst +++ /dev/null @@ -1,31 +0,0 @@ -brainpy.simulation.connectivity.GridFour -======================================== - -.. currentmodule:: brainpy.simulation.connectivity - -.. autoclass:: GridFour - - - .. automethod:: __init__ - - - .. rubric:: Methods - - .. autosummary:: - - ~GridFour.__init__ - ~GridFour.make_conn_mat - ~GridFour.make_mat2ij - ~GridFour.make_post2pre - ~GridFour.make_post2syn - ~GridFour.make_post_slice - ~GridFour.make_pre2post - ~GridFour.make_pre2syn - ~GridFour.make_pre_slice - ~GridFour.requires - - - - - - \ No newline at end of file diff --git a/docs/apis/simulation/generated/brainpy.simulation.connectivity.GridN.rst b/docs/apis/simulation/generated/brainpy.simulation.connectivity.GridN.rst deleted file mode 100644 index 037769d7..00000000 --- a/docs/apis/simulation/generated/brainpy.simulation.connectivity.GridN.rst +++ /dev/null @@ -1,31 +0,0 @@ -brainpy.simulation.connectivity.GridN -===================================== - -.. currentmodule:: brainpy.simulation.connectivity - -.. autoclass:: GridN - - - .. automethod:: __init__ - - - .. rubric:: Methods - - .. autosummary:: - - ~GridN.__init__ - ~GridN.make_conn_mat - ~GridN.make_mat2ij - ~GridN.make_post2pre - ~GridN.make_post2syn - ~GridN.make_post_slice - ~GridN.make_pre2post - ~GridN.make_pre2syn - ~GridN.make_pre_slice - ~GridN.requires - - - - - - \ No newline at end of file diff --git a/docs/apis/simulation/generated/brainpy.simulation.connectivity.One2One.rst b/docs/apis/simulation/generated/brainpy.simulation.connectivity.One2One.rst deleted file mode 100644 index 925a80fd..00000000 --- a/docs/apis/simulation/generated/brainpy.simulation.connectivity.One2One.rst +++ /dev/null @@ -1,31 +0,0 @@ -brainpy.simulation.connectivity.One2One -======================================= - -.. currentmodule:: brainpy.simulation.connectivity - -.. autoclass:: One2One - - - .. automethod:: __init__ - - - .. rubric:: Methods - - .. autosummary:: - - ~One2One.__init__ - ~One2One.make_conn_mat - ~One2One.make_mat2ij - ~One2One.make_post2pre - ~One2One.make_post2syn - ~One2One.make_post_slice - ~One2One.make_pre2post - ~One2One.make_pre2syn - ~One2One.make_pre_slice - ~One2One.requires - - - - - - \ No newline at end of file diff --git a/docs/apis/simulation/generated/brainpy.simulation.connectivity.PowerLaw.rst b/docs/apis/simulation/generated/brainpy.simulation.connectivity.PowerLaw.rst deleted file mode 100644 index 99c03fc1..00000000 --- a/docs/apis/simulation/generated/brainpy.simulation.connectivity.PowerLaw.rst +++ /dev/null @@ -1,31 +0,0 @@ -brainpy.simulation.connectivity.PowerLaw -======================================== - -.. currentmodule:: brainpy.simulation.connectivity - -.. autoclass:: PowerLaw - - - .. automethod:: __init__ - - - .. rubric:: Methods - - .. autosummary:: - - ~PowerLaw.__init__ - ~PowerLaw.make_conn_mat - ~PowerLaw.make_mat2ij - ~PowerLaw.make_post2pre - ~PowerLaw.make_post2syn - ~PowerLaw.make_post_slice - ~PowerLaw.make_pre2post - ~PowerLaw.make_pre2syn - ~PowerLaw.make_pre_slice - ~PowerLaw.requires - - - - - - \ No newline at end of file diff --git a/docs/apis/simulation/generated/brainpy.simulation.connectivity.ScaleFreeBA.rst b/docs/apis/simulation/generated/brainpy.simulation.connectivity.ScaleFreeBA.rst deleted file mode 100644 index 7fd2e4f9..00000000 --- a/docs/apis/simulation/generated/brainpy.simulation.connectivity.ScaleFreeBA.rst +++ /dev/null @@ -1,31 +0,0 @@ -brainpy.simulation.connectivity.ScaleFreeBA -=========================================== - -.. currentmodule:: brainpy.simulation.connectivity - -.. autoclass:: ScaleFreeBA - - - .. automethod:: __init__ - - - .. rubric:: Methods - - .. autosummary:: - - ~ScaleFreeBA.__init__ - ~ScaleFreeBA.make_conn_mat - ~ScaleFreeBA.make_mat2ij - ~ScaleFreeBA.make_post2pre - ~ScaleFreeBA.make_post2syn - ~ScaleFreeBA.make_post_slice - ~ScaleFreeBA.make_pre2post - ~ScaleFreeBA.make_pre2syn - ~ScaleFreeBA.make_pre_slice - ~ScaleFreeBA.requires - - - - - - \ No newline at end of file diff --git a/docs/apis/simulation/generated/brainpy.simulation.connectivity.ScaleFreeBADual.rst b/docs/apis/simulation/generated/brainpy.simulation.connectivity.ScaleFreeBADual.rst deleted file mode 100644 index 07147069..00000000 --- a/docs/apis/simulation/generated/brainpy.simulation.connectivity.ScaleFreeBADual.rst +++ /dev/null @@ -1,31 +0,0 @@ -brainpy.simulation.connectivity.ScaleFreeBADual -=============================================== - -.. currentmodule:: brainpy.simulation.connectivity - -.. autoclass:: ScaleFreeBADual - - - .. automethod:: __init__ - - - .. rubric:: Methods - - .. autosummary:: - - ~ScaleFreeBADual.__init__ - ~ScaleFreeBADual.make_conn_mat - ~ScaleFreeBADual.make_mat2ij - ~ScaleFreeBADual.make_post2pre - ~ScaleFreeBADual.make_post2syn - ~ScaleFreeBADual.make_post_slice - ~ScaleFreeBADual.make_pre2post - ~ScaleFreeBADual.make_pre2syn - ~ScaleFreeBADual.make_pre_slice - ~ScaleFreeBADual.requires - - - - - - \ No newline at end of file diff --git a/docs/apis/simulation/generated/brainpy.simulation.connectivity.SmallWorld.rst b/docs/apis/simulation/generated/brainpy.simulation.connectivity.SmallWorld.rst deleted file mode 100644 index 685521f1..00000000 --- a/docs/apis/simulation/generated/brainpy.simulation.connectivity.SmallWorld.rst +++ /dev/null @@ -1,31 +0,0 @@ -brainpy.simulation.connectivity.SmallWorld -========================================== - -.. currentmodule:: brainpy.simulation.connectivity - -.. autoclass:: SmallWorld - - - .. automethod:: __init__ - - - .. rubric:: Methods - - .. autosummary:: - - ~SmallWorld.__init__ - ~SmallWorld.make_conn_mat - ~SmallWorld.make_mat2ij - ~SmallWorld.make_post2pre - ~SmallWorld.make_post2syn - ~SmallWorld.make_post_slice - ~SmallWorld.make_pre2post - ~SmallWorld.make_pre2syn - ~SmallWorld.make_pre_slice - ~SmallWorld.requires - - - - - - \ No newline at end of file diff --git a/docs/apis/simulation/generated/brainpy.simulation.connectivity.ij2mat.rst b/docs/apis/simulation/generated/brainpy.simulation.connectivity.ij2mat.rst deleted file mode 100644 index 473ebb64..00000000 --- a/docs/apis/simulation/generated/brainpy.simulation.connectivity.ij2mat.rst +++ /dev/null @@ -1,6 +0,0 @@ -brainpy.simulation.connectivity.ij2mat -====================================== - -.. currentmodule:: brainpy.simulation.connectivity - -.. autofunction:: ij2mat \ No newline at end of file diff --git a/docs/apis/simulation/generated/brainpy.simulation.connectivity.mat2ij.rst b/docs/apis/simulation/generated/brainpy.simulation.connectivity.mat2ij.rst deleted file mode 100644 index ba27878c..00000000 --- a/docs/apis/simulation/generated/brainpy.simulation.connectivity.mat2ij.rst +++ /dev/null @@ -1,6 +0,0 @@ -brainpy.simulation.connectivity.mat2ij -====================================== - -.. currentmodule:: brainpy.simulation.connectivity - -.. autofunction:: mat2ij \ No newline at end of file diff --git a/docs/apis/simulation/generated/brainpy.simulation.connectivity.post2pre.rst b/docs/apis/simulation/generated/brainpy.simulation.connectivity.post2pre.rst deleted file mode 100644 index 9aac20cb..00000000 --- a/docs/apis/simulation/generated/brainpy.simulation.connectivity.post2pre.rst +++ /dev/null @@ -1,6 +0,0 @@ -brainpy.simulation.connectivity.post2pre -======================================== - -.. currentmodule:: brainpy.simulation.connectivity - -.. autofunction:: post2pre \ No newline at end of file diff --git a/docs/apis/simulation/generated/brainpy.simulation.connectivity.post2syn.rst b/docs/apis/simulation/generated/brainpy.simulation.connectivity.post2syn.rst deleted file mode 100644 index df5a3a33..00000000 --- a/docs/apis/simulation/generated/brainpy.simulation.connectivity.post2syn.rst +++ /dev/null @@ -1,6 +0,0 @@ -brainpy.simulation.connectivity.post2syn -======================================== - -.. currentmodule:: brainpy.simulation.connectivity - -.. autofunction:: post2syn \ No newline at end of file diff --git a/docs/apis/simulation/generated/brainpy.simulation.connectivity.post_slice.rst b/docs/apis/simulation/generated/brainpy.simulation.connectivity.post_slice.rst deleted file mode 100644 index 83a1cc33..00000000 --- a/docs/apis/simulation/generated/brainpy.simulation.connectivity.post_slice.rst +++ /dev/null @@ -1,6 +0,0 @@ -brainpy.simulation.connectivity.post\_slice -=========================================== - -.. currentmodule:: brainpy.simulation.connectivity - -.. autofunction:: post_slice \ No newline at end of file diff --git a/docs/apis/simulation/generated/brainpy.simulation.connectivity.pre2post.rst b/docs/apis/simulation/generated/brainpy.simulation.connectivity.pre2post.rst deleted file mode 100644 index e4ce658b..00000000 --- a/docs/apis/simulation/generated/brainpy.simulation.connectivity.pre2post.rst +++ /dev/null @@ -1,6 +0,0 @@ -brainpy.simulation.connectivity.pre2post -======================================== - -.. currentmodule:: brainpy.simulation.connectivity - -.. autofunction:: pre2post \ No newline at end of file diff --git a/docs/apis/simulation/generated/brainpy.simulation.connectivity.pre2syn.rst b/docs/apis/simulation/generated/brainpy.simulation.connectivity.pre2syn.rst deleted file mode 100644 index 56d37784..00000000 --- a/docs/apis/simulation/generated/brainpy.simulation.connectivity.pre2syn.rst +++ /dev/null @@ -1,6 +0,0 @@ -brainpy.simulation.connectivity.pre2syn -======================================= - -.. currentmodule:: brainpy.simulation.connectivity - -.. autofunction:: pre2syn \ No newline at end of file diff --git a/docs/apis/simulation/generated/brainpy.simulation.connectivity.pre_slice.rst b/docs/apis/simulation/generated/brainpy.simulation.connectivity.pre_slice.rst deleted file mode 100644 index ef996b38..00000000 --- a/docs/apis/simulation/generated/brainpy.simulation.connectivity.pre_slice.rst +++ /dev/null @@ -1,6 +0,0 @@ -brainpy.simulation.connectivity.pre\_slice -========================================== - -.. currentmodule:: brainpy.simulation.connectivity - -.. autofunction:: pre_slice \ No newline at end of file diff --git a/docs/apis/simulation/generated/brainpy.simulation.monitor.Monitor.rst b/docs/apis/simulation/generated/brainpy.simulation.monitor.Monitor.rst deleted file mode 100644 index 48018ebf..00000000 --- a/docs/apis/simulation/generated/brainpy.simulation.monitor.Monitor.rst +++ /dev/null @@ -1,25 +0,0 @@ -brainpy.simulation.monitor.Monitor -================================== - -.. currentmodule:: brainpy.simulation.monitor - -.. autoclass:: Monitor - - - .. automethod:: __init__ - - - .. rubric:: Methods - - .. autosummary:: - - ~Monitor.__init__ - ~Monitor.build - ~Monitor.check - ~Monitor.check_mon_idx - - - - - - \ No newline at end of file diff --git a/docs/quickstart/numerical_solvers.ipynb b/docs/quickstart/numerical_solvers.ipynb index cfd63ed2..5f4b926b 100644 --- a/docs/quickstart/numerical_solvers.ipynb +++ b/docs/quickstart/numerical_solvers.ipynb @@ -13,7 +13,9 @@ "id": "destroyed-smooth", "metadata": {}, "source": [ - "Brain modeling toolkit provided in BrainPy is focused on **differential equations**. How to solve differential equations is the essence of the neurodynamics simulation. The exact algebraic solutions are only available for low-order differential equations. For the coupled high-dimensional non-linear brain dynamical systems, we need to resort to using numerical methods for solving such differential equations. In this section, I will illustrate how to define ordinary differential quations (ODEs), stochastic differential equations (SDEs), and how to define the numerical integration methods in BrainPy for these difined DEs." + "Brain modeling toolkit provided in BrainPy is focused on **differential equations**. How to solve differential equations is the essence of the neurodynamics simulation. The exact algebraic solutions are only available for low-order differential equations. For the coupled high-dimensional non-linear brain dynamical systems, we need to resort to using numerical methods for solving such differential equations. \n", + "\n", + "In this section, I will illustrate how to define ordinary differential quations (ODEs), stochastic differential equations (SDEs), and how to define the numerical integration methods in BrainPy for these difined DEs." ] }, { @@ -820,11 +822,8 @@ "source": [ "---\n", "\n", - "**Author**:\n", - "\n", - "- Chaoming Wang\n", - "- Email: adaduo@outlook.com\n", - "- Date: 2021.03.25, updated @2021.05.29 @2021.09.02\n", + "- Chaoming Wang (adaduo@outlook.com)\n", + "- Updated at 2021.05.29\n", "\n", "---" ] -- 2.34.1 From e91818976e7dab90d0e788c4f0019e85d6d861a8 Mon Sep 17 00:00:00 2001 From: chaoming Date: Wed, 8 Sep 2021 19:57:11 +0800 Subject: [PATCH 15/30] rename 'DynamicSystem' to 'DynamicalSystem' --- brainpy/__init__.py | 2 +- brainpy/analysis/utils.py | 8 +++--- brainpy/base/base.py | 5 ++-- brainpy/base/collector.py | 4 +-- brainpy/dnn/base.py | 2 +- brainpy/math/jax/compilation.py | 22 ++++++++--------- brainpy/math/numpy/ast2numba.py | 22 ++++++++--------- brainpy/math/numpy/compilation.py | 3 +-- brainpy/simulation/brainobjects/base.py | 26 +++++++++----------- brainpy/simulation/brainobjects/channel.py | 4 +-- brainpy/simulation/brainobjects/delays.py | 4 +-- brainpy/simulation/brainobjects/dendrite.py | 4 +-- brainpy/simulation/brainobjects/molecular.py | 4 +-- brainpy/simulation/brainobjects/neuron.py | 4 +-- brainpy/simulation/brainobjects/soma.py | 4 +-- brainpy/simulation/brainobjects/synapse.py | 4 +-- brainpy/simulation/utils.py | 2 +- 17 files changed, 60 insertions(+), 64 deletions(-) diff --git a/brainpy/__init__.py b/brainpy/__init__.py index 001bd39c..0f17fcc6 100644 --- a/brainpy/__init__.py +++ b/brainpy/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -__version__ = "1.1.0-beta" +__version__ = "1.1.0rc1" # "base" module diff --git a/brainpy/analysis/utils.py b/brainpy/analysis/utils.py index 631c2611..a871e830 100644 --- a/brainpy/analysis/utils.py +++ b/brainpy/analysis/utils.py @@ -10,7 +10,7 @@ import numpy as np from brainpy import errors, math, tools from brainpy.integrators import analysis_by_ast, odeint, utils -from brainpy.simulation.brainobjects.base import DynamicSystem +from brainpy.simulation.brainobjects.base import DynamicalSystem try: import numba @@ -85,14 +85,14 @@ def transform_integrals(integrals, method='euler'): # node of integral f_node = None - if hasattr(integral, '__self__') and isinstance(integral.__self__, DynamicSystem): + if hasattr(integral, '__self__') and isinstance(integral.__self__, DynamicalSystem): f_node = integral.__self__ # node of derivative function func_node = None if f_node: func_node = f_node - elif hasattr(func, '__self__') and isinstance(func.__self__, DynamicSystem): + elif hasattr(func, '__self__') and isinstance(func.__self__, DynamicalSystem): func_node = func.__self__ # code scope @@ -113,7 +113,7 @@ def transform_integrals(integrals, method='euler'): target = func_node for i in range(1, len(split_keys)): next_target = getattr(target, split_keys[i]) - if not isinstance(next_target, DynamicSystem): + if not isinstance(next_target, DynamicalSystem): break target = next_target else: diff --git a/brainpy/base/base.py b/brainpy/base/base.py index 20b7674c..18da86a8 100644 --- a/brainpy/base/base.py +++ b/brainpy/base/base.py @@ -8,8 +8,7 @@ __all__ = [ 'Base', ] -math = None -DE_INT = None +math = DE_INT = None class Base(object): @@ -18,7 +17,7 @@ class Base(object): The subclass of Base includes: - ``Module`` in brainpy.dnn.base.py - - ``DynamicSystem`` in brainpy.simulation.brainobjects.base.py + - ``DynamicalSystem`` in brainpy.simulation.brainobjects.base.py """ diff --git a/brainpy/base/collector.py b/brainpy/base/collector.py index 9ed63f44..f4382f81 100644 --- a/brainpy/base/collector.py +++ b/brainpy/base/collector.py @@ -119,8 +119,8 @@ class ArrayCollector(Collector): with some additional methods to make manipulation of collections of variables easy. A Collection is ordered by insertion order. It is the object - returned by DynamicSystem.vars() and used as input - in many DynamicSystem instance: optimizers, Jit, etc...""" + returned by DynamicalSystem.vars() and used as input + in many DynamicalSystem instance: optimizers, Jit, etc...""" def __setitem__(self, key, value): """Overload bracket assignment to catch potential conflicts during assignment.""" diff --git a/brainpy/dnn/base.py b/brainpy/dnn/base.py index 22a9a48d..ad5ba4c9 100644 --- a/brainpy/dnn/base.py +++ b/brainpy/dnn/base.py @@ -101,7 +101,7 @@ class Sequential(Module): def vars(self, method='absolute'): """Collect all the variables (and their names) contained - in the list and its children instance of DynamicSystem. + in the list and its children instance of Module. Parameters ---------- diff --git a/brainpy/math/jax/compilation.py b/brainpy/math/jax/compilation.py index 30eceaaf..1492b876 100644 --- a/brainpy/math/jax/compilation.py +++ b/brainpy/math/jax/compilation.py @@ -62,7 +62,7 @@ def jit(obj_or_func, vars_to_change=None, vars_needed=None, """JIT (Just-In-Time) Compilation for JAX backend. This function has the same ability to Just-In-Time compile a pure function, - but it can also JIT compile a :py:class:`brainpy.DynamicSystem`, or a + but it can also JIT compile a :py:class:`brainpy.DynamicalSystem`, or a :py:class:`brainpy.Base` object, or a bounded method of a :py:class:`brainpy.Base` object. @@ -72,7 +72,7 @@ def jit(obj_or_func, vars_to_change=None, vars_needed=None, Examples -------- - You can JIT a :py:class:`brainpy.DynamicSystem` + You can JIT a :py:class:`brainpy.DynamicalSystem` >>> import brainpy as bp >>> @@ -150,10 +150,10 @@ def jit(obj_or_func, vars_to_change=None, vars_needed=None, A wrapped version of Base object or function, set up for just-in-time compilation. """ - from brainpy.simulation.brainobjects.base import DynamicSystem + from brainpy.simulation.brainobjects.base import DynamicalSystem - if isinstance(obj_or_func, DynamicSystem): - if len(obj_or_func.steps): # DynamicSystem has step functions + if isinstance(obj_or_func, DynamicalSystem): + if len(obj_or_func.steps): # DynamicalSystem has step functions # dynamical variables vars_to_change = (vars_to_change or obj_or_func.vars().unique()) @@ -329,10 +329,10 @@ def vmap(obj_or_func, vars=None, vars_batched=None, with extra array axes at positions indicated by ``out_axes``. """ - from brainpy.simulation.brainobjects.base import DynamicSystem + from brainpy.simulation.brainobjects.base import DynamicalSystem - if isinstance(obj_or_func, DynamicSystem): - if len(obj_or_func.steps): # DynamicSystem has step functions + if isinstance(obj_or_func, DynamicalSystem): + if len(obj_or_func.steps): # DynamicalSystem has step functions # dynamical variables vars = (vars or obj_or_func.vars().unique()) @@ -553,10 +553,10 @@ def pmap(obj_or_func, vars=None, axis_name=None, in_axes=0, out_axes=0, static_b """ - from brainpy.simulation.brainobjects.base import DynamicSystem + from brainpy.simulation.brainobjects.base import DynamicalSystem - if isinstance(obj_or_func, DynamicSystem): - if len(obj_or_func.steps): # DynamicSystem has step functions + if isinstance(obj_or_func, DynamicalSystem): + if len(obj_or_func.steps): # DynamicalSystem has step functions # dynamical variables all_vars = (vars or obj_or_func.vars().unique()) diff --git a/brainpy/math/numpy/ast2numba.py b/brainpy/math/numpy/ast2numba.py index a63ee421..9d5698fb 100644 --- a/brainpy/math/numpy/ast2numba.py +++ b/brainpy/math/numpy/ast2numba.py @@ -21,7 +21,7 @@ from brainpy.base.collector import Collector from brainpy.base.function import Function from brainpy.math import profile -DE_INT = DynamicSystem = Container = None +DE_INT = DynamicalSystem = Container = None __all__ = [ 'jit', @@ -66,11 +66,11 @@ def jit(obj_or_fun, show_code=False, **jit_setting): def jit_DS(obj_or_fun, show_code=False, **jit_setting): - global DynamicSystem - if DynamicSystem is None: - from brainpy.simulation.brainobjects.base import DynamicSystem + global DynamicalSystem + if DynamicalSystem is None: + from brainpy.simulation.brainobjects.base import DynamicalSystem - if not isinstance(obj_or_fun, DynamicSystem): + if not isinstance(obj_or_fun, DynamicalSystem): raise errors.UnsupportedError(f'JIT compilation in numpy backend only ' f'supports {Base.__name__}, but we got ' f'{type(obj_or_fun)}.') @@ -238,7 +238,7 @@ def _jit_cls_func(f, code=None, host=None, show_code=False, **jit_setting): # code_scope.update(nodes) func_name = f'{host.name}_{f.__name__}' - # step function of normal DynamicSystem + # step function of normal DynamicalSystem else: code = (code or tools.deindent(inspect.getsource(f)).strip()) # function name @@ -267,9 +267,9 @@ def _jit_cls_func(f, code=None, host=None, show_code=False, **jit_setting): def _jit_intg_func(f, show_code=False, **jit_setting): - global DynamicSystem - if DynamicSystem is None: - from brainpy.simulation.brainobjects.base import DynamicSystem + global DynamicalSystem + if DynamicalSystem is None: + from brainpy.simulation.brainobjects.base import DynamicalSystem # exponential euler methods if f.brainpy_data['method'].startswith('exponential'): @@ -290,7 +290,7 @@ def _jit_intg_func(f, show_code=False, **jit_setting): # jit raw functions f_node = None remove_self = None - if hasattr(f, '__self__') and isinstance(f.__self__, DynamicSystem): + if hasattr(f, '__self__') and isinstance(f.__self__, DynamicalSystem): f_node = f.__self__ _arg = tree.body[0].args.args.pop(0) # remove "self" arg # remove "self" in functional call @@ -302,7 +302,7 @@ def _jit_intg_func(f, show_code=False, **jit_setting): func_node = None if f_node: func_node = f_node - elif hasattr(func, '__self__') and isinstance(func.__self__, DynamicSystem): + elif hasattr(func, '__self__') and isinstance(func.__self__, DynamicalSystem): func_node = func.__self__ # get new compiled function diff --git a/brainpy/math/numpy/compilation.py b/brainpy/math/numpy/compilation.py index 54bf0568..8260ec29 100644 --- a/brainpy/math/numpy/compilation.py +++ b/brainpy/math/numpy/compilation.py @@ -11,7 +11,6 @@ except ModuleNotFoundError: ast2numba = None numba = None -DE_INT = DynamicSystem = None __all__ = [ 'jit', @@ -29,7 +28,7 @@ def jit(obj_or_fun, nopython=True, fastmath=True, parallel=False, nogil=False, JIT compilation in NumPy backend relies on `Numba `_. However, in BrainPy, `bp.math.numpy.jit()` can apply to class objects, especially the instance - of :py:class:`brainpy.DynamicSystem`. + of :py:class:`brainpy.DynamicalSystem`. If you are using JAX backend, please refer to the JIT compilation in JAX backend `bp.math.jax.jit() `_. diff --git a/brainpy/simulation/brainobjects/base.py b/brainpy/simulation/brainobjects/base.py index e2e74f74..a035e731 100644 --- a/brainpy/simulation/brainobjects/base.py +++ b/brainpy/simulation/brainobjects/base.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- -import numpy as np - from brainpy import math, errors from brainpy.base import collector from brainpy.base.base import Base @@ -9,7 +7,7 @@ from brainpy.simulation import utils from brainpy.simulation.monitor import Monitor __all__ = [ - 'DynamicSystem', + 'DynamicalSystem', 'Container', ] @@ -19,7 +17,7 @@ _error_msg = 'Unknown model type: {type}. ' \ 'tuple of function names.' -class DynamicSystem(Base): +class DynamicalSystem(Base): """Base Dynamic System class. Any object has step functions will be a dynamical system. @@ -38,7 +36,7 @@ class DynamicSystem(Base): target_backend = None def __init__(self, steps=(), monitors=None, name=None): - super(DynamicSystem, self).__init__(name=name) + super(DynamicalSystem, self).__init__(name=name) # step functions self.steps = collector.Collector() @@ -104,7 +102,7 @@ class DynamicSystem(Base): Parameters ---------- inputs : list, tuple - The inputs for this instance of DynamicSystem. It should the format + The inputs for this instance of DynamicalSystem. It should the format of `[(target, value, [type, operation])]`, where `target` is the input target, `value` is the input value, `type` is the input type (such as "fix" or "iter"), `operation` is the operation for inputs @@ -174,10 +172,10 @@ class DynamicSystem(Base): return running_time -class Container(DynamicSystem): +class Container(DynamicalSystem): """Container object which is designed to add other instances of DynamicalSystem. - What's different from the other objects of DynamicSystem is that Container has + What's different from the other objects of DynamicalSystem is that Container has one more useful function :py:func:`add`. It can be used to add the children objects. @@ -192,23 +190,23 @@ class Container(DynamicSystem): show_code : bool Whether show the formatted code. ds_dict : dict of (str, ) - The instance of DynamicSystem with the format of "key=dynamic_system". + The instance of DynamicalSystem with the format of "key=dynamic_system". """ def __init__(self, *ds_tuple, steps=None, monitors=None, name=None, **ds_dict): # children dynamical systems self.child_ds = dict() for ds in ds_tuple: - if not isinstance(ds, DynamicSystem): + if not isinstance(ds, DynamicalSystem): raise errors.BrainPyError(f'{self.__class__.__name__} receives instances of ' - f'DynamicSystem, however, we got {type(ds)}.') + f'DynamicalSystem, however, we got {type(ds)}.') if ds.name in self.child_ds: raise ValueError(f'{ds.name} has been paired with {ds}. Please change a unique name.') self.child_ds[ds.name] = ds for key, ds in ds_dict.items(): - if not isinstance(ds, DynamicSystem): + if not isinstance(ds, DynamicalSystem): raise errors.BrainPyError(f'{self.__class__.__name__} receives instances of ' - f'DynamicSystem, however, we got {type(ds)}.') + f'DynamicalSystem, however, we got {type(ds)}.') if key in self.child_ds: raise ValueError(f'{key} has been paired with {ds}. Please change a unique name.') self.child_ds[key] = ds @@ -234,7 +232,7 @@ class Container(DynamicSystem): def vars(self, method='absolute'): """Collect all the variables (and their names) contained - in the list and its children instance of DynamicSystem. + in the list and its children instance of DynamicalSystem. Parameters ---------- diff --git a/brainpy/simulation/brainobjects/channel.py b/brainpy/simulation/brainobjects/channel.py index 85fb2881..e9a5fa82 100644 --- a/brainpy/simulation/brainobjects/channel.py +++ b/brainpy/simulation/brainobjects/channel.py @@ -1,13 +1,13 @@ # -*- coding: utf-8 -*- -from brainpy.simulation.brainobjects.base import DynamicSystem +from brainpy.simulation.brainobjects.base import DynamicalSystem __all__ = [ 'Channel' ] -class Channel(DynamicSystem): +class Channel(DynamicalSystem): """Ion Channel object. Parameters diff --git a/brainpy/simulation/brainobjects/delays.py b/brainpy/simulation/brainobjects/delays.py index afa2abfa..991ab489 100644 --- a/brainpy/simulation/brainobjects/delays.py +++ b/brainpy/simulation/brainobjects/delays.py @@ -4,7 +4,7 @@ import math as pmath from brainpy import errors from brainpy import math as bmath -from brainpy.simulation.brainobjects.base import DynamicSystem +from brainpy.simulation.brainobjects.base import DynamicalSystem from brainpy.simulation.utils import size2len __all__ = [ @@ -13,7 +13,7 @@ __all__ = [ ] -class Delay(DynamicSystem): +class Delay(DynamicalSystem): def __init__(self, steps=('update',), name=None): super(Delay, self).__init__(steps=steps, monitors=None, name=name) diff --git a/brainpy/simulation/brainobjects/dendrite.py b/brainpy/simulation/brainobjects/dendrite.py index 1b0835b4..f2a0699c 100644 --- a/brainpy/simulation/brainobjects/dendrite.py +++ b/brainpy/simulation/brainobjects/dendrite.py @@ -1,13 +1,13 @@ # -*- coding: utf-8 -*- -from brainpy.simulation.brainobjects.base import DynamicSystem +from brainpy.simulation.brainobjects.base import DynamicalSystem __all__ = [ 'Dendrite' ] -class Dendrite(DynamicSystem): +class Dendrite(DynamicalSystem): """Dendrite object. """ diff --git a/brainpy/simulation/brainobjects/molecular.py b/brainpy/simulation/brainobjects/molecular.py index 6768b14b..4e647890 100644 --- a/brainpy/simulation/brainobjects/molecular.py +++ b/brainpy/simulation/brainobjects/molecular.py @@ -1,13 +1,13 @@ # -*- coding: utf-8 -*- -from brainpy.simulation.brainobjects.base import DynamicSystem +from brainpy.simulation.brainobjects.base import DynamicalSystem __all__ = [ 'Molecular' ] -class Molecular(DynamicSystem): +class Molecular(DynamicalSystem): """Molecular object for neuron modeling. """ diff --git a/brainpy/simulation/brainobjects/neuron.py b/brainpy/simulation/brainobjects/neuron.py index f86b7edf..6b273d63 100644 --- a/brainpy/simulation/brainobjects/neuron.py +++ b/brainpy/simulation/brainobjects/neuron.py @@ -2,7 +2,7 @@ from brainpy import errors from brainpy.simulation import utils -from brainpy.simulation.brainobjects.base import DynamicSystem, Container +from brainpy.simulation.brainobjects.base import DynamicalSystem, Container from brainpy.simulation.brainobjects.channel import Channel __all__ = [ @@ -11,7 +11,7 @@ __all__ = [ ] -class NeuGroup(DynamicSystem): +class NeuGroup(DynamicalSystem): """Neuron Group. Parameters diff --git a/brainpy/simulation/brainobjects/soma.py b/brainpy/simulation/brainobjects/soma.py index 5b1086ca..5d5faeb4 100644 --- a/brainpy/simulation/brainobjects/soma.py +++ b/brainpy/simulation/brainobjects/soma.py @@ -1,13 +1,13 @@ # -*- coding: utf-8 -*- -from brainpy.simulation.brainobjects.base import DynamicSystem +from brainpy.simulation.brainobjects.base import DynamicalSystem __all__ = [ 'Soma' ] -class Soma(DynamicSystem): +class Soma(DynamicalSystem): """Soma object for neuron modeling. """ diff --git a/brainpy/simulation/brainobjects/synapse.py b/brainpy/simulation/brainobjects/synapse.py index 27facd31..76b5064f 100644 --- a/brainpy/simulation/brainobjects/synapse.py +++ b/brainpy/simulation/brainobjects/synapse.py @@ -2,7 +2,7 @@ from brainpy import errors, math from brainpy.simulation.connectivity import TwoEndConnector, MatConn, IJConn -from brainpy.simulation.brainobjects.base import DynamicSystem +from brainpy.simulation.brainobjects.base import DynamicalSystem from brainpy.simulation.brainobjects.delays import ConstantDelay from brainpy.simulation.brainobjects.neuron import NeuGroup @@ -11,7 +11,7 @@ __all__ = [ ] -class TwoEndConn(DynamicSystem): +class TwoEndConn(DynamicalSystem): """Two End Synaptic Connections. Parameters diff --git a/brainpy/simulation/utils.py b/brainpy/simulation/utils.py index 8d3b295c..85a07ddd 100644 --- a/brainpy/simulation/utils.py +++ b/brainpy/simulation/utils.py @@ -115,7 +115,7 @@ def check_and_format_inputs(host, inputs): Parameters ---------- - host : DynamicSystem + host : DynamicalSystem The host which contains all data. inputs : tuple, list The inputs of the population. -- 2.34.1 From d85d316288af38550ea6bcd893f9838103be024c Mon Sep 17 00:00:00 2001 From: chaoming Date: Thu, 9 Sep 2021 14:39:38 +0800 Subject: [PATCH 16/30] fix bugs on Monitor checking --- brainpy/simulation/brainobjects/base.py | 19 ++++++++++---- brainpy/simulation/monitor.py | 33 +------------------------ brainpy/simulation/utils.py | 4 +++ 3 files changed, 19 insertions(+), 37 deletions(-) diff --git a/brainpy/simulation/brainobjects/base.py b/brainpy/simulation/brainobjects/base.py index a035e731..660bc444 100644 --- a/brainpy/simulation/brainobjects/base.py +++ b/brainpy/simulation/brainobjects/base.py @@ -18,7 +18,7 @@ _error_msg = 'Unknown model type: {type}. ' \ class DynamicalSystem(Base): - """Base Dynamic System class. + """Base Dynamical System class. Any object has step functions will be a dynamical system. That is to say, in BrainPy, the essence of the dynamical system @@ -35,12 +35,13 @@ class DynamicalSystem(Base): """ target_backend = None - def __init__(self, steps=(), monitors=None, name=None): + def __init__(self, steps=None, monitors=None, name=None): super(DynamicalSystem, self).__init__(name=name) # step functions + if steps is None: + steps = ('update', ) self.steps = collector.Collector() - steps = tuple() if steps is None else steps if isinstance(steps, tuple): for step in steps: if isinstance(step, str): @@ -83,12 +84,20 @@ class DynamicalSystem(Base): raise errors.BrainPyError(f'Unknown setting of "target_backend": {self.target_backend}') # runner and run function - self.driver = None self._input_step = lambda _t, _dt: None self._monitor_step = lambda _t, _dt: None def update(self, _t, _dt): - raise NotImplementedError + """The function to specify the updating rule. + + Parameters + ---------- + _t : float + The current time. + _dt : float + The time step. + """ + raise NotImplementedError('Must implement "update" function by user self.') def _step_run(self, _t, _dt): self._monitor_step(_t, _dt) diff --git a/brainpy/simulation/monitor.py b/brainpy/simulation/monitor.py index 23368b7f..38367b59 100644 --- a/brainpy/simulation/monitor.py +++ b/brainpy/simulation/monitor.py @@ -97,25 +97,6 @@ class Monitor(object): self.num_item = len(variables) super(Monitor, self).__init__() - def check(self, mon_key): - """Check whether the key has defined in the target. - - Parameters - ---------- - mon_key : str - The string to specify the target item. - """ - if mon_key in self._KEYWORDS: - raise ValueError(f'"{mon_key}" is a keyword in Monitor class. ' - f'Please change to another name.') - data = self.target - for s in mon_key.split('.'): - try: - data = getattr(data, s) - except AttributeError: - raise errors.BrainPyError(f"Item \"{mon_key}\" isn't defined in model " - f"{self.target}, so it can not be monitored.") - def build(self): if not self.has_build: item_names = [] @@ -140,7 +121,7 @@ class Monitor(object): else: raise errors.BrainPyError(f'Unknown monitor item: {str(mon_var)}') - self.check(mon_key) + # self.check(mon_key) item_names.append(mon_key) item_indices.append(mon_idx) item_contents[mon_key] = [] @@ -171,18 +152,6 @@ class Monitor(object): self.num_item = len(item_contents) self.has_build = True - @staticmethod - def check_mon_idx(mon_idx): - if isinstance(mon_idx, int): - mon_idx = math.array([mon_idx]) - else: - mon_idx = math.array(mon_idx) - if len(math.shape(mon_idx)) != 1: - raise errors.BrainPyError(f'Monitor item index only supports ' - f'an int or a one-dimensional vector, ' - f'not {str(mon_idx)}') - return mon_idx - def __getitem__(self, item: str): """Get item in the monitor values. diff --git a/brainpy/simulation/utils.py b/brainpy/simulation/utils.py index 85a07ddd..e86f57da 100644 --- a/brainpy/simulation/utils.py +++ b/brainpy/simulation/utils.py @@ -12,6 +12,9 @@ __all__ = [ 'check_duration', 'run_model', 'check_and_format_inputs', + 'build_input_func', + 'check_and_format_monitors', + 'build_monitor_func', ] SUPPORTED_INPUT_OPS = ['-', '+', '*', '/', '='] @@ -158,6 +161,7 @@ def check_and_format_inputs(host, inputs): # absolute access nodes = host.nodes(method='absolute') + nodes[host.name] = host for one_input in inputs: key = one_input[0] if not isinstance(key, str): -- 2.34.1 From 16d20f68388f4a16a33a77b554b17e5258a03412 Mon Sep 17 00:00:00 2001 From: chaoming Date: Thu, 9 Sep 2021 16:58:03 +0800 Subject: [PATCH 17/30] fix bugs on re expression which include/exclude dot --- brainpy/math/numpy/ast2numba.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/brainpy/math/numpy/ast2numba.py b/brainpy/math/numpy/ast2numba.py index 9d5698fb..6cc9ac6d 100644 --- a/brainpy/math/numpy/ast2numba.py +++ b/brainpy/math/numpy/ast2numba.py @@ -472,17 +472,23 @@ def _analyze_cls_func(host, code, show_code, code_scope, self_name=None, pop_sel raise errors.BrainPyError data = getattr(target, split_keys[i]) - key = '.'.join(split_keys[:i + 1]) - # analyze data if isinstance(data, math.Variable): arguments.add(f'{target.name}_{split_keys[i]}') arg2call[f'{target.name}_{split_keys[i]}'] = f'{target.name}.{split_keys[-1]}.value' nodes[target.name] = target - data_to_replace[key] = f'{target.name}_{split_keys[i]}' # replace the data + # replace the data + if len(split_keys) == i + 1: + data_to_replace[key] = f'{target.name}_{split_keys[i]}' + else: + data_to_replace[key] = f'{target.name}_{split_keys[i]}.{".".join(split_keys[i:])}' elif isinstance(data, np.random.RandomState): - data_to_replace[key] = f'{target.name}_{split_keys[i]}' # replace the data code_scope[f'{target.name}_{split_keys[i]}'] = np.random # replace RandomState + # replace the data + if len(split_keys) == i + 1: + data_to_replace[key] = f'{target.name}_{split_keys[i]}' + else: + data_to_replace[key] = f'{target.name}_{split_keys[i]}.{".".join(split_keys[i:])}' elif callable(data): assert len(split_keys) == i + 1 r = _jit_func(obj_or_fun=data, show_code=show_code, **jit_setting) @@ -495,14 +501,19 @@ def _analyze_cls_func(host, code, show_code, code_scope, self_name=None, pop_sel data_to_replace[key] = f'{target.name}_{split_keys[i]}' # replace the data else: code_scope[f'{target.name}_{split_keys[i]}'] = data - data_to_replace[key] = f'{target.name}_{split_keys[i]}' # replace the data + # replace the data + if len(split_keys) == i + 1: + data_to_replace[key] = f'{target.name}_{split_keys[i]}' + else: + data_to_replace[key] = f'{target.name}_{split_keys[i]}.{".".join(split_keys[i:])}' # final code tree.body[0].decorator_list.clear() tree.body[0].args.args.extend([ast.Name(id=a) for a in sorted(arguments)]) tree.body[0].args.defaults.extend([ast.Constant(None) for _ in sorted(arguments)]) code = tools.ast2code(tree) - code = tools.word_replace(code, data_to_replace, exclude_dot=False) + # code = tools.word_replace(code, data_to_replace, exclude_dot=False) + code = tools.word_replace(code, data_to_replace, exclude_dot=True) return code, arguments, arg2call, nodes, code_scope -- 2.34.1 From 2bd5e2ff0ed3be6306a74cde9f3e7774021160c3 Mon Sep 17 00:00:00 2001 From: chaoming Date: Thu, 9 Sep 2021 16:58:56 +0800 Subject: [PATCH 18/30] update docs --- docs/apis/simulation/brainobjects.rst | 12 +- docs/index.rst | 2 +- docs/quickstart/dynamics_simulation.ipynb | 1211 ++++++++++++----- docs/quickstart/jit_compilation.ipynb | 85 +- docs/quickstart/numerical_solvers.ipynb | 124 +- .../tutorial_analysis/dynamics_analysis.ipynb | 9 +- .../tutorial_intg/ode_numerical_solvers.ipynb | 9 +- .../tutorial_intg/sde_numerical_solvers.ipynb | 9 +- .../efficient_synaptic_computation.ipynb | 9 +- ...monitor.ipynb => monitor_and_inputs.ipynb} | 9 +- docs/tutorial_simulation/repeat_mode.ipynb | 28 +- .../synaptic_connectivity.ipynb | 29 +- 12 files changed, 1081 insertions(+), 455 deletions(-) rename docs/tutorial_simulation/{monitor.ipynb => monitor_and_inputs.ipynb} (99%) diff --git a/docs/apis/simulation/brainobjects.rst b/docs/apis/simulation/brainobjects.rst index 9e86abc1..a5b36769 100644 --- a/docs/apis/simulation/brainobjects.rst +++ b/docs/apis/simulation/brainobjects.rst @@ -8,9 +8,10 @@ Brain Objects .. autosummary:: :toctree: generated/ - DynamicSystem + DynamicalSystem Container NeuGroup + CondNeuGroup TwoEndConn Network Channel @@ -19,10 +20,9 @@ Brain Objects Dendrite Soma Molecular - CondNeuGroup -.. autoclass:: DynamicSystem +.. autoclass:: DynamicalSystem :members: .. autoclass:: Container @@ -31,6 +31,9 @@ Brain Objects .. autoclass:: NeuGroup :members: +.. autoclass:: CondNeuGroup + :members: + .. autoclass:: TwoEndConn :members: @@ -55,7 +58,4 @@ Brain Objects .. autoclass:: Molecular :members: -.. autoclass:: CondNeuGroup - :members: - diff --git a/docs/index.rst b/docs/index.rst index c621b582..3197b2e0 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -53,7 +53,7 @@ high-performance brain modeling. Among its key ingredients, BrainPy supports: tutorial_simulation/efficient_synaptic_computation tutorial_simulation/synaptic_connectivity - tutorial_simulation/monitor + tutorial_simulation/monitor_and_inputs tutorial_simulation/inputs diff --git a/docs/quickstart/dynamics_simulation.ipynb b/docs/quickstart/dynamics_simulation.ipynb index 59d80923..965e1bf8 100644 --- a/docs/quickstart/dynamics_simulation.ipynb +++ b/docs/quickstart/dynamics_simulation.ipynb @@ -11,58 +11,712 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "**Contents**\n", + "> What I cannot create, I do not understand. --- Richard Feynman" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Brain is a complex dynamical system. In order to simulate it, we provide [brainpy.DynamicalSystem](../apis/simulation/generated/brainpy.simulation.brainobjects.DynamicalSystem.rst). ``brainpy.DynamicalSystem`` can be used to define any brain objects which have dynamics. Various children classes are implemented to model these brain elements, such like: [brainpy.Channel](../apis/simulation/generated/brainpy.simulation.brainobjects.Channel.rst) for neuron channels, [brainpy.NeuGroup](../apis/simulation/generated/brainpy.simulation.brainobjects.NeuGroup.rst) for neuron groups, [brainpy.TwoEndConn](../apis/simulation/generated/brainpy.simulation.brainobjects.TwoEndConn.rst) for synaptic connections, [brainpy.Network](../apis/simulation/generated/brainpy.simulation.brainobjects.Network.rst) for networks, etc. Arbitrary composition of these objects is also an instance of ``brainpy.DynamicalSystem``. Therefore, ``brainpy.DynamicalSystem`` is the universal language to define dynamical models in BrainPy. " + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "ExecuteTime": { + "end_time": "2021-03-25T03:02:48.939126Z", + "start_time": "2021-03-25T03:02:47.073698Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'1.1.0rc1'" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import brainpy as bp\n", + "\n", + "bp.__version__" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## ``brainpy.DynamicalSystem``" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this section, let's try to understand the mechanism and the function of ``brainpy.DynamicalSystem``. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### What is DynamicalSystem?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First, *what can be defined as DynamicalSystem?*" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Intuitively, a `dynamical system` is a system which has the time-dependent state. \n", + "\n", + "Mathematically, it can be expressed as\n", + "\n", + "$$\n", + "\\dot{X} = f(X, t)\n", + "$$\n", + "\n", + "where $X$ is the state of the system, $t$ is the time, and $f$ is a function describes the time dependence of the system state. \n", + "\n", + "Alternatively, the evolution of the system along the time can be given by\n", + "\n", + "$$\n", + "X(t+dt) = F\\left(X(t), t, dt\\right)\n", + "$$\n", + "\n", + "where $dt$ is the time step, and $F$ is the evolution rule to update the system's state." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Accordingly, in BrainPy, any subclass of ``brainpy.DynamicalSystem`` must implement this updating rule in the *update* function (``def update(self, _t, _dt)``). One dynamical system may have multiple updating rules, therefore, users can define multiple *update* functions. All updating functions are wrapped into an inner data structure **self.steps** (a Python dictionary specifies the *name* and the *function* of updating rules). Let's take a look." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "class FitzHughNagumoModel(bp.DynamicalSystem):\n", + " def __init__(self, a=0.8, b=0.7, tau=12.5, **kwargs):\n", + " super(FitzHughNagumoModel, self).__init__(**kwargs)\n", + " \n", + " # parameters\n", + " self.a = a\n", + " self.b = b\n", + " self.tau = tau\n", + " \n", + " # variables\n", + " self.v = bp.math.Variable(0.)\n", + " self.w = bp.math.Variable(0.)\n", + " self.I = bp.math.Variable(0.)\n", + " \n", + " def update(self, _t, _dt):\n", + " # _t : the current time, the system keyword \n", + " # _dt : the time step, the system keyword \n", + " \n", + " self.w += (self.v + self.a - self.b * self.w) / self.tau * _dt\n", + " self.v += (self.v - self.v ** 3 / 3 - self.w + self.I) * _dt\n", + " self.I[...] = 0." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here, we have defined a dynamical system called [FitzHugh–Nagumo neuron model](https://en.wikipedia.org/wiki/FitzHugh%E2%80%93Nagumo_model), whose dynamics is given by: \n", + "\n", + "$$\n", + "{\\dot {v}}=v-{\\frac {v^{3}}{3}}-w+I, \\\\\n", + "\\tau {\\dot {w}}=v+a-bw.\n", + "$$\n", + "\n", + "By using the [Euler method](../apis/integrators/generated/brainpy.integrators.ode.explicit_rk.euler.rst), this system can be updated by the following rule:\n", + "\n", + "$$\n", + "\\begin{aligned}\n", + "v(t+dt) &= v(t) + [v(t)-{v(t)^{3}/3}-w(t)+RI] * dt, \\\\\n", + "w(t + dt) &= w(t) + [v(t) + a - b w(t)] * dt.\n", + "\\end{aligned}\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can inspect all update functions in the model by ``xxx.steps``. " + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'update': >}" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fnh = FitzHughNagumoModel()\n", + "\n", + "fnh.steps # all update functions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Why to use DynamicalSystem?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "So, *why should I define my dynamical system as brainpy.DynamicalSystem?*" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There are several benefits. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "1. ``brainpy.DynamicalSystem`` has a systematic naming system. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First, every instance of ``DynamicalSystem`` has its unique name." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'FitzHughNagumoModel0'" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fnh.name # name for \"fnh\" instance" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "FitzHughNagumoModel1\n", + "FitzHughNagumoModel2\n", + "FitzHughNagumoModel3\n", + "FitzHughNagumoModel4\n", + "FitzHughNagumoModel5\n" + ] + } + ], + "source": [ + "# every instance has its unique name\n", + "\n", + "for _ in range(5):\n", + " print(FitzHughNagumoModel().name)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'X'" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# the model name can be specified by yourself\n", + "\n", + "fnh2 = FitzHughNagumoModel(name='X')\n", + "\n", + "fnh2.name" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "In BrainPy, each object should have a unique name. However, we detect that <__main__.FitzHughNagumoModel object at 0x7fd757a87df0> has a used name \"X\".\n" + ] + } + ], + "source": [ + "# same name will cause error\n", + "\n", + "try:\n", + " FitzHughNagumoModel(name='X')\n", + "except bp.errors.UniqueNameError as e:\n", + " print(e)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Second, variables, children nodes, etc. inside an instance can be easily accessed by the *absolute* or *relative* path. " + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'X.v': Variable(0.), 'X.w': Variable(0.), 'X.I': Variable(0.)}" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# All variables can be acessed by \n", + "# 1). the absolute path\n", + "\n", + "fnh2.vars()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'v': Variable(0.), 'w': Variable(0.), 'I': Variable(0.)}" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# 2). or, the relative path\n", + "\n", + "fnh2.vars(method='relative')" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "# If we wrap many instances into a container: brainpy.Network,\n", + "# variables and nodes can also be accessed by absolute or relative path\n", + "\n", + "fnh_net = bp.Network(f1=fnh, f2=fnh2)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'FitzHughNagumoModel0.v': Variable(0.),\n", + " 'FitzHughNagumoModel0.w': Variable(0.),\n", + " 'FitzHughNagumoModel0.I': Variable(0.),\n", + " 'X.v': Variable(0.),\n", + " 'X.w': Variable(0.),\n", + " 'X.I': Variable(0.)}" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# absolute access of variables\n", + "\n", + "fnh_net.vars()" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'f1.v': Variable(0.),\n", + " 'f1.w': Variable(0.),\n", + " 'f1.I': Variable(0.),\n", + " 'f2.v': Variable(0.),\n", + " 'f2.w': Variable(0.),\n", + " 'f2.I': Variable(0.)}" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# relative access of variables\n", + "\n", + "fnh_net.vars(method='relative')" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'FitzHughNagumoModel0': <__main__.FitzHughNagumoModel at 0x7fd7b8766610>,\n", + " 'X': <__main__.FitzHughNagumoModel at 0x7fd757a87070>}" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# absolute access of nodes\n", + "\n", + "fnh_net.nodes()" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'f1': <__main__.FitzHughNagumoModel at 0x7fd7b8766610>,\n", + " 'f2': <__main__.FitzHughNagumoModel at 0x7fd757a87070>}" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# relative access of nodes\n", + "\n", + "fnh_net.nodes(method='relative')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "2. Automatic monitors. Any instance of ``brainpy.DynamicalSystem`` can ``.run()``. During running, a [brainpy.Monitor](../apis/simulation/generated/brainpy.simulation.monitor.Monitor.rst) inside the dynamical system (``xxx.mon``) can be used to automatically monitor the history values of the interested variables during model running. Details please see the tutorial of [Monitor and Inputs](../tutorial_simulation/monitor_and_inputs.ipynb)." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "# in \"fnh3\" instance, we try to monitor \"v\", \"w\", and \"I\" variables\n", + "fnh3 = FitzHughNagumoModel(monitors=['v', 'w', 'I'])\n", + "\n", + "# in \"fnh4\" instance, we only monitor \"v\" variable\n", + "fnh4 = FitzHughNagumoModel(monitors=['v'], name='Y')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "3. Convenient input operations. During model running, users can specify the inputs for each model component, with the format of ``(target, value, [type, operation])``. Details please see the tutorial of [Monitor and Inputs](../tutorial_simulation/monitor_and_inputs.ipynb).\n", + " - The ``target`` is the variable accessed by the *absolute* or *relative* path. *Absolute* path access will be very useful in a huge network model.\n", + " - The default input ``type`` is \"fix\", means the ``value`` must be a constant scalar or array over time. \"iter\" type of input is also allowed, which means the ``value`` can be an iterable objects (arrays, or iterable functions, etc.). \n", + " - The default ``operation`` is ``+``, which means the input ``value`` will be added to the ``target``. Allowed operations include ``+``, ``-``, ``*``, ``/``, and ``=``. " + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "bp.math.set_dt(dt=0.01)\n", + "\n", + "fnh3.run(duration=100, \n", + " # relative path to access variable 'I'\n", + " inputs=('I', 1.5))\n", + "\n", + "plt.plot(fnh3.mon.ts, fnh3.mon.v, label='v')\n", + "plt.plot(fnh3.mon.ts, fnh3.mon.w, label='w')\n", + "plt.legend()" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXwAAAD4CAYAAADvsV2wAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8QVMy6AAAACXBIWXMAAAsTAAALEwEAmpwYAAA520lEQVR4nO2deXxU5fX/32cyySSZyb5ACPsiyA5GBHHBteBSbUWFalW0omirfr+tS7/9tXb7ttrFKrXKlyqi1ELdpYr7hgsCYd9lhxAg+zpJZpI8vz9mBiNNIMks986d5/165UXm3pt5zuXOfO655znPOaKUQqPRaDTWx2a0ARqNRqOJDFrwNRqNJkbQgq/RaDQxghZ8jUajiRG04Gs0Gk2MYDfagBORnZ2t+vfvb7QZGo1GEzWsWbOmTCmV094+Uwt+//79KSwsNNoMjUajiRpEZH9H+3RIR6PRaGIELfgajUYTI2jB12g0mhjB1DF8jUajMQqv10tRURGNjY1Gm9IuiYmJ9O7dm/j4+E7/jRZ8jUajaYeioiJSUlLo378/ImK0Od9AKUV5eTlFRUUMGDCg03+nQzoajUbTDo2NjWRlZZlO7AFEhKysrC4/fQQt+CLSR0Q+EpFtIrJFRO5u5xgRkbkisktENorI+GDH1Wg0mnBjRrEP0B3bQhHSaQZ+rJRaKyIpwBoReU8ptbXNMdOAIf6fM4An/f9qNJoYo6bRy8trikhJjOfSUXkkJcQZbVLMELTgK6UOA4f9v9eKyDYgH2gr+FcAzylf8f0vRSRdRPL8fxsxln9VyjOf72VUfhpzpgzWHzSNJsIopbj5mdUU7q8E4NH3v+KP08cwaVCWwZbFBiGN4YtIf2AcsPK4XfnAwTavi/zb2nuP2SJSKCKFpaWlIbPtSHUjty1aw4aiauZ+uIsr//Y5R6rNOfuu8fHe1qPM+cca5n2ymwZPi9HmaELA57vKKdxfyW+uGMHzPzgDu0247qkvmffJbnQzpvATMsEXERfwMnCPUqrm+N3t/Em7V1cpNV8pVaCUKsjJabccRLd4ff0hGrwtvDLnTJ67eQKHqhq46skvOFjhDtkYmtCx/UgNty0qZMWech56azuXP/4Ze0rrjDZLEyQfbi/BYbdxdUEfJg/O5s27zmbaqDweems7P/znOuqbmo020TTcf//9PPHEE8de//KXv+TPf/5zUO8ZkrRMEYnHJ/bPK6VeaeeQIqBPm9e9geJQjN1ZPt5RyvC8VPpnO+mf7WTJ7Ilc99RKZv79S164bRK90pMiaY7mJCxasZ/E+Dg+/skUNh2q5u4l67nyb5+z4KbTKeifabR5mm6ycm85p/XLIDHeF051Ouw8PnMcY3qn8dBb29lZUsu8609jYI7LYEu/ya/+vYWtxcf7scExvFcqD14+osP9M2bM4J577uGOO+4A4IUXXuDtt98OasxQZOkI8DSwTSn1SAeHLQVu8GfrTASqIxm/V0qx9XANY/umH9s2Mj+NRbdMoNrt5Xt//5KSGh3eMQtKKT7cXsI5Q3JIT07g7CE5vH7nZLJdDq57aiUfbDtqtImabtDaqthZUsfI/LRvbBcRZp8ziEW3nEFpbRNXPP45723V13jcuHGUlJRQXFzMhg0byMjIoG/fvkG9Zyg8/MnA94FNIrLev+1/gL4ASql5wDLgEmAX4AZmhWDcTnOkppHqBi+n9kz5xvbRvdNZePMEvv/0Sr731EqWzJ5ItssRSdM07VBa28Th6kZmn/O1J98nM5kXb5/ErIWrmb1oDQ99dxRXF/Q5wbtozEZxdQOe5lYGZDvb3T95cDb//tFZzPnHWm59rpC7LhjCPRcMwWYzPjXyRJ54OJk+fTovvfQSR44cYcaMGUG/XyiydD6j/Rh922MUcGewY3WXPaX1AAzOTfmPfaf1y2DBTadz0zOruP6plfxr9iTSkju/VFkTenYcrQVg6HE36CyXg3/eOpE5/1jDvS9tpKLew23nDjLCRE032Fvm+x72z2pf8AF6Z/hu7D9/bTNzP9jJpqIqHr12XMx+J2fMmMGtt95KWVkZn3zySdDvFxMrbQ/7s3F6pSe2u3/iwCzmf7+A3aV1zFq4Sk8cGcxXR32Ts6f0+M8btMth5+kbT+ey0Xn8/q3t/G7ZNp3dESUc8CdI9MtKPuFxifFx/GH6aH575Ug+21XG5Y9/xrbDoY2fRwsjRoygtraW/Px88vLygn6/mBD8I9UNAPRIbV/wAc45JYe5M8ax/mAVty1aQ6NXpwEaxcEKNykOe4fhtQS7jcdmjOOGSf2Yv3wPP3lxI80trRG2UtNVymo9AJ0Km4oI10/sx5LZk2hqbuE7T3zO6+sPhdtEU7Jp0yY++uijkLxXbAh+TSPpyfHHMgM6YtqoPP4wfQyf7SrjrsXrtIgYxNGaRnqkdXxzBoizCb/69gj+68JTeHltEbf/Q9+kzU5ZXRNpSfEk2DsvO6f1y+DfPzqL0fnp3L1kPb9cugVPs/5edpfYEPzqJnqewLtvy/TTevPLy4fz7taj3PfSRlpbdbgg0hytaaRHaue8wLsvHMJvrhzJB9tL+P7TK6lu8EbAQk13KKtrItuV0OW/y01J5Plbz2DW5P4s/GIfV//fCr1+ppvEhOCX1DaS20nBB7hp8gB+fNEpvLLuEA8u3aJjxBHmaE0TPVI6f72+P7Eff53pC8dd+38rdIqtSSmv85DVzSy4+DgbD14+gievG8+ekjoumfspb28+EmIL/xMzf/e7Y1tMCH6V20tGF2f5f3j+YGafM5BFX+7nj+/sCJNlmuNpbVVdvkEDXDa6F8/cNIEDFW6umvcF+/wZIRrzUFbXRE6Qac/TRuXx5l1nMyDbye3/WMOv/h2+EE9iYiLl5eWmFP1APfzExK59T2KiAUp1g5fUxK4Jvojw02nDqG1s5omPd5OSGM+cKToFMNxUNXjxtihyU7ouDGcNyWbxrROZtXA10+d9wcJZE/5jkY/GOMrqmsjqRkjnePpm+VI3H3prO898vo81+yt5fOZ4+p4k+6er9O7dm6KiIkJZ0yuUBDpedQXLC35rq6Km0UtaUtfzeEWE3145krqmZh5+ezspiXaun9gvDFZqAlS6fZkcmc7uCcOYPum8cNskblywipnzv2T+DQW6EqMJUEpR29TcZcerIxz2OB68fARnDMjivpc2cOncT/nD9NFMGxV86mKA+Pj4LnWTigYsH9KpbWpGKbol+ODLBnnkmjGcPyyXn7++OWZTwyJFjX/StbvXC2BwrouX5kyiZ1oiNz6zKiKxXs2JaWpuRSlCXpJ86sievHnX2QzMdTHn+bXc/9JGvY7mBFhe8EMhIPFxNp64bjxnDMjkv1/YoOt8hJFAlk1qUnAPn3lpSbx4+yRG9ErljufXsGTVgVCYp+kmbn956+Qw9KDok5nMi7dN4o4pg3hhzUEunfsp6w9WhXwcK2B5wf9aQIJ7lEyMj+OpG09nZK9U7vznWj7fVRYK8zTHUR2CG3SA9OQEnv/BGZw9JIcHXtnEEx/vMuUEXCzg9vi87nAIPvgW4903dRhLbp2It0Vx1ZNfMPeDnXotzXFYXvBD4eEHcDnsLJw1gQFZTm59rpB1ByqDfk/NN6lp9AlDqGK9yQl2nrqxgCvH9uIPb+/gN29s02srDOBrDz+804ZnDMxi2d1nc9noPB557yuunf+lztlvg+UFP5QeI0CGM4FFt0wg2+Xg5oWr2a2bcoSUmhA9kbUlPs7GI9eMZdbk/iz4fC///cJ6vVozwoQzpHM8aUnxPDZjHI9eO5avjtQy7bFPebHwoH66I5YEP4TV9nJTE3nu5gnE2YQbnl7FUb3QJ2TUNHhx2G0nLYPRVWw24ReXDee+qUN5bX0xP3iuUE/uRZBASCeSfaSvHJfPW/eczfBeqdz70kZmLVzNYX9drVjF8oJf5/9SuxyhfZTsn+1k4awJVLk93LhglV7SHyKqG7wh9e7bIiLcMWUwD181is92lvK9p1ZSUe8Jy1iab9IQoZDO8fTOSGbJrRN58PLhrNxTwcWPLOeF1bHr7Vte8MP5KDkyP43/85dVvvW5Ql28KwRUN3RvzURXuPb0vsy7/jS2H65h+rwvKKrUMd5wE8mQzvHYbMKsyQN42+/t3/fyRm58ZjXFVbHn7YdE8EVkgYiUiMjmDvZPEZFqEVnv//lFKMbtDA3eFuLjhPi48NzbzhqSzZ+vGcuqvRXcs2Q9LXpCMChqGr2kJobfC7x4RM9jLfWmP7mCr/xNVzThIeDhJ4U4VNcV+mU5WXzrRH59xQhW763g4r8sZ/GqAzE1iR8qFVwITD3JMZ8qpcb6f34donFPSoOnJeTx4OP59phe/Pyy4by95Qi/eH1zzD4uhoK6xmZcIcrQORkTBmTy4u2TaFWKq+etYM3+ioiMG4uEOy2zs9hswg2T+vPOPecwKj+Nn76yiWvnx84NPySCr5RaDpjy29LgaYnIh+yWswZw27kDeX7lAeZ+sCvs41kVt6eF5Ah6gcN6pvLynDPJciboBulhxO01JobfEX2zkvnnrWfwh+mj2VVSxyWPfcrDb28/9iRiVSIZw58kIhtE5C0R6bAjsIjMFpFCESkMRdEit7clYh+yB6YO46rxvfnL+1/xr9V6ZWd3cEfoBt2WQIP0U3qkMHvRGl4sPBjR8WOBBk8LIpAYb55pQxHhmoI+fPDjKXxnXD5Pfrybi/7yCR9tLzHatLARqf/9tUA/pdQY4K/Aax0dqJSar5QqUEoV5OTkBD1wJEI6AUSEh64axdlDsvnZq5v5Qq/G7TIN3haSHZF/7A80SJ80MIt7X9rIvE9269BcCHF7WkiKj0NEjDblP8h0JvDHq8fwr9kTSYyPY9bC1dy2qJAD5dabzI+I4CulapRSdf7flwHxIpIdibEbvM0R9Rjj42z87brxx+p17yrRC7O6gtvTbNhjv8thZ8FNp3P5mF489NZ2/vdNvSo3VBh5XTvLGQOzWHbX2dz7raEs/6qMCx/5hIff3k5to3VSriMi+CLSU/y3dhGZ4B+3PBJjN/g9i0iSmhjPgptOJ8Fu4+aFqymva4ro+NFKS6ui0dtqaCZHgt3GY9eO5aYz+/PUZ3v58Ysb8Op6LEFjRKiuOyTYbdx53mA++skULhuTx5Mf7+a8P33Cv1YfsEQGXqjSMhcDK4ChIlIkIreIyO0icrv/kOnAZhHZAMwFZqgIPS+7PS0RXd0XoE9mMvNvKOBoTSOzF+kG252hwWtcrnZbbDbhwcuHc++3hvLqukP84NnCY1kmmu4RLYIfoGdaIo9cM5bX75xMv6xk7n95E99+/DOWf1Ua1aG+UGXpzFRK5Sml4pVSvZVSTyul5iml5vn3P66UGqGUGqOUmqiU+iIU43aGRm/kPfwA4/tm8Mg1Y1mzv5L7XtoY1R+USGCW1D3wzcfced5gHvruKD7dWcr3/r6SSr0qt9s0GOR4BcuYPum8dPsk5s4cR5Xbyw0LVjFj/pcU7jNlUuJJMc+UeZgw2rO4dHQe935rKEs3FPPEx7sNsyMaOLY4x0Sx3hkT+vLk9aex1b8q91AMrs4MBb4YfvQJPvhu/t8e04sPf3Iuv/r2CHaX1jN93gpmPbOKzYeqjTavS1he8COZpdMRd0wZxLfH9OJP7+7g4x3WTfkKFiOX35+Ib43oyaKbJ1BS28T0J79gZ4ws0gklviwd89zIu4PDHseNZ/Zn+X1TuH/qMNYeqOKyv37GD55dHTWL9qwv+F7jY4ciwsNXjWZojxTuWryO/eX1htpjVtzHPHxzCT74MjheuG0Sza2K6fNWsGa/7oXQFczwPQwVyQl25kwZxPL7zuOeC4ewZn8lVz25gmvmreCj7SWmDt1aWvA9za00typDsz4CJCXEMf/7BYgIty1aoycB2yEQ0nGaKKTTllPzUnllzplkJMdz3VNf8uF2vSq3sxgdWg0HaUnx3HPhKXz+wPn84rLhHKx0M2vhaqY++inPr9xvyvLblhb8QNaHWTzGvlnJzJ05jh1Ha/UkbjuYadK2I/pkJvPSnDMZkpvCrc+t4eU1RUabFBVE66RtZ0hOsHPzWQP45N7z+OP00dhsws9e3cwZv/uAB1/fbKoQoLUF34QhgnNPyeHebw3ljY2HWfTlfqPNMRVmu0F3RLbLweLZE5k4MJMfv7iB+cv1ZPyJUErh9jSb9sktVCTYbVxd0Idld53Fy3PO5KLhPVi86iAX/WU5V/ztc575fC+ltcauybG24Jskr/t4bj9nEFOG5vDbN7extbjGaHNMg1knbdsjsCr30tF5/G7Zdn63TK/K7Yim5lZalflv5KFCRDitXwZ/uXYsK356Pv9zyTA8za386t9bmfj7D7hhwSqWrDpAiQGd8ix9yz3WVs0EMfy22GzCn64ewyWPfcqPFq/l3z86y/TLziPBMcGPkmwOhz2Ov84YR7YzgfnL91BW18TDV40OW++FaCWabuShJsvlYPY5g5h9ziB2HKnltfWHWLq+mAde2QTAqPw0zh+Wy5mDshjTJz3sGYXR8c3qJo1e8+V1B8h2OXj02rFc9/RKfrV0Kw9PH220SYbTYEDf02Cx2YRffnsEWS4Hj7z3FZX1Hv523Xh9A29DNMzNRIKhPVO4f+ow7vvWUHYcreWDbSV8uL2EuR/u5LEPdpIQZ2NU7zQK+mUwunc6l4zqGfJic5b+VLpN0GXnRJw5OJs7pwzm8Y92ce7QHC4ZlWe0SYbi9rRgtwkJ9ujykEWEuy4YQrbLwf97bRPXP7WShTdPIDVCjVzMjhkX1BmJiDCsZyrDeqZy53mDqXJ7KNxXyer9FazeW8GCz/eS6Uzg0tGh1wNLX4GGKHiUvOfCISzfWcrPX9vMGQMyyXI5jDbJMIyqexQqvndGXzKS4/nR4nV8/6mVPHfLGWHvzxsNfB2qi95rG07SkxO4cHgPLhzeA4Cm5haOVIcnvh9drlQXCUzaGr3S9kTY42z8cfoYahq9PLh0i9HmGIoVMjmmjco7Vorhuqe+pMqt6+/Ecgy/OzjscfTLcoblva0t+FHyQRvaM4W7LxjCGxsP8/bmw0abYxhWWZxz0fAezP9+AV8dreN7f19JRYwXXWvwRt/cjFWxtOBHk2dx27mDGJmfyv97bQs1Fmq40BWstDjnvGG5/P2GAnaX1vG9v39JWQz3RPj6exjdT29WwNKCHw0hnQDxcTYe+u5oyuubePS9nUabYwhW8fADnHtKDgtuOp195fXMnB+7oh9NjpfVsbbge1qwCTiiJOtjZH4a35vQl2dX7GPHEfMsx44Ubm+L5TI5Jg/O5pmbJnCw0s0NT6+iuiH2nt6iJbQaC4Sq49UCESkRkc0d7BcRmSsiu0Rko4iMD8W4J6PBa97GyR3xk4uHkpJo55dLt8RcrZ0GT7MlMzkmDcpi3vWnsbOklpsXro65wnk6pGMeQuX6LgSmnmD/NGCI/2c28GSIxj0hvjS/6PqQZTgT+PHFQ1mxp5z3tsZWNUarhXTaMmVoLnNnjGPdgUpuW7SGpubYaXnp9jQjAonx0fGkbWVC1eJwOXCiDgBXAM8pH18C6SIS9lVGjd4WkhKi70M28/Q+DMx28ud3v4qp+ixWmrRtj2mj8nj4qtF8urOMuxavs0RT7M7ga34SXU/aViVSapgPHGzzusi/7T8QkdkiUigihaWlpUEN6vY0R01dlrbY42z810WnsONoLf/eWGy0ORHD7WnB6Yi+69UVri7ow4OXD+edLUf5zRtbjTYnIlj5yS3aiJTgt3drb9e9UUrNV0oVKKUKcnJyghq0wdtKYpR+0C4dlcepeak88t5XNLe0Gm1O2GltVcfmXKzOrMkD+MFZA1j4xT4Wfr7XaHPCToOn2dJPbtFEpAS/COjT5nVvIOyuazRPAtpswn9dOIT95W6WbT5itDlhp7E5tjI5fnrJqVw0vAe/fmMrH2yz9lyN29MSlU/aViRSgr8UuMGfrTMRqFZKhX1JaYM3umPCF57ag4E5TuYv3235jJ36ptgS/Dib8NiMsYzolcaPFq8zVVekUBPt30MrEaq0zMXACmCoiBSJyC0icruI3O4/ZBmwB9gF/B24IxTjnoxoL8Zlswm3nj2QzYdqWLGn3GhzwkosVlRMTrDz1I0FJCfEMef5tabsgRoKdAzfPIQqS2emUipPKRWvlOqtlHpaKTVPKTXPv18ppe5USg1SSo1SShWGYtyT0eCJ/pjwd8blk+1K4OlPrR3rdXtjs2Z6j9RE5s4cx57SOn76yiZLPslpwTcP0Zez2AUavNH/QUuMj+Oagj58tKMkbCVTzcCx3gVRfr26w5mDsvnxxUNZuqGYxasOnvwPoowGT7NedGUSLC34bgt4+ADXFPShVcHLa4uMNiVsNMR4zfQ55w7i7CHZ/PbNrRwodxttTkjRHr55sKzgt7QqPM2tlvAY+2c7mTQwi3+tPmjZhVixvvzeZhMevmo0cSLc+9IGS13naJ9LsxKWFfxApUwrePgA157ehwMVbgr3VxptSlhwR2E/21DTKz2JX1w+nJV7K1j05X6jzQkJSinfAsgYvq5mwrqCb7EKfRcO74HDbuMtizZIsdr16i7TT+vN2UOy+dO7Oyi3QDnlpuZWWlXsPrmZDcsLfjTUwu8MLoedc0/J4e3NRyz1uB8g8EQW64IvIjx4+XAaPC386d2vjDYnaI6l21rkexjtWFfwvdaLCV8yKo/D1Y2sL6oy2pSQE8tZOsczODeFm87sz5LVB9haXGO0OUHh1jdyU2FZwf86JmydUzz/1Fzi44R3t1hvKX6Dp4U4m5AQZ53rFQw/umAIKQ47j74f3V5+g56bMRWW/XZ9/ShpHQ8/NTGe8X0z+HRncFVEzYiv3oouoRsgLSmeW84ayLtbj7L5ULXR5nSbWM++MhuWFXyr9tE855QcthTXWGJCry0NXl1R8Xhumtyf1EQ7j3+4y2hTuk3ge+jU19YUWFbwrToJeNbgbAA+21VmsCWhRS/O+U/SkuK5bmI/3t16hKLK6FyM1aDnZkyFdQXfoh+0kflppCfH89lO6wl+LBVO6yzXT+wHwPMrDxhsSffQIR1zYVnBD0zaWu2DFmcTCvplsMZiC7B8he4s+3HsNvnpSVw0vAdLVh2Iyj649Z7YLIpnViz7DbNyOti4vhnsKaunst5jtCkhw60LbHXIzAl9qXR7+XhH9E3WW/VJO1qxrOA3eFoQAYfdeqc4vm8GAOsOWsfL1/VWOuaswdlkORNYuj76+htbNXkiWrGeGvoJ1MK3YprfmD5pxNmEtfurjDYlZFihlHW4sMfZuHR0Hu9vO0pto9doc7pEIA8/0a6vrRkIVcerqSKyQ0R2icgD7eyfIiLVIrLe//OLUIx7ItwWFpDkBDvDeqaw/mCV0aaEDJ2lc2IuH9OLpuZWPoqysE6gRLnNZj3HKxoJWvBFJA74GzANGA7MFJHh7Rz6qVJqrP/n18GOezIaLB4iGJ6XyvYj0b3svi2+JzIdw++I8X0zSE+O5+MdJUab0iXc3hacDn1dzUIoPPwJwC6l1B6llAdYAlwRgvcNCrenmWQLC8iwvFTK6jyU1kb/AixdQvfkxNmEc0/J4ZMdpVFVPM/d1IzToa+rWQiF4OcDbfuyFfm3Hc8kEdkgIm+JyIgQjHtCrD4JeGpeCoAlvPxACV0rX69QcN7QXMrrPWyMolIL9RbpOmcVQiH47QXnjndB1gL9lFJjgL8Cr3X4ZiKzRaRQRApLS7sfr2y0cAwfYFjPVAC2H6412JLg0bXwO8dZQ3yrrFfsLjfYks7j9jTrkI6JCIXgFwF92rzuDXwjf0wpVaOUqvP/vgyIF5Hs9t5MKTVfKVWglCrIycnptlFW6WfbEZnOBHqkOthmAQ/fymsmQkm2y8HAHCeF+yqMNqXT1DdZ2/GKNkIh+KuBISIyQEQSgBnA0rYHiEhP8edHisgE/7hhdVOsPmkLMDjXxZ7SeqPNCJqvS+hqT/BknN4vk8L9lVETx3d7mnHq62oaghZ8pVQz8EPgHWAb8IJSaouI3C4it/sPmw5sFpENwFxghlIqrJ/YWEjz65flZF959Av+scU5Fn4iCxUF/TOobvCyu7TOaFM6RX1TC8l60tY0hOTW6w/TLDtu27w2vz8OPB6KsTpLLCzVH5DlpMrtpcrtIT05wWhzuk1dk8/DdyVa+3qFgoL+mQCs2V/JkB4pBltzcrSHby6su9LWa/2QTv9sJwB7y6Lby69r9Au+ntw7Kf0yk3E57Gw9HB1zN26P9vDNhCUF39vSirdFWT5EMCA7GSDqwzqBiopa8E+OzSacmpcSFb1um1taaWpu1R6+ibCk4Aean1jdw++TmYwI7CuLzuYYAY55+Dqk0ymG56Wy7XCN6SdudfaV+bCm4MdISVaHPY5eaUkcrIhuwa9t0h5+VxjeK5V6TwsHTH7d3U3+9ob6upoGSwp+LJVkzUtLpLi6wWgzgqK+qRm7TSxZyjocnJrnX3Rn8jUYuvmJ+bDkN+zrScB4gy0JP3npSRyubjTajKCoa/StxrRiKetwMDDHBcAek0/WH/PwdQzfNFhT8GMoRNArLZHD1Y2EeVlDWKlraomJaxUqXA47uSkO9pp80Z328M2HpQU/JQYmAfPSEvE0t1Iexe0O65q8WvC7yIBsp/k9/IDg62trGiwq+L6uQLEwWZSXngTA4aroDevUNTXrDJ0uMjDHafr1F4G5NKf28E2DNQU/hhby9ErzCX40T9zqkE7XGZDtpKLeQ5XbvE92gRi+1bPloglrCr7/gxYLIpKXngjA4aooFvxGHdLpKv2zfKus95WbNzWzxt9/NyXR+skT0YJFBd9LnE1IjLfk6X2DzOQE4mxCaV30dr6q1x5+l8nP8D/ZmfhGXxtDT9rRgiUVsa6xGVeMpPnZbEKWM4GyWvM+2p+MuibdJKOr9E73ldUwu+C7HHbidANz02BNwY8xjzHL5aC8Pjo9/NZW5Zu01QW2ukRqkh1nQhyHTCz4NY3emMiUiyYsKvixFRPOdiVQWhedHn6grEJqko7zdgURoVd6ksk9fC34ZsOigh9baX45LgflURrDr2nwTeylacHvMj7BN286bm1js56wNRkWFfyWmIoJZ7kSKKtrisrVtlVuLfjdxfwefjOpMeR4RQMhEXwRmSoiO0Rkl4g80M5+EZG5/v0bRWR8KMbtiLpGLykxJPjZLgeN3lbq/Qtdoolqv4cfzR27jCI/PZHyeg+NXnNed18MX9/IzUTQgi8iccDfgGnAcGCmiAw/7rBpwBD/z2zgyWDHPRG+ScDYEnwgKsM6VQ2+uQft4Xed3BTfGozSWnNed19IJ3a+h9FAKDz8CcAupdQepZQHWAJccdwxVwDPKR9fAukikheCsdslUH0xVshO8Ql+WRQK/tcevhb8rpLjv+5mXIOhlPJP2urraiZCIfj5wME2r4v827p6DAAiMltECkWksLS0tFsG3XHeYKYMzenW30Yjmf5wSEW912BLuk61nrTtNoEnuzITevhNzb42o9rDNxehuBrtrao4fvawM8f4Nio1H5gPUFBQ0K1ZyDvPG9ydP4taAt5xQDyjiWq3F4fdRqLF+w+Hg+wU343ejB5+oKyCnrQ1F6Hw8IuAPm1e9waKu3GMppuk+QXfzIW0OqK6wau9+26S5Qx4+Oa77jUNgRLl+tqaiVAI/mpgiIgMEJEEYAaw9LhjlgI3+LN1JgLVSqnDIRhbA6T4l69Ho4df5daC310S7DbSk+MprTNfLr4O1ZmToJ+3lFLNIvJD4B0gDliglNoiIrf7988DlgGXALsANzAr2HE1XyMipCbaj+W0RxPVDV49YRsEOS6HKT38wNNmhlOn25qJkATYlFLL8Il6223z2vyugDtDMZamfdKTE6iKQg+/0u2ht7/yo6brZLscpozhV/g7sGXom7mpsORK21gkLSk+KkM6FfWeY7FoTdfJSXGYMg8/8LSpPXxzoQXfIqQlxVMdZZO2ra2K8nrPsWwTTdfJdCZQacJ+xhVuD3abxNSK92hAC75FSE+Oj7qQTnWDl5ZWpT38IEhPjqe2qRlvS6vRpnyDKreH9OSEmOhJEU1owbcI6UnxUTdpG1gZHFgprOk6Gf5Fd2YL51XUe8h06vi92dCCbxHSkhOoafTS2ho9FTPL/DX8s3Wct9ukH1uDYS7Br3R7dUE8E6IF3yKkJcWj1NcrHKOBQJeuLJf28LtLQFTNtuiust5zrOSHxjxowbcIgSXsgcbR0UCgBky2SwtDd0lPMquH79EZOiZEC75FCBSpqmuKHsEvr/dgE10LPxgCMfxKE3n4ra2KSrdX5+CbEC34FsHl8H25oknwy+qayHQ6iLPpTI7ukmbCGH6F20NLqyJXT8abDi34FiHQw7cuikI6R2uatCgESWqir45SoJGMGSip8YXqclMTDbZEczxa8C1CoMNXbRR5+MVVDfRK16IQDCJCWlI8lSby8EtqfcXc9M3cfGjBtwgpUejhH65uJC9N19EJlvTkeKrNJPh+D7+H9vBNhxZ8ixDw8OuazPPFPxFuTzPVDV56pmlRCJaM5ARTTdoGPPwc7eGbDi34FiE5IQ6R6PHwD1f7REGHdIInJdFuqvUXJbVNpCbadRczE6IF3yKICC6HPWpi+IerfIKvQzrBk5IYb6obfUlNk56wNSla8C1EisNuqi/+iSiubgAgT4d0giYl0W6qBXcltY16wtakBCX4IpIpIu+JyE7/vxkdHLdPRDaJyHoRKQxmTE3HuBLtUZOHH/Dw9cRe8JhN8I9UN9JTX1dTEqyH/wDwgVJqCPCB/3VHnKeUGquUKghyTE0HuBzRI/gHKtz0TE3Ucd4QkJoYj6ellUZvi9Gm4Glu5UhNI70zk402RdMOwQr+FcCz/t+fBa4M8v00QeBKjDeVp3ci9pfX0y9Li0IoSDFRHaXD1Q20KnTbSpMSrOD3UEodBvD/m9vBcQp4V0TWiMjsE72hiMwWkUIRKSwtLQ3SvNgiJYo8/H3l9QzIdhpthiX4WvCNz9QpqvTNzWjBNycn7T8mIu8DPdvZ9bMujDNZKVUsIrnAeyKyXSm1vL0DlVLzgfkABQUF0VPc3QS4omTStrbRS1mdh35ZWvBDQYq/jpIZPPyiSjcAfTL005sZOangK6Uu7GifiBwVkTyl1GERyQNKOniPYv+/JSLyKjABaFfwNd3HlWg3hZd3MvaX+0RhQLYWhVBgppBOUWUDcTbR2VcmJdiQzlLgRv/vNwKvH3+AiDhFJCXwO3AxsDnIcTXt4HTYcXtbTN/1al95PYD28ENESmKgUqrxN/uiygZ6piZij9MZ32Yk2KvyEHCRiOwELvK/RkR6icgy/zE9gM9EZAOwCnhTKfV2kONq2sGZEIdS0NhsfLbGidhdUo8I9NeCHxICHn6NCTz8AxVuHb83MScN6ZwIpVQ5cEE724uBS/y/7wHGBDOOpnMk++vp1De1kJwQ1KUNK9uP1NA/y0lSgk7JDAWpieaI4Sul2FVSx6Wj8wy1Q9Mx+rnLQiT7c9obPOb28HccqWVojxSjzbAMLpNk6VTUe6hu8DI4x2WoHZqO0YJvIZwOn+DXe4x/tO+IBk8Le8vrGdpTC36oiLMJzoQ4wz38XSV1AAzK1YJvVrTgW4hAGMdtYsHfWVKLUnBqnhb8UJKSGG+4h7+71DcZPyhHz82YFS34FiLZHxOvbzJvSGf74VoAhvZMNdgSa2GGejq7S+tIio+jl66Aalq04FuIrz188wr+hqIqUhx2+ulaKyHFZQLB31lSx8AcJzbdlN60aMG3EIEYvplDOusOVDG2b7oWhRCTkhhvaC8EpRRbi6s5NU8/uZkZLfgWIuDh15vUw3d7mtl+pIZxfdKNNsVy+HohGBfDL6ltoqzOw8heWvDNjBZ8CxGI4btNWkBtY1E1rQrG9W23bYImCFIM7oWw+VA1ACPz0wyzQXNytOBbiKT4QEjHnB7+ugNVAIzVHn7IcTmMjeFvPlSDCDqkY3K04FsIm01ITogzbQz/yz3lDM51keFMMNoUy+FKtOP2tNBiUB2lLcXVDMh24nSYd4W3Rgu+5UhOsJsyhu9pbmXV3grOGpxttCmW5FgBNQO8fKUU6w5WMVqHc0yPFnyLkZwQZ8oY/roDlTR4WzhzUJbRpliSFL9nXWtAxcz95W5Ka5s4fUBmxMfWdA0t+BbDF9Ixn4f/+a4ybAJnDNSCHw4C9XSMmLhdta8CgAn9teCbHS34FsPpsJtS8D/ZWcbo3umkJcUbbYolMbIJyuq9FaQnxzNIF00zPVrwLUZyQpzpiqcdqW5kw8EqLhrew2hTLIvLH9IxIoZfuL+Sgn6ZejFdFKAF32L4Yvjm8vDf23YUgIu14IeNYx5+hEM6h6oa2FtWz8SBOpwTDQQl+CJytYhsEZFWESk4wXFTRWSHiOwSkQeCGVNzYpwJdtxec3n47245wsBsJ4N12dywkXKsCUpkJ22Xf1UKwLmn5ER0XE33CNbD3wx8lxM0JBeROOBvwDRgODBTRIYHOa6mA5Id5vLwq91eVuwu5+IRPRHRj/zhwqiQzic7SumVlqhv5lFCUIKvlNqmlNpxksMmALuUUnuUUh5gCXBFMONqOsaZYDdVDP/fG4tpblVcptvehZXkhDhsEtksHW9LK5/vKuPcoTn6Zh4lRCKGnw8cbPO6yL+tXURktogUikhhaWlp2I2zGkkJcTR6Ww1bcXk8L68tYmiPFEboolphRUQiXl5hzf5KapuadTgnijip4IvI+yKyuZ2fznrp7d36O1QjpdR8pVSBUqogJ0d/kLqK018xs8FrfFhnd2kd6w5UcdVp+doDjAC+rleRE/y3Nh3GYbdx9hD9PY0WTlr4Qil1YZBjFAF92rzuDRQH+Z6aDkh2fF0x02VwXZNX1hZhE7hybIcPdJoQ4nLYqYvQStvWVsVbm49w3tBcXT8niohESGc1MEREBohIAjADWBqBcWMSp0lq4jc1t/Cv1Qc5b2guuamJhtoSK0SyzWHh/kpKapu4RM/NRBXBpmV+R0SKgEnAmyLyjn97LxFZBqCUagZ+CLwDbANeUEptCc5sTUckJZij69WbGw9TVufhpsn9DbUjlnBFsCb+Mn8454JhuREZTxMagnoWU0q9CrzazvZi4JI2r5cBy4IZS9M5nCbpa/vsF/sYlOPU1TEjiMthZ3+5O+zjeFtaeWNjMecP0+GcaEOvtLUYgRh+vYEVM9ceqGRDUTU3ndlfT9ZGkEhN2n6wrYSyOg9XF/QO+1ia0KIF32KYwcN/4qPdpCXF893xWhAiia/NYfgnbV8sPEiPVAfn6OycqEMLvsU41tfWIMHfWlzD+9uOcvPkAfpxP8K4HHYava14W1rDNsbRmkY+2lHCVeN7Y4/T8hFt6CtmMZINnrR9/KOdpDjserLWAAIF1MJZXuGF1QdpVXBNQZ+TH6wxHVrwLUbAqzaiEcbOo7W8tfkIN57ZX9e9NwBXmK+9p7mV577czzmn5NA/2xmWMTThRQu+xXDYbcTZxJBJ24ff3oEzwc7NZw2I+Niarz38mjBVzHxjYzGltU3coq9v1KIF32IEaqrUR7hi5so95by/7Shzpgwi05kQ0bE1PsLZyFwpxdOf7WVwrotzhuhU22hFC74FiXQRLaUUv3trOz1TE7l5svb+jCKcIZ2VeyvYUlzDzZMH6FTbKEYLvgWJZE0VgDc2HmbDwSp+fPEpx1b6aiKPK4x9bf/64U6yXQl8Z5yuixTNaMG3IK7EyIV06pua+f2ybQzrmaLz7g0mXG0OC/dV8Pmucm47Z5C+oUc5WvAtiNNhj1hv07kf7qS4upHfXjmSON3E2lBSHOFpc/jYBzvJciZw3cS+IX1fTeTRgm9BUhx26iLQ23THkVqe/nQv1xT0pqC/bmJtNInxvgytUE7artlfyac7y5h9zkCSE/RCumhHC74FcTriwh7SUUrx89c240q088C0U8M6lqZziIi/vEJoBF8pxZ/e2UGmM4HrJ/YLyXtqjEULvgVxOeLDvvDqhcKDrNpXwQNTh+k0TBMRygytD7eXsGJPOXdfMESXybAIWvAtSKAuemuY+toeqmrgt29s44wBmXqJvckIleA3t7Ty+7e2MyDbyffO0LF7q6AF34K4Am0Ow9DXVinFAy9vpEUp/jh9DDY9UWsqUhPjQ5KS+6/Cg+wqqeP+qcOI10XSLEOwHa+uFpEtItIqIgUnOG6fiGwSkfUiUhjMmJqT43KEb8XlktUH+XRnGT+dNoy+Wckhf39NcLhC0OawrqmZv7y3k4J+GXxrRI8QWaYxA8EG5jYD3wX+rxPHnqeUKgtyPE0ncPo9fJ+nF7p+skWVbn77xlbOHJTFdWfoSTwz4nLY2R3k/M2j731FWV0Tf7/hNL2q1mIE2+JwG6A/FCbjWJncEGbqtLQq/vtfGwB4+KrROpRjUlIS7UE92W0/UsMzX+xj5oQ+jOubEULLNGYgUsE5BbwrImtEZPaJDhSR2SJSKCKFpaWlETLPWoQjpPP4h7tYta+C31w5kj6ZOpRjVlyJdmoavSjV9Qn7QKptaqKd+741LAzWaYzmpB6+iLwP9Gxn18+UUq93cpzJSqliEckF3hOR7Uqp5e0dqJSaD8wHKCgoCE+aicX5ZkgneFbvq+CxD77iO+PydfkEk5PlTMDboqhpbO5yT4KX1x5i9b5KHr5qFBk61daSnFTwlVIXBjuIUqrY/2+JiLwKTADaFXxN8Hy9xD54D7/K7eHuxevom5nMb64cGfT7acJLTooDgLK6pi4JfpXbw++XbWN833SuPk2n2lqVsId0RMQpIimB34GL8U32asJEWrLvi17lDs7D96VgbqKktom5M8cdK7+rMS/ZLr/g1zZ16e9+9e+tVDd4+d/vjNLzMxYm2LTM74hIETAJeFNE3vFv7yUiy/yH9QA+E5ENwCrgTaXU28GMqzkxqYl24mxCpdsT1Ps8t2I/b285wn1ThzK6d3pojNOElWOCX9f5a//+1qO8uu4Qd5w3mFPzUsNlmsYEBJul8yrwajvbi4FL/L/vAcYEM46ma4gIGckJQQn+mv0V/OaNrVwwLJcfnDUwhNZpwsnXgt85D7/a7eV/Xt3EsJ4p/PC8weE0TWMC9DO6Rcl0xlNR3z3BL61t4o7n19IrPYlHrh2rH/GjiExnAjaB8k4K/m/e3Ep5vYenbzydBLteUWt19BW2KD4Pv+sx/OaWVn74z7VUub3Mu/60Lmd6aIwlziZkOh2UdkLwP9pRwktrirjtnIGM6p0WAes0RqMF36JkOhOo7IaH/4d3drBybwW/+84ohvfS8dxopGeag+KqxhMeU1Hv4b6XNjIk18VdFwyJkGUao9GCb1EynF2P4S/bdJj5y/dw/cS+XHWazrePVvplOjlQ4e5wv1KK+1/eSLXby2MzxpEYr9sWxgpa8C1Kpj+k09kSyduP1HDvixsY2yedn182PMzWacJJ36xkDla4aW5pbXf/4lUHeW/rUe6bOlQ/xcUYWvAtSm6qg5ZWRVn9yWO55XVN/ODZQpwOO/OuPw2HXXt80Uy/zGSaWxWHq/8zrLOrpI5fv7GFs4dkc/PkAQZYpzESLfgWpVdaEsBJY7me5lbmPL+Wktom5t9QQM+00FXX1BjDoFwX4Os53JYGTws//OdakuLj+PPVupdBLKIF36LkZ/gE/1BlQ4fHKKV4cOkWVu2t4I/TRzO2T3qErNOEk5G90rDbhHUHK49tU0rxs1c3seNoLX+5diy5qfrGHotowbcovdL9gl/V8eTdcyv2s3jVAeZMGcQVY/MjZZomzCQlxHFqXiqr9lYc27bg8328su4Q91xwClOG5hponcZItOBblLSkeFIcdoo68PA/21nGr9/YyoWn5nLvxUMjbJ0m3Fw8vAer91Wyp7SOJasO8Js3tnLx8B786Hy9mjaW0SttLczgHi62HxfHBV9Gzpx/rGFwjotHZ4zTsVwLcu3pfZj/6R6mPfYpTc2tnD0km7kz9bWOdbTgW5hR+Wm8svYQra3q2Bf9cHUDs55ZTbIjjgWzTtcVMC1Kbmoiz948gX+s2M+I/DRunNQPu25GHvPob7uFGZWfxnMr9rPtSA0jeqVRUe9h1jOrqW1s5oXbJpHvj/NrrMn4vhmM120KNW3Qgm9hzh+WS5xNeGXtIdKS4rllYSH7yutZcNPpesGNRhODaMG3MFkuB1eOzefpz/ayaMV+Euw2Ftx0OpMHZxttmkajMQAt+Bbn11eMoEeqA7enhZsnD6Bvlm5ArtHEKkEJvoj8Ebgc8AC7gVlKqap2jpsKPAbEAU8ppR4KZlxN53E67Nw3dZjRZmg0GhMQ7LT9e8BIpdRo4Cvgp8cfICJxwN+AacBwYKaI6OpcGo1GE2GCEnyl1LtKqWb/yy+B9mrqTgB2KaX2KKU8wBLgimDG1Wg0Gk3XCWVi7s3AW+1szwcOtnld5N+m0Wg0mghy0hi+iLwP9Gxn18+UUq/7j/kZ0Aw8395btLOtwyLtIjIbmA3Qt2/fk5mn0Wg0mk5yUsFXSl14ov0iciNwGXCBUqo9IS8C+rR53RsoPsF484H5AAUFBZ3r3qHRaDSakxJUSMeffXM/8G2lVEdlGVcDQ0RkgIgkADOApcGMq9FoNJquE2wM/3EgBXhPRNaLyDwAEeklIssA/JO6PwTeAbYBLyiltgQ5rkaj0Wi6SFB5+EqpdmutKqWKgUvavF4GLAtmLI1Go9EEh7QfdjcHIlIK7O/mn2cDZSE0JxrQ52x9Yu18QZ9zV+mnlMppb4epBT8YRKRQKVVgtB2RRJ+z9Ym18wV9zqFEF8jWaDSaGEELvkaj0cQIVhb8+UYbYAD6nK1PrJ0v6HMOGZaN4Ws0Go3mm1jZw9doNBpNG7TgazQaTYxgOcEXkakiskNEdonIA0bbEw5EpI+IfCQi20Rki4jc7d+eKSLvichO/7+W62AtInEisk5E3vC/tvQ5i0i6iLwkItv913tSDJzzf/k/15tFZLGIJFrtnEVkgYiUiMjmNts6PEcR+alf03aIyLe6O66lBD+Gmq00Az9WSp0KTATu9J/nA8AHSqkhwAf+11bjbnwlOgJY/ZwfA95WSg0DxuA7d8ues4jkA3cBBUqpkfi65M3Aeue8EJh63LZ2z9H/3Z4BjPD/zRN+resylhJ8YqTZilLqsFJqrf/3WnwikI/vXJ/1H/YscKUhBoYJEekNXAo81WazZc9ZRFKBc4CnAZRSHn8LUcuesx87kCQidiAZX3VdS52zUmo5UHHc5o7O8QpgiVKqSSm1F9iFT+u6jNUEP+aarYhIf2AcsBLooZQ6DL6bApBroGnh4FHgPqC1zTYrn/NAoBR4xh/GekpEnFj4nJVSh4A/AQeAw0C1UupdLHzObejoHEOma1YT/C41W4l2RMQFvAzco5SqMdqecCIilwElSqk1RtsSQezAeOBJpdQ4oJ7oD2WcEH/c+gpgANALcIrI9cZaZTgh0zWrCX6Xmq1EMyISj0/sn1dKveLffFRE8vz784ASo+wLA5OBb4vIPnyhuvNF5B9Y+5yLgCKl1Er/65fw3QCsfM4XAnuVUqVKKS/wCnAm1j7nAB2dY8h0zWqCHxPNVkRE8MV1tymlHmmzaylwo//3G4HXI21buFBK/VQp1Vsp1R/fdf1QKXU91j7nI8BBERnq33QBsBULnzO+UM5EEUn2f84vwDdHZeVzDtDROS4FZoiIQ0QGAEOAVd0aQSllqR98dfi/Anbj67truE1hOMez8D3SbQTW+38uAbLwze7v9P+babStYTr/KcAb/t8tfc7AWKDQf61fAzJi4Jx/BWwHNgOLAIfVzhlYjG+OwovPg7/lROcI/MyvaTuAad0dV5dW0Gg0mhjBaiEdjUaj0XSAFnyNRqOJEbTgazQaTYygBV+j0WhiBC34Go1GEyNowddoNJoYQQu+RqPRxAj/Hxe2Z/XO9uJIAAAAAElFTkSuQmCC\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "inputs = bp.math.linspace(1., 2., 10000)\n", + "\n", + "fnh4.run(duration=100, \n", + " inputs=('Y.I', # specify 'target' by the absolute path access\n", + " inputs, # specify 'value' with the iterable \"inputs\"\n", + " 'iter')) # \"iter\" input 'type' must be explicitly specified\n", + "\n", + "plt.plot(fnh4.mon.ts, fnh4.mon.v, label='v')\n", + "plt.legend()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "4. ``brainpy.DynamicalSystem`` is a subclass of [brainpy.Base](../apis/generated/brainpy.base.Base.rst), therefore, any instance of ``brainpy.DynamicalSystem`` can be [just-in-time compiled](./jit_compilation.ipynb) into efficient machine codes targeting on CPUs, GPUs, or TPUs. " + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "fnh3_jit = bp.math.jit(fnh3)\n", "\n", - "- [brainpy.NeuGroup](#brainpy.NeuGroup)\n", - "- [brainpy.TwoEndConn](#brainpy.TwoEndConn)\n", - "- [brainpy.Network](#brainpy.Network)" + "fnh3_jit.run(duration=100, inputs=('I', 1.5))\n", + "\n", + "plt.plot(fnh3_jit.mon.ts, fnh3_jit.mon.v, label='v')\n", + "plt.plot(fnh3_jit.mon.ts, fnh3_jit.mon.w, label='w')\n", + "plt.legend()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "For brain modeling, BrainPy provides the interface of `brainpy.NeuGroup`, `brainpy.TwoEndConn`, and `brainpy.Network` for convenient neurodynamics simulation." + "5. ``brainpy.DynamicalSystem`` can be combined arbitrarily. Any composed system can also benefit from the above convenient interfaces. " ] }, { "cell_type": "code", - "execution_count": 1, - "metadata": { - "ExecuteTime": { - "end_time": "2021-03-25T03:02:48.939126Z", - "start_time": "2021-03-25T03:02:47.073698Z" + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" } - }, - "outputs": [], + ], "source": [ - "import sys\n", - "sys.path.append('../../')\n", + "# compose two FitzHughNagumoModel instances into a Network\n", + "net2 = bp.Network(f1=fnh3, f2=fnh4, monitors=['f1.v', 'Y.v'])\n", "\n", - "import brainpy as bp\n", + "net2.run(100, inputs=[\n", + " ('f1.I', 1.5), # relative access variable \"I\" in 'fnh3'\n", + " ('Y.I', 1.0), # absolute access variable \"I\" in 'fnh4'\n", + "])\n", "\n", - "bp.__version__" + "plt.plot(net2.mon.ts, net2.mon['f1.v'], label='v1')\n", + "plt.plot(net2.mon.ts, net2.mon['Y.v'], label='v2')\n", + "plt.legend()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In next sections, we will illustrate how to define common brain objects by subclasses of ``brainpy.DynamicalSystem``. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## brainpy.NeuGroup" + "## ``brainpy.NeuGroup``" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "`brainpy.NeuGroup` is used for neuron group modeling. User-defined neuron group models must inherit from the `brainpy.NeuGroup`. Let's take the [leaky integrate-and-fire](https://en.wikipedia.org/wiki/Biological_neuron_model#Leaky_integrate-and-fire) (LIF) model and [Hodgkin–Huxley neuron model](https://en.wikipedia.org/wiki/Hodgkin%E2%80%93Huxley_model) as the illustrated examples. " + "`brainpy.NeuGroup` is used for neuron group modeling. User-defined neuron group models should inherit from the `brainpy.NeuGroup`. Let's take the [leaky integrate-and-fire](https://en.wikipedia.org/wiki/Biological_neuron_model#Leaky_integrate-and-fire) (LIF) model and [Hodgkin–Huxley neuron model](https://en.wikipedia.org/wiki/Hodgkin%E2%80%93Huxley_model) as the illustrated examples. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### LIF model" + "### LIF neuron model" ] }, { @@ -72,11 +726,11 @@ "The formal equations of a LIF model is given by:\n", "\n", "$$\n", - "\\tau_m \\frac{dV}{dt} = - (V(t) - V_{rest}) + I(t) \n", - "\\\\\n", - "\\text{after}\\, V(t) \\gt V_{th}, V(t) =V_{rest}\n", - "\\,\n", - "\\text{last}\\, \\tau_{ref}\\, \\text{ms} \n", + "\\begin{aligned}\n", + "\\tau_m \\frac{dV}{dt} = - (V(t) - V_{rest}) + I(t) \\quad\\quad (1) \\\\\n", + "\\text{after} \\, V(t) \\gt V_{th}, V(t) =V_{rest} \\,\n", + "\\text{last} \\, \\tau_{ref} \\, \\text{ms} \\quad\\quad (2)\n", + "\\end{aligned}\n", "$$\n", "\n", "where $V$ is the membrane potential, $V_{rest}$ is the rest membrane potential, $V_{th}$ is the spike threshold, $\\tau_m$ is the time constant, $\\tau_{ref}$ is the refractory time period, and $I$ is the time-variant synaptic inputs. " @@ -86,30 +740,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "As stated above, the numerical integration of the differential equation in LIF model can be coded as:" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "ExecuteTime": { - "end_time": "2021-03-25T03:02:48.959138Z", - "start_time": "2021-03-25T03:02:48.941426Z" - } - }, - "outputs": [], - "source": [ - "@bp.odeint\n", - "def int_V(V, t, Iext, V_rest, R, tau):\n", - " return (- (V - V_rest) + R * Iext) / tau" + "The above two equations mean that: when the membrane potential $V$ is below $V_{th}$, the model integrates $V$ with the equation (1); once $V > V_{th}$, according to equation (2), we will reset the membrane potential to $V_{rest}$, and the model enters into the refractory period which lasts $\\tau_{ref}$ ms. In the refractory period, the membrane potential $V$ will no longer change. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Then, we will define the following items to store the neuron state:\n", + "Let's start to code this LIF neuron model. First, we will define the following items to store the neuron state:\n", "\n", "- ``V``: The membrane potential.\n", "- ``input``: The synaptic input.\n", @@ -122,18 +760,19 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 21, "metadata": { "ExecuteTime": { "end_time": "2021-03-25T03:02:48.995265Z", "start_time": "2021-03-25T03:02:48.969008Z" - } + }, + "scrolled": false }, "outputs": [], "source": [ "class LIF(bp.NeuGroup):\n", - " def __init__(self, size, t_refractory=1., V_rest=0.,\n", - " V_reset=-5., V_th=20., R=1., tau=10., **kwargs):\n", + " def __init__(self, size, t_refractory=1., V_rest=0., V_reset=-5.,\n", + " V_th=20., R=1., tau=10., **kwargs):\n", " super(LIF, self).__init__(size=size, **kwargs)\n", "\n", " # parameters\n", @@ -145,30 +784,34 @@ " self.t_refractory = t_refractory\n", "\n", " # variables\n", - " self.t_last_spike = bp.math.Variable(bp.math.ones(self.num) * -1e7)\n", - " self.refractory = bp.math.Variable(bp.math.zeros(self.num))\n", + "\n", + " self.V = bp.math.Variable(bp.math.random.randn(self.num) * 5. + V_reset)\n", " self.input = bp.math.Variable(bp.math.zeros(self.num))\n", + " self.t_last_spike = bp.math.Variable(bp.math.ones(self.num) * -1e7)\n", + " self.refractory = bp.math.Variable(bp.math.zeros(self.num, dtype=bool))\n", " self.spike = bp.math.Variable(bp.math.zeros(self.num, dtype=bool))\n", - " self.V = bp.math.Variable(bp.math.ones(self.num) * V_reset)\n", "\n", - " @bp.odeint\n", + " @bp.odeint(method='exponential_euler')\n", " def int_V(self, V, t, Iext):\n", - " return (- (V - self.V_rest) + self.R * Iext) / self.tau\n", + " dvdt = (- (V - self.V_rest) + self.R * Iext) / self.tau\n", + " return dvdt\n", "\n", - " def update(self, _t, _i):\n", + " def update(self, _t, _dt):\n", " for i in range(self.num):\n", " if _t - self.t_last_spike[i] <= self.t_refractory:\n", - " self.refractory[i] = 1.\n", + " self.refractory[i] = True\n", + " self.spike[i] = False\n", " else:\n", - " self.refractory[0] = 0.\n", " V = self.int_V(self.V[i], _t, self.input[i])\n", " if V >= self.V_th:\n", " self.V[i] = self.V_reset\n", - " self.spike[i] = 1.\n", " self.t_last_spike[i] = _t\n", + " self.spike[i] = True\n", + " self.refractory[i] = True\n", " else:\n", - " self.spike[i] = 0.\n", " self.V[i] = V\n", + " self.spike[i] = False\n", + " self.refractory[i] = False\n", " self.input[i] = 0." ] }, @@ -176,19 +819,30 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "That's all, we have coded a LIF neuron model. \n", - "\n", + "That's all, we have coded a LIF neuron model. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "Each NeuGroup has a powerful function: ``.run()``. In this function, it receives the following arguments:\n", "\n", - "- ``duration``: Specify the simulation duration. Can be a tuple with ``(start time, end time)``. Or it can be a int to specify the duration ``length`` (then the default start time is ``0``).\n", - "- ``inputs``: Specify the inputs for each model component. With the format of ``(target, value, [operation])``. The default operation is ``+``, which means the input ``value`` will be added to the ``target``. Or, the operation can be ``+``, ``-``, ``*``, ``/``, or ``=``.\n", - "\n", - "Now, let's run it." + "- ``duration``: Specify the simulation duration. It can be a *tuple* with ``(start time, end time)``, or a *int* to specify the duration ``length`` (then the default start time is ``0``).\n", + "- ``inputs``: Specify the inputs for each model component. With the format of ``(target, value, [type, operation])``. Details please see the tutorial of [Monitor and Inputs](../tutorial_simulation/monitor_and_inputs.ipynb).\n", + "- ``report``: a *float* to specify the progress percent to report. \"0\" (default) means doesn't report running progress. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, let's run the defined model." ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 22, "metadata": { "ExecuteTime": { "end_time": "2021-03-25T03:02:49.010946Z", @@ -202,7 +856,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 23, "metadata": { "ExecuteTime": { "end_time": "2021-03-25T03:02:49.941507Z", @@ -214,25 +868,17 @@ "name": "stdout", "output_type": "stream", "text": [ - "Compilation used 0.0000 s.\n", + "Compilation used 0.0003 s.\n", "Start running ...\n", - "Run 10.0% used 0.054 s.\n", - "Run 20.0% used 0.121 s.\n", - "Run 30.0% used 0.177 s.\n", - "Run 40.0% used 0.231 s.\n", - "Run 50.0% used 0.283 s.\n", - "Run 60.0% used 0.337 s.\n", - "Run 70.0% used 0.387 s.\n", - "Run 80.0% used 0.440 s.\n", - "Run 90.0% used 0.496 s.\n", - "Run 100.0% used 0.550 s.\n", - "Simulation is done in 0.550 s.\n", + "Run 50.0% used 3.288 s.\n", + "Run 100.0% used 6.467 s.\n", + "Simulation is done in 6.467 s.\n", "\n" ] }, { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -244,13 +890,13 @@ } ], "source": [ - "group.run(duration=200., inputs=('input', 26.), report=True)\n", + "group.run(duration=200., inputs=('input', 26.), report=0.5)\n", "bp.visualize.line_plot(group.mon.ts, group.mon.V, show=True)" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 24, "metadata": { "ExecuteTime": { "end_time": "2021-03-25T03:02:50.811867Z", @@ -263,25 +909,20 @@ "name": "stdout", "output_type": "stream", "text": [ - "Compilation used 0.0010 s.\n", + "Compilation used 0.0004 s.\n", "Start running ...\n", - "Run 10.0% used 0.052 s.\n", - "Run 20.0% used 0.108 s.\n", - "Run 30.0% used 0.161 s.\n", - "Run 40.0% used 0.214 s.\n", - "Run 50.0% used 0.273 s.\n", - "Run 60.0% used 0.319 s.\n", - "Run 70.0% used 0.379 s.\n", - "Run 80.0% used 0.432 s.\n", - "Run 90.0% used 0.483 s.\n", - "Run 100.0% used 0.542 s.\n", - "Simulation is done in 0.542 s.\n", + "Run 20.0% used 1.370 s.\n", + "Run 40.0% used 2.755 s.\n", + "Run 60.0% used 4.166 s.\n", + "Run 80.0% used 5.599 s.\n", + "Run 100.0% used 7.034 s.\n", + "Simulation is done in 7.034 s.\n", "\n" ] }, { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -293,7 +934,7 @@ } ], "source": [ - "group.run(duration=(200, 400.), report=True)\n", + "group.run(duration=(200, 400.), report=0.2)\n", "bp.visualize.line_plot(group.mon.ts, group.mon.V, show=True)" ] }, @@ -301,381 +942,282 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "As you experienced just now, the benefit of inheriting `brainpy.NeuGroup` lies at the following several ways:\n", + "In the model definition, BrainPy endows you with the fully data/logic flow control. You can define models with any data you need and any logic you want. There are little limitations/constrains on your customization. \n", "\n", - "- Easy way to monitor variable trajectories.\n", - "- Powerful \"inputs\" support.\n", - "- Continuous running support. \n", - "- Progress report. " + "1. you should \"super()\" initialize the `brainpy.NeuGroup` with the keyword of the group `size`. \n", + "2. you should define the `update` function." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "On the model definition, BrainPy endows you the fully data/logic flow control. You can define models with any data you need and any logic you want. There are little limitations/constrains on your customization. 1, you should set what computing backend do your defined model support by the keyword `target_backend`. 2, you should \"super()\" initialize the `brainpy.NeuGroup` with the keyword of the group `size`. 3, you should define the `update` function." + "## ``brainpy.TwoEndConn``" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Hodgkin–Huxley model" + "For synaptic computations, BrainPy provides `brainpy.TwoEndConn` to help you construct the connections between pre-synaptic and post-synaptic neuron groups, and provides `brainpy.connect.TwoEndConnector` for synaptic projections between pre- and post- groups. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "The updating logic in the above LIF model is coded with a for loop, which is very suitable for Numba backend (because Numba is a Just-In-Time compiler, and it is good at the for loop optimization). However, for array-oriented programming languages, such as NumPy, PyTorch and TensorFlow, this coding schema is inefficient. Here, let's use the HH neuron model as example to demonstrate how to code an array-based neuron model for general backends." + "``brainpy.TwoEndConn`` can help to construct **automatic delay** in synaptic computations. The modeling of synapses usually includes a delay time (typically 0.3–0.5 ms) required for a neurotransmitter to be released from a presynaptic membrane, diffuse across the synaptic cleft, and bind to a receptor site on the post-synaptic membrane. BrainPy provides [register_constant_dely()](../apis/simulation/brainobjects.html#brainpy.simulation.brainobjects.TwoEndConn.register_constant_delay) for automatic state delay. " ] }, { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "ExecuteTime": { - "end_time": "2021-03-25T03:02:50.837658Z", - "start_time": "2021-03-25T03:02:50.814760Z" - } - }, - "outputs": [], + "cell_type": "markdown", + "metadata": {}, "source": [ - "class HH(bp.NeuGroup):\n", - " def __init__(self, size, ENa=50., EK=-77., EL=-54.387, C=1.0,\n", - " gNa=120., gK=36., gL=0.03, V_th=20., **kwargs):\n", - " super(HH, self).__init__(size=size, **kwargs)\n", - "\n", - " # parameters\n", - " self.ENa = ENa\n", - " self.EK = EK\n", - " self.EL = EL\n", - " self.C = C\n", - " self.gNa = gNa\n", - " self.gK = gK\n", - " self.gL = gL\n", - " self.V_th = V_th\n", - "\n", - " # variables\n", - " self.V = bp.math.ones(size) * -65.\n", - " self.m = bp.math.ones(size) * 0.5\n", - " self.h = bp.math.ones(size) * 0.6\n", - " self.n = bp.math.ones(size) * 0.32\n", - " self.spike = bp.math.zeros(size)\n", - " self.input = bp.math.zeros(size)\n", - "\n", - " @bp.odeint(method='rk4', dt=0.01)\n", - " def integral(self, V, m, h, n, t, Iext):\n", - " alpha = 0.1 * (V + 40) / (1 - bp.math.exp(-(V + 40) / 10))\n", - " beta = 4.0 * bp.math.exp(-(V + 65) / 18)\n", - " dmdt = alpha * (1 - m) - beta * m\n", - "\n", - " alpha = 0.07 * bp.math.exp(-(V + 65) / 20.)\n", - " beta = 1 / (1 + bp.math.exp(-(V + 35) / 10))\n", - " dhdt = alpha * (1 - h) - beta * h\n", - "\n", - " alpha = 0.01 * (V + 55) / (1 - bp.math.exp(-(V + 55) / 10))\n", - " beta = 0.125 * bp.math.exp(-(V + 65) / 80)\n", - " dndt = alpha * (1 - n) - beta * n\n", - "\n", - " I_Na = (self.gNa * m ** 3.0 * h) * (V - self.ENa)\n", - " I_K = (self.gK * n ** 4.0) * (V - self.EK)\n", - " I_leak = self.gL * (V - self.EL)\n", - " dVdt = (- I_Na - I_K - I_leak + Iext) / self.C\n", - "\n", - " return dVdt, dmdt, dhdt, dndt\n", - "\n", - " def update(self, _t, _i):\n", - " V, m, h, n = self.integral(self.V, self.m, self.h, self.n, _t, self.input)\n", - " self.spike[:] = bp.math.logical_and(self.V < self.V_th, V >= self.V_th)\n", - " self.V[:] = V\n", - " self.m[:] = m\n", - " self.h[:] = h\n", - " self.n[:] = n\n", - " self.input[:] = 0" + "``brainpy.connect.TwoEndConnector`` provides convenient interfaces for **connectivity structure construction**. Various synaptic structures, like *pre_ids*, *post_ids*, *conn_mat*, *pre2post*, *post2pre*, *pre2syn*, *post2syn*, *pre_slice*, and *post_slice*. Users can **require** such data structures by calling `connector.requires('pre_ids', 'post_ids', ...)`. We will detail this function in [Synaptic Connections](../tutorial_simulation/efficient_synaptic_computation.ipynb)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "In HH example, all the operations (including \"zeros\", \"ones\" and \"exp\") are used from the `brainpy.ops` as `bp.math.zeros`, `bp.math.ones` and `bp.math.exp`. What's more, we set the \"target_backend\" as `general`, which means it can run on any backends. So, let's try to run this model on various backends." + "Here, let's illustrate how to use `brainpy.TwoEndConn` with the [Exponential synapse model](https://brainmodels.readthedocs.io/en/latest/apis/generated/brainmodels.synapses.ExponentialCOBA.html)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Second is NumPy." + "### Exponential synapse model" ] }, { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "ExecuteTime": { - "end_time": "2021-03-25T03:03:00.330366Z", - "start_time": "2021-03-25T03:02:58.561102Z" - }, - "scrolled": true - }, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], + "cell_type": "markdown", + "metadata": {}, "source": [ - "bp.backend.set('numpy')\n", + "Exponential synapse model assumes that once a pre-synaptic neuron generates a spike, the synaptic state arises instantaneously, then decays with a certain time constant $\\tau_{decay}$. Its dynamics is given by:\n", "\n", - "group = HH(100, monitors=['V'])\n", + "$$\n", + "\\frac{d s}{d t} = -\\frac{s}{\\tau_{decay}}+\\sum_{k} \\delta(t-D-t^{k})\n", + "$$\n", + "\n", + "where $s$ is the synaptic state, $t^{k}$ is the spike time of the pre-synaptic neuron, and $D$ is the synaptic delay. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Afterward, the current output onto the post-synaptic neuron is given in the conductance-based form\n", "\n", - "group.run(200., inputs=('input', 10.))\n", + "$$\n", + "I_{syn}(t) = g_{max} s \\left( V(t)-E \\right)\n", + "$$\n", "\n", - "bp.visualize.line_plot(group.mon.ts, group.mon.V, show=True)" + "where $E$ is the reversal potential of the synapse, $V$ is the post-synaptic membrane potential, $g_{max}$ is the maximum synaptic conductance. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "The last is Numba." + "So, let's try to implement this synapse model. " ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 25, "metadata": { "ExecuteTime": { - "end_time": "2021-03-25T03:03:12.362447Z", - "start_time": "2021-03-25T03:03:00.335509Z" - } + "end_time": "2021-03-25T03:03:12.401462Z", + "start_time": "2021-03-25T03:03:12.369072Z" + }, + "scrolled": false }, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ - "bp.backend.set('numba')\n", + "class Exponential(bp.TwoEndConn):\n", + " def __init__(self, pre, post, conn, g_max=1., delay=0., tau=8.0, E=0., **kwargs):\n", + " super(Exponential, self).__init__(pre=pre, post=post, conn=conn, **kwargs)\n", "\n", - "group = HH(100, monitors=['V'])\n", - "group.run(200., inputs=('input', 10.))\n", - "bp.visualize.line_plot(group.mon.ts, group.mon.V, show=True)" + " # parameters\n", + " self.g_max = g_max\n", + " self.E = E\n", + " self.tau = tau\n", + " self.delay = delay\n", + "\n", + " # connections\n", + " self.pre_ids, self.post_ids = self.conn.requires('pre_ids', 'post_ids')\n", + " self.num = len(self.pre_ids)\n", + " \n", + " # variables\n", + " self.s = bp.math.Variable(bp.math.zeros(self.num))\n", + " self.pre_spike = self.register_constant_delay('ps', size=self.pre.num, delay=delay)\n", + "\n", + " @bp.odeint(method='exponential_euler')\n", + " def int_s(self, s, t):\n", + " dsdt = - s / self.tau\n", + " return dsdt\n", + "\n", + " def update(self, _t, _dt):\n", + " # P1: push the pre-synaptic spikes into the delay\n", + " self.pre_spike.push(self.pre.spike)\n", + " # P2: pull the delayed pre-synaptic spikes\n", + " delayed_pre_spike = self.pre_spike.pull()\n", + " # P3: update the synatic state\n", + " self.s[:] = self.int_s(self.s, _t)\n", + " for syn_i in range(self.num):\n", + " pre_i, post_i = self.pre_ids[syn_i], self.post_ids[syn_i]\n", + " # P4: whether pre-synaptic neuron generates a spike\n", + " if delayed_pre_spike[pre_i]:\n", + " self.s[syn_i] += 1.\n", + " # P5: output the synapse current onto the post-synaptic neuron\n", + " self.post.input[post_i] += self.g_max * self.s[syn_i] * (self.E - self.post.V[post_i])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## brainpy.TwoEndConn" + "Here, we create a synaptic model by using the synaptic structures of *pre_ids* and *post_ids* , looks like this:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "For synaptic connections, BrainPy provides `brainpy.TwoEndConn` to help you construct the projection between pre-synaptic and post-synaptic neuron groups, and provides `brainpy.connect.Connector` for synaptic connectivity between pre- and post- groups. \n", - "\n", - "- The benefit of using `brainpy.TwoEndConn` lies at the **automatical synaptic delay**. The synapse modeling usually includes a delay time (typically 0.3–0.5 ms) required for a neurotransmitter to be released from a presynaptic membrane, diffuse across the synaptic cleft, and bind to a receptor site on the post-synaptic membrane. BrainPy provides `register_constant_dely()` for automatical state delay. \n", - "\n", - "- Another benefit of using `brainpy.connect.Connector` lies at the **connectivity structure construction**. `brainpy.connect.Connector` provides various synaptic structures, like \"pre_ids\", \"post_ids\", \"conn_mat\", \"pre2post\", \"post2pre\", \"pre2syn\", \"post2syn\", \"pre_slice\", and \"post_slice\". Users can \"requires\" such data structures by calling `connector.requires('pre_ids', 'post_ids', ...)`. We will detail this function in [Synaptic Connections](../tutorials/efficient_synaptic_computation.ipynb)." + "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Here, let's illustrate how to use `brainpy.TwoEndConn` with the AMPA synapse model." + "The pre-synaptic neuron index (``pre ids``) is shown in the green color. The post-synaptic neuron index (``post ids``) is shown in the red color. Each pair of *(pre id, post id)* denotes a synapse between two neuron groups. Each synapse connection also has a unique index, called the *synapse index*, which is shown in the third row (``syn ids``). " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### AMPA Synapse Model" + "## ``brainpy.Network``" ] }, { - "cell_type": "code", - "execution_count": 11, - "metadata": { - "ExecuteTime": { - "end_time": "2021-03-25T03:03:12.401462Z", - "start_time": "2021-03-25T03:03:12.369072Z" - } - }, - "outputs": [], + "cell_type": "markdown", + "metadata": {}, "source": [ - "class AMPA(bp.TwoEndConn):\n", - " def __init__(self, pre, post, conn, delay=0., g_max=0.10, E=0., tau=2.0, **kwargs):\n", - " super(AMPA, self).__init__(pre=pre, post=post, conn=conn, **kwargs)\n", - "\n", - " # parameters\n", - " self.g_max = g_max\n", - " self.E = E\n", - " self.tau = tau\n", - " self.delay = delay\n", - "\n", - " # connections\n", - " self.conn = conn(pre.size, post.size)\n", - " self.conn_mat = conn.requires('conn_mat')\n", - " size = bp.math.shape(self.conn_mat)\n", - "\n", - " # variables\n", - " self.s = bp.math.Variable(bp.math.zeros(size))\n", - " self.g = self.register_constant_delay('g', size=size, delay=delay)\n", - "\n", - " @bp.odeint(dt=0.01)\n", - " def int_s(self, s, t):\n", - " return - s / self.tau\n", - "\n", - " def update(self, _t, _i):\n", - " self.s[:] = self.int_s(self.s, _t)\n", - " for i in range(self.pre.size[0]):\n", - " if self.pre.spike[i] > 0:\n", - " self.s[i] += self.conn_mat[i]\n", - " self.g.push(self.g_max * self.s)\n", - " g = self.g.pull()\n", - " self.post.input[:] -= bp.math.sum(g, axis=0) * (self.post.V - self.E)" + "In above, we have illustrated how to define **neurons** by [brainpy.NeuGroup](../apis/simulation/generated/brainpy.simulation.brainobjects.NeuGroup.rst) and **synapses** by [brainpy.TwoEndConn](../apis/simulation/generated/brainpy.simulation.brainobjects.TwoEndConn.rst). In the next, we talk about how to create a network by using [brainpy.Network](../apis/simulation/generated/brainpy.simulation.brainobjects.Network.rst). " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "To define a two-end synaptic projection is very much like the NeuGroup. Users need to inherit the `brainpy.TwoEndConn`, and provide the \"target_backend\" specification, \"update\" function and then \"super()\" initialize the parent class. But what different are two aspects: 1. connection. We need construct the synaptic connectivity by \"connector.requires\". 2. delay. We can register a constant delay variable by \"self.register_constant_delay()\"." + "### E/I balanced network" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Here, we create a matrix-based connectivity (with the shape of `(num_pre, num_post)`). " + "Here, we try to create a [E/I balanced network](https://brainmodels.readthedocs.io/en/latest/examples/EI_nets/Brette_2007_COBA.html) according to the reference [1]. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "" + "This EI network has 4000 leaky integrate-and-fire neurons. Each integrate-and-fire neuron is characterized by a time constant, $\\tau$ = 20 ms, and a resting membrane potential, $V_{rest}$ = -60 mV. Whenever the membrane potential crosses a spiking threshold of -50 mV, an action potential is generated and the membrane potential is reset to the resting potential, where it remains clamped for a 5 ms refractory period. " + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [], + "source": [ + "num_exc = 3200\n", + "num_inh = 800\n", + "\n", + "E = LIF(num_exc, tau=20, V_th=-50, V_rest=-60, V_reset=-60, t_refractory=5., monitors=['spike'])\n", + "I = LIF(num_inh, tau=20, V_th=-50, V_rest=-60, V_reset=-60, t_refractory=5.)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "And then register a delay variable \"self.g\" with the shape of `(num_pre, num_post)`." + "The ratio of the excitatory and inhibitory neurons are 4:1. The neurons connect to each other randomly with a connection probability of 2%. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## brainpy.Network" + "The kinetics of the synapse is governed by the exponential synapse model shown above. Specifically, synaptic time constants $\\tau_e$ = 5 ms for excitatory synapses and $\\tau_i$ = 10 ms for inhibitory synapse. The maximum synaptic conductance is $0.6$ for the excitatory synapse and $6.7$ for the inhibitory synapse. Reversal potentials are $E_e$ = 0 mV and $E_i$ = -80 mV. " + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [], + "source": [ + "E2E = Exponential(E, E, bp.connect.FixedProb(prob=0.02), E=0., g_max=0.6, tau=5)\n", + "E2I = Exponential(E, I, bp.connect.FixedProb(prob=0.02), E=0., g_max=0.6, tau=5)\n", + "I2E = Exponential(I, E, bp.connect.FixedProb(prob=0.02), E=-80., g_max=6.7, tau=10)\n", + "I2I = Exponential(I, I, bp.connect.FixedProb(prob=0.02), E=-80., g_max=6.7, tau=10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Now, let's put the above defined HH model and AMPA synapse together to construct a network with `brainpy.Network`." + "After this, we can create a network to wrap these object together. " ] }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 28, "metadata": { - "ExecuteTime": { - "end_time": "2021-03-25T03:03:12.440969Z", - "start_time": "2021-03-25T03:03:12.421049Z" - } + "scrolled": false }, "outputs": [], "source": [ - "group = HH(10, monitors=['V', 'spike'])\n", - "syn = AMPA(pre=group, post=group, conn=bp.connect.All2All(), delay=1.5, monitors=['s'])" + "net = bp.Network(E2E, E2I, I2I, I2E, E=E, I=I)\n", + "net = bp.math.jit(net)" ] }, { "cell_type": "code", - "execution_count": 14, - "metadata": { - "ExecuteTime": { - "end_time": "2021-03-25T03:03:14.861988Z", - "start_time": "2021-03-25T03:03:12.443012Z" - } - }, + "execution_count": 29, + "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Compilation used 0.3254 s.\n", + "Compilation used 4.5976 s.\n", "Start running ...\n", - "Run 10.0% used 0.060 s.\n", - "Run 20.0% used 0.120 s.\n", - "Run 30.0% used 0.180 s.\n", - "Run 40.0% used 0.250 s.\n", - "Run 50.0% used 0.310 s.\n", - "Run 60.0% used 0.370 s.\n", - "Run 70.0% used 0.420 s.\n", - "Run 80.0% used 0.490 s.\n", - "Run 90.0% used 0.547 s.\n", - "Run 100.0% used 0.611 s.\n", - "Simulation is done in 0.611 s.\n", + "Run 10.0% used 1.305 s.\n", + "Run 20.0% used 2.613 s.\n", + "Run 30.0% used 3.945 s.\n", + "Run 40.0% used 5.276 s.\n", + "Run 50.0% used 6.630 s.\n", + "Run 60.0% used 8.041 s.\n", + "Run 70.0% used 9.364 s.\n", + "Run 80.0% used 10.709 s.\n", + "Run 90.0% used 12.052 s.\n", + "Run 100.0% used 13.378 s.\n", + "Simulation is done in 13.378 s.\n", "\n" ] }, { "data": { + "image/png": "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\n", "text/plain": [ - "0.6110615730285645" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "net = bp.Network(group, syn)\n", - "net.run(duration=200., inputs=(group, \"input\", 20.), report=True)" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": { - "ExecuteTime": { - "end_time": "2021-03-25T03:03:15.584605Z", - "start_time": "2021-03-25T03:03:14.865850Z" - }, - "scrolled": false - }, - "outputs": [ - { - "data": { - "image/png": "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nXMjnHMJzrkS59yfnHN3hJIjQr3XbnXOjXXOTXfOReEEa46slARSE+N4fqWq2UQk9jgH04ozCRi8oOuYeGz69Om89NJLfOMb3+D1118nKyuLT3ziE9xzzz3s3buXt99+m/PPPx+AxMRELrroIgDmzJnDpk2bDtremDFj2LBhA1/60pd47rnnyMzMPGidnkpOw9VQPaomqx1sSfEBThyXy4urdvDf86YRCKj1v4jElmFpScwtG8bzK3fwr+dM9DociRKHKumJlAkTJrB48WKeeeYZvvWtb3HOOefw6U9/mosvvpjk5GSuuuoq4uODaUdCQsL+RCYuLo62tjba29uZM2cOAJdccgm33XYby5Yt4/nnn+c3v/kNDzzwAN///ve5+OKLAfj85z/PzJkz2br1o07z5eXlFBUVheV4fJsgdSad504dzjMrtvPe1r3MGZXjbVAiIn3Qee987tTh/PdTq9i0q56yvDRPYxL/qqysZNiwYVx//fWkp6dz1113UVRURFFREf/zP//Diy++eMifj4uLY+nSpfvf79q1i8TERK644grGjh3LTTfdRGlp6QHrtLW1sW7dOjZu3EhxcTHz58/n3nvvDcvx+DZBAjCD0ycVkBBnPL9yuxIkEYk5BpwzpZD/fmoVz6/czudOHet1SOJTK1as4Gtf+xqBQICEhAR+97vfAXDddddRVVXFlClT+rS9iooKbr75Zjo6gh2p/vd///egdeLj4/n1r3/NueeeS3t7O5/85CeZOjU8pWe+TpAAMpMTOGFsHs+v3M63zp+kQbZEJOaUDktlalGmEiTx1Lnnnsu555570PI33niDz3zmMwcsq6ur2//6yiuv5Morrzzo52bOnMmSJYefxvWCCy7gggsu6EfEhxapbv5Rr2u7rnOnDmdzdQNrd9R6F5CISF85R+c93blTh7Nky1527NNEpRI95syZw/Lly7n++uu9DqXPfJsgAVhowO+zpxQSMHhmhXqBiEhsOn/acACeXRGVQ86JTy1evJjXXnuNpKQkr0PpM98mSK7LzCf5GUkcNyaXp5ZXarA1EYkZXa9W4wszmFiYwVPLlSCJhINvEySArs2NLppRxIaqelZtC+/AWiIikdS11eRFM0awaPMeKvc2ehaPeEs3+T3rz+/F1wlSV+dNG05cwHT3JSIx66KZwfFfnlE1my8lJydTXV2tJKkb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\n", - "text/plain": [ - "
" + "
" ] }, "metadata": { @@ -685,13 +1227,17 @@ } ], "source": [ - "fig, gs = bp.visualize.get_figure(2, 1, 3, 8)\n", - "\n", - "fig.add_subplot(gs[0, 0])\n", - "bp.visualize.line_plot(group.mon.ts, group.mon.V, legend='pre-V')\n", + "net.run(100., inputs=[('E.input', 20.), ('I.input', 20.)], report=0.1)\n", + "bp.visualize.raster_plot(E.mon.ts, E.mon.spike, show=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**References**: \n", "\n", - "fig.add_subplot(gs[1, 0])\n", - "bp.visualize.line_plot(syn.mon.ts, syn.mon.s, legend='syn-s', show=True)" + "[1] Brette, R., Rudolph, M., Carnevale, T., Hines, M., Beeman, D., Bower, J. M., et al. (2007), Simulation of networks of spiking neurons: a review of tools and strategies., J. Comput. Neurosci., 23, 3, 349–98\n" ] }, { @@ -700,11 +1246,8 @@ "source": [ "---\n", "\n", - "**Author**:\n", - "\n", - "- Chaoming Wang\n", - "- Email: adaduo@outlook.com\n", - "- Date: 2021.03.25, update at 2021.05.29\n", + "- Chaoming Wang (adaduo@outlook.com)\n", + "- Update at 2021.09.08\n", "\n", "---" ] diff --git a/docs/quickstart/jit_compilation.ipynb b/docs/quickstart/jit_compilation.ipynb index bf0421ff..46643dfb 100644 --- a/docs/quickstart/jit_compilation.ipynb +++ b/docs/quickstart/jit_compilation.ipynb @@ -26,11 +26,14 @@ "metadata": {}, "outputs": [ { - "name": "stderr", - "output_type": "stream", - "text": [ - "WARNING:absl:No GPU/TPU found, falling back to CPU. (Set TF_CPP_MIN_LOG_LEVEL=0 and rerun for more info.)\n" - ] + "data": { + "text/plain": [ + "'1.1.0rc1'" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ @@ -376,9 +379,9 @@ { "data": { "text/plain": [ - "JaxArray(DeviceArray([[0.84606385, 0.14539516, 0.98411 , 0.5173148 , 0.9132446 ],\n", - " [0.39373338, 0.70007217, 0.524508 , 0.25626922, 0.9771589 ],\n", - " [0.21962452, 0.14170194, 0.87090707, 0.31382847, 0.44447434]], dtype=float32))" + "JaxArray(DeviceArray([[0.3719796 , 0.40873682, 0.92993236, 0.9059397 , 0.6716608 ],\n", + " [0.84367204, 0.03071105, 0.36278927, 0.52648306, 0.64811254],\n", + " [0.57296276, 0.76439524, 0.05158341, 0.947039 , 0.640892 ]], dtype=float32))" ] }, "execution_count": 10, @@ -398,10 +401,10 @@ { "data": { "text/plain": [ - "JaxArray(DeviceArray([[ 2.7446158 , -0.86415875, 2.2743175 , 0.87442636,\n", - " 3.4002311 ],\n", - " [ 2.7979205 , -1.6518768 , -0.7373221 , 0.7196598 ,\n", - " -0.12697993]], dtype=float32))" + "JaxArray(DeviceArray([[ 1.4749131 , 0.6137249 , -0.70614064, 2.148907 ,\n", + " -0.10390168],\n", + " [-0.11172882, -2.085571 , -2.9504242 , 1.7263914 ,\n", + " 3.2560937 ]], dtype=float32))" ] }, "execution_count": 11, @@ -430,8 +433,10 @@ { "data": { "text/plain": [ - "JaxArray(DeviceArray([[ 8.340457 , -4.1679125, 0.7996733, 2.313746 , 3.1462712],\n", - " [19.42553 , -9.199984 , 3.873664 , 5.5019183, 9.692774 ]], dtype=float32))" + "JaxArray(DeviceArray([[ 1.2514555, -3.5574172, -6.606989 , 5.60169 ,\n", + " 6.4082856],\n", + " [ 3.9778242, -6.5011096, -13.920118 , 13.352286 ,\n", + " 12.71267 ]], dtype=float32))" ] }, "execution_count": 12, @@ -561,7 +566,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "279 µs ± 1.36 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)\n" + "292 µs ± 7.83 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)\n" ] } ], @@ -583,7 +588,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "68.4 µs ± 901 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)\n" + "66.4 µs ± 170 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)\n" ] } ], @@ -614,7 +619,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "3.48 ms ± 17.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" + "3.53 ms ± 55 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n" ] } ], @@ -634,7 +639,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "1.88 ms ± 68.6 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" + "1.91 ms ± 65.7 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)\n" ] } ], @@ -703,7 +708,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Logistic Regression model without jit used time 18.506070852279663 s\n" + "Logistic Regression model without jit used time 19.143301725387573 s\n" ] } ], @@ -732,7 +737,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Logistic Regression model with jit used time 11.58539867401123 s\n" + "Logistic Regression model with jit used time 11.75181531906128 s\n" ] } ], @@ -759,7 +764,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Logistic Regression model with jit+parallel used time 7.264842987060547 s\n" + "Logistic Regression model with jit+parallel used time 7.351796865463257 s\n" ] } ], @@ -873,8 +878,8 @@ "\n", "The namespace of the above function:\n", "------------------------------------\n", - "{'Demo20': <__main__.Demo2 object at 0x7ff00c1cba30>,\n", - " 'update': CPUDispatcher()}\n", + "{'Demo20': <__main__.Demo2 object at 0x7f588c079f40>,\n", + " 'update': CPUDispatcher()}\n", "\n" ] }, @@ -985,7 +990,7 @@ { "data": { "text/plain": [ - "140668971726128" + "140016271963120" ] }, "execution_count": 29, @@ -1020,7 +1025,7 @@ { "data": { "text/plain": [ - "140668971726128" + "140016271963120" ] }, "execution_count": 30, @@ -1042,7 +1047,7 @@ { "data": { "text/plain": [ - "140668971726128" + "140016271963120" ] }, "execution_count": 31, @@ -1064,7 +1069,7 @@ { "data": { "text/plain": [ - "140668971726128" + "140016271963120" ] }, "execution_count": 32, @@ -1086,7 +1091,7 @@ { "data": { "text/plain": [ - "140668971726128" + "140016271963120" ] }, "execution_count": 33, @@ -1108,7 +1113,7 @@ { "data": { "text/plain": [ - "140668971726128" + "140016271963120" ] }, "execution_count": 34, @@ -1149,7 +1154,7 @@ { "data": { "text/plain": [ - "140668971726128" + "140016271963120" ] }, "execution_count": 35, @@ -1171,7 +1176,7 @@ { "data": { "text/plain": [ - "140668971726128" + "140016271963120" ] }, "execution_count": 36, @@ -1193,7 +1198,7 @@ { "data": { "text/plain": [ - "140668971726128" + "140016271963120" ] }, "execution_count": 37, @@ -1215,7 +1220,7 @@ { "data": { "text/plain": [ - "140668971726128" + "140016271963120" ] }, "execution_count": 38, @@ -1237,7 +1242,7 @@ { "data": { "text/plain": [ - "140668971726128" + "140016271963120" ] }, "execution_count": 39, @@ -1257,6 +1262,18 @@ "source": [ "More advanced usage please see our forthcoming tutorials. " ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "\n", + "- Chaoming Wang (adaduo@outlook.com)\n", + "- Update at 2021.09.06\n", + "\n", + "---" + ] } ], "metadata": { diff --git a/docs/quickstart/numerical_solvers.ipynb b/docs/quickstart/numerical_solvers.ipynb index 5f4b926b..6e47599c 100644 --- a/docs/quickstart/numerical_solvers.ipynb +++ b/docs/quickstart/numerical_solvers.ipynb @@ -15,7 +15,7 @@ "source": [ "Brain modeling toolkit provided in BrainPy is focused on **differential equations**. How to solve differential equations is the essence of the neurodynamics simulation. The exact algebraic solutions are only available for low-order differential equations. For the coupled high-dimensional non-linear brain dynamical systems, we need to resort to using numerical methods for solving such differential equations. \n", "\n", - "In this section, I will illustrate how to define ordinary differential quations (ODEs), stochastic differential equations (SDEs), and how to define the numerical integration methods in BrainPy for these difined DEs." + "In this section, I will illustrate how to define ordinary differential quations (ODEs), stochastic differential equations (SDEs), and how to define the numerical integration methods for them in BrainPy." ] }, { @@ -32,7 +32,7 @@ { "data": { "text/plain": [ - "'1.1.0-alpha'" + "'1.1.0rc1'" ] }, "execution_count": 1, @@ -41,26 +41,11 @@ } ], "source": [ - "import sys\n", - "sys.path.append('../../')\n", - "\n", "import brainpy as bp\n", "\n", "bp.__version__" ] }, - { - "cell_type": "code", - "execution_count": 2, - "id": "72fcea24", - "metadata": {}, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "\n", - "%matplotlib inline" - ] - }, { "cell_type": "markdown", "id": "opposite-mixer", @@ -94,7 +79,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "id": "failing-headset", "metadata": { "ExecuteTime": { @@ -117,12 +102,12 @@ "source": [ "where `t` denotes the current time, `p1` and `p2` which after the `t` are represented as parameters needed in this system, and `x` and `y` passed before `t` denotes the dynamical variables. In the function body, the derivative for each variable can be customized by the user need `f1` and `f2`. Finally, we return the corresponding derivatives `dx` and `dy` with the order the same as the variables in the function arguments.\n", "\n", - "For each variable `x` or `y`, it can be a scalar (`var_type = bp.SCALAR_VAR`), a vector/matrix (`var_type = bp.POPU_VAR`), or a system (`var_type = bp.SYSTEM_VAR`). Here, the \"system\" means that the argument `x` denotes an array of vairables. Take the above example as the demonstration again, we can redefine it as:" + "For each variable `x` or `y`, it can be a scalar (`var_type = bp.integrators.SCALAR_VAR`), a vector/matrix (`var_type = bp.integrators.POP_VAR`), or a system (`var_type = bp.integrators.SYSTEM_VAR`). Here, the \"system\" means that the argument `x` denotes an array of variables. Take the above example as the demonstration again, we can redefine it as:" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "id": "historical-chapel", "metadata": { "ExecuteTime": { @@ -157,7 +142,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "id": "apparent-structure", "metadata": { "ExecuteTime": { @@ -189,7 +174,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "id": "artificial-curtis", "metadata": { "ExecuteTime": { @@ -223,7 +208,7 @@ " 'ssprk3']" ] }, - "execution_count": 6, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -242,7 +227,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "id": "bronze-sport", "metadata": { "ExecuteTime": { @@ -286,7 +271,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "id": "saved-participation", "metadata": { "ExecuteTime": { @@ -313,7 +298,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "id": "annual-wrestling", "metadata": { "ExecuteTime": { @@ -334,6 +319,18 @@ "the solution of the FHN model between 0 and 100 ms can be approximated by " ] }, + { + "cell_type": "code", + "execution_count": 9, + "id": "72fcea24", + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "%matplotlib inline" + ] + }, { "cell_type": "code", "execution_count": 10, @@ -348,7 +345,7 @@ { "data": { "text/plain": [ - "[]" + "[]" ] }, "execution_count": 10, @@ -357,7 +354,7 @@ }, { "data": { - "image/png": "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\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXwAAAD4CAYAAADvsV2wAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8QVMy6AAAACXBIWXMAAAsTAAALEwEAmpwYAAA7X0lEQVR4nO2dd3xb1d3/319Zlm157wxn74RsJ4Rd9myBFigrBAiFPC2l/Gj7QB+626eLtk9LC4RAGAEaSkvZYZeySeJMsuOEDGfYjrcl21rn94ck47rekqWre8/79fIrlnStc26O9Lnf+z3fIUopNBqNRmN+bPGegEaj0WhigxZ8jUajsQha8DUajcYiaMHXaDQai6AFX6PRaCyCPd4T6ImCggI1evToeE9Do9FoEoZ169YdU0oVdvWaoQV/9OjRlJWVxXsaGo1GkzCIyP7uXtMuHY1Go7EIWvA1Go3GImjB12g0GougBV+j0WgsghZ8jUajsQgRC76IjBCRd0Rku4hsFZFvdXGMiMi9IlIuIptFZE6k42o0Go2mf0QjLNMHfFsptV5EMoF1IvKmUmpbh2POByaEfo4HHgj9q9FoNJoYEbGFr5Q6opRaH/q9CdgODO902MXAChXkEyBHRIZGOnZ/qahzs/TdPazeWxProTUajSbuRDXxSkRGA7OB1Z1eGg4c7PC4IvTckS7e42bgZoCRI0dGbW7NbT6+8sBHVDa2AXDtgpH8+IvTsCfpbQyNRmMNoqZ2IpIBPAvcrpRq7PxyF3/SZecVpdQypVSpUqq0sLDL7OAB8dyGQ1Q2trHixvncfOpYnvzkALf+ZQNtPn/UxtBoNBojExULX0SSCYr9U0qpf3RxSAUwosPjEuBwNMbuK29tq2R0vpNTJhRw6sRChmSl8tOXt3Hbyg38+eo5JGtLP2FoavXy0PufgVJcu2AURVmp8Z6Sph/srW5mxcf7GVOQzpXzR5BiT4r3lCxDxIIvIgIsB7YrpX7fzWEvAreKyNMEN2sblFL/4c4ZLJRSfHqogbOnFBOcLtx48hhsAj9+aRt3PLOJP3x1Fkm2rm5ENEbju3/bzGtbj2ITeOKT/dx/zVxOGJcf72lp+oCrzcfVD62mqqmVgIK/rj3II9fPY0i2vmjHgmiYtScBC4EzRGRj6OcCEVkiIktCx6wC9gLlwEPA16Mwbp+pbm6j1uVh8tDMf3v++pPGcNf5k3lp02HufHYzgYDu72t0Dta6eW3rUW47Yzxv/L/TKMhIYdGja3hj69F4T03TB17efJijja2s/NoCHrqulP01Lr58/4fsr3HFe2qWIBpROh8opUQpNUMpNSv0s0optVQptTR0jFJKfUMpNU4pNV0pFdMSmLsrmwGYVJz5H68tOW0ct581gb+vq+AHL2xBN3U3Nm9uqwTg8tIRjC/K4JlbTmDK0Cy+/tR63gq9pjEub2ytpCQ3jflj8jh7ajF/veUEWrx+rlr2CQdr3fGenumxhOP6cH0LAMNz07p8/VtnTmDJaeN4avUBfvXqDi36BmZTRT1DslIZkecEIDfdwROL5zNtWFD0391VHecZanpiU0U9C8bmt7tWjxuezZM3HY/L4+fKZZ9QUadFfzCxhOBXNrYCUNzN5p6IcOd5k1i4YBQPvreX+94pj+X0NP3g04oGZpRk/9tzWanJrLjxeMYXZXDzijI+2nMsTrPT9ERVUyvHmj1MGZr1b89PG5bNUzcdT1Orl6sfWs3RhtY4zdD8WELwjzS0kutMJjW5+2gAEeEnX5rGl2cP57dv7OLRDz+L4Qw1fcHnD7C/1s3ELlxz2c5knlg8n1H5ThY/VkbZvto4zFDTEzuPNgEwZch/rt9xw7NZsfh4aprbuHb5amqa22I9PUtgCcGvbGzt1rrviM0m/OayGZw7rZifvLSNZ8oO9vo3mthxtLEVf0BR0o1rLj8jhSdvOp6h2anc8OhathxqiPEMNT1xqC7oWh2Z7+zy9Vkjclh+/TwO1rq57pE1NLR4Yzk9S2ARwW/rk+AD2JNs3HvVbE6ZUMBdz27mlc0xix7V9EJYMLrbiwEoykzlyZuOJystmYXLV7O7silW09P0wuGGVkSCa9QdC8bm8+DCueyqbOLGx9bi9vhiOEPzYwnBr3V5yE939Pn4FHsSDy6cy9xRudz+1w28s6NqEGen6SsVIcEvye3aQgwzLCeNp246HnuSjWuXr+ZAjd4INAJHG1ooyEjBYe9Zdr4wqYh7r5zNhgN1fG1FGa1enQ0fLSwh+I0tXrLSkvv1N06HneXXz2PSkEyWPLmOT3TBtbhzpCEo+EP7kKQzuiCdJxcfT5svwDXLP9EbgQbgSEMrw/qYYHX+9KH85rKZfFhew61/2YDXHxjk2VkD0wu+P6BoavP1W/Dh8+iPEXlOFj+2lo0H66M/QU2fqXV5yUix97j53pFJQzJ5/Ib51DZ7uHb5ampdnkGeoaYnjjS09iuj9rK5Jfzs4mm8tb2Sbz+zCb9OjIwY0wt+U2tw4yd7AIIPkJfu4MnFx5OX4WDRI2vaIw00safO7SE3vX/rOPPfNgJX09iqNwLjRa3LQ0FGSr/+ZuEJo7nzvMm8uOkwdz/3qc6RiRDTC354p3+ggg8wJDuVpxYvIDU56BPed0yngceDGpeHPGff92LCLBibz9KFc9l5tIkbH9UbgfEgEFDUuz3kOPv/PfyvL4zj1tPH8/Tag/z8le1a9CNAC34fGZnv5MnFx+MPKK55eHV79q4mdtS5POT1Y/O9I6dPKuIPX53N+gN13PLEOl0WO8Y0e3wEFOSkDWz9vn3ORK4/cTTLP/iMP7y1O8qzsw5a8PvBhOJMVtw4n8YWL9c+vJpjOjkkptS6POQOUPABLpwxlF99ZQbv7z7GbSs34NMbgTGjwR36Hg7AwodgYuQPL5rK5XNL+OPbu3novb3RnJ5l0ILfT44bns2jN8zjSEMrC5evaf8gawafOvfAXDoduaJ0BD+8aCqvb63kv3WF1JhRH/qe5ETwPbTZhF99ZQYXzhjK/67azlOr90drepZBC/4AKB2dx7Lr5rKnqpnrH1uDq037hAebVq8ft8cfkYUf5saTx/Dtsyfyj/WH+PFLW7VPOAbUtwQjpCJdvySb8H9XzOKMyUV8//ktPLehIhrTswymF/ym1qAYZ6ZGtX0vp0wo5N6rZrO5okEnh8SAxgijrTpz6xnjueXUsaz4eD/3vL4zKu+p6Z5oWPhhHHYb918zhwVj8vnO3zbzuu6F0GdML/huT1CI0/oYu90fzjtuCL+9fAYf7anh1r+s18khg4i7LbiO6SnRWUcR4a7zJ3PN8SO5/197uP9fukLqYFLfEpkPvzOpyUk8tKiU6cOz+ebKDXy8RydG9oWoCL6IPCIiVSKypZvXvyAiDR06Yv0wGuP2hRaPj9RkG7ZBal946ewSfnbJcby1vUonhwwirlAoZVpy9O7URISfXXwcl8waxm9e28mKj/dF7b01/059KOktmq7VjBQ7j90wj1F5Tr62okwXy+sD0bLwHwPO6+WY9zt0xPpplMbtlRavH6cjuu6czixcMIq7zg8mh3z/ed01azAI36lFy8IPY7MJ91w+k7OnFvOjF7fy0qbDUX1/TZDGVi+pybaoNyzPcTpYsXg+Wal2rn90jc6R6YWoCL5S6j3AkAXI3R7/oLhzOrPktHF84/RxrFxzgP/TccJRJyz4g3HxTk6y8aerZjNvdB53PLORD8t1A5Vo4/L4yUgZHMNraHYaK0I5Mtc9soaqRl03qTti6cM/QUQ2icirIjKtu4NE5GYRKRORsurqyNvVtXr9pDkGX/ABvnPOJK4oLeHet3frkLEo4w5FQkXbwg+TmpzEQ9eVMrYgg1ueWKfdA1HG3eYb1O/h+KIMHr1hPsea21j06FpdS78bYiX464FRSqmZwJ+A57s7UCm1TClVqpQqLSwsjHjgWFn4EPQJ/+LS6Zw5uYgfPL9FRw9EEVfYpTOI7rnstGQevzHsHliryypHEbfHP6hrB8EGKkuvnUt5VZOOnOuGmAi+UqpRKdUc+n0VkCwiBbEYu8UTOwsfgg1U/nT1bGaU5HDbyg2s1a32okK4/o1zkNdySHYqKxbPxxcIcN0jOps6Wrg9/kFfO4BTJxby28tnsnZfrc6m7oKYCL6IDJFQm3oRmR8aNyZxVC3e2Fn4YZwOO49cP4/hOWksfmwtu3TXpYhxtYdlDq6VCDC+KJPli+ZxtLGVGx5dqxProoDL4xv04IkwF88azo8umsob2yr5wQs6iKIj0QrLXAl8DEwSkQoRWSwiS0RkSeiQy4AtIrIJuBe4UsVoFVpiZFl0Ji/dweM3ziclOYlFj6xpb96hGRhujw+bQEov3ZKixdxRudx39Ry2HWlkyZPr8Pi0pRgJsf4eXn/SmFAQxUGWvqvr7oSJVpTOVUqpoUqpZKVUiVJquVJqqVJqaej1PyulpimlZiqlFiilPorGuH0hlj78zozIc/LYDfNoavWx6JE1uhZ7BLjagj7g0I1iTDhzSjG/vHQ67+8+xvef17XYI8Hl8cXk7qwj3zlnEl+aOYxfv7aDlzfrcFuwQKZtLKN0umLasGweXDiXvdUuvvkX7VMcKG6PD+cgRej0xBXzRnDr6eN5pqyCZbpC44Bxt8X+eygi/OayGZSOyuWOZzaxbn9dTMc3IqYX/Hha+GFOGl/Azy45jnd3VfPzV7bHdS6JiisGUR7dccfZE7lw+lB+9doOHXk1QIJROrH/HqYmJ7HsulKGZafytRVl7K+xdmKWqQVfKRXKtI2v4ANcNX8ki08ew2Mf7eMJncLfb9xt8bHwIZiN+7srZjKjJIfbn96oY/T7iT+gYpLx3h156Q4evWE+AaW44bG11Lut29vY1ILf6g26T1INIPgA/3PBFM6YXMSPX9rG+7sjTyqzEi6PD2cU6+j0l2Bi1lzy0h0sfnwtRxt0NmdfafGGs6Tj9z0cU5DOQ9eVUlHbwq0Wdq2aWvDbP2hxdumESbIJ9141mwlFGXz9qfWUVzXHe0oJg9vjj5uFH6YoM5WHF5XS3OrjphVrdWJPH2nPoYjxpm1n5o3O4+eXHscH5cf4jUVLYpta8MMftHhu2nYmI8XOw4tKcSTZWPLkOpp1jHefiEWmZl+YMjSLP145my2HGvmBLpTXJ9pLWxvge3hF6QiuO2EUy97bywsbD8V7OjHH1IIftsDSDCAUHSnJdfKnq2azt7qZO/++WYtGH3C3+QyxFwNw1tRivnnGeP62roK/rDkQ7+kYHld7lrQxvoc/uGgq80fnceezm9l62Fr7MaYW/BZP0E8X7yidrjhxfAHfPXcyr3x6hOUffBbv6Rgel8cf8zjunrj9rImcOrGQH7+4lQ0HdLhfT7R44u/D70hyko37rplDTpqDW55YR53LOpu4phb8WNVfGShLThvLudOK+eWrO1i9V3fs6Qm3xzgWPoT2Y66cRXFWKv/15Hpdc6cHXIPUyyASCjNTWLpwLpWNrXzXQnfZphb88KZtqgEtfAgmhtxz+UxG5jn55soN1FrI0ugPHl8Ar18ZysKHYPONpdfOpdbt4bt/22QZ0egv4dLWRnHphJk1Ioe7zp/CW9srefyjffGeTkwwt+Ab7FayK7JSk7nv6jnUu73c+ax1LI3+YOQ7teOGZ/O98yfzzs5qVnyseyB0hdvA38MbTxrNmZOL+MWqHZbIrzC34HsHr4F5NJk6LIvvnjuJN7dV8vTag/GejuGIRS38SLj+xNF8YVIh/7tqOzuP6sqonXEbbNO2I+G77Lx0B99cucH0lVFNLfhGtiw6s/jkMZw0Pp+fvrSNPdU6Pr8j7S4BA/mAOyIi3HPZTLJS7dy2coOOz++EEX34HclLd/CHK2exr8bFL181d+kTUwt++ItnlEzbnrDZhN9dPouUZBvffmYT/oB27YQxuoUPwU3Aey6fyc7KJv70T93TuCPhC3ZqlBuYR5MFY/O58aQxPPnJAT7aY96exqYW/LCFb3SXTpgh2an8+IvT2HiwnhW63k47n2/6GXsdT59UxFfmlPDgu3vZdrgx3tMxDOFuVzZb7EpbD4TvnDOJ0flO7nx2s2ldO6YW/Bavn+QkITkpcU7z4lnDOG1iIfe8vpND9bppCnR0CRjXwg/zg4umkONM5s5nN1u2XktnXJ74FU7rD2mOJH5z2Uwq6lq4x6SlFxJHCQdAiwFKI/cXEeF/Lz0OgO8/p5tugDFLZHRHjtPBj780jU8PNfDoh/viPR1DYLQcip6YPyaPhQtGseLjfabMwo1Wi8NHRKRKRLZ087qIyL0iUi4im0VkTjTG7Y1YNzCPFiW5Tu44eyLv7Kzm7e1V8Z5O3HEngA+/IxdOH8oZk4v449u7qW7SCVmxamAeLb599iRynA5+8uI20xlc0bLwHwPO6+H184EJoZ+bgQeiNG6PuONYgztSFp04mnGF6fxi1Xa8FncNuAwepdMZEeH7F06h1evn92+a0zXQH9xxaG8YCdnOZL577iTW7KvlxU3mao0YrZ627wG1PRxyMbBCBfkEyBGRodEYuydaPH7DZtn2RnKSjbsvnMLeYy6e/MTaCT3t4bUJtJZjCzNYdOJonl570JSugf7gakssCx+CVTWnD8/ml6t2mCrMNlY+/OFAx4yiitBz/4GI3CwiZSJSVl0dWZOQFm/i+A674vRJRZw8voA/vLXb0g3QXR4fKXYb9gTafAe47cwJ5Dod/OrVHfGeSlxpSTCXDgRrJd194RSONraayuCK1Teoq3isLp1jSqllSqlSpVRpYWFhRIMm4qZtR0SEu86fTEOLl8csvAHobjNWpcy+kp2WzJLTxvL+7mOs29/TDbC5cXl8CbP/0pEFY/M5ZUIB9/9rj2nCNGMl+BXAiA6PS4BBd465E3TTtiPHDc/mrCnFLP/gM5osauW7EijKozPXLhhFfrqDP7xl3WSsRP4e3nH2RGpdHh790BwlzGMl+C8C14WidRYADUqpI4M9aKs3sS38MN86cwINLV7LFudytxmj29VAcDrs3GJxKz/RNm07MntkLmdOLmL5B5+1F2NMZKIVlrkS+BiYJCIVIrJYRJaIyJLQIauAvUA58BDw9WiM2xvuBHfphJleks3pkwp55IPPTLWB1FdcHl/CROh0xbULRpHjTObh981hJfYHf0DR6g0k7B0awM2njqXO7eUfGyriPZWIiVaUzlVKqaFKqWSlVIlSarlSaqlSamnodaWU+oZSapxSarpSqiwa4/ZGizdxbyU7c+PJY6hxeXhl86DfGBkOo/SzHShOh50r543k9a1HLZc9beTS1n1l/pg8ZpRks/z9zwgkeI2rxAp76CeJmnjVFSePL2B8UQaPf7zPdMkgveEyUD/bgbLwhFGIiOVqJH3ekyJxL9giwuKTx7D3mIt/7UrsREjTCr7XH8AXUAkVu90TIsKiE0axuaKBjQfr4z2dmOI2WD/bgTA8J41zpxXz9JqDlnLLGb00cl+5YPpQCjIc/DXB+1WYVvDbK2UmuGXYkUvnlJCabOPv6xLfl9gfEjnKoyNXzhtJQ4uXd3YktpXYH1wGbW/YX5KTbFwyazhvb6+iJoH7F5tW8MNWlBmEIkxGip1zpg7h5c1H8PisU27B7fGRboJ1PGl8AUWZKfxjw6F4TyVmhLvOJbpLDuDy0hH4AornEnj9TCv4iVYLv69cOmd40ErcaQ0rMRBQoeJbiW0hQjB785LZw3lnR5VlGta7EqSXQV+YNCSTmSXZWvCNSItJBf+U8QUUZDh4YWPifuj6Q9hCTHQfcJhLZg3HF1C8usUa0VbuBOpl0BcunDGUrYcbOVjrjvdUBoR5Bd+bODXU+4M9ycbZU4fw7s5q2nzm3/xzGbgB9kCYMjSTkXlO3tpWGe+pxISwhZ/IYbUdOXfaEABe33o0zjMZGOYVfE/Qx20WoejIWVOKcHn8rN5r/sxNd5u5LHwR4eypxXy4p8Y09Vl6or3SqUkMr1H56UwZmqUF32iYIeGjO04aX0Bqso23t5vfSjSbhQ9w1pRiPL4A7++OrBpsIhBeP7O4dADOnVZM2f466hJwH8a0gt9iwiidMKnJSZw8vpC3d1SZPgkr0bpd9YV5o3PJSrXzzg7zC36Lx49NIMVuHqk5dWIhSsHHe2viPZV+Y55V6IRZo3TCnDqxgIq6FirqzJ2qH3Z7mOnCbU+ycfzY/IQUjP7iChW+E+mqQnpiMmN4Npkpdj4oPxbvqfQb0wp+i8l8h505fkw+AJ+YXDRcIR9+holcAgAnjM3nQK3b9LV13Ale+K4r7Ek2FozL50Mt+MbBzC4dgAlFGeSlO/jE5Bu3n/uAzbWOJ4wLXbD3mPyCbZIcis6cNC6f/TVuKuoSKzzTtILv9vhIsgmOBGuL11dsNmH+6DwLWPjmCusLM6k4k1xnsunXz22CwnddUTo6D4D1B+rjO5F+Yk41JBiWmZacZCrfYWfmjcnjUH0LlY2t8Z7KoGG2xJ0wNpswe2Qumyrq4z2VQSVR2xv2xuQhmaQm29ioBd8YtHh9pnXnhJlZkg3ApxUNcZ7J4OFq85GcJDhMFOURZkZJNuVVzaaOx3d7/Kbz4UPQjz9jeA4bDtbFeyr9Ilodr84TkZ0iUi4id3Xx+hdEpEFENoZ+fhiNcXsiWH/FfB+0jkwdloVNYPMhcwu+2az7MDNLcggo2GL29TOhhQ8wa2QOWw81JlTGe8SCLyJJwH3A+cBU4CoRmdrFoe8rpWaFfn4a6bi90WKS9oY94XTYmVCUyacmdgu4ErzbVU9MD92hbTbxHVqLiQ2vWSNy8PgD7DzaFO+p9JloWPjzgXKl1F6llAd4Grg4Cu8bEWZqb9gT00uy+fRQg2kTsMzQ7ao7CjJSGJ6TZu47NBM0r+mOyUMyASwn+MOBjm1gKkLPdeYEEdkkIq+KyLTu3kxEbhaRMhEpq64eeCaiFVw6ANOGZXGs2UN1Ajdl6AkzCwYERWN3ZeIIRn9xe8x7wR6Vn06K3WY5we8qDKazubkeGKWUmgn8CXi+uzdTSi1TSpUqpUoLCwsHPKmgS8e8QhFmfFEGAOVVzXGeyeAQ9OGbUzAAxhdnsLfahc9vvoY2Hl8Ar1+Z9oKdZBMmFGewM4Eu2NEQ/ApgRIfHJcDhjgcopRqVUs2h31cBySJSEIWxu8UqLp0JRcHbyj1mFnyT+vABJhZl4vEH2J+g9dV7wswFDMNMKs6ynIW/FpggImNExAFcCbzY8QARGSKhgHgRmR8ad1AzTtwen2kamPdEcVYKGSl2dptV8D3mjdIBmFAcvEMzo1vHZcLCd52ZNCSDqqa2hKmcGbHgK6V8wK3A68B24Bml1FYRWSIiS0KHXQZsEZFNwL3AlWqQdxlbTNL4ujdEhHFFGaZ16bjb/KZ26YwrDAu++dbPHW5vaOL1C7tU9x5zxXkmfSMql96Qm2ZVp+eWdvj9z8CfozFWX7GKSweCdXXe3WXOUrvNJnfppKfYGZ6TRnm1+QTfChb+qPx0APbXuJg7KjfOs+kd86UvAl5/cLPICi4dgDEF6VQ3tbX7TM2Czx+gzRcwtUsHYGSeM2F7pPaEGUtbd6YkNw0R2F+TGOtnSsE3e6XMzpTkpgGYrja+y+QlrsOMzHNyoNZcawfQ1BoU/MxU816wU+xJDMtOY39NYrh0zCn47UJh3g9aR0bkOQFMZyWG71jMVgu/MyPy0jjW3Nb+uTULTa1eALJSk+M8k8FlVL4zYaKsTCn4Zmuc3Bsjcs0p+K72TT+zC35o/RKstnpvWMHCh6AfX7t04khzq/kaJ/dEQYaDtOQkDprMpWMVwQgL/oEEEY2+El4/s9+hjcp3UuvytN/RGBlzCn6bNT5oYUSEktw001n4jSHByDK54I8MC77J1q+p1YvTkYTdpE2IwgzNTgXgaIPx+1KYciWsJvgQ3Lg1m4Xf2GINH3B+uoPUZBtHGsy1fk2tPtPfnQEMzQ4GTRxNgEZEphT8sO83wwIftjBDc9JM1/kq7BLISjO34IsIxVmpVDaaqwBec5vPEkZX2MI/oi38+NDUZs7G1z1RnJlKrcuDx2eeIlyNIZ+oFazE4szUhLAQ+0Njq5dMk9+dARRlpQDapRM3whZ+Zor5P2xhikMfOjOVSW5s8WK3iekb2UBQNKpMJvhWcemk2JMoyHBoCz9eNLf6sAmkJpvy9LqkOCt4W2kmt05YMMzciD7MkJBLx0yNbJpavabffwlTnJXK0QTYgzGlIjaH+qBaQSjCFGYGLXwzWYmNrV7T++/DFGel0uL1t0cmmQGrWPgQ9OMfTYA9GNMKfqYFNos68rmFb/wPXV9pbLGQhRja+DPTBbup1RqbtqAt/LjiajN3DfWuyE93kGQTqprMJRhWsRCLQ3doZrlg+/wBWrx+S2zaQrA/cX2L1/Cdy0wp+M1tPkuFZALYbEJRZoppBANCLh2LCEZR6A7NLBdsq2RJh8nPcKAU1LmNnW1rSsG30q1kRwozU6hqMpHgt/jISrPGOualOwCoTZDOSb1R5w6eR266NS7Y+enBOzSjr19UBF9EzhORnSJSLiJ3dfG6iMi9odc3i8icaIzbHS6LJHx0Ji/dQb3b2B+4/tBkkThuCJaPSE4SwwtGXwlbujlOR5xnEhvyM4LnWWPwsOiIBV9EkoD7gPOBqcBVIjK102HnAxNCPzcDD0Q6bk80W9CHD5DndJhGMLz+AC6P3zIuHREh10TrFzY8cq0i+KE7tGMGX79oWPjzgXKl1F6llAd4Gri40zEXAytUkE+AHBEZGoWxu8QqKd2dyU13JEwz5d4In0dehjUEA4J3aDVmWb+QhZ/rtMYFOz8j5NIxu4UPDAcOdnhcEXquv8cAICI3i0iZiJRVVw+sT+tJ4wqYOixrQH+byOSlO3B5/LR6E7+RRm3IQsyziIUIwfUzywU7bOFbxaWTk5aMTTD8BTsagt9VdlPndMG+HBN8UqllSqlSpVRpYWHhgCa0dOFcrigdMaC/TWTCt8/1Bo8U6Ath10Z4M9MK5Kabx6VT5/aQZBPTl7YOY7MJeekplhD8CqCjupYAhwdwjCZC8kIREWYQjTpX8KJlJcHPT3e039kkOnVuLzlpyZbKds9Pd5h/0xZYC0wQkTEi4gCuBF7sdMyLwHWhaJ0FQINS6kgUxtZ0IGzh15lANGpdwS+OVcL6ILh+9W7jJ+/0hTqXhxyL+O/D5KYntxsqRiXi+y2llE9EbgVeB5KAR5RSW0VkSej1pcAq4AKgHHADN0Q6ruY/MVMsd60rvOlnIQs/I3zB9rbXRkpU6tweS60dQHZaMvuOGbtrWVQcbEqpVQRFveNzSzv8roBvRGMsTffkppvHwq9ze8hMtZNs8vZ4Hel4h5bogl/v9rb36rUK2WnJNLQY28K3zrfJAuSEKkvWNCe+4Ne6PO2xzVYhO7R+jQYXjb5Q6/JYJiQzjBZ8TUyxJ9nITLG3d4pKZGpdnvY7FqsQLgWd6OsXCChqXIl/l9JfstOSafH6Dd11Tgu+ychKS6axJfFrqte4PJaKwQfaQxgTff1q3R78AUVhhvUEHzC0la8F32RkptppSnALEaC6qa29V6hVMIuFXx0q4BeuAGoVsrTga2JNVmpywguG1x+gxtVGUabFBCPVHD78cMVWK7p0QAu+JoZkpdkT3iVwrLkNpT7v4mUVHHYbaclJCd/msN3Ct6jgG/mCrQXfZGSawMIPN3EptphLB8IX7MRev3ATF23hGw8t+CYjK9Xe3m0oUakM9XW1moUP5nDJVTe1kZFix+mwRh2dMFrwNTEnKy2ZplYvgUCXtekSgqr2TT9rWYhgjiirqqY2y1n3oDdtNXEgM9VOQIHLk7iiUdXYik0+bxtnJbJSEz+PorKh1XL+e4DkJBvpjiQt+JrYEY70SGS3TmVjK4WZKSTZrFNpMUzQwjeuYPSFQ/UtDM9Ni/c04kJGqh1Xm3G/e1rwTYYZYrkP17cyJNuaghH04RtXMHrD4wtQ2dhKSY411y89xU6TFnxNrMg0QbbmwTo3IyxqIYajdIL1BhOPow2tBBTWtfBTtIWviSGfu3QS08L3BxSH61ssV2kxTGZqMr6AoiVB21RW1AfLA5fkWnP9MlLsNBv4Dk0LvskIW/hG3jjqiaONrXj9ihEWFYx0RxIArrbEFPxDdS0ADLewS6dZW/iaWJEREnwj31b2xMHaoIU4Is+6ggGJu36H6oOCPzTHejkUAJla8DWxJCMsGJ7EtBDbBd+qFn5o/YwsGj1xsLaF4qwUUuxJ8Z5KXEg3uA8/olQ4EckD/gqMBvYBVyil6ro4bh/QBPgBn1KqNJJxNd2TlpyESOJaiAfrWhCBYRZ1CWQkuIW/91gzYwsy4j2NuJGRam4L/y7gbaXUBODt0OPuOF0pNUuL/eAiIqQ77AnrA95b3UxJbhoOuzVvPttdOgmYOKeUYm+1i7GF6fGeStzISLHj9SvafMb8/kX6rboYeDz0++PAJRG+nyYKOB1JCWshllc1M6EoM97TiBvhTdvmBLxg17g8NLR4GVtoXQu/ff0MGqkTqeAXK6WOAIT+LermOAW8ISLrROTmnt5QRG4WkTIRKauuro5wetYkI8WekBaiP6DYe8zF+CILC0bIwncn4AV7b7ULgHFWtvBDYdFGvcPu1YcvIm8BQ7p46e5+jHOSUuqwiBQBb4rIDqXUe10dqJRaBiwDKC0tTczskzjjTElMC/9grRuPL6AFn8TctN1b3QzAOAtb+BkpQQu/qc2YYdG9Cr5S6qzuXhORShEZqpQ6IiJDgapu3uNw6N8qEXkOmA90KfiayElUH/7uqqBgWFrwEzgOv7yqmRS7zbIb7gAZKca28CN16bwILAr9vgh4ofMBIpIuIpnh34FzgC0RjqvpgUR16eyuagKsLfj2JBupybaEXL+thxuZPCTTkkXvwqSnhPdgjGnhRyr4vwLOFpHdwNmhx4jIMBFZFTqmGPhARDYBa4BXlFKvRTiupgecBo8F7o6thxoZkZfWXh7CqqQ7jB3a1xVKKbYebmDa8Ox4TyWuhDPdjbrpHlEcvlKqBjizi+cPAxeEft8LzIxkHE3/yEhJSsjEq08PNTDd4oIBxk/e6YqKuhYaW31MG5YV76nElfY9GJNG6WgMiNOReILR4PZyoNbN9OE58Z5K3ElEwd96uBGAacOsfcE2emkMLfgmJD3FjtvjT6g2h58eagDQFj6hOzSDugS6Y+vhBmwCk4dYN4cCgu44MG6UlRZ8ExKO9HAnUIldLfifk56Am+7r9tcxdVgWqcnWrKETJskmpCUbNyxaC74JScTknbJ9tYwtTCfbae0NW0i8TVuvP8CGA/WUjsqL91QMgZEv2FrwTUhGgiXv+AOKNftqOX5MfrynYgjSEyxxbuvhRlq8fuaN1oIPxnbJacE3Ic4ES97ZfqSRplYfC8ZqwYDwpm1irB0E784ASkfnxnkmxsDIm+5a8E1IRoJVXFz9WVAwtIUfJJw4lyh9bT/ZW8vIPCfFWdZsetIZI7vktOCbEKfBQ8M68/GeGkblOxmSrQUDgmG1SpEQfW09vgAf7znGKRMK4j0Vw5CekmRYY0sLvgkJF3BKhOSrVq+fD8uPcdrEwnhPxTC0r18CuHXK9tXi8vj5wqTuCuVaDyO75LTgmxCnI3Es/I/31tDi9XPGZC0YYRJp/f61qxpHko0Tx2l3XJgM7cPXxBKjZ/t15J/bq3A6klgwVgtGmETqevWvnVXMG5PbPmeNsTPdteCbkEQpsauU4p87qjh5fIHlE3Y6kp4gLp3yqmZ2VTZz5uTieE/FUIRrWRkx010LvgmxJ9lIsdtwG9xCXH+gnkP1LZwzrav+OtYlUSz8lzcfRgQunDE03lMxFO2JjwbcdNeCb1KMnO0X5oWNh0ix2zh3mrYQO5KeAD58pRQvbz7CvNF5OhyzE0bOdNeCb1KCjcyNZ2GE8foDvLL5CGdNKSbT4vXvOxN26bgNvH47K5sor2rmi9q6/w+MnOmuBd+kGDlSAOCD8mPUuDxcPGtYvKdiOIxecRHgmbUVJCcJF0zXgt8ZI2e6RyT4InK5iGwVkYCIlPZw3HkislNEykXkrkjG1PQNp8O4yR8AK1cfID/dwWmTdPx9Z9pdAgZdv1avn2fXV3DutCHkZ6TEezqGw8wW/hbgy/TQkFxEkoD7gPOBqcBVIjI1wnE1vWDk5I/D9S28tb2SK+aNIMWuo3M647DbSE4SwybOvbblKA0tXq6aPzLeUzEkRr5gRyT4SqntSqmdvRw2HyhXSu1VSnmAp4GLIxlX0zvpDrshP3AAK9ccQAFXa8HoFiPHcv9l9QFG5Ts5QedOdEm6iS38vjAcONjhcUXouS4RkZtFpExEyqqrqwd9cmbFqBZ+m8/PyjUHOWNSESPynPGejmHJMOj6bTxYz5p9tSxcMAqbTeI9HUNi5DyKXtPjROQtoKtA6buVUi/0YYyuPhXdZiQopZYBywBKS0uNl7mQIBi1gNOz6w5xrLmNG04aE++pGJpglJXx1m/Ze3vITLVzpb476xYjZ7r3KvhKqbMiHKMCGNHhcQlwOML31PSCEV0CPn+Ape/uYWZJNieN1+6AnjBiHsW+Yy5e23KUJaeNa9+Y1Pwn7XkUBls/iI1LZy0wQUTGiIgDuBJ4MQbjWpqMlCS8foXHF4j3VNp55dMjHKh18/XTxyOi3QE9YcSuVw++txe7zcb1J42O91QMjZH72kYalnmpiFQAJwCviMjroeeHicgqAKWUD7gVeB3YDjyjlNoa2bQ1vRGuuGiUjVt/QHHfO+VMLM7g7Ck6s7Y3gpvuxvEB769x8beyg3x13giKMnVmbW+kpyTRnIg+/J5QSj0HPNfF84eBCzo8XgWsimQsTf/oGAuc43TEeTbwj/UV7Kps5r6r5+jNvj6QnmKsrkn/9+Yu7EnCN88YH++pJARGbXOoM21NijOcnm8AK7HV6+f3b+5i5ogcLpiuC6X1hfSUJEOsHcCOo428sOkw1584hiJdN6dPGDUsWgu+STFSLPDjH+3jSEMrd503Wfvu+4iR+qL++tUdZKTYWXLa2HhPJWHIMNgdWhgt+CYlHCkQ7wJcNc1t3PdOOadPKuQE3RWpzzgddjy+AF5/fDfd/7mjknd2VnPr6eMN4RpMFIKb7sa4Q+uIFnyT0l7AKc63lb9+bQduj5//uWBKXOeRaKQbwCXX5vPz05e2MbYwXedN9BOn9uFrYkmGAZI/1u2v5ZmyChafPIYJxZlxm0ciYoTkneUffMa+Gjc/+uI0HHYtFf0hw2G8PArQgm9awpu28SrA5fMH+P7zWxmancptZ06IyxwSmXgX4DpU38Kf/1nO2VOLOW2irmjaX4xa2kQLvkmJt4X/+Mf72X6kkR9cNFU3uB4A4b7E8YjlVkrxvX98CsAPL9KFbQdCRqi0iVLGqg6jBd+kpNqTEIlPm7V9x1zc8/oOTp9UyPnH6TDMgRDPNnnPrj/Ee7uq+e9zJ+kCdwPEmWJHKWOERXdEC75JsdkEZ3JSzF06gYDiu3/fRHKSjV9+eYYOwxwg8ep6VdXUys9e3kbpqFyuO2F0TMc2E0ZtRK8F38TEI9vv0Y/2sXZfHT/64jSGZOsknYESjygdpRQ/fH4rLV4/v75shs6IjoAMg5ZI1oJvYoIVF2P3gfss5Mo5Y3IRX5nTbcsDTR+IR+Lcs+sP8drWo9xx9kTGFWbEbFwz0l4x02ChmVrwTUx6SlLMfMAeX4Dbn96AI8nGL788XbtyIiTWUTr7jrn40QtbOH5MHl87RWfURopR+9rq8AkT44xhev7v3tzJpooG7r9mDsW63krEpCXHLkrH6w9w+183kmQT/u+rs0jSrpyIcRogj6IrtIVvYjJSYlNi971d1Tz47l6umj+SC6YPHfTxrEC4pnos7tDufXs3Gw/W88svz2BYTtqgj2cFMuKcB9MdWvBNTCza5FU3tXHHM5uYUJShY7ajTCz2YFbvreG+d8q5fG4JF87QF+toYYRM6a7QLh0Tkz7I6d2BgOI7f9tEU6uXJ2+aT1ooWUgTHQa761VVUyvfXLmBUfnp/PhL0wZtHCtiVMGPtOPV5SKyVUQCIlLaw3H7RORTEdkoImWRjKnpO+kp9kGtlvnAu3t4d1c1379wCpOHZA3aOFZlMGuq+/wBblu5gcZWL/dfM0dnQ0cZZ/sejLEEP9JV3gJ8GXiwD8eerpQ6FuF4mn6Q3iG9O9pRM+/vruZ3b+zkizOHce2CUVF9b02QYJu8wRGM3725i0/21vLby2cyZai+WEcbe5KN1GSb4TJtI21xuB3QIXgGJT3FTkBBqzcQVXdLRZ2b21ZuYEJRJr/+ig7BHCzSU+zUujxRf983t1XywL/2cNX8EVw2tyTq768JYsQmKLHatFXAGyKyTkRu7ulAEblZRMpEpKy6ujpG0zMnnxfgit6HrtXr5+tPrcfnVyxdOLe9Wbom+gxG16sDNW6+/cxGjhuexY++qP32g4kR+9r2+m0VkbeAripg3a2UeqGP45yklDosIkXAmyKyQyn1XlcHKqWWAcsASktLjVVqLsHITE0GoLHVS2FmSlTe88cvbmVzRQPLFs5lTEF6VN5T0zXBxLnouQRaPH7+66l1iAgPXDOX1GS9yT6YOB0JKPhKqbMiHUQpdTj0b5WIPAfMB7oUfE30yHEGBb/e7Y3K+/117QGeXnuQb5w+jnOm6SqYg01majINLdFZO6WCRe22HWnkkUXzdBXMGJBhwDaHg+7SEZF0EckM/w6cQ3CzVzPI5IZ6kNa7I/cDr9tfyw+e38rJ4wu44+xJEb+fpndyncm0eP20eiMXjfv/tYeXNx/hzvMmc/rkoijMTtMbwTwKY1n4kYZlXioiFcAJwCsi8nro+WEisip0WDHwgYhsAtYAryilXotkXE3fCFv4dRFa+IfqW7jliXUMzUnlz1fP1qn3MSI3PXzBjmz93txWyT2v7+SSWcO45VRdJydWpBtw0zbSKJ3ngOe6eP4wcEHo973AzEjG0QyMnChY+K42Hzc9XkabN8DTN5e2v6dm8MkL/V/XujwDLjW9q7KJ25/ewIySbH71Fd2fIJZkOOw0txpL8HVpBROTlWonySbUDVDwAwHFt5/ZxM6jjfzp6tmML9KNyGNJpBfsOpeHmx4vw5liZ9nCUr1JG2Ny0x3Uu72GanOoBd/EiAjZackDdun84a1dvLb1KHdfOJUvTNJ+31iTF3Lp1A5A8L3+AN/4y3qONrTy4MK5uhlNHCjIcODxB2g0kJWvBd/k5DiTaRiA4L+46TD3/rOcr5aO4MaTRkd/YppeyQ3vwQwg+ernL2/joz01/OLL05kzMjfaU9P0gYKMYCh0TXNbv/6uxePnSEPLYExJC77ZyXU6+u3S2XSwnu/+bRPzR+fxs0uO037fOBF26fT3Du2xDz/j8Y/387VTxuhM2jiSnxFcv2PNff/+BQKKO57ZyKX3fTQoMfxa8E1OfrqD6qa+WxhHG1r52ooyCjJSeODaOTjs+iMSLxx2Gxn9LK/wzx2V/PTlbZw9tZi7zp8yiLPT9MZALPx73tjJq1uOctMpYwaloJ3+NpucYTlpHGlo7dOxzW0+bnhsLW6Pn+XXl5KfEZ3sXM3AKcpKobKxb+u39XADt/5lA9OGZfPHK3XnqnjzuYXfN8F/Zu1BHvjXHq4+fiSLTx4zKHPSgm9yhman0tzmo6m1Z7eAzx/gm39Zz67KJu67Zo4ud2wQhuekcai+d39uZWMrix8rIzstmYcXleoaRwYgz+nAJvTpDvuj8mP8z3OfcsqEAn7ypWmD5kbVgm9yhoZa1vVk5Sul+MlL23hnZzU/vXgap00sjNX0NL1QkpvG4V4E3+3xsfjxtTS1elm+aJ7uKWwQ7Ek2hmancbCu5/Urr2pmyZPrGFuYzn3XzCE5afBkWQu+yRkWCsfrSTSWf/AZT3yyn1tOHcs1x+va9kZiWHYax5o93ZZX8AcUt63cyLbDjfz56jlMHabvzIzEyDwn+2tc3b5+rLmNGx9bi8NuY/mieWSFCh4OFlrwTU64SNb+GneXr7+25Sj/u2o7F0wfwp3nTY7l1DR9YHhu8A6tohsr8RertvPW9kp+9MVpukaOARmV7+RAbdffPbfHx+LH1lLV1MpD15XGpKCdFnyTU5SZQo4zmR1HG//jtQ0H6rj9rxuYWZLD76+YhU1v8hmOicXB7Oau1u/h9/ey/IPPuP7E0Sw6cXSMZ6bpC6Py0znW7PmPqqfBPbMNfHqogT9dNYfZMcqV0IJvckSEKUOy2Hak6d+e313ZxA2PraUwM4WHF+m0e6MysTiT5CRhy6F/F/znNxzi568E78x+cNHUOM1O0xszSrKBYG5LGKUUP3hhK2/vqOKnFx/H2VOLYzYfLfgWYNbIHLYeami3Mg7Wulm4fA12m40nFx/fHi+sMR4Ou42pQ7NY/VlN+3Pv7KjiO3/bxIKxefz+Ch1+aWRmjsjBJrB2Xy0QFPtfvbaDlWsO8I3Tx8W8H7QWfAtw1pRifAHFy5sPs/1II1c8+DEuj48VN85nVL7uWmV0zpk2hA0H6tlT3cwLGw9x8xNlTB6aybLr9J2Z0clIsTN/TB7PbzxEi8fPT17axoPv7uXaBSP5zjmx7yshRqrk1pnS0lJVVlYW72kkPEopLlv6MRtDt5V56Q4ev2G+juhIEKqaWjnzt+/S5g/g8QWYNzqXhxfNIzttcCM6NNHh7e2VLH68DIfdhscXYPHJY7j7gimDtmcmIuuUUqVdvqYF3xpUNbVy3z/LcabYuenkMTqLNsHYeLCepz7Zz7RhWVyzYNSgxmpros9zGyr4sLyGC6YP4YzJg+uz14Kv0Wg0FqEnwY+0xeE9IrJDRDaLyHMiktPNceeJyE4RKReRuyIZU6PRaDQDI9L7wjeB45RSM4BdwPc6HyAiScB9wPnAVOAqEdFxZBqNRhNjIhJ8pdQbSqlw0eZPgK6Kb88HypVSe5VSHuBp4OJIxtVoNBpN/4nmzs+NwKtdPD8cONjhcUXouS4RkZtFpExEyqqrq6M4PY1Go7E2vdZQFZG3gCFdvHS3UuqF0DF3Az7gqa7eoovnut0pVkotA5ZBcNO2t/lpNBqNpm/0KvhKqbN6el1EFgEXAWeqrkN+KoARHR6XAIf7M0mNRqPRRE6kUTrnAXcCX1JKdV0SDtYCE0RkjIg4gCuBFyMZV6PRaDT9J1If/p+BTOBNEdkoIksBRGSYiKwCCG3q3gq8DmwHnlFKbY1wXI1Go9H0E0MnXolINbB/gH9eAByL4nQSAX3O5sdq5wv6nPvLKKVUl23rDC34kSAiZd1lm5kVfc7mx2rnC/qco4kuyKHRaDQWQQu+RqPRWAQzC/6yeE8gDuhzNj9WO1/Q5xw1TOvD12g0Gs2/Y2YLX6PRaDQd0IKv0Wg0FsF0gm+F2vsiMkJE3hGR7SKyVUS+FXo+T0TeFJHdoX9z4z3XaCMiSSKyQUReDj029TmLSI6I/D3Ud2K7iJxggXP+f6HP9RYRWSkiqWY7ZxF5RESqRGRLh+e6PUcR+V5I03aKyLkDHddUgm+h2vs+4NtKqSnAAuAbofO8C3hbKTUBeDv02Gx8i2DGdhizn/MfgdeUUpOBmQTP3bTnLCLDgduAUqXUcUASwXIsZjvnx4DzOj3X5TmGvttXAtNCf3N/SOv6jakEH4vU3ldKHVFKrQ/93kRQBIYTPNfHQ4c9DlwSlwkOEiJSAlwIPNzhadOes4hkAacCywGUUh6lVD0mPucQdiBNROyAk2CxRVOds1LqPaC209PdnePFwNNKqTal1GdAOUGt6zdmE/x+1d43AyIyGpgNrAaKlVJHIHhRAIriOLXB4A/AfwOBDs+Z+ZzHAtXAoyE31sMiko6Jz1kpdQj4LXAAOAI0KKXewMTn3IHuzjFqumY2we9X7f1ER0QygGeB25VSjfGez2AiIhcBVUqpdfGeSwyxA3OAB5RSswEXie/K6JGQ3/piYAwwDEgXkWvjO6u4EzVdM5vgW6b2vogkExT7p5RS/wg9XSkiQ0OvDwWq4jW/QeAk4Esiso+gq+4MEXkSc59zBVChlFodevx3ghcAM5/zWcBnSqlqpZQX+AdwIuY+5zDdnWPUdM1sgm+J2vsiIgT9utuVUr/v8NKLwKLQ74uAF2I9t8FCKfU9pVSJUmo0wXX9p1LqWsx9zkeBgyIyKfTUmcA2THzOBF05C0TEGfqcn0lwj8rM5xymu3N8EbhSRFJEZAwwAVgzoBGUUqb6AS4AdgF7CLZhjPucBuEcTyZ4S7cZ2Bj6uQDIJ7i7vzv0b1685zpI5/8F4OXQ76Y+Z2AWUBZa6+eBXAuc80+AHcAW4AkgxWznDKwkuEfhJWjBL+7pHIG7Q5q2Ezh/oOPq0goajUZjEczm0tFoNBpNN2jB12g0GougBV+j0WgsghZ8jUajsQha8DUajcYiaMHXaDQai6AFX6PRaCzC/weOmC222Hn6egAAAABJRU5ErkJggg==\n", "text/plain": [ "
" ] @@ -464,6 +461,56 @@ "C=1.0; gNa=120.; gK=36.; gL=0.03" ] }, + { + "cell_type": "code", + "execution_count": 13, + "id": "fb23a248", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "hist_times = bp.math.arange(0, 100, 0.01)\n", + "hist_V, hist_m, hist_h, hist_n = [], [], [], []\n", + "V, m, h, n = 0., 0., 0., 0.\n", + "for t in hist_times:\n", + " V, m, h, n = integral(V, m, h, n, t, Iext, gNa, ENa, gK, EK, gL, EL, C)\n", + " hist_V.append(V)\n", + " hist_m.append(m)\n", + " hist_h.append(h)\n", + " hist_n.append(n)\n", + "\n", + "plt.subplot(211)\n", + "plt.plot(hist_times, hist_V, label='V')\n", + "plt.legend()\n", + "plt.subplot(212)\n", + "plt.plot(hist_times, hist_m, label='m')\n", + "plt.plot(hist_times, hist_h, label='h')\n", + "plt.plot(hist_times, hist_n, label='n')\n", + "plt.legend()" + ] + }, { "cell_type": "markdown", "id": "corrected-cream", @@ -506,7 +553,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 14, "id": "skilled-continuity", "metadata": { "ExecuteTime": { @@ -609,7 +656,7 @@ "id": "japanese-chart", "metadata": {}, "source": [ - "In BrainPy, these two different integrals can be easily implemented. What need the users do is to provide a keyword `sde_type` in decorator `bp.sdeint`. `sde_type` can be \"bp.STRA_SDE\" or \"bp.ITO_SDE\" (default). Also, the different type of Wiener process can also be easily distinguished by the `wiener_type` keyword. It can be \"bp.SCALAR_WIENER\" (default) or \"bp.VECTOR_WIENER\"." + "In BrainPy, these two different integrals can be easily implemented. What need the users do is to provide a keyword `sde_type` in decorator `bp.sdeint`. `intg_type` can be \"bp.integrators.STRA_SDE\" or \"bp.integrators.ITO_SDE\" (default). Also, the different type of Wiener process can also be easily distinguished by the `wiener_type` keyword. It can be \"bp.integrators.SCALAR_WIENER\" (default) or \"bp.integrators.VECTOR_WIENER\"." ] }, { @@ -622,7 +669,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 15, "id": "beginning-buying", "metadata": { "ExecuteTime": { @@ -652,7 +699,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 16, "id": "casual-architecture", "metadata": { "ExecuteTime": { @@ -683,7 +730,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 17, "id": "ready-conspiracy", "metadata": { "ExecuteTime": { @@ -734,7 +781,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 18, "id": "checked-greece", "metadata": { "ExecuteTime": { @@ -756,7 +803,8 @@ " dz = x * y - beta * z\n", " return dx, dy, dz\n", "\n", - "lorenz = bp.sdeint(f=lorenz_f, g=lorenz_g, \n", + "lorenz = bp.sdeint(f=lorenz_f, \n", + " g=lorenz_g, \n", " intg_type=bp.integrators.ITO_SDE,\n", " wiener_type=bp.integrators.SCALAR_WIENER,\n", " dt=0.005)" @@ -764,7 +812,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 19, "id": "thick-threat", "metadata": { "ExecuteTime": { @@ -780,13 +828,13 @@ "Text(0.5, 0, 'z')" ] }, - "execution_count": 18, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" }, { "data": { - "image/png": "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rVZz1llnsWLFilFHzEwFg8FAQUEBERERU2rWN9VYusTwtsMzyXipsNIUEIVCQaqdkcKzjU6nY+/evYSHh6PRaMatDGtra+OCCy7g0Ucf5eSTT57RczMajZxzzjmcfvrp/P73vwcgLS3tl2mWO0pXVxeffPIJ69evp6GhgdNOO43zzjtvWvZ9UolkcnLytBYKDO/cIgl9PLG2tbVRV1fn0OC56WZ4KmxAQAAajQZfX99D3nwCDnZzSU9Pl8swR+uf7uPjg06n4/zzz+df//oXp5122oyemyiKXHXVVQQGBvL444/Lj9955502mWddXV088sgjjhzylyFua3p7e9mwYQPr16+nsrKS5cuXs2rVKubPnz9hoUvVS446ZCaL9eooNRuwjqVLNDQ00N7e7lDceKYxmUzs3btXTjSa7e4tw7En7OFYJ/vccMMNNDQ0cNZZZ/HHP/6RhISEGT2/zZs3c/zxx5OZmSlfh//6179YvHixTebZmjVrHM2X+OWJ25qBgQG++OIL1q5dS0lJCSeffDIrV65k0aJF416AarVaLgWczSyv4bF0KS4tDXHPyMg4pEPm4eAUkICAAOLi4mxSYa292NPhW3AESdiONqDo6enh/PPP54YbbsBoNLJt2zZeeeWVQ255TJBftrit0Wq1fP3116xdu5a9e/dy/PHHs2rVKpYuXTpiFWxsbJQzqw5lqqHZbJZrwg0GAxEREYSFhR2yJobSOeXn58uFOsOxXh07OjpQKpVyhtxMVILp9Xq5vbUjwu7r6+OCCy7gtttu48ILL5z285lFnOK2h16vZ+PGjaxdu5YdO3awdOlSVq5cybHHHsu6devIyMg4JBlww7FYLJSUlODm5kZiYqIcS+/t7cXPz08uupitpBkpCy4iIsLhskcpT1+tVo8YezTVG5QkbEdTfwcGBli9ejU33ngjl1566ZTe25prr72WDRs2EBoaSlFREcBUMs8cxSnu8TAajfzwww988MEHbNiwgdTUVG677TZOPvnkGc9bHwtpTlZAQMCIAXhSModkBkux9ODg4Bm7IRmNRvbt2zduFtx4x5DCVdKWY7JppVKTR2lm+HgMDg6yevVqrrrqKq666qpJnf9obNq0CW9vb6688kpZ3HfddddkM88cxSluR7n00ktZuHAhCxcuZO3atfzwww9kZWWxatUqli1bNuv7bqnt8Hgr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\n", + "image/png": "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\n", "text/plain": [ "
" ] diff --git a/docs/tutorial_analysis/dynamics_analysis.ipynb b/docs/tutorial_analysis/dynamics_analysis.ipynb index e72010b2..134675e7 100644 --- a/docs/tutorial_analysis/dynamics_analysis.ipynb +++ b/docs/tutorial_analysis/dynamics_analysis.ipynb @@ -580,11 +580,8 @@ "source": [ "---\n", "\n", - "**Author**:\n", - "\n", - "- Chaoming Wang\n", - "- Email: adaduo@outlook.com\n", - "- Date: 2021.03.25\n", + "- Chaoming Wang (adaduo@outlook.com)\n", + "- Update at 2021.03.25\n", "\n", "---" ] @@ -611,7 +608,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.11" + "version": "3.8.10" }, "latex_envs": { "LaTeX_envs_menu_present": true, diff --git a/docs/tutorial_intg/ode_numerical_solvers.ipynb b/docs/tutorial_intg/ode_numerical_solvers.ipynb index 58d7bef2..58ee3b93 100644 --- a/docs/tutorial_intg/ode_numerical_solvers.ipynb +++ b/docs/tutorial_intg/ode_numerical_solvers.ipynb @@ -632,11 +632,8 @@ "source": [ "---\n", "\n", - "**Author**:\n", - "\n", - "- Chaoming Wang\n", - "- Email: adaduo@outlook.com\n", - "- Date: 2021.03.25, updated @2021.05.29 @2021.09.02\n", + "- Chaoming Wang (adaduo@outlook.com)\n", + "- Update at 2021.09.02\n", "\n", "---" ] @@ -662,7 +659,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.11" + "version": "3.8.10" }, "latex_envs": { "LaTeX_envs_menu_present": true, diff --git a/docs/tutorial_intg/sde_numerical_solvers.ipynb b/docs/tutorial_intg/sde_numerical_solvers.ipynb index df0930f2..0de42754 100644 --- a/docs/tutorial_intg/sde_numerical_solvers.ipynb +++ b/docs/tutorial_intg/sde_numerical_solvers.ipynb @@ -90,11 +90,8 @@ "source": [ "---\n", "\n", - "**Author**:\n", - "\n", - "- Chaoming Wang\n", - "- Email: adaduo@outlook.com\n", - "- Date: 2021.03.25, updated @2021.05.29 @2021.09.02\n", + "- Chaoming Wang (adaduo@outlook.com)\n", + "- Update at 2021.09.02\n", "\n", "---" ] @@ -120,7 +117,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.11" + "version": "3.8.10" }, "latex_envs": { "LaTeX_envs_menu_present": true, diff --git a/docs/tutorial_simulation/efficient_synaptic_computation.ipynb b/docs/tutorial_simulation/efficient_synaptic_computation.ipynb index 1b6b0875..6f9270e9 100644 --- a/docs/tutorial_simulation/efficient_synaptic_computation.ipynb +++ b/docs/tutorial_simulation/efficient_synaptic_computation.ipynb @@ -1297,11 +1297,10 @@ "\n", "[1] Vogels, T. P. and Abbott, L. F. (2005), Signal propagation and logic gating in networks of integrate-and-fire neurons., J. Neurosci., 25, 46, 10786–95\n", "\n", - "**Author**:\n", + "---\n", "\n", - "- Chaoming Wang\n", - "- Email: adaduo@outlook.com\n", - "- Date: 2021.04.18\n", + "- Chaoming Wang (adaduo@outlook.com)\n", + "- Update at 2021.04.18\n", "\n", "---" ] @@ -1326,7 +1325,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.11" + "version": "3.8.10" }, "latex_envs": { "LaTeX_envs_menu_present": true, diff --git a/docs/tutorial_simulation/monitor.ipynb b/docs/tutorial_simulation/monitor_and_inputs.ipynb similarity index 99% rename from docs/tutorial_simulation/monitor.ipynb rename to docs/tutorial_simulation/monitor_and_inputs.ipynb index 9b848bb0..a2cbf49b 100644 --- a/docs/tutorial_simulation/monitor.ipynb +++ b/docs/tutorial_simulation/monitor_and_inputs.ipynb @@ -356,11 +356,8 @@ "source": [ "---\n", "\n", - "**Author**:\n", - "\n", - "- Chaoming Wang\n", - "- Email: adaduo@outlook.com\n", - "- Date: 2021.05.24\n", + "- Chaoming Wang (adaduo@outlook.com)\n", + "- Update at 2021.05.24\n", "\n", "---" ] @@ -382,7 +379,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.11" + "version": "3.8.10" }, "latex_envs": { "LaTeX_envs_menu_present": true, diff --git a/docs/tutorial_simulation/repeat_mode.ipynb b/docs/tutorial_simulation/repeat_mode.ipynb index 6b80893e..d24e7427 100644 --- a/docs/tutorial_simulation/repeat_mode.ipynb +++ b/docs/tutorial_simulation/repeat_mode.ipynb @@ -592,12 +592,10 @@ "\n", "Another thing worthy noting is that if the model variable states rely on the time (for example, the LIF neuron model ``self.t_last_spike``). Setting the continuous time duration between each repeat run is necessary, because the model's logic is dependent on the current time ``_t``. \n", "\n", + "---\n", "\n", - "**Author**:\n", - "\n", - "- Chaoming Wang\n", - "- Email: adaduo@outlook.com\n", - "- Date: 2021.05.25\n", + "- Chaoming Wang (adaduo@outlook.com)\n", + "- Update at 2021.05.25\n", "\n", "---\n" ] @@ -620,7 +618,25 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.8" + "version": "3.8.10" + }, + "latex_envs": { + "LaTeX_envs_menu_present": true, + "autoclose": false, + "autocomplete": true, + "bibliofile": "biblio.bib", + "cite_by": "apalike", + "current_citInitial": 1, + "eqLabelWithNumbers": true, + "eqNumInitial": 1, + "hotkeys": { + "equation": "Ctrl-E", + "itemize": "Ctrl-I" + }, + "labels_anchors": false, + "latex_user_defs": false, + "report_style_numbering": false, + "user_envs_cfg": false }, "toc": { "base_numbering": 1, diff --git a/docs/tutorial_simulation/synaptic_connectivity.ipynb b/docs/tutorial_simulation/synaptic_connectivity.ipynb index 178d5f8d..41b7c8a3 100644 --- a/docs/tutorial_simulation/synaptic_connectivity.ipynb +++ b/docs/tutorial_simulation/synaptic_connectivity.ipynb @@ -725,11 +725,8 @@ "source": [ "---\n", "\n", - "**Author**:\n", - "\n", - "- Chaoming Wang\n", - "- Email: adaduo@outlook.com\n", - "- Date: 2021.04.16\n", + "- Chaoming Wang (adaduo@outlook.com)\n", + "- Update at 2021.04.16\n", "\n", "---" ] @@ -738,7 +735,7 @@ "metadata": { "hide_input": false, "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -752,7 +749,25 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.8" + "version": "3.9.5" + }, + "latex_envs": { + "LaTeX_envs_menu_present": true, + "autoclose": false, + "autocomplete": true, + "bibliofile": "biblio.bib", + "cite_by": "apalike", + "current_citInitial": 1, + "eqLabelWithNumbers": true, + "eqNumInitial": 1, + "hotkeys": { + "equation": "Ctrl-E", + "itemize": "Ctrl-I" + }, + "labels_anchors": false, + "latex_user_defs": false, + "report_style_numbering": false, + "user_envs_cfg": false }, "toc": { "base_numbering": 1, -- 2.34.1 From 3433f86d8d6cfe41114b5c83e7d8d203639e8446 Mon Sep 17 00:00:00 2001 From: chaoming Date: Thu, 9 Sep 2021 22:23:08 +0800 Subject: [PATCH 19/30] adaptive exp_euler method for new API design (allow 'dt' variable) --- brainpy/integrators/ode/wrapper.py | 12 +++++++----- brainpy/integrators/sde/euler_and_milstein.py | 9 ++++----- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/brainpy/integrators/ode/wrapper.py b/brainpy/integrators/ode/wrapper.py index 44d2dad1..3c442c89 100644 --- a/brainpy/integrators/ode/wrapper.py +++ b/brainpy/integrators/ode/wrapper.py @@ -329,6 +329,7 @@ def exp_euler_wrapper(f, show_code, dt, var_type): _f_kw: 'the derivative function', _dt_kw: 'the precision of numerical integration', 'exp': 'the exponential function', + 'math': 'the math module', } for v in variables: keywords[f'{v}_new'] = 'the intermediate value' @@ -341,7 +342,7 @@ def exp_euler_wrapper(f, show_code, dt, var_type): code_scope = dict(closure_vars.nonlocals) code_scope.update(dict(closure_vars.globals)) code_scope[_f_kw] = f - code_scope['exp'] = math.exp + code_scope['math'] = math analysis = separate_variables(f) variables_for_returns = analysis['variables_for_returns'] @@ -383,17 +384,18 @@ def exp_euler_wrapper(f, show_code, dt, var_type): linear = sympy.collect(df_expr, var, evaluate=False)[var] code_lines.append(f' {s_linear.name} = {analysis_by_sympy.sympy2str(linear)}') # linear exponential - linear_exp = sympy.exp(linear * dt) - code_lines.append(f' {s_linear_exp.name} = {analysis_by_sympy.sympy2str(linear_exp)}') + # linear_exp = sympy.exp(linear * dt) + # code_lines.append(f' {s_linear_exp.name} = {analysis_by_sympy.sympy2str(linear_exp)}') + code_lines.append(f' {s_linear_exp.name} = math.exp({s_linear.name} * {_dt_kw})') # 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} = {dt} ** 0.5') + code_lines.append(f' {s_linear_exp.name} = {_dt_kw} ** 0.5') # df part - code_lines.append(f' {s_df_part.name} = {analysis_by_sympy.sympy2str(dt * s_df)}') + code_lines.append(f' {s_df_part.name} = {s_df.name} * {_dt_kw}') # update expression update = var + s_df_part diff --git a/brainpy/integrators/sde/euler_and_milstein.py b/brainpy/integrators/sde/euler_and_milstein.py index bab48cc1..ce169684 100644 --- a/brainpy/integrators/sde/euler_and_milstein.py +++ b/brainpy/integrators/sde/euler_and_milstein.py @@ -139,7 +139,7 @@ class Wrapper(object): code_scope['f'] = f code_scope['g'] = g code_scope['math'] = math - code_scope['exp'] = math.exp + # code_scope['exp'] = math.exp # 2. code lines code_lines = [f'def {func_name}({", ".join(arguments)}):'] @@ -209,17 +209,16 @@ class Wrapper(object): linear = sympy.collect(df_expr, var, evaluate=False)[var] code_lines.append(f' {s_linear.name} = {analysis_by_sympy.sympy2str(linear)}') # linear exponential - linear_exp = sympy.exp(linear * dt) - code_lines.append(f' {s_linear_exp.name} = {analysis_by_sympy.sympy2str(linear_exp)}') + code_lines.append(f' {s_linear_exp.name} = math.exp({linear.name} * {vdt})') # 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} = sqrt({dt})') + code_lines.append(f' {s_linear_exp.name} = {vdt}_sqrt') # df part - code_lines.append(f' {s_df_part.name} = {analysis_by_sympy.sympy2str(dt * s_df)}') + code_lines.append(f' {s_df_part.name} = {s_df.name} * {vdt}') # update expression update = var + s_df_part -- 2.34.1 From a79fe7d93c682f40dede822f2f4b91558a21d718 Mon Sep 17 00:00:00 2001 From: chaoming Date: Fri, 10 Sep 2021 10:03:33 +0800 Subject: [PATCH 20/30] reorganize 'analysis' module --- brainpy/analysis/__init__.py | 8 ++++---- brainpy/analysis/by_sympy/__init__.py | 13 +++++++++++++ brainpy/analysis/{ => by_sympy}/base.py | 0 brainpy/analysis/{ => by_sympy}/bifurcation.py | 5 +++-- brainpy/analysis/{ => by_sympy}/phase_plane.py | 5 +++-- brainpy/analysis/{ => by_sympy}/trajectory.py | 0 brainpy/analysis/continuation/__init__.py | 1 + 7 files changed, 24 insertions(+), 8 deletions(-) create mode 100644 brainpy/analysis/by_sympy/__init__.py rename brainpy/analysis/{ => by_sympy}/base.py (100%) rename brainpy/analysis/{ => by_sympy}/bifurcation.py (99%) rename brainpy/analysis/{ => by_sympy}/phase_plane.py (99%) rename brainpy/analysis/{ => by_sympy}/trajectory.py (100%) create mode 100644 brainpy/analysis/continuation/__init__.py diff --git a/brainpy/analysis/__init__.py b/brainpy/analysis/__init__.py index 38e6fc5a..5ceadc2b 100644 --- a/brainpy/analysis/__init__.py +++ b/brainpy/analysis/__init__.py @@ -6,10 +6,10 @@ This module provides analysis tools for ordinary differential equations. """ -from .base import * -from .bifurcation import * -from .phase_plane import * +# from brainpy.analysis.by_sympy.base import * +# from brainpy.analysis.by_sympy.bifurcation import * +# from brainpy.analysis.by_sympy.phase_plane import * +# from brainpy.analysis.by_sympy.trajectory import * from .solver import * from .stability import * -from .trajectory import * from .utils import * diff --git a/brainpy/analysis/by_sympy/__init__.py b/brainpy/analysis/by_sympy/__init__.py new file mode 100644 index 00000000..8b1aaad0 --- /dev/null +++ b/brainpy/analysis/by_sympy/__init__.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- + + +""" +Dynamics analysis with the aid of `SymPy `_ symbolic inference. + +""" + + +from .base import * +from .bifurcation import * +from .phase_plane import * +from .trajectory import * diff --git a/brainpy/analysis/base.py b/brainpy/analysis/by_sympy/base.py similarity index 100% rename from brainpy/analysis/base.py rename to brainpy/analysis/by_sympy/base.py diff --git a/brainpy/analysis/bifurcation.py b/brainpy/analysis/by_sympy/bifurcation.py similarity index 99% rename from brainpy/analysis/bifurcation.py rename to brainpy/analysis/by_sympy/bifurcation.py index f7c5b747..de996d88 100644 --- a/brainpy/analysis/bifurcation.py +++ b/brainpy/analysis/by_sympy/bifurcation.py @@ -8,8 +8,9 @@ import numpy as np from mpl_toolkits.mplot3d import Axes3D from brainpy import errors, math -from brainpy.analysis import base, stability, utils -from brainpy.analysis.trajectory import Trajectory +from brainpy.analysis import stability, utils +from brainpy.analysis.by_sympy import base +from brainpy.analysis.by_sympy.trajectory import Trajectory logger = logging.getLogger('brainpy.analysis') diff --git a/brainpy/analysis/phase_plane.py b/brainpy/analysis/by_sympy/phase_plane.py similarity index 99% rename from brainpy/analysis/phase_plane.py rename to brainpy/analysis/by_sympy/phase_plane.py index ee5e34e9..dbc2418e 100644 --- a/brainpy/analysis/phase_plane.py +++ b/brainpy/analysis/by_sympy/phase_plane.py @@ -6,8 +6,9 @@ import matplotlib.pyplot as plt import numpy as np from brainpy import errors, math -from brainpy.analysis import base, stability, utils -from brainpy.analysis.trajectory import Trajectory +from brainpy.analysis import stability, utils +from brainpy.analysis.by_sympy import base +from brainpy.analysis.by_sympy.trajectory import Trajectory logger = logging.getLogger('brainpy.analysis') diff --git a/brainpy/analysis/trajectory.py b/brainpy/analysis/by_sympy/trajectory.py similarity index 100% rename from brainpy/analysis/trajectory.py rename to brainpy/analysis/by_sympy/trajectory.py diff --git a/brainpy/analysis/continuation/__init__.py b/brainpy/analysis/continuation/__init__.py new file mode 100644 index 00000000..40a96afc --- /dev/null +++ b/brainpy/analysis/continuation/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- -- 2.34.1 From b4182485fc515567f92659b2075a4b2a4c1f7bb4 Mon Sep 17 00:00:00 2001 From: chaoming Date: Fri, 10 Sep 2021 10:04:05 +0800 Subject: [PATCH 21/30] add 'CondNeuGroup' to model conductance neuron models --- brainpy/simulation/__init__.py | 2 - brainpy/simulation/brainobjects/__init__.py | 3 - brainpy/simulation/brainobjects/base.py | 8 +- brainpy/simulation/brainobjects/channel.py | 27 ---- brainpy/simulation/brainobjects/delays.py | 45 ++++--- brainpy/simulation/brainobjects/dendrite.py | 16 --- brainpy/simulation/brainobjects/molecular.py | 2 +- brainpy/simulation/brainobjects/network.py | 2 +- brainpy/simulation/brainobjects/neuron.py | 132 ++++++++++++++++--- brainpy/simulation/brainobjects/soma.py | 16 --- brainpy/simulation/brainobjects/synapse.py | 2 +- 11 files changed, 150 insertions(+), 105 deletions(-) delete mode 100644 brainpy/simulation/brainobjects/channel.py delete mode 100644 brainpy/simulation/brainobjects/dendrite.py delete mode 100644 brainpy/simulation/brainobjects/soma.py diff --git a/brainpy/simulation/__init__.py b/brainpy/simulation/__init__.py index 70a24a42..f32912dc 100644 --- a/brainpy/simulation/__init__.py +++ b/brainpy/simulation/__init__.py @@ -9,7 +9,5 @@ This module provides APIs for brain simulations. from .brainobjects import * from .connectivity import * -from .inputs import * -from .measure import * from .monitor import * from .utils import * diff --git a/brainpy/simulation/brainobjects/__init__.py b/brainpy/simulation/brainobjects/__init__.py index f141d2c4..8d204b93 100644 --- a/brainpy/simulation/brainobjects/__init__.py +++ b/brainpy/simulation/brainobjects/__init__.py @@ -1,11 +1,8 @@ # -*- coding: utf-8 -*- from .base import * -from .channel import * from .delays import * -from .dendrite import * from .molecular import * from .network import * from .neuron import * -from .soma import * from .synapse import * diff --git a/brainpy/simulation/brainobjects/base.py b/brainpy/simulation/brainobjects/base.py index 660bc444..c99af937 100644 --- a/brainpy/simulation/brainobjects/base.py +++ b/brainpy/simulation/brainobjects/base.py @@ -184,13 +184,9 @@ class DynamicalSystem(Base): class Container(DynamicalSystem): """Container object which is designed to add other instances of DynamicalSystem. - What's different from the other objects of DynamicalSystem is that Container has - one more useful function :py:func:`add`. It can be used to add the children - objects. - Parameters ---------- - steps : function, list of function, tuple of function, dict of (str, function), optional + steps : tuple of function, tuple of str, dict of (str, function), optional The step functions. monitors : tuple, list, Monitor, optional The monitor object. @@ -270,5 +266,3 @@ class Container(DynamicalSystem): return children_ds[item] else: return super(Container, self).__getattribute__(item) - - diff --git a/brainpy/simulation/brainobjects/channel.py b/brainpy/simulation/brainobjects/channel.py deleted file mode 100644 index e9a5fa82..00000000 --- a/brainpy/simulation/brainobjects/channel.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- - -from brainpy.simulation.brainobjects.base import DynamicalSystem - -__all__ = [ - 'Channel' -] - - -class Channel(DynamicalSystem): - """Ion Channel object. - - Parameters - ---------- - name : str - The name of the channel. - - """ - - def __init__(self, name=None, **kwargs): - super(Channel, self).__init__(name=name, **kwargs) - - def update(self, *args, **kwargs): - raise NotImplementedError - - def current(self, *args, **kwargs): - raise NotImplementedError diff --git a/brainpy/simulation/brainobjects/delays.py b/brainpy/simulation/brainobjects/delays.py index 991ab489..7514dd10 100644 --- a/brainpy/simulation/brainobjects/delays.py +++ b/brainpy/simulation/brainobjects/delays.py @@ -14,6 +14,9 @@ __all__ = [ class Delay(DynamicalSystem): + """Base class to model delay variables. + + """ def __init__(self, steps=('update',), name=None): super(Delay, self).__init__(steps=steps, monitors=None, name=name) @@ -22,15 +25,15 @@ class Delay(DynamicalSystem): class ConstantDelay(Delay): - """Constant delay object. + """Class used to model constant delay variables. For examples: - >>> ConstantDelay(size=10, delay=10.) + >>> import brainpy as bp >>> - >>> import numpy as np - >>> ConstantDelay(size=100, delay=lambda: np.random.randint(5, 10)) - >>> ConstantDelay(size=100, delay=np.random.random(100) * 4 + 10) + >>> bp.ConstantDelay(size=10, delay=10.) + >>> bp.ConstantDelay(size=100, delay=lambda: bp.math.random.randint(5, 10)) + >>> bp.ConstantDelay(size=100, delay= bp.math.random.random(100) * 4 + 10) Parameters ---------- @@ -57,9 +60,9 @@ class ConstantDelay(Delay): # data and operations if isinstance(delay, (int, float)): # uniform delay self.uniform_delay = True - self.num_step = bmath.array([int(pmath.ceil(delay / bmath.get_dt())) + 1]) - self.data = bmath.Variable(bmath.zeros((self.num_step[0],) + self.size, dtype=dtype)) - self.out_idx = bmath.Variable(bmath.array([0])) + self.num_step = int(pmath.ceil(delay / bmath.get_dt())) + 1 + self.data = bmath.Variable(bmath.zeros((self.num_step,) + self.size, dtype=dtype)) + self.out_idx = bmath.Variable(0) self.in_idx = bmath.Variable(self.num_step - 1) self.push = self._push_for_uniform_delay @@ -95,22 +98,34 @@ class ConstantDelay(Delay): super(ConstantDelay, self).__init__(name=name) def _pull_for_uniform_delay(self): - return self.data[self.out_idx[0]] + """Pull delayed data for variables with the uniform delay. + """ + return self.data[self.out_idx] def _pull_for_nonuniform_delay(self): + """Pull delayed data for variables with the non-uniform delay. + """ return self.data[self.out_idx, self.diag] def _push_for_uniform_delay(self, value): - self.data[self.in_idx[0]] = value + """Push the latest data to the delay bottom. + """ + self.data[self.in_idx] = value def _push_for_nonuniform_delay(self, value): + """Push the latest data to the delay bottom. + """ self.data[self.in_idx, self.diag] = value def update(self, _t, _dt): - self.in_idx[:] = (self.in_idx + 1) % self.num_step - self.out_idx[:] = (self.out_idx + 1) % self.num_step + """Update the delay index. + """ + self.in_idx[...] = (self.in_idx + 1) % self.num_step + self.out_idx[...] = (self.out_idx + 1) % self.num_step def reset(self): - self.data[:] = 0 - self.in_idx[:] = self.num_step - 1 - self.out_idx[:] = 0 if self.uniform_delay else bmath.zeros(self.num, dtype=bmath.int_) + """Reset the variables. + """ + self.data[...] = 0 + self.in_idx[...] = self.num_step - 1 + self.out_idx[...] = 0 diff --git a/brainpy/simulation/brainobjects/dendrite.py b/brainpy/simulation/brainobjects/dendrite.py deleted file mode 100644 index f2a0699c..00000000 --- a/brainpy/simulation/brainobjects/dendrite.py +++ /dev/null @@ -1,16 +0,0 @@ -# -*- coding: utf-8 -*- - -from brainpy.simulation.brainobjects.base import DynamicalSystem - -__all__ = [ - 'Dendrite' -] - - -class Dendrite(DynamicalSystem): - """Dendrite object. - - """ - - def __init__(self, name, **kwargs): - super(Dendrite, self).__init__(name=name, **kwargs) diff --git a/brainpy/simulation/brainobjects/molecular.py b/brainpy/simulation/brainobjects/molecular.py index 4e647890..395674ea 100644 --- a/brainpy/simulation/brainobjects/molecular.py +++ b/brainpy/simulation/brainobjects/molecular.py @@ -8,7 +8,7 @@ __all__ = [ class Molecular(DynamicalSystem): - """Molecular object for neuron modeling. + """Base class to model molecular objects. """ diff --git a/brainpy/simulation/brainobjects/network.py b/brainpy/simulation/brainobjects/network.py index 35e0ad5a..b0cb714f 100644 --- a/brainpy/simulation/brainobjects/network.py +++ b/brainpy/simulation/brainobjects/network.py @@ -8,7 +8,7 @@ __all__ = [ class Network(Container): - """Network object, an alias of Container. + """Base class to model network objects, an alias of Container. Network instantiates a network, which is aimed to load neurons, synapses, and other brain objects. diff --git a/brainpy/simulation/brainobjects/neuron.py b/brainpy/simulation/brainobjects/neuron.py index 6b273d63..71802c44 100644 --- a/brainpy/simulation/brainobjects/neuron.py +++ b/brainpy/simulation/brainobjects/neuron.py @@ -1,24 +1,28 @@ # -*- coding: utf-8 -*- -from brainpy import errors +from brainpy import errors, math +from brainpy.base.collector import Collector from brainpy.simulation import utils -from brainpy.simulation.brainobjects.base import DynamicalSystem, Container -from brainpy.simulation.brainobjects.channel import Channel +from brainpy.integrators.wrapper import odeint +from brainpy.simulation.brainobjects.base import DynamicalSystem __all__ = [ 'NeuGroup', + 'Channel', 'CondNeuGroup', + 'Soma', + 'Dendrite', ] class NeuGroup(DynamicalSystem): - """Neuron Group. + """Base class to model neuronal groups. Parameters ---------- size : int, tuple of int, list of int The neuron group geometry. - steps : function, list of function, tuple of function, dict of (str, function), optional + steps : tuple of str, tuple of function, dict of (str, function), optional The step functions. name : str, optional The group name. @@ -26,7 +30,6 @@ class NeuGroup(DynamicalSystem): def __init__(self, size, name=None, steps=('update',), **kwargs): # size - # ---- if isinstance(size, (list, tuple)): if len(size) <= 0: raise errors.BrainPyError('size must be int, or a tuple/list of int.') @@ -41,22 +44,119 @@ class NeuGroup(DynamicalSystem): self.num = utils.size2len(size) # initialize - # ---------- super(NeuGroup, self).__init__(steps=steps, name=name, **kwargs) def update(self, _t, _dt): - raise NotImplementedError + """The function to specify the updating rule. + Parameters + ---------- + _t : float + The current time. + _dt : float + The time step. + """ + raise NotImplementedError(f'Subclass of {self.__class__.__name__} must ' + f'implement "update" function.') -class CondNeuGroup(Container): - def __init__(self, *channels, monitors=None, name=None, **dict_channels): - children_channels = dict() +# ---------------------------------------------------- +# +# Conductance Neuron Model +# +# ---------------------------------------------------- - for ch in channels: - assert isinstance(ch, Channel) +class Channel(DynamicalSystem): + """Base class to model ion channels.""" - for key, ch in dict_channels.items(): - assert isinstance(ch, Channel) + def __init__(self, **kwargs): + super(Channel, self).__init__(**kwargs) - super(CondNeuGroup, self).__init__(monitors=monitors, name=name, **children_channels) + def init(self, host): + """Initialize variables in the channel.""" + if not isinstance(host, CondNeuGroup): + raise ValueError + self.host = host + + def update(self, _t, _dt): + """The function to specify the updating rule.""" + raise NotImplementedError(f'Subclass of {self.__class__.__name__} must ' + f'implement "update" function.') + + +class CondNeuGroup(NeuGroup): + """Conductance neuron group.""" + def __init__(self, C=1., A=1e-3, Vth=0., **channels): + self.C = C + self.A = A + self.Vth = Vth + + # children channels + self.child_channels = Collector() + for key, ch in channels.items(): + if not isinstance(ch, Channel): + raise errors.BrainPyError(f'{self.__class__.__name__} only receives {Channel.__name__} ' + f'instance, while we got {type(ch)}: {ch}.') + self.child_channels[key] = ch + + def init(self, size, monitors=None, name=None): + super(CondNeuGroup, self).__init__(size, steps=('update',), monitors=monitors, name=name) + + # initialize variables + self.V = math.Variable(math.zeros(self.num, dtype=math.float_)) + self.spike = math.Variable(math.zeros(self.num, dtype=math.bool_)) + self.input = math.Variable(math.zeros(self.num, dtype=math.float_)) + + # initialize node variables + for ch in self.child_channels.values(): + ch.init(host=self) + + # checking + self._output_channels = [] + self._update_channels = [] + for ch in self.child_channels.values(): + if not hasattr(ch, 'I'): + self._update_channels.append(ch) + else: + if not isinstance(getattr(ch, 'I'), math.Variable): + raise errors.BrainPyError + self._output_channels.append(ch) + + return self + + def update(self, _t, _dt): + for ch in self._update_channels: + ch.update(_t, _dt) + for ch in self._output_channels: + self.input += ch.update(_t, _dt) + + # update variables + V = self.V + self.input / self.C / self.A * _dt + self.spike[:] = math.logical_and(V >= self.Vth, self.V < self.Vth) + self.V[:] = V + self.input[:] = 0. + + + +# --------------------------------------------------------- +# +# Multi-Compartment Neuron Model +# +# --------------------------------------------------------- + +class Dendrite(DynamicalSystem): + """Base class to model dendrites. + + """ + + def __init__(self, name, **kwargs): + super(Dendrite, self).__init__(name=name, **kwargs) + + +class Soma(DynamicalSystem): + """Base class to model soma in multi-compartment neuron models. + + """ + + def __init__(self, name, **kwargs): + super(Soma, self).__init__(name=name, **kwargs) diff --git a/brainpy/simulation/brainobjects/soma.py b/brainpy/simulation/brainobjects/soma.py deleted file mode 100644 index 5d5faeb4..00000000 --- a/brainpy/simulation/brainobjects/soma.py +++ /dev/null @@ -1,16 +0,0 @@ -# -*- coding: utf-8 -*- - -from brainpy.simulation.brainobjects.base import DynamicalSystem - -__all__ = [ - 'Soma' -] - - -class Soma(DynamicalSystem): - """Soma object for neuron modeling. - - """ - - def __init__(self, name, **kwargs): - super(Soma, self).__init__(name=name, **kwargs) diff --git a/brainpy/simulation/brainobjects/synapse.py b/brainpy/simulation/brainobjects/synapse.py index 76b5064f..2ba7a1e3 100644 --- a/brainpy/simulation/brainobjects/synapse.py +++ b/brainpy/simulation/brainobjects/synapse.py @@ -12,7 +12,7 @@ __all__ = [ class TwoEndConn(DynamicalSystem): - """Two End Synaptic Connections. + """Base class to model two-end synaptic connections. Parameters ---------- -- 2.34.1 From 37302416b90f9f4a3edda68058eba5316ad18019 Mon Sep 17 00:00:00 2001 From: chaoming Date: Fri, 10 Sep 2021 13:46:23 +0800 Subject: [PATCH 22/30] reorganize 'analysis' code structure --- brainpy/analysis/__init__.py | 4 ---- brainpy/analysis/{by_sympy => symbolic}/__init__.py | 7 ++++++- brainpy/analysis/{by_sympy => symbolic}/base.py | 0 brainpy/analysis/{by_sympy => symbolic}/bifurcation.py | 4 ++-- brainpy/analysis/{by_sympy => symbolic}/phase_plane.py | 4 ++-- brainpy/analysis/{by_sympy => symbolic}/trajectory.py | 0 6 files changed, 10 insertions(+), 9 deletions(-) rename brainpy/analysis/{by_sympy => symbolic}/__init__.py (50%) rename brainpy/analysis/{by_sympy => symbolic}/base.py (100%) rename brainpy/analysis/{by_sympy => symbolic}/bifurcation.py (99%) rename brainpy/analysis/{by_sympy => symbolic}/phase_plane.py (99%) rename brainpy/analysis/{by_sympy => symbolic}/trajectory.py (100%) diff --git a/brainpy/analysis/__init__.py b/brainpy/analysis/__init__.py index 5ceadc2b..81f6dd5c 100644 --- a/brainpy/analysis/__init__.py +++ b/brainpy/analysis/__init__.py @@ -6,10 +6,6 @@ This module provides analysis tools for ordinary differential equations. """ -# from brainpy.analysis.by_sympy.base import * -# from brainpy.analysis.by_sympy.bifurcation import * -# from brainpy.analysis.by_sympy.phase_plane import * -# from brainpy.analysis.by_sympy.trajectory import * from .solver import * from .stability import * from .utils import * diff --git a/brainpy/analysis/by_sympy/__init__.py b/brainpy/analysis/symbolic/__init__.py similarity index 50% rename from brainpy/analysis/by_sympy/__init__.py rename to brainpy/analysis/symbolic/__init__.py index 8b1aaad0..eb34004a 100644 --- a/brainpy/analysis/by_sympy/__init__.py +++ b/brainpy/analysis/symbolic/__init__.py @@ -4,8 +4,13 @@ """ Dynamics analysis with the aid of `SymPy `_ symbolic inference. -""" +This module provide basic dynamics analysis for low-dimensional dynamical systems, including + +- phase plane analysis (1d or 2d systems) +- bifurcation analysis (1d or 2d systems) +- fast slow bifurcation analysis (2d or 3d systems) +""" from .base import * from .bifurcation import * diff --git a/brainpy/analysis/by_sympy/base.py b/brainpy/analysis/symbolic/base.py similarity index 100% rename from brainpy/analysis/by_sympy/base.py rename to brainpy/analysis/symbolic/base.py diff --git a/brainpy/analysis/by_sympy/bifurcation.py b/brainpy/analysis/symbolic/bifurcation.py similarity index 99% rename from brainpy/analysis/by_sympy/bifurcation.py rename to brainpy/analysis/symbolic/bifurcation.py index de996d88..e077016b 100644 --- a/brainpy/analysis/by_sympy/bifurcation.py +++ b/brainpy/analysis/symbolic/bifurcation.py @@ -9,8 +9,8 @@ from mpl_toolkits.mplot3d import Axes3D from brainpy import errors, math from brainpy.analysis import stability, utils -from brainpy.analysis.by_sympy import base -from brainpy.analysis.by_sympy.trajectory import Trajectory +from brainpy.analysis.symbolic import base +from brainpy.analysis.symbolic.trajectory import Trajectory logger = logging.getLogger('brainpy.analysis') diff --git a/brainpy/analysis/by_sympy/phase_plane.py b/brainpy/analysis/symbolic/phase_plane.py similarity index 99% rename from brainpy/analysis/by_sympy/phase_plane.py rename to brainpy/analysis/symbolic/phase_plane.py index dbc2418e..7fab4f01 100644 --- a/brainpy/analysis/by_sympy/phase_plane.py +++ b/brainpy/analysis/symbolic/phase_plane.py @@ -7,8 +7,8 @@ import numpy as np from brainpy import errors, math from brainpy.analysis import stability, utils -from brainpy.analysis.by_sympy import base -from brainpy.analysis.by_sympy.trajectory import Trajectory +from brainpy.analysis.symbolic import base +from brainpy.analysis.symbolic.trajectory import Trajectory logger = logging.getLogger('brainpy.analysis') diff --git a/brainpy/analysis/by_sympy/trajectory.py b/brainpy/analysis/symbolic/trajectory.py similarity index 100% rename from brainpy/analysis/by_sympy/trajectory.py rename to brainpy/analysis/symbolic/trajectory.py -- 2.34.1 From 6a86439d22b19d3aaa8cc955911f97816f8b1a64 Mon Sep 17 00:00:00 2001 From: chaoming Date: Fri, 10 Sep 2021 13:46:43 +0800 Subject: [PATCH 23/30] reorganize 'connect' structure and documentation --- .../simulation/connectivity/classic_conn.py | 16 ---------------- brainpy/simulation/connectivity/custom_conn.py | 6 ++++-- docs/apis/simulation/connectivity.rst | 18 ++++++++++++++++++ 3 files changed, 22 insertions(+), 18 deletions(-) delete mode 100644 brainpy/simulation/connectivity/classic_conn.py diff --git a/brainpy/simulation/connectivity/classic_conn.py b/brainpy/simulation/connectivity/classic_conn.py deleted file mode 100644 index c0c7fa93..00000000 --- a/brainpy/simulation/connectivity/classic_conn.py +++ /dev/null @@ -1,16 +0,0 @@ -# -*- coding: utf-8 -*- - - -from brainpy.simulation.connectivity.base import TwoEndConnector - -try: - import numba as nb -except ModuleNotFoundError: - nb = None - -__all__ = [ -] - - -class CompleteGraph(TwoEndConnector): - pass diff --git a/brainpy/simulation/connectivity/custom_conn.py b/brainpy/simulation/connectivity/custom_conn.py index 60893d7a..c0234ecf 100644 --- a/brainpy/simulation/connectivity/custom_conn.py +++ b/brainpy/simulation/connectivity/custom_conn.py @@ -13,6 +13,7 @@ __all__ = [ class MatConn(TwoEndConnector): + """Connector built from the connection matrix.""" def __init__(self, conn_mat): super(MatConn, self).__init__() @@ -25,6 +26,7 @@ class MatConn(TwoEndConnector): class IJConn(TwoEndConnector): + """Connector built from the ``pre_ids`` and ``post_ids`` connections.""" def __init__(self, i, j): super(IJConn, self).__init__() @@ -37,10 +39,10 @@ class IJConn(TwoEndConnector): self.post_ids = math.asarray(j, dtype=math.int_) def __call__(self, pre_size, post_size): - # this is ncessary when create "pre2post" , + # this is necessary when create "pre2post" , # "pre2syn" etc. structures self.num_pre = utils.size2len(pre_size) - # this is ncessary when create "post2pre" , + # this is necessary when create "post2pre" , # "post2syn" etc. structures self.num_post = utils.size2len(post_size) return self diff --git a/docs/apis/simulation/connectivity.rst b/docs/apis/simulation/connectivity.rst index f7c7c0d8..e86fd0b7 100644 --- a/docs/apis/simulation/connectivity.rst +++ b/docs/apis/simulation/connectivity.rst @@ -84,6 +84,24 @@ Random Connections :members: + +Custom Connections +------------------ + +.. autosummary:: + :toctree: generated/ + + MatConn + IJConn + + +.. autoclass:: MatConn + :members: + +.. autoclass:: IJConn + :members: + + Formatter Functions ------------------- -- 2.34.1 From 4234810084f050f831986f59f9a64fc932b909fd Mon Sep 17 00:00:00 2001 From: chaoming Date: Sat, 11 Sep 2021 17:20:25 +0800 Subject: [PATCH 24/30] rename analysis by sympy --- brainpy/analysis/stability.py | 10 +++++----- .../{symbolic => symbolic_analysis}/__init__.py | 2 +- .../analysis/{symbolic => symbolic_analysis}/base.py | 0 .../{symbolic => symbolic_analysis}/bifurcation.py | 4 ++-- .../{symbolic => symbolic_analysis}/phase_plane.py | 4 ++-- .../{symbolic => symbolic_analysis}/trajectory.py | 0 brainpy/analysis/utils.py | 2 ++ 7 files changed, 12 insertions(+), 10 deletions(-) rename brainpy/analysis/{symbolic => symbolic_analysis}/__init__.py (90%) rename brainpy/analysis/{symbolic => symbolic_analysis}/base.py (100%) rename brainpy/analysis/{symbolic => symbolic_analysis}/bifurcation.py (99%) rename brainpy/analysis/{symbolic => symbolic_analysis}/phase_plane.py (99%) rename brainpy/analysis/{symbolic => symbolic_analysis}/trajectory.py (100%) diff --git a/brainpy/analysis/stability.py b/brainpy/analysis/stability.py index e86f6678..83ce2c40 100644 --- a/brainpy/analysis/stability.py +++ b/brainpy/analysis/stability.py @@ -53,9 +53,9 @@ plot_scheme.update({ CENTER_2D: {'color': 'lime'}, STABLE_NODE_2D: {"color": 'tab:red'}, STABLE_FOCUS_2D: {"color": 'tab:purple'}, - STABLE_STAR_2D: {'color': 'orange'}, + STABLE_STAR_2D: {'color': 'tab:olive'}, STABLE_DEGENERATE_2D: {'color': 'blueviolet'}, - UNSTABLE_NODE_2D: {"color": 'tab:olive'}, + UNSTABLE_NODE_2D: {"color": 'tab:orange'}, UNSTABLE_FOCUS_2D: {"color": 'tab:cyan'}, UNSTABLE_STAR_2D: {'color': 'green'}, UNSTABLE_DEGENERATE_2D: {'color': 'springgreen'}, @@ -72,13 +72,13 @@ UNSTABLE_FOCUS_3D = 'unstable focus' UNSTABLE_CENTER_3D = 'unstable center' UNKNOWN_3D = 'unknown 3d' plot_scheme.update({ - STABLE_POINT_3D: {'color': 'tab:pink'}, + STABLE_POINT_3D: {'color': 'tab:gray'}, UNSTABLE_POINT_3D: {'color': 'tab:purple'}, STABLE_NODE_3D: {'color': 'tab:green'}, UNSTABLE_SADDLE_3D: {'color': 'tab:red'}, - UNSTABLE_FOCUS_3D: {'color': 'tab:orange'}, + UNSTABLE_FOCUS_3D: {'color': 'tab:pink'}, STABLE_FOCUS_3D: {'color': 'tab:purple'}, - UNSTABLE_NODE_3D: {'color': 'tab:gray'}, + UNSTABLE_NODE_3D: {'color': 'tab:orange'}, UNSTABLE_CENTER_3D: {'color': 'tab:olive'}, UNKNOWN_3D: {'color': 'tab:cyan'}, }) diff --git a/brainpy/analysis/symbolic/__init__.py b/brainpy/analysis/symbolic_analysis/__init__.py similarity index 90% rename from brainpy/analysis/symbolic/__init__.py rename to brainpy/analysis/symbolic_analysis/__init__.py index eb34004a..9db41b1c 100644 --- a/brainpy/analysis/symbolic/__init__.py +++ b/brainpy/analysis/symbolic_analysis/__init__.py @@ -2,7 +2,7 @@ """ -Dynamics analysis with the aid of `SymPy `_ symbolic inference. +Dynamics analysis with the aid of `SymPy `_ symbolic_analysis inference. This module provide basic dynamics analysis for low-dimensional dynamical systems, including diff --git a/brainpy/analysis/symbolic/base.py b/brainpy/analysis/symbolic_analysis/base.py similarity index 100% rename from brainpy/analysis/symbolic/base.py rename to brainpy/analysis/symbolic_analysis/base.py diff --git a/brainpy/analysis/symbolic/bifurcation.py b/brainpy/analysis/symbolic_analysis/bifurcation.py similarity index 99% rename from brainpy/analysis/symbolic/bifurcation.py rename to brainpy/analysis/symbolic_analysis/bifurcation.py index e077016b..e7aae5ff 100644 --- a/brainpy/analysis/symbolic/bifurcation.py +++ b/brainpy/analysis/symbolic_analysis/bifurcation.py @@ -9,8 +9,8 @@ from mpl_toolkits.mplot3d import Axes3D from brainpy import errors, math from brainpy.analysis import stability, utils -from brainpy.analysis.symbolic import base -from brainpy.analysis.symbolic.trajectory import Trajectory +from brainpy.analysis.symbolic_analysis import base +from brainpy.analysis.symbolic_analysis.trajectory import Trajectory logger = logging.getLogger('brainpy.analysis') diff --git a/brainpy/analysis/symbolic/phase_plane.py b/brainpy/analysis/symbolic_analysis/phase_plane.py similarity index 99% rename from brainpy/analysis/symbolic/phase_plane.py rename to brainpy/analysis/symbolic_analysis/phase_plane.py index 7fab4f01..54ec20b7 100644 --- a/brainpy/analysis/symbolic/phase_plane.py +++ b/brainpy/analysis/symbolic_analysis/phase_plane.py @@ -7,8 +7,8 @@ import numpy as np from brainpy import errors, math from brainpy.analysis import stability, utils -from brainpy.analysis.symbolic import base -from brainpy.analysis.symbolic.trajectory import Trajectory +from brainpy.analysis.symbolic_analysis import base +from brainpy.analysis.symbolic_analysis.trajectory import Trajectory logger = logging.getLogger('brainpy.analysis') diff --git a/brainpy/analysis/symbolic/trajectory.py b/brainpy/analysis/symbolic_analysis/trajectory.py similarity index 100% rename from brainpy/analysis/symbolic/trajectory.py rename to brainpy/analysis/symbolic_analysis/trajectory.py diff --git a/brainpy/analysis/utils.py b/brainpy/analysis/utils.py index a871e830..7cdfc133 100644 --- a/brainpy/analysis/utils.py +++ b/brainpy/analysis/utils.py @@ -165,6 +165,8 @@ def transform_integrals_to_model(integrals, method='euler'): if callable(integrals): integrals = [integrals] + if isinstance(integrals, DynamicalSystem): + integrals = list(integrals.ints().unique().values()) integrals, pars_update = transform_integrals(integrals, method=method) -- 2.34.1 From b82b55f95f29ac2d3fa1c05b1079bd0710c8b8cc Mon Sep 17 00:00:00 2001 From: chaoming Date: Sat, 11 Sep 2021 17:20:53 +0800 Subject: [PATCH 25/30] add self in nodes() access --- brainpy/base/base.py | 12 ++++++++++-- brainpy/simulation/brainobjects/base.py | 2 +- brainpy/simulation/utils.py | 2 +- tests/base/test_circular_reference.py | 4 ++-- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/brainpy/base/base.py b/brainpy/base/base.py index 18da86a8..debfdc93 100644 --- a/brainpy/base/base.py +++ b/brainpy/base/base.py @@ -121,8 +121,11 @@ class Base(object): nodes.append(v) for v in nodes: gather.update(v.nodes(method=method, _paths=_paths)) + gather[self.name] = self + elif method == 'relative': nodes = [] + gather[''] = self for k, v in self.__dict__.items(): if isinstance(v, Base): path = (id(self), id(v)) @@ -132,7 +135,8 @@ class Base(object): nodes.append((k, v)) for k, v in nodes: for k2, v2 in v.nodes(method=method, _paths=_paths).items(): - gather[f'{k}.{k2}'] = v2 + if k2: gather[f'{k}.{k2}'] = v2 + else: raise ValueError(f'No support for the method of "{method}".') return gather @@ -153,8 +157,11 @@ class Base(object): for node in nodes: gather[node.name] = node gather.update(node.nodes(method=method, _paths=_paths)) + gather[self.name] = self + elif method == 'relative': nodes = [] + gather[''] = self for key, node in dict_container.items(): path = (id(self), id(node)) if path not in _paths: @@ -163,7 +170,8 @@ class Base(object): nodes.append((key, node)) for key, node in nodes: for key2, node2 in node.nodes(method=method, _paths=_paths).items(): - gather[f'{key}.{key2}'] = node2 + if key2: gather[f'{key}.{key2}'] = node2 + else: raise ValueError(f'No support for the method of "{method}".') return gather diff --git a/brainpy/simulation/brainobjects/base.py b/brainpy/simulation/brainobjects/base.py index c99af937..f3be5395 100644 --- a/brainpy/simulation/brainobjects/base.py +++ b/brainpy/simulation/brainobjects/base.py @@ -171,7 +171,7 @@ class DynamicalSystem(Base): # -- # 6.1 add 'ts' variable to every monitor # 6.2 wrap the monitor iterm with the 'list' type into the 'ndarray' type - for node in [self] + list(self.nodes().unique().values()): + for node in self.nodes().unique().values(): if node.mon.num_item > 0: node.mon.ts = times for key, val in list(node.mon.item_contents.items()): diff --git a/brainpy/simulation/utils.py b/brainpy/simulation/utils.py index e86f57da..7f5e447f 100644 --- a/brainpy/simulation/utils.py +++ b/brainpy/simulation/utils.py @@ -289,7 +289,7 @@ def check_and_format_monitors(host): # reshape monitors # ---- - all_nodes = [host] + list(host.nodes().unique().values()) + all_nodes = list(host.nodes().unique().values()) for node in all_nodes: node.mon.build() # build the monitor for key in node.mon.item_contents.keys(): diff --git a/tests/base/test_circular_reference.py b/tests/base/test_circular_reference.py index ab572be8..68afbafd 100644 --- a/tests/base/test_circular_reference.py +++ b/tests/base/test_circular_reference.py @@ -70,8 +70,8 @@ def test_nodes(): print(abs_nodes) print(rel_nodes) - assert len(abs_nodes) == 2 - assert len(rel_nodes) == 4 + assert len(abs_nodes) == 3 + assert len(rel_nodes) == 5 def test_ints(): -- 2.34.1 From aa453a07c59a25d13478ec036d7f6ba4f1ce255d Mon Sep 17 00:00:00 2001 From: chaoming Date: Sun, 12 Sep 2021 10:24:04 +0800 Subject: [PATCH 26/30] support compile list/tuple/dict of step functions / Base instances --- brainpy/math/numpy/ast2numba.py | 557 +++++++++++++++++++++-------- tests/math/numpy/test_ast2numba.py | 211 +++++++++++ 2 files changed, 611 insertions(+), 157 deletions(-) diff --git a/brainpy/math/numpy/ast2numba.py b/brainpy/math/numpy/ast2numba.py index 6cc9ac6d..8892c360 100644 --- a/brainpy/math/numpy/ast2numba.py +++ b/brainpy/math/numpy/ast2numba.py @@ -8,6 +8,7 @@ TODO: enable code debug and error report; See https://github.com/numba/numba/iss import ast import inspect import re +from copy import deepcopy from pprint import pprint import numba @@ -21,7 +22,7 @@ from brainpy.base.collector import Collector from brainpy.base.function import Function from brainpy.math import profile -DE_INT = DynamicalSystem = Container = None +DE_INT = DynamicalSystem = None __all__ = [ 'jit', @@ -191,7 +192,7 @@ def _jit_Function(func, show_code=False, **jit_setting): # code, _scope = _add_try_except(code) # code_scope.update(_scope) if show_code: - output_compiled_codes(code, code_scope) + show_compiled_codes(code, code_scope) exec(compile(code, '', 'exec'), code_scope) func = code_scope[func._f.__name__] func = numba.jit(func, **jit_setting) @@ -201,10 +202,6 @@ def _jit_Function(func, show_code=False, **jit_setting): def _jit_cls_func(f, code=None, host=None, show_code=False, **jit_setting): - global Container - if Container is None: - from brainpy.simulation.brainobjects.base import Container - host = (host or f.__self__) # data to return @@ -213,51 +210,27 @@ def _jit_cls_func(f, code=None, host=None, show_code=False, **jit_setting): nodes = Collector() nodes[host.name] = host - # step function of Container - if isinstance(host, Container): - # if f.__name__ != 'update': - # raise errors.UnsupportedError(f'Currently, BrainPy only supports compile "update" step ' - # f'function, while we got {f.__name__}: {f}') - code_lines = [] - code_scope = {} - for key, step in host.child_steps.items(): - r = _jit_func(obj_or_fun=step, show_code=show_code, **jit_setting) - # if r['func'] != step: - arguments.update(r['arguments']) - arg2call.update(r['arg2call']) - nodes.update(r['nodes']) - code_scope[key.replace('.', '_')] = r['func'] - call_args = [f'{arg}={arg}' for arg in sorted(r['arguments'])] - code_lines.append("{call}(_t, _dt, {args})".format(call=key.replace('.', '_'), - args=", ".join(call_args))) - # args=_items2lines(call_args, line_break='\n\t\t\t'))) - code_lines = [' ' + line for line in code_lines] - # code_lines.insert(0, f'def {host.name}_update(_t, _dt, {_items2lines(sorted(arguments))}):') - code_lines.insert(0, f'def {host.name}_update(_t, _dt, {", ".join(sorted(arguments))}):') - code = '\n'.join(code_lines) - # code_scope.update(nodes) - func_name = f'{host.name}_{f.__name__}' - - # step function of normal DynamicalSystem - else: - code = (code or tools.deindent(inspect.getsource(f)).strip()) - # function name - func_name = f.__name__ - # code scope - closure_vars = inspect.getclosurevars(f) - code_scope = dict(closure_vars.nonlocals) - code_scope.update(closure_vars.globals) - code, _arguments, _arg2call, _nodes, code_scope = _analyze_cls_func( - host=host, code=code, show_code=show_code, code_scope=code_scope, **jit_setting) - arguments.update(_arguments) - arg2call.update(_arg2call) - nodes.update(_nodes) + # code + code = (code or tools.deindent(inspect.getsource(f)).strip()) + # function name + func_name = f.__name__ + # code scope + closure_vars = inspect.getclosurevars(f) + code_scope = dict(closure_vars.nonlocals) + code_scope.update(closure_vars.globals) + # analyze class function + code, _arguments, _arg2call, _nodes, _code_scope = _analyze_cls_func( + host=host, code=code, show_code=show_code, **jit_setting) + arguments.update(_arguments) + arg2call.update(_arg2call) + nodes.update(_nodes) + code_scope.update(_code_scope) # compile new function # code, _scope = _add_try_except(code) # code_scope.update(_scope) if show_code: - output_compiled_codes(code, code_scope) + show_compiled_codes(code, code_scope) exec(compile(code, '', 'exec'), code_scope) func = code_scope[func_name] func = numba.jit(func, **jit_setting) @@ -298,7 +271,7 @@ def _jit_intg_func(f, show_code=False, **jit_setting): need_recompile = False for key, func in raw_func.items(): - # get node + # get node of host func_node = None if f_node: func_node = f_node @@ -308,13 +281,17 @@ def _jit_intg_func(f, show_code=False, **jit_setting): # get new compiled function if isinstance(func, Dispatcher): continue - elif func_node: + elif func_node is not None: need_recompile = True - r = _jit_cls_func(f=func, host=func_node, show_code=show_code, **jit_setting) + r = _jit_cls_func(f=func, + host=func_node, + show_code=show_code, + **jit_setting) if len(r['arguments']) or remove_self: - tree = _replace_func(tree, func_call=key, - arg_to_append=r['arguments'], - remove_self=remove_self) + tree = _replace_func_call_by_tee(tree, + func_call=key, + arg_to_append=r['arguments'], + remove_self=remove_self) code_scope[key] = r['func'] arguments.update(r['arguments']) # update arguments arg2call.update(r['arg2call']) # update arg2call @@ -334,7 +311,7 @@ def _jit_intg_func(f, show_code=False, **jit_setting): code_scope_backup = {k: v for k, v in code_scope.items()} # compile functions if show_code: - output_compiled_codes(code, code_scope) + show_compiled_codes(code, code_scope) exec(compile(code, '', 'exec'), code_scope) new_f = code_scope[func_name] new_f.brainpy_data = {key: val for key, val in f.brainpy_data.items()} @@ -346,83 +323,8 @@ def _jit_intg_func(f, show_code=False, **jit_setting): return dict(func=f, arguments=arguments, arg2call=arg2call, nodes=nodes) -class FuncTransformer(ast.NodeTransformer): - """Transform a functional call. - - This class automatically transform a functional call. - For example, in your original code: - - ... code-block:: python - - def update(self, _t, _dt): - V, m, h, n = self.integral(self.V, self.m, self.h, self.n, _t, self.input) - self.spike[:] = (self.V < self.V_th) * (V >= self.V_th) - - you want to add new arguments ``gNa``, ``gK`` and ``gL`` into the - function ``self.integral``. Then this Transformer will help you - automatically do this: - - ... code-block:: python - - def update(self, _t, _dt): - V, m, h, n = self.integral(self.V, self.m, self.h, self.n, _t, self.input, - gNa=gNa, gK=gK, gL=gL) - self.spike[:] = (self.V < self.V_th) * (V >= self.V_th) - - """ - - def __init__(self, func_name, arg_to_append, remove_self=None): - self.func_name = func_name - self.arg_to_append = sorted(arg_to_append) - self.remove_self = remove_self - - def visit_Call(self, node, level=0): - if getattr(node, 'starargs', None) is not None: - raise ValueError("Variable number of arguments (*args) are not supported") - if getattr(node, 'kwargs', None) is not None: - raise ValueError("Keyword arguments (**kwargs) are not supported") - - # get function name - call = tools.ast2code(node.func) - if call == self.func_name: - # args - args = [self.generic_visit(arg) for arg in node.args] - # remove self arg - if self.remove_self: - if args[0].id == self.remove_self: - args.pop(0) - # kwargs - kwargs = [self.generic_visit(keyword) for keyword in node.keywords] - # new kwargs - code = f'f({", ".join([f"{k}={k}" for k in self.arg_to_append])})' - tree = ast.parse(code) - new_keywords = tree.body[0].value.keywords - kwargs.extend(new_keywords) - # final function - return ast.Call(func=node.func, args=args, keywords=kwargs) - return node - - -def _replace_func(code_or_tree, func_call, arg_to_append, remove_self=None): - assert isinstance(func_call, str) - assert isinstance(arg_to_append, (list, tuple, set)) - - if isinstance(code_or_tree, str): - tree = ast.parse(code_or_tree) - elif isinstance(code_or_tree, ast.Module): - tree = code_or_tree - else: - raise ValueError - - transformer = FuncTransformer(func_name=func_call, - arg_to_append=arg_to_append, - remove_self=remove_self) - new_tree = transformer.visit(tree) - return new_tree - - -def _analyze_cls_func(host, code, show_code, code_scope, self_name=None, pop_self=True, **jit_setting): - """ +def _analyze_cls_func(host, code, show_code, self_name=None, pop_self=True, **jit_setting): + """Analyze the bounded function of one object. Parameters ---------- @@ -433,14 +335,7 @@ def _analyze_cls_func(host, code, show_code, code_scope, self_name=None, pop_sel self_name : optional, str The class name, like "self", "cls". show_code : bool - - - Returns - ------- - """ - arguments, arg2call, nodes = set(), dict(), Collector() - # arguments tree = ast.parse(code) if self_name is None: @@ -451,6 +346,20 @@ def _analyze_cls_func(host, code, show_code, code_scope, self_name=None, pop_sel f'{profile.CLASS_KEYWORDS}, but we got {self_name}.') if pop_self: tree.body[0].args.args.pop(0) # remove "self" etc. class argument + + # analyze function body + r = _analyze_cls_func_body(host=host, self_name=self_name, code=code, tree=tree, + show_code=show_code, has_func_def=True, **jit_setting) + code, arguments, arg2call, nodes, code_scope = r + + return code, arguments, arg2call, nodes, code_scope + + +def _analyze_cls_func_body(host, self_name, code, tree, show_code=False, + has_func_def=False, **jit_setting): + arguments, arg2call, nodes, code_scope = set(), dict(), Collector(), dict() + + # all self data self_data = re.findall('\\b' + self_name + '\\.[A-Za-z_][A-Za-z0-9_.]*\\b', code) self_data = list(set(self_data)) @@ -473,7 +382,7 @@ def _analyze_cls_func(host, code, show_code, code_scope, self_name=None, pop_sel data = getattr(target, split_keys[i]) # analyze data - if isinstance(data, math.Variable): + if isinstance(data, math.Variable): # data is a variable arguments.add(f'{target.name}_{split_keys[i]}') arg2call[f'{target.name}_{split_keys[i]}'] = f'{target.name}.{split_keys[-1]}.value' nodes[target.name] = target @@ -482,24 +391,58 @@ def _analyze_cls_func(host, code, show_code, code_scope, self_name=None, pop_sel data_to_replace[key] = f'{target.name}_{split_keys[i]}' else: data_to_replace[key] = f'{target.name}_{split_keys[i]}.{".".join(split_keys[i:])}' - elif isinstance(data, np.random.RandomState): - code_scope[f'{target.name}_{split_keys[i]}'] = np.random # replace RandomState + + elif isinstance(data, np.random.RandomState): # data is a RandomState + # replace RandomState + code_scope[f'{target.name}_{split_keys[i]}'] = np.random # replace the data if len(split_keys) == i + 1: data_to_replace[key] = f'{target.name}_{split_keys[i]}' else: data_to_replace[key] = f'{target.name}_{split_keys[i]}.{".".join(split_keys[i:])}' - elif callable(data): + + elif callable(data): # data is a function assert len(split_keys) == i + 1 r = _jit_func(obj_or_fun=data, show_code=show_code, **jit_setting) - if len(r): - tree = _replace_func(tree, func_call=key, arg_to_append=r['arguments']) - arguments.update(r['arguments']) - arg2call.update(r['arg2call']) - nodes.update(r['nodes']) - code_scope[f'{target.name}_{split_keys[i]}'] = r['func'] - data_to_replace[key] = f'{target.name}_{split_keys[i]}' # replace the data - else: + # if len(r['arguments']): + tree = _replace_func_call_by_tee(tree, func_call=key, arg_to_append=r['arguments']) + # code = _replace_func_call_by_code(code, func_call=key, arg_to_append=r['arguments']) + arguments.update(r['arguments']) + arg2call.update(r['arg2call']) + nodes.update(r['nodes']) + code_scope[f'{target.name}_{split_keys[i]}'] = r['func'] + data_to_replace[key] = f'{target.name}_{split_keys[i]}' # replace the data + + elif isinstance(data, (dict, list, tuple)): # data is a list/tuple/dict of function/object + # get all values + if isinstance(data, dict): # check dict + if len(split_keys) != i + 2 and split_keys[-1] != 'values': + raise errors.BrainPyError(f'Only support iter dict.values(). while we got ' + f'dict.{split_keys[-1]} for data: \n\n{data}') + values = list(data.values()) + iter_name = key + '()' + else: # check list / tuple + assert len(split_keys) == i + 1 + values = list(data) + iter_name = key + + # check iterable values + if len(values) == 0: + raise errors.BrainPyError('Cannot analyze tuple/list/dict with no values.') + if not (isinstance(values[0], Base) or callable(values[0])): + raise errors.BrainPyError(f'Only support JIT an iterable objects of function ' + f'or Base object, but we got:\n\n {data}') + + # replace this for-loop + r = _replace_this_forloop(tree=tree, iter_name=iter_name, loop_values=values, + show_code=show_code, **jit_setting) + tree, _arguments, _arg2call, _nodes, _code_scope = r + arguments.update(_arguments) + arg2call.update(_arg2call) + nodes.update(_nodes) + code_scope.update(_code_scope) + + else: # constants code_scope[f'{target.name}_{split_keys[i]}'] = data # replace the data if len(split_keys) == i + 1: @@ -507,17 +450,230 @@ def _analyze_cls_func(host, code, show_code, code_scope, self_name=None, pop_sel else: data_to_replace[key] = f'{target.name}_{split_keys[i]}.{".".join(split_keys[i:])}' - # final code - tree.body[0].decorator_list.clear() - tree.body[0].args.args.extend([ast.Name(id=a) for a in sorted(arguments)]) - tree.body[0].args.defaults.extend([ast.Constant(None) for _ in sorted(arguments)]) + if has_func_def: + tree.body[0].decorator_list.clear() + tree.body[0].args.args.extend([ast.Name(id=a) for a in sorted(arguments)]) + tree.body[0].args.defaults.extend([ast.Constant(None) for _ in sorted(arguments)]) + + # replace words code = tools.ast2code(tree) - # code = tools.word_replace(code, data_to_replace, exclude_dot=False) code = tools.word_replace(code, data_to_replace, exclude_dot=True) return code, arguments, arg2call, nodes, code_scope +def _replace_this_forloop(tree, iter_name, loop_values, show_code=False, **jit_setting): + assert isinstance(tree, ast.Module) + + replacer = ReplaceThisForLoop(loop_values=loop_values, iter_name=iter_name, + show_code=show_code, **jit_setting) + tree = replacer.visit(tree) + return tree, replacer.arguments, replacer.arg2call, replacer.nodes, replacer.code_scope + + +class ReplaceThisForLoop(ast.NodeTransformer): + def __init__(self, loop_values, iter_name, show_code=False, **jit_setting): + # targets + self.loop_values = loop_values + self.iter_name = iter_name + + # setting + self.show_code = show_code + self.jit_setting = jit_setting + self.success = False + + # results + self.arguments = set() + self.arg2call = dict() + self.nodes = Collector() + self.code_scope = dict() + + def visit_For(self, node): + self.success = True + data_to_replace = Collector() + final_node = ast.Module(body=[]) + + iter_ = tools.ast2code(ast.fix_missing_locations(node.iter)) + if iter_ == self.iter_name: + # target + assert isinstance(node.target, ast.Name) + target = node.target.id + + # module and code + module = ast.Module(body=node.body) + code = tools.ast2code(module) + + # for loop values + for i, value in enumerate(self.loop_values): + if callable(value): + r = _jit_func(obj_or_fun=value, show_code=self.show_code, **self.jit_setting) + tree = _replace_func_call_by_tee(deepcopy(module), + func_call=target, + arg_to_append=r['arguments'], + new_func_name=f'{target}_{i}') + self.arguments.update(r['arguments']) + self.arg2call.update(r['arg2call']) + self.nodes.update(r['nodes']) + + # replace the data + if isinstance(value, Base): + host = value + replace_name = f'{host.name}_{target}' + elif hasattr(value, '__self__') and isinstance(value.__self__, Base): + host = value.__self__ + replace_name = f'{host.name}_{target}' + else: + replace_name = f'{target}_{i}' + self.code_scope[replace_name] = r['func'] + data_to_replace[f'{target}_{i}'] = replace_name + + final_node.body.extend(tree.body) + + elif isinstance(value, Base): + r = _analyze_cls_func_body(host=value, + self_name=target, + code=code, + tree=module, + show_code=self.show_code, + **self.jit_setting) + + new_code, arguments, arg2call, nodes, code_scope = r + self.arguments.update(arguments) + self.arg2call.update(arg2call) + self.arg2call.update(arg2call) + self.nodes.update(nodes) + self.code_scope.update(code_scope) + + final_node.body.extend(ast.parse(new_code).body) + + else: + raise errors.BrainPyError + + final_code = tools.ast2code(final_node) + final_code = tools.word_replace(final_code, data_to_replace, exclude_dot=True) + final_node = ast.parse(final_code) + self.generic_visit(final_node) + return final_node + + +def _replace_func_call_by_tee(tree, func_call, arg_to_append, remove_self=None, + new_func_name=None): + assert isinstance(func_call, str) + assert isinstance(arg_to_append, (list, tuple, set)) + assert isinstance(tree, ast.Module) + + transformer = FuncTransformer(func_name=func_call, + arg_to_append=arg_to_append, + remove_self=remove_self, + new_func_name=new_func_name) + new_tree = transformer.visit(tree) + return new_tree + + +def _replace_func_call_by_code(code, func_call, arg_to_append, remove_self=None): + """Replace functional call. + + This class automatically transform a functional call. + For example, in your original code: + + >>> V, m, h, n = self.integral(self.V, self.m, self.h, self.n, _t, self.input) + + you want to add new arguments ``gNa``, ``gK`` and ``gL`` into the + function ``self.integral``. Then this Transformer will help you + automatically do this: + + >>> V, m, h, n = self.integral(self.V, self.m, self.h, self.n, _t, self.input, + >>> gNa=gNa, gK=gK, gL=gL) + + Parameters + ---------- + code : str + The original code string. + func_call : str + The functional call. + arg_to_append : set/list/tuple of str + The arguments to append. + remove_self : str, optional + The self class name to remove. + + Returns + ------- + new_code : str + The new code string. + """ + assert isinstance(func_call, str) + assert isinstance(arg_to_append, (list, tuple, set)) + + tree = ast.parse(code) + transformer = FuncTransformer(func_name=func_call, + arg_to_append=arg_to_append, + remove_self=remove_self) + new_tree = transformer.visit(tree) + return tools.ast2code(new_tree) + + +class FuncTransformer(ast.NodeTransformer): + """Transform a functional call. + + This class automatically transform a functional call. + For example, in your original code: + + ... code-block:: python + + def update(self, _t, _dt): + V, m, h, n = self.integral(self.V, self.m, self.h, self.n, _t, self.input) + self.spike[:] = (self.V < self.V_th) * (V >= self.V_th) + + you want to add new arguments ``gNa``, ``gK`` and ``gL`` into the + function ``self.integral``. Then this Transformer will help you + automatically do this: + + ... code-block:: python + + def update(self, _t, _dt): + V, m, h, n = self.integral(self.V, self.m, self.h, self.n, _t, self.input, + gNa=gNa, gK=gK, gL=gL) + self.spike[:] = (self.V < self.V_th) * (V >= self.V_th) + + """ + + def __init__(self, func_name, arg_to_append, remove_self=None, new_func_name=None): + self.func_name = func_name + self.new_func_name = new_func_name + self.arg_to_append = sorted(arg_to_append) + self.remove_self = remove_self + + def visit_Call(self, node, level=0): + if getattr(node, 'starargs', None) is not None: + raise ValueError("Variable number of arguments (*args) are not supported") + if getattr(node, 'kwargs', None) is not None: + raise ValueError("Keyword arguments (**kwargs) are not supported") + + # get function name + call = tools.ast2code(node.func) + if call == self.func_name: + # args + args = [self.generic_visit(arg) for arg in node.args] + # remove self arg + if self.remove_self: + if args[0].id == self.remove_self: + args.pop(0) + # kwargs + kwargs = [self.generic_visit(keyword) for keyword in node.keywords] + # new kwargs + code = f'f({", ".join([f"{k}={k}" for k in self.arg_to_append])})' + tree = ast.parse(code) + new_keywords = tree.body[0].value.keywords + kwargs.extend(new_keywords) + # final function + if self.new_func_name: + func_call = ast.parse(f'{self.new_func_name}()').body[0].value.func + else: + func_call = node.func + return ast.Call(func=func_call, args=args, keywords=kwargs) + return node + + def _add_try_except(code): splits = re.compile(r'\)\s*?:').split(code) if len(splits) == 1: @@ -564,7 +720,7 @@ def _form_final_call(f_org, f_rep, arg2call, arguments, nodes, show_code=False, # code, _scope = _add_try_except(code) # code_scope.update(_scope) if show_code: - output_compiled_codes(code, code_scope) + show_compiled_codes(code, code_scope) exec(compile(code, '', 'exec'), code_scope) func = code_scope[f'new_{name}'] return func @@ -605,12 +761,99 @@ def _get_args(f): return class_kw, reduced_args, original_args -def output_compiled_codes(code, scope): +def show_compiled_codes(code, scope): print('The recompiled function:') print('-------------------------') print(code) print() print('The namespace of the above function:') - print('------------------------------------') pprint(scope) print() + + +def _find_all_forloop(code_or_tree): + """Find all for-loops in the code. + + >>> code = ''' + >>> for ch in self._update_channels: + >>> ch.update(_t, _dt) + >>> for ch in self._output_channels: + >>> self.input += ch.update(_t, _dt) + >>> ''' + >>> _find_all_forloop(code) + {'self._output_channels': ('ch', + <_ast.Module object at 0x00000155BD23B730>, + 'self.input += ch.update(_t, _dt)\n'), + 'self._update_channels': ('ch', + <_ast.Module object at 0x00000155B699AD90>, + 'ch.update(_t, _dt)\n')} + + >>> code = ''' + >>> self.pre_spike.push(self.pre.spike) + >>> pre_spike = self.pre_spike.pull() + >>> + >>> self.g[:] = self.integral(self.g, _t, dt=_dt) + >>> for pre_id in range(self.pre.num): + >>> if pre_spike[pre_id]: + >>> start, end = self.pre_slice[pre_id] + >>> for post_id in self.post_ids[start: end]: + >>> self.g[post_id] += self.g_max + >>> + >>> self.post.input[:] += self.output_current(self.g) + >>> ''' + >>> _find_all_forloop(code) + {'range(self.pre.num)': ('pre_id', + <_ast.Module object at 0x000001D0AB120D60>, + 'if pre_spike[pre_id]:\n' + ' start, end = self.pre_slice[pre_id]\n' + ' for post_id in self.post_ids[start:end]:\n' + ' self.g[post_id] += self.g_max\n'), + 'self.post_ids[start:end]': ('post_id', + <_ast.Module object at 0x000001D0AB11B460>, + 'self.g[post_id] += self.g_max\n')} + + Parameters + ---------- + code_or_tree: str, ast.Module + + Returns + ------- + res : dict + with + """ + + # code or tree + if isinstance(code_or_tree, str): + code_or_tree = ast.parse(code_or_tree) + elif isinstance(code_or_tree, ast.Module): + code_or_tree = code_or_tree + else: + raise ValueError + + # finder + finder = FindAllForLoop() + finder.visit(code_or_tree) + + # dictionary results + res = dict() + for iter_, target, body, body_str in zip(finder.for_iter, finder.for_target, + finder.for_body, finder.for_body_str): + res[iter_] = (target, body, body_str) + return res + + +class FindAllForLoop(ast.NodeVisitor): + def __init__(self): + self.for_iter = [] + self.for_target = [] + self.for_body = [] + self.for_body_str = [] + + def visit_For(self, node): + self.for_target.append(tools.ast2code(ast.fix_missing_locations(node.target))) + self.for_iter.append(tools.ast2code(ast.fix_missing_locations(node.iter))) + self.for_body.append(ast.Module(body=[deepcopy(r) for r in node.body])) + codes = tuple(tools.ast2code(ast.fix_missing_locations(r)) for r in node.body) + self.for_body_str.append('\n'.join(codes)) + + self.generic_visit(node) diff --git a/tests/math/numpy/test_ast2numba.py b/tests/math/numpy/test_ast2numba.py index dd62f4d1..3e020ea7 100644 --- a/tests/math/numpy/test_ast2numba.py +++ b/tests/math/numpy/test_ast2numba.py @@ -5,6 +5,8 @@ import re from pprint import pprint import brainpy as bp +from brainpy.math.numpy.ast2numba import FindAllForLoop +from brainpy.math.numpy.ast2numba import _find_all_forloop from brainpy.math.numpy.ast2numba import FuncTransformer from brainpy.math.numpy.ast2numba import _jit_cls_func from brainpy.tools import ast2code @@ -294,6 +296,106 @@ def test_cls_func_ampa1(): pprint(r['nodes']) +def test_container1(): + class HH(bp.NeuGroup): + def __init__(self, size, ENa=50., EK=-77., EL=-54.387, + C=1.0, gNa=120., gK=36., gL=0.03, V_th=20., + **kwargs): + # parameters + self.ENa = ENa + self.EK = EK + self.EL = EL + self.C = C + self.gNa = bp.math.Variable(gNa) + self.gK = gK + self.gL = gL + self.V_th = bp.math.Variable(V_th) + + # variables + self.V = bp.math.Variable(bp.math.ones(size) * -65.) + self.m = bp.math.Variable(bp.math.ones(size) * 0.5) + self.h = bp.math.Variable(bp.math.ones(size) * 0.6) + self.n = bp.math.Variable(bp.math.ones(size) * 0.32) + self.spike = bp.math.Variable(bp.math.zeros(size, dtype=bool)) + self.input = bp.math.Variable(bp.math.zeros(size)) + + super(HH, self).__init__(size=size, **kwargs) + + @bp.odeint(method='rk4') + def integral(self, V, m, h, n, t, Iext): + alpha = 0.1 * (V + 40) / (1 - bp.math.exp(-(V + 40) / 10)) + beta = 4.0 * bp.math.exp(-(V + 65) / 18) + dmdt = alpha * (1 - m) - beta * m + + alpha = 0.07 * bp.math.exp(-(V + 65) / 20.) + beta = 1 / (1 + bp.math.exp(-(V + 35) / 10)) + dhdt = alpha * (1 - h) - beta * h + + alpha = 0.01 * (V + 55) / (1 - bp.math.exp(-(V + 55) / 10)) + beta = 0.125 * bp.math.exp(-(V + 65) / 80) + dndt = alpha * (1 - n) - beta * n + + I_Na = (self.gNa * m ** 3.0 * h) * (V - self.ENa) + I_K = (self.gK * n ** 4.0) * (V - self.EK) + I_leak = self.gL * (V - self.EL) + dVdt = (- I_Na - I_K - I_leak + Iext) / self.C + + return dVdt, dmdt, dhdt, dndt + + def update(self, _t, _i): + V, m, h, n = self.integral(self.V, self.m, self.h, self.n, _t, self.input) + self.spike[:] = (self.V < self.V_th) * (V >= self.V_th) + self.V[:] = V + self.m[:] = m + self.h[:] = h + self.n[:] = n + self.input[:] = 0. + + class AMPA_vec(bp.TwoEndConn): + def __init__(self, pre, post, conn, delay=0., g_max=0.10, E=0., tau=2.0, **kwargs): + super(AMPA_vec, self).__init__(pre=pre, post=post, **kwargs) + + # parameters + self.g_max = g_max + self.E = E + self.tau = tau + self.delay = delay + + # connections + self.conn = conn(pre.size, post.size) + self.pre_ids, self.post_ids = conn.requires('pre_ids', 'post_ids') + self.size = len(self.pre_ids) + + # data + self.s = bp.math.Variable(bp.math.zeros(self.size)) + self.g = self.register_constant_delay('g', size=self.size, delay=delay) + + @bp.odeint + def int_s(self, s, t): + return - s / self.tau + + def update(self, _t, _i): + for i in range(self.size): + pre_id = self.pre_ids[i] + self.s[i] = self.int_s(self.s[i], _t) + self.s[i] += self.pre.spike[pre_id] + self.g.push(i, self.g_max * self.s[i]) + post_id = self.post_ids[i] + self.post.input[post_id] -= self.g.pull(i) * (self.post.V[post_id] - self.E) + + hh = HH(10) + ampa = AMPA_vec(pre=hh, post=hh, conn=bp.connect.All2All(), delay=10.) + net = bp.Network(hh, ampa) + + r = _jit_cls_func(net.update, show_code=True) + pprint(r['func']) + pprint('arguments:') + pprint(r['arguments']) + pprint('arg2call:') + pprint(r['arg2call']) + pprint('nodes:') + pprint(r['nodes']) + def test_hh1(): class HH(bp.NeuGroup): @@ -506,3 +608,112 @@ def test_hh_ampa_net1(): bp.math.jit(net, show_code=True) + +def test_FindForLoop1(): + code = ''' +for ch in self._update_channels: + ch.update(_t, _dt) +for ch in self._output_channels: + self.input += ch.update(_t, _dt) + +# update variables +V = self.V + self.input / self.C / self.A * _dt +self.spike[:] = math.logical_and(V >= self.Vth, self.V < self.Vth) +self.V[:] = V +self.input[:] = 0. + ''' + tree = ast.parse(code) + + finder = FindAllForLoop() + finder.visit(tree) + + print() + pprint(finder.for_target) + pprint(finder.for_iter) + pprint(finder.for_body) + pprint(finder.for_body_str) + + print() + pprint(_find_all_forloop(tree)) + + +def test_FindForLoop2(): + code = ''' +for step in self.child_steps.values(): + step(_t, _dt) + ''' + + tree = ast.parse(code) + + finder = FindAllForLoop() + finder.visit(tree) + + print() + pprint(finder.for_target) + pprint(finder.for_iter) + pprint(finder.for_body) + pprint(finder.for_body_str) + + print() + pprint(_find_all_forloop(tree)) + +def test_FindForLoop3(): + code = ''' +for i in range(self.num): + spike = False + refractory = (_t - self.t_last_spike[i] <= self.t_refractory) + if not refractory: + V = self.integral(self.V[i], _t, self.input[i], dt=_dt) + spike = (V >= self.V_th) + if spike: + V = self.V_reset + self.t_last_spike[i] = _t + refractory = True + self.V[i] = V + self.spike[i] = spike + self.refractory[i] = refractory +self.input[:] = 0. + ''' + tree = ast.parse(code) + + finder = FindAllForLoop() + finder.visit(tree) + + print() + pprint(finder.for_target) + pprint(finder.for_iter) + pprint(finder.for_body) + pprint(finder.for_body_str) + + print() + pprint(_find_all_forloop(tree)) + +def test_FindForLoop4(): + code = ''' +self.pre_spike.push(self.pre.spike) +pre_spike = self.pre_spike.pull() + +self.g[:] = self.integral(self.g, _t, dt=_dt) +for pre_id in range(self.pre.num): + if pre_spike[pre_id]: + start, end = self.pre_slice[pre_id] + for post_id in self.post_ids[start: end]: + self.g[post_id] += self.g_max + +self.post.input[:] += self.output_current(self.g) + ''' + tree = ast.parse(code) + + finder = FindAllForLoop() + finder.visit(tree) + + print() + pprint(finder.for_target) + pprint(finder.for_iter) + pprint(finder.for_body) + pprint(finder.for_body_str) + + print() + pprint(_find_all_forloop(tree)) + + -- 2.34.1 From 180446f38d349515a14aec417a28994652344294 Mon Sep 17 00:00:00 2001 From: chaoming Date: Sun, 12 Sep 2021 13:47:39 +0800 Subject: [PATCH 27/30] fix bugs of list/tuple/dict compilation --- brainpy/math/numpy/ast2numba.py | 169 +++++++++++++++++++++++--------- 1 file changed, 123 insertions(+), 46 deletions(-) diff --git a/brainpy/math/numpy/ast2numba.py b/brainpy/math/numpy/ast2numba.py index 8892c360..f79dd2ab 100644 --- a/brainpy/math/numpy/ast2numba.py +++ b/brainpy/math/numpy/ast2numba.py @@ -37,22 +37,29 @@ def jit(obj_or_fun, show_code=False, **jit_setting): if callable(obj_or_fun): # Function if isinstance(obj_or_fun, Function): - return jit_Func(obj_or_fun, show_code=show_code, **jit_setting) + return jit_Func(obj_or_fun, + show_code=show_code, + **jit_setting) # Base elif isinstance(obj_or_fun, Base): - return jit_Base(func=obj_or_fun.__call__, host=obj_or_fun, + return jit_Base(func=obj_or_fun.__call__, + host=obj_or_fun, name=obj_or_fun.name + '_call', show_code=show_code, **jit_setting) # integrator elif hasattr(obj_or_fun, '__name__') and obj_or_fun.__name__.startswith(DE_INT): - return jit_integrator(intg=obj_or_fun, show_code=show_code, **jit_setting) + return jit_integrator(intg=obj_or_fun, + show_code=show_code, + **jit_setting) # bounded method elif hasattr(obj_or_fun, '__self__') and isinstance(obj_or_fun.__self__, Base): - return jit_Base(func=obj_or_fun, host=obj_or_fun.__self__, - show_code=show_code, **jit_setting) + return jit_Base(func=obj_or_fun, + host=obj_or_fun.__self__, + show_code=show_code, + **jit_setting) else: # native function @@ -63,7 +70,9 @@ def jit(obj_or_fun, show_code=False, **jit_setting): return obj_or_fun else: - return jit_DS(obj_or_fun, show_code=show_code, **jit_setting) + return jit_DS(obj_or_fun, + show_code=show_code, + **jit_setting) def jit_DS(obj_or_fun, show_code=False, **jit_setting): @@ -135,21 +144,29 @@ def _jit_func(obj_or_fun, show_code=False, **jit_setting): if callable(obj_or_fun): # integrator if hasattr(obj_or_fun, '__name__') and obj_or_fun.__name__.startswith(DE_INT): - return _jit_intg_func(obj_or_fun, show_code=show_code, **jit_setting) + return _jit_intg_func(obj_or_fun, + show_code=show_code, + **jit_setting) # bounded method elif hasattr(obj_or_fun, '__self__') and isinstance(obj_or_fun.__self__, Base): - return _jit_cls_func(obj_or_fun, host=obj_or_fun.__self__, - show_code=show_code, **jit_setting) + return _jit_cls_func(obj_or_fun, + host=obj_or_fun.__self__, + show_code=show_code, + **jit_setting) # wrapped function elif isinstance(obj_or_fun, Function): - return _jit_Function(obj_or_fun, show_code=show_code, **jit_setting) + return _jit_Function(obj_or_fun, + show_code=show_code, + **jit_setting) # base class function elif isinstance(obj_or_fun, Base): - return _jit_cls_func(obj_or_fun.__call__, host=obj_or_fun, - show_code=show_code, **jit_setting) + return _jit_cls_func(obj_or_fun.__call__, + host=obj_or_fun, + show_code=show_code, + **jit_setting) else: # native function @@ -406,7 +423,6 @@ def _analyze_cls_func_body(host, self_name, code, tree, show_code=False, r = _jit_func(obj_or_fun=data, show_code=show_code, **jit_setting) # if len(r['arguments']): tree = _replace_func_call_by_tee(tree, func_call=key, arg_to_append=r['arguments']) - # code = _replace_func_call_by_code(code, func_call=key, arg_to_append=r['arguments']) arguments.update(r['arguments']) arg2call.update(r['arg2call']) nodes.update(r['nodes']) @@ -426,16 +442,12 @@ def _analyze_cls_func_body(host, self_name, code, tree, show_code=False, values = list(data) iter_name = key - # check iterable values - if len(values) == 0: - raise errors.BrainPyError('Cannot analyze tuple/list/dict with no values.') - if not (isinstance(values[0], Base) or callable(values[0])): - raise errors.BrainPyError(f'Only support JIT an iterable objects of function ' - f'or Base object, but we got:\n\n {data}') - # replace this for-loop - r = _replace_this_forloop(tree=tree, iter_name=iter_name, loop_values=values, - show_code=show_code, **jit_setting) + r = _replace_this_forloop(tree=tree, + iter_name=iter_name, + loop_values=values, + show_code=show_code, + **jit_setting) tree, _arguments, _arg2call, _nodes, _code_scope = r arguments.update(_arguments) arg2call.update(_arg2call) @@ -463,16 +475,63 @@ def _analyze_cls_func_body(host, self_name, code, tree, show_code=False, def _replace_this_forloop(tree, iter_name, loop_values, show_code=False, **jit_setting): + """Replace the given for-loop. + + This function aims to replace the specific for-loop structure, like: + + replace this for-loop + + >>> def update(_t, _dt): + >>> for step in self.child_steps.values(): + >>> step(_t, _dt) + + to + + >>> def update(_t, _dt, AMPA_vec0_delay_g_data=None, AMPA_vec0_delay_g_in_idx=None, + >>> AMPA_vec0_delay_g_out_idx=None, AMPA_vec0_s=None, HH0_V=None, HH0_V_th=None, + >>> HH0_gNa=None, HH0_h=None, HH0_input=None, HH0_m=None, HH0_n=None, HH0_spike=None): + >>> HH0_step(_t, _dt, HH0_V=HH0_V, HH0_V_th=HH0_V_th, HH0_gNa=HH0_gNa, + >>> HH0_h=HH0_h, HH0_input=HH0_input, HH0_m=HH0_m, HH0_n=HH0_n, + >>> HH0_spike=HH0_spike) + >>> AMPA_vec0_step(_t, _dt, AMPA_vec0_delay_g_data=AMPA_vec0_delay_g_data, + >>> AMPA_vec0_delay_g_in_idx=AMPA_vec0_delay_g_in_idx, + >>> AMPA_vec0_delay_g_out_idx=AMPA_vec0_delay_g_out_idx, + >>> AMPA_vec0_s=AMPA_vec0_s, HH0_V=HH0_V, HH0_input=HH0_input, + >>> HH0_spike=HH0_spike) + >>> AMPA_vec0_delay_g_step(_t, _dt, AMPA_vec0_delay_g_in_idx=AMPA_vec0_delay_g_in_idx, + >>> AMPA_vec0_delay_g_out_idx=AMPA_vec0_delay_g_out_idx) + + Parameters + ---------- + tree : ast.Module + The target code tree. + iter_name : str + The for-loop iter. + loop_values : list/tuple + The iter contents in the current loop. + show_code : bool + Whether show the formatted code. + """ assert isinstance(tree, ast.Module) - replacer = ReplaceThisForLoop(loop_values=loop_values, iter_name=iter_name, - show_code=show_code, **jit_setting) + replacer = ReplaceThisForLoop(loop_values=loop_values, + iter_name=iter_name, + show_code=show_code, + **jit_setting) tree = replacer.visit(tree) + if not replacer.success: + raise errors.BrainPyError(f'Do not find the for-loop for "{iter_name}", ' + f'currently we only support for-loop like ' + f'"for xxx in {iter_name}:". Does your for-loop ' + f'structure is not like this. ') + return tree, replacer.arguments, replacer.arg2call, replacer.nodes, replacer.code_scope class ReplaceThisForLoop(ast.NodeTransformer): def __init__(self, loop_values, iter_name, show_code=False, **jit_setting): + self.success = False + # targets self.loop_values = loop_values self.iter_name = iter_name @@ -480,7 +539,6 @@ class ReplaceThisForLoop(ast.NodeTransformer): # setting self.show_code = show_code self.jit_setting = jit_setting - self.success = False # results self.arguments = set() @@ -489,28 +547,36 @@ class ReplaceThisForLoop(ast.NodeTransformer): self.code_scope = dict() def visit_For(self, node): - self.success = True - data_to_replace = Collector() - final_node = ast.Module(body=[]) - iter_ = tools.ast2code(ast.fix_missing_locations(node.iter)) - if iter_ == self.iter_name: + + if iter_.strip() == self.iter_name: + data_to_replace = Collector() + final_node = ast.Module(body=[]) + self.success = True + # target - assert isinstance(node.target, ast.Name) + if not isinstance(node.target, ast.Name): + raise errors.BrainPyError(f'Only support scalar iter, like "for x in xxxx:", not "for ' + f'{tools.ast2code(ast.fix_missing_locations(node.target))} ' + f'in {iter_}:') target = node.target.id - # module and code - module = ast.Module(body=node.body) - code = tools.ast2code(module) - # for loop values for i, value in enumerate(self.loop_values): - if callable(value): - r = _jit_func(obj_or_fun=value, show_code=self.show_code, **self.jit_setting) + # module and code + module = ast.Module(body=deepcopy(node).body) + code = tools.ast2code(module) + + if callable(value): # transform functions + r = _jit_func(obj_or_fun=value, + show_code=self.show_code, + **self.jit_setting) tree = _replace_func_call_by_tee(deepcopy(module), func_call=target, arg_to_append=r['arguments'], new_func_name=f'{target}_{i}') + + # update import parameters self.arguments.update(r['arguments']) self.arg2call.update(r['arg2call']) self.nodes.update(r['nodes']) @@ -529,7 +595,7 @@ class ReplaceThisForLoop(ast.NodeTransformer): final_node.body.extend(tree.body) - elif isinstance(value, Base): + elif isinstance(value, Base): # transform Base objects r = _analyze_cls_func_body(host=value, self_name=target, code=code, @@ -547,11 +613,17 @@ class ReplaceThisForLoop(ast.NodeTransformer): final_node.body.extend(ast.parse(new_code).body) else: - raise errors.BrainPyError + raise errors.BrainPyError(f'Only support JIT an iterable objects of function ' + f'or Base object, but we got:\n\n {value}') + + # replace words + final_code = tools.ast2code(final_node) + final_code = tools.word_replace(final_code, data_to_replace, exclude_dot=True) + final_node = ast.parse(final_code) + + else: + final_node = node - final_code = tools.ast2code(final_node) - final_code = tools.word_replace(final_code, data_to_replace, exclude_dot=True) - final_node = ast.parse(final_code) self.generic_visit(final_node) return final_node @@ -661,10 +733,15 @@ class FuncTransformer(ast.NodeTransformer): # kwargs kwargs = [self.generic_visit(keyword) for keyword in node.keywords] # new kwargs - code = f'f({", ".join([f"{k}={k}" for k in self.arg_to_append])})' - tree = ast.parse(code) - new_keywords = tree.body[0].value.keywords - kwargs.extend(new_keywords) + arg_to_append = deepcopy(self.arg_to_append) + for arg in kwargs: + if arg.arg in arg_to_append: + arg_to_append.remove(arg.arg) + if len(arg_to_append): + code = f'f({", ".join([f"{k}={k}" for k in arg_to_append])})' + tree = ast.parse(code) + new_keywords = tree.body[0].value.keywords + kwargs.extend(new_keywords) # final function if self.new_func_name: func_call = ast.parse(f'{self.new_func_name}()').body[0].value.func -- 2.34.1 From 36c8393b4cf87d6cf1ca7a2d5bf695ce591e9be6 Mon Sep 17 00:00:00 2001 From: chaoming Date: Sun, 12 Sep 2021 13:48:06 +0800 Subject: [PATCH 28/30] fix bugs monitor func --- brainpy/simulation/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/brainpy/simulation/utils.py b/brainpy/simulation/utils.py index 7f5e447f..385a243e 100644 --- a/brainpy/simulation/utils.py +++ b/brainpy/simulation/utils.py @@ -363,6 +363,7 @@ def build_monitor_func(monitors, show_code=False): for node, key, target, variable, idx, interval in monitors: code_scope[node.name] = node + code_scope[target.name] = target # get data data = target -- 2.34.1 From 3b5a6942008bc90dc62c25083ad894152a480b9d Mon Sep 17 00:00:00 2001 From: chaoming Date: Sun, 12 Sep 2021 14:00:01 +0800 Subject: [PATCH 29/30] support multi-scale modeling in NumPy jit --- .gitignore | 1 + TODO.md | 16 +- brainpy/math/numpy/ast2numba.py | 3 + brainpy/math/numpy/compilation.py | 2 +- brainpy/simulation/brainobjects/neuron.py | 12 +- docs/index.rst | 4 +- docs/quickstart/dynamics_simulation.ipynb | 54 ++++- docs/tutorial_analysis/dynamics_analysis.py | 256 ++++++++++++++++++++ docs/tutorial_jit/jit_jax.rst | 0 docs/tutorial_jit/jit_numba.rst | 0 10 files changed, 338 insertions(+), 10 deletions(-) create mode 100644 docs/tutorial_analysis/dynamics_analysis.py create mode 100644 docs/tutorial_jit/jit_jax.rst create mode 100644 docs/tutorial_jit/jit_numba.rst diff --git a/.gitignore b/.gitignore index 75139841..732d0070 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ publishment.md #experimental/ .vscode +examples/ examples/recurrent_neural_network/neurogym develop/iconip_paper develop/benchmark/COBA/results diff --git a/TODO.md b/TODO.md index 8d4f4af8..7d3ac9ac 100644 --- a/TODO.md +++ b/TODO.md @@ -14,6 +14,7 @@ ## Base +- [ ] ``vars()``: must check whether has circular references - [x] ``Collector``: must check whether the ``(key,value)`` pair are duplicate (done @2021/08/26 by @chaoming) - [x] ``nodes``: slove the problem of circular reference (done @2021/08/24 by @chaoming) - [x] ``ints``: get integrators based on all nodes (done @2021/08/24 by @chaoming) @@ -26,7 +27,7 @@ ## Math -- [ ] **[Numpy]**: JIT compilation in numpy backend supports ``Base`` and ``Function`` objects +- [x] **[Numpy]**: JIT compilation in numpy backend supports ``Base`` and ``Function`` objects (done by chaoming@2021/09/08) - [ ] **[Numpy]**: support JIT in `fft` sub-module - [x] **[Numpy]:** support JIT compilation in numpy backend (done @ 2021.09.01 by @chaoming) - [x] **[Numpy]:** support 'fft' (done @ 2021.09.03 by @chaoming) @@ -107,7 +108,16 @@ ## Documentation +- [ ] +- [ ] documentation +- [ ] tutorial for "Numerical Solvers": more precise and intuitive +- [x] tutorial for "JIT compilation": BrainPy's JIT for class objects (done by chaoming@2021.09.08) +- [x] tutorial for "Dynamics Simulation", detail the function of ``brainpy.DynamicalSystem`` (done by chaoming@2021/09/09) - [x] documentation for ``math`` module (done @2021.09) +- [ ] documentation for BrainModels + - [ ] documentation for neuron models + - [ ] documentation for synapse models + - [ ] documentation for COBA, CUBA synapses - [ ] detailed documentation for numerical solvers of SDEs - [x] doc comments for ODEs, like Euler, RK2, etc. We should provide the detailed mathematical equations, and the corresponding suggestions for the corresponding algorithm. (done @2021.09.01 @chaoming) - [x] APIs for integrators (done @2021/08/23 by @chaoming) @@ -137,6 +147,10 @@ + + + + # Version 1.0.2 diff --git a/brainpy/math/numpy/ast2numba.py b/brainpy/math/numpy/ast2numba.py index f79dd2ab..9a966d6d 100644 --- a/brainpy/math/numpy/ast2numba.py +++ b/brainpy/math/numpy/ast2numba.py @@ -84,6 +84,9 @@ def jit_DS(obj_or_fun, show_code=False, **jit_setting): raise errors.UnsupportedError(f'JIT compilation in numpy backend only ' f'supports {Base.__name__}, but we got ' f'{type(obj_or_fun)}.') + if not hasattr(obj_or_fun, 'steps'): + raise errors.BrainPyError(f'Please init this DynamicalSystem {obj_or_fun} first, ' + f'then apply JIT.') # function analysis for key, step in list(obj_or_fun.steps.items()): diff --git a/brainpy/math/numpy/compilation.py b/brainpy/math/numpy/compilation.py index 8260ec29..fa42e4de 100644 --- a/brainpy/math/numpy/compilation.py +++ b/brainpy/math/numpy/compilation.py @@ -116,7 +116,7 @@ def jit(obj_or_fun, nopython=True, fastmath=True, parallel=False, nogil=False, # checking if ast2numba is None or numba is None: raise errors.PackageMissingError('JIT compilation in numpy backend need Numba. ' - 'Please install numba via: \n\n' + 'Please install numba via: \n' '>>> pip install numba\n' '>>> # or \n' '>>> conda install numba') diff --git a/brainpy/simulation/brainobjects/neuron.py b/brainpy/simulation/brainobjects/neuron.py index 71802c44..26905ebb 100644 --- a/brainpy/simulation/brainobjects/neuron.py +++ b/brainpy/simulation/brainobjects/neuron.py @@ -128,14 +128,22 @@ class CondNeuGroup(NeuGroup): for ch in self._update_channels: ch.update(_t, _dt) for ch in self._output_channels: - self.input += ch.update(_t, _dt) + ch.update(_t, _dt) + self.input += ch.I # update variables - V = self.V + self.input / self.C / self.A * _dt + V = self.V + self.input / self.C * _dt + # V = self.V + self.input / self.C / self.A * _dt self.spike[:] = math.logical_and(V >= self.Vth, self.V < self.Vth) self.V[:] = V self.input[:] = 0. + def __getattr__(self, item): + child_channels = super(CondNeuGroup, self).__getattribute__('child_channels') + if item in child_channels: + return child_channels[item] + else: + return super(CondNeuGroup, self).__getattribute__(item) # --------------------------------------------------------- diff --git a/docs/index.rst b/docs/index.rst index 3197b2e0..b40da6c4 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -36,7 +36,9 @@ high-performance brain modeling. Among its key ingredients, BrainPy supports: .. toctree:: :maxdepth: 1 - :caption: Tutorials for math module + :caption: JIT tutorials + + .. toctree:: diff --git a/docs/quickstart/dynamics_simulation.ipynb b/docs/quickstart/dynamics_simulation.ipynb index 965e1bf8..955854d3 100644 --- a/docs/quickstart/dynamics_simulation.ipynb +++ b/docs/quickstart/dynamics_simulation.ipynb @@ -560,7 +560,9 @@ { "cell_type": "code", "execution_count": 18, - "metadata": {}, + "metadata": { + "code_folding": [] + }, "outputs": [ { "data": { @@ -589,9 +591,51 @@ "inputs = bp.math.linspace(1., 2., 10000)\n", "\n", "fnh4.run(duration=100, \n", - " inputs=('Y.I', # specify 'target' by the absolute path access\n", - " inputs, # specify 'value' with the iterable \"inputs\"\n", - " 'iter')) # \"iter\" input 'type' must be explicitly specified\n", + " inputs=('Y.I', # specify 'target' by the absolute path access\n", + " inputs, # specify 'value' with an iterable array\n", + " 'iter')) # \"iter\" input 'type' must be explicitly specified\n", + "\n", + "plt.plot(fnh4.mon.ts, fnh4.mon.v, label='v')\n", + "plt.legend()" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "code_folding": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "inputs = lambda: yield 1.5\n", + "\n", + "fnh4.run(duration=100, \n", + " inputs=('Y.I', # specify 'target' by the absolute path access\n", + " inputs, # specify 'value' with an iterable function\n", + " 'iter')) # \"iter\" input 'type' must be explicitly specified\n", "\n", "plt.plot(fnh4.mon.ts, fnh4.mon.v, label='v')\n", "plt.legend()" @@ -1273,7 +1317,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.10" + "version": "3.8.11" }, "latex_envs": { "LaTeX_envs_menu_present": true, diff --git a/docs/tutorial_analysis/dynamics_analysis.py b/docs/tutorial_analysis/dynamics_analysis.py new file mode 100644 index 00000000..6bb0b1e3 --- /dev/null +++ b/docs/tutorial_analysis/dynamics_analysis.py @@ -0,0 +1,256 @@ +# -*- coding: utf-8 -*- +# --- +# jupyter: +# jupytext: +# formats: ipynb,py:percent +# text_representation: +# extension: .py +# format_name: percent +# format_version: '1.3' +# jupytext_version: 1.11.5 +# kernelspec: +# display_name: brainpy +# language: python +# name: brainpy +# --- + +# %% [markdown] +# # Dynamics Analysis + +# %% [markdown] +# **Contents**: +# +# - [Phase Plane Analysis](#Phase-Plane-Analysis) +# - [Bifurcation Analysis](#Bifurcation-Analysis) +# - [Fast-Slow System Bifurcation](#Fast-Slow-System-Bifurcation) + +# %% [markdown] +# In addition to the flexible and effecient *neurodynamics simulation*, another ambition of BrainPy is to provide an integrative platform for **neurodynamics analysis**. + +# %% [markdown] +# As is known to us all, dynamics analysis is necessary in neurodynamics. This is because blind simulation of nonlinear systems is likely to produce few results or misleading results. For example, attractors and repellors can be easily obtained through simulation by time forward and backward, while saddles can be hard to find. +# +# Currently, BrainPy supports neurodynamics analysis for low-dimensional dynamical systems. Specifically, BrainPy provides the following methods for dynamics analysis: +# +# 1. phase plane analysis for one-dimensional and two-dimensional systems; +# 2. codimension one and codimension two bifurcation analysis; +# 3. bifurcation analysis of the fast-slow system. +# + +# %% [markdown] +# In this section, I will illustrate how to do neuron dynamics analysis in BrainPy and how BrainPy implements it. + +# %% +import sys + +sys.path.append('../../') + +import brainpy as bp + +# %% [markdown] +# ## Phase Plane Analysis + +# %% [markdown] +# Here, I will illustrate how to do phase plane analysis by using a well-known neuron model FitzHugh-Nagumo model. + +# %% [markdown] +# **FitzHugh-Nagumo model** + +# %% [markdown] +# The FitzHugh-Nagumo model is given by: +# +# $$ +# \frac {dV} {dt} = V(1 - \frac {V^2} 3) - w + I_{ext} \\ +# \tau \frac {dw} {dt} = V + a - b w +# $$ +# +# There are two variables $V$ and $w$, so this is a two-dimensional system with three parameters $a, b$ and $\tau$. + +# %% +a = 0.7 +b = 0.8 +tau = 12.5 +Vth = 1.9 + + +@bp.odeint +def int_fhn(V, w, t, Iext): + dw = (V + a - b * w) / tau + dV = V - V * V * V / 3 - w + Iext + return dV, dw + + +# %% [markdown] +# Phase Plane Analysis is implemented in `brainpy.analysis.PhasePlane`. It receives the following parameters: +# +# - ``integrals``: The integral functions to be analysis. +# - ``target_vars``: The variables to be analuzed. It must a dictionary with the format of `{var: variable range}`. +# - ``fixed_vars``: The variables to be fixed (optional). +# - ``pars_update``: Parameters to update (optional). + +# %% [markdown] +# `brainpy.analysis.PhasePlane` provides interface to analyze the system's +# +# - **nullcline**: The zero-growth isoclines, such as $g(x, y)=0$ and $g(x, y)=0$. +# - **fixed points**: The equilibrium points of the system, which are located at all of the nullclines intersect. +# - **vector filed**: The vector field of the system. +# - **Trajectory**: A given simulation trajectory with the fixed variables. + +# %% [markdown] +# Here we perform a phase plane analysis with parameters $a=0.7, b=0.8, \tau=12.5$, and input $I_{ext} = 0.8$. + +# %% +analyzer = bp.analysis.PhasePlane( + integrals=int_fhn, + target_vars={'V': [-3, 3], 'w': [-3., 3.]}, + pars_update={'Iext': 0.8}) +analyzer.plot_nullcline() +analyzer.plot_vector_field() +analyzer.plot_fixed_point() +analyzer.plot_trajectory([{'V': -2.8, 'w': -1.8}], + duration=100., + show=True) + + +# %% [markdown] +# We can see an unstable-node at the point (v=-0.27, w=0.53) inside a limit cycle. Then we can run a simulation with the same parameters and initial values to see the periodic activity that correspond to the limit cycle. + +# %% +class FHN(bp.NeuGroup): + def __init__(self, num, **kwargs): + super(FHN, self).__init__(size=num, **kwargs) + self.V = bp.math.Variable(bp.math.ones(num) * -2.8) + self.w = bp.math.Variable(bp.math.ones(num) * -1.8) + self.Iext = bp.math.Variable(bp.math.zeros(num)) + + def update(self, _t, _i): + self.V[:], self.w[:] = int_fhn(self.V, self.w, _t, self.Iext) + + +group = FHN(1, monitors=['V', 'w']) +group.run(100., inputs=('Iext', 0.8, 'fix', '=')) +bp.visualize.line_plot(group.mon.ts, group.mon.V, legend='v', ) +bp.visualize.line_plot(group.mon.ts, group.mon.w, legend='w', show=True) + +# %% [markdown] +# Note that the `fixed_vars` can be used to specify the neuron model's state `ST`, it can also be used to specify the functional arguments in integrators (like the `Iext` in `int_v()`). + +# %% [markdown] +# ## Bifurcation Analysis + +# %% [markdown] +# Bifurcation analysis is implemented within `brainpy.analysis.Bifurcation`. Which support codimension-1 and codimension-2 bifurcation analysis. Specifically, it receives the following parameter settings: +# +# - ``integrals``: The integral functions to be analysis. +# - ``target_pars``: The target parameters. Must be a dictionary with the format of `{par: parameter range}`. +# - ``target_vars``: The target variables. Must be a dictionary with the format of `{var: variable range}`. +# - ``fixed_vars``: The fixed variables. +# - ``pars_update``: The parameters to update. + +# %% [markdown] +# **Codimension 1 bifurcation analysis** + +# %% [markdown] +# We will first see the codimension 1 bifurcation anlysis of the model. For example, we vary the input $I_{ext}$ between 0 to 1 and see how the system change it's stability. + +# %% +analyzer = bp.analysis.Bifurcation( + integrals=int_fhn, + target_pars={'Iext': [0., 1.]}, + target_vars={'V': [-3, 3], 'w': [-3., 3.]}, + numerical_resolution=0.001, +) +res = analyzer.plot_bifurcation(show=True) + +# %% [markdown] +# **Codimension 2 bifurcation analysis** + +# %% [markdown] +# We simulaneously change $I_{ext}$ and parameter $a$. + +# %% +analyzer = bp.analysis.Bifurcation( + integrals=int_fhn, + target_pars=dict(a=[0.5, 1.], Iext=[0., 1.]), + target_vars=dict(V=[-3, 3], w=[-3., 3.]), + numerical_resolution=0.01, +) +res = analyzer.plot_bifurcation(show=True) + +# %% [markdown] +# ## Fast-Slow System Bifurcation + +# %% [markdown] +# BrainPy also provides a tool for fast-slow system bifurcation analysis by using `brainpy.analysis.FastSlowBifurcation`. This method is proposed by John Rinzel [1, 2, 3]. (J Rinzel, 1985, 1986, 1987) proposed that in a fast-slow dynamical system, we can treat the slow variables as the bifurcation parameters, and then study how the different value of slow variables affect the bifurcation of the fast sub-system. +# +# +# `brainpy.analysis.FastSlowBifurcation` is very usefull in the bursting neuron analysis. I will illustrate this by using the Hindmarsh-Rose model. The Hindmarsh–Rose model of neuronal activity is aimed to study the spiking-bursting behavior of the membrane potential observed in experiments made with a single neuron. Its dynamics are governed by: +# +# $$ +# \begin{align} +# \frac{d V}{d t} &= y - a V^3 + b V^2 - z + I\\ +# \frac{d y}{d t} &= c - d V^2 - y\\ +# \frac{d z}{d t} &= r (s (V - V_{rest}) - z) +# \end{align} +# $$ +# +# +# +# +# + +# %% [markdown] +# First of all, let's define the Hindmarsh–Rose model with BrainPy. + +# %% +a = 1. +b = 3. +c = 1. +d = 5. +s = 4. +x_r = -1.6 +r = 0.001 +Vth = 1.9 + + +@bp.odeint(method='rk4', dt=0.02) +def int_hr(x, y, z, t, Isyn): + dx = y - a * x ** 3 + b * x * x - z + Isyn + dy = c - d * x * x - y + dz = r * (s * (x - x_r) - z) + return dx, dy, dz + + +# %% [markdown] +# We now can start to analysis the underlying bifurcation mechanism. + +# %% +analyzer = bp.analysis.FastSlowBifurcation( + integrals=int_hr, + fast_vars={'x': [-3, 3], 'y': [-10., 5.]}, + slow_vars={'z': [-5., 5.]}, + pars_update={'Isyn': 0.5}, + numerical_resolution=0.001 +) +analyzer.plot_bifurcation() +analyzer.plot_trajectory([{'x': 1., 'y': 0., 'z': -0.0}], + duration=100., + show=True) + +# %% [markdown] +# **References**: +# +# [1] Rinzel, John. "Bursting oscillations in an excitable membrane model." In Ordinary and partial differential equations, pp. 304-316. Springer, Berlin, Heidelberg, 1985. +# +# [2] Rinzel, John , and Y. S. Lee . On Different Mechanisms for Membrane Potential Bursting. Nonlinear Oscillations in Biology and Chemistry. Springer Berlin Heidelberg, 1986. +# +# [3] Rinzel, John. "A formal classification of bursting mechanisms in excitable systems." In Mathematical topics in population biology, morphogenesis and neurosciences, pp. 267-281. Springer, Berlin, Heidelberg, 1987. +# + +# %% [markdown] +# --- +# +# - Chaoming Wang (adaduo@outlook.com) +# - Update at 2021.03.25 +# +# --- diff --git a/docs/tutorial_jit/jit_jax.rst b/docs/tutorial_jit/jit_jax.rst new file mode 100644 index 00000000..e69de29b diff --git a/docs/tutorial_jit/jit_numba.rst b/docs/tutorial_jit/jit_numba.rst new file mode 100644 index 00000000..e69de29b -- 2.34.1 From 6b92a4745009b0579b3647f0b5b0b50dd87636c3 Mon Sep 17 00:00:00 2001 From: chaoming Date: Sun, 12 Sep 2021 22:34:52 +0800 Subject: [PATCH 30/30] update tests and README --- README.md | 6 +++--- brainpy/__init__.py | 3 ++- tests/integrators/test_ast_analysis.py | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 22cc16f3..c7706d4b 100644 --- a/README.md +++ b/README.md @@ -86,9 +86,9 @@ If you have installed the previous version of BrainPy, please uninstall the old ## Step 2: useful links - **Documentation:** https://brainpy.readthedocs.io/ -- **Source code:** https://github.com/PKU-NIP-Lab/BrainPy or https://git.openi.org.cn/OpenI/BrainPy -- **Bug reports:** https://github.com/PKU-NIP-Lab/BrainPy/issues or Email to adaduo@outlook.com -- **Examples from papers**: https://brainmodels.readthedocs.io/en/latest/from_papers.html +- **Source code:** https://github.com/PKU-NIP-Lab/BrainPy or https://git.openi.org.cn/OpenI/BrainPy +- **Bug reports:** https://github.com/PKU-NIP-Lab/BrainPy/issues or Email to adaduo@outlook.com +- **Examples from papers**: https://brainmodels.readthedocs.io/en/latest/ diff --git a/brainpy/__init__.py b/brainpy/__init__.py index 0f17fcc6..01ac94b7 100644 --- a/brainpy/__init__.py +++ b/brainpy/__init__.py @@ -36,7 +36,8 @@ from . import dnn # "analysis" module -from . import analysis +from .analysis import symbolic_analysis as sym_analysis +from .analysis import continuation # "visualization" module diff --git a/tests/integrators/test_ast_analysis.py b/tests/integrators/test_ast_analysis.py index a310864f..6bfb042c 100644 --- a/tests/integrators/test_ast_analysis.py +++ b/tests/integrators/test_ast_analysis.py @@ -6,8 +6,8 @@ from pprint import pprint import pytest from brainpy.errors import DiffEqError -from brainpy.integrators.ast_analysis import DiffEqReader -from brainpy.integrators.ast_analysis import separate_variables +from brainpy.integrators.analysis_by_ast import DiffEqReader +from brainpy.integrators.analysis_by_ast import separate_variables def test_reader1(): -- 2.34.1