diff --git a/.github/ISSUE_TEMPLATE/Feature_request.md b/.github/ISSUE_TEMPLATE/Feature_request.md deleted file mode 100644 index 3e0bb71a..00000000 --- a/.github/ISSUE_TEMPLATE/Feature_request.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -name: 'Feature Request' -about: 'Suggest a new idea or improvement for Brainpy' -labels: 'enhancement' ---- - -Please: - -- [ ] Check for duplicate requests. -- [ ] Describe your goal, and if possible provide a code snippet with a motivating example. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 98171646..0c17e794 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,5 +1,5 @@ --- -name: 'Bug report' +name: 'Bug Report' about: 'Report a bug to help improve the package' labels: 'bug' --- diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 1583f23a..4d3640ad 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,5 @@ blank_issues_enabled: false contact_links: - name: Question - url: https://github.com/google/jax/discussions + url: https://github.com/PKU-NIP-Lab/BrainPy/discussions about: Please ask questions on the Discussions tab \ No newline at end of file diff --git a/.github/workflows/Linux_CI.yml b/.github/workflows/Linux_CI.yml index 8f3e319d..dfe658f9 100644 --- a/.github/workflows/Linux_CI.yml +++ b/.github/workflows/Linux_CI.yml @@ -5,9 +5,9 @@ name: Linux CI on: push: - branches: [ master, brainpy-2.x, V2.1.0 ] + branches: [ master ] pull_request: - branches: [ master, brainpy-2.x, V2.1.0 ] + branches: [ master ] jobs: @@ -16,7 +16,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.7", "3.8", "3.9"] + python-version: ["3.7", "3.8", "3.9", "3.10"] steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/MacOS_CI.yml b/.github/workflows/MacOS_CI.yml index f1dfd34d..debd1a53 100644 --- a/.github/workflows/MacOS_CI.yml +++ b/.github/workflows/MacOS_CI.yml @@ -5,19 +5,18 @@ name: MacOS CI on: push: - branches: [ master, brainpy-2.x, V2.1.0 ] + branches: [ master ] pull_request: - branches: [ master, brainpy-2.x, V2.1.0 ] + branches: [ master ] jobs: build: - runs-on: ${{ matrix.os }} + runs-on: macos-latest strategy: fail-fast: false matrix: - os: [macos-10.15, macos-11, macos-latest] - python-version: ["3.7", "3.8", "3.9"] + python-version: ["3.7", "3.8", "3.9", "3.10"] steps: - uses: actions/checkout@v2 @@ -39,4 +38,4 @@ jobs: flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - name: Test with pytest run: | - pytest + pytest brainpy/ diff --git a/.github/workflows/Sync_branches.yml b/.github/workflows/Sync_branches.yml new file mode 100644 index 00000000..76a98f9c --- /dev/null +++ b/.github/workflows/Sync_branches.yml @@ -0,0 +1,18 @@ +name: Sync multiple branches +on: + pull_request: + branches: + - master +jobs: + sync-branch: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + + - name: Merge master -> brainpy-2.x + uses: devmasx/merge-branch@v1.3.1 + with: + type: now + from_branch: master + target_branch: brainpy-2.x + github_token: ${{ github.token }} \ No newline at end of file diff --git a/.github/workflows/Windows_CI.yml b/.github/workflows/Windows_CI.yml index 10dea073..bcc46f32 100644 --- a/.github/workflows/Windows_CI.yml +++ b/.github/workflows/Windows_CI.yml @@ -5,9 +5,9 @@ name: Windows CI on: push: - branches: [ master, brainpy-2.x, V2.1.0 ] + branches: [ master ] pull_request: - branches: [ master, brainpy-2.x, V2.1.0 ] + branches: [ master ] jobs: @@ -16,7 +16,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.7", "3.8", "3.9"] + python-version: ["3.7", "3.8", "3.9", "3.10"] steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/contributors.yml b/.github/workflows/contributors.yml index b456b4b9..5cbd6145 100644 --- a/.github/workflows/contributors.yml +++ b/.github/workflows/contributors.yml @@ -2,6 +2,8 @@ name: Add contributors on: schedule: - cron: '20 20 * * *' + push: + branches: [ master ] jobs: add-contributors: diff --git a/.gitignore b/.gitignore index 84676411..548a943f 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ BrainModels/ book/ docs/examples docs/apis/jaxsetting.rst +docs/quickstart/data examples/recurrent_neural_network/neurogym develop/iconip_paper develop/benchmark/COBA/results diff --git a/README.md b/README.md index 5bfc879c..906c186d 100644 --- a/README.md +++ b/README.md @@ -28,9 +28,9 @@ BrainPy is a flexible, efficient, and extensible framework for computational neu -## Install +## Installation -BrainPy is based on Python (>=3.6) and can be installed on Linux (Ubuntu 16.04 or later), macOS (10.12 or later), and Windows platforms. Install the latest version of BrainPy: +BrainPy is based on Python (>=3.7) and can be installed on Linux (Ubuntu 16.04 or later), macOS (10.12 or later), and Windows platforms. Install the latest version of BrainPy: ```bash $ pip install brain-py -U @@ -54,7 +54,121 @@ import brainpy as bp -**1\. E-I balance network** +### 1. Operator level + +Mathematical operators in BrainPy are the same as those in NumPy. + +```python +>>> import numpy as np +>>> import brainpy.math as bm + +# array creation +>>> np_arr = np.zeros((2, 4)); np_arr +array([[0., 0., 0., 0.], + [0., 0., 0., 0.]]) +>>> bm_arr = bm.zeros((2, 4)); bm_arr +JaxArray([[0., 0., 0., 0.], + [0., 0., 0., 0.]], dtype=float32) + +# in-place updating +>>> np_arr[0] += 1.; np_arr +array([[1., 1., 1., 1.], + [0., 0., 0., 0.]]) +>>> bm_arr[0] += 1.; bm_arr +JaxArray([[1., 1., 1., 1.], + [0., 0., 0., 0.]], dtype=float32) + +# mathematical functions +>>> np.sin(np_arr) +array([[0.84147098, 0.84147098, 0.84147098, 0.84147098], + [0. , 0. , 0. , 0. ]]) +>>> bm.sin(bm_arr) +JaxArray([[0.84147096, 0.84147096, 0.84147096, 0.84147096], + [0. , 0. , 0. , 0. ]], dtype=float32) + +# linear algebra +>>> np.dot(np_arr, np.ones((4, 2))) +array([[4., 4.], + [0., 0.]]) +>>> bm.dot(bm_arr, bm.ones((4, 2))) +JaxArray([[4., 4.], + [0., 0.]], dtype=float32) + +# random number generation +>>> np.random.uniform(-0.1, 0.1, (2, 3)) +array([[-0.02773637, 0.03766689, -0.01363128], + [-0.01946991, -0.06669802, 0.09426067]]) +>>> bm.random.uniform(-0.1, 0.1, (2, 3)) +JaxArray([[-0.03044081, -0.07787752, 0.04346445], + [-0.01366713, -0.0522548 , 0.04372055]], dtype=float32) +``` + + + +### 2. Integrator level + +Numerical methods for ordinary differential equations (ODEs). + +```python +sigma = 10; beta = 8/3; rho = 28 + +@bp.odeint(method='rk4') +def lorenz_system(x, y, z, t): + dx = sigma * (y - x) + dy = x * (rho - z) - y + dz = x * y - beta * z + return dx, dy, dz + +runner = bp.integrators.IntegratorRunner(lorenz_system, dt=0.01) +runner.run(100.) +``` + + + +Numerical methods for stochastic differential equations (SDEs). + +```python +sigma = 10; beta = 8/3; rho = 28 +p=0.1 + +def lorenz_noise(x, y, z, t): + return p*x, p*y, p*z + +@bp.odeint(method='milstein', g=lorenz_noise) +def lorenz_system(x, y, z, t): + dx = sigma * (y - x) + dy = x * (rho - z) - y + dz = x * y - beta * z + return dx, dy, dz + +runner = bp.integrators.IntegratorRunner(lorenz_system, dt=0.01) +runner.run(100.) +``` + + + +Numerical methods for delay differential equations (SDEs). + +```python +xdelay = bm.TimeDelay(bm.zeros(1), delay_len=1., before_t0=1., dt=0.01) + + +@bp.ddeint(method='rk4', state_delays={'x': xdelay}) +def second_order_eq(x, y, t): + dx = y + dy = -y - 2 * x - 0.5 * xdelay(t - 1) + return dx, dy + + +runner = bp.integrators.IntegratorRunner(second_order_eq, dt=0.01) +runner.run(100.) +``` + + + +### 3. Dynamics simulation level + +Building an E-I balance network. ```python class EINet(bp.dyn.Network): @@ -77,9 +191,36 @@ runner = bp.dyn.DSRunner(net) runner(100.) ``` +Simulating a whole brain network by using rate models. + +```python +import numpy as np +class WholeBrainNet(bp.dyn.Network): + def __init__(self, signal_speed=20.): + super(WholeBrainNet, self).__init__() -**2\. Echo state network** + self.fhn = bp.dyn.RateFHN(80, x_ou_sigma=0.01, y_ou_sigma=0.01, name='fhn') + self.syn = bp.dyn.DiffusiveDelayCoupling(self.fhn, self.fhn, + 'x->input', + conn_mat=conn_mat, + delay_mat=delay_mat) + + def update(self, _t, _dt): + self.syn.update(_t, _dt) + self.fhn.update(_t, _dt) + + +net = WholeBrainNet() +runner = bp.dyn.DSRunner(net, monitors=['fhn.x'], inputs=['fhn.input', 0.72]) +runner.run(6e3) +``` + + + +### 4. Dynamics training level + +Training an echo state network. ```python i = bp.nn.Input(3) @@ -88,16 +229,14 @@ o = bp.nn.LinearReadout(3) net = i >> r >> o -# Ridge Regression -trainer = bp.nn.RidgeTrainer(net, beta=1e-5) +trainer = bp.nn.RidgeTrainer(net, beta=1e-5) # Ridge Regression -# FORCE Learning -trainer = bp.nn.FORCELearning(net, alpha=1.) +trainer = bp.nn.FORCELearning(net, alpha=1.) # FORCE Learning ``` -**3. Next generation reservoir computing** +Training a next-generation reservoir computing model. ```python i = bp.nn.Input(3) @@ -111,7 +250,7 @@ trainer = bp.nn.RidgeTrainer(net, beta=1e-5) -**4. Recurrent neural network** +Training an artificial recurrent neural network. ```python i = bp.nn.Input(3) @@ -128,7 +267,9 @@ trainer = bp.nn.BPTT(net, -**5\. Analyzing a low-dimensional FitzHugh–Nagumo neuron model** +### 5. Dynamics analysis level + +Analyzing a low-dimensional FitzHugh–Nagumo neuron model. ```python bp.math.enable_x64() @@ -149,9 +290,10 @@ analyzer.show_figure()
-For **more functions and examples**, please refer to the [documentation](https://brainpy.readthedocs.io/) and [examples](https://brainpy-examples.readthedocs.io/). +### 6. More others +For **more functions and examples**, please refer to the [documentation](https://brainpy.readthedocs.io/) and [examples](https://brainpy-examples.readthedocs.io/). ## License diff --git a/README2.md b/README2.md deleted file mode 100644 index 0c14038b..00000000 --- a/README2.md +++ /dev/null @@ -1,159 +0,0 @@ -- -
- - - - - -:clap::clap: **CHEERS**: A new version of BrainPy (>=2.0.0, long term support) has been released! :clap::clap: - - - -# Why use BrainPy - -``BrainPy`` is an integrative framework for computational neuroscience and brain-inspired computation based on the Just-In-Time (JIT) compilation (built on top of [JAX](https://github.com/google/jax)). Core functions provided in BrainPy includes - -- **JIT compilation** for class objects. -- **Numerical solvers** for ODEs, SDEs, and others. -- **Dynamics simulation tools** for various brain objects, like neurons, synapses, networks, soma, dendrites, channels, and even more. -- **Dynamics analysis tools** for differential equations, including phase plane analysis and bifurcation analysis, and linearization analysis. -- **Seamless integration with deep learning models**. -- And more ...... - -`BrainPy` is designed to effectively satisfy your basic requirements: - -- **Pythonic**: BrainPy is based on Python language and has a Pythonic coding style. -- **Flexible and transparent**: BrainPy endows the users with full data/logic flow control. Users can code any logic they want with BrainPy. -- **Extensible**: BrainPy allows users to extend new functionality just based on Python code. Almost every part of the BrainPy system can be extended to be customized. -- **Efficient**: All codes in BrainPy can be just-in-time compiled (based on [JAX](https://github.com/google/jax)) to run on CPU, GPU, or TPU devices, thus guaranteeing its running efficiency. - - - -# How to use BrainPy - -## Step 1: installation - -``BrainPy`` is based on Python (>=3.6), and the following packages are required to be installed to use ``BrainPy``: `numpy >= 1.15`, `matplotlib >= 3.4`, and `jax >= 0.2.10` ([how to install jax?](https://brainpy.readthedocs.io/en/latest/quickstart/installation.html#dependency-2-jax)) - -``BrainPy`` can be installed on Linux (Ubuntu 16.04 or later), macOS (10.12 or later), and Windows platforms. Use the following instructions to install ``brainpy``: - -```bash -pip install brain-py -U -``` - -*For the full installation details please see documentation: [Quickstart/Installation](https://brainpy.readthedocs.io/en/latest/quickstart/installation.html)* - - - - -## Step 2: useful links - -- **Documentation:** https://brainpy.readthedocs.io/ -- **Bug reports:** https://github.com/PKU-NIP-Lab/BrainPy/issues -- **Examples from papers**: https://brainpy-examples.readthedocs.io/ -- **Canonical brain models**: https://brainmodels.readthedocs.io/ - - - -## Step 3: inspirational examples - -Here we list several examples of BrainPy. For more detailed examples and tutorials please see [**BrainModels**](https://brainmodels.readthedocs.io) or [**BrainPy-Examples**](https://brainpy-examples.readthedocs.io/en/brainpy-2.x/). - - - -### Neuron models - -- [Leaky integrate-and-fire neuron model](https://brainmodels.readthedocs.io/en/brainpy-2.x/apis/generated/brainmodels.neurons.LIF.html), [source code](https://github.com/PKU-NIP-Lab/BrainModels/blob/brainpy-2.x/brainmodels/neurons/LIF.py) -- [Exponential integrate-and-fire neuron model](https://brainmodels.readthedocs.io/en/brainpy-2.x/apis/generated/brainmodels.neurons.ExpIF.html), [source code](https://github.com/PKU-NIP-Lab/BrainModels/blob/brainpy-2.x/brainmodels/neurons/ExpIF.py) -- [Quadratic integrate-and-fire neuron model](https://brainmodels.readthedocs.io/en/brainpy-2.x/apis/generated/brainmodels.neurons.QuaIF.html), [source code](https://github.com/PKU-NIP-Lab/BrainModels/blob/brainpy-2.x/brainmodels/neurons/QuaIF.py) -- [Adaptive Quadratic integrate-and-fire model](https://brainmodels.readthedocs.io/en/brainpy-2.x/apis/generated/brainmodels.neurons.AdQuaIF.html), [source code](https://github.com/PKU-NIP-Lab/BrainModels/blob/brainpy-2.x/brainmodels/neurons/AdQuaIF.py) -- [Adaptive Exponential integrate-and-fire model](https://brainmodels.readthedocs.io/en/brainpy-2.x/apis/generated/brainmodels.neurons.AdExIF.html), [source code](https://github.com/PKU-NIP-Lab/BrainModels/blob/brainpy-2.x/brainmodels/neurons/AdExIF.py) -- [Generalized integrate-and-fire model](https://brainmodels.readthedocs.io/en/brainpy-2.x/apis/generated/brainmodels.neurons.GIF.html), [source code](https://github.com/PKU-NIP-Lab/BrainModels/blob/brainpy-2.x/brainmodels/neurons/GIF.py) -- [Hodgkin–Huxley neuron model](https://brainmodels.readthedocs.io/en/brainpy-2.x/apis/generated/brainmodels.neurons.HH.html), [source code](https://github.com/PKU-NIP-Lab/BrainModels/blob/brainpy-2.x/brainmodels/neurons/HH.py) -- [Izhikevich neuron model](https://brainmodels.readthedocs.io/en/brainpy-2.x/apis/generated/brainmodels.neurons.Izhikevich.html), [source code](https://github.com/PKU-NIP-Lab/BrainModels/blob/brainpy-2.x/brainmodels/neurons/Izhikevich.py) -- [Morris-Lecar neuron model](https://brainmodels.readthedocs.io/en/brainpy-2.x/apis/generated/brainmodels.neurons.MorrisLecar.html), [source code](https://github.com/PKU-NIP-Lab/BrainModels/blob/brainpy-2.x/brainmodels/neurons/MorrisLecar.py) -- [Hindmarsh-Rose bursting neuron model](https://brainmodels.readthedocs.io/en/brainpy-2.x/apis/generated/brainmodels.neurons.HindmarshRose.html), [source code](https://github.com/PKU-NIP-Lab/BrainModels/blob/brainpy-2.x/brainmodels/neurons/HindmarshRose.py) - -See [brainmodels.neurons](https://brainmodels.readthedocs.io/en/brainpy-2.x/apis/neurons.html) to find more. - - - -### Synapse models - -- [Voltage jump synapse model](https://brainmodels.readthedocs.io/en/brainpy-2.x/apis/generated/brainmodels.synapses.VoltageJump.html), [source code](https://github.com/PKU-NIP-Lab/BrainModels/blob/brainpy-2.x/brainmodels/synapses/voltage_jump.py) -- [Exponential synapse model](https://brainmodels.readthedocs.io/en/brainpy-2.x/apis/generated/brainmodels.synapses.ExpCUBA.html), [source code](https://github.com/PKU-NIP-Lab/BrainModels/blob/brainpy-2.x/brainmodels/synapses/exponential.py) -- [Alpha synapse model](https://brainmodels.readthedocs.io/en/brainpy-2.x/apis/generated/brainmodels.synapses.AlphaCUBA.html), [source code](https://github.com/PKU-NIP-Lab/BrainModels/blob/brainpy-2.x/brainmodels/synapses/alpha.py) -- [Dual exponential synapse model](https://brainmodels.readthedocs.io/en/brainpy-2.x/apis/generated/brainmodels.synapses.DualExpCUBA.html), [source code](https://github.com/PKU-NIP-Lab/BrainModels/blob/brainpy-2.x/brainmodels/synapses/dual_exp.py) -- [AMPA synapse model](https://brainmodels.readthedocs.io/en/brainpy-2.x/apis/generated/brainmodels.synapses.AMPA.html), [source code](https://github.com/PKU-NIP-Lab/BrainModels/blob/brainpy-2.x/brainmodels/synapses/AMPA.py) -- [GABAA synapse model](https://brainmodels.readthedocs.io/en/brainpy-2.x/apis/generated/brainmodels.synapses.GABAa.html), [source code](https://github.com/PKU-NIP-Lab/BrainModels/blob/brainpy-2.x/brainmodels/synapses/GABAa.py) -- [NMDA synapse model](https://brainmodels.readthedocs.io/en/brainpy-2.x/apis/generated/brainmodels.synapses.NMDA.html), [source code](https://github.com/PKU-NIP-Lab/BrainModels/blob/brainpy-2.x/brainmodels/synapses/NMDA.py) -- [Short-term plasticity model](https://brainmodels.readthedocs.io/en/brainpy-2.x/apis/generated/brainmodels.synapses.STP.html), [source code](https://github.com/PKU-NIP-Lab/BrainModels/blob/brainpy-2.x/brainmodels/synapses/STP.py) - -See [brainmodels.synapses](https://brainmodels.readthedocs.io/en/brainpy-2.x/apis/synapses.html) to find more. - - - -### Network models - -- **[CANN]** [*(Si Wu, 2008)* Continuous-attractor Neural Network](https://brainpy-examples.readthedocs.io/en/brainpy-2.x/cann/Wu_2008_CANN.html) -- [*(Vreeswijk & Sompolinsky, 1996)* E/I balanced network](https://brainpy-examples.readthedocs.io/en/brainpy-2.x/ei_nets/Vreeswijk_1996_EI_net.html) -- [*(Sherman & Rinzel, 1992)* Gap junction leads to anti-synchronization](https://brainpy-examples.readthedocs.io/en/brainpy-2.x/gj_nets/Sherman_1992_gj_antisynchrony.html) -- [*(Wang & Buzsáki, 1996)* Gamma Oscillation](https://brainpy-examples.readthedocs.io/en/brainpy-2.x/oscillation_synchronization/Wang_1996_gamma_oscillation.html) -- [*(Brunel & Hakim, 1999)* Fast Global Oscillation](https://brainpy-examples.readthedocs.io/en/brainpy-2.x/oscillation_synchronization/Brunel_Hakim_1999_fast_oscillation.html) -- [*(Diesmann, et, al., 1999)* Synfire Chains](https://brainpy-examples.readthedocs.io/en/brainpy-2.x/oscillation_synchronization/Diesmann_1999_synfire_chains.html) -- **[Working Memory]** [*(Mi, et. al., 2017)* STP for Working Memory Capacity](https://brainpy-examples.readthedocs.io/en/brainpy-2.x/working_memory/Mi_2017_working_memory_capacity.html) -- **[Working Memory]** [*(Bouchacourt & Buschman, 2019)* Flexible Working Memory Model](https://brainpy-examples.readthedocs.io/en/brainpy-2.x/working_memory/Bouchacourt_2019_Flexible_working_memory.html) -- **[Decision Making]** [*(Wang, 2002)* Decision making spiking model](https://brainpy-examples.readthedocs.io/en/brainpy-2.x/decision_making/Wang_2002_decision_making_spiking.html) - - - -### Dynamics training - -- [Train Integrator RNN with BP](https://brainpy-examples.readthedocs.io/en/brainpy-2.x/recurrent_networks/integrator_rnn.html) - -- [*(Sussillo & Abbott, 2009)* FORCE Learning](https://brainpy-examples.readthedocs.io/en/brainpy-2.x/recurrent_networks/Sussillo_Abbott_2009_FORCE_Learning.html) - -- [*(Laje & Buonomano, 2013)* Robust Timing in RNN](https://brainpy-examples.readthedocs.io/en/brainpy-2.x/recurrent_networks/Laje_Buonomano_2013_robust_timing_rnn.html) -- [*(Song, et al., 2016)*: Training excitatory-inhibitory recurrent network](https://brainpy-examples.readthedocs.io/en/brainpy-2.x/recurrent_networks/Song_2016_EI_RNN.html) -- **[Working Memory]** [*(Masse, et al., 2019)*: RNN with STP for Working Memory](https://brainpy-examples.readthedocs.io/en/brainpy-2.x/recurrent_networks/Masse_2019_STP_RNN.html) - - - - -### Low-dimensional dynamics analysis - -- [[1D] Simple systems](https://brainpy-examples.readthedocs.io/en/brainpy-2.x/dynamics_analysis/1d_simple_systems.html) -- [[2D] NaK model analysis](https://brainpy-examples.readthedocs.io/en/brainpy-2.x/dynamics_analysis/2d_NaK_model.html) -- [[3D] Hindmarsh Rose Model](https://brainpy-examples.readthedocs.io/en/brainpy-2.x/dynamics_analysis/3d_hindmarsh_rose_model.html) -- **[Decision Making Model]** [[2D] Decision making rate model](https://brainpy-examples.readthedocs.io/en/brainpy-2.x/decision_making/Wang_2006_decision_making_rate.html) - - - -### High-dimensional dynamics analysis - -- [*(Yang, 2020)*: Dynamical system analysis for RNN](https://brainpy-examples.readthedocs.io/en/brainpy-2.x/recurrent_networks/Yang_2020_RNN_Analysis.html) -- [Continuous-attractor Neural Network](https://brainpy-examples.readthedocs.io/en/brainpy-2.x/dynamics_analysis/highdim_CANN.html) -- [Gap junction-coupled FitzHugh-Nagumo Model](https://brainpy-examples.readthedocs.io/en/brainpy-2.x/dynamics_analysis/highdim_gj_coupled_fhn.html) - - - -# BrainPy 1.x - -If you are using ``brainpy==1.x``, you can find *documentation*, *examples*, and *models* through the following links: - -- **Documentation:** https://brainpy.readthedocs.io/en/brainpy-1.x/ -- **Examples from papers**: https://brainpy-examples.readthedocs.io/en/brainpy-1.x/ -- **Canonical brain models**: https://brainmodels.readthedocs.io/en/brainpy-1.x/ - -The changes from ``brainpy==1.x`` to ``brainpy==2.x`` can be inspected through [API documentation: release notes](https://brainpy.readthedocs.io/en/latest/apis/auto/changelog.html). - - -# Contributors diff --git a/brainpy/__init__.py b/brainpy/__init__.py index 7521db97..de872844 100644 --- a/brainpy/__init__.py +++ b/brainpy/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -__version__ = "2.1.0" +__version__ = "2.1.2" try: @@ -15,7 +15,7 @@ except ModuleNotFoundError: # fundamental modules -from . import errors, tools +from . import errors, tools, check # "base" module @@ -37,9 +37,11 @@ from . import integrators from .integrators import ode from .integrators import sde from .integrators import dde +from .integrators import fde from .integrators.ode import odeint from .integrators.sde import sdeint from .integrators.dde import ddeint +from .integrators.fde import fdeint from .integrators.joint_eq import JointEq @@ -59,12 +61,12 @@ from . import running from . import analysis -# "visualization" module, will be remove soon +# "visualization" module, will be removed soon from .visualization import visualize # compatible interface -from .compact import * # compact +from .compat import * # compat # convenient access diff --git a/brainpy/analysis/highdim/slow_points.py b/brainpy/analysis/highdim/slow_points.py index 57c58114..9cec0107 100644 --- a/brainpy/analysis/highdim/slow_points.py +++ b/brainpy/analysis/highdim/slow_points.py @@ -1,17 +1,18 @@ # -*- coding: utf-8 -*- -import inspect import time +import warnings from functools import partial +from jax import vmap import jax.numpy import numpy as np from jax.scipy.optimize import minimize import brainpy.math as bm +from brainpy import optimizers as optim from brainpy.analysis import utils from brainpy.errors import AnalyzerError -from brainpy import optimizers as optim __all__ = [ 'SlowPointFinder', @@ -56,15 +57,15 @@ class SlowPointFinder(object): if f_loss_batch is None: if f_type == 'discrete': self.f_loss = bm.jit(lambda h: bm.mean((h - f_cell(h)) ** 2)) - self.f_loss_batch = bm.jit(lambda h: bm.mean((h - bm.vmap(f_cell, auto_infer=False)(h)) ** 2, axis=1)) + self.f_loss_batch = bm.jit(lambda h: bm.mean((h - vmap(f_cell)(h)) ** 2, axis=1)) if f_type == 'continuous': self.f_loss = bm.jit(lambda h: bm.mean(f_cell(h) ** 2)) - self.f_loss_batch = bm.jit(lambda h: bm.mean((bm.vmap(f_cell, auto_infer=False)(h)) ** 2, axis=1)) + self.f_loss_batch = bm.jit(lambda h: bm.mean((vmap(f_cell)(h)) ** 2, axis=1)) else: self.f_loss_batch = f_loss_batch self.f_loss = bm.jit(lambda h: bm.mean(f_cell(h) ** 2)) - self.f_jacob_batch = bm.jit(bm.vmap(bm.jacobian(f_cell))) + self.f_jacob_batch = bm.jit(vmap(bm.jacobian(f_cell))) # essential variables self._losses = None @@ -87,8 +88,13 @@ class SlowPointFinder(object): """The selected ids of candidate points.""" return self._selected_ids - def find_fps_with_gd_method(self, candidates, tolerance=1e-5, num_batch=100, - num_opt=10000, opt_setting=None): + def find_fps_with_gd_method(self, + candidates, + tolerance=1e-5, + num_batch=100, + num_opt=10000, + optimizer=None, + opt_setting=None): """Optimize fixed points with gradient descent methods. Parameters @@ -104,17 +110,30 @@ class SlowPointFinder(object): Print training information during optimization every so often. opt_setting: optional, dict The optimization settings. + + .. deprecated:: 2.1.2 + Use "optimizer" to set optimization method instead. + + optimizer: optim.Optimizer + The optimizer instance. + + .. versionadded:: 2.1.2 """ # optimization settings if opt_setting is None: - opt_method = optim.Adam - opt_lr = optim.ExponentialDecay(0.2, 1, 0.9999) - opt_setting = {'beta1': 0.9, - 'beta2': 0.999, - 'eps': 1e-8, - 'name': None} + if optimizer is None: + optimizer = optim.Adam(lr=optim.ExponentialDecay(0.2, 1, 0.9999), + beta1=0.9, beta2=0.999, eps=1e-8) + else: + assert isinstance(optimizer, optim.Optimizer), (f'Must be an instance of ' + f'{optim.Optimizer.__name__}, ' + f'while we got {type(optimizer)}') else: + warnings.warn('Please use "optimizer" to set optimization method. ' + '"opt_setting" is deprecated since version 2.1.2. ', + DeprecationWarning) + assert isinstance(opt_setting, dict) assert 'method' in opt_setting assert 'lr' in opt_setting @@ -122,26 +141,25 @@ class SlowPointFinder(object): if isinstance(opt_method, str): assert opt_method in optim.__dict__ opt_method = getattr(optim, opt_method) - assert isinstance(opt_method, type) - if optim.Optimizer not in inspect.getmro(opt_method): - raise ValueError + assert issubclass(opt_method, optim.Optimizer) opt_lr = opt_setting.pop('lr') assert isinstance(opt_lr, (int, float, optim.Scheduler)) opt_setting = opt_setting + optimizer = opt_method(lr=opt_lr, **opt_setting) if self.verbose: - print(f"Optimizing with {opt_method.__name__} to find fixed points:") + print(f"Optimizing with {optimizer.__name__} to find fixed points:") # set up optimization fixed_points = bm.Variable(bm.asarray(candidates)) grad_f = bm.grad(lambda: self.f_loss_batch(fixed_points.value).mean(), grad_vars={'a': fixed_points}, return_value=True) - opt = opt_method(train_vars={'a': fixed_points}, lr=opt_lr, **opt_setting) - dyn_vars = opt.vars() + {'_a': fixed_points} + optimizer.register_vars({'a': fixed_points}) + dyn_vars = optimizer.vars() + {'_a': fixed_points} def train(idx): gradients, loss = grad_f() - opt.update(gradients) + optimizer.update(gradients) return loss @partial(bm.jit, dyn_vars=dyn_vars, static_argnames=('start_i', 'num_batch')) @@ -191,7 +209,7 @@ class SlowPointFinder(object): opt_method = lambda f, x0: minimize(f, x0, method='BFGS') if self.verbose: print(f"Optimizing to find fixed points:") - f_opt = bm.jit(bm.vmap(lambda x0: opt_method(self.f_loss, x0))) + f_opt = bm.jit(vmap(lambda x0: opt_method(self.f_loss, x0))) res = f_opt(bm.as_device_array(candidates)) valid_ids = jax.numpy.where(res.success)[0] self._fixed_points = np.asarray(res.x[valid_ids]) diff --git a/brainpy/analysis/lowdim/lowdim_analyzer.py b/brainpy/analysis/lowdim/lowdim_analyzer.py index a5698246..0d7ac1b6 100644 --- a/brainpy/analysis/lowdim/lowdim_analyzer.py +++ b/brainpy/analysis/lowdim/lowdim_analyzer.py @@ -2,8 +2,8 @@ from functools import partial -import matplotlib.pyplot as plt import numpy as np +from jax import vmap from jax import numpy as jnp from jax.scipy.optimize import minimize @@ -12,6 +12,8 @@ from brainpy import errors, tools from brainpy.analysis import constants as C, utils from brainpy.base.collector import Collector +pyplot = None + __all__ = [ 'LowDimAnalyzer', 'Num1DAnalyzer', @@ -207,7 +209,10 @@ class LowDimAnalyzer(object): self.analyzed_results = tools.DictPlus() def show_figure(self): - plt.show() + global pyplot + if pyplot is None: + from matplotlib import pyplot + pyplot.show() class Num1DAnalyzer(LowDimAnalyzer): @@ -258,7 +263,7 @@ class Num1DAnalyzer(LowDimAnalyzer): @property def F_vmap_fx(self): if C.F_vmap_fx not in self.analyzed_results: - self.analyzed_results[C.F_vmap_fx] = bm.jit(bm.vmap(self.F_fx), device=self.jit_device) + self.analyzed_results[C.F_vmap_fx] = bm.jit(vmap(self.F_fx), device=self.jit_device) return self.analyzed_results[C.F_vmap_fx] @property @@ -285,7 +290,7 @@ class Num1DAnalyzer(LowDimAnalyzer): # --- # "X": a two-dimensional matrix: (num_batch, num_var) # "args": a list of one-dimensional vectors, each has the shape of (num_batch,) - self.analyzed_results[C.F_vmap_fp_aux] = bm.jit(bm.vmap(self.F_fixed_point_aux)) + self.analyzed_results[C.F_vmap_fp_aux] = bm.jit(vmap(self.F_fixed_point_aux)) return self.analyzed_results[C.F_vmap_fp_aux] @property @@ -304,7 +309,7 @@ class Num1DAnalyzer(LowDimAnalyzer): # --- # "X": a two-dimensional matrix: (num_batch, num_var) # "args": a list of one-dimensional vectors, each has the shape of (num_batch,) - self.analyzed_results[C.F_vmap_fp_opt] = bm.jit(bm.vmap(self.F_fixed_point_opt)) + self.analyzed_results[C.F_vmap_fp_opt] = bm.jit(vmap(self.F_fixed_point_opt)) return self.analyzed_results[C.F_vmap_fp_opt] def _get_fixed_points(self, candidates, *args, num_seg=None, tol_aux=1e-7, loss_screen=None): @@ -497,7 +502,7 @@ class Num2DAnalyzer(Num1DAnalyzer): @property def F_vmap_fy(self): if C.F_vmap_fy not in self.analyzed_results: - self.analyzed_results[C.F_vmap_fy] = bm.jit(bm.vmap(self.F_fy), device=self.jit_device) + self.analyzed_results[C.F_vmap_fy] = bm.jit(vmap(self.F_fy), device=self.jit_device) return self.analyzed_results[C.F_vmap_fy] @property @@ -659,7 +664,7 @@ class Num2DAnalyzer(Num1DAnalyzer): if self.F_x_by_y_in_fx is not None: utils.output("I am evaluating fx-nullcline by F_x_by_y_in_fx ...") - vmap_f = bm.jit(bm.vmap(self.F_x_by_y_in_fx), device=self.jit_device) + vmap_f = bm.jit(vmap(self.F_x_by_y_in_fx), device=self.jit_device) for j, pars in enumerate(par_seg): if len(par_seg.arg_id_segments[0]) > 1: utils.output(f"{C.prefix}segment {j} ...") mesh_values = jnp.meshgrid(*((ys,) + pars)) @@ -675,7 +680,7 @@ class Num2DAnalyzer(Num1DAnalyzer): elif self.F_y_by_x_in_fx is not None: utils.output("I am evaluating fx-nullcline by F_y_by_x_in_fx ...") - vmap_f = bm.jit(bm.vmap(self.F_y_by_x_in_fx), device=self.jit_device) + vmap_f = bm.jit(vmap(self.F_y_by_x_in_fx), device=self.jit_device) for j, pars in enumerate(par_seg): if len(par_seg.arg_id_segments[0]) > 1: utils.output(f"{C.prefix}segment {j} ...") mesh_values = jnp.meshgrid(*((xs,) + pars)) @@ -693,9 +698,9 @@ class Num2DAnalyzer(Num1DAnalyzer): utils.output("I am evaluating fx-nullcline by optimization ...") # auxiliary functions f2 = lambda y, x, *pars: self.F_fx(x, y, *pars) - vmap_f2 = bm.jit(bm.vmap(f2), device=self.jit_device) - vmap_brentq_f2 = bm.jit(bm.vmap(utils.jax_brentq(f2)), device=self.jit_device) - vmap_brentq_f1 = bm.jit(bm.vmap(utils.jax_brentq(self.F_fx)), device=self.jit_device) + vmap_f2 = bm.jit(vmap(f2), device=self.jit_device) + vmap_brentq_f2 = bm.jit(vmap(utils.jax_brentq(f2)), device=self.jit_device) + vmap_brentq_f1 = bm.jit(vmap(utils.jax_brentq(self.F_fx)), device=self.jit_device) # num segments for _j, Ps in enumerate(par_seg): @@ -752,7 +757,7 @@ class Num2DAnalyzer(Num1DAnalyzer): if self.F_x_by_y_in_fy is not None: utils.output("I am evaluating fy-nullcline by F_x_by_y_in_fy ...") - vmap_f = bm.jit(bm.vmap(self.F_x_by_y_in_fy), device=self.jit_device) + vmap_f = bm.jit(vmap(self.F_x_by_y_in_fy), device=self.jit_device) for j, pars in enumerate(par_seg): if len(par_seg.arg_id_segments[0]) > 1: utils.output(f"{C.prefix}segment {j} ...") mesh_values = jnp.meshgrid(*((ys,) + pars)) @@ -768,7 +773,7 @@ class Num2DAnalyzer(Num1DAnalyzer): elif self.F_y_by_x_in_fy is not None: utils.output("I am evaluating fy-nullcline by F_y_by_x_in_fy ...") - vmap_f = bm.jit(bm.vmap(self.F_y_by_x_in_fy), device=self.jit_device) + vmap_f = bm.jit(vmap(self.F_y_by_x_in_fy), device=self.jit_device) for j, pars in enumerate(par_seg): if len(par_seg.arg_id_segments[0]) > 1: utils.output(f"{C.prefix}segment {j} ...") mesh_values = jnp.meshgrid(*((xs,) + pars)) @@ -787,9 +792,9 @@ class Num2DAnalyzer(Num1DAnalyzer): # auxiliary functions f2 = lambda y, x, *pars: self.F_fy(x, y, *pars) - vmap_f2 = bm.jit(bm.vmap(f2), device=self.jit_device) - vmap_brentq_f2 = bm.jit(bm.vmap(utils.jax_brentq(f2)), device=self.jit_device) - vmap_brentq_f1 = bm.jit(bm.vmap(utils.jax_brentq(self.F_fy)), device=self.jit_device) + vmap_f2 = bm.jit(vmap(f2), device=self.jit_device) + vmap_brentq_f2 = bm.jit(vmap(utils.jax_brentq(f2)), device=self.jit_device) + vmap_brentq_f1 = bm.jit(vmap(utils.jax_brentq(self.F_fy)), device=self.jit_device) for j, Ps in enumerate(par_seg): if len(par_seg.arg_id_segments[0]) > 1: utils.output(f"{C.prefix}segment {j} ...") @@ -837,7 +842,7 @@ class Num2DAnalyzer(Num1DAnalyzer): xs = self.resolutions[self.x_var].value ys = self.resolutions[self.y_var].value P = tuple(self.resolutions[p].value for p in self.target_par_names) - f_select = bm.jit(bm.vmap(lambda vals, ids: vals[ids], in_axes=(1, 1))) + f_select = bm.jit(vmap(lambda vals, ids: vals[ids], in_axes=(1, 1))) # num seguments if isinstance(num_segments, int): @@ -917,10 +922,10 @@ class Num2DAnalyzer(Num1DAnalyzer): if self.convert_type() == C.x_by_y: num_seg = len(self.resolutions[self.y_var]) - f_vmap = bm.jit(bm.vmap(self.F_y_convert[1])) + f_vmap = bm.jit(vmap(self.F_y_convert[1])) else: num_seg = len(self.resolutions[self.x_var]) - f_vmap = bm.jit(bm.vmap(self.F_x_convert[1])) + f_vmap = bm.jit(vmap(self.F_x_convert[1])) # get the signs signs = jnp.sign(f_vmap(candidates, *args)) signs = signs.reshape((num_seg, -1)) @@ -950,10 +955,10 @@ class Num2DAnalyzer(Num1DAnalyzer): # get another value if self.convert_type() == C.x_by_y: y_values = fps - x_values = bm.jit(bm.vmap(self.F_y_convert[0]))(y_values, *args) + x_values = bm.jit(vmap(self.F_y_convert[0]))(y_values, *args) else: x_values = fps - y_values = bm.jit(bm.vmap(self.F_x_convert[0]))(x_values, *args) + y_values = bm.jit(vmap(self.F_x_convert[0]))(x_values, *args) fps = jnp.stack([x_values, y_values]).T return fps, selected_ids, args diff --git a/brainpy/analysis/lowdim/lowdim_bifurcation.py b/brainpy/analysis/lowdim/lowdim_bifurcation.py index fab83071..58ac8469 100644 --- a/brainpy/analysis/lowdim/lowdim_bifurcation.py +++ b/brainpy/analysis/lowdim/lowdim_bifurcation.py @@ -3,7 +3,7 @@ from functools import partial import jax.numpy as jnp -import matplotlib.pyplot as plt +from jax import vmap import numpy as np import brainpy.math as bm @@ -11,6 +11,8 @@ from brainpy import errors from brainpy.analysis import stability, utils, constants as C from brainpy.analysis.lowdim.lowdim_analyzer import * +pyplot = None + __all__ = [ 'Bifurcation1D', 'Bifurcation2D', @@ -41,12 +43,14 @@ class Bifurcation1D(Num1DAnalyzer): @property def F_vmap_dfxdx(self): if C.F_vmap_dfxdx not in self.analyzed_results: - f = bm.jit(bm.vmap(bm.vector_grad(self.F_fx, argnums=0)), device=self.jit_device) + f = bm.jit(vmap(bm.vector_grad(self.F_fx, argnums=0)), device=self.jit_device) self.analyzed_results[C.F_vmap_dfxdx] = f return self.analyzed_results[C.F_vmap_dfxdx] def plot_bifurcation(self, with_plot=True, show=False, with_return=False, tol_aux=1e-8, loss_screen=None): + global pyplot + if pyplot is None: from matplotlib import pyplot utils.output('I am making bifurcation analysis ...') xs = self.resolutions[self.x_var] @@ -72,21 +76,21 @@ class Bifurcation1D(Num1DAnalyzer): container[fp_type]['x'].append(x) # visualization - plt.figure(self.x_var) + pyplot.figure(self.x_var) for fp_type, points in container.items(): if len(points['x']): plot_style = stability.plot_scheme[fp_type] - plt.plot(points['p'], points['x'], '.', **plot_style, label=fp_type) - plt.xlabel(self.target_par_names[0]) - plt.ylabel(self.x_var) + pyplot.plot(points['p'], points['x'], '.', **plot_style, label=fp_type) + pyplot.xlabel(self.target_par_names[0]) + pyplot.ylabel(self.x_var) scale = (self.lim_scale - 1) / 2 - plt.xlim(*utils.rescale(self.target_pars[self.target_par_names[0]], scale=scale)) - plt.ylim(*utils.rescale(self.target_vars[self.x_var], scale=scale)) + pyplot.xlim(*utils.rescale(self.target_pars[self.target_par_names[0]], scale=scale)) + pyplot.ylim(*utils.rescale(self.target_vars[self.x_var], scale=scale)) - plt.legend() + pyplot.legend() if show: - plt.show() + pyplot.show() elif len(self.target_pars) == 2: container = {c: {'p0': [], 'p1': [], 'x': []} for c in stability.get_1d_stability_types()} @@ -99,7 +103,7 @@ class Bifurcation1D(Num1DAnalyzer): container[fp_type]['x'].append(x) # visualization - fig = plt.figure(self.x_var) + fig = pyplot.figure(self.x_var) ax = fig.add_subplot(projection='3d') for fp_type, points in container.items(): if len(points['x']): @@ -121,7 +125,7 @@ class Bifurcation1D(Num1DAnalyzer): ax.grid(True) ax.legend() if show: - plt.show() + pyplot.show() else: raise errors.BrainPyError(f'Cannot visualize co-dimension {len(self.target_pars)} ' @@ -156,7 +160,7 @@ class Bifurcation2D(Num2DAnalyzer): if C.F_vmap_jacobian not in self.analyzed_results: f1 = lambda xy, *args: jnp.array([self.F_fx(xy[0], xy[1], *args), self.F_fy(xy[0], xy[1], *args)]) - f2 = bm.jit(bm.vmap(bm.jacobian(f1)), device=self.jit_device) + f2 = bm.jit(vmap(bm.jacobian(f1)), device=self.jit_device) self.analyzed_results[C.F_vmap_jacobian] = f2 return self.analyzed_results[C.F_vmap_jacobian] @@ -212,6 +216,8 @@ class Bifurcation2D(Num2DAnalyzer): - parameters: a 2D matrix with the shape of (num_point, num_par) - jacobians: a 3D tensors with the shape of (num_point, 2, 2) """ + global pyplot + if pyplot is None: from matplotlib import pyplot utils.output('I am making bifurcation analysis ...') if self._can_convert_to_one_eq(): @@ -289,21 +295,21 @@ class Bifurcation2D(Num2DAnalyzer): # visualization for var in self.target_var_names: - plt.figure(var) + pyplot.figure(var) for fp_type, points in container.items(): if len(points['p']): plot_style = stability.plot_scheme[fp_type] - plt.plot(points['p'], points[var], '.', **plot_style, label=fp_type) - plt.xlabel(self.target_par_names[0]) - plt.ylabel(var) + pyplot.plot(points['p'], points[var], '.', **plot_style, label=fp_type) + pyplot.xlabel(self.target_par_names[0]) + pyplot.ylabel(var) scale = (self.lim_scale - 1) / 2 - plt.xlim(*utils.rescale(self.target_pars[self.target_par_names[0]], scale=scale)) - plt.ylim(*utils.rescale(self.target_vars[var], scale=scale)) + pyplot.xlim(*utils.rescale(self.target_pars[self.target_par_names[0]], scale=scale)) + pyplot.ylim(*utils.rescale(self.target_vars[var], scale=scale)) - plt.legend() + pyplot.legend() if show: - plt.show() + pyplot.show() # bifurcation analysis of co-dimension 2 elif len(self.target_pars) == 2: @@ -320,7 +326,7 @@ class Bifurcation2D(Num2DAnalyzer): # visualization for var in self.target_var_names: - fig = plt.figure(var) + fig = pyplot.figure(var) ax = fig.add_subplot(projection='3d') for fp_type, points in container.items(): if len(points['p0']): @@ -340,7 +346,7 @@ class Bifurcation2D(Num2DAnalyzer): ax.grid(True) ax.legend() if show: - plt.show() + pyplot.show() else: raise ValueError('Unknown length of parameters.') @@ -350,6 +356,8 @@ class Bifurcation2D(Num2DAnalyzer): def plot_limit_cycle_by_sim(self, duration=100, with_plot=True, with_return=False, plot_style=None, tol=0.001, show=False, dt=None, offset=1.): + global pyplot + if pyplot is None: from matplotlib import pyplot utils.output('I am plotting the limit cycle ...') if self._fixed_points is None: utils.output('No fixed points found, you may call "plot_bifurcation(with_plot=True)" first.') @@ -386,31 +394,33 @@ class Bifurcation2D(Num2DAnalyzer): # visualization if with_plot: if plot_style is None: plot_style = dict() - fmt = plot_style.pop('fmt', '.') + fmt = plot_style.pop('fmt', '*') if len(self.target_par_names) == 2: - for i, var in enumerate(self.target_var_names): - plt.figure(var) - plt.plot(ps_limit_cycle[0], ps_limit_cycle[1], vs_limit_cycle[i]['max'], - **plot_style, label='limit cycle (max)') - plt.plot(ps_limit_cycle[0], ps_limit_cycle[1], vs_limit_cycle[i]['min'], - **plot_style, label='limit cycle (min)') - plt.legend() + if len(ps_limit_cycle[0]): + for i, var in enumerate(self.target_var_names): + pyplot.figure(var) + pyplot.plot(ps_limit_cycle[0], ps_limit_cycle[1], vs_limit_cycle[i]['max'], + **plot_style, label='limit cycle (max)') + pyplot.plot(ps_limit_cycle[0], ps_limit_cycle[1], vs_limit_cycle[i]['min'], + **plot_style, label='limit cycle (min)') + pyplot.legend() elif len(self.target_par_names) == 1: - for i, var in enumerate(self.target_var_names): - plt.figure(var) - plt.plot(ps_limit_cycle[0], vs_limit_cycle[i]['max'], fmt, - **plot_style, label='limit cycle (max)') - plt.plot(ps_limit_cycle[0], vs_limit_cycle[i]['min'], fmt, - **plot_style, label='limit cycle (min)') - plt.legend() + if len(ps_limit_cycle[0]): + for i, var in enumerate(self.target_var_names): + pyplot.figure(var) + pyplot.plot(ps_limit_cycle[0], vs_limit_cycle[i]['max'], fmt, + **plot_style, label='limit cycle (max)') + pyplot.plot(ps_limit_cycle[0], vs_limit_cycle[i]['min'], fmt, + **plot_style, label='limit cycle (min)') + pyplot.legend() else: raise errors.AnalyzerError if show: - plt.show() + pyplot.show() if with_return: return vs_limit_cycle, ps_limit_cycle @@ -437,6 +447,8 @@ class FastSlow1D(Bifurcation1D): def plot_trajectory(self, initials, duration, plot_durations=None, dt=None, show=False, with_plot=True, with_return=False): + global pyplot + if pyplot is None: from matplotlib import pyplot utils.output('I am plotting the trajectory ...') # check the initial values @@ -470,14 +482,14 @@ class FastSlow1D(Bifurcation1D): end = int(plot_durations[i][1] / dt) p1_var = self.target_par_names[0] if len(self.target_par_names) == 1: - lines = plt.plot(mon_res[self.x_var][start: end, i], - mon_res[p1_var][start: end, i], label=legend) + lines = pyplot.plot(mon_res[self.x_var][start: end, i], + mon_res[p1_var][start: end, i], label=legend) elif len(self.target_par_names) == 2: p2_var = self.target_par_names[1] - lines = plt.plot(mon_res[self.x_var][start: end, i], - mon_res[p1_var][start: end, i], - mon_res[p2_var][start: end, i], - label=legend) + lines = pyplot.plot(mon_res[self.x_var][start: end, i], + mon_res[p1_var][start: end, i], + mon_res[p2_var][start: end, i], + label=legend) else: raise ValueError utils.add_arrow(lines[0]) @@ -488,10 +500,10 @@ class FastSlow1D(Bifurcation1D): # scale = (self.lim_scale - 1.) / 2 # plt.xlim(*utils.rescale(self.target_vars[self.x_var], scale=scale)) # plt.ylim(*utils.rescale(self.target_vars[self.target_par_names[0]], scale=scale)) - plt.legend() + pyplot.legend() if show: - plt.show() + pyplot.show() if with_return: return mon_res @@ -517,6 +529,8 @@ class FastSlow2D(Bifurcation2D): def plot_trajectory(self, initials, duration, plot_durations=None, dt=None, show=False, with_plot=True, with_return=False): + global pyplot + if pyplot is None: from matplotlib import pyplot utils.output('I am plotting the trajectory ...') # check the initial values @@ -548,25 +562,25 @@ class FastSlow2D(Bifurcation2D): end = int(plot_durations[i][1] / dt) # visualization - plt.figure(self.x_var) - lines = plt.plot(mon_res[self.target_par_names[0]][start: end, i], - mon_res[self.x_var][start: end, i], - label=legend) + pyplot.figure(self.x_var) + lines = pyplot.plot(mon_res[self.target_par_names[0]][start: end, i], + mon_res[self.x_var][start: end, i], + label=legend) utils.add_arrow(lines[0]) - plt.figure(self.y_var) - lines = plt.plot(mon_res[self.target_par_names[0]][start: end, i], - mon_res[self.y_var][start: end, i], - label=legend) + pyplot.figure(self.y_var) + lines = pyplot.plot(mon_res[self.target_par_names[0]][start: end, i], + mon_res[self.y_var][start: end, i], + label=legend) utils.add_arrow(lines[0]) - plt.figure(self.x_var) - plt.legend() - plt.figure(self.y_var) - plt.legend() + pyplot.figure(self.x_var) + pyplot.legend() + pyplot.figure(self.y_var) + pyplot.legend() if show: - plt.show() + pyplot.show() if with_return: return mon_res diff --git a/brainpy/analysis/lowdim/lowdim_phase_plane.py b/brainpy/analysis/lowdim/lowdim_phase_plane.py index ab3af243..693d93f7 100644 --- a/brainpy/analysis/lowdim/lowdim_phase_plane.py +++ b/brainpy/analysis/lowdim/lowdim_phase_plane.py @@ -1,14 +1,16 @@ # -*- coding: utf-8 -*- import jax.numpy as jnp -import matplotlib.pyplot as plt import numpy as np +from jax import vmap import brainpy.math as bm from brainpy import errors, math from brainpy.analysis import stability, constants as C, utils from brainpy.analysis.lowdim.lowdim_analyzer import * +pyplot = None + __all__ = [ 'PhasePlane1D', 'PhasePlane2D', @@ -62,6 +64,8 @@ class PhasePlane1D(Num1DAnalyzer): def plot_vector_field(self, show=False, with_plot=True, with_return=False): """Plot the vector filed.""" + global pyplot + if pyplot is None: from matplotlib import pyplot utils.output('I am creating the vector field ...') # Nullcline of the x variable @@ -72,19 +76,21 @@ class PhasePlane1D(Num1DAnalyzer): if with_plot: label = f"d{self.x_var}dt" x_style = dict(color='lightcoral', alpha=.7, linewidth=4) - plt.plot(np.asarray(self.resolutions[self.x_var]), y_val, **x_style, label=label) - plt.axhline(0) - plt.xlabel(self.x_var) - plt.ylabel(label) - plt.xlim(*utils.rescale(self.target_vars[self.x_var], scale=(self.lim_scale - 1.) / 2)) - plt.legend() - if show: plt.show() + pyplot.plot(np.asarray(self.resolutions[self.x_var]), y_val, **x_style, label=label) + pyplot.axhline(0) + pyplot.xlabel(self.x_var) + pyplot.ylabel(label) + pyplot.xlim(*utils.rescale(self.target_vars[self.x_var], scale=(self.lim_scale - 1.) / 2)) + pyplot.legend() + if show: pyplot.show() # return if with_return: return y_val def plot_fixed_point(self, show=False, with_plot=True, with_return=False): """Plot the fixed point.""" + global pyplot + if pyplot is None: from matplotlib import pyplot utils.output('I am searching fixed points ...') # fixed points and stability analysis @@ -102,10 +108,10 @@ class PhasePlane1D(Num1DAnalyzer): for fp_type, points in container.items(): if len(points): plot_style = stability.plot_scheme[fp_type] - plt.plot(points, [0] * len(points), '.', markersize=20, **plot_style, label=fp_type) - plt.legend() + pyplot.plot(points, [0] * len(points), '.', markersize=20, **plot_style, label=fp_type) + pyplot.legend() if show: - plt.show() + pyplot.show() # return if with_return: @@ -153,7 +159,7 @@ class PhasePlane2D(Num2DAnalyzer): @property def F_vmap_brentq_fy(self): if C.F_vmap_brentq_fy not in self.analyzed_results: - f_opt = bm.jit(bm.vmap(utils.jax_brentq(self.F_fy))) + f_opt = bm.jit(vmap(utils.jax_brentq(self.F_fy))) self.analyzed_results[C.F_vmap_brentq_fy] = f_opt return self.analyzed_results[C.F_vmap_brentq_fy] @@ -178,6 +184,8 @@ class PhasePlane2D(Num2DAnalyzer): "units", "angles", "scale". More settings please check https://matplotlib.org/api/_as_gen/matplotlib.pyplot.quiver.html. """ + global pyplot + if pyplot is None: from matplotlib import pyplot utils.output('I am creating the vector field ...') # get vector fields @@ -197,7 +205,7 @@ class PhasePlane2D(Num2DAnalyzer): speed = np.sqrt(dx ** 2 + dy ** 2) dx = dx / speed dy = dy / speed - plt.quiver(X, Y, dx, dy, **plot_style) + pyplot.quiver(X, Y, dx, dy, **plot_style) elif plot_method == 'streamplot': if plot_style is None: plot_style = dict(arrowsize=1.2, density=1, color='thistle') @@ -207,15 +215,15 @@ class PhasePlane2D(Num2DAnalyzer): min_width, max_width = 0.5, 5.5 speed = np.nan_to_num(np.sqrt(dx ** 2 + dy ** 2)) linewidth = min_width + max_width * (speed / speed.max()) - plt.streamplot(X, Y, dx, dy, linewidth=linewidth, **plot_style) + pyplot.streamplot(X, Y, dx, dy, linewidth=linewidth, **plot_style) else: raise errors.AnalyzerError(f'Unknown plot_method "{plot_method}", ' f'only supports "quiver" and "streamplot".') - plt.xlabel(self.x_var) - plt.ylabel(self.y_var) + pyplot.xlabel(self.x_var) + pyplot.ylabel(self.y_var) if show: - plt.show() + pyplot.show() if with_return: # return vector fields return dx, dy @@ -224,6 +232,8 @@ class PhasePlane2D(Num2DAnalyzer): y_style=None, x_style=None, show=False, coords=None, tol_nullcline=1e-7): """Plot the nullcline.""" + global pyplot + if pyplot is None: from matplotlib import pyplot utils.output('I am computing fx-nullcline ...') if coords is None: @@ -240,7 +250,7 @@ class PhasePlane2D(Num2DAnalyzer): if x_style is None: x_style = dict(color='cornflowerblue', alpha=.7, ) fmt = x_style.pop('fmt', '.') - plt.plot(x_values_in_fx, y_values_in_fx, fmt, **x_style, label=f"{self.x_var} nullcline") + pyplot.plot(x_values_in_fx, y_values_in_fx, fmt, **x_style, label=f"{self.x_var} nullcline") # Nullcline of the y variable utils.output('I am computing fy-nullcline ...') @@ -252,17 +262,17 @@ class PhasePlane2D(Num2DAnalyzer): if y_style is None: y_style = dict(color='lightcoral', alpha=.7, ) fmt = y_style.pop('fmt', '.') - plt.plot(x_values_in_fy, y_values_in_fy, fmt, **y_style, label=f"{self.y_var} nullcline") + pyplot.plot(x_values_in_fy, y_values_in_fy, fmt, **y_style, label=f"{self.y_var} nullcline") if with_plot: - plt.xlabel(self.x_var) - plt.ylabel(self.y_var) + pyplot.xlabel(self.x_var) + pyplot.ylabel(self.y_var) scale = (self.lim_scale - 1.) / 2 - plt.xlim(*utils.rescale(self.target_vars[self.x_var], scale=scale)) - plt.ylim(*utils.rescale(self.target_vars[self.y_var], scale=scale)) - plt.legend() + pyplot.xlim(*utils.rescale(self.target_vars[self.x_var], scale=scale)) + pyplot.ylim(*utils.rescale(self.target_vars[self.y_var], scale=scale)) + pyplot.legend() if show: - plt.show() + pyplot.show() if with_return: return {self.x_var: (x_values_in_fx, y_values_in_fx), @@ -273,6 +283,8 @@ class PhasePlane2D(Num2DAnalyzer): select_candidates='fx-nullcline', num_rank=100, ): """Plot the fixed point and analyze its stability. """ + global pyplot + if pyplot is None: from matplotlib import pyplot utils.output('I am searching fixed points ...') if self._can_convert_to_one_eq(): @@ -338,10 +350,10 @@ class PhasePlane2D(Num2DAnalyzer): for fp_type, points in container.items(): if len(points['x']): plot_style = stability.plot_scheme[fp_type] - plt.plot(points['x'], points['y'], '.', markersize=20, **plot_style, label=fp_type) - plt.legend() + pyplot.plot(points['x'], points['y'], '.', markersize=20, **plot_style, label=fp_type) + pyplot.legend() if show: - plt.show() + pyplot.show() if with_return: return fixed_points @@ -377,7 +389,8 @@ class PhasePlane2D(Num2DAnalyzer): show : bool Whether show or not. """ - + global pyplot + if pyplot is None: from matplotlib import pyplot utils.output('I am plotting the trajectory ...') if axes not in ['v-v', 't-v']: @@ -413,28 +426,31 @@ class PhasePlane2D(Num2DAnalyzer): start = int(plot_durations[i][0] / dt) end = int(plot_durations[i][1] / dt) if axes == 'v-v': - lines = plt.plot(mon_res[self.x_var][start: end, i], mon_res[self.y_var][start: end, i], - label=legend, **kwargs) + lines = pyplot.plot(mon_res[self.x_var][start: end, i], + mon_res[self.y_var][start: end, i], + label=legend, **kwargs) utils.add_arrow(lines[0]) else: - plt.plot(mon_res.ts[start: end], mon_res[self.x_var][start: end, i], - label=legend + f', {self.x_var}', **kwargs) - plt.plot(mon_res.ts[start: end], mon_res[self.y_var][start: end, i], - label=legend + f', {self.y_var}', **kwargs) + pyplot.plot(mon_res.ts[start: end], + mon_res[self.x_var][start: end, i], + label=legend + f', {self.x_var}', **kwargs) + pyplot.plot(mon_res.ts[start: end], + mon_res[self.y_var][start: end, i], + label=legend + f', {self.y_var}', **kwargs) # visualization of others if axes == 'v-v': - plt.xlabel(self.x_var) - plt.ylabel(self.y_var) + pyplot.xlabel(self.x_var) + pyplot.ylabel(self.y_var) scale = (self.lim_scale - 1.) / 2 - plt.xlim(*utils.rescale(self.target_vars[self.x_var], scale=scale)) - plt.ylim(*utils.rescale(self.target_vars[self.y_var], scale=scale)) - plt.legend() + pyplot.xlim(*utils.rescale(self.target_vars[self.x_var], scale=scale)) + pyplot.ylim(*utils.rescale(self.target_vars[self.y_var], scale=scale)) + pyplot.legend() else: - plt.legend(title='Initial values') + pyplot.legend(title='Initial values') if show: - plt.show() + pyplot.show() if with_return: return mon_res @@ -462,6 +478,8 @@ class PhasePlane2D(Num2DAnalyzer): show : bool Whether show or not. """ + global pyplot + if pyplot is None: from matplotlib import pyplot utils.output('I am plotting the limit cycle ...') # 1. format the initial values @@ -487,18 +505,18 @@ class PhasePlane2D(Num2DAnalyzer): x_cycle = x_data[max_index[0]: max_index[1]] y_cycle = y_data[max_index[0]: max_index[1]] # 5.5 visualization - lines = plt.plot(x_cycle, y_cycle, label='limit cycle') + lines = pyplot.plot(x_cycle, y_cycle, label='limit cycle') utils.add_arrow(lines[0]) else: utils.output(f'No limit cycle found for initial value {initial}') # 6. visualization - plt.xlabel(self.x_var) - plt.ylabel(self.y_var) + pyplot.xlabel(self.x_var) + pyplot.ylabel(self.y_var) scale = (self.lim_scale - 1.) / 2 - plt.xlim(*utils.rescale(self.target_vars[self.x_var], scale=scale)) - plt.ylim(*utils.rescale(self.target_vars[self.y_var], scale=scale)) - plt.legend() + pyplot.xlim(*utils.rescale(self.target_vars[self.x_var], scale=scale)) + pyplot.ylim(*utils.rescale(self.target_vars[self.y_var], scale=scale)) + pyplot.legend() if show: - plt.show() + pyplot.show() diff --git a/brainpy/analysis/utils/measurement.py b/brainpy/analysis/utils/measurement.py index 3cf4e76b..24d7d9dd 100644 --- a/brainpy/analysis/utils/measurement.py +++ b/brainpy/analysis/utils/measurement.py @@ -2,6 +2,7 @@ import jax.numpy as jnp import numpy as np +from brainpy.tools.others import numba_jit __all__ = [ @@ -10,7 +11,7 @@ __all__ = [ ] -# @tools.numba_jit +@numba_jit def _f1(arr, grad, tol): condition = np.logical_and(grad[:-1] * grad[1:] <= 0, grad[:-1] >= 0) indexes = np.where(condition)[0] @@ -19,7 +20,8 @@ def _f1(arr, grad, tol): length = np.max(data) - np.min(data) a = arr[indexes[-2]] b = arr[indexes[-1]] - if np.abs(a - b) <= tol * length: + # TODO: how to choose length threshold, 1e-3? + if length > 1e-3 and np.abs(a - b) <= tol * length: return indexes[-2:] return np.array([-1, -1]) diff --git a/brainpy/analysis/utils/model.py b/brainpy/analysis/utils/model.py index d499394d..2a3ab2b1 100644 --- a/brainpy/analysis/utils/model.py +++ b/brainpy/analysis/utils/model.py @@ -49,8 +49,7 @@ def model_transform(model): new_model = [] for intg in model: if isinstance(intg.f, JointEq): - new_model.extend([type(intg)(eq, var_type=intg.var_type, dt=intg.dt, dyn_var=intg.dyn_var) - for eq in intg.f.eqs]) + new_model.extend([type(intg)(eq, var_type=intg.var_type, dt=intg.dt) for eq in intg.f.eqs]) else: new_model.append(intg) diff --git a/brainpy/analysis/utils/optimization.py b/brainpy/analysis/utils/optimization.py index c1a5a618..f24fc11b 100644 --- a/brainpy/analysis/utils/optimization.py +++ b/brainpy/analysis/utils/optimization.py @@ -4,7 +4,7 @@ import jax.lax import jax.numpy as jnp import numpy as np -from jax import grad, jit +from jax import grad, jit, vmap from jax.flatten_util import ravel_pytree import brainpy.math as bm @@ -197,7 +197,7 @@ def brentq_candidates(vmap_f, *values, args=()): def brentq_roots(f, starts, ends, *vmap_args, args=()): in_axes = (0, 0, tuple([0] * len(vmap_args)) + tuple([None] * len(args))) - vmap_f_opt = bm.jit(bm.vmap(jax_brentq(f), in_axes=in_axes)) + vmap_f_opt = bm.jit(vmap(jax_brentq(f), in_axes=in_axes)) all_args = vmap_args + args if len(all_args): res = vmap_f_opt(starts, ends, all_args) @@ -397,7 +397,7 @@ def roots_of_1d_by_x(f, candidates, args=()): return fps starts = candidates[candidate_ids] ends = candidates[candidate_ids + 1] - f_opt = bm.jit(bm.vmap(jax_brentq(f), in_axes=(0, 0, None))) + f_opt = bm.jit(vmap(jax_brentq(f), in_axes=(0, 0, None))) res = f_opt(starts, ends, args) valid_idx = jnp.where(res['status'] == ECONVERGED)[0] fps2 = res['root'][valid_idx] @@ -406,7 +406,7 @@ def roots_of_1d_by_x(f, candidates, args=()): def roots_of_1d_by_xy(f, starts, ends, args): f = f_without_jaxarray_return(f) - f_opt = bm.jit(bm.vmap(jax_brentq(f))) + f_opt = bm.jit(vmap(jax_brentq(f))) res = f_opt(starts, ends, (args,)) valid_idx = jnp.where(res['status'] == ECONVERGED)[0] xs = res['root'][valid_idx] diff --git a/brainpy/analysis/utils/others.py b/brainpy/analysis/utils/others.py index 446ebe89..5266ca23 100644 --- a/brainpy/analysis/utils/others.py +++ b/brainpy/analysis/utils/others.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import jax.numpy as jnp +from jax import vmap import numpy as np import brainpy.math as bm @@ -76,7 +77,7 @@ def get_sign(f, xs, ys): def get_sign2(f, *xyz, args=()): in_axes = tuple(range(len(xyz))) + tuple([None] * len(args)) - f = bm.jit(bm.vmap(f_without_jaxarray_return(f), in_axes=in_axes)) + f = bm.jit(vmap(f_without_jaxarray_return(f), in_axes=in_axes)) xyz = tuple((v.value if isinstance(v, bm.JaxArray) else v) for v in xyz) XYZ = jnp.meshgrid(*xyz) XYZ = tuple(jnp.moveaxis(v, 1, 0).flatten() for v in XYZ) diff --git a/brainpy/check.py b/brainpy/check.py new file mode 100644 index 00000000..55fc5a9d --- /dev/null +++ b/brainpy/check.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- + + +__all__ = [ + 'is_checking', + 'turn_on', + 'turn_off', +] + +_check = True + + +def is_checking(): + """Whether the checking is turn on.""" + return _check + + +def turn_on(): + """Turn on the checking.""" + global _check + _check = True + + +def turn_off(): + """Turn off the checking.""" + global _check + _check = False diff --git a/brainpy/compact/models.py b/brainpy/compact/models.py deleted file mode 100644 index ac2db882..00000000 --- a/brainpy/compact/models.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- coding: utf-8 -*- - -from brainpy.dyn import LIF, AdExIF, Izhikevich, ExpCOBA, ExpCUBA, DeltaSynapse - -__all__ = [ - 'LIF', - 'AdExIF', - 'Izhikevich', - 'ExpCOBA', - 'ExpCUBA', - 'DeltaSynapse', -] diff --git a/brainpy/compact/runners.py b/brainpy/compact/runners.py deleted file mode 100644 index d533e888..00000000 --- a/brainpy/compact/runners.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- - -from brainpy.integrators.runner import IntegratorRunner -from brainpy.dyn.runners import DSRunner, StructRunner, ReportRunner - -__all__ = [ - 'IntegratorRunner', - 'DSRunner', - 'StructRunner', - 'ReportRunner' -] diff --git a/brainpy/compact/__init__.py b/brainpy/compat/__init__.py similarity index 100% rename from brainpy/compact/__init__.py rename to brainpy/compat/__init__.py diff --git a/brainpy/compact/brainobjects.py b/brainpy/compat/brainobjects.py similarity index 77% rename from brainpy/compact/brainobjects.py rename to brainpy/compat/brainobjects.py index 6b5b1459..d2f0fbe2 100644 --- a/brainpy/compact/brainobjects.py +++ b/brainpy/compat/brainobjects.py @@ -15,6 +15,11 @@ __all__ = [ class DynamicalSystem(dyn.DynamicalSystem): + """Dynamical System. + + .. deprecated:: 2.1.0 + Please use "brainpy.dyn.DynamicalSystem" instead. + """ def __init__(self, *args, **kwargs): warnings.warn('Please use "brainpy.dyn.DynamicalSystem" instead. ' '"brainpy.DynamicalSystem" is deprecated since ' @@ -23,6 +28,11 @@ class DynamicalSystem(dyn.DynamicalSystem): class Container(dyn.Container): + """Container. + + .. deprecated:: 2.1.0 + Please use "brainpy.dyn.Container" instead. + """ def __init__(self, *args, **kwargs): warnings.warn('Please use "brainpy.dyn.Container" instead. ' '"brainpy.Container" is deprecated since ' @@ -31,6 +41,11 @@ class Container(dyn.Container): class Network(dyn.Network): + """Network. + + .. deprecated:: 2.1.0 + Please use "brainpy.dyn.Network" instead. + """ def __init__(self, *args, **kwargs): warnings.warn('Please use "brainpy.dyn.Network" instead. ' '"brainpy.Network" is deprecated since ' @@ -39,6 +54,11 @@ class Network(dyn.Network): class ConstantDelay(dyn.ConstantDelay): + """Constant Delay. + + .. deprecated:: 2.1.0 + Please use "brainpy.dyn.ConstantDelay" instead. + """ def __init__(self, *args, **kwargs): warnings.warn('Please use "brainpy.dyn.ConstantDelay" instead. ' '"brainpy.ConstantDelay" is deprecated since ' @@ -47,6 +67,11 @@ class ConstantDelay(dyn.ConstantDelay): class NeuGroup(dyn.NeuGroup): + """Neuron group. + + .. deprecated:: 2.1.0 + Please use "brainpy.dyn.NeuGroup" instead. + """ def __init__(self, *args, **kwargs): warnings.warn('Please use "brainpy.dyn.NeuGroup" instead. ' '"brainpy.NeuGroup" is deprecated since ' @@ -55,6 +80,11 @@ class NeuGroup(dyn.NeuGroup): class TwoEndConn(dyn.TwoEndConn): + """Two-end synaptic connection. + + .. deprecated:: 2.1.0 + Please use "brainpy.dyn.TwoEndConn" instead. + """ def __init__(self, *args, **kwargs): warnings.warn('Please use "brainpy.dyn.TwoEndConn" instead. ' '"brainpy.TwoEndConn" is deprecated since ' diff --git a/brainpy/compact/integrators.py b/brainpy/compat/integrators.py similarity index 71% rename from brainpy/compact/integrators.py rename to brainpy/compat/integrators.py index 29d10b65..3980ad44 100644 --- a/brainpy/compact/integrators.py +++ b/brainpy/compat/integrators.py @@ -13,6 +13,11 @@ __all__ = [ def set_default_odeint(method): + """Set default ode integrator. + + .. deprecated:: 2.1.0 + Please use "brainpy.ode.set_default_odeint" instead. + """ warnings.warn('Please use "brainpy.ode.set_default_odeint" instead. ' '"brainpy.set_default_odeint" is deprecated since ' 'version 2.1.0', DeprecationWarning) @@ -20,6 +25,11 @@ def set_default_odeint(method): def get_default_odeint(): + """Get default ode integrator. + + .. deprecated:: 2.1.0 + Please use "brainpy.ode.get_default_odeint" instead. + """ warnings.warn('Please use "brainpy.ode.get_default_odeint" instead. ' '"brainpy.get_default_odeint" is deprecated since ' 'version 2.1.0', DeprecationWarning) @@ -27,6 +37,11 @@ def get_default_odeint(): def set_default_sdeint(method): + """Set default sde integrator. + + .. deprecated:: 2.1.0 + Please use "brainpy.ode.set_default_sdeint" instead. + """ warnings.warn('Please use "brainpy.sde.set_default_sdeint" instead. ' '"brainpy.set_default_sdeint" is deprecated since ' 'version 2.1.0', DeprecationWarning) @@ -34,6 +49,11 @@ def set_default_sdeint(method): def get_default_sdeint(): + """Get default sde integrator. + + .. deprecated:: 2.1.0 + Please use "brainpy.ode.get_default_sdeint" instead. + """ warnings.warn('Please use "brainpy.sde.get_default_sdeint" instead. ' '"brainpy.get_default_sdeint" is deprecated since ' 'version 2.1.0', DeprecationWarning) diff --git a/brainpy/compact/layers.py b/brainpy/compat/layers.py similarity index 92% rename from brainpy/compact/layers.py rename to brainpy/compat/layers.py index 167394fd..23a17727 100644 --- a/brainpy/compact/layers.py +++ b/brainpy/compat/layers.py @@ -23,7 +23,10 @@ def _check_args(args): class Module(Base): - """Basic module class.""" + """Basic module class. + + .. deprecated:: 2.1.0 + """ @staticmethod def get_param(param, size): @@ -47,7 +50,7 @@ class Module(Base): def __init__(self, name=None): # initialize parameters warnings.warn('Please use "brainpy.rnns.Module" instead. ' '"brainpy.layers.Module" is deprecated since ' - 'version 2.0.3.', DeprecationWarning) + 'version 2.1.0.', DeprecationWarning) super(Module, self).__init__(name=name) def __call__(self, *args, **kwargs): # initialize variables diff --git a/brainpy/compat/models.py b/brainpy/compat/models.py new file mode 100644 index 00000000..4aec16d2 --- /dev/null +++ b/brainpy/compat/models.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- + +import warnings + +from brainpy.dyn import neurons, synapses + +__all__ = [ + 'LIF', + 'AdExIF', + 'Izhikevich', + 'ExpCOBA', + 'ExpCUBA', + 'DeltaSynapse', +] + + +class LIF(neurons.LIF): + """LIF neuron model. + + .. deprecated:: 2.1.0 + Please use "brainpy.dyn.LIF" instead. + """ + + def __init__(self, *args, **kwargs): + warnings.warn('Please use "brainpy.dyn.LIF" instead. ' + '"brainpy.models.LIF" is deprecated since ' + 'version 2.1.0', DeprecationWarning) + super(LIF, self).__init__(*args, **kwargs) + + +class AdExIF(neurons.AdExIF): + """AdExIF neuron model. + + .. deprecated:: 2.1.0 + Please use "brainpy.dyn.AdExIF" instead. + """ + + def __init__(self, *args, **kwargs): + warnings.warn('Please use "brainpy.dyn.AdExIF" instead. ' + '"brainpy.models.AdExIF" is deprecated since ' + 'version 2.1.0', DeprecationWarning) + super(AdExIF, self).__init__(*args, **kwargs) + + +class Izhikevich(neurons.Izhikevich): + """Izhikevich neuron model. + + .. deprecated:: 2.1.0 + Please use "brainpy.dyn.Izhikevich" instead. + """ + + def __init__(self, *args, **kwargs): + warnings.warn('Please use "brainpy.dyn.Izhikevich" instead. ' + '"brainpy.models.Izhikevich" is deprecated since ' + 'version 2.1.0', DeprecationWarning) + super(Izhikevich, self).__init__(*args, **kwargs) + + +class ExpCOBA(synapses.ExpCOBA): + """ExpCOBA synapse model. + + .. deprecated:: 2.1.0 + Please use "brainpy.dyn.ExpCOBA" instead. + """ + + def __init__(self, *args, **kwargs): + warnings.warn('Please use "brainpy.dyn.ExpCOBA" instead. ' + '"brainpy.models.ExpCOBA" is deprecated since ' + 'version 2.1.0', DeprecationWarning) + super(ExpCOBA, self).__init__(*args, **kwargs) + + +class ExpCUBA(synapses.ExpCUBA): + """ExpCUBA synapse model. + + .. deprecated:: 2.1.0 + Please use "brainpy.dyn.ExpCUBA" instead. + """ + + def __init__(self, *args, **kwargs): + warnings.warn('Please use "brainpy.dyn.ExpCUBA" instead. ' + '"brainpy.models.ExpCUBA" is deprecated since ' + 'version 2.1.0', DeprecationWarning) + super(ExpCUBA, self).__init__(*args, **kwargs) + + +class DeltaSynapse(synapses.DeltaSynapse): + """Delta synapse model. + + .. deprecated:: 2.1.0 + Please use "brainpy.dyn.DeltaSynapse" instead. + """ + + def __init__(self, *args, **kwargs): + warnings.warn('Please use "brainpy.dyn.DeltaSynapse" instead. ' + '"brainpy.models.DeltaSynapse" is deprecated since ' + 'version 2.1.0', DeprecationWarning) + super(DeltaSynapse, self).__init__(*args, **kwargs) diff --git a/brainpy/compact/monitor.py b/brainpy/compat/monitor.py similarity index 77% rename from brainpy/compact/monitor.py rename to brainpy/compat/monitor.py index d4dbe4b6..c21cf0da 100644 --- a/brainpy/compact/monitor.py +++ b/brainpy/compat/monitor.py @@ -9,8 +9,13 @@ __all__ = [ class Monitor(monitor.Monitor): + """Monitor class. + + .. deprecated:: 2.1.0 + Please use "brainpy.running.Monitor" instead. + """ def __init__(self, *args, **kwargs): - super(Monitor, self).__init__(*args, **kwargs) warnings.warn('Please use "brainpy.running.Monitor" instead. ' - '"brainpy.Monitor" is deprecated since version 2.0.3.', + '"brainpy.Monitor" is deprecated since version 2.1.0.', DeprecationWarning) + super(Monitor, self).__init__(*args, **kwargs) diff --git a/brainpy/compat/runners.py b/brainpy/compat/runners.py new file mode 100644 index 00000000..83b38423 --- /dev/null +++ b/brainpy/compat/runners.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- + +import warnings + +from brainpy.dyn import runners as dyn_runner +from brainpy.integrators import runner as intg_runner + +__all__ = [ + 'IntegratorRunner', + 'DSRunner', + 'StructRunner', + 'ReportRunner' +] + + +class IntegratorRunner(intg_runner.IntegratorRunner): + """Integrator runner class. + + .. deprecated:: 2.1.0 + Please use "brainpy.integrators.IntegratorRunner" instead. + """ + def __init__(self, *args, **kwargs): + warnings.warn('Please use "brainpy.integrators.IntegratorRunner" instead. ' + '"brainpy.IntegratorRunner" is deprecated since ' + 'version 2.1.0', DeprecationWarning) + super(IntegratorRunner, self).__init__(*args, **kwargs) + + +class DSRunner(dyn_runner.DSRunner): + """Dynamical system runner class. + + .. deprecated:: 2.1.0 + Please use "brainpy.dyn.DSRunner" instead. + """ + def __init__(self, *args, **kwargs): + warnings.warn('Please use "brainpy.dyn.DSRunner" instead. ' + '"brainpy.DSRunner" is deprecated since ' + 'version 2.1.0', DeprecationWarning) + super(DSRunner, self).__init__(*args, **kwargs) + + +class StructRunner(dyn_runner.StructRunner): + """Dynamical system runner class. + + .. deprecated:: 2.1.0 + Please use "brainpy.dyn.StructRunner" instead. + """ + def __init__(self, *args, **kwargs): + warnings.warn('Please use "brainpy.dyn.StructRunner" instead. ' + '"brainpy.StructRunner" is deprecated since ' + 'version 2.1.0', DeprecationWarning) + super(StructRunner, self).__init__(*args, **kwargs) + + +class ReportRunner(dyn_runner.ReportRunner): + """Dynamical system runner class. + + .. deprecated:: 2.1.0 + Please use "brainpy.dyn.ReportRunner" instead. + """ + def __init__(self, *args, **kwargs): + warnings.warn('Please use "brainpy.dyn.ReportRunner" instead. ' + '"brainpy.ReportRunner" is deprecated since ' + 'version 2.1.0', DeprecationWarning) + super(ReportRunner, self).__init__(*args, **kwargs) diff --git a/brainpy/connect/tests/test_regular_conn.py b/brainpy/connect/tests/test_regular_conn.py index f2f46467..f6d9e79a 100644 --- a/brainpy/connect/tests/test_regular_conn.py +++ b/brainpy/connect/tests/test_regular_conn.py @@ -14,7 +14,7 @@ def test_one2one(): num = bp.tools.size2num(size) actual_mat = bp.math.zeros((num, num), dtype=bp.math.bool_) - actual_mat = bp.math.fill_diagonal(actual_mat, True) + bp.math.fill_diagonal(actual_mat, True) assert bp.math.array_equal(actual_mat, conn_mat) assert bp.math.array_equal(pre_ids, bp.math.arange(num)) @@ -42,7 +42,7 @@ def test_all2all(): print(mat) actual_mat = bp.math.ones((num, num), dtype=bp.math.bool_) if not has_self: - actual_mat = bp.math.fill_diagonal(actual_mat, False) + bp.math.fill_diagonal(actual_mat, False) assert bp.math.array_equal(actual_mat, mat) diff --git a/brainpy/datasets/chaotic_systems.py b/brainpy/datasets/chaotic_systems.py index 89687ada..98885a68 100644 --- a/brainpy/datasets/chaotic_systems.py +++ b/brainpy/datasets/chaotic_systems.py @@ -167,8 +167,8 @@ def mackey_glass_series(duration, dt=0.1, beta=2., gamma=1., tau=2., n=9.65, assert isinstance(inits, (bm.ndarray, jnp.ndarray)) rng = bm.random.RandomState(seed) - xdelay = bm.FixedLenDelay(inits.shape, tau, dt=dt) - xdelay.data = inits + 0.2 * (rng.random((xdelay.num_delay_steps,) + inits.shape) - 0.5) + xdelay = bm.TimeDelay(inits, tau, dt=dt) + xdelay.data = inits + 0.2 * (rng.random((xdelay.num_delay_step,) + inits.shape) - 0.5) @ddeint(method=method, state_delays={'x': xdelay}) def mg_eq(x, t): diff --git a/brainpy/dyn/neurons/IF_models.py b/brainpy/dyn/neurons/IF_models.py deleted file mode 100644 index 6fd0d904..00000000 --- a/brainpy/dyn/neurons/IF_models.py +++ /dev/null @@ -1,752 +0,0 @@ -# -*- coding: utf-8 -*- - -import brainpy.math as bm -from brainpy.integrators.joint_eq import JointEq -from brainpy.integrators.ode import odeint -from brainpy.dyn.base import NeuGroup - -__all__ = [ - 'LIF', - 'ExpIF', - 'AdExIF', - 'QuaIF', - 'AdQuaIF', - 'GIF', -] - - -class LIF(NeuGroup): - r"""Leaky integrate-and-fire neuron model. - - **Model Descriptions** - - The formal equations of a LIF model [1]_ is given by: - - .. math:: - - \tau \frac{dV}{dt} = - (V(t) - V_{rest}) + I(t) \\ - \text{after} \quad V(t) \gt V_{th}, V(t) = V_{reset} \quad - \text{last} \quad \tau_{ref} \quad \text{ms} - - where :math:`V` is the membrane potential, :math:`V_{rest}` is the resting - membrane potential, :math:`V_{reset}` is the reset membrane potential, - :math:`V_{th}` is the spike threshold, :math:`\tau` is the time constant, - :math:`\tau_{ref}` is the refractory time period, - and :math:`I` is the time-variant synaptic inputs. - - **Model Examples** - - - `(Brette, Romain. 2004) LIF phase locking?ki zKeU5;nSL5GYZl_K@OE7&Js-Bu|Dv7)e6UZO6s(Ke`RvBVo2hH9zx*+iyv};dj!RqD z8GcLyouC?~{Ipwh-HAJ!x1Kn0!u@nW;dz?fbkf^u8?-5*X8b!-!2d9PCU-j55{k|v z92FoqnUxd+}Y(r+$8!Xut6HWM)XT^rlx`Dvu?}b5s@$; zlN)h0qEC=bTZ)3{bMXHENtZL}?P<+u5QE4g^#_n%se^;(OKbdw(hB_(-!s~)EJC=tGx zq&FEJO)dyZdf*9D4T;5dIrH3#%e ;J9^2M=1}B{Zt%jdO6=U{HX2dEp#!kR z0>RJ+bH{*{FBuN_xdmTG18#FiG0a9pUyteMpc;kb!N`T^#-Ovwo*}9)W9fT*UDCas zNz3}CQO?qE=sf*x?p4H4-XfODYc_YlMvyr!;dh)2V)$3`Xb3`Ro-uMMy;};*FYVrL zJW#@&Jg`{kUqg?|bHUFJG76(K+!5okHja>pk=br}J8)@bIA~`>kzsObP=1o9kE%Ic zQJL_E$(yF4qsc`<`42p&%RGbfO&kb@=E(y=(o*T?NRoF-w4oZtR#f{c9!{f>itNtG zb94) poB4AHZ&>p)lUdj_c9r8}t-Mof(4 z(BvR#v?pBk8@)$X?{2;fs8t(kkf_PpxH4m7mk$mb7|5duvsCmW*#Z)!&<(j`u+*b6 zfOFrzYt6OPVMRoH2EH2XBOG(VR(_8VM6qx&h ;D{0aED7om5m+YnPARgJ z?}zUY2wJd(F%x#!7?S)fni@st&Yf0AvI$u(Ye7m4MV864LHSrTKXtv9ya^LDw#!%I z7-VF!#G6=MFChmhtC~!pI6@Y6I%`^I{wQ}X;SRpVmsKtD(Ek4GL0&ruD<%szl6&Dh zi$8li_7_zNpKHMU*Pqn}sL)0xmzCJE(SW@i0)#z|4iG-W&OIWnb^+nxBs_W?^vol5 zz586au^Uk;b-UK#1`iEMS)hB6Rq%p^G~9R^`Ms#y135z4WcSV~qeeK^eIOix4A}&_ z)6Gc7B9WoZ5b8>P+h!%L;&mT$0GX3gOY8_Ki#mek5DyRj3bU|gW?dv5h5J-Bl_lq2 zxE@sD jeW{`^l8&Ri)0Ru9QlN{ZS$J2r#bN^Qxy25g`Xa zbrnR}RLwKF5zBVPjqM)Q$dCi`wj@c=dC#bhm}?;itlVVb8Zq !ASr|#2od6d27$*y9l`4d3%j$gAa3-dHtF2d1Pg VM=g#igYL}9KY2Cskhnk9d9a7r2devn*t-!`L#q;|b_e*K?-q39R~EZ7 z_$`W{k2_P(X}vt@0H1IvbcQOJ>;PE!c)50b^0F0`GQIqnfv=q?Vcx(^s*ww(qh@SG zI!WJk=#1B6qF$<-KW|LVh+G%kEb4=kUGhyD9#F%N%JYaA5V|VNb|b^TF^Y6zYAPi` z*Wg-tCw<~4*_$$4E30OcS+K&`(NsVT%56=M6_{xrsD)1$`2Rk2ZkmuLn8di2B-u zlW$^QiyL*hbbz^<0cvJ6_C@4-WsnIt^?Kw_&s61{RX`>_mZOs|)!9uGq}La)(PU1R z?HW+Bo!65z-(g4kPz}J{G0EDI$(L&ECMg}l`0bOi*;hayuL~VV?stM8K!VqgTLW%z zXhIXJ0qE0Z5fVIPz~j0e1nqpt?!BHGsx^1a7tl+}#f5CdFIH5PIy{;q8mRhbR$g0x z?)bHMNC+O-Lhv2nFA2aP@k%#^Y7C?-%27k=s}unb3C$zqF3pZz0^PCDqMszix5@WP zT@{Jdc4&O^r83(+a`_tuGn S*PD{bg_nlE#({ako-0Ov!i zzbl_?>{>gGDH70(5k> a<6XUYh_`m(65Keg^)F-WZMO$idbfkl2`NaDlzl+7$zIhMkmY%ShN zELv~dlMQ2gd>=ZGjF=hQfYBBR< NolN` z?@?r@T`9{(Y_p;UgAjA5fL_ 8Cwn#J EJLt1Q zoh|i4!5Z}z{zUV3!|_?}WdB24nfV@f^HjI=?AjhpRy`VABnoL)1FlKgCVveix=8&V zp%eZgf>4gb4`DYLK6tiP_2nPcTDz eB za|hzS=pEix`m5FB@nysD$wpZ?t$4Y+$Wb_TF3WbXLK;<5FE@_i>fYe+8 pi9K7&4gH}~J zQlS@2jG7}) OXJ1827!FOG((dc9S>N!Po#1RstfK{Xl@K8LJS zebuxY=c1dtk*+&&WrD}AgE7^=`XeAHe^lgYb)jYRxH5}(mOhlgDzb|fE_LUd4K^m2 zsa09p-SCiMV6 ~2NJowadxL49X4Eic`InA^M?>W^9VK03f zYopK3HPG*4@GGvP%;Dk+|1$_e7bTpgEa;!jZ0%HVhG#`qcWDSWb13N)Ie6i3?=JeG zmU`*T7DU%#C^1ZV+BT)TBxFG%%N9-0Le&@`$sDE(H&@r6=DL;urKE%_-jlKX6CeiM) e9ryWWkaoDsc%+?mZTr<5ha~B$=c$O5}Upj1rB1N?IgcUm0 zTB6YKEUwWkLKfa`xZwKU!z#I;=R7Z00%ajdd@(ZVsCvD_y+37f7Y11cUup4(4jX0h zg4^QiKfO>-dm!^U!R*4Ll;H2NVTz}QLFyTguXrfv#^Br-gl%_=%$y$c28`AvVO2ih z%VNqSGq^c(R^^ln8$VNGI)dTO6iMWk7iE^Xiu~w-nX00?Bi%UVh6|=KaYMbOMZ-np zyX{Uv6f5Qx=>Qkpcp QtwR0Zm)xw>bEp(PCm!KUo$1(@htqc$!MKgJ*gY5Yc{bCpXWsJc ze)1{C)w?})@rH^&Po1-vd~~H!4nE*O+f&wB2($Na(5JnQDeNjY@U6%hrd`CqWZ}{& zjlhVrmVvWG)2~kabPFf0hMPE-%a<&Wk^5j>!*tb8l$gMUdswO7EFTbjk%0Y{v|-~c zqFn70T@ZKt7CFVw<-+~#i*AXS0$G_^p%rIQbrX5^a5|+afd?sgxbE>#^+XX2ML7~f z8VXAw7(0wx!Sas#(XPud#(M)BJKcm9hP&1B9Ji1g$u<|L3z;{fq^%|j3E=U$ePuzF zF_YFd2nVZvvTJE9NlUkJ&ZOAhYp>!i-)~vhsk%zROOmsiy~Q2MoGH0V4R~~Q1)2+% zGqf;2TE;BHXcyn+FL}1ZhpjCGA3U85A<|W;Z}Q(=%(+tTJ>`bh&6U4Mj*ux~8F3I( z$_toR4uNgSN1+C{i7v`*Oe%72Y^#ZTJ;*^?;*Xx{me8dSD8~?uo6L_Nb>B&A*spu* z7JGeZPUbG-pet*WThW7-siw8xpTF_PZ@A2vVqW#L7q%6}edfJ-)AdXi<2}W#L{WAL z5SLK64ViPxm!-O31~+b3FYGfpmHK43(!vp~3A{$AldZ;~3pr`bIOk8U>dYg{`-pV< za8w3`u<$|ZnbmB>4c;RP;Uhx-KwM3=yH1m5p$1tKdYxsDA#O$cW #W_E_JCF_sR^=!)IR+bgG+pBJk9*+jgpk{a{f)Lei z(B`_L`)!wVJ8AI2)b`hbhR#{)z>6y9Xq@RS+Q*L_N9&U=(g;rJO7_rQ@Rb|&PhsY| zk?+*6HT5};?~`q=YScuBL}V55@;$O9BMVg-yCE9$gNh*wz#?6=j3B8@KhWr-wGBE+ zOE|$IQu;ZCa75Vf%%J6BY?%NqxA8IRxA>wNdzy!|V!4tmYa!?Q!N#sBN2x)pyD&Gr zQ^;>E`;IE+=4?U3F-4K*f{2K7aJ}o3U?wv>=lw;zNe?IcuBUwOzT9uAudgZm;Qd~l zj{T&WeO`xs5bt`fF1b;;^{S_A^Qn;Ww_HJbE`j-Z?77Yb>A4x#fEhH5+y{ujD9hwp z^o;ApfQiFB!3$b(HH(n>m)&-F?n9 7PB>cbeK2KsIc5RjOW~jIjoC8Bq^K*)qG-DW zZVjHag%Xl4 )<(3#((_K1c zw}8>q=3}xWHD}mV&6yF4LKqn+JS?5)#_8=~JRd5{R$@^6T4hJ8oIIWLi0K5n?=ZBt ze0W?@nFMY7xpPk;o-i|h->+h7yui`#<@1VYGD233ZSvMXk-LDBt=XkQAX{6~q563- z$t*6_9{EpIWVi(pB44_((O``vi{U$!tYc27*D&b%lTQ1|0h{dYWj_VcX6L%@mLqge zv>I_d`$Dt&G|RE|ePx5L9{4Hg#NYv3=~R?mZ%jR<2`^`;Doge-j*gkUTF*LKD#@Gn z-8+r#H^O!5Z~5h=1E<%3M=In! JEFs zB9EpI#X;e+SPdT(tRBbZBAj(Y)X}^HWR{CnJvZrnfC`# #lodP2f$50t}7HV1%J&H4}ck`d<+dZ>OJSpIs%o?rN`l+ZF+)jXGJu7F#q z_GEwY_HDqV$b5JGluDp_nq>>8D9pQH5?&)vuO^kTEb}`vxn;AKa@%Q96tZ;(csJQU zk6Fm;A4?&ahPtQ*^CF@-0Yz$6|03I>V@zmmk!bx6(~nh+yAI8Y+<8SVM|Pt 5k*MEjj|KsAg9I~?tYiWvag`yL$Sq3?A^ zsFRMH2q3pVO(pdd@qU8NMt|tMgTBHt4<+WXbz=hc+<7>Id<-qM;8{xPM6ae&p?YA` zhY~9U36^uwmqVUzxyoJ&`!7Q+VA!F&hBAwJupB(wzPwSh96j!g$Rhrp`uHWav}9>{ zXO?Gk?>Kmb{~Che*1_rNnmbfW%q~osZI+cwpJiRpNGa#HTs9dwW-eX8i?QUVfFg*n z+bC)bxi=$pCuMuS-R(0Mlw=2NbDE^_1l$zz-TR~O@WP%>%N-7=fHTq(H(-;v-<>Tn z3 YYF KoECfuK6q*1F%La;l7AqV0qpaLnX7$<`wHdww>hzlw=Hy6C+3 z Q<1POnl=b3i #+XU*uSUY7%daDv*h@2^-<)7^^}L=PA@MNycfPE~xDl%VjZqlyx;ia% zN0_TS*_31?ipFBa%fKPPUL{jz&W1H0fWHi!jJx|9q4lh*{K0n302=nBTJSS9_5SjE zC8E{Lg(frf1ykwk-Vb1?Z}0G(2Y35#dQuPCc06LKq!G`&qw1@7=e%L5B&Tyr=MzNJ z?~IwV_hFQqOEu`yDctW}ghQhX^_slr9#Tpt%M!+;ybf@+ed` )9lcqIC5jyfYCwl11_(@h&rA^ zh5<$kBTk_xMrAH=sth&{6Gb`8U*4JbdVDfOiJdO-y;Csz)&UJ!rnkRNu~mB~Yr0uh zqT6Y+Dez)hEIE+hty_qdil|;Y*p*_!S)g=0yT4}1IsW)HwVQFkYV17a*U_@lsk5|i zPpi%`F*>=2d8%6-JWp2zWHl!6-qR|2LOr1TXyrJvQwUiieQ)s ai@8n!O z4=YEVEuCd?+nR!r)i`8-Su|gh^_`5~Cz -f9KRePE@EbKM#XDIBeo@VA_?CKAX$n|1( zCK8=yYtU`hNH18#)tm!z%_mYlTc&nVRM~5BBhQzrh>&U`y}EJH;%9YqWFr|^U`4}R z-Ge8bO^z6&BHSOrl+i(1!22$*B+P<}Xkn&T70%WqPPydLgAlH`Jn Z!zWe^~`dhBHhU_Cb=iXauE3c%RyO>(}9i1 $M$tke6Di74;R^fi#hU zt6sxvk57(SQ4OLw0#4IXd%3%~%URzm+82`feOr>=BS$-(_s)MWr!LH6-A^sN&?q(v zRA!shRlFV(J-8UdPUgf#jN`eR#_J9bJ2IJ_@ya|u-5%%?K{NZ_3AC1P;~b<|fuB+u z`!J2LMz*9Ydmx&~O9*$vQ#X^Nm#edELAiXJ-8UZauS@s51#fE9Ks|FL$Bg1Q;LW{> zF~q*QpPT6*l=JNlf3p$9e36cU>OuA8W}(D3jz&Zehy2^n=6XoQnBKO??aq0VQmLPb zz)s LxS78CW zexN$%__po6x`z~(fqh#LOwSN%ti~2B0O_58J1*0a*FrV|c~I>`j$T+Ayt>n2GWJiD z67?&CYuAW#Nwz>=x*`KvOYVXIo5ar^Xpc?$SW!iJKK`J`3OCOv14vksL=6)4PKh@5 zkGa^k_O{2>7}MZFZme+HypSU1CvkGSl3uYQT+Gjb15jfXcFYPlpipBe3liNrwiN{G zxax%^UQ2Bxin10reQ%jo_ogi72qP)nSmfY0kAEdoJ4g2bHh)3N`%QUno0>&E0-46Q zZz+!bzjXdbYN0G4nj;|ul6C(2Ye0kf)(Z*JJDihhfb`0pvh?(lEE%{E$WoH!R+`Ll z<(2C|cS?2uT6FR n`4tp_BAJ{5h+9`Gab ze36_=js%`bMH&AmHQ`srG0u<~mc`8Xf1&kf9Vi%72J`EXGekPzoFEh5)LN~();o3? zhzwHjdQ@Z(47^ !^r9oe)X@^AF zAe=VVm*^JUtljw`JbxU#SQ&+vB-*e{)K>!q{3JMea;0Y6l6Ar-W>nXsvNEiC93os+ z@SdtjDk*&>?L%x`KYIhwv-!q@S`eO|Y_5MKw|E$5B5MF|w#~g#UECxqNs(Ncbp>&s zg{vx<>Oqc5-ch|FxP&=<5v#Z|ec0l?me0}b0&VYuR_>&hJI3yxY^*9**sYju#iqEj z+2l)=FzI`yvfigdCSGS03f;(tq@F?V2uY1EQnmCY;`QbCh9Fh^p}_~0XR=kd2n##E zsHyZdO|`;2AJ=sBI$x4JJmP&IY$f8Wdlp;EqR^mREeaa%eSb{j?vttsJIkF7v2XiH zHoqLxm`|qfMvcC00tK%4 o+#m!=#My{|!@)0_hyEN)eEmF}XKOU{`JlB7;roiK) zuNDETKFw08>ynbge3t&M*1AXBdx|6CL?f=!cG;=aDahd #VIDAYs)A2Ul z?NzV}bXFZmTm3qD3H+k|NyM=)=IQ5TTkRgK*@@)_Tx+Nc&tMnbJYVMv6+c^7pQP`AS5 zR&~E*{p?yK4XYT4Yr)zF T}!mo >sqBR2AJ0m5*0oc@8gl?^SKBwr#y4T=RP9AkMWs;Is$M z$Uz#>;!K^TaVG;i*APelOwiIU-p@^!EMqV}d0^jNCHA2YM^c?Q*maLOnF9;Dm8C=1 zuzKy#O2$x(^laigZ}?&$1&i7?>KiT-!nphJFn1^V+k$LI@~YDOIUBcg2)EKZnGa~0 z;E{C}W2-E_ 9c8vC z)mU+)eveW=rv7$HEn97MX!N%nG bB0hHcBWxSFI7oxeYbb>&wkcz>_9 zJ(9&f`W)Qa8W_;q68oa7vP=IqvM*eUIewfkyE|~@{2%68De1=B=s?y6(Hd~~5&gWY zLEU+sRkc!P(AayKc@07q_dws~@_kB{{*F*Kk-?#L-)Hj229EsbP`Gq8``#7hx}Q6U zA5c+oHLVWDT$8yn)oo*ng 57~W^YHug?6s7X#fV4Y>zGv;pIdJ| zer~77+Im9YKGJ4+17_ BTMi~}y=uF&0J|wvXfhG>&T-Ynm%HI6BwUIAE?ctBm2MAt=ctI^ zuUH-%3lu9!^%Z!IedMmG6_vgP+VS{m<5Rk)GQ&fvH~iq*Wb&;@8$Q#El6-Wujph_m zGtni#YTp$Gxy$g~z=EL+r3=b#>P!D>y8|r@{=w-Xa)&=L%7SsXP8HhURQ=niU>NRo zOu{thQ+St0IdfNROYiz1%WtbuZ$zQKH|@#W?6Vh<1BK{Ix?}>7eOTo!X+K`} h+yg0}f`GJd&tWy^D}=|4z6Si5i5Lt(qY?k^C+7o*lcYDjGI24`b-X ze789^^GU4uun(w^qWaFyDdEeE&ZYS0Ew>bBPC(96%$t6NW*1zThJY6{ZvC+4erJ (o}dXU6WPZZT0jUNk?|~=&|c{n<{h4!Z+kX z?N(#oTdo%>^ZnT?!c+ZaXzRr1L6ua(i*x1TfPGJ4e3lmRT+N*CoP)SUtX3U8wguYI z_yK9@ncSp)_h|)IkvJJC` s`v=L>-pRFvt0yK z>wF?kXUlR`KcP%BD5sIh+TtXLZ z<(%Vc0xdc|geR@<{8)fhoKZh7R?}cbw?^It2d}|zf`aT|k81ZQ!p?F*Zjp3^+-e6U z?k0r5aEkL~oh$fVN6Msmeebg(x**ioS-w5)n4oatrEn8 XtkwF4+q!Tsp<>v<8&+P~7^?!vB%qpR_CB zB880P0RFW(gUFYHx^0oCeyq~ArIrR{lx8`J=PX8eUI)`b$j5@qmlbL+BCq{q`SO4` zhJb!@uFeq;FrO`~t^5o}#EshQC*2wxsW)E^_pK^-XO}ajx8}#>!MUOO o|@^=u1 z+}Y>QPpj{OV=1MStD>$(yBOi|Ex|qxkS>kP6P+d+tI^M|wXb8$(najn*;o1c7oDjG z7$~lU{dc8tE3q?yoU3cae4O{DRG;)VJ**26^U#4_4Jxz+Zy0ngca_)GHt_Pj|JKH9 zYHoFE6h9S}@lFrwGx}Pjlcwb~v6p-&Yl imJW8wc2|59pvD>+#tbf z1mf=Y#bCwK?8#ySopiLdScyCFUUly8oj(_smB7bW-}7Gft2E$Z8b#8pD&wb?cGAQ# z>tXhY+l%7tC!s51ojS>`Mt@xP9$)jUaIjdZZEucj;=cM92Ph6RU;Wo9`H3vCs$r`V zTp;#Mi6uOwa_&{Ysu@>>Kc?whULuZien(Pq??obgO&9AQbT5CPL(EOd*OS~ge|Pk^ z>O`ABe>;PC_W%D=XNa%;Zf$VYa@@4@R+#`RIO!UmiRBeGrdQQSPsHiF?UV^OT} z$A;1@(AvH*velI5dq~^cP}&T)MjVQ%4P{fmI4dYn%5o0rUsI aXdNAqHBNX*@lJ Ma7qb z);yq8gNcuZ2OpleuG1<|d^vVe2;qIn=t3Tw8oH6J#N*5;hKqJf=nuadhqnrr&OaLBD(;zF@7ZrHTEp6% zqV*r((aq11T5zymx%G{zhr6vSrp(kElovtsMN<|;yj)|VTRqU_;lkf5%jREqC2=joI-_g7~gYZM1 z=U(N*+Fzk^^mv}Cn>4&(tArr6>n_~>A9Q19;GFgJ- t~!d1E~5W=74KSog2agYmXr5pduR^8US<7GJqTN8ZP z_!X=$ikqXSPAenOcnO=+Y6Z9Ph>g#nP|89_aip1#7c!|@&k$v3;9Nb$cplJlmEtI8 zYW_~RpzVvvfl IHZt43koD0ZO8t%nHcr;Tg ztDqsf491eqEB8FLK&WWl8lpItR!S*w=f24=MZj~q7ZbQNeP2vGK)aqX@h950(z%3d zEm(<1V;3bYfT?Y-2EEH;7vsI)ws7(aIX4FI&VTojhb4>lLMZkfen?-()aTLoxdp_2 z9-RVYS@NCNuj_nh`YK*p0{T^KWOZe5WInzRQd`#jpJ}~y?4ku`+-NcMT&iit+5L;5 zc~6Ojb_gs6|ivT zx!|@O#pEwVV^#{{swazCnT8AhjhRq+(NaxnUx@Nk#9{#a&_ha5UcoX`U+AwtS0 z(RLkFEP<;seaMGmNdNNR^b^15(9s`MJU41EkcNLf6ah6e-vhj;egONNMf^8`OqQ~L zsrx2YGI-^?JOh8XPcm_vrsTB6+`otFXAKwxR^ _RSU)=5-xrWolA04%QXZt6w>iw&`eskT!PDAbN<)6=gy57He6EEL%(v4Y62| zD_j}J2Fw9j(16fy=Zs;6Lwb)Qy-ELwA(U3CedP2L&&VDl2HsD5mrXJb$X^;~fOiG| zGBRG#UZ|>;@tD%UZzKnX6|?OB6dB6M_2)?;yObV^243-F?&Jsi-&(lb4e3^fO9cUQ zX&E(MAu`bCe&cQbh&4e`y-{a6Ekn-AQBLtQn2bCu?gGY%5 )ZHFG^>NN-n5AeksyGnLdT0DIIf;Z-G9g%sf|vQY31 z8ABy8JAxbOBR4F&Jf^OLla(q(d99$RVh8+HGB}-E9~9C?^cCm$dRL~+u`c{YJhy+$ zWB!yl2PZykU;;su{78MRCcN};Gu=E#;pi2N6V@EZx!eAF{)D*sf@ryEV!Yb?;$jko zlXQ4By{$Lw_TrC|H7;F$%9hgm=i7fI&Pd{P8L;92l8E7%yyvT#Nbv-sX0=gcS?-V@ z^~cG#_uZ_mk4Fby#H?l#!QyTW{>kNPm+$>Vc}>>A@wcng#EaMLAJlMSYOCV>Xt-na zFB$@QO7Lp-vIrsSQ*EqfHCgK+S~+H;alOVs>eb{G)Ff-sZ#5NqgZCpvDb5K7;l#{o z{DZk{U@i=txH~_R0_1+#vLRq`PB9xIGH!=8ZBmqdJUgM(F_yEM(9{Vg0x4b~eY>i- z4vc7RIQ}AF;oNE__Rnci^R_>yMYB;qnESai(1=wdmg0$D%PKWFal$zLxM(dt_r;he z&QdllyNFZZX%9uo-0(~^Mw|+5)fCO|O}}R$?2*?JzRgQR=Q>u;xb;5rIgSzQ |RU2%say42rPV+O {6n8>SwLQ)C^IP3+a#Guo6v{fp8g0l3OvCv#9z{ zWgh%e;EltLrG>KU|6|=nMlHP$sPN#Z$o)k@Np5p58K!UQ0o0}-BYGb&rDEu6w0|vQ z`BOCvZkjJFiW$Vzj#B?4*P_}ppu6EWQU9^-Mg+PC2qu9M^!-ag3DZqfoMW#Lyg 9tS_N#QI6^q~kWfuu{D`)JGjLiRk>!gyppRzm@+83MOpsGLP@(h&sH}wZN|`c& z04koJ{+jeb1S^Vmug)3k7JKLf6j8n58A#7gL>SL6^EJ*$d6F;FdZewxyO#YJ+uuK| zWH?xW`dl=8QW})*9WScAT700v{UcIhh4KKX)Esg$X+4ZJ$$R!2VZ-B# bOpC%u1`YVnr*m}qZURIIuLmHjI|Us1W(HT5Aq(^*%7 z@8sx#;EeqNmHa&j(N*Qe;u;-`p+q9`EBq)Mf1&so)jurFq^%Z}g_aMLQ$YsEaR3d> zscNfU_GoVgcjrZhg4Dy{XYtI~7*pMeh?8RpKy6pjiq{lfUV{2P%lnxN=DB(FVZGv| zG6lZm9RQ05I2+x(E$;9aEW64)S?=qm7mMToX&G8iqUW*7Lig-ik fnqwc%1+WQ z$TBkB;Pw|#6cGzPV1A>H4Wm#-EQQthZk)Xl6FN6hxHe1@dFLI2%rX?A?(uSLV4wI6Ny zlFtCzn#B$x=bN`8fnzvXKC`}+qmDr`5h43@h>xyx{4UY+CPw05acK7CEy94hC~*4I zk}Nqm9+=ybRffx{VN-xNq?TCB=}J9tx)@-v{|*AMy#;=m gFN$-VUg7S$2L22u!4wyoXHP~??Y|6;H(E#XUuj}tc zLFv$#`s0E}%7o8*WKV2yVwP;X(Kx5Azi5-OQv<4fz{V?|cvW08idbPx9$B$vsN#v; zoPbm3EwAs16U;{HC0BF!4GFEorgV kq!=gsU`|#UIVb!Ds2ogg z&xXQ*)31n=JFhFXY{r9iSzn${&J4;gUOC~TdS9y{ow=fHJTjdBYV>~6c4yr~3c#^E zk%oG2&0UgS(fVeM_E9>6VCEy @`{bg8PB8BeMUyOrC*){B_a~TFw?3I7T9Pb`yH(z ze$+F%gYZYlLZvjko+WJmhCdZ1E*T$kdNdfAR$^gQL*FJbz%KPxGdnpl*^rp<&Nu&^ zz0y)s!ZG;$Ln8y>ap7*leVuC8Dj5nn-omkH5holGk|zKzkXDMehaaw)evR_^s8xK$ z(n8O6%nUh-W;`}N(OPFrdXBw!1{N+ewW~gZm(A^8lm5P@sSqtQrZet2Van@wk3~#Y zRg+c5M39|;F+ShwWb1jzOVmZ^Fm`{AE4hfd?n2$3Z*?QTva2&ELLEey`UKTB#1b7+ zi?n>d0w*<19-~Y58+>myJLy%)ri6V>^xW`H$H2z&)jqtBTO97SPsqMSCE6wv|E~r& zJd#7x9)8Rui b zSA -vbKjc z>4it}!t6f$#O26q1B s{Pz Y7{ZeOj!&mj|s1hb?2u08fARkHhM4W$ZrwP-NIIedat~-Y%Ph# zIJti;KdPENsoX<4YBsXq*%G_ofuFr59V@+Q%uIKmzslkk{;PiIa@BXsuWyG5FWK zLCTt7w3fYf8F$6*&<>vl^$uFKxq(inhmgSMA;q!$_LX*sP30{hq60&eJ~d^ZV7 z)hSee492Wn4s*DU{TKl$4u-$HSz$oN(wWO=qtEr1b&P78XYi6J8Q25mX!^w`lrFMH zaMDD(;mBl&8avK|`z?>GG9fPa{Z0F3XgXBw +t}&B z_p^I5zZZ=%QH!0u@n(;eGbU^7RCQaY=sn@5VW=CY?WcDMqSEqZvoSp=TfDw61?P%i zqevzcX*q~|2l2u!8a99gI`f*iu5XO#opJ&oNaWx`5m>HqmoIJcUc0v5am%E{Qr~MX z#<5oZkXP~ZFJtJ}A`Y%eu6M>fzKnVAa>DmOvhFCC+qOP;;VHiQR?d4Z5vq$pXJCYi zFZlwtW*-tjT4BWa5wjdsHChcLO-9xvTUpi&lp7EP>g4Y7fpFec#2f!Mzc_}TJ7!DU zETB9Mb`xs5upbkBySB)`lF7@mH4P}}U>_NovO?4y8oWopEY(tCru$wtmjbr=2=-Am z8~omQz(Xi7v$cN1mLk7v?rXJQEdGw>xu3RS6(AF}1jv?;q!eR(L~_I2-rLc{6e*`S zRUS528o)* e6XjlFb07cENPGJo1(VD3iNO~4HQ7e5F%28lEa;hH z@GCisl^(`h4}LS}RJ90>SO2wCTur5j-c}1ceE`Dk9r2O~AglfLjI?vLM_dgY_@8~3 zM&rjR<1vQbbSdv}@&Ty94WbHFP=w7l?iUs2w` 741Ay-HK$@`JF;zIIE6`A=1I#$$d2IOXqFHme^aV30b z7gP`+uXFJcZrJl{Zdj6Ol%W-M1^@>Qt(|orpZmS)Ix)-M^jO0)V@Vw4QS5^%iRXe% zY)aSNgsm1Y@$*zGt-~UI*L!+R2^865_ClZ5GFtRTj*Thkw?B--a=fk-NAQbo&hEL? zspF{X+2MYfj2Av9I6#C|x@47EhCxhJ$i64tR#XHuI@p5oRJ~Xl9(y73cc+=8?FNCK zjhlL$s;OsN^ouWfy&;}YeSR$|e*hCntoGeW40?}7q4e%poiEfSXJ`gQpUdV@n1okq zz8&taWZ`%x0#U1p95nz}N8S!mT5j0HzNrlChdF&cHteQ_R}SoFD9EGqtlS4v2Azes zZ#!GcMeG8AJrmmIQ#+CLiA_VG;xG*@I|7zzgDZ^kaV7cE+FpQV)r&z4^7qv0@_m>& zH>|r<4I#Tkp_k(^TgW7<%vn12<0^mCdm{}B&uL$OOdqwbQ}N6r^sn*Ht)tI%-c2B$ zPJQeyc-XeizPS&6&a^dzoOK&Ca!EraEFSH%w>+@~9wo?(^X%wNR23sSlT?n79Pykl z0BS8rOXYF)HZhpw8jyb2q-j(U9v?I@YA3vYA3zpe!0-48^yMY-RBEaSt~o$Ic0vSS z05xCuH*C{>v@5*?z~9!Aw>A=?G;Tb91^`xwI29*q7f> %p4lU*;00;t1I=@09rv}83x`~tW ze{Q)KKqyYo3|MNA>5TU2$w2rhg1*EOEwqKv!*K!r`u9X|7RT%kjZMB(V#nDUAi0O@ zV>kvvL@4bp1c0IirPz7p5G_&>S`wd~Mexq*UI38q(GeNsOs)#aIsyQ0aIsuyKpSVQ z1&wS`(kVXgl}r3rykRjAbki7?TFYhmi%HseWfRRrfK|*%i2?u!`k2p?kcF=@@cQy; zq!))qo$n33D40Y>-wN3Ws+plNZxfzX?FNgATRimY{=)D*n~uQa=El#}8<5OhZL2 z&&v0XDFAEZP>M0~W97C!5yNLAiM=-FBQnNRL2YR4jas%FIRF5129TP)&lFaQ5TEt$ z&56LrmFCk*(2+!yLcwsk!L}A>Nl=~Pe&~*d1V0H|P2m58{I}VNc`ND#lpRJz5~K=% zFkiD=XP5jCFo^0)YH%2!=KJ3Y=pjd61kPr#)uV<9VpwXO;xo%k+-&dL*}U10mjMe0 z>P5Eq-ioWy1e;W@Xb%EA9#!24Il;{k8BksD$y4$9Tc2nb+peVQI++206i!vSAz>c? z3zGQge9`=X)VQ(=l+B6>6g{$>=(kl#ySOF5mAf1o7V9bE)wVmT M_A^Y{`4fE+tp&W_lNTK2^QIN+~F z(ujKlCxH<$Skv6wdD?PB22(MVwF wqYODasvXcA~-I& zz%7t@ie>E#nFly*!W;E-yOhB(YH{dhBQv;V$>x;ZJLLy zy%N(7YlWO#xZ^H@8h1`9A%uz)0^v3=4s<_}{7bZU{n`~$=-3^q=!xu+$@*VRM-Bks zyZpQiJd?Z0E{$9ZV9|?VG IcF{|#8%CSYh+T7MKuh WloC!C zpwwOX92@`InRN*wp!=GtQ6`PnzZP_@v}&}Fh1GxGvG@M=e}ldc9~AAoMe+!^V(x?^ zlKs0p WY8PpL|{xw>6hE+9`42L9=eT$CnDR*X<7!tob_ z7T(k8wWS5OGwuR=UJNP=QdPMfl;W&9NAJ;4dNvYElmt*5c>ri+&P9;Ue*yj{FAbq{ zo%sC69&Y?UFz$$ ;PIvOq;O*LUa?gD~P@5HS zGeZoq7uuQ<{%a?VJJ78HUTTC|Zb2@fA*GSuAOpo*5mZv-3<@3KzY!RC|9bmD9dP>r z?_X~}$URn5GE>BycIQ*8u}IRx!=wNl=V`aOkW2pwl!$r$(;`Pmo4Pd=Id z-!F_XkpKX6694bq6_FIP(q*3~f@SKCg1`xk?X0S$csEj_2eO+HP74}fh#~f4?T|1& ze79%|lj9*@1MG?xPFF1saXi!opLe0Lxe@I5?X?mh&Hw3sk8-s{&&DFN +ShLwh(C&t2lsT#=`Kj$WE)QiU3MkebP+?pA8h6oV{agyeY$<~s9Z zW_8S4J8Q-sx`gi*-A}>@ZTuhZ@zDR00rj3jBfV2fu@6fR01x;Qlv{gQX(bBn|0~|v k^8fe>nyY!;LOJ+>moXnUd}WL7p!wPQ{K>)-*M9xK0AMZIWdHyG literal 0 HcmV?d00001 diff --git a/docs/apis/compat.rst b/docs/apis/compat.rst new file mode 100644 index 00000000..03d41492 --- /dev/null +++ b/docs/apis/compat.rst @@ -0,0 +1,16 @@ +``brainpy.compat`` module +=========================== + +.. currentmodule:: brainpy.compat +.. automodule:: brainpy.compat + + +.. toctree:: + :maxdepth: 1 + + auto/compat/brainobjects + auto/compat/integrators + auto/compat/layers + auto/compat/models + auto/compat/runners + auto/compat/monitor diff --git a/docs/apis/integrators.rst b/docs/apis/integrators.rst index bd499f2b..cf6fb02b 100644 --- a/docs/apis/integrators.rst +++ b/docs/apis/integrators.rst @@ -12,4 +12,5 @@ integrators/ODE integrators/SDE integrators/DDE + integrators/FDE diff --git a/docs/apis/integrators/FDE.rst b/docs/apis/integrators/FDE.rst new file mode 100644 index 00000000..0c116455 --- /dev/null +++ b/docs/apis/integrators/FDE.rst @@ -0,0 +1,15 @@ +Numerical Methods for FDEs +========================== + +.. currentmodule:: brainpy.integrators.sde +.. automodule:: brainpy.integrators.sde + + +.. toctree:: + :maxdepth: 2 + + ../auto/integrators/fde_base + ../auto/integrators/fde_generic + ../auto/integrators/fde_Caputo + ../auto/integrators/fde_GL + diff --git a/docs/apis/math_compat.rst b/docs/apis/math_compat.rst new file mode 100644 index 00000000..2f2983c1 --- /dev/null +++ b/docs/apis/math_compat.rst @@ -0,0 +1,12 @@ +``brainpy.math.compat`` module +=============================== + +.. currentmodule:: brainpy.math.compat +.. automodule:: brainpy.math.compat + + +.. toctree:: + :maxdepth: 1 + + auto/math_compat/optimizers + auto/math_compat/losses diff --git a/docs/auto_generater.py b/docs/auto_generater.py index 7c8bf39c..811322af 100644 --- a/docs/auto_generater.py +++ b/docs/auto_generater.py @@ -6,14 +6,13 @@ import os from brainpy.math import (activations, autograd, controls, function, jit, operators, parallels, setting, delay_vars, - compact) - + compat) block_list = ['test', 'register_pytree_node'] for module in [jit, autograd, function, controls, activations, operators, parallels, setting, - delay_vars, compact]: + delay_vars, compat]: for k in dir(module): if (not k.startswith('_')) and (not inspect.ismodule(getattr(module, k))): block_list.append(k) @@ -230,20 +229,26 @@ def generate_dyn_docs(path='apis/auto/dyn/'): filename=os.path.join(path, 'base.rst'), header='Base Class') - module_and_name = [('biological_models', 'Biological Models'), - ('IF_models', 'Integrate-and-Fire Models'), - ('input_models', 'Input Models'), - ('rate_models', 'Rate Models'), - ('reduced_models', 'Reduced Models'), ] + module_and_name = [ + ('biological_models', 'Biological Models'), + ('fractional_models', 'Fractional-order Models'), + ('input_models', 'Input Models'), + ('noise_models', 'Noise Models'), + ('rate_models', 'Rate Models'), + ('reduced_models', 'Reduced Models'), + ] write_submodules(module_name='brainpy.dyn.neurons', filename=os.path.join(path, 'neurons.rst'), header='Neuron Models', submodule_names=[a[0] for a in module_and_name], section_names=[a[1] for a in module_and_name]) - module_and_name = [('biological_models', 'Biological Models'), - ('abstract_models', 'Abstract Models'), - ('learning_rules', 'Learning Rules'), ] + module_and_name = [ + ('biological_models', 'Biological Models'), + ('abstract_models', 'Abstract Models'), + ('delay_coupling', 'Delay Coupling Models'), + ('learning_rules', 'Learning Rule Models'), + ] write_submodules(module_name='brainpy.dyn.synapses', filename=os.path.join(path, 'synapses.rst'), header='Synapse Models', @@ -325,6 +330,20 @@ def generate_integrators_doc(path='apis/auto/integrators/'): filename=os.path.join(path, 'dde_explicit_rk.rst'), header='Explicit Runge-Kutta Methods') + # FDE + write_module(module_name='brainpy.integrators.fde.base', + filename=os.path.join(path, 'fde_base.rst'), + header='Base Integrator') + write_module(module_name='brainpy.integrators.fde.generic', + filename=os.path.join(path, 'fde_generic.rst'), + header='Generic Functions') + write_module(module_name='brainpy.integrators.fde.Caputo', + filename=os.path.join(path, 'fde_Caputo.rst'), + header='Methods for Caputo Fractional Derivative') + write_module(module_name='brainpy.integrators.fde.GL', + filename=os.path.join(path, 'fde_GL.rst'), + header='Methods for Riemann-Liouville Fractional Derivative') + # Others write_module(module_name='brainpy.integrators.joint_eq', filename=os.path.join(path, 'joint_eq.rst'), @@ -444,8 +463,6 @@ def generate_nn_docs(path='apis/auto/nn/'): header='Nodes: reservoir computing') - - def generate_optimizers_docs(path='apis/auto/'): if not os.path.exists(path): os.makedirs(path) @@ -491,3 +508,39 @@ def generate_tools_docs(path='apis/auto/tools/'): filename=os.path.join(path, 'others.rst'), header='Other Tools') + +def generate_compact_docs(path='apis/auto/compat/'): + if not os.path.exists(path): + os.makedirs(path) + + write_module(module_name='brainpy.compat.brainobjects', + filename=os.path.join(path, 'brainobjects.rst'), + header='Brain Objects') + write_module(module_name='brainpy.compat.integrators', + filename=os.path.join(path, 'integrators.rst'), + header='Integrators') + write_module(module_name='brainpy.compat.layers', + filename=os.path.join(path, 'layers.rst'), + header='Layers') + write_module(module_name='brainpy.compat.models', + filename=os.path.join(path, 'models.rst'), + header='Models') + write_module(module_name='brainpy.compat.monitor', + filename=os.path.join(path, 'monitor.rst'), + header='Monitor') + write_module(module_name='brainpy.compat.runners', + filename=os.path.join(path, 'runners.rst'), + header='Runners') + + +def generate_math_compact_docs(path='apis/auto/math_compat/'): + if not os.path.exists(path): + os.makedirs(path) + + write_module(module_name='brainpy.math.compat.optimizers', + filename=os.path.join(path, 'optimizers.rst'), + header='Optimizers') + + write_module(module_name='brainpy.math.compat.losses', + filename=os.path.join(path, 'losses.rst'), + header='Losses') diff --git a/docs/conf.py b/docs/conf.py index bc245596..9b123c3e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -35,6 +35,9 @@ auto_generater.generate_optimizers_docs() auto_generater.generate_measure_docs() auto_generater.generate_datasets_docs() auto_generater.generate_tools_docs() +auto_generater.generate_compact_docs() +auto_generater.generate_math_compact_docs() + import shutil diff --git a/docs/index.rst b/docs/index.rst index 7a0371b2..deccd712 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -7,7 +7,8 @@ high-performance Brain Dynamics Programming (BDP). Among its key ingredients, Br - **JIT compilation** and **automatic differentiation** for class objects. - **Numerical methods** for ordinary differential equations (ODEs), stochastic differential equations (SDEs), - delay differential equations (DDEs), etc. + delay differential equations (DDEs), + fractional differential equations (FDEs), etc. - **Dynamics simulation** tools for various brain objects, like neurons, synapses, networks, soma, dendrites, channels, and even more. - **Dynamics training** tools with various machine learning algorithms, @@ -37,6 +38,7 @@ The code of BrainPy is open-sourced at GitHub: quickstart/installation quickstart/simulation + quickstart/rate_model quickstart/training quickstart/analysis @@ -58,11 +60,13 @@ The code of BrainPy is open-sourced at GitHub: tutorial_toolbox/ode_numerical_solvers tutorial_toolbox/sde_numerical_solvers tutorial_toolbox/dde_numerical_solvers + tutorial_toolbox/fde_numerical_solvers tutorial_toolbox/joint_equations tutorial_toolbox/synaptic_connections tutorial_toolbox/synaptic_weights tutorial_toolbox/optimizers tutorial_toolbox/runners + tutorial_toolbox/inputs tutorial_toolbox/monitors tutorial_toolbox/saving_and_loading @@ -97,6 +101,8 @@ The code of BrainPy is open-sourced at GitHub: apis/auto/measure.rst apis/auto/running.rst apis/tools.rst + apis/compat.rst + apis/math_compat.rst apis/auto/changelog-brainpy.rst apis/auto/changelog-brainpylib.rst diff --git a/docs/quickstart/analysis.ipynb b/docs/quickstart/analysis.ipynb index e3b35012..3c74def7 100644 --- a/docs/quickstart/analysis.ipynb +++ b/docs/quickstart/analysis.ipynb @@ -34,7 +34,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 1, "id": "993ca509", "metadata": {}, "outputs": [], @@ -56,20 +56,24 @@ }, { "cell_type": "markdown", - "source": [ - "Here, we demonstrate how to perform a bifurcation analysis through a one-dimensional neuron model." - ], + "id": "b600c817", "metadata": { - "collapsed": false, "pycharm": { "name": "#%% md\n" } - } + }, + "source": [ + "Here, we demonstrate how to perform a bifurcation analysis through a one-dimensional neuron model." + ] }, { - "cell_type": "code", - "execution_count": null, - "outputs": [], + "cell_type": "markdown", + "id": "59fed6d5", + "metadata": { + "pycharm": { + "name": "#%%\n" + } + }, "source": [ "Let's try to analyze how the external input influences the dynamics of the Exponential Integrate-and-Fire (ExpIF) model. The ExpIF model is a one-variable neuron model whose dynamics is defined by:\n", "\n", @@ -77,13 +81,7 @@ "\\tau {\\dot {V}}= - (V - V_\\mathrm{rest}) + \\Delta_T \\exp(\\frac{V - V_T}{\\Delta_T}) + RI \\\\\n", "\\mathrm{if}\\, \\, V > \\theta, \\quad V \\gets V_\\mathrm{reset}\n", "$$" - ], - "metadata": { - "collapsed": false, - "pycharm": { - "name": "#%%\n" - } - } + ] }, { "cell_type": "markdown", @@ -93,36 +91,28 @@ "We can analyze the change of ${\\dot {V}}$ with respect to $V$. First, let's generate an ExpIF model using pre-defined modules in ``brainpy.dyn``:" ] }, - { - "cell_type": "markdown", - "id": "5d5e66ad", - "metadata": {}, - "source": [ - "expif = bp.dyn.ExpIF(1, delta_T=1.)" - ] - }, { "cell_type": "code", - "execution_count": 7, - "id": "d94df42f", + "execution_count": 2, + "id": "8d6b11cb", "metadata": {}, "outputs": [], "source": [ - "The default value of other parameters can be accessed directly by their names:" + "expif = bp.dyn.ExpIF(1, delta_T=1.)" ] }, { "cell_type": "markdown", - "id": "d7cb929d", + "id": "a818b78c", "metadata": {}, "source": [ - "expif.V_rest, expif.V_T, expif.R, expif.tau" + "The default value of other parameters can be accessed directly by their names:" ] }, { "cell_type": "code", - "execution_count": 8, - "id": "31e1ee06", + "execution_count": 3, + "id": "040b7004", "metadata": {}, "outputs": [ { @@ -131,33 +121,27 @@ "(-65.0, -59.9, 1.0, 10.0)" ] }, - "execution_count": 8, + "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "After defining the model, we can use it for bifurcation analysis." + "expif.V_rest, expif.V_T, expif.R, expif.tau" ] }, { "cell_type": "markdown", - "id": "88acb8ac", + "id": "09f5722a", "metadata": {}, "source": [ - "bif = bp.analysis.Bifurcation1D(\n", - " model=expif,\n", - " target_vars={'V': [-70., -55.]},\n", - " target_pars={'I_ext': [0., 6.]},\n", - " resolutions=0.01\n", - ")\n", - "bif.plot_bifurcation(show=True)" + "After defining the model, we can use it for bifurcation analysis." ] }, { "cell_type": "code", - "execution_count": 12, - "id": "aa6d013a", + "execution_count": 4, + "id": "358060fb", "metadata": {}, "outputs": [ { @@ -180,6 +164,20 @@ "output_type": "display_data" } ], + "source": [ + "bif = bp.analysis.Bifurcation1D(\n", + " model=expif,\n", + " target_vars={'V': [-70., -55.]},\n", + " target_pars={'I_ext': [0., 6.]},\n", + " resolutions=0.01\n", + ")\n", + "bif.plot_bifurcation(show=True)" + ] + }, + { + "cell_type": "markdown", + "id": "4f03e723", + "metadata": {}, "source": [ "In the ``Bifurcation1D`` analyzer, ``model`` refers to the modelto be analyzed (essentially the analyzer will access the derivative function in the model), ``target_vars`` denotes the target variables, ``target_pars`` denotes the changing parameters, and ``resolution`` determines the resolutioin of the analysis." ] @@ -223,46 +221,28 @@ "Users can easily define a FHN model which is also provided by BrainPy:" ] }, - { - "cell_type": "markdown", - "id": "d5d3c97e", - "metadata": {}, - "source": [ - "fhn = bp.dyn.FHN(1)" - ] - }, { "cell_type": "code", - "execution_count": 13, - "id": "7caae875", + "execution_count": 5, + "id": "e6b176c7", "metadata": {}, "outputs": [], "source": [ - "Because there are two variables, $v$ and $w$, in the FHN model, we shall use 2-D phase plane analysis to visualize how these two variables change over time." + "fhn = bp.dyn.FHN(1)" ] }, { "cell_type": "markdown", - "id": "cc02c0ef", + "id": "b13e4ee9", "metadata": {}, "source": [ - "analyzer = bp.analysis.PhasePlane2D(\n", - " model=fhn,\n", - " target_vars={'V': [-3, 3], 'w': [-3., 3.]},\n", - " pars_update={'I_ext': 0.8}, \n", - " resolutions=0.01,\n", - ")\n", - "analyzer.plot_nullcline()\n", - "analyzer.plot_vector_field()\n", - "analyzer.plot_fixed_point()\n", - "analyzer.plot_trajectory({'V': [-2.8], 'w': [-1.8]}, duration=100.)\n", - "analyzer.show_figure()" + "Because there are two variables, $v$ and $w$, in the FHN model, we shall use 2-D phase plane analysis to visualize how these two variables change over time." ] }, { "cell_type": "code", - "execution_count": 11, - "id": "bdbb963c", + "execution_count": 6, + "id": "78078951", "metadata": {}, "outputs": [ { @@ -279,7 +259,7 @@ "\tThere are 866 candidates\n", "I am trying to filter out duplicate fixed points ...\n", "\tFound 1 fixed points.\n", - "\t#1 V=-0.2738719079019268, w=0.5329731347793121 is a unstable node.\n", + "\t#1 V=-0.2738719079879798, w=0.5329731346879486 is a unstable node.\n", "I am plotting the trajectory ...\n" ] }, @@ -296,6 +276,24 @@ "output_type": "display_data" } ], + "source": [ + "analyzer = bp.analysis.PhasePlane2D(\n", + " model=fhn,\n", + " target_vars={'V': [-3, 3], 'w': [-3., 3.]},\n", + " pars_update={'I_ext': 0.8}, \n", + " resolutions=0.01,\n", + ")\n", + "analyzer.plot_nullcline()\n", + "analyzer.plot_vector_field()\n", + "analyzer.plot_fixed_point()\n", + "analyzer.plot_trajectory({'V': [-2.8], 'w': [-1.8]}, duration=100.)\n", + "analyzer.show_figure()" + ] + }, + { + "cell_type": "markdown", + "id": "247760da", + "metadata": {}, "source": [ "In the ``PhasePlane2D`` analyzer, the parameters ``model``, ``target_vars``, and ``resolution`` is the same as those in ``Bifurcation1D``. ``pars_update`` specifies the parameters to be updated during analysis. After defining the analyzer, users can visualize the nullcline, vector field, fixed points and the trajectory in the image. The phase plane gives users intuitive interpretation of the changes of $v$ and $w$ guided by the vector field (violet arrows)." ] @@ -314,42 +312,34 @@ }, { "cell_type": "markdown", - "source": [ - "- For more details about how to perform bifurcation analysis and phase plane analysis, please see the tutorial of [Low-dimensional Analyzers](../tutorial_analysis/lowdim_analysis.ipynb).\n", - "- A good example of phase plane analysis and bifurcation analysis is the decision-making model, please see the tutorial in [Analysis of a Decision-making Model](../tutorial_analysis/decision_making_model.ipynb)\n", - "- If you want to how to analyze the slow points (or fixed points) of your high-dimensional dynamical models, please see the tutorial of [High-dimensional Analyzers](../tutorial_analysis/highdim_analysis.ipynb)" - ], + "id": "315c47ff", "metadata": { - "collapsed": false, "pycharm": { "name": "#%% md\n" } - } + }, + "source": [ + "- For more details about how to perform bifurcation analysis and phase plane analysis, please see the tutorial of [Low-dimensional Analyzers](../tutorial_analysis/lowdim_analysis.ipynb).\n", + "- A good example of phase plane analysis and bifurcation analysis is the decision-making model, please see the tutorial in [Analysis of a Decision-making Model](../tutorial_analysis/decision_making_model.ipynb)\n", + "- If you want to how to analyze the slow points (or fixed points) of your high-dimensional dynamical models, please see the tutorial of [High-dimensional Analyzers](../tutorial_analysis/highdim_analysis.ipynb)" + ] }, { "cell_type": "code", "execution_count": null, - "outputs": [], - "source": [], + "id": "77fc3778", "metadata": { - "collapsed": false, "pycharm": { "name": "#%%\n" } - } - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8955c420", - "metadata": {}, + }, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -363,9 +353,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.7" + "version": "3.8.8" } }, "nbformat": 4, "nbformat_minor": 5 -} \ No newline at end of file +} diff --git a/docs/quickstart/dynamics_intro.ipynb b/docs/quickstart/dynamics_intro.ipynb deleted file mode 100644 index 17b1255e..00000000 --- a/docs/quickstart/dynamics_intro.ipynb +++ /dev/null @@ -1,523 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Dynamics Programming Introduction" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "@[Chaoming Wang](https://github.com/chaoming0625)\n", - "@[Xiaoyu Chen](mailto:c-xy17@tsinghua.org.cn)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "> What I cannot create, I do not understand. --- Richard Feynman" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The brain is a complex dynamical system. To simulate the dynamics of the brain, one of the most important things is to model the dynamically changed states of each component. Mathematically, the dynamics of a system 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", - "Simulation of such dynamical systems is called **dynamic modeling**. BrainPy provides users with various tools and convenient interface for neurodynamic modeling, including **dynamic building**, **dynamic simulation**, **dynamic analysis** and **dynamic training**. This section helps users to get familiar with the basic structure and common operations of neurodynamic modeling 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": [], - "source": [ - "import brainpy as bp\n", - "import brainpy.math as bm\n", - "\n", - "bp.math.set_platform('cpu')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Dynamical System Building" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In BrainPy, [``brainpy.DynamicalSystem``](../apis/auto/building/generated/brainpy.building.brainobjects.DynamicalSystem.rst) is used to define dynamic brain objects. Various children classes are implemented to build different elements, such as [brainpy.NeuGroup](../apis/auto/building/generated/brainpy.building.brainobjects.NeuGroup.rst) for neuron group modeling, [brainpy.TwoEndConn](../apis/auto/building/generated/brainpy.building.brainobjects.TwoEndConn.rst) for synaptic computation, [brainpy.Network](../apis/auto/building/generated/brainpy.building.brainobjects.Network.rst) for network modeling, 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": "markdown", - "metadata": {}, - "source": [ - "According to the definition of a dynamical system, any subclass of ``brainpy.DynamicalSystem`` must implement the updating rule in the *update* function (``def update(self, _t, _dt)``), and dynamically changed variables should be defined in the system." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "class YourDynamicalSystem(bp.DynamicalSystem):\n", - " \n", - " def __init__(self):\n", - " # define dynamically changed variables\n", - " pass\n", - " \n", - " def update(self, _t, _dt):\n", - " # update the variables\n", - " pass" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Here, we illustrate how to build a dynamcial system by using the well known [FitzHugh–Nagumo neuron model](https://brainmodels.readthedocs.io/en/latest/apis/generated/brainmodels.neurons.FHN.html), 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", - "$$" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This model contains two differential equations. In BrainPy, the numerical integration of ordinary differential equations can be accomplished with [brainpy.odeint](../apis/integrators/generated/brainpy.integrators.odeint.rst) (please see [Numerical Integrator](../tutorial_intg/index.rst) for more details). \n", - "\n", - "The above two differential equations as Python functions can be defined as:" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "def dV(V, t, w, Iext=0.): \n", - " return V - V * V * V / 3 - w + Iext\n", - " \n", - "def dw(w, t, V, a=0.7, b=0.8): \n", - " return (V + a - b * w) / self.tau" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "where ``t`` is the time variable, **arguments before ``t`` are variables, and arguments after ``t`` are parameters**.\n", - "\n", - "Thereafter, the numerical solvers for the two equations can be defined as:" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "int_V = bp.odeint(dV, method='euler')\n", - "\n", - "int_w = bp.odeint(dw, method='euler')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "where the ``method`` defines the numerical integration method to use (all implemented methods can be referred to in [Numerical Solvers for ODEs](../tutorial_intg/ode_numerical_solvers.ipynb)). " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The FitzHugh–Nagumo neuron model can be defined as a Python class, in which the parameters, variables, and integral functions are defined in the constructor ``__init__()``, and the updating rule from the current time $\\mathrm{\\_t}$ to the next time $\\mathrm{\\_t + \\_dt}$ can be defined in the update function ``update()``." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "class FitzHughNagumoModel(bp.DynamicalSystem):\n", - " def __init__(self, num, method='exp_auto'):\n", - " super(FitzHughNagumoModel, self).__init__()\n", - "\n", - " # parameters\n", - " self.a = 0.7\n", - " self.b = 0.8\n", - " self.tau = 12.5\n", - "\n", - " # variables\n", - " self.V = bm.Variable(bm.zeros(num))\n", - " self.w = bm.Variable(bm.zeros(num))\n", - " self.Iext = bm.Variable(bm.zeros(num)) # to receive the external input\n", - "\n", - " # functions\n", - " def dV(V, t, w, Iext=0.): \n", - " return V - V * V * V / 3 - w + Iext\n", - " def dw(w, t, V, a=0.7, b=0.8): \n", - " return (V + a - b * w) / self.tau\n", - " self.int_V = bp.odeint(dV, method=method)\n", - " self.int_w = bp.odeint(dw, method=method)\n", - "\n", - " def update(self, _t, _dt):\n", - " self.V.value = self.int_V(self.V, _t, self.w, self.Iext, _dt)\n", - " self.w.value = self.int_w(self.w, _t, self.V, self.a, self.b, _dt)\n", - " self.Iext[:] = 0." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "# instantiation\n", - "fhn = FitzHughNagumoModel(2)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In BrainPy, any dynamical model can be defined as a Python class. More advanced usage of dynamical system building can be obtained in [Dynamics Building](../tutorial_building/index.rst)." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Dynamical System Simulation" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Dynamics simulation in BrainPy is highly efficient. It can deploy models to CPUs or GPUs. To switch the backend device, you can use ``brainpy.math.set_platform(\"cpu\" or \"gpu\")`` at the top of your script. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Runners are used for dynamic simulation. They can [**monitor**](../tutorial_simulation/monitors_and_inputs.ipynb) variable trajectories and give [**inputs**](../tutorial_simulation/monitors_and_inputs.ipynb) to target variables during simulation. Currently, BrainPy provides several [runners](../apis/auto/simulation/runner.rst) to satisfy different simulation requirements. Here, we use ``brainpy.StructRunner`` to run the above instance ``fhn``. During simulation, we monitor variables ``V`` and ``w``, and give inputs to ``Iext`` variable. " - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "dcefc71b42c64080916703e550f1b365", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - " 0%| | 0/1000 [00:00, ?it/s]" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "0.1874241828918457" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "runner = bp.StructRunner(fhn, monitors=['V', 'w'], inputs=['Iext', 1.])\n", - "\n", - "# run 100 time units\n", - "runner.run(100.)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The monitored values of variables over time are recorded in ``runner.mon`` and can be visualized through [brainpy.visualize](../apis/auto/visualization.rst)." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "