pax_global_header00006660000000000000000000000064144520110610014504gustar00rootroot0000000000000052 comment=04c91b824ef6d8c9872e80f8d14154ee815be058 brian2-2.5.4/000077500000000000000000000000001445201106100126715ustar00rootroot00000000000000brian2-2.5.4/.coveragerc000066400000000000000000000012571445201106100150170ustar00rootroot00000000000000# .coveragerc to control coverage.py # following the example at http://nedbatchelder.com/code/coverage/config.html [run] relative_files = True branch = True source_pkgs = brian2 omit = */brian2/tests/* */brian2/sphinxext/* */brian2/hears.py */*.py_ */*.pyx [report] # Regexes for lines to exclude from consideration exclude_lines = # Have to re-enable the standard pragma pragma: no cover # Don't complain if tests don't hit defensive assertion code: raise AssertionError raise NotImplementedError # Don't complain if non-runnable code isn't run: if 0: if __name__ == .__main__.: if __name__==.__main__.: ignore_errors = True brian2-2.5.4/.devcontainer/000077500000000000000000000000001445201106100154305ustar00rootroot00000000000000brian2-2.5.4/.devcontainer/Dockerfile000066400000000000000000000026601445201106100174260ustar00rootroot00000000000000# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.217.4/containers/python-3/.devcontainer/base.Dockerfile # [Choice] Python version (use -bullseye variants on local arm64/Apple Silicon): 3, 3.10, 3.9, 3.8, 3.7, 3.6, 3-bullseye, 3.10-bullseye, 3.9-bullseye, 3.8-bullseye, 3.7-bullseye, 3.6-bullseye, 3-buster, 3.10-buster, 3.9-buster, 3.8-buster, 3.7-buster, 3.6-buster ARG VARIANT="3.10-bullseye" FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT} # [Choice] Node.js version: none, lts/*, 16, 14, 12, 10 ARG NODE_VERSION="none" RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi # [Optional] Uncomment this section to install additional OS packages. RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ && apt-get -y install --no-install-recommends libgsl-dev # [Optional] Uncomment this line to install global node packages. # RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 # [Optional] If your pip requirements rarely change, uncomment this section to add them to the image. # Upgrade pip and install developer requirements COPY .devcontainer/dev-requirements.txt /tmp/pip-tmp/ RUN pip3 install --upgrade pip \ && pip3 --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/dev-requirements.txt \ && rm -rf /tmp/pip-tmp brian2-2.5.4/.devcontainer/README.md000066400000000000000000000051741445201106100167160ustar00rootroot00000000000000VS Code Development Container ============================= Using this development environment requires: * [Docker](https://www.docker.com/get-started) * [VS Code](https://code.visualstudio.com/) * [Remote development extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack) Cloning the Brian repository and opening it in VS Code should result in a prompt to reopen the project in a container. Clicking `Yes` will start the build process. This will take a few minutes for the first time but will be faster on subsequent rebuilds. Once the container is built, there will be another prompt saying that [`pylance`](https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-pylance) has been installed and asking if you wish to reload the container to activate it (click `Yes` to enable Python language support). You now have an isolated development environment (which will not conflict with packages installed elsewhere on your system) with all the dependencies needed for Brian already installed. The container environment can be customised in many ways, such as with [dotfiles](https://code.visualstudio.com/docs/remote/containers#_personalizing-with-dotfile-repositories) if hosted in a public repository. Further documentation for development in containers can be found here: https://code.visualstudio.com/docs/remote/containers. The exact dependency versions used in this container will be saved in `.devcontainer/frozen_dependencies.txt`, which may be useful for debugging. Note, when updating the packages in `.devcontainer/dev-requirements.txt`, the versions specified in `.pre-commit-config.yaml` must also be updated to match in order for the pre-commit hooks to work. Plots can be saved directly to disk e.g. as `png` files for viewing. [JupyterLab](https://jupyterlab.readthedocs.io/en/stable/) is also provided for running code in notebooks and interactive plotting. The backend can be chosen with one of the following "magic" commands: * `%matplotlib inline` - This is the default and will render images as PNGs in the notebook. * `%matplotlib widget` - This generates an ipywidget that renders plots with interactive controls (e.g. resizing, panning and zooming). See the [`README`](https://github.com/matplotlib/ipympl) and these [notes](https://github.com/microsoft/vscode-jupyter/wiki/Using-%25matplotlib-widget-instead-of-%25matplotlib-notebook,tk,etc) on using Jupyter within a VS Code container for more information. Extra [settings](https://github.com/microsoft/vscode-jupyter/wiki/IPyWidget-Support-in-VS-Code-Python) are included to automatically obtain the required supporting files. brian2-2.5.4/.devcontainer/dev-requirements.txt000066400000000000000000000003131445201106100214650ustar00rootroot00000000000000# GSL>=1.16 # Installed in Dockerfile scipy >= 0.13.3 matplotlib >= 2.0 jupyterlab ipympl pre-commit == 3.3.* black == 22.10.0 isort == 5.12.0 pyupgrade == 3.4.0 flake8 == 6.0.0 flake8-bugbear == 23.5.9 brian2-2.5.4/.devcontainer/devcontainer.json000066400000000000000000000053131445201106100210060ustar00rootroot00000000000000// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: // https://github.com/microsoft/vscode-dev-containers/tree/v0.217.4/containers/python-3 { "name": "Python 3", "build": { "dockerfile": "Dockerfile", "context": "..", "args": { // Update 'VARIANT' to pick a Python version: 3, 3.10, 3.9, 3.8, 3.7, 3.6 // Append -bullseye or -buster to pin to an OS version. // Use -bullseye variants on local on arm64/Apple Silicon. "VARIANT": "3.11-bullseye", // Options "NODE_VERSION": "none" } }, // Set *default* container specific settings.json values on container create. "customizations":{ "vscode": { "settings":{ "python.defaultInterpreterPath": "/usr/local/bin/python", "python.linting.enabled": true, "python.linting.flake8Enabled": true, "python.formatting.provider": "black", "python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8", "python.formatting.blackPath": "black", "python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf", "python.linting.banditPath": "/usr/local/py-utils/bin/bandit", "python.linting.flake8Path": "flake8", "python.linting.mypyPath": "/usr/local/py-utils/bin/mypy", "python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle", "python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle", "python.linting.pylintPath": "/usr/local/py-utils/bin/pylint", "jupyter.widgetScriptSources": [ "jsdelivr.com", "unpkg.com" ], "[python]": { "editor.codeActionsOnSave": { "source.organizeImports": true } } }, // Add the IDs of extensions you want installed when the container is created. "extensions": [ "ms-python.python", "ms-python.vscode-pylance", "ms-python.vscode-isort", "ms-azuretools.vscode-docker", "ms-python.flake8" ] } }, // Use 'forwardPorts' to make a list of ports inside the container available locally. // "forwardPorts": [], // Use 'postCreateCommand' to run commands after the container is created. // "postCreateCommand": "pip3 install --user -r requirements.txt", "postCreateCommand": "pre-commit install && pip3 install --user -e '.[test,docs]' && pip3 freeze > .devcontainer/frozen-requirements.txt", // Workaround for git security restrictions and dubious ownership in devcontainers "postStartCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}", // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. "remoteUser": "vscode", // https: //code.visualstudio.com/docs/remote/containers#_dev-container-features-preview "features": { // "git": "latest", "github-cli": "latest" } } brian2-2.5.4/.git-blame-ignore-revs000066400000000000000000000011551445201106100167730ustar00rootroot00000000000000# This file collects all bulk commits, which should not appear when using `git blame`. # To configure git to ignore these commits during `git blame`, set: # # git config blame.ignoreRevsFile .git-blame-ignore-revs # # Unify string formatting and single versus double quotes 9e862585b6f1a2bf286be2484db47cebcd1c938f # Reformatting with black 1e9ea598491444fe7c4ee9ece2ec94ad7c5020ec # Reformatting with isort 67bf6d3760fa3fb8b3aa121b1b972d6cf36ec048 # Update syntax to Python 3.8 with pyupgrade 28b02c51545298cb9a76d8295e64a5df391b9207 # Fix a number of issues flagged by flake8 0344b005e6ffd232f55e69621630ef01876bc0fb brian2-2.5.4/.git_archival.txt000066400000000000000000000002171445201106100161440ustar00rootroot00000000000000node: 04c91b824ef6d8c9872e80f8d14154ee815be058 node-date: 2023-07-07T15:25:05+02:00 describe-name: 2.5.4 ref-names: HEAD -> master, tag: 2.5.4 brian2-2.5.4/.gitattributes000066400000000000000000000013211445201106100155610ustar00rootroot00000000000000# Set the default behavior, in case people don't have core.autocrlf set. * text=auto # These files are text and should be normalized (CRLF --> LF) # Source code *.py text diff=python *.cpp text diff=cpp *.h text diff=cpp *.pyx text *.py_ text *.ipynb text # Configuration files and scripts *.cfg text *.yaml text *.yml text *.bat text *.cmd text *.ps1 text *.sh text *.rc text .coveragerc text .gitignore text MANIFEST.in text # Documentation *.rst text *.txt text # Other text files *.swc text # Binary files that shouldn't be normalized *.png binary *.pptx binary .git_archival.txt export-subst brian2-2.5.4/.github/000077500000000000000000000000001445201106100142315ustar00rootroot00000000000000brian2-2.5.4/.github/workflows/000077500000000000000000000000001445201106100162665ustar00rootroot00000000000000brian2-2.5.4/.github/workflows/publish_to_pypi.yml000066400000000000000000000103731445201106100222260ustar00rootroot00000000000000name: Build and publish to TestPyPI or PyPI on: [push, pull_request] jobs: get_python_versions: name: "Determine Python versions" runs-on: ubuntu-latest outputs: min-python: ${{ steps.nep29.outputs.min-python }} max-python: ${{ steps.nep29.outputs.max-python }} steps: - name: "calculate versions according to NEP29" id: nep29 uses: mstimberg/github-calc-nep29@v0.5 with: token: ${{ secrets.GITHUB_TOKEN }} build: needs: [get_python_versions] name: Build 🎡 on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ windows-latest, macOS-latest ] steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - name: Set up Python 3.x uses: actions/setup-python@v4 with: python-version: '3.x' - name: Build wheels uses: pypa/cibuildwheel@v2.12.1 with: output-dir: dist env: CIBW_PROJECT_REQUIRES_PYTHON: ">=${{ needs.get_python_versions.outputs.min-python }}" CIBW_ARCHS_WINDOWS: auto64 CIBW_ARCHS_MACOS: x86_64 universal2 CIBW_TEST_SKIP: '*_arm64 *_universal2:arm64' CIBW_SKIP: 'pp*' CIBW_TEST_COMMAND: python {project}/dev/continuous-integration/run_simple_test.py CIBW_TEST_REQUIRES: pytest - name: store distribution 📦 uses: actions/upload-artifact@v3 with: name: packages path: dist build-linux: needs: [get_python_versions] name: Build ${{ matrix.arch }} 🎡 and source 📦 on Linux runs-on: ubuntu-latest strategy: fail-fast: false matrix: arch: [ auto64, aarch64 ] steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - name: Set up Python 3.x uses: actions/setup-python@v4 with: python-version: '3.x' - name: Build source tarball run: | python -m pip install --upgrade pip build python -m pip install "cython>=0.29" oldest-supported-numpy "setuptools>=61" "setuptools_scm[toml]>=6.2" python -m build --sdist --config-setting=--formats=gztar --config-setting=--with-cython --config-setting=--fail-on-error if: ${{ matrix.arch == 'auto64' }} - name: Set up QEMU uses: docker/setup-qemu-action@v2 with: platforms: all - name: Build wheels uses: pypa/cibuildwheel@v2.12.1 with: output-dir: dist env: CIBW_PROJECT_REQUIRES_PYTHON: ">=${{ needs.get_python_versions.outputs.min-python }}" CIBW_ARCHS_LINUX: ${{ matrix.arch }} CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014 CIBW_SKIP: 'pp* *-musllinux_aarch64' CIBW_TEST_COMMAND: python {project}/dev/continuous-integration/run_simple_test.py CIBW_TEST_REQUIRES: pytest - name: store distribution 📦 uses: actions/upload-artifact@v3 with: name: packages path: dist deploy_dev: name: Publish development 📦 to TestPyPI runs-on: ubuntu-latest if: github.repository == 'brian-team/brian2' && github.ref == 'refs/heads/master' environment: development_release permissions: id-token: write # IMPORTANT: mandatory for trusted publishing needs: - build - build-linux steps: - name: load distribution 📦 uses: actions/download-artifact@v3 with: name: packages path: dist/ - name: Publish distribution 📦 to Test PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: repository-url: https://test.pypi.org/legacy/ deploy: name: Publish release 📦 to PyPI runs-on: ubuntu-latest if: github.repository == 'brian-team/brian2' && startsWith(github.ref, 'refs/tags') environment: release permissions: id-token: write # IMPORTANT: mandatory for trusted publishing needs: - build - build-linux steps: - name: load distribution 📦 uses: actions/download-artifact@v3 with: name: packages path: dist/ - name: Publish distribution release 📦 to PyPI uses: pypa/gh-action-pypi-publish@release/v1 brian2-2.5.4/.github/workflows/test_latest.yml000066400000000000000000000042321445201106100213450ustar00rootroot00000000000000name: Test against latest dependencies on: schedule: - cron: '25 5 * * SUN' workflow_dispatch: jobs: get_python_versions: name: "Determine Python versions" runs-on: ubuntu-latest outputs: min-python: ${{ steps.nep29.outputs.min-python }} max-python: ${{ steps.nep29.outputs.max-python }} steps: - name: "calculate versions according to NEP29" id: nep29 uses: mstimberg/github-calc-nep29@v0.5 with: token: ${{ secrets.GITHUB_TOKEN }} testing: needs: [get_python_versions] name: "Python ${{ matrix.python-version }} on ${{ matrix.os }} (standalone: ${{ matrix.standalone }}, 32bit: ${{ matrix.float_dtype_32 }})" runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macOS-latest] standalone: [false, true] float_dtype_32: [false, true] python-version: ["${{ needs.get_python_versions.outputs.max-python }}"] defaults: run: shell: bash -l {0} steps: - name: Checkout Repository uses: actions/checkout@v3 with: fetch-depth: 0 - name: Setup Conda and Python uses: conda-incubator/setup-miniconda@v2 with: auto-update-conda: true auto-activate-base: false channels: conda-forge,defaults channel-priority: true activate-environment: 'test_env' python-version: ${{ matrix.python-version }} - name: Install Brian2 and dependencies run: | conda install -n test_env --quiet --yes -c conda-forge gsl pip conda activate test_env python -m pip install --pre pytest cython sympy pyparsing numpy jinja2 scipy sphinx python -m pip install . - name: Run Tests run: | cd $GITHUB_WORKSPACE/.. # move out of the workspace to avoid direct import python -Wd $GITHUB_WORKSPACE/dev/continuous-integration/run_test_suite.py env: DEPRECATION_ERROR: true AGENT_OS: ${{runner.os}} STANDALONE: ${{ matrix.standalone }} FLOAT_DTYPE_32: ${{ matrix.float_dtype_32 }} brian2-2.5.4/.github/workflows/testsuite.yml000066400000000000000000000114051445201106100210430ustar00rootroot00000000000000name: TestSuite on: [push, pull_request] jobs: get_python_versions: name: "Determine Python versions" runs-on: ubuntu-latest outputs: min-python: ${{ steps.nep29.outputs.min-python }} max-python: ${{ steps.nep29.outputs.max-python }} steps: - name: "calculate versions according to NEP29" id: nep29 uses: mstimberg/github-calc-nep29@v0.5 with: token: ${{ secrets.GITHUB_TOKEN }} pre-commit: name: Run linters with pre-commit runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: '3.10' cache: 'pip' cache-dependency-path: .devcontainer/dev-requirements.txt - name: Install deps run: pip3 install -r .devcontainer/dev-requirements.txt - name: Run pre-commit hooks run: pre-commit run --all-files --show-diff-on-failure testing: needs: [get_python_versions, pre-commit] name: "Python ${{ matrix.python-version }} on ${{ matrix.os }} (standalone: ${{ matrix.standalone }}, 32bit: ${{ matrix.float_dtype_32 }})" runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-20.04, windows-2019, macOS-11] standalone: [false, true] float_dtype_32: [false, true] python-version: ["${{ needs.get_python_versions.outputs.max-python }}"] include: - os: ubuntu-20.04 standalone: false python-version: "${{ needs.get_python_versions.outputs.min-python }}" float_dtype_32: false - os: ubuntu-20.04 standalone: true python-version: "${{ needs.get_python_versions.outputs.min-python }}" float_dtype_32: false defaults: run: shell: bash -l {0} steps: - name: Checkout Repository uses: actions/checkout@v3 with: fetch-depth: 0 - name: Setup Conda and Python uses: conda-incubator/setup-miniconda@v2 with: auto-update-conda: true auto-activate-base: false channels: conda-forge,defaults channel-priority: true activate-environment: 'test_env' python-version: ${{ matrix.python-version }} - name: Install Brian2 and dependencies run: | conda install -n test_env --quiet --yes -c conda-forge pip pytest cython sympy future pyparsing numpy jinja2 six scipy sphinx gsl coverage pip install . - name: Run Tests run: | cd $GITHUB_WORKSPACE/.. # move out of the workspace to avoid direct import coverage run --rcfile=$GITHUB_WORKSPACE/.coveragerc $GITHUB_WORKSPACE/$SCRIPT_NAME env: SCRIPT_NAME: dev/continuous-integration/run_test_suite.py SPHINX_DIR: ${{ github.workspace }}/docs_sphinx AGENT_OS: ${{runner.os}} STANDALONE: ${{ matrix.standalone }} FLOAT_DTYPE_32: ${{ matrix.float_dtype_32 }} - name: Upload coverage data to coveralls if: ${{ startsWith(matrix.os, 'ubuntu-') && matrix.python-version == needs.get_python_versions.outputs.max-python }} run: | cp $GITHUB_WORKSPACE/../.coverage . python -m pip install --upgrade coveralls coveralls --service=github --rcfile=$GITHUB_WORKSPACE/.coveragerc env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} COVERALLS_FLAG_NAME: ${{ github.job }} COVERALLS_PARALLEL: true coveralls: name: Indicate completion to coveralls.io needs: testing runs-on: ubuntu-latest container: python:3-slim steps: - name: Finished run: | python -m pip install --upgrade coveralls coveralls --service=github --finish --rcfile=$GITHUB_WORKSPACE/.coveragerc env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} test_doc_build: needs: get_python_versions name: Test building the documentation runs-on: ubuntu-latest defaults: run: shell: bash -l {0} steps: - name: Checkout Repository uses: actions/checkout@v3 with: fetch-depth: 0 submodules: true - name: Setup Conda and Python uses: conda-incubator/setup-miniconda@v2 with: auto-update-conda: true auto-activate-base: false channels: conda-forge,defaults channel-priority: true activate-environment: 'test_env' python-version: "${{ needs.get_python_versions.outputs.max-python }}" - name: Install dependencies run: pip install -r rtd-requirements.txt - name: Install brian2 run: pip install . - name: Build HTML documentation run: | cd docs_sphinx sphinx-build -b html . ../docs env: READTHEDOCS: True brian2-2.5.4/.gitignore000066400000000000000000000012361445201106100146630ustar00rootroot00000000000000brian2/_version.py /dev/ideas/randomkit/CUBA /brian2/random/randomkit/randkit.c /dev/benchmarks/compare_to_brian1.png # Compiled Python files *.py[cod] # Backup files *~ *.bak # C extensions *.so # generated directories dist build .eggs/ /docs docs_sphinx/_build/ docs_sphinx/reference output/ # Unit test / coverage reports .coverage # Test cache .pytest_cache # Eclipse project files and settings .project .pydevproject /.settings # pycharm project files .idea /Brian2.egg-info # VisualStudio Code .vscode/ # VS Code devcontainer metadata .devcontainer/frozen-requirements.txt # vim files *.swp .rope* # IPython .ipynb_checkpoints # System files .DS_Store brian2-2.5.4/.gitmodules000066400000000000000000000002001445201106100150360ustar00rootroot00000000000000[submodule "docs_sphinx/resources"] path = docs_sphinx/resources url = https://github.com/brian-team/brian2-doc-resources.git brian2-2.5.4/.pre-commit-config.yaml000066400000000000000000000014711445201106100171550ustar00rootroot00000000000000repos: - repo: meta hooks: - id: check-hooks-apply - id: check-useless-excludes - repo: https://github.com/asottile/pyupgrade rev: v3.4.0 hooks: - id: pyupgrade args: [--py39-plus] files: '^brian2/.*\.pyi?$' - repo: https://github.com/pycqa/flake8 rev: 6.0.0 hooks: - id: flake8 # We exclude all __init__.py files, since we use wildcard imports, etc. exclude: '^brian2/tests/.*$|^brian2/.*__init__[.]py$' files: '^brian2/.*\.pyi?$' additional_dependencies: ['flake8-bugbear==23.5.9'] - repo: https://github.com/pycqa/isort rev: 5.12.0 hooks: - id: isort files: '^brian2/.*\.pyi?$' - repo: https://github.com/psf/black rev: '22.10.0' hooks: - id: black files: '^brian2/.*\.pyi?$' brian2-2.5.4/.readthedocs.yaml000066400000000000000000000003531445201106100161210ustar00rootroot00000000000000version: 2 submodules: include: all build: os: "ubuntu-20.04" tools: python: "3" # latest stable CPython jobs: post_install: - python -m pip install . - python -m pip install -r rtd-requirements.txt brian2-2.5.4/AUTHORS000066400000000000000000000031251445201106100137420ustar00rootroot00000000000000Brian 2 is primarily developed by Marcel Stimberg, Dan Goodman, and Romain Brette. Full list of code and documentation contributors, ordered by the time of their first commit. See the release notes for acknowledgements of further contributions in the form of bug reports, testing, suggestions, etc. Dan Goodman (@thesamovar) Marcel Stimberg (@mstimberg) Romain Brette (@romainbrette) Cyrille Rossant (@rossant) Victor Benichoux (@victorbenichoux) Pierre Yger (@yger) Werner Beroux (@wernight) Konrad Wartke (@Kwartke) Daniel Bliss (@dabliss) Jan-Hendrik Schleimer (@ttxtea) Moritz Augustin (@moritzaugustin) Romain Cazé (@rcaze) Dominik Krzemiński (@dokato) Martino Sorbaro (@martinosorb) Benjamin Evans (@bdevans) Meng Dong (@whenov) Alex Seeholzer (@flinz) Daan Sprenkels (@dsprenkels) Edward Betts (@EdwardBetts) Thomas McColgan (@phreeza) Charlee Fletterman (@CharleeSF) Mihir Vaidya (@MihirVaidya94) Teo Stocco (@zifeo) Dylan Richard Muir (@DylanMuir) Adrien F. Vincent (@afvincent) Kapil Kumar (@kapilkd13) Matthieu Recugnat (@matrec4) Paul Brodersen (@paulbrodersen) Guillaume Dumas (@deep-introspection) Aleksandra Teska (@alTeska) Vigneswaran Chandrasekaran (@Vigneswaran-Chandrasekaran) Rahul Kumar Gupta (@rahuliitg) Rike-Benjamin Schuppner (@Debilski) Denis Alevi (@denisalevi) Dominik Spicher (@dspicher) Felix B. Kern (@kernfel) Jan Marker (@jangmarker) Rohith Varma Buddaraju (@rohithvarma3000) Kyle Johnsen (@kjohnsen) Leonardo Schwarz (@leoschwarz) Abolfazl Ziaeemehr (@Ziaeemehr) Étienne Mollier (@emollier) Sebastian Schmitt (@schmitts) Felix C. Stegerman (@obfusk) Oleksii Leonov (@aleksejleonov)brian2-2.5.4/CITATION.cff000066400000000000000000000031141445201106100145620ustar00rootroot00000000000000cff-version: 1.2.0 message: If you use this software, please cite it using these metadata. title: Brian simulator abstract: A clock-driven simulator for spiking neural networks authors: - family-names: Stimberg given-names: Marcel orcid: "https://orcid.org/0000-0002-2648-4790" - family-names: Goodman given-names: Dan F. M. orcid: "https://orcid.org/0000-0003-1007-6474" - family-names: Brette given-names: Romain orcid: "https://orcid.org/0000-0003-0110-1623" - name: "Brian contributors" version: 2.5.3 date-released: "2023-06-29" identifiers: - description: This is the collection of archived snapshots of all versions of Brian 2 type: doi value: "10.5281/zenodo.654861" - description: This is the archived snapshot of version 2.5.3 of Brian 2 type: doi value: "10.5281/zenodo.8099373" - description: Software heritage identifier for version 2.5.3 type: swh value: "swh:1:rel:e4d609023553bfbd62bf57960dd6f9d342a1d0e8" license: CECILL-2.1 repository-code: "https://github.com/brian-team/brian2" preferred-citation: authors: - family-names: Stimberg given-names: Marcel orcid: "https://orcid.org/0000-0002-2648-4790" - family-names: Goodman given-names: Dan F. M. orcid: "https://orcid.org/0000-0003-1007-6474" - family-names: Brette given-names: Romain orcid: "https://orcid.org/0000-0003-0110-1623" title: "Brian 2, an intuitive and efficient neural simulator" journal: eLife month: 8 year: 2019 volume: 8 doi: "10.7554/eLife.47314" type: article brian2-2.5.4/CITATION.md000066400000000000000000000004211445201106100144220ustar00rootroot00000000000000If you use Brian for your published research, we kindly ask you to cite our article: Stimberg, M, Brette, R, Goodman, DFM. “Brian 2, an Intuitive and Efficient Neural Simulator.” eLife 8 (2019): e47314. doi: [10.7554/eLife.47314](https://doi.org/10.7554/eLife.47314). brian2-2.5.4/CODE_OF_CONDUCT.md000066400000000000000000000064331445201106100154760ustar00rootroot00000000000000# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at team@briansimulator.org. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq brian2-2.5.4/LICENSE000066400000000000000000000761361445201106100137130ustar00rootroot00000000000000License for Brian2 ------------------ Copyright ENS, INRIA, CNRS Contributors: The Brian team: http://briansimulator.org/team/ Brian2 is a computer program whose purpose is to simulate models of biological neural networks that is released under the terms of the CeCILL 2.1 license. CeCILL FREE SOFTWARE LICENSE AGREEMENT Version 2.1 dated 2013-06-21 Notice This Agreement is a Free Software license agreement that is the result of discussions between its authors in order to ensure compliance with the two main principles guiding its drafting: * firstly, compliance with the principles governing the distribution of Free Software: access to source code, broad rights granted to users, * secondly, the election of a governing law, French law, with which it is conformant, both as regards the law of torts and intellectual property law, and the protection that it offers to both authors and holders of the economic rights over software. The authors of the CeCILL (for Ce[a] C[nrs] I[nria] L[ogiciel] L[ibre]) license are: Commissariat à l'énergie atomique et aux énergies alternatives - CEA, a public scientific, technical and industrial research establishment, having its principal place of business at 25 rue Leblanc, immeuble Le Ponant D, 75015 Paris, France. Centre National de la Recherche Scientifique - CNRS, a public scientific and technological establishment, having its principal place of business at 3 rue Michel-Ange, 75794 Paris cedex 16, France. Institut National de Recherche en Informatique et en Automatique - Inria, a public scientific and technological establishment, having its principal place of business at Domaine de Voluceau, Rocquencourt, BP 105, 78153 Le Chesnay cedex, France. Preamble The purpose of this Free Software license agreement is to grant users the right to modify and redistribute the software governed by this license within the framework of an open source distribution model. The exercising of this right is conditional upon certain obligations for users so as to preserve this status for all subsequent redistributions. In consideration of access to the source code and the rights to copy, modify and redistribute granted by the license, users are provided only with a limited warranty and the software's author, the holder of the economic rights, and the successive licensors only have limited liability. In this respect, the risks associated with loading, using, modifying and/or developing or reproducing the software by the user are brought to the user's attention, given its Free Software status, which may make it complicated to use, with the result that its use is reserved for developers and experienced professionals having in-depth computer knowledge. Users are therefore encouraged to load and test the suitability of the software as regards their requirements in conditions enabling the security of their systems and/or data to be ensured and, more generally, to use and operate it in the same conditions of security. This Agreement may be freely reproduced and published, provided it is not altered, and that no provisions are either added or removed herefrom. This Agreement may apply to any or all software for which the holder of the economic rights decides to submit the use thereof to its provisions. Frequently asked questions can be found on the official website of the CeCILL licenses family (http://www.cecill.info/index.en.html) for any necessary clarification. Article 1 - DEFINITIONS For the purpose of this Agreement, when the following expressions commence with a capital letter, they shall have the following meaning: Agreement: means this license agreement, and its possible subsequent versions and annexes. Software: means the software in its Object Code and/or Source Code form and, where applicable, its documentation, "as is" when the Licensee accepts the Agreement. Initial Software: means the Software in its Source Code and possibly its Object Code form and, where applicable, its documentation, "as is" when it is first distributed under the terms and conditions of the Agreement. Modified Software: means the Software modified by at least one Contribution. Source Code: means all the Software's instructions and program lines to which access is required so as to modify the Software. Object Code: means the binary files originating from the compilation of the Source Code. Holder: means the holder(s) of the economic rights over the Initial Software. Licensee: means the Software user(s) having accepted the Agreement. Contributor: means a Licensee having made at least one Contribution. Licensor: means the Holder, or any other individual or legal entity, who distributes the Software under the Agreement. Contribution: means any or all modifications, corrections, translations, adaptations and/or new functions integrated into the Software by any or all Contributors, as well as any or all Internal Modules. Module: means a set of sources files including their documentation that enables supplementary functions or services in addition to those offered by the Software. External Module: means any or all Modules, not derived from the Software, so that this Module and the Software run in separate address spaces, with one calling the other when they are run. Internal Module: means any or all Module, connected to the Software so that they both execute in the same address space. GNU GPL: means the GNU General Public License version 2 or any subsequent version, as published by the Free Software Foundation Inc. GNU Affero GPL: means the GNU Affero General Public License version 3 or any subsequent version, as published by the Free Software Foundation Inc. EUPL: means the European Union Public License version 1.1 or any subsequent version, as published by the European Commission. Parties: mean both the Licensee and the Licensor. These expressions may be used both in singular and plural form. Article 2 - PURPOSE The purpose of the Agreement is the grant by the Licensor to the Licensee of a non-exclusive, transferable and worldwide license for the Software as set forth in Article 5 <#scope> hereinafter for the whole term of the protection granted by the rights over said Software. Article 3 - ACCEPTANCE 3.1 The Licensee shall be deemed as having accepted the terms and conditions of this Agreement upon the occurrence of the first of the following events: * (i) loading the Software by any or all means, notably, by downloading from a remote server, or by loading from a physical medium; * (ii) the first time the Licensee exercises any of the rights granted hereunder. 3.2 One copy of the Agreement, containing a notice relating to the characteristics of the Software, to the limited warranty, and to the fact that its use is restricted to experienced users has been provided to the Licensee prior to its acceptance as set forth in Article 3.1 <#accepting> hereinabove, and the Licensee hereby acknowledges that it has read and understood it. Article 4 - EFFECTIVE DATE AND TERM 4.1 EFFECTIVE DATE The Agreement shall become effective on the date when it is accepted by the Licensee as set forth in Article 3.1 <#accepting>. 4.2 TERM The Agreement shall remain in force for the entire legal term of protection of the economic rights over the Software. Article 5 - SCOPE OF RIGHTS GRANTED The Licensor hereby grants to the Licensee, who accepts, the following rights over the Software for any or all use, and for the term of the Agreement, on the basis of the terms and conditions set forth hereinafter. Besides, if the Licensor owns or comes to own one or more patents protecting all or part of the functions of the Software or of its components, the Licensor undertakes not to enforce the rights granted by these patents against successive Licensees using, exploiting or modifying the Software. If these patents are transferred, the Licensor undertakes to have the transferees subscribe to the obligations set forth in this paragraph. 5.1 RIGHT OF USE The Licensee is authorized to use the Software, without any limitation as to its fields of application, with it being hereinafter specified that this comprises: 1. permanent or temporary reproduction of all or part of the Software by any or all means and in any or all form. 2. loading, displaying, running, or storing the Software on any or all medium. 3. entitlement to observe, study or test its operation so as to determine the ideas and principles behind any or all constituent elements of said Software. This shall apply when the Licensee carries out any or all loading, displaying, running, transmission or storage operation as regards the Software, that it is entitled to carry out hereunder. 5.2 ENTITLEMENT TO MAKE CONTRIBUTIONS The right to make Contributions includes the right to translate, adapt, arrange, or make any or all modifications to the Software, and the right to reproduce the resulting software. The Licensee is authorized to make any or all Contributions to the Software provided that it includes an explicit notice that it is the author of said Contribution and indicates the date of the creation thereof. 5.3 RIGHT OF DISTRIBUTION In particular, the right of distribution includes the right to publish, transmit and communicate the Software to the general public on any or all medium, and by any or all means, and the right to market, either in consideration of a fee, or free of charge, one or more copies of the Software by any means. The Licensee is further authorized to distribute copies of the modified or unmodified Software to third parties according to the terms and conditions set forth hereinafter. 5.3.1 DISTRIBUTION OF SOFTWARE WITHOUT MODIFICATION The Licensee is authorized to distribute true copies of the Software in Source Code or Object Code form, provided that said distribution complies with all the provisions of the Agreement and is accompanied by: 1. a copy of the Agreement, 2. a notice relating to the limitation of both the Licensor's warranty and liability as set forth in Articles 8 and 9, and that, in the event that only the Object Code of the Software is redistributed, the Licensee allows effective access to the full Source Code of the Software for a period of at least three years from the distribution of the Software, it being understood that the additional acquisition cost of the Source Code shall not exceed the cost of the data transfer. 5.3.2 DISTRIBUTION OF MODIFIED SOFTWARE When the Licensee makes a Contribution to the Software, the terms and conditions for the distribution of the resulting Modified Software become subject to all the provisions of this Agreement. The Licensee is authorized to distribute the Modified Software, in source code or object code form, provided that said distribution complies with all the provisions of the Agreement and is accompanied by: 1. a copy of the Agreement, 2. a notice relating to the limitation of both the Licensor's warranty and liability as set forth in Articles 8 and 9, and, in the event that only the object code of the Modified Software is redistributed, 3. a note stating the conditions of effective access to the full source code of the Modified Software for a period of at least three years from the distribution of the Modified Software, it being understood that the additional acquisition cost of the source code shall not exceed the cost of the data transfer. 5.3.3 DISTRIBUTION OF EXTERNAL MODULES When the Licensee has developed an External Module, the terms and conditions of this Agreement do not apply to said External Module, that may be distributed under a separate license agreement. 5.3.4 COMPATIBILITY WITH OTHER LICENSES The Licensee can include a code that is subject to the provisions of one of the versions of the GNU GPL, GNU Affero GPL and/or EUPL in the Modified or unmodified Software, and distribute that entire code under the terms of the same version of the GNU GPL, GNU Affero GPL and/or EUPL. The Licensee can include the Modified or unmodified Software in a code that is subject to the provisions of one of the versions of the GNU GPL, GNU Affero GPL and/or EUPL and distribute that entire code under the terms of the same version of the GNU GPL, GNU Affero GPL and/or EUPL. Article 6 - INTELLECTUAL PROPERTY 6.1 OVER THE INITIAL SOFTWARE The Holder owns the economic rights over the Initial Software. Any or all use of the Initial Software is subject to compliance with the terms and conditions under which the Holder has elected to distribute its work and no one shall be entitled to modify the terms and conditions for the distribution of said Initial Software. The Holder undertakes that the Initial Software will remain ruled at least by this Agreement, for the duration set forth in Article 4.2 <#term>. 6.2 OVER THE CONTRIBUTIONS The Licensee who develops a Contribution is the owner of the intellectual property rights over this Contribution as defined by applicable law. 6.3 OVER THE EXTERNAL MODULES The Licensee who develops an External Module is the owner of the intellectual property rights over this External Module as defined by applicable law and is free to choose the type of agreement that shall govern its distribution. 6.4 JOINT PROVISIONS The Licensee expressly undertakes: 1. not to remove, or modify, in any manner, the intellectual property notices attached to the Software; 2. to reproduce said notices, in an identical manner, in the copies of the Software modified or not. The Licensee undertakes not to directly or indirectly infringe the intellectual property rights on the Software of the Holder and/or Contributors, and to take, where applicable, vis-à-vis its staff, any and all measures required to ensure respect of said intellectual property rights of the Holder and/or Contributors. Article 7 - RELATED SERVICES 7.1 Under no circumstances shall the Agreement oblige the Licensor to provide technical assistance or maintenance services for the Software. However, the Licensor is entitled to offer this type of services. The terms and conditions of such technical assistance, and/or such maintenance, shall be set forth in a separate instrument. Only the Licensor offering said maintenance and/or technical assistance services shall incur liability therefor. 7.2 Similarly, any Licensor is entitled to offer to its licensees, under its sole responsibility, a warranty, that shall only be binding upon itself, for the redistribution of the Software and/or the Modified Software, under terms and conditions that it is free to decide. Said warranty, and the financial terms and conditions of its application, shall be subject of a separate instrument executed between the Licensor and the Licensee. Article 8 - LIABILITY 8.1 Subject to the provisions of Article 8.2, the Licensee shall be entitled to claim compensation for any direct loss it may have suffered from the Software as a result of a fault on the part of the relevant Licensor, subject to providing evidence thereof. 8.2 The Licensor's liability is limited to the commitments made under this Agreement and shall not be incurred as a result of in particular: (i) loss due the Licensee's total or partial failure to fulfill its obligations, (ii) direct or consequential loss that is suffered by the Licensee due to the use or performance of the Software, and (iii) more generally, any consequential loss. In particular the Parties expressly agree that any or all pecuniary or business loss (i.e. loss of data, loss of profits, operating loss, loss of customers or orders, opportunity cost, any disturbance to business activities) or any or all legal proceedings instituted against the Licensee by a third party, shall constitute consequential loss and shall not provide entitlement to any or all compensation from the Licensor. Article 9 - WARRANTY 9.1 The Licensee acknowledges that the scientific and technical state-of-the-art when the Software was distributed did not enable all possible uses to be tested and verified, nor for the presence of possible defects to be detected. In this respect, the Licensee's attention has been drawn to the risks associated with loading, using, modifying and/or developing and reproducing the Software which are reserved for experienced users. The Licensee shall be responsible for verifying, by any or all means, the suitability of the product for its requirements, its good working order, and for ensuring that it shall not cause damage to either persons or properties. 9.2 The Licensor hereby represents, in good faith, that it is entitled to grant all the rights over the Software (including in particular the rights set forth in Article 5 <#scope>). 9.3 The Licensee acknowledges that the Software is supplied "as is" by the Licensor without any other express or tacit warranty, other than that provided for in Article 9.2 <#good-faith> and, in particular, without any warranty as to its commercial value, its secured, safe, innovative or relevant nature. Specifically, the Licensor does not warrant that the Software is free from any error, that it will operate without interruption, that it will be compatible with the Licensee's own equipment and software configuration, nor that it will meet the Licensee's requirements. 9.4 The Licensor does not either expressly or tacitly warrant that the Software does not infringe any third party intellectual property right relating to a patent, software or any other property right. Therefore, the Licensor disclaims any and all liability towards the Licensee arising out of any or all proceedings for infringement that may be instituted in respect of the use, modification and redistribution of the Software. Nevertheless, should such proceedings be instituted against the Licensee, the Licensor shall provide it with technical and legal expertise for its defense. Such technical and legal expertise shall be decided on a case-by-case basis between the relevant Licensor and the Licensee pursuant to a memorandum of understanding. The Licensor disclaims any and all liability as regards the Licensee's use of the name of the Software. No warranty is given as regards the existence of prior rights over the name of the Software or as regards the existence of a trademark. Article 10 - TERMINATION 10.1 In the event of a breach by the Licensee of its obligations hereunder, the Licensor may automatically terminate this Agreement thirty (30) days after notice has been sent to the Licensee and has remained ineffective. 10.2 A Licensee whose Agreement is terminated shall no longer be authorized to use, modify or distribute the Software. However, any licenses that it may have granted prior to termination of the Agreement shall remain valid subject to their having been granted in compliance with the terms and conditions hereof. Article 11 - MISCELLANEOUS 11.1 EXCUSABLE EVENTS Neither Party shall be liable for any or all delay, or failure to perform the Agreement, that may be attributable to an event of force majeure, an act of God or an outside cause, such as defective functioning or interruptions of the electricity or telecommunications networks, network paralysis following a virus attack, intervention by government authorities, natural disasters, water damage, earthquakes, fire, explosions, strikes and labor unrest, war, etc. 11.2 Any failure by either Party, on one or more occasions, to invoke one or more of the provisions hereof, shall under no circumstances be interpreted as being a waiver by the interested Party of its right to invoke said provision(s) subsequently. 11.3 The Agreement cancels and replaces any or all previous agreements, whether written or oral, between the Parties and having the same purpose, and constitutes the entirety of the agreement between said Parties concerning said purpose. No supplement or modification to the terms and conditions hereof shall be effective as between the Parties unless it is made in writing and signed by their duly authorized representatives. 11.4 In the event that one or more of the provisions hereof were to conflict with a current or future applicable act or legislative text, said act or legislative text shall prevail, and the Parties shall make the necessary amendments so as to comply with said act or legislative text. All other provisions shall remain effective. Similarly, invalidity of a provision of the Agreement, for any reason whatsoever, shall not cause the Agreement as a whole to be invalid. 11.5 LANGUAGE The Agreement is drafted in both French and English and both versions are deemed authentic. Article 12 - NEW VERSIONS OF THE AGREEMENT 12.1 Any person is authorized to duplicate and distribute copies of this Agreement. 12.2 So as to ensure coherence, the wording of this Agreement is protected and may only be modified by the authors of the License, who reserve the right to periodically publish updates or new versions of the Agreement, each with a separate number. These subsequent versions may address new issues encountered by Free Software. 12.3 Any Software distributed under a given version of the Agreement may only be subsequently distributed under the same version of the Agreement or a subsequent version, subject to the provisions of Article 5.3.4 <#compatibility>. Article 13 - GOVERNING LAW AND JURISDICTION 13.1 The Agreement is governed by French law. The Parties agree to endeavor to seek an amicable solution to any disagreements or disputes that may arise during the performance of the Agreement. 13.2 Failing an amicable solution within two (2) months as from their occurrence, and unless emergency proceedings are necessary, the disagreements or disputes shall be referred to the Paris Courts having jurisdiction, by the more diligent Party. Licenses for incorporated software ---------------------------------- The code in brian2.sphinxext.generate_reference.py is based on sphinx.apidoc (http://sphinx-doc.org, https://bitbucket.org/birkenfeld/sphinx/) available under the BSD license: -------------------------------------------------------------------------------- Copyright (c) 2007-2013 by the Sphinx team (see AUTHORS file). All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- The code in the brian2.sphinxext package is based on the files numpydoc.py, docscrape.py, docscrape_sphinx.py, part of numpydoc (https://github.com/numpy/numpy/tree/master/doc/sphinxext), available under the BSD license: -------------------------------------------------------------------------------- Copyright (C) 2008 Stefan van der Walt , Pauli Virtanen Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ------------------------------------------------------------------------------- The implementations for binomial (brian2.input.binomial) and Poisson distributed random numbers (in brian2.codegen.generators.cython_generator and brian2.codegen.generators.cpp_generator) are based on the respective implementations in the numpy library (https://github.com/numpy/numpy), available under the BSD license: Copyright (c) 2005-2019, NumPy Developers. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the NumPy Developers nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ------------------------------------------------------------------------------- The spelling corrector code in brian2.utils.stringtools is based on the spelling corrector by Peter Norvig, available at: http://norvig.com/spell.py It is published under the MIT license: The MIT License (MIT) Copyright (c) 2007 Peter Norvig Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------- The Mersenne-Twister implementation "Randomkit" in brian2.random.randomkit is based on code by Jean-Sebastien Roy, provided under the MIT license: Copyright (c) 2003-2005, Jean-Sebastien Roy (js@jeannot.org) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -- The rk_random and rk_seed functions algorithms and the original design of the Mersenne Twister RNG: Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura, All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The names of its contributors may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -- Original algorithm for the implementation of rk_interval function from Richard J. Wagner's implementation of the Mersenne Twister RNG, optimised by Magnus Jonsson. -- Constants used in the rk_double implementation by Isaku Wada. brian2-2.5.4/MANIFEST.in000066400000000000000000000010611445201106100144250ustar00rootroot00000000000000# Include documentation include docs_sphinx/conf.py include docs_sphinx/_static/brian-logo.png recursive-include docs_sphinx *.rst prune docs_sphinx/reference prune docs_sphinx/examples prune docs_sphinx/resources # Include examples (but not tutorials) recursive-include examples *.py prune tutorials # Remove development scripts prune dev # Include license file in source tarball include LICENSE # Exclude configuration files global-exclude .gitignore exclude *.yml exclude .coveragerc exclude .gitattributes exclude .gitmodules include brian2/_version.py brian2-2.5.4/README.rst000066400000000000000000000107051445201106100143630ustar00rootroot00000000000000Brian2 ====== *A clock-driven simulator for spiking neural networks* Brian is a free, open source simulator for spiking neural networks. It is written in the Python programming language and is available on almost all platforms. We believe that a simulator should not only save the time of processors, but also the time of scientists. Brian is therefore designed to be easy to learn and use, highly flexible and easily extensible. Please report issues at the github issue tracker (https://github.com/brian-team/brian2/issues) or in the Brian forum (https://brian.discourse.group). Documentation for Brian2 can be found at http://brian2.readthedocs.org Brian2 is released under the terms of the `CeCILL 2.1 license `_. If you use Brian for your published research, we kindly ask you to cite our article: Stimberg, M, Brette, R, Goodman, DFM. “Brian 2, an Intuitive and Efficient Neural Simulator.” eLife 8 (2019): e47314. `doi: 10.7554/eLife.47314 `_. .. image:: https://img.shields.io/pypi/v/Brian2.svg :target: https://pypi.python.org/pypi/Brian2 .. image:: https://img.shields.io/conda/vn/conda-forge/brian2.svg :target: https://anaconda.org/conda-forge/brian2 .. image:: https://img.shields.io/debian/v/python3-brian/testing :alt: Debian package :target: https://packages.debian.org/testing/python3-brian .. image:: https://img.shields.io/fedora/v/python3-brian2 :alt: Fedora package :target: https://packages.fedoraproject.org/pkgs/python-brian2/python3-brian2/ .. image:: https://img.shields.io/spack/v/py-brian2 :alt: Spack :target: https://spack.readthedocs.io/en/latest/package_list.html#py-brian2 .. image:: https://img.shields.io/aur/version/python-brian2 :alt: AUR version :target: https://aur.archlinux.org/packages/python-brian2 .. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.8099373.svg :target: https://doi.org/10.5281/zenodo.8099373 .. image:: https://archive.softwareheritage.org/badge/origin/https://github.com/brian-team/brian2/ :target: https://archive.softwareheritage.org/browse/origin/?origin_url=https://github.com/brian-team/brian2 .. image:: https://archive.softwareheritage.org/badge/swh:1:rel:e4d609023553bfbd62bf57960dd6f9d342a1d0e8/ :target: https://archive.softwareheritage.org/swh:1:rel:e4d609023553bfbd62bf57960dd6f9d342a1d0e8;origin=https://github.com/brian-team/brian2;visit=swh:1:snp:becb452d988ab8ea1386d27f3ad4c2221ec0ae7b .. image:: https://img.shields.io/badge/Contributor%20Covenant-v1.4%20adopted-ff69b4.svg :target: CODE_OF_CONDUCT.md :alt: Contributor Covenant .. image:: https://img.shields.io/discourse/topics?server=https%3A%2F%2Fbrian.discourse.group :target: https://brian.discourse.group :alt: Discourse topics .. image:: https://badges.gitter.im/Join%20Chat.svg :alt: Join the chat at https://gitter.im/brian-team/brian2 :target: https://gitter.im/brian-team/brian2?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge .. image:: https://img.shields.io/badge/code%20style-black-000000.svg :alt: code style: black :target: https://github.com/psf/black Quickstart ---------- Try out Brian on the `mybinder `_ service: .. image:: http://mybinder.org/badge.svg :target: http://mybinder.org/v2/gh/brian-team/brian2-binder/master?filepath=index.ipynb Dependencies ------------ The following packages need to be installed to use Brian 2 (cf. `setup.py `_): * Python >= 3.7 * NumPy >=1.17 * SymPy >= 1.2 * Cython >= 0.29 * PyParsing * Jinja2 >= 2.7 * setuptools >= 24.2 * py-cpuinfo (only required on Windows) For full functionality, you might also want to install: * GSL >=1.16 * SciPy >=0.13.3 * Matplotlib >= 2.0 To build the documentation: * Sphinx (>=1.8) To run the test suite: * pytest * pytest-xdist (optional) Testing status for master branch -------------------------------- .. image:: https://github.com/brian-team/brian2/actions/workflows/testsuite.yml/badge.svg :target: https://github.com/brian-team/brian2/actions/workflows/testsuite.yml :alt: Test status on GitHub Actions .. image:: https://img.shields.io/coveralls/brian-team/brian2/master.svg :target: https://coveralls.io/r/brian-team/brian2?branch=master :alt: Test coverage .. image:: https://readthedocs.org/projects/brian2/badge/?version=stable :target: https://brian2.readthedocs.io/en/stable/?badge=stable :alt: Documentation Status brian2-2.5.4/brian2/000077500000000000000000000000001445201106100140465ustar00rootroot00000000000000brian2-2.5.4/brian2/__init__.py000066400000000000000000000146331445201106100161660ustar00rootroot00000000000000""" Brian 2 """ import logging def _check_dependencies(): """Check basic dependencies""" import sys missing = [] try: import numpy except ImportError as ex: sys.stderr.write(f"Importing numpy failed: '{ex}'\n") missing.append("numpy") try: import sympy except ImportError as ex: sys.stderr.write(f"Importing sympy failed: '{ex}'\n") missing.append("sympy") try: import pyparsing except ImportError as ex: sys.stderr.write(f"Importing pyparsing failed: '{ex}'\n") missing.append("pyparsing") try: import jinja2 except ImportError as ex: sys.stderr.write(f"Importing Jinja2 failed: '{ex}'\n") missing.append("jinja2") if len(missing): raise ImportError( f"Some required dependencies are missing:\n{', '.join(missing)}" ) _check_dependencies() try: from pylab import * except ImportError: # Do the non-matplotlib pylab imports manually # don't let numpy's datetime hide stdlib import datetime import numpy.ma as ma from numpy import * from numpy.fft import * from numpy.linalg import * from numpy.random import * # Make sure that Brian's unit-aware functions are used, even when directly # using names prefixed with numpy or np import brian2.numpy_ as numpy import brian2.numpy_ as np try: from ._version import __version__, __version_tuple__ except ImportError: try: from setuptools_scm import get_version __version__ = get_version( root="..", relative_to=__file__, version_scheme="post-release", local_scheme="no-local-version", ) __version_tuple__ = tuple(int(x) for x in __version__.split(".")[:3]) except ImportError: logging.getLogger("brian2").warn( "Cannot determine Brian version, running from source and " "setuptools_scm is not installed." ) __version__ = "unknown" __version_tuple__ = (0, 0, 0) # delete some annoying names from the namespace if "x" in globals(): del x if "f" in globals(): del f if "rate" in globals(): del rate __docformat__ = "restructuredtext en" from brian2.only import * from brian2.only import test # Check for outdated dependency versions def _check_dependency_version(name, version): import sys from packaging.version import Version from .core.preferences import prefs from .utils.logger import get_logger logger = get_logger(__name__) module = sys.modules[name] if not isinstance(module.__version__, str): # mocked module return if not Version(module.__version__) >= Version(version): message = ( f"{name} is outdated (got version {module.__version__}, need version" f" {version})" ) if prefs.core.outdated_dependency_error: raise ImportError(message) else: logger.warn(message, "outdated_dependency") def _check_dependency_versions(): for name, version in [("numpy", "1.10"), ("sympy", "1.2"), ("jinja2", "2.7")]: _check_dependency_version(name, version) _check_dependency_versions() # Initialize the logging system BrianLogger.initialize() logger = get_logger(__name__) # Check the caches def _get_size_recursively(dirname): import os total_size = 0 for dirpath, _, filenames in os.walk(dirname): for fname in filenames: # When other simulations are running, files may disappear while # we walk through the directory (in particular with Cython, where # we delete the source files after compilation by default) try: size = os.path.getsize(os.path.join(dirpath, fname)) total_size += size except OSError: pass # ignore the file return total_size #: Stores the cache directory for code generation targets _cache_dirs_and_extensions = {} def check_cache(target): cache_dir, _ = _cache_dirs_and_extensions.get(target, (None, None)) if cache_dir is None: return size = _get_size_recursively(cache_dir) size_in_mb = int(round(size / 1024.0 / 1024.0)) if size_in_mb > prefs.codegen.max_cache_dir_size: logger.info( f"Cache size for target '{target}': {size_in_mb} MB.\n" f"You can call clear_cache('{target}') to delete all " "files from the cache or manually delete files in the " f"'{cache_dir}' directory." ) else: logger.debug(f"Cache size for target '{target}': {size_in_mb} MB") def clear_cache(target): """ Clears the on-disk cache with the compiled files for a given code generation target. Parameters ---------- target : str The code generation target (e.g. ``'cython'``) Raises ------ ValueError If the given code generation target does not have an on-disk cache IOError If the cache directory contains unexpected files, suggesting that deleting it would also delete files unrelated to the cache. """ import os import shutil cache_dir, extensions = _cache_dirs_and_extensions.get(target, (None, None)) if cache_dir is None: raise ValueError(f'No cache directory registered for target "{target}".') cache_dir = os.path.abspath(cache_dir) # just to make sure... for folder, _, files in os.walk(cache_dir): for f in files: for ext in extensions: if f.endswith(ext): break else: raise OSError( f"The cache directory for target '{target}' contains " f"the file '{os.path.join(folder, f)}' of an unexpected type and " "will therefore not be removed. Delete files in " f"'{cache_dir}' manually" ) logger.debug(f"Clearing cache for target '{target}' (directory '{cache_dir}').") shutil.rmtree(cache_dir) def _check_caches(): from brian2.codegen.runtime.cython_rt.extension_manager import ( get_cython_cache_dir, get_cython_extensions, ) for target, (dirname, extensions) in [ ("cython", (get_cython_cache_dir(), get_cython_extensions())) ]: _cache_dirs_and_extensions[target] = (dirname, extensions) if prefs.codegen.max_cache_dir_size > 0: check_cache(target) _check_caches() brian2-2.5.4/brian2/codegen/000077500000000000000000000000001445201106100154525ustar00rootroot00000000000000brian2-2.5.4/brian2/codegen/__init__.py000066400000000000000000000004341445201106100175640ustar00rootroot00000000000000""" Package providing the code generation framework. """ # Import the runtime packages so that they can register their preferences # isort:skip_file from .runtime import * from . import _prefs from . import cpp_prefs as _cpp_prefs __all__ = ["NumpyCodeObject", "CythonCodeObject"] brian2-2.5.4/brian2/codegen/_prefs.py000066400000000000000000000044131445201106100173040ustar00rootroot00000000000000""" Module declaring general code generation preferences. Preferences ----------- .. document_brian_prefs:: codegen """ from brian2.core.preferences import BrianPreference, prefs from .codeobject import CodeObject # Preferences prefs.register_preferences( "codegen", "Code generation preferences", target=BrianPreference( default="auto", docs=""" Default target for code generation. Can be a string, in which case it should be one of: * ``'auto'`` the default, automatically chose the best code generation target available. * ``'cython'``, uses the Cython package to generate C++ code. Needs a working installation of Cython and a C++ compiler. * ``'numpy'`` works on all platforms and doesn't need a C compiler but is often less efficient. Or it can be a ``CodeObject`` class. """, validator=lambda target: isinstance(target, str) or issubclass(target, CodeObject), ), string_expression_target=BrianPreference( default="numpy", docs=""" Default target for the evaluation of string expressions (e.g. when indexing state variables). Should normally not be changed from the default numpy target, because the overhead of compiling code is not worth the speed gain for simple expressions. Accepts the same arguments as `codegen.target`, except for ``'auto'`` """, validator=lambda target: isinstance(target, str) or issubclass(target, CodeObject), ), loop_invariant_optimisations=BrianPreference( default=True, docs=""" Whether to pull out scalar expressions out of the statements, so that they are only evaluated once instead of once for every neuron/synapse/... Can be switched off, e.g. because it complicates the code (and the same optimisation is already performed by the compiler) or because the code generation target does not deal well with it. Defaults to ``True``. """, ), max_cache_dir_size=BrianPreference( default=1000, docs=""" The size of a directory (in MB) with cached code for Cython that triggers a warning. Set to 0 to never get a warning. """, ), ) brian2-2.5.4/brian2/codegen/codeobject.py000066400000000000000000000404541445201106100201340ustar00rootroot00000000000000""" Module providing the base `CodeObject` and related functions. """ import collections import copy import platform import weakref from brian2.core.functions import Function from brian2.core.names import Nameable from brian2.equations.unitcheck import check_units_statements from brian2.utils.logger import get_logger from brian2.utils.stringtools import code_representation, indent from .translation import analyse_identifiers __all__ = ["CodeObject", "constant_or_scalar"] logger = get_logger(__name__) #: Dictionary with basic information about the current system (OS, etc.) sys_info = { "system": platform.system(), "architecture": platform.architecture(), "machine": platform.machine(), } def constant_or_scalar(varname, variable): """ Convenience function to generate code to access the value of a variable. Will return ``'varname'`` if the ``variable`` is a constant, and ``array_name[0]`` if it is a scalar array. """ from brian2.devices.device import get_device # avoid circular import if variable.array: return f"{get_device().get_array_name(variable)}[0]" else: return f"{varname}" class CodeObject(Nameable): """ Executable code object. The ``code`` can either be a string or a `brian2.codegen.templates.MultiTemplate`. After initialisation, the code is compiled with the given namespace using ``code.compile(namespace)``. Calling ``code(key1=val1, key2=val2)`` executes the code with the given variables inserted into the namespace. """ #: The `CodeGenerator` class used by this `CodeObject` generator_class = None #: A short name for this type of `CodeObject` class_name = None def __init__( self, owner, code, variables, variable_indices, template_name, template_source, compiler_kwds, name="codeobject*", ): Nameable.__init__(self, name=name) try: owner = weakref.proxy(owner) except TypeError: pass # if owner was already a weakproxy then this will be the error raised self.owner = owner self.code = code self.compiled_code = {} self.variables = variables self.variable_indices = variable_indices self.template_name = template_name self.template_source = template_source self.compiler_kwds = compiler_kwds @classmethod def is_available(cls): """ Whether this target for code generation is available. Should use a minimal example to check whether code generation works in general. """ raise NotImplementedError( f"CodeObject class {cls.__name__} is missing an 'is_available' method." ) def update_namespace(self): """ Update the namespace for this timestep. Should only deal with variables where *the reference* changes every timestep, i.e. where the current reference in `namespace` is not correct. """ pass def compile_block(self, block): raise NotImplementedError("Implement compile_block method") def compile(self): for block in ["before_run", "run", "after_run"]: self.compiled_code[block] = self.compile_block(block) def __call__(self, **kwds): self.update_namespace() self.namespace.update(**kwds) return self.run() def run_block(self, block): raise NotImplementedError("Implement run_block method") def before_run(self): """ Runs the preparation code in the namespace. This code will only be executed once per run. Returns ------- return_value : dict A dictionary with the keys corresponding to the `output_variables` defined during the call of `CodeGenerator.code_object`. """ return self.run_block("before_run") def run(self): """ Runs the main code in the namespace. Returns ------- return_value : dict A dictionary with the keys corresponding to the `output_variables` defined during the call of `CodeGenerator.code_object`. """ return self.run_block("run") def after_run(self): """ Runs the finalizing code in the namespace. This code will only be executed once per run. Returns ------- return_value : dict A dictionary with the keys corresponding to the `output_variables` defined during the call of `CodeGenerator.code_object`. """ return self.run_block("after_run") def _error_msg(code, name): """ Little helper function for error messages. """ error_msg = f"Error generating code for code object '{name}' " code_lines = [line for line in code.split("\n") if len(line.strip())] # If the abstract code is only one line, display it in full if len(code_lines) <= 1: error_msg += f"from this abstract code: '{code_lines[0]}'\n" else: error_msg += ( f"from {len(code_lines)} lines of abstract code, first line is: " "'code_lines[0]'\n" ) return error_msg def check_compiler_kwds(compiler_kwds, accepted_kwds, target): """ Internal function to check the provided compiler keywords against the list of understood keywords. Parameters ---------- compiler_kwds : dict Dictionary of compiler keywords and respective list of values. accepted_kwds : list of str The compiler keywords understood by the code generation target target : str The name of the code generation target (used for the error message). Raises ------ ValueError If a compiler keyword is not understood """ for key in compiler_kwds: if key not in accepted_kwds: formatted_kwds = ", ".join(f"'{kw}'" for kw in accepted_kwds) raise ValueError( f"The keyword argument '{key}' is not understood by " f"the code generation target '{target}'. The valid " f"arguments are: {formatted_kwds}." ) def _merge_compiler_kwds(list_of_kwds): """ Merges a list of keyword dictionaries. Values in these dictionaries are lists of values, the merged dictionaries will contain the concatenations of lists specified for the same key. Parameters ---------- list_of_kwds : list of dict A list of compiler keyword dictionaries that should be merged. Returns ------- merged_kwds : dict The merged dictionary """ merged_kwds = collections.defaultdict(list) for kwds in list_of_kwds: for key, values in kwds.items(): if not isinstance(values, list): raise TypeError( f"Compiler keyword argument '{key}' requires a list of values." ) merged_kwds[key].extend(values) return merged_kwds def _gather_compiler_kwds(function, codeobj_class): """ Gather all the compiler keywords for a function and its dependencies. Parameters ---------- function : `Function` The function for which the compiler keywords should be gathered codeobj_class : type The class of `CodeObject` to use Returns ------- kwds : dict A dictionary with the compiler arguments, a list of values for each key. """ implementation = function.implementations[codeobj_class] all_kwds = [implementation.compiler_kwds] if implementation.dependencies is not None: for dependency in implementation.dependencies.values(): all_kwds.append(_gather_compiler_kwds(dependency, codeobj_class)) return _merge_compiler_kwds(all_kwds) def create_runner_codeobj( group, code, template_name, run_namespace, user_code=None, variable_indices=None, name=None, check_units=True, needed_variables=None, additional_variables=None, template_kwds=None, override_conditional_write=None, codeobj_class=None, ): """Create a `CodeObject` for the execution of code in the context of a `Group`. Parameters ---------- group : `Group` The group where the code is to be run code : str or dict of str The code to be executed. template_name : str The name of the template to use for the code. run_namespace : dict-like An additional namespace that is used for variable lookup (either an explicitly defined namespace or one taken from the local context). user_code : str, optional The code that had been specified by the user before other code was added automatically. If not specified, will be assumed to be identical to ``code``. variable_indices : dict-like, optional A mapping from `Variable` objects to index names (strings). If none is given, uses the corresponding attribute of `group`. name : str, optional A name for this code object, will use ``group + '_codeobject*'`` if none is given. check_units : bool, optional Whether to check units in the statement. Defaults to ``True``. needed_variables: list of str, optional A list of variables that are neither present in the abstract code, nor in the ``USES_VARIABLES`` statement in the template. This is only rarely necessary, an example being a `StateMonitor` where the names of the variables are neither known to the template nor included in the abstract code statements. additional_variables : dict-like, optional A mapping of names to `Variable` objects, used in addition to the variables saved in `group`. template_kwds : dict, optional A dictionary of additional information that is passed to the template. override_conditional_write: list of str, optional A list of variable names which are used as conditions (e.g. for refractoriness) which should be ignored. codeobj_class : class, optional The `CodeObject` class to run code with. If not specified, defaults to the `group`'s ``codeobj_class`` attribute. """ if name is None: if group is not None: name = f"{group.name}_{template_name}_codeobject*" else: name = f"{template_name}_codeobject*" if user_code is None: user_code = code if isinstance(code, str): code = {None: code} user_code = {None: user_code} msg = ( f"Creating code object (group={group.name}, template name={template_name}) for" " abstract code:\n" ) msg += indent(code_representation(code)) logger.diagnostic(msg) from brian2.devices import get_device device = get_device() if override_conditional_write is None: override_conditional_write = set() else: override_conditional_write = set(override_conditional_write) if codeobj_class is None: codeobj_class = device.code_object_class(group.codeobj_class) else: codeobj_class = device.code_object_class(codeobj_class) template = getattr(codeobj_class.templater, template_name) template_variables = getattr(template, "variables", None) all_variables = dict(group.variables) if additional_variables is not None: all_variables.update(additional_variables) # Determine the identifiers that were used identifiers = set() user_identifiers = set() for v, u_v in zip(code.values(), user_code.values()): _, uk, u = analyse_identifiers(v, all_variables, recursive=True) identifiers |= uk | u _, uk, u = analyse_identifiers(u_v, all_variables, recursive=True) user_identifiers |= uk | u # Add variables that are not in the abstract code, nor specified in the # template but nevertheless necessary if needed_variables is None: needed_variables = [] # Resolve all variables (variables used in the code and variables needed by # the template) variables = group.resolve_all( sorted(identifiers | set(needed_variables) | set(template_variables)), # template variables are not known to the user: user_identifiers=user_identifiers, additional_variables=additional_variables, run_namespace=run_namespace, ) # We raise this error only now, because there is some non-obvious code path # where Jinja tries to get a Synapse's "name" attribute via syn['name'], # which then triggers the use of the `group_get_indices` template which does # not exist for standalone. Putting the check for template == None here # means we will first raise an error about the unknown identifier which will # then make Jinja try syn.name if template is None: codeobj_class_name = codeobj_class.class_name or codeobj_class.__name__ raise AttributeError( f"'{codeobj_class_name}' does not provide a code " f"generation template '{template_name}'" ) conditional_write_variables = {} # Add all the "conditional write" variables for var in variables.values(): cond_write_var = getattr(var, "conditional_write", None) if cond_write_var in override_conditional_write: continue if cond_write_var is not None: if ( cond_write_var.name in variables and not variables[cond_write_var.name] is cond_write_var ): logger.diagnostic( f"Variable '{cond_write_var.name}' is needed for the " "conditional write mechanism of variable " f"'{var.name}'. Its name is already used for " f"{variables[cond_write_var.name]!r}." ) else: conditional_write_variables[cond_write_var.name] = cond_write_var variables.update(conditional_write_variables) if check_units: for c in code.values(): # This is the first time that the code is parsed, catch errors try: check_units_statements(c, variables) except (SyntaxError, ValueError) as ex: error_msg = _error_msg(c, name) raise ValueError(error_msg) from ex all_variable_indices = copy.copy(group.variables.indices) if additional_variables is not None: all_variable_indices.update(additional_variables.indices) if variable_indices is not None: all_variable_indices.update(variable_indices) # Make "conditional write" variables use the same index as the variable # that depends on them for varname, var in variables.items(): cond_write_var = getattr(var, "conditional_write", None) if cond_write_var is not None: all_variable_indices[cond_write_var.name] = all_variable_indices[varname] # Check that all functions are available for varname, value in variables.items(): if isinstance(value, Function): try: value.implementations[codeobj_class] except KeyError as ex: # if we are dealing with numpy, add the default implementation from brian2.codegen.runtime.numpy_rt import NumpyCodeObject if codeobj_class is NumpyCodeObject: value.implementations.add_numpy_implementation(value.pyfunc) else: raise NotImplementedError( f"Cannot use function '{varname}': {ex}" ) from ex # Gather the additional compiler arguments declared by function # implementations all_keywords = [ _gather_compiler_kwds(var, codeobj_class) for var in variables.values() if isinstance(var, Function) ] compiler_kwds = _merge_compiler_kwds(all_keywords) # Add the indices needed by the variables for varname in list(variables): var_index = all_variable_indices[varname] if var_index not in ("_idx", "0"): variables[var_index] = all_variables[var_index] return device.code_object( owner=group, name=name, abstract_code=code, variables=variables, template_name=template_name, variable_indices=all_variable_indices, template_kwds=template_kwds, codeobj_class=codeobj_class, override_conditional_write=override_conditional_write, compiler_kwds=compiler_kwds, ) brian2-2.5.4/brian2/codegen/cpp_prefs.py000066400000000000000000000331551445201106100200140ustar00rootroot00000000000000""" Preferences related to C++ compilation Preferences -------------------- .. document_brian_prefs:: codegen.cpp """ import distutils import json import os import platform import re import socket import struct import subprocess import sys import tempfile from distutils.ccompiler import get_default_compiler from setuptools import msvc from brian2.core.preferences import BrianPreference, prefs from brian2.utils.filetools import ensure_directory from brian2.utils.logger import get_logger, std_silent __all__ = ["get_compiler_and_args", "get_msvc_env", "compiler_supports_c99", "C99Check"] logger = get_logger(__name__) # default_buildopts stores default build options for Gcc compiler default_buildopts = [] # Try to get architecture information to get the best compiler setting for # Windows msvc_arch_flag = "" if platform.system() == "Windows": flags = None previously_stored_flags = None # Check whether we've already stored the CPU flags previously user_dir = os.path.join(os.path.expanduser("~"), ".brian") ensure_directory(user_dir) flag_file = os.path.join(user_dir, "cpu_flags.txt") hostname = socket.gethostname() if os.path.isfile(flag_file): try: with open(flag_file, encoding="utf-8") as f: previously_stored_flags = json.load(f) if hostname not in previously_stored_flags: logger.debug("Ignoring stored CPU flags for a different host") else: flags = previously_stored_flags[hostname] except OSError as ex: logger.debug( f'Opening file "{flag_file}" to get CPU flags failed with error' f' "{str(ex)}".' ) if flags is None: # If we don't have stored info, run get_cpu_flags.py get_cpu_flags_script = os.path.join( os.path.dirname(__file__), "get_cpu_flags.py" ) get_cpu_flags_script = os.path.abspath(get_cpu_flags_script) try: output = subprocess.check_output( [sys.executable, get_cpu_flags_script], text=True, encoding="utf-8", ) flags = json.loads(output) # Store flags to a file so we don't have to call cpuinfo next time try: if previously_stored_flags is not None: to_store = previously_stored_flags to_store[hostname] = flags else: to_store = {hostname: flags} with open(flag_file, "w", encoding="utf-8") as f: json.dump(to_store, f) except OSError as ex: logger.debug( f'Writing file "{flag_file}" to store CPU flags failed with error' f' "{str(ex)}".' ) except subprocess.CalledProcessError as ex: logger.debug( "Could not determine optimized MSVC flags, get_cpu_flags failed with:" f" {str(ex)}" ) if flags is not None: # Note that this overwrites the arch_flag, i.e. only the best option # will be used if "sse" in flags: msvc_arch_flag = "/arch:SSE" if "sse2" in flags: msvc_arch_flag = "/arch:SSE2" if "avx" in flags: msvc_arch_flag = "/arch:AVX" if "avx2" in flags: msvc_arch_flag = "/arch:AVX2" else: # Optimized default build options for a range a CPU architectures machine = os.uname().machine if re.match("^(x86_64|aarch64|arm.*|s390.*|i.86.*)$", machine): default_buildopts = [ "-w", "-O3", "-ffast-math", "-fno-finite-math-only", "-march=native", "-std=c++11", ] elif re.match("^(alpha|ppc.*|sparc.*)$", machine): default_buildopts = [ "-w", "-O3", "-ffast-math", "-fno-finite-math-only", "-mcpu=native", "-mtune=native", "-std=c++11", ] elif re.match("^(parisc.*|riscv.*|mips.*)$", machine): default_buildopts = [ "-w", "-O3", "-ffast-math", "-fno-finite-math-only", "-std=c++11", ] else: default_buildopts = ["-w"] if os.environ.get("READTHEDOCS", "False").lower() == "true": # We are getting imported during a documentation build. Set a fake prefix # to avoid having the name of the local environment in the documentation sys_prefix = "/path/to/your/Python/environment" else: sys_prefix = sys.prefix if sys.platform == "win32": prefix_dir = os.path.join(sys_prefix, "Library") else: prefix_dir = sys_prefix # Preferences prefs.register_preferences( "codegen.cpp", "C++ compilation preferences", compiler=BrianPreference( default="", docs=""" Compiler to use (uses default if empty). Should be ``'unix'`` or ``'msvc'``. To specify a specific compiler binary on unix systems, set the `CXX` environment variable instead. """, ), extra_compile_args=BrianPreference( default=None, validator=lambda v: True, docs=""" Extra arguments to pass to compiler (if None, use either ``extra_compile_args_gcc`` or ``extra_compile_args_msvc``). """, ), extra_compile_args_gcc=BrianPreference( default=default_buildopts, docs=""" Extra compile arguments to pass to GCC compiler """, ), extra_compile_args_msvc=BrianPreference( default=["/Ox", "/w", msvc_arch_flag, "/MP"], docs=""" Extra compile arguments to pass to MSVC compiler (the default ``/arch:`` flag is determined based on the processor architecture) """, ), extra_link_args=BrianPreference( default=[], docs=""" Any extra platform- and compiler-specific information to use when linking object files together. """, ), include_dirs=BrianPreference( default=[os.path.join(prefix_dir, "include")], docs=""" Include directories to use. The default value is ``$prefix/include`` (or ``$prefix/Library/include`` on Windows), where ``$prefix`` is Python's site-specific directory prefix as returned by `sys.prefix`. This will make compilation use library files installed into a conda environment. """, ), library_dirs=BrianPreference( default=[os.path.join(prefix_dir, "lib")], docs=""" List of directories to search for C/C++ libraries at link time. The default value is ``$prefix/lib`` (or ``$prefix/Library/lib`` on Windows), where ``$prefix`` is Python's site-specific directory prefix as returned by `sys.prefix`. This will make compilation use library files installed into a conda environment. """, ), runtime_library_dirs=BrianPreference( default=[os.path.join(prefix_dir, "lib")] if sys.platform != "win32" else [], docs=""" List of directories to search for C/C++ libraries at run time. The default value is ``$prefix/lib`` (not used on Windows), where ``$prefix`` is Python's site-specific directory prefix as returned by `sys.prefix`. This will make compilation use library files installed into a conda environment. """, ), libraries=BrianPreference( default=[], docs=""" List of library names (not filenames or paths) to link against. """, ), headers=BrianPreference( default=[], docs=""" A list of strings specifying header files to use when compiling the code. The list might look like ["","'my_header'"]. Note that the header strings need to be in a form than can be pasted at the end of a #include statement in the C++ code. """, ), define_macros=BrianPreference( default=[], docs=""" List of macros to define; each macro is defined using a 2-tuple, where 'value' is either the string to define it to or None to define it without a particular value (equivalent of "#define FOO" in source or -DFOO on Unix C compiler command line). """, ), msvc_vars_location=BrianPreference( default="", docs=""" Location of the MSVC command line tool (or search for best by default). """, ), msvc_architecture=BrianPreference( default="", docs=""" MSVC architecture name (or use system architectue by default). Could take values such as x86, amd64, etc. """, ), ) # check whether compiler supports a flag # Adapted from https://github.com/pybind/pybind11/ def _determine_flag_compatibility(compiler, flagname): import tempfile from distutils.errors import CompileError with tempfile.TemporaryDirectory( prefix="brian_flag_test_" ) as temp_dir, std_silent(): fname = os.path.join(temp_dir, "flag_test.cpp") with open(fname, "w") as f: f.write("int main (int argc, char **argv) { return 0; }") try: compiler.compile([fname], output_dir=temp_dir, extra_postargs=[flagname]) except CompileError: logger.warn(f"Removing unsupported flag '{flagname}' from compiler flags.") return False return True _compiler_flag_compatibility = {} def has_flag(compiler, flagname): if compiler.compiler_type == "msvc": # MSVC does not raise an error for illegal flags, so determining # whether it accepts a flag would mean parsing the output for warnings # This is non-trivial so we don't do it (the main reason to check # flags in the first place are differences between gcc and clang) return True else: compiler_exe = " ".join(compiler.executables["compiler_cxx"]) if (compiler_exe, flagname) not in _compiler_flag_compatibility: compatibility = _determine_flag_compatibility(compiler, flagname) _compiler_flag_compatibility[(compiler_exe, flagname)] = compatibility return _compiler_flag_compatibility[(compiler_exe, flagname)] def get_compiler_and_args(): """ Returns the computed compiler and compilation flags """ compiler = prefs["codegen.cpp.compiler"] if compiler == "": compiler = get_default_compiler() extra_compile_args = prefs["codegen.cpp.extra_compile_args"] if extra_compile_args is None: if compiler in ("gcc", "unix"): extra_compile_args = prefs["codegen.cpp.extra_compile_args_gcc"] elif compiler == "msvc": extra_compile_args = prefs["codegen.cpp.extra_compile_args_msvc"] else: extra_compile_args = [] logger.warn(f"Unsupported compiler '{compiler}'.") from distutils.ccompiler import new_compiler from distutils.sysconfig import customize_compiler compiler_obj = new_compiler(compiler=compiler, verbose=0) customize_compiler(compiler_obj) extra_compile_args = [ flag for flag in extra_compile_args if has_flag(compiler_obj, flag) ] return compiler, extra_compile_args _msvc_env = None def get_msvc_env(): global _msvc_env arch_name = prefs["codegen.cpp.msvc_architecture"] if arch_name == "": bits = struct.calcsize("P") * 8 if bits == 64: arch_name = "x86_amd64" else: arch_name = "x86" # Manual specification of vcvarsall.bat location by the user vcvars_loc = prefs["codegen.cpp.msvc_vars_location"] if vcvars_loc: vcvars_cmd = f'"{vcvars_loc}" {arch_name}' return None, vcvars_cmd # Search for MSVC environment if not already cached if _msvc_env is None: try: _msvc_env = msvc.msvc14_get_vc_env(arch_name) except distutils.errors.DistutilsPlatformError: raise OSError( "Cannot find Microsoft Visual Studio, You " "can try to set the path to vcvarsall.bat " "via the codegen.cpp.msvc_vars_location " "preference explicitly." ) return _msvc_env, None _compiler_supports_c99 = None def compiler_supports_c99(): global _compiler_supports_c99 if _compiler_supports_c99 is None: if platform.system() == "Windows": fd, tmp_file = tempfile.mkstemp(suffix=".cpp") os.write( fd, b""" #if _MSC_VER < 1800 #error #endif """, ) os.close(fd) msvc_env, vcvars_cmd = get_msvc_env() if vcvars_cmd: cmd = f"{vcvars_cmd} && cl /E {tmp_file} > NUL 2>&1" else: os.environ.update(msvc_env) cmd = f"cl /E {tmp_file} > NUL 2>&1" return_value = os.system(cmd) _compiler_supports_c99 = return_value == 0 os.remove(tmp_file) else: cmd = ( 'echo "#if (__STDC_VERSION__ < 199901L)\n#error\n#endif" | ' "cc -xc -E - > /dev/null 2>&1" ) return_value = os.system(cmd) _compiler_supports_c99 = return_value == 0 return _compiler_supports_c99 class C99Check: """ Helper class to create objects that can be passed as an ``availability_check`` to a `FunctionImplementation`. """ def __init__(self, name): self.name = name def __call__(self, *args, **kwargs): if not compiler_supports_c99(): raise NotImplementedError( f'The "{self.name}" function needs C99 compiler support' ) brian2-2.5.4/brian2/codegen/generators/000077500000000000000000000000001445201106100176235ustar00rootroot00000000000000brian2-2.5.4/brian2/codegen/generators/GSL_generator.py000066400000000000000000001303151445201106100226730ustar00rootroot00000000000000""" GSLCodeGenerators for code that uses the ODE solver provided by the GNU Scientific Library (GSL) """ import os import re import numpy as np from brian2.codegen.generators import c_data_type from brian2.codegen.permutation_analysis import ( OrderDependenceError, check_for_order_independence, ) from brian2.codegen.translation import make_statements from brian2.core.functions import Function from brian2.core.preferences import BrianPreference, PreferenceError, prefs from brian2.core.variables import ArrayVariable, AuxiliaryVariable, Constant from brian2.parsing.statements import parse_statement from brian2.units.fundamentalunits import fail_for_dimension_mismatch from brian2.utils.logger import get_logger from brian2.utils.stringtools import get_identifiers, word_substitute __all__ = ["GSLCodeGenerator", "GSLCPPCodeGenerator", "GSLCythonCodeGenerator"] logger = get_logger(__name__) def valid_gsl_dir(val): """ Validate given string to be path containing required GSL files. """ if val is None: return True if not isinstance(val, str): raise PreferenceError( f"Illegal value for GSL directory: {str(val)}, has to be str" ) if not os.path.isdir(val): raise PreferenceError( f"Illegal value for GSL directory: {val}, has to be existing directory" ) if any( not os.path.isfile(os.path.join(val, "gsl", filename)) for filename in ["gsl_odeiv2.h", "gsl_errno.h", "gsl_matrix.h"] ): raise PreferenceError( f"Illegal value for GSL directory: '{val}', " "has to contain gsl_odeiv2.h, gsl_errno.h " "and gsl_matrix.h" ) return True prefs.register_preferences( "GSL", "Directory containing GSL code", directory=BrianPreference( validator=valid_gsl_dir, docs=( "Set path to directory containing GSL header files (gsl_odeiv2.h etc.)" "\nIf this directory is already in Python's include (e.g. because of " "conda installation), this path can be set to None." ), default=None, ), ) class GSLCodeGenerator: """ GSL code generator. Notes ----- Approach is to first let the already existing code generator for a target language do the bulk of the translating from abstract_code to actual code. This generated code is slightly adapted to render it GSL compatible. The most critical part here is that the vector_code that is normally contained in a loop in the ```main()``` is moved to the function ```_GSL_func``` that is sent to the GSL integrator. The variables used in the vector_code are added to a struct named ```dataholder``` and their values are set from the Brian namespace just before the scalar code block. """ def __init__( self, variables, variable_indices, owner, iterate_all, codeobj_class, name, template_name, override_conditional_write=None, allows_scalar_write=False, ): self.generator = codeobj_class.original_generator_class( variables, variable_indices, owner, iterate_all, codeobj_class, name, template_name, override_conditional_write, allows_scalar_write, ) self.method_options = dict(owner.state_updater.method_options) self.integrator = owner.state_updater.integrator # default timestep to start with is the timestep of the NeuronGroup itself self.method_options["dt_start"] = owner.dt.variable.get_value()[0] self.variable_flags = owner.state_updater._gsl_variable_flags def __getattr__(self, item): return getattr(self.generator, item) # A series of functions that should be overridden by child class: def c_data_type(self, dtype): """ Get string version of object dtype that is attached to Brian variables. c pp_generator already has this function, but the Cython generator does not, but we need it for GSL code generation. """ return NotImplementedError def initialize_array(self, varname, values): """ Initialize a static array with given floating point values. E.g. in C++, when called with arguments ``array`` and ``[1.0, 3.0, 2.0]``, this method should return ``double array[] = {1.0, 3.0, 2.0}``. Parameters ---------- varname : str The name of the array variable that should be initialized values : list of float The values that should be assigned to the array Returns ------- code : str One or more lines of array initialization code. """ raise NotImplementedError def var_init_lhs(self, var, type): """ Get string version of the left hand side of an initializing expression Parameters ---------- var : str type : str Returns ------- code : str For cpp returns type + var, while for cython just var """ raise NotImplementedError def unpack_namespace_single(self, var_obj, in_vector, in_scalar): """ Writes the code necessary to pull single variable out of the Brian namespace into the generated code. The code created is significantly different between cpp and cython, so I decided to not make this function general over all target languages (i.e. in contrast to most other functions that only have syntactical differences) """ raise NotImplementedError # GSL functions that are the same for all target languages: def find_function_names(self): """ Return a list of used function names in the self.variables dictionary Functions need to be ignored in the GSL translation process, because the brian generator already sufficiently dealt with them. However, the brian generator also removes them from the variables dict, so there is no way to check whether an identifier is a function after the brian translation process. This function is called before this translation process and the list of function names is stored to be used in the GSL translation. Returns ------- function_names : list list of strings that are function names used in the code """ variables = self.variables return [ var for var, var_obj in variables.items() if isinstance(var_obj, Function) ] def is_cpp_standalone(self): """ Check whether we're running with cpp_standalone. Test if `get_device()` is instance `CPPStandaloneDevice`. Returns ------- is_cpp_standalone : bool whether currently using cpp_standalone device See Also -------- is_constant_and_cpp_standalone : uses the returned value """ # imports here to avoid circular imports from brian2.devices.cpp_standalone.device import CPPStandaloneDevice from brian2.devices.device import get_device device = get_device() return isinstance(device, CPPStandaloneDevice) def is_constant_and_cpp_standalone(self, var_obj): """Check whether self.cpp_standalone and variable is Constant. This check is needed because in the case of using the cpp_standalone device we do not want to apply our GSL variable conversion (var --> _GSL_dataholder.var), because the cpp_standalone code generation process involves replacing constants with their actual value ('freezing'). This results in code that looks like (if for example var = 1.2): _GSL_dataholder.1.2 = 1.2 and _GSL_dataholder->1.2. To prevent repetitive calls to get_device() etc. the outcome of is_cpp_standalone is saved. Parameters ---------- var_obj : `Variable` instance of brian Variable class describing the variable Returns ------- is_cpp_standalone : bool whether the used device is cpp_standalone and the given variable is an instance of Constant """ if not hasattr(self, "cpp_standalone"): self.cpp_standalone = self.is_cpp_standalone() return isinstance(var_obj, Constant) and self.cpp_standalone def find_differential_variables(self, code): """ Find the variables that were tagged _gsl_{var}_f{ind} and return var, ind pairs. `GSLStateUpdater` tagged differential variables and here we extract the information given in these tags. Parameters ---------- code : list of strings A list of strings containing gsl tagged variables Returns ------- diff_vars : dict A dictionary with variable names as keys and differential equation index as value """ diff_vars = {} for expr_set in code: for expr in expr_set.split("\n"): expr = expr.strip(" ") try: lhs, op, rhs, comment = parse_statement(expr) except ValueError: pass m = re.search("_gsl_(.+?)_f([0-9]*)$", lhs) if m: diff_vars[m.group(1)] = m.group(2) return diff_vars def diff_var_to_replace(self, diff_vars): """ Add differential variable-related strings that need to be replaced to go from normal brian to GSL code From the code generated by Brian's 'normal' generators (cpp_generator or cython_generator a few bits of text need to be replaced to get GSL compatible code. The bits of text related to differential equation variables are put in the replacer dictionary in this function. Parameters ---------- diff_vars : dict dictionary with variables as keys and differential equation index as value Returns ------- to_replace : dict dictionary with strings that need to be replaced as keys and the strings that will replace them as values """ variables = self.variables to_replace = {} for var, diff_num in list(diff_vars.items()): to_replace.update(self.var_replace_diff_var_lhs(var, diff_num)) var_obj = variables[var] array_name = self.generator.get_array_name(var_obj, access_data=True) idx_name = "_idx" # TODO: could be dynamic? replace_what = f"{var} = {array_name}[{idx_name}]" replace_with = f"{var} = _GSL_y[{diff_num}]" to_replace[replace_what] = replace_with return to_replace def get_dimension_code(self, diff_num): """ Generate code for function that sets the dimension of the ODE system. GSL needs to know how many differential variables there are in the ODE system. Since the current approach is to have the code in the vector loop the same for all simulations, this dimension is set by an external function. The code for this set_dimension functon is written here. It is assumed the code will be the same for each target language with the exception of some syntactical differences Parameters ---------- diff_num : int Number of differential variables that describe the ODE system Returns ------- set_dimension_code : str The code describing the target language function in a single string """ code = ["\n{start_declare}int set_dimension(size_t * dimension){open_function}"] code += ["\tdimension[0] = %d{end_statement}" % diff_num] code += ["\treturn GSL_SUCCESS{end_statement}{end_function}"] return ("\n").join(code).format(**self.syntax) def yvector_code(self, diff_vars): """ Generate code for function dealing with GSLs y vector. The values of differential variables have to be transferred from Brian's namespace to a vector that is given to GSL. The transferring from Brian --> y and back from y --> Brian after integration happens in separate functions. The code for these is written here. Parameters ---------- diff_vars : dictionary Dictionary containing variable names as keys (str) and differential variable index as value Returns ------- yvector_code : str The code for the two functions (``_fill_y_vector`` and ``_empty_y_vector``) as single string. """ fill_y = [ "\n{start_declare}int _fill_y_vector(_dataholder *" "_GSL_dataholder, double * _GSL_y, int _idx){open_function}" ] empty_y = [ "\n{start_declare}int _empty_y_vector(_dataholder * " "_GSL_dataholder, double * _GSL_y, int _idx){" "open_function}" ] for var, diff_num in list(diff_vars.items()): diff_num = int(diff_num) array_name = self.generator.get_array_name( self.variables[var], access_data=True ) fill_y += [ "\t_GSL_y[%d] = _GSL_dataholder{access_pointer}%s[_idx]{end_statement}" % (diff_num, array_name) ] empty_y += [ "\t_GSL_dataholder{access_pointer}%s[_idx] = _GSL_y[%d]{end_statement}" % (array_name, diff_num) ] fill_y += ["\treturn GSL_SUCCESS{end_statement}{end_function}"] empty_y += ["\treturn GSL_SUCCESS{end_statement}{end_function}"] return ("\n").join(fill_y + empty_y).format(**self.syntax) def make_function_code(self, lines): """ Add lines of GSL translated vector code to 'non-changing' _GSL_func code. Adds nonchanging aspects of GSL _GSL_func code to lines of code written somewhere else (`translate_vector_code`). Here these lines are put between the non-changing parts of the code and the target-language specific syntax is added. Parameters ---------- lines : str Code containing GSL version of equations Returns ------- function_code : str code describing ``_GSL_func`` that is sent to GSL integrator. """ code = [ "\n{start_declare}int _GSL_func(double t, const double " "_GSL_y[], double f[], void * params){open_function}" "\n\t{start_declare}_dataholder * _GSL_dataholder = {open_cast}" "_dataholder *{close_cast} params{end_statement}" "\n\t{start_declare}int _idx = _GSL_dataholder{access_pointer}_idx" "{end_statement}" ] code += [lines] code += ["\treturn GSL_SUCCESS{end_statement}{end_function}"] return ("\n").join(code).format(**self.syntax) def write_dataholder_single(self, var_obj): """ Return string declaring a single variable in the ``_dataholder`` struct. Parameters ---------- var_obj : `Variable` Returns ------- code : str string describing this variable object as required for the ``_dataholder`` struct (e.g. ``double* _array_neurongroup_v``) """ dtype = self.c_data_type(var_obj.dtype) if isinstance(var_obj, ArrayVariable): pointer_name = self.get_array_name(var_obj, access_data=True) try: restrict = self.generator.restrict except AttributeError: restrict = "" if var_obj.scalar or var_obj.size == 1: restrict = "" return f"{dtype}* {restrict} {pointer_name}{{end_statement}}" else: return f"{dtype} {var_obj.name}{{end_statement}}" def write_dataholder(self, variables_in_vector): """ Return string with full code for _dataholder struct. Parameters ---------- variables_in_vector : dict dictionary containing variable name as key and `Variable` as value Returns ------- code : str code for _dataholder struct """ code = ["\n{start_declare}struct _dataholder{open_struct}"] code += ["\tint _idx{end_statement}"] for var, var_obj in list(variables_in_vector.items()): if ( var == "t" or "_gsl" in var or self.is_constant_and_cpp_standalone(var_obj) ): continue code += [f" {self.write_dataholder_single(var_obj)}"] code += ["{end_struct}"] return ("\n").join(code).format(**self.syntax) def scale_array_code(self, diff_vars, method_options): """ Return code for definition of ``_GSL_scale_array`` in generated code. Parameters ---------- diff_vars : dict dictionary with variable name (str) as key and differential variable index (int) as value method_options : dict dictionary containing integrator settings Returns ------- code : str full code describing a function returning a array containing doubles with the absolute errors for each differential variable (according to their assigned index in the GSL StateUpdater) """ # get scale values per variable from method_options abs_per_var = method_options["absolute_error_per_variable"] abs_default = method_options["absolute_error"] if not isinstance(abs_default, float): raise TypeError( "The absolute_error key in method_options should be " f"a float. Was type {type(abs_default)}" ) if abs_per_var is None: diff_scale = {var: float(abs_default) for var in list(diff_vars.keys())} elif isinstance(abs_per_var, dict): diff_scale = {} for var, error in list(abs_per_var.items()): # first do some checks on input if var not in diff_vars: if var not in self.variables: raise KeyError( "absolute_error specified for variable that " f"does not exist: {var}" ) else: raise KeyError( "absolute_error specified for variable that is " f"not being integrated: {var}" ) fail_for_dimension_mismatch( error, self.variables[var], "Unit of absolute_error_per_variable " f"for variable {var} does not match " "unit of variable itself", ) # if all these are passed we can add the value for error in base units diff_scale[var] = float(error) # set the variables that are not mentioned to default value for var in list(diff_vars.keys()): if var not in abs_per_var: diff_scale[var] = float(abs_default) else: raise TypeError( "The absolute_error_per_variable key in method_options " "should either be None or a dictionary " "containing the error for each individual state variable. " f"Was type {type(abs_per_var)}" ) # write code return self.initialize_array( "_GSL_scale_array", [diff_scale[var] for var in sorted(diff_vars)] ) def find_undefined_variables(self, statements): r""" Find identifiers that are not in ``self.variables`` dictionary. Brian does not save the ``_lio_`` variables it uses anywhere. This is problematic for our GSL implementation because we save the lio variables in the ``_dataholder`` struct (for which we need the datatype of the variables). This function adds the left hand side variables that are used in the vector code to the variable dictionary as `AuxiliaryVariable`\ s (all we need later is the datatype). Parameters ---------- statements : list list of statement objects (need to have the dtype attribute) Notes ----- I keep ``self.variables`` and ``other_variables`` separate so I can distinguish what variables are in the Brian namespace and which ones are defined in the code itself. """ variables = self.variables other_variables = {} for statement in statements: var = statement.var if var not in variables: other_variables[var] = AuxiliaryVariable(var, dtype=statement.dtype) return other_variables def find_used_variables(self, statements, other_variables): """ Find all the variables used on the right hand side of the given expressions. Parameters ---------- statements : list list of statement objects Returns ------- used_variables : dict dictionary of variables that are used as variable name (str), `Variable` pairs. """ variables = self.variables used_variables = {} for statement in statements: rhs = statement.expr for var in get_identifiers(rhs): if var in self.function_names: continue try: var_obj = variables[var] except KeyError: var_obj = other_variables[var] used_variables[var] = var_obj # save as object because this has # all needed info (dtype, name, isarray) # I don't know a nicer way to do this, the above way misses write # variables (e.g. not_refractory).. read, write, _ = self.array_read_write(statements) for var in read | write: if var not in used_variables: used_variables[var] = variables[var] # will always be array and # thus exist in variables return used_variables def to_replace_vector_vars(self, variables_in_vector, ignore=frozenset()): """ Create dictionary containing key, value pairs with to be replaced text to translate from conventional Brian to GSL. Parameters ---------- variables_in_vector : dict dictionary with variable name (str), `Variable` pairs of variables occurring in vector code ignore : set, optional set of strings with variable names that should be ignored Returns ------- to_replace : dict dictionary with strings that need to be replaced i.e. _lio_1 will be _GSL_dataholder._lio_1 (in cython) or _GSL_dataholder->_lio_1 (cpp) Notes ----- t will always be added because GSL defines its own t. i.e. for cpp: {'const t = _ptr_array_defaultclock_t[0];' : ''} """ access_pointer = self.syntax["access_pointer"] to_replace = {} t_in_code = None for var, var_obj in list(variables_in_vector.items()): if var_obj.name == "t": t_in_code = var_obj continue if "_gsl" in var or var in ignore: continue if self.is_constant_and_cpp_standalone(var_obj): # does not have to be processed by GSL generator self.variables_to_be_processed.remove(var_obj.name) continue if isinstance(var_obj, ArrayVariable): pointer_name = self.get_array_name(var_obj, access_data=True) to_replace[ pointer_name ] = f"_GSL_dataholder{access_pointer}{pointer_name}" else: to_replace[var] = f"_GSL_dataholder{access_pointer}{var}" # also make sure t declaration is replaced if in code if t_in_code is not None: t_declare = self.var_init_lhs("t", "const double ") array_name = self.get_array_name(t_in_code, access_data=True) end_statement = self.syntax["end_statement"] replace_what = f"{t_declare} = {array_name}[0]{end_statement}" to_replace[replace_what] = "" self.variables_to_be_processed.remove("t") return to_replace def unpack_namespace( self, variables_in_vector, variables_in_scalar, ignore=frozenset() ): """ Write code that unpacks Brian namespace to cython/cpp namespace. For vector code this means putting variables in _dataholder (i.e. _GSL_dataholder->var or _GSL_dataholder.var = ...) Note that code is written so a variable could occur both in scalar and vector code Parameters ---------- variables_in_vector : dict dictionary with variable name (str), `Variable` pairs of variables occurring in vector code variables_in_scalar : dict dictionary with variable name (str), `Variable` pairs of variables occurring in scalar code ignore : set, optional set of string names of variables that should be ignored Returns ------- unpack_namespace_code : str code fragment unpacking the Brian namespace (setting variables in the _dataholder struct in case of vector) """ code = [] for var, var_obj in list(self.variables.items()): if var in ignore: continue if self.is_constant_and_cpp_standalone(var_obj): continue in_vector = var in variables_in_vector in_scalar = var in variables_in_scalar if in_vector: self.variables_to_be_processed.remove(var) code += [self.unpack_namespace_single(var_obj, in_vector, in_scalar)] return ("\n").join(code) def translate_vector_code(self, code_lines, to_replace): """ Translate vector code to GSL compatible code by substituting fragments of code. Parameters ---------- code_lines : list list of strings describing the vector_code to_replace: dict dictionary with to be replaced strings (see to_replace_vector_vars and to_replace_diff_vars) Returns ------- vector_code : str New code that is now to be added to the function that is sent to the GSL integrator """ code = [] for expr_set in code_lines: for line in expr_set.split( "\n" ): # every line seperate to make tabbing correct code += [f" {line}"] code = ("\n").join(code) code = word_substitute(code, to_replace) # special substitute because of limitations of regex word boundaries with # variable[_idx] for from_sub, to_sub in list(to_replace.items()): m = re.search(r"\[(\w+)\];?$", from_sub) if m: code = re.sub(re.sub(r"\[", r"\[", from_sub), to_sub, code) if "_gsl" in code: raise AssertionError( "Translation failed, _gsl still in code (should only " "be tag, and should be replaced).\n" f"Code:\n{code}" ) return code def translate_scalar_code( self, code_lines, variables_in_scalar, variables_in_vector ): """ Translate scalar code: if calculated variables are used in the vector_code their value is added to the variable in the _dataholder. Parameters ---------- code_lines : list list of strings containing scalar code variables_in_vector : dict dictionary with variable name (str), `Variable` pairs of variables occurring in vector code variables_in_scalar : dict dictionary with variable name (str), `Variable` pairs of variables occurring in scalar code Returns ------- scalar_code : str code fragment that should be injected in the main before the loop """ code = [] for line in code_lines: m = re.search(r"(\w+ = .*)", line) try: new_line = m.group(1) var, op, expr, comment = parse_statement(new_line) except (ValueError, AttributeError): code += [line] continue if var in list(variables_in_scalar.keys()): code += [line] elif var in list(variables_in_vector.keys()): if var == "t": continue try: self.variables_to_be_processed.remove(var) except KeyError: raise AssertionError( "Trying to process variable named %s by " "putting its value in the _GSL_dataholder " "based on scalar code, but the variable " "has been processed already." % var ) code += [f"_GSL_dataholder.{var} {op} {expr} {comment}"] return "\n".join(code) def add_gsl_variables_as_non_scalar(self, diff_vars): """ Add _gsl variables as non-scalar. In `GSLStateUpdater` the differential equation variables are substituted with GSL tags that describe the information needed to translate the conventional Brian code to GSL compatible code. This function tells Brian that the variables that contain these tags should always be vector variables. If we don't do this, Brian renders the tag-variables as scalar if no vector variables are used in the right hand side of the expression. Parameters ---------- diff_vars : dict dictionary with variables as keys and differential equation index as value """ for var, ind in list(diff_vars.items()): name = f"_gsl_{var}_f{ind}" self.variables[name] = AuxiliaryVariable(var, scalar=False) def add_meta_variables(self, options): if options["use_last_timestep"]: try: N = self.variables["N"].item() self.owner.variables.add_array( "_last_timestep", size=N, values=np.ones(N) * options["dt_start"], dtype=np.float64, ) except KeyError: # has already been run pass self.variables["_last_timestep"] = self.owner.variables.get( "_last_timestep" ) pointer_last_timestep = ( f"{self.get_array_name(self.variables['_last_timestep'])}[_idx]" ) else: pointer_last_timestep = None if options["save_failed_steps"]: N = self.variables["N"].item() try: self.owner.variables.add_array("_failed_steps", size=N, dtype=np.int32) except KeyError: # has already been run pass self.variables["_failed_steps"] = self.owner.variables.get("_failed_steps") pointer_failed_steps = ( f"{self.get_array_name(self.variables['_failed_steps'])}[_idx]" ) else: pointer_failed_steps = None if options["save_step_count"]: N = int(self.variables["N"].get_value()) try: self.owner.variables.add_array("_step_count", size=N, dtype=np.int32) except KeyError: # has already been run pass self.variables["_step_count"] = self.owner.variables.get("_step_count") pointer_step_count = ( f"{self.get_array_name(self.variables['_step_count'])}[_idx]" ) else: pointer_step_count = None return { "pointer_last_timestep": pointer_last_timestep, "pointer_failed_steps": pointer_failed_steps, "pointer_step_count": pointer_step_count, } def translate( self, code, dtype ): # TODO: it's not so nice we have to copy the contents of this function.. """ Translates an abstract code block into the target language. """ # first check if user code is not using variables that are also used by GSL reserved_variables = [ "_dataholder", "_fill_y_vector", "_empty_y_vector", "_GSL_dataholder", "_GSL_y", "_GSL_func", ] if any([var in self.variables for var in reserved_variables]): # import here to avoid circular import raise ValueError( f"The variables {str(reserved_variables)} are reserved for the GSL" " internal code." ) # if the following statements are not added, Brian translates the # differential expressions in the abstract code for GSL to scalar statements # in the case no non-scalar variables are used in the expression diff_vars = self.find_differential_variables(list(code.values())) self.add_gsl_variables_as_non_scalar(diff_vars) # add arrays we want to use in generated code before self.generator.translate() so # brian does namespace unpacking for us pointer_names = self.add_meta_variables(self.method_options) scalar_statements = {} vector_statements = {} for ac_name, ac_code in code.items(): statements = make_statements( ac_code, self.variables, dtype, optimise=True, blockname=ac_name ) scalar_statements[ac_name], vector_statements[ac_name] = statements for vs in vector_statements.values(): # Check that the statements are meaningful independent on the order of # execution (e.g. for synapses) try: if self.has_repeated_indices( vs ): # only do order dependence if there are repeated indices check_for_order_independence( vs, self.generator.variables, self.generator.variable_indices ) except OrderDependenceError: # If the abstract code is only one line, display it in full if len(vs) <= 1: error_msg = f"Abstract code: '{vs[0]}'\n" else: error_msg = ( f"{len(vs)} lines of abstract code, first line is: '{vs[0]}'\n" ) logger.warn( "Came across an abstract code block that may not be " "well-defined: the outcome may depend on the " "order of execution. You can ignore this warning if " "you are sure that the order of operations does not " "matter. " + error_msg ) # save function names because self.generator.translate_statement_sequence # deletes these from self.variables but we need to know which identifiers # we can safely ignore (i.e. we can ignore the functions because they are # handled by the original generator) self.function_names = self.find_function_names() scalar_code, vector_code, kwds = self.generator.translate_statement_sequence( scalar_statements, vector_statements ) ############ translate code for GSL # first check if any indexing other than '_idx' is used (currently not supported) for code_list in list(scalar_code.values()) + list(vector_code.values()): for code in code_list: m = re.search(r"\[(\w+)\]", code) if m is not None: if m.group(1) != "0" and m.group(1) != "_idx": from brian2.stateupdaters.base import ( UnsupportedEquationsException, ) raise UnsupportedEquationsException( "Equations result in state " "updater code with indexing " "other than '_idx', which " "is currently not supported " "in combination with the " "GSL stateupdater." ) # differential variable specific operations to_replace = self.diff_var_to_replace(diff_vars) GSL_support_code = self.get_dimension_code(len(diff_vars)) GSL_support_code += self.yvector_code(diff_vars) # analyze all needed variables; if not in self.variables: put in separate dic. # also keep track of variables needed for scalar statements and vector statements other_variables = self.find_undefined_variables( scalar_statements[None] + vector_statements[None] ) variables_in_scalar = self.find_used_variables( scalar_statements[None], other_variables ) variables_in_vector = self.find_used_variables( vector_statements[None], other_variables ) # so that _dataholder holds diff_vars as well, even if they don't occur # in the actual statements for var in list(diff_vars.keys()): if var not in variables_in_vector: variables_in_vector[var] = self.variables[var] # let's keep track of the variables that eventually need to be added to # the _GSL_dataholder somehow self.variables_to_be_processed = list(variables_in_vector.keys()) # add code for _dataholder struct GSL_support_code = self.write_dataholder(variables_in_vector) + GSL_support_code # add e.g. _lio_1 --> _GSL_dataholder._lio_1 to replacer to_replace.update( self.to_replace_vector_vars( variables_in_vector, ignore=list(diff_vars.keys()) ) ) # write statements that unpack (python) namespace to _dataholder struct # or local namespace GSL_main_code = self.unpack_namespace( variables_in_vector, variables_in_scalar, ["t"] ) # rewrite actual calculations described by vector_code and put them in _GSL_func func_code = self.translate_one_statement_sequence( vector_statements[None], scalar=False ) GSL_support_code += self.make_function_code( self.translate_vector_code(func_code, to_replace) ) scalar_func_code = self.translate_one_statement_sequence( scalar_statements[None], scalar=True ) # rewrite scalar code, keep variables that are needed in scalar code normal # and add variables to _dataholder for vector_code GSL_main_code += ( f"\n{self.translate_scalar_code(scalar_func_code, variables_in_scalar, variables_in_vector)}" ) if len(self.variables_to_be_processed) > 0: raise AssertionError( "Not all variables that will be used in the vector " "code have been added to the _GSL_dataholder. This " "might mean that the _GSL_func is using " "uninitialized variables.\n" "The unprocessed variables " f"are: {self.variables_to_be_processed}" ) scalar_code["GSL"] = GSL_main_code kwds["define_GSL_scale_array"] = self.scale_array_code( diff_vars, self.method_options ) kwds["n_diff_vars"] = len(diff_vars) kwds["GSL_settings"] = dict(self.method_options) kwds["GSL_settings"]["integrator"] = self.integrator kwds["support_code_lines"] += GSL_support_code.split("\n") kwds["t_array"] = f"{self.get_array_name(self.variables['t'])}[0]" kwds["dt_array"] = f"{self.get_array_name(self.variables['dt'])}[0]" kwds["define_dt"] = "dt" not in variables_in_scalar kwds["cpp_standalone"] = self.is_cpp_standalone() for key, value in list(pointer_names.items()): kwds[key] = value return scalar_code, vector_code, kwds class GSLCythonCodeGenerator(GSLCodeGenerator): syntax = { "end_statement": "", "access_pointer": ".", "start_declare": "cdef extern ", "open_function": ":", "open_struct": ":", "end_function": "", "end_struct": "", "open_cast": "<", "close_cast": ">", "diff_var_declaration": "", } def c_data_type(self, dtype): return c_data_type(dtype) def initialize_array(self, varname, values): value_list = ", ".join(repr(v) for v in values) code = "cdef double {varname}[{n_values}]\n" code += "{varname}[:] = [{value_list}]" return code.format(varname=varname, value_list=value_list, n_values=len(values)) def var_replace_diff_var_lhs(self, var, ind): return {f"_gsl_{var}_f{ind}": f"f[{ind}]"} def var_init_lhs(self, var, type): return var def unpack_namespace_single(self, var_obj, in_vector, in_scalar): code = [] if isinstance(var_obj, ArrayVariable): array_name = self.generator.get_array_name(var_obj) dtype = self.c_data_type(var_obj.dtype) if in_vector: code += [ f"_GSL_dataholder.{array_name} = <{dtype} *> _buf_{array_name}.data" ] if in_scalar: code += [f"{array_name} = <{dtype} *> _buf_{array_name}.data"] else: if in_vector: code += [ f'_GSL_dataholder.{var_obj.name} = _namespace["{var_obj.name}"]' ] if in_scalar: code += [f'{var_obj.name} = _namespace["{var_obj.name}"]'] return "\n".join(code) @staticmethod def get_array_name(var, access_data=True): # We have to do the import here to avoid circular import dependencies. from brian2.codegen.generators.cython_generator import CythonCodeGenerator return CythonCodeGenerator.get_array_name(var, access_data) class GSLCPPCodeGenerator(GSLCodeGenerator): def __getattr__(self, item): return getattr(self.generator, item) syntax = { "end_statement": ";", "access_pointer": "->", "start_declare": 'extern "C" ', "open_function": "\n{", "open_struct": "\n{", "end_function": "\n}", "end_struct": "\n};", "open_cast": "(", "close_cast": ")", "diff_var_declaration": "const scalar ", } def c_data_type(self, dtype): return self.generator.c_data_type(dtype) def initialize_array(self, varname, values): value_list = ", ".join(repr(v) for v in values) return f"double const {varname}[] = {{{value_list}}};" def var_replace_diff_var_lhs(self, var, ind): scalar_dtype = self.c_data_type(prefs.core.default_float_dtype) f = f"f[{ind}]" try: if "unless refractory" in self.variable_flags[var]: return { f"_gsl_{var}_f{ind}": f, f"{scalar_dtype} _gsl_{var}_f{ind};": "", f"{scalar_dtype} {f};": "", } # in case the replacement of # _gsl_var_find to f[ind] happens # first except KeyError: pass return {f"const {scalar_dtype} _gsl_{var}_f{ind}": f} def var_init_lhs(self, var, type): return type + var def unpack_namespace_single(self, var_obj, in_vector, in_scalar): if isinstance(var_obj, ArrayVariable): pointer_name = self.get_array_name(var_obj, access_data=True) array_name = self.get_array_name(var_obj) if in_vector: return f"_GSL_dataholder.{pointer_name} = {array_name};" else: return "" else: if in_vector: return f"_GSL_dataholder.{var_obj.name} = {var_obj.name};" else: return "" brian2-2.5.4/brian2/codegen/generators/__init__.py000066400000000000000000000010141445201106100217300ustar00rootroot00000000000000# Register the base category before importing the indivial generators with # their subcategories from brian2.core.preferences import prefs prefs.register_preferences( "codegen.generators", "Codegen generator preferences (see subcategories for individual languages)", ) from .base import * from .cpp_generator import * from .numpy_generator import * try: from .cython_generator import * except ImportError: pass # todo: raise a warning? try: from .GSL_generator import * except ImportError: pass brian2-2.5.4/brian2/codegen/generators/base.py000066400000000000000000000270051445201106100211130ustar00rootroot00000000000000""" Base class for generating code in different programming languages, gives the methods which should be overridden to implement a new language. """ from brian2.codegen.permutation_analysis import ( OrderDependenceError, check_for_order_independence, ) from brian2.codegen.translation import make_statements from brian2.core.functions import Function from brian2.core.variables import ArrayVariable from brian2.utils.logger import get_logger from brian2.utils.stringtools import get_identifiers __all__ = ["CodeGenerator"] logger = get_logger(__name__) class CodeGenerator: """ Base class for all languages. See definition of methods below. TODO: more details here """ # Subclasses should override this class_name = "" def __init__( self, variables, variable_indices, owner, iterate_all, codeobj_class, name, template_name, override_conditional_write=None, allows_scalar_write=False, ): # We have to do the import here to avoid circular import dependencies. from brian2.devices.device import get_device self.device = get_device() self.variables = variables self.variable_indices = variable_indices self.func_name_replacements = {} for varname, var in variables.items(): if isinstance(var, Function): if codeobj_class in var.implementations: impl_name = var.implementations[codeobj_class].name if impl_name is not None: self.func_name_replacements[varname] = impl_name self.iterate_all = iterate_all self.codeobj_class = codeobj_class self.owner = owner if override_conditional_write is None: self.override_conditional_write = set() else: self.override_conditional_write = set(override_conditional_write) self.allows_scalar_write = allows_scalar_write self.name = name self.template_name = template_name # Gather the names of functions that should get an additional # "_vectorisation_idx" argument in the generated code. Take care # of storing their translated name (e.g. "_rand" instead of "rand") # if necessary self.auto_vectorise = { self.func_name_replacements.get(name, name) for name in self.variables if getattr(self.variables[name], "auto_vectorise", False) } @staticmethod def get_array_name(var, access_data=True): """ Get a globally unique name for a `ArrayVariable`. Parameters ---------- var : `ArrayVariable` The variable for which a name should be found. access_data : bool, optional For `DynamicArrayVariable` objects, specifying `True` here means the name for the underlying data is returned. If specifying `False`, the name of object itself is returned (e.g. to allow resizing). Returns ------- name : str A uniqe name for `var`. """ # We have to do the import here to avoid circular import dependencies. from brian2.devices.device import get_device device = get_device() return device.get_array_name(var, access_data=access_data) def translate_expression(self, expr): """ Translate the given expression string into a string in the target language, returns a string. """ raise NotImplementedError def translate_statement(self, statement): """ Translate a single line `Statement` into the target language, returns a string. """ raise NotImplementedError def determine_keywords(self): """ A dictionary of values that is made available to the templated. This is used for example by the `CPPCodeGenerator` to set up all the supporting code """ return {} def translate_one_statement_sequence(self, statements, scalar=False): raise NotImplementedError def translate_statement_sequence(self, scalar_statements, vector_statements): """ Translate a sequence of `Statement` into the target language, taking care to declare variables, etc. if necessary. Returns a tuple ``(scalar_code, vector_code, kwds)`` where ``scalar_code`` is a list of the lines of code executed before the inner loop, ``vector_code`` is a list of the lines of code in the inner loop, and ``kwds`` is a dictionary of values that is made available to the template. """ scalar_code = {} vector_code = {} for name, block in scalar_statements.items(): scalar_code[name] = self.translate_one_statement_sequence( block, scalar=True ) for name, block in vector_statements.items(): vector_code[name] = self.translate_one_statement_sequence( block, scalar=False ) kwds = self.determine_keywords() return scalar_code, vector_code, kwds def array_read_write(self, statements): """ Helper function, gives the set of ArrayVariables that are read from and written to in the series of statements. Returns the pair read, write of sets of variable names. """ variables = self.variables variable_indices = self.variable_indices read = set() write = set() for stmt in statements: ids = get_identifiers(stmt.expr) # if the operation is inplace this counts as a read. if stmt.inplace: ids.add(stmt.var) read = read.union(ids) if stmt.scalar or variable_indices[stmt.var] == "0": if stmt.op != ":=" and not self.allows_scalar_write: raise SyntaxError( f"Writing to scalar variable {stmt.var} not allowed in this" " context." ) for name in ids: if ( name in variables and isinstance(variables[name], ArrayVariable) and not ( variables[name].scalar or variable_indices[name] == "0" ) ): raise SyntaxError( "Cannot write to scalar variable " f"'{stmt.var}' with an expression " f"referring to vector variable '{name}'" ) write.add(stmt.var) read = { varname for varname, var in list(variables.items()) if isinstance(var, ArrayVariable) and varname in read } write = { varname for varname, var in list(variables.items()) if isinstance(var, ArrayVariable) and varname in write } # Gather the indices stored as arrays (ignore _idx which is special) indices = set() indices |= { variable_indices[varname] for varname in read if not variable_indices[varname] in ("_idx", "0") and isinstance(variables[variable_indices[varname]], ArrayVariable) } indices |= { variable_indices[varname] for varname in write if not variable_indices[varname] in ("_idx", "0") and isinstance(variables[variable_indices[varname]], ArrayVariable) } # don't list arrays that are read explicitly and used as indices twice read -= indices return read, write, indices def get_conditional_write_vars(self): """ Helper function, returns a dict of mappings ``(varname, condition_var_name)`` indicating that when ``varname`` is written to, it should only be when ``condition_var_name`` is ``True``. """ conditional_write_vars = {} for varname, var in list(self.variables.items()): if getattr(var, "conditional_write", None) is not None: cvar = var.conditional_write cname = cvar.name if cname not in self.override_conditional_write: conditional_write_vars[varname] = cname return conditional_write_vars def arrays_helper(self, statements): """ Combines the two helper functions `array_read_write` and `get_conditional_write_vars`, and updates the ``read`` set. """ read, write, indices = self.array_read_write(statements) conditional_write_vars = self.get_conditional_write_vars() read |= {var for var in write if var in conditional_write_vars} read |= { conditional_write_vars[var] for var in write if var in conditional_write_vars } return read, write, indices, conditional_write_vars def has_repeated_indices(self, statements): """ Whether any of the statements potentially uses repeated indices (e.g. pre- or postsynaptic indices). """ variables = self.variables variable_indices = self.variable_indices read, write, indices, conditional_write_vars = self.arrays_helper(statements) # Check whether we potentially deal with repeated indices (which will # be the case most importantly when we write to pre- or post-synaptic # variables in synaptic code) used_indices = {variable_indices[var] for var in write} all_unique = all( variables[index].unique for index in used_indices if index not in ("_idx", "0") ) return not all_unique def translate(self, code, dtype): """ Translates an abstract code block into the target language. """ scalar_statements = {} vector_statements = {} for ac_name, ac_code in code.items(): statements = make_statements( ac_code, self.variables, dtype, optimise=True, blockname=ac_name ) scalar_statements[ac_name], vector_statements[ac_name] = statements for vs in vector_statements.values(): # Check that the statements are meaningful independent on the order of # execution (e.g. for synapses) try: if self.has_repeated_indices( vs ): # only do order dependence if there are repeated indices check_for_order_independence( vs, self.variables, self.variable_indices ) except OrderDependenceError: # If the abstract code is only one line, display it in full if len(vs) <= 1: error_msg = f"Abstract code: '{vs[0]}'\n" else: error_msg = ( f"{len(vs)} lines of abstract code, first line is: '{vs[0]}'\n" ) logger.warn( "Came across an abstract code block that may not be " "well-defined: the outcome may depend on the " "order of execution. You can ignore this warning if " "you are sure that the order of operations does not " "matter. " + error_msg ) translated = self.translate_statement_sequence( scalar_statements, vector_statements ) return translated brian2-2.5.4/brian2/codegen/generators/cpp_generator.py000066400000000000000000000562061445201106100230360ustar00rootroot00000000000000import itertools import numpy from brian2.codegen.cpp_prefs import C99Check from brian2.core.functions import DEFAULT_FUNCTIONS, Function from brian2.core.preferences import BrianPreference, prefs from brian2.core.variables import ArrayVariable from brian2.parsing.rendering import CPPNodeRenderer from brian2.utils.logger import get_logger from brian2.utils.stringtools import ( deindent, stripped_deindented_lines, word_substitute, ) from .base import CodeGenerator logger = get_logger(__name__) __all__ = ["CPPCodeGenerator", "c_data_type"] def c_data_type(dtype): """ Gives the C language specifier for numpy data types. For example, ``numpy.int32`` maps to ``int32_t`` in C. """ # this handles the case where int is specified, it will be int32 or int64 # depending on platform if dtype is int: dtype = numpy.array([1]).dtype.type if dtype is float: dtype = numpy.array([1.0]).dtype.type if dtype == numpy.float32: dtype = "float" elif dtype == numpy.float64: dtype = "double" elif dtype == numpy.int8: dtype = "int8_t" elif dtype == numpy.int16: dtype = "int16_t" elif dtype == numpy.int32: dtype = "int32_t" elif dtype == numpy.int64: dtype = "int64_t" elif dtype == numpy.uint16: dtype = "uint16_t" elif dtype == numpy.uint32: dtype = "uint32_t" elif dtype == numpy.uint64: dtype = "uint64_t" elif dtype == numpy.bool_ or dtype is bool: dtype = "char" else: raise ValueError(f"dtype {str(dtype)} not known.") return dtype # Preferences prefs.register_preferences( "codegen.generators.cpp", "C++ codegen preferences", restrict_keyword=BrianPreference( default="__restrict", docs=""" The keyword used for the given compiler to declare pointers as restricted. This keyword is different on different compilers, the default works for gcc and MSVS. """, ), flush_denormals=BrianPreference( default=False, docs=""" Adds code to flush denormals to zero. The code is gcc and architecture specific, so may not compile on all platforms. The code, for reference is:: #define CSR_FLUSH_TO_ZERO (1 << 15) unsigned csr = __builtin_ia32_stmxcsr(); csr |= CSR_FLUSH_TO_ZERO; __builtin_ia32_ldmxcsr(csr); Found at ``_. """, ), ) typestrs = ["int", "long", "long long", "float", "double", "long double"] floattypestrs = ["float", "double", "long double"] hightype_support_code = "template < typename T1, typename T2 > struct _higher_type;\n" for ix, xtype in enumerate(typestrs): for iy, ytype in enumerate(typestrs): hightype = typestrs[max(ix, iy)] hightype_support_code += f""" template < > struct _higher_type<{xtype},{ytype}> {{ typedef {hightype} type; }}; """ mod_support_code = """ template < typename T1, typename T2 > static inline typename _higher_type::type _brian_mod(T1 x, T2 y) {{ return x-y*floor(1.0*x/y); }} """ floordiv_support_code = """ template < typename T1, typename T2 > static inline typename _higher_type::type _brian_floordiv(T1 x, T2 y) {{ return floor(1.0*x/y); }} """ pow_support_code = """ #ifdef _MSC_VER #define _brian_pow(x, y) (pow((double)(x), (y))) #else #define _brian_pow(x, y) (pow((x), (y))) #endif """ _universal_support_code = ( hightype_support_code + mod_support_code + floordiv_support_code + pow_support_code ) class CPPCodeGenerator(CodeGenerator): """ C++ language C++ code templates should provide Jinja2 macros with the following names: ``main`` The main loop. ``support_code`` The support code (function definitions, etc.), compiled in a separate file. For user-defined functions, there are two keys to provide: ``support_code`` The function definition which will be added to the support code. ``hashdefine_code`` The ``#define`` code added to the main loop. See `TimedArray` for an example of these keys. """ class_name = "cpp" universal_support_code = _universal_support_code def __init__(self, *args, **kwds): super().__init__(*args, **kwds) self.c_data_type = c_data_type @property def restrict(self): return f"{prefs['codegen.generators.cpp.restrict_keyword']} " @property def flush_denormals(self): return prefs["codegen.generators.cpp.flush_denormals"] @staticmethod def get_array_name(var, access_data=True): # We have to do the import here to avoid circular import dependencies. from brian2.devices.device import get_device device = get_device() if access_data: return f"_ptr{device.get_array_name(var)}" else: return device.get_array_name(var, access_data=False) def translate_expression(self, expr): expr = word_substitute(expr, self.func_name_replacements) return ( CPPNodeRenderer(auto_vectorise=self.auto_vectorise) .render_expr(expr) .strip() ) def translate_statement(self, statement): var, op, expr, comment = ( statement.var, statement.op, statement.expr, statement.comment, ) # For C++ we replace complex expressions involving boolean variables into a sequence of # if/then expressions with simpler expressions. This is provided by the optimise_statements # function. if statement.used_boolean_variables is not None and len( statement.used_boolean_variables ): used_boolvars = statement.used_boolean_variables bool_simp = statement.boolean_simplified_expressions if op == ":=": # we have to declare the variable outside the if/then statement (which # unfortunately means we can't make it const but the optimisation is worth # it anyway). codelines = [f"{self.c_data_type(statement.dtype)} {var};"] op = "=" else: codelines = [] firstline = True # bool assigns is a sequence of (var, value) pairs giving the conditions under # which the simplified expression simp_expr holds for bool_assigns, simp_expr in bool_simp.items(): # generate a boolean expression like ``var1 && var2 && !var3`` atomics = [] for boolvar, boolval in bool_assigns: if boolval: atomics.append(boolvar) else: atomics.append(f"!{boolvar}") if firstline: line = "" else: line = "else " # only need another if statement when we have more than one boolean variables if firstline or len(used_boolvars) > 1: line += f"if({' && '.join(atomics)})" line += "\n " line += f"{var} {op} {self.translate_expression(simp_expr)};" codelines.append(line) firstline = False code = "\n".join(codelines) else: if op == ":=": decl = f"{self.c_data_type(statement.dtype)} " op = "=" if statement.constant: decl = f"const {decl}" else: decl = "" code = f"{decl + var} {op} {self.translate_expression(expr)};" if len(comment): code += f" // {comment}" return code def translate_to_read_arrays(self, read, write, indices): lines = [] # index and read arrays (index arrays first) for varname in itertools.chain(sorted(indices), sorted(read)): index_var = self.variable_indices[varname] var = self.variables[varname] if varname not in write: line = "const " else: line = "" line = f"{line + self.c_data_type(var.dtype)} {varname} = " line = f"{line + self.get_array_name(var)}[{index_var}];" lines.append(line) return lines def translate_to_declarations(self, read, write, indices): lines = [] # simply declare variables that will be written but not read for varname in sorted(write): if varname not in read and varname not in indices: var = self.variables[varname] line = f"{self.c_data_type(var.dtype)} {varname};" lines.append(line) return lines def translate_to_statements(self, statements, conditional_write_vars): lines = [] # the actual code for stmt in statements: line = self.translate_statement(stmt) if stmt.var in conditional_write_vars: condvar = conditional_write_vars[stmt.var] lines.append(f"if({condvar})") lines.append(f" {line}") else: lines.append(line) return lines def translate_to_write_arrays(self, write): lines = [] # write arrays for varname in sorted(write): index_var = self.variable_indices[varname] var = self.variables[varname] line = f"{self.get_array_name(var)}[{index_var}] = {varname};" lines.append(line) return lines def translate_one_statement_sequence(self, statements, scalar=False): # Note that we do not call this function from # `translate_statement_sequence` (which has been overwritten) # It is nevertheless implemented, so that it can be called explicitly # (e.g. from the GSL code generation) read, write, indices, cond_write = self.arrays_helper(statements) lines = [] # index and read arrays (index arrays first) lines += self.translate_to_read_arrays(read, write, indices) # simply declare variables that will be written but not read lines += self.translate_to_declarations(read, write, indices) # the actual code lines += self.translate_to_statements(statements, cond_write) # write arrays lines += self.translate_to_write_arrays(write) return stripped_deindented_lines("\n".join(lines)) def translate_statement_sequence(self, sc_statements, ve_statements): # This function is overwritten, since we do not want to completely # separate the code generation for scalar and vector code assert set(sc_statements.keys()) == set(ve_statements.keys()) kwds = self.determine_keywords() sc_code = {} ve_code = {} for block_name in sc_statements: sc_block = sc_statements[block_name] ve_block = ve_statements[block_name] (sc_read, sc_write, sc_indices, sc_cond_write) = self.arrays_helper( sc_block ) (ve_read, ve_write, ve_indices, ve_cond_write) = self.arrays_helper( ve_block ) # We want to read all scalar variables that are needed in the # vector code already in the scalar code, if they are not written for varname in set(ve_read): var = self.variables[varname] if var.scalar and varname not in ve_write: sc_read.add(varname) ve_read.remove(varname) for code, stmts, read, write, indices, cond_write in [ (sc_code, sc_block, sc_read, sc_write, sc_indices, sc_cond_write), (ve_code, ve_block, ve_read, ve_write, ve_indices, ve_cond_write), ]: lines = [] # index and read arrays (index arrays first) lines += self.translate_to_read_arrays(read, write, indices) # simply declare variables that will be written but not read lines += self.translate_to_declarations(read, write, indices) # the actual code lines += self.translate_to_statements(stmts, cond_write) # write arrays lines += self.translate_to_write_arrays(write) code[block_name] = stripped_deindented_lines("\n".join(lines)) return sc_code, ve_code, kwds def denormals_to_zero_code(self): if self.flush_denormals: return """ #define CSR_FLUSH_TO_ZERO (1 << 15) unsigned csr = __builtin_ia32_stmxcsr(); csr |= CSR_FLUSH_TO_ZERO; __builtin_ia32_ldmxcsr(csr); """ else: return "" def _add_user_function(self, varname, variable, added): impl = variable.implementations[self.codeobj_class] if (impl.name, variable) in added: return # nothing to do else: added.add((impl.name, variable)) support_code = [] hash_defines = [] pointers = [] user_functions = [(varname, variable)] funccode = impl.get_code(self.owner) if isinstance(funccode, str): # Rename references to any dependencies if necessary for dep_name, dep in impl.dependencies.items(): dep_impl = dep.implementations[self.codeobj_class] dep_impl_name = dep_impl.name if dep_impl_name is None: dep_impl_name = dep.pyfunc.__name__ if dep_name != dep_impl_name: funccode = word_substitute(funccode, {dep_name: dep_impl_name}) funccode = {"support_code": funccode} if funccode is not None: # To make namespace variables available to functions, we # create global variables and assign to them in the main # code func_namespace = impl.get_namespace(self.owner) or {} for ns_key, ns_value in func_namespace.items(): if hasattr(ns_value, "dtype"): if ns_value.shape == (): raise NotImplementedError( "Directly replace scalar values in the function " "instead of providing them via the namespace" ) type_str = f"{self.c_data_type(ns_value.dtype)}*" else: # e.g. a function type_str = "py::object" support_code.append(f"static {type_str} _namespace{ns_key};") pointers.append(f"_namespace{ns_key} = {ns_key};") support_code.append(deindent(funccode.get("support_code", ""))) hash_defines.append(deindent(funccode.get("hashdefine_code", ""))) dep_hash_defines = [] dep_pointers = [] dep_support_code = [] if impl.dependencies is not None: for dep_name, dep in impl.dependencies.items(): if dep_name not in self.variables: self.variables[dep_name] = dep dep_impl = dep.implementations[self.codeobj_class] if dep_name != dep_impl.name: self.func_name_replacements[dep_name] = dep_impl.name user_function = self._add_user_function(dep_name, dep, added) if user_function is not None: hd, ps, sc, uf = user_function dep_hash_defines.extend(hd) dep_pointers.extend(ps) dep_support_code.extend(sc) user_functions.extend(uf) return ( dep_hash_defines + hash_defines, dep_pointers + pointers, dep_support_code + support_code, user_functions, ) def determine_keywords(self): # set up the restricted pointers, these are used so that the compiler # knows there is no aliasing in the pointers, for optimisation pointers = [] # It is possible that several different variable names refer to the # same array. E.g. in gapjunction code, v_pre and v_post refer to the # same array if a group is connected to itself handled_pointers = set() template_kwds = {} # Again, do the import here to avoid a circular dependency. from brian2.devices.device import get_device device = get_device() for var in self.variables.values(): if isinstance(var, ArrayVariable): # This is the "true" array name, not the restricted pointer. array_name = device.get_array_name(var) pointer_name = self.get_array_name(var) if pointer_name in handled_pointers: continue if getattr(var, "ndim", 1) > 1: continue # multidimensional (dynamic) arrays have to be treated differently restrict = self.restrict # turn off restricted pointers for scalars for safety if var.scalar or var.size == 1: restrict = " " line = ( f"{self.c_data_type(var.dtype)}* {restrict} {pointer_name} =" f" {array_name};" ) pointers.append(line) handled_pointers.add(pointer_name) # set up the functions user_functions = [] support_code = [] hash_defines = [] added = set() # keep track of functions that were added for varname, variable in list(self.variables.items()): if isinstance(variable, Function): user_func = self._add_user_function(varname, variable, added) if user_func is not None: hd, ps, sc, uf = user_func user_functions.extend(uf) support_code.extend(sc) pointers.extend(ps) hash_defines.extend(hd) support_code.append(self.universal_support_code) keywords = { "pointers_lines": stripped_deindented_lines("\n".join(pointers)), "support_code_lines": stripped_deindented_lines("\n".join(support_code)), "hashdefine_lines": stripped_deindented_lines("\n".join(hash_defines)), "denormals_code_lines": stripped_deindented_lines( "\n".join(self.denormals_to_zero_code()) ), } keywords.update(template_kwds) return keywords ################################################################################ # Implement functions ################################################################################ # Functions that exist under the same name in C++ for func in [ "sin", "cos", "tan", "sinh", "cosh", "tanh", "exp", "log", "log10", "sqrt", "ceil", "floor", ]: DEFAULT_FUNCTIONS[func].implementations.add_implementation( CPPCodeGenerator, code=None ) DEFAULT_FUNCTIONS["expm1"].implementations.add_implementation( CPPCodeGenerator, code=None, availability_check=C99Check("expm1") ) DEFAULT_FUNCTIONS["log1p"].implementations.add_implementation( CPPCodeGenerator, code=None, availability_check=C99Check("log1p") ) # Functions that need a name translation for func, func_cpp in [ ("arcsin", "asin"), ("arccos", "acos"), ("arctan", "atan"), ("int", "int_"), # from stdint_compat.h ]: DEFAULT_FUNCTIONS[func].implementations.add_implementation( CPPCodeGenerator, code=None, name=func_cpp ) exprel_code = """ static inline double _exprel(double x) { if (fabs(x) < 1e-16) return 1.0; if (x > 717) return INFINITY; return expm1(x)/x; } """ DEFAULT_FUNCTIONS["exprel"].implementations.add_implementation( CPPCodeGenerator, code=exprel_code, name="_exprel", availability_check=C99Check("exprel"), ) abs_code = """ #define _brian_abs std::abs """ DEFAULT_FUNCTIONS["abs"].implementations.add_implementation( CPPCodeGenerator, code=abs_code, name="_brian_abs" ) clip_code = """ template static inline T _clip(const T value, const double a_min, const double a_max) { if (value < a_min) return a_min; if (value > a_max) return a_max; return value; } """ DEFAULT_FUNCTIONS["clip"].implementations.add_implementation( CPPCodeGenerator, code=clip_code, name="_clip" ) sign_code = """ template static inline int _sign(T val) { return (T(0) < val) - (val < T(0)); } """ DEFAULT_FUNCTIONS["sign"].implementations.add_implementation( CPPCodeGenerator, code=sign_code, name="_sign" ) timestep_code = """ static inline int64_t _timestep(double t, double dt) { return (int64_t)((t + 1e-3*dt)/dt); } """ DEFAULT_FUNCTIONS["timestep"].implementations.add_implementation( CPPCodeGenerator, code=timestep_code, name="_timestep" ) poisson_code = """ double _loggam(double x) { double x0, x2, xp, gl, gl0; int32_t k, n; static double a[10] = {8.333333333333333e-02, -2.777777777777778e-03, 7.936507936507937e-04, -5.952380952380952e-04, 8.417508417508418e-04, -1.917526917526918e-03, 6.410256410256410e-03, -2.955065359477124e-02, 1.796443723688307e-01, -1.39243221690590e+00}; x0 = x; n = 0; if ((x == 1.0) || (x == 2.0)) return 0.0; else if (x <= 7.0) { n = (int32_t)(7 - x); x0 = x + n; } x2 = 1.0 / (x0 * x0); xp = 2 * M_PI; gl0 = a[9]; for (k=8; k>=0; k--) { gl0 *= x2; gl0 += a[k]; } gl = gl0 / x0 + 0.5 * log(xp) + (x0 - 0.5) * log(x0) - x0; if (x <= 7.0) { for (k=1; k<=n; k++) { gl -= log(x0 - 1.0); x0 -= 1.0; } } return gl; } int32_t _poisson_mult(double lam, int _vectorisation_idx) { int32_t X; double prod, U, enlam; enlam = exp(-lam); X = 0; prod = 1.0; while (1) { U = _rand(_vectorisation_idx); prod *= U; if (prod > enlam) X += 1; else return X; } } int32_t _poisson_ptrs(double lam, int _vectorisation_idx) { int32_t k; double U, V, slam, loglam, a, b, invalpha, vr, us; slam = sqrt(lam); loglam = log(lam); b = 0.931 + 2.53 * slam; a = -0.059 + 0.02483 * b; invalpha = 1.1239 + 1.1328 / (b - 3.4); vr = 0.9277 - 3.6224 / (b - 2); while (1) { U = _rand(_vectorisation_idx) - 0.5; V = _rand(_vectorisation_idx); us = 0.5 - abs(U); k = (int32_t)floor((2 * a / us + b) * U + lam + 0.43); if ((us >= 0.07) && (V <= vr)) return k; if ((k < 0) || ((us < 0.013) && (V > us))) continue; if ((log(V) + log(invalpha) - log(a / (us * us) + b)) <= (-lam + k * loglam - _loggam(k + 1))) return k; } } int32_t _poisson(double lam, int32_t _idx) { if (lam >= 10) return _poisson_ptrs(lam, _idx); else if (lam == 0) return 0; else return _poisson_mult(lam, _idx); } """ DEFAULT_FUNCTIONS["poisson"].implementations.add_implementation( CPPCodeGenerator, code=poisson_code, name="_poisson", dependencies={"_rand": DEFAULT_FUNCTIONS["rand"]}, ) brian2-2.5.4/brian2/codegen/generators/cython_generator.py000066400000000000000000000561731445201106100235630ustar00rootroot00000000000000import itertools from brian2.codegen.cpp_prefs import C99Check from brian2.core.functions import DEFAULT_FUNCTIONS, Function from brian2.core.variables import ( AuxiliaryVariable, Constant, Subexpression, Variable, get_dtype_str, ) from brian2.devices.device import all_devices from brian2.parsing.bast import brian_dtype_from_dtype from brian2.parsing.rendering import NodeRenderer from brian2.utils.stringtools import deindent, indent, word_substitute from .base import CodeGenerator __all__ = ["CythonCodeGenerator"] # fmt: off data_type_conversion_table = [ # canonical C++ Numpy ('float32', 'float', 'float32'), ('float64', 'double', 'float64'), ('int32', 'int32_t', 'int32'), ('int64', 'int64_t', 'int64'), ('bool', 'bool', 'bool'), ('uint8', 'char', 'uint8'), ('uint64', 'uint64_t', 'uint64'), ] # fmt: on cpp_dtype = {canonical: cpp for canonical, cpp, np in data_type_conversion_table} numpy_dtype = {canonical: np for canonical, cpp, np in data_type_conversion_table} def get_cpp_dtype(obj): return cpp_dtype[get_dtype_str(obj)] def get_numpy_dtype(obj): return numpy_dtype[get_dtype_str(obj)] class CythonNodeRenderer(NodeRenderer): def render_NameConstant(self, node): return {True: "1", False: "0"}.get(node.value, node.value) def render_Name(self, node): return {"True": "1", "False": "0"}.get(node.id, node.id) def render_BinOp(self, node): if node.op.__class__.__name__ == "Mod": left = self.render_node(node.left) right = self.render_node(node.right) return f"((({left})%({right}))+({right}))%({right})" else: return super().render_BinOp(node) class CythonCodeGenerator(CodeGenerator): """ Cython code generator """ class_name = "cython" def __init__(self, *args, **kwds): self.temporary_vars = set() super().__init__(*args, **kwds) def translate_expression(self, expr): expr = word_substitute(expr, self.func_name_replacements) return ( CythonNodeRenderer(auto_vectorise=self.auto_vectorise) .render_expr(expr, self.variables) .strip() ) def translate_statement(self, statement): var, op, expr, comment = ( statement.var, statement.op, statement.expr, statement.comment, ) if op == ":=": # make no distinction in Cython (declaration are done elsewhere) op = "=" # For Cython we replace complex expressions involving boolean variables into a sequence of # if/then expressions with simpler expressions. This is provided by the optimise_statements # function. if ( statement.used_boolean_variables is not None and len(statement.used_boolean_variables) # todo: improve dtype analysis so that this isn't necessary and brian_dtype_from_dtype(statement.dtype) == "float" ): used_boolvars = statement.used_boolean_variables bool_simp = statement.boolean_simplified_expressions codelines = [] firstline = True # bool assigns is a sequence of (var, value) pairs giving the conditions under # which the simplified expression simp_expr holds for bool_assigns, simp_expr in bool_simp.items(): # generate a boolean expression like ``var1 and var2 and not var3`` atomics = [] for boolvar, boolval in bool_assigns: if boolval: atomics.append(boolvar) else: atomics.append(f"not {boolvar}") # use if/else/elif correctly if firstline: line = f"if {' and '.join(atomics)}:" else: if len(used_boolvars) > 1: line = f"elif {' and '.join(atomics)}:" else: line = "else:" line += "\n " line += f"{var} {op} {self.translate_expression(simp_expr)}" codelines.append(line) firstline = False code = "\n".join(codelines) else: code = f"{var} {op} {self.translate_expression(expr)}" if len(comment): code += f" # {comment}" return code def translate_one_statement_sequence(self, statements, scalar=False): # Note that we do not call this function from # `translate_statement_sequence` (which has been overwritten) # It is nevertheless implemented, so that it can be called explicitly # (e.g. from the GSL code generation) read, write, indices, conditional_write_vars = self.arrays_helper(statements) lines = [] # index and read arrays (index arrays first) lines += self.translate_to_read_arrays(read, indices) # the actual code lines += self.translate_to_statements(statements, conditional_write_vars) # write arrays lines += self.translate_to_write_arrays(write) return lines def translate_to_read_arrays(self, read, indices): lines = [] for varname in itertools.chain(sorted(indices), sorted(read)): var = self.variables[varname] index = self.variable_indices[varname] arrayname = self.get_array_name(var) line = f"{varname} = {arrayname}[{index}]" lines.append(line) return lines def translate_to_statements(self, statements, conditional_write_vars): lines = [] for stmt in statements: if stmt.op == ":=" and stmt.var not in self.variables: self.temporary_vars.add((stmt.var, stmt.dtype)) line = self.translate_statement(stmt) if stmt.var in conditional_write_vars: condvar = conditional_write_vars[stmt.var] lines.append(f"if {condvar}:") lines.append(indent(line)) else: lines.append(line) return lines def translate_to_write_arrays(self, write): lines = [] for varname in sorted(write): index_var = self.variable_indices[varname] var = self.variables[varname] line = ( f"{self.get_array_name(var, self.variables)}[{index_var}] = {varname}" ) lines.append(line) return lines def translate_statement_sequence(self, sc_statements, ve_statements): # This function is overwritten, since we do not want to completely # separate the code generation for scalar and vector code assert set(sc_statements.keys()) == set(ve_statements.keys()) sc_code = {} ve_code = {} for block_name in sc_statements: sc_block = sc_statements[block_name] ve_block = ve_statements[block_name] (sc_read, sc_write, sc_indices, sc_cond_write) = self.arrays_helper( sc_block ) (ve_read, ve_write, ve_indices, ve_cond_write) = self.arrays_helper( ve_block ) # We want to read all scalar variables that are needed in the # vector code already in the scalar code, if they are not written for varname in set(ve_read): var = self.variables[varname] if var.scalar and varname not in ve_write: sc_read.add(varname) ve_read.remove(varname) for code, stmts, read, write, indices, cond_write in [ (sc_code, sc_block, sc_read, sc_write, sc_indices, sc_cond_write), (ve_code, ve_block, ve_read, ve_write, ve_indices, ve_cond_write), ]: lines = [] # index and read arrays (index arrays first) lines += self.translate_to_read_arrays(read, indices) # the actual code lines += self.translate_to_statements(stmts, cond_write) # write arrays lines += self.translate_to_write_arrays(write) code[block_name] = "\n".join(lines) kwds = self.determine_keywords() return sc_code, ve_code, kwds def _add_user_function(self, varname, var, added): user_functions = [] load_namespace = [] support_code = [] impl = var.implementations[self.codeobj_class] if (impl.name, var) in added: return # nothing to do else: added.add((impl.name, var)) func_code = impl.get_code(self.owner) # Implementation can be None if the function is already # available in Cython (possibly under a different name) if func_code is not None: if isinstance(func_code, str): # Function is provided as Cython code # To make namespace variables available to functions, we # create global variables and assign to them in the main # code user_functions.append((varname, var)) func_namespace = impl.get_namespace(self.owner) or {} for ns_key, ns_value in func_namespace.items(): load_namespace.append(f"# namespace for function {varname}") if hasattr(ns_value, "dtype"): if ns_value.shape == (): raise NotImplementedError( "Directly replace scalar values in the function " "instead of providing them via the namespace" ) newlines = [ "global _namespace{var_name}", "global _namespace_num{var_name}", ( "cdef _numpy.ndarray[{cpp_dtype}, ndim=1, mode='c']" " _buf_{var_name} = _namespace['{var_name}']" ), ( "_namespace{var_name} = <{cpp_dtype} *>" " _buf_{var_name}.data" ), "_namespace_num{var_name} = len(_namespace['{var_name}'])", ] support_code.append( f"cdef {get_cpp_dtype(ns_value.dtype)} *_namespace{ns_key}" ) else: # e.g. a function newlines = ["_namespace{var_name} = namespace['{var_name}']"] for line in newlines: load_namespace.append( line.format( cpp_dtype=get_cpp_dtype(ns_value.dtype), numpy_dtype=get_numpy_dtype(ns_value.dtype), var_name=ns_key, ) ) # Rename references to any dependencies if necessary for dep_name, dep in impl.dependencies.items(): dep_impl = dep.implementations[self.codeobj_class] dep_impl_name = dep_impl.name if dep_impl_name is None: dep_impl_name = dep.pyfunc.__name__ if dep_name != dep_impl_name: func_code = word_substitute( func_code, {dep_name: dep_impl_name} ) support_code.append(deindent(func_code)) elif callable(func_code): self.variables[varname] = func_code line = f'{varname} = _namespace["{varname}"]' load_namespace.append(line) else: raise TypeError( "Provided function implementation for function " f"'{varname}' is neither a string nor callable (is " f"type {type(func_code)} instead)." ) dep_support_code = [] dep_load_namespace = [] dep_user_functions = [] if impl.dependencies is not None: for dep_name, dep in impl.dependencies.items(): if dep_name not in self.variables: self.variables[dep_name] = dep user_func = self._add_user_function(dep_name, dep, added) if user_func is not None: sc, ln, uf = user_func dep_support_code.extend(sc) dep_load_namespace.extend(ln) dep_user_functions.extend(uf) return ( support_code + dep_support_code, dep_load_namespace + load_namespace, dep_user_functions + user_functions, ) def determine_keywords(self): from brian2.devices.device import get_device device = get_device() # load variables from namespace load_namespace = [] support_code = [] handled_pointers = set() user_functions = [] added = set() for varname, var in sorted(self.variables.items()): if isinstance(var, Variable) and not isinstance( var, (Subexpression, AuxiliaryVariable) ): load_namespace.append(f'_var_{varname} = _namespace["_var_{varname}"]') if isinstance(var, AuxiliaryVariable): line = f"cdef {get_cpp_dtype(var.dtype)} {varname}" load_namespace.append(line) elif isinstance(var, Subexpression): dtype = get_cpp_dtype(var.dtype) line = f"cdef {dtype} {varname}" load_namespace.append(line) elif isinstance(var, Constant): dtype_name = get_cpp_dtype(var.value) line = f'cdef {dtype_name} {varname} = _namespace["{varname}"]' load_namespace.append(line) elif isinstance(var, Variable): if var.dynamic: pointer_name = self.get_array_name(var, False) load_namespace.append( f'{pointer_name} = _namespace["{pointer_name}"]' ) # This is the "true" array name, not the restricted pointer. array_name = device.get_array_name(var) pointer_name = self.get_array_name(var) if pointer_name in handled_pointers: continue if getattr(var, "ndim", 1) > 1: continue # multidimensional (dynamic) arrays have to be treated differently if get_dtype_str(var.dtype) == "bool": newlines = [ ( "cdef _numpy.ndarray[char, ndim=1, mode='c', cast=True]" " _buf_{array_name} = _namespace['{array_name}']" ), ( "cdef {cpp_dtype} * {array_name} = <{cpp_dtype} *>" " _buf_{array_name}.data" ), ] else: newlines = [ ( "cdef _numpy.ndarray[{cpp_dtype}, ndim=1, mode='c']" " _buf_{array_name} = _namespace['{array_name}']" ), ( "cdef {cpp_dtype} * {array_name} = <{cpp_dtype} *>" " _buf_{array_name}.data" ), ] if not var.scalar: newlines += [ "cdef size_t _num{array_name} = len(_namespace['{array_name}'])" ] if var.scalar and var.constant: newlines += ['cdef {cpp_dtype} {varname} = _namespace["{varname}"]'] else: newlines += ["cdef {cpp_dtype} {varname}"] for line in newlines: line = line.format( cpp_dtype=get_cpp_dtype(var.dtype), numpy_dtype=get_numpy_dtype(var.dtype), pointer_name=pointer_name, array_name=array_name, varname=varname, ) load_namespace.append(line) handled_pointers.add(pointer_name) elif isinstance(var, Function): user_func = self._add_user_function(varname, var, added) if user_func is not None: sc, ln, uf = user_func support_code.extend(sc) load_namespace.extend(ln) user_functions.extend(uf) else: # fallback to Python object load_namespace.append(f'{varname} = _namespace["{varname}"]') for varname, dtype in sorted(self.temporary_vars): cpp_dtype = get_cpp_dtype(dtype) line = f"cdef {cpp_dtype} {varname}" load_namespace.append(line) return { "load_namespace": "\n".join(load_namespace), "support_code_lines": support_code, } ############################################################################### # Implement functions ################################################################################ # Functions that exist under the same name in C++ for func in [ "sin", "cos", "tan", "sinh", "cosh", "tanh", "exp", "log", "log10", "sqrt", "ceil", "floor", "abs", ]: DEFAULT_FUNCTIONS[func].implementations.add_implementation( CythonCodeGenerator, code=None ) DEFAULT_FUNCTIONS["expm1"].implementations.add_implementation( CythonCodeGenerator, code=None, availability_check=C99Check("expm1") ) DEFAULT_FUNCTIONS["log1p"].implementations.add_implementation( CythonCodeGenerator, code=None, availability_check=C99Check("log1p") ) # Functions that need a name translation for func, func_cpp in [ ("arcsin", "asin"), ("arccos", "acos"), ("arctan", "atan"), ("int", "int_"), # from stdint_compat.h ]: DEFAULT_FUNCTIONS[func].implementations.add_implementation( CythonCodeGenerator, code=None, name=func_cpp ) exprel_code = """ cdef inline double _exprel(double x) nogil: if fabs(x) < 1e-16: return 1.0 elif x > 717: # near log(DBL_MAX) return NPY_INFINITY else: return expm1(x) / x """ DEFAULT_FUNCTIONS["exprel"].implementations.add_implementation( CythonCodeGenerator, code=exprel_code, name="_exprel", availability_check=C99Check("exprel"), ) _BUFFER_SIZE = 20000 rand_code = """ cdef double _rand(int _idx): cdef double **buffer_pointer = _namespace_rand_buffer cdef double *buffer = buffer_pointer[0] cdef _numpy.ndarray _new_rand if(_namespace_rand_buffer_index[0] == 0): if buffer != NULL: free(buffer) _new_rand = _numpy.random.rand(_BUFFER_SIZE) buffer = _numpy.PyArray_DATA(_new_rand) PyArray_CLEARFLAGS(<_numpy.PyArrayObject*>_new_rand, _numpy.NPY_OWNDATA) buffer_pointer[0] = buffer cdef double val = buffer[_namespace_rand_buffer_index[0]] _namespace_rand_buffer_index[0] += 1 if _namespace_rand_buffer_index[0] == _BUFFER_SIZE: _namespace_rand_buffer_index[0] = 0 return val """.replace( "_BUFFER_SIZE", str(_BUFFER_SIZE) ) randn_code = rand_code.replace("rand", "randn").replace("randnom", "random") poisson_code = """ cdef double _loggam(double x): cdef double x0, x2, xp, gl, gl0 cdef int32_t k, n cdef double a[10] a[:] = [8.333333333333333e-02, -2.777777777777778e-03, 7.936507936507937e-04, -5.952380952380952e-04, 8.417508417508418e-04, -1.917526917526918e-03, 6.410256410256410e-03, -2.955065359477124e-02, 1.796443723688307e-01, -1.39243221690590e+00] x0 = x n = 0 if (x == 1.0) or (x == 2.0): return 0.0 elif x <= 7.0: n = (7 - x) x0 = x + n x2 = 1.0 / (x0 * x0) xp = 2 * M_PI gl0 = a[9] for k in range(8, -1, -1): gl0 *= x2 gl0 += a[k] gl = gl0 / x0 + 0.5 * log(xp) + (x0 - 0.5) * log(x0) - x0 if x <= 7.0: for k in range(1, n+1): gl -= log(x0 - 1.0) x0 -= 1.0 return gl cdef int32_t _poisson_mult(double lam, int _vectorisation_idx): cdef int32_t X cdef double prod, U, enlam enlam = exp(-lam) X = 0 prod = 1.0 while True: U = _rand(_vectorisation_idx) prod *= U if (prod > enlam): X += 1 else: return X cdef int32_t _poisson_ptrs(double lam, int _vectorisation_idx): cdef int32_t k cdef double U, V, slam, loglam, a, b, invalpha, vr, us slam = sqrt(lam) loglam = log(lam) b = 0.931 + 2.53 * slam a = -0.059 + 0.02483 * b invalpha = 1.1239 + 1.1328 / (b - 3.4) vr = 0.9277 - 3.6224 / (b - 2) while True: U = _rand(_vectorisation_idx) - 0.5 V = _rand(_vectorisation_idx) us = 0.5 - abs(U) k = floor((2 * a / us + b) * U + lam + 0.43) if (us >= 0.07) and (V <= vr): return k if ((k < 0) or ((us < 0.013) and (V > us))): continue if ((log(V) + log(invalpha) - log(a / (us * us) + b)) <= (-lam + k * loglam - _loggam(k + 1))): return k cdef int32_t _poisson(double lam, int32_t _idx): if lam >= 10: return _poisson_ptrs(lam, _idx) elif lam == 0: return 0 else: return _poisson_mult(lam, _idx) """ device = all_devices["runtime"] DEFAULT_FUNCTIONS["rand"].implementations.add_implementation( CythonCodeGenerator, code=rand_code, name="_rand", namespace={ "_rand_buffer": device.rand_buffer, "_rand_buffer_index": device.rand_buffer_index, }, ) DEFAULT_FUNCTIONS["randn"].implementations.add_implementation( CythonCodeGenerator, code=randn_code, name="_randn", namespace={ "_randn_buffer": device.randn_buffer, "_randn_buffer_index": device.randn_buffer_index, }, ) DEFAULT_FUNCTIONS["poisson"].implementations.add_implementation( CythonCodeGenerator, code=poisson_code, name="_poisson", dependencies={"_rand": DEFAULT_FUNCTIONS["rand"]}, ) sign_code = """ ctypedef fused _to_sign: char short int long float double cdef int _sign(_to_sign x): return (0 < x) - (x < 0) """ DEFAULT_FUNCTIONS["sign"].implementations.add_implementation( CythonCodeGenerator, code=sign_code, name="_sign" ) clip_code = """ ctypedef fused _to_clip: char short int long float double cdef _to_clip _clip(_to_clip x, double low, double high): if x < low: return <_to_clip?>low if x > high: return <_to_clip?>high return x """ DEFAULT_FUNCTIONS["clip"].implementations.add_implementation( CythonCodeGenerator, code=clip_code, name="_clip" ) timestep_code = """ cdef int64_t _timestep(double t, double dt): return ((t + 1e-3*dt)/dt) """ DEFAULT_FUNCTIONS["timestep"].implementations.add_implementation( CythonCodeGenerator, code=timestep_code, name="_timestep" ) brian2-2.5.4/brian2/codegen/generators/numpy_generator.py000066400000000000000000000364411445201106100234230ustar00rootroot00000000000000import itertools import numpy as np from brian2.core.functions import DEFAULT_FUNCTIONS, timestep from brian2.core.variables import ArrayVariable from brian2.parsing.bast import brian_dtype_from_dtype from brian2.parsing.rendering import NumpyNodeRenderer from brian2.utils.logger import get_logger from brian2.utils.stringtools import get_identifiers, indent, word_substitute from .base import CodeGenerator __all__ = ["NumpyCodeGenerator"] logger = get_logger(__name__) class VectorisationError(Exception): pass class NumpyCodeGenerator(CodeGenerator): """ Numpy language Essentially Python but vectorised. """ class_name = "numpy" _use_ufunc_at_vectorisation = True # allow this to be off for testing only def translate_expression(self, expr): expr = word_substitute(expr, self.func_name_replacements) return ( NumpyNodeRenderer(auto_vectorise=self.auto_vectorise) .render_expr(expr, self.variables) .strip() ) def translate_statement(self, statement): # TODO: optimisation, translate arithmetic to a sequence of inplace # operations like a=b+c -> add(b, c, a) var, op, expr, comment = ( statement.var, statement.op, statement.expr, statement.comment, ) if op == ":=": op = "=" # For numpy we replace complex expressions involving a single boolean variable into a # where(boolvar, expr_if_true, expr_if_false) if ( statement.used_boolean_variables is not None and len(statement.used_boolean_variables) == 1 and brian_dtype_from_dtype(statement.dtype) == "float" and statement.complexity_std > sum(statement.complexities.values()) ): used_boolvars = statement.used_boolean_variables bool_simp = statement.boolean_simplified_expressions boolvar = used_boolvars[0] for bool_assigns, simp_expr in bool_simp.items(): _, boolval = bool_assigns[0] if boolval: expr_true = simp_expr else: expr_false = simp_expr code = f"{var} {op} _numpy.where({boolvar}, {expr_true}, {expr_false})" else: code = f"{var} {op} {self.translate_expression(expr)}" if len(comment): code += f" # {comment}" return code def ufunc_at_vectorisation( self, statement, variables, indices, conditional_write_vars, created_vars, used_variables, ): if not self._use_ufunc_at_vectorisation: raise VectorisationError() # Avoids circular import from brian2.devices.device import device # See https://github.com/brian-team/brian2/pull/531 for explanation used = set(get_identifiers(statement.expr)) used = used.intersection( k for k in list(variables.keys()) if k in indices and indices[k] != "_idx" ) used_variables.update(used) if statement.var in used_variables: raise VectorisationError() expr = NumpyNodeRenderer(auto_vectorise=self.auto_vectorise).render_expr( statement.expr ) if ( statement.op == ":=" or indices[statement.var] == "_idx" or not statement.inplace ): if statement.op == ":=": op = "=" else: op = statement.op line = f"{statement.var} {op} {expr}" elif statement.inplace: if statement.op == "+=": ufunc_name = "_numpy.add" elif statement.op == "*=": ufunc_name = "_numpy.multiply" elif statement.op == "/=": ufunc_name = "_numpy.divide" elif statement.op == "-=": ufunc_name = "_numpy.subtract" else: raise VectorisationError() array_name = device.get_array_name(variables[statement.var]) idx = indices[statement.var] line = f"{ufunc_name}.at({array_name}, {idx}, {expr})" line = self.conditional_write( line, statement, variables, conditional_write_vars=conditional_write_vars, created_vars=created_vars, ) else: raise VectorisationError() if len(statement.comment): line += f" # {statement.comment}" return line def vectorise_code(self, statements, variables, variable_indices, index="_idx"): created_vars = {stmt.var for stmt in statements if stmt.op == ":="} try: lines = [] used_variables = set() for statement in statements: lines.append( "# Abstract code: " f" {statement.var} {statement.op} {statement.expr}" ) # We treat every statement individually with its own read and write code # to be on the safe side read, write, indices, conditional_write_vars = self.arrays_helper( [statement] ) # We make sure that we only add code to `lines` after it went # through completely ufunc_lines = [] # No need to load a variable if it is only in read because of # the in-place operation if ( statement.inplace and variable_indices[statement.var] != "_idx" and statement.var not in get_identifiers(statement.expr) ): read = read - {statement.var} ufunc_lines.extend( self.read_arrays(read, write, indices, variables, variable_indices) ) ufunc_lines.append( self.ufunc_at_vectorisation( statement, variables, variable_indices, conditional_write_vars, created_vars, used_variables, ) ) # Do not write back such values, the ufuncs have modified the # underlying array already if statement.inplace and variable_indices[statement.var] != "_idx": write = write - {statement.var} ufunc_lines.extend( self.write_arrays( [statement], read, write, variables, variable_indices ) ) lines.extend(ufunc_lines) except VectorisationError: if self._use_ufunc_at_vectorisation: logger.info( "Failed to vectorise code, falling back on Python loop: note that" " this will be very slow! Switch to another code generation target" " for best performance (e.g. cython). First line is: " + str(statements[0]), once=True, ) lines = [] lines.extend( [ "_full_idx = _idx", "for _idx in _full_idx:", " _vectorisation_idx = _idx", ] ) read, write, indices, conditional_write_vars = self.arrays_helper( statements ) lines.extend( indent(code) for code in self.read_arrays( read, write, indices, variables, variable_indices ) ) for statement in statements: line = self.translate_statement(statement) if statement.var in conditional_write_vars: lines.append(indent(f"if {conditional_write_vars[statement.var]}:")) lines.append(indent(line, 2)) else: lines.append(indent(line)) lines.extend( indent(code) for code in self.write_arrays( statements, read, write, variables, variable_indices ) ) return lines def read_arrays(self, read, write, indices, variables, variable_indices): # index and read arrays (index arrays first) lines = [] for varname in itertools.chain(indices, read): var = variables[varname] index = variable_indices[varname] # if index in iterate_all: # line = '{varname} = {array_name}' # else: # line = '{varname} = {array_name}.take({index})' # line = line.format(varname=varname, array_name=self.get_array_name(var), index=index) line = f"{varname} = {self.get_array_name(var)}" if index not in self.iterate_all: line += f"[{index}]" elif varname in write: # avoid potential issues with aliased variables, see github #259 line += ".copy()" lines.append(line) return lines def write_arrays(self, statements, read, write, variables, variable_indices): # write arrays lines = [] for varname in write: var = variables[varname] index_var = variable_indices[varname] # check if all operations were inplace and we're operating on the # whole vector, if so we don't need to write the array back if index_var not in self.iterate_all or varname in read: all_inplace = False else: all_inplace = True for stmt in statements: if stmt.var == varname and not stmt.inplace: all_inplace = False break if not all_inplace: line = self.get_array_name(var) if index_var in self.iterate_all: line = f"{line}[:]" else: line = f"{line}[{index_var}]" line = f"{line} = {varname}" lines.append(line) return lines def conditional_write( self, line, stmt, variables, conditional_write_vars, created_vars ): if stmt.var in conditional_write_vars: subs = {} index = conditional_write_vars[stmt.var] # we replace all var with var[index], but actually we use this repl_string first because # we don't want to end up with lines like x[not_refractory[not_refractory]] when # multiple substitution passes are invoked repl_string = ( # this string shouldn't occur anywhere I hope! :) "#$(@#&$@$*U#@)$@(#" ) for varname, var in list(variables.items()): if isinstance(var, ArrayVariable) and not var.scalar: subs[varname] = f"{varname}[{repl_string}]" # all newly created vars are arrays and will need indexing for varname in created_vars: subs[varname] = f"{varname}[{repl_string}]" # Also index _vectorisation_idx so that e.g. rand() works correctly subs["_vectorisation_idx"] = f"_vectorisation_idx[{repl_string}]" line = word_substitute(line, subs) line = line.replace(repl_string, index) return line def translate_one_statement_sequence(self, statements, scalar=False): variables = self.variables variable_indices = self.variable_indices read, write, indices, conditional_write_vars = self.arrays_helper(statements) lines = [] all_unique = not self.has_repeated_indices(statements) if scalar or all_unique: # Simple translation lines.extend( self.read_arrays(read, write, indices, variables, variable_indices) ) created_vars = {stmt.var for stmt in statements if stmt.op == ":="} for stmt in statements: line = self.translate_statement(stmt) line = self.conditional_write( line, stmt, variables, conditional_write_vars, created_vars ) lines.append(line) lines.extend( self.write_arrays(statements, read, write, variables, variable_indices) ) else: # More complex translation to deal with repeated indices lines.extend(self.vectorise_code(statements, variables, variable_indices)) return lines ################################################################################ # Implement functions ################################################################################ # Functions that exist under the same name in numpy for func_name, func in [ ("sin", np.sin), ("cos", np.cos), ("tan", np.tan), ("sinh", np.sinh), ("cosh", np.cosh), ("tanh", np.tanh), ("exp", np.exp), ("log", np.log), ("log10", np.log10), ("sqrt", np.sqrt), ("arcsin", np.arcsin), ("arccos", np.arccos), ("arctan", np.arctan), ("abs", np.abs), ("sign", np.sign), ]: DEFAULT_FUNCTIONS[func_name].implementations.add_implementation( NumpyCodeGenerator, code=func ) # Functions that are implemented in a somewhat special way def randn_func(vectorisation_idx): try: N = len(vectorisation_idx) return np.random.randn(N) except TypeError: # scalar value return np.random.randn() def rand_func(vectorisation_idx): try: N = len(vectorisation_idx) return np.random.rand(N) except TypeError: # scalar value return np.random.rand() def poisson_func(lam, vectorisation_idx): try: N = len(vectorisation_idx) return np.random.poisson(lam, size=N) except TypeError: # scalar value return np.random.poisson(lam) DEFAULT_FUNCTIONS["randn"].implementations.add_implementation( NumpyCodeGenerator, code=randn_func ) DEFAULT_FUNCTIONS["rand"].implementations.add_implementation( NumpyCodeGenerator, code=rand_func ) DEFAULT_FUNCTIONS["poisson"].implementations.add_implementation( NumpyCodeGenerator, code=poisson_func ) clip_func = lambda array, a_min, a_max: np.clip(array, a_min, a_max) DEFAULT_FUNCTIONS["clip"].implementations.add_implementation( NumpyCodeGenerator, code=clip_func ) int_func = lambda value: np.int32(value) DEFAULT_FUNCTIONS["int"].implementations.add_implementation( NumpyCodeGenerator, code=int_func ) ceil_func = lambda value: np.int32(np.ceil(value)) DEFAULT_FUNCTIONS["ceil"].implementations.add_implementation( NumpyCodeGenerator, code=ceil_func ) floor_func = lambda value: np.int32(np.floor(value)) DEFAULT_FUNCTIONS["floor"].implementations.add_implementation( NumpyCodeGenerator, code=floor_func ) # We need to explicitly add an implementation for the timestep function, # otherwise Brian would *add* units during simulation, thinking that the # timestep function would not work correctly otherwise. This would slow the # function down significantly. DEFAULT_FUNCTIONS["timestep"].implementations.add_implementation( NumpyCodeGenerator, code=timestep ) brian2-2.5.4/brian2/codegen/get_cpu_flags.py000066400000000000000000000007721445201106100206340ustar00rootroot00000000000000""" This script is used to ask for the CPU flags on Windows. We use this instead of importing the cpuinfo package, because recent versions of py-cpuinfo use the multiprocessing module, and any import of cpuinfo that is not within a `if __name__ == '__main__':` block will lead to the script being executed twice. The CPU flags are printed to stdout encoded as JSON. """ import json if __name__ == "__main__": import cpuinfo flags = cpuinfo.get_cpu_info()["flags"] print(json.dumps(flags)) brian2-2.5.4/brian2/codegen/optimisation.py000066400000000000000000000651071445201106100205540ustar00rootroot00000000000000""" Simplify and optimise sequences of statements by rewriting and pulling out loop invariants. """ import ast import copy import itertools from collections import OrderedDict from functools import reduce from brian2.core.functions import DEFAULT_CONSTANTS, DEFAULT_FUNCTIONS from brian2.core.preferences import prefs from brian2.core.variables import AuxiliaryVariable from brian2.parsing.bast import ( BrianASTRenderer, brian_ast, brian_dtype_from_dtype, dtype_hierarchy, ) from brian2.parsing.rendering import NodeRenderer, get_node_value from brian2.utils.stringtools import get_identifiers, word_substitute from .statements import Statement # Default namespace has all the standard functions and constants in it defaults_ns = {k: v.pyfunc for k, v in DEFAULT_FUNCTIONS.items()} defaults_ns.update({k: v.value for k, v in DEFAULT_CONSTANTS.items()}) __all__ = ["optimise_statements", "ArithmeticSimplifier", "Simplifier"] def evaluate_expr(expr, ns): """ Try to evaluate the expression in the given namespace Returns either (value, True) if successful, or (expr, False) otherwise. Examples -------- >>> assumptions = {'exp': DEFAULT_FUNCTIONS['exp'].pyfunc, ... 'inf': DEFAULT_CONSTANTS['inf'].value} >>> evaluate_expr('1/2', assumptions) (0.5, True) >>> evaluate_expr('exp(-inf)', assumptions) (0.0, True) >>> evaluate_expr('sin(2*pi*freq*t)', assumptions) ('sin(2*pi*freq*t)', False) >>> evaluate_expr('1/0', assumptions) ('1/0', False) """ try: val = eval(expr, ns) return val, True except (NameError, ArithmeticError): return expr, False def expression_complexity(expr, variables): return brian_ast(expr, variables).complexity def optimise_statements(scalar_statements, vector_statements, variables, blockname=""): """ Optimise a sequence of scalar and vector statements Performs the following optimisations: 1. Constant evaluations (e.g. exp(0) to 1). See `evaluate_expr`. 2. Arithmetic simplifications (e.g. 0*x to 0). See `ArithmeticSimplifier`, `collect`. 3. Pulling out loop invariants (e.g. v*exp(-dt/tau) to a=exp(-dt/tau) outside the loop and v*a inside). See `Simplifier`. 4. Boolean simplifications (allowing the replacement of expressions with booleans with a sequence of if/thens). See `Simplifier`. Parameters ---------- scalar_statements : sequence of Statement Statements that only involve scalar values and should be evaluated in the scalar block. vector_statements : sequence of Statement Statements that involve vector values and should be evaluated in the vector block. variables : dict of (str, Variable) Definition of the types of the variables. blockname : str, optional Name of the block (used for LIO constant prefixes to avoid name clashes) Returns ------- new_scalar_statements : sequence of Statement As above but with loop invariants pulled out from vector statements new_vector_statements : sequence of Statement Simplified/optimised versions of statements """ boolvars = { k: v for k, v in variables.items() if hasattr(v, "dtype") and brian_dtype_from_dtype(v.dtype) == "boolean" } # We use the Simplifier class by rendering each expression, which generates new scalar statements # stored in the Simplifier object, and these are then added to the scalar statements. simplifier = Simplifier(variables, scalar_statements, extra_lio_prefix=blockname) new_vector_statements = [] for stmt in vector_statements: # Carry out constant evaluation, arithmetic simplification and loop invariants new_expr = simplifier.render_expr(stmt.expr) new_stmt = Statement( stmt.var, stmt.op, new_expr, stmt.comment, dtype=stmt.dtype, constant=stmt.constant, subexpression=stmt.subexpression, scalar=stmt.scalar, ) # Now check if boolean simplification can be carried out complexity_std = expression_complexity(new_expr, simplifier.variables) idents = get_identifiers(new_expr) used_boolvars = [var for var in boolvars if var in idents] if len(used_boolvars): # We want to iterate over all the possible assignments of boolean variables to values in (True, False) bool_space = [[False, True] for _ in used_boolvars] expanded_expressions = {} complexities = {} for bool_vals in itertools.product(*bool_space): # substitute those values into the expr and simplify (including potentially pulling out new # loop invariants) subs = {var: str(val) for var, val in zip(used_boolvars, bool_vals)} curexpr = word_substitute(new_expr, subs) curexpr = simplifier.render_expr(curexpr) key = tuple((var, val) for var, val in zip(used_boolvars, bool_vals)) expanded_expressions[key] = curexpr complexities[key] = expression_complexity(curexpr, simplifier.variables) # See Statement for details on these new_stmt.used_boolean_variables = used_boolvars new_stmt.boolean_simplified_expressions = expanded_expressions new_stmt.complexity_std = complexity_std new_stmt.complexities = complexities new_vector_statements.append(new_stmt) # Generate additional scalar statements for the loop invariants new_scalar_statements = copy.copy(scalar_statements) for expr, name in simplifier.loop_invariants.items(): dtype_name = simplifier.loop_invariant_dtypes[name] if dtype_name == "boolean": dtype = bool elif dtype_name == "integer": dtype = int else: dtype = prefs.core.default_float_dtype new_stmt = Statement( name, ":=", expr, "", dtype=dtype, constant=True, subexpression=False, scalar=True, ) new_scalar_statements.append(new_stmt) return new_scalar_statements, new_vector_statements def _replace_with_zero(zero_node, node): """ Helper function to return a "zero node" of the correct type. Parameters ---------- zero_node : `ast.Num` The node to replace node : `ast.Node` The node that determines the type Returns ------- zero_node : `ast.Num` The original ``zero_node`` with its value replaced by 0 or 0.0. """ # must not change the dtype of the output, # e.g. handle 0/float->0.0 and 0.0/int->0.0 zero_node.dtype = node.dtype if node.dtype == "integer": zero_node.value = 0 else: zero_node.value = prefs.core.default_float_dtype(0.0) return zero_node class ArithmeticSimplifier(BrianASTRenderer): """ Carries out the following arithmetic simplifications: 1. Constant evaluation (e.g. exp(0)=1) by attempting to evaluate the expression in an "assumptions namespace" 2. Binary operators, e.g. 0*x=0, 1*x=x, etc. You have to take care that the dtypes match here, e.g. if x is an integer, then 1.0*x shouldn't be replaced with x but left as 1.0*x. Parameters ---------- variables : dict of (str, Variable) Usual definition of variables. assumptions : sequence of str Additional assumptions that can be used in simplification, each assumption is a string statement. These might be the scalar statements for example. """ def __init__(self, variables): BrianASTRenderer.__init__(self, variables, copy_variables=False) self.assumptions = [] self.assumptions_ns = dict(defaults_ns) self.bast_renderer = BrianASTRenderer(variables, copy_variables=False) def render_node(self, node): """ Assumes that the node has already been fully processed by BrianASTRenderer """ if not hasattr(node, "simplified"): node = super().render_node(node) node.simplified = True # can't evaluate vector expressions, so abandon in this case if not node.scalar: return node # No evaluation necessary for simple names or numbers if node.__class__.__name__ in ["Name", "NameConstant", "Num", "Constant"]: return node # Don't evaluate stateful nodes (e.g. those containing a rand() call) if not node.stateless: return node # try fully evaluating using assumptions expr = NodeRenderer().render_node(node) val, evaluated = evaluate_expr(expr, self.assumptions_ns) if evaluated: if node.dtype == "boolean": val = bool(val) if hasattr(ast, "Constant"): newnode = ast.Constant(val) elif hasattr(ast, "NameConstant"): newnode = ast.NameConstant(val) else: # None is the expression context, we don't use it so we just set to None newnode = ast.Name(repr(val), None) elif node.dtype == "integer": val = int(val) else: val = prefs.core.default_float_dtype(val) if node.dtype != "boolean": if hasattr(ast, "Constant"): newnode = ast.Constant(val) else: newnode = ast.Num(val) newnode.dtype = node.dtype newnode.scalar = True newnode.stateless = node.stateless newnode.complexity = 0 return newnode return node def render_BinOp(self, node): if node.dtype == "float": # only try to collect float type nodes if node.op.__class__.__name__ in [ "Mult", "Div", "Add", "Sub", ] and not hasattr(node, "collected"): newnode = self.bast_renderer.render_node(collect(node)) newnode.collected = True return self.render_node(newnode) left = node.left = self.render_node(node.left) right = node.right = self.render_node(node.right) node = super().render_BinOp(node) op = node.op # Handle multiplication by 0 or 1 if op.__class__.__name__ == "Mult": for operand, other in [(left, right), (right, left)]: if operand.__class__.__name__ in ["Num", "Constant"]: op_value = get_node_value(operand) if op_value == 0: # Do not remove stateful functions if node.stateless: return _replace_with_zero(operand, node) if op_value == 1: # only simplify this if the type wouldn't be cast by the operation if ( dtype_hierarchy[operand.dtype] <= dtype_hierarchy[other.dtype] ): return other # Handle division by 1, or 0/x elif op.__class__.__name__ == "Div": if ( left.__class__.__name__ in ["Num", "Constant"] and get_node_value(left) == 0 ): # 0/x if node.stateless: # Do not remove stateful functions return _replace_with_zero(left, node) if ( right.__class__.__name__ in ["Num", "Constant"] and get_node_value(right) == 1 ): # x/1 # only simplify this if the type wouldn't be cast by the operation if dtype_hierarchy[right.dtype] <= dtype_hierarchy[left.dtype]: return left elif op.__class__.__name__ == "FloorDiv": if ( left.__class__.__name__ in ["Num", "Constant"] and get_node_value(left) == 0 ): # 0//x if node.stateless: # Do not remove stateful functions return _replace_with_zero(left, node) # Only optimise floor division by 1 if both numbers are integers, # for floating point values, floor division by 1 changes the value, # and division by 1.0 can change the type for an integer value if ( left.dtype == right.dtype == "integer" and right.__class__.__name__ in ["Num", "Constant"] and get_node_value(right) == 1 ): # x//1 return left # Handle addition of 0 elif op.__class__.__name__ == "Add": for operand, other in [(left, right), (right, left)]: if ( operand.__class__.__name__ in ["Num", "Constant"] and get_node_value(operand) == 0 ): # only simplify this if the type wouldn't be cast by the operation if dtype_hierarchy[operand.dtype] <= dtype_hierarchy[other.dtype]: return other # Handle subtraction of 0 elif op.__class__.__name__ == "Sub": if ( right.__class__.__name__ in ["Num", "Constant"] and get_node_value(right) == 0 ): # only simplify this if the type wouldn't be cast by the operation if dtype_hierarchy[right.dtype] <= dtype_hierarchy[left.dtype]: return left # simplify e.g. 2*float to 2.0*float to make things more explicit: not strictly necessary # but might be useful for some codegen targets if node.dtype == "float" and op.__class__.__name__ in [ "Mult", "Add", "Sub", "Div", ]: for subnode in [node.left, node.right]: if subnode.__class__.__name__ in ["Num", "Constant"] and not ( get_node_value(subnode) is True or get_node_value(subnode) is False ): subnode.dtype = "float" subnode.value = prefs.core.default_float_dtype( get_node_value(subnode) ) return node class Simplifier(BrianASTRenderer): """ Carry out arithmetic simplifications (see `ArithmeticSimplifier`) and loop invariants Parameters ---------- variables : dict of (str, Variable) Usual definition of variables. scalar_statements : sequence of Statement Predefined scalar statements that can be used as part of simplification Notes ----- After calling `render_expr` on a sequence of expressions (coming from vector statements typically), this object will have some new attributes: ``loop_invariants`` : OrderedDict of (expression, varname) varname will be of the form ``_lio_N`` where ``N`` is some integer, and the expressions will be strings that correspond to scalar-only expressions that can be evaluated outside of the vector block. ``loop_invariant_dtypes`` : dict of (varname, dtypename) dtypename will be one of ``'boolean'``, ``'integer'``, ``'float'``. """ def __init__(self, variables, scalar_statements, extra_lio_prefix=""): BrianASTRenderer.__init__(self, variables, copy_variables=False) self.loop_invariants = OrderedDict() self.loop_invariant_dtypes = {} self.value = 0 self.node_renderer = NodeRenderer() self.arithmetic_simplifier = ArithmeticSimplifier(variables) self.scalar_statements = scalar_statements if extra_lio_prefix is None: extra_lio_prefix = "" if len(extra_lio_prefix): extra_lio_prefix = f"{extra_lio_prefix}_" self.extra_lio_prefix = extra_lio_prefix def render_expr(self, expr): node = brian_ast(expr, self.variables) node = self.arithmetic_simplifier.render_node(node) node = self.render_node(node) return self.node_renderer.render_node(node) def render_node(self, node): """ Assumes that the node has already been fully processed by BrianASTRenderer """ # can we pull this out? if node.scalar and node.complexity > 0: expr = self.node_renderer.render_node( self.arithmetic_simplifier.render_node(node) ) if expr in self.loop_invariants: name = self.loop_invariants[expr] else: self.value += 1 name = f"_lio_{self.extra_lio_prefix}{str(self.value)}" self.loop_invariants[expr] = name self.loop_invariant_dtypes[name] = node.dtype numpy_dtype = { "boolean": bool, "integer": int, "float": prefs.core.default_float_dtype, }[node.dtype] self.variables[name] = AuxiliaryVariable( name, dtype=numpy_dtype, scalar=True ) # None is the expression context, we don't use it so we just set to None newnode = ast.Name(name, None) newnode.scalar = True newnode.dtype = node.dtype newnode.complexity = 0 newnode.stateless = node.stateless return newnode # otherwise, render node as usual return super().render_node(node) def reduced_node(terms, op): """ Reduce a sequence of terms with the given operator For examples, if terms were [a, b, c] and op was multiplication then the reduction would be (a*b)*c. Parameters ---------- terms : list AST nodes. op : AST node Could be `ast.Mult` or `ast.Add`. Examples -------- >>> import ast >>> nodes = [ast.Name(id='x'), ast.Name(id='y'), ast.Name(id='z')] >>> ast.dump(reduced_node(nodes, ast.Mult), annotate_fields=False) "BinOp(BinOp(Name('x'), Mult(), Name('y')), Mult(), Name('z'))" >>> nodes = [ast.Name(id='x')] >>> ast.dump(reduced_node(nodes, ast.Add), annotate_fields=False) "Name('x')" """ # Remove None terms terms = [term for term in terms if term is not None] if not len(terms): return None return reduce(lambda left, right: ast.BinOp(left, op(), right), terms) def cancel_identical_terms(primary, inverted): """ Cancel terms in a collection, e.g. a+b-a should be cancelled to b Simply renders the nodes into expressions and removes whenever there is a common expression in primary and inverted. Parameters ---------- primary : list of AST nodes These are the nodes that are positive with respect to the operator, e.g. in x*y/z it would be [x, y]. inverted : list of AST nodes These are the nodes that are inverted with respect to the operator, e.g. in x*y/z it would be [z]. Returns ------- primary : list of AST nodes Primary nodes after cancellation inverted : list of AST nodes Inverted nodes after cancellation """ nr = NodeRenderer() expressions = {node: nr.render_node(node) for node in primary} expressions.update({node: nr.render_node(node) for node in inverted}) new_primary = [] inverted_expressions = [expressions[term] for term in inverted] for term in primary: expr = expressions[term] if expr in inverted_expressions and term.stateless: new_inverted = [] for iterm in inverted: if expressions[iterm] == expr: expr = "" # handled else: new_inverted.append(iterm) inverted = new_inverted inverted_expressions = [expressions[term] for term in inverted] else: new_primary.append(term) return new_primary, inverted def collect(node): """ Attempts to collect commutative operations into one and simplifies them. For example, if x and y are scalars, and z is a vector, then (x*z)*y should be rewritten as (x*y)*z to minimise the number of vector operations. Similarly, ((x*2)*3)*4 should be rewritten as x*24. Works for either multiplication/division or addition/subtraction nodes. The final output is a subexpression of the following maximal form: (((numerical_value*(product of scalars))/(product of scalars))*(product of vectors))/(product of vectors) Any possible cancellations will have been done. Parameters ---------- node : Brian AST node The node to be collected/simplified. Returns ------- node : Brian AST node Simplified node. """ node.collected = True orignode_dtype = node.dtype # we only work on */ or +- ops, which are both BinOp if node.__class__.__name__ != "BinOp": return node # primary would be the * or + nodes, and inverted would be the / or - nodes terms_primary = [] terms_inverted = [] # we handle both multiplicative and additive nodes in the same way by using these variables if node.op.__class__.__name__ in ["Mult", "Div"]: op_primary = ast.Mult op_inverted = ast.Div op_null = prefs.core.default_float_dtype(1.0) # the identity for the operator op_py_primary = lambda x, y: x * y op_py_inverted = lambda x, y: x / y elif node.op.__class__.__name__ in ["Add", "Sub"]: op_primary = ast.Add op_inverted = ast.Sub op_null = prefs.core.default_float_dtype(0.0) op_py_primary = lambda x, y: x + y op_py_inverted = lambda x, y: x - y else: return node if node.dtype == "integer": op_null_with_dtype = int(op_null) else: op_null_with_dtype = op_null # recursively collect terms into the terms_primary and terms_inverted lists collect_commutative(node, op_primary, op_inverted, terms_primary, terms_inverted) x = op_null # extract the numerical nodes and fully evaluate remaining_terms_primary = [] remaining_terms_inverted = [] for term in terms_primary: if term.__class__.__name__ == "Num": x = op_py_primary(x, term.n) elif term.__class__.__name__ == "Constant": x = op_py_primary(x, term.value) else: remaining_terms_primary.append(term) for term in terms_inverted: if term.__class__.__name__ == "Num": x = op_py_inverted(x, term.n) elif term.__class__.__name__ == "Constant": x = op_py_inverted(x, term.value) else: remaining_terms_inverted.append(term) # if the fully evaluated node is just the identity/null element then we # don't have to make it into an explicit term if x != op_null: if hasattr(ast, "Constant"): num_node = ast.Constant(x) else: num_node = ast.Num(x) else: num_node = None terms_primary = remaining_terms_primary terms_inverted = remaining_terms_inverted node = num_node for scalar in (True, False): primary_terms = [term for term in terms_primary if term.scalar == scalar] inverted_terms = [term for term in terms_inverted if term.scalar == scalar] primary_terms, inverted_terms = cancel_identical_terms( primary_terms, inverted_terms ) # produce nodes that are the reduction of the operator on these subsets prod_primary = reduced_node(primary_terms, op_primary) prod_inverted = reduced_node(inverted_terms, op_primary) # construct the simplest version of the fully simplified node (only doing operations where necessary) node = reduced_node([node, prod_primary], op_primary) if prod_inverted is not None: if node is None: if hasattr(ast, "Constant"): node = ast.Constant(op_null_with_dtype) else: node = ast.Num(op_null_with_dtype) node = ast.BinOp(node, op_inverted(), prod_inverted) if node is None: # everything cancelled if hasattr(ast, "Constant"): node = ast.Constant(op_null_with_dtype) else: node = ast.Num(op_null_with_dtype) if ( hasattr(node, "dtype") and dtype_hierarchy[node.dtype] < dtype_hierarchy[orignode_dtype] ): node = ast.BinOp(ast.Num(op_null_with_dtype), op_primary(), node) node.collected = True return node def collect_commutative( node, primary, inverted, terms_primary, terms_inverted, add_to_inverted=False ): # This function is called recursively, so we use add_to_inverted to keep track of whether or not # we're working in the numerator/denominator (for multiplicative nodes, equivalent for additive). op_primary = node.op.__class__ is primary # this should only be called with node a BinOp of type primary or inverted # left_exact is the condition that we can collect terms (we can do it with floats or add/sub, # but not integer mult/div - the reason being that for C-style division e.g. 3/(4/3)!=(3*3)/4 left_exact = node.left.dtype == "float" or ( hasattr(node.left, "op") and node.left.op.__class__.__name__ in ["Add", "Sub"] ) if ( node.left.__class__.__name__ == "BinOp" and node.left.op.__class__ in [primary, inverted] and left_exact ): collect_commutative( node.left, primary, inverted, terms_primary, terms_inverted, add_to_inverted=add_to_inverted, ) else: if add_to_inverted: terms_inverted.append(node.left) else: terms_primary.append(node.left) right_exact = node.right.dtype == "float" or ( hasattr(node.right, "op") and node.right.op.__class__.__name__ in ["Add", "Sub"] ) if ( node.right.__class__.__name__ == "BinOp" and node.right.op.__class__ in [primary, inverted] and right_exact ): if node.op.__class__ is primary: collect_commutative( node.right, primary, inverted, terms_primary, terms_inverted, add_to_inverted=add_to_inverted, ) else: collect_commutative( node.right, primary, inverted, terms_primary, terms_inverted, add_to_inverted=not add_to_inverted, ) else: if (not add_to_inverted and op_primary) or (add_to_inverted and not op_primary): terms_primary.append(node.right) else: terms_inverted.append(node.right) brian2-2.5.4/brian2/codegen/permutation_analysis.py000066400000000000000000000116331445201106100223020ustar00rootroot00000000000000""" Module for analysing synaptic pre and post code for synapse order independence. """ from brian2.core.functions import Function from brian2.core.variables import Constant from brian2.utils.stringtools import get_identifiers __all__ = ["OrderDependenceError", "check_for_order_independence"] class OrderDependenceError(Exception): pass def check_for_order_independence(statements, variables, indices): """ Check that the sequence of statements doesn't depend on the order in which the indices are iterated through. """ # Remove stateless functions from variables (only bother with ones that are used) all_used_vars = set() for statement in statements: all_used_vars.update(get_identifiers(statement.expr)) variables = variables.copy() for var in set(variables.keys()).intersection(all_used_vars): val = variables[var] if isinstance(val, Function): if val.stateless: del variables[var] else: raise OrderDependenceError( "Function %s may have internal state, " "which can lead to order dependence." % var ) all_variables = [v for v in variables if not isinstance(variables[v], Constant)] # Main index variables are those whose index corresponds to the main index being iterated through. By # assumption/definition, these indices are unique, and any order-dependence cannot come from their values, # only from the values of the derived indices. In the most common case of Synapses, the main index would be # the synapse index, and the derived indices would be pre and postsynaptic indices (which can be repeated). unique_index = lambda v: ( indices[v] != "0" and getattr(variables[indices[v]], "unique", False) ) main_index_variables = { v for v in all_variables if indices[v] == "_idx" or unique_index(v) } different_index_variables = set(all_variables) - main_index_variables # At the start, we assume all the different/derived index variables are permutation independent and we continue # to scan through the list of statements checking whether or not permutation-dependence has been introduced # until the permutation_independent set has stopped changing. permutation_independent = list(different_index_variables) permutation_dependent_aux_vars = set() changed_permutation_independent = True for statement in statements: if statement.op == ":=" and statement.var not in all_variables: main_index_variables.add(statement.var) all_variables.append(statement.var) while changed_permutation_independent: changed_permutation_independent = False for statement in statements: vars_in_expr = get_identifiers(statement.expr).intersection(all_variables) # any time a statement involves a LHS and RHS which only depend on itself, this doesn't change anything if {statement.var} == vars_in_expr: continue nonsyn_vars_in_expr = vars_in_expr.intersection(different_index_variables) permdep = any( var not in permutation_independent for var in nonsyn_vars_in_expr ) permdep = permdep or any( var in permutation_dependent_aux_vars for var in vars_in_expr ) if statement.op == ":=": # auxiliary variable created if permdep: if statement.var not in permutation_dependent_aux_vars: permutation_dependent_aux_vars.add(statement.var) changed_permutation_independent = True continue elif statement.var in main_index_variables: if permdep: raise OrderDependenceError() elif statement.var in different_index_variables: if statement.op in ("+=", "*=", "-=", "/="): if permdep: raise OrderDependenceError() if statement.var in permutation_independent: permutation_independent.remove(statement.var) changed_permutation_independent = True elif statement.op == "=": otheridx = [ v for v in variables if indices[v] not in (indices[statement.var], "_idx", "0") ] if any(var in otheridx for var in vars_in_expr): raise OrderDependenceError() if permdep: raise OrderDependenceError() if any(var in main_index_variables for var in vars_in_expr): raise OrderDependenceError() else: raise OrderDependenceError() else: raise AssertionError("Should never get here...") brian2-2.5.4/brian2/codegen/runtime/000077500000000000000000000000001445201106100171355ustar00rootroot00000000000000brian2-2.5.4/brian2/codegen/runtime/GSLcython_rt/000077500000000000000000000000001445201106100215145ustar00rootroot00000000000000brian2-2.5.4/brian2/codegen/runtime/GSLcython_rt/GSLcython_rt.py000066400000000000000000000037041445201106100244510ustar00rootroot00000000000000""" Module containing the Cython CodeObject for code generation for integration using the ODE solver provided in the GNU Scientific Library (GSL) """ import sys from distutils.errors import CompileError from brian2.core.preferences import prefs from ...generators.cython_generator import CythonCodeGenerator from ...generators.GSL_generator import GSLCythonCodeGenerator from ..cython_rt import CythonCodeObject __all__ = ["GSLCythonCodeObject", "IntegrationError"] class GSLCompileError(Exception): pass class IntegrationError(Exception): """ Error used to signify that GSL was unable to complete integration (only works for cython) """ pass class GSLCythonCodeObject(CythonCodeObject): templater = CythonCodeObject.templater.derive("brian2.codegen.runtime.GSLcython_rt") # CodeGenerator that is used to do bulk of abstract_code --> language specific translation original_generator_class = CythonCodeGenerator generator_class = GSLCythonCodeGenerator def compile(self): self.libraries += ["gsl", "gslcblas"] self.headers += [ "", "", "", "", "", ] if sys.platform == "win32": self.define_macros += [("WIN32", "1"), ("GSL_DLL", "1")] if prefs.GSL.directory is not None: self.include_dirs += [prefs.GSL.directory] try: super().compile() except CompileError as err: raise GSLCompileError( "\nCompilation of files generated for integration with GSL has failed." "\nOne cause for this could be incorrect installation of GSL itself." "\nIf GSL is installed but Python cannot find the correct files, it is " "also possible to give the gsl directory manually by specifying " "prefs.GSL.directory = ..." ) from err brian2-2.5.4/brian2/codegen/runtime/GSLcython_rt/__init__.py000066400000000000000000000000341445201106100236220ustar00rootroot00000000000000from .GSLcython_rt import * brian2-2.5.4/brian2/codegen/runtime/GSLcython_rt/templates/000077500000000000000000000000001445201106100235125ustar00rootroot00000000000000brian2-2.5.4/brian2/codegen/runtime/GSLcython_rt/templates/stateupdate.pyx000066400000000000000000000115101445201106100265750ustar00rootroot00000000000000{# ITERATE_ALL { _idx } #} {# USES_VARIABLES { N } #} {# ALLOWS_SCALAR_WRITE #} {% extends 'common_group.pyx' %} {% block template_support_code %} from brian2.codegen.runtime.GSLcython_rt import IntegrationError from libc.stdlib cimport malloc, free cdef enum: GSL_SUCCESS = 0 cdef extern from "gsl/gsl_odeiv2.h": # gsl_odeiv2_system ctypedef struct gsl_odeiv2_system: int (* function) (double t, double _GSL_y[], double dydt[], void * params) int (* jacobian) (double t, double _GSL_y[], double * dfdy, double dfdt[], void * params) size_t dimension void * params ctypedef struct gsl_odeiv2_driver: gsl_odeiv2_system * sys gsl_odeiv2_step *s gsl_odeiv2_control *c gsl_odeiv2_evolve *e double h double hmin double hmax unsigned long int n unsigned long int nmax ctypedef struct gsl_odeiv2_evolve: size_t dimension double *y0 double *yerr double *dydt_in double *dydt_out double last_step unsigned long int count unsigned long int failed_steps const gsl_odeiv2_driver *driver ctypedef struct gsl_odeiv2_step ctypedef struct gsl_odeiv2_control ctypedef struct gsl_odeiv2_step_type gsl_odeiv2_step_type *gsl_odeiv2_step_{{GSL_settings['integrator']}} int gsl_odeiv2_driver_apply( gsl_odeiv2_driver *_GSL_driver, double *t, double t1, double _GSL_y[]) int gsl_odeiv2_driver_apply_fixed_step( gsl_odeiv2_driver *_GSL_driver, double *t, const double h, const unsigned long int n, double _GSL_y[]) int gsl_odeiv2_driver_reset_hstart( gsl_odeiv2_driver *_GSL_driver, const double hstart) int gsl_odeiv2_driver_reset( gsl_odeiv2_driver *GSL_driver) int gsl_odeiv2_driver_set_nmax( gsl_odeiv2_driver *GSL_driver, const unsigned long int nmax) int gsl_odeiv2_driver_set_hmax( gsl_odeiv2_driver *GSL_driver, const double hmax) gsl_odeiv2_driver *gsl_odeiv2_driver_alloc_scaled_new( gsl_odeiv2_system *_sys, gsl_odeiv2_step_type *T, double hstart, double epsabs, double epsrel, double a_y, double a_dydt, double scale[]) int gsl_odeiv2_driver_free(gsl_odeiv2_driver *_GSL_driver) {% endblock %} {% block maincode %} # scalar code _vectorisation_idx = 1 {% if define_dt %} dt = {{dt_array}} {% endif %} cdef double t1 cdef _dataholder * _GSL_dataholder = <_dataholder *>malloc(sizeof(_dataholder)) {{scalar_code['GSL']|autoindent}} cdef double _GSL_y[{{n_diff_vars}}] {{define_GSL_scale_array|autoindent}} cdef gsl_odeiv2_system _sys _sys.function = _GSL_func set_dimension(&_sys.dimension) _sys.params = _GSL_dataholder cdef gsl_odeiv2_driver * _GSL_driver = gsl_odeiv2_driver_alloc_scaled_new(&_sys,gsl_odeiv2_step_{{GSL_settings['integrator']}}, {{GSL_settings['dt_start']}},1, 0, 0, 0, _GSL_scale_array) gsl_odeiv2_driver_set_nmax(_GSL_driver, {{GSL_settings['max_steps']}}) gsl_odeiv2_driver_set_hmax(_GSL_driver, {{GSL_settings['dt_start']}}) # vector code for _idx in range(N): _vectorisation_idx = _idx t = {{t_array}} t1 = t + dt _GSL_dataholder._idx = _idx _fill_y_vector(_GSL_dataholder, _GSL_y, _idx) {%if GSL_settings['use_last_timestep']%} gsl_odeiv2_driver_reset_hstart(_GSL_driver, {{pointer_last_timestep}}) {% else %} gsl_odeiv2_driver_reset(_GSL_driver) {% endif %} if ({{'gsl_odeiv2_driver_apply(_GSL_driver, &t, t1, _GSL_y)' if GSL_settings['adaptable_timestep'] else 'gsl_odeiv2_driver_apply_fixed_step(_GSL_driver, &t, dt, 1, _GSL_y)'}} != GSL_SUCCESS): raise IntegrationError(("GSL integrator failed to integrate the equations." {% if GSL_settings['adaptable_timestep'] %} "\nThis means that the desired error cannot be achieved with the given maximum number of steps. " "Try using a larger error or a larger number of steps." {% else %} "\n This means that the size of the timestep results in an error larger than that set by absolute_error." {% endif %} )) _empty_y_vector(_GSL_dataholder, _GSL_y, _idx) {%if GSL_settings['use_last_timestep']%} {{pointer_last_timestep}} = _GSL_driver.h {% endif %} {%if GSL_settings['save_failed_steps']%} {{pointer_failed_steps}} = _GSL_driver.e.failed_steps {% endif %} {%if GSL_settings['save_step_count']%} {{pointer_step_count}} = _GSL_driver.n {% endif %} gsl_odeiv2_driver_free(_GSL_driver) {% endblock %} brian2-2.5.4/brian2/codegen/runtime/__init__.py000066400000000000000000000011021445201106100212400ustar00rootroot00000000000000""" Runtime targets for code generation. """ # Register the base category before importing the indivial codegen targets with # their subcategories from brian2.core.preferences import prefs from brian2.utils.logger import get_logger prefs.register_preferences( "codegen.runtime", "Runtime codegen preferences (see subcategories for individual targets)", ) logger = get_logger(__name__) from .numpy_rt import * try: from .cython_rt import * except ImportError: pass # todo: raise a warning? try: from .GSLcython_rt import * except ImportError: pass brian2-2.5.4/brian2/codegen/runtime/cython_rt/000077500000000000000000000000001445201106100211465ustar00rootroot00000000000000brian2-2.5.4/brian2/codegen/runtime/cython_rt/__init__.py000066400000000000000000000000311445201106100232510ustar00rootroot00000000000000from .cython_rt import * brian2-2.5.4/brian2/codegen/runtime/cython_rt/cython_rt.py000066400000000000000000000244371445201106100235430ustar00rootroot00000000000000import platform import numpy from brian2.core.base import BrianObjectException from brian2.core.functions import Function from brian2.core.preferences import BrianPreference, prefs from brian2.core.variables import ( ArrayVariable, AuxiliaryVariable, DynamicArrayVariable, Subexpression, ) from brian2.utils.logger import get_logger from brian2.utils.stringtools import get_identifiers from ...codeobject import check_compiler_kwds, constant_or_scalar from ...cpp_prefs import get_compiler_and_args from ...generators.cython_generator import ( CythonCodeGenerator, get_cpp_dtype, get_numpy_dtype, ) from ...targets import codegen_targets from ...templates import Templater from ..numpy_rt import NumpyCodeObject from .extension_manager import cython_extension_manager __all__ = ["CythonCodeObject"] logger = get_logger(__name__) # Preferences prefs.register_preferences( "codegen.runtime.cython", "Cython runtime codegen preferences", multiprocess_safe=BrianPreference( default=True, docs=""" Whether to use a lock file to prevent simultaneous write access to cython .pyx and .so files. """, ), cache_dir=BrianPreference( default=None, validator=lambda x: x is None or isinstance(x, str), docs=""" Location of the cache directory for Cython files. By default, will be stored in a ``brian_extensions`` subdirectory where Cython inline stores its temporary files (the result of ``get_cython_cache_dir()``). """, ), delete_source_files=BrianPreference( default=True, docs=""" Whether to delete source files after compiling. The Cython source files can take a significant amount of disk space, and are not used anymore when the compiled library file exists. They are therefore deleted by default, but keeping them around can be useful for debugging. """, ), ) class CythonCodeObject(NumpyCodeObject): """ Execute code using Cython. """ templater = Templater( "brian2.codegen.runtime.cython_rt", ".pyx", env_globals={ "cpp_dtype": get_cpp_dtype, "numpy_dtype": get_numpy_dtype, "dtype": numpy.dtype, "constant_or_scalar": constant_or_scalar, }, ) generator_class = CythonCodeGenerator class_name = "cython" def __init__( self, owner, code, variables, variable_indices, template_name, template_source, compiler_kwds, name="cython_code_object*", ): check_compiler_kwds( compiler_kwds, [ "libraries", "include_dirs", "library_dirs", "runtime_library_dirs", "sources", ], "Cython", ) super().__init__( owner, code, variables, variable_indices, template_name, template_source, compiler_kwds={}, # do not pass the actual args name=name, ) self.compiler, self.extra_compile_args = get_compiler_and_args() self.define_macros = list(prefs["codegen.cpp.define_macros"]) self.extra_link_args = list(prefs["codegen.cpp.extra_link_args"]) self.headers = [] # not actually used self.include_dirs = list(prefs["codegen.cpp.include_dirs"]) + compiler_kwds.get( "include_dirs", [] ) self.include_dirs = list(prefs["codegen.cpp.include_dirs"]) self.library_dirs = list(prefs["codegen.cpp.library_dirs"]) + compiler_kwds.get( "library_dirs", [] ) self.runtime_library_dirs = list( prefs["codegen.cpp.runtime_library_dirs"] ) + compiler_kwds.get("runtime_library_dirs", []) self.libraries = list(prefs["codegen.cpp.libraries"]) + compiler_kwds.get( "libraries", [] ) self.sources = compiler_kwds.get("sources", []) @classmethod def is_available(cls): try: compiler, extra_compile_args = get_compiler_and_args() code = """ #cython: language_level=3 def main(): cdef int x x = 0""" compiled = cython_extension_manager.create_extension( code, compiler=compiler, extra_compile_args=extra_compile_args, extra_link_args=prefs["codegen.cpp.extra_link_args"], include_dirs=prefs["codegen.cpp.include_dirs"], library_dirs=prefs["codegen.cpp.library_dirs"], runtime_library_dirs=prefs["codegen.cpp.runtime_library_dirs"], ) compiled.main() return True except Exception as ex: msg = ( f"Cannot use Cython, a test compilation failed: {str(ex)} " f"({ex.__class__.__name__})" ) if platform.system() != "Windows": msg += ( "\nCertain compiler configurations (e.g. clang in a conda " "environment on OS X) are known to be problematic. Note that " "you can switch the compiler by setting the 'CC' and 'CXX' " "environment variables. For example, you may want to try " "'CC=gcc' and 'CXX=g++'." ) logger.warn(msg, "failed_compile_test") return False def compile_block(self, block): code = getattr(self.code, block, "").strip() if not code or "EMPTY_CODE_BLOCK" in code: return None return cython_extension_manager.create_extension( code, define_macros=self.define_macros, libraries=self.libraries, extra_compile_args=self.extra_compile_args, extra_link_args=self.extra_link_args, include_dirs=self.include_dirs, library_dirs=self.library_dirs, runtime_library_dirs=self.runtime_library_dirs, compiler=self.compiler, owner_name=f"{self.owner.name}_{self.template_name}", sources=self.sources, ) def run_block(self, block): compiled_code = self.compiled_code[block] if compiled_code: try: return compiled_code.main(self.namespace) except Exception as exc: message = ( "An exception occured during the execution of the " f"'{block}' block of code object '{self.name}'.\n" ) raise BrianObjectException(message, self.owner) from exc def _insert_func_namespace(self, func): impl = func.implementations[self] func_namespace = impl.get_namespace(self.owner) if func_namespace is not None: self.namespace.update(func_namespace) if impl.dependencies is not None: for dep in impl.dependencies.values(): self._insert_func_namespace(dep) def variables_to_namespace(self): # Variables can refer to values that are either constant (e.g. dt) # or change every timestep (e.g. t). We add the values of the # constant variables here and add the names of non-constant variables # to a list # A list containing tuples of name and a function giving the value self.nonconstant_values = [] for name, var in self.variables.items(): if isinstance(var, Function): self._insert_func_namespace(var) if isinstance(var, (AuxiliaryVariable, Subexpression)): continue try: value = var.get_value() except (TypeError, AttributeError): # A dummy Variable without value or a function self.namespace[name] = var continue if isinstance(var, ArrayVariable): self.namespace[self.device.get_array_name(var, self.variables)] = value self.namespace[f"_num{name}"] = var.get_len() if var.scalar and var.constant: self.namespace[name] = value.item() else: self.namespace[name] = value if isinstance(var, DynamicArrayVariable): dyn_array_name = self.generator_class.get_array_name( var, access_data=False ) self.namespace[dyn_array_name] = self.device.get_value( var, access_data=False ) # Also provide the Variable object itself in the namespace (can be # necessary for resize operations, for example) self.namespace[f"_var_{name}"] = var # Get all identifiers in the code -- note that this is not a smart # function, it will get identifiers from strings, comments, etc. This # is not a problem here, since we only use this list to filter out # things. If we include something incorrectly, this only means that we # will pass something into the namespace unnecessarily. all_identifiers = get_identifiers(self.code.run) # Filter out all unneeded objects self.namespace = { k: v for k, v in self.namespace.items() if k in all_identifiers } # There is one type of objects that we have to inject into the # namespace with their current value at each time step: dynamic # arrays that change in size during runs, where the size change is not # initiated by the template itself for name, var in self.variables.items(): if isinstance(var, DynamicArrayVariable) and var.needs_reference_update: array_name = self.device.get_array_name(var, self.variables) if array_name in self.namespace: self.nonconstant_values.append((array_name, var.get_value)) if f"_num{name}" in self.namespace: self.nonconstant_values.append((f"_num{name}", var.get_len)) def update_namespace(self): # update the values of the non-constant values in the namespace for name, func in self.nonconstant_values: self.namespace[name] = func() codegen_targets.add(CythonCodeObject) brian2-2.5.4/brian2/codegen/runtime/cython_rt/extension_manager.py000066400000000000000000000277611445201106100252430ustar00rootroot00000000000000""" Cython automatic extension builder/manager Inspired by IPython's Cython cell magics, see: https://github.com/ipython/ipython/blob/master/IPython/extensions/cythonmagic.py """ import glob import hashlib import importlib.util import os import shutil import sys import time from distutils.command.build_ext import build_ext from distutils.core import Distribution, Extension import numpy try: import Cython import Cython.Build as Cython_Build import Cython.Compiler as Cython_Compiler from Cython.Utils import get_cython_cache_dir as base_cython_cache_dir except ImportError: Cython = None from brian2.core.preferences import prefs from brian2.utils.filelock import FileLock from brian2.utils.logger import get_logger, std_silent from brian2.utils.stringtools import deindent __all__ = ["cython_extension_manager"] logger = get_logger(__name__) def get_cython_cache_dir(): cache_dir = prefs.codegen.runtime.cython.cache_dir if cache_dir is None and Cython is not None: cache_dir = os.path.join(base_cython_cache_dir(), "brian_extensions") return cache_dir def get_cython_extensions(): return { ".pyx", ".pxd", ".pyd", ".cpp", ".c", ".so", ".o", ".o.d", ".lock", ".dll", ".obj", ".exp", ".lib", } class CythonExtensionManager: def __init__(self): self._code_cache = {} def create_extension( self, code, force=False, name=None, define_macros=None, include_dirs=None, library_dirs=None, runtime_library_dirs=None, extra_compile_args=None, extra_link_args=None, libraries=None, compiler=None, sources=None, owner_name="", ): if sources is None: sources = [] self._simplify_paths() if Cython is None: raise ImportError("Cython is not available") code = deindent(code) lib_dir = get_cython_cache_dir() if "~" in lib_dir: lib_dir = os.path.expanduser(lib_dir) try: os.makedirs(lib_dir) except OSError: if not os.path.exists(lib_dir): raise OSError( f"Couldn't create Cython cache directory '{lib_dir}', try setting" " the cache directly with prefs.codegen.runtime.cython.cache_dir." ) numpy_version = ".".join( numpy.__version__.split(".")[:2] ) # Only use major.minor version # avoid some issues when manually switching compilers CC = os.environ.get("CC", None) CXX = os.environ.get("CXX", None) key = ( code, sys.version_info, sys.executable, Cython.__version__, numpy_version, CC, CXX, ) if force: # Force a new module name by adding the current time to the # key which is hashed to determine the module name. key += (time.time(),) # Note the trailing comma (this is a tuple) if key in self._code_cache: return self._code_cache[key] if name is not None: module_name = name # py3compat.unicode_to_str(args.name) else: module_name = ( f"_cython_magic_{hashlib.md5(str(key).encode('utf-8')).hexdigest()}" ) if owner_name: logger.diagnostic(f'"{owner_name}" using Cython module "{module_name}"') module_path = os.path.join(lib_dir, module_name + self.so_ext) if prefs["codegen.runtime.cython.multiprocess_safe"]: lock = FileLock(os.path.join(lib_dir, f"{module_name}.lock")) with lock: module = self._load_module( module_path, define_macros=define_macros, include_dirs=include_dirs, library_dirs=library_dirs, extra_compile_args=extra_compile_args, extra_link_args=extra_link_args, libraries=libraries, code=code, lib_dir=lib_dir, module_name=module_name, runtime_library_dirs=runtime_library_dirs, compiler=compiler, key=key, sources=sources, ) return module else: return self._load_module( module_path, define_macros=define_macros, include_dirs=include_dirs, library_dirs=library_dirs, extra_compile_args=extra_compile_args, extra_link_args=extra_link_args, libraries=libraries, code=code, lib_dir=lib_dir, module_name=module_name, runtime_library_dirs=runtime_library_dirs, compiler=compiler, key=key, sources=sources, ) @property def so_ext(self): """The extension suffix for compiled modules.""" try: return self._so_ext except AttributeError: self._so_ext = self._get_build_extension().get_ext_filename("") return self._so_ext def _clear_distutils_mkpath_cache(self): """clear distutils mkpath cache prevents distutils from skipping re-creation of dirs that have been removed """ try: from distutils.dir_util import _path_created except ImportError: pass else: _path_created.clear() def _get_build_extension(self, compiler=None): self._clear_distutils_mkpath_cache() dist = Distribution() config_files = dist.find_config_files() try: config_files.remove("setup.cfg") except ValueError: pass dist.parse_config_files(config_files) build_extension = build_ext(dist) if compiler is not None: build_extension.compiler = compiler build_extension.finalize_options() return build_extension def _load_module( self, module_path, define_macros, include_dirs, library_dirs, extra_compile_args, extra_link_args, libraries, code, lib_dir, module_name, runtime_library_dirs, compiler, key, sources, ): have_module = os.path.isfile(module_path) if not have_module: if define_macros is None: define_macros = [] if include_dirs is None: include_dirs = [] if library_dirs is None: library_dirs = [] if runtime_library_dirs is None: runtime_library_dirs = [] if extra_compile_args is None: extra_compile_args = [] if extra_link_args is None: extra_link_args = [] if libraries is None: libraries = [] c_include_dirs = include_dirs if "numpy" in code: import numpy c_include_dirs.append(numpy.get_include()) # TODO: We should probably have a special folder just for header # files that are shared between different codegen targets import brian2.synapses as synapses synapses_dir = os.path.dirname(synapses.__file__) c_include_dirs.append(synapses_dir) pyx_file = os.path.join(lib_dir, f"{module_name}.pyx") # ignore Python 3 unicode stuff for the moment # pyx_file = py3compat.cast_bytes_py2(pyx_file, encoding=sys.getfilesystemencoding()) # with io.open(pyx_file, 'w', encoding='utf-8') as f: # f.write(code) with open(pyx_file, "w") as f: f.write(code) for source in sources: if not source.lower().endswith(".pyx"): raise ValueError( "Additional Cython source files need to have an .pyx ending" ) # Copy source and header file (if present) to library directory shutil.copyfile(source, os.path.join(lib_dir, os.path.basename(source))) name_without_ext = os.path.splitext(os.path.basename(source))[0] header_name = f"{name_without_ext}.pxd" if os.path.exists(os.path.join(os.path.dirname(source), header_name)): shutil.copyfile( os.path.join(os.path.dirname(source), header_name), os.path.join(lib_dir, header_name), ) final_sources = [ os.path.join(lib_dir, os.path.basename(source)) for source in sources ] extension = Extension( name=module_name, sources=[pyx_file], define_macros=define_macros, include_dirs=c_include_dirs, library_dirs=library_dirs, runtime_library_dirs=runtime_library_dirs, extra_compile_args=extra_compile_args, extra_link_args=extra_link_args, libraries=libraries, language="c++", ) build_extension = self._get_build_extension(compiler=compiler) try: opts = dict( quiet=True, annotate=False, force=True, ) # suppresses the output on stdout with std_silent(): build_extension.extensions = Cython_Build.cythonize( [extension] + final_sources, **opts ) build_extension.build_temp = os.path.dirname(pyx_file) build_extension.build_lib = lib_dir build_extension.run() if prefs["codegen.runtime.cython.delete_source_files"]: # we can delete the source files to save disk space cpp_file = os.path.join(lib_dir, f"{module_name}.cpp") try: os.remove(pyx_file) os.remove(cpp_file) temp_dir = os.path.join( lib_dir, os.path.dirname(pyx_file)[1:], f"{module_name}.*", ) for fname in glob.glob(temp_dir): os.remove(fname) except OSError as ex: logger.debug( "Deleting Cython source files failed with error:" f" {str(ex)}" ) except Cython_Compiler.Errors.CompileError: return # Temporarily insert the Cython directory to the Python path so that # code importing from an external module that was declared via # sources works sys.path.insert(0, lib_dir) spec = importlib.util.spec_from_file_location(module_name, module_path) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) sys.path.pop(0) self._code_cache[key] = module return module def _simplify_paths(self): if "lib" in os.environ: os.environ["lib"] = simplify_path_env_var(os.environ["lib"]) if "include" in os.environ: os.environ["include"] = simplify_path_env_var(os.environ["include"]) cython_extension_manager = CythonExtensionManager() def simplify_path_env_var(path): allpaths = path.split(os.pathsep) knownpaths = set() uniquepaths = [] for p in allpaths: if p not in knownpaths: knownpaths.add(p) uniquepaths.append(p) return os.pathsep.join(uniquepaths) brian2-2.5.4/brian2/codegen/runtime/cython_rt/templates/000077500000000000000000000000001445201106100231445ustar00rootroot00000000000000brian2-2.5.4/brian2/codegen/runtime/cython_rt/templates/common_group.pyx000066400000000000000000000052071445201106100264160ustar00rootroot00000000000000{% macro cython_directives() %} #cython: language_level=3 #cython: boundscheck=False #cython: wraparound=False #cython: cdivision=False #cython: infer_types=True {% endmacro %} {% macro imports() %} import numpy as _numpy cimport numpy as _numpy from libc.math cimport fabs, sin, cos, tan, sinh, cosh, tanh, exp, log, log10, expm1, log1p, sqrt, asin, acos, atan, fmod, floor, ceil, isinf cdef extern from "math.h": double M_PI # Import the two versions of std::abs from libc.stdlib cimport abs # For integers from libc.math cimport abs # For floating point values from libc.limits cimport INT_MIN, INT_MAX from libcpp cimport bool from libcpp.set cimport set from cython.operator cimport dereference as _deref, preincrement as _preinc cimport cython as _cython _numpy.import_array() cdef extern from "numpy/ndarraytypes.h": void PyArray_CLEARFLAGS(_numpy.PyArrayObject *arr, int flags) from libc.stdlib cimport free cdef extern from "numpy/npy_math.h": bint npy_isinf(double x) double NPY_INFINITY cdef extern from "stdint_compat.h": # Longness only used for type promotion # Actual compile time size used for conversion ctypedef signed int int32_t ctypedef signed long int64_t ctypedef unsigned long uint64_t # It seems we cannot used a fused type here cdef int int_(bool) cdef int int_(char) cdef int int_(short) cdef int int_(int) cdef int int_(long) cdef int int_(float) cdef int int_(double) cdef int int_(long double) {% endmacro %} {% macro before_run() %} {{ cython_directives() }} {{ imports() }} # support code {{ support_code_lines | autoindent }} def main(_namespace): {{ load_namespace | autoindent }} if '_owner' in _namespace: _owner = _namespace['_owner'] {% block before_code %} # EMPTY_CODE_BLOCK -- overwrite in child template {% endblock %} {% endmacro %} {% macro run() %} {{ cython_directives() }} {{ imports() }} # support code {{ support_code_lines | autoindent }} # template-specific support code {% block template_support_code %} {% endblock %} def main(_namespace): cdef size_t _idx cdef size_t _vectorisation_idx {{ load_namespace | autoindent }} if '_owner' in _namespace: _owner = _namespace['_owner'] {% block maincode %} {% endblock %} {% endmacro %} {% macro after_run() %} {{ cython_directives() }} {{ imports() }} # support code {{support_code_lines | autoindent}} def main(_namespace): {{ load_namespace | autoindent }} if '_owner' in _namespace: _owner = _namespace['_owner'] {% block after_code %} # EMPTY_CODE_BLOCK -- overwrite in child template {% endblock %} {% endmacro %} brian2-2.5.4/brian2/codegen/runtime/cython_rt/templates/group_get_indices.pyx000066400000000000000000000011271445201106100274000ustar00rootroot00000000000000{# USES_VARIABLES { N, _indices } #} {% extends 'common_group.pyx' %} {% block maincode %} _vectorisation_idx = 1 cdef size_t _num_elements = 0 cdef _numpy.ndarray[int, ndim=1, mode='c'] _elements = _numpy.zeros(N, dtype=_numpy.int32) cdef int[:] _elements_view = _elements {{scalar_code|autoindent}} for _idx in range(N): _vectorisation_idx = _idx {{vector_code|autoindent}} if _cond: _elements_view[_num_elements] = _idx _num_elements += 1 return _elements[:_num_elements] {% endblock %} brian2-2.5.4/brian2/codegen/runtime/cython_rt/templates/group_variable_get.pyx000066400000000000000000000017331445201106100275520ustar00rootroot00000000000000{# Note that we use this template only for subexpressions -- for normal arrays we do not generate any code but simply access the data in the underlying array directly. See RuntimeDevice.get_with_array #} {# USES_VARIABLES { _group_idx } #} {% extends 'common_group.pyx' %} {% block maincode %} {%set c_type = cpp_dtype(variables['_variable'].dtype) %} {%set np_type = numpy_dtype(variables['_variable'].dtype) %} _vectorisation_idx = 1 cdef size_t _num_elements = 0 cdef _numpy.ndarray[{{c_type}}, ndim=1, mode='c'] _elements = _numpy.zeros(_num{{_group_idx}}, dtype=_numpy.{{np_type}}) cdef {{c_type}}[:] _elements_view = _elements {{scalar_code|autoindent}} for _idx_group_idx in range(_num{{_group_idx}}): _idx = {{_group_idx}}[_idx_group_idx] _vectorisation_idx = _idx {{vector_code|autoindent}} _elements_view[_idx_group_idx] = _variable return _elements {% endblock %} brian2-2.5.4/brian2/codegen/runtime/cython_rt/templates/group_variable_get_conditional.pyx000066400000000000000000000014461445201106100321360ustar00rootroot00000000000000{# USES_VARIABLES { N } #} {% extends 'common_group.pyx' %} {% block maincode %} {%set c_type = cpp_dtype(variables['_variable'].dtype) %} {%set np_type = numpy_dtype(variables['_variable'].dtype) %} _vectorisation_idx = 1 cdef size_t _N = {{constant_or_scalar('N', variables['N'])}} cdef size_t _num_elements = 0 cdef _numpy.ndarray[{{c_type}}, ndim=1, mode='c'] _elements = _numpy.zeros(_N, dtype=_numpy.{{np_type}}) cdef {{c_type}}[:] _elements_view = _elements {{scalar_code|autoindent}} for _idx in range(_N): _vectorisation_idx = _idx {{vector_code|autoindent}} if _cond: _elements_view[_num_elements] = _variable _num_elements += 1 return _elements[:_num_elements] {% endblock %} brian2-2.5.4/brian2/codegen/runtime/cython_rt/templates/group_variable_set.pyx000066400000000000000000000006671445201106100275730ustar00rootroot00000000000000{# USES_VARIABLES { _group_idx } #} {# ALLOWS_SCALAR_WRITE #} {% extends 'common_group.pyx' %} {% block maincode %} cdef size_t _target_idx _vectorisation_idx = 1 {{scalar_code|autoindent}} for _idx_group_idx in range(_num{{_group_idx}}): _idx = {{_group_idx}}[_idx_group_idx] _vectorisation_idx = _idx _target_idx = _idx {{vector_code|autoindent}} {% endblock %} brian2-2.5.4/brian2/codegen/runtime/cython_rt/templates/group_variable_set_conditional.pyx000066400000000000000000000012121445201106100321410ustar00rootroot00000000000000{# USES_VARIABLES { N } #} {# ALLOWS_SCALAR_WRITE #} {% extends 'common_group.pyx' %} {% block maincode %} cdef size_t _N = {{constant_or_scalar('N', variables['N'])}} _vectorisation_idx = 1 {{scalar_code['condition']|autoindent}} {{scalar_code['statement']|autoindent}} for _idx in range(_N): _vectorisation_idx = _idx {{vector_code['condition']|autoindent}} if _cond: # this looks silly but is necessary to avoid an error if there is # no vector code (setting a scalar variable) pass {{vector_code['statement']|autoindent}} {% endblock %} brian2-2.5.4/brian2/codegen/runtime/cython_rt/templates/ratemonitor.pyx000066400000000000000000000023541445201106100262550ustar00rootroot00000000000000{# USES_VARIABLES { N, t, rate, _clock_t, _clock_dt, _spikespace, _num_source_neurons, _source_start, _source_stop } #} {% extends 'common_group.pyx' %} {% block maincode %} cdef size_t _num_spikes = {{_spikespace}}[_num{{_spikespace}}-1] # For subgroups, we do not want to record all spikes # We assume that spikes are ordered cdef int _start_idx = -1 cdef int _end_idx = -1 cdef size_t _j for _j in range(_num_spikes): _idx = {{_spikespace}}[_j] if _idx >= _source_start: _start_idx = _j break if _start_idx == -1: _start_idx = _num_spikes for _j in range(_start_idx, _num_spikes): _idx = {{_spikespace}}[_j] if _idx >= _source_stop: _end_idx = _j break if _end_idx == -1: _end_idx =_num_spikes _num_spikes = _end_idx - _start_idx # Calculate the new length for the arrays cdef size_t _new_len = {{_dynamic_t}}.shape[0] + 1 # Resize the arrays _owner.resize(_new_len) {{N}} = _new_len # Set the new values {{_dynamic_t}}.data[_new_len-1] = {{_clock_t}} {{_dynamic_rate}}.data[_new_len-1] = _num_spikes/{{_clock_dt}}/_num_source_neurons {% endblock %} brian2-2.5.4/brian2/codegen/runtime/cython_rt/templates/reset.pyx000066400000000000000000000010541445201106100250300ustar00rootroot00000000000000{% extends 'common_group.pyx' %} {% block maincode %} # scalar code _vectorisation_idx = 1 {{scalar_code|autoindent}} {# Get the name of the array that stores these events (e.g. the spikespace array) #} {% set _eventspace = get_array_name(eventspace_variable) %} cdef size_t _num_events = {{_eventspace}}[_num{{_eventspace}}-1] for _index_events in range(_num_events): # vector code _idx = {{_eventspace}}[_index_events] _vectorisation_idx = _idx {{ vector_code | autoindent }} {% endblock %} brian2-2.5.4/brian2/codegen/runtime/cython_rt/templates/spatialstateupdate.pyx000066400000000000000000000205711445201106100276140ustar00rootroot00000000000000{# USES_VARIABLES { Cm, dt, v, N, Ic, Ri, _ab_star0, _ab_star1, _ab_star2, _b_plus, _b_minus, _v_star, _u_plus, _u_minus, _v_previous, _gtot_all, _I0_all, _c, _P_diag, _P_parent, _P_children, _B, _morph_parent_i, _starts, _ends, _morph_children, _morph_children_num, _morph_idxchild, _invr0, _invrn, _invr, r_length_1, r_length_2, area } #} {% extends 'common_group.pyx' %} {% block before_code %} cdef int _i cdef int _counter cdef int _first cdef int _last cdef double __invr0 cdef double __invrn cdef double _Ri = {{Ri}} # Ri is a shared variable # Inverse axial resistance for _i in range(1, N): {{_invr}}[_i] =1.0/(_Ri*(1/{{r_length_2}}[_i-1] + 1/{{r_length_1}}[_i])) # Cut sections for _i in range(_num{{_starts}}): {{_invr}}[{{_starts}}[_i]] = 0 # Linear systems # The particular solution # a[i,j]=ab[u+i-j,j] -- u is the number of upper diagonals = 1 for _i in range(N): {{_ab_star1}}[_i] = (-({{Cm}}[_i] / {{dt}}) - {{_invr}}[_i] / {{area}}[_i]) for _i in range(1, N): {{_ab_star0}}[_i] = {{_invr}}[_i] / {{area}}[_i-1] {{_ab_star2}}[_i-1] = {{_invr}}[_i] / {{area}}[_i] {{_ab_star1}}[_i-1] -= {{_invr}}[_i] / {{area}}[_i-1] # Set the boundary conditions for _counter in range(_num{{_starts}}): _first = {{_starts}}[_counter] _last = {{_ends}}[_counter] -1 # the compartment indices are in the interval [starts, ends[ # Inverse axial resistances at the ends: r0 and rn __invr0 = {{r_length_1}}[_first]/_Ri __invrn = {{r_length_2}}[_last]/_Ri {{_invr0}}[_counter] = __invr0 {{_invrn}}[_counter] = __invrn # Correction for boundary conditions {{_ab_star1}}[_first] -= (__invr0 / {{area}}[_first]) {{_ab_star1}}[_last] -= (__invrn / {{area}}[_last]) # RHS for homogeneous solutions {{_b_plus}}[_last] = -(__invrn / {{area}}[_last]) {{_b_minus}}[_first] = -(__invr0 / {{area}}[_first]) {% endblock %} {% block maincode %} cdef int _i cdef int _j cdef int _k cdef int _j_start cdef int _j_end cdef double _ai cdef double _bi cdef double _m cdef int _i_parent cdef int _i_childind cdef int _first cdef int _last cdef int _num_children cdef double _subfac cdef int _children_rowlength # MAIN CODE _vectorisation_idx = 1 {{scalar_code|autoindent}} # STEP 1: compute g_total and I_0 for _i in range(0, N): _idx = _i _vectorisation_idx = _idx {{vector_code|autoindent}} {{_gtot_all}}[_idx] = _gtot {{_I0_all}}[_idx] = _I0 {{_v_previous}}[_idx] = {{v}}[_idx] # STEP 2: for each section: solve three tridiagonal systems # system 2a: solve for _v_star for _i in range(0, _num{{_B}} - 1): # first and last index of the i-th section _j_start = {{_starts}}[_i] _j_end = {{_ends}}[_i] # upper triangularization of tridiagonal system for _v_star, _u_plus, and _u_minus for _j in range(_j_start, _j_end): {{_v_star}}[_j]=-({{Cm}}[_j]/{{dt}}*{{v}}[_j])-{{_I0_all}}[_j] # RHS -> _v_star (solution) {{_u_plus}}[_j]={{_b_plus}}[_j] # RHS -> _u_plus (solution) {{_u_minus}}[_j]={{_b_minus}}[_j] # RHS -> _u_minus (solution) _bi={{_ab_star1}}[_j]-{{_gtot_all}}[_j] # main diagonal if _j < N-1: {{_c}}[_j]={{_ab_star0}}[_j+1] # superdiagonal if _j > 0: _ai={{_ab_star2}}[_j-1] # subdiagonal _m=1.0/(_bi-_ai*{{_c}}[_j-1]) {{_c}}[_j]={{_c}}[_j]*_m {{_v_star}}[_j]=({{_v_star}}[_j] - _ai*{{_v_star}}[_j-1])*_m {{_u_plus}}[_j]=({{_u_plus}}[_j] - _ai*{{_u_plus}}[_j-1])*_m {{_u_minus}}[_j]=({{_u_minus}}[_j] - _ai*{{_u_minus}}[_j-1])*_m else: {{_c}}[0]={{_c}}[0]/_bi {{_v_star}}[0]={{_v_star}}[0]/_bi {{_u_plus}}[0]={{_u_plus}}[0]/_bi {{_u_minus}}[0]={{_u_minus}}[0]/_bi # backwards substitution of the upper triangularized system for _v_star for _j in range(_j_end-2, _j_start-1, -1): {{_v_star}}[_j]={{_v_star}}[_j] - {{_c}}[_j]*{{_v_star}}[_j+1] {{_u_plus}}[_j]={{_u_plus}}[_j] - {{_c}}[_j]*{{_u_plus}}[_j+1] {{_u_minus}}[_j]={{_u_minus}}[_j] - {{_c}}[_j]*{{_u_minus}}[_j+1] # STEP 3: solve the coupling system # indexing for _P_children which contains the elements above the diagonal of the coupling matrix _P _children_rowlength = _num{{_morph_children}}//_num{{_morph_children_num}} # STEP 3a: construct the coupling system with matrix _P in sparse form. s.t. # _P_diag contains the diagonal elements # _P_children contains the super diagonal entries # _P_parent contains the single sub diagonal entry for each row # _B contains the right hand side for _i in range(0, _num{{_B}} - 1): _i_parent = {{_morph_parent_i}}[_i] _i_childind = {{_morph_idxchild}}[_i] _first = {{_starts}}[_i] _last = {{_ends}}[_i] - 1 # the compartment indices are in the interval [starts, ends[ _this_invr0 = {{_invr0}}[_i] _this_invrn = {{_invrn}}[_i] # Towards parent if _i == 0: # first section, sealed end {{_P_diag}}[0] = {{_u_minus}}[_first] - 1 {{_P_children}}[0 + 0] = {{_u_plus}}[_first] # RHS {{_B}}[0] = -{{_v_star}}[_first] else: {{_P_diag}}[_i_parent] += (1 - {{_u_minus}}[_first]) * _this_invr0 {{_P_children}}[_i_parent * _children_rowlength + _i_childind] = -{{_u_plus}}[_first] * _this_invr0 # RHS {{_B}}[_i_parent] += {{_v_star}}[_first] * _this_invr0 # Towards children {{_P_diag}}[_i+1] = (1 - {{_u_plus}}[_last]) * _this_invrn {{_P_parent}}[_i] = -{{_u_minus}}[_last] * _this_invrn # RHS {{_B}}[_i+1] = {{_v_star}}[_last] * _this_invrn # STEP 3b: solve the linear system (the result will be stored in the former rhs _B in the end) # use efficient O(n) solution of the sparse linear system (structure-specific Gaussian elemination) # part 1: lower triangularization for _i in range(_num{{_B}}-1, -1, -1): _num_children = {{_morph_children_num}}[_i] # for every child eliminate the corresponding matrix element of row i for _k in range(0, _num_children): _j = {{_morph_children}}[_i * _children_rowlength + _k] # child index # subtracting _subfac times the j-th from the i-th row _subfac = {{_P_children}}[_i * _children_rowlength + _k] / {{_P_diag}}[_j] # element i,j appears only here # the following commented (superdiagonal) element is not used in the following anymore since # it is 0 by definition of (lower) triangularization we keep it here for algorithmic clarity #{{_P_children}}[_i * _children_rowlength +_k] = {{_P_children}}[_i * _children_rowlength + _k] - _subfac * {{_P_diag}}[_j] # = 0 {{_P_diag}}[_i] = {{_P_diag}}[_i] - _subfac * {{_P_parent}}[_j-1] # note: element j,i is only used here {{_B}}[_i] = {{_B}}[_i] - _subfac * {{_B}}[_j] # part 2: forwards substitution {{_B}}[0] = {{_B}}[0] / {{_P_diag}}[0] # the first section does not have a parent for _i in range(1, _num{{_B}}): _j = {{_morph_parent_i}}[_i-1] # parent index {{_B}}[_i] = {{_B}}[_i] - {{_P_parent}}[_i-1] * {{_B}}[_j] {{_B}}[_i] = {{_B}}[_i] / {{_P_diag}}[_i] # STEP 4: for each section compute the final solution by linear # combination of the general solution (independent: sections & compartments) for _i in range(0, _num{{_B}} - 1): _i_parent = {{_morph_parent_i}}[_i] _j_start = {{_starts}}[_i] _j_end = {{_ends}}[_i] for _j in range(_j_start, _j_end): if _j < _num{{v}}: # don't go beyond the last element {{v}}[_j] = ({{_v_star}}[_j] + {{_B}}[_i_parent] * {{_u_minus}}[_j] + {{_B}}[_i+1] * {{_u_plus}}[_j]) for _i in range(0, N): {{Ic}}[_i] = {{Cm}}[_i]*({{v}}[_i] - {{_v_previous}}[_i])/{{dt}} {% endblock %} brian2-2.5.4/brian2/codegen/runtime/cython_rt/templates/spikegenerator.pyx000066400000000000000000000016231445201106100267320ustar00rootroot00000000000000{% extends 'common_group.pyx' %} {% block maincode %} {# USES_VARIABLES { _spikespace, neuron_index, _timebins, _period_bins, _lastindex, t_in_timesteps, N } #} cdef int32_t _the_period = {{_period_bins}} cdef int32_t _timebin = {{t_in_timesteps}} cdef int32_t _cpp_numspikes = 0; if _the_period > 0: _timebin %= _the_period # If there is a periodicity in the SpikeGenerator, we need to reset the # lastindex when the period has passed if {{_lastindex}} > 0 and {{_timebins}}[{{_lastindex}} - 1] >= _timebin: {{_lastindex}} = 0 for _idx in range({{_lastindex}}, _num{{_timebins}}): if ({{_timebins}}[_idx] > _timebin): break {{_spikespace}}[_cpp_numspikes] = {{neuron_index}}[_idx] _cpp_numspikes += 1 {{_spikespace}}[N] = _cpp_numspikes {{_lastindex}} += _cpp_numspikes {% endblock %} brian2-2.5.4/brian2/codegen/runtime/cython_rt/templates/spikemonitor.pyx000066400000000000000000000040371445201106100264350ustar00rootroot00000000000000{# USES_VARIABLES { N, _clock_t, count, _source_start, _source_stop} #} {% extends 'common_group.pyx' %} {% block maincode %} {# Get the name of the array that stores these events (e.g. the spikespace array) #} {% set _eventspace = get_array_name(eventspace_variable) %} cdef size_t _num_events = {{_eventspace}}[_num{{_eventspace}}-1] cdef size_t _start_idx, _end_idx, _curlen, _newlen, _j {% for varname, var in record_variables | dictsort %} cdef {{cpp_dtype(var.dtype)}}[:] _{{varname}}_view {% endfor %} if _num_events > 0: # For subgroups, we do not want to record all spikes # We assume that spikes are ordered _start_idx = _num_events _end_idx = _num_events for _j in range(_num_events): _idx = {{_eventspace}}[_j] if _idx >= _source_start: _start_idx = _j break for _j in range(_num_events-1, _start_idx-1, -1): _idx = {{_eventspace}}[_j] if _idx < _source_stop: break _end_idx = _j _num_events = _end_idx - _start_idx if _num_events > 0: # scalar code _vectorisation_idx = 1 {{ scalar_code|autoindent }} _curlen = {{N}} _newlen = _curlen + _num_events # Resize the arrays _owner.resize(_newlen) {{N}} = _newlen {% for varname, var in record_variables | dictsort %} _{{varname}}_view = {{get_array_name(var, access_data=False)}}.data {% endfor %} # Copy the values across for _j in range(_start_idx, _end_idx): _idx = {{_eventspace}}[_j] _vectorisation_idx = _idx {{ vector_code|autoindent }} {% for varname in record_variables | sort %} _{{varname}}_view [_curlen + _j - _start_idx] = _to_record_{{varname}} {% endfor %} {{count}}[_idx - _source_start] += 1 {% endblock %} brian2-2.5.4/brian2/codegen/runtime/cython_rt/templates/statemonitor.pyx000066400000000000000000000031171445201106100264400ustar00rootroot00000000000000{# USES_VARIABLES { t, _clock_t, _indices, N } #} {% extends 'common_group.pyx' %} {% block maincode %} cdef size_t _new_len = {{N}} + 1 # Resize the recorded times _var_t.resize(_new_len) {{_dynamic_t}}[_new_len-1] = {{_clock_t}} # scalar code _vectorisation_idx = 1 {{ scalar_code|autoindent }} cdef size_t _i {% for varname, var in _recorded_variables | dictsort %} {% set c_type = cpp_dtype(variables[varname].dtype) %} {% set np_type = numpy_dtype(variables[varname].dtype) %} # Resize the recorded variable "{{varname}}" and get the (potentially # changed) reference to the underlying data _var_{{varname}}.resize((_new_len, _num{{_indices}})) {% if c_type == 'bool'%} cdef _numpy.ndarray[char, ndim=2, mode='c', cast=True] _record_buf_{{varname}} = {{get_array_name(var, access_data=False)}}.data cdef bool* _record_data_{{varname}} = <{{c_type}}*> _record_buf_{{varname}}.data {% else %} cdef _numpy.ndarray[{{c_type}}, ndim=2, mode='c'] _record_buf_{{varname}} = {{get_array_name(var, access_data=False)}}.data cdef {{c_type}}* _record_data_{{varname}} = <{{c_type}}*> _record_buf_{{varname}}.data {% endif %} for _i in range(_num{{_indices}}): # vector code _idx = {{_indices}}[_i] _vectorisation_idx = _idx {{ vector_code | autoindent }} _record_data_{{varname}}[(_new_len-1)*_num{{_indices}} + _i] = _to_record_{{varname}} {% endfor %} # set the N variable explicitly (since we do not call `StateMonitor.resize`) {{N}} = _new_len {% endblock %} brian2-2.5.4/brian2/codegen/runtime/cython_rt/templates/stateupdate.pyx000066400000000000000000000005311445201106100262300ustar00rootroot00000000000000{# ITERATE_ALL { _idx } #} {# USES_VARIABLES { N } #} {# ALLOWS_SCALAR_WRITE #} {% extends 'common_group.pyx' %} {% block maincode %} # scalar code _vectorisation_idx = 1 {{scalar_code|autoindent}} # vector code for _idx in range(N): _vectorisation_idx = _idx {{vector_code|autoindent}} {% endblock %} brian2-2.5.4/brian2/codegen/runtime/cython_rt/templates/summed_variable.pyx000066400000000000000000000012541445201106100270470ustar00rootroot00000000000000{# USES_VARIABLES { N } #} {% extends 'common_group.pyx' %} {% block maincode %} {% set _target_var_array = get_array_name(_target_var) %} {% set _index_array = get_array_name(_index_var) %} cdef int _target_idx # Set all the target variable values to zero for _target_idx in range({{_target_size_name}}): {{_target_var_array}}[_target_idx + {{_target_start}}] = 0 # scalar code _vectorisation_idx = 1 {{scalar_code|autoindent}} for _idx in range({{N}}): # vector_code vectorisation_idx = _idx {{ vector_code | autoindent }} {{_target_var_array}}[{{_index_array}}[_idx]] += _synaptic_var {% endblock %} brian2-2.5.4/brian2/codegen/runtime/cython_rt/templates/synapses.pyx000066400000000000000000000010751445201106100255560ustar00rootroot00000000000000{# USES_VARIABLES { _queue } #} {% extends 'common_group.pyx' %} {% block maincode %} cdef _numpy.ndarray[int32_t, ndim=1, mode='c'] _spiking_synapses = _queue.peek() # scalar code _vectorisation_idx = 1 {{ scalar_code | autoindent }} cdef size_t _spiking_synapse_idx for _spiking_synapse_idx in range(len(_spiking_synapses)): # vector code _idx = _spiking_synapses[_spiking_synapse_idx] _vectorisation_idx = _idx {{ vector_code | autoindent }} # Advance the spike queue _queue.advance() {% endblock %} brian2-2.5.4/brian2/codegen/runtime/cython_rt/templates/synapses_create_array.pyx000066400000000000000000000026131445201106100302760ustar00rootroot00000000000000{# USES_VARIABLES { _synaptic_pre, _synaptic_post, sources, targets, N, N_pre, N_post, _source_offset, _target_offset } #} {# WRITES_TO_READ_ONLY_VARIABLES { _synaptic_pre, _synaptic_post, N} #} {% extends 'common_group.pyx' %} {% block maincode %} cdef size_t _old_num_synapses = {{N}} cdef size_t _new_num_synapses = _old_num_synapses + _num{{sources}} {{_dynamic__synaptic_pre}}.resize(_new_num_synapses) {{_dynamic__synaptic_post}}.resize(_new_num_synapses) # Get the potentially newly created underlying data arrays cdef int32_t[:] _synaptic_pre_data = {{_dynamic__synaptic_pre}}.data cdef int32_t[:] _synaptic_post_data = {{_dynamic__synaptic_post}}.data for _idx in range(_num{{sources}}): # After this code has been executed, the arrays _real_sources and # _real_variables contain the final indices. Having any code here it all is # only necessary for supporting subgroups {{ vector_code | autoindent }} _synaptic_pre_data[_idx + _old_num_synapses] = _real_sources _synaptic_post_data[_idx + _old_num_synapses] = _real_targets # now we need to resize all registered variables and set the total number # of synapses (via Python) _owner._resize(_new_num_synapses) # And update N_incoming, N_outgoing and synapse_number _owner._update_synapse_numbers(_old_num_synapses) {% endblock %} brian2-2.5.4/brian2/codegen/runtime/cython_rt/templates/synapses_create_generator.pyx000066400000000000000000000207531445201106100311530ustar00rootroot00000000000000{# USES_VARIABLES { _synaptic_pre, _synaptic_post, rand, N, N_pre, N_post, _source_offset, _target_offset } #} {# WRITES_TO_READ_ONLY_VARIABLES { _synaptic_pre, _synaptic_post, N} #} {# ITERATE_ALL { _idx } #} {% extends 'common_group.pyx' %} ######################## TEMPLATE SUPPORT CODE ############################## {% block template_support_code %} cdef int _buffer_size = 1024 cdef int[:] _prebuf = _numpy.zeros(_buffer_size, dtype=_numpy.int32) cdef int[:] _postbuf = _numpy.zeros(_buffer_size, dtype=_numpy.int32) cdef int _curbuf = 0 cdef int _raw_pre_idx cdef int _raw_post_idx cdef void _flush_buffer(buf, dynarr, int buf_len): cdef size_t _curlen = dynarr.shape[0] cdef size_t _newlen = _curlen+buf_len # Resize the array dynarr.resize(_newlen) # Get the potentially newly created underlying data arrays data = dynarr.data data[_curlen:_curlen+buf_len] = buf[:buf_len] {% endblock %} ######################## MAIN CODE ############################## {% block maincode %} cdef int* _prebuf_ptr = &(_prebuf[0]) cdef int* _postbuf_ptr = &(_postbuf[0]) global _curbuf cdef size_t oldsize = len({{_dynamic__synaptic_pre}}) cdef size_t newsize # The following variables are only used for probabilistic connections {% if iterator_func=='sample' %} cdef int _iter_sign {% if iterator_kwds['sample_size'] == 'fixed' %} cdef bool _selection_algo cdef set[int] _selected_set = set[int]() cdef set[int].iterator _selected_it cdef int _n_selected cdef int _n_dealt_with cdef int _n_total cdef double _U {% else %} cdef bool _jump_algo cdef double _log1p, _pconst cdef size_t _jump {% endif %} {% endif %} {# For a connect call j='k+i for k in range(0, N_post, 2) if k+i < N_post' "j" is called the "result index" (and "_post_idx" the "result index array", etc.) "i" is called the "outer index" (and "_pre_idx" the "outer index array", etc.) "k" is called the inner variable #} # scalar code _vectorisation_idx = 1 {{scalar_code['setup_iterator']|autoindent}} {{scalar_code['generator_expr']|autoindent}} {{scalar_code['create_cond']|autoindent}} {{scalar_code['update']|autoindent}} for _{{outer_index}} in range({{outer_index_size}}): _raw{{outer_index_array}} = _{{outer_index}} + {{outer_index_offset}} {% if not result_index_condition %} {{vector_code['create_cond']|autoindent}} if not _cond: continue {% endif %} {{vector_code['setup_iterator']|autoindent}} {% if iterator_func=='range' %} for {{inner_variable}} in range(_iter_low, _iter_high, _iter_step): {% elif iterator_func=='sample' %} {% if iterator_kwds['sample_size'] == 'fixed' %} # Note that the following code is written in a slightly convoluted way, # but we have to plug it together with the following code that checks # for the fulfillment of the condition. _n_selected = 0 _n_dealt_with = 0 with _cython.cdivision(True): if _iter_step > 0: _n_total = (_iter_high - _iter_low - 1) // _iter_step + 1 else: _n_total = (_iter_low - _iter_high - 1) // -_iter_step + 1 # Value determined by benchmarking, see github PR #1280 _selection_algo = 1.0*_iter_size / _n_total > 0.06 if _iter_size > _n_total: {% if skip_if_invalid %} _iter_size = _n_total {% else %} raise IndexError(f"Requested sample size {_iter_size} is bigger than the " f"population size {_n_total}.") {% endif %} elif _iter_size < 0: {% if skip_if_invalid %} continue {% else %} raise IndexError(f"Requested sample size {_iter_size} is negative.") {% endif %} if _selection_algo: {{inner_variable}} = _iter_low - _iter_step else: # For the tracking algorithm, we have to first create all values # to make sure they will be iterated in sorted order _selected_set.clear() while _n_selected < _iter_size: _r = (_rand(_vectorisation_idx) * _n_total) while not _selected_set.insert(_r).second: # .second will be False if duplicate _r = (_rand(_vectorisation_idx) * _n_total) _n_selected += 1 _n_selected = 0 _selected_it = _selected_set.begin() while _n_selected < _iter_size: if _selection_algo: {{inner_variable}} += _iter_step # Selection sampling technique # See section 3.4.2 of Donald E. Knuth, AOCP, Vol 2, # Seminumerical Algorithms _n_dealt_with += 1 _U = _rand(_vectorisation_idx) if (_n_total - _n_dealt_with) * _U >= _iter_size - _n_selected: continue else: {{inner_variable}} = _iter_low + _deref(_selected_it)*_iter_step _preinc(_selected_it) _n_selected += 1 {% else %} if _iter_p==0: continue if _iter_step < 0: _iter_sign = -1 else: _iter_sign = 1 _jump_algo = _iter_p<0.25 if _jump_algo: _log1p = log(1-_iter_p) else: _log1p = 1.0 # will be ignored _pconst = 1.0/_log1p {{inner_variable}} = _iter_low-_iter_step while _iter_sign*({{inner_variable}} + _iter_step) < _iter_sign*_iter_high: {{inner_variable}} += _iter_step if _jump_algo: _jump = (log(_rand(_vectorisation_idx))*_pconst)*_iter_step {{inner_variable}} += _jump if _iter_sign*{{inner_variable}} >= _iter_sign*_iter_high: break else: if _rand(_vectorisation_idx)>=_iter_p: continue {% endif %} {% endif %} {{vector_code['generator_expr']|autoindent}} _raw{{result_index_array}} = _{{result_index}} + {{result_index_offset}} {% if result_index_condition %} {% if result_index_used %} {# The condition could index outside of array range #} if _{{result_index}}<0 or _{{result_index}}>={{result_index_size}}: {% if skip_if_invalid %} continue {% else %} # Note that with Jinja using a lot of curly braces, it is a better # solution to use the outdated % syntax instead of f-strings here. raise IndexError("index {{result_index}}=%d outside allowed range from 0 to %d" % (_{{result_index}}, {{result_index_size}}-1)) {% endif %} {% endif %} {{vector_code['create_cond']|autoindent}} {% endif %} {% if if_expression!='True' and result_index_condition %} if not _cond: continue {% endif %} {% if not result_index_used %} {# Otherwise, we already checked before #} if _{{result_index}}<0 or _{{result_index}}>={{result_index_size}}: {% if skip_if_invalid %} continue {% else %} raise IndexError("index j=%d outside allowed range from 0 to %d" % (_{{result_index}}, {{result_index_size}}-1)) {% endif %} {% endif %} {{vector_code['update']|autoindent}} for _repetition in range(_n): _prebuf_ptr[_curbuf] = _pre_idx _postbuf_ptr[_curbuf] = _post_idx _curbuf += 1 # Flush buffer if _curbuf==_buffer_size: _flush_buffer(_prebuf, {{_dynamic__synaptic_pre}}, _curbuf) _flush_buffer(_postbuf, {{_dynamic__synaptic_post}}, _curbuf) _curbuf = 0 # Final buffer flush _flush_buffer(_prebuf, {{_dynamic__synaptic_pre}}, _curbuf) _flush_buffer(_postbuf, {{_dynamic__synaptic_post}}, _curbuf) _curbuf = 0 # reset the buffer for the next run newsize = len({{_dynamic__synaptic_pre}}) # now we need to resize all registered variables and set the total number # of synapse (via Python) _owner._resize(newsize) # And update N_incoming, N_outgoing and synapse_number _owner._update_synapse_numbers(oldsize) {% endblock %} brian2-2.5.4/brian2/codegen/runtime/cython_rt/templates/synapses_push_spikes.pyx000066400000000000000000000002451445201106100301710ustar00rootroot00000000000000{% extends 'common_group.pyx' %} {% block before_code %} _owner.initialise_queue() {% endblock %} {% block maincode %} _owner.push_spikes() {% endblock %} brian2-2.5.4/brian2/codegen/runtime/cython_rt/templates/threshold.pyx000066400000000000000000000022301445201106100256770ustar00rootroot00000000000000{# USES_VARIABLES { N } #} {% extends 'common_group.pyx' %} {% block maincode %} {# t, not_refractory and lastspike are added as needed_variables in the Thresholder class, we cannot use the USES_VARIABLE mechanism conditionally #} # scalar code _vectorisation_idx = 1 {{ scalar_code | autoindent }} cdef size_t _cpp_numevents = 0 {# Get the name of the array that stores these events (e.g. the spikespace array) #} {% set _eventspace = get_array_name(eventspace_variable) %} for _idx in range(N): # vector code _vectorisation_idx = _idx {{ vector_code | autoindent }} if _cond: {{_eventspace}}[_cpp_numevents] = _idx _cpp_numevents += 1 {% if _uses_refractory %} {{not_refractory}}[_idx] = False {{lastspike}}[_idx] = {{t}} {% endif %} {{_eventspace}}[N] = _cpp_numevents {% endblock %} {% block after_code %} {% set _eventspace = get_array_name(eventspace_variable) %} {{_eventspace}}[N] = 0 # Note that this is not an off-by-1-error: the array has N+1 elements {% endblock %} brian2-2.5.4/brian2/codegen/runtime/numpy_rt/000077500000000000000000000000001445201106100210125ustar00rootroot00000000000000brian2-2.5.4/brian2/codegen/runtime/numpy_rt/__init__.py000066400000000000000000000002211445201106100231160ustar00rootroot00000000000000""" Numpy runtime implementation. Preferences -------------------- .. document_brian_prefs:: codegen.runtime.numpy """ from .numpy_rt import * brian2-2.5.4/brian2/codegen/runtime/numpy_rt/numpy_rt.py000066400000000000000000000235351445201106100232510ustar00rootroot00000000000000""" Module providing `NumpyCodeObject`. """ import sys from collections.abc import Iterable import numpy as np from brian2.core.base import BrianObjectException from brian2.core.functions import Function from brian2.core.preferences import BrianPreference, prefs from brian2.core.variables import ( ArrayVariable, AuxiliaryVariable, DynamicArrayVariable, Subexpression, ) from ...codeobject import CodeObject, check_compiler_kwds, constant_or_scalar from ...generators.numpy_generator import NumpyCodeGenerator from ...targets import codegen_targets from ...templates import Templater __all__ = ["NumpyCodeObject"] # Preferences prefs.register_preferences( "codegen.runtime.numpy", "Numpy runtime codegen preferences", discard_units=BrianPreference( default=False, docs=""" Whether to change the namespace of user-specifed functions to remove units. """, ), ) class LazyArange(Iterable): """ A class that can be used as a `~numpy.arange` replacement (with an implied step size of 1) but does not actually create an array of values until necessary. It is somewhat similar to the ``range()`` function in Python 3, but does not use a generator. It is tailored to a special use case, the ``_vectorisation_idx`` variable in numpy templates, and not meant for general use. The ``_vectorisation_idx`` is used for stateless function calls such as ``rand()`` and for the numpy codegen target determines the number of values produced by such a call. This will often be the number of neurons or synapses, and this class avoids creating a new array of that size at every code object call when all that is needed is the *length* of the array. Examples -------- >>> from brian2.codegen.runtime.numpy_rt.numpy_rt import LazyArange >>> ar = LazyArange(10) >>> len(ar) 10 >>> len(ar[:5]) 5 >>> type(ar[:5]) >>> ar[5] 5 >>> for value in ar[3:7]: ... print(value) ... 3 4 5 6 >>> len(ar[np.array([1, 2, 3])]) 3 """ def __init__(self, stop, start=0, indices=None): self.start = start self.stop = stop self.indices = indices def __len__(self): if self.indices is None: return self.stop - self.start else: return len(self.indices) def __getitem__(self, item): if isinstance(item, slice): if self.indices is None: start, stop, step = item.start, item.stop, item.step if step not in [None, 1]: raise NotImplementedError("Step should be 1") if start is None: start = 0 if stop is None: stop = len(self) return LazyArange( start=self.start + start, stop=min([self.start + stop, self.stop]) ) else: raise NotImplementedError("Cannot slice LazyArange with indices") elif isinstance(item, np.ndarray): if item.dtype == np.dtype(bool): item = np.nonzero(item)[0] # convert boolean array into integers if len(item) == 0: return np.array([], dtype=np.int32) if np.min(item) < 0 or np.max(item) > len(self): raise IndexError("Indexing array contains out-of-bounds values") return LazyArange(start=self.start, stop=self.stop, indices=item) elif isinstance(item, int): if self.indices is None: index = self.start + item if index >= self.stop: raise IndexError(index) return index else: return self.indices[item] else: raise TypeError("Can only index with integer, numpy array, or slice.") def __iter__(self): if self.indices is None: return iter(np.arange(self.start, self.stop)) else: return iter(self.indices) # Allow conversion to a proper array with np.array(...) def __array__(self, dtype=None): if self.indices is None: return np.arange(self.start, self.stop) else: return self.indices + self.start # Allow basic arithmetics (used when shifting stuff for subgroups) def __add__(self, other): if isinstance(other, int): return LazyArange(start=self.start + other, stop=self.stop + other) else: return NotImplemented def __radd__(self, other): return self.__add__(other) def __sub__(self, other): if isinstance(other, int): return LazyArange(start=self.start - other, stop=self.stop - other) else: return NotImplemented class NumpyCodeObject(CodeObject): """ Execute code using Numpy Default for Brian because it works on all platforms. """ templater = Templater( "brian2.codegen.runtime.numpy_rt", ".py_", env_globals={"constant_or_scalar": constant_or_scalar}, ) generator_class = NumpyCodeGenerator class_name = "numpy" def __init__( self, owner, code, variables, variable_indices, template_name, template_source, compiler_kwds, name="numpy_code_object*", ): check_compiler_kwds(compiler_kwds, [], "numpy") from brian2.devices.device import get_device self.device = get_device() self.namespace = { "_owner": owner, # TODO: This should maybe go somewhere else "logical_not": np.logical_not, } CodeObject.__init__( self, owner, code, variables, variable_indices, template_name, template_source, compiler_kwds=compiler_kwds, name=name, ) self.variables_to_namespace() @classmethod def is_available(cls): # no test necessary for numpy return True def variables_to_namespace(self): # Variables can refer to values that are either constant (e.g. dt) # or change every timestep (e.g. t). We add the values of the # constant variables here and add the names of non-constant variables # to a list # A list containing tuples of name and a function giving the value self.nonconstant_values = [] for name, var in self.variables.items(): if isinstance(var, (AuxiliaryVariable, Subexpression)): continue try: if not hasattr(var, "get_value"): raise TypeError() value = var.get_value() except TypeError: # Either a dummy Variable without a value or a Function object if isinstance(var, Function): impl = var.implementations[self.__class__].get_code(self.owner) self.namespace[name] = impl else: self.namespace[name] = var continue if isinstance(var, ArrayVariable): self.namespace[self.generator_class.get_array_name(var)] = value if var.scalar and var.constant: self.namespace[name] = value[0] else: self.namespace[name] = value if isinstance(var, DynamicArrayVariable): dyn_array_name = self.generator_class.get_array_name( var, access_data=False ) self.namespace[dyn_array_name] = self.device.get_value( var, access_data=False ) # Also provide the Variable object itself in the namespace (can be # necessary for resize operations, for example) self.namespace[f"_var_{name}"] = var # There is one type of objects that we have to inject into the # namespace with their current value at each time step: dynamic # arrays that change in size during runs (i.e. not synapses but # e.g. the structures used in monitors) if isinstance(var, DynamicArrayVariable) and var.needs_reference_update: self.nonconstant_values.append( ( self.generator_class.get_array_name(var, self.variables), var.get_value, ) ) def update_namespace(self): # update the values of the non-constant values in the namespace for name, func in self.nonconstant_values: self.namespace[name] = func() def compile_block(self, block): code = getattr(self.code, block, "").strip() if not code or "EMPTY_CODE_BLOCK" in code: return None return compile(code, "(string)", "exec") def run_block(self, block): compiled_code = self.compiled_code[block] if not compiled_code: return try: exec(compiled_code, self.namespace) except Exception as exc: code = getattr(self.code, block) message = ( "An exception occured during the execution of the " f"'{block}' block of code object {self.name}.\n" ) lines = code.split("\n") message += "The error was raised in the following line:\n" _, _, tb = sys.exc_info() tb = tb.tb_next # Line in the code object's code message += f"{lines[tb.tb_lineno - 1]}\n" raise BrianObjectException(message, self.owner) from exc # output variables should land in the variable name _return_values if "_return_values" in self.namespace: return self.namespace["_return_values"] codegen_targets.add(NumpyCodeObject) brian2-2.5.4/brian2/codegen/runtime/numpy_rt/templates/000077500000000000000000000000001445201106100230105ustar00rootroot00000000000000brian2-2.5.4/brian2/codegen/runtime/numpy_rt/templates/common_group.py_000066400000000000000000000011161445201106100262240ustar00rootroot00000000000000{% macro before_run() %} import numpy as _numpy from brian2.codegen.runtime.numpy_rt.numpy_rt import LazyArange {% block before_code %} # EMPTY_CODE_BLOCK -- overwrite in child template {% endblock %} {% endmacro %} {% macro run() %} import numpy as _numpy from brian2.codegen.runtime.numpy_rt.numpy_rt import LazyArange {% block maincode %} {% endblock %} {% endmacro %} {% macro after_run() %} import numpy as _numpy from brian2.codegen.runtime.numpy_rt.numpy_rt import LazyArange {% block after_code %} # EMPTY_CODE_BLOCK -- overwrite in child template {% endblock %} {% endmacro %} brian2-2.5.4/brian2/codegen/runtime/numpy_rt/templates/group_get_indices.py_000066400000000000000000000005641445201106100272170ustar00rootroot00000000000000{# USES_VARIABLES { N } #} {% extends 'common_group.py_' %} {% block maincode %} # scalar code _vectorisation_idx = 1 {{scalar_code|autoindent}} # vector code _idx = LazyArange(N) _vectorisation_idx = _idx {{vector_code|autoindent}} if _cond is True: _cond = slice(None) if _cond is False: _cond = [] _return_values = _numpy.array(_idx[_cond]) {% endblock %} brian2-2.5.4/brian2/codegen/runtime/numpy_rt/templates/group_variable_get.py_000066400000000000000000000010661445201106100273640ustar00rootroot00000000000000{# Note that we use this template only for subexpressions -- for normal arrays we do not generate any code but simply access the data in the underlying array directly. See RuntimeDevice.get_with_array #} {# USES_VARIABLES { _group_idx } #} {% extends 'common_group.py_' %} {% block maincode %} # scalar code _vectorisation_idx = 1 {{scalar_code|autoindent}} # vector code _idx = {{_group_idx}} _vectorisation_idx = _idx {{vector_code|autoindent}} # _variable is set in the abstract code, see Group._get_with_array _return_values = _variable {% endblock %}brian2-2.5.4/brian2/codegen/runtime/numpy_rt/templates/group_variable_get_conditional.py_000066400000000000000000000007451445201106100317520ustar00rootroot00000000000000{# ITERATE_ALL { _idx } #} {# USES_VARIABLES { N } #} {% extends 'common_group.py_' %} {% block maincode %} # scalar code _vectorisation_idx = 1 {{scalar_code|autoindent}} # vector code _vectorisation_idx = LazyArange({{constant_or_scalar('N', variables['N'])}}) {{vector_code|autoindent}} if _cond is True: _cond = slice(None) if _cond is False: _cond = [] # _variable is set in the abstract code, see Group._get_with_code _return_values = _variable[_cond] {% endblock %} brian2-2.5.4/brian2/codegen/runtime/numpy_rt/templates/group_variable_set.py_000066400000000000000000000005021445201106100273720ustar00rootroot00000000000000{# USES_VARIABLES { _group_idx, N } #} {# ITERATE_ALL { _target_idx } #} {% extends 'common_group.py_' %} {% block maincode %} # scalar code _vectorisation_idx = 1 {{scalar_code|autoindent}} # vector code _idx = {{_group_idx}} _target_idx = slice(None) _vectorisation_idx = _idx {{vector_code|autoindent}} {% endblock %}brian2-2.5.4/brian2/codegen/runtime/numpy_rt/templates/group_variable_set_conditional.py_000066400000000000000000000027641445201106100317710ustar00rootroot00000000000000{# USES_VARIABLES { N } #} {# ALLOWS_SCALAR_WRITE #} {% extends 'common_group.py_' %} {% block maincode %} numpy_False = _numpy.bool_(False) numpy_True = _numpy.bool_(True) # Phase 1: we compute the indices where the conditional setting is to # be applied, and to do this we want to vectorise over all the values, # but we don't want to do the iterate all protocol, so we explicitly # set the idx to be slice(None) # scalar code _vectorisation_idx = 1 {{scalar_code['condition']|autoindent}} # vector code _idx = slice(None) _vectorisation_idx = LazyArange({{constant_or_scalar('N', variables['N'])}}) {{vector_code['condition']|autoindent}} # Phase 2: having computed _cond, the boolean array of points where # the setting is to be applied, we want to vectorise over idx being # only these values. {# Note that we don't write to scalar variables conditionally. The scalar code should therefore only include the calculation of scalar expressions that are used below for writing to a vector variable. The only exception is the "conditional writing" with the condition True. #} # scalar code _vectorisation_idx = 1 {{scalar_code['statement']|autoindent}} # vector code if _cond is True or _cond is numpy_True: _idx = slice(None) _vectorisation_idx = LazyArange(N) elif _cond is False or _cond is numpy_False: _idx = [] _vectorisation_idx = _numpy.array([], dtype=_numpy.int32) else: _vectorisation_idx = _idx = _numpy.nonzero(_cond)[0] {{vector_code['statement']|autoindent}} {% endblock %}brian2-2.5.4/brian2/codegen/runtime/numpy_rt/templates/ratemonitor.py_000066400000000000000000000010311445201106100260570ustar00rootroot00000000000000{# USES_VARIABLES { rate, t, _spikespace, _num_source_neurons, _clock_t, _clock_dt, _source_start, _source_stop, N } #} {% extends 'common_group.py_' %} {% block maincode %} _spikes = {{_spikespace}}[:{{_spikespace}}[-1]] # Take subgroups into account _spikes = _spikes[(_spikes >= _source_start) & (_spikes < _source_stop)] _new_len = {{N}} + 1 _owner.resize(_new_len) {{N}} = _new_len {{_dynamic_t}}[-1] = {{_clock_t}} {{_dynamic_rate}}[-1] = 1.0 * len(_spikes) / {{_clock_dt}} / _num_source_neurons {% endblock %}brian2-2.5.4/brian2/codegen/runtime/numpy_rt/templates/reset.py_000066400000000000000000000011321445201106100246400ustar00rootroot00000000000000{# Note that we don't write to scalar variables conditionally. The scalar code should therefore only include the calculation of scalar expressions that are used below for writing to a vector variable #} {% extends 'common_group.py_' %} {% block maincode %} # scalar code _vectorisation_idx = 1 {{scalar_code|autoindent}} {# Get the name of the array that stores these events (e.g. the spikespace array) #} {% set _eventspace = get_array_name(eventspace_variable) %} # vector code _idx = {{_eventspace}}[:{{_eventspace}}[-1]] _vectorisation_idx = _idx {{vector_code|autoindent}} {% endblock %}brian2-2.5.4/brian2/codegen/runtime/numpy_rt/templates/spatialstateupdate.py_000066400000000000000000000146661445201106100274370ustar00rootroot00000000000000{# USES_VARIABLES { Cm, dt, v, N, Ic, Ri, _ab_star0, _ab_star1, _ab_star2, _b_plus, _b_minus, _v_star, _u_plus, _u_minus, _v_previous, _gtot_all, _I0_all, v _c, _P_diag, _P_parent, _P_children, _B, _morph_parent_i, _starts, _ends, _morph_children, _morph_children_num, _morph_idxchild, _invr0, _invrn, _invr, r_length_1, r_length_2, area} #} {# ITERATE_ALL { _idx } #} """ Solves the cable equation (spatial diffusion of currents). This is where most time-consuming time computations are done. """ {% extends 'common_group.py_' %} {# Preparation of the data structures #} {% block before_code %} # Inverse axial resistance {{_invr}}[1:] = 1.0/({{Ri}}*(1/{{r_length_2}}[:-1] + 1/{{r_length_1}}[1:])); # Cut sections for _first in {{_starts}}: {{_invr}}[_first] = 0 # Linear systems # The particular solution """a[i,j]=ab[u+i-j,j]""" # u is the number of upper diagonals = 1 {{_ab_star0}}[1:] = {{_invr}}[1:] / {{area}}[:-1] {{_ab_star2}}[:-1] = {{_invr}}[1:] / {{area}}[1:] {{_ab_star1}}[:] = (-({{Cm}} / {{dt}}) - {{_invr}} / {{area}}) {{_ab_star1}}[:-1] -= {{_invr}}[1:] / {{area}}[:-1] # Set the boundary conditions for _counter, (_first, _last) in enumerate(zip({{_starts}}, {{_ends}})): _last = _last -1 # the compartment indices are in the interval [starts, ends[ # Inverse axial resistances at the ends: r0 and rn {{_invr0}}[_counter] = _invr0 = {{r_length_1}}[_first]/{{Ri}} {{_invrn}}[_counter] = _invrn = {{r_length_2}}[_last]/{{Ri}} # Correction for boundary conditions {{_ab_star1}}[_first] -= (_invr0 / {{area}}[_first]) {{_ab_star1}}[_last] -= (_invrn / {{area}}[_last]) # RHS for homogeneous solutions {{_b_plus}}[_last] = -(_invrn / {{area}}[_last]) {{_b_minus}}[_first] = -(_invr0 / {{area}}[_first]) {% endblock %} {% block maincode %} from numpy import pi from numpy import zeros try: from scipy.linalg import solve_banded except ImportError: raise ImportError("Install 'scipy' to run multi-compartmental neurons with numpy") # scalar code _vectorisation_idx = 1 {{scalar_code|autoindent}} # vector code _vectorisation_idx = LazyArange(N) {{vector_code|autoindent}} {{_v_previous}}[:] = {{v}} # Particular solution _b=-({{Cm}}/{{dt}}*{{v}})-_I0 _ab = zeros((3,N)) _ab[0,:] = {{_ab_star0}} _ab[1,:] = {{_ab_star1}} - _gtot _ab[2,:] = {{_ab_star2}} {{_v_star}}[:] = solve_banded((1,1),_ab,_b,overwrite_ab=True,overwrite_b=True) # Homogeneous solutions _b[:] = {{_b_plus}} _ab[0,:] = {{_ab_star0}} _ab[1,:] = {{_ab_star1}} - _gtot _ab[2,:] = {{_ab_star2}} {{_u_plus}}[:] = solve_banded((1,1),_ab,_b,overwrite_ab=True,overwrite_b=True) _b[:] = {{_b_minus}} _ab[0,:] = {{_ab_star0}} _ab[1,:] = {{_ab_star1}} - _gtot _ab[2,:] = {{_ab_star2}} {{_u_minus}}[:] = solve_banded((1,1),_ab,_b,overwrite_ab=True,overwrite_b=True) # indexing for _P_children which contains the elements above the diagonal of the coupling matrix _P children_rowlength = len({{_morph_children}})//len({{_morph_children_num}}) # Construct the coupling system with matrix _P in sparse form. s.t. # _P_diag contains the diagonal elements # _P_children contains the super diagonal entries # _P_parent contains the single sub diagonal entry for each row # _B contains the right hand side _P_children_2d = {{_P_children}}.reshape(-1, children_rowlength) for _i, (_i_parent, _i_childind, _first, _last, _invr0, _invrn) in enumerate(zip({{_morph_parent_i}}, {{_morph_idxchild}}, {{_starts}}, {{_ends}}, {{_invr0}}, {{_invrn}})): _last = _last - 1 # the compartment indices are in the interval [starts, ends[ # Towards parent if _i == 0: # first section, sealed end {{_P_diag}}[0] = {{_u_minus}}[_first] - 1 _P_children_2d[0, 0] = {{_u_plus}}[_first] # RHS {{_B}}[0] = -{{_v_star}}[_first] else: {{_P_diag}}[_i_parent] += (1 - {{_u_minus}}[_first]) * _invr0 _P_children_2d[_i_parent, _i_childind] = -{{_u_plus}}[_first] * _invr0 # RHS {{_B}}[_i_parent] += {{_v_star}}[_first] * _invr0 # Towards children {{_P_diag}}[_i+1] = (1 - {{_u_plus}}[_last]) * _invrn {{_P_parent}}[_i] = -{{_u_minus}}[_last] * _invrn # RHS {{_B}}[_i+1] = {{_v_star}}[_last] * _invrn # Solve the linear system (the result will be stored in the former rhs _B in the end) # use efficient O(n) solution of the sparse linear system (structure-specific Gaussian elemination) _morph_children_2d = {{_morph_children}}.reshape(-1, children_rowlength) # part 1: lower triangularization for _i in range(len({{_B}})-1, -1, -1): _num_children = {{_morph_children_num}}[_i]; for _k in range(_num_children): _j = _morph_children_2d[_i, _k] # child index # subtracting _subfac times the j-th from the _i-th row _subfac = _P_children_2d[_i, _k] / {{_P_diag}}[_j] {{_P_diag}}[_i] = {{_P_diag}}[_i] - _subfac * {{_P_parent}}[_j-1] {{_B}}[_i] = {{_B}}[_i] - _subfac * {{_B}}[_j] # part 2: forwards substitution {{_B}}[0] = {{_B}}[0] / {{_P_diag}}[0] # the first section does not have a parent for _i, j in enumerate({{_morph_parent_i}}): {{_B}}[_i+1] -= {{_P_parent}}[_i] * {{_B}}[j] {{_B}}[_i+1] /= {{_P_diag}}[_i+1] # For each section compute the final solution by linear combination of the general solution for _i, (_B_parent, _j_start, _j_end) in enumerate(zip({{_B}}[{{_morph_parent_i}}], {{_starts}}, {{_ends}})): _B_current = {{_B}}[_i+1] if _j_start == _j_end: {{v}}[_j_start] = ({{_v_star}}[_j_start] + _B_parent * {{_u_minus}}[_j_start] + _B_current * {{_u_plus}}[_j_start]) else: {{v}}[_j_start:_j_end] = ({{_v_star}}[_j_start:_j_end] + _B_parent * {{_u_minus}}[_j_start:_j_end] + _B_current * {{_u_plus}}[_j_start:_j_end]) {{Ic}}[:] = {{Cm}}*({{v}} - {{_v_previous}})/{{dt}} {% endblock %} brian2-2.5.4/brian2/codegen/runtime/numpy_rt/templates/spikegenerator.py_000066400000000000000000000016041445201106100265440ustar00rootroot00000000000000{# USES_VARIABLES { _spikespace, neuron_index, _timebins, _period_bins, _lastindex, t_in_timesteps } #} {% extends 'common_group.py_' %} {% block maincode %} _the_period = {{_period_bins}} _timebin = {{t_in_timesteps}} _n_spikes = 0 _lastindex_before = {{_lastindex}} if _the_period > 0: _timebin %= _the_period # If there is a periodicity in the SpikeGenerator, we need to reset the # lastindex when the period has passed if _lastindex_before > 0 and {{_timebins}}[_lastindex_before - 1] >= _timebin: _lastindex_before = 0 _n_spikes = _numpy.searchsorted({{_timebins}}[_lastindex_before:], _timebin, side='right') {{_lastindex}} = _lastindex_before + _n_spikes _indices = {{neuron_index}}[_lastindex_before:_lastindex_before+_n_spikes] {{_spikespace}}[:_n_spikes] = _indices {{_spikespace}}[-1] = _n_spikes {% endblock %} brian2-2.5.4/brian2/codegen/runtime/numpy_rt/templates/spikemonitor.py_000066400000000000000000000022221445201106100262420ustar00rootroot00000000000000{# USES_VARIABLES {N, count, _clock_t, _source_start, _source_stop, _source_N} #} {% extends 'common_group.py_' %} {% block maincode %} {# Get the name of the array that stores these events (e.g. the spikespace array) #} {% set _eventspace = get_array_name(eventspace_variable) %} _n_events = {{_eventspace}}[-1] if _n_events > 0: _events = {{_eventspace}}[:_n_events] # Take subgroups into account if _source_start != 0 or _source_stop != _source_N: _events = _events[(_events >= _source_start) & (_events < _source_stop)] _n_events = len(_events) if _n_events > 0: _vectorisation_idx = 1 {{scalar_code|autoindent}} _curlen = {{N}} _newlen = _curlen + _n_events _owner.resize(_newlen) {{N}} = _newlen _vectorisation_idx = _events _idx = _events {{vector_code|autoindent}} {% for varname, var in record_variables.items() %} {% set dynamic_varname = get_array_name(var, access_data=False) %} {{dynamic_varname}}[_curlen:_newlen] = _to_record_{{varname}} {% endfor %} {{count}}[_events - _source_start] += 1 {% endblock %} brian2-2.5.4/brian2/codegen/runtime/numpy_rt/templates/statemonitor.py_000066400000000000000000000010531445201106100262500ustar00rootroot00000000000000{# USES_VARIABLES { t, _clock_t, _indices } #} {% extends 'common_group.py_' %} {% block maincode %} # Resize dynamic arrays _new_len = len({{_dynamic_t}}) + 1 _owner.resize(_new_len) # Store values {{_dynamic_t}}[-1] = {{_clock_t}} # scalar code _vectorisation_idx = 1 {{scalar_code|autoindent}} # vector code _vectorisation_idx = {{_indices}} _idx = {{_indices}} {{vector_code|autoindent}} {% for varname, var in _recorded_variables.items() %} {{get_array_name(var, access_data=False)}}[-1, :] = _to_record_{{varname}} {% endfor %} {% endblock %} brian2-2.5.4/brian2/codegen/runtime/numpy_rt/templates/stateupdate.py_000066400000000000000000000004431445201106100260450ustar00rootroot00000000000000{# ITERATE_ALL { _idx } #} {# USES_VARIABLES { N } #} {# ALLOWS_SCALAR_WRITE #} {% extends 'common_group.py_' %} {% block maincode %} # scalar code _vectorisation_idx = 1 {{scalar_code|autoindent}} # vector code _vectorisation_idx = LazyArange(N) {{vector_code|autoindent}} {% endblock %} brian2-2.5.4/brian2/codegen/runtime/numpy_rt/templates/summed_variable.py_000066400000000000000000000022371445201106100266640ustar00rootroot00000000000000{# USES_VARIABLES { N } #} {# ITERATE_ALL { _idx } #} {% extends 'common_group.py_' %} {% block maincode %} {% set _target_var_array = get_array_name(_target_var) %} {% set _index_array = get_array_name(_index_var) %} # scalar code _vectorisation_idx = 1 {{scalar_code|autoindent}} # vector code _vectorisation_idx = LazyArange(N) {{vector_code|autoindent}} # We write to the array, using the name provided as a keyword argument to the # template # Note that for subgroups, we do not want to overwrite the full array {% if _target_start > 0 %} _indices = {{_index_array}} - {{_target_start}} {% else %} _indices = {{_index_array}} {% endif %} {# Handle the situation that the target did not have a stop variable, e.g. for synapses: #} {% if _target_stop < 0 %} _target_stop = {{constant_or_scalar(_target_size_name, variables[_target_size_name])}} {% else %} _target_stop = {{_target_stop}} {% endif %} _length = _target_stop - {{_target_start}} {{_target_var_array}}[{{_target_start}}:_target_stop] = _numpy.bincount(_indices, minlength=_length, weights=_numpy.broadcast_to(_synaptic_var, (N, ))) {% endblock %} brian2-2.5.4/brian2/codegen/runtime/numpy_rt/templates/synapses.py_000066400000000000000000000011131445201106100253620ustar00rootroot00000000000000{# USES_VARIABLES { _queue } #} {% extends 'common_group.py_' %} {% block maincode %} _spiking_synapses = _queue.peek() if len(_spiking_synapses): # scalar code {# Note that we don't write to scalar variables conditionally. The scalar code should therefore only include the calculation of scalar expressions that are used below for writing to a vector variable #} {{scalar_code|autoindent}} # vector code _idx = _spiking_synapses _vectorisation_idx = _idx {{vector_code|autoindent}} # Advance the spike queue _queue.advance() {% endblock %} brian2-2.5.4/brian2/codegen/runtime/numpy_rt/templates/synapses_create_array.py_000066400000000000000000000022151445201106100301070ustar00rootroot00000000000000{# USES_VARIABLES { _synaptic_pre, _synaptic_post, sources, targets, N, N_pre, N_post, _source_offset, _target_offset } #} {# WRITES_TO_READ_ONLY_VARIABLES { _synaptic_pre, _synaptic_post, N} #} {# This is to show that we don't need to index the sources/targets #} {# ITERATE_ALL { _idx } #} {% extends 'common_group.py_' %} {% block maincode %} {# After this code has been executed, the arrays _real_sources and _real_variables contain the final indices. Having any code here at all is only necessary for supporting subgroups #} {{vector_code|autoindent}} _old_num_synapses = {{N}} _new_num_synapses = _old_num_synapses + len({{sources}}) {{_dynamic__synaptic_pre}}.resize(_new_num_synapses) {{_dynamic__synaptic_post}}.resize(_new_num_synapses) {{_dynamic__synaptic_pre}}[_old_num_synapses:] = _real_sources {{_dynamic__synaptic_post}}[_old_num_synapses:] = _real_targets # Resize all dependent dynamic arrays (synaptic weights, delays, etc.) and set # the total number of synapses _owner._resize(_new_num_synapses) # And update N_incoming, N_outgoing and synapse_number _owner._update_synapse_numbers(_old_num_synapses) {% endblock %} brian2-2.5.4/brian2/codegen/runtime/numpy_rt/templates/synapses_create_generator.py_000066400000000000000000000230651445201106100307650ustar00rootroot00000000000000{# USES_VARIABLES { _synaptic_pre, _synaptic_post, N, N_pre, N_post, _source_offset, _target_offset } #} {# WRITES_TO_READ_ONLY_VARIABLES { _synaptic_pre, _synaptic_post, N} #} {# ITERATE_ALL { _idx } #} {% extends 'common_group.py_' %} {% block maincode %} # Only need to do the initial import once, and trying is not expensive # only catching exceptions is expensive (which we just do the first time) try: _done_initial_import except: _done_initial_import = True import sys as _sys from numpy.random import rand as _np_rand if _sys.version_info[0]==2: range = xrange from numpy.random import binomial as _binomial import bisect as _bisect def _sample_without_replacement(low, high, step, p=None, size=None): if p == 0: return _numpy.array([], dtype=_numpy.int32) if p == 1: return _numpy.arange(low, high, step) if step > 0: n = (high-low-1)//step+1 else: n = (low-high-1)//-step+1 if n <= 0 or (size is not None and size == 0): return _numpy.array([], dtype=_numpy.int32) elif size is not None and size < 0: {% if skip_if_invalid %} return _numpy.array([], dtype=_numpy.int32) {% else %} raise IndexError(f"Requested sample size {size} is negative.") {% endif %} elif size is not None and size > n: {% if skip_if_invalid %} size = n {% else %} raise IndexError(f"Requested sample size {size} is bigger than the " f"population size {n}.") {% endif %} if n == size: return _numpy.arange(low, high, step) if p is None: if step == 1: _choice = _numpy.random.choice(n, size=size, replace=False) + low else: _chose_from = _numpy.arange(low, high, step) _choice = _numpy.random.choice(_chose_from, size=size, replace=False) return _numpy.sort(_choice) if n <= 1000 or p > 0.25: # based on rough profiling samples, = (_np_rand(n)= n: break if len(all_samples) > 1: samples = _numpy.hstack(all_samples) samples = samples[:_bisect.bisect_left(samples, n)] # only keep the ones in the range we want return samples*step+low # number of synapses in the beginning _old_num_synapses = {{N}} # number of synapses during the creation process _cur_num_synapses = _old_num_synapses # scalar code _vectorisation_idx = 1 {{scalar_code['setup_iterator']|autoindent}} {{scalar_code['generator_expr']|autoindent}} {{scalar_code['create_cond']|autoindent}} {{scalar_code['update']|autoindent}} {# For a connect call j='k+i for k in range(0, N_post, 2) if k+i < N_post' "j" is called the "result index" (and "_post_idx" the "result index array", etc.) "i" is called the "outer index" (and "_pre_idx" the "outer index array", etc.) "k" is called the inner variable #} for _{{outer_index}} in range({{outer_index_size}}): _raw{{outer_index_array}} = _{{outer_index}} + {{outer_index_offset}} {% if not result_index_condition %} {{vector_code['create_cond']|autoindent}} if not _cond: continue {% endif %} {{vector_code['setup_iterator']|autoindent}} {% if iterator_func=='range' %} {{inner_variable}} = _numpy.arange(_iter_low, _iter_high, _iter_step) {% elif iterator_func=='sample' %} {% if iterator_kwds['sample_size'] == 'fixed' %} {{inner_variable}} = _sample_without_replacement(_iter_low, _iter_high, _iter_step, None, size=_iter_size) {% else %} {{inner_variable}} = _sample_without_replacement(_iter_low, _iter_high, _iter_step, _iter_p) {% endif %} {% endif %} _vectorisation_idx = {{inner_variable}} {{vector_code['generator_expr']|autoindent}} _vectorisation_idx = _{{result_index}} _raw{{result_index_array}} = _{{result_index}} + {{result_index_offset}} {% if result_index_condition %} {% if result_index_used %} {# The condition could index outside of array range #} {% if skip_if_invalid %} if _numpy.isscalar(_{{result_index}}): if _{{result_index}}<0 or _{{result_index}}>={{result_index_size}}: continue else: _in_range = _numpy.logical_and(_{{result_index}}>=0, _{{result_index}}<{{result_index_size}}) _{{result_index}} = _{{result_index}}[_in_range] {% else %} if _numpy.isscalar(_{{result_index}}): _min_val = _max_val = _{{result_index}} elif _{{result_index}}.shape == (0, ): continue else: _min_val = _numpy.min(_{{result_index}}) _max_val = _numpy.max(_{{result_index}}) if _min_val < 0: # Stick to % formatting, since curly braces are used by Jinja2 already raise IndexError("index {{result_index}}=%d outside allowed range from 0 to %d, and the condition refers to a post-synaptic variable" % (_min_val, {{result_index_size}}-1)) elif _max_val >= {{result_index_size}}: raise IndexError("index {{result_index}}=%d outside allowed range from 0 to %d, and the condition refers to a post-synaptic variable" % (_max_val, {{result_index_size}}-1)) {% endif %} {% endif %} {{vector_code['create_cond']|autoindent}} {% endif %} {% if if_expression!='True' and result_index_condition %} _{{result_index}}, _cond = _numpy.broadcast_arrays(_{{result_index}}, _cond) _{{result_index}} = _{{result_index}}[_cond] {% else %} _{{result_index}}, {{inner_variable}} = _numpy.broadcast_arrays(_{{result_index}}, {{inner_variable}}) {% endif %} {% if skip_if_invalid %} if _numpy.isscalar(_{{result_index}}): if _{{result_index}}<0 or _{{result_index}}>={{result_index_size}}: continue else: _in_range = _numpy.logical_and(_{{result_index}}>=0, _{{result_index}}<{{result_index_size}}) _{{result_index}} = _{{result_index}}[_in_range] {% elif not result_index_used %} {# Otherwise, we already checked before #} if _numpy.isscalar(_{{result_index}}): _min_val = _max_val = _{{result_index}} elif _{{result_index}}.shape == (0, ): continue else: _min_val = _numpy.min(_{{result_index}}) _max_val = _numpy.max(_{{result_index}}) if _min_val < 0: raise IndexError("index _{{result_index}}=%d outside allowed range from 0 to %d" % (_min_val, {{result_index_size}}-1)) elif _max_val >= {{result_index_size}}: raise IndexError("index _{{result_index}}=%d outside allowed range from 0 to %d" % (_max_val, {{result_index_size}}-1)) {% endif %} _vectorisation_idx = _{{result_index}} _raw{{result_index_array}} = _{{result_index}} + {{result_index_offset}} {{vector_code['update']|autoindent}} if not _numpy.isscalar(_n): # The "n" expression involved the target variable {{result_index_array}} = {{result_index_array}}.repeat(_n[_j]) elif _n != 1: # We have a target-independent number {{result_index_array}} = {{result_index_array}}.repeat(_n) _numnew = len({{result_index_array}}) _new_num_synapses = _cur_num_synapses + _numnew {{_dynamic__synaptic_pre}}.resize(_new_num_synapses) {{_dynamic__synaptic_post}}.resize(_new_num_synapses) {{_dynamic__synaptic_pre}}[_cur_num_synapses:] = _pre_idx {{_dynamic__synaptic_post}}[_cur_num_synapses:] = _post_idx _cur_num_synapses += _numnew # Resize all dependent dynamic arrays (synaptic weights, delays, etc.) and set # the total number of synapses _owner._resize(_cur_num_synapses) # And update N_incoming, N_outgoing and synapse_number _owner._update_synapse_numbers(_old_num_synapses) {% endblock %} brian2-2.5.4/brian2/codegen/runtime/numpy_rt/templates/synapses_push_spikes.py_000066400000000000000000000002351445201106100300030ustar00rootroot00000000000000{% extends 'common_group.py_' %} {% block before_code %} _owner.initialise_queue() {% endblock %} {% block maincode %} _owner.push_spikes() {% endblock %} brian2-2.5.4/brian2/codegen/runtime/numpy_rt/templates/threshold.py_000066400000000000000000000023721445201106100255210ustar00rootroot00000000000000{# USES_VARIABLES { N } #} {# ITERATE_ALL { _idx } #} {# t, not_refractory and lastspike are added as needed_variables in the Thresholder class, we cannot use the USES_VARIABLE mechanism conditionally Same goes for "eventspace" (e.g. spikespace) which depends on the type of event #} {% extends 'common_group.py_' %} {% block addcode %} {% endblock %} {% block maincode %} numpy_True = _numpy.bool_(True) numpy_False = _numpy.bool_(False) # scalar code _vectorisation_idx = 1 {{scalar_code|autoindent}} # vector code _vectorisation_idx = LazyArange(N) {{vector_code|autoindent}} if _cond is True or _cond is numpy_True: _events = _numpy.arange(N) elif _cond is False or _cond is numpy_False: _events = _numpy.array([]) else: _events, = _cond.nonzero() {# Get the name of the array that stores these events (e.g. the spikespace array) #} {% set _eventspace = get_array_name(eventspace_variable) %} {{_eventspace}}[-1] = len(_events) {{_eventspace}}[:len(_events)] = _events {% if _uses_refractory %} # Set the neuron to refractory {{not_refractory}}[_events] = False {{lastspike}}[_events] = {{t}} {% endif %} {% endblock %} {% block after_code %} {% set _eventspace = get_array_name(eventspace_variable) %} {{_eventspace}}[-1] = 0 {% endblock %}brian2-2.5.4/brian2/codegen/statements.py000066400000000000000000000074361445201106100202250ustar00rootroot00000000000000""" Module providing the `Statement` class. """ class Statement: """ A single line mathematical statement. The structure is ``var op expr``. Parameters ---------- var : str The left hand side of the statement, the value being written to. op : str The operation, can be any of the standard Python operators (including ``+=`` etc.) or a special operator ``:=`` which means you are defining a new symbol (whereas ``=`` means you are setting the value of an existing symbol). expr : str, `Expression` The right hand side of the statement. dtype : `dtype` The numpy dtype of the value or array `var`. constant : bool, optional Set this flag to ``True`` if the value will not change (only applies for ``op==':='``. subexpression : bool, optional Set this flag to ``True`` if the variable is a subexpression. In some languages (e.g. Python) you can use this to save a memory copy, because you don't need to do ``lhs[:] = rhs`` but a redefinition ``lhs = rhs``. scalar : bool, optional Set this flag to ``True`` if `var` and `expr` are scalar. Notes ----- Will compute the following attribute: ``inplace`` True or False depending if the operation is in-place or not. Boolean simplification notes: Will initially set the attribute ``used_boolean_variables`` to ``None``. This is set by `~brian2.codegen.optimisation.optimise_statements` when it is called on a sequence of statements to the list of boolean variables that are used in this expression. In addition, the attribute ``boolean_simplified_expressions`` is set to a dictionary with keys consisting of a tuple of pairs ``(var, value)`` where ``var`` is the name of the boolean variable (will be in ``used_boolean_variables``) and ``var`` is ``True`` or ``False``. The values of the dictionary are strings representing the simplified version of the expression if each ``var=value`` substitution is made for that key. The keys will range over all possible values of the set of boolean variables. The complexity of the original statement is set as the attribute ``complexity_std``, and the complexity of the simplified versions are in the dictionary ``complexities`` (with the same keys). This information can be used to generate code that replaces a complex expression that varies depending on the value of one or more boolean variables with an ``if/then`` sequence where each subexpression is simplified. It is optional to use this (e.g. the numpy codegen does not, but the cython one does). """ def __init__( self, var, op, expr, comment, dtype, constant=False, subexpression=False, scalar=False, ): self.var = var.strip() self.op = op.strip() self.expr = expr self.comment = comment self.dtype = dtype self.constant = constant self.subexpression = subexpression self.scalar = scalar if constant and self.op != ":=": raise ValueError(f"Should not set constant flag for operation {self.op}") if op.endswith("=") and op != "=" and op != ":=": self.inplace = True else: self.inplace = False self.used_boolean_variables = None self.boolean_simplified_expressions = None def __str__(self): s = f"{self.var} {self.op} {str(self.expr)}" if self.constant: s += " (constant)" if self.subexpression: s += " (subexpression)" if self.inplace: s += " (in-place)" if len(self.comment): s += f" # {self.comment}" return s __repr__ = __str__ brian2-2.5.4/brian2/codegen/targets.py000066400000000000000000000002651445201106100175000ustar00rootroot00000000000000""" Module that stores all known code generation targets as `codegen_targets`. """ __all__ = ["codegen_targets"] # This should be filled in by subpackages codegen_targets = set() brian2-2.5.4/brian2/codegen/templates.py000066400000000000000000000230451445201106100200260ustar00rootroot00000000000000""" Handles loading templates from a directory. """ import re from collections.abc import Mapping from jinja2 import ( ChoiceLoader, Environment, PackageLoader, StrictUndefined, TemplateNotFound, ) from brian2.utils.stringtools import get_identifiers, indent, strip_empty_lines __all__ = ["Templater"] AUTOINDENT_START = "%%START_AUTOINDENT%%" AUTOINDENT_END = "%%END_AUTOINDENT%%" def autoindent(code): if isinstance(code, list): code = "\n".join(code) if not code.startswith("\n"): code = f"\n{code}" if not code.endswith("\n"): code = f"{code}\n" return AUTOINDENT_START + code + AUTOINDENT_END def autoindent_postfilter(code): lines = code.split("\n") outlines = [] addspaces = 0 for line in lines: if AUTOINDENT_START in line: if addspaces > 0: raise SyntaxError("Cannot nest autoindents") addspaces = line.find(AUTOINDENT_START) line = line.replace(AUTOINDENT_START, "") if AUTOINDENT_END in line: line = line.replace(AUTOINDENT_END, "") addspaces = 0 outlines.append(" " * addspaces + line) return "\n".join(outlines) def variables_to_array_names(variables, access_data=True): from brian2.devices.device import get_device device = get_device() names = [device.get_array_name(var, access_data=access_data) for var in variables] return names class LazyTemplateLoader: """ Helper object to load templates only when they are needed. """ def __init__(self, environment, extension): self.env = environment self.extension = extension self._templates = {} def get_template(self, name): if name not in self._templates: try: template = CodeObjectTemplate( self.env.get_template(name + self.extension), self.env.loader.get_source(self.env, name + self.extension)[0], ) except TemplateNotFound: try: # Try without extension as well (e.g. for makefiles) template = CodeObjectTemplate( self.env.get_template(name), self.env.loader.get_source(self.env, name)[0], ) except TemplateNotFound: raise KeyError(f'No template with name "{name}" found.') self._templates[name] = template return self._templates[name] class Templater: """ Class to load and return all the templates a `CodeObject` defines. Parameters ---------- package_name : str, tuple of str The package where the templates are saved. If this is a tuple then each template will be searched in order starting from the first package in the tuple until the template is found. This allows for derived templates to be used. See also `~Templater.derive`. extension : str The file extension (e.g. ``.pyx``) used for the templates. env_globals : dict (optional) A dictionary of global values accessible by the templates. Can be used for providing utility functions. In all cases, the filter 'autoindent' is available (see existing templates for example usage). templates_dir : str, tuple of str, optional The name of the directory containing the templates. Defaults to ``'templates'``. Notes ----- Templates are accessed using ``templater.template_base_name`` (the base name is without the file extension). This returns a `CodeObjectTemplate`. """ def __init__( self, package_name, extension, env_globals=None, templates_dir="templates" ): if isinstance(package_name, str): package_name = (package_name,) if isinstance(templates_dir, str): templates_dir = (templates_dir,) loader = ChoiceLoader( [ PackageLoader(name, t_dir) for name, t_dir in zip(package_name, templates_dir) ] ) self.env = Environment( loader=loader, trim_blocks=True, lstrip_blocks=True, undefined=StrictUndefined, ) self.env.globals["autoindent"] = autoindent self.env.filters["autoindent"] = autoindent self.env.filters["variables_to_array_names"] = variables_to_array_names if env_globals is not None: self.env.globals.update(env_globals) else: env_globals = {} self.env_globals = env_globals self.package_names = package_name self.templates_dir = templates_dir self.extension = extension self.templates = LazyTemplateLoader(self.env, extension) def __getattr__(self, item): return self.templates.get_template(item) def derive( self, package_name, extension=None, env_globals=None, templates_dir="templates" ): """ Return a new Templater derived from this one, where the new package name and globals overwrite the old. """ if extension is None: extension = self.extension if isinstance(package_name, str): package_name = (package_name,) if env_globals is None: env_globals = {} if isinstance(templates_dir, str): templates_dir = (templates_dir,) package_name = package_name + self.package_names templates_dir = templates_dir + self.templates_dir new_env_globals = self.env_globals.copy() new_env_globals.update(**env_globals) return Templater( package_name, extension=extension, env_globals=new_env_globals, templates_dir=templates_dir, ) class CodeObjectTemplate: """ Single template object returned by `Templater` and used for final code generation Should not be instantiated by the user, but only directly by `Templater`. Notes ----- The final code is obtained from this by calling the template (see `~CodeObjectTemplater.__call__`). """ def __init__(self, template, template_source): self.template = template self.template_source = template_source #: The set of variables in this template self.variables = set() #: The indices over which the template iterates completely self.iterate_all = set() #: Read-only variables that are changed by this template self.writes_read_only = set() # This is the bit inside {} for USES_VARIABLES { list of words } specifier_blocks = re.findall( r"\bUSES_VARIABLES\b\s*\{(.*?)\}", template_source, re.M | re.S ) # Same for ITERATE_ALL iterate_all_blocks = re.findall( r"\bITERATE_ALL\b\s*\{(.*?)\}", template_source, re.M | re.S ) # And for WRITES_TO_READ_ONLY_VARIABLES writes_read_only_blocks = re.findall( r"\bWRITES_TO_READ_ONLY_VARIABLES\b\s*\{(.*?)\}", template_source, re.M | re.S, ) #: Does this template allow writing to scalar variables? self.allows_scalar_write = "ALLOWS_SCALAR_WRITE" in template_source for block in specifier_blocks: self.variables.update(get_identifiers(block)) for block in iterate_all_blocks: self.iterate_all.update(get_identifiers(block)) for block in writes_read_only_blocks: self.writes_read_only.update(get_identifiers(block)) def __call__(self, scalar_code, vector_code, **kwds): """ Return a usable code block or blocks from this template. Parameters ---------- scalar_code : dict Dictionary of scalar code blocks. vector_code : dict Dictionary of vector code blocks **kwds Additional parameters to pass to the template Notes ----- Returns either a string (if macros were not used in the template), or a `MultiTemplate` (if macros were used). """ if ( scalar_code is not None and len(scalar_code) == 1 and list(scalar_code)[0] is None ): scalar_code = scalar_code[None] if ( vector_code is not None and len(vector_code) == 1 and list(vector_code)[0] is None ): vector_code = vector_code[None] kwds["scalar_code"] = scalar_code kwds["vector_code"] = vector_code module = self.template.make_module(kwds) if len([k for k in module.__dict__ if not k.startswith("_")]): return MultiTemplate(module) else: return autoindent_postfilter(str(module)) class MultiTemplate(Mapping): """ Code generated by a `CodeObjectTemplate` with multiple blocks Each block is a string stored as an attribute with the block name. The object can also be accessed as a dictionary. """ def __init__(self, module): self._templates = {} for k, f in module.__dict__.items(): if not k.startswith("_"): s = autoindent_postfilter(str(f())) setattr(self, k, s) self._templates[k] = s def __getitem__(self, item): return self._templates[item] def __iter__(self): return iter(self._templates) def __len__(self): return len(self._templates) def __str__(self): s = "" for k, v in list(self._templates.items()): s += f"{k}:\n" s += f"{strip_empty_lines(indent(v))}\n" return s __repr__ = __str__ brian2-2.5.4/brian2/codegen/translation.py000066400000000000000000000407561445201106100203760ustar00rootroot00000000000000""" This module translates a series of statements into a language-specific syntactically correct code block that can be inserted into a template. It infers whether or not a variable can be declared as constant, etc. It should handle common subexpressions, and so forth. The input information needed: * The sequence of statements (a multiline string) in standard mathematical form * The list of known variables, common subexpressions and functions, and for each variable whether or not it is a value or an array, and if an array what the dtype is. * The dtype to use for newly created variables * The language to translate to """ import re from collections.abc import Mapping import numpy as np import sympy from brian2.core.functions import Function from brian2.core.preferences import prefs from brian2.core.variables import AuxiliaryVariable, Subexpression, Variable from brian2.parsing.bast import brian_ast from brian2.parsing.statements import parse_statement from brian2.parsing.sympytools import str_to_sympy, sympy_to_str from brian2.utils.caching import cached from brian2.utils.stringtools import deindent, get_identifiers, strip_empty_lines from brian2.utils.topsort import topsort from .optimisation import optimise_statements from .statements import Statement __all__ = ["analyse_identifiers", "get_identifiers_recursively"] class LineInfo: """ A helper class, just used to store attributes. """ def __init__(self, **kwds): for k, v in kwds.items(): setattr(self, k, v) # TODO: This information should go somewhere else, I guess STANDARD_IDENTIFIERS = {"and", "or", "not", "True", "False"} def analyse_identifiers(code, variables, recursive=False): """ Analyses a code string (sequence of statements) to find all identifiers by type. In a given code block, some variable names (identifiers) must be given as inputs to the code block, and some are created by the code block. For example, the line:: a = b+c This could mean to create a new variable a from b and c, or it could mean modify the existing value of a from b or c, depending on whether a was previously known. Parameters ---------- code : str The code string, a sequence of statements one per line. variables : dict of `Variable`, set of names Specifiers for the model variables or a set of known names recursive : bool, optional Whether to recurse down into subexpressions (defaults to ``False``). Returns ------- newly_defined : set A set of variables that are created by the code block. used_known : set A set of variables that are used and already known, a subset of the ``known`` parameter. unknown : set A set of variables which are used by the code block but not defined by it and not previously known. Should correspond to variables in the external namespace. """ if isinstance(variables, Mapping): known = { k for k, v in variables.items() if not isinstance(k, AuxiliaryVariable) } else: known = set(variables) variables = {k: Variable(name=k, dtype=np.float64) for k in known} known |= STANDARD_IDENTIFIERS scalar_stmts, vector_stmts = make_statements( code, variables, np.float64, optimise=False ) stmts = scalar_stmts + vector_stmts defined = {stmt.var for stmt in stmts if stmt.op == ":="} if len(stmts) == 0: allids = set() elif recursive: if not isinstance(variables, Mapping): raise TypeError("Have to specify a variables dictionary.") allids = get_identifiers_recursively( [stmt.expr for stmt in stmts], variables ) | {stmt.var for stmt in stmts} else: allids = set.union(*[get_identifiers(stmt.expr) for stmt in stmts]) | { stmt.var for stmt in stmts } dependent = allids.difference(defined, known) used_known = allids.intersection(known) - STANDARD_IDENTIFIERS return defined, used_known, dependent def get_identifiers_recursively(expressions, variables, include_numbers=False): """ Gets all the identifiers in a list of expressions, recursing down into subexpressions. Parameters ---------- expressions : list of str List of expressions to check. variables : dict-like Dictionary of `Variable` objects include_numbers : bool, optional Whether to include number literals in the output. Defaults to ``False``. """ if len(expressions): identifiers = set.union( *[ get_identifiers(expr, include_numbers=include_numbers) for expr in expressions ] ) else: identifiers = set() for name in set(identifiers): if name in variables and isinstance(variables[name], Subexpression): s_identifiers = get_identifiers_recursively( [variables[name].expr], variables, include_numbers=include_numbers ) identifiers |= s_identifiers return identifiers def is_scalar_expression(expr, variables): """ Whether the given expression is scalar. Parameters ---------- expr : str The expression to check variables : dict-like `Variable` and `Function` object for all the identifiers used in `expr` Returns ------- scalar : bool Whether `expr` is a scalar expression """ # determine whether this is a scalar variable identifiers = get_identifiers_recursively([expr], variables) # In the following we assume that all unknown identifiers are # scalar constants -- this should cover numerical literals and # e.g. "True" or "inf". return all( name not in variables or getattr(variables[name], "scalar", False) or (isinstance(variables[name], Function) and variables[name].stateless) for name in identifiers ) @cached def make_statements(code, variables, dtype, optimise=True, blockname=""): """ make_statements(code, variables, dtype, optimise=True, blockname='') Turn a series of abstract code statements into Statement objects, inferring whether each line is a set/declare operation, whether the variables are constant or not, and handling the cacheing of subexpressions. Parameters ---------- code : str A (multi-line) string of statements. variables : dict-like A dictionary of with `Variable` and `Function` objects for every identifier used in the `code`. dtype : `dtype` The data type to use for temporary variables optimise : bool, optional Whether to optimise expressions, including pulling out loop invariant expressions and putting them in new scalar constants. Defaults to ``False``, since this function is also used just to in contexts where we are not interested by this kind of optimisation. For the main code generation stage, its value is set by the `codegen.loop_invariant_optimisations` preference. blockname : str, optional A name for the block (used to name intermediate variables to avoid name clashes when multiple blocks are used together) Returns ------- scalar_statements, vector_statements : (list of `Statement`, list of `Statement`) Lists with statements that are to be executed once and statements that are to be executed once for every neuron/synapse/... (or in a vectorised way) Notes ----- If ``optimise`` is ``True``, then the ``scalar_statements`` may include newly introduced scalar constants that have been identified as loop-invariant and have therefore been pulled out of the vector statements. The resulting statements will also use augmented assignments where possible, i.e. a statement such as ``w = w + 1`` will be replaced by ``w += 1``. Also, statements involving booleans will have additional information added to them (see `Statement` for details) describing how the statement can be reformulated as a sequence of if/then statements. Calls `~brian2.codegen.optimisation.optimise_statements`. """ code = strip_empty_lines(deindent(code)) lines = re.split(r"[;\n]", code) lines = [LineInfo(code=line) for line in lines if len(line)] # Do a copy so we can add stuff without altering the original dict variables = dict(variables) # we will do inference to work out which lines are := and which are = defined = {k for k, v in variables.items() if not isinstance(v, AuxiliaryVariable)} for line in lines: statement = None # parse statement into "var op expr" var, op, expr, comment = parse_statement(line.code) if var in variables and isinstance(variables[var], Subexpression): raise SyntaxError( f"Illegal line '{line.code}' in abstract code. Cannot write to" f" subexpression '{var}'." ) if op == "=": if var not in defined: op = ":=" defined.add(var) if var not in variables: annotated_ast = brian_ast(expr, variables) is_scalar = annotated_ast.scalar if annotated_ast.dtype == "boolean": use_dtype = bool elif annotated_ast.dtype == "integer": use_dtype = int else: use_dtype = dtype new_var = AuxiliaryVariable(var, dtype=use_dtype, scalar=is_scalar) variables[var] = new_var elif not variables[var].is_boolean: sympy_expr = str_to_sympy(expr, variables) if variables[var].is_integer: sympy_var = sympy.Symbol(var, integer=True) else: sympy_var = sympy.Symbol(var, real=True) try: collected = sympy.collect( sympy_expr, sympy_var, exact=True, evaluate=False ) except AttributeError: # If something goes wrong during collection, e.g. collect # does not work for logical expressions collected = {1: sympy_expr} if ( len(collected) == 2 and set(collected.keys()) == {1, sympy_var} and collected[sympy_var] == 1 ): # We can replace this statement by a += assignment statement = Statement( var, "+=", sympy_to_str(collected[1]), comment, dtype=variables[var].dtype, scalar=variables[var].scalar, ) elif len(collected) == 1 and sympy_var in collected: # We can replace this statement by a *= assignment statement = Statement( var, "*=", sympy_to_str(collected[sympy_var]), comment, dtype=variables[var].dtype, scalar=variables[var].scalar, ) if statement is None: statement = Statement( var, op, expr, comment, dtype=variables[var].dtype, scalar=variables[var].scalar, ) line.statement = statement # for each line will give the variable being written to line.write = var # each line will give a set of variables which are read line.read = get_identifiers_recursively([expr], variables) # All writes to scalar variables must happen before writes to vector # variables scalar_write_done = False for line in lines: stmt = line.statement if stmt.op != ":=" and variables[stmt.var].scalar and scalar_write_done: raise SyntaxError( "All writes to scalar variables in a code block " "have to be made before writes to vector " f"variables. Illegal write to '{line.write}'." ) elif not variables[stmt.var].scalar: scalar_write_done = True # backwards compute whether or not variables will be read again # note that will_read for a line gives the set of variables it will read # on the current line or subsequent ones. will_write gives the set of # variables that will be written after the current line will_read = set() will_write = set() for line in lines[::-1]: will_read = will_read.union(line.read) line.will_read = will_read.copy() line.will_write = will_write.copy() will_write.add(line.write) subexpressions = { name: val for name, val in variables.items() if isinstance(val, Subexpression) } # Check that no scalar subexpression refers to a vectorised function # (e.g. rand()) -- otherwise it would be differently interpreted depending # on whether it is used in a scalar or a vector context (i.e., even though # the subexpression is supposed to be scalar, it would be vectorised when # used as part of non-scalar expressions) for name, subexpr in subexpressions.items(): if subexpr.scalar: identifiers = get_identifiers(subexpr.expr) for identifier in identifiers: if identifier in variables and getattr( variables[identifier], "auto_vectorise", False ): raise SyntaxError( f"The scalar subexpression '{name}' refers to " f"the implicitly vectorised function '{identifier}' " "-- this is not allowed since it leads " "to different interpretations of this " "subexpression depending on whether it " "is used in a scalar or vector " "context." ) # sort subexpressions into an order so that subexpressions that don't depend # on other subexpressions are first subexpr_deps = { name: [dep for dep in subexpr.identifiers if dep in subexpressions] for name, subexpr in subexpressions.items() } sorted_subexpr_vars = topsort(subexpr_deps) statements = [] # none are yet defined (or declared) subdefined = {name: None for name in subexpressions} for line in lines: # update/define all subexpressions needed by this statement for var in sorted_subexpr_vars: if var not in line.read: continue subexpression = subexpressions[var] # if already defined/declared if subdefined[var] == "constant": continue elif subdefined[var] == "variable": op = "=" constant = False else: op = ":=" # check if the referred variables ever change ids = subexpression.identifiers constant = all(v not in line.will_write for v in ids) subdefined[var] = "constant" if constant else "variable" statement = Statement( var, op, subexpression.expr, comment="", dtype=variables[var].dtype, constant=constant, subexpression=True, scalar=variables[var].scalar, ) statements.append(statement) stmt = line.statement var, op, expr, comment = stmt.var, stmt.op, stmt.expr, stmt.comment # constant only if we are declaring a new variable and we will not # write to it again constant = op == ":=" and var not in line.will_write statement = Statement( var, op, expr, comment, dtype=variables[var].dtype, constant=constant, scalar=variables[var].scalar, ) statements.append(statement) scalar_statements = [s for s in statements if s.scalar] vector_statements = [s for s in statements if not s.scalar] if optimise and prefs.codegen.loop_invariant_optimisations: scalar_statements, vector_statements = optimise_statements( scalar_statements, vector_statements, variables, blockname=blockname ) return scalar_statements, vector_statements brian2-2.5.4/brian2/conftest.py000066400000000000000000000106201445201106100162440ustar00rootroot00000000000000""" Module containing fixtures and hooks used by the pytest test suite. """ import re import numpy as np import pytest from brian2.core.clocks import defaultclock from brian2.core.functions import DEFAULT_FUNCTIONS, Function from brian2.devices import get_device, reinit_devices from brian2.devices.device import reinit_and_delete, set_device from brian2.units import ms def pytest_ignore_collect(path, config): if config.option.doctestmodules: if "tests" in str(path): return True # Ignore tests package for doctests # Do not test brian2.hears bridge (needs Brian1) if str(path).endswith("hears.py"): return True # The "random" values are always 0.5 def fake_randn(vectorisation_idx): return 0.5 * np.ones_like(vectorisation_idx) fake_randn = Function( fake_randn, arg_units=[], return_unit=1, auto_vectorise=True, stateless=False ) fake_randn.implementations.add_implementation( "cpp", """ double randn(int vectorisation_idx) { return 0.5; } """, ) fake_randn.implementations.add_implementation( "cython", """ cdef double randn(int vectorisation_idx): return 0.5 """, ) @pytest.fixture def fake_randn_randn_fixture(): orig_randn = DEFAULT_FUNCTIONS["randn"] DEFAULT_FUNCTIONS["randn"] = fake_randn yield None DEFAULT_FUNCTIONS["randn"] = orig_randn # Fixture that is used for all tests @pytest.fixture(autouse=True) def setup_and_teardown(request): # Set preferences before each test import brian2 if hasattr(request.config, "workerinput"): config = request.config.workerinput for key, value in config["brian_prefs"].items(): if isinstance(value, tuple) and value[0] == "TYPE": matches = re.match(r"<(type|class) 'numpy\.(.+)'>", value[1]) if matches is None or len(matches.groups()) != 2: raise TypeError( f"Do not know how to handle {value[1]} in preferences" ) t = matches.groups()[1] if t == "float64": value = np.float64 elif t == "float32": value = np.float32 elif t == "int64": value = np.int64 elif t == "int32": value = np.int32 brian2.prefs[key] = value set_device(config["device"], **config["device_options"]) else: for k, v in request.config.brian_prefs.items(): brian2.prefs[k] = v set_device(request.config.device, **request.config.device_options) brian2.prefs._backup() # Print output changed in numpy 1.14, stick with the old format to # avoid doctest failures try: np.set_printoptions(legacy="1.13") except TypeError: pass # using a numpy version < 1.14 yield # run test # Reset defaultclock.dt to be sure defaultclock.dt = 0.1 * ms # (Optionally) mark tests raising NotImplementedError as skipped (mostly used # for testing Brian2GeNN) @pytest.hookimpl(hookwrapper=True) def pytest_runtest_makereport(item, call): if hasattr(item.config, "workerinput"): fail_for_not_implemented = item.config.workerinput["fail_for_not_implemented"] else: fail_for_not_implemented = item.config.fail_for_not_implemented outcome = yield rep = outcome.get_result() if rep.outcome == "failed": project_dir = getattr(get_device(), "project_dir", None) if project_dir is not None: rep.sections.append(("Standalone project directory", f"{project_dir}")) reinit_devices() if not fail_for_not_implemented: exc_cause = getattr(call.excinfo.value, "__cause__", None) if call.excinfo.errisinstance(NotImplementedError) or isinstance( exc_cause, NotImplementedError ): rep.outcome = "skipped" r = call.excinfo._getreprcrash() rep.longrepr = (str(r.path), r.lineno, r.message) else: # clean up after the test (delete directory for standalone) reinit_and_delete() brian2-2.5.4/brian2/core/000077500000000000000000000000001445201106100147765ustar00rootroot00000000000000brian2-2.5.4/brian2/core/.gitignore000066400000000000000000000000141445201106100167610ustar00rootroot00000000000000/test_prefs brian2-2.5.4/brian2/core/__init__.py000066400000000000000000000002471445201106100171120ustar00rootroot00000000000000""" Essential Brian modules, in particular base classes for all kinds of brian objects. Built-in preferences -------------------- .. document_brian_prefs:: core """ brian2-2.5.4/brian2/core/base.py000066400000000000000000000324001445201106100162610ustar00rootroot00000000000000""" All Brian objects should derive from `BrianObject`. """ import functools import os import sys import traceback import weakref from brian2.core.names import Nameable from brian2.units.allunits import second from brian2.units.fundamentalunits import check_units from brian2.utils.logger import get_logger __all__ = [ "BrianObject", "BrianObjectException", ] logger = get_logger(__name__) class BrianObject(Nameable): """ All Brian objects derive from this class, defines magic tracking and update. See the documentation for `Network` for an explanation of which objects get updated in which order. Parameters ---------- dt : `Quantity`, optional The time step to be used for the simulation. Cannot be combined with the `clock` argument. clock : `Clock`, optional The update clock to be used. If neither a clock, nor the `dt` argument is specified, the `defaultclock` will be used. when : str, optional In which scheduling slot to simulate the object during a time step. Defaults to ``'start'``. See :ref:`scheduling` for possible values. order : int, optional The priority of this object for operations occurring at the same time step and in the same scheduling slot. Defaults to 0. namespace: dict, optional A dictionary mapping identifier names to objects. If not given, the namespace will be filled in at the time of the call of `Network.run`, with either the values from the ``namespace`` argument of the `Network.run` method or from the local context, if no such argument is given. name : str, optional A unique name for the object - one will be assigned automatically if not provided (of the form ``brianobject_1``, etc.). Notes ----- The set of all `BrianObject` objects is stored in ``BrianObject.__instances__()``. """ @check_units(dt=second) def __init__( self, dt=None, clock=None, when="start", order=0, namespace=None, name="brianobject*", ): # Setup traceback information for this object creation_stack = [] bases = [] for modulename in ["brian2"]: if modulename in sys.modules: base, _ = os.path.split(sys.modules[modulename].__file__) bases.append(base) for fname, linenum, funcname, line in traceback.extract_stack(): if all(base not in fname for base in bases): s = f" File '{fname}', line {linenum}, in {funcname}\n {line}" creation_stack.append(s) creation_stack = [""] + creation_stack #: A string indicating where this object was created (traceback with any parts of Brian code removed) self._creation_stack = ( "Object was created here (most recent call only, full details in " "debug log):\n" + creation_stack[-1] ) self._full_creation_stack = "Object was created here:\n" + "\n".join( creation_stack ) if dt is not None and clock is not None: raise ValueError("Can only specify either a dt or a clock, not both.") if not isinstance(when, str): from brian2.core.clocks import Clock # Give some helpful error messages for users coming from the alpha # version if isinstance(when, Clock): raise TypeError( "Do not use the 'when' argument for " "specifying a clock, either provide a " "timestep for the 'dt' argument or a Clock " "object for 'clock'." ) if isinstance(when, tuple): raise TypeError( "Use the separate keyword arguments, 'dt' (or " "'clock'), 'when', and 'order' instead of " "providing a tuple for 'when'. Only use the " "'when' argument for the scheduling slot." ) # General error raise TypeError( "The 'when' argument has to be a string " "specifying the scheduling slot (e.g. 'start')." ) Nameable.__init__(self, name) #: The clock used for simulating this object self._clock = clock if clock is None: from brian2.core.clocks import Clock, defaultclock if dt is not None: self._clock = Clock(dt=dt, name=self.name + "_clock*") else: self._clock = defaultclock if getattr(self._clock, "_is_proxy", False): from brian2.devices.device import get_device self._clock = get_device().defaultclock #: Used to remember the `Network` in which this object has been included #: before, to raise an error if it is included in a new `Network` self._network = None #: The ID string determining when the object should be updated in `Network.run`. self.when = when #: The order in which objects with the same clock and ``when`` should be updated self.order = order self._dependencies = set() self._contained_objects = [] self._code_objects = [] self._active = True #: The scope key is used to determine which objects are collected by magic self._scope_key = self._scope_current_key # Make sure that keys in the namespace are valid if namespace is None: # Do not overwrite namespace if already set (e.g. in StateMonitor) namespace = getattr(self, "namespace", {}) for key in namespace: if key.startswith("_"): raise ValueError( "Names starting with underscores are " "reserved for internal use an cannot be " "defined in the namespace argument." ) #: The group-specific namespace self.namespace = namespace logger.diagnostic( f"Created BrianObject with name {self.name}, " f"clock={self._clock}, " f"when={self.when}, order={self.order}" ) #: Global key value for ipython cell restrict magic _scope_current_key = 0 #: Whether or not `MagicNetwork` is invalidated when a new `BrianObject` of this type is added invalidates_magic_network = True #: Whether or not the object should be added to a `MagicNetwork`. Note that #: all objects in `BrianObject.contained_objects` are automatically added #: when the parent object is added, therefore e.g. `NeuronGroup` should set #: `add_to_magic_network` to ``True``, but it should not be set for all the #: dependent objects such as `StateUpdater` add_to_magic_network = False def add_dependency(self, obj): """ Add an object to the list of dependencies. Takes care of handling subgroups correctly (i.e., adds its parent object). Parameters ---------- obj : `BrianObject` The object that this object depends on. """ from brian2.groups.subgroup import Subgroup if isinstance(obj, Subgroup): self._dependencies.add(obj.source.id) else: self._dependencies.add(obj.id) def before_run(self, run_namespace): """ Optional method to prepare the object before a run. Called by `Network.after_run` before the main simulation loop starts. """ for codeobj in self._code_objects: codeobj.before_run() def after_run(self): """ Optional method to do work after a run is finished. Called by `Network.after_run` after the main simulation loop terminated. """ for codeobj in self._code_objects: codeobj.after_run() def run(self): for codeobj in self._code_objects: codeobj() contained_objects = property( fget=lambda self: self._contained_objects, doc=""" The list of objects contained within the `BrianObject`. When a `BrianObject` is added to a `Network`, its contained objects will be added as well. This allows for compound objects which contain a mini-network structure. Note that this attribute cannot be set directly, you need to modify the underlying list, e.g. ``obj.contained_objects.extend([A, B])``. """, ) code_objects = property( fget=lambda self: self._code_objects, doc=""" The list of `CodeObject` contained within the `BrianObject`. TODO: more details. Note that this attribute cannot be set directly, you need to modify the underlying list, e.g. ``obj.code_objects.extend([A, B])``. """, ) updaters = property( fget=lambda self: self._updaters, doc=""" The list of `Updater` that define the runtime behaviour of this object. TODO: more details. Note that this attribute cannot be set directly, you need to modify the underlying list, e.g. ``obj.updaters.extend([A, B])``. """, ) clock = property( fget=lambda self: self._clock, doc=""" The `Clock` determining when the object should be updated. Note that this cannot be changed after the object is created. """, ) def _set_active(self, val): val = bool(val) self._active = val for obj in self.contained_objects: obj.active = val active = property( fget=lambda self: self._active, fset=_set_active, doc=""" Whether or not the object should be run. Inactive objects will not have their `update` method called in `Network.run`. Note that setting or unsetting the `active` attribute will set or unset it for all `contained_objects`. """, ) def __repr__(self): classname = self.__class__.__name__ description = ( f"{classname}(clock={self._clock}, when={self.when}, " f"order={self.order}, name={self.name!r})" ) return description # This is a repeat from Nameable.name, but we want to get the documentation # here again name = Nameable.name def weakproxy_with_fallback(obj): """ Attempts to create a `weakproxy` to the object, but falls back to the object if not possible. """ try: return weakref.proxy(obj) except TypeError: return obj def device_override(name): """ Decorates a function/method to allow it to be overridden by the current `Device`. The ``name`` is the function name in the `Device` to use as an override if it exists. The returned function has an additional attribute ``original_function`` which is a reference to the original, undecorated function. """ def device_override_decorator(func): def device_override_decorated_function(*args, **kwds): from brian2.devices.device import get_device curdev = get_device() if hasattr(curdev, name): return getattr(curdev, name)(*args, **kwds) else: return func(*args, **kwds) device_override_decorated_function.original_function = func functools.update_wrapper(device_override_decorated_function, func) return device_override_decorated_function return device_override_decorator class BrianObjectException(Exception): """ High level exception that adds extra Brian-specific information to exceptions This exception should only be raised at a fairly high level in Brian code to pass information back to the user. It adds extra information about where a `BrianObject` was defined to better enable users to locate the source of problems. Parameters ---------- message : str Additional error information to add to the original exception. brianobj : BrianObject The object that caused the error to happen. original_exception : Exception The original exception that was raised. """ def __init__(self, message, brianobj): self._brian_message = message self._brian_objname = brianobj.name self._brian_objcreate = brianobj._creation_stack logger.diagnostic( "Error was encountered with object " f"'{self._brian_objname}':\n" f"{brianobj._full_creation_stack}" ) def __str__(self): return ( f"Error encountered with object named '{self._brian_objname}'.\n" f"{self._brian_objcreate}\n\n" f"{self._brian_message} " "(See above for original error message and traceback.)" ) def brian_object_exception(message, brianobj, original_exception): """ Returns a `BrianObjectException` derived from the original exception. Creates a new class derived from the class of the original exception and `BrianObjectException`. This allows exception handling code to respond both to the original exception class and `BrianObjectException`. See `BrianObjectException` for arguments and notes. """ raise NotImplementedError( "The brian_object_exception function is no longer used. " "Raise a BrianObjectException directly." ) brian2-2.5.4/brian2/core/clocks.py000066400000000000000000000166701445201106100166400ustar00rootroot00000000000000""" Clocks for the simulator. """ __docformat__ = "restructuredtext en" import numpy as np from brian2.core.names import Nameable from brian2.core.variables import Variables from brian2.groups.group import VariableOwner from brian2.units.allunits import second from brian2.units.fundamentalunits import Quantity, check_units from brian2.utils.logger import get_logger __all__ = ["Clock", "defaultclock"] logger = get_logger(__name__) def check_dt(new_dt, old_dt, target_t): """ Check that the target time can be represented equally well with the new dt. Parameters ---------- new_dt : float The new dt value old_dt : float The old dt value target_t : float The target time Raises ------ ValueError If using the new dt value would lead to a difference in the target time of more than `Clock.epsilon_dt` times ``new_dt`` (by default, 0.01% of the new dt). Examples -------- >>> from brian2 import * >>> check_dt(float(17*ms), float(0.1*ms), float(0*ms)) # For t=0s, every dt is fine >>> check_dt(float(0.05*ms), float(0.1*ms), float(10*ms)) # t=10*ms can be represented with the new dt >>> check_dt(float(0.2*ms), float(0.1*ms), float(10.1*ms)) # t=10.1ms cannot be represented with dt=0.2ms # doctest: +ELLIPSIS Traceback (most recent call last): ... ValueError: Cannot set dt from 100. us to 200. us, the time 10.1 ms is not a multiple of 200. us. """ old_t = np.int64(np.round(target_t / old_dt)) * old_dt new_t = np.int64(np.round(target_t / new_dt)) * new_dt error_t = target_t if abs(new_t - old_t) / new_dt > Clock.epsilon_dt: old = str(old_dt * second) new = str(new_dt * second) t = str(error_t * second) raise ValueError( f"Cannot set dt from {old} to {new}, the " f"time {t} is not a multiple of {new}." ) class Clock(VariableOwner): """ An object that holds the simulation time and the time step. Parameters ---------- dt : float The time step of the simulation as a float name : str, optional An explicit name, if not specified gives an automatically generated name Notes ----- Clocks are run in the same `Network.run` iteration if `~Clock.t` is the same. The condition for two clocks to be considered as having the same time is ``abs(t1-t2) 2**40: logger.warn( "The end time of the simulation has been set to " f"{str(end*second)}, which based on the dt value of " f"{str(self.dt)} means that {self._i_end} " "time steps will be simulated. This can lead to " "numerical problems, e.g. the times t will not " "correspond to exact multiples of " "dt.", "many_timesteps", ) #: The relative difference for times (in terms of dt) so that they are #: considered identical. epsilon_dt = 1e-4 class DefaultClockProxy: """ Method proxy to access the defaultclock of the currently active device """ def __getattr__(self, name): if name == "_is_proxy": return True from brian2.devices.device import active_device return getattr(active_device.defaultclock, name) def __setattr__(self, key, value): from brian2.devices.device import active_device setattr(active_device.defaultclock, key, value) #: The standard clock, used for objects that do not specify any clock or dt defaultclock = DefaultClockProxy() brian2-2.5.4/brian2/core/core_preferences.py000066400000000000000000000035521445201106100206660ustar00rootroot00000000000000""" Definitions, documentation, default values and validation functions for core Brian preferences. """ from numpy import float32, float64, int32 from brian2.core.preferences import BrianPreference, prefs __all__ = [] def dtype_repr(dtype): return dtype.__name__ def default_float_dtype_validator(dtype): return dtype in [float32, float64] prefs.register_preferences( "core", "Core Brian preferences", default_float_dtype=BrianPreference( default=float64, docs=""" Default dtype for all arrays of scalars (state variables, weights, etc.). """, representor=dtype_repr, validator=default_float_dtype_validator, ), default_integer_dtype=BrianPreference( default=int32, docs=""" Default dtype for all arrays of integer scalars. """, representor=dtype_repr, ), outdated_dependency_error=BrianPreference( default=True, docs=""" Whether to raise an error for outdated dependencies (``True``) or just a warning (``False``). """, ), ) prefs.register_preferences( "legacy", "Preferences to enable legacy behaviour", refractory_timing=BrianPreference( default=False, docs=""" Whether to use the semantics for checking the refractoriness condition that were in place up until (including) version 2.1.2. In that implementation, refractory periods that were multiples of dt could lead to a varying number of refractory timesteps due to the nature of floating point comparisons). This preference is only provided for exact reproducibility of previously obtained results, new simulations should use the improved mechanism which uses a more robust mechanism to convert refractoriness into timesteps. Defaults to ``False``. """, ), ) brian2-2.5.4/brian2/core/functions.py000066400000000000000000001031751445201106100173670ustar00rootroot00000000000000import inspect import types from collections.abc import Mapping from typing import Callable import numpy as np import sympy from numpy.random import rand, randn from sympy import Function as sympy_Function from sympy import S from sympy.codegen import cfunctions as sympy_cfunctions import brian2.units.unitsafefunctions as unitsafe from brian2.core.preferences import prefs from brian2.core.variables import Constant from brian2.units.allunits import second from brian2.units.fundamentalunits import ( DIMENSIONLESS, Quantity, fail_for_dimension_mismatch, get_dimensions, is_dimensionless, ) __all__ = ["DEFAULT_FUNCTIONS", "Function", "implementation", "declare_types"] BRIAN_DTYPES = ["boolean", "integer", "float"] VALID_ARG_TYPES = BRIAN_DTYPES + ["any"] VALID_RETURN_TYPES = BRIAN_DTYPES + ["highest"] def declare_types(**types): """ Decorator to declare argument and result types for a function Usage is similar to `check_units` except that types must be one of ``{VALID_ARG_TYPES}`` and the result type must be one of ``{VALID_RETURN_TYPES}``. Unspecified argument types are assumed to be ``'all'`` (i.e. anything is permitted), and an unspecified result type is assumed to be ``'float'``. Note that the ``'highest'`` option for result type will give the highest type of its argument, e.g. if the arguments were boolean and integer then the result would be integer, if the arguments were integer and float it would be float. """ def annotate_function_with_types(f): if hasattr(f, "_orig_arg_names"): arg_names = f._orig_arg_names else: arg_names = f.__code__.co_varnames[0 : f.__code__.co_argcount] argtypes = [] for name in arg_names: arg_type = types.get(name, "any") if arg_type not in VALID_ARG_TYPES: raise ValueError( f"Argument type {arg_type} is not valid, must be one of" f" {VALID_ARG_TYPES}, for argument {name}" ) argtypes.append(arg_type) for n in types: if n not in arg_names and n != "result": raise ValueError(f"Type specified for unknown argument {n}") return_type = types.get("result", "float") if return_type not in VALID_RETURN_TYPES: raise ValueError( f"Result type {return_type} is not valid, must be one of" f" {VALID_RETURN_TYPES}" ) f._arg_types = argtypes f._return_type = return_type f._orig_arg_names = arg_names f._annotation_attributes = getattr(f, "_annotation_attributes", []) + [ "_arg_types", "_return_type", ] return f return annotate_function_with_types class Function: """ An abstract specification of a function that can be used as part of model equations, etc. Parameters ---------- pyfunc : function A Python function that is represented by this `Function` object. sympy_func : `sympy.Function`, optional A corresponding sympy function (if any). Allows functions to be interpreted by sympy and potentially make simplifications. For example, ``sqrt(x**2)`` could be replaced by ``abs(x)``. arg_units : list of `Unit`, optional If `pyfunc` does not provide unit information (which typically means that it was not annotated with a `check_units` decorator), the units of the arguments have to specified explicitly using this parameter. return_unit : `Unit` or callable, optional Same as for `arg_units`: if `pyfunc` does not provide unit information, this information has to be provided explictly here. `return_unit` can either be a specific `Unit`, if the function always returns the same unit, or a function of the input units, e.g. a "square" function would return the square of its input units, i.e. `return_unit` could be specified as ``lambda u: u**2``. arg_types : list of str, optional Similar to `arg_units`, but gives the type of the argument rather than its unit. In the current version of Brian arguments are specified by one of the following strings: 'boolean', 'integer', 'float', 'any'. If `arg_types` is not specified, 'any' will be assumed. In future versions, a more refined specification may be possible. Note that any argument with a type other than float should have no units. If return_type : str, optional Similar to `return_unit` and `arg_types`. In addition to 'boolean', 'integer' and 'float' you can also use 'highest' which will return the highest type of its arguments. You can also give a function, as for `return_unit`. If the return type is not specified, it is assumed to be 'float'. stateless : bool, optional Whether this function does not have an internal state, i.e. if it always returns the same output when called with the same arguments. This is true for mathematical functions but not true for ``rand()``, for example. Defaults to ``True``. auto_vectorise : bool, optional Whether the implementations of this function should get an additional argument (not specified in abstract code) that can be used to determine the number of values that should be returned (for the numpy target), or an index potentially useful for generating deterministic values independent of the order of vectorisation (for all other targets). The main use case are random number functions, e.g. equations refer to ``rand()``, but the generate code will actually call ``rand(_vectorisation_idx)``. Defaults to ``False``. Notes ----- If a function should be usable for code generation targets other than Python/numpy, implementations for these target languages have to be added using the `~brian2.codegen.functions.implementation` decorator or using the `~brian2.codegen.functions.add_implementations` function. """ def __init__( self, pyfunc, sympy_func=None, arg_units=None, arg_names=None, return_unit=None, arg_types=None, return_type=None, stateless=True, auto_vectorise=False, ): self.pyfunc = pyfunc self.sympy_func = sympy_func self._arg_units = arg_units self._arg_names = arg_names self._return_unit = return_unit if return_unit == bool: self._returns_bool = True else: self._returns_bool = False self._arg_types = arg_types self._return_type = return_type self.stateless = stateless self.auto_vectorise = auto_vectorise if self._arg_units is None: if not hasattr(pyfunc, "_arg_units"): raise ValueError( f"The Python function '{pyfunc.__name__}' does not specify " "how it deals with units, need to specify " "'arg_units' or use the '@check_units' " "decorator." ) elif pyfunc._arg_units is None: # @check_units sets _arg_units to None if the units aren't # specified for all of its arguments raise ValueError( f"The Python function '{pyfunc.__name__}' does not " "specify the units for all of its arguments." ) else: self._arg_units = pyfunc._arg_units else: if any(isinstance(u, str) for u in self._arg_units): if self._arg_names is None: raise TypeError("Need to specify the names of the arguments.") if len(self._arg_names) != len(self._arg_units): raise TypeError( "arg_names and arg_units need to have the " f"same length ({len(self._arg_names)} != " f"({len(self._arg_units)})" ) if self._return_unit is None: if not hasattr(pyfunc, "_return_unit"): raise ValueError( f"The Python function '{pyfunc.__name__}' does not " "specify how it deals with units, need to specify " "'return_unit' or use the '@check_units' decorator." ) elif pyfunc._return_unit is None: # @check_units sets _return_unit to None if no "result=..." # keyword is specified. raise ValueError( f"The Python function '{pyfunc.__name__}' does not " "specify the unit for its return value." ) else: self._return_unit = pyfunc._return_unit if self._arg_types is None: if hasattr(pyfunc, "_arg_types"): self._arg_types = pyfunc._arg_types else: self._arg_types = ["any"] * len(self._arg_units) if self._return_type is None: self._return_type = getattr(pyfunc, "_return_type", "float") for argtype, u in zip(self._arg_types, self._arg_units): if ( argtype != "float" and argtype != "any" and u is not None and not is_dimensionless(u) ): raise TypeError( "Non-float arguments must be dimensionless in function" f" {pyfunc.__name__}" ) if argtype not in VALID_ARG_TYPES: raise ValueError( f"Argument type {argtype} is not valid, must be one " f"of {VALID_ARG_TYPES}, in function " f"'{pyfunc.__name__}'." ) if self._return_type not in VALID_RETURN_TYPES: raise ValueError( f"Return type {self._return_typ} is not valid, must " f"be one of {VALID_RETURN_TYPES}, in function " f"'{pyfunc.__name__}'" ) #: Stores implementations for this function in a #: `FunctionImplementationContainer` self.implementations = FunctionImplementationContainer(self) def is_locally_constant(self, dt): """ Return whether this function (if interpreted as a function of time) should be considered constant over a timestep. This is most importantly used by `TimedArray` so that linear integration can be used. In its standard implementation, always returns ``False``. Parameters ---------- dt : float The length of a timestep (without units). Returns ------- constant : bool Whether the results of this function can be considered constant over one timestep of length `dt`. """ return False def __call__(self, *args): return self.pyfunc(*args) class FunctionImplementation: """ A simple container object for function implementations. Parameters ---------- name : str, optional The name of the function in the target language. Should only be specified if the function has to be renamed for the target language. code : language-dependent, optional A language dependent argument specifying the implementation in the target language, e.g. a code string or a dictionary of code strings. namespace : dict-like, optional A dictionary of mappings from names to values that should be added to the namespace of a `CodeObject` using the function. dependencies : dict-like, optional A mapping of names to `Function` objects, for additional functions needed by this function. availability_check : callable, optional A function that will be called to check whether the function should be made available (e.g. depending on whether it is supported by the compiler). The function should do nothing if the function is available, or raise a ``NotImplementedError`` with a message explaining why it isn't. dynamic : bool, optional Whether this `code`/`namespace` is dynamic, i.e. generated for each new context it is used in. If set to ``True``, `code` and `namespace` have to be callable with a `Group` as an argument and are expected to return the final `code` and `namespace`. Defaults to ``False``. """ def __init__( self, name=None, code=None, namespace=None, dependencies=None, availability_check=None, dynamic=False, compiler_kwds=None, ): if compiler_kwds is None: compiler_kwds = {} self.name = name if dependencies is None: dependencies = {} self.dependencies = dependencies self._code = code self._namespace = namespace self.dynamic = dynamic self.compiler_kwds = compiler_kwds self.availability_check = availability_check def get_code(self, owner): if self.availability_check is not None: self.availability_check() if self.dynamic: return self._code(owner) else: return self._code def get_namespace(self, owner): if self.dynamic: return self._namespace(owner) else: return self._namespace class FunctionImplementationContainer(Mapping): """ Helper object to store implementations and give access in a dictionary-like fashion, using `CodeGenerator` implementations as a fallback for `CodeObject` implementations. """ def __init__(self, function): self._function = function self._implementations = dict() def __getitem__(self, key): """ Find an implementation for this function that can be used by the `CodeObject` given as `key`. Will find implementations registered for `key` itself (or one of its parents), or for the `CodeGenerator` class that `key` uses (or one of its parents). In all cases, implementations registered for the corresponding names qualify as well. Parameters ---------- key : `CodeObject` The `CodeObject` that will use the `Function` Returns ------- implementation : `FunctionImplementation` An implementation suitable for `key`. """ fallback = getattr(key, "generator_class", None) # in some cases we do the code generation with original_generator_class instead (e.g. GSL) fallback_parent = getattr(key, "original_generator_class", None) for K in [key, fallback, fallback_parent]: name = getattr(K, "class_name", "no class name for key") for impl_key, impl in self._implementations.items(): impl_key_name = getattr( impl_key, "class_name", "no class name for implementation" ) if (impl_key_name is not None and impl_key_name in [K, name]) or ( impl_key is not None and impl_key in [K, name] ): return impl if hasattr(K, "__bases__"): for cls in inspect.getmro(K): if cls in self._implementations: return self._implementations[cls] name = getattr(cls, "class_name", None) if name in self._implementations: return self._implementations[name] # Give a nicer error message if possible if getattr(key, "class_name", None) is not None: key = key.class_name elif getattr(fallback, "class_name", None) is not None: key = fallback.class_name keys = ", ".join( [getattr(k, "class_name", str(k)) for k in self._implementations] ) raise KeyError( f"No implementation available for target '{key}'. " f"Available implementations: {keys}" ) def add_numpy_implementation( self, wrapped_func, dependencies=None, discard_units=None, compiler_kwds=None ): """ Add a numpy implementation to a `Function`. Parameters ---------- function : `Function` The function description for which an implementation should be added. wrapped_func : callable The original function (that will be used for the numpy implementation) dependencies : list of `Function`, optional A list of functions this function needs. discard_units : bool, optional See `implementation`. """ if discard_units is None: discard_units = prefs["codegen.runtime.numpy.discard_units"] # Get the original function inside the check_units decorator if hasattr(wrapped_func, "_orig_func"): orig_func = wrapped_func._orig_func else: orig_func = wrapped_func if discard_units: new_globals = dict(orig_func.__globals__) # strip away units in the function by changing its namespace for key, value in new_globals.items(): if isinstance(value, Quantity): new_globals[key] = np.asarray(value) unitless_func = types.FunctionType( orig_func.__code__, new_globals, orig_func.__name__, orig_func.__defaults__, orig_func.__closure__, ) self._implementations["numpy"] = FunctionImplementation( name=None, code=unitless_func, dependencies=dependencies, compiler_kwds=None, ) else: def wrapper_function(*args): arg_units = list(self._function._arg_units) if self._function.auto_vectorise: arg_units += [DIMENSIONLESS] if not len(args) == len(arg_units): func_name = self._function.pyfunc.__name__ raise ValueError( f"Function {func_name} got {len(args)} arguments, " f"expected {len(arg_units)}." ) new_args = [] for arg, arg_unit in zip(args, arg_units): if ( arg_unit == bool or arg_unit is None or isinstance(arg_unit, str) ): new_args.append(arg) else: new_args.append( Quantity.with_dimensions(arg, get_dimensions(arg_unit)) ) result = orig_func(*new_args) if isinstance(self._function._return_unit, Callable): return_unit = self._function._return_unit( *[get_dimensions(a) for a in args] ) else: return_unit = self._function._return_unit if return_unit == bool: if not ( isinstance(result, bool) or np.asarray(result).dtype == bool ): raise TypeError( f"The function {orig_func.__name__} returned " f"'{result}', but it was expected to return a " "boolean value " ) elif ( isinstance(return_unit, int) and return_unit == 1 ) or return_unit.dim is DIMENSIONLESS: fail_for_dimension_mismatch( result, return_unit, f"The function '{orig_func.__name__}' " f"returned {result}, but it was " "expected to return a dimensionless " "quantity.", ) else: fail_for_dimension_mismatch( result, return_unit, f"The function '{orig_func.__name__}' " f"returned {result}, but it was " "expected to return a quantity with " f"units {return_unit!r}.", ) return np.asarray(result) self._implementations["numpy"] = FunctionImplementation( name=None, code=wrapper_function, dependencies=dependencies ) def add_implementation( self, target, code, namespace=None, dependencies=None, availability_check=None, name=None, compiler_kwds=None, ): self._implementations[target] = FunctionImplementation( name=name, code=code, dependencies=dependencies, availability_check=availability_check, namespace=namespace, compiler_kwds=compiler_kwds, ) def add_dynamic_implementation( self, target, code, namespace=None, dependencies=None, availability_check=None, name=None, compiler_kwds=None, ): """ Adds an "dynamic implementation" for this function. `code` and `namespace` arguments are expected to be callables that will be called in `Network.before_run` with the owner of the `CodeObject` as an argument. This allows to generate code that depends on details of the context it is run in, e.g. the ``dt`` of a clock. """ if not callable(code): raise TypeError( f"code argument has to be a callable, is type {type(code)} instead" ) if namespace is not None and not callable(namespace): raise TypeError( f"namespace argument has to be a callable, is type {type(code)} instead" ) self._implementations[target] = FunctionImplementation( name=name, code=code, namespace=namespace, dependencies=dependencies, availability_check=availability_check, dynamic=True, compiler_kwds=compiler_kwds, ) def __len__(self): return len(self._implementations) def __iter__(self): return iter(self._implementations) def implementation( target, code=None, namespace=None, dependencies=None, discard_units=None, name=None, **compiler_kwds, ): """ A simple decorator to extend user-written Python functions to work with code generation in other languages. Parameters ---------- target : str Name of the code generation target (e.g. ``'cython'``) for which to add an implementation. code : str or dict-like, optional What kind of code the target language expects is language-specific, e.g. C++ code allows for a dictionary of code blocks instead of a single string. namespaces : dict-like, optional A namespace dictionary (i.e. a mapping of names to values) that should be added to a `CodeObject` namespace when using this function. dependencies : dict-like, optional A mapping of names to `Function` objects, for additional functions needed by this function. discard_units: bool, optional Numpy functions can internally make use of the unit system. However, during a simulation run, state variables are passed around as unitless values for efficiency. If `discard_units` is set to ``False``, input arguments will have units added to them so that the function can still use units internally (the units will be stripped away from the return value as well). Alternatively, if `discard_units` is set to ``True``, the function will receive unitless values as its input. The namespace of the function will be altered to make references to units (e.g. ``ms``) refer to the corresponding floating point values so that no unit mismatch errors are raised. Note that this system cannot work in all cases, e.g. it does not work with functions that internally imports values (e.g. does ``from brian2 import ms``) or access values with units indirectly (e.g. uses ``brian2.ms`` instead of ``ms``). If no value is given, defaults to the preference setting `codegen.runtime.numpy.discard_units`. name : str, optional The name of the function in the target language. Should only be specified if the function has to be renamed for the target language. compiler_kwds : dict, optional Additional keyword arguments will be transferred to the code generation stage, e.g. for C++-based targets, the code can make use of additional header files by providing a list of strings as the ``headers`` argument. Notes ----- While it is in principle possible to provide a numpy implementation as an argument for this decorator, this is normally not necessary -- the numpy implementation should be provided in the decorated function. If this decorator is used with other decorators such as `check_units` or `declare_types`, it should be the uppermost decorator (that is, the last one to be applied). Examples -------- Sample usage:: @implementation('cpp',''' #include inline double usersin(double x) { return sin(x); } ''') def usersin(x): return sin(x) """ def do_user_implementation(func): # Allow nesting of decorators if isinstance(func, Function): function = func else: function = Function(func) if discard_units: # Add a numpy implementation that discards units if not (target == "numpy" and code is None): raise TypeError( "'discard_units' can only be set for code " "generation target 'numpy', without providing " "any code." ) function.implementations.add_numpy_implementation( wrapped_func=func, dependencies=dependencies, discard_units=discard_units, compiler_kwds=compiler_kwds, ) else: function.implementations.add_implementation( target, code=code, dependencies=dependencies, namespace=namespace, name=name, compiler_kwds=compiler_kwds, ) # # copy any annotation attributes # if hasattr(func, '_annotation_attributes'): # for attrname in func._annotation_attributes: # setattr(function, attrname, getattr(func, attrname)) # function._annotation_attributes = getattr(func, '_annotation_attributes', []) return function return do_user_implementation class SymbolicConstant(Constant): """ Class for representing constants (e.g. pi) that are understood by sympy. """ def __init__(self, name, sympy_obj, value): super().__init__(name, value=value) self.sympy_obj = sympy_obj ################################################################################ # Standard functions and constants ################################################################################ def _exprel(x): if x.is_zero: return S.One else: return (sympy.exp(x) - S.One) / x class exprel(sympy_Function): """ Represents ``(exp(x) - 1)/x``. The benefit of using ``exprel(x)`` over ``(exp(x) - 1)/x`` is that the latter is prone to cancellation under finite precision arithmetic when x is close to zero, and cannot be evaluated when x is equal to zero. """ nargs = 1 def fdiff(self, argindex=1): """ Returns the first derivative of this function. """ if argindex == 1: return (sympy.exp(*self.args) * (self.args[0] - S.One) + S.One) / self.args[ 0 ] ** 2 else: raise sympy.ArgumentIndexError(self, argindex) def _eval_expand_func(self, **hints): return _exprel(*self.args) def _eval_rewrite_as_exp(self, arg, **kwargs): if arg.is_zero: return S.One else: return (sympy.exp(arg) - S.One) / arg _eval_rewrite_as_tractable = _eval_rewrite_as_exp @classmethod def eval(cls, arg): if arg is None: return None if arg.is_zero: return S.One exp_arg = sympy.exp.eval(arg) if exp_arg is not None: return (exp_arg - S.One) / arg def _eval_is_real(self): return self.args[0].is_real def _eval_is_finite(self): return self.args[0].is_finite _infinity_int = 1073741823 # maximum 32bit integer divided by 2 def timestep(t, dt): """ Converts a given time to an integer time step. This function slightly shifts the time before dividing it by ``dt`` to make sure that multiples of ``dt`` do not end up in the preceding time step due to floating point issues. This function is used in the refractoriness calculation. .. versionadded:: 2.1.3 Parameters ---------- t : np.ndarray, float, Quantity The time to convert. dt : float or Quantity The length of a simulation time step. Returns ------- ts : np.ndarray, np.int64 The time step corresponding to the given time. Notes ----- This function cannot handle infinity values, use big values instead (e.g. a `NeuronGroup` will use ``-1e4*second`` as the value of the ``lastspike`` variable for neurons that never spiked). """ elapsed_steps = np.array((t + 1e-3 * dt) / dt, dtype=np.int64) if elapsed_steps.shape == (): elapsed_steps = elapsed_steps.item() return elapsed_steps DEFAULT_FUNCTIONS = { # numpy functions that have the same name in numpy and math.h "cos": Function( unitsafe.cos, sympy_func=sympy.functions.elementary.trigonometric.cos ), "sin": Function( unitsafe.sin, sympy_func=sympy.functions.elementary.trigonometric.sin ), "tan": Function( unitsafe.tan, sympy_func=sympy.functions.elementary.trigonometric.tan ), "cosh": Function( unitsafe.cosh, sympy_func=sympy.functions.elementary.hyperbolic.cosh ), "sinh": Function( unitsafe.sinh, sympy_func=sympy.functions.elementary.hyperbolic.sinh ), "tanh": Function( unitsafe.tanh, sympy_func=sympy.functions.elementary.hyperbolic.tanh ), "exp": Function( unitsafe.exp, sympy_func=sympy.functions.elementary.exponential.exp ), "log": Function( unitsafe.log, sympy_func=sympy.functions.elementary.exponential.log ), "log10": Function(unitsafe.log10, sympy_func=sympy_cfunctions.log10), "expm1": Function(unitsafe.expm1, sympy_func=sympy_cfunctions.expm1), "exprel": Function(unitsafe.exprel, sympy_func=exprel), "log1p": Function(unitsafe.log1p, sympy_func=sympy_cfunctions.log1p), "sqrt": Function( np.sqrt, sympy_func=sympy.functions.elementary.miscellaneous.sqrt, arg_units=[None], return_unit=lambda u: u**0.5, ), "ceil": Function( np.ceil, sympy_func=sympy.functions.elementary.integers.ceiling, arg_units=[None], return_unit=lambda u: u, ), "floor": Function( np.floor, sympy_func=sympy.functions.elementary.integers.floor, arg_units=[None], return_unit=lambda u: u, ), # numpy functions that have a different name in numpy and math.h "arccos": Function( unitsafe.arccos, sympy_func=sympy.functions.elementary.trigonometric.acos ), "arcsin": Function( unitsafe.arcsin, sympy_func=sympy.functions.elementary.trigonometric.asin ), "arctan": Function( unitsafe.arctan, sympy_func=sympy.functions.elementary.trigonometric.atan ), "abs": Function( np.abs, return_type="highest", sympy_func=sympy.functions.elementary.complexes.Abs, arg_units=[None], return_unit=lambda u: u, ), "sign": Function( pyfunc=np.sign, sympy_func=sympy.sign, return_type="highest", arg_units=[None], return_unit=1, ), # functions that need special treatment "rand": Function( pyfunc=rand, arg_units=[], return_unit=1, stateless=False, auto_vectorise=True ), "randn": Function( pyfunc=randn, arg_units=[], return_unit=1, stateless=False, auto_vectorise=True ), "poisson": Function( pyfunc=np.random.poisson, arg_units=[1], return_unit=1, return_type="integer", stateless=False, auto_vectorise=True, ), "clip": Function( pyfunc=np.clip, arg_units=[None, "a", "a"], arg_names=["a", "a_min", "a_max"], return_type="highest", return_unit=lambda u1, u2, u3: u1, ), "int": Function( pyfunc=np.int_, return_type="integer", arg_units=[1], return_unit=1 ), "timestep": Function( pyfunc=timestep, return_type="integer", arg_units=[second, second], return_unit=1, ), } DEFAULT_CONSTANTS = { "pi": SymbolicConstant("pi", sympy.pi, value=np.pi), "e": SymbolicConstant("e", sympy.E, value=np.e), "inf": SymbolicConstant("inf", S.Infinity, value=np.inf), "-inf": SymbolicConstant("-inf", S.NegativeInfinity, value=-np.inf), } brian2-2.5.4/brian2/core/magic.py000066400000000000000000000406211445201106100164330ustar00rootroot00000000000000import gc import inspect import itertools import weakref from brian2.units.allunits import second from brian2.units.fundamentalunits import check_units from brian2.utils.logger import get_logger from .base import BrianObject from .network import Network __all__ = [ "MagicNetwork", "magic_network", "MagicError", "run", "stop", "collect", "store", "restore", "start_scope", ] logger = get_logger(__name__) def _get_contained_objects(obj): """ Helper function to recursively get all contained objects. Parameters ---------- obj : `BrianObject` An object that (potentially) contains other objects, e.g. a `NeuronGroup` contains a `StateUpdater`, etc. Returns ------- objects : list of `BrianObject` A list of all the objects contained in `obj` """ objects = [] contained_objects = getattr(obj, "contained_objects", []) objects.extend(contained_objects) for contained_obj in contained_objects: objects.extend(_get_contained_objects(contained_obj)) return objects def get_objects_in_namespace(level): r""" Get all the objects in the current namespace that derive from `BrianObject`. Used to determine the objects for the `MagicNetwork`. Parameters ---------- level : int, optional How far to go back to get the locals/globals. Each function/method call should add ``1`` to this argument, functions/method with a decorator have to add ``2``. Returns ------- objects : set A set with weak references to the `BrianObject`\ s in the namespace. """ # Get the locals and globals from the stack frame objects = set() frame = inspect.stack()[level + 1][0] for _, v in itertools.chain(frame.f_globals.items(), frame.f_locals.items()): # We are only interested in numbers and functions, not in # everything else (classes, modules, etc.) if isinstance(v, BrianObject): objects.add(weakref.ref(v)) del frame return objects class MagicError(Exception): """ Error that is raised when something goes wrong in `MagicNetwork` See notes to `MagicNetwork` for more details. """ pass class MagicNetwork(Network): """ `Network` that automatically adds all Brian objects In order to avoid bugs, this class will occasionally raise `MagicError` when the intent of the user is not clear. See the notes below for more details on this point. If you persistently see this error, then Brian is not able to safely guess what you intend to do, and you should use a `Network` object and call `Network.run` explicitly. Note that this class cannot be instantiated by the user, there can be only one instance `magic_network` of `MagicNetwork`. Notes ----- All Brian objects that are visible at the point of the `run` call will be included in the network. This class is designed to work in the following two major use cases: 1. You create a collection of Brian objects, and call `run` to run the simulation. Subsequently, you may call `run` again to run it again for a further duration. In this case, the `Network.t` time will start at 0 and for the second call to `run` will continue from the end of the previous run. 2. You have a loop in which at each iteration, you create some Brian objects and run a simulation using them. In this case, time is reset to 0 for each call to `run`. In any other case, you will have to explicitly create a `Network` object yourself and call `Network.run` on this object. Brian has a built in system to guess which of the cases above applies and behave correctly. When it is not possible to safely guess which case you are in, it raises `MagicError`. The rules for this guessing system are explained below. If a simulation consists only of objects that have not been run, it will assume that you want to start a new simulation. If a simulation only consists of objects that have been simulated in the previous `run` call, it will continue that simulation at the previous time. If neither of these two situations apply, i.e., the network consists of a mix of previously run objects and new objects, an error will be raised. In these checks, "non-invalidating" objects (i.e. objects that have `BrianObject.invalidates_magic_network` set to ``False``) are ignored, e.g. creating new monitors is always possible. See Also -------- Network, collect, run, stop, store, restore """ _already_created = False def __init__(self): if MagicNetwork._already_created: raise ValueError("There can be only one MagicNetwork.") MagicNetwork._already_created = True super().__init__(name="magicnetwork*") self._previous_refs = set() def add(self, *objs): """ You cannot add objects directly to `MagicNetwork` """ raise MagicError("Cannot directly modify MagicNetwork") def remove(self, *objs): """ You cannot remove objects directly from `MagicNetwork` """ raise MagicError("Cannot directly modify MagicNetwork") def _update_magic_objects(self, level): objects = collect(level + 1) contained_objects = set() for obj in objects: for contained in _get_contained_objects(obj): contained_objects.add(contained) objects |= contained_objects # check whether we should restart time, continue time, or raise an # error some_known = False some_new = False for obj in objects: if obj._network == self.id: some_known = True # we are continuing a previous run elif obj._network is None and obj.invalidates_magic_network: some_new = True # Note that the inclusion of objects that have been run as part of # other objects will lead to an error in `Network.before_run`, we # do not have to deal with this case here. if some_known and some_new: raise MagicError( "The magic network contains a mix of objects " "that has been run before and new objects, Brian " "does not know whether you want to start a new " "simulation or continue an old one. Consider " "explicitly creating a Network object. Also note " "that you can find out which objects will be " "included in a magic network with the " "collect() function." ) elif some_new: # all objects are new, start a new simulation # reset time self.t_ = 0.0 # reset id -- think of this as a new Network self.assign_id() for obj in objects: if obj._network is None: obj._network = self.id self.objects = objects numobjs = len(self.objects) names = ", ".join(obj.name for obj in self.objects) logger.debug( f"Updated MagicNetwork to include {numobjs} objects with names {names}", name_suffix="magic_objects", ) def check_dependencies(self): all_ids = {obj.id for obj in self.objects} for obj in self.objects: if not obj.active: continue # object is already inactive, no need to check it for dependency in obj._dependencies: if dependency not in all_ids: logger.warn( f"'{obj.name}' has been included in the network but " "not the object on which it depends." f"Setting '{obj.name}' to inactive.", name_suffix="dependency_warning", ) obj.active = False break def after_run(self): super().after_run() self.objects.clear() gc.collect() # Make sure that all unused objects are cleared def run( self, duration, report=None, report_period=10 * second, namespace=None, profile=None, level=0, ): self._update_magic_objects(level=level + 1) Network.run( self, duration, report=report, report_period=report_period, namespace=namespace, profile=profile, level=level + 1, ) def store(self, name="default", filename=None, level=0): """ See `Network.store`. """ self._update_magic_objects(level=level + 1) super().store(name=name, filename=filename) self.objects.clear() def restore( self, name="default", filename=None, restore_random_state=False, level=0 ): """ See `Network.restore`. """ self._update_magic_objects(level=level + 1) super().restore( name=name, filename=filename, restore_random_state=restore_random_state ) self.objects.clear() def get_states(self, units=True, format="dict", subexpressions=False, level=0): """ See `Network.get_states`. """ self._update_magic_objects(level=level + 1) states = super().get_states(units, format, subexpressions, level=level + 1) self.objects.clear() return states def set_states(self, values, units=True, format="dict", level=0): """ See `Network.set_states`. """ self._update_magic_objects(level=level + 1) super().set_states(values, units, format, level=level + 1) self.objects.clear() def __str__(self): return "MagicNetwork()" __repr__ = __str__ #: Automatically constructed `MagicNetwork` of all Brian objects magic_network = MagicNetwork() def collect(level=0): r""" Return the list of `BrianObject`\ s that will be simulated if `run` is called. Parameters ---------- level : int, optional How much further up to go in the stack to find the objects. Needs only to be specified if `collect` is called as part of a function and should be increased by 1 for every level of nesting. Defaults to 0. Returns ------- objects : set of `BrianObject` The objects that will be simulated. """ all_objects = set() for obj in get_objects_in_namespace(level=level + 1): obj = obj() if obj.add_to_magic_network: gk = BrianObject._scope_current_key k = obj._scope_key if gk != k: continue all_objects.add(obj) return all_objects @check_units(duration=second, report_period=second) def run( duration, report=None, report_period=10 * second, namespace=None, profile=None, level=0, ): """ run(duration, report=None, report_period=10*second, namespace=None, level=0) Runs a simulation with all "visible" Brian objects for the given duration. Calls `collect` to gather all the objects, the simulation can be stopped by calling the global `stop` function. In order to avoid bugs, this function will occasionally raise `MagicError` when the intent of the user is not clear. See the notes to `MagicNetwork` for more details on this point. If you persistently see this error, then Brian is not able to safely guess what you intend to do, and you should use a `Network` object and call `Network.run` explicitly. Parameters ---------- duration : `Quantity` The amount of simulation time to run for. If the network consists of new objects since the last time `run` was called, the start time will be reset to 0. If `run` is called twice or more without changing the set of objects, the second and subsequent runs will start from the end time of the previous run. To explicitly reset the time to 0, do ``magic_network.t = 0*second``. report : {None, 'text', 'stdout', 'stderr', function}, optional How to report the progress of the simulation. If ``None``, do not report progress. If ``'text'`` or ``'stdout'`` is specified, print the progress to stdout. If ``'stderr'`` is specified, print the progress to stderr. Alternatively, you can specify a callback ``callable(elapsed, completed, start, duration)`` which will be passed the amount of time elapsed as a `Quantity`, the fraction ``completed`` from 0.0 to 1.0, the ``start`` time of the simulation as a `Quantity` and the total duration of the simulation (in biological time) as a `Quantity`. The function will always be called at the beginning and the end (i.e. for fractions 0.0 and 1.0), regardless of the ``report_period``. report_period : `Quantity` How frequently (in real time) to report progress. profile : bool, optional Whether to record profiling information (see `Network.profiling_info`). Defaults to ``None`` (which will use the value set by ``set_device``, if any). namespace : dict-like, optional A namespace in which objects which do not define their own namespace will be run. If not namespace is given, the locals and globals around the run function will be used. level : int, optional How deep to go down the stack frame to look for the locals/global (see `namespace` argument). Only necessary under particular circumstances, e.g. when calling the run function as part of a function call or lambda expression. This is used in tests, e.g.: ``assert_raises(MagicError, lambda: run(1*ms, level=3))``. See Also -------- Network.run, MagicNetwork, collect, start_scope, stop Raises ------ MagicError Error raised when it was not possible for Brian to safely guess the intended use. See `MagicNetwork` for more details. """ return magic_network.run( duration, report=report, report_period=report_period, namespace=namespace, profile=profile, level=2 + level, ) run.__module__ = __name__ def store(name="default", filename=None): """ Store the state of the network and all included objects. Parameters ---------- name : str, optional A name for the snapshot, if not specified uses ``'default'``. filename : str, optional A filename where the state should be stored. If not specified, the state will be stored in memory. See Also -------- Network.store """ magic_network.store(name=name, filename=filename, level=1) def restore(name="default", filename=None, restore_random_state=False): """ Restore the state of the network and all included objects. Parameters ---------- name : str, optional The name of the snapshot to restore, if not specified uses ``'default'``. filename : str, optional The name of the file from where the state should be restored. If not specified, it is expected that the state exist in memory (i.e. `Network.store` was previously called without the ``filename`` argument). restore_random_state : bool, optional Whether to restore the state of the random number generator. If set to ``True``, going back to an earlier state of the simulation will continue exactly where it left off, even if the simulation is stochastic. If set to ``False`` (the default), random numbers are independent between runs (except for explicitly set random seeds), regardless of whether `store`/`restore` has been used or not. Note that this also restores numpy's random number generator (since it is used internally by Brian), but it does *not* restore Python's builtin random number generator in the ``random`` module. See Also -------- Network.restore """ magic_network.restore( name=name, filename=filename, restore_random_state=restore_random_state, level=1 ) def stop(): """ Stops all running simulations. See Also -------- Network.stop, run, reinit """ Network._globally_stopped = True def start_scope(): """ Starts a new scope for magic functions All objects created before this call will no longer be automatically included by the magic functions such as `run`. """ BrianObject._scope_current_key += 1 brian2-2.5.4/brian2/core/names.py000066400000000000000000000104171445201106100164560ustar00rootroot00000000000000import re import uuid from brian2.core.tracking import Trackable from brian2.utils.logger import get_logger __all__ = ["Nameable"] logger = get_logger(__name__) def find_name(name, names=None): """ Determine a unique name. If the desired ``name`` is already taken, will try to use a derived ``name_1``, ``name_2``, etc. Parameters ---------- name : str The desired name. names : Iterable, optional A set of names that are already taken. If not provided, will use the names of all Brian objects as stored in `Nameable`. Returns ------- unique_name : str A name based on ``name`` or ``name`` itself, unique with respect to the names in ``names``. """ if not name.endswith("*"): # explicitly given names are used as given. Network.before_run (and # the device in case of standalone) will check for name clashes later return name name = name[:-1] if names is None: instances = set(Nameable.__instances__()) allnames = {obj().name for obj in instances if hasattr(obj(), "name")} else: allnames = names # Try the name without any additions first: if name not in allnames: return name # Name is already taken, try _1, _2, etc. i = 1 while f"{name}_{str(i)}" in allnames: i += 1 return f"{name}_{str(i)}" class Nameable(Trackable): """ Base class to find a unique name for an object If you specify a name explicitly, and it has already been taken, a `ValueError` is raised. You can also specify a name with a wildcard asterisk in the end, i.e. in the form ``'name*'``. It will then try ``name`` first but if this is already specified, it will try ``name_1``, `name__2``, etc. This is the default mechanism used by most core objects in Brian, e.g. `NeuronGroup` uses a default name of ``'neurongroup*'``. Parameters ---------- name : str An name for the object, possibly ending in ``*`` to specify that variants of this name should be tried if the name (without the asterisk) is already taken. If (and only if) the name for this object has already been set, it is also possible to call the initialiser with ``None`` for the `name` argument. This situation can arise when a class derives from multiple classes that derive themselves from `Nameable` (e.g. `Group` and `CodeRunner`) and their initialisers are called explicitely. Raises ------ ValueError If the name is already taken. """ def __init__(self, name): if getattr(self, "_name", None) is not None and name is None: # name has already been specified previously return self.assign_id() if not isinstance(name, str): raise TypeError( "'name' argument has to be a string, is type " f"{repr(type(name))} instead" ) if not re.match(r"[_A-Za-z][_a-zA-Z0-9]*\*?$", name): raise ValueError(f"Name {name} not valid variable name") self._name = find_name(name) logger.diagnostic( f"Created object of class {self.__class__.__name__} with name {self._name}" ) def assign_id(self): """ Assign a new id to this object. Under most circumstances, this method should only be called once at the creation of the object to generate a unique id. In the case of the `MagicNetwork`, however, the id should change when a new, independent set of objects is simulated. """ self._id = uuid.uuid4() name = property( fget=lambda self: self._name, doc=""" The unique name for this object. Used when generating code. Should be an acceptable variable name, i.e. starting with a letter character and followed by alphanumeric characters and ``_``. """, ) id = property( fget=lambda self: self._id, doc=""" A unique id for this object. In contrast to names, which may be reused, the id stays unique. This is used in the dependency checking to not have to deal with the chore of comparing weak references, weak proxies and strong references. """, ) brian2-2.5.4/brian2/core/namespace.py000066400000000000000000000044271445201106100173130ustar00rootroot00000000000000""" Implementation of the namespace system, used to resolve the identifiers in model equations of `NeuronGroup` and `Synapses` """ import collections import inspect import itertools from brian2.core.functions import DEFAULT_CONSTANTS, DEFAULT_FUNCTIONS from brian2.units.fundamentalunits import ( additional_unit_register, standard_unit_register, ) from brian2.units.stdunits import stdunits from brian2.utils.logger import get_logger __all__ = [ "get_local_namespace", "DEFAULT_FUNCTIONS", "DEFAULT_UNITS", "DEFAULT_CONSTANTS", ] logger = get_logger(__name__) def get_local_namespace(level): """ Get the surrounding namespace. Parameters ---------- level : int, optional How far to go back to get the locals/globals. Each function/method call should add ``1`` to this argument, functions/method with a decorator have to add ``2``. Returns ------- namespace : dict The locals and globals at the given depth of the stack frame. """ # Get the locals and globals from the stack frame frame = inspect.currentframe() for _ in range(level + 1): frame = frame.f_back # We return the full stack here, even if it contains a lot of stuff we are # not interested in -- it is cheaper to later raise an error when we find # a specific object with an incorrect type instead of going through this big # list now to check the types of all objects return dict(itertools.chain(frame.f_globals.items(), frame.f_locals.items())) def _get_default_unit_namespace(): """ Return the namespace that is used by default for looking up units when defining equations. Contains all registered units and everything from `brian2.units.stdunits` (ms, mV, nS, etc.). Returns ------- namespace : dict The unit namespace """ namespace = collections.OrderedDict(standard_unit_register.units) namespace.update(stdunits) # Include all "simple" units from additional_units, i.e. units like mliter # but not "newton * metre" namespace.update( { name: unit for name, unit in additional_unit_register.units.items() if not unit.iscompound } ) return namespace DEFAULT_UNITS = _get_default_unit_namespace() brian2-2.5.4/brian2/core/network.py000066400000000000000000001523341445201106100170510ustar00rootroot00000000000000""" Module defining the `Network` object, the basis of all simulation runs. Preferences ----------- .. document_brian_prefs:: core.network """ import os import pickle as pickle import sys import time from collections import Counter, defaultdict, namedtuple from collections.abc import Mapping, Sequence from brian2.core.base import BrianObject, BrianObjectException from brian2.core.clocks import Clock, defaultclock from brian2.core.names import Nameable from brian2.core.namespace import get_local_namespace from brian2.core.preferences import BrianPreference, prefs from brian2.devices.device import all_devices, get_device from brian2.groups.group import Group from brian2.synapses.synapses import SummedVariableUpdater from brian2.units.allunits import msecond, second from brian2.units.fundamentalunits import Quantity, check_units from brian2.utils.logger import get_logger from .base import device_override __all__ = ["Network", "profiling_summary", "scheduling_summary"] logger = get_logger(__name__) prefs.register_preferences( "core.network", "Network preferences", default_schedule=BrianPreference( default=[ "start", "groups", "thresholds", "synapses", "resets", "end", ], docs=""" Default schedule used for networks that don't specify a schedule. """, ), ) def _format_time(time_in_s): """ Helper function to format time in seconds, minutes, hours, days, depending on the magnitude. Examples -------- >>> from brian2.core.network import _format_time >>> _format_time(12345) '3h 25m 45s' >>> _format_time(123) '2m 3s' >>> _format_time(12.5) '12s' >>> _format_time(.5) '< 1s' """ divisors = [24 * 60 * 60, 60 * 60, 60, 1] letters = ["d", "h", "m", "s"] remaining = time_in_s text = "" for divisor, letter in zip(divisors, letters): time_to_represent = int(remaining / divisor) remaining -= time_to_represent * divisor if time_to_represent > 0 or len(text): if len(text): text += " " text += f"{int(time_to_represent)}{letter}" # less than one second if len(text) == 0: text = "< 1s" return text class TextReport: """ Helper object to report simulation progress in `Network.run`. Parameters ---------- stream : file The stream to write to, commonly `sys.stdout` or `sys.stderr`. """ def __init__(self, stream): self.stream = stream def __call__(self, elapsed, completed, start, duration): if completed == 0.0: self.stream.write( f"Starting simulation at t={start} for a duration of {duration}\n" ) else: t = str(completed * duration) percent = int(completed * 100.0) real_t = _format_time(float(elapsed)) report_msg = f"{t} ({percent}%) simulated in {real_t}" if completed < 1.0: remaining = int(round((1 - completed) / completed * float(elapsed))) remaining_msg = f", estimated {_format_time(remaining)} remaining.\n" else: remaining_msg = "\n" self.stream.write(report_msg + remaining_msg) # Flush the stream, this is useful if stream is a file self.stream.flush() def _format_table(header, values, cell_formats): # table = [header] + values table_format = len(values) * [cell_formats] col_widths = [ max(len(format.format(cell, 0)) for format, cell in zip(col_format, col)) for col_format, col in zip( list(zip(*([len(header) * ["{}"]] + table_format))), list(zip(*([header] + values))), ) ] line = "-+-".join("-" * width for width in col_widths) content = [ " | ".join( format.format(cell, width) for format, cell, width in zip(row_format, row, col_widths) ) for row_format, row in zip(table_format, values) ] formatted_header = " | ".join( "{:^{}}".format(h, width) for h, width in zip(header, col_widths) ) return "\n".join([formatted_header, line] + content) class SchedulingSummary: """ Object representing the schedule that is used to simulate the objects in a network. Objects of this type are returned by `scheduling_summary`, they should not be created manually by the user. Parameters ---------- objects : list of `BrianObject` The sorted list of objects that are simulated by the network. """ def __init__(self, objects): # Map each dt to a rank (i.e. smallest dt=0, second smallest=1, etc.) self.dts = { dt: rank for rank, dt in enumerate(sorted({float(obj.clock.dt) for obj in objects})) } ScheduleEntry = namedtuple( "ScheduleEntry", field_names=[ "when", "order", "dt", "name", "type", "active", "owner_name", "owner_type", ], ) entries = [] for obj in objects: if len(obj.contained_objects): continue owner = getattr(obj, "group", None) if owner is None: owner_name, owner_type = None, None else: owner_name = owner.name owner_type = owner.__class__.__name__ entries.append( ScheduleEntry( when=obj.when, order=obj.order, dt=obj.clock.dt, name=obj.name, type=obj.__class__.__name__, active=obj.active, owner_name=owner_name, owner_type=owner_type, ) ) self.entries = entries self.all_dts = sorted({float(entry.dt) for entry in self.entries}) # How many steps compared to the fastest clock? self.steps = {float(dt): int(dt / self.all_dts[0]) for dt in self.all_dts} def __repr__(self): return _format_table( ["object", "part of", "Clock dt", "when", "order", "active"], [ [ f"{entry.name} ({entry.type})", f"{entry.owner_name} ({entry.owner_type})" if entry.owner_name is not None else "--", "{} (every {})".format( str(entry.dt), "step" if self.steps[float(entry.dt)] == 1 else f"{self.steps[float(entry.dt)]} steps", ), entry.when, entry.order, "yes" if entry.active else "no", ] for entry in self.entries ], ["{:<{}}", "{:<{}}", "{:<{}}", "{:<{}}", "{:{}d}", "{:^{}}"], ) def _repr_html_(self): rows = [ """\ {} {} {} {} {} {} """.format( f"{entry.name} ({entry.type})", f"{entry.owner_name} ({entry.owner_type})" if entry.owner_name is not None else "–", "{} (every {})".format( str(entry.dt), "step" if self.steps[float(entry.dt)] == 1 else f"{self.steps[float(entry.dt)]} steps", ), entry.when, entry.order, "yes" if entry.active else "no", ) for entry in self.entries ] html_code = """ {rows}
object part of Clock dt when order active
""".format( rows="\n".join(rows) ) return html_code def _check_multiple_summed_updaters(objects): """ Helper function that checks whether multiple `SummedVariableUpdater` target the same target variable. Raises a `NotImplementedError` if this is the case (and problematic, i.e. not when using non-overlapping subgroups). Parameters ---------- objects : list of `BrianObject` The list of objects in the network. """ summed_targets = {} for obj in objects: if isinstance(obj, SummedVariableUpdater): if obj.target_var in summed_targets: other_target = summed_targets[obj.target_var] if obj.target == other_target: # We raise an error, even though this could be ok in # principle (e.g. two Synapses could target different # subsets of the target groups, without using subgroups) msg = ( "Multiple 'summed variables' target the " f"variable '{obj.target_var.name}' in group " f"'{obj.target.name}'. Use multiple variables in " "the target group instead." ) raise NotImplementedError(msg) elif ( obj.target.start < other_target.stop and other_target.start < obj.target.stop ): # Overlapping subgroups msg = ( "Multiple 'summed variables' target the " f"variable '{obj.target_var.name}' in overlapping " f"groups '{other_target.name}' and '{obj.target.name}'. " "Use separate variables in the target groups instead." ) raise NotImplementedError(msg) summed_targets[obj.target_var] = obj.target def _get_all_objects(objs): """ Helper function to get all objects of a 'Network' along with their corresponding ``contained_objects``. Parameters ---------- objs : Iterable List or set of objects Returns ------- all_objects : set A set of all Network's objects and respective child objects. """ all_objects = set() for obj in objs: all_objects.add(obj) all_objects |= _get_all_objects(obj.contained_objects) return all_objects class Network(Nameable): """ Network(*objs, name='network*') The main simulation controller in Brian `Network` handles the running of a simulation. It contains a set of Brian objects that are added with `~Network.add`. The `~Network.run` method actually runs the simulation. The main run loop, determining which objects get called in what order is described in detail in the notes below. The objects in the `Network` are accesible via their names, e.g. `net['neurongroup']` would return the `NeuronGroup` with this name. Parameters ---------- objs : (`BrianObject`, container), optional A list of objects to be added to the `Network` immediately, see `~Network.add`. name : str, optional An explicit name, if not specified gives an automatically generated name Notes ----- The main run loop performs the following steps: 1. Prepare the objects if necessary, see `~Network.prepare`. 2. Determine the end time of the simulation as `~Network.t`+``duration``. 3. Determine which set of clocks to update. This will be the clock with the smallest value of `~Clock.t`. If there are several with the same value, then all objects with these clocks will be updated simultaneously. Set `~Network.t` to the clock time. 4. If the `~Clock.t` value of these clocks is past the end time of the simulation, stop running. If the `Network.stop` method or the `stop` function have been called, stop running. Set `~Network.t` to the end time of the simulation. 5. For each object whose `~BrianObject.clock` is set to one of the clocks from the previous steps, call the `~BrianObject.update` method. This method will not be called if the `~BrianObject.active` flag is set to ``False``. The order in which the objects are called is described below. 6. Increase `Clock.t` by `Clock.dt` for each of the clocks and return to step 2. The order in which the objects are updated in step 4 is determined by the `Network.schedule` and the objects `~BrianObject.when` and `~BrianObject.order` attributes. The `~Network.schedule` is a list of string names. Each `~BrianObject.when` attribute should be one of these strings, and the objects will be updated in the order determined by the schedule. The default schedule is ``['start', 'groups', 'thresholds', 'synapses', 'resets', 'end']``. In addition to the names provided in the schedule, automatic names starting with ``before_`` and ``after_`` can be used. That means that all objects with ``when=='before_start'`` will be updated first, then those with ``when=='start'``, ``when=='after_start'``, ``when=='before_groups'``, ``when=='groups'`` and so forth. If several objects have the same `~BrianObject.when` attribute, then the order is determined by the `~BrianObject.order` attribute (lower first). See Also -------- MagicNetwork, run, stop """ def __init__(self, *objs, **kwds): #: The set of objects in the Network, should not normally be modified #: directly. #: Note that in a `MagicNetwork`, this attribute only contains the #: objects during a run: it is filled in `before_run` and emptied in #: `after_run` self.objects = set() name = kwds.pop("name", "network*") if kwds: raise TypeError("Only keyword argument to Network is 'name'.") Nameable.__init__(self, name=name) #: Current time as a float self.t_ = 0.0 for obj in objs: self.add(obj) #: Stored state of objects (store/restore) self._stored_state = {} # Stored profiling information (if activated via the keyword option) self._profiling_info = None self._schedule = None t = property( fget=lambda self: Quantity(self.t_, dim=second.dim, copy=False), doc=""" Current simulation time in seconds (`Quantity`) """, ) @device_override("network_get_profiling_info") def get_profiling_info(self): """ The only reason this is not directly implemented in `profiling_info` is to allow devices (e.g. `CPPStandaloneDevice`) to overwrite this. """ if self._profiling_info is None: raise ValueError( "No profiling info collected (did you run with 'profile=True?')" ) return sorted(self._profiling_info, key=lambda item: item[1], reverse=True) @property def profiling_info(self): """ The time spent in executing the various `CodeObject` s. A list of ``(name, time)`` tuples, containing the name of the `CodeObject` and the total execution time for simulations of this object (as a `Quantity` with unit `second`). The list is sorted descending with execution time. Profiling has to be activated using the ``profile`` keyword in `run` or `Network.run`. """ return self.get_profiling_info() _globally_stopped = False def __getitem__(self, item): if not isinstance(item, str): raise TypeError( f"Need a name to access objects in a Network, got {type(item)} instead" ) all_objects = _get_all_objects(self.objects) for obj in all_objects: if obj.name == item: return obj raise KeyError(f'No object with name "{item}" found') def __delitem__(self, key): if not isinstance(key, str): raise TypeError( "Need a name to access objects in a Network, got {type(key)} instead" ) for obj in self.objects: if obj.name == key: self.remove(obj) return raise KeyError(f"No object with name '{key}' found") def __contains__(self, item): all_objects = _get_all_objects(self.objects) for obj in all_objects: if obj.name == item: return True return False def __len__(self): all_objects = _get_all_objects(self.objects) return len(all_objects) def __iter__(self): all_objects = _get_all_objects(self.objects) return iter(all_objects) def add(self, *objs): """ Add objects to the `Network` Parameters ---------- objs : (`BrianObject`, container) The `BrianObject` or container of Brian objects to be added. Specify multiple objects, or lists (or other containers) of objects. Containers will be added recursively. If the container is a `dict` then it will add the values from the dictionary but not the keys. If you want to add the keys, do ``add(objs.keys())``. """ for obj in objs: if isinstance(obj, BrianObject): if obj._network is not None: raise RuntimeError( f"{obj.name} has already been simulated, cannot " "add it to the network. If you were " "trying to remove and add an object to " "temporarily stop it from being run, " "set its active flag to False instead." ) self.objects.add(obj) else: # allow adding values from dictionaries if isinstance(obj, Mapping): self.add(*list(obj.values())) else: try: for o in obj: # The following "if" looks silly but avoids an infinite # recursion if a string is provided as an argument # (which might occur during testing) if o is obj: raise TypeError() self.add(o) except TypeError: raise TypeError( "Can only add objects of type BrianObject, " "or containers of such objects to Network" ) def remove(self, *objs): """ Remove an object or sequence of objects from a `Network`. Parameters ---------- objs : (`BrianObject`, container) The `BrianObject` or container of Brian objects to be removed. Specify multiple objects, or lists (or other containers) of objects. Containers will be removed recursively. """ for obj in objs: if isinstance(obj, BrianObject): self.objects.remove(obj) else: try: for o in obj: self.remove(o) except TypeError: raise TypeError( "Can only remove objects of type " "BrianObject, or containers of such " "objects from Network" ) def _full_state(self): all_objects = _get_all_objects(self.objects) state = {} for obj in all_objects: if hasattr(obj, "_full_state"): state[obj.name] = obj._full_state() clocks = {obj.clock for obj in all_objects} for clock in clocks: state[clock.name] = clock._full_state() # Store the time as "0_t" -- this name is guaranteed not to clash with # the name of an object as names are not allowed to start with a digit state["0_t"] = self.t_ return state @device_override("network_store") def store(self, name="default", filename=None): """ store(name='default', filename=None) Store the state of the network and all included objects. Parameters ---------- name : str, optional A name for the snapshot, if not specified uses ``'default'``. filename : str, optional A filename where the state should be stored. If not specified, the state will be stored in memory. Notes ----- The state stored to disk can be restored with the `Network.restore` function. Note that it will only restore the *internal state* of all the objects (including undelivered spikes) -- the objects have to exist already and they need to have the same name as when they were stored. Equations, thresholds, etc. are *not* stored -- this is therefore not a general mechanism for object serialization. Also, the format of the file is not guaranteed to work across platforms or versions. If you are interested in storing the state of a network for documentation or analysis purposes use `Network.get_states` instead. """ clocks = {obj.clock for obj in _get_all_objects(self.objects)} # Make sure that all clocks are up to date for clock in clocks: clock._set_t_update_dt(target_t=self.t) state = self._full_state() # Store the state of the random number generator dev = get_device() state["_random_generator_state"] = dev.get_random_state() if filename is None: self._stored_state[name] = state else: # A single file can contain several states, so we'll read in the # existing file first if it exists if os.path.exists(filename): with open(filename, "rb") as f: store_state = pickle.load(f) else: store_state = {} store_state[name] = state with open(filename, "wb") as f: pickle.dump(store_state, f, protocol=pickle.HIGHEST_PROTOCOL) @device_override("network_restore") def restore(self, name="default", filename=None, restore_random_state=False): """ restore(name='default', filename=None, restore_random_state=False) Retore the state of the network and all included objects. Parameters ---------- name : str, optional The name of the snapshot to restore, if not specified uses ``'default'``. filename : str, optional The name of the file from where the state should be restored. If not specified, it is expected that the state exist in memory (i.e. `Network.store` was previously called without the ``filename`` argument). restore_random_state : bool, optional Whether to restore the state of the random number generator. If set to ``True``, going back to an earlier state of the simulation will continue exactly where it left off, even if the simulation is stochastic. If set to ``False`` (the default), random numbers are independent between runs (except for explicitly set random seeds), regardless of whether `store`/`restore` has been used or not. Note that this also restores numpy's random number generator (since it is used internally by Brian), but it does *not* restore Python's builtin random number generator in the ``random`` module. """ all_objects = _get_all_objects(self.objects) if filename is None: state = self._stored_state[name] else: with open(filename, "rb") as f: state = pickle.load(f)[name] self.t_ = state["0_t"] if restore_random_state: dev = get_device() dev.set_random_state(state["_random_generator_state"]) clocks = {obj.clock for obj in all_objects} restored_objects = set() for obj in all_objects: if obj.name in state: obj._restore_from_full_state(state[obj.name]) restored_objects.add(obj.name) elif hasattr(obj, "_restore_from_full_state"): raise KeyError( "Stored state does not have a stored state for " f"'{obj.name}'. Note that the names of all objects have " "to be identical to the names when they were " "stored." ) for clock in clocks: clock._restore_from_full_state(state[clock.name]) clock_names = {c.name for c in clocks} unnused = ( set(state.keys()) - restored_objects - clock_names - {"0_t", "_random_generator_state"} ) if len(unnused): raise KeyError( "The stored state contains the state of the " "following objects which were not present in the " f"network: {', '.join(unnused)}. Note that the names of all " "objects have to be identical to the names when they " "were stored." ) def get_states( self, units=True, format="dict", subexpressions=False, read_only_variables=True, level=0, ): """ Return a copy of the current state variable values of objects in the network.. The returned arrays are copies of the actual arrays that store the state variable values, therefore changing the values in the returned dictionary will not affect the state variables. Parameters ---------- vars : list of str, optional The names of the variables to extract. If not specified, extract all state variables (except for internal variables, i.e. names that start with ``'_'``). If the ``subexpressions`` argument is ``True``, the current values of all subexpressions are returned as well. units : bool, optional Whether to include the physical units in the return value. Defaults to ``True``. format : str, optional The output format. Defaults to ``'dict'``. subexpressions: bool, optional Whether to return subexpressions when no list of variable names is given. Defaults to ``False``. This argument is ignored if an explicit list of variable names is given in ``vars``. read_only_variables : bool, optional Whether to return read-only variables (e.g. the number of neurons, the time, etc.). Setting it to ``False`` will assure that the returned state can later be used with `set_states`. Defaults to ``True``. level : int, optional How much higher to go up the stack to resolve external variables. Only relevant if extracting subexpressions that refer to external variables. Returns ------- values : dict A dictionary mapping object names to the state variables of that object, in the specified ``format``. See Also -------- VariableOwner.get_states """ states = dict() for obj in self.sorted_objects: if hasattr(obj, "get_states"): states[obj.name] = obj.get_states( vars=None, units=units, format=format, subexpressions=subexpressions, read_only_variables=read_only_variables, level=level + 1, ) return states def set_states(self, values, units=True, format="dict", level=0): """ Set the state variables of objects in the network. Parameters ---------- values : dict A dictionary mapping object names to objects of ``format``, setting the states of this object. units : bool, optional Whether the ``values`` include physical units. Defaults to ``True``. format : str, optional The format of ``values``. Defaults to ``'dict'`` level : int, optional How much higher to go up the stack to _resolve external variables. Only relevant when using string expressions to set values. See Also -------- Group.set_states """ # For the moment, 'dict' is the only supported format -- later this will # be made into an extensible system, see github issue #306 for obj_name, obj_values in values.items(): if obj_name not in self: raise KeyError( "Network does not include a network with name '%s'." % obj_name ) self[obj_name].set_states( obj_values, units=units, format=format, level=level + 1 ) def _get_schedule(self): if self._schedule is None: return list(prefs.core.network.default_schedule) else: return list(self._schedule) def _set_schedule(self, schedule): if schedule is None: self._schedule = None logger.debug("Resetting network {self.name} schedule to default schedule") else: if not isinstance(schedule, Sequence) or not all( isinstance(slot, str) for slot in schedule ): raise TypeError( "Schedule has to be None or a sequence of scheduling slots" ) if any( slot.startswith("before_") or slot.startswith("after_") for slot in schedule ): raise ValueError( "Slot names are not allowed to start with " "'before_' or 'after_' -- such slot names " "are created automatically based on the " "existing slot names." ) self._schedule = list(schedule) logger.debug( f"Setting network '{self.name}' schedule to {self._schedule}", "_set_schedule", ) schedule = property( fget=_get_schedule, fset=_set_schedule, doc=""" List of ``when`` slots in the order they will be updated, can be modified. See notes on scheduling in `Network`. Note that additional ``when`` slots can be added, but the schedule should contain at least all of the names in the default schedule: ``['start', 'groups', 'thresholds', 'synapses', 'resets', 'end']``. The schedule can also be set to ``None``, resetting it to the default schedule set by the `core.network.default_schedule` preference. """, ) @property def sorted_objects(self): """ The sorted objects of this network in the order defined by the schedule. Objects are sorted first by their ``when`` attribute, and secondly by the ``order`` attribute. The order of the ``when`` attribute is defined by the ``schedule``. In addition to the slot names defined in the schedule, automatic slot names starting with ``before_`` and ``after_`` can be used (e.g. the slots ``['groups', 'thresholds']`` allow to use ``['before_groups', 'groups', 'after_groups', 'before_thresholds', 'thresholds', 'after_thresholds']``). Final ties are resolved using the objects' names, leading to an arbitrary but deterministic sorting. """ # Provided slot names are assigned positions 1, 4, 7, ... # before_... names are assigned positions 0, 3, 6, ... # after_... names are assigned positions 2, 5, 8, ... all_objects = _get_all_objects(self.objects) when_to_int = {when: 1 + i * 3 for i, when in enumerate(self.schedule)} when_to_int.update( (f"before_{when}", i * 3) for i, when in enumerate(self.schedule) ) when_to_int.update( (f"after_{when}", 2 + i * 3) for i, when in enumerate(self.schedule) ) return sorted( all_objects, key=lambda obj: (when_to_int[obj.when], obj.order, obj.name) ) def scheduling_summary(self): """ Return a `SchedulingSummary` object, representing the scheduling information for all objects included in the network. Returns ------- summary : `SchedulingSummary` Object representing the scheduling information. """ return SchedulingSummary(self.sorted_objects) def check_dependencies(self): all_objects = _get_all_objects(self.objects) all_ids = [obj.id for obj in all_objects] for obj in all_objects: for dependency in obj._dependencies: if dependency not in all_ids: raise ValueError( f"'{obj.name}' has been included in the network " "but not the object on which it " "depends." ) @device_override("network_before_run") def before_run(self, run_namespace): """ before_run(namespace) Prepares the `Network` for a run. Objects in the `Network` are sorted into the correct running order, and their `BrianObject.before_run` methods are called. Parameters ---------- run_namespace : dict-like, optional A namespace in which objects which do not define their own namespace will be run. """ all_objects = self.sorted_objects prefs.check_all_validated() # Check names in the network for uniqueness names = [obj.name for obj in all_objects] non_unique_names = [name for name, count in Counter(names).items() if count > 1] if len(non_unique_names): formatted_names = ", ".join(f"'{name}'" for name in non_unique_names) raise ValueError( "All objects in a network need to have unique " "names, the following name(s) were used more than " f"once: {formatted_names}" ) # Check that there are no SummedVariableUpdaters targeting the same # target variable _check_multiple_summed_updaters(all_objects) self._stopped = False Network._globally_stopped = False device = get_device() if device.network_schedule is not None: # The device defines a fixed network schedule if device.network_schedule != self.schedule: # TODO: The human-readable name of a device should be easier to get device_name = list(all_devices.keys())[ list(all_devices.values()).index(device) ] logger.warn( f"The selected device '{device_name}' only " "supports a fixed schedule, but this schedule is " "not consistent with the network's schedule. The " "simulation will use the device's schedule.\n" f"Device schedule: {device.network_schedule}\n" f"Network schedule: {self.schedule}\n" "Set the network schedule explicitly or set the " "core.network.default_schedule preference to " "avoid this warning.", name_suffix="schedule_conflict", once=True, ) objnames = ", ".join(obj.name for obj in all_objects) logger.debug( f"Preparing network '{self.name}' with {len(all_objects)} " f"objects: {objnames}", "before_run", ) self.check_dependencies() for obj in all_objects: if obj.active: try: obj.before_run(run_namespace) except Exception as ex: raise BrianObjectException( "An error occurred when preparing an object.", obj ) from ex # Check that no object has been run as part of another network before for obj in all_objects: if obj._network is None: obj._network = self.id elif obj._network != self.id: raise RuntimeError( f"'{obj.name}' has already been run in the " "context of another network. Use " "add/remove to change the objects " "in a simulated network instead of " "creating a new one." ) clocknames = ", ".join(f"{obj.name} (dt={obj.dt})" for obj in self._clocks) logger.debug( f"Network '{self.name}' uses {len(self._clocks)} clocks: {clocknames}", "before_run", ) @device_override("network_after_run") def after_run(self): """ after_run() """ for obj in self.sorted_objects: if obj.active: obj.after_run() def _nextclocks(self): clocks_times_dt = [ (c, self._clock_variables[c][1][0], self._clock_variables[c][2][0]) for c in self._clocks ] minclock, min_time, minclock_dt = min(clocks_times_dt, key=lambda k: k[1]) curclocks = { clock for clock, time, dt in clocks_times_dt if ( time == min_time or abs(time - min_time) / min(minclock_dt, dt) < Clock.epsilon_dt ) } return minclock, curclocks @device_override("network_run") @check_units(duration=second, report_period=second) def run( self, duration, report=None, report_period=10 * second, namespace=None, profile=None, level=0, ): """ run(duration, report=None, report_period=60*second, namespace=None, level=0) Runs the simulation for the given duration. Parameters ---------- duration : `Quantity` The amount of simulation time to run for. report : {None, 'text', 'stdout', 'stderr', function}, optional How to report the progress of the simulation. If ``None``, do not report progress. If ``'text'`` or ``'stdout'`` is specified, print the progress to stdout. If ``'stderr'`` is specified, print the progress to stderr. Alternatively, you can specify a callback ``callable(elapsed, completed, start, duration)`` which will be passed the amount of time elapsed as a `Quantity`, the fraction ``completed`` from 0.0 to 1.0, the ``start`` time of the simulation as a `Quantity` and the total duration of the simulation (in biological time) as a `Quantity`. The function will always be called at the beginning and the end (i.e. for fractions 0.0 and 1.0), regardless of the ``report_period``. report_period : `Quantity` How frequently (in real time) to report progress. namespace : dict-like, optional A namespace that will be used in addition to the group-specific namespaces (if defined). If not specified, the locals and globals around the run function will be used. profile : bool, optional Whether to record profiling information (see `Network.profiling_info`). Defaults to ``None`` (which will use the value set by ``set_device``, if any). level : int, optional How deep to go up the stack frame to look for the locals/global (see `namespace` argument). Only used by run functions that call this run function, e.g. `MagicNetwork.run` to adjust for the additional nesting. Notes ----- The simulation can be stopped by calling `Network.stop` or the global `stop` function. """ device = get_device() # Do not use the ProxyDevice -- slightly faster if profile is None: profile = device.build_options.get("profile", False) all_objects = self.sorted_objects self._clocks = {obj.clock for obj in all_objects} single_clock = len(self._clocks) == 1 t_start = self.t t_end = self.t + duration if single_clock: clock = list(self._clocks)[0] clock.set_interval(self.t, t_end) else: # We get direct references to the underlying variables for all clocks # to avoid expensive access during the run loop self._clock_variables = { c: ( c.variables["timestep"].get_value(), c.variables["t"].get_value(), c.variables["dt"].get_value(), ) for c in self._clocks } for clock in self._clocks: clock.set_interval(self.t, t_end) # Get the local namespace if namespace is None: namespace = get_local_namespace(level=level + 3) self.before_run(namespace) if len(all_objects) == 0: return # TODO: raise an error? warning? start_time = time.time() logger.debug( f"Simulating network '{self.name}' from time {t_start} to {t_end}.", "run" ) if report is not None: report_period = float(report_period) next_report_time = start_time + report_period if report == "text" or report == "stdout": report_callback = TextReport(sys.stdout) elif report == "stderr": report_callback = TextReport(sys.stderr) elif isinstance(report, str): raise ValueError( f'Do not know how to handle report argument "{report}".' ) elif callable(report): report_callback = report else: raise TypeError( "Do not know how to handle report argument, " "it has to be one of 'text', 'stdout', " "'stderr', or a callable function/object, " f"but it is of type {type(report)}" ) report_callback(0 * second, 0.0, t_start, duration) profiling_info = defaultdict(float) if single_clock: timestep, t, dt = ( clock.variables["timestep"].get_value(), clock.variables["t"].get_value(), clock.variables["dt"].get_value(), ) else: # Find the first clock to be updated (see note below) clock, curclocks = self._nextclocks() timestep, _, _ = self._clock_variables[clock] running = timestep[0] < clock._i_end active_objects = [obj for obj in all_objects if obj.active] while running and not self._stopped and not Network._globally_stopped: if not single_clock: timestep, t, dt = self._clock_variables[clock] # update the network time to this clock's time self.t_ = t[0] if report is not None: current = time.time() if current > next_report_time: report_callback( (current - start_time) * second, (self.t_ - float(t_start)) / float(t_end - t_start), t_start, duration, ) next_report_time = current + report_period # update the objects and tick forward the clock(s) if single_clock: if profile: for obj in active_objects: obj_time = time.time() obj.run() profiling_info[obj.name] += time.time() - obj_time else: for obj in active_objects: obj.run() timestep[0] += 1 t[0] = timestep[0] * dt[0] else: if profile: for obj in active_objects: if obj._clock in curclocks: obj_time = time.time() obj.run() profiling_info[obj.name] += time.time() - obj_time else: for obj in active_objects: if obj._clock in curclocks: obj.run() for c in curclocks: timestep, t, dt = self._clock_variables[c] timestep[0] += 1 t[0] = timestep[0] * dt[0] # find the next clocks to be updated. The < operator for Clock # determines that the first clock to be updated should be the one # with the smallest t value, unless there are several with the # same t value in which case we update all of them clock, curclocks = self._nextclocks() timestep, _, _ = self._clock_variables[clock] if ( device._maximum_run_time is not None and time.time() - start_time > float(device._maximum_run_time) ): self._stopped = True else: running = timestep[0] < clock._i_end end_time = time.time() if self._stopped or Network._globally_stopped: self.t_ = clock.t_ else: self.t_ = float(t_end) device._last_run_time = end_time - start_time if duration > 0: device._last_run_completed_fraction = (self.t - t_start) / duration else: device._last_run_completed_fraction = 1.0 # check for nans for obj in all_objects: if isinstance(obj, Group): obj._check_for_invalid_states() if report is not None: report_callback((end_time - start_time) * second, 1.0, t_start, duration) self.after_run() logger.debug( f"Finished simulating network '{self.name}' " f"(took {end_time-start_time:.2f}s)", "run", ) # Store profiling info (or erase old info to avoid confusion) if profile: self._profiling_info = [ (name, t * second) for name, t in profiling_info.items() ] # Dump a profiling summary to the log logger.debug(f"\n{str(profiling_summary(self))}") else: self._profiling_info = None @device_override("network_stop") def stop(self): """ stop() Stops the network from running, this is reset the next time `Network.run` is called. """ self._stopped = True def __repr__(self): objects = ", ".join(obj.__repr__() for obj in _get_all_objects(self.objects)) return ( f"<{self.__class__.__name__} at time t={self.t!s}, containing " f"objects: {objects}>" ) class ProfilingSummary: """ Class to nicely display the results of profiling. Objects of this class are returned by `profiling_summary`. Parameters ---------- net : `Network` The `Network` object to profile. show : int, optional The number of results to show (the longest results will be shown). If not specified, all results will be shown. See Also -------- Network.profiling_info """ def __init__(self, net, show=None): prof = net.profiling_info if len(prof): names, times = list(zip(*prof)) else: # Can happen if a network has been run for 0ms # Use a dummy entry to prevent problems with empty lists later names = ["no code objects have been run"] times = [0 * second] self.total_time = sum(times) self.time_unit = msecond if self.total_time > 1 * second: self.time_unit = second if show is not None: names = names[:show] times = times[:show] if self.total_time > 0 * second: self.percentages = [100.0 * time / self.total_time for time in times] else: self.percentages = [0.0 for _ in times] self.names_maxlen = max(len(name) for name in names) self.names = [name + " " * (self.names_maxlen - len(name)) for name in names] self.times = times def __repr__(self): times = [f"{time / self.time_unit:.2f} {self.time_unit}" for time in self.times] times_maxlen = max(len(time) for time in times) times = [" " * (times_maxlen - len(time)) + time for time in times] percentages = [f"{percentage:.2f} %" for percentage in self.percentages] percentages_maxlen = max(len(percentage) for percentage in percentages) percentages = [ (" " * (percentages_maxlen - len(percentage))) + percentage for percentage in percentages ] s = "Profiling summary" s += f"\n{'=' * len(s)}\n" for name, t, percentage in zip(self.names, times, percentages): s += f"{name} {t} {percentage}\n" return s def _repr_html_(self): times = [f"{time / self.time_unit:.2f} {self.time_unit}" for time in self.times] percentages = [f"{percentage:.2f} %" for percentage in self.percentages] s = '

Profiling summary

\n' s += '\n' for name, t, percentage in zip(self.names, times, percentages): s += "" s += f"" s += f'' s += f'' s += "\n" s += "
{name}{t}{percentage}
" return s def profiling_summary(net=None, show=None): """ Returns a `ProfilingSummary` of the profiling info for a run. This object can be transformed to a string explicitly but on an interactive console simply calling `profiling_summary` is enough since it will automatically convert the `ProfilingSummary` object. Parameters ---------- net : {`Network`, None} optional The `Network` object to profile, or `magic_network` if not specified. show : int The number of results to show (the longest results will be shown). If not specified, all results will be shown. """ if net is None: from .magic import magic_network net = magic_network return ProfilingSummary(net, show) def scheduling_summary(net=None): """ Returns a `SchedulingSummary` object, representing the scheduling information for all objects included in the given `Network` (or the "magic" network, if none is specified). The returned objects can be printed or converted to a string to give an ASCII table representation of the schedule. In a Jupyter notebook, the output can be displayed as a HTML table. Parameters ---------- net : `Network`, optional The network for which the scheduling information should be displayed. Defaults to the "magic" network. Returns ------- summary : `SchedulingSummary` An object that represents the scheduling information. """ if net is None: from .magic import magic_network magic_network._update_magic_objects(level=1) net = magic_network return net.scheduling_summary() def schedule_propagation_offset(net=None): """ Returns the minimal time difference for a post-synaptic effect after a spike. With the default schedule, this time difference is 0, since the ``thresholds`` slot precedes the ``synapses`` slot. For the GeNN device, however, a post-synaptic effect will occur in the following time step, this function therefore returns one ``dt``. Parameters ---------- net : `Network` The network to check (uses the magic network if not specified). Returns ------- offset : `Quantity` The minimum spike propagation delay: ``0*ms`` for the standard schedule but ``dt`` for schedules where ``synapses`` precedes ``thresholds``. Notes ----- This function always returns ``0*ms`` or ``defaultclock.dt`` -- no attempt is made to deal with other clocks. """ from brian2.core.magic import magic_network device = get_device() if device.network_schedule is not None: schedule = device.network_schedule else: if net is None: net = magic_network schedule = net.schedule if schedule.index("thresholds") < schedule.index("synapses"): return 0 * second else: return defaultclock.dt brian2-2.5.4/brian2/core/operations.py000066400000000000000000000201371445201106100175360ustar00rootroot00000000000000import inspect from brian2.core.base import BrianObject __all__ = ["NetworkOperation", "network_operation"] class NetworkOperation(BrianObject): """Object with function that is called every time step. Parameters ---------- function : function The function to call every time step, should take either no arguments in which case it is called as ``function()`` or one argument, in which case it is called with the current `Clock` time (`Quantity`). dt : `Quantity`, optional The time step to be used for the simulation. Cannot be combined with the `clock` argument. clock : `Clock`, optional The update clock to be used. If neither a clock, nor the `dt` argument is specified, the `defaultclock` will be used. when : str, optional In which scheduling slot to execute the operation during a time step. Defaults to ``'start'``. See :ref:`scheduling` for possible values. order : int, optional The priority of this operation for operations occurring at the same time step and in the same scheduling slot. Defaults to 0. See Also -------- network_operation, Network, BrianObject """ add_to_magic_network = True def __init__( self, function, dt=None, clock=None, when="start", order=0, name="networkoperation*", ): BrianObject.__init__( self, dt=dt, clock=clock, when=when, order=order, name=name ) #: The function to be called each time step self.function = function is_method = inspect.ismethod(function) if hasattr(function, "__code__"): argcount = function.__code__.co_argcount if is_method: if argcount == 2: self._has_arg = True elif argcount == 1: self._has_arg = False else: raise TypeError( f"Method '{function.__name__}' cannot be used as a " "network operation, it needs to have either " "only 'self' or 'self, t' as arguments, but it " f"has {argcount} arguments." ) else: if argcount >= 1 and function.__code__.co_varnames[0] == "self": raise TypeError( "The first argument of the function " "'{function.__name__}' is 'self', suggesting it " "is an instance method and not a function. Did " "you use @network_operation on a class method? " "This will not work, explicitly create a " "NetworkOperation object instead -- see " "the documentation for more " "details." ) if argcount == 1: self._has_arg = True elif argcount == 0: self._has_arg = False else: raise TypeError( f"Function '{function.__name__}' cannot be used as " "a network operation, it needs to have either " "only 't' as an argument or have no arguments, " f"but it has {argcount} arguments." ) else: self._has_arg = False def run(self): if self._has_arg: self.function(self._clock.t) else: self.function() def network_operation(*args, **kwds): """ network_operation(when=None) Decorator to make a function get called every time step of a simulation. The function being decorated should either have no arguments, or a single argument which will be called with the current time ``t``. Parameters ---------- dt : `Quantity`, optional The time step to be used for the simulation. Cannot be combined with the `clock` argument. clock : `Clock`, optional The update clock to be used. If neither a clock, nor the `dt` argument is specified, the `defaultclock` will be used. when : str, optional In which scheduling slot to execute the operation during a time step. Defaults to ``'start'``. See :ref:`scheduling` for possible values. order : int, optional The priority of this operation for operations occurring at the same time step and in the same scheduling slot. Defaults to 0. Examples -------- Print something each time step: >>> from brian2 import * >>> @network_operation ... def f(): ... print('something') ... >>> net = Network(f) Print the time each time step: >>> @network_operation ... def f(t): ... print('The time is', t) ... >>> net = Network(f) Specify a dt, etc.: >>> @network_operation(dt=0.5*ms, when='end') ... def f(): ... print('This will happen at the end of each timestep.') ... >>> net = Network(f) Notes ----- Converts the function into a `NetworkOperation`. If using the form:: @network_operations(when='end') def f(): ... Then the arguments to network_operation must be keyword arguments. See Also -------- NetworkOperation, Network, BrianObject """ # Notes on this decorator: # Normally, a decorator comes in two types, with or without arguments. If # it has no arguments, e.g. # @decorator # def f(): # ... # then the decorator function is defined with an argument, and that # argument is the function f. In this case, the decorator function # returns a new function in place of f. # # However, you can also define: # @decorator(arg) # def f(): # ... # in which case the argument to the decorator function is arg, and the # decorator function returns a 'function factory', that is a callable # object that takes a function as argument and returns a new function. # # It might be clearer just to note that the first form above is equivalent # to: # f = decorator(f) # and the second to: # f = decorator(arg)(f) # # In this case, we're allowing the decorator to be called either with or # without an argument, so we have to look at the arguments and determine # if it's a function argument (in which case we do the first case above), # or if the arguments are arguments to the decorator, in which case we # do the second case above. # # Here, the 'function factory' is the locally defined class # do_network_operation, which is a callable object that takes a function # as argument and returns a NetworkOperation object. class do_network_operation: def __init__(self, **kwds): self.kwds = kwds def __call__(self, f): new_network_operation = NetworkOperation(f, **self.kwds) # Depending on whether we were called as @network_operation or # @network_operation(...) we need different levels, the level is # 2 in the first case and 1 in the second case (because in the # first case we go originalcaller->network_operation->do_network_operation # and in the second case we go originalcaller->do_network_operation # at the time when this method is called). new_network_operation.__name__ = f.__name__ new_network_operation.__doc__ = f.__doc__ new_network_operation.__dict__.update(f.__dict__) return new_network_operation if len(args) == 1 and callable(args[0]): # We're in case (1), the user has written: # @network_operation # def f(): # ... # and the single argument to the decorator is the function f return do_network_operation()(args[0]) else: # We're in case (2), the user has written: # @network_operation(...) # def f(): # ... # and the arguments must be keyword arguments return do_network_operation(**kwds) brian2-2.5.4/brian2/core/preferences.py000066400000000000000000000615111445201106100176550ustar00rootroot00000000000000""" Brian global preferences are stored as attributes of a `BrianGlobalPreferences` object ``prefs``. """ import os import re from collections.abc import MutableMapping from io import StringIO from brian2.units.fundamentalunits import Quantity, have_same_dimensions from brian2.utils.stringtools import deindent, indent __all__ = ["PreferenceError", "BrianPreference", "prefs", "brian_prefs"] def parse_preference_name(name): """ Split a preference name into a base and end name. Parameters ---------- name : str The full name of the preference. Returns ------- basename : str The first part of the name up to the final ``.``. endname : str The last part of the name from the final ``.`` onwards. Examples -------- >>> parse_preference_name('core.default_float_dtype') ('core', 'default_float_dtype') >>> parse_preference_name('codegen.cpp.compiler') ('codegen.cpp', 'compiler') """ # parse the name parts = name.split(".") basename = ".".join(parts[:-1]) endname = parts[-1] return basename, endname def check_preference_name(name): """ Make sure that a preference name is valid. This currently checks that the name does not contain illegal characters and does not clash with method names such as "keys" or "items". Parameters ---------- name : str The name to check. Raises ------ PreferenceError In case the name is invalid. """ if not re.match("[A-Za-z][_a-zA-Z0-9]*$", name): raise PreferenceError( f"Illegal preference name '{name}': A preference " "name can only start with a letter and only " "contain letters, digits or underscore." ) if name in dir(MutableMapping) or name in prefs.__dict__: raise PreferenceError( f"Illegal preference name '{name}': This is also the name of a method." ) class PreferenceError(Exception): """ Exception relating to the Brian preferences system. """ pass class DefaultValidator: """ Default preference validator Used by `BrianPreference` as the default validator if none is given. First checks if the provided value is of the same class as the default value, and then if the default is a `Quantity`, checks that the units match. """ def __init__(self, value): self.value = value def __call__(self, value): if not isinstance(value, self.value.__class__): return False if isinstance(self.value, Quantity): if not have_same_dimensions(self.value, value): return False return True class BrianPreference: """ Used for defining a Brian preference. Parameters ---------- default : object The default value. docs : str Documentation for the preference value. validator : func A function that True or False depending on whether the preference value is valid or not. If not specified, uses the `DefaultValidator` for the default value provided (check if the class is the same, and for `Quantity` objects, whether the units are consistent). representor : func A function that returns a string representation of a valid preference value that can be passed to `eval`. By default, uses `repr` which works in almost all cases. """ def __init__(self, default, docs, validator=None, representor=repr): self.representor = representor if validator is None: validator = DefaultValidator(default) self.validator = validator self.default = default self.docs = docs class BrianGlobalPreferences(MutableMapping): """ Class of the ``prefs`` object. Used for getting/setting/validating/registering preference values. All preferences must be registered via `register_preferences`. To get or set a preference, you can either use a dictionary-based or an attribute-based interface:: prefs['core.default_float_dtype'] = float32 prefs.core.default_float_dtype = float32 Preferences can be read from files, see `load_preferences` and `read_preference_file`. Note that `load_preferences` is called automatically when Brian has finished importing. """ def __init__(self): self.prefs = {} self.backup_prefs = {} self.prefs_unvalidated = {} self.pref_register = {} self.eval_namespace = {} exec( deindent( """ from numpy import * from brian2.units import * from brian2.units.stdunits import * """ ), self.eval_namespace, ) def __getitem__(self, item): if item in self.pref_register: # This asks for a category, not a single preference return BrianGlobalPreferencesView(item, self) return self.prefs[item] def __len__(self): return len(self.prefs) def __iter__(self): return iter(self.prefs) def __contains__(self, item): return item in self.prefs def __setitem__(self, name, value): basename, endname = parse_preference_name(name) if basename not in self.pref_register: raise PreferenceError( "Preference category " + basename + " is unregistered. Spelling error?" ) prefdefs, _ = self.pref_register[basename] if endname in prefdefs: # do validation pref = prefdefs[endname] if not pref.validator(value): raise PreferenceError( f"Value '{value}' for preference '{name}' is invalid." ) self.prefs[name] = value if name in self.prefs_unvalidated: del self.prefs_unvalidated[name] else: raise PreferenceError( f"Preference '{name}' is unregistered. Spelling error?" ) def __delitem__(self, item): raise PreferenceError("Preferences cannot be deleted.") def __getattr__(self, name): if name in self.__dict__ or name.startswith("__"): return MutableMapping.__getattr__(self, name) # This function might get called from BrianGlobalPreferencesView with # a prefixed name -- therefore the name can contain dots! if name in self.pref_register: # This asks for a category, not a single preference return BrianGlobalPreferencesView(name, self) basename, _ = parse_preference_name(name) if len(basename) and basename not in self.pref_register: raise AssertionError( f"__getattr__ received basename '{basename}' which is " "unregistered. This should never happen!" ) return self[name] def __setattr__(self, name, value): # Do not allow to set a category name to something else if "pref_register" in self.__dict__ and name in self.pref_register: raise PreferenceError("Cannot set a preference category.") else: MutableMapping.__setattr__(self, name, value) def __delattr__(self, name): if "pref_register" in self.__dict__ and name in self.pref_register: raise PreferenceError("Cannot delete a preference category.") else: MutableMapping.__delattr__(self, name) toplevel_categories = property( fget=lambda self: [ category for category in self.pref_register if "." not in category ], doc="The toplevel preference categories", ) def _get_docstring(self): """ Document the toplevel categories, used as a docstring for the object. """ s = "Preference categories:\n\n" for category in self.toplevel_categories: s += "** %s **\n" % category _, category_doc = self.pref_register[category] s += " " + category_doc + "\n\n" return s def __dir__(self): res = dir(type(self)) + list(self.__dict__) categories = self.toplevel_categories res.extend(categories) return res def eval_pref(self, value): """ Evaluate a string preference in the units namespace """ return eval(value, self.eval_namespace) def _set_preference(self, name, value): """ Try to set the preference and allow for unregistered base names. This method is used internally when reading preferences from the file because the preferences are potentially defined in packages that are not imported yet. Unvalidated preferences are safed and will be validated as soon as the category is registered. `Network.run` will also check for unvalidated preferences. """ basename, _ = parse_preference_name(name) if basename not in self.pref_register: self.prefs_unvalidated[name] = value else: # go via the standard __setitem__ method self[name] = value def _backup(self): """ Store a backup copy of the preferences to restore with `_restore`. """ self.backup_prefs.update(**self.prefs) def _restore(self): """ Restore a copy of the values of the preferences backed up with `_backup`. """ self.prefs.update(**self.backup_prefs) def _get_one_documentation(self, basename, link_targets): """ Document a single category of preferences. """ s = "" if basename not in self.pref_register: raise ValueError( f"No preferences under the name '{basename}' are registered" ) prefdefs, basedoc = self.pref_register[basename] s += deindent(basedoc, docstring=True).strip() + "\n\n" for name in sorted(prefdefs.keys()): pref = prefdefs[name] name = basename + "." + name linkname = name.replace("_", "-").replace(".", "-") if link_targets: # Make a link target s += f".. _brian-pref-{linkname}:\n\n" s += f"``{name}`` = ``{pref.representor(pref.default)}``\n" s += indent(deindent(pref.docs, docstring=True)) s += "\n\n" return s def get_documentation(self, basename=None, link_targets=True): """ Generates a string documenting all preferences with the given `basename`. If no `basename` is given, all preferences are documented. """ s = "" if basename is None: basenames = sorted( [tuple(basename.split(".")) for basename in self.pref_register] ) for basename in basenames: lev = len(basename) basename = ".".join(basename) if lev == 1: s += basename + "\n" + '"' * len(basename) + "\n\n" else: s += "**" + basename + "**\n\n" s += self._get_one_documentation(basename, link_targets) # for basename in self.pref_register: # s += '**' + basename + '**\n\n' # s += basename+'\n'+'"'*len(basename)+'\n\n' # s += self._get_one_documentation(basename, link_targets) else: s += self._get_one_documentation(basename, link_targets) return s def _as_pref_file(self, valuefunc): """ Helper function used to generate the preference file for the default or current preference values. """ s = "" for basename, (prefdefs, basedoc) in self.pref_register.items(): s += "#" + "-" * 79 + "\n" s += ( "\n".join( [ "# " + line for line in deindent(basedoc, docstring=True) .strip() .split("\n") ] ) + "\n" ) s += "#" + "-" * 79 + "\n\n" s += "[" + basename + "]\n\n" for name in sorted(prefdefs.keys()): pref = prefdefs[name] s += ( "\n".join( [ "# " + line for line in deindent(pref.docs, docstring=True) .strip() .split("\n") ] ) + "\n\n" ) s += ( name + " = " + pref.representor(valuefunc(pref, basename + "." + name)) + "\n\n" ) return s def _get_defaults_as_file(self): return self._as_pref_file(lambda pref, fullname: pref.default) defaults_as_file = property( fget=_get_defaults_as_file, doc="Get a Brian preference doc file format string for the default preferences", ) def _get_as_file(self): return self._as_pref_file(lambda pref, fullname: self[fullname]) as_file = property( fget=_get_as_file, doc="Get a Brian preference doc file format string for the current preferences", ) def read_preference_file(self, file): """ Reads a Brian preferences file The file format for Brian preferences is a plain text file of the form:: a.b.c = 1 # Comment line [a] b.d = 2 [a.b] e = 3 Blank and comment lines are ignored, all others should be of one of the following two forms:: key = value [section] `eval` is called on the values, so strings should be written as, e.g. ``'3'`` rather than ``3``. The eval is called with all unit names available. Within a section, the section name is prepended to the key. So in the above example, it would give the following unvalidated dictionary:: {'a.b.c': 1, 'a.b.d': 2, 'a.b.e': 3, } Parameters ---------- file : file, str The file object or filename of the preference file. """ if isinstance(file, str): filename = file file = open(file) else: filename = repr(file) lines = file.readlines() file.close() # remove empty lines lines = [line.strip() for line in lines] lines = [line for line in lines if line] # Remove comments lines = [line for line in lines if not line.startswith("#")] bases = [] # start with no base for line in lines: # Match section names, which are used as a prefix for subsequent entries m = re.match(r"\[([^\]]*)\]", line) if m: bases = m.group(1).strip().split(".") continue # Match entries m = re.match("(.*?)=(.*)", line) if m: extname = m.group(1).strip() value = m.group(2).strip() keyname = ".".join(bases + extname.split(".")) self._set_preference(keyname, self.eval_pref(value)) continue # Otherwise raise a parsing error raise PreferenceError("Parsing error in preference file " + filename) def load_preferences(self): """ Load all the preference files, but do not validate them. Preference files are read in the following order: 1. ``~/.brian/user_preferences`` from the user's home directory 2. ``./brian_preferences`` from the current directory Files that are missing are ignored. Preferences read at each step override preferences from previous steps. See Also -------- read_preference_file """ user_dir = os.path.join(os.path.expanduser("~"), ".brian") user_prefs = os.path.join(user_dir, "user_preferences") cur_prefs = "brian_preferences" files = [user_prefs, cur_prefs] for file in files: try: self.read_preference_file(file) except OSError: pass # The "default_preferences" file is no longer used, but we raise a # warning if it is present (note that we do this after reading the # preference files, since they can affect the preferences of the logger # itself) curdir, _ = os.path.split(__file__) basedir = os.path.normpath(os.path.join(curdir, "..")) default_prefs = os.path.join(basedir, "default_preferences") if os.path.exists(default_prefs): from brian2.utils.logger import get_logger logger = get_logger(__name__) logger.warn( "Brian no longer loads preferences from the " f"'default_preferences' file (in '{basedir}'). Use a " "'user_preferences' file in " f"'{user_dir}', " "or a 'brian_preferences' file in the current " "directory instead.", name_suffix="deprecated_default_preferences", once=True, ) def reset_to_defaults(self): """ Resets the parameters to their default values. """ self.read_preference_file(StringIO(self.defaults_as_file)) def register_preferences(self, prefbasename, prefbasedoc, **prefs): """ Registers a set of preference names, docs and validation functions. Parameters ---------- prefbasename : str The base name of the preference. prefbasedoc : str Documentation for this base name **prefs : dict of (name, `BrianPreference`) pairs The preference names to be defined. The full preference name will be ``prefbasename.name``, and the `BrianPreference` value is used to define the default value, docs, and validation function. Raises ------ PreferenceError If the base name is already registered. See Also -------- BrianPreference """ if prefbasename in self.pref_register: # During the initial import phase the same base category may be # created twice, ignore that previous = self.pref_register[prefbasename] if not (len(previous[0]) == 0 and previous[1] == prefbasedoc): raise PreferenceError( "Base name " + prefbasename + " already registered." ) # Check that the new category does not clash with a preference name of # the parent category. For example, if a category "a" with the # preference "b" is already registered, do not allow to register a # preference category "a.b" basename, category_name = parse_preference_name(prefbasename) if len(basename) and basename in self.pref_register: parent_preferences, _ = self.pref_register[basename] if category_name in parent_preferences: raise PreferenceError( f"Cannot register category '{prefbasename}', " f"parent category '{basename}' already has a " f"preference named '{category_name}'." ) self.pref_register[prefbasename] = (prefs, prefbasedoc) for k, v in prefs.items(): fullname = prefbasename + "." + k # The converse of the above check: Check that a preference name # does not clash with a category if fullname in self.pref_register: raise PreferenceError( f"Cannot register '{fullname}' as a preference, " "it is already registered as a " "preference category." ) check_preference_name(k) self.prefs_unvalidated[fullname] = v.default self.do_validation() # Update the docstring (a new toplevel category might have been added) self.__doc__ = self._get_docstring() def do_validation(self): """ Validates preferences that have not yet been validated. """ for name, value in dict(self.prefs_unvalidated).items(): self[name] = value def check_all_validated(self): """ Checks that all preferences that have been set have been validated. Logs a warning if not. Should be called by `Network.run` or other key Brian functions. """ if len(self.prefs_unvalidated): from brian2.utils.logger import get_logger logger = get_logger(__name__) logger.warn( "The following preferences values have been set but " "are not registered preferences:\n%s\nThis is usually " "because of a spelling mistake or missing library " "import." % ", ".join(self.prefs_unvalidated), once=True, ) def __repr__(self): description = "<{classname} with top-level categories: {categories}>" categories = ", ".join( ['"%s"' % category for category in self.toplevel_categories] ) return description.format( classname=self.__class__.__name__, categories=categories ) class BrianGlobalPreferencesView(MutableMapping): """ A class allowing for accessing preferences in a subcategory. It forwards requests to `BrianGlobalPreferences` and provides documentation and autocompletion support for all preferences in the given category. This object is used to allow accessing preferences via attributes of the `prefs` object. Parameters ---------- basename : str The name of the preference category. Has to correspond to a key in `BrianGlobalPreferences.pref_register`. all_prefs : `BrianGlobalPreferences` A reference to the main object storing the preferences. """ def __init__(self, basename, all_prefs): self._basename = basename self._all_prefs = all_prefs self._subcategories = [ key for key in all_prefs.pref_register if key.startswith(basename + ".") ] self._preferences = list(all_prefs.pref_register[basename][0].keys()) self.__doc__ = all_prefs.get_documentation( basename=basename, link_targets=False ) _sub_preferences = property( lambda self: [ pref[len(self._basename + ".") :] for pref in self._all_prefs if pref.startswith(self._basename + ".") ], doc="All preferences in this category and its subcategories", ) def __getitem__(self, item): return self._all_prefs[self._basename + "." + item] def __setitem__(self, item, value): self._all_prefs[self._basename + "." + item] = value def __delitem__(self, item): raise PreferenceError("Preferences cannot be deleted.") def __len__(self): return len(self._sub_preferences) def __iter__(self): return iter(self._sub_preferences) def __contains__(self, item): return item in self._sub_preferences def __getattr__(self, name): return getattr(self._all_prefs, self._basename + "." + name) def __setattr__(self, name, value): # Names starting with an underscore are not preferences but normal # instance attributes if name.startswith("_"): MutableMapping.__setattr__(self, name, value) else: self._all_prefs[self._basename + "." + name] = value def __delattr__(self, name): # Names starting with an underscore are not preferences but normal # instance attributes if name.startswith("_"): MutableMapping.__delattr__(self, name) else: del self._all_prefs[self._basename + "." + name] def __dir__(self): res = dir(type(self)) + list(self.__dict__) res.extend(self._preferences) res.extend( [category[len(self._basename + ".") :] for category in self._subcategories] ) return res def __repr__(self): description = '<{classname} for preference category "{category}">' return description.format( classname=self.__class__.__name__, category=self._basename ) # : Object storing Brian's preferences prefs = BrianGlobalPreferences() # Simple class to give a useful error message when using `brian_prefs` class ErrorRaiser: def __getattr__(self, item): raise AttributeError( "The global preferences object has been renamed " "from 'brian_prefs' to 'prefs'" ) def __getitem__(self, item): raise AttributeError( "The global preferences object has been renamed " "from 'brian_prefs' to 'prefs'" ) brian_prefs = ErrorRaiser() brian2-2.5.4/brian2/core/spikesource.py000066400000000000000000000017471445201106100177150ustar00rootroot00000000000000__all__ = ["SpikeSource"] class SpikeSource: """ A source of spikes. An object that can be used as a source of spikes for objects such as `SpikeMonitor`, `Synapses`, etc. The defining properties of `SpikeSource` are that it should have: * A length that can be extracted with ``len(obj)``, where the maximum spike index possible is ``len(obj)-1``. * An attribute `spikes`, an array of ints each from 0 to ``len(obj)-1`` with no repeats (but possibly not in sorted order). This should be updated each time step. * A `clock` attribute, this will be used as the default clock for objects with this as a source. .. attribute:: spikes An array of ints, each from 0 to ``len(obj)-1`` with no repeats (but possibly not in sorted order). Updated each time step. .. attribute:: clock The clock on which the spikes will be updated. """ # No implementation, just used for documentation purposes. pass brian2-2.5.4/brian2/core/tracking.py000066400000000000000000000043261445201106100171570ustar00rootroot00000000000000from collections import defaultdict from weakref import ref __all__ = ["Trackable"] class InstanceTrackerSet(set): """ A `set` of `weakref.ref` to all existing objects of a certain class. Should not normally be directly used. """ def add(self, value): """ Adds a `weakref.ref` to the ``value`` """ # The second argument to ref is a callback that is called with the # ref as argument when the object has been deleted, here we just # remove it from the set in that case wr = ref(value, self.remove) set.add(self, wr) def remove(self, value): """ Removes the ``value`` (which should be a weakref) if it is in the set Sometimes the value will have been removed from the set by `clear`, so we ignore `KeyError` in this case. """ try: set.remove(self, value) except KeyError: pass class InstanceFollower: """ Keep track of all instances of classes derived from `Trackable` The variable ``__instancesets__`` is a dictionary with keys which are class objects, and values which are `InstanceTrackerSet`, so ``__instanceset__[cls]`` is a set tracking all of the instances of class ``cls`` (or a subclass). """ instance_sets = defaultdict(InstanceTrackerSet) def add(self, value): for ( cls ) in ( value.__class__.__mro__ ): # MRO is the Method Resolution Order which contains all the superclasses of a class self.instance_sets[cls].add(value) def get(self, cls): return self.instance_sets[cls] class Trackable: """ Classes derived from this will have their instances tracked. The `classmethod` `__instances__()` will return an `InstanceTrackerSet` of the instances of that class, and its subclasses. """ __instancefollower__ = ( InstanceFollower() ) # static property of all objects of class derived from Trackable def __new__(typ, *args, **kw): obj = object.__new__(typ) obj.__instancefollower__.add(obj) return obj @classmethod def __instances__(cls): return cls.__instancefollower__.get(cls) brian2-2.5.4/brian2/core/variables.py000066400000000000000000002276321445201106100173340ustar00rootroot00000000000000""" Classes used to specify the type of a function, variable or common sub-expression. """ import collections import functools import numbers from collections.abc import Mapping import numpy as np from brian2.units.fundamentalunits import ( DIMENSIONLESS, Dimension, Quantity, fail_for_dimension_mismatch, get_unit, get_unit_for_display, ) from brian2.utils.caching import CacheKey from brian2.utils.logger import get_logger from brian2.utils.stringtools import get_identifiers, word_substitute from .base import device_override, weakproxy_with_fallback from .preferences import prefs __all__ = [ "Variable", "Constant", "ArrayVariable", "DynamicArrayVariable", "Subexpression", "AuxiliaryVariable", "VariableView", "Variables", "LinkedVariable", "linked_var", ] logger = get_logger(__name__) def get_dtype(obj): """ Helper function to return the `numpy.dtype` of an arbitrary object. Parameters ---------- obj : object Any object (but typically some kind of number or array). Returns ------- dtype : `numpy.dtype` The type of the given object. """ if hasattr(obj, "dtype"): return obj.dtype else: return np.obj2sctype(type(obj)) def get_dtype_str(val): """ Returns canonical string representation of the dtype of a value or dtype Returns ------- dtype_str : str The numpy dtype name """ if isinstance(val, np.dtype): return val.name if isinstance(val, type): return get_dtype_str(val()) is_bool = val is True or val is False or val is np.True_ or val is np.False_ if is_bool: return "bool" if hasattr(val, "dtype"): return get_dtype_str(val.dtype) if isinstance(val, numbers.Number): return get_dtype_str(np.array(val).dtype) return f"unknown[{str(val)}, {val.__class__.__name__}]" def variables_by_owner(variables, owner): owner_name = getattr(owner, "name", None) return { varname: var for varname, var in variables.items() if getattr(var.owner, "name", None) is owner_name } class Variable(CacheKey): r""" An object providing information about model variables (including implicit variables such as ``t`` or ``xi``). This class should never be instantiated outside of testing code, use one of its subclasses instead. Parameters ---------- name : 'str' The name of the variable. Note that this refers to the *original* name in the owning group. The same variable may be known under other names in other groups (e.g. the variable ``v`` of a `NeuronGroup` is known as ``v_post`` in a `Synapse` connecting to the group). dimensions : `Dimension`, optional The physical dimensions of the variable. owner : `Nameable`, optional The object that "owns" this variable, e.g. the `NeuronGroup` or `Synapses` object that declares the variable in its model equations. Defaults to ``None`` (the value used for `Variable` objects without an owner, e.g. external `Constant`\ s). dtype : `dtype`, optional The dtype used for storing the variable. Defaults to the preference `core.default_scalar.dtype`. scalar : bool, optional Whether the variable is a scalar value (``True``) or vector-valued, e.g. defined for every neuron (``False``). Defaults to ``False``. constant: bool, optional Whether the value of this variable can change during a run. Defaults to ``False``. read_only : bool, optional Whether this is a read-only variable, i.e. a variable that is set internally and cannot be changed by the user (this is used for example for the variable ``N``, the number of neurons in a group). Defaults to ``False``. array : bool, optional Whether this variable is an array. Allows for simpler check than testing ``isinstance(var, ArrayVariable)``. Defaults to ``False``. """ _cache_irrelevant_attributes = {"owner"} def __init__( self, name, dimensions=DIMENSIONLESS, owner=None, dtype=None, scalar=False, constant=False, read_only=False, dynamic=False, array=False, ): assert isinstance(dimensions, Dimension) #: The variable's dimensions. self.dim = dimensions #: The variable's name. self.name = name #: The `Group` to which this variable belongs. self.owner = weakproxy_with_fallback(owner) if owner is not None else None #: The dtype used for storing the variable. self.dtype = dtype if dtype is None: self.dtype = prefs.core.default_float_dtype if self.is_boolean: if dimensions is not DIMENSIONLESS: raise ValueError("Boolean variables can only be dimensionless") #: Whether the variable is a scalar self.scalar = scalar #: Whether the variable is constant during a run self.constant = constant #: Whether the variable is read-only self.read_only = read_only #: Whether the variable is dynamically sized (only for non-scalars) self.dynamic = dynamic #: Whether the variable is an array self.array = array @property def is_boolean(self): return np.issubdtype(self.dtype, np.bool_) @property def is_integer(self): return np.issubdtype(self.dtype, np.signedinteger) @property def dtype_str(self): """ String representation of the numpy dtype """ return get_dtype_str(self) @property def unit(self): """ The `Unit` of this variable """ return get_unit(self.dim) def get_value(self): """ Return the value associated with the variable (without units). This is the way variables are accessed in generated code. """ raise TypeError(f"Cannot get value for variable {self}") def set_value(self, value): """ Set the value associated with the variable. """ raise TypeError(f"Cannot set value for variable {self}") def get_value_with_unit(self): """ Return the value associated with the variable (with units). """ return Quantity(self.get_value(), self.dim) def get_addressable_value(self, name, group): """ Get the value (without units) of this variable in a form that can be indexed in the context of a group. For example, if a postsynaptic variable ``x`` is accessed in a synapse ``S`` as ``S.x_post``, the synaptic indexing scheme can be used. Parameters ---------- name : str The name of the variable group : `Group` The group providing the context for the indexing. Note that this `group` is not necessarily the same as `Variable.owner`: a variable owned by a `NeuronGroup` can be indexed in a different way if accessed via a `Synapses` object. Returns ------- variable : object The variable in an indexable form (without units). """ return self.get_value() def get_addressable_value_with_unit(self, name, group): """ Get the value (with units) of this variable in a form that can be indexed in the context of a group. For example, if a postsynaptic variable ``x`` is accessed in a synapse ``S`` as ``S.x_post``, the synaptic indexing scheme can be used. Parameters ---------- name : str The name of the variable group : `Group` The group providing the context for the indexing. Note that this `group` is not necessarily the same as `Variable.owner`: a variable owned by a `NeuronGroup` can be indexed in a different way if accessed via a `Synapses` object. Returns ------- variable : object The variable in an indexable form (with units). """ return self.get_value_with_unit() def get_len(self): """ Get the length of the value associated with the variable or ``0`` for a scalar variable. """ if self.scalar: return 0 else: return len(self.get_value()) def __len__(self): return self.get_len() def __repr__(self): description = ( "<{classname}(dimensions={dimensions}, " " dtype={dtype}, scalar={scalar}, constant={constant}," " read_only={read_only})>" ) return description.format( classname=self.__class__.__name__, dimensions=repr(self.dim), dtype=getattr(self.dtype, "__name__", repr(self.dtype)), scalar=repr(self.scalar), constant=repr(self.constant), read_only=repr(self.read_only), ) # ------------------------------------------------------------------------------ # Concrete classes derived from `Variable` -- these are the only ones ever # instantiated. # ------------------------------------------------------------------------------ class Constant(Variable): """ A scalar constant (e.g. the number of neurons ``N``). Information such as the dtype or whether this variable is a boolean are directly derived from the `value`. Most of the time `Variables.add_constant` should be used instead of instantiating this class directly. Parameters ---------- name : str The name of the variable dimensions : `Dimension`, optional The physical dimensions of the variable. Note that the variable itself (as referenced by value) should never have units attached. value: reference to the variable value The value of the constant. owner : `Nameable`, optional The object that "owns" this variable, for constants that belong to a specific group, e.g. the ``N`` constant for a `NeuronGroup`. External constants will have ``None`` (the default value). """ def __init__(self, name, value, dimensions=DIMENSIONLESS, owner=None): # Determine the type of the value is_bool = ( value is True or value is False or value is np.True_ or value is np.False_ ) if is_bool: dtype = bool else: dtype = get_dtype(value) # Use standard Python types if possible for numpy scalars if getattr(value, "shape", None) == () and hasattr(value, "dtype"): numpy_type = value.dtype if np.can_cast(numpy_type, np.int_): value = int(value) elif np.can_cast(numpy_type, np.float_): value = float(value) elif np.can_cast(numpy_type, np.complex_): value = complex(value) elif value is np.True_: value = True elif value is np.False_: value = False #: The constant's value self.value = value super().__init__( dimensions=dimensions, name=name, owner=owner, dtype=dtype, scalar=True, constant=True, read_only=True, ) def get_value(self): return self.value def item(self): return self.value class AuxiliaryVariable(Variable): """ Variable description for an auxiliary variable (most likely one that is added automatically to abstract code, e.g. ``_cond`` for a threshold condition), specifying its type and unit for code generation. Most of the time `Variables.add_auxiliary_variable` should be used instead of instantiating this class directly. Parameters ---------- name : str The name of the variable dimensions : `Dimension`, optional The physical dimensions of the variable. dtype : `dtype`, optional The dtype used for storing the variable. If none is given, defaults to `core.default_float_dtype`. scalar : bool, optional Whether the variable is a scalar value (``True``) or vector-valued, e.g. defined for every neuron (``False``). Defaults to ``False``. """ def __init__(self, name, dimensions=DIMENSIONLESS, dtype=None, scalar=False): super().__init__(dimensions=dimensions, name=name, dtype=dtype, scalar=scalar) def get_value(self): raise TypeError( f"Cannot get the value for an auxiliary variable ({self.name})." ) class ArrayVariable(Variable): """ An object providing information about a model variable stored in an array (for example, all state variables). Most of the time `Variables.add_array` should be used instead of instantiating this class directly. Parameters ---------- name : 'str' The name of the variable. Note that this refers to the *original* name in the owning group. The same variable may be known under other names in other groups (e.g. the variable ``v`` of a `NeuronGroup` is known as ``v_post`` in a `Synapse` connecting to the group). dimensions : `Dimension`, optional The physical dimensions of the variable owner : `Nameable` The object that "owns" this variable, e.g. the `NeuronGroup` or `Synapses` object that declares the variable in its model equations. size : int The size of the array device : `Device` The device responsible for the memory access. dtype : `dtype`, optional The dtype used for storing the variable. If none is given, defaults to `core.default_float_dtype`. constant : bool, optional Whether the variable's value is constant during a run. Defaults to ``False``. scalar : bool, optional Whether this array is a 1-element array that should be treated like a scalar (e.g. for a single delay value across synapses). Defaults to ``False``. read_only : bool, optional Whether this is a read-only variable, i.e. a variable that is set internally and cannot be changed by the user. Defaults to ``False``. unique : bool, optional Whether the values in this array are all unique. This information is only important for variables used as indices and does not have to reflect the actual contents of the array but only the possibility of non-uniqueness (e.g. synaptic indices are always unique but the corresponding pre- and post-synaptic indices are not). Defaults to ``False``. """ def __init__( self, name, owner, size, device, dimensions=DIMENSIONLESS, dtype=None, constant=False, scalar=False, read_only=False, dynamic=False, unique=False, ): super().__init__( dimensions=dimensions, name=name, owner=owner, dtype=dtype, scalar=scalar, constant=constant, read_only=read_only, dynamic=dynamic, array=True, ) #: Wether all values in this arrays are necessarily unique (only #: relevant for index variables). self.unique = unique #: The `Device` responsible for memory access. self.device = device #: The size of this variable. self.size = size if scalar and size != 1: raise ValueError(f"Scalar variables need to have size 1, not size {size}.") #: Another variable, on which the write is conditioned (e.g. a variable #: denoting the absence of refractoriness) self.conditional_write = None def set_conditional_write(self, var): if not var.is_boolean: raise TypeError( "A variable can only be conditionally writeable " f"depending on a boolean variable, '{var.name}' is not " "boolean." ) self.conditional_write = var def get_value(self): return self.device.get_value(self) def item(self): if self.size == 1: return self.get_value().item() else: raise ValueError("can only convert an array of size 1 to a Python scalar") def set_value(self, value): self.device.fill_with_array(self, value) def get_len(self): return self.size def get_addressable_value(self, name, group): return VariableView(name=name, variable=self, group=group, dimensions=None) def get_addressable_value_with_unit(self, name, group): return VariableView(name=name, variable=self, group=group, dimensions=self.dim) class DynamicArrayVariable(ArrayVariable): """ An object providing information about a model variable stored in a dynamic array (used in `Synapses`). Most of the time `Variables.add_dynamic_array` should be used instead of instantiating this class directly. Parameters ---------- name : 'str' The name of the variable. Note that this refers to the *original* name in the owning group. The same variable may be known under other names in other groups (e.g. the variable ``v`` of a `NeuronGroup` is known as ``v_post`` in a `Synapse` connecting to the group). dimensions : `Dimension`, optional The physical dimensions of the variable. owner : `Nameable` The object that "owns" this variable, e.g. the `NeuronGroup` or `Synapses` object that declares the variable in its model equations. size : int or tuple of int The (initial) size of the variable. device : `Device` The device responsible for the memory access. dtype : `dtype`, optional The dtype used for storing the variable. If none is given, defaults to `core.default_float_dtype`. constant : bool, optional Whether the variable's value is constant during a run. Defaults to ``False``. needs_reference_update : bool, optional Whether the code objects need a new reference to the underlying data at every time step. This should be set if the size of the array can be changed by other code objects. Defaults to ``False``. scalar : bool, optional Whether this array is a 1-element array that should be treated like a scalar (e.g. for a single delay value across synapses). Defaults to ``False``. read_only : bool, optional Whether this is a read-only variable, i.e. a variable that is set internally and cannot be changed by the user. Defaults to ``False``. unique : bool, optional Whether the values in this array are all unique. This information is only important for variables used as indices and does not have to reflect the actual contents of the array but only the possibility of non-uniqueness (e.g. synaptic indices are always unique but the corresponding pre- and post-synaptic indices are not). Defaults to ``False``. """ # The size of a dynamic variable can of course change and changes in # size should not invalidate the cache _cache_irrelevant_attributes = ArrayVariable._cache_irrelevant_attributes | {"size"} def __init__( self, name, owner, size, device, dimensions=DIMENSIONLESS, dtype=None, constant=False, needs_reference_update=False, resize_along_first=False, scalar=False, read_only=False, unique=False, ): if isinstance(size, int): ndim = 1 else: ndim = len(size) #: The number of dimensions self.ndim = ndim if constant and needs_reference_update: raise ValueError("A variable cannot be constant and need reference updates") #: Whether this variable needs an update of the reference to the #: underlying data whenever it is passed to a code object self.needs_reference_update = needs_reference_update #: Whether this array will be only resized along the first dimension self.resize_along_first = resize_along_first super().__init__( dimensions=dimensions, owner=owner, name=name, size=size, device=device, constant=constant, dtype=dtype, scalar=scalar, dynamic=True, read_only=read_only, unique=unique, ) @property def dimensions(self): logger.warn( "The DynamicArrayVariable.dimensions attribute is " "deprecated, use .ndim instead", "deprecated_dimensions", once=True, ) return self.ndim def resize(self, new_size): """ Resize the dynamic array. Calls `self.device.resize` to do the actual resizing. Parameters ---------- new_size : int or tuple of int The new size. """ if self.resize_along_first: self.device.resize_along_first(self, new_size) else: self.device.resize(self, new_size) self.size = new_size class Subexpression(Variable): """ An object providing information about a named subexpression in a model. Most of the time `Variables.add_subexpression` should be used instead of instantiating this class directly. Parameters ---------- name : str The name of the subexpression. dimensions : `Dimension`, optional The physical dimensions of the subexpression. owner : `Group` The group to which the expression refers. expr : str The subexpression itself. device : `Device` The device responsible for the memory access. dtype : `dtype`, optional The dtype used for the expression. Defaults to `core.default_float_dtype`. scalar: bool, optional Whether this is an expression only referring to scalar variables. Defaults to ``False`` """ def __init__( self, name, owner, expr, device, dimensions=DIMENSIONLESS, dtype=None, scalar=False, ): super().__init__( dimensions=dimensions, owner=owner, name=name, dtype=dtype, scalar=scalar, constant=False, read_only=True, ) #: The `Device` responsible for memory access self.device = device #: The expression defining the subexpression self.expr = expr.strip() #: The identifiers used in the expression self.identifiers = get_identifiers(expr) def get_addressable_value(self, name, group): return VariableView( name=name, variable=self, group=group, dimensions=DIMENSIONLESS ) def get_addressable_value_with_unit(self, name, group): return VariableView(name=name, variable=self, group=group, dimensions=self.dim) def __contains__(self, var): return var in self.identifiers def __repr__(self): description = ( "<{classname}(name={name}, dimensions={dimensions}, dtype={dtype}, " "expr={expr}, owner=<{owner}>)>" ) return description.format( classname=self.__class__.__name__, name=repr(self.name), dimensions=repr(self.dim), dtype=repr(self.dtype), expr=repr(self.expr), owner=self.owner.name, ) # ------------------------------------------------------------------------------ # Classes providing views on variables and storing variables information # ------------------------------------------------------------------------------ class LinkedVariable: """ A simple helper class to make linking variables explicit. Users should use `linked_var` instead. Parameters ---------- group : `Group` The group through which the `variable` is accessed (not necessarily the same as ``variable.owner``. name : str The name of `variable` in `group` (not necessarily the same as ``variable.name``). variable : `Variable` The variable that should be linked. index : str or `ndarray`, optional An indexing array (or the name of a state variable), providing a mapping from the entries in the link source to the link target. """ def __init__(self, group, name, variable, index=None): self.group = group self.name = name self.variable = variable self.index = index def linked_var(group_or_variable, name=None, index=None): """ Represents a link target for setting a linked variable. Parameters ---------- group_or_variable : `NeuronGroup` or `VariableView` Either a reference to the target `NeuronGroup` (e.g. ``G``) or a direct reference to a `VariableView` object (e.g. ``G.v``). In case only the group is specified, `name` has to be specified as well. name : str, optional The name of the target variable, necessary if `group_or_variable` is a `NeuronGroup`. index : str or `ndarray`, optional An indexing array (or the name of a state variable), providing a mapping from the entries in the link source to the link target. Examples -------- >>> from brian2 import * >>> G1 = NeuronGroup(10, 'dv/dt = -v / (10*ms) : volt') >>> G2 = NeuronGroup(10, 'v : volt (linked)') >>> G2.v = linked_var(G1, 'v') >>> G2.v = linked_var(G1.v) # equivalent """ if isinstance(group_or_variable, VariableView): if name is not None: raise ValueError( "Cannot give a variable and a variable name at the same time." ) return LinkedVariable( group_or_variable.group, group_or_variable.name, group_or_variable.variable, index=index, ) elif name is None: raise ValueError("Need to provide a variable name") else: return LinkedVariable( group_or_variable, name, group_or_variable.variables[name], index=index ) class VariableView: """ A view on a variable that allows to treat it as an numpy array while allowing special indexing (e.g. with strings) in the context of a `Group`. Parameters ---------- name : str The name of the variable (not necessarily the same as ``variable.name``). variable : `Variable` The variable description. group : `Group` The group through which the variable is accessed (not necessarily the same as `variable.owner`). dimensions : `Dimension`, optional The physical dimensions to be used for the variable, should be `None` when a variable is accessed without units (e.g. when accessing ``G.var_``). """ def __init__(self, name, variable, group, dimensions=None): self.name = name self.variable = variable self.index_var_name = group.variables.indices[name] if self.index_var_name in ("_idx", "0"): self.index_var = self.index_var_name else: self.index_var = group.variables[self.index_var_name] if isinstance(variable, Subexpression): # For subexpressions, we *always* have to go via codegen to get # their value -- since we cannot do this without the group, we # hold a strong reference self.group = group else: # For state variable arrays, we can do most access without the full # group, using the indexing reference below. We therefore only keep # a weak reference to the group. self.group = weakproxy_with_fallback(group) self.group_name = group.name # We keep a strong reference to the `Indexing` object so that basic # indexing is still possible, even if the group no longer exists self.indexing = self.group._indices self.dim = dimensions @property def unit(self): """ The `Unit` of this variable """ return get_unit(self.dim) def get_item(self, item, level=0, namespace=None): """ Get the value of this variable. Called by `__getitem__`. Parameters ---------- item : slice, `ndarray` or string The index for the setting operation level : int, optional How much farther to go up in the stack to find the implicit namespace (if used, see `run_namespace`). namespace : dict-like, optional An additional namespace that is used for variable lookup (if not defined, the implicit namespace of local variables is used). """ from brian2.core.namespace import get_local_namespace # avoids circular import if isinstance(item, str): # Check whether the group still exists to give a more meaningful # error message if it does not try: self.group.name except ReferenceError: raise ReferenceError( "Cannot use string expressions, the " f"group '{self.group_name}', providing the " "context for the expression, no longer exists. " "Consider holding an explicit reference " "to it to keep it alive." ) if namespace is None: namespace = get_local_namespace(level=level + 1) values = self.get_with_expression(item, run_namespace=namespace) else: if isinstance(self.variable, Subexpression): if namespace is None: namespace = get_local_namespace(level=level + 1) values = self.get_subexpression_with_index_array( item, run_namespace=namespace ) else: values = self.get_with_index_array(item) if self.dim is DIMENSIONLESS or self.dim is None: return values else: return Quantity(values, self.dim) def __getitem__(self, item): return self.get_item(item, level=1) def set_item(self, item, value, level=0, namespace=None): """ Set this variable. This function is called by `__setitem__` but there is also a situation where it should be called directly: if the context for string-based expressions is higher up in the stack, this function allows to set the `level` argument accordingly. Parameters ---------- item : slice, `ndarray` or string The index for the setting operation value : `Quantity`, `ndarray` or number The value for the setting operation level : int, optional How much farther to go up in the stack to find the implicit namespace (if used, see `run_namespace`). namespace : dict-like, optional An additional namespace that is used for variable lookup (if not defined, the implicit namespace of local variables is used). """ from brian2.core.namespace import get_local_namespace # avoids circular import variable = self.variable if variable.read_only: raise TypeError(f"Variable {self.name} is read-only.") # Check whether the group allows writing to the variable (e.g. for # synaptic variables, writing is only allowed after a connect) try: self.group.check_variable_write(variable) except ReferenceError: # Ignore problems with weakly referenced groups that don't exist # anymore at this time (e.g. when doing neuron.axon.var = ...) pass # The second part is equivalent to item == slice(None) but formulating # it this way prevents a FutureWarning if one of the elements is a # numpy array if isinstance(item, slice) and ( item.start is None and item.stop is None and item.step is None ): item = "True" check_units = self.dim is not None if namespace is None: namespace = get_local_namespace(level=level + 1) # Both index and values are strings, use a single code object do deal # with this situation if isinstance(value, str) and isinstance(item, str): self.set_with_expression_conditional( item, value, check_units=check_units, run_namespace=namespace ) elif isinstance(item, str): try: if isinstance(value, str): raise TypeError # Will be dealt with below value = np.asanyarray(value).item() except (TypeError, ValueError): if item != "True": raise TypeError( "When setting a variable based on a string " "index, the value has to be a string or a " "scalar." ) if item == "True": # We do not want to go through code generation for runtime self.set_with_index_array(slice(None), value, check_units=check_units) else: self.set_with_expression_conditional( item, repr(value), check_units=check_units, run_namespace=namespace ) elif isinstance(value, str): self.set_with_expression( item, value, check_units=check_units, run_namespace=namespace ) else: # No string expressions involved self.set_with_index_array(item, value, check_units=check_units) def __setitem__(self, item, value): self.set_item(item, value, level=1) @device_override("variableview_set_with_expression") def set_with_expression(self, item, code, run_namespace, check_units=True): """ Sets a variable using a string expression. Is called by `VariableView.set_item` for statements such as ``S.var[:, :] = 'exp(-abs(i-j)/space_constant)*nS'`` Parameters ---------- item : `ndarray` The indices for the variable (in the context of this `group`). code : str The code that should be executed to set the variable values. Can contain references to indices, such as `i` or `j` run_namespace : dict-like, optional An additional namespace that is used for variable lookup (if not defined, the implicit namespace of local variables is used). check_units : bool, optional Whether to check the units of the expression. run_namespace : dict-like, optional An additional namespace that is used for variable lookup (if not defined, the implicit namespace of local variables is used). """ # Some fairly complicated code to raise a warning in ambiguous # situations, when indexing with a group. For example, in: # group.v[subgroup] = 'i' # the index 'i' is the index of 'group' ("absolute index") and not of # subgroup ("relative index") if hasattr(item, "variables") or ( isinstance(item, tuple) and any(hasattr(one_item, "variables") for one_item in item) ): # Determine the variables that are used in the expression from brian2.codegen.translation import get_identifiers_recursively identifiers = get_identifiers_recursively([code], self.group.variables) variables = self.group.resolve_all( identifiers, run_namespace, user_identifiers=set() ) if not isinstance(item, tuple): index_groups = [item] else: index_groups = item for varname, var in variables.items(): for index_group in index_groups: if not hasattr(index_group, "variables"): continue if ( varname in index_group.variables or var.name in index_group.variables ): indexed_var = index_group.variables.get( varname, index_group.variables.get(var.name) ) if indexed_var is not var: logger.warn( "The string expression used for setting " f"'{self.name}' refers to '{varname}' which " "might be ambiguous. It will be " "interpreted as referring to " f"'{varname}' in '{self.group.name}', not as " "a variable of a group used for " "indexing.", "ambiguous_string_expression", ) break # no need to warn more than once for a variable indices = np.atleast_1d(self.indexing(item)) abstract_code = f"{self.name} = {code}" variables = Variables(self.group) variables.add_array( "_group_idx", size=len(indices), dtype=np.int32, values=indices ) # TODO: Have an additional argument to avoid going through the index # array for situations where iterate_all could be used from brian2.codegen.codeobject import create_runner_codeobj from brian2.devices.device import get_device device = get_device() codeobj = create_runner_codeobj( self.group, abstract_code, "group_variable_set", additional_variables=variables, check_units=check_units, run_namespace=run_namespace, codeobj_class=device.code_object_class( fallback_pref="codegen.string_expression_target" ), ) codeobj() @device_override("variableview_set_with_expression_conditional") def set_with_expression_conditional( self, cond, code, run_namespace, check_units=True ): """ Sets a variable using a string expression and string condition. Is called by `VariableView.set_item` for statements such as ``S.var['i!=j'] = 'exp(-abs(i-j)/space_constant)*nS'`` Parameters ---------- cond : str The string condition for which the variables should be set. code : str The code that should be executed to set the variable values. run_namespace : dict-like, optional An additional namespace that is used for variable lookup (if not defined, the implicit namespace of local variables is used). check_units : bool, optional Whether to check the units of the expression. """ variable = self.variable if variable.scalar and cond != "True": raise IndexError( f"Cannot conditionally set the scalar variable '{self.name}'." ) abstract_code_cond = f"_cond = {cond}" abstract_code = f"{self.name} = {code}" variables = Variables(None) variables.add_auxiliary_variable("_cond", dtype=bool) from brian2.codegen.codeobject import create_runner_codeobj # TODO: Have an additional argument to avoid going through the index # array for situations where iterate_all could be used from brian2.devices.device import get_device device = get_device() codeobj = create_runner_codeobj( self.group, {"condition": abstract_code_cond, "statement": abstract_code}, "group_variable_set_conditional", additional_variables=variables, check_units=check_units, run_namespace=run_namespace, codeobj_class=device.code_object_class( fallback_pref="codegen.string_expression_target" ), ) codeobj() @device_override("variableview_get_with_expression") def get_with_expression(self, code, run_namespace): """ Gets a variable using a string expression. Is called by `VariableView.get_item` for statements such as ``print(G.v['g_syn > 0'])``. Parameters ---------- code : str An expression that states a condition for elements that should be selected. Can contain references to indices, such as ``i`` or ``j`` and to state variables. For example: ``'i>3 and v>0*mV'``. run_namespace : dict-like An additional namespace that is used for variable lookup (either an explicitly defined namespace or one taken from the local context). """ variable = self.variable if variable.scalar: raise IndexError( f"Cannot access the variable '{self.name}' with a " "string expression, it is a scalar variable." ) # Add the recorded variable under a known name to the variables # dictionary. Important to deal correctly with # the type of the variable in C++ variables = Variables(None) variables.add_auxiliary_variable( "_variable", dimensions=variable.dim, dtype=variable.dtype, scalar=variable.scalar, ) variables.add_auxiliary_variable("_cond", dtype=bool) abstract_code = f"_variable = {self.name}\n" abstract_code += f"_cond = {code}" from brian2.codegen.codeobject import create_runner_codeobj from brian2.devices.device import get_device device = get_device() codeobj = create_runner_codeobj( self.group, abstract_code, "group_variable_get_conditional", additional_variables=variables, run_namespace=run_namespace, codeobj_class=device.code_object_class( fallback_pref="codegen.string_expression_target" ), ) return codeobj() @device_override("variableview_get_with_index_array") def get_with_index_array(self, item): variable = self.variable if variable.scalar: if not (isinstance(item, slice) and item == slice(None)): raise IndexError( f"Illegal index for variable '{self.name}', it is a " "scalar variable." ) indices = 0 elif ( isinstance(item, slice) and item == slice(None) and self.index_var == "_idx" ): indices = slice(None) # Quick fix for matplotlib calling 1-d variables as var[:, np.newaxis] # The test is a bit verbose, but we need to avoid comparisons that raise errors # (e.g. comparing an array to slice(None)) elif ( isinstance(item, tuple) and len(item) == 2 and isinstance(item[0], slice) and item[0] == slice(None) and item[1] is None ): if self.index_var == "_idx": return variable.get_value()[item] else: return variable.get_value()[self.index_var.get_value()][item] else: indices = self.indexing(item, self.index_var) return variable.get_value()[indices] @device_override("variableview_get_subexpression_with_index_array") def get_subexpression_with_index_array(self, item, run_namespace): variable = self.variable if variable.scalar: if not (isinstance(item, slice) and item == slice(None)): raise IndexError( f"Illegal index for variable '{self.name}', it is a " "scalar variable." ) indices = np.array(0) else: indices = self.indexing(item, self.index_var) # For "normal" variables, we can directly access the underlying data # and use the usual slicing syntax. For subexpressions, however, we # have to evaluate code for the given indices variables = Variables(None, default_index="_group_index") variables.add_auxiliary_variable( "_variable", dimensions=variable.dim, dtype=variable.dtype, scalar=variable.scalar, ) if indices.shape == (): single_index = True indices = np.array([indices]) else: single_index = False variables.add_array("_group_idx", size=len(indices), dtype=np.int32) variables["_group_idx"].set_value(indices) # Force the use of this variable as a replacement for the original # index variable using_orig_index = [ varname for varname, index in self.group.variables.indices.items() if index == self.index_var_name and index != "0" ] for varname in using_orig_index: variables.indices[varname] = "_idx" abstract_code = f"_variable = {self.name}\n" from brian2.codegen.codeobject import create_runner_codeobj from brian2.devices.device import get_device device = get_device() codeobj = create_runner_codeobj( self.group, abstract_code, "group_variable_get", # Setting the user code to an empty # string suppresses warnings if the # subexpression refers to variable # names that are also present in the # local namespace user_code="", needed_variables=["_group_idx"], additional_variables=variables, run_namespace=run_namespace, codeobj_class=device.code_object_class( fallback_pref="codegen.string_expression_target" ), ) result = codeobj() if single_index and not variable.scalar: return result[0] else: return result @device_override("variableview_set_with_index_array") def set_with_index_array(self, item, value, check_units): variable = self.variable if check_units: fail_for_dimension_mismatch( variable.dim, value, f"Incorrect unit for setting variable {self.name}" ) if variable.scalar: if not (isinstance(item, slice) and item == slice(None)): raise IndexError( "Illegal index for variable '{self.name}', it is a scalar variable." ) indices = 0 elif ( isinstance(item, slice) and item == slice(None) and self.index_var == "_idx" ): indices = slice(None) else: indices = self.indexing(item, self.index_var) q = Quantity(value, copy=False) if len(q.shape): if not len(q.shape) == 1 or len(q) != 1 and len(q) != len(indices): raise ValueError( "Provided values do not match the size " "of the indices, " f"{len(q)} != {len(indices)}." ) variable.get_value()[indices] = value # Allow some basic calculations directly on the ArrayView object def __array__(self, dtype=None): try: # This will fail for subexpressions that refer to external # parameters self[:] except ValueError: var = self.variable.name raise ValueError( f"Cannot get the values for variable {var}. If it " "is a subexpression referring to external " f"variables, use 'group.{var}[:]' instead of " f"'group.{var}'" ) return np.asanyarray(self[:], dtype=dtype) def __array_prepare__(self, array, context=None): if self.dim is None: return array else: this = self[:] if isinstance(this, Quantity): return Quantity.__array_prepare__(this, array, context=context) else: return array def __array_wrap__(self, out_arr, context=None): if self.dim is None: return out_arr else: this = self[:] if isinstance(this, Quantity): return Quantity.__array_wrap__(self[:], out_arr, context=context) else: return out_arr def __len__(self): return len(self.get_item(slice(None), level=1)) def __neg__(self): return -self.get_item(slice(None), level=1) def __pos__(self): return self.get_item(slice(None), level=1) def __add__(self, other): return self.get_item(slice(None), level=1) + np.asanyarray(other) def __radd__(self, other): return np.asanyarray(other) + self.get_item(slice(None), level=1) def __sub__(self, other): return self.get_item(slice(None), level=1) - np.asanyarray(other) def __rsub__(self, other): return np.asanyarray(other) - self.get_item(slice(None), level=1) def __mul__(self, other): return self.get_item(slice(None), level=1) * np.asanyarray(other) def __rmul__(self, other): return np.asanyarray(other) * self.get_item(slice(None), level=1) def __div__(self, other): return self.get_item(slice(None), level=1) / np.asanyarray(other) def __truediv__(self, other): return self.get_item(slice(None), level=1) / np.asanyarray(other) def __floordiv__(self, other): return self.get_item(slice(None), level=1) // np.asanyarray(other) def __rdiv__(self, other): return np.asanyarray(other) / self.get_item(slice(None), level=1) def __rtruediv__(self, other): return np.asanyarray(other) / self.get_item(slice(None), level=1) def __rfloordiv__(self, other): return np.asanyarray(other) // self.get_item(slice(None), level=1) def __mod__(self, other): return self.get_item(slice(None), level=1) % np.asanyarray(other) def __pow__(self, power, modulo=None): if modulo is not None: return self.get_item(slice(None), level=1) ** power % modulo else: return self.get_item(slice(None), level=1) ** power def __rpow__(self, other): if self.dim is not DIMENSIONLESS: raise TypeError( f"Cannot use '{self.name}' as an exponent, it has " f"dimensions {get_unit_for_display(self.unit)}." ) return other ** self.get_item(slice(None), level=1) def __iadd__(self, other): if isinstance(other, str): raise TypeError( "In-place modification with strings not " "supported. Use group.var = 'var + expression' " "instead of group.var += 'expression'." ) elif isinstance(self.variable, Subexpression): raise TypeError("Cannot assign to a subexpression.") else: rhs = self[:] + np.asanyarray(other) self[:] = rhs return self def __isub__(self, other): if isinstance(other, str): raise TypeError( "In-place modification with strings not " "supported. Use group.var = 'var - expression' " "instead of group.var -= 'expression'." ) elif isinstance(self.variable, Subexpression): raise TypeError("Cannot assign to a subexpression.") else: rhs = self[:] - np.asanyarray(other) self[:] = rhs return self def __imul__(self, other): if isinstance(other, str): raise TypeError( "In-place modification with strings not " "supported. Use group.var = 'var * expression' " "instead of group.var *= 'expression'." ) elif isinstance(self.variable, Subexpression): raise TypeError("Cannot assign to a subexpression.") else: rhs = self[:] * np.asanyarray(other) self[:] = rhs return self def __idiv__(self, other): if isinstance(other, str): raise TypeError( "In-place modification with strings not " "supported. Use group.var = 'var / expression' " "instead of group.var /= 'expression'." ) elif isinstance(self.variable, Subexpression): raise TypeError("Cannot assign to a subexpression.") else: rhs = self[:] / np.asanyarray(other) self[:] = rhs return self def __ifloordiv__(self, other): if isinstance(other, str): raise TypeError( "In-place modification with strings not " "supported. Use group.var = 'var // expression' " "instead of group.var //= 'expression'." ) elif isinstance(self.variable, Subexpression): raise TypeError("Cannot assign to a subexpression.") else: rhs = self[:] // np.asanyarray(other) self[:] = rhs return self def __imod__(self, other): if isinstance(other, str): raise TypeError( "In-place modification with strings not " "supported. Use group.var = 'var // expression' " "instead of group.var //= 'expression'." ) elif isinstance(self.variable, Subexpression): raise TypeError("Cannot assign to a subexpression.") else: rhs = self[:] % np.asanyarray(other) self[:] = rhs return self def __ipow__(self, other): if isinstance(other, str): raise TypeError( "In-place modification with strings not " "supported. Use group.var = 'var ** expression' " "instead of group.var **= 'expression'." ) elif isinstance(self.variable, Subexpression): raise TypeError("Cannot assign to a subexpression.") else: rhs = self[:] ** np.asanyarray(other) self[:] = rhs return self # Also allow logical comparisons def __eq__(self, other): return self.get_item(slice(None), level=1) == np.asanyarray(other) def __ne__(self, other): return self.get_item(slice(None), level=1) != np.asanyarray(other) def __lt__(self, other): return self.get_item(slice(None), level=1) < np.asanyarray(other) def __le__(self, other): return self.get_item(slice(None), level=1) <= np.asanyarray(other) def __gt__(self, other): return self.get_item(slice(None), level=1) > np.asanyarray(other) def __ge__(self, other): return self.get_item(slice(None), level=1) >= np.asanyarray(other) def __repr__(self): varname = self.name if self.dim is None: varname += "_" if self.variable.scalar: dim = self.dim if self.dim is not None else DIMENSIONLESS values = repr(Quantity(self.variable.get_value().item(), dim=dim)) else: try: # This will fail for subexpressions that refer to external # parameters values = repr(self[:]) except KeyError: values = ( "[Subexpression refers to external parameters. Use " f"'group.{self.variable.name}[:]']" ) return f"<{self.group_name}.{varname}: {values}>" # Get access to some basic properties of the underlying array @property def shape(self): return self.get_item(slice(None), level=1).shape @property def ndim(self): return self.get_item(slice(None), level=1).ndim @property def dtype(self): return self.get_item(slice(None), level=1).dtype class Variables(Mapping): """ A container class for storing `Variable` objects. Instances of this class are used as the `Group.variables` attribute and can be accessed as (read-only) dictionaries. Parameters ---------- owner : `Nameable` The object (typically a `Group`) "owning" the variables. default_index : str, optional The index to use for the variables (only relevant for `ArrayVariable` and `DynamicArrayVariable`). Defaults to ``'_idx'``. """ def __init__(self, owner, default_index="_idx"): #: A reference to the `Group` owning these variables self.owner = weakproxy_with_fallback(owner) # The index that is used for arrays if no index is given explicitly self.default_index = default_index # We do the import here to avoid a circular dependency. from brian2.devices.device import get_device self.device = get_device() self._variables = {} #: A dictionary given the index name for every array name self.indices = collections.defaultdict(functools.partial(str, default_index)) # Note that by using functools.partial (instead of e.g. a lambda # function) above, this object remains pickable. def __getitem__(self, item): return self._variables[item] def __len__(self): return len(self._variables) def __iter__(self): return iter(self._variables) def _add_variable(self, name, var, index=None): if name in self._variables: raise KeyError( f"The name '{name}' is already present in the variables dictionary." ) # TODO: do some check for the name, part of it has to be device-specific self._variables[name] = var if isinstance(var, ArrayVariable): # Tell the device to actually create the array (or note it down for # later code generation in standalone). self.device.add_array(var) if getattr(var, "scalar", False): if index not in (None, "0"): raise ValueError("Cannot set an index for a scalar variable") self.indices[name] = "0" if index is not None: self.indices[name] = index def add_array( self, name, size, dimensions=DIMENSIONLESS, values=None, dtype=None, constant=False, read_only=False, scalar=False, unique=False, index=None, ): """ Add an array (initialized with zeros). Parameters ---------- name : str The name of the variable. dimensions : `Dimension`, optional The physical dimensions of the variable. size : int The size of the array. values : `ndarray`, optional The values to initalize the array with. If not specified, the array is initialized to zero. dtype : `dtype`, optional The dtype used for storing the variable. If none is given, defaults to `core.default_float_dtype`. constant : bool, optional Whether the variable's value is constant during a run. Defaults to ``False``. scalar : bool, optional Whether this is a scalar variable. Defaults to ``False``, if set to ``True``, also implies that `size` equals 1. read_only : bool, optional Whether this is a read-only variable, i.e. a variable that is set internally and cannot be changed by the user. Defaults to ``False``. index : str, optional The index to use for this variable. Defaults to `Variables.default_index`. unique : bool, optional See `ArrayVariable`. Defaults to ``False``. """ if np.asanyarray(size).shape == (): # We want a basic Python type for the size instead of something # like numpy.int64 size = int(size) var = ArrayVariable( name=name, dimensions=dimensions, owner=self.owner, device=self.device, size=size, dtype=dtype, constant=constant, scalar=scalar, read_only=read_only, unique=unique, ) self._add_variable(name, var, index) # This could be avoided, but we currently need it so that standalone # allocates the memory self.device.init_with_zeros(var, dtype) if values is not None: if scalar: if np.asanyarray(values).shape != (): raise ValueError("Need a scalar value.") self.device.fill_with_array(var, values) else: if len(values) != size: raise ValueError( "Size of the provided values does not match " f"size: {len(values)} != {size}" ) self.device.fill_with_array(var, values) def add_arrays( self, names, size, dimensions=DIMENSIONLESS, dtype=None, constant=False, read_only=False, scalar=False, unique=False, index=None, ): """ Adds several arrays (initialized with zeros) with the same attributes (size, units, etc.). Parameters ---------- names : list of str The names of the variable. dimensions : `Dimension`, optional The physical dimensions of the variable. size : int The sizes of the arrays. dtype : `dtype`, optional The dtype used for storing the variables. If none is given, defaults to `core.default_float_dtype`. constant : bool, optional Whether the variables' values are constant during a run. Defaults to ``False``. scalar : bool, optional Whether these are scalar variables. Defaults to ``False``, if set to ``True``, also implies that `size` equals 1. read_only : bool, optional Whether these are read-only variables, i.e. variables that are set internally and cannot be changed by the user. Defaults to ``False``. index : str, optional The index to use for these variables. Defaults to `Variables.default_index`. unique : bool, optional See `ArrayVariable`. Defaults to ``False``. """ for name in names: self.add_array( name, dimensions=dimensions, size=size, dtype=dtype, constant=constant, read_only=read_only, scalar=scalar, unique=unique, index=index, ) def add_dynamic_array( self, name, size, dimensions=DIMENSIONLESS, values=None, dtype=None, constant=False, needs_reference_update=False, resize_along_first=False, read_only=False, unique=False, scalar=False, index=None, ): """ Add a dynamic array. Parameters ---------- name : str The name of the variable. dimensions : `Dimension`, optional The physical dimensions of the variable. size : int or tuple of int The (initital) size of the array. values : `ndarray`, optional The values to initalize the array with. If not specified, the array is initialized to zero. dtype : `dtype`, optional The dtype used for storing the variable. If none is given, defaults to `core.default_float_dtype`. constant : bool, optional Whether the variable's value is constant during a run. Defaults to ``False``. needs_reference_update : bool, optional Whether the code objects need a new reference to the underlying data at every time step. This should be set if the size of the array can be changed by other code objects. Defaults to ``False``. scalar : bool, optional Whether this is a scalar variable. Defaults to ``False``, if set to ``True``, also implies that `size` equals 1. read_only : bool, optional Whether this is a read-only variable, i.e. a variable that is set internally and cannot be changed by the user. Defaults to ``False``. index : str, optional The index to use for this variable. Defaults to `Variables.default_index`. unique : bool, optional See `DynamicArrayVariable`. Defaults to ``False``. """ var = DynamicArrayVariable( name=name, dimensions=dimensions, owner=self.owner, device=self.device, size=size, dtype=dtype, constant=constant, needs_reference_update=needs_reference_update, resize_along_first=resize_along_first, scalar=scalar, read_only=read_only, unique=unique, ) self._add_variable(name, var, index) if np.prod(size) > 0: self.device.resize(var, size) if values is None and np.prod(size) > 0: self.device.init_with_zeros(var, dtype) elif values is not None: if len(values) != size: raise ValueError( "Size of the provided values does not match " f"size: {len(values)} != {size}" ) if np.prod(size) > 0: self.device.fill_with_array(var, values) def add_arange( self, name, size, start=0, dtype=np.int32, constant=True, read_only=True, unique=True, index=None, ): """ Add an array, initialized with a range of integers. Parameters ---------- name : str The name of the variable. size : int The size of the array. start : int The start value of the range. dtype : `dtype`, optional The dtype used for storing the variable. If none is given, defaults to `np.int32`. constant : bool, optional Whether the variable's value is constant during a run. Defaults to ``True``. read_only : bool, optional Whether this is a read-only variable, i.e. a variable that is set internally and cannot be changed by the user. Defaults to ``True``. index : str, optional The index to use for this variable. Defaults to `Variables.default_index`. unique : bool, optional See `ArrayVariable`. Defaults to ``True`` here. """ self.add_array( name=name, dimensions=DIMENSIONLESS, size=size, dtype=dtype, constant=constant, read_only=read_only, unique=unique, index=index, ) self.device.init_with_arange(self._variables[name], start, dtype=dtype) def add_constant(self, name, value, dimensions=DIMENSIONLESS): """ Add a scalar constant (e.g. the number of neurons `N`). Parameters ---------- name : str The name of the variable value: reference to the variable value The value of the constant. dimensions : `Dimension`, optional The physical dimensions of the variable. Note that the variable itself (as referenced by value) should never have units attached. """ var = Constant(name=name, dimensions=dimensions, owner=self.owner, value=value) self._add_variable(name, var) def add_subexpression( self, name, expr, dimensions=DIMENSIONLESS, dtype=None, scalar=False, index=None ): """ Add a named subexpression. Parameters ---------- name : str The name of the subexpression. dimensions : `Dimension` The physical dimensions of the subexpression. expr : str The subexpression itself. dtype : `dtype`, optional The dtype used for the expression. Defaults to `core.default_float_dtype`. scalar : bool, optional Whether this is an expression only referring to scalar variables. Defaults to ``False`` index : str, optional The index to use for this variable. Defaults to `Variables.default_index`. """ var = Subexpression( name=name, dimensions=dimensions, expr=expr, owner=self.owner, dtype=dtype, device=self.device, scalar=scalar, ) self._add_variable(name, var, index=index) def add_auxiliary_variable( self, name, dimensions=DIMENSIONLESS, dtype=None, scalar=False ): """ Add an auxiliary variable (most likely one that is added automatically to abstract code, e.g. ``_cond`` for a threshold condition), specifying its type and unit for code generation. Parameters ---------- name : str The name of the variable dimensions : `Dimension` The physical dimensions of the variable. dtype : `dtype`, optional The dtype used for storing the variable. If none is given, defaults to `core.default_float_dtype`. scalar : bool, optional Whether the variable is a scalar value (``True``) or vector-valued, e.g. defined for every neuron (``False``). Defaults to ``False``. """ var = AuxiliaryVariable( name=name, dimensions=dimensions, dtype=dtype, scalar=scalar ) self._add_variable(name, var) def add_referred_subexpression(self, name, group, subexpr, index): identifiers = subexpr.identifiers substitutions = {} for identifier in identifiers: if identifier not in subexpr.owner.variables: # external variable --> nothing to do continue subexpr_var = subexpr.owner.variables[identifier] if hasattr(subexpr_var, "owner"): new_name = f"_{name}_{subexpr.owner.name}_{identifier}" else: new_name = f"_{name}_{identifier}" substitutions[identifier] = new_name subexpr_var_index = group.variables.indices[identifier] if subexpr_var_index == group.variables.default_index: subexpr_var_index = index elif subexpr_var_index == "0": pass # nothing to do for a shared variable elif subexpr_var_index == index: pass # The same index as the main subexpression elif index != self.default_index: index_var = self._variables.get(index, None) if isinstance(index_var, DynamicArrayVariable): raise TypeError( f"Cannot link to subexpression '{name}': it refers " f"to the variable '{identifier}' which is indexed " f"with the dynamic index '{subexpr_var_index}'." ) else: self.add_reference(subexpr_var_index, group) self.indices[new_name] = subexpr_var_index if isinstance(subexpr_var, Subexpression): self.add_referred_subexpression( new_name, group, subexpr_var, subexpr_var_index ) else: self.add_reference(new_name, group, identifier, subexpr_var_index) new_expr = word_substitute(subexpr.expr, substitutions) new_subexpr = Subexpression( name, self.owner, new_expr, dimensions=subexpr.dim, device=subexpr.device, dtype=subexpr.dtype, scalar=subexpr.scalar, ) self._variables[name] = new_subexpr def add_reference(self, name, group, varname=None, index=None): """ Add a reference to a variable defined somewhere else (possibly under a different name). This is for example used in `Subgroup` and `Synapses` to refer to variables in the respective `NeuronGroup`. Parameters ---------- name : str The name of the variable (in this group, possibly a different name from `var.name`). group : `Group` The group from which `var` is referenced varname : str, optional The variable to refer to. If not given, defaults to `name`. index : str, optional The index that should be used for this variable (defaults to `Variables.default_index`). """ if varname is None: varname = name if varname not in group.variables: raise KeyError(f"Group {group.name} does not have a variable {varname}.") if index is None: if group.variables[varname].scalar: index = "0" else: index = self.default_index if ( self.owner is not None and self.owner.name != group.name and index in self.owner.variables ): if ( not self.owner.variables[index].read_only or isinstance(self.owner.variables[index], DynamicArrayVariable) ) and group.variables.indices[varname] != group.variables.default_index: raise TypeError( f"Cannot link variable '{name}' to '{varname}' in " f"group '{group.name}' -- need to precalculate " f"direct indices but index {index} can change" ) # We don't overwrite existing names with references if name not in self._variables: var = group.variables[varname] if isinstance(var, Subexpression): self.add_referred_subexpression(name, group, var, index) else: self._variables[name] = var self.indices[name] = index def add_references(self, group, varnames, index=None): """ Add all `Variable` objects from a name to `Variable` mapping with the same name as in the original mapping. Parameters ---------- group : `Group` The group from which the `variables` are referenced varnames : iterable of str The variables that should be referred to in the current group index : str, optional The index to use for all the variables (defaults to `Variables.default_index`) """ for name in varnames: self.add_reference(name, group, name, index) def add_object(self, name, obj): """ Add an arbitrary Python object. This is only meant for internal use and therefore only names starting with an underscore are allowed. Parameters ---------- name : str The name used for this object (has to start with an underscore). obj : object An arbitrary Python object that needs to be accessed directly from a `CodeObject`. """ if not name.startswith("_"): raise ValueError( "This method is only meant for internally used " "objects, the name therefore has to start with " "an underscore" ) self._variables[name] = obj def create_clock_variables(self, clock, prefix=""): """ Convenience function to add the ``t`` and ``dt`` attributes of a `clock`. Parameters ---------- clock : `Clock` The clock that should be used for ``t`` and ``dt``. prefix : str, optional A prefix for the variable names. Used for example in monitors to not confuse the dynamic array of recorded times with the current time in the recorded group. """ self.add_reference(f"{prefix}t", clock, "t") self.add_reference(f"{prefix}dt", clock, "dt") self.add_reference(f"{prefix}t_in_timesteps", clock, "timestep") brian2-2.5.4/brian2/devices/000077500000000000000000000000001445201106100154705ustar00rootroot00000000000000brian2-2.5.4/brian2/devices/__init__.py000066400000000000000000000001641445201106100176020ustar00rootroot00000000000000""" Package providing the "devices" infrastructure. """ from . import device as device_module from .device import * brian2-2.5.4/brian2/devices/cpp_standalone/000077500000000000000000000000001445201106100204625ustar00rootroot00000000000000brian2-2.5.4/brian2/devices/cpp_standalone/GSLcodeobject.py000066400000000000000000000012451445201106100235050ustar00rootroot00000000000000""" Module containing CPPStandalone CodeObject for code generation for integration using the ODE solver provided in the GNU Scientific Library """ from brian2.codegen.codeobject import CodeObject from brian2.codegen.generators.cpp_generator import CPPCodeGenerator from brian2.codegen.generators.GSL_generator import GSLCPPCodeGenerator from brian2.devices.cpp_standalone import CPPStandaloneCodeObject class GSLCPPStandaloneCodeObject(CodeObject): templater = CPPStandaloneCodeObject.templater.derive( "brian2.devices.cpp_standalone", templates_dir="templates_GSL" ) original_generator_class = CPPCodeGenerator generator_class = GSLCPPCodeGenerator brian2-2.5.4/brian2/devices/cpp_standalone/__init__.py000066400000000000000000000003361445201106100225750ustar00rootroot00000000000000""" Package implementing the C++ "standalone" `Device` and `CodeObject`. """ from .codeobject import CPPStandaloneCodeObject from .device import cpp_standalone_device from .GSLcodeobject import GSLCPPStandaloneCodeObject brian2-2.5.4/brian2/devices/cpp_standalone/brianlib/000077500000000000000000000000001445201106100222445ustar00rootroot00000000000000brian2-2.5.4/brian2/devices/cpp_standalone/brianlib/clocks.h000066400000000000000000000021221445201106100236700ustar00rootroot00000000000000#ifndef _BRIAN_CLOCKS_H #define _BRIAN_CLOCKS_H #include #include #include #include namespace { inline int64_t fround(double x) { return (int64_t)(x+0.5); }; }; class Clock { public: double epsilon; double *dt; int64_t *timestep; double *t; Clock(double _epsilon=1e-14) : epsilon(_epsilon) { i_end = 0;}; inline void tick() { timestep[0] += 1; t[0] = timestep[0] * dt[0]; } inline bool running() { return timestep[0] #include #ifdef _MSC_VER #define INFINITY (std::numeric_limits::infinity()) #define NAN (std::numeric_limits::quiet_NaN()) #define M_PI 3.14159265358979323846 #endif #endif brian2-2.5.4/brian2/devices/cpp_standalone/brianlib/dynamic_array.h000066400000000000000000000026271445201106100252460ustar00rootroot00000000000000#ifndef _BRIAN_DYNAMIC_ARRAY_H #define _BRIAN_DYNAMIC_ARRAY_H #include /* * 2D Dynamic array class * * Efficiency note: if you are regularly resizing, make sure it is the first dimension that * is resized, not the second one. * */ template class DynamicArray2D { int old_n, old_m; std::vector< std::vector* > data; public: int n, m; DynamicArray2D(int _n=0, int _m=0) { old_n = 0; old_m = 0; resize(_n, _m); }; ~DynamicArray2D() { resize(0, 0); // handles deallocation } void resize() { if(old_n!=n) { if(nold_n) { for(int i=old_n; i; } } if(old_m!=m) { for(int i=0; iresize(m); } else if(n>old_n) { for(int i=old_n; iresize(m); } } else if(old_m!=m) { for(int i=0; iresize(m); } } old_n = n; old_m = m; }; void resize(int _n, int _m) { n = _n; m = _m; resize(); } // We cannot simply use T& as the return type here, since we don't // get a bool& out of a std::vector inline typename std::vector::reference operator()(int i, int j) { return (*data[i])[j]; } inline std::vector& operator()(int i) { return (*data[i]); } }; #endif brian2-2.5.4/brian2/devices/cpp_standalone/codeobject.py000066400000000000000000000137741445201106100231510ustar00rootroot00000000000000""" Module implementing the C++ "standalone" `CodeObject` """ from brian2.codegen.codeobject import CodeObject, constant_or_scalar from brian2.codegen.generators.cpp_generator import CPPCodeGenerator, c_data_type from brian2.codegen.targets import codegen_targets from brian2.codegen.templates import Templater from brian2.core.functions import DEFAULT_FUNCTIONS from brian2.core.preferences import prefs from brian2.devices.device import get_device from brian2.utils.stringtools import replace __all__ = ["CPPStandaloneCodeObject"] def openmp_pragma(pragma_type): nb_threads = prefs.devices.cpp_standalone.openmp_threads openmp_on = not (nb_threads == 0) ## First we need to deal with some special cases that have to be handle in case ## openmp is not activated if pragma_type == "set_num_threads": if not openmp_on: return "" elif nb_threads > 0: # We have to fix the exact number of threads in all parallel sections return f"omp_set_dynamic(0);\nomp_set_num_threads({int(nb_threads)});" elif pragma_type == "get_thread_num": if not openmp_on: return "0" else: return "omp_get_thread_num()" elif pragma_type == "get_num_threads": if not openmp_on: return "1" else: return f"{int(nb_threads)}" elif pragma_type == "with_openmp": # The returned value is a proper Python boolean, i.e. not something # that should be included in the generated code but rather for use # in {% if ... %} statements in the template return openmp_on ## Then by default, if openmp is off, we do not return any pragma statement in the templates elif not openmp_on: return "" ## Otherwise, we return the correct pragma statement elif pragma_type == "static": return "#pragma omp for schedule(static)" elif pragma_type == "single": return "#pragma omp single" elif pragma_type == "single-nowait": return "#pragma omp single nowait" elif pragma_type == "critical": return "#pragma omp critical" elif pragma_type == "atomic": return "#pragma omp atomic" elif pragma_type == "once": return "#pragma once" elif pragma_type == "parallel-static": return "#pragma omp parallel for schedule(static)" elif pragma_type == "static-ordered": return "#pragma omp for schedule(static) ordered" elif pragma_type == "ordered": return "#pragma omp ordered" elif pragma_type == "include": return "#include " elif pragma_type == "parallel": return "#pragma omp parallel" elif pragma_type == "master": return "#pragma omp master" elif pragma_type == "barrier": return "#pragma omp barrier" elif pragma_type == "compilation": return "-fopenmp" elif pragma_type == "sections": return "#pragma omp sections" elif pragma_type == "section": return "#pragma omp section" else: raise ValueError(f'Unknown OpenMP pragma "{pragma_type}"') class CPPStandaloneCodeObject(CodeObject): """ C++ standalone code object The ``code`` should be a `~brian2.codegen.templates.MultiTemplate` object with two macros defined, ``main`` (for the main loop code) and ``support_code`` for any support code (e.g. function definitions). """ templater = Templater( "brian2.devices.cpp_standalone", ".cpp", env_globals={ "c_data_type": c_data_type, "openmp_pragma": openmp_pragma, "constant_or_scalar": constant_or_scalar, "prefs": prefs, "zip": zip, }, ) generator_class = CPPCodeGenerator def __init__(self, *args, **kwds): super().__init__(*args, **kwds) #: Store whether this code object defines before/after blocks self.before_after_blocks = [] def __call__(self, **kwds): return self.run() def compile_block(self, block): pass # Compilation will be handled in device def run_block(self, block): if block == "run": get_device().main_queue.append((f"{block}_code_object", (self,))) else: # Check the C++ code whether there is anything to run cpp_code = getattr(self.code, f"{block}_cpp_file") if len(cpp_code) and "EMPTY_CODE_BLOCK" not in cpp_code: get_device().main_queue.append((f"{block}_code_object", (self,))) self.before_after_blocks.append(block) codegen_targets.add(CPPStandaloneCodeObject) # At module initialization time, we do not yet know whether the code will be # run with OpenMP or not. We therefore use a "dynamic implementation" which # generates the rand/randn implementation during code generation. def generate_rand_code(rand_func, owner): nb_threads = prefs.devices.cpp_standalone.openmp_threads if nb_threads == 0: # no OpenMP thread_number = "0" else: thread_number = "omp_get_thread_num()" if rand_func == "rand": rk_call = "rk_double" elif rand_func == "randn": rk_call = "rk_gauss" else: raise AssertionError(rand_func) code = """ double _%RAND_FUNC%(const int _vectorisation_idx) { return %RK_CALL%(brian::_mersenne_twister_states[%THREAD_NUMBER%]); } """ code = replace( code, { "%THREAD_NUMBER%": thread_number, "%RAND_FUNC%": rand_func, "%RK_CALL%": rk_call, }, ) return {"support_code": code} rand_impls = DEFAULT_FUNCTIONS["rand"].implementations rand_impls.add_dynamic_implementation( CPPStandaloneCodeObject, code=lambda owner: generate_rand_code("rand", owner), namespace=lambda owner: {}, name="_rand", ) randn_impls = DEFAULT_FUNCTIONS["randn"].implementations randn_impls.add_dynamic_implementation( CPPStandaloneCodeObject, code=lambda owner: generate_rand_code("randn", owner), namespace=lambda owner: {}, name="_randn", ) brian2-2.5.4/brian2/devices/cpp_standalone/device.py000066400000000000000000002244171445201106100223050ustar00rootroot00000000000000""" Module implementing the C++ "standalone" device. """ import inspect import itertools import numbers import os import shutil import subprocess import sys import tempfile import time import zlib from collections import Counter, defaultdict from distutils import ccompiler import numpy as np import brian2 from brian2.codegen.codeobject import check_compiler_kwds from brian2.codegen.cpp_prefs import get_compiler_and_args, get_msvc_env from brian2.codegen.generators.cpp_generator import c_data_type from brian2.core.functions import Function from brian2.core.namespace import get_local_namespace from brian2.core.preferences import BrianPreference, prefs from brian2.core.variables import ( ArrayVariable, Constant, DynamicArrayVariable, Variable, VariableView, ) from brian2.devices.device import Device, all_devices, reset_device, set_device from brian2.groups.group import Group from brian2.parsing.rendering import CPPNodeRenderer from brian2.synapses.synapses import Synapses from brian2.units import second from brian2.units.fundamentalunits import Quantity from brian2.utils.filetools import copy_directory, ensure_directory, in_directory from brian2.utils.logger import get_logger, std_silent from brian2.utils.stringtools import word_substitute from .codeobject import CPPStandaloneCodeObject, openmp_pragma __all__ = [] logger = get_logger(__name__) # Preferences prefs.register_preferences( "devices.cpp_standalone", "C++ standalone preferences ", openmp_threads=BrianPreference( default=0, docs=""" The number of threads to use if OpenMP is turned on. By default, this value is set to 0 and the C++ code is generated without any reference to OpenMP. If greater than 0, then the corresponding number of threads are used to launch the simulation. """, ), openmp_spatialneuron_strategy=BrianPreference( default=None, validator=lambda val: val in [None, "branches", "systems"], docs=""" DEPRECATED. Previously used to chose the strategy to parallelize the solution of the three tridiagonal systems for multicompartmental neurons. Now, its value is ignored. """, ), make_cmd_unix=BrianPreference( default="make", docs=""" The make command used to compile the standalone project. Defaults to the standard GNU make commane "make".""", ), run_cmd_unix=BrianPreference( default="./main", validator=lambda val: isinstance(val, str) or isinstance(val, list), docs=""" The command used to run the compiled standalone project. Defaults to executing the compiled binary with "./main". Must be a single binary as string or a list of command arguments (e.g. ["./binary", "--key", "value"]). """, ), extra_make_args_unix=BrianPreference( default=["-j"], docs=""" Additional flags to pass to the GNU make command on Linux/OS-X. Defaults to "-j" for parallel compilation.""", ), extra_make_args_windows=BrianPreference( default=[], docs=""" Additional flags to pass to the nmake command on Windows. By default, no additional flags are passed. """, ), run_environment_variables=BrianPreference( default={"LD_BIND_NOW": "1"}, docs=""" Dictionary of environment variables and their values that will be set during the execution of the standalone code. """, ), ) class CPPWriter: def __init__(self, project_dir): self.project_dir = project_dir self.source_files = set() self.header_files = set() def write(self, filename, contents): logger.diagnostic(f"Writing file {filename}:\n{contents}") if filename.lower().endswith(".cpp") or filename.lower().endswith(".c"): self.source_files.add(filename) elif filename.lower().endswith(".h"): self.header_files.add(filename) elif filename.endswith(".*"): self.write(f"{filename[:-1]}cpp", contents.cpp_file) self.write(f"{filename[:-1]}h", contents.h_file) return fullfilename = os.path.join(self.project_dir, filename) if os.path.exists(fullfilename): with open(fullfilename) as f: if f.read() == contents: return with open(fullfilename, "w") as f: f.write(contents) def invert_dict(x): return {v: k for k, v in x.items()} class CPPStandaloneDevice(Device): """ The `Device` used for C++ standalone simulations. """ def __init__(self): super().__init__() #: Dictionary mapping `ArrayVariable` objects to their globally #: unique name self.arrays = {} #: Dictionary mapping `ArrayVariable` objects to their value or to #: ``None`` if the value (potentially) depends on executed code. This #: mechanism allows to access state variables in standalone mode if #: their value is known at run time self.array_cache = {} #: List of all dynamic arrays #: Dictionary mapping `DynamicArrayVariable` objects with 1 dimension to #: their globally unique name self.dynamic_arrays = {} #: Dictionary mapping `DynamicArrayVariable` objects with 2 dimensions #: to their globally unique name self.dynamic_arrays_2d = {} #: List of all arrays to be filled with zeros (list of (var, varname) ) self.zero_arrays = [] #: List of all arrays to be filled with numbers (list of #: (var, varname, start) tuples self.arange_arrays = [] #: Set of all existing synapses self.synapses = set() #: Whether the simulation has been run self.has_been_run = False #: Whether a run should trigger a build self.build_on_run = False #: build options self.build_options = None #: The directory which contains the generated code and results self.project_dir = None #: Whether to generate profiling information (stored in an instance #: variable to be accessible during CodeObject generation) self.enable_profiling = False #: CodeObjects that use profiling (users can potentially enable #: profiling only for a subset of runs) self.profiled_codeobjects = [] #: Dict of all static saved arrays self.static_arrays = {} self.code_objects = {} self.main_queue = [] self.runfuncs = {} self.networks = set() self.static_array_specs = [] self.report_func = "" #: Code lines that have been manually added with `device.insert_code` #: Dictionary mapping slot names to lists of lines. #: Note that the main slot is handled separately as part of `main_queue` self.code_lines = { "before_start": [], "after_start": [], "before_network_run": [], "after_network_run": [], "before_end": [], "after_end": [], } #: Dictionary storing compile and binary execution times self.timers = {"run_binary": None, "compile": {"clean": None, "make": None}} self.clocks = set() self.extra_compile_args = [] self.define_macros = [] self.headers = [] self.include_dirs = ["brianlib/randomkit"] self.library_dirs = ["brianlib/randomkit"] self.runtime_library_dirs = [] self.run_environment_variables = {} if sys.platform.startswith("darwin"): if "DYLD_LIBRARY_PATH" in os.environ: dyld_library_path = ( f"{os.environ['DYLD_LIBRARY_PATH']}:{os.path.join(sys.prefix, 'lib')}" ) else: dyld_library_path = os.path.join(sys.prefix, "lib") self.run_environment_variables["DYLD_LIBRARY_PATH"] = dyld_library_path self.libraries = [] if sys.platform == "win32": self.libraries += ["advapi32"] self.extra_link_args = [] self.writer = None def reinit(self): # Remember the build_on_run setting and its options -- important during # testing build_on_run = self.build_on_run build_options = self.build_options self.__init__() super().reinit() self.build_on_run = build_on_run self.build_options = build_options def spike_queue(self, source_start, source_end): return None # handled differently def freeze(self, code, ns): # TODO: Remove this function at some point logger.warn( "The CPPStandaloneDevice.freeze function should no longer " "be used, add constant definitions directly to the " 'code in the "CONSTANTS" section instead.', name_suffix="deprecated_freeze_use", once=True, ) # this is a bit of a hack, it should be passed to the template somehow for k, v in ns.items(): if isinstance(v, Variable) and v.scalar and v.constant and v.read_only: try: v = v.get_value() except NotImplementedError: continue if isinstance(v, str): code = word_substitute(code, {k: v}) elif isinstance(v, numbers.Number): # Use a renderer to correctly transform constants such as True or inf renderer = CPPNodeRenderer() string_value = renderer.render_expr(repr(v)) if prefs.core.default_float_dtype == np.float32 and isinstance( v, (float, np.float32, np.float64) ): string_value += "f" if v < 0: string_value = f"({string_value})" code = word_substitute(code, {k: string_value}) else: pass # don't deal with this object return code def insert_code(self, slot, code): """ Insert code directly into main.cpp """ if slot == "main": self.main_queue.append(("insert_code", code)) elif slot in self.code_lines: self.code_lines[slot].append(code) else: logger.warn(f"Ignoring device code, unknown slot: {slot}, code: {code}") def static_array(self, name, arr): arr = np.atleast_1d(arr) assert len(arr), f"length for {name}: {len(arr)}" name = f"_static_array_{name}" basename = name i = 0 while name in self.static_arrays: i += 1 name = f"{basename}_{str(i)}" self.static_arrays[name] = arr.copy() return name def get_array_name(self, var, access_data=True): """ Return a globally unique name for `var`. Parameters ---------- access_data : bool, optional For `DynamicArrayVariable` objects, specifying `True` here means the name for the underlying data is returned. If specifying `False`, the name of object itself is returned (e.g. to allow resizing). """ if isinstance(var, DynamicArrayVariable): if access_data: return self.arrays[var] elif var.ndim == 1: return self.dynamic_arrays[var] else: return self.dynamic_arrays_2d[var] elif isinstance(var, ArrayVariable): return self.arrays[var] else: raise TypeError(f"Do not have a name for variable of type {type(var)}.") def get_array_filename(self, var, basedir="results"): """ Return a file name for a variable. Parameters ---------- var : `ArrayVariable` The variable to get a filename for. basedir : str The base directory for the filename, defaults to ``'results'``. Returns ------- filename : str A filename of the form ``'results/'+varname+'_'+str(zlib.crc32(varname))``, where varname is the name returned by `get_array_name`. Notes ----- The reason that the filename is not simply ``'results/' + varname`` is that this could lead to file names that are not unique in file systems that are not case sensitive (e.g. on Windows). """ varname = self.get_array_name(var, access_data=False) return os.path.join( basedir, f"{varname}_{str(zlib.crc32(varname.encode('utf-8')))}" ) def add_array(self, var): # Note that a dynamic array variable is added to both the arrays and # the _dynamic_array dictionary if isinstance(var, DynamicArrayVariable): # The code below is slightly more complicated than just looking # for a unique name as above for static_array, the name has # potentially to be unique for more than one dictionary, with # different prefixes. This is because dynamic arrays are added to # a ``dynamic_arrays`` dictionary (with a `_dynamic` prefix) and to # the general ``arrays`` dictionary. We want to make sure that we # use the same name in the two dictionaries, not for example # ``_dynamic_array_source_name_2`` and ``_array_source_name_1`` # (this would work fine, but it would make the code harder to read). orig_dynamic_name = ( dynamic_name ) = f"_dynamic_array_{var.owner.name}_{var.name}" orig_array_name = array_name = f"_array_{var.owner.name}_{var.name}" suffix = 0 if var.ndim == 1: dynamic_dict = self.dynamic_arrays elif var.ndim == 2: dynamic_dict = self.dynamic_arrays_2d else: raise AssertionError( "Did not expect a dynamic array with {var.ndim} dimensions." ) while ( dynamic_name in dynamic_dict.values() or array_name in self.arrays.values() ): suffix += 1 dynamic_name = f"{orig_dynamic_name}_{int(suffix)}" array_name = f"{orig_array_name}_{int(suffix)}" dynamic_dict[var] = dynamic_name self.arrays[var] = array_name else: orig_array_name = array_name = f"_array_{var.owner.name}_{var.name}" suffix = 0 while array_name in self.arrays.values(): suffix += 1 array_name = f"{orig_array_name}_{int(suffix)}" self.arrays[var] = array_name def init_with_zeros(self, var, dtype): if isinstance(var, DynamicArrayVariable): varname = f"_dynamic{self.arrays[var]}" else: varname = self.arrays[var] self.zero_arrays.append((var, varname)) self.array_cache[var] = np.zeros(var.size, dtype=dtype) def init_with_arange(self, var, start, dtype): if isinstance(var, DynamicArrayVariable): varname = f"_dynamic{self.arrays[var]}" else: varname = self.arrays[var] self.arange_arrays.append((var, varname, start)) self.array_cache[var] = np.arange(0, var.size, dtype=dtype) + start def fill_with_array(self, var, arr): arr = np.asarray(arr) if arr.size == 0: return # nothing to do array_name = self.get_array_name(var, access_data=False) if isinstance(var, DynamicArrayVariable): # We can never be sure about the size of a dynamic array, so # we can't do correct broadcasting. Therefore, we do not cache # them at all for now. self.array_cache[var] = None else: new_arr = np.empty(var.size, dtype=var.dtype) new_arr[:] = arr self.array_cache[var] = new_arr if arr.size == 1: if var.size == 1: value = CPPNodeRenderer().render_expr(repr(arr.item(0))) # For a single assignment, generate a code line instead of storing the array self.main_queue.append(("set_by_single_value", (array_name, 0, value))) else: self.main_queue.append( ( "set_by_constant", (array_name, arr.item(), isinstance(var, DynamicArrayVariable)), ) ) else: # Using the std::vector instead of a pointer to the underlying # data for dynamic arrays is fast enough here and it saves us some # additional work to set up the pointer static_array_name = self.static_array(array_name, arr) self.main_queue.append( ( "set_by_array", ( array_name, static_array_name, isinstance(var, DynamicArrayVariable), ), ) ) def resize(self, var, new_size): array_name = self.get_array_name(var, access_data=False) self.main_queue.append(("resize_array", (array_name, new_size))) def variableview_set_with_index_array(self, variableview, item, value, check_units): if isinstance(item, slice) and item == slice(None): item = "True" value = Quantity(value) if ( isinstance(item, int) or (isinstance(item, np.ndarray) and item.shape == ()) ) and value.size == 1: array_name = self.get_array_name(variableview.variable, access_data=False) value_str = CPPNodeRenderer().render_expr(repr(np.asarray(value).item(0))) if self.array_cache.get(variableview.variable, None) is not None: self.array_cache[variableview.variable][item] = value # For a single assignment, generate a code line instead of storing the array self.main_queue.append( ("set_by_single_value", (array_name, item, value_str)) ) # Simple case where we don't have to do any indexing elif item == "True" and variableview.index_var in ("_idx", "0"): self.fill_with_array(variableview.variable, value) else: # We have to calculate indices. This will not work for synaptic # variables try: indices = np.asarray( variableview.indexing(item, index_var=variableview.index_var) ) except NotImplementedError: raise NotImplementedError( f"Cannot set variable '{variableview.name}' " "this way in standalone, try using " "string expressions." ) # Using the std::vector instead of a pointer to the underlying # data for dynamic arrays is fast enough here and it saves us some # additional work to set up the pointer arrayname = self.get_array_name(variableview.variable, access_data=False) if indices.shape != () and ( value.shape == () or (value.size == 1 and indices.size > 1) ): value = np.repeat(value, indices.size) elif value.shape != indices.shape and len(value) != len(indices): raise ValueError( "Provided values do not match the size " "of the indices, " f"{len(value)} != len(indices)." ) staticarrayname_index = self.static_array(f"_index_{arrayname}", indices) staticarrayname_value = self.static_array(f"_value_{arrayname}", value) self.array_cache[variableview.variable] = None self.main_queue.append( ( "set_array_by_array", (arrayname, staticarrayname_index, staticarrayname_value), ) ) def get_value(self, var, access_data=True): # Usually, we cannot retrieve the values of state variables in # standalone scripts since their values might depend on the evaluation # of expressions at runtime. For some variables we do know the value # however (values that have been set with explicit values and not # changed in code objects) if self.array_cache.get(var, None) is not None: return self.array_cache[var] else: # After the network has been run, we can retrieve the values from # disk if self.has_been_run: dtype = var.dtype fname = os.path.join(self.project_dir, self.get_array_filename(var)) with open(fname, "rb") as f: data = np.fromfile(f, dtype=dtype) # This is a bit of an heuristic, but our 2d dynamic arrays are # only expanding in one dimension, we assume here that the # other dimension has size 0 at the beginning if isinstance(var.size, tuple) and len(var.size) == 2: if var.size[0] * var.size[1] == len(data): size = var.size elif var.size[0] == 0: size = (len(data) // var.size[1], var.size[1]) elif var.size[1] == 0: size = (var.size[0], len(data) // var.size[0]) else: raise IndexError( "Do not now how to deal with 2d " f"array of size {var.size!s}, the array on " f"disk has length {len(data)}." ) var.size = size return data.reshape(var.size) var.size = len(data) return data raise NotImplementedError( "Cannot retrieve the values of state " "variables in standalone code before the " "simulation has been run." ) def variableview_get_subexpression_with_index_array( self, variableview, item, run_namespace=None ): if not self.has_been_run: raise NotImplementedError( "Cannot retrieve the values of state " "variables in standalone code before the " "simulation has been run." ) # Temporarily switch to the runtime device to evaluate the subexpression # (based on the values stored on disk) set_device("runtime") result = VariableView.get_subexpression_with_index_array( variableview, item, run_namespace=run_namespace ) reset_device() return result def variableview_get_with_expression(self, variableview, code, run_namespace=None): raise NotImplementedError( "Cannot retrieve the values of state " "variables with string expressions in " "standalone scripts." ) def code_object_class(self, codeobj_class=None, fallback_pref=None): """ Return `CodeObject` class (either `CPPStandaloneCodeObject` class or input) Parameters ---------- codeobj_class : a `CodeObject` class, optional If this is keyword is set to None or no arguments are given, this method will return the default (`CPPStandaloneCodeObject` class). fallback_pref : str, optional For the cpp_standalone device this option is ignored. Returns ------- codeobj_class : class The `CodeObject` class that should be used """ # Ignore the requested pref (used for optimization in runtime) if codeobj_class is None: return CPPStandaloneCodeObject else: return codeobj_class def code_object( self, owner, name, abstract_code, variables, template_name, variable_indices, codeobj_class=None, template_kwds=None, override_conditional_write=None, compiler_kwds=None, ): if compiler_kwds is None: compiler_kwds = {} check_compiler_kwds( compiler_kwds, [ "headers", "sources", "define_macros", "libraries", "include_dirs", "library_dirs", "runtime_library_dirs", ], "C++ standalone", ) if template_kwds is None: template_kwds = dict() else: template_kwds = dict(template_kwds) # In standalone mode, the only place where we use additional header # files is by inserting them into the template codeobj_headers = compiler_kwds.get("headers", []) template_kwds["user_headers"] = ( self.headers + prefs["codegen.cpp.headers"] + codeobj_headers ) template_kwds["profiled"] = self.enable_profiling do_not_invalidate = set() if template_name == "synapses_create_array": cache = self.array_cache if ( cache[variables["N"]] is None ): # synapses have been previously created with code # Nothing we can do logger.debug( f"Synapses for '{owner.name}' have previously been created with " "code, we therefore cannot cache the synapses created with arrays " f"via '{name}'", name_suffix="code_created_synapses_exist", ) else: # first time we create synapses, or all previous connect calls were with arrays cache[variables["N"]][0] += variables["sources"].size do_not_invalidate.add(variables["N"]) for var, value in [ ( variables["_synaptic_pre"], variables["sources"].get_value() + variables["_source_offset"].get_value(), ), ( variables["_synaptic_post"], variables["targets"].get_value() + variables["_target_offset"].get_value(), ), ]: cache[var] = np.append( cache.get(var, np.empty(0, dtype=int)), value ) do_not_invalidate.add(var) codeobj = super().code_object( owner, name, abstract_code, variables, template_name, variable_indices, codeobj_class=codeobj_class, template_kwds=template_kwds, override_conditional_write=override_conditional_write, compiler_kwds=compiler_kwds, ) self.code_objects[codeobj.name] = codeobj if self.enable_profiling: self.profiled_codeobjects.append(codeobj.name) # We mark all writeable (i.e. not read-only) variables used by the code # as "dirty" to avoid that the cache contains incorrect values. This # might remove a number of variables from the cache unnecessarily, # since many variables are only read in the code. # On the other hand, there are also *read-only* variables that can be # changed by code (the "read-only" attribute only refers to the user # being able to change values directly). For example, synapse creation # write source and target indices, and monitors write the shared values. # To correctly mark these values as changed, templates can include a # "WRITES_TO_READ_ONLY_VARIABLES" comment, stating the name of the # changed variables. For a monitor, this would for example state that # the number of recorded values "N" changes. For the recorded variables, # however, this information cannot be included in the template because # it is up to the user to define which variables are recorded. For such # cases, the "owner" object (e.g. a SpikeMonitor) can define a # "written_readonly_vars" attribute, storing a set of `Variable` objects # that will be changed by the owner's code objects. template = getattr(codeobj.templater, template_name) written_readonly_vars = { codeobj.variables[varname] for varname in template.writes_read_only } | getattr(owner, "written_readonly_vars", set()) for var in codeobj.variables.values(): if ( isinstance(var, ArrayVariable) and var not in do_not_invalidate and (not var.read_only or var in written_readonly_vars) ): self.array_cache[var] = None return codeobj def check_openmp_compatible(self, nb_threads): if nb_threads > 0: logger.warn( "OpenMP code is not yet well tested, and may be inaccurate.", "openmp", once=True, ) logger.diagnostic(f"Using OpenMP with {int(nb_threads)} threads ") if prefs.devices.cpp_standalone.openmp_spatialneuron_strategy is not None: logger.warn( "The devices.cpp_standalone.openmp_spatialneuron_strategy " "preference is no longer used and will be removed in " "future versions of Brian.", "openmp_spatialneuron_strategy", once=True, ) def generate_objects_source( self, writer, arange_arrays, synapses, static_array_specs, networks ): arr_tmp = self.code_object_class().templater.objects( None, None, array_specs=self.arrays, dynamic_array_specs=self.dynamic_arrays, dynamic_array_2d_specs=self.dynamic_arrays_2d, zero_arrays=self.zero_arrays, arange_arrays=arange_arrays, synapses=synapses, clocks=self.clocks, static_array_specs=static_array_specs, networks=networks, get_array_filename=self.get_array_filename, get_array_name=self.get_array_name, profiled_codeobjects=self.profiled_codeobjects, code_objects=list(self.code_objects.values()), ) writer.write("objects.*", arr_tmp) def generate_main_source(self, writer): main_lines = [] procedures = [("", main_lines)] runfuncs = {} for func, args in self.main_queue: if func == "before_run_code_object": (codeobj,) = args main_lines.append(f"_before_run_{codeobj.name}();") elif func == "run_code_object": (codeobj,) = args main_lines.append(f"_run_{codeobj.name}();") elif func == "after_run_code_object": (codeobj,) = args main_lines.append(f"_after_run_{codeobj.name}();") elif func == "run_network": net, netcode = args main_lines.extend(netcode) elif func == "set_by_constant": arrayname, value, is_dynamic = args size_str = f"{arrayname}.size()" if is_dynamic else f"_num_{arrayname}" code = f""" {openmp_pragma('static')} for(int i=0; i<{size_str}; i++) {{ {arrayname}[i] = {CPPNodeRenderer().render_expr(repr(value))}; }} """ main_lines.extend(code.split("\n")) elif func == "set_by_array": arrayname, staticarrayname, is_dynamic = args size_str = f"{arrayname}.size()" if is_dynamic else f"_num_{arrayname}" code = f""" {openmp_pragma('static')} for(int i=0; i<{size_str}; i++) {{ {arrayname}[i] = {staticarrayname}[i]; }} """ main_lines.extend(code.split("\n")) elif func == "set_by_single_value": arrayname, item, value = args code = f"{arrayname}[{item}] = {value};" main_lines.extend([code]) elif func == "set_array_by_array": arrayname, staticarrayname_index, staticarrayname_value = args code = f""" {openmp_pragma('static')} for(int i=0; i<_num_{staticarrayname_index}; i++) {{ {arrayname}[{staticarrayname_index}[i]] = {staticarrayname_value}[i]; }} """ main_lines.extend(code.split("\n")) elif func == "resize_array": array_name, new_size = args main_lines.append(f"{array_name}.resize({new_size});") elif func == "insert_code": main_lines.append(args) elif func == "start_run_func": name, include_in_parent = args if include_in_parent: main_lines.append(f"{name}();") main_lines = [] procedures.append((name, main_lines)) elif func == "end_run_func": name, include_in_parent = args name, main_lines = procedures.pop(-1) runfuncs[name] = main_lines name, main_lines = procedures[-1] elif func == "seed": seed = args nb_threads = prefs.devices.cpp_standalone.openmp_threads if nb_threads == 0: # no OpenMP nb_threads = 1 main_lines.append(f"for (int _i=0; _i<{nb_threads}; _i++)") if seed is None: # random main_lines.append( " rk_randomseed(brian::_mersenne_twister_states[_i]);" ) else: main_lines.append( f" rk_seed({seed!r}L + _i," " brian::_mersenne_twister_states[_i]);" ) else: raise NotImplementedError(f"Unknown main queue function type {func}") self.runfuncs = runfuncs # generate the finalisations for codeobj in self.code_objects.values(): if hasattr(codeobj.code, "main_finalise"): main_lines.append(codeobj.code.main_finalise) user_headers = self.headers + prefs["codegen.cpp.headers"] main_tmp = self.code_object_class().templater.main( None, None, main_lines=main_lines, code_lines=self.code_lines, code_objects=list(self.code_objects.values()), report_func=self.report_func, dt=float(self.defaultclock.dt), user_headers=user_headers, ) writer.write("main.cpp", main_tmp) def generate_codeobj_source(self, writer): # Generate data for non-constant values renderer = CPPNodeRenderer() code_object_defs = defaultdict(list) for codeobj in self.code_objects.values(): lines = [] for k, v in codeobj.variables.items(): if isinstance(v, ArrayVariable): try: if isinstance(v, DynamicArrayVariable): if v.ndim == 1: dyn_array_name = self.dynamic_arrays[v] array_name = self.arrays[v] c_type = c_data_type(v.dtype) line = ( f"{c_type}* const {array_name} =" f" {dyn_array_name}.empty()? 0 :" f" &{dyn_array_name}[0];" ) lines.append(line) line = ( f"const size_t _num{k} = {dyn_array_name}.size();" ) lines.append(line) else: lines.append(f"const size_t _num{k} = {v.size};") except TypeError: pass elif isinstance(v, Constant): value = renderer.render_expr(repr(v.value)) c_type = c_data_type(v.dtype) line = f"const {c_type} {k} = {value};" lines.append(line) for line in lines: # Sometimes an array is referred to by to different keys in our # dictionary -- make sure to never add a line twice if line not in code_object_defs[codeobj.name]: code_object_defs[codeobj.name].append(line) # Generate the code objects for codeobj in self.code_objects.values(): # Before/after run code for block in codeobj.before_after_blocks: cpp_code = getattr(codeobj.code, f"{block}_cpp_file") cpp_code = cpp_code.replace( "%CONSTANTS%", "\n".join(code_object_defs[codeobj.name]) ) h_code = getattr(codeobj.code, f"{block}_h_file") writer.write(f"code_objects/{block}_{codeobj.name}.cpp", cpp_code) writer.write(f"code_objects/{block}_{codeobj.name}.h", h_code) # Main code code = codeobj.code.cpp_file code = code.replace( "%CONSTANTS%", "\n".join(code_object_defs[codeobj.name]) ) writer.write(f"code_objects/{codeobj.name}.cpp", code) writer.write(f"code_objects/{codeobj.name}.h", codeobj.code.h_file) def generate_network_source(self, writer, compiler): maximum_run_time = self._maximum_run_time if maximum_run_time is not None: maximum_run_time = float(maximum_run_time) network_tmp = self.code_object_class().templater.network( None, None, maximum_run_time=maximum_run_time ) writer.write("network.*", network_tmp) def generate_synapses_classes_source(self, writer): synapses_classes_tmp = self.code_object_class().templater.synapses_classes( None, None ) writer.write("synapses_classes.*", synapses_classes_tmp) def generate_run_source(self, writer): run_tmp = self.code_object_class().templater.run( None, None, run_funcs=self.runfuncs, code_objects=list(self.code_objects.values()), user_headers=self.headers, array_specs=self.arrays, clocks=self.clocks, ) writer.write("run.*", run_tmp) def generate_makefile( self, writer, compiler, compiler_flags, linker_flags, nb_threads, debug ): if compiler == "msvc": if nb_threads > 1: openmp_flag = "/openmp" else: openmp_flag = "" if debug: compiler_debug_flags = "/DEBUG /DDEBUG" linker_debug_flags = "/DEBUG" else: compiler_debug_flags = "" linker_debug_flags = "" # Generate the visual studio makefile source_bases = [ fname.replace(".cpp", "").replace(".c", "").replace("/", "\\") for fname in sorted(writer.source_files) ] win_makefile_tmp = self.code_object_class().templater.win_makefile( None, None, source_files=sorted(writer.source_files), source_bases=source_bases, compiler_flags=compiler_flags, compiler_debug_flags=compiler_debug_flags, linker_flags=linker_flags, linker_debug_flags=linker_debug_flags, openmp_flag=openmp_flag, ) writer.write("win_makefile", win_makefile_tmp) # write the list of sources source_list = " ".join(source_bases) source_list_fname = os.path.join(self.project_dir, "sourcefiles.txt") if os.path.exists(source_list_fname): with open(source_list_fname) as f: if f.read() == source_list: return with open(source_list_fname, "w") as f: f.write(source_list) else: # Generate the makefile if os.name == "nt": rm_cmd = "del *.o /s\n\tdel main.exe $(DEPS)" else: rm_cmd = "rm $(OBJS) $(PROGRAM) $(DEPS)" if debug: compiler_debug_flags = "-g -DDEBUG" linker_debug_flags = "-g" else: compiler_debug_flags = "" linker_debug_flags = "" makefile_tmp = self.code_object_class().templater.makefile( None, None, source_files=" ".join(sorted(writer.source_files)), header_files=" ".join(sorted(writer.header_files)), compiler_flags=compiler_flags, compiler_debug_flags=compiler_debug_flags, linker_debug_flags=linker_debug_flags, linker_flags=linker_flags, rm_cmd=rm_cmd, ) writer.write("makefile", makefile_tmp) def copy_source_files(self, writer, directory): # Copy the brianlibdirectory brianlib_dir = os.path.join( os.path.split(inspect.getsourcefile(CPPStandaloneCodeObject))[0], "brianlib" ) brianlib_files = copy_directory( brianlib_dir, os.path.join(directory, "brianlib") ) for file in brianlib_files: if file.lower().endswith(".cpp"): writer.source_files.add(f"brianlib/{file}") elif file.lower().endswith(".h"): writer.header_files.add(f"brianlib/{file}") # Copy the CSpikeQueue implementation shutil.copy2( os.path.join( os.path.split(inspect.getsourcefile(Synapses))[0], "cspikequeue.cpp" ), os.path.join(directory, "brianlib", "spikequeue.h"), ) shutil.copy2( os.path.join( os.path.split(inspect.getsourcefile(Synapses))[0], "stdint_compat.h" ), os.path.join(directory, "brianlib", "stdint_compat.h"), ) # Copy the RandomKit implementation if not os.path.exists(os.path.join(directory, "brianlib", "randomkit")): os.mkdir(os.path.join(directory, "brianlib", "randomkit")) shutil.copy2( os.path.join( os.path.split(inspect.getsourcefile(brian2))[0], "random", "randomkit", "randomkit.c", ), os.path.join(directory, "brianlib", "randomkit", "randomkit.c"), ) shutil.copy2( os.path.join( os.path.split(inspect.getsourcefile(brian2))[0], "random", "randomkit", "randomkit.h", ), os.path.join(directory, "brianlib", "randomkit", "randomkit.h"), ) def _insert_func_namespace(self, func, code_object, namespace): impl = func.implementations[self.code_object_class()] func_namespace = impl.get_namespace(code_object.owner) if func_namespace is not None: namespace.update(func_namespace) if impl.dependencies is not None: for dep in impl.dependencies.values(): self._insert_func_namespace(dep, code_object, namespace) def write_static_arrays(self, directory): # Write Function namespaces as static arrays for code_object in self.code_objects.values(): for var in code_object.variables.values(): if isinstance(var, Function): self._insert_func_namespace(var, code_object, self.static_arrays) logger.diagnostic(f"static arrays: {str(sorted(self.static_arrays.keys()))}") static_array_specs = [] for name, arr in sorted(self.static_arrays.items()): arr.tofile(os.path.join(directory, "static_arrays", name)) static_array_specs.append((name, c_data_type(arr.dtype), arr.size, name)) self.static_array_specs = static_array_specs def compile_source(self, directory, compiler, debug, clean): with in_directory(directory): if compiler == "msvc": msvc_env, vcvars_cmd = get_msvc_env() make_cmd = "nmake /f win_makefile" make_args = " ".join( prefs.devices.cpp_standalone.extra_make_args_windows ) if os.path.exists("winmake.log"): os.remove("winmake.log") if vcvars_cmd: with open("winmake.log", "w") as f: f.write(f"{vcvars_cmd}\n") else: with open("winmake.log", "w") as f: f.write("MSVC environment: \n") for key, value in msvc_env.items(): f.write(f"{key}={value}\n") with std_silent(debug): if vcvars_cmd: if clean: start_time = time.time() os.system( f"{vcvars_cmd} >>winmake.log 2>&1 && {make_cmd} clean >" " NUL 2>&1" ) self.timers["compile"]["clean"] = time.time() - start_time start_time = time.time() x = os.system( f"{vcvars_cmd} >>winmake.log 2>&1 &&" f" {make_cmd} {make_args}>>winmake.log 2>&1" ) self.timers["compile"]["make"] = time.time() - start_time else: os.environ.update(msvc_env) if clean: start_time = time.time() os.system(f"{make_cmd} clean > NUL 2>&1") self.timers["compile"]["clean"] = time.time() - start_time start_time = time.time() x = os.system(f"{make_cmd} {make_args}>>winmake.log 2>&1") self.timers["compile"]["make"] = time.time() - start_time if x != 0: if os.path.exists("winmake.log"): with open("winmake.log") as f: print(f.read()) error_message = ( "Project compilation failed (error code: %u)." % x ) if not clean: error_message += ( " Consider running with " '"clean=True" to force a complete ' "rebuild." ) raise RuntimeError(error_message) else: with std_silent(debug): if clean: start_time = time.time() os.system("make clean >/dev/null 2>&1") self.timers["compile"]["clean"] = time.time() - start_time make_cmd = prefs.devices.cpp_standalone.make_cmd_unix make_args = " ".join( prefs.devices.cpp_standalone.extra_make_args_unix ) start_time = time.time() x = os.system(f"{make_cmd} {make_args}") self.timers["compile"]["make"] = time.time() - start_time if x != 0: error_message = ( "Project compilation failed (error code: %u)." % x ) if not clean: error_message += ( " Consider running with " '"clean=True" to force a complete ' "rebuild." ) raise RuntimeError(error_message) def seed(self, seed=None): """ Set the seed for the random number generator. Parameters ---------- seed : int, optional The seed value for the random number generator, or ``None`` (the default) to set a random seed. """ self.main_queue.append(("seed", seed)) def run(self, directory, with_output, run_args): with in_directory(directory): # Set environment variables for key, value in itertools.chain( prefs["devices.cpp_standalone.run_environment_variables"].items(), self.run_environment_variables.items(), ): if key in os.environ and os.environ[key] != value: logger.info( f'Overwriting environment variable "{key}"', name_suffix="overwritten_env_var", once=True, ) os.environ[key] = value if not with_output: stdout = open("results/stdout.txt", "w") else: stdout = None if os.name == "nt": start_time = time.time() x = subprocess.call(["main"] + run_args, stdout=stdout) self.timers["run_binary"] = time.time() - start_time else: run_cmd = prefs.devices.cpp_standalone.run_cmd_unix if isinstance(run_cmd, str): run_cmd = [run_cmd] start_time = time.time() x = subprocess.call(run_cmd + run_args, stdout=stdout) self.timers["run_binary"] = time.time() - start_time if stdout is not None: stdout.close() if x: if os.path.exists("results/stdout.txt"): with open("results/stdout.txt") as f: print(f.read()) raise RuntimeError( "Project run failed (project directory:" f" {os.path.abspath(directory)})" ) self.has_been_run = True if os.path.isfile("results/last_run_info.txt"): with open("results/last_run_info.txt") as f: last_run_info = f.read() run_time, completed_fraction = last_run_info.split() self._last_run_time = float(run_time) self._last_run_completed_fraction = float(completed_fraction) # Make sure that integration did not create NaN or very large values owners = [var.owner for var in self.arrays] # We don't want to check the same owner twice but var.owner is a # weakproxy which we can't put into a set. We therefore store the name # of all objects we already checked. Furthermore, under some specific # instances a variable might have been created whose owner no longer # exists (e.g. a `_sub_idx` variable for a subgroup) -- we ignore the # resulting reference error. already_checked = set() for owner in owners: try: if owner.name in already_checked: continue if isinstance(owner, Group): owner._check_for_invalid_states() already_checked.add(owner.name) except ReferenceError: pass def build( self, directory="output", compile=True, run=True, debug=False, clean=False, with_output=True, additional_source_files=None, run_args=None, direct_call=True, **kwds, ): """ Build the project TODO: more details Parameters ---------- directory : str, optional The output directory to write the project to, any existing files will be overwritten. If the given directory name is ``None``, then a temporary directory will be used (used in the test suite to avoid problems when running several tests in parallel). Defaults to ``'output'``. compile : bool, optional Whether or not to attempt to compile the project. Defaults to ``True``. run : bool, optional Whether or not to attempt to run the built project if it successfully builds. Defaults to ``True``. debug : bool, optional Whether to compile in debug mode. Defaults to ``False``. with_output : bool, optional Whether or not to show the ``stdout`` of the built program when run. Output will be shown in case of compilation or runtime error. Defaults to ``True``. clean : bool, optional Whether or not to clean the project before building. Defaults to ``False``. additional_source_files : list of str, optional A list of additional ``.cpp`` files to include in the build. direct_call : bool, optional Whether this function was called directly. Is used internally to distinguish an automatic build due to the ``build_on_run`` option from a manual ``device.build`` call. """ if self.build_on_run and direct_call: raise RuntimeError( "You used set_device with build_on_run=True " "(the default option), which will automatically " "build the simulation at the first encountered " "run call - do not call device.build manually " "in this case. If you want to call it manually, " "e.g. because you have multiple run calls, use " "set_device with build_on_run=False." ) if self.has_been_run: raise RuntimeError( "The network has already been built and run " "before. To build several simulations in " 'the same script, call "device.reinit()" ' 'and "device.activate()". Note that you ' "will have to set build options (e.g. the " "directory) and defaultclock.dt again." ) renames = { "project_dir": "directory", "compile_project": "compile", "run_project": "run", } if len(kwds): msg = "" for kwd in kwds: if kwd in renames: msg += ( f"Keyword argument '{kwd}' has been renamed to " f"'{renames[kwd]}'. " ) else: msg += f"Unknown keyword argument '{kwd}'. " raise TypeError(msg) if additional_source_files is None: additional_source_files = [] if run_args is None: run_args = [] if directory is None: directory = tempfile.mkdtemp(prefix="brian_standalone_") self.project_dir = directory ensure_directory(directory) # Determine compiler flags and directories compiler, default_extra_compile_args = get_compiler_and_args() extra_compile_args = self.extra_compile_args + default_extra_compile_args extra_link_args = self.extra_link_args + prefs["codegen.cpp.extra_link_args"] codeobj_define_macros = [ macro for codeobj in self.code_objects.values() for macro in codeobj.compiler_kwds.get("define_macros", []) ] define_macros = ( self.define_macros + prefs["codegen.cpp.define_macros"] + codeobj_define_macros ) codeobj_include_dirs = [ include_dir for codeobj in self.code_objects.values() for include_dir in codeobj.compiler_kwds.get("include_dirs", []) ] include_dirs = ( self.include_dirs + prefs["codegen.cpp.include_dirs"] + codeobj_include_dirs ) codeobj_library_dirs = [ library_dir for codeobj in self.code_objects.values() for library_dir in codeobj.compiler_kwds.get("library_dirs", []) ] library_dirs = ( self.library_dirs + prefs["codegen.cpp.library_dirs"] + codeobj_library_dirs ) codeobj_runtime_dirs = [ runtime_dir for codeobj in self.code_objects.values() for runtime_dir in codeobj.compiler_kwds.get("runtime_library_dirs", []) ] runtime_library_dirs = ( self.runtime_library_dirs + prefs["codegen.cpp.runtime_library_dirs"] + codeobj_runtime_dirs ) codeobj_libraries = [ library for codeobj in self.code_objects.values() for library in codeobj.compiler_kwds.get("libraries", []) ] libraries = self.libraries + prefs["codegen.cpp.libraries"] + codeobj_libraries compiler_obj = ccompiler.new_compiler(compiler=compiler) compiler_flags = ( ccompiler.gen_preprocess_options(define_macros, include_dirs) + extra_compile_args ) linker_flags = ( ccompiler.gen_lib_options( compiler_obj, library_dirs=library_dirs, runtime_library_dirs=runtime_library_dirs, libraries=libraries, ) + extra_link_args ) codeobj_source_files = [ source_file for codeobj in self.code_objects.values() for source_file in codeobj.compiler_kwds.get("sources", []) ] additional_source_files += codeobj_source_files + [ "brianlib/randomkit/randomkit.c" ] for d in ["code_objects", "results", "static_arrays"]: ensure_directory(os.path.join(directory, d)) self.writer = CPPWriter(directory) # Get the number of threads if specified in an openmp context nb_threads = prefs.devices.cpp_standalone.openmp_threads # If the number is negative, we need to throw an error if nb_threads < 0: raise ValueError("The number of OpenMP threads can not be negative !") logger.diagnostic( "Writing C++ standalone project to directory " f"'{os.path.normpath(directory)}'." ) self.check_openmp_compatible(nb_threads) self.write_static_arrays(directory) # Check that all names are globally unique names = [obj.name for net in self.networks for obj in net.sorted_objects] non_unique_names = [name for name, count in Counter(names).items() if count > 1] if len(non_unique_names): formatted_names = ", ".join(f"'{name}'" for name in non_unique_names) raise ValueError( "All objects need to have unique names in " "standalone mode, the following name(s) were used " f"more than once: {formatted_names}" ) self.generate_objects_source( self.writer, self.arange_arrays, self.synapses, self.static_array_specs, self.networks, ) self.generate_main_source(self.writer) self.generate_codeobj_source(self.writer) self.generate_network_source(self.writer, compiler) self.generate_synapses_classes_source(self.writer) self.generate_run_source(self.writer) self.copy_source_files(self.writer, directory) self.writer.source_files.update(additional_source_files) self.generate_makefile( self.writer, compiler, compiler_flags=" ".join(compiler_flags), linker_flags=" ".join(linker_flags), nb_threads=nb_threads, debug=debug, ) if compile: self.compile_source(directory, compiler, debug, clean) if run: self.run(directory, with_output, run_args) time_measurements = { "'make clean'": self.timers["compile"]["clean"], "'make'": self.timers["compile"]["make"], "running 'main'": self.timers["run_binary"], } logged_times = [ f"{task}: {measurement:.2f}s" for task, measurement in time_measurements.items() if measurement is not None ] logger.debug(f"Time measurements: {', '.join(logged_times)}") def delete(self, code=True, data=True, directory=True, force=False): if self.project_dir is None: return # Nothing to delete if directory and not (code and data): raise ValueError( "When deleting the directory, code and data will" "be deleted as well. Set the corresponding " "parameters to True." ) fnames = [] # Delete data if data: results_dir = os.path.join(self.project_dir, "results") logger.debug(f"Deleting data files in '{results_dir}'") fnames.append(os.path.join("results", "last_run_info.txt")) if self.profiled_codeobjects: fnames.append(os.path.join("results", "profiling_info.txt")) for var in self.arrays: fnames.append(self.get_array_filename(var)) # Delete code if code: logger.debug(f"Deleting code files in '{self.project_dir}'") if sys.platform == "win32": fnames.extend( [ "sourcefiles.txt", "win_makefile", "main.exe", "main.ilk", "main.pdb", "winmake.log", ] ) else: fnames.extend(["make.deps", "makefile", "main"]) fnames.extend( [ os.path.join("brianlib", "spikequeue.h"), os.path.join("brianlib", "randomkit", "randomkit.h"), os.path.join("brianlib", "stdint_compat.h"), ] ) fnames.extend(self.writer.header_files) for source_file in self.writer.source_files: fnames.append(source_file) base_name, _ = os.path.splitext(source_file) if sys.platform == "win32": fnames.append(f"{base_name}.obj") else: fnames.append(f"{base_name}.o") for static_array_name in self.static_arrays: fnames.append(os.path.join("static_arrays", static_array_name)) for fname in fnames: full_fname = os.path.join(self.project_dir, fname) try: os.remove(full_fname) except OSError as ex: logger.debug(f'File "{full_fname}" could not be deleted: {str(ex)}') # Delete directories if directory: directories = [ os.path.join("brianlib", "randomkit"), "brianlib", "code_objects", "results", "static_arrays", "", ] full_directories = [ os.path.join(self.project_dir, directory) for directory in directories ] for full_directory in full_directories: try: os.rmdir(full_directory) except OSError: if not os.path.exists(full_directory): continue # The directory is not empty: if force: logger.debug( 'Directory "{}" is not empty, but ' "deleting it due to the use of the force " "option." ) shutil.rmtree(full_directory) else: # We only give a warning if there is a file or a # directory we do not know about. We do not want to e.g. # complain about an unknown file in the results # directory and then again complain about the results # directory when deleting the main directory still_present = [ name for name in os.listdir(full_directory) if os.path.isfile(name) or os.path.join(full_directory, name) not in full_directories ] if len(still_present): still_present = ", ".join( f'"{name}"' for name in still_present ) logger.warn( f"Not deleting the '{full_directory}' directory, " "because it contains files/directories " f"not added by Brian: {still_present}", name_suffix="delete_skips_directory", ) def network_run( self, net, duration, report=None, report_period=10 * second, namespace=None, profile=None, level=0, **kwds, ): self.networks.add(net) if kwds: logger.warn( "Unsupported keyword argument(s) provided for run: %s" % ", ".join(kwds.keys()) ) # We store this as an instance variable for later access by the # `code_object` method self.enable_profiling = profile # Allow setting `profile` in the `set_device` call (used e.g. in brian2cuda # SpeedTest configurations) if profile is None: self.enable_profiling = self.build_options.get("profile", False) all_objects = net.sorted_objects net._clocks = {obj.clock for obj in all_objects} t_end = net.t + duration for clock in net._clocks: clock.set_interval(net.t, t_end) # Get the local namespace if namespace is None: namespace = get_local_namespace(level=level + 2) net.before_run(namespace) self.synapses |= {s for s in net.objects if isinstance(s, Synapses)} self.clocks.update(net._clocks) net.t_ = float(t_end) # TODO: remove this horrible hack for clock in self.clocks: if clock.name == "clock": clock._name = "_clock" # Extract all the CodeObjects # Note that since we ran the Network object, these CodeObjects will be sorted into the right # running order, assuming that there is only one clock code_objects = [] for obj in all_objects: if obj.active: for codeobj in obj._code_objects: code_objects.append((obj.clock, codeobj)) # Code for a progress reporting function standard_code = """ std::string _format_time(float time_in_s) { float divisors[] = {24*60*60, 60*60, 60, 1}; char letters[] = {'d', 'h', 'm', 's'}; float remaining = time_in_s; std::string text = ""; int time_to_represent; for (int i =0; i < sizeof(divisors)/sizeof(float); i++) { time_to_represent = int(remaining / divisors[i]); remaining -= time_to_represent * divisors[i]; if (time_to_represent > 0 || text.length()) { if(text.length() > 0) { text += " "; } text += (std::to_string(time_to_represent)+letters[i]); } } //less than one second if(text.length() == 0) { text = "< 1s"; } return text; } void report_progress(const double elapsed, const double completed, const double start, const double duration) { if (completed == 0.0) { %STREAMNAME% << "Starting simulation at t=" << start << " s for duration " << duration << " s"; } else { %STREAMNAME% << completed*duration << " s (" << (int)(completed*100.) << "%) simulated in " << _format_time(elapsed); if (completed < 1.0) { const int remaining = (int)((1-completed)/completed*elapsed+0.5); %STREAMNAME% << ", estimated " << _format_time(remaining) << " remaining."; } } %STREAMNAME% << std::endl << std::flush; } """ if report is None: report_func = "" elif report == "text" or report == "stdout": report_func = standard_code.replace("%STREAMNAME%", "std::cout") elif report == "stderr": report_func = standard_code.replace("%STREAMNAME%", "std::cerr") elif isinstance(report, str): report_func = """ void report_progress(const double elapsed, const double completed, const double start, const double duration) { %REPORT% } """.replace( "%REPORT%", report ) else: raise TypeError( "report argument has to be either 'text', " "'stdout', 'stderr', or the code for a report " "function" ) if report_func != "": if self.report_func != "" and report_func != self.report_func: raise NotImplementedError( "The C++ standalone device does not " "support multiple report functions, " "each run has to use the same (or " "none)." ) self.report_func = report_func if report is not None: report_call = "report_progress" else: report_call = "NULL" # Generate the updaters run_lines = [f"{net.name}.clear();"] all_clocks = set() for clock, codeobj in code_objects: run_lines.append(f"{net.name}.add(&{clock.name}, _run_{codeobj.name});") all_clocks.add(clock) # Under some rare circumstances (e.g. a NeuronGroup only defining a # subexpression that is used by other groups (via linking, or recorded # by a StateMonitor) *and* not calculating anything itself *and* using a # different clock than all other objects) a clock that is not used by # any code object should nevertheless advance during the run. We include # such clocks without a code function in the network. for clock in net._clocks: if clock not in all_clocks: run_lines.append(f"{net.name}.add(&{clock.name}, NULL);") run_lines.extend(self.code_lines["before_network_run"]) run_lines.append( f"{net.name}.run({float(duration)!r}, {report_call}," f" {float(report_period)!r});" ) run_lines.extend(self.code_lines["after_network_run"]) self.main_queue.append(("run_network", (net, run_lines))) net.after_run() # Manually set the cache for the clocks, simulation scripts might # want to access the time (which has been set in code and is therefore # not accessible by the normal means until the code has been built and # run) for clock in net._clocks: self.array_cache[clock.variables["timestep"]] = np.array([clock._i_end]) self.array_cache[clock.variables["t"]] = np.array( [clock._i_end * clock.dt_] ) if self.build_on_run: if self.has_been_run: raise RuntimeError( "The network has already been built and run " "before. Use set_device with " "build_on_run=False and an explicit " "device.build call to use multiple run " "statements with this device." ) self.build(direct_call=False, **self.build_options) def network_store(self, net, *args, **kwds): raise NotImplementedError( "The store/restore mechanism is not supported in the C++ standalone" ) def network_restore(self, net, *args, **kwds): raise NotImplementedError( "The store/restore mechanism is not supported in the C++ standalone" ) def network_get_profiling_info(self, net): fname = os.path.join(self.project_dir, "results", "profiling_info.txt") if not os.path.exists(fname): raise ValueError( "No profiling info collected (did you run with 'profile=True'?)" ) net._profiling_info = [] with open(fname) as f: for line in f: (key, val) = line.split() net._profiling_info.append((key, float(val) * second)) return sorted(net._profiling_info, key=lambda item: item[1], reverse=True) def run_function(self, name, include_in_parent=True): """ Context manager to divert code into a function Code that happens within the scope of this context manager will go into the named function. Parameters ---------- name : str The name of the function to divert code into. include_in_parent : bool Whether or not to include a call to the newly defined function in the parent context. """ return RunFunctionContext(name, include_in_parent) class RunFunctionContext: def __init__(self, name, include_in_parent): self.name = name self.include_in_parent = include_in_parent def __enter__(self): cpp_standalone_device.main_queue.append( ("start_run_func", (self.name, self.include_in_parent)) ) def __exit__(self, type, value, traceback): cpp_standalone_device.main_queue.append( ("end_run_func", (self.name, self.include_in_parent)) ) cpp_standalone_device = CPPStandaloneDevice() all_devices["cpp_standalone"] = cpp_standalone_device brian2-2.5.4/brian2/devices/cpp_standalone/templates/000077500000000000000000000000001445201106100224605ustar00rootroot00000000000000brian2-2.5.4/brian2/devices/cpp_standalone/templates/common_group.cpp000066400000000000000000000064151445201106100256760ustar00rootroot00000000000000{% macro before_run_cpp_file() %} #include "code_objects/before_run_{{codeobj_name}}.h" #include "objects.h" #include "brianlib/common_math.h" #include "brianlib/stdint_compat.h" #include #include #include #include #include {% for name in user_headers | sort %} #include {{name}} {% endfor %} ////// SUPPORT CODE /////// namespace { {{support_code_lines|autoindent}} } void _before_run_{{codeobj_name}}() { using namespace brian; ///// CONSTANTS /////////// %CONSTANTS% ///// POINTERS //////////// {{pointers_lines|autoindent}} {% block before_code %} // EMPTY_CODE_BLOCK -- will be overwritten in child templates {% endblock %} } {% endmacro %} {% macro before_run_h_file() %} #ifndef _INCLUDED_{{codeobj_name}}_before #define _INCLUDED_{{codeobj_name}}_before void _before_run_{{codeobj_name}}(); #endif {% endmacro %} {% macro cpp_file() %} #include "code_objects/{{codeobj_name}}.h" #include "objects.h" #include "brianlib/common_math.h" #include "brianlib/stdint_compat.h" #include #include #include #include #include {% block extra_headers %} {% endblock %} {% for name in user_headers | sort %} #include {{name}} {% endfor %} ////// SUPPORT CODE /////// namespace { {{support_code_lines|autoindent}} } ////// HASH DEFINES /////// {{hashdefine_lines|autoindent}} void _run_{{codeobj_name}}() { using namespace brian; {% if profiled %} {% if openmp_pragma('with_openmp') %} const double _start_time = omp_get_wtime(); {% else %} const std::clock_t _start_time = std::clock(); {% endif %} {% endif %} ///// CONSTANTS /////////// %CONSTANTS% ///// POINTERS //////////// {{pointers_lines|autoindent}} {% block maincode %} {# Will be overwritten in child templates #} {% endblock %} {% if profiled %} {% if openmp_pragma('with_openmp') %} const double _run_time = omp_get_wtime() -_start_time; {% else %} const double _run_time = (double)(std::clock() -_start_time)/CLOCKS_PER_SEC; {% endif %} {{codeobj_name}}_profiling_info += _run_time; {% endif %} } {% block extra_functions_cpp %} {% endblock %} {% endmacro %} {% macro h_file() %} #ifndef _INCLUDED_{{codeobj_name}} #define _INCLUDED_{{codeobj_name}} void _run_{{codeobj_name}}(); {% block extra_functions_h %} {% endblock %} #endif {% endmacro %} {% macro after_run_cpp_file() %} #include "objects.h" #include "code_objects/after_run_{{codeobj_name}}.h" #include "brianlib/common_math.h" #include "brianlib/stdint_compat.h" #include #include #include #include #include {% for name in user_headers | sort %} #include {{name}} {% endfor %} ////// SUPPORT CODE /////// namespace { {{support_code_lines|autoindent}} } void _after_run_{{codeobj_name}}() { using namespace brian; ///// CONSTANTS /////////// %CONSTANTS% ///// POINTERS //////////// {{pointers_lines|autoindent}} {% block after_code %} // EMPTY_CODE_BLOCK -- will be overwritten in child templates {% endblock %} } {% endmacro %} {% macro after_run_h_file() %} #ifndef _INCLUDED_{{codeobj_name}}_after #define _INCLUDED_{{codeobj_name}}_after void _after_run_{{codeobj_name}}(); #endif {% endmacro %}brian2-2.5.4/brian2/devices/cpp_standalone/templates/common_synapses.cpp000066400000000000000000000002141445201106100263760ustar00rootroot00000000000000{% extends 'common_group.cpp' %} {% block extra_headers %} #include "brianlib/stdint_compat.h" #include "synapses_classes.h" {% endblock %} brian2-2.5.4/brian2/devices/cpp_standalone/templates/group_variable_set.cpp000066400000000000000000000010301445201106100270320ustar00rootroot00000000000000{# USES_VARIABLES { _group_idx } #} {% extends 'common_group.cpp' %} {% block maincode %} //// MAIN CODE //////////// // scalar code const size_t _vectorisation_idx = -1; {{scalar_code|autoindent}} {{ openmp_pragma('parallel-static') }} for(int _idx_group_idx=0; _idx_group_idx<(int)_num_group_idx; _idx_group_idx++) { // vector code const size_t _idx = {{_group_idx}}[_idx_group_idx]; const size_t _vectorisation_idx = _idx; {{vector_code|autoindent}} } {% endblock %} brian2-2.5.4/brian2/devices/cpp_standalone/templates/group_variable_set_conditional.cpp000066400000000000000000000017231445201106100314260ustar00rootroot00000000000000{% extends 'common_group.cpp' %} {# USES_VARIABLES { N } #} {% block maincode %} //// MAIN CODE //////////// // scalar code const size_t _vectorisation_idx = -1; {# Note that the scalar_code['statement'] will not write to any scalar variables (except if the condition is simply 'True' and no vector code is present), it will only read in scalar variables that are used by the vector code. #} {{scalar_code['condition']|autoindent}} {{scalar_code['statement']|autoindent}} {# N is a constant in most cases (NeuronGroup, etc.), but a scalar array for synapses, we therefore have to take care to get its value in the right way. #} const int _N = {{constant_or_scalar('N', variables['N'])}}; {{ openmp_pragma('parallel-static') }} for(int _idx=0; _idx<_N; _idx++) { // vector code const size_t _vectorisation_idx = _idx; {{vector_code['condition']|autoindent}} if (_cond) { {{vector_code['statement']|autoindent}} } } {% endblock %} brian2-2.5.4/brian2/devices/cpp_standalone/templates/main.cpp000066400000000000000000000017151445201106100241140ustar00rootroot00000000000000#include #include "objects.h" #include #include {{ openmp_pragma('include') }} #include "run.h" #include "brianlib/common_math.h" #include "randomkit.h" {% for codeobj in code_objects | sort(attribute='name') %} #include "code_objects/{{codeobj.name}}.h" {% for block in codeobj.before_after_blocks %} #include "code_objects/{{block}}_{{codeobj.name}}.h" {% endfor %} {% endfor %} {% for name in user_headers | sort %} #include {{name}} {% endfor %} #include #include #include {{report_func|autoindent}} int main(int argc, char **argv) { {{'\n'.join(code_lines['before_start'])|autoindent}} brian_start(); {{'\n'.join(code_lines['after_start'])|autoindent}} { using namespace brian; {{ openmp_pragma('set_num_threads') }} {{main_lines|autoindent}} } {{'\n'.join(code_lines['before_end'])|autoindent}} brian_end(); {{'\n'.join(code_lines['after_end'])|autoindent}} return 0; } brian2-2.5.4/brian2/devices/cpp_standalone/templates/makefile000066400000000000000000000012541445201106100241620ustar00rootroot00000000000000PROGRAM = main SRCS = {{source_files}} H_SRCS = {{header_files}} OBJS = ${SRCS:.cpp=.o} OBJS := ${OBJS:.c=.o} OPTIMISATIONS = {{ compiler_flags }} CXXFLAGS = -c -Wno-write-strings $(OPTIMISATIONS) -I. {{ openmp_pragma('compilation') }} {{ compiler_debug_flags }} LFLAGS = {{ openmp_pragma('compilation') }} {{ linker_flags }} {{ linker_debug_flags }} DEPS = make.deps all: $(PROGRAM) .PHONY: all clean $(PROGRAM): $(OBJS) $(DEPS) makefile $(CXX) $(OBJS) -o $(PROGRAM) $(LFLAGS) clean: {{ rm_cmd }} make.deps: $(SRCS) $(H_SRCS) $(CXX) $(CXXFLAGS) -MM $(SRCS) > make.deps ifneq ($(wildcard $(DEPS)), ) include $(DEPS) endif %.o : %.cpp makefile $(CXX) $(CXXFLAGS) $< -o $@ brian2-2.5.4/brian2/devices/cpp_standalone/templates/network.cpp000066400000000000000000000120661445201106100246620ustar00rootroot00000000000000{% macro cpp_file() %} #include "network.h" #include #include #include #include {{ openmp_pragma('include') }} #define Clock_epsilon 1e-14 double Network::_last_run_time = 0.0; double Network::_last_run_completed_fraction = 0.0; Network::Network() { t = 0.0; } void Network::clear() { objects.clear(); } void Network::add(Clock* clock, codeobj_func func) { #if defined(_MSC_VER) && (_MSC_VER>=1700) objects.push_back(std::make_pair(std::move(clock), std::move(func))); #else objects.push_back(std::make_pair(clock, func)); #endif } void Network::run(const double duration, void (*report_func)(const double, const double, const double, const double), const double report_period) { {% if openmp_pragma('with_openmp') %} double start; {% else %} std::clock_t start, current; {% endif %} const double t_start = t; const double t_end = t + duration; double next_report_time = report_period; // compute the set of clocks compute_clocks(); // set interval for all clocks for(std::set::iterator i=clocks.begin(); i!=clocks.end(); i++) (*i)->set_interval(t, t_end); {% if openmp_pragma('with_openmp') %} start = omp_get_wtime(); {% else %} start = std::clock(); {% endif %} if (report_func) { report_func(0.0, 0.0, t_start, duration); } Clock* clock = next_clocks(); double elapsed_realtime; bool did_break_early = false; while(clock && clock->running()) { t = clock->t[0]; for(size_t i=0; i next_report_time) { report_func(elapsed, (clock->t[0]-t_start)/duration, t_start, duration); next_report_time += report_period; } } Clock *obj_clock = objects[i].first; // Only execute the object if it uses the right clock for this step if (curclocks.find(obj_clock) != curclocks.end()) { codeobj_func func = objects[i].second; if (func) // code objects can be NULL in cases where we store just the clock func(); } } for(std::set::iterator i=curclocks.begin(); i!=curclocks.end(); i++) (*i)->tick(); clock = next_clocks(); {% if openmp_pragma('with_openmp') %} elapsed_realtime = omp_get_wtime() - start; {% else %} current = std::clock(); elapsed_realtime = (double)(current - start)/({{ openmp_pragma('get_num_threads') }} * CLOCKS_PER_SEC); {% endif %} {% if maximum_run_time is not none %} if(elapsed_realtime>{{maximum_run_time}}) { did_break_early = true; break; } {% endif %} } if(!did_break_early) t = t_end; _last_run_time = elapsed_realtime; if(duration>0) { _last_run_completed_fraction = (t-t_start)/duration; } else { _last_run_completed_fraction = 1.0; } if (report_func) { report_func(elapsed_realtime, 1.0, t_start, duration); } } void Network::compute_clocks() { clocks.clear(); for(int i=0; i::iterator i=clocks.begin(); i!=clocks.end(); i++) { Clock *clock = *i; if(clock->t[0]t[0]) minclock = clock; } // find set of equal clocks curclocks.clear(); double t = minclock->t[0]; for(std::set::iterator i=clocks.begin(); i!=clocks.end(); i++) { Clock *clock = *i; double s = clock->t[0]; if(s==t || fabs(s-t)<=Clock_epsilon) curclocks.insert(clock); } return minclock; } {% endmacro %} {% macro h_file() %} #ifndef _BRIAN_NETWORK_H #define _BRIAN_NETWORK_H #include #include #include #include "brianlib/clocks.h" typedef void (*codeobj_func)(); class Network { std::set clocks, curclocks; void compute_clocks(); Clock* next_clocks(); public: std::vector< std::pair< Clock*, codeobj_func > > objects; double t; static double _last_run_time; static double _last_run_completed_fraction; Network(); void clear(); void add(Clock *clock, codeobj_func func); void run(const double duration, void (*report_func)(const double, const double, const double, const double), const double report_period); }; #endif {% endmacro %} brian2-2.5.4/brian2/devices/cpp_standalone/templates/objects.cpp000066400000000000000000000226631445201106100246260ustar00rootroot00000000000000{% macro cpp_file() %} #include "objects.h" #include "synapses_classes.h" #include "brianlib/clocks.h" #include "brianlib/dynamic_array.h" #include "brianlib/stdint_compat.h" #include "network.h" #include "randomkit.h" #include #include #include namespace brian { std::vector< rk_state* > _mersenne_twister_states; //////////////// networks ///////////////// {% for net in networks | sort(attribute='name') %} Network {{net.name}}; {% endfor %} //////////////// arrays /////////////////// {% for var, varname in array_specs | dictsort(by='value') %} {% if not var in dynamic_array_specs %} {{c_data_type(var.dtype)}} * {{varname}}; const int _num_{{varname}} = {{var.size}}; {% endif %} {% endfor %} //////////////// dynamic arrays 1d ///////// {% for var, varname in dynamic_array_specs | dictsort(by='value') %} std::vector<{{c_data_type(var.dtype)}}> {{varname}}; {% endfor %} //////////////// dynamic arrays 2d ///////// {% for var, varname in dynamic_array_2d_specs | dictsort(by='value') %} DynamicArray2D<{{c_data_type(var.dtype)}}> {{varname}}; {% endfor %} /////////////// static arrays ///////////// {% for (name, dtype_spec, N, filename) in static_array_specs | sort %} {# arrays that are initialized from static data are already declared #} {% if not (name in array_specs.values() or name in dynamic_array_specs.values() or name in dynamic_array_2d_specs.values())%} {{dtype_spec}} * {{name}}; const int _num_{{name}} = {{N}}; {% endif %} {% endfor %} //////////////// synapses ///////////////// {% for S in synapses | sort(attribute='name') %} // {{S.name}} {% for path in S._pathways | sort(attribute='name') %} SynapticPathway {{path.name}}( {{dynamic_array_specs[path.synapse_sources]}}, {{path.source.start}}, {{path.source.stop}}); {% endfor %} {% endfor %} //////////////// clocks /////////////////// {% for clock in clocks | sort(attribute='name') %} Clock {{clock.name}}; // attributes will be set in run.cpp {% endfor %} {% if profiled_codeobjects is defined %} // Profiling information for each code object {% for codeobj in profiled_codeobjects | sort %} double {{codeobj}}_profiling_info = 0.0; {% endfor %} {% endif %} } void _init_arrays() { using namespace brian; // Arrays initialized to 0 {% for var, varname in zero_arrays | sort(attribute='1') %} {% if varname in dynamic_array_specs.values() %} {{varname}}.resize({{var.size}}); {% else %} {{varname}} = new {{c_data_type(var.dtype)}}[{{var.size}}]; {% endif %} {{ openmp_pragma('parallel-static')}} for(int i=0; i<{{var.size}}; i++) {{varname}}[i] = 0; {% endfor %} // Arrays initialized to an "arange" {% for var, varname, start in arange_arrays | sort(attribute='1')%} {% if varname in dynamic_array_specs.values() %} {{varname}}.resize({{var.size}}); {% else %} {{varname}} = new {{c_data_type(var.dtype)}}[{{var.size}}]; {% endif %} {{ openmp_pragma('parallel-static')}} for(int i=0; i<{{var.size}}; i++) {{varname}}[i] = {{start}} + i; {% endfor %} // static arrays {% for (name, dtype_spec, N, filename) in static_array_specs | sort %} {% if name in dynamic_array_specs.values() %} {{name}}.resize({{N}}); {% else %} {{name}} = new {{dtype_spec}}[{{N}}]; {% endif %} {% endfor %} // Random number generator states for (int i=0; i<{{openmp_pragma('get_num_threads')}}; i++) _mersenne_twister_states.push_back(new rk_state()); } void _load_arrays() { using namespace brian; {% for (name, dtype_spec, N, filename) in static_array_specs | sort %} ifstream f{{name}}; f{{name}}.open("static_arrays/{{name}}", ios::in | ios::binary); if(f{{name}}.is_open()) { {% if name in dynamic_array_specs.values() %} f{{name}}.read(reinterpret_cast(&{{name}}[0]), {{N}}*sizeof({{dtype_spec}})); {% else %} f{{name}}.read(reinterpret_cast({{name}}), {{N}}*sizeof({{dtype_spec}})); {% endif %} } else { std::cout << "Error opening static array {{name}}." << endl; } {% endfor %} } void _write_arrays() { using namespace brian; {% for var, varname in array_specs | dictsort(by='value') %} {% if not (var in dynamic_array_specs or var in dynamic_array_2d_specs) %} ofstream outfile_{{varname}}; outfile_{{varname}}.open("{{get_array_filename(var) | replace('\\', '\\\\')}}", ios::binary | ios::out); if(outfile_{{varname}}.is_open()) { outfile_{{varname}}.write(reinterpret_cast({{varname}}), {{var.size}}*sizeof({{get_array_name(var)}}[0])); outfile_{{varname}}.close(); } else { std::cout << "Error writing output file for {{varname}}." << endl; } {% endif %} {% endfor %} {% for var, varname in dynamic_array_specs | dictsort(by='value') %} ofstream outfile_{{varname}}; outfile_{{varname}}.open("{{get_array_filename(var) | replace('\\', '\\\\')}}", ios::binary | ios::out); if(outfile_{{varname}}.is_open()) { if (! {{varname}}.empty() ) { outfile_{{varname}}.write(reinterpret_cast(&{{varname}}[0]), {{varname}}.size()*sizeof({{varname}}[0])); outfile_{{varname}}.close(); } } else { std::cout << "Error writing output file for {{varname}}." << endl; } {% endfor %} {% for var, varname in dynamic_array_2d_specs | dictsort(by='value') %} ofstream outfile_{{varname}}; outfile_{{varname}}.open("{{get_array_filename(var) | replace('\\', '\\\\')}}", ios::binary | ios::out); if(outfile_{{varname}}.is_open()) { for (int n=0; n<{{varname}}.n; n++) { if (! {{varname}}(n).empty()) { outfile_{{varname}}.write(reinterpret_cast(&{{varname}}(n, 0)), {{varname}}.m*sizeof({{varname}}(0, 0))); } } outfile_{{varname}}.close(); } else { std::cout << "Error writing output file for {{varname}}." << endl; } {% endfor %} {% if profiled_codeobjects is defined and profiled_codeobjects %} // Write profiling info to disk ofstream outfile_profiling_info; outfile_profiling_info.open("results/profiling_info.txt", ios::out); if(outfile_profiling_info.is_open()) { {% for codeobj in profiled_codeobjects | sort %} outfile_profiling_info << "{{codeobj}}\t" << {{codeobj}}_profiling_info << std::endl; {% endfor %} outfile_profiling_info.close(); } else { std::cout << "Error writing profiling info to file." << std::endl; } {% endif %} // Write last run info to disk ofstream outfile_last_run_info; outfile_last_run_info.open("results/last_run_info.txt", ios::out); if(outfile_last_run_info.is_open()) { outfile_last_run_info << (Network::_last_run_time) << " " << (Network::_last_run_completed_fraction) << std::endl; outfile_last_run_info.close(); } else { std::cout << "Error writing last run info to file." << std::endl; } } void _dealloc_arrays() { using namespace brian; {% for var, varname in array_specs | dictsort(by='value') %} {% if varname in dynamic_array_specs.values() %} if({{varname}}!=0) { delete [] {{varname}}; {{varname}} = 0; } {% endif %} {% endfor %} // static arrays {% for (name, dtype_spec, N, filename) in static_array_specs | sort %} {% if not name in dynamic_array_specs.values() %} if({{name}}!=0) { delete [] {{name}}; {{name}} = 0; } {% endif %} {% endfor %} } {% endmacro %} ///////////////////////////////////////////////////////////////////////////////////////////////////// {% macro h_file() %} #ifndef _BRIAN_OBJECTS_H #define _BRIAN_OBJECTS_H #include "synapses_classes.h" #include "brianlib/clocks.h" #include "brianlib/dynamic_array.h" #include "brianlib/stdint_compat.h" #include "network.h" #include "randomkit.h" #include {{ openmp_pragma('include') }} namespace brian { // In OpenMP we need one state per thread extern std::vector< rk_state* > _mersenne_twister_states; //////////////// clocks /////////////////// {% for clock in clocks | sort(attribute='name') %} extern Clock {{clock.name}}; {% endfor %} //////////////// networks ///////////////// {% for net in networks | sort(attribute='name') %} extern Network {{net.name}}; {% endfor %} //////////////// dynamic arrays /////////// {% for var, varname in dynamic_array_specs | dictsort(by='value') %} extern std::vector<{{c_data_type(var.dtype)}}> {{varname}}; {% endfor %} //////////////// arrays /////////////////// {% for var, varname in array_specs | dictsort(by='value') %} {% if not var in dynamic_array_specs %} extern {{c_data_type(var.dtype)}} *{{varname}}; extern const int _num_{{varname}}; {% endif %} {% endfor %} //////////////// dynamic arrays 2d ///////// {% for var, varname in dynamic_array_2d_specs | dictsort(by='value') %} extern DynamicArray2D<{{c_data_type(var.dtype)}}> {{varname}}; {% endfor %} /////////////// static arrays ///////////// {% for (name, dtype_spec, N, filename) in static_array_specs | sort(attribute='0') %} {# arrays that are initialized from static data are already declared #} {% if not (name in array_specs.values() or name in dynamic_array_specs.values() or name in dynamic_array_2d_specs.values())%} extern {{dtype_spec}} *{{name}}; extern const int _num_{{name}}; {% endif %} {% endfor %} //////////////// synapses ///////////////// {% for S in synapses | sort(attribute='name') %} // {{S.name}} {% for path in S._pathways | sort(attribute='name') %} extern SynapticPathway {{path.name}}; {% endfor %} {% endfor %} {% if profiled_codeobjects is defined %} // Profiling information for each code object {% for codeobj in profiled_codeobjects | sort %} extern double {{codeobj}}_profiling_info; {% endfor %} {% endif %} } void _init_arrays(); void _load_arrays(); void _write_arrays(); void _dealloc_arrays(); #endif {% endmacro %} brian2-2.5.4/brian2/devices/cpp_standalone/templates/ratemonitor.cpp000066400000000000000000000022121445201106100255240ustar00rootroot00000000000000{# USES_VARIABLES { N, rate, t, _spikespace, _clock_t, _clock_dt, _num_source_neurons, _source_start, _source_stop } #} {# WRITES_TO_READ_ONLY_VARIABLES { N } #} {% extends 'common_group.cpp' %} {% block maincode %} size_t _num_spikes = {{_spikespace}}[_num_spikespace-1]; // For subgroups, we do not want to record all spikes // We assume that spikes are ordered int _start_idx = -1; int _end_idx = -1; for(size_t _j=0; _j<_num_spikes; _j++) { const size_t _idx = {{_spikespace}}[_j]; if (_idx >= _source_start) { _start_idx = _j; break; } } if (_start_idx == -1) _start_idx = _num_spikes; for(size_t _j=_start_idx; _j<_num_spikes; _j++) { const size_t _idx = {{_spikespace}}[_j]; if (_idx >= _source_stop) { _end_idx = _j; break; } } if (_end_idx == -1) _end_idx =_num_spikes; _num_spikes = _end_idx - _start_idx; {{_dynamic_rate}}.push_back(1.0*_num_spikes/{{_clock_dt}}/_num_source_neurons); {{_dynamic_t}}.push_back({{_clock_t}}); {{N}}++; {% endblock %} brian2-2.5.4/brian2/devices/cpp_standalone/templates/reset.cpp000066400000000000000000000013211445201106100243030ustar00rootroot00000000000000{# USES_VARIABLES { N } #} {% extends 'common_group.cpp' %} {% block maincode %} {# Get the name of the array that stores these events (e.g. the spikespace array) #} {% set _eventspace = get_array_name(eventspace_variable) %} const int32_t *_events = {{_eventspace}}; const int32_t _num_events = {{_eventspace}}[N]; //// MAIN CODE //////////// // scalar code const size_t _vectorisation_idx = -1; {{scalar_code|autoindent}} {{ openmp_pragma('parallel-static') }} for(int32_t _index_events=0; _index_events<_num_events; _index_events++) { // vector code const size_t _idx = _events[_index_events]; const size_t _vectorisation_idx = _idx; {{vector_code|autoindent}} } {% endblock %} brian2-2.5.4/brian2/devices/cpp_standalone/templates/run.cpp000066400000000000000000000026171445201106100237760ustar00rootroot00000000000000{% macro cpp_file() %} #include #include "objects.h" #include #include "randomkit.h" {% for codeobj in code_objects | sort(attribute='name') %} #include "code_objects/{{codeobj.name}}.h" {% endfor %} {% for name in user_headers | sort %} #include {{name}} {% endfor %} void brian_start() { _init_arrays(); _load_arrays(); // Initialize clocks (link timestep and dt to the respective arrays) {% for clock in clocks | sort(attribute='name') %} brian::{{clock.name}}.timestep = brian::{{array_specs[clock.variables['timestep']]}}; brian::{{clock.name}}.dt = brian::{{array_specs[clock.variables['dt']]}}; brian::{{clock.name}}.t = brian::{{array_specs[clock.variables['t']]}}; {% endfor %} for (int i=0; i<{{openmp_pragma('get_num_threads')}}; i++) rk_randomseed(brian::_mersenne_twister_states[i]); // Note that this seed can be potentially replaced in main.cpp } void brian_end() { _write_arrays(); _dealloc_arrays(); } {% for name, lines in run_funcs.items() | sort(attribute='name') %} void {{name}}() { using namespace brian; {{lines|autoindent}} } {% endfor %} {% endmacro %} ///////////////////////////////////////////////////////////////////////////////////////////////////// {% macro h_file() %} void brian_start(); void brian_end(); {% for name, lines in run_funcs.items() | sort(attribute='name') %} void {{name}}(); {% endfor %} {% endmacro %} brian2-2.5.4/brian2/devices/cpp_standalone/templates/spatialstateupdate.cpp000066400000000000000000000220361445201106100270700ustar00rootroot00000000000000{# USES_VARIABLES { Cm, dt, v, N, Ic, Ri, _ab_star0, _ab_star1, _ab_star2, _b_plus, _b_minus, _v_star, _u_plus, _u_minus, _v_previous, _gtot_all, _I0_all, _c, _P_diag, _P_parent, _P_children, _B, _morph_parent_i, _starts, _ends, _morph_children, _morph_children_num, _morph_idxchild, _invr0, _invrn, _invr, r_length_1, r_length_2, area } #} {% extends 'common_group.cpp' %} {% block before_code %} const double _Ri = {{Ri}}; // Ri is a shared variable // Inverse axial resistance {{ openmp_pragma('parallel-static') }} for (int _i=1; _i _v_star (solution) {{_u_plus}}[_j]={{_b_plus}}[_j]; // RHS -> _u_plus (solution) {{_u_minus}}[_j]={{_b_minus}}[_j]; // RHS -> _u_minus (solution) _bi={{_ab_star1}}[_j]-{{_gtot_all}}[_j]; // main diagonal if (_j0) { _ai={{_ab_star2}}[_j-1]; // subdiagonal _m=1.0/(_bi-_ai*{{_c}}[_j-1]); {{_c}}[_j]={{_c}}[_j]*_m; {{_v_star}}[_j]=({{_v_star}}[_j] - _ai*{{_v_star}}[_j-1])*_m; {{_u_plus}}[_j]=({{_u_plus}}[_j] - _ai*{{_u_plus}}[_j-1])*_m; {{_u_minus}}[_j]=({{_u_minus}}[_j] - _ai*{{_u_minus}}[_j-1])*_m; } else { {{_c}}[0]={{_c}}[0]/_bi; {{_v_star}}[0]={{_v_star}}[0]/_bi; {{_u_plus}}[0]={{_u_plus}}[0]/_bi; {{_u_minus}}[0]={{_u_minus}}[0]/_bi; } } // backwards substituation of the upper triangularized system for _v_star for(int _j=_j_end-2; _j>=_j_start; _j--) { {{_v_star}}[_j]={{_v_star}}[_j] - {{_c}}[_j]*{{_v_star}}[_j+1]; {{_u_plus}}[_j]={{_u_plus}}[_j] - {{_c}}[_j]*{{_u_plus}}[_j+1]; {{_u_minus}}[_j]={{_u_minus}}[_j] - {{_c}}[_j]*{{_u_minus}}[_j+1]; } } // STEP 3: solve the coupling system // indexing for _P_children which contains the elements above the diagonal of the coupling matrix _P const int _children_rowlength = _num_morph_children/_num_morph_children_num; #define _IDX_C(idx_row,idx_col) _children_rowlength * idx_row + idx_col // STEP 3a: construct the coupling system with matrix _P in sparse form. s.t. // _P_diag contains the diagonal elements // _P_children contains the super diagonal entries // _P_parent contains the single sub diagonal entry for each row // _B contains the right hand side for (size_t _i=0; _i<_num_B - 1; _i++) { const int _i_parent = {{_morph_parent_i}}[_i]; const int _i_childind = {{_morph_idxchild}}[_i]; const int _first = {{_starts}}[_i]; const int _last = {{_ends}}[_i] - 1; // the compartment indices are in the interval [starts, ends[ const double _invr0 = {{_invr0}}[_i]; const double _invrn = {{_invrn}}[_i]; // Towards parent if (_i == 0) // first section, sealed end { // sparse matrix version {{_P_diag}}[0] = {{_u_minus}}[_first] - 1; {{_P_children}}[_IDX_C(0,0)] = {{_u_plus}}[_first]; // RHS {{_B}}[0] = -{{_v_star}}[_first]; } else { // sparse matrix version {{_P_diag}}[_i_parent] += (1 - {{_u_minus}}[_first]) * _invr0; {{_P_children}}[_IDX_C(_i_parent, _i_childind)] = -{{_u_plus}}[_first] * _invr0; // RHS {{_B}}[_i_parent] += {{_v_star}}[_first] * _invr0; } // Towards children // sparse matrix version {{_P_diag}}[_i+1] = (1 - {{_u_plus}}[_last]) * _invrn; {{_P_parent}}[_i] = -{{_u_minus}}[_last] * _invrn; // RHS {{_B}}[_i+1] = {{_v_star}}[_last] * _invrn; } // STEP 3b: solve the linear system (the result will be stored in the former rhs _B in the end) // use efficient O(n) solution of the sparse linear system (structure-specific Gaussian elemination) // part 1: lower triangularization for (int _i=_num_B-1; _i>=0; _i--) { const int _num_children = {{_morph_children_num}}[_i]; // for every child eliminate the corresponding matrix element of row i for (size_t _k=0; _k<_num_children; _k++) { int _j = {{_morph_children}}[_IDX_C(_i,_k)]; // child index // subtracting _subfac times the j-th from the i-th row double _subfac = {{_P_children}}[_IDX_C(_i,_k)] / {{_P_diag}}[_j]; // element i,j appears only here // the following commented (superdiagonal) element is not used in the following anymore since // it is 0 by definition of (lower) triangularization; we keep it here for algorithmic clarity //{{_P_children}}[_IDX_C(_i,_k)] = {{_P_children}}[_IDX_C(_i,_k)] - _subfac * {{_P_diag}}[_j]; // = 0; {{_P_diag}}[_i] = {{_P_diag}}[_i] - _subfac * {{_P_parent}}[_j-1]; // note: element j,i is only used here {{_B}}[_i] = {{_B}}[_i] - _subfac * {{_B}}[_j]; } } // part 2: forwards substitution {{_B}}[0] = {{_B}}[0] / {{_P_diag}}[0]; // the first section does not have a parent for (int _i=1; _i<_num_B; _i++) { const int _j = {{_morph_parent_i}}[_i-1]; // parent index {{_B}}[_i] = {{_B}}[_i] - {{_P_parent}}[_i-1] * {{_B}}[_j]; {{_B}}[_i] = {{_B}}[_i] / {{_P_diag}}[_i]; } // STEP 4: for each section compute the final solution by linear // combination of the general solution (independent: sections & compartments) for (size_t _i=0; _i<_num_B - 1; _i++) { const int _i_parent = {{_morph_parent_i}}[_i]; const int _j_start = {{_starts}}[_i]; const int _j_end = {{_ends}}[_i]; for (int _j=_j_start; _j<_j_end; _j++) if (_j < _numv) // don't go beyond the last element {{v}}[_j] = {{_v_star}}[_j] + {{_B}}[_i_parent] * {{_u_minus}}[_j] + {{_B}}[_i+1] * {{_u_plus}}[_j]; } {{ openmp_pragma('parallel-static') }} for (int _i=0; _i 0) { _timebin %= _the_period; // If there is a periodicity in the SpikeGenerator, we need to reset the // lastindex when the period has passed if ({{_lastindex}} > 0 && {{_timebins}}[{{_lastindex}} - 1] >= _timebin) {{_lastindex}} = 0; } int32_t _cpp_numspikes = 0; for(size_t _idx={{_lastindex}}; _idx < _num_timebins; _idx++) { if ({{_timebins}}[_idx] > _timebin) break; {{_spikespace}}[_cpp_numspikes++] = {{neuron_index}}[_idx]; } {{_spikespace}}[N] = _cpp_numspikes; {{_lastindex}} += _cpp_numspikes; {% endblock %}brian2-2.5.4/brian2/devices/cpp_standalone/templates/spikemonitor.cpp000066400000000000000000000042661445201106100257170ustar00rootroot00000000000000{# USES_VARIABLES { N, _clock_t, count, _source_start, _source_stop} #} {# WRITES_TO_READ_ONLY_VARIABLES { N, count } #} {% extends 'common_group.cpp' %} {% block maincode %} //// MAIN CODE //////////// {# Get the name of the array that stores these events (e.g. the spikespace array) #} {% set _eventspace = get_array_name(eventspace_variable) %} int32_t _num_events = {{_eventspace}}[_num{{eventspace_variable.name}}-1]; if (_num_events > 0) { size_t _start_idx = _num_events; size_t _end_idx = _num_events; for(size_t _j=0; _j<_num_events; _j++) { const int _idx = {{_eventspace}}[_j]; if (_idx >= _source_start) { _start_idx = _j; break; } } for(size_t _j=_num_events-1; _j>=_start_idx; _j--) { const int _idx = {{_eventspace}}[_j]; if (_idx < _source_stop) { break; } _end_idx = _j; } _num_events = _end_idx - _start_idx; if (_num_events > 0) { const size_t _vectorisation_idx = 1; {{scalar_code|autoindent}} for(size_t _j=_start_idx; _j<_end_idx; _j++) { const size_t _idx = {{_eventspace}}[_j]; const size_t _vectorisation_idx = _idx; {{vector_code|autoindent}} {% for varname, var in record_variables | dictsort %} {{get_array_name(var, access_data=False)}}.push_back(_to_record_{{varname}}); {% endfor %} {{count}}[_idx-_source_start]++; } {{N}} += _num_events; } } {% endblock %} {% block extra_functions_cpp %} void _debugmsg_{{codeobj_name}}() { using namespace brian; {# We need the pointers and constants here to get the access to N working #} %CONSTANTS% {{pointers_lines|autoindent}} std::cout << "Number of spikes: " << {{N}} << endl; } {% endblock %} {% block extra_functions_h %} void _debugmsg_{{codeobj_name}}(); {% endblock %} {% macro main_finalise() %} #ifdef DEBUG _debugmsg_{{codeobj_name}}(); #endif {% endmacro %} brian2-2.5.4/brian2/devices/cpp_standalone/templates/statemonitor.cpp000066400000000000000000000024761445201106100257250ustar00rootroot00000000000000{# USES_VARIABLES { t, _clock_t, _indices, N } #} {# WRITES_TO_READ_ONLY_VARIABLES { t, N } #} {% extends 'common_group.cpp' %} {% block maincode %} {{_dynamic_t}}.push_back({{_clock_t}}); const size_t _new_size = {{_dynamic_t}}.size(); // Resize the dynamic arrays {% for varname, var in _recorded_variables | dictsort %} {% set _recorded = get_array_name(var, access_data=False) %} {{_recorded}}.resize(_new_size, _num_indices); {% endfor %} // scalar code const size_t _vectorisation_idx = -1; {{scalar_code|autoindent}} {{ openmp_pragma('parallel-static') }} for (int _i = 0; _i < (int)_num_indices; _i++) { // vector code const size_t _idx = {{_indices}}[_i]; const size_t _vectorisation_idx = _idx; {{vector_code|autoindent}} {% for varname, var in _recorded_variables | dictsort %} {% set _recorded = get_array_name(var, access_data=False) %} {% if c_data_type(var.dtype) == 'bool' %} {{ openmp_pragma('critical') }} { // std::vector is not threadsafe {{_recorded}}(_new_size-1, _i) = _to_record_{{varname}}; } {% else %} {{_recorded}}(_new_size-1, _i) = _to_record_{{varname}}; {% endif %} {% endfor %} } {{N}} = _new_size; {% endblock %} brian2-2.5.4/brian2/devices/cpp_standalone/templates/stateupdate.cpp000066400000000000000000000012511445201106100255060ustar00rootroot00000000000000{# USES_VARIABLES { N } #} {# ALLOWS_SCALAR_WRITE #} {% extends 'common_group.cpp' %} {% block maincode %} //// MAIN CODE //////////// // scalar code const size_t _vectorisation_idx = -1; {{scalar_code|autoindent}} {# N is a constant in most cases (NeuronGroup, etc.), but a scalar array for synapses, we therefore have to take care to get its value in the right way. #} const int _N = {{constant_or_scalar('N', variables['N'])}}; {{openmp_pragma('parallel-static')}} for(int _idx=0; _idx<_N; _idx++) { // vector code const size_t _vectorisation_idx = _idx; {{vector_code|autoindent}} } {% endblock %}brian2-2.5.4/brian2/devices/cpp_standalone/templates/summed_variable.cpp000066400000000000000000000017311445201106100263250ustar00rootroot00000000000000{# USES_VARIABLES { N } #} {% extends 'common_group.cpp' %} {% block maincode %} {% set _target_var_array = get_array_name(_target_var) %} {% set _index_array = get_array_name(_index_var) %} //// MAIN CODE //////////// {# This enables summed variables for connections to a synapse #} const int _target_size = {{constant_or_scalar(_target_size_name, variables[_target_size_name])}}; // Set all the target variable values to zero {{ openmp_pragma('parallel-static') }} for (int _target_idx=0; _target_idx<_target_size; _target_idx++) { {{_target_var_array}}[_target_idx + {{_target_start}}] = 0; } // scalar code const size_t _vectorisation_idx = -1; {{scalar_code|autoindent}} for(int _idx=0; _idx<{{N}}; _idx++) { // vector code const size_t _vectorisation_idx = _idx; {{vector_code|autoindent}} {{_target_var_array}}[{{_index_array}}[_idx]] += _synaptic_var; } {% endblock %} brian2-2.5.4/brian2/devices/cpp_standalone/templates/synapses.cpp000066400000000000000000000034471445201106100250410ustar00rootroot00000000000000{% extends 'common_synapses.cpp' %} {% set _non_synaptic = [] %} {% for var in variables %} {% if variable_indices[var] not in ('_idx', '0') %} {# This is a trick to get around the scoping problem #} {% if _non_synaptic.append(1) %}{% endif %} {% endif %} {% endfor %} {% block maincode %} // This is only needed for the _debugmsg function below {# USES_VARIABLES { _synaptic_pre } #} // scalar code const size_t _vectorisation_idx = -1; {{scalar_code|autoindent}} {{ openmp_pragma('parallel') }} { std::vector *_spiking_synapses = {{pathway.name}}.peek(); const int _num_spiking_synapses = _spiking_synapses->size(); {% if _non_synaptic %} {{ openmp_pragma('master') }} { for(int _spiking_synapse_idx=0; _spiking_synapse_idx<_num_spiking_synapses; _spiking_synapse_idx++) { const size_t _idx = (*_spiking_synapses)[_spiking_synapse_idx]; const size_t _vectorisation_idx = _idx; {{vector_code|autoindent}} } } {% else %} {{ openmp_pragma('static') }} for(int _spiking_synapse_idx=0; _spiking_synapse_idx<_num_spiking_synapses; _spiking_synapse_idx++) { const size_t _idx = (*_spiking_synapses)[_spiking_synapse_idx]; const size_t _vectorisation_idx = _idx; {{vector_code|autoindent}} } {% endif %} } {% endblock %} {% block extra_functions_cpp %} void _debugmsg_{{codeobj_name}}() { using namespace brian; std::cout << "Number of synapses: " << {{_dynamic__synaptic_pre}}.size() << endl; } {% endblock %} {% block extra_functions_h %} void _debugmsg_{{codeobj_name}}(); {% endblock %} {% macro main_finalise() -%} #ifdef DEBUG _debugmsg_{{codeobj_name}}(); #endif {% endmacro %} brian2-2.5.4/brian2/devices/cpp_standalone/templates/synapses_classes.cpp000066400000000000000000000050451445201106100265520ustar00rootroot00000000000000{% macro cpp_file() %} {% endmacro %} {% macro h_file() %} #ifndef _BRIAN_SYNAPSES_H #define _BRIAN_SYNAPSES_H #include #include {{ openmp_pragma('include') }} #include "brianlib/spikequeue.h" class SynapticPathway { public: int Nsource, Ntarget, _nb_threads; std::vector &sources; std::vector all_peek; std::vector< CSpikeQueue * > queue; SynapticPathway(std::vector &_sources, int _spikes_start, int _spikes_stop) : sources(_sources) { _nb_threads = {{ openmp_pragma('get_num_threads') }}; for (int _idx=0; _idx < _nb_threads; _idx++) queue.push_back(new CSpikeQueue(_spikes_start, _spikes_stop)); }; ~SynapticPathway() { for (int _idx=0; _idx < _nb_threads; _idx++) delete(queue[_idx]); } void push(int *spikes, int nspikes) { queue[{{ openmp_pragma('get_thread_num') }}]->push(spikes, nspikes); } void advance() { queue[{{ openmp_pragma('get_thread_num') }}]->advance(); } vector* peek() { {{ openmp_pragma('static-ordered') }} for(int _thread=0; _thread < {{ openmp_pragma('get_num_threads') }}; _thread++) { {{ openmp_pragma('ordered') }} { if (_thread == 0) all_peek.clear(); all_peek.insert(all_peek.end(), queue[_thread]->peek()->begin(), queue[_thread]->peek()->end()); } } return &all_peek; } template void prepare(int n_source, int n_target, scalar *real_delays, int n_delays, int *sources, int n_synapses, double _dt) { Nsource = n_source; Ntarget = n_target; {{ openmp_pragma('parallel') }} { int length; if ({{ openmp_pragma('get_thread_num') }} == _nb_threads - 1) length = n_synapses - (int){{ openmp_pragma('get_thread_num') }}*(n_synapses/_nb_threads); else length = (int) n_synapses/_nb_threads; int padding = {{ openmp_pragma('get_thread_num') }}*(n_synapses/_nb_threads); queue[{{ openmp_pragma('get_thread_num') }}]->openmp_padding = padding; if (n_delays > 1) queue[{{ openmp_pragma('get_thread_num') }}]->prepare(&real_delays[padding], length, &sources[padding], length, _dt); else if (n_delays == 1) queue[{{ openmp_pragma('get_thread_num') }}]->prepare(&real_delays[0], 1, &sources[padding], length, _dt); else // no synapses queue[{{ openmp_pragma('get_thread_num') }}]->prepare((scalar *)NULL, 0, &sources[padding], length, _dt); } } }; #endif {% endmacro %}brian2-2.5.4/brian2/devices/cpp_standalone/templates/synapses_create_array.cpp000066400000000000000000000045651445201106100275640ustar00rootroot00000000000000{# USES_VARIABLES { _synaptic_pre, _synaptic_post, sources, targets, N_incoming, N_outgoing, N, N_pre, N_post, _source_offset, _target_offset } #} {# WRITES_TO_READ_ONLY_VARIABLES { _synaptic_pre, _synaptic_post, N_incoming, N_outgoing, N} #} {% extends 'common_group.cpp' %} {% block maincode %} const size_t _old_num_synapses = {{N}}; const size_t _new_num_synapses = _old_num_synapses + _numsources; {# Get N_post and N_pre in the correct way, regardless of whether they are constants or scalar arrays#} const size_t _N_pre = {{constant_or_scalar('N_pre', variables['N_pre'])}}; const size_t _N_post = {{constant_or_scalar('N_post', variables['N_post'])}}; {{_dynamic_N_incoming}}.resize(_N_post + _target_offset); {{_dynamic_N_outgoing}}.resize(_N_pre + _source_offset); for (size_t _idx=0; _idx<_numsources; _idx++) { {# After this code has been executed, the arrays _real_sources and _real_variables contain the final indices. Having any code here it all is only necessary for supporting subgroups #} {{vector_code|autoindent}} {{_dynamic__synaptic_pre}}.push_back(_real_sources); {{_dynamic__synaptic_post}}.push_back(_real_targets); // Update the number of total outgoing/incoming synapses per source/target neuron {{_dynamic_N_outgoing}}[_real_sources]++; {{_dynamic_N_incoming}}[_real_targets]++; } // now we need to resize all registered variables const size_t newsize = {{_dynamic__synaptic_pre}}.size(); {% for varname in owner._registered_variables | variables_to_array_names(access_data=False) | sort %} {{varname}}.resize(newsize); {% endfor %} // Also update the total number of synapses {{N}} = newsize; {% if multisynaptic_index %} // Update the "synapse number" (number of synapses for the same // source-target pair) std::map, int32_t> source_target_count; for (size_t _i=0; _i source_target = std::pair({{_dynamic__synaptic_pre}}[_i], {{_dynamic__synaptic_post}}[_i]); {{get_array_name(variables[multisynaptic_index], access_data=False)}}[_i] = source_target_count[source_target]; source_target_count[source_target]++; } {% endif %} {% endblock %}brian2-2.5.4/brian2/devices/cpp_standalone/templates/synapses_create_generator.cpp000066400000000000000000000256731445201106100304370ustar00rootroot00000000000000{# USES_VARIABLES { _synaptic_pre, _synaptic_post, rand, N_incoming, N_outgoing, N, N_pre, N_post, _source_offset, _target_offset } #} {# WRITES_TO_READ_ONLY_VARIABLES { _synaptic_pre, _synaptic_post, N_incoming, N_outgoing, N} #} {% extends 'common_synapses.cpp' %} {% block extra_headers %} {{ super() }} #include #include {% endblock %} {% block maincode %} {# Get N_post and N_pre in the correct way, regardless of whether they are constants or scalar arrays#} const size_t _N_pre = {{constant_or_scalar('N_pre', variables['N_pre'])}}; const size_t _N_post = {{constant_or_scalar('N_post', variables['N_post'])}}; {{_dynamic_N_incoming}}.resize(_N_post + _target_offset); {{_dynamic_N_outgoing}}.resize(_N_pre + _source_offset); size_t _raw_pre_idx, _raw_post_idx; {# For a connect call j='k+i for k in range(0, N_post, 2) if k+i < N_post' "j" is called the "result index" (and "_post_idx" the "result index array", etc.) "i" is called the "outer index" (and "_pre_idx" the "outer index array", etc.) "k" is called the inner variable #} // scalar code const size_t _vectorisation_idx = -1; {{scalar_code['setup_iterator']|autoindent}} {{scalar_code['generator_expr']|autoindent}} {{scalar_code['create_cond']|autoindent}} {{scalar_code['update']|autoindent}} for(size_t _{{outer_index}}=0; _{{outer_index}}<_{{outer_index_size}}; _{{outer_index}}++) { bool __cond, _cond; _raw{{outer_index_array}} = _{{outer_index}} + {{outer_index_offset}}; {% if not result_index_condition %} { {{vector_code['create_cond']|autoindent}} __cond = _cond; } _cond = __cond; if(!_cond) continue; {% endif %} // Some explanation of this hackery. The problem is that we have multiple code blocks. // Each code block is generated independently of the others, and they declare variables // at the beginning if necessary (including declaring them as const if their values don't // change). However, if two code blocks follow each other in the same C++ scope then // that causes a redeclaration error. So we solve it by putting each block inside a // pair of braces to create a new scope specific to each code block. However, that brings // up another problem: we need the values from these code blocks. I don't have a general // solution to this problem, but in the case of this particular template, we know which // values we need from them so we simply create outer scoped variables to copy the value // into. Later on we have a slightly more complicated problem because the original name // _j has to be used, so we create two variables __j, _j at the outer scope, copy // _j to __j in the inner scope (using the inner scope version of _j), and then // __j to _j in the outer scope (to the outer scope version of _j). This outer scope // version of _j will then be used in subsequent blocks. long _uiter_low; long _uiter_high; long _uiter_step; {% if iterator_func=='sample' %} long _uiter_size; double _uiter_p; {% endif %} { {{vector_code['setup_iterator']|autoindent}} _uiter_low = _iter_low; _uiter_high = _iter_high; _uiter_step = _iter_step; {% if iterator_func=='sample' %} {% if iterator_kwds['sample_size'] == 'fixed' %} _uiter_size = _iter_size; {% else %} _uiter_p = _iter_p; {% endif %} {% endif %} } {% if iterator_func=='range' %} for(long {{inner_variable}}=_uiter_low; {{inner_variable}}<_uiter_high; {{inner_variable}}+=_uiter_step) { {% elif iterator_func=='sample' %} const int _iter_sign = _uiter_step > 0 ? 1 : -1; {% if iterator_kwds['sample_size'] == 'fixed' %} std::set _selected_set = std::set(); std::set::iterator _selected_it; int _n_selected = 0; int _n_dealt_with = 0; int _n_total; if (_uiter_step > 0) _n_total = (_uiter_high - _uiter_low - 1) / _uiter_step + 1; else _n_total = (_uiter_low - _uiter_high - 1) / -_uiter_step + 1; // Value determined by benchmarking, see github PR #1280 const bool _selection_algo = 1.0*_uiter_size / _n_total > 0.06; if (_uiter_size > _n_total) { {% if skip_if_invalid %} _uiter_size = _n_total; {% else %} cout << "Error: Requested sample size " << _uiter_size << " is bigger than the " << "population size " << _n_total << "." << endl; exit(1); {% endif %} } else if (_uiter_size < 0) { {% if skip_if_invalid %} continue; {% else %} cout << "Error: Requested sample size " << _uiter_size << " is negative." << endl; exit(1); {% endif %} } else if (_uiter_size == 0) continue; long {{inner_variable}}; if (_selection_algo) { {{inner_variable}} = _uiter_low - _uiter_step; } else { // For the tracking algorithm, we have to first create all values // to make sure they will be iterated in sorted order _selected_set.clear(); while (_n_selected < _uiter_size) { int _r = (int)(_rand(_vectorisation_idx) * _n_total); while (! _selected_set.insert(_r).second) _r = (int)(_rand(_vectorisation_idx) * _n_total); _n_selected++; } _n_selected = 0; _selected_it = _selected_set.begin(); } while (_n_selected < _uiter_size) { if (_selection_algo) { // Selection sampling technique // See section 3.4.2 of Donald E. Knuth, AOCP, Vol 2, Seminumerical Algorithms {{inner_variable}} += _uiter_step; _n_dealt_with++; const double _U = _rand(_vectorisation_idx); if ((_n_total - _n_dealt_with) * _U >= _uiter_size - _n_selected) continue; } else { {{inner_variable}} = _uiter_low + (*_selected_it)*_uiter_step; _selected_it++; } _n_selected++; {% else %} if(_uiter_p==0) continue; const bool _jump_algo = _uiter_p<0.25; double _log1p; if(_jump_algo) _log1p = log(1-_uiter_p); else _log1p = 1.0; // will be ignored const double _pconst = 1.0/log(1-_uiter_p); for(long {{inner_variable}}=_uiter_low; _iter_sign*{{inner_variable}}<_iter_sign*_uiter_high; {{inner_variable}} += _uiter_step) { if(_jump_algo) { const double _r = _rand(_vectorisation_idx); if(_r==0.0) break; const int _jump = floor(log(_r)*_pconst)*_uiter_step; {{inner_variable}} += _jump; if (_iter_sign*{{inner_variable}} >= _iter_sign * _uiter_high) continue; } else { if (_rand(_vectorisation_idx)>=_uiter_p) continue; } {% endif %} {% endif %} long __{{result_index}}, _{{result_index}}, {{outer_index_array}}, _{{outer_index_array}}; { {{vector_code['generator_expr']|autoindent}} __{{result_index}} = _{{result_index}}; // pick up the locally scoped var and store in outer var _{{outer_index_array}} = {{outer_index_array}}; } _{{result_index}} = __{{result_index}}; // make the previously locally scoped var available {{outer_index_array}} = _{{outer_index_array}}; _raw{{result_index_array}} = _{{result_index}} + {{result_index_offset}}; {% if result_index_condition %} { {% if result_index_used %} {# The condition could index outside of array range #} if(_{{result_index}}<0 || _{{result_index}}>=_{{result_index_size}}) { {% if skip_if_invalid %} continue; {% else %} cout << "Error: tried to create synapse to neuron {{result_index}}=" << _{{result_index}} << " outside range 0 to " << _{{result_index_size}}-1 << endl; exit(1); {% endif %} } {% endif %} {{vector_code['create_cond']|autoindent}} __cond = _cond; } _cond = __cond; {% endif %} {% if if_expression!='True' %} if(!_cond) continue; {% endif %} {% if not result_index_used %} {# Otherwise, we already checked before #} if(_{{result_index}}<0 || _{{result_index}}>=_{{result_index_size}}) { {% if skip_if_invalid %} continue; {% else %} cout << "Error: tried to create synapse to neuron {{result_index}}=" << _{{result_index}} << " outside range 0 to " << _{{result_index_size}}-1 << endl; exit(1); {% endif %} } {% endif %} {{vector_code['update']|autoindent}} for (size_t _repetition=0; _repetition<_n; _repetition++) { {{_dynamic_N_outgoing}}[_pre_idx] += 1; {{_dynamic_N_incoming}}[_post_idx] += 1; {{_dynamic__synaptic_pre}}.push_back(_pre_idx); {{_dynamic__synaptic_post}}.push_back(_post_idx); } } } // now we need to resize all registered variables const int32_t newsize = {{_dynamic__synaptic_pre}}.size(); {% for varname in owner._registered_variables | variables_to_array_names(access_data=False) | sort%} {{varname}}.resize(newsize); {% endfor %} // Also update the total number of synapses {{N}} = newsize; {% if multisynaptic_index %} // Update the "synapse number" (number of synapses for the same // source-target pair) std::map, int32_t> source_target_count; for (size_t _i=0; _i source_target = std::pair({{_dynamic__synaptic_pre}}[_i], {{_dynamic__synaptic_post}}[_i]); {{get_array_name(variables[multisynaptic_index], access_data=False)}}[_i] = source_target_count[source_target]; source_target_count[source_target]++; } {% endif %} {% endblock %} brian2-2.5.4/brian2/devices/cpp_standalone/templates/synapses_push_spikes.cpp000066400000000000000000000026051445201106100274510ustar00rootroot00000000000000{# USES_VARIABLES { _n_sources, _n_targets, delay, _source_dt} #} {% extends 'common_group.cpp' %} {% block before_code %} {% set scalar = c_data_type(variables['delay'].dtype) %} std::vector<{{scalar}}> &real_delays = {{get_array_name(variables['delay'], access_data=False)}}; {{scalar}}* real_delays_data = real_delays.empty() ? 0 : &(real_delays[0]); int32_t* sources = {{owner.name}}.sources.empty() ? 0 : &({{owner.name}}.sources[0]); const size_t n_delays = real_delays.size(); const size_t n_synapses = {{owner.name}}.sources.size(); {{owner.name}}.prepare({{constant_or_scalar('_n_sources', variables['_n_sources'])}}, {{constant_or_scalar('_n_targets', variables['_n_targets'])}}, real_delays_data, n_delays, sources, n_synapses, {{_source_dt}}); {% endblock %} {% block maincode %} // we do advance at the beginning rather than at the end because it saves us making // a copy of the current spiking synapses {# Get the name of the array that stores these events (e.g. the spikespace array) #} {% set _eventspace = get_array_name(eventspace_variable) %} {{openmp_pragma('parallel')}} { {{owner.name}}.advance(); {{owner.name}}.push({{_eventspace}}, {{_eventspace}}[_num{{eventspace_variable.name}}-1]); } {% endblock %} brian2-2.5.4/brian2/devices/cpp_standalone/templates/threshold.cpp000066400000000000000000000023331445201106100251610ustar00rootroot00000000000000{# USES_VARIABLES { N } #} {% extends 'common_group.cpp' %} {% block maincode %} {# not_refractory and lastspike are added as needed_variables in the Thresholder class, we cannot use the USES_VARIABLE mechanism conditionally Same goes for "eventspace" (e.g. spikespace) which depends on the type of event #} //// MAIN CODE //////////// // scalar code const int _vectorisation_idx = -1; {{scalar_code|autoindent}} {# Get the name of the array that stores these events (e.g. the spikespace array) #} {% set _eventspace = get_array_name(eventspace_variable) %} long _count = 0; for(size_t _idx=0; _idxh; {% endif %} {%if GSL_settings['save_failed_steps']%} {{pointer_failed_steps}} = _GSL_driver->e->failed_steps; {% endif %} {%if GSL_settings['save_step_count']%} {{pointer_step_count}} = _GSL_driver->n; {% endif %} _empty_y_vector(&_GSL_dataholder, _GSL_y, _idx); } gsl_odeiv2_driver_free(_GSL_driver); {% endblock %} brian2-2.5.4/brian2/devices/device.py000066400000000000000000000620031445201106100173020ustar00rootroot00000000000000""" Module containing the `Device` base class as well as the `RuntimeDevice` implementation and some helper functions to access/set devices. """ import numbers from weakref import WeakKeyDictionary import numpy as np from brian2.codegen.targets import codegen_targets from brian2.core.names import find_name from brian2.core.preferences import prefs from brian2.core.variables import ArrayVariable, DynamicArrayVariable from brian2.memory.dynamicarray import DynamicArray, DynamicArray1D from brian2.units import ms from brian2.utils.logger import get_logger from brian2.utils.stringtools import code_representation, indent __all__ = [ "Device", "RuntimeDevice", "get_device", "set_device", "all_devices", "reinit_devices", "reinit_and_delete", "reset_device", "device", "seed", ] logger = get_logger(__name__) all_devices = {} prefs.register_preferences("devices", "Device preferences") #: caches the automatically determined code generation target _auto_target = None def auto_target(): """ Automatically chose a code generation target (invoked when the `codegen.target` preference is set to `'auto'`. Caches its result so it only does the check once. Prefers cython > numpy. Returns ------- target : class derived from `CodeObject` The target to use """ global _auto_target if _auto_target is None: target_dict = { target.class_name: target for target in codegen_targets if target.class_name } using_fallback = False if "cython" in target_dict and target_dict["cython"].is_available(): _auto_target = target_dict["cython"] else: _auto_target = target_dict["numpy"] using_fallback = True if using_fallback: logger.info( "Cannot use compiled code, falling back to the numpy " "code generation target. Note that this will likely " "be slower than using compiled code. Set the code " "generation to numpy manually to avoid this message:\n" 'prefs.codegen.target = "numpy"', "codegen_fallback", once=True, ) else: logger.debug( "Chosing %r as the code generation target." % _auto_target.class_name ) return _auto_target class Device: """ Base Device object. """ def __init__(self): #: The network schedule that this device supports. If the device only #: supports a specific, fixed schedule, it has to set this attribute to #: the respective schedule (see `Network.schedule` for details). If it #: supports arbitrary schedules, it should be set to ``None`` (the #: default). self.network_schedule = None self.defaultclock = None self._maximum_run_time = None self._state_tuple = (self.__module__, self.__class__.__name__) def _set_maximum_run_time(self, maximum_run_time): """ Sets a maximum time for a run before it will break. Used primarily for testing purposes. Not guaranteed to be respected by a device. """ self._maximum_run_time = maximum_run_time def get_array_name(self, var, access_data=True): """ Return a globally unique name for `var`. Parameters ---------- access_data : bool, optional For `DynamicArrayVariable` objects, specifying `True` here means the name for the underlying data is returned. If specifying `False`, the name of object itself is returned (e.g. to allow resizing). Returns ------- name : str The name for `var`. """ raise NotImplementedError() def get_len(self, array): """ Return the length of the array. Parameters ---------- array : `ArrayVariable` The array for which the length is requested. Returns ------- l : int The length of the array. """ raise NotImplementedError() def add_array(self, var): """ Add an array to this device. Parameters ---------- var : `ArrayVariable` The array to add. """ raise NotImplementedError() def init_with_zeros(self, var, dtype): """ Initialize an array with zeros. Parameters ---------- var : `ArrayVariable` The array to initialize with zeros. dtype : `dtype` The data type to use for the array. """ raise NotImplementedError() def init_with_arange(self, var, start, dtype): """ Initialize an array with an integer range. Parameters ---------- var : `ArrayVariable` The array to fill with the integer range. start : int The start value for the integer range dtype : `dtype` The data type to use for the array. """ raise NotImplementedError() def fill_with_array(self, var, arr): """ Fill an array with the values given in another array. Parameters ---------- var : `ArrayVariable` The array to fill. arr : `ndarray` The array values that should be copied to `var`. """ raise NotImplementedError() def spike_queue(self, source_start, source_end): """ Create and return a new `SpikeQueue` for this `Device`. Parameters ---------- source_start : int The start index of the source group (necessary for subgroups) source_end : int The end index of the source group (necessary for subgroups) """ raise NotImplementedError() def resize(self, var, new_size): """ Resize a `DynamicArrayVariable`. Parameters ---------- var : `DynamicArrayVariable` The variable that should be resized. new_size : int The new size of the variable """ raise NotImplementedError() def resize_along_first(self, var, new_size): # Can be overwritten with a better implementation return self.resize(var, new_size) def seed(self, seed=None): """ Set the seed for the random number generator. Parameters ---------- seed : int, optional The seed value for the random number generator, or ``None`` (the default) to set a random seed. """ raise NotImplementedError() def code_object_class(self, codeobj_class=None, fallback_pref="codegen.target"): """ Return `CodeObject` class according to input/default settings Parameters ---------- codeobj_class : a `CodeObject` class, optional If this is keyword is set to None or no arguments are given, this method will return the default. fallback_pref : str, optional String describing which attribute of prefs to access to retrieve the 'default' target. Usually this is codegen.target, but in some cases we want to use object-specific targets such as codegen.string_expression_target. Returns ------- codeobj_class : class The `CodeObject` class that should be used """ if isinstance(codeobj_class, str): raise TypeError( "codeobj_class argument given to code_object_class device method " "should be a CodeObject class, not a string. You can, however, " "send a string description of the target desired for the CodeObject " "under the keyword fallback_pref" ) if codeobj_class is None: codeobj_class = prefs[fallback_pref] if isinstance(codeobj_class, str): if codeobj_class == "auto": return auto_target() for target in codegen_targets: if target.class_name == codeobj_class: return target # No target found targets = ["auto"] + [ target.class_name for target in codegen_targets if target.class_name ] raise ValueError( f"Unknown code generation target: {codeobj_class}, should be one" f" of {targets}" ) else: return codeobj_class def code_object( self, owner, name, abstract_code, variables, template_name, variable_indices, codeobj_class=None, template_kwds=None, override_conditional_write=None, compiler_kwds=None, ): if compiler_kwds is None: compiler_kwds = {} name = find_name(name) codeobj_class = self.code_object_class(codeobj_class) template = getattr(codeobj_class.templater, template_name) iterate_all = template.iterate_all generator = codeobj_class.generator_class( variables=variables, variable_indices=variable_indices, owner=owner, iterate_all=iterate_all, codeobj_class=codeobj_class, override_conditional_write=override_conditional_write, allows_scalar_write=template.allows_scalar_write, name=name, template_name=template_name, ) if template_kwds is None: template_kwds = dict() else: template_kwds = template_kwds.copy() logger.diagnostic( f"{name} abstract code:\n{indent(code_representation(abstract_code))}" ) scalar_code, vector_code, kwds = generator.translate( abstract_code, dtype=prefs["core.default_float_dtype"] ) # Add the array names as keywords as well for varname, var in variables.items(): if isinstance(var, ArrayVariable): pointer_name = generator.get_array_name(var) if var.scalar: pointer_name += "[0]" template_kwds[varname] = pointer_name if hasattr(var, "resize"): dyn_array_name = generator.get_array_name(var, access_data=False) template_kwds[f"_dynamic_{varname}"] = dyn_array_name template_kwds.update(kwds) logger.diagnostic( f"{name} snippet (scalar):\n{indent(code_representation(scalar_code))}" ) logger.diagnostic( f"{name} snippet (vector):\n{indent(code_representation(vector_code))}" ) code = template( scalar_code, vector_code, owner=owner, variables=variables, codeobj_name=name, variable_indices=variable_indices, get_array_name=generator.get_array_name, **template_kwds, ) logger.diagnostic(f"{name} code:\n{indent(code_representation(code))}") codeobj = codeobj_class( owner, code, variables, variable_indices, template_name=template_name, template_source=template.template_source, name=name, compiler_kwds=compiler_kwds, ) codeobj.compile() return codeobj def activate(self, build_on_run=True, **kwargs): """ Called when this device is set as the current device. """ from brian2.core.clocks import Clock # avoid import issues if self.defaultclock is None: self.defaultclock = Clock(dt=0.1 * ms, name="defaultclock") self._set_maximum_run_time(None) self.build_on_run = build_on_run self.build_options = dict(kwargs) def insert_device_code(self, slot, code): # Deprecated raise AttributeError( "The method 'insert_device_code' has been renamed to 'insert_code'." ) def insert_code(self, slot, code): """ Insert code directly into a given slot in the device. By default does nothing. """ logger.warn(f"Ignoring device code, unknown slot: {slot}, code: {code}") def build(self, **kwds): """ For standalone projects, called when the project is ready to be built. Does nothing for runtime mode. """ pass def reinit(self): """ Reinitialize the device. For standalone devices, clears all the internal state of the device. """ pass def delete(self, data=True, code=True, directory=True, force=False): """ Delete code and/or data generated/stored by the device. Parameters ---------- data : bool, optional Whether to delete the data generated by the simulation (final values of state variables, data stored in monitors, etc.). Defaults to ``True``. code : bool, optional Whether to delete the code generated by the simulation. Includes the numerical values used for initialization of state variables in assignments not using strings. Defaults to ``True``. directory : bool, optional Whether to delete the project directory generated by the simulation. Will not delete directories that contain files not created by Brian unless the ``force`` option is specfied. Defaults to ``True``. force : bool, optional Whether to delete the project directory with all its content, even if it contains files that were not created by Brian. Useful only when the ``directory`` option is set to ``True`` as well. Defaults to ``False``. """ pass def get_random_state(self): """ Return a (pickable) representation of the current random number generator state. Providing the returned object (e.g. a dict) to `.Device.set_random_state` should restore the random number generator state. Returns ------- state The state of the random number generator in a representation that can be passed as an argument to `.Device.set_random_state`. """ raise NotImplementedError( "Device does not support getting the state of the random number generator." ) def set_random_state(self, state): """ Reset the random number generator state to a previously stored state (see `.Device.get_random_state`). Parameters ---------- state A random number generator state as provided by `Device.get_random_state`. """ raise NotImplementedError( "Device does not support setting the state of the random number generator." ) class RuntimeDevice(Device): """ The default device used in Brian, state variables are stored as numpy arrays in memory. """ def __init__(self): super().__init__() #: Mapping from `Variable` objects to numpy arrays (or `DynamicArray` #: objects). Arrays in this dictionary will disappear as soon as the #: last reference to the `Variable` object used as a key is gone self.arrays = WeakKeyDictionary() # Note that the buffers only store a pointer to the actual random # numbers -- the buffer will be filled in Cython code self.randn_buffer = np.zeros(1, dtype=np.intp) self.rand_buffer = np.zeros(1, dtype=np.intp) self.randn_buffer_index = np.zeros(1, dtype=np.int32) self.rand_buffer_index = np.zeros(1, dtype=np.int32) def get_array_name(self, var, access_data=True): # if no owner is set, this is a temporary object (e.g. the array # of indices when doing G.x[indices] = ...). The name is not # necessarily unique over several CodeObjects in this case. owner_name = getattr(var.owner, "name", "temporary") if isinstance(var, DynamicArrayVariable): if access_data: return f"_array_{owner_name}_{var.name}" else: return f"_dynamic_array_{owner_name}_{var.name}" elif isinstance(var, ArrayVariable): return f"_array_{owner_name}_{var.name}" else: raise TypeError(f"Do not have a name for variable of type {type(var)}.") def add_array(self, var): # This creates the actual numpy arrays (or DynamicArrayVariable objects) if isinstance(var, DynamicArrayVariable): if var.ndim == 1: arr = DynamicArray1D(var.size, dtype=var.dtype) else: arr = DynamicArray(var.size, dtype=var.dtype) else: arr = np.empty(var.size, dtype=var.dtype) self.arrays[var] = arr def get_value(self, var, access_data=True): if isinstance(var, DynamicArrayVariable) and access_data: return self.arrays[var].data else: return self.arrays[var] def set_value(self, var, value): self.arrays[var][:] = value def resize(self, var, new_size): self.arrays[var].resize(new_size) def resize_along_first(self, var, new_size): self.arrays[var].resize_along_first(new_size) def init_with_zeros(self, var, dtype): self.arrays[var][:] = 0 def init_with_arange(self, var, start, dtype): self.arrays[var][:] = np.arange(start, stop=var.get_len() + start, dtype=dtype) def fill_with_array(self, var, arr): self.arrays[var][:] = arr def spike_queue(self, source_start, source_end): # Use the C++ version of the SpikeQueue when available try: from brian2.synapses.cythonspikequeue import SpikeQueue logger.diagnostic("Using the C++ SpikeQueue", once=True) except ImportError: from brian2.synapses.spikequeue import SpikeQueue logger.diagnostic("Using the Python SpikeQueue", once=True) return SpikeQueue(source_start=source_start, source_end=source_end) def seed(self, seed=None): """ Set the seed for the random number generator. Parameters ---------- seed : int, optional The seed value for the random number generator, or ``None`` (the default) to set a random seed. """ np.random.seed(seed) self.rand_buffer_index[:] = 0 self.randn_buffer_index[:] = 0 def get_random_state(self): return { "numpy_state": np.random.get_state(), "rand_buffer_index": np.array(self.rand_buffer_index), "rand_buffer": np.array(self.rand_buffer), "randn_buffer_index": np.array(self.randn_buffer_index), "randn_buffer": np.array(self.randn_buffer), } def set_random_state(self, state): np.random.set_state(state["numpy_state"]) self.rand_buffer_index[:] = state["rand_buffer_index"] self.rand_buffer[:] = state["rand_buffer"] self.randn_buffer_index[:] = state["randn_buffer_index"] self.randn_buffer[:] = state["randn_buffer"] class Dummy: """ Dummy object """ def __getattr__(self, name): return Dummy() def __call__(self, *args, **kwds): return Dummy() def __enter__(self): return Dummy() def __exit__(self, type, value, traceback): pass def __getitem__(self, i): return Dummy() def __setitem__(self, i, val): pass class CurrentDeviceProxy: """ Method proxy for access to the currently active device """ def __getattr__(self, name): if not hasattr(active_device, name): # We special case the name "shape" here, since some IDEs (e.g. The Python # console in PyDev and PyCharm) use the "shape" attribute to determine # whether an object is "array-like". if name.startswith("_") or name == "shape": # Do not fake private/magic attributes raise AttributeError( f"Active device does not have an attribute '{name}'." ) else: logger.warn( f"Active device does not have an attribute '{name}', ignoring this." ) attr = Dummy() else: attr = getattr(active_device, name) return attr #: Proxy object to access methods of the current device device = CurrentDeviceProxy() #: The currently active device (set with `set_device`) active_device = None def get_device(): """ Gets the actve `Device` object """ global active_device return active_device #: A stack of previously set devices as a tuple with their options (see #: `set_device`): (device, build_on_run, build_options) previous_devices = [] def set_device(device, build_on_run=True, **kwargs): """ Set the device used for simulations. Parameters ---------- device : `Device` or str The `Device` object or the name of the device. build_on_run : bool, optional Whether a call to `run` (or `Network.run`) should directly trigger a `Device.build`. This is only relevant for standalone devices and means that a run call directly triggers the start of a simulation. If the simulation consists of multiple run calls, set ``build_on_run`` to ``False`` and call `Device.build` explicitly. Defaults to ``True``. kwargs : dict, optional Only relevant when ``build_on_run`` is ``True``: additional arguments that will be given to the `Device.build` call. """ global previous_devices if active_device is not None: prev_build_on_run = getattr(active_device, "build_on_run", True) prev_build_options = getattr(active_device, "build_options", {}) previous_devices.append((active_device, prev_build_on_run, prev_build_options)) _do_set_device(device, build_on_run, **kwargs) def _do_set_device(device, build_on_run=True, **kwargs): global active_device if isinstance(device, str): device = all_devices[device] if active_device is not None and active_device.defaultclock is not None: previous_dt = active_device.defaultclock.dt else: previous_dt = None active_device = device active_device.activate(build_on_run=build_on_run, **kwargs) if previous_dt is not None: # Copy over the dt information of the defaultclock active_device.defaultclock.dt = previous_dt def reset_device(device=None): """ Reset to a previously used device. Restores also the previously specified build options (see `set_device`) for the device. Mostly useful for internal Brian code and testing on various devices. Parameters ---------- device : `Device` or str, optional The device to go back to. If none is specified, go back to the device chosen with `set_device` before the current one. """ global previous_devices if isinstance(device, str): device = all_devices[device] if len(previous_devices) == 0 and device is None: device = runtime_device build_on_run = True build_options = {} elif device is None: device, build_on_run, build_options = previous_devices.pop() else: build_on_run = device.build_on_run build_options = device.build_options _do_set_device(device, build_on_run, **build_options) def reinit_devices(): """ Reinitialize all devices, call `Device.activate` again on the current device and reset the preferences. Used as a "teardown" function in testing, if users want to reset their device (e.g. for multiple standalone runs in a single script), calling ``device.reinit()`` followed by ``device.activate()`` should normally be sufficient. Notes ----- This also resets the `defaultclock`, i.e. a non-standard ``dt`` has to be set again. """ from brian2 import restore_initial_state # avoids circular import for device in all_devices.values(): device.reinit() if active_device is not None: # Reactivate the current device reset_device(active_device) restore_initial_state() def reinit_and_delete(): """ Calls `reinit_devices` and additionally deletes the files left behind by the standalone mode in the temporary directory. Silently suppresses errors that occur while deleting the directory. """ reinit_devices() device.delete(directory=True, force=True) def seed(seed=None): """ Set the seed for the random number generator. Parameters ---------- seed : int, optional The seed value for the random number generator, or ``None`` (the default) to set a random seed. Notes ----- This function delegates the call to `Device.seed` of the current device. """ if seed is not None and not isinstance(seed, numbers.Integral): raise TypeError(f"Seed has to be None or an integer, was {type(seed)}") get_device().seed(seed) runtime_device = RuntimeDevice() all_devices["runtime"] = runtime_device brian2-2.5.4/brian2/equations/000077500000000000000000000000001445201106100160565ustar00rootroot00000000000000brian2-2.5.4/brian2/equations/__init__.py000066400000000000000000000004341445201106100201700ustar00rootroot00000000000000""" Module handling equations and "code strings", expressions or statements, used for example for the reset and threshold definition of a neuron. """ from .codestrings import Expression, Statements from .equations import Equations __all__ = ["Equations", "Expression", "Statements"] brian2-2.5.4/brian2/equations/codestrings.py000066400000000000000000000206361445201106100207630ustar00rootroot00000000000000""" Module defining `CodeString`, a class for a string of code together with information about its namespace. Only serves as a parent class, its subclasses `Expression` and `Statements` are the ones that are actually used. """ from collections.abc import Hashable import sympy from brian2.parsing.sympytools import str_to_sympy, sympy_to_str from brian2.utils.logger import get_logger from brian2.utils.stringtools import get_identifiers __all__ = ["Expression", "Statements"] logger = get_logger(__name__) class CodeString(Hashable): """ A class for representing "code strings", i.e. a single Python expression or a sequence of Python statements. Parameters ---------- code : str The code string, may be an expression or a statement(s) (possibly multi-line). """ def __init__(self, code): self._code = code.strip() # : Set of identifiers in the code string self.identifiers = get_identifiers(code) code = property(lambda self: self._code, doc="The code string") def __str__(self): return self.code def __repr__(self): return f"{self.__class__.__name__}({self.code!r})" def __eq__(self, other): if not isinstance(other, CodeString): return NotImplemented return self.code == other.code def __ne__(self, other): return not self == other def __hash__(self): return hash(self.code) class Statements(CodeString): """ Class for representing statements. Parameters ---------- code : str The statement or statements. Several statements can be given as a multi-line string or separated by semicolons. Notes ----- Currently, the implementation of this class does not add anything to `~brian2.equations.codestrings.CodeString`, but it should be used instead of that class for clarity and to allow for future functionality that is only relevant to statements and not to expressions. """ pass class Expression(CodeString): """ Class for representing an expression. Parameters ---------- code : str, optional The expression. Note that the expression has to be written in a form that is parseable by sympy. Alternatively, a sympy expression can be provided (in the ``sympy_expression`` argument). sympy_expression : sympy expression, optional A sympy expression. Alternatively, a plain string expression can be provided (in the ``code`` argument). """ def __init__(self, code=None, sympy_expression=None): if code is None and sympy_expression is None: raise TypeError("Have to provide either a string or a sympy expression") if code is not None and sympy_expression is not None: raise TypeError( "Provide a string expression or a sympy expression, not both" ) if code is None: code = sympy_to_str(sympy_expression) else: # Just try to convert it to a sympy expression to get syntax errors # for incorrect expressions str_to_sympy(code) super().__init__(code=code) stochastic_variables = property( lambda self: { variable for variable in self.identifiers if variable == "xi" or variable.startswith("xi_") }, doc="Stochastic variables in this expression", ) def split_stochastic(self): """ Split the expression into a stochastic and non-stochastic part. Splits the expression into a tuple of one `Expression` objects f (the non-stochastic part) and a dictionary mapping stochastic variables to `Expression` objects. For example, an expression of the form ``f + g * xi_1 + h * xi_2`` would be returned as: ``(f, {'xi_1': g, 'xi_2': h})`` Note that the `Expression` objects for the stochastic parts do not include the stochastic variable itself. Returns ------- (f, d) : (`Expression`, dict) A tuple of an `Expression` object and a dictionary, the first expression being the non-stochastic part of the equation and the dictionary mapping stochastic variables (``xi`` or starting with ``xi_``) to `Expression` objects. If no stochastic variable is present in the code string, a tuple ``(self, None)`` will be returned with the unchanged `Expression` object. """ stochastic_variables = [] for identifier in self.identifiers: if identifier == "xi" or identifier.startswith("xi_"): stochastic_variables.append(identifier) # No stochastic variable if not len(stochastic_variables): return (self, None) stochastic_symbols = [ sympy.Symbol(variable, real=True) for variable in stochastic_variables ] # Note that collect only works properly if the expression is expanded collected = ( str_to_sympy(self.code).expand().collect(stochastic_symbols, evaluate=False) ) f_expr = None stochastic_expressions = {} for var, s_expr in collected.items(): expr = Expression(sympy_expression=s_expr) if var == 1: if any(s_expr.has(s) for s in stochastic_symbols): raise AssertionError( "Error when separating expression " f"'{self.code}' into stochastic and non-" "stochastic term: non-stochastic " f"part was determined to be '{s_expr}' but " "contains a stochastic symbol." ) f_expr = expr elif var in stochastic_symbols: stochastic_expressions[str(var)] = expr else: raise ValueError( f"Expression '{self.code}' cannot be separated into " "stochastic and non-stochastic " "term" ) if f_expr is None: f_expr = Expression("0.0") return f_expr, stochastic_expressions def _repr_pretty_(self, p, cycle): """ Pretty printing for ipython. """ if cycle: raise AssertionError("Cyclical call of 'CodeString._repr_pretty'") # Make use of sympy's pretty printing p.pretty(str_to_sympy(self.code)) def __eq__(self, other): if not isinstance(other, Expression): return NotImplemented return self.code == other.code def __ne__(self, other): return not self.__eq__(other) def __hash__(self): return hash(self.code) def is_constant_over_dt(expression, variables, dt_value): """ Check whether an expression can be considered as constant over a time step. This is *not* the case when the expression either: 1. contains the variable ``t`` (except as the argument of a function that can be considered as constant over a time step, e.g. a `TimedArray` with a dt equal to or greater than the dt used to evaluate this expression) 2. refers to a stateful function such as ``rand()``. Parameters ---------- expression : `sympy.Expr` The (sympy) expression to analyze variables : dict The variables dictionary. dt_value : float or None The length of a timestep (without units), can be ``None`` if the time step is not yet known. Returns ------- is_constant : bool Whether the expression can be considered to be constant over a time step. """ t_symbol = sympy.Symbol("t", real=True, positive=True) if expression == t_symbol: return False # The full expression is simply "t" func_name = str(expression.func) func_variable = variables.get(func_name, None) if func_variable is not None and not func_variable.stateless: return False for arg in expression.args: if arg == t_symbol and dt_value is not None: # We found "t" -- if it is not the only argument of a locally # constant function we bail out if not ( func_variable is not None and func_variable.is_locally_constant(dt_value) ): return False else: if not is_constant_over_dt(arg, variables, dt_value): return False return True brian2-2.5.4/brian2/equations/equations.py000066400000000000000000001410411445201106100204410ustar00rootroot00000000000000""" Differential equations for Brian models. """ import keyword import re import string from collections import namedtuple from collections.abc import Hashable, Mapping import sympy from pyparsing import ( CharsNotIn, Combine, Group, LineEnd, OneOrMore, Optional, ParseException, Suppress, Word, ZeroOrMore, restOfLine, ) from brian2.core.namespace import DEFAULT_CONSTANTS, DEFAULT_FUNCTIONS, DEFAULT_UNITS from brian2.parsing.sympytools import str_to_sympy, sympy_to_str from brian2.units.allunits import second from brian2.units.fundamentalunits import ( DIMENSIONLESS, DimensionMismatchError, Quantity, Unit, get_dimensions, get_unit, get_unit_for_display, ) from brian2.utils.caching import CacheKey, cached from brian2.utils.logger import get_logger from brian2.utils.stringtools import get_identifiers from brian2.utils.topsort import topsort from .codestrings import Expression from .unitcheck import check_dimensions __all__ = ["Equations"] logger = get_logger(__name__) # Equation types (currently simple strings but always use the constants, # this might get refactored into objects, for example) PARAMETER = "parameter" DIFFERENTIAL_EQUATION = "differential equation" SUBEXPRESSION = "subexpression" # variable types (FLOAT is the only one that is possible for variables that # have dimensions). These types will be later translated into dtypes, either # using the default values from the preferences, or explicitly given dtypes in # the construction of the `NeuronGroup`, `Synapses`, etc. object FLOAT = "float" INTEGER = "integer" BOOLEAN = "boolean" # Definitions of equation structure for parsing with pyparsing # TODO: Maybe move them somewhere else to not pollute the namespace here? # Only IDENTIFIER and EQUATIONS are ever used later ############################################################################### # Basic Elements ############################################################################### # identifiers like in C: can start with letter or underscore, then a # combination of letters, numbers and underscores # Note that the check_identifiers function later performs more checks, e.g. # names starting with underscore should only be used internally IDENTIFIER = Word( string.ascii_letters + "_", string.ascii_letters + string.digits + "_" ).setResultsName("identifier") # very broad definition here, expression will be analysed by sympy anyway # allows for multi-line expressions, where each line can have comments EXPRESSION = Combine( OneOrMore( (CharsNotIn(":#\n") + Suppress(Optional(LineEnd()))).ignore("#" + restOfLine) ), joinString=" ", ).setResultsName("expression") # a unit # very broad definition here, again. Whether this corresponds to a valid unit # string will be checked later UNIT = Word(string.ascii_letters + string.digits + "*/.- ").setResultsName("unit") # a single Flag (e.g. "const" or "event-driven") FLAG = Word(string.ascii_letters, string.ascii_letters + "_- " + string.digits) # Flags are comma-separated and enclosed in parantheses: "(flag1, flag2)" FLAGS = ( Suppress("(") + FLAG + ZeroOrMore(Suppress(",") + FLAG) + Suppress(")") ).setResultsName("flags") ############################################################################### # Equations ############################################################################### # Three types of equations # Parameter: # x : volt (flags) PARAMETER_EQ = Group( IDENTIFIER + Suppress(":") + UNIT + Optional(FLAGS) ).setResultsName(PARAMETER) # Static equation: # x = 2 * y : volt (flags) STATIC_EQ = Group( IDENTIFIER + Suppress("=") + EXPRESSION + Suppress(":") + UNIT + Optional(FLAGS) ).setResultsName(SUBEXPRESSION) # Differential equation # dx/dt = -x / tau : volt DIFF_OP = Suppress("d") + IDENTIFIER + Suppress("/") + Suppress("dt") DIFF_EQ = Group( DIFF_OP + Suppress("=") + EXPRESSION + Suppress(":") + UNIT + Optional(FLAGS) ).setResultsName(DIFFERENTIAL_EQUATION) # ignore comments EQUATION = (PARAMETER_EQ | STATIC_EQ | DIFF_EQ).ignore("#" + restOfLine) EQUATIONS = ZeroOrMore(EQUATION) class EquationError(Exception): """ Exception type related to errors in an equation definition. """ pass def check_identifier_basic(identifier): """ Check an identifier (usually resulting from an equation string provided by the user) for conformity with the rules. The rules are: 1. Only ASCII characters 2. Starts with a character, then mix of alphanumerical characters and underscore 3. Is not a reserved keyword of Python Parameters ---------- identifier : str The identifier that should be checked Raises ------ SyntaxError If the identifier does not conform to the above rules. """ # Check whether the identifier is parsed correctly -- this is always the # case, if the identifier results from the parsing of an equation but there # might be situations where the identifier is specified directly parse_result = list(IDENTIFIER.scanString(identifier)) # parse_result[0][0][0] refers to the matched string -- this should be the # full identifier, if not it is an illegal identifier like "3foo" which only # matched on "foo" if len(parse_result) != 1 or parse_result[0][0][0] != identifier: raise SyntaxError(f"'{identifier}' is not a valid variable name.") if keyword.iskeyword(identifier): raise SyntaxError( f"'{identifier}' is a Python keyword and cannot be used as a variable." ) if identifier.startswith("_"): raise SyntaxError( f"Variable '{identifier}' starts with an underscore, " "this is only allowed for variables used " "internally" ) def check_identifier_reserved(identifier): """ Check that an identifier is not using a reserved special variable name. The special variables are: 't', 'dt', and 'xi', as well as everything starting with `xi_`. Parameters ---------- identifier: str The identifier that should be checked Raises ------ SyntaxError If the identifier is a special variable name. """ if identifier in ( "t", "dt", "t_in_timesteps", "xi", "i", "N", ) or identifier.startswith("xi_"): raise SyntaxError( f"'{identifier}' has a special meaning in equations and " "cannot be used as a variable name." ) def check_identifier_units(identifier): """ Make sure that identifier names do not clash with unit names. """ if identifier in DEFAULT_UNITS: raise SyntaxError( f"'{identifier}' is the name of a unit, cannot be used as a variable name." ) def check_identifier_functions(identifier): """ Make sure that identifier names do not clash with function names. """ if identifier in DEFAULT_FUNCTIONS: raise SyntaxError( f"'{identifier}' is the name of a function, cannot be used as " "a variable name." ) def check_identifier_constants(identifier): """ Make sure that identifier names do not clash with function names. """ if identifier in DEFAULT_CONSTANTS: raise SyntaxError( f"'{identifier}' is the name of a constant, cannot be used as " "a variable name." ) _base_units_with_alternatives = None _base_units = None def dimensions_and_type_from_string(unit_string): """ Returns the physical dimensions that results from evaluating a string like "siemens / metre ** 2", allowing for the special string "1" to signify dimensionless units, the string "boolean" for a boolean and "integer" for an integer variable. Parameters ---------- unit_string : str The string that should evaluate to a unit Returns ------- d, type : (`Dimension`, {FLOAT, INTEGER or BOOL}) The resulting physical dimensions and the type of the variable. Raises ------ ValueError If the string cannot be evaluated to a unit. """ # Lazy import to avoid circular dependency from brian2.core.namespace import DEFAULT_UNITS global _base_units_with_alternatives global _base_units if _base_units_with_alternatives is None: base_units_for_dims = {} for unit_name, unit in reversed(DEFAULT_UNITS.items()): if float(unit) == 1.0 and repr(unit)[-1] not in ["2", "3"]: if unit.dim in base_units_for_dims: if unit_name not in base_units_for_dims[unit.dim]: base_units_for_dims[unit.dim].append(unit_name) else: base_units_for_dims[unit.dim] = [repr(unit)] if unit_name != repr(unit): base_units_for_dims[unit.dim].append(unit_name) alternatives = sorted( [tuple(values) for values in base_units_for_dims.values()] ) _base_units = {v: DEFAULT_UNITS[v] for values in alternatives for v in values} # Create a string that lists all allowed base units alternative_strings = [] for units in alternatives: string = units[0] if len(units) > 1: other_units = ", ".join(units[1:]) string += f" ({other_units})" alternative_strings.append(string) _base_units_with_alternatives = ", ".join(alternative_strings) unit_string = unit_string.strip() # Special case: dimensionless unit if unit_string == "1": return DIMENSIONLESS, FLOAT # Another special case: boolean variable if unit_string == "boolean": return DIMENSIONLESS, BOOLEAN if unit_string == "bool": raise TypeError("Use 'boolean' not 'bool' as the unit for a boolean variable.") # Yet another special case: integer variable if unit_string == "integer": return DIMENSIONLESS, INTEGER # Check first whether the expression only refers to base units identifiers = get_identifiers(unit_string) for identifier in identifiers: if identifier not in _base_units: if identifier in DEFAULT_UNITS: # A known unit, but not a base unit base_unit = get_unit(DEFAULT_UNITS[identifier].dim) if not repr(base_unit) in _base_units: # Make sure that we don't suggest a unit that is not allowed # (should not happen, normally) base_unit = Unit(1, dim=base_unit.dim) raise ValueError( "Unit specification refers to " f"'{identifier}', but this is not a base " f"unit. Use '{base_unit!r}' instead." ) else: # Not a known unit raise ValueError( "Unit specification refers to " f"'{identifier}', but this is not a base " "unit. The following base units are " f"allowed: {_base_units_with_alternatives}." ) try: evaluated_unit = eval(unit_string, _base_units) except Exception as ex: raise ValueError( f"Could not interpret '{unit_string}' as a unit specification: {ex}" ) # Check whether the result is a unit if not isinstance(evaluated_unit, Unit): if isinstance(evaluated_unit, Quantity): raise ValueError( f"'{unit_string}' does not evaluate to a unit but to a " "quantity -- make sure to only use units, e.g. " "'siemens/metre**2' and not '1 * siemens/metre**2'" ) else: raise ValueError( f"'{unit_string}' does not evaluate to a unit, the result " f"has type {type(evaluated_unit)} instead." ) # No error has been raised, all good return evaluated_unit.dim, FLOAT @cached def parse_string_equations(eqns): """ parse_string_equations(eqns) Parse a string defining equations. Parameters ---------- eqns : str The (possibly multi-line) string defining the equations. See the documentation of the `Equations` class for details. Returns ------- equations : dict A dictionary mapping variable names to `~brian2.equations.equations.Equations` objects """ equations = {} try: parsed = EQUATIONS.parseString(eqns, parseAll=True) except ParseException as p_exc: raise EquationError( "Parsing failed: \n" + str(p_exc.line) + "\n" + " " * (p_exc.column - 1) + "^\n" + str(p_exc) ) from p_exc for eq in parsed: eq_type = eq.getName() eq_content = dict(eq.items()) # Check for reserved keywords identifier = eq_content["identifier"] # Convert unit string to Unit object try: dims, var_type = dimensions_and_type_from_string(eq_content["unit"]) except ValueError as ex: raise EquationError( "Error parsing the unit specification for " f"variable '{identifier}': {ex}" ) expression = eq_content.get("expression", None) if expression is not None: # Replace multiple whitespaces (arising from joining multiline # strings) with single space p = re.compile(r"\s{2,}") expression = Expression(p.sub(" ", expression)) flags = list(eq_content.get("flags", [])) equation = SingleEquation( eq_type, identifier, dims, var_type=var_type, expr=expression, flags=flags ) if identifier in equations: raise EquationError(f"Duplicate definition of variable '{identifier}'") equations[identifier] = equation return equations class SingleEquation(Hashable, CacheKey): """ Class for internal use, encapsulates a single equation or parameter. .. note:: This class should never be used directly, it is only useful as part of the `Equations` class. Parameters ---------- type : {PARAMETER, DIFFERENTIAL_EQUATION, SUBEXPRESSION} The type of the equation. varname : str The variable that is defined by this equation. dimensions : `Dimension` The physical dimensions of the variable var_type : {FLOAT, INTEGER, BOOLEAN} The type of the variable (floating point value or boolean). expr : `Expression`, optional The expression defining the variable (or ``None`` for parameters). flags: list of str, optional A list of flags that give additional information about this equation. What flags are possible depends on the type of the equation and the context. """ _cache_irrelevant_attributes = {"update_order"} def __init__( self, type, varname, dimensions, var_type=FLOAT, expr=None, flags=None ): self.type = type self.varname = varname self.dim = get_dimensions(dimensions) self.var_type = var_type if dimensions is not DIMENSIONLESS: if var_type == BOOLEAN: raise TypeError("Boolean variables are necessarily dimensionless.") elif var_type == INTEGER: raise TypeError("Integer variables are necessarily dimensionless.") if type == DIFFERENTIAL_EQUATION: if var_type != FLOAT: raise TypeError( "Differential equations can only define floating point variables" ) self.expr = expr if flags is None: self.flags = [] else: self.flags = list(flags) # will be set later in the sort_subexpressions method of Equations self.update_order = -1 unit = property(lambda self: get_unit(self.dim), doc="The `Unit` of this equation.") identifiers = property( lambda self: self.expr.identifiers if self.expr is not None else set(), doc="All identifiers in the RHS of this equation.", ) stochastic_variables = property( lambda self: { variable for variable in self.identifiers if variable == "xi" or variable.startswith("xi_") }, doc="Stochastic variables in the RHS of this equation", ) def __eq__(self, other): if not isinstance(other, SingleEquation): return NotImplemented return self._state_tuple == other._state_tuple def __ne__(self, other): return not self == other def __hash__(self): return hash(self._state_tuple) def _latex(self, *args): if self.type == DIFFERENTIAL_EQUATION: return ( r"\frac{\mathrm{d}" + sympy.latex(sympy.Symbol(self.varname)) + r"}{\mathrm{d}t} = " + sympy.latex(str_to_sympy(self.expr.code)) ) elif self.type == SUBEXPRESSION: return ( sympy.latex(sympy.Symbol(self.varname)) + " = " + sympy.latex(str_to_sympy(self.expr.code)) ) elif self.type == PARAMETER: return sympy.latex(sympy.Symbol(self.varname)) def __str__(self): if self.type == DIFFERENTIAL_EQUATION: s = "d" + self.varname + "/dt" else: s = self.varname if self.expr is not None: s += " = " + str(self.expr) s += " : " + get_unit_for_display(self.dim) if len(self.flags): s += " (" + ", ".join(self.flags) + ")" return s def __repr__(self): s = "<" + self.type + " " + self.varname if self.expr is not None: s += ": " + self.expr.code s += " (Unit: " + get_unit_for_display(self.dim) if len(self.flags): s += ", flags: " + ", ".join(self.flags) s += ")>" return s def _repr_pretty_(self, p, cycle): """ Pretty printing for ipython. """ if cycle: # should never happen raise AssertionError("Cyclical call of SingleEquation._repr_pretty") if self.type == DIFFERENTIAL_EQUATION: p.text("d" + self.varname + "/dt") else: p.text(self.varname) if self.expr is not None: p.text(" = ") p.pretty(self.expr) p.text(" : ") p.pretty(get_unit(self.dim)) if len(self.flags): p.text(" (" + ", ".join(self.flags) + ")") def _repr_latex_(self): return "$" + sympy.latex(self) + "$" class Equations(Hashable, Mapping): """ Container that stores equations from which models can be created. String equations can be of any of the following forms: 1. ``dx/dt = f : unit (flags)`` (differential equation) 2. ``x = f : unit (flags)`` (equation) 3. ``x : unit (flags)`` (parameter) String equations can span several lines and contain Python-style comments starting with ``#`` Parameters ---------- eqs : `str` or list of `SingleEquation` objects A multiline string of equations (see above) -- for internal purposes also a list of `SingleEquation` objects can be given. This is done for example when adding new equations to implement the refractory mechanism. Note that in this case the variable names are not checked to allow for "internal names", starting with an underscore. kwds: keyword arguments Keyword arguments can be used to replace variables in the equation string. Arguments have to be of the form ``varname=replacement``, where `varname` has to correspond to a variable name in the given equation. The replacement can be either a string (replacing a name with a new name, e.g. ``tau='tau_e'``) or a value (replacing the variable name with the value, e.g. ``tau=tau_e`` or ``tau=10*ms``). """ def __init__(self, eqns, **kwds): if isinstance(eqns, str): self._equations = parse_string_equations(eqns) # Do a basic check for the identifiers self.check_identifiers() else: self._equations = {} for eq in eqns: if not isinstance(eq, SingleEquation): raise TypeError( "The list should only contain " f"SingleEquation objects, not {type(eq)}" ) if eq.varname in self._equations: raise EquationError( f"Duplicate definition of variable '{eq.varname}'" ) self._equations[eq.varname] = eq self._equations = self._substitute(kwds) # Check for special symbol xi (stochastic term) uses_xi = None for eq in self._equations.values(): if eq.expr is not None and "xi" in eq.expr.identifiers: if not eq.type == DIFFERENTIAL_EQUATION: raise EquationError( f"The equation defining '{eq.varname}' " "contains the symbol 'xi' but is not a " "differential equation." ) elif uses_xi is not None: raise EquationError( f"The equation defining {eq.varname} contains " "the symbol 'xi', but it is already used " f"in the equation defining {uses_xi}. Rename " "the variables to 'xi_...' to make " "clear whether they are the same or " "independent random variables. Using " "the same name twice will lead to " "identical noise realizations " "whereas using different names will " "lead to independent noise " "realizations." ) else: uses_xi = eq.varname # rearrange subexpressions self._sort_subexpressions() #: Cache for equations with the subexpressions substituted self._substituted_expressions = None def _substitute(self, replacements): if len(replacements) == 0: return self._equations new_equations = {} for eq in self.values(): # Replace the name of a model variable (works only for strings) if eq.varname in replacements: new_varname = replacements[eq.varname] if not isinstance(new_varname, str): raise ValueError( f"Cannot replace model variable '{eq.varname}' with a value." ) if new_varname in self or new_varname in new_equations: raise EquationError( f"Cannot replace model variable '{eq.varname}' " f"with '{new_varname}', duplicate definition " f"of '{new_varname}'." ) # make sure that the replacement is a valid identifier Equations.check_identifier(new_varname) else: new_varname = eq.varname if eq.type in [SUBEXPRESSION, DIFFERENTIAL_EQUATION]: # Replace values in the RHS of the equation new_code = eq.expr.code for to_replace, replacement in replacements.items(): if to_replace in eq.identifiers: if isinstance(replacement, str): # replace the name with another name new_code = re.sub( "\\b" + to_replace + "\\b", replacement, new_code ) else: # replace the name with a value new_code = re.sub( "\\b" + to_replace + "\\b", "(" + repr(replacement) + ")", new_code, ) try: Expression(new_code) except ValueError as ex: raise ValueError( 'Replacing "%s" with "%r" failed: %s' % (to_replace, replacement, ex) ) new_equations[new_varname] = SingleEquation( eq.type, new_varname, dimensions=eq.dim, var_type=eq.var_type, expr=Expression(new_code), flags=eq.flags, ) else: new_equations[new_varname] = SingleEquation( eq.type, new_varname, dimensions=eq.dim, var_type=eq.var_type, flags=eq.flags, ) return new_equations def substitute(self, **kwds): return Equations(list(self._substitute(kwds).values())) def __iter__(self): return iter(self._equations) def __len__(self): return len(self._equations) def __getitem__(self, key): return self._equations[key] def __add__(self, other_eqns): if isinstance(other_eqns, str): other_eqns = parse_string_equations(other_eqns) elif not isinstance(other_eqns, Equations): return NotImplemented return Equations(list(self.values()) + list(other_eqns.values())) def __hash__(self): return hash(frozenset(self._equations.items())) #: A set of functions that are used to check identifiers (class attribute). #: Functions can be registered with the static method #: `Equations.register_identifier_check` and will be automatically #: used when checking identifiers identifier_checks = { check_identifier_basic, check_identifier_reserved, check_identifier_functions, check_identifier_constants, check_identifier_units, } @staticmethod def register_identifier_check(func): """ Register a function for checking identifiers. Parameters ---------- func : callable The function has to receive a single argument, the name of the identifier to check, and raise a ValueError if the identifier violates any rule. """ if not callable(func): raise ValueError("Can only register callables.") Equations.identifier_checks.add(func) @staticmethod def check_identifier(identifier): """ Perform all the registered checks. Checks can be registered via `Equations.register_identifier_check`. Parameters ---------- identifier : str The identifier that should be checked Raises ------ ValueError If any of the registered checks fails. """ for check_func in Equations.identifier_checks: check_func(identifier) def check_identifiers(self): """ Check all identifiers for conformity with the rules. Raises ------ ValueError If an identifier does not conform to the rules. See also -------- Equations.check_identifier : The function that is called for each identifier. """ for name in self.names: Equations.check_identifier(name) def get_substituted_expressions(self, variables=None, include_subexpressions=False): """ Return a list of ``(varname, expr)`` tuples, containing all differential equations (and optionally subexpressions) with all the subexpression variables substituted with the respective expressions. Parameters ---------- variables : dict, optional A mapping of variable names to `Variable`/`Function` objects. include_subexpressions : bool Whether also to return substituted subexpressions. Defaults to ``False``. Returns ------- expr_tuples : list of (str, `CodeString`) A list of ``(varname, expr)`` tuples, where ``expr`` is a `CodeString` object with all subexpression variables substituted with the respective expression. """ if self._substituted_expressions is None: self._substituted_expressions = [] substitutions = {} for eq in self.ordered: # Skip parameters if eq.expr is None: continue new_sympy_expr = str_to_sympy(eq.expr.code, variables).xreplace( substitutions ) new_str_expr = sympy_to_str(new_sympy_expr) expr = Expression(new_str_expr) if eq.type == SUBEXPRESSION: if eq.var_type == INTEGER: sympy_var = sympy.Symbol(eq.varname, integer=True) else: sympy_var = sympy.Symbol(eq.varname, real=True) substitutions.update( {sympy_var: str_to_sympy(expr.code, variables)} ) self._substituted_expressions.append((eq.varname, expr)) elif eq.type == DIFFERENTIAL_EQUATION: # a differential equation that we have to check self._substituted_expressions.append((eq.varname, expr)) else: raise AssertionError(f"Unknown equation type {eq.type}") if include_subexpressions: return self._substituted_expressions else: return [ (name, expr) for name, expr in self._substituted_expressions if self[name].type == DIFFERENTIAL_EQUATION ] def _get_stochastic_type(self): """ Returns the type of stochastic differential equations (additivive or multiplicative). The system is only classified as ``additive`` if *all* equations have only additive noise (or no noise). Returns ------- type : str Either ``None`` (no noise variables), ``'additive'`` (factors for all noise variables are independent of other state variables or time), ``'multiplicative'`` (at least one of the noise factors depends on other state variables and/or time). """ if not self.is_stochastic: return None for _, expr in self.get_substituted_expressions(): _, stochastic = expr.split_stochastic() if stochastic is not None: for factor in stochastic.values(): if "t" in factor.identifiers: # noise factor depends on time return "multiplicative" for identifier in factor.identifiers: if identifier in self.diff_eq_names: # factor depends on another state variable return "multiplicative" return "additive" ############################################################################ # Properties ############################################################################ # Lists of equations or (variable, expression tuples) ordered = property( lambda self: sorted( self._equations.values(), key=lambda key: (key.update_order, key.varname) ), doc=( "A list of all equations, sorted " "according to the order in which they should " "be updated" ), ) diff_eq_expressions = property( lambda self: [ (varname, eq.expr) for varname, eq in self.items() if eq.type == DIFFERENTIAL_EQUATION ], doc=( "A list of (variable name, expression) " "tuples of all differential equations." ), ) eq_expressions = property( lambda self: [ (varname, eq.expr) for varname, eq in self.items() if eq.type in (SUBEXPRESSION, DIFFERENTIAL_EQUATION) ], doc="A list of (variable name, expression) tuples of all equations.", ) # Sets of names names = property( lambda self: {eq.varname for eq in self.ordered}, doc="All variable names defined in the equations.", ) diff_eq_names = property( lambda self: { eq.varname for eq in self.ordered if eq.type == DIFFERENTIAL_EQUATION }, doc="All differential equation names.", ) subexpr_names = property( lambda self: {eq.varname for eq in self.ordered if eq.type == SUBEXPRESSION}, doc="All subexpression names.", ) eq_names = property( lambda self: { eq.varname for eq in self.ordered if eq.type in (DIFFERENTIAL_EQUATION, SUBEXPRESSION) }, doc="All equation names (including subexpressions).", ) parameter_names = property( lambda self: {eq.varname for eq in self.ordered if eq.type == PARAMETER}, doc="All parameter names.", ) dimensions = property( lambda self: {var: eq.dim for var, eq in self._equations.items()}, doc=( "Dictionary of all internal variables and their " "corresponding physical dimensions." ), ) identifiers = property( lambda self: set().union(*[eq.identifiers for eq in self._equations.values()]) - self.names, doc=( "Set of all identifiers used in the equations, " "excluding the variables defined in the equations" ), ) stochastic_variables = property( lambda self: { variable for variable in self.identifiers if variable == "xi" or variable.startswith("xi_") } ) # general properties is_stochastic = property( lambda self: len(self.stochastic_variables) > 0, doc="Whether the equations are stochastic.", ) stochastic_type = property(fget=_get_stochastic_type) def _sort_subexpressions(self): """ Sorts the subexpressions in a way that resolves their dependencies upon each other. After this method has been run, the subexpressions returned by the ``ordered`` property are in the order in which they should be updated """ # Get a dictionary of all the dependencies on other subexpressions, # i.e. ignore dependencies on parameters and differential equations static_deps = {} for eq in self._equations.values(): if eq.type == SUBEXPRESSION: static_deps[eq.varname] = [ dep for dep in eq.identifiers if dep in self._equations and self._equations[dep].type == SUBEXPRESSION ] try: sorted_eqs = topsort(static_deps) except ValueError: raise ValueError( "Cannot resolve dependencies between static " "equations, dependencies contain a cycle." ) # put the equations objects in the correct order for order, static_variable in enumerate(sorted_eqs): self._equations[static_variable].update_order = order # Sort differential equations and parameters after subexpressions for eq in self._equations.values(): if eq.type == DIFFERENTIAL_EQUATION: eq.update_order = len(sorted_eqs) elif eq.type == PARAMETER: eq.update_order = len(sorted_eqs) + 1 @property def dependencies(self): """ Calculate the dependencies of all differential equations and subexpressions. """ # Create a dictionary mapping differential equations and # subexpressions to a list of their dependencies within the equations # (ignoring external constants, unit names, etc.) # Note that a differential equation such as "dv/dt = -v / tau" does not # mean that the variable "v" depends on itself. To make the distinction between # a variable and its derivative, we use the variable name + the prime symbol # in this dictionary. # As an example, the equations: # dv/dt = I_m / C_m : volt # I_m = I_ext + I_pas : amp # I_ext = 1*nA + sin(2*pi*100*Hz*t)*nA : amp # I_pas = g_L*(E_L - v) : amp # would be translated into the following dictionary # {"v" : [], # "v'": ["I_m"] # "I_m": ["I_ext", "I_pas"], # "I_ext": [], # "I_pas": ["v"] } deps = {} for eq in self._equations.values(): if eq.type == SUBEXPRESSION: name = eq.varname elif eq.type == DIFFERENTIAL_EQUATION: name = eq.varname + "'" deps[eq.varname] = [] else: continue deps[name] = [ dep for dep in eq.identifiers if dep in self._equations and self._equations[dep].type != PARAMETER ] try: sorted_eqs = topsort(deps) except ValueError: raise ValueError( "Cannot resolve dependencies between static " "equations, dependencies contain a cycle." ) # Remove the dummy entries for differential equations and rename # x' → x sorted_eqs = [ x.replace("'", "") for x in sorted_eqs if x not in self.diff_eq_names ] # Now recursively fill in the dependencies – this only needs a single # pass due to the previous sorting deps = {} Dependency = namedtuple( "Dependency", ["equation", "via"], defaults=((),) ) # default for via is empty tuple for eq in sorted_eqs: dep_names = { dep for dep in self._equations[eq].identifiers if dep in self._equations } deps[eq] = [Dependency(equation=self._equations[dep]) for dep in dep_names] # add all indirect dependencies for dep in dep_names: for indirect_dep in deps.get(dep, []): if indirect_dep.equation.varname == dep: continue # do not go into recursion if a variable depends on itself if any( indirect_dep.equation.varname == existing_dep.equation.varname for existing_dep in deps[eq] ): continue # Do not add indirect dependencies for things we also depend on directly deps[eq].append( Dependency( equation=indirect_dep.equation, via=(dep,) + indirect_dep.via, ) ) return deps def check_units(self, group, run_namespace): """ Check all the units for consistency. Parameters ---------- group : `Group` The group providing the context run_namespace : dict-like, optional An additional namespace that is used for variable lookup (if not defined, the implicit namespace of local variables is used). level : int, optional How much further to go up in the stack to find the calling frame Raises ------ DimensionMismatchError In case of any inconsistencies. """ all_variables = dict(group.variables) external = frozenset().union( *[expr.identifiers for _, expr in self.eq_expressions] ) external -= set(all_variables.keys()) resolved_namespace = group.resolve_all( external, run_namespace, user_identifiers=external ) # all variables are user defined all_variables.update(resolved_namespace) for var, eq in self._equations.items(): if eq.type == PARAMETER: # no need to check units for parameters continue if eq.type == DIFFERENTIAL_EQUATION: try: check_dimensions( str(eq.expr), self.dimensions[var] / second.dim, all_variables ) except DimensionMismatchError as ex: raise DimensionMismatchError( "Inconsistent units in " "differential equation " f"defining variable '{eq.varname}':" f"\n{ex.desc}", *ex.dims, ) from ex elif eq.type == SUBEXPRESSION: try: check_dimensions(str(eq.expr), self.dimensions[var], all_variables) except DimensionMismatchError as ex: raise DimensionMismatchError( "Inconsistent units in " f"subexpression {eq.varname}:" f"\n%{ex.desc}", *ex.dims, ) from ex else: raise AssertionError(f"Unknown equation type: '{eq.type}'") def check_flags(self, allowed_flags, incompatible_flags=None): """ Check the list of flags. Parameters ---------- allowed_flags : dict A dictionary mapping equation types (PARAMETER, DIFFERENTIAL_EQUATION, SUBEXPRESSION) to a list of strings (the allowed flags for that equation type) incompatible_flags : list of tuple A list of flag combinations that are not allowed for the same equation. Notes ----- Not specifying allowed flags for an equation type is the same as specifying an empty list for it. Raises ------ ValueError If any flags are used that are not allowed. """ if incompatible_flags is None: incompatible_flags = [] for eq in self.values(): for flag in eq.flags: if eq.type not in allowed_flags or len(allowed_flags[eq.type]) == 0: raise ValueError( f"Equations of type '{eq.type}' cannot have any flags." ) if flag not in allowed_flags[eq.type]: raise ValueError( f"Equations of type '{eq.type}' cannot have a " f"flag '{flag}', only the following flags " f"are allowed: {allowed_flags[eq.type]}" ) # Check for incompatibilities for flag_combinations in incompatible_flags: if flag in flag_combinations: remaining_flags = set(flag_combinations) - {flag} for remaining_flag in remaining_flags: if remaining_flag in eq.flags: raise ValueError( f"Flag '{flag}' cannot be " "combined with flag " f"'{remaining_flag}'" ) ############################################################################ # Representation ############################################################################ def __str__(self): strings = [str(eq) for eq in self.ordered] return "\n".join(strings) def __repr__(self): return f"" def _latex(self, *args): equations = [] for eq in self._equations.values(): # do not use SingleEquations._latex here as we want nice alignment varname = sympy.Symbol(eq.varname) if eq.type == DIFFERENTIAL_EQUATION: lhs = r"\frac{\mathrm{d}" + sympy.latex(varname) + r"}{\mathrm{d}t}" else: # Normal equation or parameter lhs = varname if not eq.type == PARAMETER: rhs = str_to_sympy(eq.expr.code) if len(eq.flags): flag_str = ", flags: " + ", ".join(eq.flags) else: flag_str = "" if eq.type == PARAMETER: eq_latex = r"{} &&& \text{{(unit: ${}${})}}".format( sympy.latex(lhs), sympy.latex(get_unit(eq.dim)), flag_str, ) else: eq_latex = r"{} &= {} && \text{{(unit of ${}$: ${}${})}}".format( lhs, # already a string sympy.latex(rhs), sympy.latex(varname), sympy.latex(get_unit(eq.dim)), flag_str, ) equations.append(eq_latex) return r"\begin{align*}" + (r"\\" + "\n").join(equations) + r"\end{align*}" def _repr_latex_(self): return sympy.latex(self) def _repr_pretty_(self, p, cycle): """Pretty printing for ipython""" if cycle: # Should never happen raise AssertionError("Cyclical call of 'Equations._repr_pretty_'") for eq in self._equations.values(): p.pretty(eq) p.breakable("\n") def is_stateful(expression, variables): """ Whether the given expression refers to stateful functions (and is therefore not guaranteed to give the same result if called repetively). Parameters ---------- expression : `sympy.Expression` The sympy expression to check. variables : dict The dictionary mapping variable names to `Variable` or `Function` objects. Returns ------- stateful : bool ``True``, if the given expression refers to a stateful function like ``rand()`` and ``False`` otherwise. """ func_name = str(expression.func) func_variable = variables.get(func_name, None) if func_variable is not None and not func_variable.stateless: return True for arg in expression.args: if is_stateful(arg, variables): return True return False def check_subexpressions(group, equations, run_namespace): """ Checks the subexpressions in the equations and raises an error if a subexpression refers to stateful functions without being marked as "constant over dt". Parameters ---------- group : `Group` The group providing the context. equations : `Equations` The equations to check. run_namespace : dict The run namespace for resolving variables. Raises ------ SyntaxError For subexpressions not marked as "constant over dt" that refer to stateful functions. """ for eq in equations.ordered: if eq.type == SUBEXPRESSION: # Check whether the expression is stateful (most commonly by # referring to rand() or randn() variables = group.resolve_all( eq.identifiers, run_namespace, # we don't need to raise any warnings # for the user here, warnings will # be raised in create_runner_codeobj user_identifiers=set(), ) expression = str_to_sympy(eq.expr.code, variables=variables) # Check whether the expression refers to stateful functions if is_stateful(expression, variables): raise SyntaxError( f"The subexpression '{eq.varname}' refers to a " "stateful function (e.g. rand()). Such " "expressions should only be evaluated " "once per timestep, add the 'constant " "over dt' flag." ) def extract_constant_subexpressions(eqs): without_const_subexpressions = [] const_subexpressions = [] for eq in eqs.ordered: if eq.type == SUBEXPRESSION and "constant over dt" in eq.flags: flags = set(eq.flags) - {"constant over dt"} without_const_subexpressions.append( SingleEquation( PARAMETER, eq.varname, eq.dim, var_type=eq.var_type, flags=flags ) ) const_subexpressions.append(eq) else: without_const_subexpressions.append(eq) return (Equations(without_const_subexpressions), Equations(const_subexpressions)) brian2-2.5.4/brian2/equations/refractory.py000066400000000000000000000051051445201106100206110ustar00rootroot00000000000000""" Module implementing Brian's refractory mechanism. """ from brian2.units.allunits import second from brian2.units.fundamentalunits import DIMENSIONLESS from .equations import ( BOOLEAN, DIFFERENTIAL_EQUATION, PARAMETER, Equations, Expression, SingleEquation, ) __all__ = ["add_refractoriness"] def check_identifier_refractory(identifier): """ Check that the identifier is not using a name reserved for the refractory mechanism. The reserved names are `not_refractory`, `refractory`, `refractory_until`. Parameters ---------- identifier : str The identifier to check. Raises ------ ValueError If the identifier is a variable name used for the refractory mechanism. """ if identifier in ("not_refractory", "refractory", "refractory_until"): raise SyntaxError( f"The name '{identifier}' is used in the refractory mechanism " " and should not be used as a variable " "name." ) Equations.register_identifier_check(check_identifier_refractory) def add_refractoriness(eqs): """ Extends a given set of equations with the refractory mechanism. New parameters are added and differential equations with the "unless refractory" flag are changed so that their right-hand side is 0 when the neuron is refractory (by multiplication with the ``not_refractory`` variable). Parameters ---------- eqs : `Equations` The equations without refractory mechanism. Returns ------- new_eqs : `Equations` New equations, with added parameters and changed differential equations having the "unless refractory" flag. """ new_equations = [] # replace differential equations having the active flag for eq in eqs.values(): if eq.type == DIFFERENTIAL_EQUATION and "unless refractory" in eq.flags: # the only case where we have to change anything new_code = f"int(not_refractory)*({eq.expr.code})" new_equations.append( SingleEquation( DIFFERENTIAL_EQUATION, eq.varname, eq.dim, expr=Expression(new_code), flags=eq.flags, ) ) else: new_equations.append(eq) # add new parameters new_equations.append( SingleEquation(PARAMETER, "not_refractory", DIMENSIONLESS, var_type=BOOLEAN) ) new_equations.append(SingleEquation(PARAMETER, "lastspike", second.dim)) return Equations(new_equations) brian2-2.5.4/brian2/equations/unitcheck.py000066400000000000000000000074321445201106100204130ustar00rootroot00000000000000""" Utility functions for handling the units in `Equations`. """ import re from brian2.core.variables import Variable from brian2.parsing.expressions import parse_expression_dimensions from brian2.parsing.statements import parse_statement from brian2.units.fundamentalunits import ( fail_for_dimension_mismatch, get_dimensions, get_unit, ) __all__ = ["check_dimensions", "check_units_statements"] def check_dimensions(expression, dimensions, variables): """ Compares the physical dimensions of an expression to expected dimensions in a given namespace. Parameters ---------- expression : str The expression to evaluate. dimensions : `Dimension` The expected physical dimensions for the `expression`. variables : dict Dictionary of all variables (including external constants) used in the `expression`. Raises ------ KeyError In case on of the identifiers cannot be resolved. DimensionMismatchError If an unit mismatch occurs during the evaluation. """ expr_dims = parse_expression_dimensions(expression, variables) expected = repr(get_unit(dimensions)) err_msg = ( f"Expression '{expression.strip()}' does not have the expected unit {expected}" ) fail_for_dimension_mismatch(expr_dims, dimensions, err_msg) def check_units_statements(code, variables): """ Check the units for a series of statements. Setting a model variable has to use the correct unit. For newly introduced temporary variables, the unit is determined and used to check the following statements to ensure consistency. Parameters ---------- code : str The statements as a (multi-line) string variables : dict of `Variable` objects The information about all variables used in `code` (including `Constant` objects for external variables) Raises ------ KeyError In case on of the identifiers cannot be resolved. DimensionMismatchError If an unit mismatch occurs during the evaluation. """ variables = dict(variables) # Avoid a circular import from brian2.codegen.translation import analyse_identifiers newly_defined, _, unknown = analyse_identifiers(code, variables) if len(unknown): raise AssertionError( "Encountered unknown identifiers, this should not " f"happen at this stage. Unknown identifiers: {unknown}" ) code = re.split(r"[;\n]", code) for line in code: line = line.strip() if not len(line): continue # skip empty lines varname, op, expr, comment = parse_statement(line) if op in ("+=", "-=", "*=", "/=", "%="): # Replace statements such as "w *=2" by "w = w * 2" expr = f"{varname} {op[0]} {expr}" elif op == "=": pass else: raise AssertionError(f'Unknown operator "{op}"') expr_unit = parse_expression_dimensions(expr, variables) if varname in variables: expected_unit = variables[varname].dim fail_for_dimension_mismatch( expr_unit, expected_unit, "The right-hand-side of code " 'statement "%s" does not have the ' "expected unit {expected}" % line, expected=expected_unit, ) elif varname in newly_defined: # note the unit for later variables[varname] = Variable( name=varname, dimensions=get_dimensions(expr_unit), scalar=False ) else: raise AssertionError( f"Variable '{varname}' is neither in the variables " "dictionary nor in the list of undefined " "variables." ) brian2-2.5.4/brian2/groups/000077500000000000000000000000001445201106100153655ustar00rootroot00000000000000brian2-2.5.4/brian2/groups/__init__.py000066400000000000000000000003261445201106100174770ustar00rootroot00000000000000""" Package providing groups such as `NeuronGroup` or `PoissonGroup`. """ from .group import * from .neurongroup import * from .subgroup import * __all__ = ["CodeRunner", "Group", "VariableOwner", "NeuronGroup"] brian2-2.5.4/brian2/groups/group.py000066400000000000000000001404541445201106100171030ustar00rootroot00000000000000""" This module defines the `VariableOwner` class, a mix-in class for everything that saves state variables, e.g. `Clock` or `NeuronGroup`, the class `Group` for objects that in addition to storing state variables also execute code, i.e. objects such as `NeuronGroup` or `StateMonitor` but not `Clock`, and finally `CodeRunner`, a class to run code in the context of a `Group`. """ import inspect import numbers import weakref from collections import OrderedDict from collections.abc import Mapping import numpy as np from brian2.codegen.codeobject import create_runner_codeobj from brian2.core.base import BrianObject, weakproxy_with_fallback from brian2.core.functions import Function from brian2.core.names import Nameable, find_name from brian2.core.namespace import ( DEFAULT_CONSTANTS, DEFAULT_FUNCTIONS, DEFAULT_UNITS, get_local_namespace, ) from brian2.core.preferences import prefs from brian2.core.variables import ( ArrayVariable, AuxiliaryVariable, Constant, DynamicArrayVariable, Subexpression, Variable, Variables, ) from brian2.equations.equations import BOOLEAN, FLOAT, INTEGER, Equations from brian2.importexport.importexport import ImportExport from brian2.units.fundamentalunits import ( DIMENSIONLESS, fail_for_dimension_mismatch, get_unit, ) from brian2.utils.logger import get_logger from brian2.utils.stringtools import SpellChecker, get_identifiers __all__ = ["Group", "VariableOwner", "CodeRunner"] logger = get_logger(__name__) def _display_value(obj): """ Helper function for warning messages that display the value of objects. This functions returns a nicer representation for symbolic constants and functions. Parameters ---------- obj : object The object to display Returns ------- value : str A string representation of the object """ if isinstance(obj, Function): return "" try: obj = obj.get_value() except AttributeError: pass try: obj = obj.value except AttributeError: pass # We (temporarily) set numpy's print options so that array with more than # 10 elements are only shown in an abbreviated way old_options = np.get_printoptions() np.set_printoptions(threshold=10) try: str_repr = repr(obj) except Exception: str_repr = f"" finally: np.set_printoptions(**old_options) return str_repr def _conflict_warning(message, resolutions): """ A little helper functions to generate warnings for logging. Specific to the `Group._resolve` method and should only be used by it. Parameters ---------- message : str The first part of the warning message. resolutions : list of str A list of (namespace, object) tuples. """ if len(resolutions) == 0: # nothing to warn about return elif len(resolutions) == 1: second_part = ( "but the name also refers to a variable in the " f"{resolutions[0][0]} " f"namespace with value '{_display_value(resolutions[0][1])}'." ) else: second_part = ( "but the name also refers to a variable in the following " f"namespaces: {', '.join([r[0] for r in resolutions])}." ) logger.warn( f"{message} {second_part}", "Group.resolve.resolution_conflict", once=True ) def get_dtype(equation, dtype=None): """ Helper function to interpret the `dtype` keyword argument in `NeuronGroup` etc. Parameters ---------- equation : `SingleEquation` The equation for which a dtype should be returned dtype : `dtype` or dict, optional Either the `dtype` to be used as a default dtype for all float variables (instead of the `core.default_float_dtype` preference) or a dictionary stating the `dtype` for some variables; all other variables will use the preference default Returns ------- d : `dtype` The dtype for the variable defined in `equation` """ # Check explicitly provided dtype for compatibility with the variable type if isinstance(dtype, Mapping): if equation.varname in dtype: BASIC_TYPES = {BOOLEAN: "b", INTEGER: "iu", FLOAT: "f"} provided_dtype = np.dtype(dtype[equation.varname]) if provided_dtype.kind not in BASIC_TYPES[equation.var_type]: raise TypeError( "Error determining dtype for variable " f"{equation.varname}: {provided_dtype.name} is not " f"a correct type for {equation.var_type} variables" ) else: return dtype[equation.varname] else: # continue as if no dtype had been specified at all dtype = None # Use default dtypes (or a provided standard dtype for floats) if equation.var_type == BOOLEAN: return bool elif equation.var_type == INTEGER: return prefs["core.default_integer_dtype"] elif equation.var_type == FLOAT: if dtype is not None: dtype = np.dtype(dtype) if not dtype.kind == "f": raise TypeError(f"{dtype} is not a valid floating point dtype.") return dtype else: return prefs["core.default_float_dtype"] else: raise ValueError( "Do not know how to determine a dtype for " f"variable '{equation.varname}' of type {equation.var_type}" ) def _same_value(obj1, obj2): """ Helper function used during namespace resolution. """ if obj1 is obj2: return True try: obj1 = obj1.get_value() except (AttributeError, TypeError): pass try: obj2 = obj2.get_value() except (AttributeError, TypeError): pass return obj1 is obj2 def _same_function(func1, func2): """ Helper function, used during namespace resolution for comparing whether to functions are the same. This takes care of treating a function and a `Function` variables whose `Function.pyfunc` attribute matches as the same. This prevents the user from getting spurious warnings when having for example a numpy function such as :np:func:`~random.randn` in the local namespace, while the ``randn`` symbol in the numpy namespace used for the code objects refers to a `RandnFunction` specifier. """ # use the function itself if it doesn't have a pyfunc attribute func1 = getattr(func1, "pyfunc", func1) func2 = getattr(func2, "pyfunc", func2) return func1 is func2 class Indexing: """ Object responsible for calculating flat index arrays from arbitrary group- specific indices. Stores strong references to the necessary variables so that basic indexing (i.e. slicing, integer arrays/values, ...) works even when the respective `VariableOwner` no longer exists. Note that this object does not handle string indexing. """ def __init__(self, group, default_index="_idx"): self.group = weakref.proxy(group) self.N = group.variables["N"] self.default_index = default_index def __call__(self, item=slice(None), index_var=None): # noqa: B008 """ Return flat indices to index into state variables from arbitrary group specific indices. In the default implementation, raises an error for multidimensional indices and transforms slices into arrays. Parameters ---------- item : slice, array, int The indices to translate. Returns ------- indices : `numpy.ndarray` The flat indices corresponding to the indices given in `item`. See Also -------- SynapticIndexing """ if index_var is None: index_var = self.default_index if hasattr(item, "_indices"): item = item._indices() if isinstance(item, tuple): raise IndexError( f"Can only interpret 1-d indices, got {len(item)} dimensions." ) else: if isinstance(item, str) and item == "True": item = slice(None) if isinstance(item, slice): if index_var == "0": return 0 if index_var == "_idx": start, stop, step = item.indices(self.N.item()) else: start, stop, step = item.indices(index_var.size) index_array = np.arange(start, stop, step, dtype=np.int32) else: index_array = np.asarray(item) if index_array.dtype == bool: index_array = np.nonzero(index_array)[0] elif not np.issubdtype(index_array.dtype, np.signedinteger): raise TypeError( "Indexing is only supported for integer " "and boolean arrays, not for type " f"{index_array.dtype}" ) if index_var != "_idx": try: return index_var.get_value()[index_array] except IndexError as ex: # We try to emulate numpy's indexing semantics here: # slices never lead to IndexErrors, instead they return an # empty array if they don't match anything if isinstance(item, slice): return np.array([], dtype=np.int32) else: raise ex else: return index_array class IndexWrapper: """ Convenience class to allow access to the indices via indexing syntax. This allows for example to get all indices for synapses originating from neuron 10 by writing `synapses.indices[10, :]` instead of `synapses._indices.((10, slice(None))`. """ def __init__(self, group): self.group = weakref.proxy(group) self.indices = group._indices def __getitem__(self, item): if isinstance(item, str): variables = Variables(None) variables.add_auxiliary_variable("_indices", dtype=np.int32) variables.add_auxiliary_variable("_cond", dtype=bool) abstract_code = f"_cond = {item}" namespace = get_local_namespace(level=1) from brian2.devices.device import get_device device = get_device() codeobj = create_runner_codeobj( self.group, abstract_code, "group_get_indices", run_namespace=namespace, additional_variables=variables, codeobj_class=device.code_object_class( fallback_pref="codegen.string_expression_target" ), ) return codeobj() else: return self.indices(item) class VariableOwner(Nameable): """ Mix-in class for accessing arrays by attribute. # TODO: Overwrite the __dir__ method to return the state variables # (should make autocompletion work) """ def _enable_group_attributes(self): if not hasattr(self, "variables"): raise ValueError( "Classes derived from VariableOwner need a variables attribute." ) if "N" not in self.variables: raise ValueError("Each VariableOwner needs an 'N' variable.") if not hasattr(self, "codeobj_class"): self.codeobj_class = None if not hasattr(self, "_indices"): self._indices = Indexing(self) if not hasattr(self, "indices"): self.indices = IndexWrapper(self) if not hasattr(self, "_stored_states"): self._stored_states = {} self._group_attribute_access_active = True def state(self, name, use_units=True, level=0): """ Return the state variable in a way that properly supports indexing in the context of this group Parameters ---------- name : str The name of the state variable use_units : bool, optional Whether to use the state variable's unit. level : int, optional How much farther to go down in the stack to find the namespace. Returns ------- var : `VariableView` or scalar value The state variable's value that can be indexed (for non-scalar values). """ try: var = self.variables[name] except KeyError as exc: raise KeyError(f"State variable {name} not found.") from exc if use_units: return var.get_addressable_value_with_unit(name=name, group=self) else: return var.get_addressable_value(name=name, group=self) def __getattr__(self, name): # We do this because __setattr__ and __getattr__ are not active until # _group_attribute_access_active attribute is set, and if it is set, # then __getattr__ will not be called. Therefore, if getattr is called # with this name, it is because it hasn't been set yet and so this # method should raise an AttributeError to agree that it hasn't been # called yet. if name == "_group_attribute_access_active": raise AttributeError if "_group_attribute_access_active" not in self.__dict__: raise AttributeError if ( name in self.__getattribute__("__dict__") or name in self.__getattribute__("__class__").__dict__ ): # Makes sure that classes can override the "variables" mechanism # with instance/class attributes and properties return object.__getattribute__(self, name) # We want to make sure that accessing variables without units is fast # because this is what is used during simulations # We do not specifically check for len(name) here, we simply assume # that __getattr__ is not called with an empty string (which wouldn't # be possible using the normal dot syntax, anyway) try: if name[-1] == "_": name = name[:-1] use_units = False else: use_units = True return self.state(name, use_units) except KeyError: raise AttributeError(f"No attribute with name {name}") def __setattr__(self, name, val, level=0): # attribute access is switched off until this attribute is created by # _enable_group_attributes if not hasattr(self, "_group_attribute_access_active") or name in self.__dict__: object.__setattr__(self, name, val) elif ( name in self.__getattribute__("__dict__") or name in self.__getattribute__("__class__").__dict__ ): # Makes sure that classes can override the "variables" mechanism # with instance/class attributes and properties return object.__setattr__(self, name, val) elif name in self.variables: var = self.variables[name] if not isinstance(val, str): if var.dim is DIMENSIONLESS: fail_for_dimension_mismatch( val, var.dim, "%s should be set with a dimensionless value, but got {value}" % name, value=val, ) else: fail_for_dimension_mismatch( val, var.dim, "%s should be set with a value with units %r, but got {value}" % (name, get_unit(var.dim)), value=val, ) if var.read_only: raise TypeError(f"Variable {name} is read-only.") # Make the call X.var = ... equivalent to X.var[:] = ... var.get_addressable_value_with_unit(name, self).set_item( slice(None), val, level=level + 1 ) elif len(name) and name[-1] == "_" and name[:-1] in self.variables: # no unit checking var = self.variables[name[:-1]] if var.read_only: raise TypeError(f"Variable {name[:-1]} is read-only.") # Make the call X.var = ... equivalent to X.var[:] = ... var.get_addressable_value(name[:-1], self).set_item( slice(None), val, level=level + 1 ) elif hasattr(self, name) or name.startswith("_"): object.__setattr__(self, name, val) else: # Try to suggest the correct name in case of a typo checker = SpellChecker( [ varname for varname, var in self.variables.items() if not (varname.startswith("_") or var.read_only) ] ) if name.endswith("_"): suffix = "_" name = name[:-1] else: suffix = "" error_msg = f'Could not find a state variable with name "{name}".' suggestions = checker.suggest(name) if len(suggestions) == 1: (suggestion,) = suggestions error_msg += f' Did you mean to write "{suggestion}{suffix}"?' elif len(suggestions) > 1: suggestion_str = ", ".join( [f"'{suggestion}{suffix}'" for suggestion in suggestions] ) error_msg += ( f" Did you mean to write any of the following: {suggestion_str} ?" ) error_msg += ( " Use the add_attribute method if you intend to add " "a new attribute to the object." ) raise AttributeError(error_msg) def add_attribute(self, name): """ Add a new attribute to this group. Using this method instead of simply assigning to the new attribute name is necessary because Brian will raise an error in that case, to avoid bugs passing unnoticed (misspelled state variable name, un-declared state variable, ...). Parameters ---------- name : str The name of the new attribute Raises ------ AttributeError If the name already exists as an attribute or a state variable. """ if name in self.variables: raise AttributeError( f'Cannot add an attribute "{name}", it is already a state variable of' " this group." ) if hasattr(self, name): raise AttributeError( f'Cannot add an attribute "{name}", it is already an attribute of this' " group." ) object.__setattr__(self, name, None) def get_states( self, vars=None, units=True, format="dict", subexpressions=False, read_only_variables=True, level=0, ): """ Return a copy of the current state variable values. The returned arrays are copies of the actual arrays that store the state variable values, therefore changing the values in the returned dictionary will not affect the state variables. Parameters ---------- vars : list of str, optional The names of the variables to extract. If not specified, extract all state variables (except for internal variables, i.e. names that start with ``'_'``). If the ``subexpressions`` argument is ``True``, the current values of all subexpressions are returned as well. units : bool, optional Whether to include the physical units in the return value. Defaults to ``True``. format : str, optional The output format. Defaults to ``'dict'``. subexpressions: bool, optional Whether to return subexpressions when no list of variable names is given. Defaults to ``False``. This argument is ignored if an explicit list of variable names is given in ``vars``. read_only_variables : bool, optional Whether to return read-only variables (e.g. the number of neurons, the time, etc.). Setting it to ``False`` will assure that the returned state can later be used with `set_states`. Defaults to ``True``. level : int, optional How much higher to go up the stack to resolve external variables. Only relevant if extracting subexpressions that refer to external variables. Returns ------- values : dict or specified format The variables specified in ``vars``, in the specified ``format``. """ if format not in ImportExport.methods: raise NotImplementedError(f"Format '{format}' is not supported") if vars is None: vars = [] for name, var in self.variables.items(): if name.startswith("_"): continue if subexpressions or not isinstance(var, Subexpression): if read_only_variables or not getattr(var, "read_only", False): if not isinstance(var, AuxiliaryVariable): vars.append(name) data = ImportExport.methods[format].export_data( self, vars, units=units, level=level ) return data def set_states(self, values, units=True, format="dict", level=0): """ Set the state variables. Parameters ---------- values : depends on ``format`` The values according to ``format``. units : bool, optional Whether the ``values`` include physical units. Defaults to ``True``. format : str, optional The format of ``values``. Defaults to ``'dict'`` level : int, optional How much higher to go up the stack to resolve external variables. Only relevant when using string expressions to set values. """ # For the moment, 'dict' is the only supported format -- later this will # be made into an extensible system, see github issue #306 if format not in ImportExport.methods: raise NotImplementedError(f"Format '{format}' is not supported") ImportExport.methods[format].import_data(self, values, units=units, level=level) def check_variable_write(self, variable): """ Function that can be overwritten to raise an error if writing to a variable should not be allowed. Note that this does *not* deal with incorrect writes that are general to all kind of variables (incorrect units, writing to a read-only variable, etc.). This function is only used for type-specific rules, e.g. for raising an error in `Synapses` when writing to a synaptic variable before any `~Synapses.connect` call. By default this function does nothing. Parameters ---------- variable : `Variable` The variable that the user attempts to set. """ pass def _full_state(self): state = {} for var in self.variables.values(): if not isinstance(var, ArrayVariable): continue # we are only interested in arrays if var.owner is None or var.owner.name != self.name: continue # we only store the state of our own variables state[var.name] = (var.get_value().copy(), var.size) return state def _restore_from_full_state(self, state): for var_name, (values, size) in state.items(): var = self.variables[var_name] if isinstance(var, DynamicArrayVariable): var.resize(size) var.set_value(values) def _check_expression_scalar(self, expr, varname, level=0, run_namespace=None): """ Helper function to check that an expression only refers to scalar variables, used when setting a scalar variable with a string expression. Parameters ---------- expr : str The expression to check. varname : str The variable that is being set (only used for the error message) level : int, optional How far to go up in the stack to find the local namespace (if `run_namespace` is not set). run_namespace : dict-like, optional A specific namespace provided for this expression. Raises ------ ValueError If the expression refers to a non-scalar variable. """ identifiers = get_identifiers(expr) referred_variables = self.resolve_all( identifiers, run_namespace=run_namespace, level=level + 1 ) for ref_varname, ref_var in referred_variables.items(): if not getattr(ref_var, "scalar", False): raise ValueError( "String expression for setting scalar " f"variable '{varname} refers to '{ref_varname} which " "is not scalar." ) def __len__(self): return self.variables["N"].item() class Group(VariableOwner, BrianObject): def _resolve( self, identifier, run_namespace, user_identifier=True, additional_variables=None ): """ Resolve an identifier (i.e. variable, constant or function name) in the context of this group. This function will first lookup the name in the state variables, then look for a standard function or unit of that name and finally look in `Group.namespace` and in `run_namespace`. If the latter is not given, it will try to find the variable in the local namespace where the original function call took place. See :ref:`external-variables`. Parameters ---------- identifiers : str The name to look up. run_namespace : dict-like, optional An additional namespace that is used for variable lookup (if not defined, the implicit namespace of local variables is used). user_identifier : bool, optional Whether this is an identifier that was used by the user (and not something automatically generated that the user might not even know about). Will be used to determine whether to display a warning in the case of namespace clashes. Defaults to ``True``. additional_variables : dict-like, optional An additional mapping of names to `Variable` objects that will be checked before `Group.variables`. Returns ------- obj : `Variable` or `Function` Returns a `Variable` object describing the variable or a `Function` object for a function. External variables are represented as `Constant` objects Raises ------ KeyError If the `identifier` could not be resolved """ resolved_internal = None if identifier in (additional_variables or {}): resolved_internal = additional_variables[identifier] elif identifier in getattr(self, "variables", {}): resolved_internal = self.variables[identifier] if resolved_internal is not None: if not user_identifier: return resolved_internal # no need to go further # We already found the identifier, but we try to resolve it in the # external namespace nevertheless, to report a warning if it is # present there as well. self._resolve_external( identifier, run_namespace=run_namespace, internal_variable=resolved_internal, ) return resolved_internal # We did not find the name internally, try to resolve it in the external # namespace return self._resolve_external(identifier, run_namespace=run_namespace) def resolve_all( self, identifiers, run_namespace, user_identifiers=None, additional_variables=None, ): """ Resolve a list of identifiers. Calls `Group._resolve` for each identifier. Parameters ---------- identifiers : iterable of str The names to look up. run_namespace : dict-like, optional An additional namespace that is used for variable lookup (if not defined, the implicit namespace of local variables is used). user_identifiers : iterable of str, optional The names in ``identifiers`` that were provided by the user (i.e. are part of user-specified equations, abstract code, etc.). Will be used to determine when to issue namespace conflict warnings. If not specified, will be assumed to be identical to ``identifiers``. additional_variables : dict-like, optional An additional mapping of names to `Variable` objects that will be checked before `Group.variables`. Returns ------- variables : dict of `Variable` or `Function` A mapping from name to `Variable`/`Function` object for each of the names given in `identifiers` Raises ------ KeyError If one of the names in `identifier` cannot be resolved """ if user_identifiers is None: user_identifiers = identifiers assert isinstance(run_namespace, Mapping) resolved = {} for identifier in identifiers: resolved[identifier] = self._resolve( identifier, user_identifier=identifier in user_identifiers, additional_variables=additional_variables, run_namespace=run_namespace, ) return resolved def _resolve_external( self, identifier, run_namespace, user_identifier=True, internal_variable=None ): """ Resolve an external identifier in the context of a `Group`. If the `Group` declares an explicit namespace, this namespace is used in addition to the standard namespace for units and functions. Additionally, the namespace in the `run_namespace` argument (i.e. the namespace provided to `Network.run`) is used. Parameters ---------- identifier : str The name to resolve. group : `Group` The group that potentially defines an explicit namespace for looking up external names. run_namespace : dict A namespace (mapping from strings to objects), as provided as an argument to the `Network.run` function or returned by `get_local_namespace`. user_identifier : bool, optional Whether this is an identifier that was used by the user (and not something automatically generated that the user might not even know about). Will be used to determine whether to display a warning in the case of namespace clashes. Defaults to ``True``. internal_variable : `Variable`, optional The internal variable object that corresponds to this name (if any). This is used to give warnings if it also corresponds to a variable from an external namespace. """ # We save tuples of (namespace description, referred object) to # give meaningful warnings in case of duplicate definitions matches = [] namespaces = OrderedDict() # Default namespaces (units and functions) namespaces["constants"] = DEFAULT_CONSTANTS namespaces["units"] = DEFAULT_UNITS namespaces["functions"] = DEFAULT_FUNCTIONS if getattr(self, "namespace", None) is not None: namespaces["group-specific"] = self.namespace # explicit or implicit run namespace namespaces["run"] = run_namespace for description, namespace in namespaces.items(): if identifier in namespace: match = namespace[identifier] if ( isinstance( match, (numbers.Number, np.ndarray, np.number, Function, Variable), ) ) or ( inspect.isfunction(match) and hasattr(match, "_arg_units") and hasattr(match, "_return_unit") ): matches.append((description, match)) if len(matches) == 0: # No match at all if internal_variable is not None: return None else: # Give a more detailed explanation for the lastupdate variable # that was removed with PR #1003 if identifier == "lastupdate": error_msg = ( 'The identifier "lastupdate" could not be ' "resolved. Note that this variable is only " "automatically defined for models with " "event-driven synapses. You can define it " 'manually by adding "lastupdate : second" to ' 'the equations and setting "lastupdate = t" ' "at the end of your on_pre and/or on_post " "statements." ) else: error_msg = f'The identifier "{identifier}" could not be resolved.' raise KeyError(error_msg) elif len(matches) > 1: # Possibly, all matches refer to the same object first_obj = matches[0][1] found_mismatch = False for m in matches: if _same_value(m[1], first_obj): continue if _same_function(m[1], first_obj): continue try: proxy = weakref.proxy(first_obj) if m[1] is proxy: continue except TypeError: pass # Found a mismatch found_mismatch = True break if found_mismatch and user_identifier and internal_variable is None: _conflict_warning( 'The name "%s" refers to different objects ' "in different namespaces used for resolving " 'names in the context of group "%s". ' "Will use the object from the %s namespace " "with the value %s," % ( identifier, getattr(self, "name", ""), matches[0][0], _display_value(first_obj), ), matches[1:], ) if internal_variable is not None and user_identifier: # Filter out matches that are identical (a typical case being an # externally defined "N" with the the number of neurons and a later # use of "N" in an expression (which refers to the internal variable # storing the number of neurons in the group) if isinstance(internal_variable, Constant): filtered_matches = [] for match in matches: if not _same_value(match[1], internal_variable): filtered_matches.append(match) else: filtered_matches = matches if len(filtered_matches) == 0: pass # Nothing to warn about else: warning_message = ( f"'{identifier}' is an internal variable of group " f"'{self.name}', but also exists in the " ) if len(matches) == 1: namespace = filtered_matches[0][0] value = _display_value(filtered_matches[0][1]) warning_message += f"{namespace} namespace with the value {value}. " else: namespaces_list = " ,".join(match[0] for match in filtered_matches) warning_message += f"following namespaces: {namespaces_list}. " warning_message += "The internal variable will be used." logger.warn( warning_message, "Group.resolve.resolution_conflict", once=True ) if internal_variable is not None: return None # We were only interested in the warnings above # use the first match (according to resolution order) resolved = matches[0][1] # Replace pure Python functions by a Functions object if callable(resolved) and not isinstance(resolved, Function): resolved = Function( resolved, arg_units=getattr(resolved, "_arg_units", None), arg_names=getattr(resolved, "_arg_names", None), return_unit=getattr(resolved, "_return_unit", None), stateless=getattr(resolved, "stateless", False), ) if not isinstance(resolved, (Function, Variable)): # Wrap the value in a Constant object dimensions = getattr(resolved, "dim", DIMENSIONLESS) value = np.asarray(resolved) if value.shape != (): raise KeyError( f"Variable {identifier} was found in the namespace, but is not a" " scalar value" ) resolved = Constant(identifier, dimensions=dimensions, value=value) return resolved def runner(self, *args, **kwds): raise AttributeError("The 'runner' method has been renamed to 'run_regularly'.") def custom_operation(self, *args, **kwds): raise AttributeError( "The 'custom_operation' method has been renamed to 'run_regularly'." ) def run_regularly( self, code, dt=None, clock=None, when="start", order=0, name=None, codeobj_class=None, ): """ Run abstract code in the group's namespace. The created `CodeRunner` object will be automatically added to the group, it therefore does not need to be added to the network manually. However, a reference to the object will be returned, which can be used to later remove it from the group or to set it to inactive. Parameters ---------- code : str The abstract code to run. dt : `Quantity`, optional The time step to use for this custom operation. Cannot be combined with the `clock` argument. clock : `Clock`, optional The update clock to use for this operation. If neither a clock nor the `dt` argument is specified, defaults to the clock of the group. when : str, optional When to run within a time step, defaults to the ``'start'`` slot. See :ref:`scheduling` for possible values. name : str, optional A unique name, if non is given the name of the group appended with 'run_regularly', 'run_regularly_1', etc. will be used. If a name is given explicitly, it will be used as given (i.e. the group name will not be prepended automatically). codeobj_class : class, optional The `CodeObject` class to run code with. If not specified, defaults to the `group`'s ``codeobj_class`` attribute. Returns ------- obj : `CodeRunner` A reference to the object that will be run. """ if name is None: names = [o.name for o in self.contained_objects] name = find_name(f"{self.name}_run_regularly*", names) if dt is None and clock is None: clock = self._clock # Subgroups are normally not included in their parent's # contained_objects list, since there's no need to include them in the # network (they don't perform any computation on their own). However, # if a subgroup declares a `run_regularly` operation, then we want to # include this operation automatically, i.e. with the parent group # (adding just the run_regularly operation to the parent group's # contained objects would no be enough, since the CodeObject needs a # reference to the group providing the context for the operation, i.e. # the subgroup instead of the parent group. See github issue #922 source_group = getattr(self, "source", None) if source_group is not None: if self not in source_group.contained_objects: source_group.contained_objects.append(self) runner = CodeRunner( self, "stateupdate", code=code, name=name, dt=dt, clock=clock, when=when, order=order, codeobj_class=codeobj_class, ) self.contained_objects.append(runner) return runner def _check_for_invalid_states(self): """ Checks if any state variables updated by differential equations have invalid values, and logs a warning if so. """ equations = getattr(self, "equations", None) if not isinstance(equations, Equations): return for varname in equations.diff_eq_names: self._check_for_invalid_values( varname, self.state(varname, use_units=False) ) def _check_for_invalid_values(self, k, v): """ Checks if variable named k value v has invalid values, and logs a warning if so. """ v = np.asarray(v) if np.isnan(v).any() or (np.abs(v) > 1e50).any(): logger.warn( f"{self.name}'s variable '{k}' has NaN, very large values, " "or encountered an error in numerical integration. " "This is usually a sign that an unstable or invalid " "integration method was " "chosen.", name_suffix="invalid_values", once=True, ) class CodeRunner(BrianObject): """ A "code runner" that runs a `CodeObject` every timestep and keeps a reference to the `Group`. Used in `NeuronGroup` for `Thresholder`, `Resetter` and `StateUpdater`. On creation, we try to run the before_run method with an empty additional namespace (see `Network.before_run`). If the namespace is already complete this might catch unit mismatches. Parameters ---------- group : `Group` The group to which this object belongs. template : `Template` The template that should be used for code generation code : str, optional The abstract code that should be executed every time step. The `update_abstract_code` method might generate this code dynamically before every run instead. dt : `Quantity`, optional The time step to be used for the simulation. Cannot be combined with the `clock` argument. user_code : str, optional The abstract code as specified by the user, i.e. without any additions of internal code that the user not necessarily knows about. This will be used for warnings and error messages. clock : `Clock`, optional The update clock to be used. If neither a clock, nor the `dt` argument is specified, the `defaultclock` will be used. when : str, optional In which scheduling slot to execute the operation during a time step. Defaults to ``'start'``. See :ref:`scheduling` for possible values. order : int, optional The priority of this operation for operations occurring at the same time step and in the same scheduling slot. Defaults to 0. name : str, optional The name for this object. check_units : bool, optional Whether the units should be checked for consistency before a run. Is activated (``True``) by default but should be switched off for state updaters (units are already checked for the equations and the generated abstract code might have already replaced variables with their unit-less values) template_kwds : dict, optional A dictionary of additional information that is passed to the template. needed_variables: list of str, optional A list of variables that are neither present in the abstract code, nor in the ``USES_VARIABLES`` statement in the template. This is only rarely necessary, an example being a `StateMonitor` where the names of the variables are neither known to the template nor included in the abstract code statements. override_conditional_write: list of str, optional A list of variable names which are used as conditions (e.g. for refractoriness) which should be ignored. codeobj_class : class, optional The `CodeObject` class to run code with. If not specified, defaults to the `group`'s ``codeobj_class`` attribute. generate_empty_code : bool, optional Whether to generate a `CodeObject` if there is no abstract code to execute. Defaults to ``True`` but should be switched off e.g. for a `StateUpdater` when there is nothing to do. """ add_to_magic_network = True invalidates_magic_network = True def __init__( self, group, template, code="", user_code=None, dt=None, clock=None, when="start", order=0, name="coderunner*", check_units=True, template_kwds=None, needed_variables=None, override_conditional_write=None, codeobj_class=None, generate_empty_code=True, ): BrianObject.__init__( self, clock=clock, dt=dt, when=when, order=order, name=name ) self.group = weakproxy_with_fallback(group) self.template = template self.user_code = user_code self.abstract_code = code self.check_units = check_units if needed_variables is None: needed_variables = [] self.needed_variables = needed_variables self.template_kwds = template_kwds self.override_conditional_write = override_conditional_write if codeobj_class is None: codeobj_class = group.codeobj_class self.codeobj_class = codeobj_class self.generate_empty_code = generate_empty_code self.codeobj = None def update_abstract_code(self, run_namespace): """ Update the abstract code for the code object. Will be called in `before_run` and should update the `CodeRunner.abstract_code` attribute. Does nothing by default. """ pass def create_default_code_object(self, run_namespace): self.update_abstract_code(run_namespace=run_namespace) # If the CodeRunner has variables, add them if hasattr(self, "variables"): additional_variables = self.variables else: additional_variables = None if not self.generate_empty_code and len(self.abstract_code) == 0: self.codeobj = None else: self.codeobj = create_runner_codeobj( group=self.group, code=self.abstract_code, user_code=self.user_code, template_name=self.template, name=f"{self.name}_codeobject*", check_units=self.check_units, additional_variables=additional_variables, needed_variables=self.needed_variables, run_namespace=run_namespace, template_kwds=self.template_kwds, override_conditional_write=self.override_conditional_write, codeobj_class=self.codeobj_class, ) return self.codeobj def create_code_objects(self, run_namespace): # By default, we only have one code object for each CodeRunner. # Overwrite this function to use more than one. code_object = self.create_default_code_object(run_namespace) if code_object: self.code_objects[:] = [weakref.proxy(code_object)] else: self.code_objects[:] = [] def before_run(self, run_namespace): self.create_code_objects(run_namespace) super().before_run(run_namespace) brian2-2.5.4/brian2/groups/neurongroup.py000066400000000000000000001171411445201106100203270ustar00rootroot00000000000000""" This model defines the `NeuronGroup`, the core of most simulations. """ import numbers import string from collections.abc import MutableMapping, Sequence import numpy as np import sympy from pyparsing import Word from brian2.codegen.translation import analyse_identifiers from brian2.core.preferences import prefs from brian2.core.spikesource import SpikeSource from brian2.core.variables import ( DynamicArrayVariable, LinkedVariable, Subexpression, Variables, ) from brian2.equations.equations import ( DIFFERENTIAL_EQUATION, PARAMETER, SUBEXPRESSION, Equations, check_subexpressions, extract_constant_subexpressions, ) from brian2.equations.refractory import add_refractoriness from brian2.parsing.expressions import ( is_boolean_expression, parse_expression_dimensions, ) from brian2.stateupdaters.base import StateUpdateMethod from brian2.units.allunits import second from brian2.units.fundamentalunits import ( DIMENSIONLESS, DimensionMismatchError, Quantity, fail_for_dimension_mismatch, ) from brian2.utils.logger import get_logger from brian2.utils.stringtools import get_identifiers from .group import CodeRunner, Group, get_dtype from .subgroup import Subgroup __all__ = ["NeuronGroup"] logger = get_logger(__name__) IDENTIFIER = Word( f"{string.ascii_letters}_", f"{string.ascii_letters + string.digits}_" ).setResultsName("identifier") def _valid_event_name(event_name): """ Helper function to check whether a name is a valid name for an event. Parameters ---------- event_name : str The name to check Returns ------- is_valid : bool Whether the given name is valid """ parse_result = list(IDENTIFIER.scanString(event_name)) # parse_result[0][0][0] refers to the matched string -- this should be the # full identifier, if not it is an illegal identifier like "3foo" which only # matched on "foo" return len(parse_result) == 1 and parse_result[0][0][0] == event_name def _guess_membrane_potential(equations): """ Little helper function to guess which variable represents the membrane potential. This follows the same logic as in Brian1 but is only used to give a suggestion in the error message when a Brian1-style syntax is used for threshold or reset. """ if len(equations) == 1: return list(equations.keys())[0] for name in equations: if name in ["V", "v", "Vm", "vm"]: return name # nothing found return None # Note that we do not register this function with # Equations.register_identifier_check, because we do not want this check to # apply unconditionally to all equation objects ("x_post = ... : ... (summed)" # needs to be allowed) def check_identifier_pre_post(identifier): "Do not allow names ending in ``_pre`` or ``_post`` to avoid confusion." if identifier.endswith("_pre") or identifier.endswith("_post"): raise ValueError( f"'{identifier}' cannot be used as a variable name, the " "'_pre' and '_post' suffixes are used to refer to pre- and " "post-synaptic variables in synapses." ) def to_start_stop(item, N): """ Helper function to transform a single number, a slice or an array of contiguous indices to a start and stop value. This is used to allow for some flexibility in the syntax of specifying subgroups in `.NeuronGroup` and `.SpatialNeuron`. Parameters ---------- item : slice, int or sequence The slice, index, or sequence of indices to use. Note that a sequence of indices has to be a sorted ascending sequence of subsequent integers. N : int The total number of elements in the group. Returns ------- start : int The start value of the slice. stop : int The stop value of the slice. Examples -------- >>> from brian2.groups.neurongroup import to_start_stop >>> to_start_stop(slice(3, 6), 10) (3, 6) >>> to_start_stop(slice(3, None), 10) (3, 10) >>> to_start_stop(5, 10) (5, 6) >>> to_start_stop([3, 4, 5], 10) (3, 6) >>> to_start_stop([3, 5, 7], 10) Traceback (most recent call last): ... IndexError: Subgroups can only be constructed using contiguous indices. """ if isinstance(item, slice): start, stop, step = item.indices(N) elif isinstance(item, numbers.Integral): start = item stop = item + 1 step = 1 elif isinstance(item, (Sequence, np.ndarray)) and not isinstance(item, str): if not (len(item) > 0 and np.all(np.diff(item) == 1)): raise IndexError( "Subgroups can only be constructed using contiguous indices." ) if not np.issubdtype(np.asarray(item).dtype, np.integer): raise TypeError("Subgroups can only be constructed using integer values.") start = int(item[0]) stop = int(item[-1]) + 1 step = 1 else: raise TypeError( "Subgroups can only be constructed using slicing " "syntax, a single index, or an array of contiguous " "indices." ) if step != 1: raise IndexError("Subgroups have to be contiguous") if start >= stop: raise IndexError( f"Illegal start/end values for subgroup, {int(start)}>={int(stop)}" ) if start >= N: raise IndexError(f"Illegal start value for subgroup, {int(start)}>={int(N)}") if stop > N: raise IndexError(f"Illegal stop value for subgroup, {int(stop)}>{int(N)}") if start < 0: raise IndexError("Indices have to be positive.") return start, stop class StateUpdater(CodeRunner): """ The `CodeRunner` that updates the state variables of a `NeuronGroup` at every timestep. """ def __init__(self, group, method, method_options=None): self.method_choice = method self.method_options = method_options CodeRunner.__init__( self, group, "stateupdate", code="", # will be set in update_abstract_code clock=group.clock, when="groups", order=group.order, name=f"{group.name}_stateupdater", check_units=False, generate_empty_code=False, ) def _get_refractory_code(self, run_namespace): ref = self.group._refractory if ref is False: # No refractoriness abstract_code = "" elif isinstance(ref, Quantity): fail_for_dimension_mismatch( ref, second, "Refractory period has to " "be specified in units " "of seconds but got " "{value}", value=ref, ) if prefs.legacy.refractory_timing: abstract_code = f"not_refractory = (t - lastspike) > {ref:f}\n" else: abstract_code = ( f"not_refractory = timestep(t - lastspike, dt) >= timestep({ref:f}," " dt)\n" ) else: identifiers = get_identifiers(ref) variables = self.group.resolve_all( identifiers, run_namespace, user_identifiers=identifiers ) dims = parse_expression_dimensions(str(ref), variables) if dims is second.dim: if prefs.legacy.refractory_timing: abstract_code = f"(t - lastspike) > {ref}\n" else: abstract_code = ( "not_refractory = timestep(t - lastspike, dt) >=" f" timestep({ref}, dt)\n" ) elif dims is DIMENSIONLESS: if not is_boolean_expression(str(ref), variables): raise TypeError( "Refractory expression is dimensionless " "but not a boolean value. It needs to " "either evaluate to a timespan or to a " "boolean value." ) # boolean condition # we have to be a bit careful here, we can't just use the given # condition as it is, because we only want to *leave* # refractoriness, based on the condition abstract_code = f"not_refractory = not_refractory or not ({ref})\n" else: raise TypeError( "Refractory expression has to evaluate to a " "timespan or a boolean value, expression" f"'{ref}' has units {dims} instead" ) return abstract_code def update_abstract_code(self, run_namespace): # Update the not_refractory variable for the refractory period mechanism self.abstract_code = self._get_refractory_code(run_namespace=run_namespace) # Get the names used in the refractory code _, used_known, unknown = analyse_identifiers( self.abstract_code, self.group.variables, recursive=True ) # Get all names used in the equations (and always get "dt") names = self.group.equations.names external_names = self.group.equations.identifiers | {"dt"} variables = self.group.resolve_all( used_known | unknown | names | external_names, run_namespace, # we don't need to raise any warnings # for the user here, warnings will # be raised in create_runner_codeobj user_identifiers=set(), ) if len(self.group.equations.diff_eq_names) > 0: stateupdate_output = StateUpdateMethod.apply_stateupdater( self.group.equations, variables, self.method_choice, method_options=self.method_options, group_name=self.group.name, ) if isinstance(stateupdate_output, str): self.abstract_code += stateupdate_output else: # Note that the reason to send self along with this method is so the StateUpdater # can be modified! i.e. in GSL StateUpdateMethod a custom CodeObject gets added # to the StateUpdater together with some auxiliary information self.abstract_code += stateupdate_output(self) user_code = "\n".join( [ f"{var} = {expr}" for var, expr in self.group.equations.get_substituted_expressions( variables ) ] ) self.user_code = user_code class SubexpressionUpdater(CodeRunner): """ The `CodeRunner` that updates the state variables storing the values of subexpressions that have been marked as "constant over dt". """ def __init__(self, group, subexpressions, when="before_start"): code_lines = [] for subexpr in subexpressions.ordered: code_lines.append(f"{subexpr.varname} = {subexpr.expr}") code = "\n".join(code_lines) CodeRunner.__init__( self, group, "stateupdate", code=code, # will be set in update_abstract_code clock=group.clock, when=when, order=group.order, name=f"{group.name}_subexpression_update*", ) class Thresholder(CodeRunner): """ The `CodeRunner` that applies the threshold condition to the state variables of a `NeuronGroup` at every timestep and sets its ``spikes`` and ``refractory_until`` attributes. """ def __init__(self, group, when="thresholds", event="spike"): self.event = event if group._refractory is False or event != "spike": template_kwds = {"_uses_refractory": False} needed_variables = [] else: template_kwds = {"_uses_refractory": True} needed_variables = ["t", "not_refractory", "lastspike"] # Since this now works for general events not only spikes, we have to # pass the information about which variable to use to the template, # it can not longer simply refer to "_spikespace" eventspace_name = f"_{event}space" template_kwds["eventspace_variable"] = group.variables[eventspace_name] needed_variables.append(eventspace_name) self.variables = Variables(self) self.variables.add_auxiliary_variable("_cond", dtype=bool) CodeRunner.__init__( self, group, "threshold", code="", # will be set in update_abstract_code clock=group.clock, when=when, order=group.order, name=f"{group.name}_{event}_thresholder", needed_variables=needed_variables, template_kwds=template_kwds, ) def update_abstract_code(self, run_namespace): code = self.group.events[self.event] # Raise a useful error message when the user used a Brian1 syntax if not isinstance(code, str): if isinstance(code, Quantity): t = "a quantity" else: t = f"{type(code)}" error_msg = f"Threshold condition has to be a string, not {t}." if self.event == "spike": try: vm_var = _guess_membrane_potential(self.group.equations) except AttributeError: # not a group with equations... vm_var = None if vm_var is not None: error_msg += f" Probably you intended to use '{vm_var} > ...'?" raise TypeError(error_msg) self.user_code = f"_cond = {code}" identifiers = get_identifiers(code) variables = self.group.resolve_all( identifiers, run_namespace, user_identifiers=identifiers ) if not is_boolean_expression(code, variables): raise TypeError(f"Threshold condition '{code}' is not a boolean expression") if self.group._refractory is False or self.event != "spike": self.abstract_code = f"_cond = {code}" else: self.abstract_code = f"_cond = ({code}) and not_refractory" class Resetter(CodeRunner): """ The `CodeRunner` that applies the reset statement(s) to the state variables of neurons that have spiked in this timestep. """ def __init__(self, group, when="resets", order=None, event="spike"): self.event = event # Since this now works for general events not only spikes, we have to # pass the information about which variable to use to the template, # it can not longer simply refer to "_spikespace" eventspace_name = f"_{event}space" template_kwds = {"eventspace_variable": group.variables[eventspace_name]} needed_variables = [eventspace_name] order = order if order is not None else group.order CodeRunner.__init__( self, group, "reset", code="", # will be set in update_abstract_code clock=group.clock, when=when, order=order, name=f"{group.name}_{event}_resetter", override_conditional_write=["not_refractory"], needed_variables=needed_variables, template_kwds=template_kwds, ) def update_abstract_code(self, run_namespace): code = self.group.event_codes[self.event] # Raise a useful error message when the user used a Brian1 syntax if not isinstance(code, str): if isinstance(code, Quantity): t = "a quantity" else: t = f"{type(code)}" error_msg = f"Reset statement has to be a string, not {t}." if self.event == "spike": vm_var = _guess_membrane_potential(self.group.equations) if vm_var is not None: error_msg += f" Probably you intended to use '{vm_var} = ...'?" raise TypeError(error_msg) self.abstract_code = code class NeuronGroup(Group, SpikeSource): """ A group of neurons. Parameters ---------- N : int Number of neurons in the group. model : str, `Equations` The differential equations defining the group method : (str, function), optional The numerical integration method. Either a string with the name of a registered method (e.g. "euler") or a function that receives an `Equations` object and returns the corresponding abstract code. If no method is specified, a suitable method will be chosen automatically. threshold : str, optional The condition which produces spikes. Should be a single line boolean expression. reset : str, optional The (possibly multi-line) string with the code to execute on reset. refractory : {str, `Quantity`}, optional Either the length of the refractory period (e.g. ``2*ms``), a string expression that evaluates to the length of the refractory period after each spike (e.g. ``'(1 + rand())*ms'``), or a string expression evaluating to a boolean value, given the condition under which the neuron stays refractory after a spike (e.g. ``'v > -20*mV'``) events : dict, optional User-defined events in addition to the "spike" event defined by the ``threshold``. Has to be a mapping of strings (the event name) to strings (the condition) that will be checked. namespace: dict, optional A dictionary mapping identifier names to objects. If not given, the namespace will be filled in at the time of the call of `Network.run`, with either the values from the ``namespace`` argument of the `Network.run` method or from the local context, if no such argument is given. dtype : (`dtype`, `dict`), optional The `numpy.dtype` that will be used to store the values, or a dictionary specifying the type for variable names. If a value is not provided for a variable (or no value is provided at all), the preference setting `core.default_float_dtype` is used. codeobj_class : class, optional The `CodeObject` class to run code with. dt : `Quantity`, optional The time step to be used for the simulation. Cannot be combined with the `clock` argument. clock : `Clock`, optional The update clock to be used. If neither a clock, nor the `dt` argument is specified, the `defaultclock` will be used. order : int, optional The priority of of this group for operations occurring at the same time step and in the same scheduling slot. Defaults to 0. name : str, optional A unique name for the group, otherwise use ``neurongroup_0``, etc. Notes ----- `NeuronGroup` contains a `StateUpdater`, `Thresholder` and `Resetter`, and these are run at the 'groups', 'thresholds' and 'resets' slots (i.e. the values of their `when` attribute take these values). The `order` attribute will be passed down to the contained objects but can be set individually by setting the `order` attribute of the `state_updater`, `thresholder` and `resetter` attributes, respectively. """ add_to_magic_network = True def __init__( self, N, model, method=("exact", "euler", "heun"), method_options=None, threshold=None, reset=None, refractory=False, events=None, namespace=None, dtype=None, dt=None, clock=None, order=0, name="neurongroup*", codeobj_class=None, ): Group.__init__( self, dt=dt, clock=clock, when="start", order=order, namespace=namespace, name=name, ) if dtype is None: dtype = {} if isinstance(dtype, MutableMapping): dtype["lastspike"] = self._clock.variables["t"].dtype self.codeobj_class = codeobj_class try: self._N = N = int(N) except ValueError: if isinstance(N, str): raise TypeError( "First NeuronGroup argument should be size, not equations." ) raise if N < 1: raise ValueError(f"NeuronGroup size should be at least 1, was {str(N)}") self.start = 0 self.stop = self._N ##### Prepare and validate equations if isinstance(model, str): model = Equations(model) if not isinstance(model, Equations): raise TypeError( "model has to be a string or an Equations " f"object, is '{type(model)}' instead." ) # Check flags model.check_flags( { DIFFERENTIAL_EQUATION: ("unless refractory",), PARAMETER: ("constant", "shared", "linked"), SUBEXPRESSION: ("shared", "constant over dt"), } ) # add refractoriness #: The original equations as specified by the user (i.e. without #: the multiplied `int(not_refractory)` term for equations marked as #: `(unless refractory)`) self.user_equations = model if refractory is not False: model = add_refractoriness(model) uses_refractoriness = len(model) and any( [ "unless refractory" in eq.flags for eq in model.values() if eq.type == DIFFERENTIAL_EQUATION ] ) # Separate subexpressions depending whether they are considered to be # constant over a time step or not model, constant_over_dt = extract_constant_subexpressions(model) self.equations = model self._linked_variables = set() logger.diagnostic( f"Creating NeuronGroup of size {self._N}, equations {self.equations}." ) # All of the following will be created in before_run #: The refractory condition or timespan self._refractory = refractory if uses_refractoriness and refractory is False: logger.warn( 'Model equations use the "unless refractory" flag but ' "no refractory keyword was given.", "no_refractory", ) #: The state update method selected by the user self.method_choice = method if events is None: events = {} if threshold is not None and (reset is None and refractory is False): if not ("rand(" in threshold or "randn(" in threshold): logger.warn( f"The NeuronGroup '{self.name}' sets a threshold but " "neither a reset condition nor a refractory " "condition has been set. Did you forget either of " "those? If this was intended, set the reset " "argument to an empty string in order to avoid " "this warning.", name_suffix="only_threshold", ) if threshold is not None: if "spike" in events: raise ValueError( "The NeuronGroup defines both a threshold and a 'spike' event" ) events["spike"] = threshold # Setup variables # Since we have to create _spikespace and possibly other "eventspace" # variables, we pass the supported events self._create_variables(dtype, events=list(events.keys())) #: Events supported by this group self.events = events #: Code that is triggered on events (e.g. reset) self.event_codes = {} #: Checks the spike threshold (or abitrary user-defined events) self.thresholder = {} #: Reset neurons which have spiked (or perform arbitrary actions for #: user-defined events) self.resetter = {} for event_name in events.keys(): if not isinstance(event_name, str): raise TypeError( "Keys in the 'events' dictionary have to be " f"strings, not type {event_name}." ) if not _valid_event_name(event_name): raise TypeError( f"The name '{event_name}' cannot be used as an event name." ) # By default, user-defined events are checked after the threshold when = "thresholds" if event_name == "spike" else "after_thresholds" # creating a Thresholder will take care of checking the validity # of the condition thresholder = Thresholder(self, event=event_name, when=when) self.thresholder[event_name] = thresholder self.contained_objects.append(thresholder) if reset is not None: self.run_on_event("spike", reset, when="resets") #: Performs numerical integration step self.state_updater = StateUpdater(self, method, method_options) self.contained_objects.append(self.state_updater) #: Update the "constant over a time step" subexpressions self.subexpression_updater = None if len(constant_over_dt): self.subexpression_updater = SubexpressionUpdater(self, constant_over_dt) self.contained_objects.append(self.subexpression_updater) if refractory is not False: # Set the refractoriness information self.variables["lastspike"].set_value(-1e4 * second) self.variables["not_refractory"].set_value(True) # Activate name attribute access self._enable_group_attributes() @property def spikes(self): """ The spikes returned by the most recent thresholding operation. """ # Note that we have to directly access the ArrayVariable object here # instead of using the Group mechanism by accessing self._spikespace # Using the latter would cut _spikespace to the length of the group spikespace = self.variables["_spikespace"].get_value() return spikespace[: spikespace[-1]] def state(self, name, use_units=True, level=0): try: return Group.state(self, name, use_units=use_units, level=level + 1) except KeyError as ex: if name in self._linked_variables: raise TypeError(f"Link target for variable {name} has not been set.") else: raise ex def run_on_event(self, event, code, when="after_resets", order=None): """ Run code triggered by a custom-defined event (see `NeuronGroup` documentation for the specification of events).The created `Resetter` object will be automatically added to the group, it therefore does not need to be added to the network manually. However, a reference to the object will be returned, which can be used to later remove it from the group or to set it to inactive. Parameters ---------- event : str The name of the event that should trigger the code code : str The code that should be executed when : str, optional The scheduling slot that should be used to execute the code. Defaults to `'after_resets'`. See :ref:`scheduling` for possible values. order : int, optional The order for operations in the same scheduling slot. Defaults to the order of the `NeuronGroup`. Returns ------- obj : `Resetter` A reference to the object that will be run. """ if event not in self.events: error_message = f"Unknown event '{event}'." if event == "spike": error_message += " Did you forget to define a threshold?" raise ValueError(error_message) if event in self.resetter: raise ValueError( "Cannot add code for event '%s', code for this " "event has already been added." % event ) self.event_codes[event] = code resetter = Resetter(self, when=when, order=order, event=event) self.resetter[event] = resetter self.contained_objects.append(resetter) return resetter def set_event_schedule(self, event, when="after_thresholds", order=None): """ Change the scheduling slot for checking the condition of an event. Parameters ---------- event : str The name of the event for which the scheduling should be changed when : str, optional The scheduling slot that should be used to check the condition. Defaults to `'after_thresholds'`. See :ref:`scheduling` for possible values. order : int, optional The order for operations in the same scheduling slot. Defaults to the order of the `NeuronGroup`. """ if event not in self.thresholder: raise ValueError(f"Unknown event '{event}'.") order = order if order is not None else self.order self.thresholder[event].when = when self.thresholder[event].order = order def __setattr__(self, key, value): # attribute access is switched off until this attribute is created by # _enable_group_attributes if not hasattr(self, "_group_attribute_access_active") or key in self.__dict__: object.__setattr__(self, key, value) elif key in self._linked_variables: if not isinstance(value, LinkedVariable): raise ValueError( "Cannot set a linked variable directly, link " "it to another variable using 'linked_var'." ) linked_var = value.variable if isinstance(linked_var, DynamicArrayVariable): raise NotImplementedError( f"Linking to variable {linked_var.name} is " "not supported, can only link to " "state variables of fixed size." ) eq = self.equations[key] if eq.dim is not linked_var.dim: raise DimensionMismatchError( f"Unit of variable '{key}' does not " "match its link target " f"'{linked_var.name}'" ) if not isinstance(linked_var, Subexpression): var_length = len(linked_var) else: var_length = len(linked_var.owner) if value.index is not None: try: index_array = np.asarray(value.index) if not np.issubsctype(index_array.dtype, int): raise TypeError() except TypeError: raise TypeError( "The index for a linked variable has to be an integer array" ) size = len(index_array) source_index = value.group.variables.indices[value.name] if source_index not in ("_idx", "0"): # we are indexing into an already indexed variable, # calculate the indexing into the target variable index_array = value.group.variables[source_index].get_value()[ index_array ] if not index_array.ndim == 1 or size != len(self): raise TypeError( f"Index array for linked variable '{key}' " "has to be a one-dimensional array of " f"length {len(self)}, but has shape " f"{index_array.shape!s}" ) if min(index_array) < 0 or max(index_array) >= var_length: raise ValueError( f"Index array for linked variable {key} " "contains values outside of the valid " f"range [0, {var_length}[" ) self.variables.add_array( f"_{key}_indices", size=size, dtype=index_array.dtype, constant=True, read_only=True, values=index_array, ) index = f"_{key}_indices" else: if linked_var.scalar or (var_length == 1 and self._N != 1): index = "0" else: index = value.group.variables.indices[value.name] if index == "_idx": target_length = var_length else: target_length = len(value.group.variables[index]) # we need a name for the index that does not clash with # other names and a reference to the index new_index = f"_{value.name}_index_{index}" self.variables.add_reference(new_index, value.group, index) index = new_index if len(self) != target_length: raise ValueError( f"Cannot link variable '{key}' to " f"'{linked_var.name}', the size of the " "target group does not match " f"({len(self)} != {target_length}). You can " "provide an indexing scheme with the " "'index' keyword to link groups with " "different sizes" ) self.variables.add_reference(key, value.group, value.name, index=index) source = (value.variable.owner.name,) sourcevar = value.variable.name log_msg = f"Setting {self.name}.{key} as a link to {source}.{sourcevar}" if index is not None: log_msg += f'(using "{index}" as index variable)' logger.diagnostic(log_msg) else: if isinstance(value, LinkedVariable): raise TypeError( f"Cannot link variable '{key}', it has to be marked " "as a linked variable with '(linked)' in the model " "equations." ) else: Group.__setattr__(self, key, value, level=1) def __getitem__(self, item): start, stop = to_start_stop(item, self._N) return Subgroup(self, start, stop) def _create_variables(self, user_dtype, events): """ Create the variables dictionary for this `NeuronGroup`, containing entries for the equation variables and some standard entries. """ self.variables = Variables(self) self.variables.add_constant("N", self._N) # Standard variables always present for event in events: self.variables.add_array( f"_{event}space", size=self._N + 1, dtype=np.int32, constant=False ) # Add the special variable "i" which can be used to refer to the neuron index self.variables.add_arange("i", size=self._N, constant=True, read_only=True) # Add the clock variables self.variables.create_clock_variables(self._clock) for eq in self.equations.values(): dtype = get_dtype(eq, user_dtype) check_identifier_pre_post(eq.varname) if eq.type in (DIFFERENTIAL_EQUATION, PARAMETER): if "linked" in eq.flags: # 'linked' cannot be combined with other flags if not len(eq.flags) == 1: raise SyntaxError( "The 'linked' flag cannot be combined with other flags" ) self._linked_variables.add(eq.varname) else: constant = "constant" in eq.flags shared = "shared" in eq.flags size = 1 if shared else self._N self.variables.add_array( eq.varname, size=size, dimensions=eq.dim, dtype=dtype, constant=constant, scalar=shared, ) elif eq.type == SUBEXPRESSION: self.variables.add_subexpression( eq.varname, dimensions=eq.dim, expr=str(eq.expr), dtype=dtype, scalar="shared" in eq.flags, ) else: raise AssertionError(f"Unknown type of equation: {eq.eq_type}") # Add the conditional-write attribute for variables with the # "unless refractory" flag if self._refractory is not False: for eq in self.equations.values(): if eq.type == DIFFERENTIAL_EQUATION and "unless refractory" in eq.flags: not_refractory_var = self.variables["not_refractory"] var = self.variables[eq.varname] var.set_conditional_write(not_refractory_var) # Stochastic variables for xi in self.equations.stochastic_variables: self.variables.add_auxiliary_variable(xi, dimensions=(second**-0.5).dim) # Check scalar subexpressions for eq in self.equations.values(): if eq.type == SUBEXPRESSION and "shared" in eq.flags: var = self.variables[eq.varname] for identifier in var.identifiers: if identifier in self.variables: if not self.variables[identifier].scalar: raise SyntaxError( f"Shared subexpression '{eq.varname}' " "refers to non-shared variable " f"'{identifier}'." ) def before_run(self, run_namespace=None): # Check units self.equations.check_units(self, run_namespace=run_namespace) # Check that subexpressions that refer to stateful functions are labeled # as "constant over dt" check_subexpressions(self, self.equations, run_namespace) super().before_run(run_namespace=run_namespace) def _repr_html_(self): text = [rf"NeuronGroup '{self.name}' with {self._N} neurons.
"] text.append(r"Model:") text.append(sympy.latex(self.equations)) def add_event_to_text(event): if event == "spike": event_header = "Spiking behaviour" event_condition = "Threshold condition" event_code = "Reset statement(s)" else: event_header = f'Event "{event}"' event_condition = "Event condition" event_code = "Executed statement(s)" condition = self.events[event] text.append( rf'{event_header}:
    ' ) text.append(rf"
  • {event_condition}: ") text.append(f"{str(condition)}
  • ") statements = self.event_codes.get(event, None) if statements is not None: text.append(rf"
  • {event_code}:") if "\n" in str(statements): text.append("
    ") text.append(rf"{str(statements)}
  • ") text.append("
") if "spike" in self.events: add_event_to_text("spike") for event in self.events: if event != "spike": add_event_to_text(event) return "\n".join(text) brian2-2.5.4/brian2/groups/subgroup.py000066400000000000000000000107331445201106100176110ustar00rootroot00000000000000from brian2.core.base import weakproxy_with_fallback from brian2.core.spikesource import SpikeSource from brian2.core.variables import Variables from .group import Group, Indexing __all__ = ["Subgroup"] class Subgroup(Group, SpikeSource): """ Subgroup of any `Group` Parameters ---------- source : SpikeSource The source object to subgroup. start, stop : int Select only spikes with indices from ``start`` to ``stop-1``. name : str, optional A unique name for the group, or use ``source.name+'_subgroup_0'``, etc. """ def __init__(self, source, start, stop, name=None): # A Subgroup should never be constructed from another Subgroup # Instead, use Subgroup(source.source, # start + source.start, stop + source.start) assert not isinstance(source, Subgroup) self.source = weakproxy_with_fallback(source) # Store a reference to the source's equations (if any) self.equations = None if hasattr(self.source, "equations"): self.equations = weakproxy_with_fallback(self.source.equations) if name is None: name = f"{source.name}_subgroup*" # We want to update the spikes attribute after it has been updated # by the parent, we do this in slot 'thresholds' with an order # one higher than the parent order to ensure it takes place after the # parent threshold operation Group.__init__( self, clock=source._clock, when="thresholds", order=source.order + 1, name=name, ) self._N = stop - start self.start = start self.stop = stop self.events = self.source.events # All the variables have to go via the _sub_idx to refer to the # appropriate values in the source group self.variables = Variables(self, default_index="_sub_idx") # overwrite the meaning of N and i if self.start > 0: self.variables.add_constant("_offset", value=self.start) self.variables.add_reference("_source_i", source, "i") self.variables.add_subexpression( "i", dtype=source.variables["i"].dtype, expr="_source_i - _offset", index="_idx", ) else: # no need to calculate anything if this is a subgroup starting at 0 self.variables.add_reference("i", source) self.variables.add_constant("N", value=self._N) self.variables.add_constant("_source_N", value=len(source)) # add references for all variables in the original group self.variables.add_references(source, list(source.variables.keys())) # Only the variable _sub_idx itself is stored in the subgroup # and needs the normal index for this group self.variables.add_arange( "_sub_idx", size=self._N, start=self.start, index="_idx" ) # special indexing for subgroups self._indices = Indexing(self, self.variables["_sub_idx"]) # Deal with special indices for key, value in self.source.variables.indices.items(): if value == "0": self.variables.indices[key] = "0" elif value == "_idx": continue # nothing to do, already uses _sub_idx correctly else: raise ValueError( f"Do not know how to deal with variable '{key}' " f"using index '{value}' in a subgroup." ) self.namespace = self.source.namespace self.codeobj_class = self.source.codeobj_class self._enable_group_attributes() spikes = property(lambda self: self.source.spikes) def __getitem__(self, item): if not isinstance(item, slice): raise TypeError("Subgroups can only be constructed using slicing syntax") start, stop, step = item.indices(self._N) if step != 1: raise IndexError("Subgroups have to be contiguous") if start >= stop: raise IndexError( f"Illegal start/end values for subgroup, {int(start)}>={int(stop)}" ) return Subgroup(self.source, self.start + start, self.start + stop) def __repr__(self): classname = self.__class__.__name__ return ( f"<{classname} {self.name!r} of {self.source.name!r} " f"from {self.start} to {self.stop}>" ) brian2-2.5.4/brian2/hears.py000066400000000000000000000152301445201106100155230ustar00rootroot00000000000000""" This is only a bridge for using Brian 1 hears with Brian 2. .. deprecated:: 2.2.2.2 Use the `brian2hears `_ package instead. NOTES: * Slicing sounds with Brian 2 units doesn't work, you need to either use Brian 1 units or replace calls to ``sound[:20*ms]`` with ``sound.slice(None, 20*ms)``, etc. TODO: handle properties (e.g. sound.duration) Not working examples: * time_varying_filter1 (care with units) """ try: import brian as b1 import brian.hears as b1h except ImportError: raise ImportError( "brian2.hears is deprecated and will be removed in a future release, please use" " the brian2hears package available at https://brian2hears.readthedocs.io/. If" " you really want to keep using it, note: brian2.hears is a bridge between" " Brian 2 and the version of Brian Hears from Brian 1, you need to have Brian 1" " installed to use it." ) from inspect import isclass, ismethod from numpy import asarray, ndarray from brian2.core.clocks import Clock from brian2.core.operations import network_operation from brian2.groups.neurongroup import NeuronGroup from brian2.units import second from brian2.units.fundamentalunits import Quantity from brian2.utils.logger import get_logger logger = get_logger(__name__) logger.warn( "brian2.hears is deprecated and will be removed in a future release, please use the" " brian2hears package available at https://brian2hears.readthedocs.io/. If you" " really want to keep using it, note that it is a bridge between Brian 2 and Brian" " Hears from Brian 1. This is not guaranteed to work in all cases that brian.hears" " works. See the limitations in the online documentation." ) def convert_unit_b1_to_b2(val): return Quantity.with_dimensions(float(val), val.dim._dims) def convert_unit_b2_to_b1(val): return b1.Quantity.with_dimensions(float(val), val.dim._dims) def modify_arg(arg): """ Modify arguments to make them compatible with Brian 1. - Arrays of units are replaced with straight arrays - Single values are replaced with Brian 1 equivalents - Slices are handled so we can use e.g. sound[:20*ms] The second part was necessary because some functions/classes test if an object is an array or not to see if it is a sequence, but because brian2.Quantity derives from ndarray this was causing problems. """ if isinstance(arg, Quantity): if len(arg.shape) == 0: arg = b1.Quantity.with_dimensions(float(arg), arg.dim._dims) else: arg = asarray(arg) elif isinstance(arg, slice): arg = slice(modify_arg(arg.start), modify_arg(arg.stop), modify_arg(arg.step)) return arg def wrap_units(f): """ Wrap a function to convert units into a form that Brian 1 can handle. Also, check the output argument, if it is a ``b1h.Sound`` wrap it. """ def new_f(*args, **kwds): newargs = [] newkwds = {} for arg in args: newargs.append(modify_arg(arg)) for k, v in kwds.items(): newkwds[k] = modify_arg(v) rv = f(*newargs, **newkwds) if rv.__class__ == b1h.Sound: rv.__class__ = BridgeSound elif isinstance(rv, b1.Quantity): rv = Quantity.with_dimensions(float(rv), rv.dim._dims) return rv return new_f def wrap_units_property(p): fget = p.fget fset = p.fset fdel = p.fdel if fget is not None: fget = wrap_units(fget) if fset is not None: fset = wrap_units(fset) if fdel is not None: fdel = wrap_units(fdel) new_p = property(fget, fset, fdel) return new_p def wrap_units_class(_C): """ Wrap a class to convert units into a form that Brian 1 can handle in all methods """ class new_class(_C): for _k in _C.__dict__: _v = getattr(_C, _k) if hasattr(ndarray, _k) and getattr(ndarray, _k) is _v: continue if ismethod(_v): _v = wrap_units(_v) exec(f"{_k} = _v") elif isinstance(_v, property): _v = wrap_units_property(_v) exec(f"{_k} = _v") del _k del _v return new_class WrappedSound = wrap_units_class(b1h.Sound) class BridgeSound(WrappedSound): """ We add a new method slice because slicing with units can't work with Brian 2 units. """ def slice(self, *args): return self.__getitem__(slice(*args)) Sound = BridgeSound class FilterbankGroup(NeuronGroup): def __init__(self, filterbank, targetvar, *args, **kwds): self.targetvar = targetvar self.filterbank = filterbank self.buffer = None filterbank.buffer_init() # Sanitize the clock - does it have the right dt value? if "clock" in kwds: if int(1 / kwds["clock"].dt) != int(filterbank.samplerate): raise ValueError("Clock should have 1/dt=samplerate") kwds["clock"] = Clock(dt=float(kwds["clock"].dt) * second) else: kwds["clock"] = Clock(dt=1 * second / float(filterbank.samplerate)) buffersize = kwds.pop("buffersize", 32) if not isinstance(buffersize, int): buffersize = int(buffersize * self.samplerate) self.buffersize = buffersize self.buffer_pointer = buffersize self.buffer_start = -buffersize NeuronGroup.__init__(self, filterbank.nchannels, *args, **kwds) @network_operation(clock=self.clock, when="start") def apply_filterbank_output(): if self.buffer_pointer >= self.buffersize: self.buffer_pointer = 0 self.buffer_start += self.buffersize self.buffer = self.filterbank.buffer_fetch( self.buffer_start, self.buffer_start + self.buffersize ) setattr(self, targetvar, self.buffer[self.buffer_pointer, :]) self.buffer_pointer += 1 self.contained_objects.append(apply_filterbank_output) def reinit(self): NeuronGroup.reinit(self) self.filterbank.buffer_init() self.buffer_pointer = self.buffersize self.buffer_start = -self.buffersize handled_explicitly = {"Sound", "FilterbankGroup"} __all__ = [k for k in b1h.__dict__ if not k.startswith("_")] for k in __all__: if k in handled_explicitly: continue curobj = getattr(b1h, k) if callable(curobj): if isclass(curobj): curobj = wrap_units_class(curobj) else: curobj = wrap_units(curobj) exec(f"{k} = curobj") __all__.extend( [ "convert_unit_b1_to_b2", "convert_unit_b2_to_b1", ] ) brian2-2.5.4/brian2/importexport/000077500000000000000000000000001445201106100166225ustar00rootroot00000000000000brian2-2.5.4/brian2/importexport/__init__.py000066400000000000000000000004061445201106100207330ustar00rootroot00000000000000""" Package providing import/export support. """ from .dictlike import * from .importexport import * __all__ = ["ImportExport"] # Register the standard ImportExport methods for obj in [DictImportExport(), PandasImportExport()]: ImportExport.register(obj) brian2-2.5.4/brian2/importexport/dictlike.py000066400000000000000000000047501445201106100207720ustar00rootroot00000000000000""" Module providing `DictImportExport` and `PandasImportExport` (requiring a working installation of pandas). """ import numpy as np from .importexport import ImportExport class DictImportExport(ImportExport): """ An importer/exporter for variables in format of dict of numpy arrays. """ @property def name(self): return "dict" @staticmethod def export_data(group, variables, units=True, level=0): data = {} for var in variables: data[var] = np.array( group.state(var, use_units=units, level=level + 1), copy=True, subok=True, ) return data @staticmethod def import_data(group, data, units=True, level=0): for key, value in data.items(): if group.variables[key].read_only: raise TypeError(f"Variable {key} is read-only.") group.state(key, use_units=units, level=level + 1)[:] = value class PandasImportExport(ImportExport): """ An importer/exporter for variables in pandas DataFrame format. """ @property def name(self): return "pandas" @staticmethod def export_data(group, variables, units=True, level=0): # pandas is not a default brian2 dependency, only import it here try: import pandas as pd except ImportError as ex: raise ImportError( "Exporting to pandas needs a working installation" " of pandas. Importing pandas failed: " + str(ex) ) if units: raise NotImplementedError( "Units not supported when exporting to pandas data frame" ) # we take advantage of the already implemented exporter data = DictImportExport.export_data(group, variables, units=units, level=level) pandas_data = pd.DataFrame(data) return pandas_data @staticmethod def import_data(group, data, units=True, level=0): if units: raise NotImplementedError( "Units not supported when importing from pandas data frame" ) colnames = data.columns array_data = data.values for e, colname in enumerate(colnames): if group.variables[colname].read_only: raise TypeError(f"Variable '{colname}' is read-only.") state = group.state(colname, use_units=units, level=level + 1) state[:] = np.squeeze(array_data[:, e]) brian2-2.5.4/brian2/importexport/importexport.py000066400000000000000000000053441445201106100217560ustar00rootroot00000000000000""" Module defining the `ImportExport` class that enables getting state variable data in and out of groups in various formats (see `Group.get_states` and `Group.set_states`). """ import abc from abc import abstractmethod class ImportExport(metaclass=abc.ABCMeta): """ Class for registering new import/export methods (via static methods). Also the base class that should be extended for such methods (`ImportExport.export_data`, `ImportExport.import_data`, and `ImportExport.name` have to be overwritten). See Also -------- VariableOwner.get_states, VariableOwner.set_states """ #: A dictionary mapping import/export methods names to `ImportExport` objects methods = dict() @staticmethod def register(importerexporter): """ Register a import/export method. Registered methods can be referred to via their name. Parameters ---------- importerexporter : `ImportExport` The importerexporter object, e.g. an `DictImportExport`. """ if not isinstance(importerexporter, ImportExport): t = str(type(importerexporter)) error_msg = ( f"Given importerexporter of type {t} does not seem to " "be a valid importerexporter." ) raise ValueError(error_msg) name = importerexporter.name if name in ImportExport.methods: raise ValueError( f"An import/export methods with the name '{name}'" "has already been registered" ) ImportExport.methods[name] = importerexporter @staticmethod @abstractmethod def export_data(group, variables): """ Asbtract static export data method with two obligatory parameters. It should return a copy of the current state variable values. The returned arrays are copies of the actual arrays that store the state variable values, therefore changing the values in the returned dictionary will not affect the state variables. Parameters ---------- group : `Group` Group object. variables : list of str The names of the variables to extract. """ raise NotImplementedError() @staticmethod @abstractmethod def import_data(group, data): """ Import and set state variables. Parameters ---------- group : `Group` Group object. data : dict_like Data to import with variable names. """ raise NotImplementedError() @property @abc.abstractmethod def name(self): """ Abstract property giving a method name. """ pass brian2-2.5.4/brian2/input/000077500000000000000000000000001445201106100152055ustar00rootroot00000000000000brian2-2.5.4/brian2/input/__init__.py000066400000000000000000000005051445201106100173160ustar00rootroot00000000000000""" Classes for providing external input to a network. """ from .binomial import * from .poissongroup import * from .poissoninput import * from .spikegeneratorgroup import * from .timedarray import * __all__ = [ "BinomialFunction", "PoissonGroup", "PoissonInput", "SpikeGeneratorGroup", "TimedArray", ] brian2-2.5.4/brian2/input/binomial.py000066400000000000000000000150651445201106100173600ustar00rootroot00000000000000""" Implementation of `BinomialFunction` """ import numpy as np from brian2.core.base import Nameable from brian2.core.functions import DEFAULT_FUNCTIONS, Function from brian2.units.fundamentalunits import check_units from brian2.utils.stringtools import replace __all__ = ["BinomialFunction"] def _pre_calc_constants_approximated(n, p): loc = n * p scale = np.sqrt(n * p * (1 - p)) return loc, scale def _pre_calc_constants(n, p): reverse = p > 0.5 if reverse: P = 1.0 - p else: P = p q = 1.0 - P qn = np.exp(n * np.log(q)) bound = min(n, n * P + 10.0 * np.sqrt(n * P * q + 1)) return reverse, q, P, qn, bound def _generate_cython_code(n, p, use_normal, name): # Cython implementation # Inversion transform sampling if use_normal: loc, scale = _pre_calc_constants_approximated(n, p) cython_code = """ cdef float %NAME%(const int vectorisation_idx): return _randn(vectorisation_idx) * %SCALE% + %LOC% """ cython_code = replace( cython_code, {"%SCALE%": f"{scale:.15f}", "%LOC%": f"{loc:.15f}", "%NAME%": name}, ) dependencies = {"_randn": DEFAULT_FUNCTIONS["randn"]} else: reverse, q, P, qn, bound = _pre_calc_constants(n, p) # The following code is an almost exact copy of numpy's # rk_binomial_inversion function # (numpy/random/mtrand/distributions.c) cython_code = """ cdef long %NAME%(const int vectorisation_idx): cdef long X = 0 cdef double px = %QN% cdef double U = _rand(vectorisation_idx) while U > px: X += 1 if X > %BOUND%: X = 0 px = %QN% U = _rand(vectorisation_idx) else: U -= px px = ((%N%-X+1) * %P% * px)/(X*%Q%) return %RETURN_VALUE% """ cython_code = replace( cython_code, { "%N%": f"{int(n)}", "%P%": f"{p:.15f}", "%Q%": f"{q:.15f}", "%QN%": f"{qn:.15f}", "%BOUND%": f"{bound:.15f}", "%RETURN_VALUE%": f"{int(n)}-X" if reverse else "X", "%NAME%": name, }, ) dependencies = {"_rand": DEFAULT_FUNCTIONS["rand"]} return cython_code, dependencies def _generate_cpp_code(n, p, use_normal, name): # C++ implementation # Inversion transform sampling if use_normal: loc, scale = _pre_calc_constants_approximated(n, p) cpp_code = """ float %NAME%(const int vectorisation_idx) { return _randn(vectorisation_idx) * %SCALE% + %LOC%; } """ cpp_code = replace( cpp_code, {"%SCALE%": f"{scale:.15f}", "%LOC%": f"{loc:.15f}", "%NAME%": name}, ) dependencies = {"_randn": DEFAULT_FUNCTIONS["randn"]} else: reverse, q, P, qn, bound = _pre_calc_constants(n, p) # The following code is an almost exact copy of numpy's # rk_binomial_inversion function # (numpy/random/mtrand/distributions.c) cpp_code = """ long %NAME%(const int vectorisation_idx) { long X = 0; double px = %QN%; double U = _rand(vectorisation_idx); while (U > px) { X++; if (X > %BOUND%) { X = 0; px = %QN%; U = _rand(vectorisation_idx); } else { U -= px; px = ((%N%-X+1) * %P% * px)/(X*%Q%); } } return %RETURN_VALUE%; } """ cpp_code = replace( cpp_code, { "%N%": f"{int(n)}", "%P%": f"{P:.15f}", "%Q%": f"{q:.15f}", "%QN%": f"{qn:.15f}", "%BOUND%": f"{bound:.15f}", "%RETURN_VALUE%": f"{int(n)}-X" if reverse else "X", "%NAME%": name, }, ) dependencies = {"_rand": DEFAULT_FUNCTIONS["rand"]} return {"support_code": cpp_code}, dependencies class BinomialFunction(Function, Nameable): r""" BinomialFunction(n, p, approximate=True, name='_binomial*') A function that generates samples from a binomial distribution. Parameters ---------- n : int Number of samples p : float Probablility approximate : bool, optional Whether to approximate the binomial with a normal distribution if :math:`n p > 5 \wedge n (1 - p) > 5`. Defaults to ``True``. """ #: Container for implementing functions for different targets #: This container can be extended by other codegeneration targets/devices #: The key has to be the name of the target, the value a function #: that takes three parameters (n, p, use_normal) and returns a tuple of #: (code, dependencies) implementations = {"cpp": _generate_cpp_code, "cython": _generate_cython_code} @check_units(n=1, p=1) def __init__(self, n, p, approximate=True, name="_binomial*"): Nameable.__init__(self, name) # Python implementation use_normal = approximate and (n * p > 5) and n * (1 - p) > 5 if use_normal: loc = n * p scale = np.sqrt(n * p * (1 - p)) def sample_function(vectorisation_idx): try: N = len(vectorisation_idx) except TypeError: N = int(vectorisation_idx) return np.random.normal(loc, scale, size=N) else: def sample_function(vectorisation_idx): try: N = len(vectorisation_idx) except TypeError: N = int(vectorisation_idx) return np.random.binomial(n, p, size=N) Function.__init__( self, pyfunc=lambda: sample_function(1), arg_units=[], return_unit=1, stateless=False, auto_vectorise=True, ) self.implementations.add_implementation("numpy", sample_function) for target, func in BinomialFunction.implementations.items(): code, dependencies = func(n=n, p=p, use_normal=use_normal, name=self.name) self.implementations.add_implementation( target, code, dependencies=dependencies, name=self.name ) brian2-2.5.4/brian2/input/poissongroup.py000066400000000000000000000126571445201106100203410ustar00rootroot00000000000000""" Implementation of `PoissonGroup`. """ import numpy as np from brian2.core.spikesource import SpikeSource from brian2.core.variables import Subexpression, Variables from brian2.groups.group import Group from brian2.groups.neurongroup import Thresholder from brian2.groups.subgroup import Subgroup from brian2.parsing.expressions import parse_expression_dimensions from brian2.units.fundamentalunits import check_units, fail_for_dimension_mismatch from brian2.units.stdunits import Hz from brian2.utils.stringtools import get_identifiers __all__ = ["PoissonGroup"] class PoissonGroup(Group, SpikeSource): """ Poisson spike source Parameters ---------- N : int Number of neurons rates : `Quantity`, str Single rate, array of rates of length N, or a string expression evaluating to a rate. This string expression will be evaluated at every time step, it can therefore be time-dependent (e.g. refer to a `TimedArray`). dt : `Quantity`, optional The time step to be used for the simulation. Cannot be combined with the `clock` argument. clock : `Clock`, optional The update clock to be used. If neither a clock, nor the `dt` argument is specified, the `defaultclock` will be used. when : str, optional When to run within a time step, defaults to the ``'thresholds'`` slot. See :ref:`scheduling` for possible values. order : int, optional The priority of of this group for operations occurring at the same time step and in the same scheduling slot. Defaults to 0. name : str, optional Unique name, or use poissongroup, poissongroup_1, etc. """ add_to_magic_network = True @check_units(rates=Hz) def __init__( self, N, rates, dt=None, clock=None, when="thresholds", order=0, namespace=None, name="poissongroup*", codeobj_class=None, ): Group.__init__( self, dt=dt, clock=clock, when=when, order=order, namespace=namespace, name=name, ) self.codeobj_class = codeobj_class self._N = N = int(N) # TODO: In principle, it would be nice to support Poisson groups with # refactoriness, but we can't currently, since the refractoriness # information is reset in the state updater which we are not using # We could either use a specific template or simply not bother and make # users write their own NeuronGroup (with threshold rand() < rates*dt) # for more complex use cases. self.variables = Variables(self) # standard variables self.variables.add_constant("N", value=self._N) self.variables.add_arange("i", self._N, constant=True, read_only=True) self.variables.add_array("_spikespace", size=N + 1, dtype=np.int32) self.variables.create_clock_variables(self._clock) # The firing rates if isinstance(rates, str): self.variables.add_subexpression("rates", dimensions=Hz.dim, expr=rates) else: self.variables.add_array("rates", size=N, dimensions=Hz.dim) self._rates = rates self.start = 0 self.stop = N self._refractory = False self.events = {"spike": "rand() < rates * dt"} self.thresholder = {"spike": Thresholder(self)} self.contained_objects.append(self.thresholder["spike"]) self._enable_group_attributes() if not isinstance(rates, str): self.rates = rates def __getitem__(self, item): if not isinstance(item, slice): raise TypeError("Subgroups can only be constructed using slicing syntax") start, stop, step = item.indices(self._N) if step != 1: raise IndexError("Subgroups have to be contiguous") if start >= stop: raise IndexError( f"Illegal start/end values for subgroup, {int(start)}>={int(stop)}" ) return Subgroup(self, start, stop) def before_run(self, run_namespace=None): rates_var = self.variables["rates"] if isinstance(rates_var, Subexpression): # Check that the units of the expression make sense expr = rates_var.expr identifiers = get_identifiers(expr) variables = self.resolve_all( identifiers, run_namespace, user_identifiers=identifiers ) unit = parse_expression_dimensions(rates_var.expr, variables) fail_for_dimension_mismatch( unit, Hz, "The expression provided for " "PoissonGroup's 'rates' " "argument, has to have units " "of Hz", ) super().before_run(run_namespace) @property def spikes(self): """ The spikes returned by the most recent thresholding operation. """ # Note that we have to directly access the ArrayVariable object here # instead of using the Group mechanism by accessing self._spikespace # Using the latter would cut _spikespace to the length of the group spikespace = self.variables["_spikespace"].get_value() return spikespace[: spikespace[-1]] def __repr__(self): classname = self.__class__.__name__ return f"{classname}({self.N}, rates={self._rates!r})" brian2-2.5.4/brian2/input/poissoninput.py000066400000000000000000000106721445201106100203370ustar00rootroot00000000000000""" Implementation of `PoissonInput`. """ from brian2.core.variables import Variables from brian2.groups.group import CodeRunner from brian2.units.fundamentalunits import ( DimensionMismatchError, check_units, get_dimensions, have_same_dimensions, ) from brian2.units.stdunits import Hz from .binomial import BinomialFunction __all__ = ["PoissonInput"] class PoissonInput(CodeRunner): """ PoissonInput(target, target_var, N, rate, weight, when='synapses', order=0) Adds independent Poisson input to a target variable of a `Group`. For large numbers of inputs, this is much more efficient than creating a `PoissonGroup`. The synaptic events are generated randomly during the simulation and are not preloaded and stored in memory. All the inputs must target the same variable, have the same frequency and same synaptic weight. All neurons in the target `Group` receive independent realizations of Poisson spike trains. Parameters ---------- target : `Group` The group that is targeted by this input. target_var : str The variable of `target` that is targeted by this input. N : int The number of inputs rate : `Quantity` The rate of each of the inputs weight : str or `Quantity` Either a string expression (that can be interpreted in the context of `target`) or a `Quantity` that will be added for every event to the `target_var` of `target`. The unit has to match the unit of `target_var` when : str, optional When to update the target variable during a time step. Defaults to the `synapses` scheduling slot. See :ref:`scheduling` for possible values. order : int, optional The priority of of the update compared to other operations occurring at the same time step and in the same scheduling slot. Defaults to 0. """ @check_units(N=1, rate=Hz) def __init__(self, target, target_var, N, rate, weight, when="synapses", order=0): if target_var not in target.variables: raise KeyError(f"{target_var} is not a variable of {target.name}") self._weight = weight self._target_var = target_var if isinstance(weight, str): weight = f"({weight})" else: weight_dims = get_dimensions(weight) target_dims = target.variables[target_var].dim # This will be checked automatically in the abstract code as well # but doing an explicit check here allows for a clearer error # message if not have_same_dimensions(weight_dims, target_dims): raise DimensionMismatchError( "The provided weight does not " "have the same unit as the " f"target variable '{target_var}'", weight_dims, target_dims, ) weight = repr(weight) self._N = N self._rate = rate binomial_sampling = BinomialFunction( N, rate * target.clock.dt, name="poissoninput_binomial*" ) code = f"{target_var} += {binomial_sampling.name}()*{weight}" self._stored_dt = target.dt_[:] # make a copy # FIXME: we need an explicit reference here for on-the-fly subgroups # For example: PoissonInput(group[:N], ...) self._group = target CodeRunner.__init__( self, group=target, template="stateupdate", code=code, user_code="", when=when, order=order, name="poissoninput*", clock=target.clock, ) self.variables = Variables(self) self.variables._add_variable(binomial_sampling.name, binomial_sampling) rate = property(fget=lambda self: self._rate, doc="The rate of each input") N = property(fget=lambda self: self._N, doc="The number of inputs") target_var = property( fget=lambda self: self._target_var, doc="The targetted variable" ) weight = property(fget=lambda self: self._weight, doc="The synaptic weight") def before_run(self, run_namespace): if self._group.dt_ != self._stored_dt: raise NotImplementedError( f"The dt used for simulating {self.group.name} " "changed after the PoissonInput source was " "created." ) CodeRunner.before_run(self, run_namespace=run_namespace) brian2-2.5.4/brian2/input/spikegeneratorgroup.py000066400000000000000000000352441445201106100216660ustar00rootroot00000000000000""" Module defining `SpikeGeneratorGroup`. """ import numpy as np from brian2.core.functions import timestep from brian2.core.spikesource import SpikeSource from brian2.core.variables import Variables from brian2.groups.group import CodeRunner, Group from brian2.units.allunits import second from brian2.units.fundamentalunits import Quantity, check_units from brian2.utils.logger import get_logger __all__ = ["SpikeGeneratorGroup"] logger = get_logger(__name__) class SpikeGeneratorGroup(Group, CodeRunner, SpikeSource): """ SpikeGeneratorGroup(N, indices, times, dt=None, clock=None, period=0*second, when='thresholds', order=0, sorted=False, name='spikegeneratorgroup*', codeobj_class=None) A group emitting spikes at given times. Parameters ---------- N : int The number of "neurons" in this group indices : array of integers The indices of the spiking cells times : `Quantity` The spike times for the cells given in ``indices``. Has to have the same length as ``indices``. period : `Quantity`, optional If this is specified, it will repeat spikes with this period. A period of 0s means not repeating spikes. dt : `Quantity`, optional The time step to be used for the simulation. Cannot be combined with the `clock` argument. clock : `Clock`, optional The update clock to be used. If neither a clock, nor the `dt` argument is specified, the `defaultclock` will be used. when : str, optional When to run within a time step, defaults to the ``'thresholds'`` slot. See :ref:`scheduling` for possible values. order : int, optional The priority of of this group for operations occurring at the same time step and in the same scheduling slot. Defaults to 0. sorted : bool, optional Whether the given indices and times are already sorted. Set to ``True`` if your events are already sorted (first by spike time, then by index), this can save significant time at construction if your arrays contain large numbers of spikes. Defaults to ``False``. Notes ----- * If `sorted` is set to ``True``, the given arrays will not be copied (only affects runtime mode).. """ @check_units(N=1, indices=1, times=second, period=second) def __init__( self, N, indices, times, dt=None, clock=None, period=0 * second, when="thresholds", order=0, sorted=False, name="spikegeneratorgroup*", codeobj_class=None, ): Group.__init__(self, dt=dt, clock=clock, when=when, order=order, name=name) # We store the indices and times also directly in the Python object, # this way we can use them for checks in `before_run` even in standalone # TODO: Remove this when the checks in `before_run` have been moved to the template #: Array of spiking neuron indices. self._neuron_index = None #: Array of spiking neuron times. self._spike_time = None #: "Dirty flag" that will be set when spikes are changed after the #: `before_run` check self._spikes_changed = True # Let other objects know that we emit spikes events self.events = {"spike": None} self.codeobj_class = codeobj_class if N < 1 or int(N) != N: raise TypeError("N has to be an integer >=1.") N = int(N) self.start = 0 self.stop = N self.variables = Variables(self) self.variables.create_clock_variables(self._clock) indices, times = self._check_args( indices, times, period, N, sorted, self._clock.dt ) self.variables.add_constant("N", value=N) self.variables.add_array( "period", dimensions=second.dim, size=1, constant=True, read_only=True, scalar=True, dtype=self._clock.variables["t"].dtype, ) self.variables.add_arange("i", N) self.variables.add_dynamic_array( "spike_number", values=np.arange(len(indices)), size=len(indices), dtype=np.int32, read_only=True, constant=True, index="spike_number", unique=True, ) self.variables.add_dynamic_array( "neuron_index", values=indices, size=len(indices), dtype=np.int32, index="spike_number", read_only=True, constant=True, ) self.variables.add_dynamic_array( "spike_time", values=times, size=len(times), dimensions=second.dim, index="spike_number", read_only=True, constant=True, dtype=self._clock.variables["t"].dtype, ) self.variables.add_dynamic_array( "_timebins", size=len(times), index="spike_number", read_only=True, constant=True, dtype=np.int32, ) self.variables.add_array( "_period_bins", size=1, constant=True, read_only=True, scalar=True, dtype=np.int32, ) self.variables.add_array("_spikespace", size=N + 1, dtype=np.int32) self.variables.add_array( "_lastindex", size=1, values=0, dtype=np.int32, read_only=True, scalar=True ) #: Remember the dt we used the last time when we checked the spike bins #: to not repeat the work for multiple runs with the same dt self._previous_dt = None CodeRunner.__init__( self, self, code="", template="spikegenerator", clock=self._clock, when=when, order=order, name=None, ) # Activate name attribute access self._enable_group_attributes() self.variables["period"].set_value(period) def _full_state(self): state = super()._full_state() # Store the internal information we use to decide whether to rebuild # the time bins state["_previous_dt"] = self._previous_dt state["_spikes_changed"] = self._spikes_changed return state def _restore_from_full_state(self, state): state = state.copy() # copy to avoid errors for multiple restores self._previous_dt = state.pop("_previous_dt") self._spikes_changed = state.pop("_spikes_changed") super()._restore_from_full_state(state) def before_run(self, run_namespace): # Do some checks on the period vs. dt dt = self.dt_[:] # make a copy period = self.period_ if period < np.inf and period != 0: if period < dt: raise ValueError( f"The period of '{self.name}' is {self.period[:]!s}, " f"which is smaller than its dt of {dt*second!s}." ) if self._spikes_changed: current_t = self.variables["t"].get_value().item() timesteps = timestep(self._spike_time, dt) current_step = timestep(current_t, dt) in_the_past = np.nonzero(timesteps < current_step)[0] if len(in_the_past): logger.warn( "The SpikeGeneratorGroup contains spike times " "earlier than the start time of the current run " f"(t = {current_t*second!s}), these spikes will be " "ignored.", name_suffix="ignored_spikes", ) self.variables["_lastindex"].set_value(in_the_past[-1] + 1) else: self.variables["_lastindex"].set_value(0) # Check that we don't have more than one spike per neuron in a time bin if self._previous_dt is None or dt != self._previous_dt or self._spikes_changed: # We shift all the spikes by a tiny amount to make sure that spikes # at exact multiples of dt do not end up in the previous time bin # This shift has to be quite significant relative to machine # epsilon, we use 1e-3 of the dt here shift = 1e-3 * dt timebins = np.asarray( np.asarray(self._spike_time + shift) / dt, dtype=np.int32 ) # time is already in sorted order, so it's enough to check if the condition # that timebins[i]==timebins[i+1] and self._neuron_index[i]==self._neuron_index[i+1] # is ever both true if ( np.logical_and(np.diff(timebins) == 0, np.diff(self._neuron_index) == 0) ).any(): raise ValueError( f"Using a dt of {self.dt!s}, some neurons of " f"SpikeGeneratorGroup '{self.name}' spike more than " "once during a time step." ) self.variables["_timebins"].set_value(timebins) period_bins = np.round(period / dt) max_int = np.iinfo(np.int32).max if period_bins > max_int: logger.warn( f"Periods longer than {max_int} timesteps " f"(={max_int*dt*second!s}) are not " "supported, the period will therefore be " "considered infinite. Set the period to 0*second " "to avoid this " "warning.", "spikegenerator_long_period", ) period = period_bins = 0 if np.abs(period_bins * dt - period) > period * np.finfo(dt.dtype).eps: raise NotImplementedError( f"The period of '{self.name}' is " f"{self.period[:]!s}, which is " "not an integer multiple of its dt " f"of {dt*second!s}." ) self.variables["_period_bins"].set_value(period_bins) self._previous_dt = dt self._spikes_changed = False super().before_run(run_namespace=run_namespace) @check_units(indices=1, times=second, period=second) def set_spikes(self, indices, times, period=0 * second, sorted=False): """ set_spikes(indices, times, period=0*second, sorted=False) Change the spikes that this group will generate. This can be used to set the input for a second run of a model based on the output of a first run (if the input for the second run is already known before the first run, then all the information should simply be included in the initial `SpikeGeneratorGroup` initializer call, instead). Parameters ---------- indices : array of integers The indices of the spiking cells times : `Quantity` The spike times for the cells given in ``indices``. Has to have the same length as ``indices``. period : `Quantity`, optional If this is specified, it will repeat spikes with this period. A period of 0s means not repeating spikes. sorted : bool, optional Whether the given indices and times are already sorted. Set to ``True`` if your events are already sorted (first by spike time, then by index), this can save significant time at construction if your arrays contain large numbers of spikes. Defaults to ``False``. """ indices, times = self._check_args( indices, times, period, self.N, sorted, self.dt ) self.variables["period"].set_value(period) self.variables["neuron_index"].resize(len(indices)) self.variables["spike_time"].resize(len(indices)) self.variables["spike_number"].resize(len(indices)) self.variables["spike_number"].set_value(np.arange(len(indices))) self.variables["_timebins"].resize(len(indices)) self.variables["neuron_index"].set_value(indices) self.variables["spike_time"].set_value(times) # _lastindex and _timebins will be set as part of before_run def _check_args(self, indices, times, period, N, sorted, dt): times = Quantity(times) if len(indices) != len(times): raise ValueError( "Length of the indices and times array must " f"match, but {len(indices)} != {len(times)}" ) if period < 0 * second: raise ValueError("The period cannot be negative.") elif len(times) and period != 0 * second: period_bins = np.round(period / dt) # Note: we have to use the timestep function here, to use the same # binning as in the actual simulation max_bin = timestep(np.max(times), dt) if max_bin >= period_bins: raise ValueError( "The period has to be greater than the maximum of the spike times" ) if len(times) and np.min(times) < 0 * second: raise ValueError("Spike times cannot be negative") if len(indices) and (np.min(indices) < 0 or np.max(indices) >= N): raise ValueError(f"Indices have to lie in the interval [0, {int(N)}[") times = np.asarray(times) indices = np.asarray(indices) if not sorted: # sort times and indices first by time, then by indices sort_indices = np.lexsort((indices, times)) indices = indices[sort_indices] times = times[sort_indices] # We store the indices and times also directly in the Python object, # this way we can use them for checks in `before_run` even in standalone # TODO: Remove this when the checks in `before_run` have been moved to the template self._neuron_index = indices self._spike_time = times self._spikes_changed = True return indices, times @property def spikes(self): """ The spikes returned by the most recent thresholding operation. """ # Note that we have to directly access the ArrayVariable object here # instead of using the Group mechanism by accessing self._spikespace # Using the latter would cut _spikespace to the length of the group spikespace = self.variables["_spikespace"].get_value() return spikespace[: spikespace[-1]] def __repr__(self): cls = self.__class__.__name__ size = self.variables["neuron_index"].size return ( f"{cls}({self.N}, indices=, times=)" ) brian2-2.5.4/brian2/input/timedarray.py000066400000000000000000000276251445201106100177340ustar00rootroot00000000000000""" Implementation of `TimedArray`. """ import numpy as np from brian2.core.clocks import defaultclock from brian2.core.functions import Function from brian2.core.names import Nameable from brian2.units.allunits import second from brian2.units.fundamentalunits import ( Quantity, check_units, get_dimensions, get_unit, ) from brian2.utils.caching import CacheKey from brian2.utils.logger import get_logger from brian2.utils.stringtools import replace __all__ = ["TimedArray"] logger = get_logger(__name__) def _find_K(group_dt, dt): dt_ratio = dt / group_dt if dt_ratio > 1 and np.floor(dt_ratio) != dt_ratio: logger.warn( "Group uses a dt of %s while TimedArray uses dt " "of %s (ratio: 1/%s) → time grids not aligned" % (group_dt * second, dt * second, dt_ratio), once=True, ) # Find an upsampling factor that should avoid rounding issues even # for multistep methods K = max(int(2 ** np.ceil(np.log2(8 / group_dt * dt))), 1) return K def _generate_cpp_code_1d(values, dt, name): def cpp_impl(owner): K = _find_K(owner.clock.dt_, dt) code = ( """ static inline double %NAME%(const double t) { const double epsilon = %DT% / %K%; int i = (int)((t/epsilon + 0.5)/%K%); if(i < 0) i = 0; if(i >= %NUM_VALUES%) i = %NUM_VALUES%-1; return _namespace%NAME%_values[i]; } """.replace( "%NAME%", name ) .replace("%DT%", f"{dt:.18f}") .replace("%K%", str(K)) .replace("%NUM_VALUES%", str(len(values))) ) return code return cpp_impl def _generate_cpp_code_2d(values, dt, name): def cpp_impl(owner): K = _find_K(owner.clock.dt_, dt) support_code = """ static inline double %NAME%(const double t, const int i) { const double epsilon = %DT% / %K%; if (i < 0 || i >= %COLS%) return NAN; int timestep = (int)((t/epsilon + 0.5)/%K%); if(timestep < 0) timestep = 0; else if(timestep >= %ROWS%) timestep = %ROWS%-1; return _namespace%NAME%_values[timestep*%COLS% + i]; } """ code = replace( support_code, { "%NAME%": name, "%DT%": f"{dt:.18f}", "%K%": str(K), "%COLS%": str(values.shape[1]), "%ROWS%": str(values.shape[0]), }, ) return code return cpp_impl def _generate_cython_code_1d(values, dt, name): def cython_impl(owner): K = _find_K(owner.clock.dt_, dt) code = ( """ cdef double %NAME%(const double t): global _namespace%NAME%_values cdef double epsilon = %DT% / %K% cdef int i = (int)((t/epsilon + 0.5)/%K%) if i < 0: i = 0 if i >= %NUM_VALUES%: i = %NUM_VALUES% - 1 return _namespace%NAME%_values[i] """.replace( "%NAME%", name ) .replace("%DT%", f"{dt:.18f}") .replace("%K%", str(K)) .replace("%NUM_VALUES%", str(len(values))) ) return code return cython_impl def _generate_cython_code_2d(values, dt, name): def cython_impl(owner): K = _find_K(owner.clock.dt_, dt) code = """ cdef double %NAME%(const double t, const int i): global _namespace%NAME%_values cdef double epsilon = %DT% / %K% if i < 0 or i >= %COLS%: return _numpy.nan cdef int timestep = (int)((t/epsilon + 0.5)/%K%) if timestep < 0: timestep = 0 elif timestep >= %ROWS%: timestep = %ROWS%-1 return _namespace%NAME%_values[timestep*%COLS% + i] """ code = replace( code, { "%NAME%": name, "%DT%": f"{dt:.18f}", "%K%": str(K), "%COLS%": str(values.shape[1]), "%ROWS%": str(values.shape[0]), }, ) return code return cython_impl class TimedArray(Function, Nameable, CacheKey): """ TimedArray(values, dt, name=None) A function of time built from an array of values. The returned object can be used as a function, including in model equations etc. The resulting function has to be called as `funcion_name(t)` if the provided value array is one-dimensional and as `function_name(t, i)` if it is two-dimensional. Parameters ---------- values : ndarray or `Quantity` An array of values providing the values at various points in time. This array can either be one- or two-dimensional. If it is two-dimensional it's first dimension should be the time. dt : `Quantity` The time distance between values in the `values` array. name : str, optional A unique name for this object, see `Nameable` for details. Defaults to ``'_timedarray*'``. Notes ----- For time values corresponding to elements outside of the range of `values` provided, the first respectively last element is returned. Examples -------- >>> from brian2 import * >>> ta = TimedArray([1, 2, 3, 4] * mV, dt=0.1*ms) >>> print(ta(0.3*ms)) 4. mV >>> G = NeuronGroup(1, 'v = ta(t) : volt') >>> mon = StateMonitor(G, 'v', record=True) >>> net = Network(G, mon) >>> net.run(1*ms) # doctest: +ELLIPSIS ... >>> print(mon[0].v) [ 1. 2. 3. 4. 4. 4. 4. 4. 4. 4.] mV >>> ta2d = TimedArray([[1, 2], [3, 4], [5, 6]]*mV, dt=0.1*ms) >>> G = NeuronGroup(4, 'v = ta2d(t, i%2) : volt') >>> mon = StateMonitor(G, 'v', record=True) >>> net = Network(G, mon) >>> net.run(0.2*ms) # doctest: +ELLIPSIS ... >>> print(mon.v[:]) [[ 1. 3.] [ 2. 4.] [ 1. 3.] [ 2. 4.]] mV """ _cache_irrelevant_attributes = {"_id", "values", "pyfunc", "implementations"} #: Container for implementing functions for different targets #: This container can be extended by other codegeneration targets/devices #: The key has to be the name of the target, the value is a tuple of #: functions, the first for a 1d array, the second for a 2d array. #: The functions have to take three parameters: (values, dt, name), i.e. the #: array values, their physical dimensions, the dt of the TimedArray, and #: the name of the TimedArray. The functions have to return *a function* #: that takes the `owner` argument (out of which they can get the context's #: dt as `owner.clock.dt_`) and returns the code. implementations = { "cpp": (_generate_cpp_code_1d, _generate_cpp_code_2d), "cython": (_generate_cython_code_1d, _generate_cython_code_2d), } @check_units(dt=second) def __init__(self, values, dt, name=None): if name is None: name = "_timedarray*" Nameable.__init__(self, name) dimensions = get_dimensions(values) self.dim = dimensions values = np.asarray(values, dtype=np.float64) self.values = values dt = float(dt) self.dt = dt if values.ndim == 1: self._init_1d() elif values.ndim == 2: self._init_2d() else: raise NotImplementedError( "Only 1d and 2d arrays are supported for TimedArray" ) def _init_1d(self): dimensions = self.dim unit = get_unit(dimensions) values = self.values dt = self.dt # Python implementation (with units), used when calling the TimedArray # directly, outside of a simulation @check_units(t=second, result=unit) def timed_array_func(t): # We round according to the current defaultclock.dt K = _find_K(float(defaultclock.dt), dt) epsilon = dt / K i = np.clip( np.int_(np.round(np.asarray(t / epsilon)) / K), 0, len(values) - 1 ) return Quantity(values[i], dim=dimensions) Function.__init__(self, pyfunc=timed_array_func) # we use dynamic implementations because we want to do upsampling # in a way that avoids rounding problems with the group's dt def create_numpy_implementation(owner): group_dt = owner.clock.dt_ K = _find_K(group_dt, dt) n_values = len(values) epsilon = dt / K def unitless_timed_array_func(t): timestep = np.clip(np.int_(np.round(t / epsilon) / K), 0, n_values - 1) return values[timestep] unitless_timed_array_func._arg_units = [second] unitless_timed_array_func._return_unit = unit return unitless_timed_array_func self.implementations.add_dynamic_implementation( "numpy", create_numpy_implementation ) namespace = lambda owner: {f"{self.name}_values": self.values} for target, (func_1d, _) in TimedArray.implementations.items(): self.implementations.add_dynamic_implementation( target, func_1d(self.values, self.dt, self.name), namespace=namespace, name=self.name, ) def _init_2d(self): dimensions = self.dim unit = get_unit(dimensions) values = self.values dt = self.dt # Python implementation (with units), used when calling the TimedArray # directly, outside of a simulation @check_units(i=1, t=second, result=unit) def timed_array_func(t, i): # We round according to the current defaultclock.dt K = _find_K(float(defaultclock.dt), dt) epsilon = dt / K time_step = np.clip( np.int_(np.round(np.asarray(t / epsilon)) / K), 0, len(values) - 1 ) return Quantity(values[time_step, i], dim=dimensions) Function.__init__(self, pyfunc=timed_array_func) # we use dynamic implementations because we want to do upsampling # in a way that avoids rounding problems with the group's dt def create_numpy_implementation(owner): group_dt = owner.clock.dt_ K = _find_K(group_dt, dt) n_values = len(values) epsilon = dt / K def unitless_timed_array_func(t, i): timestep = np.clip(np.int_(np.round(t / epsilon) / K), 0, n_values - 1) return values[timestep, i] unitless_timed_array_func._arg_units = [second] unitless_timed_array_func._return_unit = unit return unitless_timed_array_func self.implementations.add_dynamic_implementation( "numpy", create_numpy_implementation ) values_flat = self.values.astype(np.double, order="C", copy=False).ravel() namespace = lambda owner: {f"{self.name}_values": values_flat} for target, (_, func_2d) in TimedArray.implementations.items(): self.implementations.add_dynamic_implementation( target, func_2d(self.values, self.dt, self.name), namespace=namespace, name=self.name, ) def is_locally_constant(self, dt): if dt > self.dt: return False dt_ratio = self.dt / float(dt) if np.floor(dt_ratio) != dt_ratio: logger.info( "dt of the TimedArray is not an integer multiple of " "the group's dt, the TimedArray's return value can " "therefore not be considered constant over one " "timestep, making linear integration impossible.", once=True, ) return False return True brian2-2.5.4/brian2/memory/000077500000000000000000000000001445201106100153565ustar00rootroot00000000000000brian2-2.5.4/brian2/memory/__init__.py000066400000000000000000000000001445201106100174550ustar00rootroot00000000000000brian2-2.5.4/brian2/memory/dynamicarray.py000066400000000000000000000204461445201106100204210ustar00rootroot00000000000000""" TODO: rewrite this (verbatim from Brian 1.x), more efficiency """ import numpy as np __all__ = ["DynamicArray", "DynamicArray1D"] def getslices(shape, from_start=True): if from_start: return tuple(slice(0, x) for x in shape) else: return tuple(slice(x, None) for x in shape) class DynamicArray: """ An N-dimensional dynamic array class The array can be resized in any dimension, and the class will handle allocating a new block of data and copying when necessary. .. warning:: The data will NOT be contiguous for >1D arrays. To ensure this, you will either need to use 1D arrays, or to copy the data, or use the shrink method with the current size (although note that in both cases you negate the memory and efficiency benefits of the dynamic array). Initialisation arguments: ``shape``, ``dtype`` The shape and dtype of the array to initialise, as in Numpy. For 1D arrays, shape can be a single int, for ND arrays it should be a tuple. ``factor`` The resizing factor (see notes below). Larger values tend to lead to more wasted memory, but more computationally efficient code. ``use_numpy_resize``, ``refcheck`` Normally, when you resize the array it creates a new array and copies the data. Sometimes, it is possible to resize an array without a copy, and if this option is set it will attempt to do this. However, this can cause memory problems if you are not careful so the option is off by default. You need to ensure that you do not create slices of the array so that no references to the memory exist other than the main array object. If you are sure you know what you're doing, you can switch this reference check off. Note that resizing in this way is only done if you resize in the first dimension. The array is initialised with zeros. The data is stored in the attribute ``data`` which is a Numpy array. Some numpy methods are implemented and can work directly on the array object, including ``len(arr)``, ``arr[...]`` and ``arr[...]=...``. In other cases, use the ``data`` attribute. Examples -------- >>> x = DynamicArray((2, 3), dtype=int) >>> x[:] = 1 >>> x.resize((3, 3)) >>> x[:] += 1 >>> x.resize((3, 4)) >>> x[:] += 1 >>> x.resize((4, 4)) >>> x[:] += 1 >>> x.data[:] = x.data**2 >>> x.data array([[16, 16, 16, 4], [16, 16, 16, 4], [ 9, 9, 9, 4], [ 1, 1, 1, 1]]) Notes ----- The dynamic array returns a ``data`` attribute which is a view on the larger ``_data`` attribute. When a resize operation is performed, and a specific dimension is enlarged beyond the size in the ``_data`` attribute, the size is increased to the larger of ``cursize*factor`` and ``newsize``. This ensures that the amortized cost of increasing the size of the array is O(1). """ def __init__( self, shape, dtype=float, factor=2, use_numpy_resize=False, refcheck=True ): if isinstance(shape, int): shape = (shape,) self._data = np.zeros(shape, dtype=dtype) self.data = self._data self.dtype = dtype self.shape = self._data.shape self.factor = factor self.use_numpy_resize = use_numpy_resize self.refcheck = refcheck def resize(self, newshape): """ Resizes the data to the new shape, which can be a different size to the current data, but should have the same rank, i.e. same number of dimensions. """ datashapearr = np.array(self._data.shape) newshapearr = np.array(newshape) resizedimensions = newshapearr > datashapearr if resizedimensions.any(): # resize of the data is needed minnewshapearr = datashapearr # .copy() dimstoinc = minnewshapearr[resizedimensions] incdims = np.array(dimstoinc * self.factor, dtype=int) newdims = np.maximum(incdims, dimstoinc + 1) minnewshapearr[resizedimensions] = newdims newshapearr = np.maximum(newshapearr, minnewshapearr) do_resize = False if self.use_numpy_resize and self._data.flags["C_CONTIGUOUS"]: if sum(resizedimensions) == resizedimensions[0]: do_resize = True if do_resize: self.data = None self._data.resize(tuple(newshapearr), refcheck=self.refcheck) else: newdata = np.zeros(tuple(newshapearr), dtype=self.dtype) slices = getslices(self._data.shape) newdata[slices] = self._data self._data = newdata elif (newshapearr < self.shape).any(): # If we reduced the size, set the no longer used memory to 0 self._data[getslices(newshape, from_start=False)] = 0 # Reduce our view to the requested size if necessary self.data = self._data[getslices(newshape, from_start=True)] self.shape = self.data.shape def resize_along_first(self, newshape): new_dimension = newshape[0] if new_dimension > self._data.shape[0]: new_size = np.maximum(self._data.shape[0] * self.factor, new_dimension + 1) final_new_shape = np.array(self._data.shape) final_new_shape[0] = new_size if self.use_numpy_resize and self._data.flags["C_CONTIGUOUS"]: self.data = None self._data.resize(tuple(final_new_shape), refcheck=self.refcheck) else: newdata = np.zeros(tuple(final_new_shape), dtype=self.dtype) slices = getslices(self._data.shape) newdata[slices] = self._data self._data = newdata elif newshape < self.shape: # If we reduced the size, set the no longer used memory to 0 self._data[new_dimension:] = 0 # Reduce our view to the requested size if necessary self.data = self._data[:new_dimension] self.shape = newshape def shrink(self, newshape): """ Reduces the data to the given shape, which should be smaller than the current shape. `resize` can also be used with smaller values, but it will not shrink the allocated memory, whereas `shrink` will reallocate the memory. This method should only be used infrequently, as if it is used frequently it will negate the computational efficiency benefits of the DynamicArray. """ if isinstance(newshape, int): newshape = (newshape,) shapearr = np.array(self.shape) newshapearr = np.array(newshape) if (newshapearr <= shapearr).all(): newdata = np.zeros(newshapearr, dtype=self.dtype) newdata[:] = self._data[getslices(newshapearr)] self._data = newdata self.shape = tuple(newshapearr) self.data = self._data def __getitem__(self, item): return self.data.__getitem__(item) def __setitem__(self, item, val): self.data.__setitem__(item, val) def __len__(self): return len(self.data) def __str__(self): return self.data.__str__() def __repr__(self): return self.data.__repr__() class DynamicArray1D(DynamicArray): """ Version of `DynamicArray` with specialised ``resize`` method designed to be more efficient. """ def resize(self, newshape): (datashape,) = self._data.shape if newshape > datashape: (shape,) = self.shape # we work with int shapes only newdatashape = max(newshape, int(shape * self.factor) + 1) if self.use_numpy_resize and self._data.flags["C_CONTIGUOUS"]: self.data = None self._data.resize(newdatashape, refcheck=self.refcheck) else: newdata = np.zeros(newdatashape, dtype=self.dtype) newdata[:shape] = self.data self._data = newdata elif newshape < self.shape[0]: # If we reduced the size, set the no longer used memory to 0 self._data[newshape:] = 0 # Reduce our view to the requested size if necessary self.data = self._data[:newshape] self.shape = (newshape,) brian2-2.5.4/brian2/monitors/000077500000000000000000000000001445201106100157205ustar00rootroot00000000000000brian2-2.5.4/brian2/monitors/__init__.py000066400000000000000000000004101445201106100200240ustar00rootroot00000000000000""" Base package for all monitors, i.e. objects to record activity during a simulation run. """ from .ratemonitor import * from .spikemonitor import * from .statemonitor import * __all__ = ["SpikeMonitor", "EventMonitor", "StateMonitor", "PopulationRateMonitor"] brian2-2.5.4/brian2/monitors/ratemonitor.py000066400000000000000000000156361445201106100206500ustar00rootroot00000000000000""" Module defining `PopulationRateMonitor`. """ import numpy as np from brian2.core.variables import Variables from brian2.groups.group import CodeRunner, Group from brian2.units.allunits import hertz, second from brian2.units.fundamentalunits import Quantity, check_units from brian2.utils.logger import get_logger __all__ = ["PopulationRateMonitor"] logger = get_logger(__name__) class PopulationRateMonitor(Group, CodeRunner): """ Record instantaneous firing rates, averaged across neurons from a `NeuronGroup` or other spike source. Parameters ---------- source : (`NeuronGroup`, `SpikeSource`) The source of spikes to record. name : str, optional A unique name for the object, otherwise will use ``source.name+'_ratemonitor_0'``, etc. codeobj_class : class, optional The `CodeObject` class to run code with. dtype : dtype, optional The dtype to use to store the ``rate`` variable. Defaults to `~numpy.float64`, i.e. double precision. Notes ----- Currently, this monitor can only monitor the instantaneous firing rates at each time step of the source clock. Any binning/smoothing of the firing rates has to be done manually afterwards. """ invalidates_magic_network = False add_to_magic_network = True def __init__( self, source, name="ratemonitor*", codeobj_class=None, dtype=np.float64 ): #: The group we are recording from self.source = source self.codeobj_class = codeobj_class CodeRunner.__init__( self, group=self, code="", template="ratemonitor", clock=source.clock, when="end", order=0, name=name, ) self.add_dependency(source) self.variables = Variables(self) # Handle subgroups correctly start = getattr(source, "start", 0) stop = getattr(source, "stop", len(source)) self.variables.add_constant("_source_start", start) self.variables.add_constant("_source_stop", stop) self.variables.add_reference("_spikespace", source) self.variables.add_dynamic_array( "rate", size=0, dimensions=hertz.dim, read_only=True, dtype=dtype ) self.variables.add_dynamic_array( "t", size=0, dimensions=second.dim, read_only=True, dtype=self._clock.variables["t"].dtype, ) self.variables.add_reference("_num_source_neurons", source, "N") self.variables.add_array( "N", dtype=np.int32, size=1, scalar=True, read_only=True ) self.variables.create_clock_variables(self._clock, prefix="_clock_") self._enable_group_attributes() def resize(self, new_size): # Note that this does not set N, this has to be done in the template # since we use a restricted pointer to access it (which promises that # we only change the value through this pointer) self.variables["rate"].resize(new_size) self.variables["t"].resize(new_size) def reinit(self): """ Clears all recorded rates """ raise NotImplementedError() @check_units(width=second) def smooth_rate(self, window="gaussian", width=None): """ smooth_rate(self, window='gaussian', width=None) Return a smooth version of the population rate. Parameters ---------- window : str, ndarray The window to use for smoothing. Can be a string to chose a predefined window(``'flat'`` for a rectangular, and ``'gaussian'`` for a Gaussian-shaped window). In this case the width of the window is determined by the ``width`` argument. Note that for the Gaussian window, the ``width`` parameter specifies the standard deviation of the Gaussian, the width of the actual window is ``4*width + dt`` (rounded to the nearest dt). For the flat window, the width is rounded to the nearest odd multiple of dt to avoid shifting the rate in time. Alternatively, an arbitrary window can be given as a numpy array (with an odd number of elements). In this case, the width in units of time depends on the ``dt`` of the simulation, and no ``width`` argument can be specified. The given window will be automatically normalized to a sum of 1. width : `Quantity`, optional The width of the ``window`` in seconds (for a predefined window). Returns ------- rate : `Quantity` The population rate in Hz, smoothed with the given window. Note that the rates are smoothed and not re-binned, i.e. the length of the returned array is the same as the length of the ``rate`` attribute and can be plotted against the `PopulationRateMonitor` 's ``t`` attribute. """ if width is None and isinstance(window, str): raise TypeError("Need a width when using a predefined window.") if width is not None and not isinstance(window, str): raise TypeError("Can only specify a width for a predefined window") if isinstance(window, str): if window == "gaussian": width_dt = int(np.round(2 * width / self.clock.dt)) # Rounding only for the size of the window, not for the standard # deviation of the Gaussian window = np.exp( -np.arange(-width_dt, width_dt + 1) ** 2 * 1.0 / (2 * (width / self.clock.dt) ** 2) ) elif window == "flat": width_dt = int(width / 2 / self.clock.dt) * 2 + 1 used_width = width_dt * self.clock.dt if abs(used_width - width) > 1e-6 * self.clock.dt: logger.info( f"width adjusted from {width} to {used_width}", "adjusted_width", once=True, ) window = np.ones(width_dt) else: raise NotImplementedError(f'Unknown pre-defined window "{window}"') else: try: window = np.asarray(window) except TypeError: raise TypeError(f"Cannot use a window of type {type(window)}") if window.ndim != 1: raise TypeError("The provided window has to be one-dimensional.") if len(window) % 2 != 1: raise TypeError("The window has to have an odd number of values.") return Quantity( np.convolve(self.rate_, window * 1.0 / sum(window), mode="same"), dim=hertz.dim, ) def __repr__(self): classname = self.__class__.__name__ return f"<{classname}, recording {self.source.name}>" brian2-2.5.4/brian2/monitors/spikemonitor.py000066400000000000000000000515421445201106100210240ustar00rootroot00000000000000""" Module defining `EventMonitor` and `SpikeMonitor`. """ import numpy as np from brian2.core.names import Nameable from brian2.core.spikesource import SpikeSource from brian2.core.variables import Variables from brian2.groups.group import CodeRunner, Group from brian2.units.fundamentalunits import Quantity __all__ = ["EventMonitor", "SpikeMonitor"] class EventMonitor(Group, CodeRunner): """ Record events from a `NeuronGroup` or another event source. The recorded events can be accessed in various ways: the attributes `~EventMonitor.i` and `~EventMonitor.t` store all the indices and event times, respectively. Alternatively, you can get a dictionary mapping neuron indices to event trains, by calling the `event_trains` method. Parameters ---------- source : `NeuronGroup`, `SpikeSource` The source of events to record. event : str The name of the event to record variables : str or sequence of str, optional Which variables to record at the time of the event (in addition to the index of the neuron). Can be the name of a variable or a list of names. record : bool, optional Whether or not to record each event in `i` and `t` (the `count` will always be recorded). Defaults to ``True``. when : str, optional When to record the events, by default records events in the same slot where the event is emitted. See :ref:`scheduling` for possible values. order : int, optional The priority of of this group for operations occurring at the same time step and in the same scheduling slot. Defaults to the order where the event is emitted + 1, i.e. it will be recorded directly afterwards. name : str, optional A unique name for the object, otherwise will use ``source.name+'_eventmonitor_0'``, etc. codeobj_class : class, optional The `CodeObject` class to run code with. See Also -------- SpikeMonitor """ invalidates_magic_network = False add_to_magic_network = True def __init__( self, source, event, variables=None, record=True, when=None, order=None, name="eventmonitor*", codeobj_class=None, ): if not isinstance(source, SpikeSource): raise TypeError( f"{self.__class__.__name__} can only monitor groups " "producing spikes (such as NeuronGroup), but the given " f"argument is of type {type(source)}." ) #: The source we are recording from self.source = source #: Whether to record times and indices of events self.record = record #: The array of event counts (length = size of target group) self.count = None del self.count # this is handled by the Variable mechanism if event not in source.events: if event == "spike": threshold_text = " Did you forget to set a 'threshold'?" else: threshold_text = "" raise ValueError( f"Recorded group '{source.name}' does not define an event " f"'{event}'.{threshold_text}" ) if when is None: if order is not None: raise ValueError("Cannot specify order if when is not specified.") # TODO: Would be nicer if there was a common way of accessing the # relevant object for NeuronGroup and SpikeGeneratorGroup if hasattr(source, "thresholder"): parent_obj = source.thresholder[event] else: parent_obj = source when = parent_obj.when order = parent_obj.order + 1 elif order is None: order = 0 #: The event that we are listening to self.event = event if variables is None: variables = {} elif isinstance(variables, str): variables = {variables} #: The additional variables that will be recorded self.record_variables = set(variables) for variable in variables: if variable not in source.variables: raise ValueError( f"'{variable}' is not a variable of the recorded group" ) if self.record: self.record_variables |= {"i", "t"} # Some dummy code so that code generation takes care of the indexing # and subexpressions code = [f"_to_record_{v} = _source_{v}" for v in sorted(self.record_variables)] code = "\n".join(code) self.codeobj_class = codeobj_class # Since this now works for general events not only spikes, we have to # pass the information about which variable to use to the template, # it can not longer simply refer to "_spikespace" eventspace_name = f"_{event}space" # Handle subgroups correctly start = getattr(source, "start", 0) stop = getattr(source, "stop", len(source)) source_N = getattr(source, "_source_N", len(source)) Nameable.__init__(self, name=name) self.variables = Variables(self) self.variables.add_reference(eventspace_name, source) for variable in self.record_variables: source_var = source.variables[variable] self.variables.add_reference(f"_source_{variable}", source, variable) self.variables.add_auxiliary_variable( f"_to_record_{variable}", dimensions=source_var.dim, dtype=source_var.dtype, ) self.variables.add_dynamic_array( variable, size=0, dimensions=source_var.dim, dtype=source_var.dtype, read_only=True, ) self.variables.add_arange("_source_idx", size=len(source)) self.variables.add_array( "count", size=len(source), dtype=np.int32, read_only=True, index="_source_idx", ) self.variables.add_constant("_source_start", start) self.variables.add_constant("_source_stop", stop) self.variables.add_constant("_source_N", source_N) self.variables.add_array( "N", size=1, dtype=np.int32, read_only=True, scalar=True ) record_variables = { varname: self.variables[varname] for varname in self.record_variables } template_kwds = { "eventspace_variable": source.variables[eventspace_name], "record_variables": record_variables, "record": self.record, } needed_variables = {eventspace_name} | self.record_variables CodeRunner.__init__( self, group=self, code=code, template="spikemonitor", name=None, # The name has already been initialized clock=source.clock, when=when, order=order, needed_variables=needed_variables, template_kwds=template_kwds, ) self.variables.create_clock_variables(self._clock, prefix="_clock_") self.add_dependency(source) self.written_readonly_vars = { self.variables[varname] for varname in self.record_variables } self._enable_group_attributes() def resize(self, new_size): # Note that this does not set N, this has to be done in the template # since we use a restricted pointer to access it (which promises that # we only change the value through this pointer) for variable in self.record_variables: self.variables[variable].resize(new_size) def reinit(self): """ Clears all recorded spikes """ raise NotImplementedError() @property def it(self): """ Returns the pair (`i`, `t`). """ if not self.record: raise AttributeError( "Indices and times have not been recorded." "Set the record argument to True to record " "them." ) return self.i, self.t @property def it_(self): """ Returns the pair (`i`, `t_`). """ if not self.record: raise AttributeError( "Indices and times have not been recorded." "Set the record argument to True to record " "them." ) return self.i, self.t_ def _values_dict(self, first_pos, sort_indices, used_indices, var): sorted_values = self.state(var, use_units=False)[sort_indices] dim = self.variables[var].dim event_values = {} current_pos = 0 # position in the all_indices array for idx in range(len(self.source)): if current_pos < len(used_indices) and used_indices[current_pos] == idx: if current_pos < len(used_indices) - 1: event_values[idx] = Quantity( sorted_values[ first_pos[current_pos] : first_pos[current_pos + 1] ], dim=dim, copy=False, ) else: event_values[idx] = Quantity( sorted_values[first_pos[current_pos] :], dim=dim, copy=False ) current_pos += 1 else: event_values[idx] = Quantity([], dim=dim) return event_values def values(self, var): """ Return a dictionary mapping neuron indices to arrays of variable values at the time of the events (sorted by time). Parameters ---------- var : str The name of the variable. Returns ------- values : dict Dictionary mapping each neuron index to an array of variable values at the time of the events Examples -------- >>> from brian2 import * >>> G = NeuronGroup(2, '''counter1 : integer ... counter2 : integer ... max_value : integer''', ... threshold='counter1 >= max_value', ... reset='counter1 = 0') >>> G.run_regularly('counter1 += 1; counter2 += 1') # doctest: +ELLIPSIS CodeRunner(...) >>> G.max_value = [50, 100] >>> mon = EventMonitor(G, event='spike', variables='counter2') >>> run(10*ms) >>> counter2_values = mon.values('counter2') >>> print(counter2_values[0]) [ 50 100] >>> print(counter2_values[1]) [100] """ if not self.record: raise AttributeError( "Indices and times have not been recorded." "Set the record argument to True to record " "them." ) indices = self.i[:] # We have to make sure that the sort is stable, otherwise our spike # times do not necessarily remain sorted. sort_indices = np.argsort(indices, kind="mergesort") used_indices, first_pos = np.unique(self.i[:][sort_indices], return_index=True) return self._values_dict(first_pos, sort_indices, used_indices, var) def all_values(self): """ Return a dictionary mapping recorded variable names (including ``t``) to a dictionary mapping neuron indices to arrays of variable values at the time of the events (sorted by time). This is equivalent to (but more efficient than) calling `values` for each variable and storing the result in a dictionary. Returns ------- all_values : dict Dictionary mapping variable names to dictionaries which themselves are mapping neuron indicies to arrays of variable values at the time of the events. Examples -------- >>> from brian2 import * >>> G = NeuronGroup(2, '''counter1 : integer ... counter2 : integer ... max_value : integer''', ... threshold='counter1 >= max_value', ... reset='counter1 = 0') >>> G.run_regularly('counter1 += 1; counter2 += 1') # doctest: +ELLIPSIS CodeRunner(...) >>> G.max_value = [50, 100] >>> mon = EventMonitor(G, event='spike', variables='counter2') >>> run(10*ms) >>> all_values = mon.all_values() >>> print(all_values['counter2'][0]) [ 50 100] >>> print(all_values['t'][1]) [ 9.9] ms """ if not self.record: raise AttributeError( "Indices and times have not been recorded." "Set the record argument to True to record " "them." ) indices = self.i[:] sort_indices = np.argsort(indices, kind="mergesort") used_indices, first_pos = np.unique(self.i[:][sort_indices], return_index=True) all_values_dict = {} for varname in self.record_variables - {"i"}: all_values_dict[varname] = self._values_dict( first_pos, sort_indices, used_indices, varname ) return all_values_dict def event_trains(self): """ Return a dictionary mapping neuron indices to arrays of event times. Equivalent to calling ``values('t')``. Returns ------- event_trains : dict Dictionary that stores an array with the event times for each neuron index. See Also -------- SpikeMonitor.spike_trains """ return self.values("t") @property def num_events(self): """ Returns the total number of recorded events. """ return self.N[:] def __repr__(self): classname = self.__class__.__name__ return f"<{classname}, recording event '{self.event}' from '{self.group.name}'>" class SpikeMonitor(EventMonitor): """ Record spikes from a `NeuronGroup` or other spike source. The recorded spikes can be accessed in various ways (see Examples below): the attributes `~SpikeMonitor.i` and `~SpikeMonitor.t` store all the indices and spike times, respectively. Alternatively, you can get a dictionary mapping neuron indices to spike trains, by calling the `spike_trains` method. If you record additional variables with the ``variables`` argument, these variables can be accessed by their name (see Examples). Parameters ---------- source : (`NeuronGroup`, `SpikeSource`) The source of spikes to record. variables : str or sequence of str, optional Which variables to record at the time of the spike (in addition to the index of the neuron). Can be the name of a variable or a list of names. record : bool, optional Whether or not to record each spike in `i` and `t` (the `count` will always be recorded). Defaults to ``True``. when : str, optional When to record the events, by default records events in the same slot where the event is emitted. See :ref:`scheduling` for possible values. order : int, optional The priority of of this group for operations occurring at the same time step and in the same scheduling slot. Defaults to the order where the event is emitted + 1, i.e. it will be recorded directly afterwards. name : str, optional A unique name for the object, otherwise will use ``source.name+'_spikemonitor_0'``, etc. codeobj_class : class, optional The `CodeObject` class to run code with. Examples -------- >>> from brian2 import * >>> spikes = SpikeGeneratorGroup(3, [0, 1, 2], [0, 1, 2]*ms) >>> spike_mon = SpikeMonitor(spikes) >>> net = Network(spikes, spike_mon) >>> net.run(3*ms) >>> print(spike_mon.i[:]) [0 1 2] >>> print(spike_mon.t[:]) [ 0. 1. 2.] ms >>> print(spike_mon.t_[:]) [ 0. 0.001 0.002] >>> from brian2 import * >>> G = NeuronGroup(2, '''counter1 : integer ... counter2 : integer ... max_value : integer''', ... threshold='counter1 >= max_value', ... reset='counter1 = 0') >>> G.run_regularly('counter1 += 1; counter2 += 1') # doctest: +ELLIPSIS CodeRunner(...) >>> G.max_value = [50, 100] >>> mon = SpikeMonitor(G, variables='counter2') >>> net = Network(G, mon) >>> net.run(10*ms) >>> print(mon.i[:]) [0 0 1] >>> print(mon.counter2[:]) [ 50 100 100] """ def __init__( self, source, variables=None, record=True, when=None, order=None, name="spikemonitor*", codeobj_class=None, ): #: The array of spike counts (length = size of target group) self.count = None del self.count # this is handled by the Variable mechanism super().__init__( source, event="spike", variables=variables, record=record, when=when, order=order, name=name, codeobj_class=codeobj_class, ) @property def num_spikes(self): """ Returns the total number of recorded spikes. """ return self.num_events # We "re-implement" the following functions only to get more specific # doc strings (and to make sure that the methods are included in the # reference documentation for SpikeMonitor). def spike_trains(self): """ Return a dictionary mapping neuron indices to arrays of spike times. Returns ------- spike_trains : dict Dictionary that stores an array with the spike times for each neuron index. Examples -------- >>> from brian2 import * >>> spikes = SpikeGeneratorGroup(3, [0, 1, 2], [0, 1, 2]*ms) >>> spike_mon = SpikeMonitor(spikes) >>> run(3*ms) >>> spike_trains = spike_mon.spike_trains() >>> spike_trains[1] array([ 1.]) * msecond """ return self.event_trains() def values(self, var): """ Return a dictionary mapping neuron indices to arrays of variable values at the time of the spikes (sorted by time). Parameters ---------- var : str The name of the variable. Returns ------- values : dict Dictionary mapping each neuron index to an array of variable values at the time of the spikes. Examples -------- >>> from brian2 import * >>> G = NeuronGroup(2, '''counter1 : integer ... counter2 : integer ... max_value : integer''', ... threshold='counter1 >= max_value', ... reset='counter1 = 0') >>> G.run_regularly('counter1 += 1; counter2 += 1') # doctest: +ELLIPSIS CodeRunner(...) >>> G.max_value = [50, 100] >>> mon = SpikeMonitor(G, variables='counter2') >>> run(10*ms) >>> counter2_values = mon.values('counter2') >>> print(counter2_values[0]) [ 50 100] >>> print(counter2_values[1]) [100] """ return super().values(var) def all_values(self): """ Return a dictionary mapping recorded variable names (including ``t``) to a dictionary mapping neuron indices to arrays of variable values at the time of the spikes (sorted by time). This is equivalent to (but more efficient than) calling `values` for each variable and storing the result in a dictionary. Returns ------- all_values : dict Dictionary mapping variable names to dictionaries which themselves are mapping neuron indicies to arrays of variable values at the time of the spikes. Examples -------- >>> from brian2 import * >>> G = NeuronGroup(2, '''counter1 : integer ... counter2 : integer ... max_value : integer''', ... threshold='counter1 >= max_value', ... reset='counter1 = 0') >>> G.run_regularly('counter1 += 1; counter2 += 1') # doctest: +ELLIPSIS CodeRunner(...) >>> G.max_value = [50, 100] >>> mon = SpikeMonitor(G, variables='counter2') >>> run(10*ms) >>> all_values = mon.all_values() >>> print(all_values['counter2'][0]) [ 50 100] >>> print(all_values['t'][1]) [ 9.9] ms """ return super().all_values() def __repr__(self): classname = self.__class__.__name__ return f"<{classname}, recording from '{self.group.name}'>" brian2-2.5.4/brian2/monitors/statemonitor.py000066400000000000000000000410661445201106100210310ustar00rootroot00000000000000import numbers from collections.abc import Sequence import numpy as np from brian2.core.variables import Variables, get_dtype from brian2.groups.group import CodeRunner, Group from brian2.units.allunits import second from brian2.units.fundamentalunits import Quantity from brian2.utils.logger import get_logger __all__ = ["StateMonitor"] logger = get_logger(__name__) class StateMonitorView: def __init__(self, monitor, item): self.monitor = monitor self.item = item self.indices = self._calc_indices(item) self._group_attribute_access_active = True def __getattr__(self, item): # We do this because __setattr__ and __getattr__ are not active until # _group_attribute_access_active attribute is set, and if it is set, # then __getattr__ will not be called. Therefore, if getattr is called # with this name, it is because it hasn't been set yet and so this # method should raise an AttributeError to agree that it hasn't been # called yet. if item == "_group_attribute_access_active": raise AttributeError if not hasattr(self, "_group_attribute_access_active"): raise AttributeError mon = self.monitor if item == "t": return Quantity(mon.variables["t"].get_value(), dim=second.dim) elif item == "t_": return mon.variables["t"].get_value() elif item in mon.record_variables: dims = mon.variables[item].dim return Quantity( mon.variables[item].get_value().T[self.indices], dim=dims, copy=True ) elif item.endswith("_") and item[:-1] in mon.record_variables: return mon.variables[item[:-1]].get_value().T[self.indices].copy() else: raise AttributeError(f"Unknown attribute {item}") def _calc_indices(self, item): """ Convert the neuron indices to indices into the stored values. For example, if neurons [0, 5, 10] have been recorded, [5, 10] is converted to [1, 2]. """ dtype = get_dtype(item) # scalar value if np.issubdtype(dtype, np.signedinteger) and not isinstance(item, np.ndarray): indices = np.nonzero(self.monitor.record == item)[0] if len(indices) == 0: raise IndexError(f"Index number {int(item)} has not been recorded") return indices[0] if self.monitor.record_all: return item indices = [] for index in item: if index in self.monitor.record: indices.append(np.nonzero(self.monitor.record == index)[0][0]) else: raise IndexError(f"Index number {int(index)} has not been recorded") return np.array(indices) def __repr__(self): classname = self.__class__.__name__ return ( f"<{classname}, giving access to elements {self.item!r} recorded by " f"{self.monitor.name}>" ) class StateMonitor(Group, CodeRunner): """ Record values of state variables during a run To extract recorded values after a run, use the ``t`` attribute for the array of times at which values were recorded, and variable name attribute for the values. The values will have shape ``(len(indices), len(t))``, where ``indices`` are the array indices which were recorded. When indexing the `StateMonitor` directly, the returned object can be used to get the recorded values for the specified indices, i.e. the indexing semantic refers to the indices in ``source``, not to the relative indices of the recorded values. For example, when recording only neurons with even numbers, `mon[[0, 2]].v` will return the values for neurons 0 and 2, whereas `mon.v[[0, 2]]` will return the values for the first and third *recorded* neurons, i.e. for neurons 0 and 4. Parameters ---------- source : `Group` Which object to record values from. variables : str, sequence of str, True Which variables to record, or ``True`` to record all variables (note that this may use a great deal of memory). record : bool, sequence of ints Which indices to record, nothing is recorded for ``False``, everything is recorded for ``True`` (warning: may use a great deal of memory), or a specified subset of indices. dt : `Quantity`, optional The time step to be used for the monitor. Cannot be combined with the `clock` argument. clock : `Clock`, optional The update clock to be used. If neither a clock, nor the ``dt`` argument is specified, the clock of the `source` will be used. when : str, optional At which point during a time step the values should be recorded. Defaults to ``'start'``. See :ref:`scheduling` for possible values. order : int, optional The priority of of this group for operations occurring at the same time step and in the same scheduling slot. Defaults to 0. name : str, optional A unique name for the object, otherwise will use ``source.name+'statemonitor_0'``, etc. codeobj_class : `CodeObject`, optional The `CodeObject` class to create. Examples -------- Record all variables, first 5 indices:: eqs = ''' dV/dt = (2-V)/(10*ms) : 1 ''' threshold = 'V>1' reset = 'V = 0' G = NeuronGroup(100, eqs, threshold=threshold, reset=reset) G.V = rand(len(G)) M = StateMonitor(G, True, record=range(5)) run(100*ms) plot(M.t, M.V.T) show() Notes ----- Since this monitor by default records in the ``'start'`` time slot, recordings of the membrane potential in integrate-and-fire models may look unexpected: the recorded membrane potential trace will never be above threshold in an integrate-and-fire model, because the reset statement will have been applied already. Set the ``when`` keyword to a different value if this is not what you want. Note that ``record=True`` only works in runtime mode for synaptic variables. This is because the actual array of indices has to be calculated and this is not possible in standalone mode, where the synapses have not been created yet at this stage. Consider using an explicit array of indices instead, i.e. something like ``record=np.arange(n_synapses)``. """ invalidates_magic_network = False add_to_magic_network = True def __init__( self, source, variables, record, dt=None, clock=None, when="start", order=0, name="statemonitor*", codeobj_class=None, ): self.source = source # Make the monitor use the explicitly defined namespace of its source # group (if it exists) self.namespace = getattr(source, "namespace", None) self.codeobj_class = codeobj_class # run by default on source clock at the end if dt is None and clock is None: clock = source.clock # variables should always be a list of strings if variables is True: variables = source.equations.names elif isinstance(variables, str): variables = [variables] #: The variables to record self.record_variables = variables # record should always be an array of ints self.record_all = False if hasattr(record, "_indices"): # The ._indices method always returns absolute indices # If the source is already a subgroup of another group, we therefore # have to shift the indices to become relative to the subgroup record = record._indices() - getattr(source, "_offset", 0) if record is True: self.record_all = True try: record = np.arange(len(source), dtype=np.int32) except NotImplementedError: # In standalone mode, this is not possible for synaptic # variables because the number of synapses is not defined yet raise NotImplementedError( "Cannot determine the actual " "indices to record for record=True. " "This can occur for example in " "standalone mode when trying to " "record a synaptic variable. " "Consider providing an explicit " "array of indices for the record " "argument." ) elif record is False: record = np.array([], dtype=np.int32) elif isinstance(record, numbers.Number): record = np.array([record], dtype=np.int32) else: record = np.asarray(record, dtype=np.int32) #: The array of recorded indices self.record = record self.n_indices = len(record) if not self.record_all: try: if len(record) and ( np.max(record) >= len(source) or np.min(record) < 0 ): # Check whether the values in record make sense error_message = ( "The indices to record from contain values outside of the" f" range [0, {len(source)-1}] allowed for the group" f" '{source.name}'" ) raise IndexError(error_message) except NotImplementedError: logger.warn( "Cannot check whether the indices to record from are valid. This" " can happen in standalone mode when recording from synapses that" " have been created with a connection pattern. You can avoid this" " situation by using synaptic indices in the connect call.", name_suffix="cannot_check_statemonitor_indices", ) # Some dummy code so that code generation takes care of the indexing # and subexpressions code = [f"_to_record_{v} = _source_{v}" for v in variables] code = "\n".join(code) CodeRunner.__init__( self, group=self, template="statemonitor", code=code, name=name, clock=clock, dt=dt, when=when, order=order, check_units=False, ) self.add_dependency(source) # Setup variables self.variables = Variables(self) self.variables.add_dynamic_array( "t", size=0, dimensions=second.dim, constant=False, dtype=self._clock.variables["t"].dtype, ) self.variables.add_array( "N", dtype=np.int32, size=1, scalar=True, read_only=True ) self.variables.add_array( "_indices", size=len(self.record), dtype=self.record.dtype, constant=True, read_only=True, values=self.record, ) self.variables.create_clock_variables(self._clock, prefix="_clock_") for varname in variables: var = source.variables[varname] if var.scalar and len(self.record) > 1: logger.warn( "Variable %s is a shared variable but it will be " "recorded once for every target." % varname, once=True, ) index = source.variables.indices[varname] self.variables.add_reference( f"_source_{varname}", source, varname, index=index ) if index not in ("_idx", "0") and index not in variables: self.variables.add_reference(index, source) self.variables.add_dynamic_array( varname, size=(0, len(self.record)), resize_along_first=True, dimensions=var.dim, dtype=var.dtype, constant=False, read_only=True, ) for varname in variables: var = self.source.variables[varname] self.variables.add_auxiliary_variable( f"_to_record_{varname}", dimensions=var.dim, dtype=var.dtype, scalar=var.scalar, ) self.recorded_variables = { varname: self.variables[varname] for varname in variables } recorded_names = [varname for varname in variables] self.needed_variables = recorded_names self.template_kwds = {"_recorded_variables": self.recorded_variables} self.written_readonly_vars = { self.variables[varname] for varname in self.record_variables } self._enable_group_attributes() def resize(self, new_size): self.variables["N"].set_value(new_size) self.variables["t"].resize(new_size) for var in self.recorded_variables.values(): var.resize((new_size, self.n_indices)) def reinit(self): raise NotImplementedError() def __getitem__(self, item): dtype = get_dtype(item) if np.issubdtype(dtype, np.signedinteger): return StateMonitorView(self, item) elif isinstance(item, Sequence): index_array = np.array(item) if not np.issubdtype(index_array.dtype, np.signedinteger): raise TypeError("Index has to be an integer or a sequence of integers") return StateMonitorView(self, item) elif hasattr(item, "_indices"): # objects that support the indexing interface will return absolute # indices but here we need relative ones # TODO: How to we prevent the use of completely unrelated objects here? source_offset = getattr(self.source, "_offset", 0) return StateMonitorView(self, item._indices() - source_offset) else: raise TypeError(f"Cannot use object of type {type(item)} as an index") def __getattr__(self, item): # We do this because __setattr__ and __getattr__ are not active until # _group_attribute_access_active attribute is set, and if it is set, # then __getattr__ will not be called. Therefore, if getattr is called # with this name, it is because it hasn't been set yet and so this # method should raise an AttributeError to agree that it hasn't been # called yet. if item == "_group_attribute_access_active": raise AttributeError if not hasattr(self, "_group_attribute_access_active"): raise AttributeError if item in self.record_variables: var_dim = self.variables[item].dim return Quantity(self.variables[item].get_value().T, dim=var_dim, copy=True) elif item.endswith("_") and item[:-1] in self.record_variables: return self.variables[item[:-1]].get_value().T else: return Group.__getattr__(self, item) def __repr__(self): classname = self.__class__.__name__ variables = repr(self.record_variables) return f"<{classname}, recording {variables} from '{self.source.name}'>" def record_single_timestep(self): """ Records a single time step. Useful for recording the values at the end of the simulation -- otherwise a `StateMonitor` will not record the last simulated values since its ``when`` attribute defaults to ``'start'``, i.e. the last recording is at the *beginning* of the last time step. Notes ----- This function will only work if the `StateMonitor` has been already run, but a run with a length of ``0*ms`` does suffice. Examples -------- >>> from brian2 import * >>> G = NeuronGroup(1, 'dv/dt = -v/(5*ms) : 1') >>> G.v = 1 >>> mon = StateMonitor(G, 'v', record=True) >>> run(0.5*ms) >>> print(np.array_str(mon.v[:], precision=3)) [[ 1. 0.98 0.961 0.942 0.923]] >>> print(mon.t[:]) [ 0. 100. 200. 300. 400.] us >>> print(np.array_str(G.v[:], precision=3)) # last value had not been recorded [ 0.905] >>> mon.record_single_timestep() >>> print(mon.t[:]) [ 0. 100. 200. 300. 400. 500.] us >>> print(np.array_str(mon.v[:], precision=3)) [[ 1. 0.98 0.961 0.942 0.923 0.905]] """ if self.codeobj is None: raise TypeError( "Can only record a single time step after the " "network has been run once." ) self.codeobj() brian2-2.5.4/brian2/numpy_.py000066400000000000000000000012561445201106100157330ustar00rootroot00000000000000""" A dummy package to allow importing numpy and the unit-aware replacements of numpy functions without having to know which functions are overwritten. This can be used for example as ``import brian2.numpy_ as np`` """ # isort:skip_file # flake8: noqa from numpy import * from brian2.units.unitsafefunctions import * # These will not be imported with a wildcard import to not overwrite the # builtin names (mimicking the numpy behaviour) from builtins import bool, float, complex from numpy.core import round, abs, max, min import numpy import brian2.units.unitsafefunctions as brian2_functions __all__ = [] __all__.extend(numpy.__all__) __all__.extend(brian2_functions.__all__) brian2-2.5.4/brian2/only.py000066400000000000000000000073571445201106100154150ustar00rootroot00000000000000""" A dummy package to allow wildcard import from brian2 without also importing the pylab (numpy + matplotlib) namespace. Usage: ``from brian2.only import *`` """ # To minimize the problems with imports, import the packages in a sensible # order # isort:skip_file # flake8: noqa # The units and utils package does not depend on any other Brian package and # should be imported first from brian2.units import * from brian2.utils import * from brian2.core.tracking import * from brian2.core.names import * from brian2.core.spikesource import * # The following packages only depend on something in the above set from brian2.core.variables import linked_var from brian2.core.functions import * from brian2.core.preferences import * from brian2.core.clocks import * from brian2.equations import * # The base class only depends on the above sets from brian2.core.base import * # The rest... from brian2.core.network import * from brian2.core.magic import * from brian2.core.operations import * from brian2.stateupdaters import * from brian2.codegen import * from brian2.core.namespace import * from brian2.groups import * from brian2.groups.subgroup import * from brian2.synapses import * from brian2.monitors import * from brian2.importexport import * from brian2.input import * from brian2.spatialneuron import * from brian2.devices import set_device, get_device, device, all_devices, seed import brian2.devices.cpp_standalone as _cpp_standalone # preferences import brian2.core.core_preferences as _core_preferences prefs.load_preferences() prefs.do_validation() prefs._backup() set_device(all_devices["runtime"]) def restore_initial_state(): """ Restores internal Brian variables to the state they are in when Brian is imported Resets ``defaultclock.dt = 0.1*ms``, `BrianGlobalPreferences._restore` preferences, and set `BrianObject._scope_current_key` back to 0. """ import gc prefs._restore() BrianObject._scope_current_key = 0 defaultclock.dt = 0.1 * ms gc.collect() # make the test suite available via brian2.test() from brian2.tests import run as test from brian2.units import __all__ as _all_units __all__ = [ "get_logger", "BrianLogger", "std_silent", "Trackable", "Nameable", "SpikeSource", "linked_var", "DEFAULT_FUNCTIONS", "Function", "implementation", "declare_types", "PreferenceError", "BrianPreference", "prefs", "brian_prefs", "Clock", "defaultclock", "Equations", "Expression", "Statements", "BrianObject", "BrianObjectException", "Network", "profiling_summary", "scheduling_summary", "MagicNetwork", "magic_network", "MagicError", "run", "stop", "collect", "store", "restore", "start_scope", "NetworkOperation", "network_operation", "StateUpdateMethod", "linear", "exact", "independent", "milstein", "heun", "euler", "rk2", "rk4", "ExplicitStateUpdater", "exponential_euler", "gsl_rk2", "gsl_rk4", "gsl_rkf45", "gsl_rkck", "gsl_rk8pd", "NumpyCodeObject", "CythonCodeObject", "get_local_namespace", "DEFAULT_FUNCTIONS", "DEFAULT_UNITS", "DEFAULT_CONSTANTS", "CodeRunner", "Group", "VariableOwner", "NeuronGroup", "Subgroup", "Synapses", "SpikeMonitor", "EventMonitor", "StateMonitor", "PopulationRateMonitor", "ImportExport", "BinomialFunction", "PoissonGroup", "PoissonInput", "SpikeGeneratorGroup", "TimedArray", "Morphology", "Soma", "Cylinder", "Section", "SpatialNeuron", "set_device", "get_device", "device", "all_devices", "seed", "restore_initial_state", "test", ] __all__.extend(_all_units) brian2-2.5.4/brian2/parsing/000077500000000000000000000000001445201106100155115ustar00rootroot00000000000000brian2-2.5.4/brian2/parsing/__init__.py000066400000000000000000000000001445201106100176100ustar00rootroot00000000000000brian2-2.5.4/brian2/parsing/bast.py000066400000000000000000000242341445201106100170210ustar00rootroot00000000000000""" Brian AST representation This is a standard Python AST representation with additional information added. """ import ast import weakref import numpy from brian2.parsing.rendering import get_node_value from brian2.utils.logger import get_logger __all__ = ["brian_ast", "BrianASTRenderer", "dtype_hierarchy"] logger = get_logger(__name__) # This codifies the idea that operations involving e.g. boolean and integer will end up # as integer. In general the output type will be the max of the hierarchy values here. dtype_hierarchy = { "boolean": 0, "integer": 1, "float": 2, } # This is just so you can invert from number to string for tc, i in dict(dtype_hierarchy).items(): dtype_hierarchy[i] = tc def is_boolean(value): return isinstance(value, bool) def is_integer(value): return isinstance(value, (int, numpy.integer)) def is_float(value): return isinstance(value, (float, numpy.float32, numpy.float64)) def brian_dtype_from_value(value): """ Returns 'boolean', 'integer' or 'float' """ if is_float(value): return "float" elif is_integer(value): return "integer" elif is_boolean(value): return "boolean" raise TypeError(f"Unknown dtype for value {str(value)}") # The following functions are called very often during the optimisation process # so we don't use numpy.issubdtype but instead a precalculated list of all # standard types bool_dtype = numpy.dtype(bool) def is_boolean_dtype(obj): return numpy.dtype(obj) is bool_dtype integer_dtypes = {numpy.dtype(c) for c in numpy.typecodes["AllInteger"]} def is_integer_dtype(obj): return numpy.dtype(obj) in integer_dtypes float_dtypes = {numpy.dtype(c) for c in numpy.typecodes["AllFloat"]} def is_float_dtype(obj): return numpy.dtype(obj) in float_dtypes def brian_dtype_from_dtype(dtype): """ Returns 'boolean', 'integer' or 'float' """ if is_float_dtype(dtype): return "float" elif is_integer_dtype(dtype): return "integer" elif is_boolean_dtype(dtype): return "boolean" raise TypeError(f"Unknown dtype: {str(dtype)}") def brian_ast(expr, variables): """ Returns an AST tree representation with additional information Each node will be a standard Python ``ast`` node with the following additional attributes: ``dtype`` One of ``'boolean'``, ``'integer'`` or ``'float'``, referring to the data type of the value of this node. ``scalar`` Either ``True`` or ``False`` if the node uses any vector-valued variables. ``complexity`` An integer representation of the computational complexity of the node. This is a very rough representation used for things like ``2*(x+y)`` is less complex than ``2*x+2*y`` and ``exp(x)`` is more complex than ``2*x`` but shouldn't be relied on for fine distinctions between expressions. Parameters ---------- expr : str The expression to convert into an AST representation variables : dict The dictionary of `Variable` objects used in the expression. """ node = ast.parse(expr, mode="eval").body renderer = BrianASTRenderer(variables) return renderer.render_node(node) class BrianASTRenderer: """ This class is modelled after `NodeRenderer` - see there for details. """ def __init__(self, variables, copy_variables=True): if copy_variables: self.variables = variables.copy() else: self.variables = variables def render_node(self, node): nodename = node.__class__.__name__ methname = f"render_{nodename}" try: return getattr(self, methname)(node) except AttributeError: raise SyntaxError(f"Unknown syntax: {nodename}") def render_NameConstant(self, node): if node.value is not True and node.value is not False: raise SyntaxError(f"Unknown NameConstant {str(node.value)}") # NameConstant only used for True and False and None, and we don't support None node.dtype = "boolean" node.scalar = True node.complexity = 0 node.stateless = True return node def render_Name(self, node): node.complexity = 0 if node.id == "True" or node.id == "False": node.dtype = "boolean" node.scalar = True elif node.id in self.variables: var = self.variables[node.id] dtype = var.dtype node.dtype = brian_dtype_from_dtype(dtype) node.scalar = var.scalar else: # don't think we need to handle other names (pi, e, inf)? node.dtype = "float" node.scalar = True # I think this assumption is OK, but not certain node.stateless = True return node def render_Num(self, node): node.complexity = 0 node.dtype = brian_dtype_from_value(get_node_value(node)) node.scalar = True node.stateless = True return node def render_Constant(self, node): # For literals in Python 3.8 if node.value is True or node.value is False or node.value is None: return self.render_NameConstant(node) else: return self.render_Num(node) def render_Call(self, node): if len(node.keywords): raise ValueError("Keyword arguments not supported.") elif getattr(node, "starargs", None) is not None: raise ValueError("Variable number of arguments not supported") elif getattr(node, "kwargs", None) is not None: raise ValueError("Keyword arguments not supported") args = [] for subnode in node.args: subnode.parent = weakref.proxy(node) subnode = self.render_node(subnode) args.append(subnode) node.args = args node.dtype = "float" # default dtype # Condition for scalarity of function call: stateless and arguments are scalar node.scalar = False if node.func.id in self.variables: funcvar = self.variables[node.func.id] # sometimes this attribute doesn't exist, if so assume it's not stateless node.stateless = getattr(funcvar, "stateless", False) node.auto_vectorise = getattr(funcvar, "auto_vectorise", False) if node.stateless and not node.auto_vectorise: node.scalar = all(subnode.scalar for subnode in node.args) # check that argument types are valid node_arg_types = [subnode.dtype for subnode in node.args] for subnode, argtype in zip(node.args, funcvar._arg_types): if argtype != "any" and argtype != subnode.dtype: raise TypeError( f"Function '{node.func.id}' takes arguments with " f"types {funcvar._arg_types} but " f"received {node_arg_types}." ) # compute return type return_type = funcvar._return_type if return_type == "highest": return_type = dtype_hierarchy[ max(dtype_hierarchy[nat] for nat in node_arg_types) ] node.dtype = return_type else: node.stateless = False # we leave node.func because it is an ast.Name object that doesn't have a dtype # TODO: variable complexity for function calls? node.complexity = 20 + sum(subnode.complexity for subnode in node.args) return node def render_BinOp(self, node): node.left.parent = weakref.proxy(node) node.right.parent = weakref.proxy(node) node.left = self.render_node(node.left) node.right = self.render_node(node.right) # TODO: we could capture some syntax errors here, e.g. bool+bool # captures, e.g. int+float->float newdtype = dtype_hierarchy[ max(dtype_hierarchy[subnode.dtype] for subnode in [node.left, node.right]) ] if node.op.__class__.__name__ == "Div": # Division turns integers into floating point values newdtype = "float" node.dtype = newdtype node.scalar = node.left.scalar and node.right.scalar node.complexity = 1 + node.left.complexity + node.right.complexity node.stateless = node.left.stateless and node.right.stateless return node def render_BoolOp(self, node): values = [] for subnode in node.values: subnode.parent = node subnode = self.render_node(subnode) values.append(subnode) node.values = values node.dtype = "boolean" for subnode in node.values: if subnode.dtype != "boolean": raise TypeError("Boolean operator acting on non-booleans") node.scalar = all(subnode.scalar for subnode in node.values) node.complexity = 1 + sum(subnode.complexity for subnode in node.values) node.stateless = all(subnode.stateless for subnode in node.values) return node def render_Compare(self, node): node.left = self.render_node(node.left) comparators = [] for subnode in node.comparators: subnode.parent = node subnode = self.render_node(subnode) comparators.append(subnode) node.comparators = comparators node.dtype = "boolean" comparators = [node.left] + node.comparators node.scalar = all(subnode.scalar for subnode in comparators) node.complexity = 1 + sum(subnode.complexity for subnode in comparators) node.stateless = node.left.stateless and all( c.stateless for c in node.comparators ) return node def render_UnaryOp(self, node): node.operand.parent = node node.operand = self.render_node(node.operand) node.dtype = node.operand.dtype if node.dtype == "boolean" and node.op.__class__.__name__ != "Not": raise TypeError( f"Unary operator {node.op.__class__.__name__} does not apply to boolean" " types" ) node.scalar = node.operand.scalar node.complexity = 1 + node.operand.complexity node.stateless = node.operand.stateless return node brian2-2.5.4/brian2/parsing/dependencies.py000066400000000000000000000106751445201106100205220ustar00rootroot00000000000000import ast from collections import namedtuple from brian2.utils.stringtools import deindent __all__ = ["abstract_code_dependencies"] def get_read_write_funcs(parsed_code): allids = set() read = set() write = set() funcs = set() for node in ast.walk(parsed_code): if node.__class__ is ast.Name: allids.add(node.id) if node.ctx.__class__ is ast.Store: write.add(node.id) elif node.ctx.__class__ is ast.Load: read.add(node.id) else: raise SyntaxError elif node.__class__ is ast.Call: funcs.add(node.func.id) read = read - funcs # check that there's no funky stuff going on with functions if funcs.intersection(write): raise SyntaxError("Cannot assign to functions in abstract code") return allids, read, write, funcs def abstract_code_dependencies(code, known_vars=None, known_funcs=None): """ Analyses identifiers used in abstract code blocks Parameters ---------- code : str The abstract code block. known_vars : set The set of known variable names. known_funcs : set The set of known function names. Returns ------- results : namedtuple with the following fields ``all`` The set of all identifiers that appear in this code block, including functions. ``read`` The set of values that are read, excluding functions. ``write`` The set of all values that are written to. ``funcs`` The set of all function names. ``known_all`` The set of all identifiers that appear in this code block and are known. ``known_read`` The set of known values that are read, excluding functions. ``known_write`` The set of known values that are written to. ``known_funcs`` The set of known functions that are used. ``unknown_read`` The set of all unknown variables whose values are read. Equal to ``read-known_vars``. ``unknown_write`` The set of all unknown variables written to. Equal to ``write-known_vars``. ``unknown_funcs`` The set of all unknown function names, equal to ``funcs-known_funcs``. ``undefined_read`` The set of all unknown variables whose values are read before they are written to. If this set is nonempty it usually indicates an error, since a variable that is read should either have been defined in the code block (in which case it will appear in ``newly_defined``) or already be known. ``newly_defined`` The set of all variable names which are newly defined in this abstract code block. """ if known_vars is None: known_vars = set() if known_funcs is None: known_funcs = set() if not isinstance(known_vars, set): known_vars = set(known_vars) if not isinstance(known_funcs, set): known_funcs = set(known_funcs) code = deindent(code, docstring=True) parsed_code = ast.parse(code, mode="exec") # Get the list of all variables that are read from and written to, # ignoring the order allids, read, write, funcs = get_read_write_funcs(parsed_code) # Now check if there are any values that are unknown and read before # they are written to defined = known_vars.copy() newly_defined = set() undefined_read = set() for line in parsed_code.body: _, cur_read, cur_write, _ = get_read_write_funcs(line) undef = cur_read - defined undefined_read |= undef newly_defined |= (cur_write - defined) - undefined_read defined |= cur_write # Return the results as a named tuple results = dict( all=allids, read=read, write=write, funcs=funcs, known_all=allids.intersection(known_vars.union(known_funcs)), known_read=read.intersection(known_vars), known_write=write.intersection(known_vars), known_funcs=funcs.intersection(known_funcs), unknown_read=read - known_vars, unknown_write=write - known_vars, unknown_funcs=funcs - known_funcs, undefined_read=undefined_read, newly_defined=newly_defined, ) return namedtuple("AbstractCodeDependencies", list(results.keys()))(**results) brian2-2.5.4/brian2/parsing/expressions.py000066400000000000000000000421411445201106100204470ustar00rootroot00000000000000""" AST parsing based analysis of expressions """ import ast from brian2.core.functions import Function from brian2.parsing.rendering import NodeRenderer from brian2.units.fundamentalunits import ( DIMENSIONLESS, DimensionMismatchError, Unit, get_dimensions, get_unit_for_display, have_same_dimensions, ) __all__ = ["parse_expression_dimensions"] def is_boolean_expression(expr, variables): """ Determines if an expression is of boolean type or not Parameters ---------- expr : str The expression to test variables : dict-like of `Variable` The variables used in the expression. Returns ------- isbool : bool Whether or not the expression is boolean. Raises ------ SyntaxError If the expression ought to be boolean but is not, for example ``x", expr.lineno, expr.col_offset + 1, orig_expr), ) if name in variables: return get_dimensions(variables[name]) elif name in ["True", "False"]: return DIMENSIONLESS else: raise KeyError(f"Unknown identifier {name}") elif expr.__class__ is ast.Num or expr.__class__ is getattr( ast, "Constant", None ): # Python 3.8 return DIMENSIONLESS elif expr.__class__ is ast.BoolOp: # check that the units are valid in each subexpression for node in expr.values: parse_expression_dimensions(node, variables, orig_expr=orig_expr) # but the result is a bool, so we just return 1 as the unit return DIMENSIONLESS elif expr.__class__ is ast.Compare: # check that the units are consistent in each subexpression subexprs = [expr.left] + expr.comparators subunits = [] for node in subexprs: subunits.append( parse_expression_dimensions(node, variables, orig_expr=orig_expr) ) for left_dim, right_dim in zip(subunits[:-1], subunits[1:]): if not have_same_dimensions(left_dim, right_dim): left_expr = NodeRenderer().render_node(expr.left) right_expr = NodeRenderer().render_node(expr.comparators[0]) dim_left = get_dimensions(left_dim) dim_right = get_dimensions(right_dim) msg = ( "Comparison of expressions with different units. Expression " f"'{left_expr}' has unit ({dim_left}), while expression " f"'{right_expr}' has units ({dim_right})." ) raise DimensionMismatchError(msg) # but the result is a bool, so we just return 1 as the unit return DIMENSIONLESS elif expr.__class__ is ast.Call: if len(expr.keywords): raise ValueError("Keyword arguments not supported.") elif getattr(expr, "starargs", None) is not None: raise ValueError("Variable number of arguments not supported") elif getattr(expr, "kwargs", None) is not None: raise ValueError("Keyword arguments not supported") func = variables.get(expr.func.id, None) if func is None: raise SyntaxError( f"Unknown function {expr.func.id}", ("", expr.lineno, expr.col_offset + 1, orig_expr), ) if not hasattr(func, "_arg_units") or not hasattr(func, "_return_unit"): raise ValueError( f"Function {expr.func.id} does not specify how it deals with units." ) if len(func._arg_units) != len(expr.args): raise SyntaxError( f"Function '{expr.func.id}' was called with " f"{len(expr.args)} parameters, needs " f"{len(func._arg_units)}.", ( "", expr.lineno, expr.col_offset + len(expr.func.id) + 1, orig_expr, ), ) for idx, (arg, expected_unit) in enumerate(zip(expr.args, func._arg_units)): arg_unit = parse_expression_dimensions(arg, variables, orig_expr=orig_expr) # A "None" in func._arg_units means: No matter what unit if expected_unit is None: continue # A string means: same unit as other argument elif isinstance(expected_unit, str): arg_idx = func._arg_names.index(expected_unit) expected_unit = parse_expression_dimensions( expr.args[arg_idx], variables, orig_expr=orig_expr ) if not have_same_dimensions(arg_unit, expected_unit): msg = ( f"Argument number {idx + 1} for function " f"{expr.func.id} was supposed to have the " f"same units as argument number {arg_idx + 1}, but " f"'{NodeRenderer().render_node(arg)}' has unit " f"{get_unit_for_display(arg_unit)}, while " f"'{NodeRenderer().render_node(expr.args[arg_idx])}' " f"has unit {get_unit_for_display(expected_unit)}" ) raise DimensionMismatchError(msg) elif expected_unit == bool: if not is_boolean_expression(arg, variables): rendered_arg = NodeRenderer().render_node(arg) raise TypeError( f"Argument number {idx + 1} for function " f"'{expr.func.id}' was expected to be a boolean " f"value, but is '{rendered_arg}'." ) else: if not have_same_dimensions(arg_unit, expected_unit): rendered_arg = NodeRenderer().render_node(arg) arg_unit_dim = get_dimensions(arg_unit) expected_unit_dim = get_dimensions(expected_unit) msg = ( f"Argument number {idx+1} for function {expr.func.id} does " f"not have the correct units. Expression '{rendered_arg}' " f"has units ({arg_unit_dim}), but " "should be " f"({expected_unit_dim})." ) raise DimensionMismatchError(msg) if func._return_unit == bool: return DIMENSIONLESS elif isinstance(func._return_unit, (Unit, int)): # Function always returns the same unit return getattr(func._return_unit, "dim", DIMENSIONLESS) else: # Function returns a unit that depends on the arguments arg_units = [ parse_expression_dimensions(arg, variables, orig_expr=orig_expr) for arg in expr.args ] return func._return_unit(*arg_units).dim elif expr.__class__ is ast.BinOp: op = expr.op.__class__.__name__ left_dim = parse_expression_dimensions( expr.left, variables, orig_expr=orig_expr ) right_dim = parse_expression_dimensions( expr.right, variables, orig_expr=orig_expr ) if op in ["Add", "Sub", "Mod"]: # dimensions should be the same if left_dim is not right_dim: op_symbol = {"Add": "+", "Sub": "-", "Mod": "%"}.get(op) left_str = NodeRenderer().render_node(expr.left) right_str = NodeRenderer().render_node(expr.right) left_unit = get_unit_for_display(left_dim) right_unit = get_unit_for_display(right_dim) error_msg = ( f"Expression '{left_str} {op_symbol} {right_str}' uses " f"inconsistent units ('{left_str}' has unit " f"{left_unit}; '{right_str}' " f"has unit {right_unit})." ) raise DimensionMismatchError(error_msg) u = left_dim elif op == "Mult": u = left_dim * right_dim elif op == "Div": u = left_dim / right_dim elif op == "FloorDiv": if not (left_dim is DIMENSIONLESS and right_dim is DIMENSIONLESS): if left_dim is DIMENSIONLESS: col_offset = expr.right.col_offset + 1 else: col_offset = expr.left.col_offset + 1 raise SyntaxError( "Floor division can only be used on dimensionless values.", ("", expr.lineno, col_offset, orig_expr), ) u = DIMENSIONLESS elif op == "Pow": if left_dim is DIMENSIONLESS and right_dim is DIMENSIONLESS: return DIMENSIONLESS n = _get_value_from_expression(expr.right, variables) u = left_dim**n else: raise SyntaxError( f"Unsupported operation {op}", ( "", expr.lineno, getattr( expr.left, "end_col_offset", len(NodeRenderer().render_node(expr.left)), ) + 1, orig_expr, ), ) return u elif expr.__class__ is ast.UnaryOp: op = expr.op.__class__.__name__ # check validity of operand and get its unit u = parse_expression_dimensions(expr.operand, variables, orig_expr=orig_expr) if op == "Not": return DIMENSIONLESS else: return u else: raise SyntaxError( f"Unsupported operation {str(expr.__class__.__name__)}", ("", expr.lineno, expr.col_offset + 1, orig_expr), ) brian2-2.5.4/brian2/parsing/functions.py000066400000000000000000000213621445201106100200770ustar00rootroot00000000000000import ast import inspect from brian2.utils.stringtools import deindent, get_identifiers, indent from .rendering import NodeRenderer __all__ = [ "AbstractCodeFunction", "abstract_code_from_function", "extract_abstract_code_functions", "substitute_abstract_code_functions", ] class AbstractCodeFunction: """ The information defining an abstract code function Has attributes corresponding to initialisation parameters Parameters ---------- name : str The function name. args : list of str The arguments to the function. code : str The abstract code string consisting of the body of the function less the return statement. return_expr : str or None The expression returned, or None if there is nothing returned. """ def __init__(self, name, args, code, return_expr): self.name = name self.args = args self.code = code self.return_expr = return_expr def __str__(self): s = ( f"def {self.name}({', '.join(self.args)}):\n{indent(self.code)}\n return" f" {self.return_expr}\n" ) return s __repr__ = __str__ def abstract_code_from_function(func): """ Converts the body of the function to abstract code Parameters ---------- func : function, str or ast.FunctionDef The function object to convert. Note that the arguments to the function are ignored. Returns ------- func : AbstractCodeFunction The corresponding abstract code function Raises ------ SyntaxError If unsupported features are used such as if statements or indexing. """ if callable(func): code = deindent(inspect.getsource(func)) funcnode = ast.parse(code, mode="exec").body[0] elif isinstance(func, str): funcnode = ast.parse(func, mode="exec").body[0] elif func.__class__ is ast.FunctionDef: funcnode = func else: raise TypeError("Unsupported function type") if funcnode.args.vararg is not None: raise SyntaxError("No support for variable number of arguments") if funcnode.args.kwarg is not None: raise SyntaxError("No support for arbitrary keyword arguments") if len(funcnode.args.defaults): raise SyntaxError("No support for default values in functions") nodes = funcnode.body nr = NodeRenderer() lines = [] return_expr = None for node in nodes: if node.__class__ is ast.Return: return_expr = nr.render_node(node.value) break else: lines.append(nr.render_node(node)) abstract_code = "\n".join(lines) args = [arg.arg for arg in funcnode.args.args] name = funcnode.name return AbstractCodeFunction(name, args, abstract_code, return_expr) def extract_abstract_code_functions(code): """ Returns a set of abstract code functions from function definitions. Returns all functions defined at the top level and ignores any other code in the string. Parameters ---------- code : str The code string defining some functions. Returns ------- funcs : dict A mapping ``(name, func)`` for ``func`` an `AbstractCodeFunction`. """ code = deindent(code) nodes = ast.parse(code, mode="exec").body funcs = {} for node in nodes: if node.__class__ is ast.FunctionDef: func = abstract_code_from_function(node) funcs[func.name] = func return funcs class VarRewriter(ast.NodeTransformer): """ Rewrites all variable names in names by prepending pre """ def __init__(self, pre): self.pre = pre def visit_Name(self, node): return ast.Name(id=self.pre + node.id, ctx=node.ctx) def visit_Call(self, node): args = [self.visit(arg) for arg in node.args] return ast.Call( func=ast.Name(id=node.func.id, ctx=ast.Load()), args=args, keywords=[], starargs=None, kwargs=None, ) class FunctionRewriter(ast.NodeTransformer): """ Inlines a function call using temporary variables numcalls is the number of times the function rewriter has been called so far, this is used to make sure that when recursively inlining there is no name aliasing. The substitute_abstract_code_functions ensures that this is kept up to date between recursive runs. The pre attribute is the set of lines to be inserted above the currently being processed line, i.e. the inline code. The visit method returns the current line processed so that the function call is replaced with the output of the inlining. """ def __init__(self, func, numcalls=0): self.func = func self.numcalls = numcalls self.pre = [] self.suspend = False def visit_Call(self, node): # we suspend operations during an inlining operation, then resume # afterwards, see below, so we only ever try to expand one inline # function call at a time, i.e. no f(f(x)). This case is handled # by the recursion. if self.suspend: return node # We only work with the function we're provided if node.func.id != self.func.name: return node # Suspend while processing arguments (no recursion) self.suspend = True args = [self.visit(arg) for arg in node.args] self.suspend = False # The basename is used for function-local variables basename = f"_inline_{self.func.name}_{str(self.numcalls)}" # Assign all the function-local variables for argname, arg in zip(self.func.args, args): newpre = ast.Assign( targets=[ast.Name(id=f"{basename}_{argname}", ctx=ast.Store())], value=arg, ) self.pre.append(newpre) # Rewrite the lines of code of the function using the names defined # above vr = VarRewriter(f"{basename}_") for funcline in ast.parse(self.func.code).body: self.pre.append(vr.visit(funcline)) # And rewrite the return expression return_expr = vr.visit(ast.parse(self.func.return_expr, mode="eval").body) self.pre.append( ast.Assign( targets=[ast.Name(id=basename, ctx=ast.Store())], value=return_expr ) ) # Finally we replace the function call with the output of the inlining newnode = ast.Name(id=basename) self.numcalls += 1 return newnode def substitute_abstract_code_functions(code, funcs): """ Performs inline substitution of all the functions in the code Parameters ---------- code : str The abstract code to make inline substitutions into. funcs : list, dict or set of AbstractCodeFunction The function substitutions to use, note in the case of a dict, the keys are ignored and the function name is used. Returns ------- code : str The code with inline substitutions performed. """ if isinstance(funcs, (list, set)): newfuncs = dict() for f in funcs: newfuncs[f.name] = f funcs = newfuncs code = deindent(code) lines = ast.parse(code, mode="exec").body # This is a slightly nasty hack, but basically we just check by looking at # the existing identifiers how many inline operations have already been # performed by previous calls to this function ids = get_identifiers(code) funcstarts = {} for func in funcs.values(): subids = {id for id in ids if id.startswith(f"_inline_{func.name}_")} subids = {id.replace(f"_inline_{func.name}_", "") for id in subids} alli = [] for subid in subids: p = subid.find("_") if p > 0: subid = subid[:p] i = int(subid) alli.append(i) if len(alli) == 0: i = 0 else: i = max(alli) + 1 funcstarts[func.name] = i # Now we rewrite all the lines, replacing each line with a sequence of # lines performing the inlining newlines = [] for line in lines: for func in funcs.values(): rw = FunctionRewriter(func, funcstarts[func.name]) line = rw.visit(line) newlines.extend(rw.pre) funcstarts[func.name] = rw.numcalls newlines.append(line) # Now we render to a code string nr = NodeRenderer() newcode = "\n".join(nr.render_node(line) for line in newlines) # We recurse until no changes in the code to ensure that all functions # are expanded if one function refers to another, etc. if newcode == code: return newcode else: return substitute_abstract_code_functions(newcode, funcs) brian2-2.5.4/brian2/parsing/rendering.py000066400000000000000000000305771445201106100200540ustar00rootroot00000000000000import ast import numbers import sympy from brian2.core.functions import DEFAULT_CONSTANTS, DEFAULT_FUNCTIONS __all__ = [ "NodeRenderer", "NumpyNodeRenderer", "CPPNodeRenderer", "SympyNodeRenderer", "get_node_value", ] def get_node_value(node): """Helper function to mask differences between Python versions""" value = getattr(node, "n", getattr(node, "value", None)) if value is None: raise AttributeError(f'Node {node} has neither "n" nor "value" attribute') return value class NodeRenderer: expression_ops = { # BinOp "Add": "+", "Sub": "-", "Mult": "*", "Div": "/", "FloorDiv": "//", "Pow": "**", "Mod": "%", # Compare "Lt": "<", "LtE": "<=", "Gt": ">", "GtE": ">=", "Eq": "==", "NotEq": "!=", # Unary ops "Not": "not", "UAdd": "+", "USub": "-", # Bool ops "And": "and", "Or": "or", # Augmented assign "AugAdd": "+=", "AugSub": "-=", "AugMult": "*=", "AugDiv": "/=", "AugPow": "**=", "AugMod": "%=", } def __init__(self, auto_vectorise=None): if auto_vectorise is None: auto_vectorise = set() self.auto_vectorise = auto_vectorise def render_expr(self, expr, strip=True): if strip: expr = expr.strip() node = ast.parse(expr, mode="eval") return self.render_node(node.body) def render_code(self, code): lines = [] for node in ast.parse(code).body: lines.append(self.render_node(node)) return "\n".join(lines) def render_node(self, node): nodename = node.__class__.__name__ methname = f"render_{nodename}" try: return getattr(self, methname)(node) except AttributeError: raise SyntaxError(f"Unknown syntax: {nodename}") def render_func(self, node): return self.render_Name(node) def render_NameConstant(self, node): return str(node.value) def render_Name(self, node): return node.id def render_Num(self, node): return repr(get_node_value(node)) def render_Constant(self, node): # For literals in Python 3.8 if node.value is True or node.value is False or node.value is None: return self.render_NameConstant(node) else: return self.render_Num(node) def render_Call(self, node): if len(node.keywords): raise ValueError("Keyword arguments not supported.") elif getattr(node, "starargs", None) is not None: raise ValueError("Variable number of arguments not supported") elif getattr(node, "kwargs", None) is not None: raise ValueError("Keyword arguments not supported") else: if node.func.id in self.auto_vectorise: vectorisation_idx = ast.Name() vectorisation_idx.id = "_vectorisation_idx" args = node.args + [vectorisation_idx] else: args = node.args return ( f"{self.render_func(node.func)}({', '.join(self.render_node(arg) for arg in args)})" ) def render_element_parentheses(self, node): """ Render an element with parentheses around it or leave them away for numbers, names and function calls. """ if node.__class__.__name__ in ["Name", "NameConstant"]: return self.render_node(node) elif ( node.__class__.__name__ in ["Num", "Constant"] and get_node_value(node) >= 0 ): return self.render_node(node) elif node.__class__.__name__ == "Call": return self.render_node(node) else: return f"({self.render_node(node)})" def render_BinOp_parentheses(self, left, right, op): # Use a simplified checking whether it is possible to omit parentheses: # only omit parentheses for numbers, variable names or function calls. # This means we still put needless parentheses because we ignore # precedence rules, e.g. we write "3 + (4 * 5)" but at least we do # not do "(3) + ((4) + (5))" op_class = op.__class__.__name__ # Give a more useful error message when using bit-wise operators if op_class in ["BitXor", "BitAnd", "BitOr"]: correction = { "BitXor": ("^", "**"), "BitAnd": ("&", "and"), "BitOr": ("|", "or"), }.get(op_class) raise SyntaxError( f'The operator "{correction[0]}" is not supported, use' f' "{correction[1]}" instead.' ) return ( f"{self.render_element_parentheses(left)} " f"{self.expression_ops[op_class]} " f"{self.render_element_parentheses(right)}" ) def render_BinOp(self, node): return self.render_BinOp_parentheses(node.left, node.right, node.op) def render_BoolOp(self, node): op = self.expression_ops[node.op.__class__.__name__] return (f" {op} ").join( f"{self.render_element_parentheses(v)}" for v in node.values ) def render_Compare(self, node): if len(node.comparators) > 1: raise SyntaxError("Can only handle single comparisons like a 1: raise SyntaxError("Only support syntax like a=b not a=b=c") return f"{self.render_node(node.targets[0])} = {self.render_node(node.value)}" def render_AugAssign(self, node): target = node.target.id rhs = self.render_node(node.value) op = self.expression_ops[f"Aug{node.op.__class__.__name__}"] return f"{target} {op} {rhs}" class NumpyNodeRenderer(NodeRenderer): expression_ops = NodeRenderer.expression_ops.copy() expression_ops.update( { # Unary ops # We'll handle "not" explicitly below # Bool ops "And": "&", "Or": "|", } ) def render_UnaryOp(self, node): if node.op.__class__.__name__ == "Not": return f"logical_not({self.render_node(node.operand)})" else: return NodeRenderer.render_UnaryOp(self, node) class SympyNodeRenderer(NodeRenderer): expression_ops = { "Add": sympy.Add, "Mult": sympy.Mul, "Pow": sympy.Pow, "Mod": sympy.Mod, # Compare "Lt": sympy.StrictLessThan, "LtE": sympy.LessThan, "Gt": sympy.StrictGreaterThan, "GtE": sympy.GreaterThan, "Eq": sympy.Eq, "NotEq": sympy.Ne, # Unary ops are handled manually # Bool ops "And": sympy.And, "Or": sympy.Or, } def render_func(self, node): if node.id in DEFAULT_FUNCTIONS: f = DEFAULT_FUNCTIONS[node.id] if f.sympy_func is not None and isinstance( f.sympy_func, sympy.FunctionClass ): return f.sympy_func # special workaround for the "int" function if node.id == "int": return sympy.Function("int_") else: return sympy.Function(node.id) def render_Call(self, node): if len(node.keywords): raise ValueError("Keyword arguments not supported.") elif getattr(node, "starargs", None) is not None: raise ValueError("Variable number of arguments not supported") elif getattr(node, "kwargs", None) is not None: raise ValueError("Keyword arguments not supported") elif len(node.args) == 0: return self.render_func(node.func)(sympy.Symbol("_placeholder_arg")) else: return self.render_func(node.func)( *(self.render_node(arg) for arg in node.args) ) def render_Compare(self, node): if len(node.comparators) > 1: raise SyntaxError("Can only handle single comparisons like a>|<<|&|\^|\|)?=").setResultsName("operation") EXPR = CharsNotIn("#").setResultsName("expression") COMMENT = CharsNotIn("#").setResultsName("comment") STATEMENT = VARIABLE + OP + EXPR + Optional(Suppress("#") + COMMENT) @cached def parse_statement(code): """ parse_statement(code) Parses a single line of code into "var op expr". Parameters ---------- code : str A string containing a single statement of the form ``var op expr # comment``, where the ``# comment`` part is optional. Returns ------- var, op, expr, comment : str, str, str, str The four parts of the statement. Examples -------- >>> parse_statement('v = -65*mV # reset the membrane potential') ('v', '=', '-65*mV', 'reset the membrane potential') >>> parse_statement('v += dt*(-v/tau)') ('v', '+=', 'dt*(-v/tau)', '') """ try: parsed = STATEMENT.parseString(code, parseAll=True) except ParseException as p_exc: raise ValueError( "Parsing the statement failed: \n" + str(p_exc.line) + "\n" + " " * (p_exc.column - 1) + "^\n" + str(p_exc) ) if len(parsed["expression"].strip()) == 0: raise ValueError(f"Empty expression in the RHS of the statement:'{code}' ") parsed_statement = ( parsed["variable"].strip(), parsed["operation"], parsed["expression"].strip(), parsed.get("comment", "").strip(), ) return parsed_statement brian2-2.5.4/brian2/parsing/sympytools.py000066400000000000000000000164221445201106100203320ustar00rootroot00000000000000""" Utility functions for parsing expressions and statements. """ import re from collections import Counter import sympy from sympy.printing.precedence import precedence from sympy.printing.str import StrPrinter from brian2.core.functions import DEFAULT_CONSTANTS, DEFAULT_FUNCTIONS, Function from brian2.parsing.rendering import SympyNodeRenderer from brian2.utils.caching import cached def check_expression_for_multiple_stateful_functions(expr, variables): identifiers = re.findall(r"\w+", expr) # Don't bother counting if we don't have any duplicates in the first place if len(identifiers) == len(set(identifiers)): return identifier_count = Counter(identifiers) for identifier, count in identifier_count.items(): var = variables.get(identifier, None) if count > 1 and isinstance(var, Function) and not var.stateless: raise NotImplementedError( f"The expression '{expr}' contains " f"more than one call of {identifier}. This " "is currently not supported since " f"{identifier} is a stateful function and " "its multiple calls might be " "treated incorrectly (e.g." "'rand() - rand()' could be " " simplified to " "'0.0')." ) def str_to_sympy(expr, variables=None): """ Parses a string into a sympy expression. There are two reasons for not using `sympify` directly: 1) sympify does a ``from sympy import *``, adding all functions to its namespace. This leads to issues when trying to use sympy function names as variable names. For example, both ``beta`` and ``factor`` -- quite reasonable names for variables -- are sympy functions, using them as variables would lead to a parsing error. 2) We want to use a common syntax across expressions and statements, e.g. we want to allow to use `and` (instead of `&`) and function names like `ceil` (instead of `ceiling`). Parameters ---------- expr : str The string expression to parse. variables : dict, optional Dictionary mapping variable/function names in the expr to their respective `Variable`/`Function` objects. Returns ------- s_expr A sympy expression Raises ------ SyntaxError In case of any problems during parsing. """ if variables is None: variables = {} check_expression_for_multiple_stateful_functions(expr, variables) # We do the actual transformation in a separate function that is cached # If we cached `str_to_sympy` itself, it would also use the contents of the # variables dictionary as the cache key, while it is only used for the check # above and does not affect the translation to sympy return _str_to_sympy(expr) @cached def _str_to_sympy(expr): try: s_expr = SympyNodeRenderer().render_expr(expr) except (TypeError, ValueError, NameError) as ex: raise SyntaxError(f"Error during evaluation of sympy expression '{expr}': {ex}") return s_expr class CustomSympyPrinter(StrPrinter): """ Printer that overrides the printing of some basic sympy objects. E.g. print "a and b" instead of "And(a, b)". """ def _print_And(self, expr): return " and ".join([f"({self.doprint(arg)})" for arg in expr.args]) def _print_Or(self, expr): return " or ".join([f"({self.doprint(arg)})" for arg in expr.args]) def _print_Not(self, expr): if len(expr.args) != 1: raise AssertionError(f'"Not" with {len(expr.args)} arguments?') return f"not ({self.doprint(expr.args[0])})" def _print_Relational(self, expr): return ( f"{self.parenthesize(expr.lhs, precedence(expr))} " f"{self._relationals.get(expr.rel_op) or expr.rel_op} " f"{self.parenthesize(expr.rhs, precedence(expr))}" ) def _print_Function(self, expr): # Special workaround for the int function if expr.func.__name__ == "int_": return f"int({self.stringify(expr.args, ', ')})" elif expr.func.__name__ == "Mod": return f"(({self.doprint(expr.args[0])})%({self.doprint(expr.args[1])}))" else: return f"{expr.func.__name__}({self.stringify(expr.args, ', ')})" PRINTER = CustomSympyPrinter() @cached def sympy_to_str(sympy_expr): """ sympy_to_str(sympy_expr) Converts a sympy expression into a string. This could be as easy as ``str(sympy_exp)`` but it is possible that the sympy expression contains functions like ``Abs`` (for example, if an expression such as ``sqrt(x**2)`` appeared somewhere). We do want to re-translate ``Abs`` into ``abs`` in this case. Parameters ---------- sympy_expr : sympy.core.expr.Expr The expression that should be converted to a string. Returns str_expr : str A string representing the sympy expression. """ # replace the standard functions by our names if necessary replacements = { f.sympy_func: sympy.Function(name) for name, f in DEFAULT_FUNCTIONS.items() if f.sympy_func is not None and isinstance(f.sympy_func, sympy.FunctionClass) and str(f.sympy_func) != name } # replace constants with our names as well replacements.update( { c.sympy_obj: sympy.Symbol(name) for name, c in DEFAULT_CONSTANTS.items() if str(c.sympy_obj) != name } ) # Replace the placeholder argument by an empty symbol replacements[sympy.Symbol("_placeholder_arg")] = sympy.Symbol("") atoms = sympy_expr.atoms() | {f.func for f in sympy_expr.atoms(sympy.Function)} for old, new in replacements.items(): if old in atoms: sympy_expr = sympy_expr.subs(old, new) expr = PRINTER.doprint(sympy_expr) return expr def expression_complexity(expr, complexity=None): """ Returns the complexity of an expression (either string or sympy) The complexity is defined as 1 for each arithmetic operation except divide which is 2, and all other operations are 20. This can be overridden using the complexity argument. Note: calling this on a statement rather than an expression is likely to lead to errors. Parameters ---------- expr: `sympy.Expr` or str The expression. complexity: None or dict (optional) A dictionary mapping expression names to their complexity, to overwrite default behaviour. Returns ------- complexity: int The complexity of the expression. """ if isinstance(expr, str): # we do this because sympy.count_ops doesn't handle inequalities (TODO: handle sympy as well str) for op in ["<=", ">=", "==", "<", ">"]: expr = expr.replace(op, "+") # work around bug with rand() and randn() (TODO: improve this) expr = expr.replace("rand()", "rand(0)") expr = expr.replace("randn()", "randn(0)") subs = {"ADD": 1, "DIV": 2, "MUL": 1, "SUB": 1} if complexity is not None: subs.update(complexity) ops = sympy.count_ops(expr, visual=True) for atom in ops.atoms(): if hasattr(atom, "name"): subs[atom.name] = 20 # unknown operations assumed to have a large cost return ops.evalf(subs=subs) brian2-2.5.4/brian2/random/000077500000000000000000000000001445201106100153265ustar00rootroot00000000000000brian2-2.5.4/brian2/random/__init__.py000066400000000000000000000000001445201106100174250ustar00rootroot00000000000000brian2-2.5.4/brian2/random/randomkit/000077500000000000000000000000001445201106100173165ustar00rootroot00000000000000brian2-2.5.4/brian2/random/randomkit/randomkit.c000066400000000000000000000246351445201106100214640ustar00rootroot00000000000000/* Random kit 1.3 */ /* * Copyright (c) 2003-2005, Jean-Sebastien Roy (js@jeannot.org) * * The rk_random and rk_seed functions algorithms and the original design of * the Mersenne Twister RNG: * * Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura, * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * 3. The names of its contributors may not be used to endorse or promote * products derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * Original algorithm for the implementation of rk_interval function from * Richard J. Wagner's implementation of the Mersenne Twister RNG, optimised by * Magnus Jonsson. * * Constants used in the rk_double implementation by Isaku Wada. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* static char const rcsid[] = "@(#) $Jeannot: randomkit.c,v 1.28 2005/07/21 22:14:09 js Exp $"; */ #include #include #include #include #include #include #ifdef _WIN32 /* * Windows * XXX: we have to use this ugly defined(__GNUC__) because it is not easy to * detect the compiler used in distutils itself */ #if (defined(__GNUC__) && defined(NPY_NEEDS_MINGW_TIME_WORKAROUND)) /* * FIXME: ideally, we should set this to the real version of MSVCRT. We need * something higher than 0x601 to enable _ftime64 and co */ #define __MSVCRT_VERSION__ 0x0700 #include #include /* * mingw msvcr lib import wrongly export _ftime, which does not exist in the * actual msvc runtime for version >= 8; we make it an alias to _ftime64, which * is available in those versions of the runtime */ #define _FTIME(x) _ftime64((x)) #else #include #include #define _FTIME(x) _ftime((x)) #endif #ifndef RK_NO_WINCRYPT /* Windows crypto */ #ifndef _WIN32_WINNT #define _WIN32_WINNT 0x0400 #endif #include #include #endif #else /* Unix */ #include #include #include #endif #include "randomkit.h" #ifndef RK_DEV_URANDOM #define RK_DEV_URANDOM "/dev/urandom" #endif #ifndef RK_DEV_RANDOM #define RK_DEV_RANDOM "/dev/random" #endif char *rk_strerror[RK_ERR_MAX] = { "no error", "random device unvavailable" }; /* static functions */ static unsigned long rk_hash(unsigned long key); void rk_seed(unsigned long seed, rk_state *state) { int pos; seed &= 0xffffffffUL; /* Knuth's PRNG as used in the Mersenne Twister reference implementation */ for (pos = 0; pos < RK_STATE_LEN; pos++) { state->key[pos] = seed; seed = (1812433253UL * (seed ^ (seed >> 30)) + pos + 1) & 0xffffffffUL; } state->pos = RK_STATE_LEN; state->gauss = 0; state->has_gauss = 0; state->has_binomial = 0; } /* Thomas Wang 32 bits integer hash function */ unsigned long rk_hash(unsigned long key) { key += ~(key << 15); key ^= (key >> 10); key += (key << 3); key ^= (key >> 6); key += ~(key << 11); key ^= (key >> 16); return key; } rk_error rk_randomseed(rk_state *state) { #ifndef _WIN32 struct timeval tv; #else struct _timeb tv; #endif int i; if (rk_devfill(state->key, sizeof(state->key), 0) == RK_NOERR) { /* ensures non-zero key */ state->key[0] |= 0x80000000UL; state->pos = RK_STATE_LEN; state->gauss = 0; state->has_gauss = 0; state->has_binomial = 0; for (i = 0; i < 624; i++) { state->key[i] &= 0xffffffffUL; } return RK_NOERR; } #ifndef _WIN32 gettimeofday(&tv, NULL); rk_seed(rk_hash(getpid()) ^ rk_hash(tv.tv_sec) ^ rk_hash(tv.tv_usec) ^ rk_hash(clock()), state); #else _FTIME(&tv); rk_seed(rk_hash(tv.time) ^ rk_hash(tv.millitm) ^ rk_hash(clock()), state); #endif return RK_ENODEV; } /* Magic Mersenne Twister constants */ #define N 624 #define M 397 #define MATRIX_A 0x9908b0dfUL #define UPPER_MASK 0x80000000UL #define LOWER_MASK 0x7fffffffUL /* Slightly optimised reference implementation of the Mersenne Twister */ unsigned long rk_random(rk_state *state) { unsigned long y; if (state->pos == RK_STATE_LEN) { int i; for (i = 0; i < N - M; i++) { y = (state->key[i] & UPPER_MASK) | (state->key[i+1] & LOWER_MASK); state->key[i] = state->key[i+M] ^ (y>>1) ^ (-(y & 1) & MATRIX_A); } for (; i < N - 1; i++) { y = (state->key[i] & UPPER_MASK) | (state->key[i+1] & LOWER_MASK); state->key[i] = state->key[i+(M-N)] ^ (y>>1) ^ (-(y & 1) & MATRIX_A); } y = (state->key[N - 1] & UPPER_MASK) | (state->key[0] & LOWER_MASK); state->key[N - 1] = state->key[M - 1] ^ (y >> 1) ^ (-(y & 1) & MATRIX_A); state->pos = 0; } y = state->key[state->pos++]; /* Tempering */ y ^= (y >> 11); y ^= (y << 7) & 0x9d2c5680UL; y ^= (y << 15) & 0xefc60000UL; y ^= (y >> 18); return y; } long rk_long(rk_state *state) { return rk_ulong(state) >> 1; } unsigned long rk_ulong(rk_state *state) { #if ULONG_MAX <= 0xffffffffUL return rk_random(state); #else return (rk_random(state) << 32) | (rk_random(state)); #endif } unsigned long rk_interval(unsigned long max, rk_state *state) { unsigned long mask = max, value; if (max == 0) { return 0; } /* Smallest bit mask >= max */ mask |= mask >> 1; mask |= mask >> 2; mask |= mask >> 4; mask |= mask >> 8; mask |= mask >> 16; #if ULONG_MAX > 0xffffffffUL mask |= mask >> 32; #endif /* Search a random value in [0..mask] <= max */ #if ULONG_MAX > 0xffffffffUL if (max <= 0xffffffffUL) { while ((value = (rk_random(state) & mask)) > max); } else { while ((value = (rk_ulong(state) & mask)) > max); } #else while ((value = (rk_ulong(state) & mask)) > max); #endif return value; } double rk_double(rk_state *state) { /* shifts : 67108864 = 0x4000000, 9007199254740992 = 0x20000000000000 */ long a = rk_random(state) >> 5, b = rk_random(state) >> 6; return (a * 67108864.0 + b) / 9007199254740992.0; } void rk_fill(void *buffer, size_t size, rk_state *state) { unsigned long r; unsigned char *buf = (unsigned char *)buffer; for (; size >= 4; size -= 4) { r = rk_random(state); *(buf++) = r & 0xFF; *(buf++) = (r >> 8) & 0xFF; *(buf++) = (r >> 16) & 0xFF; *(buf++) = (r >> 24) & 0xFF; } if (!size) { return; } r = rk_random(state); for (; size; r >>= 8, size --) { *(buf++) = (unsigned char)(r & 0xFF); } } rk_error rk_devfill(void *buffer, size_t size, int strong) { #ifndef _WIN32 FILE *rfile; int done; if (strong) { rfile = fopen(RK_DEV_RANDOM, "rb"); } else { rfile = fopen(RK_DEV_URANDOM, "rb"); } if (rfile == NULL) { return RK_ENODEV; } done = fread(buffer, size, 1, rfile); fclose(rfile); if (done) { return RK_NOERR; } #else #ifndef RK_NO_WINCRYPT HCRYPTPROV hCryptProv; BOOL done; if (!CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT) || !hCryptProv) { return RK_ENODEV; } done = CryptGenRandom(hCryptProv, size, (unsigned char *)buffer); CryptReleaseContext(hCryptProv, 0); if (done) { return RK_NOERR; } #endif #endif return RK_ENODEV; } rk_error rk_altfill(void *buffer, size_t size, int strong, rk_state *state) { rk_error err; err = rk_devfill(buffer, size, strong); if (err) { rk_fill(buffer, size, state); } return err; } double rk_gauss(rk_state *state) { if (state->has_gauss) { const double tmp = state->gauss; state->gauss = 0; state->has_gauss = 0; return tmp; } else { double f, x1, x2, r2; do { x1 = 2.0*rk_double(state) - 1.0; x2 = 2.0*rk_double(state) - 1.0; r2 = x1*x1 + x2*x2; } while (r2 >= 1.0 || r2 == 0.0); /* Box-Muller transform */ f = sqrt(-2.0*log(r2)/r2); /* Keep for next call */ state->gauss = f*x1; state->has_gauss = 1; return f*x2; } } brian2-2.5.4/brian2/random/randomkit/randomkit.h000066400000000000000000000124041445201106100214600ustar00rootroot00000000000000/* Random kit 1.3 */ /* * Copyright (c) 2003-2005, Jean-Sebastien Roy (js@jeannot.org) * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* @(#) $Jeannot: randomkit.h,v 1.24 2005/07/21 22:14:09 js Exp $ */ /* * Typical use: * * { * rk_state state; * unsigned long seed = 1, random_value; * * rk_seed(seed, &state); // Initialize the RNG * ... * random_value = rk_random(&state); // Generate random values in [0..RK_MAX] * } * * Instead of rk_seed, you can use rk_randomseed which will get a random seed * from /dev/urandom (or the clock, if /dev/urandom is unavailable): * * { * rk_state state; * unsigned long random_value; * * rk_randomseed(&state); // Initialize the RNG with a random seed * ... * random_value = rk_random(&state); // Generate random values in [0..RK_MAX] * } */ /* * Useful macro: * RK_DEV_RANDOM: the device used for random seeding. * defaults to "/dev/urandom" */ #include #ifndef _RANDOMKIT_ #define _RANDOMKIT_ #define RK_STATE_LEN 624 typedef struct rk_state_ { unsigned long key[RK_STATE_LEN]; int pos; int has_gauss; /* !=0: gauss contains a gaussian deviate */ double gauss; /* The rk_state structure has been extended to store the following * information for the binomial generator. If the input values of n or p * are different than nsave and psave, then the other parameters will be * recomputed. RTK 2005-09-02 */ int has_binomial; /* !=0: following parameters initialized for binomial */ double psave; long nsave; double r; double q; double fm; long m; double p1; double xm; double xl; double xr; double c; double laml; double lamr; double p2; double p3; double p4; } rk_state; typedef enum { RK_NOERR = 0, /* no error */ RK_ENODEV = 1, /* no RK_DEV_RANDOM device */ RK_ERR_MAX = 2 } rk_error; /* error strings */ extern char *rk_strerror[RK_ERR_MAX]; /* Maximum generated random value */ #define RK_MAX 0xFFFFFFFFUL #ifdef __cplusplus extern "C" { #endif /* * Initialize the RNG state using the given seed. */ extern void rk_seed(unsigned long seed, rk_state *state); /* * Initialize the RNG state using a random seed. * Uses /dev/random or, when unavailable, the clock (see randomkit.c). * Returns RK_NOERR when no errors occurs. * Returns RK_ENODEV when the use of RK_DEV_RANDOM failed (for example because * there is no such device). In this case, the RNG was initialized using the * clock. */ extern rk_error rk_randomseed(rk_state *state); /* * Returns a random unsigned long between 0 and RK_MAX inclusive */ extern unsigned long rk_random(rk_state *state); /* * Returns a random long between 0 and LONG_MAX inclusive */ extern long rk_long(rk_state *state); /* * Returns a random unsigned long between 0 and ULONG_MAX inclusive */ extern unsigned long rk_ulong(rk_state *state); /* * Returns a random unsigned long between 0 and max inclusive. */ extern unsigned long rk_interval(unsigned long max, rk_state *state); /* * Returns a random double between 0.0 and 1.0, 1.0 excluded. */ extern double rk_double(rk_state *state); /* * fill the buffer with size random bytes */ extern void rk_fill(void *buffer, size_t size, rk_state *state); /* * fill the buffer with randombytes from the random device * Returns RK_ENODEV if the device is unavailable, or RK_NOERR if it is * On Unix, if strong is defined, RK_DEV_RANDOM is used. If not, RK_DEV_URANDOM * is used instead. This parameter has no effect on Windows. * Warning: on most unixes RK_DEV_RANDOM will wait for enough entropy to answer * which can take a very long time on quiet systems. */ extern rk_error rk_devfill(void *buffer, size_t size, int strong); /* * fill the buffer using rk_devfill if the random device is available and using * rk_fill if is is not * parameters have the same meaning as rk_fill and rk_devfill * Returns RK_ENODEV if the device is unavailable, or RK_NOERR if it is */ extern rk_error rk_altfill(void *buffer, size_t size, int strong, rk_state *state); /* * return a random gaussian deviate with variance unity and zero mean. */ extern double rk_gauss(rk_state *state); #ifdef __cplusplus } #endif #endif /* _RANDOMKIT_ */ brian2-2.5.4/brian2/spatialneuron/000077500000000000000000000000001445201106100167325ustar00rootroot00000000000000brian2-2.5.4/brian2/spatialneuron/__init__.py000066400000000000000000000002011445201106100210340ustar00rootroot00000000000000from .morphology import * from .spatialneuron import * __all__ = ["Morphology", "Soma", "Cylinder", "Section", "SpatialNeuron"] brian2-2.5.4/brian2/spatialneuron/morphology.py000066400000000000000000002447721445201106100215230ustar00rootroot00000000000000""" Neuronal morphology module. This module defines classes to load and build neuronal morphologies. """ import abc import numbers import os from abc import abstractmethod from collections import OrderedDict, defaultdict, namedtuple from brian2 import numpy as np from brian2.units.allunits import meter from brian2.units.fundamentalunits import Quantity, check_units, have_same_dimensions from brian2.units.stdunits import um from brian2.utils.logger import get_logger logger = get_logger(__name__) __all__ = ["Morphology", "Section", "Cylinder", "Soma"] Node = namedtuple("Node", field_names="index,comp_name,x,y,z,diameter,parent,children") def _to_meters(value): """ Helper function to convert a floating point value (or array) to a `Quantity` in units of "meter", but also allow for ``None`` and return it as it is. """ if value is None: return None else: return Quantity(value, dim=meter.dim) def _from_morphology(variable, i, j): """ Helper function to return coordinates from a main morphology (used by `SubMorphology`), dealing with ``None``. """ if variable is None: return None return variable[i:j] class MorphologyIndexWrapper: """ A simpler version of `~brian2.groups.group.IndexWrapper`, not allowing for string indexing (`Morphology` is not a `Group`). It allows to use ``morphology.indices[...]`` instead of ``morphology[...]._indices()``. """ def __init__(self, morphology): self.morphology = morphology def __getitem__(self, item): if isinstance(item, str): raise NotImplementedError("Morphologies do not support string indexing") assert isinstance(self.morphology, (SubMorphology, Morphology)) return self.morphology._indices(item) def _calc_start_idx(section): """ Calculate the absolute start index that will be used by a flattened representation. """ # calculate the absolute start index of this section # 1. find the root of the tree root = section while root._parent is not None: root = root._parent # 2. go down from the root and advance the indices until we find # the current section start_idx, found = _find_start_index(root, section) assert found return start_idx def _find_start_index(current, target_section, index=0): if current == target_section: return index, True index += current.n for child in current.children: if child == target_section: return index, True else: index, found = _find_start_index(child, target_section, index) if found: return index, True return index, False class Topology: """ A representation of the topology of a `Morphology`. Has a useful string representation, inspired by NEURON's ``topology`` function. """ def __init__(self, morphology): self.morphology = morphology def __str__(self): # TODO: Make sure that the shown compartments do not get out of hand divisor = 1 return Topology._str_topology(self.morphology, compartments_divisor=divisor) @staticmethod def _str_topology( morphology, indent=0, named_path="", compartments_divisor=1, parent=None ): """ A simple string-based representation of a morphology. Inspired by NEURON's ``topology`` function. """ description = " " * indent length = max([1, morphology.n // compartments_divisor]) if parent is not None: description += "`" if isinstance(morphology, Soma): description += "( )" else: description += "-" * length description += "|" if len(named_path) == 0: description += " [root] \n" else: description += f" {named_path}\n" for child in morphology.children: name = morphology.children.name(child) description += Topology._str_topology( child, indent=indent + 2 + length, named_path=f"{named_path}.{name}", compartments_divisor=compartments_divisor, parent=morphology, ) return description __repr__ = __str__ def _rotate(vec, axis, angle): """ Rotate a vector around an arbitrary axis. Parameters ---------- vec : `ndarray` The vector to rotate. axis : `ndarray` The axis around which the vector should be rotated. angle : float The rotation angle (in radians). Returns ------- rotated : `ndarray` The rotated vector. """ return ( vec * np.cos(angle) - np.cross(axis, vec) * np.sin(angle) + axis * np.dot(axis, vec) * (1 - np.cos(angle)) ) def _perturb(vec, sigma): if sigma == 0: return vec # Get an arbitrary orthogonal vector if vec[1] != 0 or vec[0] != 0: orthogonal = np.hstack([vec[1], vec[0], 0]) else: # special case for the [0, 0, 1] vector orthogonal = np.array([1, 0, 0]) # Rotate the orthogonal vector orthogonal = _rotate(orthogonal, vec, np.random.rand() * np.pi * 2) # Use an exponentially distributed angle for the perturbation perturbation = np.random.exponential(sigma, 1) return _rotate(vec, orthogonal, perturbation) def _add_coordinates( orig_morphology, root=None, parent=None, name=None, section_randomness=0.0, compartment_randomness=0.0, n_th_child=0, total_children=0, overwrite_existing=False, ): # Note that in the following, all values are without physical units # The new direction is based on the direction of the parent section if parent is None: section_dir = np.array([0, 0, 0]) else: section_dir = np.hstack( [ np.asarray(parent.end_x[-1] - parent.start_x[0]), np.asarray(parent.end_y[-1] - parent.start_y[0]), np.asarray(parent.end_z[-1] - parent.start_z[0]), ] ) parent_dir_norm = np.sqrt(np.sum(section_dir**2)) if parent_dir_norm != 0: section_dir /= parent_dir_norm else: section_dir = np.array([0, 0, 0]) if not overwrite_existing and orig_morphology.x is not None: section = orig_morphology.copy_section() elif isinstance(orig_morphology, Soma): # No perturbation for the soma section = Soma( diameter=orig_morphology.diameter, x=section_dir[0] * meter, y=section_dir[1] * meter, z=section_dir[2] * meter, ) else: if np.sum(section_dir**2) == 0: # We don't have any direction to base this section on (most common # case is that the root section is a soma) # We stay in the x-y plane and distribute all children in a 360 degree # circle around (0, 0, 0) section_dir = np.array([1, 0, 0]) rotation_axis = np.array([0, 0, 1]) angle_increment = 2 * np.pi / total_children rotation_angle = np.pi / 2 + angle_increment * n_th_child section_dir = _rotate(section_dir, rotation_axis, rotation_angle) else: if ( section_randomness == 0 and section_dir[2] == 0 ): # If we are in the x-y plane, stay there rotation_axis = np.array([0, 0, 1]) else: rotation_axis = np.array([-section_dir[1], section_dir[2], 0]) if section_randomness == 0: angle_increment = np.pi / (total_children + 1) rotation_angle = -np.pi / 2 + angle_increment * (n_th_child + 1) section_dir = _rotate(section_dir, rotation_axis, rotation_angle) if section_randomness > 0: # Rotate randomly section_dir = _perturb(section_dir, section_randomness) section_dir_norm = np.sqrt(np.sum(section_dir**2)) section_dir /= section_dir_norm # For a soma, we let child sections begin at the surface of the sphere if isinstance(parent, Soma): origin = parent.diameter / 2 * section_dir else: origin = (0, 0, 0) * um coordinates = np.zeros((orig_morphology.n + 1, 3)) * meter start_coords = origin coordinates[0, :] = origin # Perturb individual compartments as well for idx, length in enumerate(orig_morphology.length): compartment_dir = _perturb(section_dir, compartment_randomness) compartment_dir_norm = np.sqrt(np.sum(compartment_dir**2)) compartment_dir /= compartment_dir_norm current_coords = start_coords + length * compartment_dir coordinates[idx + 1, :] = current_coords start_coords = current_coords if isinstance(orig_morphology, Cylinder) and compartment_randomness == 0: section = Cylinder( n=orig_morphology.n, diameter=orig_morphology.diameter[0], x=coordinates[[0, -1], 0], y=coordinates[[0, -1], 1], z=coordinates[[0, -1], 2], type=orig_morphology.type, ) elif isinstance(orig_morphology, Section): section = Section( n=orig_morphology.n, diameter=np.hstack( [orig_morphology.start_diameter[0], orig_morphology.end_diameter] ) * meter, x=coordinates[:, 0], y=coordinates[:, 1], z=coordinates[:, 2], type=orig_morphology.type, ) else: raise NotImplementedError( f"Do not know how to deal with section of type {type(orig_morphology)}." ) if parent is None: root = section else: parent.children.add(name, section) for idx, child in enumerate(orig_morphology.children): _add_coordinates( child, root=root, parent=section, name=orig_morphology.children.name(child), n_th_child=idx, total_children=len(orig_morphology.children), section_randomness=section_randomness, compartment_randomness=compartment_randomness, overwrite_existing=overwrite_existing, ) return section class Children: """ Helper class to represent the children (sub trees) of a section. Can be used like a dictionary (mapping names to `Morphology` objects), but iterates over the values (sub trees) instead of over the keys (names). """ def __init__(self, owner): self._owner = owner self._counter = 0 self._children = [] self._named_children = {} self._given_name = defaultdict(lambda: None) def __iter__(self): return iter(self._children) def __len__(self): return len(self._children) def __contains__(self, item): return item in self._named_children def name(self, child): """ Return the given name (i.e. not the automatic name such as ``1``) for a child subtree. Parameters ---------- child : `Morphology` Returns ------- name : str The given name for the ``child``. """ return self._given_name[child] def __getitem__(self, item): if isinstance(item, str): return self._named_children[item] else: raise TypeError("Index has to be an integer or a string.") def add(self, name, subtree, automatic_name=False): """ Add a new child to the morphology. Parameters ---------- name : str The name (e.g. ``"axon"``, ``"soma"``) to use for this sub tree. subtree : `Morphology` The subtree to link as a child. automatic_name : bool, optional Whether to chose a new name automatically, if a subtree of the same name already exists (uses e.g. ``"dend2"`` instead ``"dend"``). Defaults to ``False`` and will raise an error instead. """ if name in self._named_children and self._named_children[name] is not subtree: if automatic_name: basename = name counter = 1 while name in self._named_children: counter += 1 name = basename + str(counter) else: raise AttributeError(f"The name {name} is already used for a subtree.") if subtree not in self._children: self._counter += 1 self._children.append(subtree) self._named_children[str(self._counter)] = subtree self._given_name[subtree] = name if name is not None: self._named_children[name] = subtree subtree._parent = self._owner def remove(self, name): """ Remove a subtree from this morphology. Parameters ---------- name : str The name of the sub tree to remove. """ if name not in self: raise AttributeError(f"The subtree {name} does not exist") subtree = self._named_children[name] del self._named_children[name] self._children.remove(subtree) subtree._parent = None def __repr__(self): n = len(self._children) s = f"<{int(n)} children" if n > 0: name_dict = {self.name(sec): sec for sec in self._children} s += f": {name_dict!r}" return f"{s}>" class Morphology(metaclass=abc.ABCMeta): """ Neuronal morphology (tree structure). The data structure is a tree where each node is an un-branched section consisting of a number of connected compartments, each one defined by its geometrical properties (length, area, diameter, position). Notes ----- You cannot create objects of this class, create a `Soma`, a `Section`, or a `Cylinder` instead. """ @check_units(n=1) def __init__(self, n, type=None): if isinstance(n, str): raise TypeError( "Need the number of compartments, not a string. " "If you want to load a morphology from a file, " "use 'Morphology.from_file' instead." ) self._n = int(n) if self._n != n: raise TypeError("The number of compartments n has to be an integer value.") if n <= 0: raise ValueError("The number of compartments n has to be at least 1.") self.type = type self._children = Children(self) self._parent = None self.indices = MorphologyIndexWrapper(self) def __getitem__(self, item): """ Return the subtree with the given name/index. Ex.: ```neuron['axon']``` or ```neuron['11213']``` ```neuron[10*um:20*um]``` returns the subbranch from 10 um to 20 um. ```neuron[10*um]``` returns one compartment. ```neuron[5]``` returns compartment number 5. """ if isinstance(item, slice): # neuron[10*um:20*um] or neuron[1:3] using_lengths = all( [ arg is None or have_same_dimensions(arg, meter) for arg in [item.start, item.stop] ] ) using_ints = all( [ arg is None or int(arg) == float(arg) for arg in [item.start, item.stop] ] ) if not (using_lengths or using_ints): raise TypeError("Index slice has to use lengths or integers") if using_lengths: if item.step is not None: raise TypeError( "Cannot provide a step argument when slicing with lengths" ) pos = np.cumsum(np.asarray(self.length)) # coordinate on the section # We use a special handling for values very close to the points # between the compartments to avoid non-intuitive rounding # effects: a point closer than 1e-12*length of section will be # considered to be within the following section (for a start # index), respectively within the previous section (for an end # index) if item.start is None: i = 0 else: diff = np.abs(float(item.start) - pos) if min(diff) < 1e-12 * pos[-1]: i = np.argmin(diff) + 1 else: i = np.searchsorted(pos, item.start) if item.stop is None: j = len(pos) else: diff = np.abs(float(item.stop) - pos) if min(diff) < 1e-12 * pos[-1]: j = np.argmin(diff) + 1 else: j = np.searchsorted(pos, item.stop) + 1 else: # integers i, j, step = item.indices(self.n) if step != 1: raise TypeError("Can only slice a contiguous segment") elif isinstance(item, Quantity) and have_same_dimensions(item, meter): pos = np.hstack( [0, np.cumsum(np.asarray(self.length))] ) # coordinate on the section if float(item) < 0 or float(item) > (1 + 1e-12) * pos[-1]: raise IndexError( f"Invalid index {item}, has to be in the interval " f"[{0 * meter!s}, {pos[-1] * meter!s}]." ) diff = np.abs(float(item) - pos) if min(diff) < 1e-12 * pos[-1]: i = np.argmin(diff) else: i = np.searchsorted(pos, item) - 1 j = i + 1 elif isinstance(item, numbers.Integral): # int: returns one compartment if item < 0: # allows e.g. to use -1 to get the last compartment item += self.n if item >= self.n: raise IndexError(f"Invalid index {item} for {self.n} compartments") i = item j = i + 1 elif isinstance(item, str): item = str(item) # convert int to string if (len(item) > 1) and all( [c in "LR123456789" for c in item] ): # binary string of the form LLLRLR or 1213 (or mixed) return self._children[item[0]][item[1:]] elif item in self._children: return self._children[item] else: raise AttributeError(f"The subtree {item} does not exist") else: raise TypeError(f"Index of type {type(item)} not understood") return SubMorphology(self, i, j) def __setitem__(self, item, child): """ Inserts the subtree and name it ``item``. Ex.: ``neuron['axon']`` or ``neuron['11213']`` """ item = str(item) # convert int to string if (len(item) > 1) and all([c in "LR123456789" for c in item]): # binary string of the form LLLRLR or 1213 (or mixed) self.children[item[0]][item[1:]] = child else: self.children.add(item, child) def __delitem__(self, item): """ Remove the subtree ``item``. """ item = str(item) # convert int to string if (len(item) > 1) and all([c in "LR123456789" for c in item]): # binary string of the form LLLRLR or 1213 (or mixed) del self._children[item[0]][item[1:]] else: self._children.remove(item) def __getattr__(self, item): """ Return the subtree named ``item``. Ex.: ``axon = neuron.axon`` """ if item.startswith("_"): return super(object, self).__getattr__(item) else: return self[item] def __setattr__(self, item, child): """ Attach a subtree and name it ``item``. Ex.: ``neuron.axon = Soma(diameter=10*um)`` """ if isinstance(child, Morphology) and not item.startswith("_"): self[item] = child else: # If it is not a subtree, then it's a normal class attribute object.__setattr__(self, item, child) def __delattr__(self, item): """ Remove the subtree ``item``. """ del self[item] def _indices(self, item=None, index_var="_idx"): """ Return compartment indices for the main section, relative to the original morphology. """ if index_var != "_idx": raise AssertionError(f"Unexpected index {index_var}") if not (item is None or item == slice(None)): if isinstance(item, slice): # So that this always returns an array of values, even if it is # just a single value return self[item]._indices(slice(None)) else: return self[item]._indices(None) else: start_idx = _calc_start_idx(self) if self.n == 1 and item is None: return start_idx else: return np.arange(start_idx, start_idx + self.n) def topology(self): """ Return a representation of the topology Returns ------- topology : `Topology` An object representing the topology (can be converted to a string by using ``str(...)`` or simply by printing it with `print`.) """ return Topology(self) def generate_coordinates( self, section_randomness=0.0, compartment_randomness=0.0, overwrite_existing=False, ): r""" Create a new `Morphology`, with coordinates filled in place where the previous morphology did not have any. This is mostly useful for plotting a morphology, it does not affect its electrical properties. Parameters ---------- section_randomness : float, optional The randomness when deciding the direction vector for each new section. The given number is the :math:`\beta` parameter of an exponential distribution (in degrees) which will be used to determine the deviation from the direction of the parent section. If the given value equals 0 (the default), then a deterministic algorithm will be used instead. compartment_randomness : float, optional The randomness when deciding the direction vector for each compartment within a section. The given number is the :math:`\beta` parameter of an exponential distribution (in degrees) which will be used to determine the deviation from the main direction of the current section. If the given value equals 0 (the default), then all compartments will be along a straight line. overwrite_existing : bool, optional Whether to overwrite existing coordinates in the morphology. This is by default set to ``False``, meaning that only sections that do not currently have any coordinates set will get new coordinates. This allows to conveniently generate a morphology that can be plotted for a morphology that is based on points but also has artificially added sections (the most common case: an axon added to a reconstructed morphology). If set to ``True``, all sections will get new coordinates. This can be useful to either get a schematic representation of the morphology (with ``section_randomness`` and ``compartment_randomness`` both 0) or to simply generate a new random variation of a morphology (which will still be electrically equivalent, of course). Returns ------- morpho_with_coordinates : `Morphology` The same morphology, but with coordinates """ # Convert to radians section_randomness *= np.pi / 180 compartment_randomness *= np.pi / 180 return _add_coordinates( self, section_randomness=section_randomness, compartment_randomness=compartment_randomness, overwrite_existing=overwrite_existing, ) @abstractmethod def copy_section(self): """ Create a copy of the current section (attributes of this section only, not re-creating the parent/children relation) Returns ------- copy : `Morphology` A copy of this section (without the links to the parent/children) """ raise NotImplementedError() @property def n(self): """ The number of compartments in this section. """ return self._n def __len__(self): """ This is not well-defined, use `Morphology.n` or `Morphology.total_compartments` instead. """ raise TypeError( "The 'length' of a Morphology is ambiguous, use its " "'n' attribute for the number of compartments in this " "section or the 'total_compartments' attribute for the " "total number of compartments in the whole sub-tree." ) @property def total_compartments(self): """ The total number of compartments in this subtree (i.e. the number of compartments in this section plus all the compartments in the sections deeper in the tree). """ return self.n + sum(c.total_compartments for c in self.children) @property def total_sections(self): """ The total number of sections in this subtree. """ return 1 + sum(c.total_sections for c in self.children) @property def parent(self): """ The parent section of this section. """ return self._parent @property def children(self): """ The children (as a `Children` object) of this section. """ return self._children @property @abc.abstractmethod def end_distance(self): """ The distance to the root of the morphology at the end of this section. """ raise NotImplementedError() # Per-compartment attributes @property @abc.abstractmethod def area(self): """ The membrane surface area of each compartment in this section. """ raise NotImplementedError() @property @abc.abstractmethod def volume(self): """ The volume of each compartment in this section. """ raise NotImplementedError() @property @abc.abstractmethod def length(self): """ The length of each compartment in this section. """ raise NotImplementedError() @property @abc.abstractmethod def r_length_1(self): """ The geometry-dependent term to calculate the conductance between the start and the midpoint of each compartment. Dividing this value by the Intracellular resistivity gives the conductance. """ raise NotImplementedError() @property @abc.abstractmethod def r_length_2(self): """ The geometry-dependent term to calculate the conductance between the midpoint and the end of each compartment. Dividing this value by the Intracellular resistivity gives the conductance. """ raise NotImplementedError() # At-midpoint attributes @property @abc.abstractmethod def diameter(self): """ The diameter at the middle of each compartment in this section. """ raise NotImplementedError() @property @abc.abstractmethod def distance(self): """ The total distance between the midpoint of each compartment and the root of the morphology. """ raise NotImplementedError() @property def start_x(self): """ The x coordinate at the beginning of each compartment. Returns ``None`` for morphologies without coordinates. """ return _to_meters(self.start_x_) @property def start_y(self): """ The y coordinate at the beginning of each compartment. Returns ``None`` for morphologies without coordinates. """ return _to_meters(self.start_y_) @property def start_z(self): """ The z coordinate at the beginning of each compartment. Returns ``None`` for morphologies without coordinates. """ return _to_meters(self.start_z_) @property @abc.abstractmethod def start_x_(self): """ The x coordinate (as a unitless floating point number) at the beginning of each compartment. Returns ``None`` for morphologies without coordinates. """ raise NotImplementedError() @property @abc.abstractmethod def start_y_(self): """ The y coordinate (as a unitless floating point number) at the beginning of each compartment. Returns ``None`` for morphologies without coordinates. """ raise NotImplementedError() @property @abc.abstractmethod def start_z_(self): """ The z coordinate (as a unitless floating point number) at the beginning of each compartment. Returns ``None`` for morphologies without coordinates. """ raise NotImplementedError() @property def x(self): """ The x coordinate at the midpoint of each compartment. Returns ``None`` for morphologies without coordinates. """ return _to_meters(self.x_) @property def y(self): """ The y coordinate at the midpoint of each compartment. Returns ``None`` for morphologies without coordinates. """ return _to_meters(self.y_) @property def z(self): """ The y coordinate at the midpoint of each compartment. Returns ``None`` for morphologies without coordinates. """ return _to_meters(self.z_) @property @abc.abstractmethod def x_(self): """ The x coordinate (as a unitless floating point number) at the midpoint of each compartment. Returns ``None`` for morphologies without coordinates. """ raise NotImplementedError() @property @abc.abstractmethod def y_(self): """ The y coordinate (as a unitless floating point number) at the midpoint of each compartment. Returns ``None`` for morphologies without coordinates. """ raise NotImplementedError() @property @abc.abstractmethod def z_(self): """ The z coordinate (as a unitless floating point number) at the midpoint of each compartment. Returns ``None`` for morphologies without coordinates. """ raise NotImplementedError() @property def end_x(self): """ The x coordinate at the end of each compartment. Returns ``None`` for morphologies without coordinates. """ return _to_meters(self.end_x_) @property def end_y(self): """ The y coordinate at the end of each compartment. Returns ``None`` for morphologies without coordinates. """ return _to_meters(self.end_y_) @property def end_z(self): """ The z coordinate at the end of each compartment. Returns ``None`` for morphologies without coordinates. """ return _to_meters(self.end_z_) @property @abc.abstractmethod def end_x_(self): """ The x coordinate (as a unitless floating point number) at the end of each compartment. Returns ``None`` for morphologies without coordinates. """ raise NotImplementedError() @property @abc.abstractmethod def end_y_(self): """ The y coordinate (as a unitless floating point number) at the end of each compartment. Returns ``None`` for morphologies without coordinates. """ raise NotImplementedError() @property @abc.abstractmethod def end_z_(self): """ The z coordinate (as a unitless floating point number) at the end of each compartment. Returns ``None`` for morphologies without coordinates. """ raise NotImplementedError() @property def coordinates(self): r""" Array with all coordinates at the start- and end-points of each compartment in this section. The array has size :math:`(n+1) \times 3`, where :math:`n` is the number of compartments in this section. Each row is one point (start point of first compartment, end point of first compartment, end point of second compartment, ...), with the columns being the x, y, and z coordinates. Returns ``None`` for morphologies without coordinates. """ if self.x_ is None: return None else: return Quantity(self.coordinates_, dim=meter.dim) @property def coordinates_(self): r""" Array with all coordinates (as unitless floating point numbers) at the start- and end-points of each compartment in this section. The array has size :math:`(n+1) \times 3`, where :math:`n` is the number of compartments in this section. Each row is one point (start point of first compartment, end point of first compartment, end point of second compartment, ...), with the columns being the x, y, and z coordinates. Returns ``None`` for morphologies without coordinates. """ if self.x_ is None: return None else: return np.vstack( [ np.hstack([self.start_x_[0], self.end_x_[:]]), np.hstack([self.start_y_[0], self.end_y_[:]]), np.hstack([self.start_z_[0], self.end_z_[:]]), ] ).T @staticmethod def _create_section(compartments, name, parent, sections, spherical_soma): if ( spherical_soma and len(compartments) == 1 and compartments[0].comp_name == "soma" ): soma = compartments[0] section = Soma( diameter=soma.diameter * um, x=soma.x * um, y=soma.y * um, z=soma.z * um ) else: sec_x, sec_y, sec_z, sec_diameter = zip( *[(c.x, c.y, c.z, c.diameter) for c in compartments] ) # Add a point for the end of the parent_idx compartment if parent is not None: n = len(compartments) if parent.comp_name is not None and parent.comp_name.lower() == "soma": # For a Soma, we don't use its diameter start_diameter = sec_diameter[0] else: start_diameter = parent.diameter # Use relative coordinates sec_x = np.array(sec_x) - parent.x sec_y = np.array(sec_y) - parent.y sec_z = np.array(sec_z) - parent.z start_x = start_y = start_z = 0.0 else: n = len(compartments) - 1 start_diameter = sec_diameter[0] sec_diameter = sec_diameter[1:] start_x = sec_x[0] start_y = sec_y[0] start_z = sec_z[0] sec_x = sec_x[1:] sec_y = sec_y[1:] sec_z = sec_z[1:] diameter = np.hstack([start_diameter, sec_diameter]) * um x = np.hstack([start_x, sec_x]) * um y = np.hstack([start_y, sec_y]) * um z = np.hstack([start_z, sec_z]) * um section = Section(n=n, diameter=diameter, x=x, y=y, z=z, type=name) # Add the section as a child to its parent if parent is not None: parent_sec = sections[parent.index] parent_sec.children.add(name, section, automatic_name=True) return section @staticmethod def _compartments_to_sections( compartment, spherical_soma, current_compartments=None, sections=None ): # Merge all unbranched compartments of the same type into a single # section if sections is None: sections = OrderedDict() if current_compartments is None: current_compartments = [] current_compartments.append(compartment) # We have to create a new section, if we are either # 1. at a leaf of the tree or at a branching point, or # 2. if the compartment type changes if ( len(compartment.children) != 1 or compartment.comp_name != compartment.children[0].comp_name ): parent = current_compartments[0].parent section = Morphology._create_section( current_compartments, compartment.comp_name, parent=parent, sections=sections, spherical_soma=spherical_soma, ) sections[current_compartments[-1].index] = section # If we are at a branching point, recurse into all subtrees for child in compartment.children: Morphology._compartments_to_sections( child, spherical_soma=spherical_soma, current_compartments=None, sections=sections, ) else: # A single child of the same type, continue (recursive call) Morphology._compartments_to_sections( compartment.children[0], spherical_soma=spherical_soma, current_compartments=current_compartments, sections=sections, ) return sections @staticmethod def _replace_three_point_soma(compartment, all_compartments): # Replace a three-point/two-cylinder soma by a single spherical soma # if possible (see http://neuromorpho.org/SomaFormat.html for some # details) # We are looking for a node with two children of the soma type (and # other childen of other types), where the two children don't have any # children of their own soma_children = [c for c in compartment.children if c.comp_name == "soma"] if ( compartment.comp_name == "soma" and len(soma_children) == 2 and all(len(c.children) == 0 for c in soma_children) ): # We've found a 3-point soma to replace soma_c = [compartment] + soma_children if not all(abs(c.diameter - soma_c[0].diameter) < 1e-15 for c in soma_c): indices = ", ".join(str(c.index) for c in soma_c) raise ValueError( f"Found a '3-point-soma' (lines: {indices}), but not " "all the diameters are " "identical." ) diameter = soma_c[0].diameter point_0 = np.array([soma_c[0].x, soma_c[0].y, soma_c[0].z]) point_1 = np.array([soma_c[1].x, soma_c[1].y, soma_c[1].z]) point_2 = np.array([soma_c[2].x, soma_c[2].y, soma_c[2].z]) length_1 = np.sqrt(np.sum((point_1 - point_0) ** 2)) length_2 = np.sqrt(np.sum((point_2 - point_0) ** 2)) if ( np.abs(length_1 - diameter / 2) > 0.01 or np.abs(length_2 - diameter / 2) > 0.01 ): raise ValueError( "Cannot replace '3-point-soma' by a single " "point, the second and third points should " "be positioned one radius away from the " f"first point. Distances are {length_1:.3d}um and " f"{length_2:.3f}um, respectively, while the " f"radius is {diameter / 2:.3f}um." ) children = [c for c in compartment.children if c not in soma_c] compartment = Node( index=compartment.index, comp_name="soma", x=point_0[0], y=point_0[1], z=point_0[2], diameter=diameter, parent=compartment.parent, children=children, ) all_compartments[compartment.index] = compartment del all_compartments[soma_children[0].index] del all_compartments[soma_children[1].index] # Recurse further down the tree all_compartments[compartment.index] = compartment for child in compartment.children: Morphology._replace_three_point_soma(child, all_compartments) @staticmethod def from_points(points, spherical_soma=True): """ Create a morphology from a sequence of points (similar to the ``SWC`` format, see `Morphology.from_swc_file`). Each point has to be a 7-tuple: ``(index, name, x, y, z, diameter, parent)`` Note that the values should not use units, but are instead all taken to be in micrometers. Parameters ---------- points : sequence of 7-tuples The points of the morphology. spherical_soma : bool, optional Whether to model a soma as a sphere. Returns ------- morphology : `Morphology` Notes ----- This format closely follows the SWC format (see `Morphology.from_swc_file`) with two differences: the ``type`` should be a string (e.g. ``'soma'``) instead of an integer and the 6-th element should be the diameter and not the radius. """ # First pass through all points to get the dependency structure compartments = OrderedDict() for counter, point in enumerate(points): if len(point) != 7: raise ValueError( "Each point needs to be described by 7 values, got" f" {len(point)} instead." ) index, name, x, y, z, diameter, parent_idx = point if index in compartments: raise ValueError(f"Two compartments with index {int(index)}") if parent_idx == index: raise ValueError( f"Compartment {int(index)} lists itself as the parent compartment." ) if counter == 0 and parent_idx == -1: parent = None # The first compartment does not have a parent elif parent_idx not in compartments: raise ValueError( f"Did not find the compartment {parent_idx} (parent " f"compartment of compartment {index}). Make sure " "that parent compartments are listed before " "their children." ) else: parent = compartments[parent_idx] children = [] node = Node(index, name, x, y, z, diameter, parent, children) compartments[index] = node if parent is not None: parent.children.append(node) if spherical_soma: Morphology._replace_three_point_soma( list(compartments.values())[0], compartments ) sections = Morphology._compartments_to_sections( list(compartments.values())[0], spherical_soma ) # Go through all the sections again and add standard names for all # sections (potentially in addition to the name they already have): # "L" + "R" for one or two children, "1", "2", "3", etc. otherwise children_counter = defaultdict(int) for section in sections.values(): parent = section.parent if parent is not None: children_counter[parent] += 1 children = parent.children nth_child = children_counter[parent] if len(children) <= 2: name = "L" if nth_child == 1 else "R" else: name = f"{int(nth_child)}" children.add(name, section) # There should only be one section without parents root = [sec for sec in sections.values() if sec.parent is None] assert len(root) == 1 return root[0] @staticmethod def from_swc_file(filename, spherical_soma=True): """ Load a morphology from a ``SWC`` file. A large database of morphologies in this format can be found at http://neuromorpho.org The format consists of an optional header of lines starting with ``#`` (ignored), followed by a sequence of points, each described in a line following the format:: index type x y z radius parent ``index`` is an integer label (starting at 1) that identifies the current point and increases by one each line. ``type`` is an integer representing the type of the neural segment. The only type that changes the interpretation by Brian is the type ``1`` which signals a soma. Types ``2`` (axon), ``3`` (dendrite), and ``4`` (apical dendrite) are used to give corresponding names to the respective sections. All other types are ignored. ``x``, ``y``, and ``z`` are the cartesian coordinates at each point and ``r`` is its radius. ``parent`` refers to the index of the parent point or is ``-1`` for the root point. Parameters ---------- filename : str The name of the ``SWC`` file. spherical_soma : bool, optional Whether to model the soma as a sphere. Returns ------- morpho : `Morphology` The morphology stored in the given file. """ swc_types = defaultdict(lambda: None) # The following names will be translated into names, all other will be # ignored swc_types.update({"1": "soma", "2": "axon", "3": "dend", "4": "apic"}) with open(filename) as f: points = [] for line_no, line in enumerate(f): line = line.strip() if line.startswith("#") or len(line) == 0: # Ignore comments or empty lines continue splitted = line.split() if len(splitted) != 7: raise ValueError( "Each line of an SWC file has to contain " "7 space-separated entries, but line " f"{line_no + 1} contains {len(splitted)}." ) index, comp_type, x, y, z, radius, parent = splitted points.append( ( int(index), swc_types[comp_type], float(x), float(y), float(z), 2 * float(radius), int(parent), ) ) return Morphology.from_points(points, spherical_soma=spherical_soma) @staticmethod def from_file(filename, spherical_soma=True): """ Convencience method to load a morphology from a given file. At the moment, only ``SWC`` files are supported, calling this function is therefore equivalent to calling `Morphology.from_swc_file` directly. Parameters ---------- filename : str The name of a file storing a morphology. spherical_soma : bool, optional Whether to model the soma as a sphere. Returns ------- morphology : `Morphology` The morphology stored in the given file. """ _, ext = os.path.splitext(filename) if ext.lower() == ".swc": return Morphology.from_swc_file(filename, spherical_soma=spherical_soma) else: raise NotImplementedError( "Currently, SWC is the only supported file format." ) class SubMorphology: """ A view on a subset of a section in a morphology. """ def __init__(self, morphology, i, j): self._morphology = morphology self.indices = MorphologyIndexWrapper(self) self._i = i self._j = j def _indices(self, item=None): if not (item is None or item == slice(None)): raise IndexError("Cannot index a view on a subset of a section further") # Start index of the main section start_idx = _calc_start_idx(self._morphology) if item is None and self.n == 1: return start_idx + self._i else: return np.arange(start_idx + self._i, start_idx + self._j) @property def n(self): """ The number of compartments in this sub-section. """ return self._j - self._i def __len__(self): return self.n @property def n_sections(self): """ The number of sections in this sub-section (always 1). """ return 1 # Per-compartment attributes @property def area(self): """ The membrane surface area of each compartment in this sub-section. """ return self._morphology.area[self._i : self._j] @property def volume(self): """ The volume of each compartment in this sub-section. """ return self._morphology.volume[self._i : self._j] @property def length(self): """ The length of each compartment in this sub-section. """ return self._morphology.length[self._i : self._j] @property def r_length_1(self): """ The geometry-dependent term to calculate the conductance between the start and the midpoint of each compartment in this sub-section. Dividing this value by the Intracellular resistivity gives the conductance. """ return self._morphology.r_length_1[self._i : self._j] @property def r_length_2(self): """ The geometry-dependent term to calculate the conductance between the midpoint and the end of each compartment in this sub-section. Dividing this value by the Intracellular resistivity gives the conductance. """ return self._morphology.r_length_2[self._i : self._j] # At-midpoint attributes @property def diameter(self): """ The diameter at the middle of each compartment in this sub-section. """ return self._morphology.diameter[self._i : self._j] @property def distance(self): """ The total distance between the midpoint of each compartment in this sub-section and the root of the morphology. """ return self._morphology.distance[self._i : self._j] @property def start_x(self): """ The x coordinate at the beginning of each compartment in this sub-section. Returns ``None`` for morphologies without coordinates. """ return _to_meters(self.start_x_) @property def start_y(self): """ The y coordinate at the beginning of each compartment in this sub-section. Returns ``None`` for morphologies without coordinates. """ return _to_meters(self.start_y_) @property def start_z(self): """ The x coordinate at the beginning of each compartment in this sub-section. Returns ``None`` for morphologies without coordinates. """ return _to_meters(self.start_z_) @property def start_x_(self): """ The x coordinate (as a unitless floating point number) at the beginning of each compartment in this sub-section. Returns ``None`` for morphologies without coordinates. """ return _from_morphology(self._morphology.start_x_, self._i, self._j) @property def start_y_(self): """ The y coordinate (as a unitless floating point number) at the beginning of each compartment in this sub-section. Returns ``None`` for morphologies without coordinates. """ return _from_morphology(self._morphology.start_y_, self._i, self._j) @property def start_z_(self): """ The z coordinate (as a unitless floating point number) at the beginning of each compartment in this sub-section. Returns ``None`` for morphologies without coordinates. """ return _from_morphology(self._morphology.start_z_, self._i, self._j) @property def x(self): """ The x coordinate at the midpoint of each compartment in this sub-section. Returns ``None`` for morphologies without coordinates. """ return _to_meters(self.x_) @property def y(self): """ The y coordinate at the midpoint of each compartment in this sub-section. Returns ``None`` for morphologies without coordinates. """ return _to_meters(self.y_) @property def z(self): """ The z coordinate at the midpoint of each compartment in this sub-section. Returns ``None`` for morphologies without coordinates. """ return _to_meters(self.z_) @property def x_(self): """ The x coordinate (as a unitless floating point number) at the midpoint of each compartment in this sub-section. Returns ``None`` for morphologies without coordinates. """ return _from_morphology(self._morphology.x_, self._i, self._j) @property def y_(self): """ The y coordinate (as a unitless floating point number) at the midpoint of each compartment in this sub-section. Returns ``None`` for morphologies without coordinates. """ return _from_morphology(self._morphology.y_, self._i, self._j) @property def z_(self): """ The z coordinate (as a unitless floating point number) at the midpoint of each compartment in this sub-section. Returns ``None`` for morphologies without coordinates. """ return _from_morphology(self._morphology.z_, self._i, self._j) @property def end_x(self): """ The x coordinate at the end of each compartment in this sub-section. Returns ``None`` for morphologies without coordinates. """ return _to_meters(self.end_x_) @property def end_y(self): """ The y coordinate at the end of each compartment in this sub-section. Returns ``None`` for morphologies without coordinates. """ return _to_meters(self.end_y_) @property def end_z(self): """ The z coordinate at the end of each compartment in this sub-section. Returns ``None`` for morphologies without coordinates. """ return _to_meters(self.end_z_) @property def end_x_(self): """ The x coordinate (as a unitless floating point number) at the end of each compartment in this sub-section. Returns ``None`` for morphologies without coordinates. """ return _from_morphology(self._morphology.end_x_, self._i, self._j) @property def end_y_(self): """ The y coordinate (as a unitless floating point number) at the end of each compartment in this sub-section. Returns ``None`` for morphologies without coordinates. """ return _from_morphology(self._morphology.end_y_, self._i, self._j) @property def end_z_(self): """ The z coordinate (as a unitless floating point number) at the end of each compartment in this sub-section. Returns ``None`` for morphologies without coordinates. """ return _from_morphology(self._morphology.end_z_, self._i, self._j) class Soma(Morphology): """ A spherical, iso-potential soma. Parameters ---------- diameter : `Quantity` Diameter of the sphere. x : `Quantity`, optional The x coordinate of the position of the soma. y : `Quantity`, optional The y coordinate of the position of the soma. z : `Quantity`, optional The z coordinate of the position of the soma. type : str, optional The ``type`` of this section, defaults to ``'soma'``. """ @check_units(diameter=meter, x=meter, y=meter, z=meter) def __init__(self, diameter, x=None, y=None, z=None, type="soma"): Morphology.__init__(self, n=1, type=type) if diameter.shape != () and len(diameter) != 1: raise TypeError("Diameter has to be a scalar value.") for coord in [x, y, z]: if coord is not None and coord.shape != () and len(coord) != 1: raise TypeError("Coordinates have to be scalar values.") self._diameter = np.ones(1) * diameter if any(coord is not None for coord in (x, y, z)): default_value = np.array([0.0]) else: default_value = None self._x = np.atleast_1d(np.asarray(x)) if x is not None else default_value self._y = np.atleast_1d(np.asarray(y)) if y is not None else default_value self._z = np.atleast_1d(np.asarray(z)) if z is not None else default_value def __repr__(self): s = f"{self.__class__.__name__}(diameter={self.diameter[0]!r}" if self._x is not None: s += f", x={self.x[0]!r}, y={self.y[0]!r}, z={self.z[0]!r}" if self.type != "soma": s += f", type={self.type!r}" return f"{s})" def copy_section(self): return Soma(self.diameter, x=self.x, y=self.y, z=self.z, type=self.type) # Note that the per-compartment properties should always return 1D arrays, # i.e. for the soma arrays of length 1 instead of scalar values @property def area(self): """ The membrane surface area of this section (as an array of length 1). """ return np.pi * self.diameter**2 @property def volume(self): """ The volume of this section (as an array of length 1). """ return (np.pi * self.diameter**3) / 6 @property def length(self): """ The "length" (equal to `diameter`) of this section (as an array of length 1). """ return self.diameter @property def r_length_1(self): """ The geometry-dependent term to calculate the conductance between the start and the midpoint of each compartment. Returns a fixed (high) value for a `Soma`, corresponding to a section with very low intracellular resistance. """ return [1] * meter @property def r_length_2(self): """ The geometry-dependent term to calculate the conductance between the midpoint and the end of each compartment. Returns a fixed (high) value for a `Soma`, corresponding to a section with very low intracellular resistance. """ return [1] * meter @property def diameter(self): """ The diameter of this section (as an array of length 1). """ return self._diameter @property def distance(self): """ The total distance between the midpoint of this section and the root of the morphology. The `Soma` is most likely the root of the morphology, and therefore the `distance` is 0. """ dist = self._parent.distance[-1:] if self._parent is not None else [0] * um return dist @property def start_x_(self): """ The x-coordinate of the current section (as an array of length 1). Note that a `Soma` is modelled as a "point" with finite surface/volume, equivalent to that of a sphere with the given `diameter`. It's start-, midpoint-, and end-coordinates are therefore identical. """ return self._x @property def start_y_(self): """ The y-coordinate of the current section (as an array of length 1). Note that a `Soma` is modelled as a "point" with finite surface/volume, equivalent to that of a sphere with the given `diameter`. It's start-, midpoint-, and end-coordinates are therefore identical. """ return self._y @property def start_z_(self): """ The z-coordinate of the current section (as an array of length 1). Note that a `Soma` is modelled as a "point" with finite surface/volume, equivalent to that of a sphere with the given `diameter`. It's start-, midpoint-, and end-coordinates are therefore identical. """ return self._z @property def x_(self): """ The x-coordinate of the current section (as an array of length 1). Note that a `Soma` is modelled as a "point" with finite surface/volume, equivalent to that of a sphere with the given `diameter`. It's start-, midpoint-, and end-coordinates are therefore identical. """ return self._x @property def y_(self): """ The y-coordinate of the current section (as an array of length 1). Note that a `Soma` is modelled as a "point" with finite surface/volume, equivalent to that of a sphere with the given `diameter`. It's start-, midpoint-, and end-coordinates are therefore identical. """ return self._y @property def z_(self): """ The z-coordinate of the current section (as an array of length 1). Note that a `Soma` is modelled as a "point" with finite surface/volume, equivalent to that of a sphere with the given `diameter`. It's start-, midpoint-, and end-coordinates are therefore identical. """ return self._z @property def end_x_(self): """ The x-coordinate of the current section (as an array of length 1). Note that a `Soma` is modelled as a "point" with finite surface/volume, equivalent to that of a sphere with the given `diameter`. It's start-, midpoint-, and end-coordinates are therefore identical. """ return self._x @property def end_y_(self): """ The y-coordinate of the current section (as an array of length 1). Note that a `Soma` is modelled as a "point" with finite surface/volume, equivalent to that of a sphere with the given `diameter`. It's start-, midpoint-, and end-coordinates are therefore identical. """ return self._y @property def end_z_(self): """ The z-coordinate of the current section (as an array of length 1). Note that a `Soma` is modelled as a "point" with finite surface/volume, equivalent to that of a sphere with the given `diameter`. It's start-, midpoint-, and end-coordinates are therefore identical. """ return self._z @property def end_distance(self): """ The distance to the root of the morphology at the end of this section. Note that since a `Soma` is modeled as a point (see docs of `x`, etc.), it does not add anything to the total distance, e.g. a section connecting to a `Soma` has a `distance` of 0 um at its start. """ dist = self._parent.end_distance if self._parent is not None else 0 * um return dist class Section(Morphology): """ A section (unbranched structure), described as a sequence of truncated cones with potentially varying diameters and lengths per compartment. Parameters ---------- diameter : `Quantity` Either a single value (the constant diameter along the whole section), or a value of length ``n+1``. When ``n+1`` values are given, they will be interpreted as the diameters at the start of the first compartment and the diameters at the end of each compartment (which is equivalent to: the diameter at the start of each compartment and the diameter at the end of the last compartment. n : int, optional The number of compartments in this section. Defaults to 1. length : `Quantity`, optional Either a single value (the total length of the section), or a value of length ``n``, the length of each individual compartment. Cannot be combined with the specification of coordinates. x : `Quantity`, optional ``n+1`` values, specifying the x coordinates of the start point of the first compartment and the end-points of all compartments (which is equivalent to: the start point of all compartments and the end point of the last compartment). The coordinates are interpreted as relative to the end point of the parent compartment (if any), so in most cases the start point should be ``0*um``. The common exception is a cylinder connecting to a `Soma`, here the start point can be used to make the cylinder start at the surface of the sphere instead of at its center. You can specify all of ``x``, ``y``, or ``z`` to specify a morphology in 3D, or only one or two out of them to specify a morphology in 1D or 2D. y : `Quantity`, optional See ``x`` z : `Quantity`, optional See ``x`` type : str, optional The type (e.g. ``"axon"``) of this `Section`. """ @check_units( n=1, length=meter, diameter=meter, start_diameter=meter, x=meter, y=meter, z=meter, ) def __init__( self, diameter, n=1, length=None, x=None, y=None, z=None, start_diameter=None, origin=None, type=None, ): n = int(n) Morphology.__init__(self, n=n, type=type) if diameter.ndim != 1 or len(diameter) != n + 1: raise TypeError( "The diameter argument has to be a one-dimensional array of length " f"{int(n + 1)}" ) self._diameter = Quantity(diameter, copy=True).reshape((n + 1,)) if (x is not None or y is not None or z is not None) and length is not None: raise TypeError("Cannot specify coordinates and length at the same time.") if length is not None: # Length if length.ndim != 1 or len(length) != n: raise TypeError( "The length argument has to be a one-dimensional array of length " f"{int(n)}" ) self._length = Quantity(length, copy=True).reshape((n,)) self._x = self._y = self._z = None else: # Coordinates if x is None and y is None and z is None: raise TypeError( "No length specified, need to specify at least " "one out of x, y, or z." ) for name, value in [("x", x), ("y", y), ("z", z)]: if value is not None and (value.ndim != 1 or len(value) != n + 1): raise TypeError( f"'{name}' needs to be a 1-dimensional array of length {n + 1}." ) self._x = ( np.asarray(x).reshape((n + 1,)) if x is not None else np.zeros(n + 1) ) self._y = ( np.asarray(y).reshape((n + 1,)) if y is not None else np.zeros(n + 1) ) self._z = ( np.asarray(z).reshape((n + 1,)) if z is not None else np.zeros(n + 1) ) length = np.sqrt( (self.end_x - self.start_x) ** 2 + (self.end_y - self.start_y) ** 2 + (self.end_z - self.start_z) ** 2 ) self._length = length def __repr__(self): if all( np.abs(self.end_diameter - self.end_diameter[0]) < self.end_diameter[0] * 1e-12 ): # Constant diameter diam = self.end_diameter[0] else: diam = ( np.hstack( [np.asarray(self.start_diameter[0]), np.asarray(self.end_diameter)] ) * meter ) s = f"{self.__class__.__name__}(diameter={diam!r}" if self.n != 1: s += f", n={self.n}" if self._x is not None: s += f", x={self._x!r}, y={self._y!r}, z={self._z!r}" else: s += f", length={sum(self._length)!r}" if self.type is not None: s += f", type={self.type!r}" return f"{s})" def copy_section(self): if self.x is None: x, y, z = None, None, None length = self.length else: x, y, z = self._x * meter, self._y * meter, self._z * meter length = None return Section( diameter=self._diameter, n=self.n, x=x, y=y, z=z, length=length, type=self.type, ) @property def area(self): r""" The membrane surface area of each compartment in this section. The surface area of each compartment is calculated as :math:`\frac{\pi}{2}(d_1 + d_2)\sqrt{\frac{(d_1 - d_2)^2}{4} + l^2)}`, where :math:`l` is the length of the compartment, and :math:`d_1` and :math:`d_2` are the diameter at the start and end of the compartment, respectively. Note that this surface area does not contain the area of the two disks at the two sides of the truncated cone. """ d_1 = self.start_diameter d_2 = self.end_diameter return ( np.pi / 2 * (d_1 + d_2) * np.sqrt(((d_1 - d_2) ** 2) / 4 + self._length**2) ) @property def volume(self): r""" The volume of each compartment in this section. The volume of each compartment is calculated as :math:`\frac{\pi}{12} l (d_1^2 + d_1 d_2 + d_2^2)`, where :math:`l` is the length of the compartment, and :math:`d_1` and :math:`d_2` are the diameter at the start and end of the compartment, respectively. """ d_1 = self.start_diameter d_2 = self.end_diameter return np.pi * self._length * (d_1**2 + d_1 * d_2 + d_2**2) / 12 @property def length(self): """ The length of each compartment in this section. """ return self._length @property def start_diameter(self): """ The diameter at the start of each compartment in this section. """ return Quantity(self._diameter[:-1], copy=True) @property def end_diameter(self): """ The diameter at the end of each compartment in this section. """ return Quantity(self._diameter[1:], copy=True) @property def diameter(self): """ The diameter at the middle of each compartment in this section. """ d_1 = self.start_diameter d_2 = self.end_diameter # Diameter at the center return 0.5 * (d_1 + d_2) @property def distance(self): """ The total distance between the midpoint of each compartment and the root of the morphology. """ dist = self._parent.end_distance if self._parent is not None else 0 * um return dist + np.cumsum(self.length) - 0.5 * self.length @property def end_distance(self): """ The distance to the root of the morphology at the end of this section. """ return self.distance[-1] + 0.5 * self.length[-1] @property def r_length_1(self): """ The geometry-dependent term to calculate the conductance between the start and the midpoint of each compartment. Dividing this value by the Intracellular resistivity gives the conductance. """ d_1 = self.start_diameter d_2 = (self.start_diameter + self.end_diameter) * 0.5 return np.pi / 2 * (d_1 * d_2) / self._length @property def r_length_2(self): """ The geometry-dependent term to calculate the conductance between the midpoint and the end of each compartment. Dividing this value by the Intracellular resistivity gives the conductance. """ d_1 = (self.start_diameter + self.end_diameter) * 0.5 d_2 = self.end_diameter return np.pi / 2 * (d_1 * d_2) / self._length @property def start_x_(self): """ The x coordinate (as a unitless floating point number) at the beginning of each compartment. Returns ``None`` for morphologies without coordinates. """ if self._x is None: return None parent_end_x = self.parent.end_x_ if self.parent is not None else None if parent_end_x is not None: return parent_end_x[-1] + self._x[:-1] else: return self._x[:-1] @property def start_y_(self): """ The y coordinate (as a unitless floating point number) at the beginning of each compartment. Returns ``None`` for morphologies without coordinates. """ if self._y is None: return None parent_end_y = self.parent.end_y_ if self.parent is not None else None if parent_end_y is not None: return parent_end_y[-1] + self._y[:-1] else: return self._y[:-1] @property def start_z_(self): """ The z coordinate (as a unitless floating point number) at the beginning of each compartment. Returns ``None`` for morphologies without coordinates. """ if self._z is None: return None parent_end_z = self.parent.end_z_ if self.parent is not None else None if parent_end_z is not None: return parent_end_z[-1] + self._z[:-1] else: return self._z[:-1] @property def x_(self): """ The x coordinate (as a unitless floating point number) at the midpoint of each compartment. Returns ``None`` for morphologies without coordinates. """ if self._x is None: return None start_x = self.start_x_ diff_x = self.end_x_ - start_x return start_x + 0.5 * diff_x @property def y_(self): """ The y coordinate (as a unitless floating point number) at the midpoint of each compartment. Returns ``None`` for morphologies without coordinates. """ if self._y is None: return None start_y = self.start_y_ diff_y = self.end_y_ - start_y return start_y + 0.5 * diff_y @property def z_(self): """ The z coordinate (as a unitless floating point number) at the midpoint of each compartment. Returns ``None`` for morphologies without coordinates. """ if self._z is None: return None start_z = self.start_z_ diff_z = self.end_z_ - start_z return start_z + 0.5 * diff_z @property def end_x_(self): """ The x coordinate (as a unitless floating point number) at the end of each compartment. Returns ``None`` for morphologies without coordinates. """ if self._x is None: return None parent_end_x = self.parent.end_x_ if self.parent is not None else None if parent_end_x is not None: return parent_end_x[-1] + self._x[1:] else: return self._x[1:] @property def end_y_(self): """ The y coordinate (as a unitless floating point number) at the end of each compartment. Returns ``None`` for morphologies without coordinates. """ if self._y is None: return None parent_end_y = self.parent.end_y_ if self.parent is not None else None if parent_end_y is not None: return parent_end_y[-1] + self._y[1:] else: return self._y[1:] @property def end_z_(self): """ The z coordinate (as a unitless floating point number) at the end of each compartment. Returns ``None`` for morphologies without coordinates. """ if self._z is None: return None parent_end_z = self.parent.end_z_ if self.parent is not None else None if parent_end_z is not None: return parent_end_z[-1] + self._z[1:] else: return self._z[1:] class Cylinder(Section): """ A cylindrical section. For sections with more complex geometry (varying length and/or diameter of each compartment), use the `Section` class. Parameters ---------- diameter : `Quantity` The diameter of the cylinder. n : int, optional The number of compartments in this section. Defaults to 1. length : `Quantity`, optional The length of the cylinder. Cannot be combined with the specification of coordinates. x : `Quantity`, optional A sequence of two values, the start and the end point of the cylinder. The coordinates are interpreted as relative to the end point of the parent compartment (if any), so in most cases the start point should be ``0*um``. The common exception is a cylinder connecting to a `Soma`, here the start point can be used to make the cylinder start at the surface of the sphere instead of at its center. You can specify all of ``x``, ``y``, or ``z`` to specify a morphology in 3D, or only one or two out of them to specify a morphology in 1D or 2D. y : `Quantity`, optional See ``x`` z : `Quantity`, optional See ``x`` type : str, optional The type (e.g. ``"axon"``) of this `Cylinder`. """ @check_units(n=1, length=meter, diameter=meter, x=meter, y=meter, z=meter) def __init__(self, diameter, n=1, length=None, x=None, y=None, z=None, type=None): n = int(n) Morphology.__init__(self, n=n, type=type) # Diameter if diameter.shape != () and (diameter.ndim > 1 or len(diameter) != 1): raise TypeError("The diameter argument has to be a single value.") diameter = np.ones(n) * diameter self._diameter = diameter if (x is not None or y is not None or z is not None) and length is not None: raise TypeError("Cannot specify coordinates and length at the same time.") if length is not None: # Length if length.shape != () and (length.ndim > 1 or len(length) != 1): raise TypeError("The length argument has to be a single value.") self._length = np.ones(n) * (length / n) # length was total length self._x = self._y = self._z = None else: # Coordinates if x is None and y is None and z is None: raise TypeError( "No length specified, need to specify at least " "one out of x, y, or z." ) for name, value in [("x", x), ("y", y), ("z", z)]: if value is not None and (value.ndim != 1 or len(value) != 2): raise TypeError( f"{name} needs to be a 1-dimensional array of length 2 (start " "and end point)" ) self._x = ( np.asarray(np.linspace(x[0], x[1], n + 1)) if x is not None else np.zeros(n + 1) ) self._y = ( np.asarray(np.linspace(y[0], y[1], n + 1)) if y is not None else np.zeros(n + 1) ) self._z = ( np.asarray(np.linspace(z[0], z[1], n + 1)) if z is not None else np.zeros(n + 1) ) length = np.sqrt( (self.end_x - self.start_x) ** 2 + (self.end_y - self.start_y) ** 2 + (self.end_z - self.start_z) ** 2 ) self._length = length def __repr__(self): s = f"{self.__class__.__name__}(diameter={self.diameter[0]!r}" if self.n != 1: s += f", n={self.n}" if self._x is not None: s += ( f", x={self._x[[0, -1]]!r}, y={self._y[[0, -1]]!r}," f" z={self._z[[0, -1]]!r}" ) else: s += f", length={sum(self._length)!r}" if self.type is not None: s += f", type={self.type!r}" return f"{s})" def copy_section(self): if self.x is None: return Cylinder( self.diameter[0], n=self.n, length=self.length, type=self.type ) else: return Cylinder( self.diameter[0], n=self.n, x=self._x[[0, -1]], y=self._y[[0, -1]], z=self._z[[0, -1]], type=self.type, ) # Overwrite the properties that differ from `Section` @property def area(self): r""" The membrane surface area of each compartment in this section. The surface area of each compartment is calculated as :math:`\pi d l`, where :math:`l` is the length of the compartment, and :math:`d` is its diameter. Note that this surface area does not contain the area of the two disks at the two sides of the cylinder. """ return np.pi * self._diameter * self.length @property def start_diameter(self): """ The diameter at the start of each compartment in this section. """ return self._diameter @property def diameter(self): """ The diameter at the middle of each compartment in this section. """ return self._diameter @property def end_diameter(self): """ The diameter at the end of each compartment in this section. """ return self._diameter @property def volume(self): r""" The volume of each compartment in this section. The volume of each compartment is calculated as :math:`\pi \frac{d}{2}^2 l` , where :math:`l` is the length of the compartment, and :math:`d` is its diameter. """ return np.pi * (self._diameter / 2) ** 2 * self.length @property def r_length_1(self): """ The geometry-dependent term to calculate the conductance between the start and the midpoint of each compartment. Dividing this value by the Intracellular resistivity gives the conductance. """ return np.pi / 2 * (self._diameter**2) / self.length @property def r_length_2(self): """ The geometry-dependent term to calculate the conductance between the midpoint and the end of each compartment. Dividing this value by the Intracellular resistivity gives the conductance. """ return np.pi / 2 * (self._diameter**2) / self.length brian2-2.5.4/brian2/spatialneuron/mp_ma_40984_gc2.CNG.swc000066400000000000000000000267671445201106100224740ustar00rootroot00000000000000# Original file mp.ma.40984.gc2.asc.swc edited by Duncan Donohue using SWCfix version 1.21 on 8/31/05. # Errors and fixes documented in mp.ma.40984.gc2.asc.swc.err. See SWCfix1.21.doc for more information. # # Amaral to SWC conversion from L-Measure. R. Scorcioni: rscorcio@gmu.edu # Original fileName:C:\Documents and Settings\Admin\Desktop\Miller-Eutectic\mp.ma.40984.gc2.asc # # ORIGINAL_SOURCE # CREATURE # REGION # FIELD/LAYER # TYPE # CONTRIBUTOR # REFERENCE # RAW # EXTRAS # SOMA_AREA # SHRINKAGE_CORRECTION # VERSION_NUMBER # VERSION_DATE # ********************************************* # SCALE 1.0 1.0 1.0 1 1 0.2917 0.04167 -0.1458 12.030 -1 2 3 12. 6.5 1. 0.850 1 3 3 15. 9. 1.5 0.75 2 4 3 18.5 10. 2.5 0.65 3 5 3 17. 8. 3. 0.15 4 6 3 18. 7. 2.5 0.15 5 7 3 16. 4. 8. 0.15 6 8 3 14. 0.5 8. 0.15 7 9 3 7. -11.5 9. 0.09 8 10 3 1.5 -19. 8. 0.09 9 11 3 -3.5 -22. 9. 0.09 10 12 3 -9.5 -24.5 9. 0.09 11 13 3 -17.5 -24. 9. 0.09 12 14 3 -21.5 -23. 10.5 0.09 13 15 3 -25. -22.5 10.5 0.09 14 16 3 21.5 10. 2. 0.45 4 17 3 24.5 11.5 2.5 0.4 16 18 3 27. 10.5 4. 0.3 17 19 3 28.5 10. 6. 0.2 18 20 3 29.5 11. 7. 0.2 19 21 3 31. 9.5 8.5 0.25 20 22 3 36. 11.5 9. 0.2 21 23 3 37.5 9. 10.5 0.2 22 24 3 38.5 4. 10. 0.25 23 25 3 42. 3.5 11. 0.2 24 26 3 44. 0.5 10. 0.2 25 27 3 48.5 -0.5 10.5 0.2 26 28 3 60. -2. 9. 0.2 27 29 3 68. -1.5 9.5 0.2 28 30 3 72. -3. 9.5 0.25 29 31 3 75.5 -6.5 9. 0.15 30 32 3 79. -9.5 9.5 0.15 31 33 3 84.5 -12. 9. 0.15 32 34 3 96. -13. 8.5 0.15 33 35 3 101.5 -16.5 10. 0.15 34 36 3 112. -17. 10. 0.15 35 37 3 119.5 -18.5 10. 0.15 36 38 3 126. -20. 10. 0.15 37 39 3 133.5 -21.5 10. 0.09 38 40 3 137.5 -21.5 10. 0.09 39 41 3 140.5 -25. 9.5 0.09 40 42 3 139.5 -30. 10.5 0.09 41 43 3 141.5 -34.5 10.5 0.09 42 44 3 141.5 -38. 10.5 0.09 43 45 3 142. -41. 10.5 0.09 44 46 3 145.5 -44. 10.5 0.09 45 47 3 149.5 -48.5 9.5 0.09 46 48 3 154.5 -52. 11. 0.09 47 49 3 158. -58.5 11. 0.09 48 50 3 157.5 -65. 12.5 0.09 49 51 3 156. -70.5 12.5 0.09 50 52 3 155. -80. 12.5 0.09 51 53 3 155. -84. 13.5 0.09 52 54 3 158.5 -84. 13.5 0.09 53 55 3 160.5 -85.5 13.5 0.09 54 56 3 10. -4. 3. 1.95 1 57 3 10.5 -6. 4. 1.85 56 58 3 14.5 -7. 3.5 1.45 57 59 3 15. -10.5 3.5 1.45 58 60 3 18.5 -12.5 3.5 1.45 59 61 3 20.5 -15.5 3.5 1.45 60 62 3 20.5 -18. 3.5 1.45 61 63 3 19.5 -22.5 7.5 0.8 62 64 3 17. -30. 8.5 0.8 63 65 3 15.5 -35. 8.5 0.9 64 66 3 14. -40. 9. 0.9 65 67 3 14. -46. 8.5 0.9 66 68 3 16. -52.5 9. 1.75 67 69 3 13.5 -56.5 9. 0.65 68 70 3 12.5 -59. 8. 0.75 69 71 3 10. -61. 8.5 0.15 70 72 3 5. -65. 7. 0.15 71 73 3 -0.5 -69.5 6.5 0.15 72 74 3 -3.5 -73.5 7. 0.09 73 75 3 -6.5 -78.5 7.5 0.09 74 76 3 -9. -82. 7.5 0.09 75 77 3 -8.5 -89. 7.5 0.09 76 78 3 -7.5 -95. 7. 0.09 77 79 3 -6. -101. 7. 0.09 78 80 3 -8. -113. 6.5 0.09 79 81 3 -11.5 -123. 7. 0.09 80 82 3 -12. -130.5 7.5 0.09 81 83 3 -11.5 -141. 7. 0.09 82 84 3 -13.5 -150.5 7.5 0.09 83 85 3 -15. -157.5 7. 0.09 84 86 3 -17.5 -162.5 7.5 0.09 85 87 3 -18.5 -168. 8. 0.09 86 88 3 -20.5 -170.5 8. 0.09 87 89 3 12. -63.5 9. 0.45 70 90 3 13. -68.5 9. 0.4 89 91 3 14.5 -73. 8.5 0.4 90 92 3 17.5 -78. 9.5 0.4 91 93 3 19. -84.5 10. 0.4 92 94 3 21.5 -92.5 9.5 0.4 93 95 3 24. -99.5 9. 0.4 94 96 3 25. -103. 9. 0.4 95 97 3 25.5 -104.5 10. 0.4 96 98 3 27. -107. 9.5 0.4 97 99 3 29. -109. 10.5 0.4 98 100 3 31.5 -114.5 10.5 0.4 99 101 3 33. -120.5 10. 0.4 100 102 3 32. -123.5 10. 0.4 101 103 3 30. -127. 9. 0.15 102 104 3 28. -131.5 8.5 0.15 103 105 3 26.5 -133.5 9.5 0.09 104 106 3 31.5 -133. 8. 0.09 104 107 3 35.5 -134.5 7. 0.09 106 108 3 33.5 -124. 9. 0.3 102 109 3 37. -125.5 9. 0.2 108 110 3 39. -126.5 8.5 0.15 109 111 3 42.5 -129.5 8.5 0.15 110 112 3 46. -132. 7. 0.15 111 113 3 49. -135.5 6.5 0.09 112 114 3 49. -137. 7. 0.09 113 115 3 53.5 -141. 6. 0.09 114 116 3 57. -145.5 6. 0.09 115 117 3 60.5 -150. 6. 0.09 116 118 3 61.5 -154. 6. 0.09 117 119 3 62. -158.5 7.5 0.09 118 120 3 67.5 -165. 7.5 0.09 119 121 3 72.5 -170.5 8. 0.09 120 122 3 72.5 -176.5 7.5 0.09 121 123 3 76.5 -186. 8.5 0.09 122 124 3 76.5 -187. 8.5 0.09 123 125 3 21.5 -53.5 9. 0.65 68 126 3 25.5 -55.5 9.5 0.55 125 127 3 30.5 -58. 10. 0.45 126 128 3 33. -58.5 9.5 0.45 127 129 3 33. -62. 9. 0.15 128 130 3 35.5 -66.5 7.5 0.15 129 131 3 38. -70. 8.5 0.15 130 132 3 39.5 -75. 9. 0.15 131 133 3 40. -78.5 8. 0.15 132 134 3 42. -81. 8.5 0.09 133 135 3 44. -86.5 9. 0.09 134 136 3 44.5 -90. 8.5 0.09 135 137 3 47. -93. 8. 0.09 136 138 3 50. -95. 8. 0.09 137 139 3 52. -97. 8. 0.09 138 140 3 54.5 -97.5 8. 0.09 139 141 3 56. -98.5 8. 0.09 140 142 3 59.5 -99.5 8. 0.09 141 143 3 62.5 -102. 9. 0.09 142 144 3 69. -105. 9.5 0.09 143 145 3 71.5 -103.5 9.5 0.09 144 146 3 77. -103. 9.5 0.09 145 147 3 79.5 -102.5 9.5 0.09 146 148 3 36.5 -57. 10. 0.3 128 149 3 39. -56. 10. 0.25 148 150 3 42. -58. 9.5 0.25 149 151 3 45. -58.5 9.5 0.2 150 152 3 49. -62. 9.5 0.2 151 153 3 52. -63. 8.5 0.2 152 154 3 55. -62.5 8.5 0.2 153 155 3 56. -64.5 8.5 0.2 154 156 3 58.5 -65.5 8.5 0.2 155 157 3 61. -65.5 7.5 0.2 156 158 3 61.5 -69. 7.5 0.2 157 159 3 63.5 -71. 7. 0.2 158 160 3 66. -73.5 6.5 0.2 159 161 3 68. -75. 6.5 0.2 160 162 3 67.5 -77. 6.5 0.2 161 163 3 69.5 -79. 6.5 0.2 162 164 3 70.5 -82. 6.5 0.2 163 165 3 73. -85.5 6.5 0.2 164 166 3 74.5 -88. 6.5 0.2 165 167 3 75. -89.5 6. 0.2 166 168 3 79.5 -89.5 6. 0.15 167 169 3 82.5 -89.5 5. 0.15 168 170 3 85. -93.5 6. 0.15 169 171 3 88. -98. 6. 0.15 170 172 3 90. -99.5 7.5 0.15 171 173 3 91.5 -100.5 7. 0.15 172 174 3 93. -103.5 7. 0.15 173 175 3 94.5 -103.5 7. 0.15 174 176 3 97. -104.5 7.5 0.15 175 177 3 101.5 -106. 8. 0.15 176 178 3 103.5 -108.5 8. 0.15 177 179 3 104.5 -111. 8. 0.15 178 180 3 106.5 -112. 8. 0.15 179 181 3 108. -115. 8.5 0.15 180 182 3 112.5 -117. 8.5 0.15 181 183 3 115.5 -120. 8.5 0.15 182 184 3 114. -124.5 8. 0.15 183 185 3 116.5 -125. 8. 0.15 184 186 3 116.5 -127.5 8. 0.15 185 187 3 119. -131.5 7.5 0.15 186 188 3 121. -132. 9. 0.15 187 189 3 120.5 -135. 8.5 0.15 188 190 3 124.5 -140.5 9.5 0.15 189 191 3 24. -22. 4. 0.75 62 192 3 25.5 -26. 3.5 0.75 191 193 3 28.5 -30. 3.5 0.850 192 194 3 28. -32.5 3. 0.5 193 195 3 28. -36.5 2.5 0.5 194 196 3 29.5 -42.5 2.5 0.45 195 197 3 30. -49. 1.5 0.45 196 198 3 28.5 -56.5 1.5 0.45 197 199 3 24.5 -65.5 1.5 0.45 198 200 3 22.5 -71. 1.5 0.45 199 201 3 20. -75. 1.5 0.350 200 202 3 17. -81.5 1. 0.350 201 203 3 14. -89. 0.5 0.350 202 204 3 11. -96.5 0. 0.350 203 205 3 9. -99. -1. 0.75 204 206 3 6. -99.5 -1. 0.09 205 207 3 0.5 -101. 1. 0.09 206 208 3 -3. -101. 0.5 0.09 207 209 3 -9. -102.5 1.5 0.09 208 210 3 -13.5 -105. 3. 0.09 209 211 3 -19. -107.5 3. 0.09 210 212 3 -28.5 -109. 5. 0.09 211 213 3 -32.5 -106.5 5. 0.09 212 214 3 -43. -107. 6.5 0.09 213 215 3 -47. -107.5 6.5 0.09 214 216 3 -51. -109. 6. 0.09 215 217 3 -57. -110. 6.5 0.09 216 218 3 -63.5 -110. 6.5 0.09 217 219 3 -70. -110. 6.5 0.09 218 220 3 -76. -110. 6.5 0.09 219 221 3 -84.5 -112.5 6. 0.09 220 222 3 -96. -109.5 7.5 0.09 221 223 3 -102. -108. 7.5 0.09 222 224 3 -107.5 -110.5 6.5 0.09 223 225 3 -113.5 -113.5 8. 0.09 224 226 3 -125. -118.5 7. 0.049 225 227 3 -137.5 -122. 7. 0.049 226 228 3 -143. -125. 6.5 0.049 227 229 3 -147. -126.5 6.5 0.049 228 230 3 10. -105.5 -1.5 0.350 205 231 3 10. -112. -2. 0.350 230 232 3 9.5 -114.5 -1.5 0.6 231 233 3 9. -117. -1. 0.15 232 234 3 6.5 -119.5 3. 0.15 233 235 3 5. -124. 3. 0.15 234 236 3 3.5 -127. 5. 0.15 235 237 3 2. -131.5 5.5 0.15 236 238 3 0.5 -135. 6.5 0.15 237 239 3 0.5 -139.5 6.5 0.15 238 240 3 -1. -142.5 7.5 0.15 239 241 3 -0.5 -149. 6. 0.15 240 242 3 -0.5 -152.5 6.5 0.09 241 243 3 -1. -161. 5.5 0.09 242 244 3 0.5 -164. 5.5 0.09 243 245 3 2. -168. 6.5 0.09 244 246 3 1. -173. 6.5 0.09 245 247 3 1.5 -178.5 6.5 0.09 246 248 3 2.5 -183.5 6.5 0.09 247 249 3 1.5 -189. 6.5 0.09 248 250 3 -0.5 -193.5 6.5 0.09 249 251 3 -1. -197. 6. 0.09 250 252 3 -3. -203.5 5.5 0.09 251 253 3 -5.5 -208.5 5.5 0.09 252 254 3 -6.5 -215. 5.5 0.09 253 255 3 -5.5 -223. 6. 0.09 254 256 3 -7. -229.5 5.5 0.09 255 257 3 -9. -236. 6. 0.09 256 258 3 -9.5 -243.5 7. 0.09 257 259 3 -11. -250. 6.5 0.09 258 260 3 -9.5 -264. 6.5 0.09 259 261 3 -6.5 -271. 7.5 0.09 260 262 3 -6.5 -277.5 7.5 0.09 261 263 3 -3.5 -279. 7.5 0.09 262 264 3 2. -153.5 5.5 0.09 241 265 3 6. -158.5 6. 0.09 264 266 3 9.5 -166.5 6.5 0.09 265 267 3 10.5 -169.5 8. 0.09 266 268 3 7.5 -175. 7.5 0.049 267 269 3 7. -180. 6.5 0.049 268 270 3 6. -186. 6.5 0.049 269 271 3 3.5 -190.5 6. 0.049 270 272 3 2. -198.5 5. 0.049 271 273 3 3. -203.5 5. 0.049 272 274 3 3.5 -210. 5. 0.049 273 275 3 2.5 -223. 5. 0.049 274 276 3 -2.5 -231.5 5. 0.049 275 277 3 -4.5 -238. 6.5 0.049 276 278 3 -6. -239.5 6.5 0.049 277 279 3 13. -173. 7. 0.09 267 280 3 18.5 -179. 7. 0.09 279 281 3 21. -180.5 7. 0.049 280 282 3 22.5 -184. 7. 0.049 281 283 3 23. -187. 6. 0.049 282 284 3 13. -118.5 -1.5 0.15 232 285 3 13. -122.5 -1.5 0.15 284 286 3 14.5 -124.5 -1.5 0.15 285 287 3 15. -128. -0.5 0.15 286 288 3 16. -130.5 0.5 0.15 287 289 3 16. -135. -0.5 0.15 288 290 3 14.5 -141.5 0.5 0.15 289 291 3 15.5 -146. 0. 0.15 290 292 3 15. -149.5 0. 0.15 291 293 3 16. -153. 0.5 0.15 292 294 3 16. -155.5 1. 0.15 293 295 3 15. -161. 1. 0.15 294 296 3 14. -165.5 1. 0.15 295 297 3 15. -168. 1.5 0.15 296 298 3 15.5 -176. 1.5 0.15 297 299 3 15.5 -181.5 1.5 0.15 298 300 3 31.5 -29.5 4. 0.45 193 301 3 34. -30. 4. 0.45 300 302 3 38. -30.5 5. 0.4 301 303 3 39.5 -32. 7.5 0.4 302 304 3 38. -33.5 6.5 0.4 303 305 3 39. -35. 7. 0.350 304 306 3 42. -34.5 6.5 0.25 305 307 3 43.5 -36.5 7.5 0.25 306 308 3 45.5 -39.5 7. 0.15 307 309 3 45.5 -42. 7. 0.15 308 310 3 49. -44. 7. 0.15 309 311 3 49.5 -47.5 7. 0.15 310 312 3 51.5 -48. 7. 0.15 311 313 3 52.5 -51.5 7. 0.15 312 314 3 57.5 -56. 7. 0.15 313 315 3 59. -58. 7.5 0.15 314 316 3 62.5 -56.5 7.5 0.15 315 317 3 64.5 -57.5 7.5 0.15 316 318 3 69. -58. 7.5 0.15 317 319 3 75. -58.5 8.5 0.15 318 320 3 82.5 -58.5 8.5 0.15 319 321 3 93. -56. 9.5 0.15 320 322 3 99. -53.5 9.5 0.15 321 323 3 106. -51. 9.5 0.15 322 324 3 110. -49.5 9.5 0.15 323 325 3 117. -49. 9.5 0.15 324 326 3 120.5 -49.5 9.5 0.2 325 327 3 123.5 -48.5 9.5 0.15 326 328 3 126. -51. 12. 0.15 327 329 3 127. -54. 12.5 0.09 328 330 3 132. -56.5 12.5 0.09 329 331 3 137. -57.5 12. 0.09 330 332 3 139. -60. 11. 0.09 331 333 3 141.5 -62.5 11. 0.09 332 334 3 144. -67. 12. 0.09 333 335 3 143.5 -74.5 12.5 0.09 334 336 3 145. -78.5 12.5 0.09 335 337 3 146.5 -82. 11.5 0.09 336 338 3 150. -85.5 11. 0.09 337 339 3 152. -89. 11. 0.09 338 340 3 154.5 -94. 12.5 0.09 339 341 3 47. -36.5 8.5 0.15 307 342 3 52.5 -36. 10.5 0.09 341 343 3 55. -36. 9.5 0.09 342 344 3 59.5 -36. 9.5 0.09 343 345 3 61. -39. 9.5 0.09 344 346 3 63. -41. 9.5 0.09 345 347 3 65. -45.5 9.5 0.09 346 348 3 68.5 -46.5 8.5 0.049 347 349 3 70. -49. 8.5 0.049 348 350 3 68.5 -52. 10. 0.049 349 351 3 68.5 -55.5 8.5 0.049 350 352 3 71.5 -61.5 10. 0.049 351 353 3 76.5 -62.5 9. 0.049 352 brian2-2.5.4/brian2/spatialneuron/spatialneuron.py000066400000000000000000000671501445201106100222010ustar00rootroot00000000000000""" Compartmental models. This module defines the `SpatialNeuron` class, which defines multicompartmental models. """ import copy import weakref import numpy as np import sympy as sp from brian2.core.variables import Variables from brian2.equations.codestrings import Expression from brian2.equations.equations import ( DIFFERENTIAL_EQUATION, PARAMETER, SUBEXPRESSION, Equations, SingleEquation, extract_constant_subexpressions, ) from brian2.groups.group import CodeRunner, Group from brian2.groups.neurongroup import NeuronGroup, SubexpressionUpdater, to_start_stop from brian2.groups.subgroup import Subgroup from brian2.parsing.sympytools import str_to_sympy, sympy_to_str from brian2.units.allunits import amp, meter, ohm, siemens, volt from brian2.units.fundamentalunits import ( DimensionMismatchError, Quantity, fail_for_dimension_mismatch, have_same_dimensions, ) from brian2.units.stdunits import cm, uF from brian2.utils.logger import get_logger __all__ = ["SpatialNeuron"] logger = get_logger(__name__) class FlatMorphology: """ Container object to store the flattened representation of a morphology. Note that all values are stored as numpy arrays without unit information (i.e. in base units). """ def __init__(self, morphology): self.n = n = morphology.total_compartments # Total number of compartments # Per-compartment attributes self.length = np.zeros(n) self.distance = np.zeros(n) self.area = np.zeros(n) self.diameter = np.zeros(n) self.volume = np.zeros(n) self.r_length_1 = np.zeros(n) self.r_length_2 = np.zeros(n) self.start_x = np.zeros(n) self.start_y = np.zeros(n) self.start_z = np.zeros(n) self.x = np.zeros(n) self.y = np.zeros(n) self.z = np.zeros(n) self.end_x = np.zeros(n) self.end_y = np.zeros(n) self.end_z = np.zeros(n) self.depth = np.zeros(n, dtype=np.int32) self.sections = sections = morphology.total_sections self.end_distance = np.zeros(sections) # Index of the parent for each section (-1 for the root) self.morph_parent_i = np.zeros(sections, dtype=np.int32) # The children indices for each section (list of lists, will be later # transformed into an array representation) self.morph_children = [] # each section is child of exactly one parent, this stores the index in # the parents list of children self.morph_idxchild = np.zeros(sections, dtype=np.int32) self.starts = np.zeros(sections, dtype=np.int32) self.ends = np.zeros(sections, dtype=np.int32) # recursively fill the data structures self._sections_without_coordinates = False self.has_coordinates = False self._offset = 0 self._section_counter = 0 self._insert_data(morphology) if self.has_coordinates and self._sections_without_coordinates: logger.info( "The morphology has a mix of sections with and " "without coordinates. The SpatialNeuron object " "will store NaN values for the coordinates of " "the sections that do not specify coordinates. " "Call generate_coordinates on the morphology " "before creating the SpatialNeuron object to fill " "in the missing coordinates." ) # Do not store coordinates for morphologies that don't define them if not self.has_coordinates: self.start_x = self.start_y = self.start_z = None self.x = self.y = self.z = None self.end_x = self.end_y = self.end_z = None # Transform the list of list of children into a 2D array (stored as # 1D) -- note that this wastes space if the number of children per # section is very different. In practice, this should not be much of a # problem since most sections have 0, 1, or 2 children (e.g. SWC files # on neuromorpho.org are all binary trees) self.morph_children_num = np.array([len(c) for c in self.morph_children] + [0]) max_children = max(self.morph_children_num) morph_children = np.zeros((sections + 1, max_children), dtype=np.int32) for idx, section_children in enumerate(self.morph_children): morph_children[idx, : len(section_children)] = section_children self.morph_children = morph_children.reshape(-1) def _insert_data(self, section, parent_idx=-1, depth=0): n = section.n start = self._offset end = self._offset + n # Compartment attributes self.depth[start:end] = depth self.length[start:end] = np.asarray(section.length) self.distance[start:end] = np.asarray(section.distance) self.area[start:end] = np.asarray(section.area) self.diameter[start:end] = np.asarray(section.diameter) self.volume[start:end] = np.asarray(section.volume) self.r_length_1[start:end] = np.asarray(section.r_length_1) self.r_length_2[start:end] = np.asarray(section.r_length_2) if section.x is None: self._sections_without_coordinates = True self.start_x[start:end] = np.ones(n) * np.nan self.start_y[start:end] = np.ones(n) * np.nan self.start_z[start:end] = np.ones(n) * np.nan self.x[start:end] = np.ones(n) * np.nan self.y[start:end] = np.ones(n) * np.nan self.z[start:end] = np.ones(n) * np.nan self.end_x[start:end] = np.ones(n) * np.nan self.end_y[start:end] = np.ones(n) * np.nan self.end_z[start:end] = np.ones(n) * np.nan else: self.has_coordinates = True self.start_x[start:end] = np.asarray(section.start_x) self.start_y[start:end] = np.asarray(section.start_y) self.start_z[start:end] = np.asarray(section.start_z) self.x[start:end] = np.asarray(section.x) self.y[start:end] = np.asarray(section.y) self.z[start:end] = np.asarray(section.z) self.end_x[start:end] = np.asarray(section.end_x) self.end_y[start:end] = np.asarray(section.end_y) self.end_z[start:end] = np.asarray(section.end_z) # Section attributes idx = self._section_counter # We start counting from 1 for the parent indices, since the index 0 # is used for the (virtual) root compartment self.morph_parent_i[idx] = parent_idx + 1 self.morph_children.append([]) self.starts[idx] = start self.ends[idx] = end # Append ourselves to the children list of our parent self.morph_idxchild[idx] = len(self.morph_children[parent_idx + 1]) self.morph_children[parent_idx + 1].append(idx + 1) self.end_distance[idx] = section.end_distance # Recurse down the tree self._offset += n self._section_counter += 1 for child in section.children: self._insert_data(child, parent_idx=idx, depth=depth + 1) class SpatialNeuron(NeuronGroup): """ A single neuron with a morphology and possibly many compartments. Parameters ---------- morphology : `Morphology` The morphology of the neuron. model : str, `Equations` The equations defining the group. method : str, function, optional The numerical integration method. Either a string with the name of a registered method (e.g. "euler") or a function that receives an `Equations` object and returns the corresponding abstract code. If no method is specified, a suitable method will be chosen automatically. threshold : str, optional The condition which produces spikes. Should be a single line boolean expression. threshold_location : (int, `Morphology`), optional Compartment where the threshold condition applies, specified as an integer (compartment index) or a `Morphology` object corresponding to the compartment (e.g. ``morpho.axon[10*um]``). If unspecified, the threshold condition applies at all compartments. Cm : `Quantity`, optional Specific capacitance in uF/cm**2 (default 0.9). It can be accessed and modified later as a state variable. In particular, its value can differ in different compartments. Ri : `Quantity`, optional Intracellular resistivity in ohm.cm (default 150). It can be accessed as a shared state variable, but modified only before the first run. It is uniform across the neuron. reset : str, optional The (possibly multi-line) string with the code to execute on reset. events : dict, optional User-defined events in addition to the "spike" event defined by the ``threshold``. Has to be a mapping of strings (the event name) to strings (the condition) that will be checked. refractory : {str, `Quantity`}, optional Either the length of the refractory period (e.g. ``2*ms``), a string expression that evaluates to the length of the refractory period after each spike (e.g. ``'(1 + rand())*ms'``), or a string expression evaluating to a boolean value, given the condition under which the neuron stays refractory after a spike (e.g. ``'v > -20*mV'``) namespace : dict, optional A dictionary mapping identifier names to objects. If not given, the namespace will be filled in at the time of the call of `Network.run`, with either the values from the ``namespace`` argument of the `Network.run` method or from the local context, if no such argument is given. dtype : (`dtype`, `dict`), optional The `numpy.dtype` that will be used to store the values, or a dictionary specifying the type for variable names. If a value is not provided for a variable (or no value is provided at all), the preference setting `core.default_float_dtype` is used. dt : `Quantity`, optional The time step to be used for the simulation. Cannot be combined with the `clock` argument. clock : `Clock`, optional The update clock to be used. If neither a clock, nor the `dt` argument is specified, the `defaultclock` will be used. order : int, optional The priority of of this group for operations occurring at the same time step and in the same scheduling slot. Defaults to 0. name : str, optional A unique name for the group, otherwise use ``spatialneuron_0``, etc. """ def __init__( self, morphology=None, model=None, threshold=None, refractory=False, reset=None, events=None, threshold_location=None, dt=None, clock=None, order=0, Cm=0.9 * uF / cm**2, Ri=150 * ohm * cm, name="spatialneuron*", dtype=None, namespace=None, method=("exact", "exponential_euler", "rk2", "heun"), method_options=None, ): # #### Prepare and validate equations if isinstance(model, str): model = Equations(model) if not isinstance(model, Equations): raise TypeError( "model has to be a string or an Equations " f"object, is '{type(model)}' instead." ) # Insert the threshold mechanism at the specified location if threshold_location is not None: if hasattr(threshold_location, "_indices"): # assuming this is a method threshold_location = threshold_location._indices() # for now, only a single compartment allowed try: int(threshold_location) except TypeError: raise AttributeError( "Threshold can only be applied on a single location" ) threshold = f"({threshold}) and (i == {str(threshold_location)})" # Check flags (we have point currents) model.check_flags( { DIFFERENTIAL_EQUATION: ("point current",), PARAMETER: ("constant", "shared", "linked", "point current"), SUBEXPRESSION: ("shared", "point current", "constant over dt"), } ) #: The original equations as specified by the user (i.e. before #: inserting point-currents into the membrane equation, before adding #: all the internally used variables and constants, etc.). self.user_equations = model # Separate subexpressions depending whether they are considered to be # constant over a time step or not (this would also be done by the # NeuronGroup initializer later, but this would give incorrect results # for the linearity check) model, constant_over_dt = extract_constant_subexpressions(model) # Extract membrane equation if "Im" in model: if len(model["Im"].flags): raise TypeError( "Cannot specify any flags for the transmembrane current 'Im'." ) membrane_expr = model["Im"].expr # the membrane equation else: raise TypeError("The transmembrane current 'Im' must be defined") model_equations = [] # Insert point currents in the membrane equation for eq in model.values(): if eq.varname == "Im": continue # ignore -- handled separately if "point current" in eq.flags: fail_for_dimension_mismatch( eq.dim, amp, f"Point current {eq.varname} should be in amp" ) membrane_expr = Expression( f"{str(membrane_expr.code)}+{eq.varname}/area" ) eq = SingleEquation( eq.type, eq.varname, eq.dim, expr=eq.expr, flags=list(set(eq.flags) - {"point current"}), ) model_equations.append(eq) model_equations.append( SingleEquation( SUBEXPRESSION, "Im", dimensions=(amp / meter**2).dim, expr=membrane_expr, ) ) model_equations.append(SingleEquation(PARAMETER, "v", volt.dim)) model = Equations(model_equations) ###### Process model equations (Im) to extract total conductance and the remaining current # Expand expressions in the membrane equation for var, expr in model.get_substituted_expressions(include_subexpressions=True): if var == "Im": Im_expr = expr break else: raise AssertionError("Model equations did not contain Im!") # Differentiate Im with respect to v Im_sympy_exp = str_to_sympy(Im_expr.code) v_sympy = sp.Symbol("v", real=True) diffed = sp.diff(Im_sympy_exp, v_sympy) unevaled_derivatives = diffed.atoms(sp.Derivative) if len(unevaled_derivatives): raise TypeError( f"Cannot take the derivative of '{Im_expr.code}' with respect to v." ) gtot_str = sympy_to_str(sp.simplify(-diffed)) I0_str = sympy_to_str(sp.simplify(Im_sympy_exp - diffed * v_sympy)) if gtot_str == "0": gtot_str += "*siemens/meter**2" if I0_str == "0": I0_str += "*amp/meter**2" gtot_str = f"gtot__private={gtot_str}: siemens/meter**2" I0_str = f"I0__private={I0_str}: amp/meter**2" model += Equations(f"{gtot_str}\n{I0_str}") # Insert morphology (store a copy) self.morphology = copy.deepcopy(morphology) # Flatten the morphology self.flat_morphology = FlatMorphology(morphology) # Equations for morphology # TODO: check whether Cm and Ri are already in the equations # no: should be shared instead of constant # yes: should be constant (check) eqs_constants = Equations( """ length : meter (constant) distance : meter (constant) area : meter**2 (constant) volume : meter**3 Ic : amp/meter**2 diameter : meter (constant) Cm : farad/meter**2 (constant) Ri : ohm*meter (constant, shared) r_length_1 : meter (constant) r_length_2 : meter (constant) time_constant = Cm/gtot__private : second space_constant = (2/pi)**(1.0/3.0) * (area/(1/r_length_1 + 1/r_length_2))**(1.0/6.0) / (2*(Ri*gtot__private)**(1.0/2.0)) : meter """ ) if self.flat_morphology.has_coordinates: eqs_constants += Equations( """ x : meter (constant) y : meter (constant) z : meter (constant) """ ) NeuronGroup.__init__( self, morphology.total_compartments, model=model + eqs_constants, method_options=method_options, threshold=threshold, refractory=refractory, reset=reset, events=events, method=method, dt=dt, clock=clock, order=order, namespace=namespace, dtype=dtype, name=name, ) # Parameters and intermediate variables for solving the cable equations # Note that some of these variables could have meaningful physical # units (e.g. _v_star is in volt, _I0_all is in amp/meter**2 etc.) but # since these variables should never be used in user code, we don't # assign them any units self.variables.add_arrays( [ "_ab_star0", "_ab_star1", "_ab_star2", "_b_plus", "_b_minus", "_v_star", "_u_plus", "_u_minus", "_v_previous", "_c", # The following two are only necessary for # C code where we cannot deal with scalars # and arrays interchangeably: "_I0_all", "_gtot_all", ], size=self.N, read_only=True, ) self.Cm = Cm self.Ri = Ri # These explict assignments will load the morphology values from disk # in standalone mode self.distance_ = self.flat_morphology.distance self.length_ = self.flat_morphology.length self.area_ = self.flat_morphology.area self.diameter_ = self.flat_morphology.diameter self.volume_ = self.flat_morphology.volume self.r_length_1_ = self.flat_morphology.r_length_1 self.r_length_2_ = self.flat_morphology.r_length_2 if self.flat_morphology.has_coordinates: self.x_ = self.flat_morphology.x self.y_ = self.flat_morphology.y self.z_ = self.flat_morphology.z # Performs numerical integration step self.add_attribute("diffusion_state_updater") self.diffusion_state_updater = SpatialStateUpdater( self, method, clock=self.clock, order=order ) # Update v after the gating variables to obtain consistent Ic and Im self.diffusion_state_updater.order = 1 # Creation of contained_objects that do the work self.contained_objects.extend([self.diffusion_state_updater]) if len(constant_over_dt): self.subexpression_updater = SubexpressionUpdater(self, constant_over_dt) self.contained_objects.append(self.subexpression_updater) def __getattr__(self, name): """ Subtrees are accessed by attribute, e.g. neuron.axon. """ return self.spatialneuron_attribute(self, name) def __getitem__(self, item): """ Selects a segment, where x is a slice of either compartment indexes or distances. Note a: segment is not a SpatialNeuron, only a Group. """ return self.spatialneuron_segment(self, item) @staticmethod def _find_subtree_end(morpho): """ Go down a morphology recursively to find the (absolute) index of the "final" compartment (i.e. the one with the highest index) of the subtree. Parameters ---------- morpho : `Morphology` The morphology for which to find the index. Returns ------- index : int The highest index within the subtree. """ indices = [morpho.indices[-1]] for child in morpho.children: indices.append(SpatialNeuron._find_subtree_end(child)) return max(indices) @staticmethod def spatialneuron_attribute(neuron, name): """ Selects a subtree from `SpatialNeuron` neuron and returns a `SpatialSubgroup`. If it does not exist, returns the `Group` attribute. """ if name == "main": # Main section, without the subtrees indices = neuron.morphology.indices[:] start, stop = indices[0], indices[-1] return SpatialSubgroup( neuron, start, stop + 1, morphology=neuron.morphology ) elif (name != "morphology") and ( (name in getattr(neuron.morphology, "children", [])) or all([c in "LR123456789" for c in name]) ): # subtree morpho = neuron.morphology[name] start = morpho.indices[0] stop = SpatialNeuron._find_subtree_end(morpho) return SpatialSubgroup(neuron, start, stop + 1, morphology=morpho) else: return Group.__getattr__(neuron, name) @staticmethod def spatialneuron_segment(neuron, item): """ Selects a segment from `SpatialNeuron` neuron, where item is a slice of either compartment indexes or distances. Note a: segment is not a `SpatialNeuron`, only a `Group`. """ if isinstance(item, slice) and isinstance(item.start, Quantity): if item.step is not None: raise ValueError( "Cannot specify a step size for slicing basedon length." ) start, stop = item.start, item.stop if not have_same_dimensions(start, meter) or not have_same_dimensions( stop, meter ): raise DimensionMismatchError( "Start and stop should have units of meter", start, stop ) # Convert to integers (compartment numbers) indices = neuron.morphology.indices[item] start, stop = indices[0], indices[-1] + 1 elif not isinstance(item, slice) and hasattr(item, "indices"): start, stop = to_start_stop(item.indices[:], neuron._N) else: start, stop = to_start_stop(item, neuron._N) if isinstance(neuron, SpatialSubgroup): start += neuron.start stop += neuron.start if start >= stop: raise IndexError( f"Illegal start/end values for subgroup, {int(start)}>={int(stop)}" ) if isinstance(neuron, SpatialSubgroup): # Note that the start/stop values calculated above are always # absolute values, even for subgroups neuron = neuron.source return Subgroup(neuron, start, stop) class SpatialSubgroup(Subgroup): """ A subgroup of a `SpatialNeuron`. Parameters ---------- source : int First compartment. stop : int Ending compartment, not included (as in slices). morphology : `Morphology` Morphology corresponding to the subgroup (not the full morphology). name : str, optional Name of the subgroup. """ def __init__(self, source, start, stop, morphology, name=None): self.morphology = morphology if isinstance(source, SpatialSubgroup): source = source.source start += source.start stop += source.start Subgroup.__init__(self, source, start, stop, name) def __getattr__(self, name): return SpatialNeuron.spatialneuron_attribute(self, name) def __getitem__(self, item): return SpatialNeuron.spatialneuron_segment(self, item) class SpatialStateUpdater(CodeRunner, Group): """ The `CodeRunner` that updates the state variables of a `SpatialNeuron` at every timestep. """ def __init__(self, group, method, clock, order=0): # group is the neuron (a group of compartments) self.method_choice = method self.group = weakref.proxy(group) compartments = group.flat_morphology.n sections = group.flat_morphology.sections CodeRunner.__init__( self, group, "spatialstateupdate", code="""_gtot = gtot__private _I0 = I0__private""", clock=clock, when="groups", order=order, name=f"{group.name}_spatialstateupdater*", check_units=False, template_kwds={"number_sections": sections}, ) self.variables = Variables(self, default_index="_section_idx") self.variables.add_reference("N", group) # One value per compartment self.variables.add_arange("_compartment_idx", size=compartments) self.variables.add_array( "_invr", dimensions=siemens.dim, size=compartments, constant=True, index="_compartment_idx", ) # one value per section self.variables.add_arange("_section_idx", size=sections) self.variables.add_array( "_P_parent", size=sections, constant=True ) # elements below diagonal self.variables.add_arrays( ["_morph_idxchild", "_morph_parent_i", "_starts", "_ends"], size=sections, dtype=np.int32, constant=True, ) self.variables.add_arrays( ["_invr0", "_invrn"], dimensions=siemens.dim, size=sections, constant=True ) # one value per section + 1 value for the root self.variables.add_arange("_section_root_idx", size=sections + 1) self.variables.add_array( "_P_diag", size=sections + 1, constant=True, index="_section_root_idx" ) self.variables.add_array( "_B", size=sections + 1, constant=True, index="_section_root_idx" ) self.variables.add_array( "_morph_children_num", size=sections + 1, dtype=np.int32, constant=True, index="_section_root_idx", ) # 2D matrices of size (sections + 1) x max children per section self.variables.add_arange( "_morph_children_idx", size=len(group.flat_morphology.morph_children) ) self.variables.add_array( "_P_children", size=len(group.flat_morphology.morph_children), index="_morph_children_idx", constant=True, ) # elements above diagonal self.variables.add_array( "_morph_children", size=len(group.flat_morphology.morph_children), dtype=np.int32, constant=True, index="_morph_children_idx", ) self._enable_group_attributes() self._morph_parent_i = group.flat_morphology.morph_parent_i self._morph_children_num = group.flat_morphology.morph_children_num self._morph_children = group.flat_morphology.morph_children self._morph_idxchild = group.flat_morphology.morph_idxchild self._starts = group.flat_morphology.starts self._ends = group.flat_morphology.ends brian2-2.5.4/brian2/sphinxext/000077500000000000000000000000001445201106100161005ustar00rootroot00000000000000brian2-2.5.4/brian2/sphinxext/__init__.py000066400000000000000000000001201445201106100202020ustar00rootroot00000000000000""" Brian-specific extension to the Sphinx documentation generation system. """ brian2-2.5.4/brian2/sphinxext/briandoc.py000066400000000000000000000226501445201106100202400ustar00rootroot00000000000000""" ======== briandoc ======== Sphinx extension that handles docstrings in the Numpy standard format with some brian-specific tweaks. [1] It will: - Convert Parameters etc. sections to field lists. - Convert See Also section to a See also entry. - Renumber references. - Extract the signature from the docstring, if it can't be determined otherwise. .. [1] https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt """ import inspect import pydoc import re from docutils import nodes, statemachine from docutils.parsers.rst import Directive, directives from docutils.statemachine import ViewList from sphinx.domains.c import CDomain from sphinx.domains.python import PythonDomain, PyXRefRole from sphinx.roles import XRefRole from brian2.core.preferences import prefs from .docscrape_sphinx import SphinxDocString, get_doc_object class BrianPrefsDirective(Directive): """ A sphinx 'Directive' for automatically generated documentation of Brian preferences. The directive takes an optional argument, the basename of the preferences to document. In addition, you can specify a `nolinks` option which means that no target links for the references are added. Do this if you document preferences in more then one place. Examples -------- Document one category of preferences and generate links:: .. document_brian_prefs:: core Document all preferences without generating links:: .. document_brian_prefs:: :nolinks: """ required_arguments = 0 optional_arguments = 1 final_argument_whitespace = True option_spec = {"nolinks": directives.flag, "as_file": directives.flag} has_content = False def run(self): # The section that should be documented if len(self.arguments): section = self.arguments[0] else: section = None if "as_file" in self.options: rawtext = prefs.as_file return [nodes.literal_block(text=rawtext)] else: rawtext = prefs.get_documentation(section, "nolinks" not in self.options) include_lines = statemachine.string2lines(rawtext, convert_whitespace=True) self.state_machine.insert_input(include_lines, "Brian preferences") return [] def brianobj_role(role, rawtext, text, lineno, inliner, options=None, content=None): """ A Sphinx role, used as a wrapper for the default `py:obj` role, allowing us to use the simple backtick syntax for brian classes/functions without having to qualify the package for classes/functions that are available after a `from brian2 import *`, e.g `NeuronGroup`. Also allows to directly link to preference names using the same syntax. """ if options is None: options = {} if content is None: content = [] if text in prefs: linktext = text.replace("_", "-").replace(".", "-") text = f"{text} " # Use sphinx's cross-reference role xref = XRefRole(warn_dangling=True) return xref("std:ref", rawtext, text, lineno, inliner, options, content) else: if text and ("~" not in text): try: # A simple class or function name if "." not in text: module = __import__("brian2", fromlist=[str(text)]) imported = getattr(module, str(text), None) if getattr(imported, "__module__", None): text = f"~{imported.__module__}.{text}" if inspect.isfunction(imported): text += "()" # Possibly a method/classmethod/attribute name elif len(text.split(".")) == 2: classname, attrname = text.split(".") # Remove trailing parentheses (will be readded for display) if attrname.endswith("()"): attrname = attrname[:-2] module = __import__("brian2", fromlist=[str(classname)]) imported = getattr(module, str(classname), None) if hasattr(imported, "__module__"): # Add trailing parentheses only for methods not for # attributes if inspect.ismethod(getattr(imported, str(attrname), None)): parentheses = "()" else: parentheses = "" text = ( f"{classname}.{attrname}{parentheses} " f"<{imported.__module__}.{classname}.{attrname}>" ) except ImportError: pass role = "py:obj" py_role = PyXRefRole() return py_role(role, rawtext, text, lineno, inliner, options, content) def mangle_docstrings(app, what, name, obj, options, lines, reference_offset=None): if reference_offset is None: reference_offset = [0] cfg = dict() if what == "module": # Strip top title title_re = re.compile(r"^\s*[#*=]{4,}\n[a-z0-9 -]+\n[#*=]{4,}\s*", re.I | re.S) lines[:] = title_re.sub("", "\n".join(lines)).split("\n") exported_members = getattr(obj, "__all__", None) if exported_members: lines.append("*Exported members:* ") # do not print more than 25 members lines.append(", ".join([f"`{member}`" for member in exported_members[:25]])) if len(exported_members) > 25: lines.append(f"... ({int(len(exported_members) - 25)} more members)") lines.append("") else: doc = get_doc_object(obj, what, "\n".join(lines), name=name, config=cfg) lines[:] = str(doc).split("\n") # replace reference numbers so that there are no duplicates references = [] for line in lines: line = line.strip() m = re.match(r"^.. \[([a-z0-9_.-])\]", line, re.I) if m: references.append(m.group(1)) # start renaming from the longest string, to avoid overwriting parts references.sort(key=lambda x: -len(x)) if references: for i in range(len(lines)): for r in references: if re.match(r"^\d+$", r): new_r = f"R{int(reference_offset[0] + int(r))}" else: new_r = f"{r}{int(reference_offset[0])}" lines[i] = lines[i].replace(f"[{r}]_", f"[{new_r}]_") lines[i] = lines[i].replace(f".. [{r}]", f".. [{new_r}]") reference_offset[0] += len(references) def mangle_signature(app, what, name, obj, options, sig, retann): # Do not try to inspect classes that don't define `__init__` if inspect.isclass(obj) and ( not hasattr(obj, "__init__") or "initializes x; see " in pydoc.getdoc(obj.__init__) ): return "", "" if not (callable(obj) or hasattr(obj, "__argspec_is_invalid_")): return if not hasattr(obj, "__doc__"): return doc = SphinxDocString(pydoc.getdoc(obj)) if doc["Signature"]: sig = re.sub("^[^(]*", "", doc["Signature"]) return sig, "" def setup(app, get_doc_object_=get_doc_object): global get_doc_object get_doc_object = get_doc_object_ app.connect("autodoc-process-docstring", mangle_docstrings) app.connect("autodoc-process-signature", mangle_signature) # Extra mangling domains app.add_domain(NumpyPythonDomain) app.add_domain(NumpyCDomain) directives.register_directive("document_brian_prefs", BrianPrefsDirective) # provide the brianobj role with a link to the Python domain app.add_role("brianobj", brianobj_role) # ------------------------------------------------------------------------------ # Docstring-mangling domains # ------------------------------------------------------------------------------ class ManglingDomainBase: directive_mangling_map = {} def __init__(self, *a, **kw): super().__init__(*a, **kw) self.wrap_mangling_directives() def wrap_mangling_directives(self): for name, objtype in self.directive_mangling_map.items(): self.directives[name] = wrap_mangling_directive( self.directives[name], objtype ) class NumpyPythonDomain(ManglingDomainBase, PythonDomain): name = "np" directive_mangling_map = { "function": "function", "class": "class", "exception": "class", "method": "function", "classmethod": "function", "staticmethod": "function", "attribute": "attribute", } class NumpyCDomain(ManglingDomainBase, CDomain): name = "np-c" directive_mangling_map = { "function": "function", "member": "attribute", "macro": "function", "type": "class", "var": "object", } def wrap_mangling_directive(base_directive, objtype): class directive(base_directive): def run(self): env = self.state.document.settings.env name = None if self.arguments: m = re.match(r"^(.*\s+)?(.*?)(\(.*)?", self.arguments[0]) name = m.group(2).strip() if not name: name = self.arguments[0] lines = list(self.content) mangle_docstrings(env.app, objtype, name, None, None, lines) self.content = ViewList(lines, self.content.parent) return base_directive.run(self) return directive brian2-2.5.4/brian2/sphinxext/docscrape.py000066400000000000000000000371371445201106100204300ustar00rootroot00000000000000"""Extract reference documentation from the NumPy source tree. """ import inspect import pydoc import re import textwrap from warnings import warn from sphinx.pycode import ModuleAnalyzer class Reader: """A line-based string reader.""" def __init__(self, data): """ Parameters ---------- data : str String with lines separated by '\n'. """ if isinstance(data, list): self._str = data else: self._str = data.split("\n") # store string as list of lines self.reset() def __getitem__(self, n): return self._str[n] def reset(self): self._l = 0 # current line nr def read(self): if not self.eof(): out = self[self._l] self._l += 1 return out else: return "" def seek_next_non_empty_line(self): for line in self[self._l :]: if line.strip(): break else: self._l += 1 def eof(self): return self._l >= len(self._str) def read_to_condition(self, condition_func): start = self._l for line in self[start:]: if condition_func(line): return self[start : self._l] self._l += 1 if self.eof(): return self[start : self._l + 1] return [] def read_to_next_empty_line(self): self.seek_next_non_empty_line() def is_empty(line): return not line.strip() return self.read_to_condition(is_empty) def read_to_next_unindented_line(self): def is_unindented(line): return line.strip() and (len(line.lstrip()) == len(line)) return self.read_to_condition(is_unindented) def peek(self, n=0): if self._l + n < len(self._str): return self[self._l + n] else: return "" def is_empty(self): return not "".join(self._str).strip() class NumpyDocString: def __init__(self, docstring, config=None): docstring = textwrap.dedent(docstring).split("\n") self._doc = Reader(docstring) self._parsed_data = { "Signature": "", "Summary": [""], "Extended Summary": [], "Parameters": [], "Returns": [], "Raises": [], "Warns": [], "Other Parameters": [], "Attributes": [], "Methods": [], "See Also": [], "Notes": [], "Warnings": [], "References": "", "Examples": "", "index": {}, } self._parse() def __getitem__(self, key): return self._parsed_data[key] def __setitem__(self, key, val): if key not in self._parsed_data: warn(f"Unknown section {key}") else: self._parsed_data[key] = val def _is_at_section(self): self._doc.seek_next_non_empty_line() if self._doc.eof(): return False l1 = self._doc.peek().strip() # e.g. Parameters if l1.startswith(".. index::"): return True l2 = self._doc.peek(1).strip() # ---------- or ========== return l2.startswith("-" * len(l1)) or l2.startswith("=" * len(l1)) def _strip(self, doc): start = stop = 0 for i, line in enumerate(doc): if line.strip(): start = i break for i, line in enumerate(doc[::-1]): if line.strip(): stop = i break return doc[start:-stop] def _read_to_next_section(self): section = self._doc.read_to_next_empty_line() while not self._is_at_section() and not self._doc.eof(): if not self._doc.peek(-1).strip(): # previous line was empty section += [""] section += self._doc.read_to_next_empty_line() return section def _read_sections(self): while not self._doc.eof(): data = self._read_to_next_section() name = data[0].strip() if name.startswith(".."): # index section yield name, data[1:] elif len(data) < 2: yield StopIteration else: yield name, self._strip(data[2:]) def _parse_param_list(self, content): r = Reader(content) params = [] while not r.eof(): header = r.read().strip() if " : " in header: arg_name, arg_type = header.split(" : ")[:2] else: arg_name, arg_type = header, "" desc = r.read_to_next_unindented_line() desc = dedent_lines(desc) params.append((arg_name, arg_type, desc)) return params _name_rgx = re.compile( r"^\s*(:(?P\w+):`(?P[a-zA-Z0-9_.-]+)`|" r" (?P[a-zA-Z0-9_.-]+))\s*", re.X, ) def _parse_see_also(self, content): """ func_name : Descriptive text continued text another_func_name : Descriptive text func_name1, func_name2, :meth:`func_name`, func_name3 """ items = [] def parse_item_name(text): """Match ':role:`name`' or 'name'""" m = self._name_rgx.match(text) if m: g = m.groups() if g[1] is None: return g[3], None else: return g[2], g[1] raise ValueError(f"{text} is not a item name") def push_item(name, rest): if not name: return name, role = parse_item_name(name) items.append((name, list(rest), role)) del rest[:] current_func = None rest = [] for line in content: if not line.strip(): continue m = self._name_rgx.match(line) if m and line[m.end() :].strip().startswith(":"): push_item(current_func, rest) current_func, line = line[: m.end()], line[m.end() :] rest = [line.split(":", 1)[1].strip()] if not rest[0]: rest = [] elif not line.startswith(" "): push_item(current_func, rest) current_func = None if "," in line: for func in line.split(","): if func.strip(): push_item(func, []) elif line.strip(): current_func = line elif current_func is not None: rest.append(line.strip()) push_item(current_func, rest) return items def _parse_index(self, section, content): """ .. index: default :refguide: something, else, and more """ def strip_each_in(lst): return [s.strip() for s in lst] out = {} section = section.split("::") if len(section) > 1: out["default"] = strip_each_in(section[1].split(","))[0] for line in content: line = line.split(":") if len(line) > 2: out[line[1]] = strip_each_in(line[2].split(",")) return out def _parse_summary(self): """Grab signature (if given) and summary""" if self._is_at_section(): return summary = self._doc.read_to_next_empty_line() summary_str = " ".join([s.strip() for s in summary]).strip() if re.compile(r"^([\w., ]+=)?\s*[\w.]+\(.*\)$").match(summary_str): self["Signature"] = summary_str if not self._is_at_section(): self["Summary"] = self._doc.read_to_next_empty_line() else: self["Summary"] = summary if not self._is_at_section(): self["Extended Summary"] = self._read_to_next_section() def _parse(self): self._doc.reset() self._parse_summary() for section, content in self._read_sections(): if not section.startswith(".."): section = " ".join([s.capitalize() for s in section.split(" ")]) if section in ( "Parameters", "Returns", "Raises", "Warns", "Other Parameters", "Attributes", "Methods", ): self[section] = self._parse_param_list(content) elif section.startswith(".. index::"): self["index"] = self._parse_index(section, content) elif section == "See Also": self["See Also"] = self._parse_see_also(content) else: self[section] = content # string conversion routines def _str_header(self, name, symbol="-"): return [name, len(name) * symbol] def _str_indent(self, doc, indent=4): out = [] for line in doc: out += [" " * indent + line] return out def _str_signature(self): if self["Signature"]: return [self["Signature"].replace("*", r"\*")] + [""] else: return [""] def _str_summary(self): if self["Summary"]: return self["Summary"] + [""] else: return [] def _str_extended_summary(self): if self["Extended Summary"]: return self["Extended Summary"] + [""] else: return [] def _str_param_list(self, name): out = [] if self[name]: out += self._str_header(name) for param, param_type, desc in self[name]: out += [f"{param} : {param_type}"] out += self._str_indent(desc) out += [""] return out def _str_section(self, name): out = [] if self[name]: out += self._str_header(name) out += self[name] out += [""] return out def _str_see_also(self, func_role): if not self["See Also"]: return [] out = [] out += self._str_header("See Also") last_had_desc = True for func, desc, role in self["See Also"]: if role: link = f":{role}:`{func}`" elif func_role: link = f":{func_role}:`{func}`" else: link = f"`{func}`_" if desc or last_had_desc: out += [""] out += [link] else: out[-1] += f", {link}" if desc: out += self._str_indent([" ".join(desc)]) last_had_desc = True else: last_had_desc = False out += [""] return out def _str_index(self): idx = self["index"] out = [] out += [f".. index:: {idx.get('default', '')}"] for section, references in idx.items(): if section == "default": continue out += [f" :{section}: {', '.join(references)}"] return out def __str__(self, func_role=""): out = [] out += self._str_signature() out += self._str_summary() out += self._str_extended_summary() for param_list in ( "Parameters", "Returns", "Other Parameters", "Raises", "Warns", ): out += self._str_param_list(param_list) out += self._str_section("Warnings") out += self._str_see_also(func_role) for s in ("Notes", "References", "Examples"): out += self._str_section(s) for param_list in ("Attributes", "Methods"): out += self._str_param_list(param_list) out += self._str_index() return "\n".join(out) def indent(str, indent=4): indent_str = " " * indent if str is None: return indent_str lines = str.split("\n") return "\n".join(indent_str + line for line in lines) def dedent_lines(lines): """Deindent a list of lines maximally""" return textwrap.dedent("\n".join(lines)).split("\n") def header(text, style="-"): return f"{text}\n{style * len(text)}\n" class FunctionDoc(NumpyDocString): def __init__(self, func, role="func", doc=None, config=None): self._f = func self._role = role # e.g. "func" or "meth" if doc is None: if func is None: raise ValueError("No function or docstring given") doc = inspect.getdoc(func) or "" NumpyDocString.__init__(self, doc) if not self["Signature"] and func is not None: func, func_name = self.get_func() try: # try to read signature argspec = str(inspect.signature(func)) argspec = argspec.replace("*", r"\*") signature = f"{func_name}{argspec}" except (TypeError, ValueError): signature = f"{func_name}()" self["Signature"] = signature def get_func(self): func_name = getattr(self._f, "__name__", self.__class__.__name__) if inspect.isclass(self._f): if callable(self._f): func = self._f.__call__ else: func = self._f.__init__ else: func = self._f return func, func_name def __str__(self): out = "" _, func_name = self.get_func() roles = {"func": "function", "meth": "method"} if self._role: if self._role not in roles: print(f"Warning: invalid role {self._role}") out += f".. {roles.get(self._role, '')}:: {func_name}\n \n\n" out += super().__str__(func_role=self._role) return out class ClassDoc(NumpyDocString): extra_public_methods = ["__call__"] def __init__(self, cls, doc=None, modulename="", func_doc=FunctionDoc, config=None): if not inspect.isclass(cls) and cls is not None: raise ValueError(f"Expected a class or None, but got {cls!r}") self._cls = cls if modulename and not modulename.endswith("."): modulename += "." self._mod = modulename if doc is None: if cls is None: raise ValueError("No class or documentation string given") doc = pydoc.getdoc(cls) NumpyDocString.__init__(self, doc) if not self["Methods"]: self["Methods"] = [(name, "", "") for name in sorted(self.methods)] if not self["Attributes"]: self["Attributes"] = [(name, "", "") for name in sorted(self.properties)] @property def methods(self): if self._cls is None: return [] methods = [ name for name, func in self._cls.__dict__.items() if ( (not name.startswith("_") or name in self.extra_public_methods) and ( (callable(func) and not isinstance(func, type)) or inspect.ismethoddescriptor(func) ) ) ] return methods @property def properties(self): if self._cls is None: return [] analyzer = ModuleAnalyzer.for_module(self._cls.__module__) instance_members = { attr_name for (class_name, attr_name) in analyzer.find_attr_docs().keys() if class_name == self._cls.__name__ } class_members = { name for name, func in self._cls.__dict__.items() if not name.startswith("_") and (func is None or inspect.isdatadescriptor(func)) } return instance_members | class_members brian2-2.5.4/brian2/sphinxext/docscrape_sphinx.py000066400000000000000000000210061445201106100220050ustar00rootroot00000000000000import inspect import pydoc import re import textwrap from sphinx.pycode import ModuleAnalyzer from .docscrape import ClassDoc, FunctionDoc, NumpyDocString class SphinxDocString(NumpyDocString): def __init__(self, docstring, config=None): if config is None: config = {} NumpyDocString.__init__(self, docstring, config=config) # string conversion routines @staticmethod def _str_header(name, symbol="`"): return [f".. rubric:: {name}", ""] @staticmethod def _str_field_list(name): return [f":{name}:"] @staticmethod def _str_indent(doc, indent=4): out = [] for line in doc: out += [" " * indent + line] return out def _str_summary(self): return self["Summary"] + [""] def _str_extended_summary(self): return self["Extended Summary"] + [""] def _str_param_list(self, name): out = [] if self[name]: out += self._str_field_list(name) out += [""] for param, param_type, desc in self[name]: out += self._str_indent([f"**{param.strip()}** : {param_type}"]) out += [""] out += self._str_indent(desc, 8) out += [""] return out @property def _obj(self): if hasattr(self, "_cls"): return self._cls elif hasattr(self, "_f"): return self._f return None def _str_member_list(self): """ Generate a member listing, autosummary:: table . """ out = [] for name in ["Attributes", "Methods"]: if not self[name]: continue out += [f".. rubric:: {name}", ""] prefix = getattr(self, "_name", "") if prefix: prefix = f"{prefix}." autosum = [] for param, _, desc in self[name]: param = param.strip() if self._obj: # Fake the attribute as a class property, but do not touch # methods if hasattr(self._obj, "__module__") and not ( hasattr(self._obj, param) and callable(getattr(self._obj, param)) ): # Do not override directly provided docstrings if not len("".join(desc).strip()): analyzer = ModuleAnalyzer.for_module(self._obj.__module__) desc = analyzer.find_attr_docs().get( (self._obj.__name__, param), "" ) # Only fake a property if we got a docstring if len("".join(desc).strip()): setattr( self._obj, param, property(lambda self: None, doc="\n".join(desc)), ) if len(prefix): autosum += [f" ~{prefix}{param}"] else: autosum += [f" {param}"] if autosum: out += [".. autosummary::", ""] out += autosum out += [""] return out def _str_member_docs(self, name): """ Generate the full member autodocs """ out = [] if self[name]: prefix = getattr(self, "_name", "") if prefix: prefix += "." for param, _, _ in self[name]: if name == "Methods": out += [f".. automethod:: {prefix}{param}"] elif name == "Attributes": out += [f".. autoattribute:: {prefix}{param}"] out += [""] return out def _str_section(self, name): out = [] if self[name]: out += self._str_header(name) out += [""] content = textwrap.dedent("\n".join(self[name])).split("\n") out += content out += [""] return out def _str_see_also(self, func_role): out = [] if self["See Also"]: see_also = super()._str_see_also(func_role) out = [".. seealso::", ""] out += self._str_indent(see_also[2:]) return out def _str_raises(self, name, func_role): if not self[name]: return [] out = [] out += self._str_header(name) for func, _, desc in self[name]: out += [f":exc:`{func}`"] if desc: out += self._str_indent([" ".join(desc)]) out += [""] return out def _str_warnings(self): out = [] if self["Warnings"]: out = [".. warning::", ""] out += self._str_indent(self["Warnings"]) return out def _str_index(self): idx = self["index"] out = [] if len(idx) == 0: return out out += [f".. index:: {idx.get('default', '')}"] for section, references in idx.items(): if section == "default": continue elif section == "refguide": out += [f" single: {', '.join(references)}"] else: out += [f" {section}: {','.join(references)}"] return out def _str_references(self): out = [] if self["References"]: out += self._str_header("References") if isinstance(self["References"], str): self["References"] = [self["References"]] out.extend(self["References"]) out += [""] # Latex collects all references to a separate bibliography, # so we need to insert links to it out += [".. only:: latex", ""] items = [] for line in self["References"]: m = re.match(r".. \[([a-z0-9._-]+)\]", line, re.I) if m: items.append(m.group(1)) out += [f" {', '.join([f'[{item}]_' for item in items])}", ""] return out def _str_examples(self): return self._str_section("Examples") def __str__(self, indent=0, func_role="brianobj"): out = [] out += self._str_index() + [""] out += self._str_summary() out += self._str_extended_summary() for param_list in ("Parameters", "Returns", "Other Parameters"): out += self._str_param_list(param_list) for param_list in ("Raises", "Warns"): out += self._str_raises(param_list, func_role) out += self._str_warnings() out += self._str_see_also(func_role) out += self._str_section("Notes") out += self._str_references() out += self._str_examples() out += self._str_member_list() if self["Attributes"] + self["Methods"]: out += [".. rubric:: Details", ""] for param_list in ("Attributes", "Methods"): out += self._str_member_docs(param_list) out = self._str_indent(out, indent) return "\n".join(out) class SphinxFunctionDoc(SphinxDocString, FunctionDoc): def __init__(self, obj, doc=None, config=None): if config is None: config = {} FunctionDoc.__init__(self, obj, doc=doc, config=config) class SphinxClassDoc(SphinxDocString, ClassDoc): def __init__(self, obj, doc=None, func_doc=None, name=None, config=None): if config is None: config = {} self.name = name ClassDoc.__init__(self, obj, doc=doc, func_doc=None, config=config) class SphinxObjDoc(SphinxDocString): def __init__(self, obj, doc=None, config=None): if config is None: config = {} self._f = obj SphinxDocString.__init__(self, doc, config=config) def get_doc_object(obj, what=None, doc=None, name=None, config=None): if config is None: config = {} if what is None: if inspect.isclass(obj): what = "class" elif inspect.ismodule(obj): what = "module" elif callable(obj): what = "function" else: what = "object" if what == "class": return SphinxClassDoc( obj, func_doc=SphinxFunctionDoc, doc=doc, name=name, config=config ) elif what in ("function", "method"): return SphinxFunctionDoc(obj, doc=doc, config=config) else: if doc is None: doc = pydoc.getdoc(obj) return SphinxObjDoc(obj, doc, config=config) brian2-2.5.4/brian2/sphinxext/examplefinder.py000066400000000000000000000052551445201106100213040ustar00rootroot00000000000000""" Automatically find examples of a Brian object or function. """ import os from collections import defaultdict from brian2.utils.stringtools import get_identifiers from .generate_examples import GlobDirectoryWalker __all__ = ["auto_find_examples"] the_examples_map = defaultdict(list) the_tutorials_map = defaultdict(list) def get_map(environ_var, relrootdir, pattern, the_map, path_exclusions=None): if path_exclusions is None: path_exclusions = [] if the_map: return the_map if environ_var in os.environ: rootdir = os.environ[environ_var] else: rootdir, _ = os.path.split(__file__) rootdir = os.path.normpath(os.path.join(rootdir, relrootdir)) fnames = [fname for fname in GlobDirectoryWalker(rootdir, f"*{pattern}")] for exclude in path_exclusions: fnames = [fname for fname in fnames if exclude not in fname] shortfnames = [os.path.relpath(fname, rootdir) for fname in fnames] exnames = [ fname.replace("/", ".").replace("\\", ".").replace(pattern, "") for fname in shortfnames ] for fname, shortfname, exname in zip(fnames, shortfnames, exnames): with open(fname) as f: ex = f.read() ids = get_identifiers(ex) for id in ids: the_map[id].append((shortfname.replace("\\", "/"), exname)) return the_map def get_examples_map(): return get_map("BRIAN2_DOCS_EXAMPLE_DIR", "../../examples", ".py", the_examples_map) def get_tutorials_map(): return get_map( "BRIAN2_DOCS_TUTORIALS_DIR", "../../tutorials", ".ipynb", the_tutorials_map, path_exclusions=[".ipynb_checkpoints"], ) def auto_find_examples(obj, headersymbol="="): """ Returns a restructured text section listing all the examples and tutorials making use of the specified object (as determined by the name being in the list of identifiers, which may occasionally make mistakes but is usually going to be correct). """ name = obj.__name__ examples = sorted(the_examples_map[name]) tutorials = sorted(the_tutorials_map[name]) if len(examples + tutorials) == 0: return "" txt = "Tutorials and examples using this" txt = f"{txt}\n{headersymbol * len(txt)}\n\n" for tutname, tutloc in tutorials: tutname = tutname.replace(".ipynb", "") txt += f"* Tutorial :doc:`{tutname} `\n" for exname, exloc in examples: exname = exname.replace(".py", "") txt += f"* Example :doc:`{exname} `\n" return f"{txt}\n" if __name__ == "__main__": from brian2 import NeuronGroup print(auto_find_examples(NeuronGroup)) brian2-2.5.4/brian2/sphinxext/generate_examples.py000066400000000000000000000167471445201106100221610ustar00rootroot00000000000000import fnmatch import glob import os import shutil from collections import defaultdict class GlobDirectoryWalker: # a forward iterator that traverses a directory tree def __init__(self, directory, pattern="*"): self.stack = [directory] self.pattern = pattern self.files = [] self.index = 0 def __getitem__(self, index): while True: try: file = self.files[self.index] self.index = self.index + 1 except IndexError: # pop next directory from stack self.directory = self.stack.pop() if os.path.isdir(self.directory): self.files = os.listdir(self.directory) else: self.files = [] self.index = 0 else: # got a filename fullname = os.path.join(self.directory, file) if os.path.isdir(fullname) and not os.path.islink(fullname): self.stack.append(fullname) if fnmatch.fnmatch(file, self.pattern): return fullname def main(rootpath, destdir): if not os.path.exists(destdir): shutil.os.makedirs(destdir) examplesfnames = [fname for fname in GlobDirectoryWalker(rootpath, "*.py")] additional_files = [ fname for fname in GlobDirectoryWalker(rootpath, "*.[!py]*") if not os.path.basename(fname) == ".gitignore" ] print(f"Documenting {len(examplesfnames)} examples") examplespaths = [] examplesbasenames = [] relativepaths = [] outnames = [] for f in examplesfnames: path, file = os.path.split(f) relpath = os.path.relpath(path, rootpath) if relpath == ".": relpath = "" path = os.path.normpath(path) filebase, ext = os.path.splitext(file) exname = filebase if relpath: exname = relpath.replace("/", ".").replace("\\", ".") + "." + exname examplespaths.append(path) examplesbasenames.append(filebase) relativepaths.append(relpath) outnames.append(exname) # We assume all files are encoded as UTF-8 examplescode = [] for fname in examplesfnames: with open(fname, encoding="utf-8") as f: examplescode.append(f.read()) examplesdocs = [] examplesafterdoccode = [] for code in examplescode: codesplit = code.split("\n") comment_lines = 0 for line in codesplit: if line.startswith("#") or len(line) == 0: comment_lines += 1 else: break codesplit = codesplit[comment_lines:] readingdoc = False doc = [] afterdoccode = "" for i in range(len(codesplit)): stripped = codesplit[i].strip() if stripped[:3] == '"""' or stripped[:3] == "'''": if not readingdoc: readingdoc = True else: afterdoccode = "\n".join(codesplit[i + 1 :]) break elif readingdoc: doc.append(codesplit[i]) else: # No doc afterdoccode = "\n".join(codesplit[i:]) break examplesdocs.append("\n".join(doc)) examplesafterdoccode.append(afterdoccode) categories = defaultdict(list) examples = zip( examplesfnames, examplespaths, examplesbasenames, examplescode, examplesdocs, examplesafterdoccode, relativepaths, outnames, ) # Get the path relative to the examples director (not relative to the # directory where this file is installed if "BRIAN2_DOCS_EXAMPLE_DIR" in os.environ: rootdir = os.environ["BRIAN2_DOCS_EXAMPLE_DIR"] else: rootdir, _ = os.path.split(__file__) rootdir = os.path.normpath(os.path.join(rootdir, "../../examples")) eximgpath = os.path.abspath( os.path.join(rootdir, "../docs_sphinx/resources/examples_images") ) print("Searching for example images in directory", eximgpath) for _fname, _path, basename, _code, docs, afterdoccode, relpath, exname in examples: categories[relpath].append((exname, basename)) title = "Example: " + basename output = ".. currentmodule:: brian2\n\n" output += ".. " + basename + ":\n\n" output += title + "\n" + "=" * len(title) + "\n\n" note = f""" .. only:: html .. |launchbinder| image:: http://mybinder.org/badge.svg .. _launchbinder: https://mybinder.org/v2/gh/brian-team/brian2-binder/master?filepath=examples/{exname.replace('.', '/')}.ipynb .. note:: You can launch an interactive, editable version of this example without installing any local files using the Binder service (although note that at some times this may be slow or fail to open): |launchbinder|_ """ output += note + "\n\n" output += docs + "\n\n::\n\n" output += "\n".join([" " + line for line in afterdoccode.split("\n")]) output += "\n\n" eximgpattern = os.path.join(eximgpath, f"{exname}.*") images = glob.glob(eximgpattern + ".png") + glob.glob(eximgpattern + ".gif") for image in sorted(images): _, image = os.path.split(image) print("Found example image file", image) output += f".. image:: ../resources/examples_images/{image}\n\n" with open(os.path.join(destdir, exname + ".rst"), "w", encoding="utf-8") as f: f.write(output) category_additional_files = defaultdict(list) for fname in additional_files: path, file = os.path.split(fname) relpath = os.path.relpath(path, rootpath) if relpath == ".": relpath = "" full_name = relpath.replace("/", ".").replace("\\", ".") + "." + file + ".rst" category_additional_files[relpath].append((file, full_name)) with open(fname, encoding="utf-8") as f: print(fname) content = f.read() output = file + "\n" + "=" * len(file) + "\n\n" output += ".. code:: none\n\n" content_lines = ["\t" + line for line in content.split("\n")] output += "\n".join(content_lines) output += "\n\n" with open(os.path.join(destdir, full_name), "w", encoding="utf-8") as f: f.write(output) mainpage_text = "Examples\n" mainpage_text += "========\n\n" def insert_category(category, mainpage_text): if category: label = category.lower().replace(" ", "-").replace("/", ".") mainpage_text += f"\n.. _{label}:\n\n" mainpage_text += "\n" + category + "\n" + "-" * len(category) + "\n\n" mainpage_text += ".. toctree::\n" mainpage_text += " :maxdepth: 1\n\n" for exname, basename in sorted(categories[category]): mainpage_text += f" {basename} <{exname}>\n" for fname, full_name in sorted(category_additional_files[category]): mainpage_text += f" {fname} <{full_name}>\n" return mainpage_text mainpage_text = insert_category("", mainpage_text) for category in sorted(categories.keys()): if category: mainpage_text = insert_category(category, mainpage_text) with open(os.path.join(destdir, "index.rst"), "w") as f: f.write(mainpage_text) if __name__ == "__main__": main("../../examples", "../../docs_sphinx/examples") brian2-2.5.4/brian2/sphinxext/generate_reference.py000066400000000000000000000226151445201106100222700ustar00rootroot00000000000000""" Automatically generate Brian's reference documentation. Based on sphinx-apidoc, published under a BSD license: http://sphinx-doc.org/ """ import inspect import os import sys from os import path from .examplefinder import auto_find_examples INITPY = "__init__.py" OPTIONS = ["show-inheritance"] def makename(package, module): """Join package and module with a dot.""" # Both package and module can be None/empty. if package: name = package if module: name += f".{module}" else: name = module return name def write_file(name, text, destdir, suffix): """Write the output file for module/package .""" fname = path.join(destdir, f"{name}.{suffix}") print(f"Creating file {fname}.") f = open(fname, "w") try: f.write(text) finally: f.close() def format_heading(level, text): """Create a heading of [1, 2 or 3 supported].""" underlining = ["=", "-", "~"][level - 1] * len(text) return f"{text}\n{underlining}\n\n" def format_directive(module, destdir, package=None, basename="brian2"): """Create the automodule directive and add the options.""" directive = f".. automodule:: {makename(package, module)}\n" for option in OPTIONS: directive += f" :{option}:\n" directive += "\n" # document all the classes in the modules full_name = f"{basename}.{module}" __import__(full_name) mod = sys.modules[full_name] dir_members = dir(mod) classes = [] functions = [] variables = [] for member in dir_members: _temp = __import__(full_name, {}, {}, [member], 0) member_obj = getattr(_temp, member) member_module = getattr(member_obj, "__module__", None) # only document members that where defined in this module if member_module == full_name and not member.startswith("_"): if inspect.isclass(member_obj): classes.append((member, member_obj)) elif inspect.isfunction(member_obj): functions.append((member, member_obj)) else: variables.append((member, member_obj)) if classes: directive += "**Classes**\n\n" for member, member_obj in classes: directive += f".. autosummary:: {member}\n" directive += " :toctree:\n\n" create_member_file(full_name, member, member_obj, destdir) if functions: directive += "**Functions**\n\n" for member, member_obj in functions: directive += f".. autosummary:: {member}\n" directive += " :toctree:\n\n" create_member_file(full_name, member, member_obj, destdir) if variables: directive += "**Objects**\n\n" for member, member_obj in variables: directive += f".. autosummary:: {member}\n" directive += " :toctree:\n\n" create_member_file(full_name, member, member_obj, destdir) return directive def find_shortest_import(module_name, obj_name): parts = module_name.split(".") for idx in range(1, len(parts) + 1): try: result = __import__( ".".join(parts[:idx]), globals(), {}, fromlist=[str(obj_name)], level=0 ) result_obj = getattr(result, obj_name, None) if ( result_obj is not None and getattr(result_obj, "__module__", None) == module_name ): # import seems to have worked return ".".join(parts[:idx]) except ImportError: pass raise AssertionError(f"Couldn't import {module_name}.{obj_name}") def create_member_file(module_name, member, member_obj, destdir, suffix="rst"): """Build the text of the file and write the file.""" text = f".. currentmodule:: {module_name}\n\n" shortest_import = find_shortest_import(module_name, member) import_text = f"(*Shortest import*: ``from {shortest_import} import {member})``\n\n" if inspect.isclass(member_obj): text += format_heading(1, f"{member} class") text += import_text text += f".. autoclass:: {member}\n\n" text += auto_find_examples(member_obj, headersymbol="-") elif inspect.isfunction(member_obj): text += format_heading(1, f"{member} function") text += import_text text += f".. autofunction:: {member}\n\n" else: text += format_heading(1, f"{member} object") text += import_text text += f".. autodata:: {member}\n" write_file(makename(module_name, member), text, destdir, suffix) def create_package_file( root, master_package, subroot, py_files, subs, destdir, excludes, suffix="rst" ): """Build the text of the file and write the file.""" package = path.split(root)[-1] text = format_heading(1, f"{package} package") # add each module in the package for py_file in py_files: if shall_skip(path.join(root, py_file)): continue is_package = py_file == INITPY py_file = path.splitext(py_file)[0] py_path = makename(subroot, py_file) # we don't want an additional header for the package, if not is_package: heading = f":mod:`{py_file}` module" text += format_heading(2, heading) text += format_directive( is_package and subroot or py_path, destdir, master_package ) text += "\n" # build a list of directories that are packages (contain an INITPY file) subs = [sub for sub in subs if path.isfile(path.join(root, sub, INITPY))] # if there are some package directories, add a TOC for theses subpackages if subs: text += format_heading(2, "Subpackages") text += ".. toctree::\n" text += " :maxdepth: 2\n\n" for sub in subs: if not is_excluded(os.path.join(root, sub), excludes): text += f" {makename(master_package, subroot)}.{sub}\n" text += "\n" write_file(makename(master_package, subroot), text, destdir, suffix) def shall_skip(module): """Check if we want to skip this module.""" # skip it if there is nothing (or just \n or \r\n) in the file return path.getsize(module) <= 2 def recurse_tree(rootpath, exclude_dirs, exclude_files, destdir): """ Look for every file in the directory tree and create the corresponding ReST files. """ # use absolute path for root, as relative paths like '../../foo' cause # 'if "/." in root ...' to filter out *all* modules otherwise rootpath = path.normpath(path.abspath(rootpath)) # check if the base directory is a package and get its name if INITPY in os.listdir(rootpath): root_package = rootpath.split(path.sep)[-1] else: # otherwise, the base is a directory with packages root_package = None toplevels = [] for root, subs, files in os.walk(rootpath): if is_excluded(root, exclude_dirs): del subs[:] continue # document only Python module files py_files = sorted( [ f for f in files if (path.splitext(f)[1] == ".py" and f not in exclude_files) ] ) is_pkg = INITPY in py_files if is_pkg: py_files.remove(INITPY) py_files.insert(0, INITPY) elif root != rootpath: # only accept non-package at toplevel del subs[:] continue # remove hidden ('.') and private ('_') directories subs[:] = sorted(sub for sub in subs if sub[0] not in [".", "_"]) if is_pkg: # we are in a package with something to document if subs or len(py_files) > 1 or not shall_skip(path.join(root, INITPY)): subpackage = ( root[len(rootpath) :].lstrip(path.sep).replace(path.sep, ".") ) create_package_file( root, root_package, subpackage, py_files, subs, destdir, exclude_dirs, ) toplevels.append(makename(root_package, subpackage)) else: raise AssertionError("Expected it to be a package") return toplevels def normalize_excludes(rootpath, excludes): """ Normalize the excluded directory list: * must be either an absolute path or start with rootpath, * otherwise it is joined with rootpath * with trailing slash """ f_excludes = [] for exclude in excludes: if not path.isabs(exclude) and not exclude.startswith(rootpath): exclude = path.join(rootpath, exclude) f_excludes.append(path.normpath(exclude) + path.sep) return f_excludes def is_excluded(root, excludes): """ Check if the directory is in the exclude list. Note: by having trailing slashes, we avoid common prefix issues, like e.g. an exlude "foo" also accidentally excluding "foobar". """ sep = path.sep if not root.endswith(sep): root += sep for exclude in excludes: if root.startswith(exclude): return True return False def main(rootpath, exclude_dirs, exclude_files, destdir): if not os.path.exists(destdir): os.makedirs(destdir) exclude_dirs = normalize_excludes(rootpath, exclude_dirs) recurse_tree(rootpath, exclude_dirs, exclude_files, destdir) brian2-2.5.4/brian2/stateupdaters/000077500000000000000000000000001445201106100167365ustar00rootroot00000000000000brian2-2.5.4/brian2/stateupdaters/GSL.py000066400000000000000000000217011445201106100177360ustar00rootroot00000000000000""" Module containg the StateUpdateMethod for integration using the ODE solver provided in the GNU Scientific Library (GSL) """ import sys from brian2.utils.logger import get_logger from ..core.preferences import prefs from ..devices.device import RuntimeDevice, all_devices, auto_target from .base import ( StateUpdateMethod, UnsupportedEquationsException, extract_method_options, ) logger = get_logger(__name__) __all__ = ["gsl_rk2", "gsl_rk4", "gsl_rkf45", "gsl_rkck", "gsl_rk8pd"] default_method_options = { "adaptable_timestep": True, "absolute_error": 1e-6, "absolute_error_per_variable": None, "max_steps": 100, "use_last_timestep": True, "save_failed_steps": False, "save_step_count": False, } class GSLContainer: """ Class that contains information (equation- or integrator-related) required for later code generation """ def __init__( self, method_options, integrator, abstract_code=None, needed_variables=None, variable_flags=None, ): if needed_variables is None: needed_variables = [] if variable_flags is None: variable_flags = [] self.method_options = method_options self.integrator = integrator self.abstract_code = abstract_code self.needed_variables = needed_variables self.variable_flags = variable_flags def get_codeobj_class(self): """ Return codeobject class based on target language and device. Choose which version of the GSL `CodeObject` to use. If ```isinstance(device, CPPStandaloneDevice)```, then we want the `GSLCPPStandaloneCodeObject`. Otherwise the return value is based on prefs.codegen.target. Returns ------- code_object : class The respective `CodeObject` class (i.e. either `GSLCythonCodeObject` or `GSLCPPStandaloneCodeObject`). """ # imports in this function to avoid circular imports from brian2.devices.cpp_standalone.device import CPPStandaloneDevice from brian2.devices.device import get_device from ..codegen.runtime.GSLcython_rt import GSLCythonCodeObject device = get_device() if ( device.__class__ is CPPStandaloneDevice ): # We do not want to accept subclasses here from ..devices.cpp_standalone.GSLcodeobject import ( GSLCPPStandaloneCodeObject, ) # In runtime mode (i.e. Cython), the compiler settings are # added for each `CodeObject` (only the files that use the GSL are # linked to the GSL). However, in C++ standalone mode, there are global # compiler settings that are used for all files (stored in the # `CPPStandaloneDevice`). Furthermore, header file includes are directly # inserted into the template instead of added during the compilation # phase. Therefore, we have to add the options here # instead of in `GSLCPPStandaloneCodeObject` # Add the GSL library if it has not yet been added if "gsl" not in device.libraries: device.libraries += ["gsl", "gslcblas"] device.headers += [ "", "", "", "", "", ] if sys.platform == "win32": device.define_macros += [("WIN32", "1"), ("GSL_DLL", "1")] if prefs.GSL.directory is not None: device.include_dirs += [prefs.GSL.directory] return GSLCPPStandaloneCodeObject elif isinstance(device, RuntimeDevice): if prefs.codegen.target == "auto": target_name = auto_target().class_name else: target_name = prefs.codegen.target if target_name == "cython": return GSLCythonCodeObject raise NotImplementedError( ( "GSL integration has not been implemented for " "for the '{target_name}' code generation target." "\nUse the 'cython' code generation target, " "or switch to the 'cpp_standalone' device." ).format(target_name=target_name) ) else: device_name = [name for name, dev in all_devices.items() if dev is device] assert len(device_name) == 1 raise NotImplementedError( ( "GSL integration has not been implemented for " "for the '{device}' device." "\nUse either the 'cpp_standalone' device, " "or the runtime device with target language " "'cython'." ).format(device=device_name[0]) ) def __call__(self, obj): """ Transfer the code object class saved in self to the object sent as an argument. This method is returned when calling `GSLStateUpdater`. This class inherits from `StateUpdateMethod` which orignally only returns abstract code. However, with GSL this returns a method because more is needed than just the abstract code: the state updater requires its own CodeObject that is different from the other `NeuronGroup` objects. This method adds this `CodeObject` to the `StateUpdater` object (and also adds the variables 't', 'dt', and other variables that are needed in the `GSLCodeGenerator`. Parameters ---------- obj : `GSLStateUpdater` the object that the codeobj_class and other variables need to be transferred to Returns ------- abstract_code : str The abstract code (translated equations), that is returned conventionally by brian and used for later code generation in the `CodeGenerator.translate` method. """ obj.codeobj_class = self.get_codeobj_class() obj._gsl_variable_flags = self.variable_flags obj.method_options = self.method_options obj.integrator = self.integrator obj.needed_variables = ["t", "dt"] + self.needed_variables return self.abstract_code class GSLStateUpdater(StateUpdateMethod): """ A statupdater that rewrites the differential equations so that the GSL generator knows how to write the code in the target language. .. versionadded:: 2.1 """ def __init__(self, integrator): self.integrator = integrator def __call__(self, equations, variables=None, method_options=None): """ Translate equations to abstract_code. Parameters ---------- equations : `Equations` object containing the equations that describe the ODE systemTransferClass(self) variables : dict dictionary containing str, `Variable` pairs Returns ------- method : callable Method that needs to be called with `StateUpdater` to add CodeObject class and some other variables so these can be sent to the `CodeGenerator` """ logger.warn( "Integrating equations with GSL is still considered experimental", once=True ) method_options = extract_method_options(method_options, default_method_options) if equations.is_stochastic: raise UnsupportedEquationsException( "Cannot solve stochastic equations with the GSL state updater." ) # the approach is to 'tag' the differential equation variables so they can # be translated to GSL code diff_eqs = equations.get_substituted_expressions(variables) code = [] count_statevariables = 0 counter = {} diff_vars = [] for diff_name, expr in diff_eqs: # if diff_name does not occur in the right hand side of the equation, Brian does not # know to add the variable to the namespace, so we add it to needed_variables diff_vars += [diff_name] counter[diff_name] = count_statevariables code += [f"_gsl_{diff_name}_f{counter[diff_name]} = {expr}"] count_statevariables += 1 # add flags to variables objects because some of them we need in the GSL generator flags = {} for eq_name, eq_obj in equations._equations.items(): if len(eq_obj.flags) > 0: flags[eq_name] = eq_obj.flags return GSLContainer( method_options=method_options, integrator=self.integrator, abstract_code=("\n").join(code), needed_variables=diff_vars, variable_flags=flags, ) gsl_rk2 = GSLStateUpdater("rk2") gsl_rk4 = GSLStateUpdater("rk4") gsl_rkf45 = GSLStateUpdater("rkf45") gsl_rkck = GSLStateUpdater("rkck") gsl_rk8pd = GSLStateUpdater("rk8pd") brian2-2.5.4/brian2/stateupdaters/__init__.py000066400000000000000000000024641445201106100210550ustar00rootroot00000000000000""" Module for transforming model equations into "abstract code" that can be then be further translated into executable code by the `codegen` module. """ from .base import * from .exact import * from .explicit import * from .exponential_euler import * from .GSL import * StateUpdateMethod.register("linear", linear) StateUpdateMethod.register("exact", exact) StateUpdateMethod.register("independent", independent) StateUpdateMethod.register("exponential_euler", exponential_euler) StateUpdateMethod.register("euler", euler) StateUpdateMethod.register("rk2", rk2) StateUpdateMethod.register("rk4", rk4) StateUpdateMethod.register("milstein", milstein) StateUpdateMethod.register("heun", heun) StateUpdateMethod.register("gsl_rk2", gsl_rk2) StateUpdateMethod.register("gsl_rk4", gsl_rk4) StateUpdateMethod.register("gsl_rkf45", gsl_rkf45) StateUpdateMethod.register("gsl_rkck", gsl_rkck) StateUpdateMethod.register("gsl_rk8pd", gsl_rk8pd) # as we consider rkf45 the default we also register it under 'gsl' StateUpdateMethod.register("gsl", gsl_rkf45) __all__ = [ "StateUpdateMethod", "linear", "exact", "independent", "milstein", "heun", "euler", "rk2", "rk4", "ExplicitStateUpdater", "exponential_euler", "gsl_rk2", "gsl_rk4", "gsl_rkf45", "gsl_rkck", "gsl_rk8pd", ] brian2-2.5.4/brian2/stateupdaters/base.py000066400000000000000000000236331445201106100202310ustar00rootroot00000000000000""" This module defines the `StateUpdateMethod` class that acts as a base class for all stateupdaters and allows to register stateupdaters so that it is able to return a suitable stateupdater object for a given set of equations. This is used for example in `NeuronGroup` when no state updater is given explicitly. """ import time from abc import ABCMeta, abstractmethod from collections.abc import Iterable from brian2.utils.caching import cached from brian2.utils.logger import get_logger __all__ = ["StateUpdateMethod"] logger = get_logger(__name__) class UnsupportedEquationsException(Exception): pass def extract_method_options(method_options, default_options): """ Helper function to check ``method_options`` against options understood by this state updater, and setting default values for all unspecified options. Parameters ---------- method_options : dict or None The options that the user specified for the state update. default_options : dict The default option values for this state updater (each admissible option needs to be present in this dictionary). To specify that a state updater does not take any options, provide an empty dictionary as the argument. Returns ------- options : dict The final dictionary with all the options either at their default or at the user-specified value. Raises ------ KeyError If the user specifies an option that is not understood by this state updater. Examples -------- >>> options = extract_method_options({'a': True}, default_options={'b': False, 'c': False}) # doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... KeyError: 'method_options specifies "a", but this is not an option for this state updater. Avalaible options are: "b", "c".' >>> options = extract_method_options({'a': True}, default_options={}) # doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... KeyError: 'method_options specifies "a", but this is not an option for this state updater. This state updater does not accept any options.' >>> options = extract_method_options({'a': True}, default_options={'a': False, 'b': False}) >>> sorted(options.items()) [('a', True), ('b', False)] """ if method_options is None: method_options = {} for key in method_options: if key not in default_options: if len(default_options): keys = sorted(default_options.keys()) options = ( "Available options are: " + ", ".join(f"'{key}'" for key in keys) + "." ) else: options = "This state updater does not accept any options." raise KeyError( f"method_options specifies '{key}', but this " "is not an option for this state updater. " f"{options}" ) filled_options = dict(default_options) filled_options.update(method_options) return filled_options class StateUpdateMethod(metaclass=ABCMeta): stateupdaters = dict() @abstractmethod def __call__(self, equations, variables=None, method_options=None): """ Generate abstract code from equations. The method also gets the the variables because some state updaters have to check whether variable names reflect other state variables (which can change from timestep to timestep) or are external values (which stay constant during a run) For convenience, this arguments are optional -- this allows to directly see what code a state updater generates for a set of equations by simply writing ``euler(eqs)``, for example. Parameters ---------- equations : `Equations` The model equations. variables : dict, optional The `Variable` objects for the model variables. method_options : dict, optional Additional options specific to the state updater. Returns ------- code : str The abstract code performing a state update step. """ pass @staticmethod def register(name, stateupdater): """ Register a state updater. Registered state updaters can be referred to via their name. Parameters ---------- name : str A short name for the state updater (e.g. `'euler'`) stateupdater : `StateUpdaterMethod` The state updater object, e.g. an `ExplicitStateUpdater`. """ # only deal with lower case names -- we don't want to have 'Euler' and # 'euler', for example name = name.lower() if name in StateUpdateMethod.stateupdaters: raise ValueError( f"A stateupdater with the name '{name}' has already been registered" ) if not isinstance(stateupdater, StateUpdateMethod): raise ValueError( f"Given stateupdater of type {type(stateupdater)} does " "not seem to be a valid stateupdater." ) StateUpdateMethod.stateupdaters[name] = stateupdater @staticmethod @cached def apply_stateupdater( equations, variables, method, method_options=None, group_name=None ): """ apply_stateupdater(equations, variables, method, method_options=None, group_name=None) Applies a given state updater to equations. If a `method` is given, the state updater with the given name is used or if is a callable, then it is used directly. If a `method` is a list of names, all the methods will be tried until one that doesn't raise an `UnsupportedEquationsException` is found. Parameters ---------- equations : `Equations` The model equations. variables : `dict` The dictionary of `Variable` objects, describing the internal model variables. method : {callable, str, list of str} A callable usable as a state updater, the name of a registered state updater or a list of names of state updaters. Returns ------- abstract_code : str The code integrating the given equations. """ if isinstance(method, Iterable) and not isinstance(method, str): the_method = None start_time = time.time() for one_method in method: try: one_method_start_time = time.time() code = StateUpdateMethod.apply_stateupdater( equations, variables, one_method, group_name=group_name ) the_method = one_method one_method_time = time.time() - one_method_start_time break except UnsupportedEquationsException: pass except TypeError: raise TypeError( "Each element in the list of methods has " "to be a string or a callable, got " f"{type(one_method)}." ) total_time = time.time() - start_time if the_method is None: raise ValueError( "No stateupdater that is suitable for the " "given equations has been found." ) # If only one method was tried if method[0] == the_method: timing = f"took {one_method_time:.2f}s" else: timing = ( f"took {one_method_time:.2f}s, trying other methods took " f"{total_time - one_method_time:.2f}s" ) if group_name is not None: msg_text = ( "No numerical integration method specified for group " f"'{group_name}', using method '{the_method}' ({timing})." ) else: msg_text = ( "No numerical integration method specified, " f"using method '{the_method}' ({timing})." ) logger.info(msg_text, "method_choice") else: if callable(method): # if this is a standard state updater, i.e. if it has a # can_integrate method, check this method and raise a warning if it # claims not to be applicable. stateupdater = method method = getattr( stateupdater, "__name__", repr(stateupdater) ) # For logging, get a nicer name elif isinstance(method, str): method = method.lower() # normalize name to lower case stateupdater = StateUpdateMethod.stateupdaters.get(method, None) if stateupdater is None: raise ValueError( "No state updater with the name '{method}' is known." ) else: raise TypeError( "method argument has to be a string, a " "callable, or an iterable of such objects. " f"Got {type(method)}" ) start_time = time.time() code = stateupdater(equations, variables, method_options) method_time = time.time() - start_time timing = "took %.2fs" % method_time if group_name is not None: logger.debug( f"Group {group_name}: using numerical integration " f"method {method} ({timing})", "method_choice", ) else: logger.debug( f"Using numerical integration method: {method} f({{timing}})", "method_choice", ) return code brian2-2.5.4/brian2/stateupdaters/exact.py000066400000000000000000000236061445201106100204230ustar00rootroot00000000000000""" Exact integration for linear equations. """ import itertools import sympy as sp from sympy import I, Symbol, Wild, im, re from brian2.equations.codestrings import is_constant_over_dt from brian2.parsing.sympytools import str_to_sympy, sympy_to_str from brian2.stateupdaters.base import ( StateUpdateMethod, UnsupportedEquationsException, extract_method_options, ) from brian2.utils.caching import cached from brian2.utils.logger import get_logger __all__ = ["linear", "exact", "independent"] logger = get_logger(__name__) def get_linear_system(eqs, variables): """ Convert equations into a linear system using sympy. Parameters ---------- eqs : `Equations` The model equations. Returns ------- (diff_eq_names, coefficients, constants) : (list of str, `sympy.Matrix`, `sympy.Matrix`) A tuple containing the variable names (`diff_eq_names`) corresponding to the rows of the matrix `coefficients` and the vector `constants`, representing the system of equations in the form M * X + B Raises ------ ValueError If the equations cannot be converted into an M * X + B form. """ diff_eqs = eqs.get_substituted_expressions(variables) diff_eq_names = [name for name, _ in diff_eqs] symbols = [Symbol(name, real=True) for name in diff_eq_names] coefficients = sp.zeros(len(diff_eq_names)) constants = sp.zeros(len(diff_eq_names), 1) for row_idx, (name, expr) in enumerate(diff_eqs): s_expr = str_to_sympy(expr.code, variables).expand() current_s_expr = s_expr for col_idx, symbol in enumerate(symbols): current_s_expr = current_s_expr.collect(symbol) constant_wildcard = Wild("c", exclude=[symbol]) factor_wildcard = Wild(f"c_{name}", exclude=symbols) one_pattern = factor_wildcard * symbol + constant_wildcard matches = current_s_expr.match(one_pattern) if matches is None: raise UnsupportedEquationsException( f"The expression '{expr}', " "defining the variable " f"'{name}', could not be " "separated into linear " "components." ) coefficients[row_idx, col_idx] = matches[factor_wildcard] current_s_expr = matches[constant_wildcard] # The remaining constant should be a true constant constants[row_idx] = current_s_expr return (diff_eq_names, coefficients, constants) class IndependentStateUpdater(StateUpdateMethod): """ A state update for equations that do not depend on other state variables, i.e. 1-dimensional differential equations. The individual equations are solved by sympy. .. deprecated:: 2.1 This method might be removed from future versions of Brian. """ def __call__(self, equations, variables=None, method_options=None): logger.warn( "The 'independent' state updater is deprecated and might be " "removed in future versions of Brian.", "deprecated_independent", once=True, ) extract_method_options(method_options, {}) if equations.is_stochastic: raise UnsupportedEquationsException( "Cannot solve stochastic equations with this state updater" ) if variables is None: variables = {} diff_eqs = equations.get_substituted_expressions(variables) t = Symbol("t", real=True, positive=True) dt = Symbol("dt", real=True, positive=True) t0 = Symbol("t0", real=True, positive=True) code = [] for name, expression in diff_eqs: rhs = str_to_sympy(expression.code, variables) # We have to be careful and use the real=True assumption as well, # otherwise sympy doesn't consider the symbol a match to the content # of the equation var = Symbol(name, real=True) f = sp.Function(name) rhs = rhs.subs(var, f(t)) derivative = sp.Derivative(f(t), t) diff_eq = sp.Eq(derivative, rhs) # TODO: simplify=True sometimes fails with 0.7.4, see: # https://github.com/sympy/sympy/issues/2666 try: general_solution = sp.dsolve(diff_eq, f(t), simplify=True) except RuntimeError: general_solution = sp.dsolve(diff_eq, f(t), simplify=False) # Check whether this is an explicit solution if not getattr(general_solution, "lhs", None) == f(t): raise UnsupportedEquationsException( f"Cannot explicitly solve: {str(diff_eq)}" ) # Solve for C1 (assuming "var" as the initial value and "t0" as time) if general_solution.has(Symbol("C1")): if general_solution.has(Symbol("C2")): raise UnsupportedEquationsException( f"Too many constants in solution: {str(general_solution)}" ) constant_solution = sp.solve(general_solution, Symbol("C1")) if len(constant_solution) != 1: raise UnsupportedEquationsException( "Couldn't solve for the constant C1 in : %s " % str(general_solution) ) constant = constant_solution[0].subs(t, t0).subs(f(t0), var) solution = general_solution.rhs.subs("C1", constant) else: solution = general_solution.rhs.subs(t, t0).subs(f(t0), var) # Evaluate the expression for one timestep solution = solution.subs(t, t + dt).subs(t0, t) # only try symplifying it -- it sometimes raises an error try: solution = solution.simplify() except ValueError: pass code.append(f"{name} = {sympy_to_str(solution)}") return "\n".join(code) class LinearStateUpdater(StateUpdateMethod): """ A state updater for linear equations. Derives a state updater step from the analytical solution given by sympy. Uses the matrix exponential (which is only implemented for diagonalizable matrices in sympy). """ @cached def __call__(self, equations, variables=None, method_options=None): method_options = extract_method_options(method_options, {"simplify": True}) if equations.is_stochastic: raise UnsupportedEquationsException( "Cannot solve stochastic equations with this state updater." ) if variables is None: variables = {} # Get a representation of the ODE system in the form of # dX/dt = M*X + B varnames, matrix, constants = get_linear_system(equations, variables) # No differential equations, nothing to do (this occurs sometimes in the # test suite where the whole model is nothing more than something like # 'v : 1') if matrix.shape == (0, 0): return "" # Make sure that the matrix M is constant, i.e. it only contains # external variables or constant variables # Check for time dependence dt_value = variables["dt"].get_value()[0] if "dt" in variables else None # This will raise an error if we meet the symbol "t" anywhere # except as an argument of a locally constant function for entry in itertools.chain(matrix, constants): if not is_constant_over_dt(entry, variables, dt_value): raise UnsupportedEquationsException( f"Expression '{sympy_to_str(entry)}' is not guaranteed to be " "constant over a time step." ) symbols = [Symbol(variable, real=True) for variable in varnames] solution = sp.solve_linear_system(matrix.row_join(constants), *symbols) if solution is None or set(symbols) != set(solution.keys()): raise UnsupportedEquationsException( "Cannot solve the given equations with this stateupdater." ) b = sp.ImmutableMatrix([solution[symbol] for symbol in symbols]) # Solve the system dt = Symbol("dt", real=True, positive=True) try: A = (matrix * dt).exp() except NotImplementedError: raise UnsupportedEquationsException( "Cannot solve the given equations with this stateupdater." ) if method_options["simplify"]: A = A.applyfunc(lambda x: sp.factor_terms(sp.cancel(sp.signsimp(x)))) C = sp.ImmutableMatrix(A * b) - b _S = sp.MatrixSymbol("_S", len(varnames), 1) updates = A * _S + C updates = updates.as_explicit() # The solution contains _S[0, 0], _S[1, 0] etc. for the state variables, # replace them with the state variable names abstract_code = [] for variable, update in zip(varnames, updates): rhs = update if rhs.has(I, re, im): raise UnsupportedEquationsException( "The solution to the linear system " "contains complex values " "which is currently not implemented." ) for row_idx, varname in enumerate(varnames): rhs = rhs.subs(_S[row_idx, 0], varname) # Do not overwrite the real state variables yet, the update step # of other state variables might still need the original values abstract_code.append(f"_{variable} = {sympy_to_str(rhs)}") # Update the state variables for variable in varnames: abstract_code.append(f"{variable} = _{variable}") return "\n".join(abstract_code) def __repr__(self): return f"{self.__class__.__name__}()" independent = IndependentStateUpdater() linear = LinearStateUpdater() exact = linear brian2-2.5.4/brian2/stateupdaters/explicit.py000066400000000000000000001001411445201106100211260ustar00rootroot00000000000000""" Numerical integration functions. """ import operator import string from functools import reduce import sympy from pyparsing import ( Group, Literal, ParseException, Suppress, Word, ZeroOrMore, restOfLine, ) from sympy.core.sympify import SympifyError from brian2.equations.codestrings import is_constant_over_dt from brian2.parsing.sympytools import str_to_sympy, sympy_to_str from .base import ( StateUpdateMethod, UnsupportedEquationsException, extract_method_options, ) __all__ = ["milstein", "heun", "euler", "rk2", "rk4", "ExplicitStateUpdater"] # =============================================================================== # Class for simple definition of explicit state updaters # =============================================================================== def _symbol(name, positive=None): """Shorthand for ``sympy.Symbol(name, real=True)``.""" return sympy.Symbol(name, real=True, positive=positive) #: reserved standard symbols SYMBOLS = { "__x": _symbol("__x"), "__t": _symbol("__t", positive=True), "dt": _symbol("dt", positive=True), "t": _symbol("t", positive=True), "__f": sympy.Function("__f"), "__g": sympy.Function("__g"), "__dW": _symbol("__dW"), } def split_expression(expr): """ Split an expression into a part containing the function ``f`` and another one containing the function ``g``. Returns a tuple of the two expressions (as sympy expressions). Parameters ---------- expr : str An expression containing references to functions ``f`` and ``g``. Returns ------- (non_stochastic, stochastic) : tuple of sympy expressions A pair of expressions representing the non-stochastic (containing function-independent terms and terms involving ``f``) and the stochastic part of the expression (terms involving ``g`` and/or ``dW``). Examples -------- >>> split_expression('dt * __f(__x, __t)') (dt*__f(__x, __t), None) >>> split_expression('dt * __f(__x, __t) + __dW * __g(__x, __t)') (dt*__f(__x, __t), __dW*__g(__x, __t)) >>> split_expression('1/(2*sqrt(dt))*(__g_support - __g(__x, __t))*(sqrt(__dW))') (0, sqrt(__dW)*__g_support/(2*sqrt(dt)) - sqrt(__dW)*__g(__x, __t)/(2*sqrt(dt))) """ f = SYMBOLS["__f"] g = SYMBOLS["__g"] dW = SYMBOLS["__dW"] # Arguments of the f and g functions x_f = sympy.Wild("x_f", exclude=[f, g], real=True) t_f = sympy.Wild("t_f", exclude=[f, g], real=True) x_g = sympy.Wild("x_g", exclude=[f, g], real=True) t_g = sympy.Wild("t_g", exclude=[f, g], real=True) # Reorder the expression so that f(x,t) and g(x,t) are factored out sympy_expr = sympy.sympify(expr, locals=SYMBOLS).expand() sympy_expr = sympy.collect(sympy_expr, f(x_f, t_f)) sympy_expr = sympy.collect(sympy_expr, g(x_g, t_g)) # Constant part, contains neither f, g nor dW independent = sympy.Wild("independent", exclude=[f, g, dW], real=True) # The exponent of the random number dW_exponent = sympy.Wild("dW_exponent", exclude=[f, g, dW, 0], real=True) # The factor for the random number, not containing the g function independent_dW = sympy.Wild("independent_dW", exclude=[f, g, dW], real=True) # The factor for the f function f_factor = sympy.Wild("f_factor", exclude=[f, g], real=True) # The factor for the g function g_factor = sympy.Wild("g_factor", exclude=[f, g], real=True) match_expr = ( independent + f_factor * f(x_f, t_f) + independent_dW * dW**dW_exponent + g_factor * g(x_g, t_g) ) matches = sympy_expr.match(match_expr) if matches is None: raise ValueError( f'Expression "{sympy_expr}" in the state updater description could not be' " parsed." ) # Non-stochastic part if x_f in matches: # Includes the f function non_stochastic = matches[independent] + ( matches[f_factor] * f(matches[x_f], matches[t_f]) ) else: # Does not include f, might be 0 non_stochastic = matches[independent] # Stochastic part if independent_dW in matches and matches[independent_dW] != 0: # includes a random variable term with a non-zero factor stochastic = ( matches[g_factor] * g(matches[x_g], matches[t_g]) + matches[independent_dW] * dW ** matches[dW_exponent] ) elif x_g in matches: # Does not include a random variable but the g function stochastic = matches[g_factor] * g(matches[x_g], matches[t_g]) else: # Contains neither random variable nor g function --> empty stochastic = None return (non_stochastic, stochastic) class ExplicitStateUpdater(StateUpdateMethod): """ An object that can be used for defining state updaters via a simple description (see below). Resulting instances can be passed to the ``method`` argument of the `NeuronGroup` constructor. As other state updater functions the `ExplicitStateUpdater` objects are callable, returning abstract code when called with an `Equations` object. A description of an explicit state updater consists of a (multi-line) string, containing assignments to variables and a final "x_new = ...", stating the integration result for a single timestep. The assignments can be used to define an arbitrary number of intermediate results and can refer to ``f(x, t)`` (the function being integrated, as a function of ``x``, the previous value of the state variable and ``t``, the time) and ``dt``, the size of the timestep. For example, to define a Runge-Kutta 4 integrator (already provided as `rk4`), use:: k1 = dt*f(x,t) k2 = dt*f(x+k1/2,t+dt/2) k3 = dt*f(x+k2/2,t+dt/2) k4 = dt*f(x+k3,t+dt) x_new = x+(k1+2*k2+2*k3+k4)/6 Note that for stochastic equations, the function `f` only corresponds to the non-stochastic part of the equation. The additional function `g` corresponds to the stochastic part that has to be multiplied with the stochastic variable xi (a standard normal random variable -- if the algorithm needs a random variable with a different variance/mean you have to multiply/add it accordingly). Equations with more than one stochastic variable do not have to be treated differently, the part referring to ``g`` is repeated for all stochastic variables automatically. Stochastic integrators can also make reference to ``dW`` (a normal distributed random number with variance ``dt``) and ``g(x, t)``, the stochastic part of an equation. A stochastic state updater could therefore use a description like:: x_new = x + dt*f(x,t) + g(x, t) * dW For simplicity, the same syntax is used for state updaters that only support additive noise, even though ``g(x, t)`` does not depend on ``x`` or ``t`` in that case. There a some restrictions on the complexity of the expressions (but most can be worked around by using intermediate results as in the above Runge- Kutta example): Every statement can only contain the functions ``f`` and ``g`` once; The expressions have to be linear in the functions, e.g. you can use ``dt*f(x, t)`` but not ``f(x, t)**2``. Parameters ---------- description : str A state updater description (see above). stochastic : {None, 'additive', 'multiplicative'} What kind of stochastic equations this state updater supports: ``None`` means no support of stochastic equations, ``'additive'`` means only equations with additive noise and ``'multiplicative'`` means supporting arbitrary stochastic equations. Raises ------ ValueError If the parsing of the description failed. Notes ----- Since clocks are updated *after* the state update, the time ``t`` used in the state update step is still at its previous value. Enumerating the states and discrete times, ``x_new = x + dt*f(x, t)`` is therefore understood as :math:`x_{i+1} = x_i + dt f(x_i, t_i)`, yielding the correct forward Euler integration. If the integrator has to refer to the time at the end of the timestep, simply use ``t + dt`` instead of ``t``. See also -------- euler, rk2, rk4, milstein """ # =========================================================================== # Parsing definitions # =========================================================================== #: Legal names for temporary variables TEMP_VAR = ~Literal("x_new") + Word( f"{string.ascii_letters}_", f"{string.ascii_letters + string.digits}_" ).setResultsName("identifier") #: A single expression EXPRESSION = restOfLine.setResultsName("expression") #: An assignment statement STATEMENT = Group(TEMP_VAR + Suppress("=") + EXPRESSION).setResultsName("statement") #: The last line of a state updater description OUTPUT = Group( Suppress(Literal("x_new")) + Suppress("=") + EXPRESSION ).setResultsName("output") #: A complete state updater description DESCRIPTION = ZeroOrMore(STATEMENT) + OUTPUT def __init__(self, description, stochastic=None, custom_check=None): self._description = description self.stochastic = stochastic self.custom_check = custom_check try: parsed = ExplicitStateUpdater.DESCRIPTION.parseString( description, parseAll=True ) except ParseException as p_exc: ex = SyntaxError(f"Parsing failed: {str(p_exc.msg)}") ex.text = str(p_exc.line) ex.offset = p_exc.column ex.lineno = p_exc.lineno raise ex self.statements = [] self.symbols = SYMBOLS.copy() for element in parsed: expression = str_to_sympy(element.expression) # Replace all symbols used in state updater expressions by unique # names that cannot clash with user-defined variables or functions expression = expression.subs(sympy.Function("f"), self.symbols["__f"]) expression = expression.subs(sympy.Function("g"), self.symbols["__g"]) symbols = list(expression.atoms(sympy.Symbol)) unique_symbols = [] for symbol in symbols: if symbol.name == "dt": unique_symbols.append(symbol) else: unique_symbols.append(_symbol(f"__{symbol.name}")) for symbol, unique_symbol in zip(symbols, unique_symbols): expression = expression.subs(symbol, unique_symbol) self.symbols.update({symbol.name: symbol for symbol in unique_symbols}) if element.getName() == "statement": self.statements.append((f"__{element.identifier}", expression)) elif element.getName() == "output": self.output = expression else: raise AssertionError(f"Unknown element name: {element.getName()}") def __repr__(self): # recreate a description string description = "\n".join([f"{var} = {expr}" for var, expr in self.statements]) if len(description): description += "\n" description += f"x_new = {str(self.output)}" classname = self.__class__.__name__ return f"{classname}('''{description}''', stochastic={self.stochastic!r})" def __str__(self): s = f"{self.__class__.__name__}\n" if len(self.statements) > 0: s += "Intermediate statements:\n" s += "\n".join( [f"{var} = {sympy_to_str(expr)}" for var, expr in self.statements] ) s += "\n" s += "Output:\n" s += sympy_to_str(self.output) return s def _latex(self, *args): from sympy import Symbol, latex s = [r"\begin{equation}"] for var, expr in self.statements: expr = expr.subs(Symbol("x"), Symbol("x_t")) s.append(f"{latex(Symbol(var))} = {latex(expr)}\\\\") expr = self.output.subs(Symbol("x"), "x_t") s.append(f"x_{{t+1}} = {latex(expr)}") s.append(r"\end{equation}") return "\n".join(s) def _repr_latex_(self): return self._latex() def replace_func(self, x, t, expr, temp_vars, eq_symbols, stochastic_variable=None): """ Used to replace a single occurance of ``f(x, t)`` or ``g(x, t)``: `expr` is the non-stochastic (in the case of ``f``) or stochastic part (``g``) of the expression defining the right-hand-side of the differential equation describing `var`. It replaces the variable `var` with the value given as `x` and `t` by the value given for `t`. Intermediate variables will be replaced with the appropriate replacements as well. For example, in the `rk2` integrator, the second step involves the calculation of ``f(k/2 + x, dt/2 + t)``. If `var` is ``v`` and `expr` is ``-v / tau``, this will result in ``-(_k_v/2 + v)/tau``. Note that this deals with only one state variable `var`, given as an argument to the surrounding `_generate_RHS` function. """ try: s_expr = str_to_sympy(str(expr)) except SympifyError as ex: raise ValueError(f'Error parsing the expression "{expr}": {str(ex)}') for var in eq_symbols: # Generate specific temporary variables for the state variable, # e.g. '_k_v' for the state variable 'v' and the temporary # variable 'k'. if stochastic_variable is None: temp_var_replacements = { self.symbols[temp_var]: _symbol(f"{temp_var}_{var}") for temp_var in temp_vars } else: temp_var_replacements = { self.symbols[temp_var]: _symbol( f"{temp_var}_{var}_{stochastic_variable}" ) for temp_var in temp_vars } # In the expression given as 'x', replace 'x' by the variable # 'var' and all the temporary variables by their # variable-specific counterparts. x_replacement = x.subs(self.symbols["__x"], eq_symbols[var]) x_replacement = x_replacement.subs(temp_var_replacements) # Replace the variable `var` in the expression by the new `x` # expression s_expr = s_expr.subs(eq_symbols[var], x_replacement) # If the expression given for t in the state updater description # is not just "t" (or rather "__t"), then replace t in the # equations by it, and replace "__t" by "t" afterwards. if t != self.symbols["__t"]: s_expr = s_expr.subs(SYMBOLS["t"], t) s_expr = s_expr.replace(self.symbols["__t"], SYMBOLS["t"]) return s_expr def _non_stochastic_part( self, eq_symbols, non_stochastic, non_stochastic_expr, stochastic_variable, temp_vars, var, ): non_stochastic_results = [] if stochastic_variable is None or len(stochastic_variable) == 0: # Replace the f(x, t) part replace_f = lambda x, t: self.replace_func( x, t, non_stochastic, temp_vars, eq_symbols ) non_stochastic_result = non_stochastic_expr.replace( self.symbols["__f"], replace_f ) # Replace x by the respective variable non_stochastic_result = non_stochastic_result.subs( self.symbols["__x"], eq_symbols[var] ) # Replace intermediate variables temp_var_replacements = { self.symbols[temp_var]: _symbol(f"{temp_var}_{var}") for temp_var in temp_vars } non_stochastic_result = non_stochastic_result.subs(temp_var_replacements) non_stochastic_results.append(non_stochastic_result) elif isinstance(stochastic_variable, str): # Replace the f(x, t) part replace_f = lambda x, t: self.replace_func( x, t, non_stochastic, temp_vars, eq_symbols, stochastic_variable ) non_stochastic_result = non_stochastic_expr.replace( self.symbols["__f"], replace_f ) # Replace x by the respective variable non_stochastic_result = non_stochastic_result.subs( self.symbols["__x"], eq_symbols[var] ) # Replace intermediate variables temp_var_replacements = { self.symbols[temp_var]: _symbol( f"{temp_var}_{var}_{stochastic_variable}" ) for temp_var in temp_vars } non_stochastic_result = non_stochastic_result.subs(temp_var_replacements) non_stochastic_results.append(non_stochastic_result) else: # Replace the f(x, t) part replace_f = lambda x, t: self.replace_func( x, t, non_stochastic, temp_vars, eq_symbols ) non_stochastic_result = non_stochastic_expr.replace( self.symbols["__f"], replace_f ) # Replace x by the respective variable non_stochastic_result = non_stochastic_result.subs( self.symbols["__x"], eq_symbols[var] ) # Replace intermediate variables temp_var_replacements = { self.symbols[temp_var]: reduce( operator.add, [_symbol(f"{temp_var}_{var}_{xi}") for xi in stochastic_variable], ) for temp_var in temp_vars } non_stochastic_result = non_stochastic_result.subs(temp_var_replacements) non_stochastic_results.append(non_stochastic_result) return non_stochastic_results def _stochastic_part( self, eq_symbols, stochastic, stochastic_expr, stochastic_variable, temp_vars, var, ): stochastic_results = [] if isinstance(stochastic_variable, str): # Replace the g(x, t) part replace_f = lambda x, t: self.replace_func( x, t, stochastic.get(stochastic_variable, 0), temp_vars, eq_symbols, stochastic_variable, ) stochastic_result = stochastic_expr.replace(self.symbols["__g"], replace_f) # Replace x by the respective variable stochastic_result = stochastic_result.subs( self.symbols["__x"], eq_symbols[var] ) # Replace dW by the respective variable stochastic_result = stochastic_result.subs( self.symbols["__dW"], stochastic_variable ) # Replace intermediate variables temp_var_replacements = { self.symbols[temp_var]: _symbol( f"{temp_var}_{var}_{stochastic_variable}" ) for temp_var in temp_vars } stochastic_result = stochastic_result.subs(temp_var_replacements) stochastic_results.append(stochastic_result) else: for xi in stochastic_variable: # Replace the g(x, t) part replace_f = lambda x, t: self.replace_func( x, t, stochastic.get(xi, 0), temp_vars, eq_symbols, xi # noqa: B023 ) stochastic_result = stochastic_expr.replace( self.symbols["__g"], replace_f ) # Replace x by the respective variable stochastic_result = stochastic_result.subs( self.symbols["__x"], eq_symbols[var] ) # Replace dW by the respective variable stochastic_result = stochastic_result.subs(self.symbols["__dW"], xi) # Replace intermediate variables temp_var_replacements = { self.symbols[temp_var]: _symbol(f"{temp_var}_{var}_{xi}") for temp_var in temp_vars } stochastic_result = stochastic_result.subs(temp_var_replacements) stochastic_results.append(stochastic_result) return stochastic_results def _generate_RHS( self, eqs, var, eq_symbols, temp_vars, expr, non_stochastic_expr, stochastic_expr, stochastic_variable=(), ): """ Helper function used in `__call__`. Generates the right hand side of an abstract code statement by appropriately replacing f, g and t. For example, given a differential equation ``dv/dt = -(v + I) / tau`` (i.e. `var` is ``v` and `expr` is ``(-v + I) / tau``) together with the `rk2` step ``return x + dt*f(x + k/2, t + dt/2)`` (i.e. `non_stochastic_expr` is ``x + dt*f(x + k/2, t + dt/2)`` and `stochastic_expr` is ``None``), produces ``v + dt*(-v - _k_v/2 + I + _k_I/2)/tau``. """ # Note: in the following we are silently ignoring the case that a # state updater does not care about either the non-stochastic or the # stochastic part of an equation. We do trust state updaters to # correctly specify their own abilities (i.e. they do not claim to # support stochastic equations but actually just ignore the stochastic # part). We can't really check the issue here, as we are only dealing # with one line of the state updater description. It is perfectly valid # to write the euler update as: # non_stochastic = dt * f(x, t) # stochastic = dt**.5 * g(x, t) * xi # return x + non_stochastic + stochastic # # In the above case, we'll deal with lines which do not define either # the stochastic or the non-stochastic part. non_stochastic, stochastic = expr.split_stochastic() if non_stochastic_expr is not None: # We do have a non-stochastic part in the state updater description non_stochastic_results = self._non_stochastic_part( eq_symbols, non_stochastic, non_stochastic_expr, stochastic_variable, temp_vars, var, ) else: non_stochastic_results = [] if not (stochastic is None or stochastic_expr is None): # We do have a stochastic part in the state # updater description stochastic_results = self._stochastic_part( eq_symbols, stochastic, stochastic_expr, stochastic_variable, temp_vars, var, ) else: stochastic_results = [] RHS = sympy.Number(0) # All the parts (one non-stochastic and potentially more than one # stochastic part) are combined with addition for non_stochastic_result in non_stochastic_results: RHS += non_stochastic_result for stochastic_result in stochastic_results: RHS += stochastic_result return sympy_to_str(RHS) def __call__(self, eqs, variables=None, method_options=None): """ Apply a state updater description to model equations. Parameters ---------- eqs : `Equations` The equations describing the model variables: dict-like, optional The `Variable` objects for the model. Ignored by the explicit state updater. method_options : dict, optional Additional options to the state updater (not used at the moment for the explicit state updaters). Examples -------- >>> from brian2 import * >>> eqs = Equations('dv/dt = -v / tau : volt') >>> print(euler(eqs)) _v = -dt*v/tau + v v = _v >>> print(rk4(eqs)) __k_1_v = -dt*v/tau __k_2_v = -dt*(__k_1_v/2 + v)/tau __k_3_v = -dt*(__k_2_v/2 + v)/tau __k_4_v = -dt*(__k_3_v + v)/tau _v = __k_1_v/6 + __k_2_v/3 + __k_3_v/3 + __k_4_v/6 + v v = _v """ extract_method_options(method_options, {}) # Non-stochastic numerical integrators should work for all equations, # except for stochastic equations if eqs.is_stochastic and self.stochastic is None: raise UnsupportedEquationsException( "Cannot integrate stochastic equations with this state updater." ) if self.custom_check: self.custom_check(eqs, variables) # The final list of statements statements = [] stochastic_variables = eqs.stochastic_variables # The variables for the intermediate results in the state updater # description, e.g. the variable k in rk2 intermediate_vars = [var for var, expr in self.statements] # A dictionary mapping all the variables in the equations to their # sympy representations eq_variables = {var: _symbol(var) for var in eqs.eq_names} # Generate the random numbers for the stochastic variables for stochastic_variable in stochastic_variables: statements.append(f"{stochastic_variable} = dt**.5 * randn()") substituted_expressions = eqs.get_substituted_expressions(variables) # Process the intermediate statements in the stateupdater description for intermediate_var, intermediate_expr in self.statements: # Split the expression into a non-stochastic and a stochastic part non_stochastic_expr, stochastic_expr = split_expression(intermediate_expr) # Execute the statement by appropriately replacing the functions f # and g and the variable x for every equation in the model. # We use the model equations where the subexpressions have # already been substituted into the model equations. for var, expr in substituted_expressions: for xi in stochastic_variables: RHS = self._generate_RHS( eqs, var, eq_variables, intermediate_vars, expr, non_stochastic_expr, stochastic_expr, xi, ) statements.append(f"{intermediate_var}_{var}_{xi} = {RHS}") if not stochastic_variables: # no stochastic variables RHS = self._generate_RHS( eqs, var, eq_variables, intermediate_vars, expr, non_stochastic_expr, stochastic_expr, ) statements.append(f"{intermediate_var}_{var} = {RHS}") # Process the "return" line of the stateupdater description non_stochastic_expr, stochastic_expr = split_expression(self.output) if eqs.is_stochastic and ( self.stochastic != "multiplicative" and eqs.stochastic_type == "multiplicative" ): # The equations are marked as having multiplicative noise and the # current state updater does not support such equations. However, # it is possible that the equations do not use multiplicative noise # at all. They could depend on time via a function that is constant # over a single time step (most likely, a TimedArray). In that case # we can integrate the equations dt_value = variables["dt"].get_value()[0] if "dt" in variables else None for _, expr in substituted_expressions: _, stoch = expr.split_stochastic() if stoch is None: continue # There could be more than one stochastic variable (e.g. xi_1, xi_2) for _, stoch_expr in stoch.items(): sympy_expr = str_to_sympy(stoch_expr.code) # The equation really has multiplicative noise, if it depends # on time (and not only via a function that is constant # over dt), or if it depends on another variable defined # via differential equations. if not is_constant_over_dt(sympy_expr, variables, dt_value) or len( stoch_expr.identifiers & eqs.diff_eq_names ): raise UnsupportedEquationsException( "Cannot integrate " "equations with " "multiplicative noise with " "this state updater." ) # Assign a value to all the model variables described by differential # equations for var, expr in substituted_expressions: RHS = self._generate_RHS( eqs, var, eq_variables, intermediate_vars, expr, non_stochastic_expr, stochastic_expr, stochastic_variables, ) statements.append(f"_{var} = {RHS}") # Assign everything to the final variables for var, _ in substituted_expressions: statements.append(f"{var} = _{var}") return "\n".join(statements) # =============================================================================== # Excplicit state updaters # =============================================================================== # these objects can be used like functions because they are callable #: Forward Euler state updater euler = ExplicitStateUpdater( "x_new = x + dt * f(x,t) + g(x,t) * dW", stochastic="additive" ) #: Second order Runge-Kutta method (midpoint method) rk2 = ExplicitStateUpdater( """ k = dt * f(x,t) x_new = x + dt*f(x + k/2, t + dt/2)""" ) #: Classical Runge-Kutta method (RK4) rk4 = ExplicitStateUpdater( """ k_1 = dt*f(x,t) k_2 = dt*f(x+k_1/2,t+dt/2) k_3 = dt*f(x+k_2/2,t+dt/2) k_4 = dt*f(x+k_3,t+dt) x_new = x+(k_1+2*k_2+2*k_3+k_4)/6 """ ) def diagonal_noise(equations, variables): """ Checks whether we deal with diagonal noise, i.e. one independent noise variable per variable. Raises ------ UnsupportedEquationsException If the noise is not diagonal. """ if not equations.is_stochastic: return stochastic_vars = [] for _, expr in equations.get_substituted_expressions(variables): expr_stochastic_vars = expr.stochastic_variables if len(expr_stochastic_vars) > 1: # More than one stochastic variable --> no diagonal noise raise UnsupportedEquationsException( "Cannot integrate stochastic " "equations with non-diagonal " "noise with this state " "updater." ) stochastic_vars.extend(expr_stochastic_vars) # If there's no stochastic variable is used in more than one equation, we # have diagonal noise if len(stochastic_vars) != len(set(stochastic_vars)): raise UnsupportedEquationsException( "Cannot integrate stochastic " "equations with non-diagonal " "noise with this state " "updater." ) #: Derivative-free Milstein method milstein = ExplicitStateUpdater( """ x_support = x + dt*f(x, t) + dt**.5 * g(x, t) g_support = g(x_support, t) k = 1/(2*dt**.5)*(g_support - g(x, t))*(dW**2) x_new = x + dt*f(x,t) + g(x, t) * dW + k """, stochastic="multiplicative", custom_check=diagonal_noise, ) #: Stochastic Heun method (for multiplicative Stratonovic SDEs with non-diagonal #: diffusion matrix) heun = ExplicitStateUpdater( """ x_support = x + g(x,t) * dW g_support = g(x_support,t+dt) x_new = x + dt*f(x,t) + .5*dW*(g(x,t)+g_support) """, stochastic="multiplicative", ) brian2-2.5.4/brian2/stateupdaters/exponential_euler.py000066400000000000000000000075241445201106100230420ustar00rootroot00000000000000import sympy as sp from brian2.parsing.sympytools import str_to_sympy, sympy_to_str from .base import ( StateUpdateMethod, UnsupportedEquationsException, extract_method_options, ) __all__ = ["exponential_euler"] def get_conditionally_linear_system(eqs, variables=None): """ Convert equations into a linear system using sympy. Parameters ---------- eqs : `Equations` The model equations. Returns ------- coefficients : dict of (sympy expression, sympy expression) tuples For every variable x, a tuple (M, B) containing the coefficients M and B (as sympy expressions) for M * x + B Raises ------ ValueError If one of the equations cannot be converted into a M * x + B form. Examples -------- >>> from brian2 import Equations >>> eqs = Equations(''' ... dv/dt = (-v + w**2.0) / tau : 1 ... dw/dt = -w / tau : 1 ... ''') >>> system = get_conditionally_linear_system(eqs) >>> print(system['v']) (-1/tau, w**2.0/tau) >>> print(system['w']) (-1/tau, 0) """ diff_eqs = eqs.get_substituted_expressions(variables) coefficients = {} for name, expr in diff_eqs: var = sp.Symbol(name, real=True) s_expr = str_to_sympy(expr.code, variables).expand() if s_expr.has(var): # Factor out the variable s_expr = sp.collect(s_expr, var, evaluate=False) if len(s_expr) > 2 or var not in s_expr: raise ValueError( f"The expression '{expr}', defining the variable " f"'{name}', could not be separated into linear " "components." ) coefficients[name] = (s_expr[var], s_expr.get(1, 0)) else: coefficients[name] = (0, s_expr) return coefficients class ExponentialEulerStateUpdater(StateUpdateMethod): """ A state updater for conditionally linear equations, i.e. equations where each variable only depends linearly on itself (but possibly non-linearly on other variables). Typical Hodgkin-Huxley equations fall into this category, it is therefore the default integration method used in the GENESIS simulator, for example. """ def __call__(self, equations, variables=None, method_options=None): extract_method_options(method_options, {}) if equations.is_stochastic: raise UnsupportedEquationsException( "Cannot solve stochastic equations with this state updater." ) # Try whether the equations are conditionally linear try: system = get_conditionally_linear_system(equations, variables) except ValueError: raise UnsupportedEquationsException( "Can only solve conditionally linear systems with this state updater." ) code = [] for var, (A, B) in system.items(): s_var = sp.Symbol(var) s_dt = sp.Symbol("dt") if A == 0: update_expression = s_var + s_dt * B elif B != 0: BA = B / A # Avoid calculating B/A twice BA_name = f"_BA_{var}" s_BA = sp.Symbol(BA_name) code += [f"{BA_name} = {sympy_to_str(BA)}"] update_expression = (s_var + s_BA) * sp.exp(A * s_dt) - s_BA else: update_expression = s_var * sp.exp(A * s_dt) # The actual update step code += [f"_{var} = {sympy_to_str(update_expression)}"] # Replace all the variables with their updated value for var in system: code += [f"{var} = _{var}"] return "\n".join(code) # Copy doc from parent class __call__.__doc__ = StateUpdateMethod.__call__.__doc__ exponential_euler = ExponentialEulerStateUpdater() brian2-2.5.4/brian2/synapses/000077500000000000000000000000001445201106100157135ustar00rootroot00000000000000brian2-2.5.4/brian2/synapses/.gitignore000066400000000000000000000000261445201106100177010ustar00rootroot00000000000000cythonspikequeue.cpp brian2-2.5.4/brian2/synapses/__init__.py000066400000000000000000000001341445201106100200220ustar00rootroot00000000000000""" Package providing synapse support. """ from .synapses import * __all__ = ["Synapses"] brian2-2.5.4/brian2/synapses/cspikequeue.cpp000066400000000000000000000153641445201106100207530ustar00rootroot00000000000000#include #include #include #include #include #include"stdint_compat.h" #include using namespace std; //TODO: The data type for indices is currently fixed (int), all floating point // variables (delays, dt) are assumed to use the same data type class CSpikeQueue { public: vector< vector > queue; // queue[(offset+i)%queue.size()] is delay i relative to current time double dt; int offset; bool scalar_delay; int *delays; int32_t source_start; int32_t source_end; int openmp_padding; vector< vector > synapses; // data structures for the store/restore mechanism CSpikeQueue(int _source_start, int _source_end) : source_start(_source_start), source_end(_source_end) { queue.resize(1); offset = 0; dt = 0.0; delays = NULL; openmp_padding = 0; scalar_delay = 0; }; ~CSpikeQueue() { if (delays) { delete[] delays; delays = NULL; } } template void prepare(scalar *real_delays, int n_delays, int32_t *sources, int n_synapses, double _dt) { assert(n_delays == 1 || n_delays == n_synapses); if (delays) delete [] delays; if (dt != 0.0 && dt != _dt) { // dt changed, we have to get the old spikes out of the queue and // reinsert them at the correct positions vector< vector > queue_copy = queue; // does a real copy const double conversion_factor = dt / _dt; const size_t oldsize = queue.size(); const size_t newsize = (int)(oldsize * conversion_factor) + 1; queue.clear(); queue.resize(newsize); for (size_t i=0; i spikes = queue_copy[(i + offset) % oldsize]; queue[(int)(i * conversion_factor + 0.5)] = spikes; } offset = 0; } delays = new int[n_delays]; synapses.clear(); synapses.resize(source_end - source_start); // Note that n_synapses and n_delays do not have to be identical // (homogeneous delays are stored as a single scalar), we therefore // use two independent loops to initialize the delays and the synapses // array scalar first_delay = n_delays > 0 ? real_delays[0] : 0.0; int min_delay = (int)(first_delay / _dt + 0.5); int max_delay = min_delay; for (int i=0; i max_delay) max_delay = delays[i]; else if (delays[i] < min_delay) min_delay = delays[i]; } for (int i=0; i > > _full_state() { pair > > state(offset, queue); return state; } void _clear() { } void _restore_from_full_state(const pair > > state) { int stored_offset = state.first; vector< vector > stored_queue = state.second; size_t size = stored_queue.size(); queue.clear(); if (size == 0) // the queue did not exist at the time of the store call size = 1; queue.resize(size); for (size_t i=0; i= (int)queue.size()) { expand(delay+1); } }; void push(int32_t *spikes, int nspikes) { if(nspikes==0) return; const int start = static_cast(distance(spikes, lower_bound(spikes, spikes+nspikes, source_start))); const int stop = static_cast(distance(spikes, upper_bound(spikes, spikes+nspikes, source_end-1))); const int32_t * __restrict rspikes = spikes; if(scalar_delay) { vector &homog_queue = queue[(offset+delays[0])%queue.size()]; for(int idx_spike=start; idx_spike* peek() { return &queue[offset]; }; void advance() { // empty the current queue, note that for most compilers this shouldn't deallocate the memory, // although VC<=7.1 will, so it will be less efficient with that compiler queue[offset].clear(); // and advance to the next offset offset = (offset+1)%queue.size(); }; }; brian2-2.5.4/brian2/synapses/cythonspikequeue.pyx000066400000000000000000000070521445201106100220660ustar00rootroot00000000000000# cython: language_level = 3 # distutils: language = c++ # distutils: sources = brian2/synapses/cspikequeue.cpp from libcpp.vector cimport vector from libcpp.pair cimport pair from libcpp.string cimport string import cython from cython.operator import dereference from cython.operator cimport dereference cimport numpy as np import numpy as np np.import_array() cdef extern from "stdint_compat.h": # Longness only used for type promotion # Actual compile time size used for conversion ctypedef signed int int32_t ctypedef signed long int64_t ctypedef pair[int, vector[vector[int32_t]]] state_pair ctypedef fused float_array: np.ndarray[double, ndim=1, mode='c'] np.ndarray[float, ndim=1, mode='c'] cdef extern from "cspikequeue.cpp": cdef cppclass CSpikeQueue: CSpikeQueue(int, int) except + void prepare[scalar](scalar*, int, int32_t*, int, double) void push(int32_t *, int) state_pair _full_state() void _restore_from_full_state(state_pair) vector[int32_t]* peek() void advance() cdef class SpikeQueue: # TODO: Currently, the data type for dt and delays is fixed cdef CSpikeQueue *thisptr cdef readonly tuple _state_tuple def __cinit__(self, int source_start, int source_end): self.thisptr = new CSpikeQueue(source_start, source_end) def __init__(self, source_start, source_end): self._state_tuple = (source_start, source_end, np.int32) def __dealloc__(self): del self.thisptr def _full_state(self): return self.thisptr._full_state() cdef object __weakref__ # Allows weak references to the SpikeQueue def _restore_from_full_state(self, state): cdef vector[vector[int32_t]] empty_queue cdef state_pair empty_state if state is not None: self.thisptr._restore_from_full_state(state) else: empty_queue = vector[vector[int32_t]]() empty_state = (0, empty_queue) self.thisptr._restore_from_full_state(empty_state) def prepare(self, float_array real_delays, double dt, np.ndarray[int32_t, ndim=1, mode='c'] sources): if real_delays.dtype == np.float32: self.thisptr.prepare(real_delays.data, real_delays.shape[0], sources.data, sources.shape[0], dt) else: self.thisptr.prepare(real_delays.data, real_delays.shape[0], sources.data, sources.shape[0], dt) def push(self, np.ndarray[int32_t, ndim=1, mode='c'] spikes): self.thisptr.push(spikes.data, spikes.shape[0]) def peek(self): # will only be used if the queue has size > 0 cdef int32_t* spikes_data cdef np.npy_intp shape[1] # This should create a numpy array from a std::vector without # copying -- &spikes[0] is guaranteed to point to a contiguous array # according to the C++ standard. cdef vector[int32_t]* spikes = self.thisptr.peek() cdef size_t spikes_size = dereference(spikes).size() if spikes_size == 0: return np.empty(0, dtype=np.int32) else: spikes_data = (&(dereference(spikes)[0])) shape[0] = spikes_size return np.PyArray_SimpleNewFromData(1, shape, np.NPY_INT32, spikes_data) def advance(self): self.thisptr.advance() brian2-2.5.4/brian2/synapses/parse_synaptic_generator_syntax.py000066400000000000000000000132131445201106100247650ustar00rootroot00000000000000import ast from brian2.parsing.rendering import NodeRenderer __all__ = ["parse_synapse_generator"] def _cname(obj): return obj.__class__.__name__ def handle_range(*args, **kwds): """ Checks the arguments/keywords for the range iterator Should have 1-3 positional arguments. Returns a dict with keys low, high, step. Default values are low=0, step=1. """ if len(args) == 0 or len(args) > 3: raise SyntaxError("Range iterator takes 1-3 positional arguments.") if len(kwds): raise SyntaxError("Range iterator doesn't accept any keyword arguments.") if len(args) == 1: high = args[0] low = "0" step = "1" elif len(args) == 2: low, high = args step = "1" else: low, high, step = args return {"low": low, "high": high, "step": step} def handle_sample(*args, **kwds): """ Checks the arguments/keywords for the sample iterator Should have 1-3 positional arguments and 1 keyword argument (either p or size). Returns a dict with keys ``low, high, step, sample_size, p, size``. Default values are ``low=0``, ``step=1`. Sample size will be either ``'random'`` or ``'fixed'``. In the first case, ``p`` will have a value and size will be ``None`` (and vice versa for the second case). """ if len(args) == 0 or len(args) > 3: raise SyntaxError("Sample iterator takes 1-3 positional arguments.") if len(kwds) != 1 or ("p" not in kwds and "size" not in kwds): raise SyntaxError( "Sample iterator accepts one keyword argument, either 'p' or 'size'." ) if len(args) == 1: high = args[0] low = "0" step = "1" elif len(args) == 2: low, high = args step = "1" else: low, high, step = args if "p" in kwds: sample_size = "random" p = kwds["p"] size = None else: sample_size = "fixed" size = kwds["size"] p = None return { "low": low, "high": high, "step": step, "p": p, "size": size, "sample_size": sample_size, } iterator_function_handlers = { "range": handle_range, "sample": handle_sample, } def parse_synapse_generator(expr): """ Returns a parsed form of a synapse generator expression. The general form is: ``element for inner_variable in iterator_func(...)`` or ``element for inner_variable in iterator_func(...) if if_expression`` Returns a dictionary with keys: ``original_expression`` The original expression as a string. ``element`` As above, a string expression. ``inner_variable`` A variable name, as above. ``iterator_func`` String. Either ``range`` or ``sample``. ``if_expression`` String expression or ``None``. ``iterator_kwds`` Dictionary of key/value pairs representing the keywords. See `handle_range` and `handle_sample`. """ nr = NodeRenderer() parse_error = ( "Error parsing expression '%s'. Expression must have " "generator syntax, for example 'k for k in range(i-10, " "i+10)'." % expr ) try: node = ast.parse(f"[{expr}]", mode="eval").body except Exception as e: raise SyntaxError(f"{parse_error} Error encountered was {e}") if _cname(node) != "ListComp": raise SyntaxError(f"{parse_error} Expression is not a generator expression.") element = node.elt if len(node.generators) != 1: raise SyntaxError( f"{parse_error} Generator expression must involve only one iterator." ) generator = node.generators[0] target = generator.target if _cname(target) != "Name": raise SyntaxError( f"{parse_error} Generator must iterate over a single variable (not tuple," " etc.)." ) inner_variable = target.id iterator = generator.iter if _cname(iterator) != "Call" or _cname(iterator.func) != "Name": raise SyntaxError( parse_error + " Iterator expression must be one of the supported functions: " + str(list(iterator_function_handlers)) ) iterator_funcname = iterator.func.id if iterator_funcname not in iterator_function_handlers: raise SyntaxError( parse_error + " Iterator expression must be one of the supported functions: " + str(list(iterator_function_handlers)) ) if ( getattr(iterator, "starargs", None) is not None or getattr(iterator, "kwargs", None) is not None ): raise SyntaxError(f"{parse_error} Star arguments not supported.") args = [] for argnode in iterator.args: args.append(nr.render_node(argnode)) keywords = {} for kwdnode in iterator.keywords: keywords[kwdnode.arg] = nr.render_node(kwdnode.value) try: iterator_handler = iterator_function_handlers[iterator_funcname] iterator_kwds = iterator_handler(*args, **keywords) except SyntaxError as exc: raise SyntaxError(f"{parse_error} {exc.msg}") if len(generator.ifs) == 0: condition = ast.parse("True", mode="eval").body elif len(generator.ifs) > 1: raise SyntaxError( f"{parse_error} Generator must have at most one if statement." ) else: condition = generator.ifs[0] parsed = { "original_expression": expr, "element": nr.render_node(element), "inner_variable": inner_variable, "iterator_func": iterator_funcname, "iterator_kwds": iterator_kwds, "if_expression": nr.render_node(condition), } return parsed brian2-2.5.4/brian2/synapses/spikequeue.py000066400000000000000000000254751445201106100204620ustar00rootroot00000000000000""" The spike queue class stores future synaptic events produced by a given presynaptic neuron group (or postsynaptic for backward propagation in STDP). """ import bisect import numpy as np from brian2.utils.arrays import calc_repeats from brian2.utils.logger import get_logger __all__ = ["SpikeQueue"] logger = get_logger(__name__) INITIAL_MAXSPIKESPER_DT = 1 class SpikeQueue: """ Data structure saving the spikes and taking care of delays. Parameters ---------- source_start : int The start of the source indices (for subgroups) source_end : int The end of the source indices (for subgroups) Notes ----- **Data structure** A spike queue is implemented as a 2D array `X` that is circular in the time direction (rows) and dynamic in the events direction (columns). The row index corresponding to the current timestep is `currentime`. Each element contains the target synapse index. **Offsets** Offsets are used to solve the problem of inserting multiple synaptic events with the same delay. This is difficult to vectorise. If there are n synaptic events with the same delay, these events are given an offset between 0 and n-1, corresponding to their relative position in the data structure. """ def __init__(self, source_start, source_end): #: The start of the source indices (for subgroups) self._source_start = source_start #: The end of the source indices (for subgroups) self._source_end = source_end self.dtype = np.int32 # TODO: Ths is fixed for now self.X = np.zeros((1, 1), dtype=self.dtype) # target synapses self.X_flat = self.X.reshape( 1, ) #: The current time (in time steps) self.currenttime = 0 #: number of events in each time step self.n = np.zeros(1, dtype=int) #: The dt used for storing the spikes (will be set in `prepare`) self._dt = None self._state_tuple = (self._source_start, self._source_end, self.dtype) def prepare(self, delays, dt, synapse_sources): """ Prepare the data structures This is called every time the network is run. The size of the of the data structure (number of rows) is adjusted to fit the maximum delay in `delays`, if necessary. A flag is set if delays are homogeneous, in which case insertion will use a faster method implemented in `insert_homogeneous`. """ if self._dt is not None: # store the current spikes spikes = self._extract_spikes() # adapt the spikes to the new dt if it changed if self._dt != dt: spiketimes = spikes[:, 0] * self._dt spikes[:, 0] = np.round(spiketimes / dt).astype(int) else: spikes = None if len(delays): delays = np.array(np.round(delays / dt)).astype(int) max_delays = max(delays) min_delays = min(delays) else: max_delays = min_delays = 0 self._delays = delays # Prepare the data structure used in propagation synapse_sources = synapse_sources[:] ss = np.ravel(synapse_sources) # mergesort to retain relative order, keeps the output lists in sorted order ss_sort_indices = np.argsort(ss, kind="mergesort") ss_sorted = ss[ss_sort_indices] splitinds = np.searchsorted( ss_sorted, np.arange(self._source_start, self._source_end + 1) ) self._neurons_to_synapses = [ ss_sort_indices[splitinds[j] : splitinds[j + 1]] for j in range(len(splitinds) - 1) ] max_events = max(map(len, self._neurons_to_synapses)) n_steps = max_delays + 1 # Adjust the maximum delay and number of events per timestep if necessary # Check if delays are homogeneous self._homogeneous = max_delays == min_delays # Resize if (n_steps > self.X.shape[0]) or (max_events > self.X.shape[1]): # Resize # Choose max_delay if is is larger than the maximum delay n_steps = max(n_steps, self.X.shape[0]) max_events = max(max_events, self.X.shape[1]) self.X = np.zeros( (n_steps, max_events), dtype=self.dtype ) # target synapses self.X_flat = self.X.reshape( n_steps * max_events, ) self.n = np.zeros(n_steps, dtype=int) # number of events in each time step # Re-insert the spikes into the data structure if spikes is not None: self._store_spikes(spikes) self._dt = dt def _extract_spikes(self): """ Get all the stored spikes Returns ------- spikes : ndarray A 2d array with two columns, where each row describes a spike. The first column gives the time (as integer time steps) and the second column gives the index of the target synapse. """ spikes = np.zeros((np.sum(self.n), 2), dtype=int) counter = 0 for idx, n in enumerate(self.n): t = (idx - self.currenttime) % len(self.n) for target in self.X[idx, :n]: spikes[counter, :] = np.array([t, target]) counter += 1 return spikes def _store_spikes(self, spikes): """ Store a list of spikes at the given positions after clearing all spikes in the queue. Parameters ---------- spikes : ndarray A 2d array with two columns, where each row describes a spike. The first column gives the time (as integer time steps) and the second column gives the index of the target synapse. """ # Clear all spikes self.n[:] = 0 for t, target in spikes: row_idx = (t + self.currenttime) % len(self.n) self.X[row_idx, self.n[row_idx]] = target self.n[row_idx] += 1 def _full_state(self): return (self._dt, self._extract_spikes(), self.X.shape) def _restore_from_full_state(self, state): if state is None: # It is possible that _full_state was called in `SynapticPathway`, # before the `SpikeQueue` was created. In that case, delete all spikes in # the queue self._store_spikes(np.empty((0, 2), dtype=int)) self._dt = None else: self._dt, spikes, X_shape = state # Restore the previous shape n_steps, max_events = X_shape self.X = np.zeros((n_steps, max_events), dtype=self.dtype) self.X_flat = self.X.reshape( n_steps * max_events, ) self.n = np.zeros(n_steps, dtype=int) self._store_spikes(spikes) ################################ SPIKE QUEUE DATASTRUCTURE ################ def advance(self): """ Advances by one timestep """ self.n[self.currenttime] = 0 # erase self.currenttime = (self.currenttime + 1) % len(self.n) def peek(self): """ Returns the all the synaptic events corresponding to the current time, as an array of synapse indexes. """ return self.X[self.currenttime, : self.n[self.currenttime]] def push(self, sources): """ Push spikes to the queue. Parameters ---------- sources : ndarray of int The indices of the neurons that spiked. """ if len(sources) and len(self._delays): start = self._source_start stop = self._source_end if start > 0: start_idx = bisect.bisect_left(sources, start) else: start_idx = 0 if stop <= sources[-1]: stop_idx = bisect.bisect_left(sources, stop, lo=start_idx) else: stop_idx = len(sources) + 1 sources = sources[start_idx:stop_idx] if len(sources) == 0: return synapse_indices = self._neurons_to_synapses indices = np.concatenate( [synapse_indices[source - start] for source in sources] ).astype(np.int32) if self._homogeneous: # homogeneous delays self._insert_homogeneous(self._delays[0], indices) else: # vectorise over synaptic events self._insert(self._delays[indices], indices) def _insert(self, delay, target): """ Vectorised insertion of spike events. Parameters ---------- delay : ndarray Delays in timesteps. target : ndarray Target synaptic indices. """ delay = np.array(delay, dtype=int) offset = calc_repeats(delay) # Calculate row indices in the data structure timesteps = (self.currenttime + delay) % len(self.n) # (Over)estimate the number of events to be stored, to resize the array # It's an overestimation for the current time, but I believe a good one # for future events m = max(self.n) + len(target) if m >= self.X.shape[1]: # overflow self._resize(m + 1) self.X_flat[timesteps * self.X.shape[1] + offset + self.n[timesteps]] = target self.n[timesteps] += offset + 1 # that's a trick (to update stack size) def _insert_homogeneous(self, delay, target): """ Inserts events at a fixed delay. Parameters ---------- delay : int Delay in timesteps. target : ndarray Target synaptic indices. """ timestep = (self.currenttime + delay) % len(self.n) nevents = len(target) m = ( self.n[timestep] + nevents + 1 ) # If overflow, then at least one self.n is bigger than the size if m >= self.X.shape[1]: self._resize(m + 1) # was m previously (not enough) k = timestep * self.X.shape[1] + self.n[timestep] self.X_flat[k : k + nevents] = target self.n[timestep] += nevents def _resize(self, maxevents): """ Resizes the underlying data structure (number of columns = spikes per dt). Parameters ---------- maxevents : int The new number of columns. It will be rounded to the closest power of 2. """ # old and new sizes old_maxevents = self.X.shape[1] new_maxevents = int(2 ** np.ceil(np.log2(maxevents))) # maybe 2 is too large # new array newX = np.zeros((self.X.shape[0], new_maxevents), dtype=self.X.dtype) newX[:, :old_maxevents] = self.X[:, :old_maxevents] # copy old data self.X = newX self.X_flat = self.X.reshape( self.X.shape[0] * new_maxevents, ) brian2-2.5.4/brian2/synapses/stdint_compat.h000066400000000000000000000007521445201106100207400ustar00rootroot00000000000000#ifndef _BRIAN_STDINT_COMPAT_H #define _BRIAN_STDINT_COMPAT_H // Work around the fact that older MSVC versions don't have stdint.h #ifdef _MSC_VER typedef __int32 int32_t; typedef __int64 int64_t; typedef unsigned __int64 uint64_t; #else #include #endif // Implement the int_ function here so that it can also be used from Cython template inline int int_(T value) { return (int)value; } template<> inline int int_(bool value) { return value ? 1 : 0; } #endif brian2-2.5.4/brian2/synapses/synapses.py000066400000000000000000002603441445201106100201430ustar00rootroot00000000000000""" Module providing the `Synapses` class and related helper classes/functions. """ import functools import numbers import re import weakref from collections import defaultdict from collections.abc import Mapping, MutableMapping, Sequence import numpy as np from brian2.codegen.codeobject import create_runner_codeobj from brian2.codegen.translation import get_identifiers_recursively from brian2.core.base import device_override, weakproxy_with_fallback from brian2.core.namespace import get_local_namespace from brian2.core.spikesource import SpikeSource from brian2.core.variables import DynamicArrayVariable, Variables from brian2.devices.device import get_device from brian2.equations.equations import ( DIFFERENTIAL_EQUATION, PARAMETER, SUBEXPRESSION, EquationError, Equations, check_subexpressions, ) from brian2.groups.group import CodeRunner, Group, get_dtype from brian2.groups.neurongroup import ( SubexpressionUpdater, check_identifier_pre_post, extract_constant_subexpressions, ) from brian2.parsing.bast import brian_ast from brian2.parsing.expressions import ( is_boolean_expression, parse_expression_dimensions, ) from brian2.parsing.rendering import NodeRenderer from brian2.stateupdaters.base import StateUpdateMethod, UnsupportedEquationsException from brian2.stateupdaters.exact import linear from brian2.synapses.parse_synaptic_generator_syntax import parse_synapse_generator from brian2.units.allunits import second from brian2.units.fundamentalunits import ( DIMENSIONLESS, DimensionMismatchError, Quantity, fail_for_dimension_mismatch, ) from brian2.utils.arrays import calc_repeats from brian2.utils.logger import get_logger from brian2.utils.stringtools import get_identifiers, word_substitute MAX_SYNAPSES = 2147483647 __all__ = ["Synapses"] logger = get_logger(__name__) class StateUpdater(CodeRunner): """ The `CodeRunner` that updates the state variables of a `Synapses` at every timestep. """ def __init__(self, group, method, clock, order, method_options=None): self.method_choice = method self.method_options = method_options CodeRunner.__init__( self, group, "stateupdate", clock=clock, when="groups", order=order, name=group.name + "_stateupdater", check_units=False, generate_empty_code=False, ) def update_abstract_code(self, run_namespace): if len(self.group.equations) > 0: # Resolve variables in the equations to correctly perform checks # for repeated stateful functions (e.g. rand() calls) names = self.group.equations.names external_names = self.group.equations.identifiers | {"dt"} variables = self.group.resolve_all( names | external_names, run_namespace, # we don't need to raise any warnings # for the user here, warnings will # be raised in create_runner_codeobj user_identifiers=set(), ) stateupdate_output = StateUpdateMethod.apply_stateupdater( self.group.equations, variables, self.method_choice, method_options=self.method_options, group_name=self.group.name, ) if isinstance(stateupdate_output, str): self.abstract_code = stateupdate_output else: # Note that the reason to send self along with this method is so the StateUpdater # can be modified! i.e. in GSL StateUpdateMethod a custom CodeObject gets added # to the StateUpdater together with some auxiliary information self.abstract_code = stateupdate_output(self) else: self.abstract_code = "" class SummedVariableUpdater(CodeRunner): """ The `CodeRunner` that updates a value in the target group with the sum over values in the `Synapses` object. """ def __init__( self, expression, target_varname, synapses, target, target_size_name, index_var ): # Handling sumped variables using the standard mechanisms is not # possible, we therefore also directly give the names of the arrays # to the template. code = f""" _synaptic_var = {expression} """ self.target_varname = target_varname self.expression = expression self.target_var = synapses.variables[target_varname] self.target = target template_kwds = { "_target_var": self.target_var, "_target_size_name": target_size_name, "_index_var": synapses.variables[index_var], "_target_start": getattr(target, "start", 0), "_target_stop": getattr(target, "stop", -1), } CodeRunner.__init__( self, group=synapses, template="summed_variable", code=code, needed_variables=[target_varname, target_size_name, index_var], # We want to update the summed variable before # the target group gets updated clock=target.clock, when="groups", order=target.order - 1, name=synapses.name + "_summed_variable_" + target_varname, template_kwds=template_kwds, ) def before_run(self, run_namespace): variables = self.group.resolve_all(self.expression.identifiers, run_namespace) rhs_unit = parse_expression_dimensions(self.expression.code, variables) fail_for_dimension_mismatch( self.target_var, # Using a quantity instead of dimensions # here makes fail_for_dimension_mismatch # state the dimensions as part of the error # message Quantity(1, dim=rhs_unit), "The target variable " f"'{self.target_varname}' does not have " "the same dimensions as the right-hand " f"side expression '{self.expression}'.", ) super().before_run(run_namespace) class SynapticPathway(CodeRunner, Group): """ The `CodeRunner` that applies the pre/post statement(s) to the state variables of synapses where the pre-/postsynaptic group spiked in this time step. Parameters ---------- synapses : `Synapses` Reference to the main `Synapses` object prepost : {'pre', 'post'} Whether this object should react to pre- or postsynaptic spikes objname : str, optional The name to use for the object, will be appendend to the name of `synapses` to create a name in the sense of `Nameable`. If ``None`` is provided (the default), ``prepost`` will be used. delay : `Quantity`, optional A scalar delay (same delay for all synapses) for this pathway. If not given, delays are expected to vary between synapses. """ def __init__( self, synapses, code, prepost, objname=None, delay=None, event="spike" ): self.code = code self.prepost = prepost self.event = event if prepost == "pre": self.source = synapses.source self.target = synapses.target self.synapse_sources = synapses.variables["_synaptic_pre"] self.synapse_targets = synapses.variables["_synaptic_post"] order = -1 elif prepost == "post": self.source = synapses.target self.target = synapses.source self.synapse_sources = synapses.variables["_synaptic_post"] self.synapse_targets = synapses.variables["_synaptic_pre"] order = 1 else: raise ValueError("prepost argument has to be either 'pre' or 'post'") self.synapses = weakref.proxy(synapses) # Allow to use the same indexing of the delay variable as in the parent # Synapses object (e.g. 2d indexing with pre- and post-synaptic indices) self._indices = self.synapses._indices if objname is None: objname = prepost CodeRunner.__init__( self, synapses, "synapses", code=code, clock=self.source.clock, when="synapses", order=order, name=synapses.name + "_" + objname, template_kwds={"pathway": self}, ) self._pushspikes_codeobj = None self.spikes_start = self.source.start self.spikes_stop = self.source.stop self.eventspace_name = f"_{event}space" self.eventspace = None # will be set in before_run # Setting the Synapses object instead of "self" as an owner makes # indexing conflicts disappear (e.g. with synapses connecting subgroups) self.variables = Variables(synapses) self.variables.add_reference(self.eventspace_name, self.source) self.variables.add_reference("N", synapses) if prepost == "pre": self.variables.add_reference("_n_sources", synapses, "N_pre") self.variables.add_reference("_n_targets", synapses, "N_post") self.variables.add_reference("_source_dt", synapses.source, "dt") else: self.variables.add_reference("_n_sources", synapses, "N_post") self.variables.add_reference("_n_targets", synapses, "N_pre") self.variables.add_reference("_source_dt", synapses.target, "dt") if delay is None: # variable delays if getattr(synapses, "N", None) is not None: n_synapses = synapses.N else: n_synapses = 0 self.variables.add_dynamic_array( "delay", dimensions=second.dim, size=n_synapses, constant=True ) # Register the object with the `SynapticIndex` object so it gets # automatically resized synapses.register_variable(self.variables["delay"]) else: if not isinstance(delay, Quantity): raise TypeError( f"Cannot set the delay for pathway '{objname}': " f"expected a quantity, got {type(delay)} instead." ) if delay.size != 1: raise TypeError( f"Cannot set the delay for pathway '{objname}': " "expected a scalar quantity, got a " f"quantity with shape {delay.shape!s} instead." ) fail_for_dimension_mismatch( delay, second, "Delay has to be specified in units of seconds but got {value}", value=delay, ) # We use a "dynamic" array of constant size here because it makes # the generated code easier, we don't need to deal with a different # type for scalar and variable delays self.variables.add_dynamic_array( "delay", dimensions=second.dim, size=1, constant=True, scalar=True ) # Since this array does not grow with the number of synapses, we # have to resize it ourselves self.variables["delay"].resize(1) self.variables["delay"].set_value(delay) self._delays = self.variables["delay"] # Re-extract the last part of the name from the full name self.objname = self.name[len(synapses.name) + 1 :] #: The `CodeObject` initalising the `SpikeQueue` at the begin of a run self._initialise_queue_codeobj = None self.namespace = synapses.namespace # Allow the use of string expressions referring to synaptic (including # pre-/post-synaptic) variables # Only include non-private variables (and their indices) synaptic_vars = { varname for varname in list(synapses.variables) if not varname.startswith("_") } synaptic_idcs = { varname: synapses.variables.indices[varname] for varname in synaptic_vars } synaptic_vars |= { index_name for index_name in synaptic_idcs.values() if index_name not in ["_idx", "0"] } self.variables.add_references(synapses, synaptic_vars) self.variables.indices.update(synaptic_idcs) #: The `SpikeQueue` self.queue = get_device().spike_queue(self.source.start, self.source.stop) self.variables.add_object("_queue", self.queue) self._enable_group_attributes() def check_variable_write(self, variable): # Forward the check to the `Synapses` object (raises an error if no # synapse has been created yet) self.synapses.check_variable_write(variable) @device_override("synaptic_pathway_update_abstract_code") def update_abstract_code(self, run_namespace=None, level=0): if self.synapses.event_driven is not None: event_driven_eqs = self.synapses.event_driven try: event_driven_update = linear(event_driven_eqs, self.group.variables) except UnsupportedEquationsException: err = ( "Cannot solve the differential equations as " "event-driven. Use (clock-driven) instead." ) raise UnsupportedEquationsException(err) # TODO: Any way to do this more elegantly? event_driven_update = re.sub( r"\bdt\b", "(t - lastupdate)", event_driven_update ) self.abstract_code = event_driven_update + "\n" else: self.abstract_code = "" self.abstract_code += self.code + "\n" if self.synapses.event_driven is not None: self.abstract_code += "lastupdate = t\n" @device_override("synaptic_pathway_before_run") def before_run(self, run_namespace): super().before_run(run_namespace) def create_code_objects(self, run_namespace): if self._pushspikes_codeobj is None: # Since this now works for general events not only spikes, we have to # pass the information about which variable to use to the template, # it can not longer simply refer to "_spikespace" # Strictly speaking this is only true for the standalone mode at the # moment, since in runtime, all the template does is to call # SynapticPathway.push_spike eventspace_name = f"_{self.event}space" template_kwds = { "eventspace_variable": self.source.variables[eventspace_name] } needed_variables = [eventspace_name] self._pushspikes_codeobj = create_runner_codeobj( self, "", # no code "synapses_push_spikes", name=self.name + "_push_spikes", check_units=False, additional_variables=self.variables, needed_variables=needed_variables, template_kwds=template_kwds, run_namespace=run_namespace, ) self.code_objects[:] = [ weakref.proxy(self._pushspikes_codeobj), weakref.proxy(self.create_default_code_object(run_namespace)), ] def initialise_queue(self): self.eventspace = self.source.variables[self.eventspace_name].get_value() n_synapses = len(self.synapses) if n_synapses == 0 and not self.synapses._connect_called: raise TypeError( "Synapses object '%s' does not do anything, since " "it has not created synapses with 'connect'. " "Set its active attribute to False if you " "intend to do only do this for a subsequent" " run." % self.synapses.name ) # Update the dt (might have changed between runs) self.queue.prepare( self._delays.get_value(), self.source.clock.dt_, self.synapse_sources.get_value(), ) if ( len({self.source.clock.dt_, self.synapses.clock.dt_, self.target.clock.dt_}) > 1 ): logger.warn( f"Note that the synaptic pathway '{self.name}' will run on the " f"clock of the group '{self.source.name}' using a dt of " f"{self.source.clock.dt}. Either the Synapses object " f"'{self.synapses.name}' or the target '{self.target.name}' " "(or both) are using a different dt. This might lead to " "unexpected results. In particular, all delays will be " f"rounded to multiples of {self.source.clock.dt}. If in " f"doubt, try to ensure that '{self.source.name}', " f"'{self.synapses.name}', and '{self.target.name}' use the " "same dt.", "synapses_dt_mismatch", once=True, ) def _full_state(self): state = super()._full_state() if self.queue is not None: state["_spikequeue"] = self.queue._full_state() else: state["_spikequeue"] = None return state def _restore_from_full_state(self, state): # We have to handle the SpikeQueue separately from the other state # variables, so remove it from the state dictionary so that it does not # get treated as a state variable by the standard mechanism in # `VariableOwner` queue_state = state.pop("_spikequeue") super()._restore_from_full_state(state) if self.queue is None: self.queue = get_device().spike_queue(self.source.start, self.source.stop) self.queue._restore_from_full_state(queue_state) # Put the spike queue state back for future restore calls state["_spikequeue"] = queue_state def push_spikes(self): # Push new events (e.g. spikes) into the queue events = self.eventspace[: self.eventspace[len(self.eventspace) - 1]] if len(events): self.queue.push(events) def slice_to_test(x): """ Returns a testing function corresponding to whether an index is in slice x. x can also be an int. """ try: x = int(x) return lambda y: (y == x) except TypeError: pass if isinstance(x, slice): if isinstance(x, slice) and x == slice(None): # No need for testing return lambda y: np.repeat(True, len(y)) start, stop, step = x.start, x.stop, x.step if start is None: # No need to test for >= start if step is None: # Only have a stop value return lambda y: (y < stop) else: # Stop and step return lambda y: (y < stop) & ((y % step) == 0) else: # We need to test for >= start if step is None: if stop is None: # Only a start value return lambda y: (y >= start) else: # Start and stop return lambda y: (y >= start) & (y < stop) else: if stop is None: # Start and step value return lambda y: (y >= start) & ((y - start) % step == 0) else: # Start, step and stop return ( lambda y: (y >= start) & ((y - start) % step == 0) & (y < stop) ) else: raise TypeError(f"Expected int or slice, got {type(x)} instead") def find_synapses(index, synaptic_neuron): try: index = index.item() except (TypeError, ValueError): pass if isinstance(index, (int, slice)): test = slice_to_test(index) found = test(synaptic_neuron) synapses = np.flatnonzero(found) else: synapses = [] for neuron in index: targets = np.flatnonzero(synaptic_neuron == neuron) synapses.extend(targets) synapses = np.array(synapses, dtype=np.int32) return synapses class SynapticSubgroup: """ A simple subgroup of `Synapses` that can be used for indexing. Parameters ---------- indices : `ndarray` of int The synaptic indices represented by this subgroup. synaptic_pre : `DynamicArrayVariable` References to all pre-synaptic indices. Only used to throw an error when new synapses where added after creating this object. """ def __init__(self, synapses, indices): self.synapses = weakproxy_with_fallback(synapses) self._stored_indices = indices self._synaptic_pre = synapses.variables["_synaptic_pre"] self._source_N = self._synaptic_pre.size # total number of synapses def _indices(self, index_var="_idx"): if index_var != "_idx": raise AssertionError(f"Did not expect index {index_var} here.") if len(self._synaptic_pre.get_value()) != self._source_N: raise RuntimeError( "Synapses have been added/removed since this " "synaptic subgroup has been created" ) return self._stored_indices def __len__(self): return len(self._stored_indices) def __repr__(self): return ( f"<{self.__class__.__name__}, storing {len(self._stored_indices):d} " f"indices of {self.synapses.name}>" ) class SynapticIndexing: def __init__(self, synapses): self.synapses = weakref.proxy(synapses) self.source = weakproxy_with_fallback(self.synapses.source) self.target = weakproxy_with_fallback(self.synapses.target) self.synaptic_pre = synapses.variables["_synaptic_pre"] self.synaptic_post = synapses.variables["_synaptic_post"] if synapses.multisynaptic_index is not None: self.synapse_number = synapses.variables[synapses.multisynaptic_index] else: self.synapse_number = None def __call__(self, index=None, index_var="_idx"): """ Returns synaptic indices for `index`, which can be a tuple of indices (including arrays and slices), a single index or a string. """ if index is None or (isinstance(index, str) and index == "True"): index = slice(None) if not isinstance(index, (tuple, str)) and ( isinstance(index, (numbers.Integral, np.ndarray, slice, Sequence)) or hasattr(index, "_indices") ): if hasattr(index, "_indices"): final_indices = index._indices(index_var=index_var).astype(np.int32) elif isinstance(index, slice): start, stop, step = index.indices(len(self.synaptic_pre.get_value())) final_indices = np.arange(start, stop, step, dtype=np.int32) else: final_indices = np.asarray(index) elif isinstance(index, tuple): if len(index) == 2: # two indices (pre- and postsynaptic cell) index = (index[0], index[1], slice(None)) elif len(index) > 3: raise IndexError(f"Need 1, 2 or 3 indices, got {len(index)}.") i_indices, j_indices, k_indices = index # Convert to absolute indices (e.g. for subgroups) # Allow the indexing to fail, we'll later return an empty array in # that case try: if hasattr( i_indices, "_indices" ): # will return absolute indices already i_indices = i_indices._indices() else: i_indices = self.source._indices(i_indices) pre_synapses = find_synapses(i_indices, self.synaptic_pre.get_value()) except IndexError: pre_synapses = np.array([], dtype=np.int32) try: if hasattr(j_indices, "_indices"): j_indices = j_indices._indices() else: j_indices = self.target._indices(j_indices) post_synapses = find_synapses(j_indices, self.synaptic_post.get_value()) except IndexError: post_synapses = np.array([], dtype=np.int32) matching_synapses = np.intersect1d( pre_synapses, post_synapses, assume_unique=True ) if isinstance(k_indices, slice) and k_indices == slice(None): final_indices = matching_synapses else: if self.synapse_number is None: raise IndexError( "To index by the third dimension you need " "to switch on the calculation of the " "'multisynaptic_index' when you create " "the Synapses object." ) if isinstance(k_indices, (numbers.Integral, slice)): test_k = slice_to_test(k_indices) else: raise NotImplementedError( "Indexing synapses with arrays notimplemented yet" ) # We want to access the raw arrays here, not go through the Variable synapse_numbers = self.synapse_number.get_value()[matching_synapses] final_indices = np.intersect1d( matching_synapses, np.flatnonzero(test_k(synapse_numbers)), assume_unique=True, ) else: raise IndexError(f"Unsupported index type {type(index)}") if index_var not in ("_idx", "0"): return index_var.get_value()[final_indices.astype(np.int32)] else: return final_indices.astype(np.int32) class Synapses(Group): """ Class representing synaptic connections. Creating a new `Synapses` object does by default not create any synapses, you have to call the `Synapses.connect` method for that. Parameters ---------- source : `SpikeSource` The source of spikes, e.g. a `NeuronGroup`. target : `Group`, optional The target of the spikes, typically a `NeuronGroup`. If none is given, the same as `source` model : `str`, `Equations`, optional The model equations for the synapses. on_pre : str, dict, optional The code that will be executed after every pre-synaptic spike. Can be either a single (possibly multi-line) string, or a dictionary mapping pathway names to code strings. In the first case, the pathway will be called ``pre`` and made available as an attribute of the same name. In the latter case, the given names will be used as the pathway/attribute names. Each pathway has its own code and its own delays. pre : str, dict, optional Deprecated. Use ``on_pre`` instead. on_post : str, dict, optional The code that will be executed after every post-synaptic spike. Same conventions as for `on_pre``, the default name for the pathway is ``post``. post : str, dict, optional Deprecated. Use ``on_post`` instead. delay : `Quantity`, dict, optional The delay for the "pre" pathway (same for all synapses) or a dictionary mapping pathway names to delays. If a delay is specified in this way for a pathway, it is stored as a single scalar value. It can still be changed afterwards, but only to a single scalar value. If you want to have delays that vary across synapses, do not use the keyword argument, but instead set the delays via the attribute of the pathway, e.g. ``S.pre.delay = ...`` (or ``S.delay = ...`` as an abbreviation), ``S.post.delay = ...``, etc. on_event : str or dict, optional Define the events which trigger the pre and post pathways. By default, both pathways are triggered by the ``'spike'`` event, i.e. the event that is triggered by the ``threshold`` condition in the connected groups. multisynaptic_index : str, optional The name of a variable (which will be automatically created) that stores the "synapse number". This number enumerates all synapses between the same source and target so that they can be distinguished. For models where each source-target pair has only a single connection, this number only wastes memory (it would always default to 0), it is therefore not stored by default. Defaults to ``None`` (no variable). namespace : dict, optional A dictionary mapping identifier names to objects. If not given, the namespace will be filled in at the time of the call of `Network.run`, with either the values from the ``namespace`` argument of the `Network.run` method or from the local context, if no such argument is given. dtype : `dtype`, dict, optional The `numpy.dtype` that will be used to store the values, or a dictionary specifying the type for variable names. If a value is not provided for a variable (or no value is provided at all), the preference setting `core.default_float_dtype` is used. codeobj_class : class, optional The `CodeObject` class to use to run code. dt : `Quantity`, optional The time step to be used for the update of the state variables. Cannot be combined with the `clock` argument. clock : `Clock`, optional The update clock to be used. If neither a clock, nor the `dt` argument is specified, the `defaultclock` will be used. order : int, optional The priority of of this group for operations occurring at the same time step and in the same scheduling slot. Defaults to 0. method : str, `StateUpdateMethod`, optional The numerical integration method to use. If none is given, an appropriate one is automatically determined. name : str, optional The name for this object. If none is given, a unique name of the form ``synapses``, ``synapses_1``, etc. will be automatically chosen. """ add_to_magic_network = True def __init__( self, source, target=None, model=None, on_pre=None, pre=None, on_post=None, post=None, connect=None, delay=None, on_event="spike", multisynaptic_index=None, namespace=None, dtype=None, codeobj_class=None, dt=None, clock=None, order=0, method=("exact", "euler", "heun"), method_options=None, name="synapses*", ): if connect is not None: raise TypeError( "The connect keyword argument is no longer " "supported, call the connect method instead." ) if pre is not None: if on_pre is not None: raise TypeError( "Cannot specify both 'pre' and 'on_pre'. The " "'pre' keyword is deprecated, use the 'on_pre' " "keyword instead." ) logger.warn( "The 'pre' keyword is deprecated, use 'on_pre' instead.", "deprecated_pre", once=True, ) on_pre = pre if post is not None: if on_post is not None: raise TypeError( "Cannot specify both 'post' and 'on_post'. The " "'post' keyword is deprecated, use the " "'on_post' keyword instead." ) logger.warn( "The 'post' keyword is deprecated, use 'on_post' instead.", "deprecated_post", once=True, ) on_post = post Group.__init__( self, dt=dt, clock=clock, when="start", order=order, namespace=namespace, name=name, ) if dtype is None: dtype = {} if isinstance(dtype, MutableMapping): dtype["lastupdate"] = self._clock.variables["t"].dtype #: remember whether connect was called to raise an error if an #: assignment to a synaptic variable is attempted without a preceding #: connect. self._connect_called = False self.codeobj_class = codeobj_class self.source = source self.add_dependency(source) if target is None: self.target = self.source else: self.target = target self.add_dependency(target) ##### Prepare and validate equations if model is None: model = "" if isinstance(model, str): model = Equations(model) if not isinstance(model, Equations): raise TypeError( "model has to be a string or an Equations " f"object, is '{type(model)}' instead." ) # Check flags model.check_flags( { DIFFERENTIAL_EQUATION: ["event-driven", "clock-driven"], SUBEXPRESSION: ["summed", "shared", "constant over dt"], PARAMETER: ["constant", "shared"], }, incompatible_flags=[ ("event-driven", "clock-driven"), # 'summed' cannot be combined with # any other flag ("summed", "shared", "constant over dt"), ], ) for name in ["i", "j", "delay"]: if name in model.names: raise SyntaxError( f"'{name}' is a reserved name that cannot be " "used as a variable name." ) # Add the "multisynaptic index", if desired self.multisynaptic_index = multisynaptic_index if multisynaptic_index is not None: if not isinstance(multisynaptic_index, str): raise TypeError("multisynaptic_index argument has to be a string") model = model + Equations(f"{multisynaptic_index} : integer") # Separate subexpressions depending whether they are considered to be # constant over a time step or not model, constant_over_dt = extract_constant_subexpressions(model) # Separate the equations into event-driven equations, # continuously updated equations and summed variable updates event_driven = [] continuous = [] summed_updates = [] for single_equation in model.values(): if "event-driven" in single_equation.flags: event_driven.append(single_equation) elif "summed" in single_equation.flags: summed_updates.append(single_equation) else: if ( single_equation.type == DIFFERENTIAL_EQUATION and "clock-driven" not in single_equation.flags ): logger.info( "The synaptic equation for the variable " f"{single_equation.varname} does not specify whether it " "should be integrated at every timestep ('clock-driven') " "or only at spiking events ('event-driven'). It will be " "integrated at every timestep which can slow down your " "simulation unnecessarily if you only need the values of " "this variable whenever a spike occurs. Specify the equation " "as clock-driven explicitly to avoid this warning.", "clock_driven", once=True, ) continuous.append(single_equation) if single_equation.type != DIFFERENTIAL_EQUATION: # General subexpressions (not summed variables) or # parameters, might be referred from event-driven equations # as well. # Note that the code generation step will ignore them if # nothing refers to them, so we don't have to filter here. event_driven.append(single_equation) # Get the dependencies of all equations dependencies = model.dependencies # Check whether there are dependencies between summed # variables/clocked-driven equations and event-driven variables for eq_name, deps in dependencies.items(): eq = model[eq_name] if not (eq.type == DIFFERENTIAL_EQUATION or "summed" in eq.flags): continue if eq in continuous: Synapses.verify_dependencies( eq, "clock-driven", deps, event_driven, "event-driven" ) elif "summed" in eq.flags: Synapses.verify_dependencies( eq, "summed", deps, event_driven, "event-driven" ) elif eq in event_driven: Synapses.verify_dependencies( eq, "event-driven", deps, continuous, "clock-driven" ) if any(eq.type == DIFFERENTIAL_EQUATION for eq in event_driven): self.event_driven = Equations(event_driven) # Add the lastupdate variable, needed for event-driven updates model += Equations("lastupdate : second") else: self.event_driven = None self._create_variables(model, user_dtype=dtype) self.equations = Equations(continuous) #: Set of `Variable` objects that should be resized when the #: number of synapses changes self._registered_variables = set() for varname, var in self.variables.items(): if ( isinstance(var, DynamicArrayVariable) and self.variables.indices[varname] == "_idx" ): # Register the array with the `SynapticItemMapping` object so # it gets automatically resized self.register_variable(var) # Support 2d indexing self._indices = SynapticIndexing(self) if delay is None: delay = {} if isinstance(delay, Quantity): delay = {"pre": delay} elif not isinstance(delay, Mapping): raise TypeError( "Delay argument has to be a quantity or a " f"dictionary, is type {type(delay)} instead." ) #: List of names of all updaters, e.g. ['pre', 'post'] self._synaptic_updaters = [] #: List of all `SynapticPathway` objects self._pathways = [] if isinstance(on_event, str): events_dict = defaultdict(lambda: on_event) else: events_dict = defaultdict(lambda: "spike") events_dict.update(on_event) #: "Events" for all the pathways self.events = events_dict for prepost, argument in zip(("pre", "post"), (on_pre, on_post)): if not argument: continue if isinstance(argument, str): pathway_delay = delay.get(prepost, None) self._add_updater( argument, prepost, delay=pathway_delay, event=self.events[prepost] ) elif isinstance(argument, Mapping): for key, value in argument.items(): if not isinstance(key, str): err_msg = ( f"Keys for the 'on_{prepost}' argument" "have to be strings, got " f"{type(key)} instead." ) raise TypeError(err_msg) pathway_delay = delay.get(key, None) self._add_updater( value, prepost, objname=key, delay=pathway_delay, event=self.events[key], ) # Check whether any delays were specified for pathways that don't exist for pathway in delay: if pathway not in self._synaptic_updaters: raise ValueError( f"Cannot set the delay for pathway '{pathway}': unknown pathway." ) #: Performs numerical integration step self.state_updater = None # We only need a state update if we have differential equations if len(self.equations.diff_eq_names): self.state_updater = StateUpdater( self, method, method_options=method_options, clock=self.clock, order=order, ) self.contained_objects.append(self.state_updater) #: Update the "constant over a time step" subexpressions self.subexpression_updater = None if len(constant_over_dt) > 0: self.subexpression_updater = SubexpressionUpdater(self, constant_over_dt) self.contained_objects.append(self.subexpression_updater) #: "Summed variable" mechanism -- sum over all synapses of a #: pre-/postsynaptic target self.summed_updaters = {} # We want to raise an error if the same variable is updated twice # using this mechanism. This could happen if the Synapses object # connected a NeuronGroup to itself since then all variables are # accessible as var_pre and var_post. summed_targets = set() for single_equation in summed_updates: varname = single_equation.varname if not (varname.endswith("_pre") or varname.endswith("_post")): raise ValueError( f"The summed variable '{varname}' does not end " "in '_pre' or '_post'." ) if varname not in self.variables: raise ValueError( f"The summed variable '{varname}' does not refer " "to any known variable in the " "target group." ) if varname.endswith("_pre"): summed_target = self.source summed_target_size_name = "N_pre" orig_varname = varname[:-4] summed_var_index = "_synaptic_pre" else: summed_target = self.target summed_target_size_name = "N_post" orig_varname = varname[:-5] summed_var_index = "_synaptic_post" target_eq = getattr(summed_target, "equations", {}).get(orig_varname, None) if target_eq is None or target_eq.type != PARAMETER: raise ValueError( f"The summed variable '{varname}' needs a " f"corresponding parameter '{orig_varname}' in the " "target group." ) fail_for_dimension_mismatch( self.variables["_summed_" + varname].dim, self.variables[varname].dim, "Summed variables need to have " "the same units in Synapses " "and the target group", ) if self.variables[varname] in summed_targets: raise ValueError( f"The target variable '{orig_varname}' is already " "updated by another summed variable" ) summed_targets.add(self.variables[varname]) updater = SummedVariableUpdater( single_equation.expr, varname, self, summed_target, summed_target_size_name, summed_var_index, ) self.summed_updaters[varname] = updater self.contained_objects.append(updater) # Activate name attribute access self._enable_group_attributes() @staticmethod def verify_dependencies( eq, eq_type, deps, should_not_depend_on, should_not_depend_on_name ): """ Helper function to verify that event-driven equations do not depend on clock-driven equations and the other way round. Parameters ---------- eq : `SingleEquation` The equation to verify eq_type : str The type of the equation (for the error message) deps : list A list of dependencies should_not_depend_on : list A list of equations to verify against the dependencies should_not_depend_on_name : str The name of the list of equations (for the error message) Raises ------ `EquationError` If the given equation depends on something in the other set of equations. """ for dep in deps: if dep.equation in should_not_depend_on and ( dep.equation.type == DIFFERENTIAL_EQUATION or "summed" in dep.equation.flags ): via_str = "" if dep.via: via_str = " (via " + ", ".join(f"'{v}'" for v in dep.via) + ")" raise EquationError( f"The {eq_type} '{eq.varname}' should " "not depend on the " f"{should_not_depend_on_name} variable " f"'{dep.equation.varname}'{via_str}." ) N_outgoing_pre = property( fget=lambda self: self.variables["N_outgoing"].get_value(), doc=( "The number of outgoing synapses for each neuron in the pre-synaptic group." ), ) N_incoming_post = property( fget=lambda self: self.variables["N_incoming"].get_value(), doc=( "The number of incoming synapses for each neuron in the " "post-synaptic group." ), ) def __getitem__(self, item): indices = self.indices[item] return SynapticSubgroup(self, indices) def _set_delay(self, delay, with_unit): if "pre" not in self._synaptic_updaters: raise AttributeError( "Synapses do not have a 'pre' pathway, " "do not know what 'delay' refers to." ) # Note that we cannot simply say: "self.pre.delay = delay" because this # would not correctly deal with references to external constants var = self.pre.variables["delay"] if with_unit: reference = var.get_addressable_value_with_unit("delay", self.pre) else: reference = var.get_addressable_value("delay", self.pre) reference.set_item("True", delay, level=2) def _get_delay(self, with_unit): if "pre" not in self._synaptic_updaters: raise AttributeError( "Synapses do not have a 'pre' pathway, " "do not know what 'delay' refers to." ) var = self.pre.variables["delay"] if with_unit: return var.get_addressable_value_with_unit("delay", self.pre) else: return var.get_addressable_value("delay", self.pre) delay = property( functools.partial(_get_delay, with_unit=True), functools.partial(_set_delay, with_unit=True), doc="The presynaptic delay (if a pre-synaptic pathway exists).", ) delay_ = property( functools.partial(_get_delay, with_unit=False), functools.partial(_set_delay, with_unit=False), doc=( "The presynaptic delay without unit information (if a" "pre-synaptic pathway exists)." ), ) def _add_updater(self, code, prepost, objname=None, delay=None, event="spike"): """ Add a new target updater. Users should call `add_pre` or `add_post` instead. Parameters ---------- code : str The abstract code that should be executed on pre-/postsynaptic spikes. prepost : {'pre', 'post'} Whether the code is triggered by presynaptic or postsynaptic spikes objname : str, optional A name for the object, see `SynapticPathway` for more details. delay : `Quantity`, optional A scalar delay (same delay for all synapses) for this pathway. If not given, delays are expected to vary between synapses. Returns ------- objname : str The final name for the object. Equals `objname` if it was explicitly given (and did not end in a wildcard character). """ if prepost == "pre": spike_group, group_name = self.source, "Source" elif prepost == "post": spike_group, group_name = self.target, "Target" else: raise AssertionError( f"'prepost' argument has to be 'pre' or 'post', is '{prepost}'." ) if event not in spike_group.events: if event == "spike": threshold_text = " Did you forget to set a 'threshold'?" else: threshold_text = "" raise ValueError( f"{group_name} group '{spike_group.name}' does not define " f"an event '{event}'.{threshold_text}" ) if not isinstance(spike_group, SpikeSource) or not hasattr( spike_group, "clock" ): raise TypeError( f"'{group_name}' has to be a SpikeSource with spikes and" f" clock attribute. Is type {type(spike_group)!r} instead." ) updater = SynapticPathway( self, code, prepost, objname, delay=delay, event=event ) objname = updater.objname if hasattr(self, objname): raise ValueError( f"Cannot add updater with name '{objname}', synapses " "object already has an attribute with this " "name." ) setattr(self, objname, updater) self._synaptic_updaters.append(objname) self._pathways.append(updater) self.contained_objects.append(updater) return objname def _create_variables(self, equations, user_dtype=None): """ Create the variables dictionary for this `Synapses`, containing entries for the equation variables and some standard entries. """ self.variables = Variables(self) # Standard variables always present self.variables.add_dynamic_array( "_synaptic_pre", size=0, dtype=np.int32, constant=True, read_only=True ) self.variables.add_dynamic_array( "_synaptic_post", size=0, dtype=np.int32, constant=True, read_only=True ) self.variables.create_clock_variables(self._clock) if "_offset" in self.target.variables: self.variables.add_reference("_target_offset", self.target, "_offset") else: self.variables.add_constant("_target_offset", value=0) if "_offset" in self.source.variables: self.variables.add_reference("_source_offset", self.source, "_offset") else: self.variables.add_constant("_source_offset", value=0) # To cope with connections to/from other synapses, N_incoming/N_outgoing # will be resized when synapses are created self.variables.add_dynamic_array( "N_incoming", size=0, dtype=np.int32, constant=True, read_only=True, index="_postsynaptic_idx", ) self.variables.add_dynamic_array( "N_outgoing", size=0, dtype=np.int32, constant=True, read_only=True, index="_presynaptic_idx", ) # We have to make a distinction here between the indices # and the arrays (even though they refer to the same object) # the synaptic propagation template would otherwise overwrite # synaptic_post in its namespace with the value of the # postsynaptic index, leading to errors for the next # propagation. self.variables.add_reference("_presynaptic_idx", self, "_synaptic_pre") self.variables.add_reference("_postsynaptic_idx", self, "_synaptic_post") # Except for subgroups (which potentially add an offset), the "i" and # "j" variables are simply equivalent to `_synaptic_pre` and # `_synaptic_post` if getattr(self.source, "start", 0) == 0: self.variables.add_reference("i", self, "_synaptic_pre") else: self.variables.add_reference( "_source_i", self.source.source, "i", index="_presynaptic_idx" ) self.variables.add_reference("_source_offset", self.source, "_offset") self.variables.add_subexpression( "i", dtype=self.source.source.variables["i"].dtype, expr="_source_i - _source_offset", index="_presynaptic_idx", ) if getattr(self.target, "start", 0) == 0: self.variables.add_reference("j", self, "_synaptic_post") else: self.variables.add_reference( "_target_j", self.target.source, "i", index="_postsynaptic_idx" ) self.variables.add_reference("_target_offset", self.target, "_offset") self.variables.add_subexpression( "j", dtype=self.target.source.variables["i"].dtype, expr="_target_j - _target_offset", index="_postsynaptic_idx", ) # Add the standard variables self.variables.add_array( "N", dtype=np.int32, size=1, scalar=True, constant=True, read_only=True ) for eq in equations.values(): dtype = get_dtype(eq, user_dtype) if eq.type in (DIFFERENTIAL_EQUATION, PARAMETER): check_identifier_pre_post(eq.varname) constant = "constant" in eq.flags shared = "shared" in eq.flags if shared: self.variables.add_array( eq.varname, size=1, dimensions=eq.dim, dtype=dtype, constant=constant, scalar=True, index="0", ) else: self.variables.add_dynamic_array( eq.varname, size=0, dimensions=eq.dim, dtype=dtype, constant=constant, ) elif eq.type == SUBEXPRESSION: if "summed" in eq.flags: # Give a special name to the subexpression for summed # variables to avoid confusion with the pre/postsynaptic # target variable varname = "_summed_" + eq.varname else: check_identifier_pre_post(eq.varname) varname = eq.varname self.variables.add_subexpression( varname, dimensions=eq.dim, expr=str(eq.expr), scalar="shared" in eq.flags, dtype=dtype, ) else: raise AssertionError(f"Unknown type of equation: {eq.eq_type}") # Stochastic variables for xi in equations.stochastic_variables: self.variables.add_auxiliary_variable(xi, dimensions=(second**-0.5).dim) # Add all the pre and post variables with _pre and _post suffixes for name in getattr(self.source, "variables", {}): # Raise an error if a variable name is also used for a synaptic # variable (we ignore 'lastupdate' to allow connections from another # Synapses object) if ( name in equations.names and name != "lastupdate" and "summed" not in equations[name].flags ): error_msg = ( f"The pre-synaptic variable {name} has the same " "name as a synaptic variable, rename the synaptic " "variable." ) if name + "_syn" not in self.variables: error_msg += f"(for example to '{name}_syn') " error_msg += "to avoid confusion" raise ValueError(error_msg) if name.startswith("_"): continue # Do not add internal variables var = self.source.variables[name] index = "0" if var.scalar else "_presynaptic_idx" try: self.variables.add_reference( name + "_pre", self.source, name, index=index ) except TypeError: logger.diagnostic( f"Cannot include a reference to '{name}' in " f"'{self.name}', '{name}' uses a non-standard " "indexing in the pre-synaptic group " f"'{self.source.name}'." ) for name in getattr(self.target, "variables", {}): # Raise an error if a variable name is also used for a synaptic # variable (we ignore 'lastupdate' to allow connections to another # Synapses object) if ( name in equations.names and name != "lastupdate" and "summed" not in equations[name].flags ): error_msg = ( f"The post-synaptic variable '{name}' has the same " "name as a synaptic variable, rename the synaptic " "variable." ) if name + "_syn" not in self.variables: error_msg += f"(for example to '{name}_syn') " error_msg += "to avoid confusion" raise ValueError(error_msg) if name.startswith("_"): continue # Do not add internal variables var = self.target.variables[name] index = "0" if var.scalar else "_postsynaptic_idx" try: self.variables.add_reference( name + "_post", self.target, name, index=index ) # Also add all the post variables without a suffix, but only if # it does not have a post or pre suffix in the target group # (which could happen when connecting to synapses) if not name.endswith("_post") or name.endswith("_pre"): self.variables.add_reference(name, self.target, name, index=index) except TypeError: logger.diagnostic( f"Cannot include a reference to '{name}' in " f"'{self.name}', '{name}' uses a non-standard " "indexing in the post-synaptic group " f"'{self.target.name}'." ) # Check scalar subexpressions for eq in equations.values(): if eq.type == SUBEXPRESSION and "shared" in eq.flags: var = self.variables[eq.varname] for identifier in var.identifiers: if identifier in self.variables: if not self.variables[identifier].scalar: raise SyntaxError( f"Shared subexpression '{eq.varname}' " "refers to non-shared variable " f"'{identifier}'." ) def before_run(self, run_namespace): self.equations.check_units(self, run_namespace=run_namespace) # Check that subexpressions that refer to stateful functions are labeled # as "constant over dt" check_subexpressions(self, self.equations, run_namespace) super().before_run(run_namespace=run_namespace) @device_override("synapses_connect") def connect( self, condition=None, i=None, j=None, p=1.0, n=1, skip_if_invalid=False, namespace=None, level=0, ): """ Add synapses. See :doc:`/user/synapses` for details. Parameters ---------- condition : str, bool, optional A boolean or string expression that evaluates to a boolean. The expression can depend on indices ``i`` and ``j`` and on pre- and post-synaptic variables. Can be combined with arguments ``n``, and ``p`` but not ``i`` or ``j``. i : int, ndarray of int, str, optional The presynaptic neuron indices It can be an index or array of indices if combined with the ``j`` argument, or it can be a string generator expression. j : int, ndarray of int, str, optional The postsynaptic neuron indices. It can be an index or array of indices if combined with the ``i`` argument, or it can be a string generator expression. p : float, str, optional The probability to create ``n`` synapses wherever the ``condition`` evaluates to true. Cannot be used with generator syntax for ``j``. n : int, str, optional The number of synapses to create per pre/post connection pair. Defaults to 1. skip_if_invalid : bool, optional If set to True, rather than raising an error if you try to create an invalid/out of range pair (i, j) it will just quietly skip those synapses. namespace : dict-like, optional A namespace that will be used in addition to the group-specific namespaces (if defined). If not specified, the locals and globals around the run function will be used. level : int, optional How deep to go up the stack frame to look for the locals/global (see ``namespace`` argument). Examples -------- >>> from brian2 import * >>> import numpy as np >>> G = NeuronGroup(10, 'dv/dt = -v / tau : 1', threshold='v>1', reset='v=0') >>> S = Synapses(G, G, 'w:1', on_pre='v+=w') >>> S.connect(condition='i != j') # all-to-all but no self-connections >>> S.connect(i=0, j=0) # connect neuron 0 to itself >>> S.connect(i=np.array([1, 2]), j=np.array([2, 1])) # connect 1->2 and 2->1 >>> S.connect() # connect all-to-all >>> S.connect(condition='i != j', p=0.1) # Connect neurons with 10% probability, exclude self-connections >>> S.connect(j='i', n=2) # Connect all neurons to themselves with 2 synapses >>> S.connect(j='k for k in range(i+1)') # Connect neuron i to all j with 0<=j<=i >>> S.connect(j='i+(-1)**k for k in range(2) if i>0 and i>> S.connect(j='k for k in sample(N_post, p=i*1.0/(N_pre-1))') # neuron i connects to j with probability i/(N-1) >>> S.connect(j='k for k in sample(N_post, size=i//2)') # Each neuron connects to i//2 other neurons (chosen randomly) """ # check types self._verify_connect_argument_types(condition, i, j, n, p) self._connect_called = True # Get namespace information if namespace is None: namespace = get_local_namespace(level=level + 2) try: # wrap everything to catch IndexError # which connection case are we in? # 1: Connection condition if condition is None and i is None and j is None: condition = True if condition is not None: if i is not None or j is not None: raise ValueError("Cannot combine condition with i or j arguments") if condition is False or condition == "False": # Nothing to do return j = self._condition_to_generator_expression(condition, p, namespace) self._add_synapses_generator( j, n, skip_if_invalid=skip_if_invalid, namespace=namespace, level=level + 2, over_presynaptic=True, ) # 2: connection indices elif (i is not None and j is not None) and not ( isinstance(i, str) or isinstance(j, str) ): if skip_if_invalid: raise ValueError("Can only use skip_if_invalid with string syntax") i, j, n = self._verify_connect_array_arguments(i, j, n) self._add_synapses_from_arrays(i, j, n, p, namespace=namespace) # 3: Generator expression over post-synaptic cells (i='...') elif isinstance(i, str): i = self._finalize_generator_expression(i, j, p, "i", "j") self._add_synapses_generator( i, n, skip_if_invalid=skip_if_invalid, namespace=namespace, level=level + 2, over_presynaptic=False, ) # 4: Generator expression over pre-synaptic cells (i='...') elif isinstance(j, str): j = self._finalize_generator_expression(j, i, p, "j", "i") self._add_synapses_generator( j, n, skip_if_invalid=skip_if_invalid, namespace=namespace, level=level + 2, over_presynaptic=True, ) else: raise ValueError( "Must specify at least one of condition, i or j arguments" ) except IndexError as e: raise IndexError( "Tried to create synapse indices outside valid " "range. Original error message: " + str(e) ) # Helper functions for Synapses.connect ↑ def _verify_connect_array_arguments(self, i, j, n): if hasattr(i, "_indices"): i = i._indices() i = np.asarray(i) if not np.issubdtype(i.dtype, np.signedinteger): raise TypeError( "Presynaptic indices have to be given as " f"integers, are type {i.dtype} " "instead." ) if hasattr(j, "_indices"): j = j._indices() j = np.asarray(j) if not np.issubdtype(j.dtype, np.signedinteger): raise TypeError( "Presynaptic indices can only be combined " "with postsynaptic integer indices))" ) if isinstance(n, str): raise TypeError( "Indices cannot be combined with a string" "expression for n. Either use an " "array/scalar for n, or a string " "expression for the connections" ) i, j, n = np.broadcast_arrays(i, j, n) if i.ndim > 1: raise ValueError("Can only use 1-dimensional indices") return i, j, n def _condition_to_generator_expression(self, condition, p, namespace): if condition is True: condition = "True" # Check that the condition is a boolean expresion identifiers = get_identifiers(condition) variables = self.resolve_all(identifiers, namespace) if not is_boolean_expression(condition, variables): raise TypeError(f"Condition '{condition}' is not a boolean condition") # Check the units (mostly to check for unit consistency within the condition) dims = parse_expression_dimensions(condition, variables) if dims is not DIMENSIONLESS: # We should not get here normally raise TypeError(f"Condition '{condition}' is not a boolean condition") condition = word_substitute(condition, {"j": "_k"}) if not isinstance(p, str) and p == 1: j = f"_k for _k in range(N_post) if {condition}" else: j = None if isinstance(p, str): identifiers = get_identifiers(p) variables = self.resolve_all(identifiers, namespace) dim = parse_expression_dimensions(p, variables) if dim is not DIMENSIONLESS: raise DimensionMismatchError( "Expression for p should be dimensionless." ) p_dep = self._expression_index_dependence(p, namespace=namespace) if "_postsynaptic_idx" in p_dep or "_iterator_idx" in p_dep: j = f"_k for _k in range(N_post) if ({condition}) and rand()<{p}" if j is None: j = f"_k for _k in sample(N_post, p={p}) if {condition}" return j def _verify_connect_argument_types(self, condition, i, j, n, p): if condition is not None and not isinstance(condition, (bool, str)): raise TypeError( "condition argument must be bool or string. If you " "want to connect based on indices, use " "connect(i=..., j=...)." ) if i is not None and not ( isinstance(i, (numbers.Integral, np.ndarray, Sequence)) or hasattr(i, "_indices") ): raise TypeError("i argument must be int, array or string") if j is not None and not ( isinstance(j, (numbers.Integral, np.ndarray, Sequence)) or hasattr(j, "_indices") ): raise TypeError("j argument must be int, array or string") # TODO: eliminate these restrictions if not isinstance(p, (int, float, str)): raise TypeError("p must be float or string") if not isinstance(n, (int, str)): raise TypeError("n must be int or string") if isinstance(condition, str) and re.search(r"\bfor\b", condition): raise ValueError( "Generator expression given for condition, write " f"connect(j='{condition}'...) instead of " f"connect('{condition}'...)." ) def check_variable_write(self, variable): """ Checks that `Synapses.connect` has been called before setting a synaptic variable. Parameters ---------- variable : `Variable` The variable that the user attempts to set. Raises ------ TypeError If `Synapses.connect` has not been called yet. """ if not self._connect_called: raise TypeError( f"Cannot write to synaptic variable '{variable.name}', you " "need to call connect(...) first" ) def _resize(self, number): if not isinstance(number, (numbers.Integral, np.integer)): raise TypeError(f"Expected an integer number, got {type(number)} instead.") if number < self.N: raise ValueError( f"Cannot reduce number of synapses, {number} < {len(self)}." ) for variable in self._registered_variables: variable.resize(number) self.variables["N"].set_value(number) def _update_synapse_numbers(self, old_num_synapses): source_offset = self.variables["_source_offset"].get_value() target_offset = self.variables["_target_offset"].get_value() # This resizing is only necessary if we are connecting to/from synapses post_with_offset = self.variables["N_post"].item() + target_offset pre_with_offset = self.variables["N_pre"].item() + source_offset self.variables["N_incoming"].resize(post_with_offset) self.variables["N_outgoing"].resize(pre_with_offset) N_outgoing = self.variables["N_outgoing"].get_value() N_incoming = self.variables["N_incoming"].get_value() synaptic_pre = self.variables["_synaptic_pre"].get_value() synaptic_post = self.variables["_synaptic_post"].get_value() # Update the number of total outgoing/incoming synapses per # source/target neuron N_outgoing[:] += np.bincount( synaptic_pre[old_num_synapses:], minlength=len(N_outgoing) ) N_incoming[:] += np.bincount( synaptic_post[old_num_synapses:], minlength=len(N_incoming) ) if self.multisynaptic_index is not None: synapse_number_var = self.variables[self.multisynaptic_index] synapse_number = synapse_number_var.get_value() # Update the "synapse number" (number of synapses for the same # source-target pair) # We wrap pairs of source/target indices into a complex number for # convenience _source_target_pairs = synaptic_pre + synaptic_post * 1j synapse_number[:] = calc_repeats(_source_target_pairs) def register_variable(self, variable): """ Register a `DynamicArray` to be automatically resized when the size of the indices change. Called automatically when a `SynapticArrayVariable` specifier is created. """ if not hasattr(variable, "resize"): raise TypeError( f"Variable of type {type(variable)} does not have a resize " "method, cannot register it with the synaptic " "indices." ) self._registered_variables.add(variable) def unregister_variable(self, variable): """ Unregister a `DynamicArray` from the automatic resizing mechanism. """ self._registered_variables.remove(variable) def _get_multisynaptic_indices(self): template_kwds = {"multisynaptic_index": self.multisynaptic_index} if self.multisynaptic_index is not None: needed_variables = [self.multisynaptic_index] else: needed_variables = [] return template_kwds, needed_variables def _add_synapses_from_arrays(self, sources, targets, n, p, namespace=None): template_kwds, needed_variables = self._get_multisynaptic_indices() variables = Variables(self) sources = np.atleast_1d(sources).astype(np.int32) targets = np.atleast_1d(targets).astype(np.int32) # Check whether the values in sources/targets make sense error_message = ( "The given {source_or_target} indices contain " "values outside of the range [0, {max_value}] " "allowed for the {source_or_target} group " '"{group_name}"' ) try: for indices, source_or_target, group in [ (sources, "source", self.source), (targets, "target", self.target), ]: if np.max(indices) >= len(group) or np.min(indices) < 0: raise IndexError( error_message.format( source_or_target=source_or_target, max_value=len(group) - 1, group_name=group.name, ) ) except NotImplementedError: logger.warn( "Cannot check whether the indices given for the connect call are valid." " This can happen in standalone mode when using indices to connect to" " synapses that have been created with a connection pattern. You can" " avoid this situation by either using a connection pattern or synaptic" " indices in both connect calls.", name_suffix="cannot_check_synapse_indices", ) n = np.atleast_1d(n) p = np.atleast_1d(p) if not len(p) == 1 or p != 1: use_connections = np.random.rand(len(sources)) < p sources = sources[use_connections] targets = targets[use_connections] n = n[use_connections] sources = sources.repeat(n) targets = targets.repeat(n) variables.add_array( "sources", len(sources), dtype=np.int32, values=sources, read_only=True ) variables.add_array( "targets", len(targets), dtype=np.int32, values=targets, read_only=True ) # These definitions are important to get the types right in C++ variables.add_auxiliary_variable("_real_sources", dtype=np.int32) variables.add_auxiliary_variable("_real_targets", dtype=np.int32) abstract_code = "" if "_offset" in self.source.variables: variables.add_reference("_source_offset", self.source, "_offset") abstract_code += "_real_sources = sources + _source_offset\n" else: abstract_code += "_real_sources = sources\n" if "_offset" in self.target.variables: variables.add_reference("_target_offset", self.target, "_offset") abstract_code += "_real_targets = targets + _target_offset\n" else: abstract_code += "_real_targets = targets" logger.debug( f"Creating synapses from group '{self.source.name}' to group " f"'{self.target.name}', using pre-defined arrays)" ) codeobj = create_runner_codeobj( self, abstract_code, "synapses_create_array", additional_variables=variables, template_kwds=template_kwds, needed_variables=needed_variables, check_units=False, run_namespace={}, ) codeobj() def _expression_index_dependence(self, expr, namespace, additional_indices=None): """ Returns the set of synaptic indices that expr depends on """ nr = NodeRenderer() expr = nr.render_expr(expr) deps = set() if additional_indices is None: additional_indices = {} identifiers = get_identifiers_recursively([expr], self.variables) variables = self.resolve_all( {name for name in identifiers if name not in additional_indices}, namespace ) if any(getattr(var, "auto_vectorise", False) for var in variables.values()): identifiers.add("_vectorisation_idx") for varname in identifiers: # Special handling of i and j -- they do not actually use pre-/ # postsynaptic indices (except for subgroups), they *are* the # pre-/postsynaptic indices if varname == "i": deps.add("_presynaptic_idx") elif varname == "j": deps.add("_iterator_idx") elif varname in additional_indices: deps.add(additional_indices[varname]) else: deps.add(self.variables.indices[varname]) if "0" in deps: deps.remove("0") return deps def _add_synapses_generator( self, gen, n, skip_if_invalid=False, over_presynaptic=True, namespace=None, level=0, ): # Get the local namespace if namespace is None: namespace = get_local_namespace(level=level + 1) parsed = parse_synapse_generator(gen) self._check_parsed_synapses_generator(parsed, namespace) # Referring to N_incoming/N_outgoing in the connect statement is # ill-defined (see github issue #1227) identifiers = get_identifiers_recursively([gen], self.variables) for var in ["N_incoming", "N_outgoing"]: if var in identifiers: raise ValueError(f"The connect statement cannot refer to '{var}'.") template_kwds, needed_variables = self._get_multisynaptic_indices() template_kwds.update(parsed) template_kwds["skip_if_invalid"] = skip_if_invalid # To support both i='...' and j='...' syntax, we provide additional keywords # to the template outer_index = "i" if over_presynaptic else "j" outer_index_size = "N_pre" if over_presynaptic else "N_post" outer_index_array = "_pre_idx" if over_presynaptic else "_post_idx" outer_index_offset = "_source_offset" if over_presynaptic else "_target_offset" result_index = "j" if over_presynaptic else "i" result_index_size = "N_post" if over_presynaptic else "N_pre" target_idx = "_postsynaptic_idx" if over_presynaptic else "_presynaptic_idx" result_index_array = "_post_idx" if over_presynaptic else "_pre_idx" result_index_offset = "_target_offset" if over_presynaptic else "_source_offset" result_index_name = "postsynaptic" if over_presynaptic else "presynaptic" template_kwds.update( { "outer_index": outer_index, "outer_index_size": outer_index_size, "outer_index_array": outer_index_array, "outer_index_offset": outer_index_offset, "result_index": result_index, "result_index_size": result_index_size, "result_index_name": result_index_name, "result_index_array": result_index_array, "result_index_offset": result_index_offset, } ) abstract_code = { "setup_iterator": "", "generator_expr": "", "create_cond": "", "update": "", } additional_indices = {parsed["inner_variable"]: "_iterator_idx"} setupiter = "" for k, v in parsed["iterator_kwds"].items(): if v is not None and k != "sample_size": deps = self._expression_index_dependence( v, namespace=namespace, additional_indices=additional_indices ) if f"_{result_index_name}_idx" in deps or "_iterator_idx" in deps: raise ValueError( f'Expression "{v}" depends on {result_index_name} ' "index or iterator" ) setupiter += f"_iter_{k} = {v}\n" # rand() in the if condition depends on _vectorisation_idx, but not if # its in the range expression (handled above) additional_indices["_vectorisation_idx"] = "_iterator_idx" result_index_condition = False result_index_used = False if parsed["if_expression"] is not None: deps = self._expression_index_dependence( parsed["if_expression"], namespace=namespace, additional_indices=additional_indices, ) if target_idx in deps: result_index_condition = True result_index_used = True elif "_iterator_idx" in deps: result_index_condition = True template_kwds["result_index_condition"] = result_index_condition template_kwds["result_index_used"] = result_index_used abstract_code["setup_iterator"] += setupiter abstract_code[ "generator_expr" ] += f"{outer_index_array} = _raw{outer_index_array} \n" abstract_code["generator_expr"] += f'_{result_index} = {parsed["element"]}\n' if result_index_condition: abstract_code[ "create_cond" ] += f"{result_index_array} = _raw{result_index_array} \n" if parsed["if_expression"] is not None: abstract_code["create_cond"] += "_cond = " + parsed["if_expression"] + "\n" abstract_code[ "update" ] += f"{result_index_array} = _raw{result_index_array} \n" abstract_code["update"] += "_n = " + str(n) + "\n" # This overwrites 'i' and 'j' in the synapses' variables dictionary # This is necessary because in the context of synapse creation, i # and j do not correspond to the sources/targets of the existing # synapses but to all the possible sources/targets variables = Variables(None) # Will be set in the template variables.add_auxiliary_variable("_i", dtype=np.int32) variables.add_auxiliary_variable("_j", dtype=np.int32) variables.add_auxiliary_variable("_iter_low", dtype=np.int32) variables.add_auxiliary_variable("_iter_high", dtype=np.int32) variables.add_auxiliary_variable("_iter_step", dtype=np.int32) variables.add_auxiliary_variable("_iter_p") variables.add_auxiliary_variable("_iter_size", dtype=np.int32) variables.add_auxiliary_variable(parsed["inner_variable"], dtype=np.int32) # Make sure that variables have the correct type in the code variables.add_auxiliary_variable("_pre_idx", dtype=np.int32) variables.add_auxiliary_variable("_post_idx", dtype=np.int32) if parsed["if_expression"] is not None: variables.add_auxiliary_variable("_cond", dtype=bool) variables.add_auxiliary_variable("_n", dtype=np.int32) if "_offset" in self.source.variables: variables.add_reference("_source_offset", self.source, "_offset") else: variables.add_constant("_source_offset", value=0) if "_offset" in self.target.variables: variables.add_reference("_target_offset", self.target, "_offset") else: variables.add_constant("_target_offset", value=0) variables.add_auxiliary_variable("_raw_pre_idx", dtype=np.int32) variables.add_auxiliary_variable("_raw_post_idx", dtype=np.int32) variable_indices = defaultdict(lambda: "_idx") for varname in self.variables: if self.variables.indices[varname] == "_presynaptic_idx": variable_indices[varname] = "_raw_pre_idx" elif self.variables.indices[varname] == "_postsynaptic_idx": variable_indices[varname] = "_raw_post_idx" if self.variables["i"] is self.variables["_synaptic_pre"]: variables.add_subexpression("i", "_i", dtype=self.variables["i"].dtype) if self.variables["j"] is self.variables["_synaptic_post"]: variables.add_subexpression("j", "_j", dtype=self.variables["j"].dtype) logger.debug( f"Creating synapses from group '{self.source.name}' to group " f"'{self.target.name}', using generator " f"'{parsed['original_expression']}'" ) codeobj = create_runner_codeobj( self, abstract_code, "synapses_create_generator", variable_indices=variable_indices, additional_variables=variables, template_kwds=template_kwds, needed_variables=needed_variables, check_units=False, run_namespace=namespace, ) codeobj() def _check_parsed_synapses_generator(self, parsed, namespace): """ Type-check the parsed synapses generator. This function will raise a TypeError if any of the arguments to the iterator function are of an invalid type. """ if parsed["iterator_func"] == "range": # We expect all arguments of the range function to be integers for argname, arg in parsed["iterator_kwds"].items(): identifiers = get_identifiers(arg) variables = self.resolve_all( identifiers, run_namespace=namespace, user_identifiers=identifiers ) annotated = brian_ast(arg, variables) if annotated.dtype != "integer": raise TypeError( f"The '{argname}' argument of the range function was " f"'{arg}', but it needs to be an integer." ) def _finalize_generator_expression( self, generator_expression, iteration_index, p, target_index_name, iteration_index_name, ): if iteration_index is not None: raise TypeError( f"Generator syntax for {target_index_name} cannot be combined with " f"{iteration_index_name} argument" ) if isinstance(p, str) or p != 1: raise ValueError("Generator syntax cannot be combined with p argument") if not re.search(r"\bfor\b", generator_expression): if_split = generator_expression.split(" if ") if len(if_split) == 1: generator_expression = f"{generator_expression} for _ in range(1)" elif len(if_split) == 2: generator_expression = ( f"{if_split[0]} for _ in range(1) if {if_split[1]}" ) else: raise SyntaxError( f"Error parsing expression '{generator_expression}'. " "Expression must have generator " "syntax, for example 'k for k in " f"range({iteration_index_name}-10, {iteration_index_name}+10)'" ) return generator_expression brian2-2.5.4/brian2/tests/000077500000000000000000000000001445201106100152105ustar00rootroot00000000000000brian2-2.5.4/brian2/tests/.gitignore000066400000000000000000000000231445201106100171730ustar00rootroot00000000000000/brian_preferences brian2-2.5.4/brian2/tests/__init__.py000066400000000000000000000464021445201106100173270ustar00rootroot00000000000000""" Package contain all unit/integration tests for the `brian2` package. """ import os import sys import tempfile from io import StringIO import numpy as np import brian2 from brian2.core.preferences import prefs from brian2.devices.device import all_devices, reinit_and_delete, reset_device try: import importlib import pytest from _pytest import doctest as pytest_doctest class OurDoctestModule(pytest_doctest.DoctestModule): def collect(self): for item in super().collect(): # Check the object for exclusion from doctests full_name = item.name.split(".") test_name = [] module_name = os.path.splitext(os.path.basename(self.name))[0] while full_name[-1] != module_name: test_name.append(full_name.pop()) tested_obj = self.obj for name in reversed(test_name): tested_obj = getattr(tested_obj, name) if not getattr(tested_obj, "_do_not_run_doctests", False): yield item # Monkey patch pytest pytest_doctest.DoctestModule = OurDoctestModule except ImportError: pytest = None class PreferencePlugin: def __init__(self, prefs, fail_for_not_implemented=True): self.prefs = prefs self.device = "runtime" self.device_options = {} self.fail_for_not_implemented = fail_for_not_implemented def pytest_configure(self, config): config.brian_prefs = dict(self.prefs) config.fail_for_not_implemented = self.fail_for_not_implemented config.device = self.device config.device_options = self.device_options if config.pluginmanager.hasplugin("xdist"): xdist_plugin = XDistPreferencePlugin(self) config.pluginmanager.register(xdist_plugin) class XDistPreferencePlugin: def __init__(self, pref_plugin): self._pref_plugin = pref_plugin def pytest_configure_node(self, node): """xdist hook""" prefs = dict(self._pref_plugin.prefs) for k, v in prefs.items(): if isinstance(v, type): prefs[k] = ("TYPE", repr(v)) node.workerinput["brian_prefs"] = prefs node.workerinput[ "fail_for_not_implemented" ] = self._pref_plugin.fail_for_not_implemented node.workerinput["device"] = self._pref_plugin.device node.workerinput["device_options"] = self._pref_plugin.device_options def clear_caches(): from brian2.utils.logger import BrianLogger BrianLogger._log_messages.clear() from brian2.codegen.translation import make_statements make_statements._cache.clear() def make_argv(dirnames, markers=None, doctests=False, test_GSL=False): """ Create the list of arguments for the ``pytests`` call. Parameters ---------- markers : str, optional The markers of the tests to include. doctests : bool, optional Whether to run doctests. Defaults to ``False``. test_GSL : bool, optional Whether to run tests requiring the GSL. If set to ``False``, tests marked with ``gsl`` will be excluded. Defaults to ``False``. Returns ------- argv : list of str The arguments for `pytest.main`. """ if doctests: if markers is not None: raise TypeError("Cannot give markers for doctests") argv = dirnames + [ "-c", os.path.join(os.path.dirname(__file__), "pytest.ini"), "--quiet", "--doctest-modules", "--doctest-glob=*.rst", "--doctest-ignore-import-errors", "--confcutdir", os.path.abspath(os.path.join(os.path.dirname(__file__), "..")), "--pyargs", "brian2", ] if len(dirnames) == 2: # If we are testing files in docs_sphinx, ignore conf.py argv += [f"--ignore={os.path.join(dirnames[1], 'conf.py')}"] else: if not test_GSL: markers += " and not gsl" argv = dirnames + [ "-c", os.path.join(os.path.dirname(__file__), "pytest.ini"), "--quiet", "-m", f"{markers}", "--confcutdir", os.path.abspath(os.path.join(os.path.dirname(__file__), "..")), ] return argv def run( codegen_targets=None, long_tests=False, test_codegen_independent=True, test_standalone=None, test_openmp=False, test_in_parallel=["codegen_independent", "numpy", "cython", "cpp_standalone"], reset_preferences=True, fail_for_not_implemented=True, test_GSL=False, build_options=None, extra_test_dirs=None, sphinx_dir=None, float_dtype=None, additional_args=None, ): """ Run brian's test suite. Needs an installation of the pytest testing tool. For testing, the preferences will be reset to the default preferences. After testing, the user preferences will be restored. Parameters ---------- codegen_targets : list of str or str A list of codegeneration targets or a single target, e.g. ``['numpy', 'cython']`` to test. The whole test suite will be repeatedly run with `codegen.target` set to the respective value. If not specified, all available code generation targets will be tested. long_tests : bool, optional Whether to run tests that take a long time. Defaults to ``False``. test_codegen_independent : bool, optional Whether to run tests that are independent of code generation. Defaults to ``True``. test_standalone : str, optional Whether to run tests for a standalone mode. Should be the name of a standalone mode (e.g. ``'cpp_standalone'``) and expects that a device of that name and an accordingly named "simple" device (e.g. ``'cpp_standalone_simple'`` exists that can be used for testing (see `CPPStandaloneSimpleDevice` for details. Defaults to ``None``, meaning that no standalone device is tested. test_openmp : bool, optional Whether to test standalone test with multiple threads and OpenMP. Will be ignored if ``cpp_standalone`` is not tested. Defaults to ``False``. reset_preferences : bool, optional Whether to reset all preferences to the default preferences before running the test suite. Defaults to ``True`` to get test results independent of the user's preference settings but can be switched off when the preferences are actually necessary to pass the tests (e.g. for device-specific settings). fail_for_not_implemented : bool, optional Whether to fail for tests raising a `NotImplementedError`. Defaults to ``True``, but can be switched off for devices known to not implement all of Brian's features. test_GSL : bool, optional Whether to test support for GSL state updaters (requires an installation of the GSL development packages). Defaults to ``False``. build_options : dict, optional Non-default build options that will be passed as arguments to the `set_device` call for the device specified in ``test_standalone``. extra_test_dirs : list of str or str, optional Additional directories as a list of strings (or a single directory as a string) that will be searched for additional tests. sphinx_dir : str, optional The full path to ``docs_sphinx``, in order to execute doc tests in the rst files. If not provided, assumes we are running from a checked out repository where the directory can be found at ``../../docs_sphinx``. Will ignore the provided directory if it does not exist. float_dtype : np.dtype, optional Set the dtype to use for floating point variables to a value different from the default `core.default_float_dtype` setting. additional_args : list of str, optional Optional command line arguments to pass to ``pytest`` """ if pytest is None: raise ImportError("Running the test suite requires the 'pytest' package.") if build_options is None: build_options = {} if os.name == "nt": test_in_parallel = [] if extra_test_dirs is None: extra_test_dirs = [] elif isinstance(extra_test_dirs, str): extra_test_dirs = [extra_test_dirs] if additional_args is None: additional_args = [] multiprocess_arguments = ["-n", "auto"] if codegen_targets is None: codegen_targets = ["numpy"] try: import Cython codegen_targets.append("cython") except ImportError: pass elif isinstance(codegen_targets, str): # allow to give a single target codegen_targets = [codegen_targets] dirname = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) dirnames = [dirname] + extra_test_dirs print(f"Running tests in {', '.join(dirnames)} ", end="") if codegen_targets: print(f"for targets {', '.join(codegen_targets)}", end="") ex_in = "including" if long_tests else "excluding" print(f" ({ex_in} long tests)") print( f"Running Brian version {brian2.__version__} from" f" '{os.path.dirname(brian2.__file__)}'" ) all_targets = set(codegen_targets) if test_standalone: if not isinstance(test_standalone, str): raise ValueError( "test_standalone argument has to be the name of a " "standalone device (e.g. 'cpp_standalone')" ) if test_standalone not in all_devices: known_devices = ", ".join(repr(d) for d in all_devices) raise ValueError( "test_standalone argument 'test_standalone' is not a known " f"device. Known devices are: {known_devices}." ) print("Testing standalone") all_targets.add(test_standalone) if test_codegen_independent: print("Testing codegen-independent code") all_targets.add("codegen_independent") parallel_tests = all_targets.intersection(set(test_in_parallel)) if parallel_tests: try: import xdist print(f"Testing with multiple processes for {', '.join(parallel_tests)}") except ImportError: test_in_parallel = [] if reset_preferences: print("Resetting to default preferences") stored_prefs = prefs.as_file prefs.reset_to_defaults() # Avoid failures in the tests for user-registered units import copy import brian2.units.fundamentalunits as fundamentalunits old_unit_registry = copy.copy(fundamentalunits.user_unit_register) fundamentalunits.user_unit_register = fundamentalunits.UnitRegistry() if float_dtype is not None: print(f"Setting dtype for floating point variables to: {float_dtype.__name__}") prefs["core.default_float_dtype"] = float_dtype print() # Suppress INFO log messages during testing from brian2.utils.logger import LOG_LEVELS, BrianLogger log_level = BrianLogger.console_handler.level BrianLogger.console_handler.setLevel(LOG_LEVELS["WARNING"]) # Switch off code optimization to get faster compilation times prefs["codegen.cpp.extra_compile_args_gcc"].extend(["-w", "-O0"]) prefs["codegen.cpp.extra_compile_args_msvc"].extend(["/Od"]) pref_plugin = PreferencePlugin(prefs, fail_for_not_implemented) try: success = [] pref_plugin.device = "runtime" pref_plugin.device_options = {} if test_codegen_independent: print("Running doctests") # Some doctests do actually use code generation, use numpy for that prefs["codegen.target"] = "numpy" # Always test doctests with 64 bit, to avoid differences in print output if float_dtype is not None: prefs["core.default_float_dtype"] = np.float64 if sphinx_dir is None: sphinx_dir = os.path.abspath( os.path.join(os.path.dirname(__file__), "..", "..", "docs_sphinx") ) if os.path.exists(sphinx_dir): sphinx_doc_dir = [sphinx_dir] else: # When running on travis, the source directory is in the SRCDIR # environment variable if "SRCDIR" in os.environ: sphinx_dir = os.path.abspath( os.path.join(os.environ["SRCDIR"], "docs_sphinx") ) if os.path.exists(sphinx_dir): sphinx_doc_dir = [sphinx_dir] else: sphinx_doc_dir = [] else: sphinx_doc_dir = [] argv = make_argv(dirnames + sphinx_doc_dir, doctests=True) if "codegen_independent" in test_in_parallel: argv.extend(multiprocess_arguments) success.append( pytest.main(argv + additional_args, plugins=[pref_plugin]) == 0 ) # Set float_dtype back again if necessary if float_dtype is not None: prefs["core.default_float_dtype"] = float_dtype print("Running tests that do not use code generation") argv = make_argv(dirnames, "codegen_independent", test_GSL=test_GSL) if "codegen_independent" in test_in_parallel: argv.extend(multiprocess_arguments) success.append( pytest.main(argv + additional_args, plugins=[pref_plugin]) == 0 ) clear_caches() for target in codegen_targets: print(f"Running tests for target {target}:") # Also set the target for string-expressions -- otherwise we'd only # ever test numpy for those prefs["codegen.target"] = target markers = "not standalone_only and not codegen_independent" if not long_tests: markers += " and not long" # explicitly ignore the brian2.hears file for testing, otherwise the # doctest search will import it, failing on Python 3 argv = make_argv(dirnames, markers, test_GSL=test_GSL) if target in test_in_parallel: argv.extend(multiprocess_arguments) success.append( pytest.main(argv + additional_args, plugins=[pref_plugin]) == 0 ) clear_caches() pref_plugin.device = test_standalone if test_standalone: from brian2.devices.device import get_device, set_device pref_plugin.device_options = {"directory": None, "with_output": False} pref_plugin.device_options.update(build_options) print(f'Testing standalone device "{test_standalone}"') print("Running standalone-compatible standard tests (single run statement)") markers = "and not long" if not long_tests else "" markers += " and not multiple_runs" argv = make_argv( dirnames, f"standalone_compatible {markers}", test_GSL=test_GSL ) if test_standalone in test_in_parallel: argv.extend(multiprocess_arguments) success.append( pytest.main(argv + additional_args, plugins=[pref_plugin]) in [0, 5] ) clear_caches() reset_device() print( "Running standalone-compatible standard tests (multiple run statements)" ) pref_plugin.device_options = { "directory": None, "with_output": False, "build_on_run": False, } pref_plugin.device_options.update(build_options) markers = " and not long" if not long_tests else "" markers += " and multiple_runs" argv = make_argv( dirnames, f"standalone_compatible{markers}", test_GSL=test_GSL ) if test_standalone in test_in_parallel: argv.extend(multiprocess_arguments) success.append( pytest.main(argv + additional_args, plugins=[pref_plugin]) in [0, 5] ) clear_caches() reset_device() if test_openmp and test_standalone == "cpp_standalone": # Run all the standalone compatible tests again with 4 threads pref_plugin.device_options = {"directory": None, "with_output": False} pref_plugin.device_options.update(build_options) prefs["devices.cpp_standalone.openmp_threads"] = 4 print( "Running standalone-compatible standard tests with OpenMP (single" " run statements)" ) markers = " and not long" if not long_tests else "" markers += " and not multiple_runs" argv = make_argv( dirnames, f"standalone_compatible{markers}", test_GSL=test_GSL ) success.append( pytest.main(argv + additional_args, plugins=[pref_plugin]) in [0, 5] ) clear_caches() reset_device() pref_plugin.device_options = { "directory": None, "with_output": False, "build_on_run": False, } pref_plugin.device_options.update(build_options) print( "Running standalone-compatible standard tests with OpenMP (multiple" " run statements)" ) markers = " and not long" if not long_tests else "" markers += " and multiple_runs" argv = make_argv( dirnames, f"standalone_compatible{markers}", test_GSL=test_GSL ) success.append( pytest.main(argv + additional_args, plugins=[pref_plugin]) in [0, 5] ) clear_caches() prefs["devices.cpp_standalone.openmp_threads"] = 0 reset_device() print("Running standalone-specific tests") exclude_openmp = " and not openmp" if not test_openmp else "" argv = make_argv( dirnames, test_standalone + exclude_openmp, test_GSL=test_GSL ) if test_standalone in test_in_parallel: argv.extend(multiprocess_arguments) success.append( pytest.main(argv + additional_args, plugins=[pref_plugin]) in [0, 5] ) clear_caches() all_success = all(success) if not all_success: print( f"ERROR: {len(success) - sum(success)}/{len(success)} test suite(s) " "did not complete successfully (see above)." ) else: print( f"OK: {len(success)}/{len(success)} test suite(s) did complete " "successfully." ) return all_success finally: BrianLogger.console_handler.setLevel(log_level) if reset_preferences: # Restore the user preferences prefs.read_preference_file(StringIO(stored_prefs)) prefs._backup() fundamentalunits.user_unit_register = old_unit_registry if __name__ == "__main__": run() brian2-2.5.4/brian2/tests/features/000077500000000000000000000000001445201106100170265ustar00rootroot00000000000000brian2-2.5.4/brian2/tests/features/__init__.py000066400000000000000000000004431445201106100211400ustar00rootroot00000000000000__all__ = [ "FeatureTest", "SpeedTest", "InaccuracyError", "Configuration", "run_feature_tests", ] # isort: skip_file # We need to do the base import first to prevent a circular import later from .base import * from . import input, monitors, neurongroup, speed, synapses brian2-2.5.4/brian2/tests/features/base.py000066400000000000000000000576361445201106100203330ustar00rootroot00000000000000import itertools import os import pickle import re import shutil import subprocess import sys import tempfile from collections import defaultdict import numpy import brian2 from brian2.utils.stringtools import indent __all__ = [ "FeatureTest", "SpeedTest", "InaccuracyError", "Configuration", "run_feature_tests", "run_single_feature_test", "run_speed_tests", "DefaultConfiguration", "LocalConfiguration", "NumpyConfiguration", "CythonConfiguration", "CPPStandaloneConfiguration", "CPPStandaloneConfigurationOpenMP", ] class InaccuracyError(AssertionError): def __init__(self, error, *args): self.error = error AssertionError.__init__(self, *args) class BaseTest: """ """ category = None # a string with the category of features name = None # a string with the particular feature name within the category tags = None # a list of tags (strings) of features used # whether or not to allow the device to override the time: this can be used to remove the # compilation overheads on certain devices (but some tests might want to include this) allow_time_override = True @classmethod def fullname(cls): return f"{cls.category}: {cls.name}" def run(self): """ Runs the feature test but do not return results (some devices may require an extra step before results are available). """ raise NotImplementedError def timed_run(self, duration): """ Do a timed run. This means that for RuntimeDevice it will run for defaultclock.dt before running for the rest of the duration. This means total run duration will be duration+defaultclock.dt. For standalone devices, this feature may or may not be implemented. """ if isinstance(brian2.get_device(), brian2.devices.RuntimeDevice): brian2.run(brian2.defaultclock.dt, level=1) brian2.run(duration, level=1) else: brian2.run(duration, level=1) class FeatureTest(BaseTest): """ """ def results(self): """ Return the results after a run call. """ raise NotImplementedError def compare(self, maxrelerr, results_base, results_test): """ Compare results from standard Brian run to another run. This method or `check` should be implemented. """ raise NotImplementedError def check(self, maxrelerr, results): """ Check results are valid (e.g. analytically). This method or `compare` should be implemented. """ raise NotImplementedError def compare_arrays(self, maxrelerr, v_base, v_test): """ Often you just want to compare the values of some arrays, this does that. """ if isinstance(v_base, dict): for k in v_base: self.compare_arrays(maxrelerr, v_base[k], v_test[k]) else: I = v_base != 0 err = numpy.amax(numpy.abs(v_base[I] - v_test[I]) / v_base[I]) if err > maxrelerr: raise InaccuracyError(err) if (v_test[-I] != 0).any(): raise InaccuracyError(numpy.inf) class SpeedTest(BaseTest): n_range = [1] n_label = "n" n_axis_log = True time_axis_log = True def __init__(self, n): self.n = n def results(self): return self.n def compare(self, maxrelerr, results_base, results_test): pass def check(self, maxrelerr, results): pass def __call__(self): return self class Configuration: """ """ name = None # The name of this configuration def __init__(self, maximum_run_time=1e7 * brian2.second): maximum_run_time = float(maximum_run_time) * brian2.second self.maximum_run_time = maximum_run_time def before_run(self): pass def after_run(self): pass def get_last_run_time(self): """ Implement this to overwrite the measured runtime (e.g. to remove overhead). """ if hasattr(brian2.device, "_last_run_time"): return brian2.device._last_run_time raise NotImplementedError def get_last_run_completed_fraction(self): """ Implement this to overwrite the amount of the last run that was completed (for devices that allow breaking early if the maximum run time is exceeded). """ if hasattr(brian2.device, "_last_run_completed_fraction"): return brian2.device._last_run_completed_fraction return 1.0 class DefaultConfiguration(Configuration): name = "Default" def before_run(self): brian2.prefs.reset_to_defaults() brian2.set_device("runtime") class LocalConfiguration(Configuration): name = "Local" def before_run(self): brian2.prefs.reset_to_defaults() brian2.set_device("runtime") brian2.prefs.load_preferences() class NumpyConfiguration(Configuration): name = "Numpy" def before_run(self): brian2.prefs.reset_to_defaults() brian2.set_device("runtime") brian2.prefs.codegen.target = "numpy" class CythonConfiguration(Configuration): name = "Cython" def before_run(self): brian2.prefs.reset_to_defaults() brian2.set_device("runtime") brian2.prefs.codegen.target = "cython" class CPPStandaloneConfiguration(Configuration): name = "C++ standalone" def before_run(self): brian2.prefs.reset_to_defaults() brian2.set_device("cpp_standalone", build_on_run=False) def after_run(self): if os.path.exists("cpp_standalone"): shutil.rmtree("cpp_standalone") brian2.device.build( directory="cpp_standalone", compile=True, run=True, with_output=False ) class CPPStandaloneConfigurationOpenMP(Configuration): name = "C++ standalone (OpenMP)" def before_run(self): brian2.prefs.reset_to_defaults() brian2.set_device("cpp_standalone", build_on_run=False) brian2.prefs.devices.cpp_standalone.openmp_threads = 4 def after_run(self): if os.path.exists("cpp_standalone"): shutil.rmtree("cpp_standalone") brian2.device.build( directory="cpp_standalone", compile=True, run=True, with_output=False ) def results(configuration, feature, n=None, maximum_run_time=1e7 * brian2.second): tempfilename = tempfile.mktemp("exception") if n is None: init_args = "" else: init_args = str(n) code_string = """ __file__ = '{fname}' import brian2 from {config_module} import {config_name} from {feature_module} import {feature_name} configuration = {config_name}() feature = {feature_name}({init_args}) import warnings, traceback, pickle, sys, os, time warnings.simplefilter('ignore') try: start_time = time.time() configuration.before_run() brian2.device._set_maximum_run_time({maximum_run_time}) feature.run() configuration.after_run() results = feature.results() run_time = time.time()-start_time if feature.allow_time_override: try: run_time = configuration.get_last_run_time() except NotImplementedError: pass lrcf = configuration.get_last_run_completed_fraction() run_time = run_time/lrcf prof_info = brian2.magic_network.profiling_info new_prof_info = [] for n, t in prof_info: new_prof_info.append((n, t/lrcf)) f = open(r'{tempfname}', 'wb') pickle.dump((None, results, run_time, new_prof_info), f, -1) f.close() except Exception, ex: #traceback.print_exc(file=sys.stdout) tb = traceback.format_exc() f = open(r'{tempfname}', 'wb') pickle.dump((tb, ex, 0.0, []), f, -1) f.close() """.format( config_module=configuration.__module__, config_name=configuration.__name__, feature_module=feature.__module__, feature_name=feature.__name__, tempfname=tempfilename, fname=__file__, init_args=init_args, maximum_run_time=float(maximum_run_time), ) args = [sys.executable, "-c", code_string] # Run the example in a new process and make sure that stdout gets # redirected into the capture plugin p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = p.communicate() # sys.stdout.write(stdout) # sys.stderr.write(stderr) with open(tempfilename, "rb") as f: tb, res, runtime, profiling_info = pickle.load(f) return tb, res, runtime, profiling_info def check_or_compare(feature, res, baseline, maxrelerr): feature = feature() try: feature.check(maxrelerr, res) except NotImplementedError: feature.compare(maxrelerr, baseline, res) def run_single_feature_test(configuration, feature): return results(configuration, feature) def run_feature_tests( configurations=None, feature_tests=None, strict=1e-5, tolerant=0.05, verbose=True, maximum_run_time=1e7 * brian2.second, ): if configurations is None: # some configurations to attempt to import try: import brian2genn.correctness_testing except: pass configurations = Configuration.__subclasses__() if feature_tests is None: feature_tests = FeatureTest.__subclasses__() if DefaultConfiguration in configurations: configurations.remove(DefaultConfiguration) configurations = [DefaultConfiguration] + configurations feature_tests.sort(key=lambda ft: ft.fullname()) if verbose: print("Running feature tests") print("Configurations:", ", ".join(c.name for c in configurations)) full_results = {} tag_results = defaultdict(lambda: defaultdict(list)) for ft in feature_tests: baseline = None if verbose: print(f"{ft.fullname()}: [", end=" ") for configuration in configurations: txt = "OK" sym = "." exc = None tb, res, runtime, prof_info = results( configuration, ft, maximum_run_time=maximum_run_time ) if isinstance(res, Exception): if isinstance(res, NotImplementedError): sym = "N" txt = "Not implemented" else: sym = "E" txt = "Error" if configuration is DefaultConfiguration: raise res else: if configuration is DefaultConfiguration: baseline = res try: check_or_compare(ft, res, baseline, strict) except InaccuracyError as exc: try: check_or_compare(ft, res, baseline, tolerant) sym = "I" txt = f"Poor (error={100.0 * exc.error:.2f}%)" except InaccuracyError as exc: sym = "F" txt = f"Fail (error={100.0 * exc.error:.2f}%)" sys.stdout.write(sym) full_results[configuration.name, ft.fullname()] = ( sym, txt, exc, tb, runtime, prof_info, ) for tag in ft.tags: tag_results[tag][configuration.name].append( (sym, txt, exc, tb, runtime, prof_info) ) if verbose: print("]") return FeatureTestResults(full_results, tag_results, configurations, feature_tests) class FeatureTestResults: def __init__(self, full_results, tag_results, configurations, feature_tests): self.full_results = full_results self.tag_results = tag_results self.configurations = configurations self.feature_tests = feature_tests @property def test_table(self): table = [] table.append(["Test"] + [c.name for c in self.configurations]) curcat = "" for ft in self.feature_tests: cat = ft.category if cat != curcat: table.append([cat] + [""] * len(self.configurations)) curcat = cat row = [ft.name] for configuration in self.configurations: sym, txt, exc, tb, runtime, prof_info = self.full_results[ configuration.name, ft.fullname() ] row.append(txt) table.append(row) return make_table(table) @property def tag_table(self): table = [] table.append(["Tag"] + [c.name for c in self.configurations]) tags = sorted(self.tag_results.keys()) for tag in tags: row = [tag] for configuration in self.configurations: tag_res = self.tag_results[tag][configuration.name] syms = [sym for sym, txt, exc, tb, runtime, prof_info in tag_res] n = len(syms) okcount = sum(sym == "." for sym in syms) poorcount = sum(sym == "I" for sym in syms) failcount = sum(sym == "F" for sym in syms) errcount = sum(sym == "E" for sym in syms) nicount = sum(sym == "N" for sym in syms) if okcount == n: txt = "OK" elif nicount == n: txt = "Not implemented" elif errcount == n: txt = "Unsupported" elif okcount + poorcount == n: txt = f"Poor ({int(int(poorcount * 100.0 / n))}%)" elif okcount + poorcount + failcount == n: txt = ( f"Fail: {int(failcount * 100.0 / n)}%" f" (poor={int(poorcount * 100.0 / n)}%)" ) else: txt = ( "Fail: OK={ok}%, Poor={poor}%, Fail={fail}%, NotImpl={ni}%" " Error={err}%".format( ok=int(okcount * 100.0 / n), poor=int(poorcount * 100.0 / n), fail=int(failcount * 100.0 / n), err=int(errcount * 100.0 / n), ni=int(nicount * 100.0 / n), ) ) row.append(txt) table.append(row) return make_table(table) @property def tables(self): r = "" s = "Feature test results" r += f"{s}\n{'-' * len(s)}\n\n{self.test_table}\n" s = "Tag results" r += f"{s}\n{'-' * len(s)}\n\n{self.tag_table}\n" return r @property def exceptions(self): exc_list = [] for configuration in self.configurations: curconfig = [] for ft in self.feature_tests: sym, txt, exc, tb, runtime, prof_info = self.full_results[ configuration.name, ft.fullname() ] if tb is not None: curconfig.append((ft.fullname(), tb)) if len(curconfig): exc_list.append((configuration.name, curconfig)) if len(exc_list) == 0: return "" r = "" s = "Exceptions" r += f"{s}\n{'-' * len(s)}\n\n" for config_name, curconfig in exc_list: s = config_name r += f"{s}\n{'^' * len(s)}\n\n" for name, tb in curconfig: r += f"{name}::\n\n{indent(tb)}\n\n" return r @property def tables_and_exceptions(self): return f"{self.tables}\n{self.exceptions}" def __str__(self): return self.tables __repr__ = __str__ def run_speed_tests( configurations=None, speed_tests=None, run_twice=True, verbose=True, n_slice=slice(None), maximum_run_time=1e7 * brian2.second, ): if configurations is None: # some configurations to attempt to import try: import brian2genn.correctness_testing except: pass configurations = Configuration.__subclasses__() if speed_tests is None: speed_tests = SpeedTest.__subclasses__() speed_tests.sort(key=lambda ft: ft.fullname()) if verbose: print("Running speed tests") print("Configurations:", ", ".join(c.name for c in configurations)) full_results = {} tag_results = defaultdict(lambda: defaultdict(list)) for ft in speed_tests: if verbose: print(f"{ft.fullname()}: ", end=" ") for n in ft.n_range[n_slice]: if verbose: print(f"n={int(n)} [", end=" ") for configuration in configurations: sym = "." for _ in range(1 + int(run_twice)): tb, res, runtime, prof_info = results( configuration, ft, n, maximum_run_time=maximum_run_time ) if isinstance(res, Exception): if isinstance(res, NotImplementedError): sym = "N" else: sym = "E" if configuration is DefaultConfiguration: raise res runtime = numpy.NAN sys.stdout.write(sym) full_results[configuration.name, ft.fullname(), n, "All"] = runtime suffixtime = defaultdict(float) overheadstime = float(runtime) for codeobjname, proftime in prof_info: # parts = codeobjname.split('_') # parts = [part for part in parts if not re.match(r'\d+', part)] # suffix = '_'.join(parts) suffix = codeobjname suffixtime[suffix] += proftime overheadstime -= float(proftime) for suffix, proftime in list(suffixtime.items()): full_results[ configuration.name, ft.fullname(), n, suffix ] = proftime full_results[ configuration.name, ft.fullname(), n, "Overheads" ] = overheadstime if verbose: print("]", end=" ") if verbose: print() return SpeedTestResults(full_results, configurations, speed_tests) class SpeedTestResults: def __init__(self, full_results, configurations, speed_tests): self.full_results = full_results self.configurations = configurations self.speed_tests = speed_tests def get_ns(self, fullname): L = [(cn, fn, n, s) for cn, fn, n, s in self.full_results if fn == fullname] confignames, fullnames, n, codeobjsuffixes = zip(*L) return numpy.array(sorted(list(set(n)))) def get_codeobjsuffixes(self, fullname): L = [(cn, fn, n, s) for cn, fn, n, s in self.full_results if fn == fullname] confignames, fullnames, n, codeobjsuffixes = zip(*L) return set(codeobjsuffixes) def plot_all_tests(self, relative=False, profiling_minimum=1.0): if relative and profiling_minimum < 1: raise ValueError("Cannot use relative plots with profiling") import pylab for st in self.speed_tests: fullname = st.fullname() pylab.figure() ns = self.get_ns(fullname) codeobjsuffixes = self.get_codeobjsuffixes(fullname) codeobjsuffixes.remove("All") codeobjsuffixes.remove("Overheads") codeobjsuffixes = ["All", "Overheads"] + sorted(codeobjsuffixes) if relative or profiling_minimum == 1: codeobjsuffixes = ["All"] baseline = None havelabel = set() markerstyles_cycle = iter( itertools.cycle(["o", "s", "d", "v", "p", "h", "^", "<", ">"]) ) dashes = {} markerstyles = {} for isuffix, suffix in enumerate(codeobjsuffixes): cols = itertools.cycle(pylab.rcParams["axes.color_cycle"]) for (iconfig, config), col in zip(enumerate(self.configurations), cols): configname = config.name runtimes = [] skip = True for n in ns: runtime = self.full_results.get( (configname, fullname, n, "All"), numpy.nan ) thistime = self.full_results.get( (configname, fullname, n, suffix), numpy.nan ) if float(thistime / runtime) >= profiling_minimum: skip = False runtimes.append(thistime) if skip: continue runtimes = numpy.array(runtimes) if relative: if baseline is None: baseline = runtimes runtimes = baseline / runtimes if suffix == "All": lw = 2 label = configname else: lw = 1 label = suffix plottable = sum(-numpy.isnan(runtimes[1:] + runtimes[:-1])) if plottable: if label in havelabel: label = None else: havelabel.add(label) dash = None msty = None if suffix != "All": if suffix in dashes: dash = dashes[suffix] msty = markerstyles[suffix] else: j = len(dashes) dash = (8, 2) for b in bin(j)[2:]: if b == "0": dash = dash + (2, 2) else: dash = dash + (4, 2) dashes[suffix] = dash markerstyles[suffix] = msty = next(markerstyles_cycle) line = pylab.plot( ns, runtimes, lw=lw, color=col, marker=msty, mec="none", ms=8, label=label, )[0] if dash is not None: line.set_dashes(dash) pylab.title(fullname) pylab.legend(loc="best", fontsize="x-small", handlelength=8.0) pylab.xlabel(st.n_label) if st.n_axis_log: pylab.gca().set_xscale("log") if st.time_axis_log: pylab.gca().set_yscale("log") # Code below auto generates restructured text tables, copied from: # http://stackoverflow.com/questions/11347505/what-are-some-approaches-to-outputting-a-python-data-structure-to-restructuredte def make_table(grid): max_cols = [ max(out) for out in map(list, zip(*[[len(item) for item in row] for row in grid])) ] rst = table_div(max_cols, 1) for i, row in enumerate(grid): header_flag = False if i == 0 or i == len(grid) - 1: header_flag = True rst += normalize_row(row, max_cols) rst += table_div(max_cols, header_flag) return rst def table_div(max_cols, header_flag=1): out = "" if header_flag == 1: style = "=" else: style = "-" for max_col in max_cols: out += f"{max_col * style} " out += "\n" return out def normalize_row(row, max_cols): r = "" for i, max_col in enumerate(max_cols): r += row[i] + (max_col - len(row[i]) + 1) * " " return f"{r}\n" brian2-2.5.4/brian2/tests/features/input.py000066400000000000000000000012011445201106100205310ustar00rootroot00000000000000""" Tests of input features """ from brian2 import * from brian2.tests.features import FeatureTest, InaccuracyError class SpikeGeneratorGroupTest(FeatureTest): category = "Input" name = "SpikeGeneratorGroup" tags = ["SpikeMonitor", "run", "SpikeGeneratorGroup"] def run(self): N = 10 numspikes = 1000 i = arange(numspikes) % N t = linspace(0, 1, numspikes) * (100 * ms) G = SpikeGeneratorGroup(N, i, t) self.M = M = SpikeMonitor(G) run(100 * ms) def results(self): return {"i": self.M.i[:], "t": self.M.t[:]} compare = FeatureTest.compare_arrays brian2-2.5.4/brian2/tests/features/monitors.py000066400000000000000000000023521445201106100212540ustar00rootroot00000000000000""" Check that various monitors work correctly. """ from brian2 import * from brian2.tests.features import FeatureTest, InaccuracyError class SpikeMonitorTest(FeatureTest): category = "Monitors" name = "SpikeMonitor" tags = ["NeuronGroup", "run", "SpikeMonitor"] def run(self): N = 100 tau = 10 * ms eqs = """ dv/dt = (I-v)/tau : 1 I : 1 """ self.G = G = NeuronGroup(N, eqs, threshold="v>1", reset="v=0") G.I = linspace(0, 2, N) self.M = M = SpikeMonitor(G) run(100 * ms) def results(self): return {"i": self.M.i[:], "t": self.M.t[:]} compare = FeatureTest.compare_arrays class StateMonitorTest(FeatureTest): category = "Monitors" name = "StateMonitor" tags = ["NeuronGroup", "run", "StateMonitor"] def run(self): N = 10 tau = 10 * ms eqs = """ dv/dt = (I-v)/tau : 1 I : 1 """ self.G = G = NeuronGroup(N, eqs, threshold="v>1", reset="v=0.1") G.v = 0.1 G.I = linspace(1.1, 2, N) self.M = M = StateMonitor(G, "v", record=True) run(100 * ms) def results(self): return self.M.v[:] compare = FeatureTest.compare_arrays brian2-2.5.4/brian2/tests/features/neurongroup.py000066400000000000000000000053151445201106100217670ustar00rootroot00000000000000""" Check that the features of `NeuronGroup` are available and correct. """ from brian2 import * from brian2.tests.features import FeatureTest, InaccuracyError class NeuronGroupIntegrationLinear(FeatureTest): category = "NeuronGroup" name = "Linear integration" tags = ["NeuronGroup", "run"] def run(self): self.tau = tau = 1 * second self.v_init = linspace(0.1, 1, 10) self.duration = 100 * ms G = self.G = NeuronGroup(10, "dv/dt=-v/tau:1") self.G.v = self.v_init run(self.duration) def results(self): v_correct = self.v_init * exp(-self.duration / self.tau) return v_correct, self.G.v[:] def check(self, maxrelerr, correct_end): v_correct, v_end = correct_end err = amax(abs(v_end - v_correct) / v_correct) if err > maxrelerr: raise InaccuracyError(err) class NeuronGroupIntegrationEuler(FeatureTest): category = "NeuronGroup" name = "Euler integration" tags = ["NeuronGroup", "run"] def run(self): self.tau = tau = 1 * second self.v_init = linspace(0.1, 1, 10) self.duration = 100 * ms G = self.G = NeuronGroup(10, "dv/dt=-v**1.1/tau:1") self.G.v = self.v_init run(self.duration) def results(self): return self.G.v[:] compare = FeatureTest.compare_arrays class NeuronGroupLIF(FeatureTest): category = "NeuronGroup" name = "Leaky integrate and fire" tags = ["NeuronGroup", "Threshold", "Reset", "run", "SpikeMonitor"] def run(self): self.tau = tau = 10 * ms self.duration = 1000 * ms G = self.G = NeuronGroup(1, "dv/dt=(1.2-v)/tau:1", threshold="v>1", reset="v=0") M = self.M = SpikeMonitor(self.G) run(self.duration) def results(self): return self.M.t[:] compare = FeatureTest.compare_arrays class NeuronGroupLIFRefractory(FeatureTest): category = "NeuronGroup" name = "Refractory leaky integrate and fire" tags = ["NeuronGroup", "Threshold", "Reset", "Refractory", "run", "SpikeMonitor"] def run(self): self.tau = tau = 10 * ms self.duration = 1000 * ms G = self.G = NeuronGroup( 1, "dv/dt=(1.2-v)/tau:1 (unless refractory)", threshold="v>1", reset="v=0", refractory=1 * ms, ) M = self.M = SpikeMonitor(self.G) run(self.duration) def results(self): return self.M.t[:] compare = FeatureTest.compare_arrays if __name__ == "__main__": # ft = NeuronGroupIntegrationLinear() # ft.run() # res = ft.results() # ft.check(res) ft = NeuronGroupIntegrationEuler() ft.run() res = ft.results() brian2-2.5.4/brian2/tests/features/speed.py000066400000000000000000000227111445201106100205030ustar00rootroot00000000000000""" Check the speed of different Brian 2 configurations """ from brian2 import * from brian2.tests.features import SpeedTest __all__ = [ "LinearNeuronsOnly", "HHNeuronsOnly", "CUBAFixedConnectivity", "COBAHHFixedConnectivity", "VerySparseMediumRateSynapsesOnly", "SparseMediumRateSynapsesOnly", "DenseMediumRateSynapsesOnly", "SparseLowRateSynapsesOnly", "SparseHighRateSynapsesOnly", "STDP", ] class LinearNeuronsOnly(SpeedTest): category = "Neurons only" name = "Linear 1D" tags = ["Neurons"] n_range = [10, 100, 1000, 10000, 100000, 1000000] n_label = "Num neurons" # configuration options duration = 10 * second def run(self): self.tau = tau = 1 * second self.v_init = linspace(0.1, 1, self.n) G = self.G = NeuronGroup(self.n, "dv/dt=-v/tau:1") self.G.v = self.v_init self.timed_run(self.duration) class HHNeuronsOnly(SpeedTest): category = "Neurons only" name = "Hodgkin-Huxley" tags = ["Neurons"] n_range = [10, 100, 1000, 10000, 100000] n_label = "Num neurons" # configuration options duration = 1 * second def run(self): num_neurons = self.n # Parameters area = 20000 * umetre**2 Cm = 1 * ufarad * cm**-2 * area gl = 5e-5 * siemens * cm**-2 * area El = -65 * mV EK = -90 * mV ENa = 50 * mV g_na = 100 * msiemens * cm**-2 * area g_kd = 30 * msiemens * cm**-2 * area VT = -63 * mV # The model eqs = Equations( """ dv/dt = (gl*(El-v) - g_na*(m*m*m)*h*(v-ENa) - g_kd*(n*n*n*n)*(v-EK) + I)/Cm : volt dm/dt = 0.32*(mV**-1)*(13.*mV-v+VT)/ (exp((13.*mV-v+VT)/(4.*mV))-1.)/ms*(1-m)-0.28*(mV**-1)*(v-VT-40.*mV)/ (exp((v-VT-40.*mV)/(5.*mV))-1.)/ms*m : 1 dn/dt = 0.032*(mV**-1)*(15.*mV-v+VT)/ (exp((15.*mV-v+VT)/(5.*mV))-1.)/ms*(1.-n)-.5*exp((10.*mV-v+VT)/(40.*mV))/ms*n : 1 dh/dt = 0.128*exp((17.*mV-v+VT)/(18.*mV))/ms*(1.-h)-4./(1+exp((40.*mV-v+VT)/(5.*mV)))/ms*h : 1 I : amp """ ) # Threshold and refractoriness are only used for spike counting group = NeuronGroup( num_neurons, eqs, threshold="v > -40*mV", refractory="v > -40*mV" ) group.v = El group.I = "0.7*nA * i / num_neurons" self.timed_run(self.duration) class CUBAFixedConnectivity(SpeedTest): category = "Full examples" name = "CUBA fixed connectivity" tags = ["Neurons", "Synapses", "SpikeMonitor"] n_range = [10, 100, 1000, 10000, 100000] n_label = "Num neurons" # configuration options duration = 1 * second def run(self): N = self.n Ne = int(0.8 * N) taum = 20 * ms taue = 5 * ms taui = 10 * ms Vt = -50 * mV Vr = -60 * mV El = -49 * mV eqs = """ dv/dt = (ge+gi-(v-El))/taum : volt (unless refractory) dge/dt = -ge/taue : volt (unless refractory) dgi/dt = -gi/taui : volt (unless refractory) """ P = NeuronGroup(N, eqs, threshold="v>Vt", reset="v = Vr", refractory=5 * ms) P.v = "Vr + rand() * (Vt - Vr)" P.ge = 0 * mV P.gi = 0 * mV we = (60 * 0.27 / 10) * mV # excitatory synaptic weight (voltage) wi = (-20 * 4.5 / 10) * mV # inhibitory synaptic weight Ce = Synapses(P, P, on_pre="ge += we") Ci = Synapses(P, P, on_pre="gi += wi") Ce.connect("i=Ne", p=80.0 / N) s_mon = SpikeMonitor(P) self.timed_run(self.duration) class COBAHHFixedConnectivity(SpeedTest): category = "Full examples" name = "COBAHH fixed connectivity" tags = ["Neurons", "Synapses", "SpikeMonitor"] n_range = [100, 500, 1000, 5000, 10000, 50000, 100000, 500000, 1000000] n_label = "Num neurons" # configuration options duration = 1 * second def run(self): N = self.n area = 20000 * umetre**2 Cm = (1 * ufarad * cm**-2) * area gl = (5e-5 * siemens * cm**-2) * area El = -60 * mV EK = -90 * mV ENa = 50 * mV g_na = (100 * msiemens * cm**-2) * area g_kd = (30 * msiemens * cm**-2) * area VT = -63 * mV # Time constants taue = 5 * ms taui = 10 * ms # Reversal potentials Ee = 0 * mV Ei = -80 * mV we = 6 * nS # excitatory synaptic weight wi = 67 * nS # inhibitory synaptic weight # The model eqs = Equations( """ dv/dt = (gl*(El-v)+ge*(Ee-v)+gi*(Ei-v)- g_na*(m*m*m)*h*(v-ENa)- g_kd*(n*n*n*n)*(v-EK))/Cm : volt dm/dt = alpha_m*(1-m)-beta_m*m : 1 dn/dt = alpha_n*(1-n)-beta_n*n : 1 dh/dt = alpha_h*(1-h)-beta_h*h : 1 dge/dt = -ge*(1./taue) : siemens dgi/dt = -gi*(1./taui) : siemens alpha_m = 0.32*(mV**-1)*(13*mV-v+VT)/ (exp((13*mV-v+VT)/(4*mV))-1.)/ms : Hz beta_m = 0.28*(mV**-1)*(v-VT-40*mV)/ (exp((v-VT-40*mV)/(5*mV))-1)/ms : Hz alpha_h = 0.128*exp((17*mV-v+VT)/(18*mV))/ms : Hz beta_h = 4./(1+exp((40*mV-v+VT)/(5*mV)))/ms : Hz alpha_n = 0.032*(mV**-1)*(15*mV-v+VT)/ (exp((15*mV-v+VT)/(5*mV))-1.)/ms : Hz beta_n = .5*exp((10*mV-v+VT)/(40*mV))/ms : Hz """ ) P = NeuronGroup( N, model=eqs, threshold="v>-20*mV", refractory=3 * ms, method="exponential_euler", ) P.v = "El + (randn() * 5 - 5)*mV" P.ge = "(randn() * 1.5 + 4) * 10.*nS" P.gi = "(randn() * 12 + 20) * 10.*nS" Pe = P[: int(0.8 * N)] Pi = P[int(0.8 * N) :] Ce = Synapses(Pe, P, on_pre="ge+=we") Ci = Synapses(Pi, P, on_pre="gi+=wi") Ce.connect(p=80.0 / N) Ci.connect(p=80.0 / N) s_mon = SpikeMonitor(P) self.timed_run(self.duration) class STDP(SpeedTest): category = "Full examples" name = "STDP with Poisson input" tags = ["Neurons", "Synapses", "SpikeMonitor", "PoissonGroup"] n_range = [100, 500, 1000, 5000, 10000, 50000, 100000, 500000, 1000000] n_label = "Num neurons" # configuration options duration = 1 * second def run(self): N = self.n taum = 10 * ms taupre = 20 * ms taupost = taupre Ee = 0 * mV vt = -54 * mV vr = -60 * mV El = -74 * mV taue = 5 * ms F = 15 * Hz gmax = 0.01 dApre = 0.01 dApost = -dApre * taupre / taupost * 1.05 dApost *= gmax dApre *= gmax eqs_neurons = """ dv/dt = (ge * (Ee-vr) + El - v) / taum : volt dge/dt = -ge / taue : 1 """ poisson_input = PoissonGroup(N, rates=F) neurons = NeuronGroup( 1, eqs_neurons, threshold="v>vt", reset="v = vr", method="exact" ) S = Synapses( poisson_input, neurons, """ w : 1 dApre/dt = -Apre / taupre : 1 (event-driven) dApost/dt = -Apost / taupost : 1 (event-driven) """, on_pre="""ge += w Apre += dApre w = clip(w + Apost, 0, gmax)""", on_post="""Apost += dApost w = clip(w + Apre, 0, gmax)""", ) S.connect() S.w = "rand() * gmax" s_mon = SpikeMonitor(poisson_input) self.timed_run(self.duration) class SynapsesOnly: category = "Synapses only" tags = ["Synapses"] n_range = [10, 100, 1000, 10000] n_label = "Num neurons" duration = 1 * second # memory usage will be approximately p**2*rate*dt*N**2*bytes_per_synapse/1024**3 GB # for CPU, bytes_per_synapse appears to be around 40? def run(self): N = self.n rate = self.rate M = int(rate * N * defaultclock.dt) if M <= 0: M = 1 G = NeuronGroup(M, "v:1", threshold="True") H = NeuronGroup(N, "w:1") S = Synapses(G, H, on_pre="w += 1.0") S.connect(True, p=self.p) # M = SpikeMonitor(G) self.timed_run( self.duration, # report='text', ) # plot(M.t/ms, M.i, ',k') class VerySparseMediumRateSynapsesOnly(SynapsesOnly, SpeedTest): name = "Very sparse, medium rate (10s duration)" rate = 10 * Hz p = 0.02 n_range = [10, 100, 1000, 10000, 100000] duration = 10 * second class SparseMediumRateSynapsesOnly(SynapsesOnly, SpeedTest): name = "Sparse, medium rate (1s duration)" rate = 10 * Hz p = 0.2 n_range = [10, 100, 1000, 10000, 100000] class DenseMediumRateSynapsesOnly(SynapsesOnly, SpeedTest): name = "Dense, medium rate (1s duration)" rate = 10 * Hz p = 1.0 n_range = [10, 100, 1000, 10000, 40000] class SparseLowRateSynapsesOnly(SynapsesOnly, SpeedTest): name = "Sparse, low rate (10s duration)" rate = 1 * Hz p = 0.2 n_range = [10, 100, 1000, 10000, 100000] duration = 10 * second class SparseHighRateSynapsesOnly(SynapsesOnly, SpeedTest): name = "Sparse, high rate (1s duration)" rate = 100 * Hz p = 0.2 n_range = [10, 100, 1000, 10000] if __name__ == "__main__": # prefs.codegen.target = 'numpy' VerySparseMediumRateSynapsesOnly(100000).run() show() brian2-2.5.4/brian2/tests/features/synapses.py000066400000000000000000000106611445201106100212510ustar00rootroot00000000000000""" Check that the features of `Synapses` are available and correct. """ import numpy from brian2 import * from brian2.tests.features import FeatureTest, InaccuracyError class SynapsesPre(FeatureTest): category = "Synapses" name = "Presynaptic code" tags = ["NeuronGroup", "run", "Synapses", "Presynaptic code"] def run(self): tau = 5 * ms eqs = """ dV/dt = k/tau : 1 k : 1 """ G = NeuronGroup(10, eqs, threshold="V>1", reset="V=0") G.k = linspace(1, 5, len(G)) H = NeuronGroup(10, "V:1") S = Synapses(G, H, on_pre="V += 1") S.connect(j="i") self.H = H run(101 * ms) def results(self): return self.H.V[:] compare = FeatureTest.compare_arrays class SynapsesPost(FeatureTest): category = "Synapses" name = "Postsynaptic code" tags = ["NeuronGroup", "run", "Synapses", "Postsynaptic code"] def run(self): tau = 5 * ms eqs = """ dV/dt = k/tau : 1 k : 1 """ G = NeuronGroup(10, eqs, threshold="V>1", reset="V=0") G.k = linspace(1, 5, len(G)) H = NeuronGroup(10, "V:1") S = Synapses(H, G, on_post="V_pre += 1") S.connect(j="i") self.H = H run(101 * ms) def results(self): return self.H.V[:] compare = FeatureTest.compare_arrays class SynapsesSTDP(FeatureTest): category = "Synapses" name = "STDP" tags = [ "NeuronGroup", "Threshold", "Reset", "Refractory", "run", "Synapses", "Postsynaptic code", "Presynaptic code", "SpikeMonitor", "StateMonitor", "SpikeGeneratorGroup", ] def run(self): n_cells = 100 n_recorded = 10 numpy.random.seed(42) taum = 20 * ms taus = 5 * ms Vt = -50 * mV Vr = -60 * mV El = -49 * mV fac = 60 * 0.27 / 10 gmax = 20 * fac dApre = 0.01 taupre = 20 * ms taupost = taupre dApost = -dApre * taupre / taupost * 1.05 dApost *= 0.1 * gmax dApre *= 0.1 * gmax connectivity = numpy.random.randn(n_cells, n_cells) sources = numpy.random.random_integers(0, n_cells - 1, 10 * n_cells) # Only use one spike per time step (to rule out that a single source neuron # has more than one spike in a time step) times = ( numpy.random.choice(numpy.arange(10 * n_cells), 10 * n_cells, replace=False) * ms ) v_init = Vr + numpy.random.rand(n_cells) * (Vt - Vr) eqs = Equations( """ dv/dt = (g-(v-El))/taum : volt dg/dt = -g/taus : volt """ ) P = NeuronGroup( n_cells, model=eqs, threshold="v>Vt", reset="v=Vr", refractory=5 * ms ) Q = SpikeGeneratorGroup(n_cells, sources, times) P.v = v_init P.g = 0 * mV S = Synapses( P, P, model="""dApre/dt=-Apre/taupre : 1 (event-driven) dApost/dt=-Apost/taupost : 1 (event-driven) w : 1""", pre="""g += w*mV Apre += dApre w = w + Apost""", post="""Apost += dApost w = w + Apre""", ) S.connect() S.w = fac * connectivity.flatten() T = Synapses(Q, P, model="w : 1", on_pre="g += w*mV") T.connect(j="i") T.w = 10 * fac spike_mon = SpikeMonitor(P) state_mon = StateMonitor(S, "w", record=np.arange(n_recorded)) v_mon = StateMonitor(P, "v", record=np.arange(n_recorded)) self.state_mon = state_mon self.spike_mon = spike_mon self.v_mon = v_mon run(0.2 * second, report="text") def results(self): return self.state_mon.w[:], self.v_mon.v[:], self.spike_mon.num_spikes def compare(self, maxrelerr, res1, res2): w1, v1, n1 = res1 w2, v2, n2 = res2 FeatureTest.compare_arrays(self, maxrelerr, w1, w2) FeatureTest.compare_arrays(self, maxrelerr, v1, v2) FeatureTest.compare_arrays( self, maxrelerr, array([n1], dtype=float), array([n2], dtype=float) ) if __name__ == "__main__": for ftc in [SynapsesPre, SynapsesPost]: ft = ftc() ft.run() print(ft.results()) brian2-2.5.4/brian2/tests/func_def_cpp.cpp000066400000000000000000000001041445201106100203220ustar00rootroot00000000000000double foo(const double x, const double y) { return x + y + 3; }brian2-2.5.4/brian2/tests/func_def_cpp.h000066400000000000000000000000471445201106100177750ustar00rootroot00000000000000double foo(const double, const double);brian2-2.5.4/brian2/tests/func_def_cython.pxd000066400000000000000000000000771445201106100210660ustar00rootroot00000000000000#cython: language_level=3 cdef double foo(double, const double)brian2-2.5.4/brian2/tests/func_def_cython.pyx000066400000000000000000000001311445201106100211020ustar00rootroot00000000000000#cython: language_level=3 cdef double foo(double x, const double y): return x + y + 3brian2-2.5.4/brian2/tests/pytest.ini000066400000000000000000000017671445201106100172540ustar00rootroot00000000000000[pytest] # This excludes the function brian2.test(): python_functions = test_* markers = long: tests that take a long time to run codegen_independent: tests that are independent of code generation standalone_compatible: tests that can be run in standalone mode multiple_runs: tests that call run multiple times standalone_only: tests that are only meant for standalone mode cpp_standalone: tests should be run with standalone_only, to be used with standalone_only marker openmp: standalone tests using OpenMP for parallel execution on multiple CPUs gsl : tests requiring the GSL # Ignore the warning "BaseException.message has been deprecated as of Python 2.6" # that occurs because we copy over all exception attributes in brian_object_exception, # including deprecated ones. # We also ignore a few numerical warnings from function tests filterwarnings = ignore:BaseException:DeprecationWarning ignore:invalid value:RuntimeWarning ignore:divide by zero:RuntimeWarning brian2-2.5.4/brian2/tests/rallpack_data/000077500000000000000000000000001445201106100177725ustar00rootroot00000000000000brian2-2.5.4/brian2/tests/rallpack_data/README000066400000000000000000000006071445201106100206550ustar00rootroot00000000000000The Rallpacks are a set of benchmarks for evaluating neuronal simulators. Bhalla, Upinder S., David H. Bilitch, and James M. Bower. “Rallpacks: A Set of Benchmarks for Neuronal Simulators.” Trends in Neurosciences 15, no. 11 (1992): 453–58. This folder contains data with theoretical solutions for Rallpack 1 and 2, and the solutions simulated by the NEURON simulator for Rallpack 3.brian2-2.5.4/brian2/tests/rallpack_data/ref_axon.0.neuron000066400000000000000000002626311445201106100231730ustar00rootroot000000000000000 -0.065 5e-05 -0.0601856 0.0001 -0.0582823 0.00015 -0.0568717 0.0002 -0.0557084 0.00025 -0.0546937 0.0003 -0.053774 0.00035 -0.0529147 0.0004 -0.0520903 0.00045 -0.0512804 0.0005 -0.0504671 0.00055 -0.0496335 0.0006 -0.0487623 0.00065 -0.0478347 0.0007 -0.0468292 0.00075 -0.0457198 0.0008 -0.044474 0.00085 -0.0430497 0.0009 -0.0413909 0.00095 -0.0394214 0.001 -0.0370361 0.00105 -0.0340882 0.0011 -0.0303727 0.00115 -0.0256099 0.0012 -0.0194435 0.00125 -0.0114985 0.0013 -0.00159644 0.00135 0.00979541 0.0014 0.0211855 0.00145 0.030577 0.0015 0.0368121 0.00155 0.0401051 0.0016 0.0413273 0.00165 0.0412677 0.0017 0.0404276 0.00175 0.0390879 0.0018 0.0374054 0.00185 0.035473 0.0019 0.0333514 0.00195 0.0310844 0.002 0.0287062 0.00205 0.0262448 0.0021 0.0237236 0.00215 0.021163 0.0022 0.0185799 0.00225 0.0159891 0.0023 0.0134027 0.00235 0.0108307 0.0024 0.00828138 0.00245 0.00576114 0.0025 0.00327499 0.00255 0.000826651 0.0026 -0.00158128 0.00265 -0.00394712 0.0027 -0.00626999 0.00275 -0.00854969 0.0028 -0.0107866 0.00285 -0.0129815 0.0029 -0.0151359 0.00295 -0.0172516 0.003 -0.0193309 0.00305 -0.0213771 0.0031 -0.0233937 0.00315 -0.0253855 0.0032 -0.0273581 0.00325 -0.0293185 0.0033 -0.031275 0.00335 -0.033237 0.0034 -0.0352157 0.00345 -0.0372234 0.0035 -0.0392732 0.00355 -0.0413785 0.0036 -0.0435513 0.00365 -0.0458007 0.0037 -0.0481295 0.00375 -0.0505306 0.0038 -0.0529817 0.00385 -0.0554424 0.0039 -0.0578523 0.00395 -0.0601364 0.004 -0.0622169 0.00405 -0.0640296 0.0041 -0.0655387 0.00415 -0.0667423 0.0042 -0.0676669 0.00425 -0.0683558 0.0043 -0.0688565 0.00435 -0.069213 0.0044 -0.0694614 0.00445 -0.06963 0.0045 -0.0697396 0.00455 -0.0698056 0.0046 -0.069839 0.00465 -0.0698476 0.0047 -0.0698375 0.00475 -0.0698126 0.0048 -0.0697764 0.00485 -0.069731 0.0049 -0.0696782 0.00495 -0.0696196 0.005 -0.069556 0.00505 -0.0694883 0.0051 -0.0694172 0.00515 -0.0693433 0.0052 -0.0692668 0.00525 -0.0691883 0.0053 -0.0691078 0.00535 -0.0690257 0.0054 -0.0689422 0.00545 -0.0688573 0.0055 -0.0687713 0.00555 -0.0686841 0.0056 -0.068596 0.00565 -0.068507 0.0057 -0.0684172 0.00575 -0.0683266 0.0058 -0.0682352 0.00585 -0.0681432 0.0059 -0.0680504 0.00595 -0.0679571 0.006 -0.0678632 0.00605 -0.0677687 0.0061 -0.0676736 0.00615 -0.067578 0.0062 -0.067482 0.00625 -0.0673854 0.0063 -0.0672884 0.00635 -0.0671909 0.0064 -0.067093 0.00645 -0.0669947 0.0065 -0.0668959 0.00655 -0.0667968 0.0066 -0.0666973 0.00665 -0.0665974 0.0067 -0.0664971 0.00675 -0.0663966 0.0068 -0.0662957 0.00685 -0.0661944 0.0069 -0.0660929 0.00695 -0.0659911 0.007 -0.065889 0.00705 -0.0657866 0.0071 -0.0656839 0.00715 -0.065581 0.0072 -0.0654779 0.00725 -0.0653745 0.0073 -0.065271 0.00735 -0.0651672 0.0074 -0.0650632 0.00745 -0.064959 0.0075 -0.0648547 0.00755 -0.0647502 0.0076 -0.0646455 0.00765 -0.0645407 0.0077 -0.0644358 0.00775 -0.0643308 0.0078 -0.0642256 0.00785 -0.0641203 0.0079 -0.064015 0.00795 -0.0639096 0.008 -0.063804 0.00805 -0.0636985 0.0081 -0.0635928 0.00815 -0.0634872 0.0082 -0.0633815 0.00825 -0.0632757 0.0083 -0.06317 0.00835 -0.0630642 0.0084 -0.0629585 0.00845 -0.0628527 0.0085 -0.062747 0.00855 -0.0626413 0.0086 -0.0625356 0.00865 -0.06243 0.0087 -0.0623244 0.00875 -0.0622188 0.0088 -0.0621134 0.00885 -0.0620079 0.0089 -0.0619026 0.00895 -0.0617974 0.009 -0.0616922 0.00905 -0.0615871 0.0091 -0.0614821 0.00915 -0.0613772 0.0092 -0.0612725 0.00925 -0.0611678 0.0093 -0.0610633 0.00935 -0.0609588 0.0094 -0.0608545 0.00945 -0.0607503 0.0095 -0.0606463 0.00955 -0.0605424 0.0096 -0.0604386 0.00965 -0.060335 0.0097 -0.0602314 0.00975 -0.0601281 0.0098 -0.0600249 0.00985 -0.0599218 0.0099 -0.0598188 0.00995 -0.0597161 0.01 -0.0596134 0.01005 -0.0595109 0.0101 -0.0594085 0.01015 -0.0593063 0.0102 -0.0592042 0.01025 -0.0591023 0.0103 -0.0590005 0.01035 -0.0588988 0.0104 -0.0587973 0.01045 -0.0586958 0.0105 -0.0585945 0.01055 -0.0584934 0.0106 -0.0583923 0.01065 -0.0582913 0.0107 -0.0581905 0.01075 -0.0580897 0.0108 -0.0579891 0.01085 -0.0578885 0.0109 -0.057788 0.01095 -0.0576876 0.011 -0.0575872 0.01105 -0.0574869 0.0111 -0.0573867 0.01115 -0.0572864 0.0112 -0.0571862 0.01125 -0.057086 0.0113 -0.0569859 0.01135 -0.0568857 0.0114 -0.0567855 0.01145 -0.0566852 0.0115 -0.0565849 0.01155 -0.0564846 0.0116 -0.0563842 0.01165 -0.0562837 0.0117 -0.0561831 0.01175 -0.0560823 0.0118 -0.0559814 0.01185 -0.0558804 0.0119 -0.0557792 0.01195 -0.0556777 0.012 -0.0555761 0.01205 -0.0554742 0.0121 -0.055372 0.01215 -0.0552696 0.0122 -0.0551668 0.01225 -0.0550637 0.0123 -0.0549602 0.01235 -0.0548562 0.0124 -0.0547519 0.01245 -0.054647 0.0125 -0.0545417 0.01255 -0.0544358 0.0126 -0.0543293 0.01265 -0.0542222 0.0127 -0.0541144 0.01275 -0.0540058 0.0128 -0.0538965 0.01285 -0.0537864 0.0129 -0.0536753 0.01295 -0.0535633 0.013 -0.0534503 0.01305 -0.0533362 0.0131 -0.0532209 0.01315 -0.0531044 0.0132 -0.0529866 0.01325 -0.0528673 0.0133 -0.0527466 0.01335 -0.0526242 0.0134 -0.0525 0.01345 -0.052374 0.0135 -0.052246 0.01355 -0.0521158 0.0136 -0.0519834 0.01365 -0.0518484 0.0137 -0.0517109 0.01375 -0.0515704 0.0138 -0.0514269 0.01385 -0.05128 0.0139 -0.0511296 0.01395 -0.0509752 0.014 -0.0508167 0.01405 -0.0506536 0.0141 -0.0504855 0.01415 -0.0503121 0.0142 -0.0501328 0.01425 -0.049947 0.0143 -0.0497542 0.01435 -0.0495537 0.0144 -0.0493447 0.01445 -0.0491264 0.0145 -0.0488977 0.01455 -0.0486576 0.0146 -0.0484047 0.01465 -0.0481377 0.0147 -0.0478548 0.01475 -0.0475541 0.0148 -0.0472333 0.01485 -0.0468898 0.0149 -0.0465206 0.01495 -0.046122 0.015 -0.0456896 0.01505 -0.0452185 0.0151 -0.0447024 0.01515 -0.0441339 0.0152 -0.0435042 0.01525 -0.0428021 0.0153 -0.0420142 0.01535 -0.041124 0.0154 -0.0401105 0.01545 -0.038948 0.0155 -0.0376038 0.01555 -0.0360373 0.0156 -0.0341972 0.01565 -0.0320206 0.0157 -0.0294306 0.01575 -0.026338 0.0158 -0.0226466 0.01585 -0.0182686 0.0159 -0.0131557 0.01595 -0.00735079 0.016 -0.00105105 0.01605 0.00535352 0.0161 0.0113237 0.01615 0.0163182 0.0162 0.01997 0.01625 0.0221769 0.0163 0.0230624 0.01635 0.0228686 0.0164 0.0218589 0.01645 0.0202639 0.0165 0.018265 0.01655 0.0159966 0.0166 0.0135559 0.01665 0.0110125 0.0167 0.00841615 0.01675 0.00580237 0.0168 0.00319671 0.01685 0.000617364 0.0169 -0.00192282 0.01695 -0.00441498 0.017 -0.0068532 0.01705 -0.00923383 0.0171 -0.0115549 0.01715 -0.0138158 0.0172 -0.0160169 0.01725 -0.0181594 0.0173 -0.0202452 0.01735 -0.0222771 0.0174 -0.0242583 0.01745 -0.0261928 0.0175 -0.0280851 0.01755 -0.0299408 0.0176 -0.0317659 0.01765 -0.0335672 0.0177 -0.0353521 0.01775 -0.0371284 0.0178 -0.0389043 0.01785 -0.0406877 0.0179 -0.0424859 0.01795 -0.044305 0.018 -0.0461488 0.01805 -0.0480178 0.0181 -0.0499078 0.01815 -0.0518089 0.0182 -0.0537043 0.01825 -0.0555702 0.0183 -0.0573773 0.01835 -0.0590926 0.0184 -0.0606844 0.01845 -0.0621263 0.0185 -0.0634009 0.01855 -0.064502 0.0186 -0.0654339 0.01865 -0.0662091 0.0187 -0.0668449 0.01875 -0.0673605 0.0188 -0.0677749 0.01885 -0.068105 0.0189 -0.0683657 0.01895 -0.0685693 0.019 -0.0687259 0.01905 -0.0688438 0.0191 -0.0689295 0.01915 -0.0689886 0.0192 -0.0690254 0.01925 -0.0690434 0.0193 -0.0690457 0.01935 -0.0690347 0.0194 -0.0690122 0.01945 -0.0689802 0.0195 -0.0689398 0.01955 -0.0688924 0.0196 -0.0688389 0.01965 -0.0687801 0.0197 -0.0687167 0.01975 -0.0686494 0.0198 -0.0685786 0.01985 -0.0685048 0.0199 -0.0684283 0.01995 -0.0683495 0.02 -0.0682686 0.02005 -0.0681859 0.0201 -0.0681016 0.02015 -0.0680158 0.0202 -0.0679286 0.02025 -0.0678403 0.0203 -0.0677509 0.02035 -0.0676605 0.0204 -0.0675692 0.02045 -0.067477 0.0205 -0.0673842 0.02055 -0.0672906 0.0206 -0.0671963 0.02065 -0.0671015 0.0207 -0.067006 0.02075 -0.06691 0.0208 -0.0668136 0.02085 -0.0667166 0.0209 -0.0666192 0.02095 -0.0665214 0.021 -0.0664232 0.02105 -0.0663246 0.0211 -0.0662257 0.02115 -0.0661264 0.0212 -0.0660267 0.02125 -0.0659268 0.0213 -0.0658266 0.02135 -0.0657261 0.0214 -0.0656253 0.02145 -0.0655243 0.0215 -0.0654231 0.02155 -0.0653216 0.0216 -0.0652199 0.02165 -0.065118 0.0217 -0.065016 0.02175 -0.0649137 0.0218 -0.0648113 0.02185 -0.0647087 0.0219 -0.064606 0.02195 -0.0645032 0.022 -0.0644002 0.02205 -0.0642971 0.0221 -0.064194 0.02215 -0.0640907 0.0222 -0.0639873 0.02225 -0.0638839 0.0223 -0.0637804 0.02235 -0.0636769 0.0224 -0.0635733 0.02245 -0.0634696 0.0225 -0.063366 0.02255 -0.0632623 0.0226 -0.0631586 0.02265 -0.0630549 0.0227 -0.0629512 0.02275 -0.0628476 0.0228 -0.0627439 0.02285 -0.0626403 0.0229 -0.0625367 0.02295 -0.0624332 0.023 -0.0623297 0.02305 -0.0622262 0.0231 -0.0621229 0.02315 -0.0620196 0.0232 -0.0619163 0.02325 -0.0618132 0.0233 -0.0617101 0.02335 -0.0616072 0.0234 -0.0615043 0.02345 -0.0614015 0.0235 -0.0612989 0.02355 -0.0611963 0.0236 -0.0610939 0.02365 -0.0609916 0.0237 -0.0608894 0.02375 -0.0607873 0.0238 -0.0606854 0.02385 -0.0605836 0.0239 -0.0604819 0.02395 -0.0603804 0.024 -0.060279 0.02405 -0.0601778 0.0241 -0.0600767 0.02415 -0.0599757 0.0242 -0.0598749 0.02425 -0.0597743 0.0243 -0.0596737 0.02435 -0.0595734 0.0244 -0.0594732 0.02445 -0.0593731 0.0245 -0.0592732 0.02455 -0.0591734 0.0246 -0.0590737 0.02465 -0.0589742 0.0247 -0.0588749 0.02475 -0.0587756 0.0248 -0.0586765 0.02485 -0.0585776 0.0249 -0.0584787 0.02495 -0.05838 0.025 -0.0582814 0.02505 -0.0581829 0.0251 -0.0580845 0.02515 -0.0579863 0.0252 -0.0578881 0.02525 -0.05779 0.0253 -0.057692 0.02535 -0.0575941 0.0254 -0.0574962 0.02545 -0.0573984 0.0255 -0.0573007 0.02555 -0.057203 0.0256 -0.0571054 0.02565 -0.0570078 0.0257 -0.0569102 0.02575 -0.0568126 0.0258 -0.056715 0.02585 -0.0566174 0.0259 -0.0565197 0.02595 -0.0564221 0.026 -0.0563243 0.02605 -0.0562265 0.0261 -0.0561287 0.02615 -0.0560307 0.0262 -0.0559326 0.02625 -0.0558344 0.0263 -0.055736 0.02635 -0.0556375 0.0264 -0.0555388 0.02645 -0.0554399 0.0265 -0.0553408 0.02655 -0.0552414 0.0266 -0.0551418 0.02665 -0.0550418 0.0267 -0.0549416 0.02675 -0.054841 0.0268 -0.05474 0.02685 -0.0546386 0.0269 -0.0545368 0.02695 -0.0544345 0.027 -0.0543317 0.02705 -0.0542284 0.0271 -0.0541245 0.02715 -0.05402 0.0272 -0.0539148 0.02725 -0.0538089 0.0273 -0.0537023 0.02735 -0.0535948 0.0274 -0.0534865 0.02745 -0.0533773 0.0275 -0.0532671 0.02755 -0.0531558 0.0276 -0.0530434 0.02765 -0.0529298 0.0277 -0.0528149 0.02775 -0.0526987 0.0278 -0.052581 0.02785 -0.0524618 0.0279 -0.0523408 0.02795 -0.0522181 0.028 -0.0520935 0.02805 -0.0519669 0.0281 -0.051838 0.02815 -0.0517068 0.0282 -0.0515731 0.02825 -0.0514366 0.0283 -0.0512973 0.02835 -0.0511547 0.0284 -0.0510088 0.02845 -0.0508592 0.0285 -0.0507056 0.02855 -0.0505477 0.0286 -0.0503852 0.02865 -0.0502176 0.0287 -0.0500444 0.02875 -0.0498653 0.0288 -0.0496795 0.02885 -0.0494866 0.0289 -0.0492858 0.02895 -0.0490762 0.029 -0.0488571 0.02905 -0.0486274 0.0291 -0.0483859 0.02915 -0.0481313 0.0292 -0.0478622 0.02925 -0.0475768 0.0293 -0.0472731 0.02935 -0.0469487 0.0294 -0.046601 0.02945 -0.0462268 0.0295 -0.0458223 0.02955 -0.0453831 0.0296 -0.0449038 0.02965 -0.0443783 0.0297 -0.0437987 0.02975 -0.0431558 0.0298 -0.0424384 0.02985 -0.0416324 0.0299 -0.0407208 0.02995 -0.0396821 0.03 -0.0384897 0.03005 -0.0371105 0.0301 -0.0355027 0.03015 -0.0336148 0.0302 -0.0313832 0.03025 -0.0287319 0.0303 -0.0255743 0.03035 -0.0218199 0.0304 -0.0173917 0.03045 -0.012259 0.0305 -0.00648815 0.03055 -0.00029949 0.0306 0.00590731 0.03065 0.0116097 0.0307 0.0163076 0.03075 0.0196806 0.0308 0.0216568 0.03085 0.0223677 0.0309 0.0220484 0.03095 0.0209504 0.031 0.0192935 0.03105 0.017251 0.0311 0.0149523 0.03115 0.0124912 0.0312 0.00993506 0.03125 0.00733224 0.0313 0.00471723 0.03135 0.00211469 0.0314 -0.000457911 0.03145 -0.00298832 0.0315 -0.00546819 0.03155 -0.00789206 0.0316 -0.0102567 0.03165 -0.0125604 0.0317 -0.0148029 0.03175 -0.0169849 0.0318 -0.0191078 0.03185 -0.0211739 0.0319 -0.0231861 0.03195 -0.025148 0.032 -0.0270638 0.03205 -0.0289386 0.0321 -0.0307781 0.03215 -0.0325885 0.0322 -0.0343771 0.03225 -0.0361512 0.0323 -0.037919 0.03235 -0.0396883 0.0324 -0.0414668 0.03245 -0.0432613 0.0325 -0.045077 0.03255 -0.0469163 0.0326 -0.0487782 0.03265 -0.0506564 0.0327 -0.0525387 0.03275 -0.0544062 0.0328 -0.0562336 0.03285 -0.0579905 0.0329 -0.0596454 0.03295 -0.0611689 0.033 -0.0625382 0.03305 -0.0637406 0.0331 -0.0647736 0.03315 -0.0656445 0.0332 -0.0663672 0.03325 -0.0669593 0.0333 -0.0674394 0.03335 -0.0678254 0.0334 -0.0681332 0.03345 -0.0683763 0.0335 -0.068566 0.03355 -0.0687118 0.0336 -0.0688211 0.03365 -0.0689 0.0337 -0.0689537 0.03375 -0.0689862 0.0338 -0.0690007 0.03385 -0.0690002 0.0339 -0.0689868 0.03395 -0.0689625 0.034 -0.0689288 0.03405 -0.0688872 0.0341 -0.0688387 0.03415 -0.0687842 0.0342 -0.0687246 0.03425 -0.0686606 0.0343 -0.0685926 0.03435 -0.0685213 0.0344 -0.068447 0.03445 -0.0683701 0.0345 -0.068291 0.03455 -0.0682097 0.0346 -0.0681267 0.03465 -0.0680421 0.0347 -0.067956 0.03475 -0.0678686 0.0348 -0.06778 0.03485 -0.0676904 0.0349 -0.0675998 0.03495 -0.0675083 0.035 -0.0674159 0.03505 -0.0673229 0.0351 -0.0672291 0.03515 -0.0671347 0.0352 -0.0670397 0.03525 -0.0669441 0.0353 -0.066848 0.03535 -0.0667513 0.0354 -0.0666543 0.03545 -0.0665567 0.0355 -0.0664588 0.03555 -0.0663605 0.0356 -0.0662618 0.03565 -0.0661627 0.0357 -0.0660634 0.03575 -0.0659636 0.0358 -0.0658636 0.03585 -0.0657634 0.0359 -0.0656628 0.03595 -0.065562 0.036 -0.0654609 0.03605 -0.0653596 0.0361 -0.0652581 0.03615 -0.0651564 0.0362 -0.0650545 0.03625 -0.0649524 0.0363 -0.0648501 0.03635 -0.0647477 0.0364 -0.0646451 0.03645 -0.0645424 0.0365 -0.0644396 0.03655 -0.0643367 0.0366 -0.0642336 0.03665 -0.0641305 0.0367 -0.0640272 0.03675 -0.0639239 0.0368 -0.0638205 0.03685 -0.0637171 0.0369 -0.0636136 0.03695 -0.0635101 0.037 -0.0634065 0.03705 -0.063303 0.0371 -0.0631994 0.03715 -0.0630958 0.0372 -0.0629922 0.03725 -0.0628886 0.0373 -0.062785 0.03735 -0.0626815 0.0374 -0.062578 0.03745 -0.0624745 0.0375 -0.0623711 0.03755 -0.0622677 0.0376 -0.0621644 0.03765 -0.0620612 0.0377 -0.061958 0.03775 -0.061855 0.0378 -0.061752 0.03785 -0.061649 0.0379 -0.0615462 0.03795 -0.0614435 0.038 -0.0613409 0.03805 -0.0612384 0.0381 -0.061136 0.03815 -0.0610338 0.0382 -0.0609316 0.03825 -0.0608296 0.0383 -0.0607277 0.03835 -0.060626 0.0384 -0.0605244 0.03845 -0.0604229 0.0385 -0.0603216 0.03855 -0.0602204 0.0386 -0.0601193 0.03865 -0.0600184 0.0387 -0.0599176 0.03875 -0.059817 0.0388 -0.0597166 0.03885 -0.0596162 0.0389 -0.0595161 0.03895 -0.059416 0.039 -0.0593162 0.03905 -0.0592164 0.0391 -0.0591168 0.03915 -0.0590174 0.0392 -0.0589181 0.03925 -0.0588189 0.0393 -0.0587199 0.03935 -0.0586209 0.0394 -0.0585222 0.03945 -0.0584235 0.0395 -0.058325 0.03955 -0.0582266 0.0396 -0.0581283 0.03965 -0.0580301 0.0397 -0.057932 0.03975 -0.057834 0.0398 -0.0577361 0.03985 -0.0576382 0.0399 -0.0575405 0.03995 -0.0574428 0.04 -0.0573452 0.04005 -0.0572476 0.0401 -0.0571501 0.04015 -0.0570526 0.0402 -0.0569551 0.04025 -0.0568577 0.0403 -0.0567602 0.04035 -0.0566628 0.0404 -0.0565653 0.04045 -0.0564678 0.0405 -0.0563703 0.04055 -0.0562727 0.0406 -0.056175 0.04065 -0.0560772 0.0407 -0.0559794 0.04075 -0.0558814 0.0408 -0.0557833 0.04085 -0.055685 0.0409 -0.0555866 0.04095 -0.0554879 0.041 -0.0553891 0.04105 -0.05529 0.0411 -0.0551907 0.04115 -0.0550911 0.0412 -0.0549912 0.04125 -0.054891 0.0413 -0.0547904 0.04135 -0.0546894 0.0414 -0.054588 0.04145 -0.0544862 0.0415 -0.0543839 0.04155 -0.0542811 0.0416 -0.0541777 0.04165 -0.0540738 0.0417 -0.0539692 0.04175 -0.0538639 0.0418 -0.0537579 0.04185 -0.0536511 0.0419 -0.0535436 0.04195 -0.0534351 0.042 -0.0533257 0.04205 -0.0532153 0.0421 -0.0531038 0.04215 -0.0529912 0.0422 -0.0528773 0.04225 -0.0527621 0.0423 -0.0526456 0.04235 -0.0525276 0.0424 -0.0524079 0.04245 -0.0522866 0.0425 -0.0521634 0.04255 -0.0520383 0.0426 -0.0519111 0.04265 -0.0517817 0.0427 -0.0516499 0.04275 -0.0515154 0.0428 -0.0513782 0.04285 -0.051238 0.0429 -0.0510946 0.04295 -0.0509477 0.043 -0.050797 0.04305 -0.0506423 0.0431 -0.0504831 0.04315 -0.0503192 0.0432 -0.05015 0.04325 -0.0499752 0.0433 -0.0497942 0.04335 -0.0496065 0.0434 -0.0494113 0.04345 -0.049208 0.0435 -0.0489957 0.04355 -0.0487736 0.0436 -0.0485405 0.04365 -0.0482953 0.0437 -0.0480366 0.04375 -0.0477628 0.0438 -0.0474722 0.04385 -0.0471625 0.0439 -0.0468315 0.04395 -0.0464762 0.044 -0.0460933 0.04405 -0.0456788 0.0441 -0.0452282 0.04415 -0.0447357 0.0442 -0.0441947 0.04425 -0.0435971 0.0443 -0.0429331 0.04435 -0.0421906 0.0444 -0.0413548 0.04445 -0.0404075 0.0445 -0.0393258 0.04455 -0.0380812 0.0446 -0.0366384 0.04465 -0.0349528 0.0447 -0.0329697 0.04475 -0.0306218 0.0448 -0.02783 0.04485 -0.0245055 0.0449 -0.0205602 0.04495 -0.0159269 0.045 -0.0105984 0.04505 -0.00468155 0.0451 0.00155027 0.04515 0.00765183 0.0452 0.0130914 0.04525 0.0174099 0.0453 0.0203597 0.04535 0.0219375 0.0454 0.0223153 0.04545 0.0217395 0.0455 0.0204548 0.04555 0.0186675 0.0456 0.0165372 0.04565 0.0141817 0.0457 0.0116861 0.04575 0.00911171 0.0458 0.00650213 0.04585 0.00388865 0.0459 0.0012936 0.04595 -0.00126729 0.046 -0.00378304 0.04605 -0.00624626 0.0461 -0.00865222 0.04615 -0.0109982 0.0462 -0.013283 0.04625 -0.0155067 0.0463 -0.0176701 0.04635 -0.019775 0.0464 -0.0218238 0.04645 -0.0238198 0.0465 -0.0257666 0.04655 -0.0276688 0.0466 -0.0295317 0.04665 -0.031361 0.0467 -0.0331635 0.04675 -0.0349463 0.0468 -0.0367172 0.04685 -0.0384841 0.0469 -0.0402549 0.04695 -0.0420372 0.047 -0.0438372 0.04705 -0.0456594 0.0471 -0.0475053 0.04715 -0.0493722 0.0472 -0.0512522 0.04725 -0.053131 0.0473 -0.0549875 0.04735 -0.0567948 0.0474 -0.0585217 0.04745 -0.0601369 0.0475 -0.0616129 0.04755 -0.0629298 0.0476 -0.0640781 0.04765 -0.0650587 0.0477 -0.0658811 0.04775 -0.0665607 0.0478 -0.0671157 0.04785 -0.0675646 0.0479 -0.0679247 0.04795 -0.068211 0.048 -0.0684365 0.04805 -0.0686118 0.0481 -0.0687456 0.04815 -0.068845 0.0482 -0.0689158 0.04825 -0.0689626 0.0483 -0.0689892 0.04835 -0.068999 0.0484 -0.0689943 0.04845 -0.0689774 0.0485 -0.0689501 0.04855 -0.0689139 0.0486 -0.0688701 0.04865 -0.0688197 0.0487 -0.0687636 0.04875 -0.0687026 0.0488 -0.0686373 0.04885 -0.0685683 0.0489 -0.0684961 0.04895 -0.068421 0.049 -0.0683434 0.04905 -0.0682635 0.0491 -0.0681818 0.04915 -0.0680982 0.0492 -0.0680131 0.04925 -0.0679266 0.0493 -0.0678389 0.04935 -0.0677499 0.0494 -0.06766 0.04945 -0.0675691 0.0495 -0.0674773 0.04955 -0.0673848 0.0496 -0.0672915 0.04965 -0.0671975 0.0497 -0.0671029 0.04975 -0.0670077 0.0498 -0.0669119 0.04985 -0.0668157 0.0499 -0.0667189 0.04995 -0.0666217 0.05 -0.066524 0.05005 -0.066426 0.0501 -0.0663275 0.05015 -0.0662287 0.0502 -0.0661296 0.05025 -0.0660301 0.0503 -0.0659303 0.05035 -0.0658302 0.0504 -0.0657298 0.05045 -0.0656292 0.0505 -0.0655283 0.05055 -0.0654271 0.0506 -0.0653258 0.05065 -0.0652242 0.0507 -0.0651224 0.05075 -0.0650205 0.0508 -0.0649183 0.05085 -0.064816 0.0509 -0.0647136 0.05095 -0.064611 0.051 -0.0645082 0.05105 -0.0644053 0.0511 -0.0643024 0.05115 -0.0641993 0.0512 -0.0640961 0.05125 -0.0639929 0.0513 -0.0638895 0.05135 -0.0637861 0.0514 -0.0636827 0.05145 -0.0635792 0.0515 -0.0634757 0.05155 -0.0633721 0.0516 -0.0632685 0.05165 -0.063165 0.0517 -0.0630614 0.05175 -0.0629578 0.0518 -0.0628542 0.05185 -0.0627506 0.0519 -0.0626471 0.05195 -0.0625436 0.052 -0.0624402 0.05205 -0.0623368 0.0521 -0.0622335 0.05215 -0.0621302 0.0522 -0.062027 0.05225 -0.0619238 0.0523 -0.0618208 0.05235 -0.0617178 0.0524 -0.061615 0.05245 -0.0615122 0.0525 -0.0614095 0.05255 -0.061307 0.0526 -0.0612045 0.05265 -0.0611022 0.0527 -0.061 0.05275 -0.0608979 0.0528 -0.0607959 0.05285 -0.0606941 0.0529 -0.0605924 0.05295 -0.0604908 0.053 -0.0603894 0.05305 -0.0602881 0.0531 -0.060187 0.05315 -0.060086 0.0532 -0.0599851 0.05325 -0.0598844 0.0533 -0.0597838 0.05335 -0.0596834 0.0534 -0.0595832 0.05345 -0.059483 0.0535 -0.0593831 0.05355 -0.0592832 0.0536 -0.0591836 0.05365 -0.059084 0.0537 -0.0589846 0.05375 -0.0588854 0.0538 -0.0587862 0.05385 -0.0586873 0.0539 -0.0585884 0.05395 -0.0584897 0.054 -0.0583911 0.05405 -0.0582926 0.0541 -0.0581942 0.05415 -0.058096 0.0542 -0.0579978 0.05425 -0.0578997 0.0543 -0.0578018 0.05435 -0.0577039 0.0544 -0.0576061 0.05445 -0.0575084 0.0545 -0.0574108 0.05455 -0.0573132 0.0546 -0.0572156 0.05465 -0.0571181 0.0547 -0.0570206 0.05475 -0.0569232 0.0548 -0.0568258 0.05485 -0.0567283 0.0549 -0.0566309 0.05495 -0.0565334 0.055 -0.0564359 0.05505 -0.0563383 0.0551 -0.0562407 0.05515 -0.056143 0.0552 -0.0560452 0.05525 -0.0559474 0.0553 -0.0558493 0.05535 -0.0557512 0.0554 -0.0556529 0.05545 -0.0555544 0.0555 -0.0554557 0.05555 -0.0553568 0.0556 -0.0552577 0.05565 -0.0551583 0.0557 -0.0550586 0.05575 -0.0549586 0.0558 -0.0548583 0.05585 -0.0547576 0.0559 -0.0546565 0.05595 -0.054555 0.056 -0.054453 0.05605 -0.0543506 0.0561 -0.0542476 0.05615 -0.0541441 0.0562 -0.0540399 0.05625 -0.0539351 0.0563 -0.0538297 0.05635 -0.0537234 0.0564 -0.0536164 0.05645 -0.0535086 0.0565 -0.0533998 0.05655 -0.0532901 0.0566 -0.0531794 0.05665 -0.0530676 0.0567 -0.0529546 0.05675 -0.0528403 0.0568 -0.0527248 0.05685 -0.0526078 0.0569 -0.0524893 0.05695 -0.0523691 0.057 -0.0522472 0.05705 -0.0521235 0.0571 -0.0519978 0.05715 -0.0518699 0.0572 -0.0517397 0.05725 -0.0516071 0.0573 -0.0514718 0.05735 -0.0513337 0.0574 -0.0511926 0.05745 -0.0510481 0.0575 -0.0509 0.05755 -0.0507481 0.0576 -0.0505921 0.05765 -0.0504315 0.0577 -0.050266 0.05775 -0.0500951 0.0578 -0.0499184 0.05785 -0.0497354 0.0579 -0.0495454 0.05795 -0.0493478 0.058 -0.0491418 0.05805 -0.0489266 0.0581 -0.0487012 0.05815 -0.0484645 0.0582 -0.0482152 0.05825 -0.047952 0.0583 -0.0476732 0.05835 -0.0473768 0.0584 -0.0470608 0.05845 -0.0467225 0.0585 -0.046359 0.05855 -0.0459668 0.0586 -0.0455416 0.05865 -0.0450786 0.0587 -0.0445718 0.05875 -0.0440141 0.0588 -0.043397 0.05885 -0.04271 0.0589 -0.0419402 0.05895 -0.0410718 0.059 -0.0400853 0.05905 -0.0389563 0.0591 -0.0376542 0.05915 -0.0361411 0.0592 -0.0343694 0.05925 -0.0322805 0.0593 -0.0298036 0.05935 -0.0268559 0.0594 -0.0233475 0.05945 -0.0191937 0.0595 -0.0143412 0.05955 -0.00881185 0.0596 -0.00275983 0.05965 0.00348499 0.0597 0.00943683 0.05975 0.0145683 0.0598 0.0184752 0.05985 0.0209882 0.0599 0.0221688 0.05995 0.0222226 0.06 0.0214015 0.06005 0.0199406 0.0601 0.0180317 0.06015 0.0158204 0.0602 0.0134135 0.06025 0.0108878 0.0603 0.00829844 0.06035 0.00568477 0.0604 0.00307499 0.06045 0.000489136 0.0605 -0.00205868 0.06055 -0.00455871 0.0606 -0.00700449 0.06065 -0.00939198 0.0607 -0.011719 0.06075 -0.0139847 0.0608 -0.0161894 0.06085 -0.0183343 0.0609 -0.0204214 0.06095 -0.0224534 0.061 -0.0244335 0.06105 -0.0263659 0.0611 -0.0282552 0.06115 -0.0301069 0.0612 -0.0319271 0.06125 -0.0337226 0.0613 -0.0355009 0.06135 -0.0372696 0.0614 -0.0390369 0.06145 -0.0408105 0.0615 -0.0425977 0.06155 -0.0444042 0.0616 -0.0462338 0.06165 -0.0480866 0.0617 -0.0499587 0.06175 -0.0518401 0.0618 -0.0537144 0.06185 -0.0555586 0.0619 -0.0573441 0.06195 -0.0590393 0.062 -0.0606137 0.06205 -0.0620416 0.0621 -0.0633063 0.06215 -0.0644016 0.0622 -0.0653314 0.06225 -0.0661074 0.0623 -0.0667461 0.06235 -0.067266 0.0624 -0.0676855 0.06245 -0.068021 0.0625 -0.0682871 0.06255 -0.0684959 0.0626 -0.0686574 0.06265 -0.0687798 0.0627 -0.0688697 0.06275 -0.0689325 0.0628 -0.0689727 0.06285 -0.0689937 0.0629 -0.0689987 0.06295 -0.06899 0.063 -0.0689697 0.06305 -0.0689395 0.0631 -0.0689008 0.06315 -0.0688549 0.0632 -0.0688026 0.06325 -0.0687449 0.0633 -0.0686825 0.06335 -0.068616 0.0634 -0.0685459 0.06345 -0.0684728 0.0635 -0.0683968 0.06355 -0.0683185 0.0636 -0.0682381 0.06365 -0.0681557 0.0637 -0.0680717 0.06375 -0.0679861 0.0638 -0.0678992 0.06385 -0.0678111 0.0639 -0.0677218 0.06395 -0.0676316 0.064 -0.0675404 0.06405 -0.0674484 0.0641 -0.0673556 0.06415 -0.0672621 0.0642 -0.0671679 0.06425 -0.0670731 0.0643 -0.0669777 0.06435 -0.0668818 0.0644 -0.0667854 0.06445 -0.0666885 0.0645 -0.0665911 0.06455 -0.0664933 0.0646 -0.0663951 0.06465 -0.0662966 0.0647 -0.0661977 0.06475 -0.0660984 0.0648 -0.0659988 0.06485 -0.0658989 0.0649 -0.0657987 0.06495 -0.0656983 0.065 -0.0655976 0.06505 -0.0654966 0.0651 -0.0653954 0.06515 -0.065294 0.0652 -0.0651923 0.06525 -0.0650905 0.0653 -0.0649885 0.06535 -0.0648863 0.0654 -0.0647839 0.06545 -0.0646814 0.0655 -0.0645787 0.06555 -0.064476 0.0656 -0.0643731 0.06565 -0.0642701 0.0657 -0.0641669 0.06575 -0.0640637 0.0658 -0.0639605 0.06585 -0.0638571 0.0659 -0.0637537 0.06595 -0.0636502 0.066 -0.0635467 0.06605 -0.0634432 0.0661 -0.0633396 0.06615 -0.0632361 0.0662 -0.0631325 0.06625 -0.0630289 0.0663 -0.0629253 0.06635 -0.0628217 0.0664 -0.0627182 0.06645 -0.0626147 0.0665 -0.0625112 0.06655 -0.0624078 0.0666 -0.0623044 0.06665 -0.0622011 0.0667 -0.0620978 0.06675 -0.0619946 0.0668 -0.0618915 0.06685 -0.0617885 0.0669 -0.0616856 0.06695 -0.0615827 0.067 -0.06148 0.06705 -0.0613774 0.0671 -0.0612748 0.06715 -0.0611724 0.0672 -0.0610701 0.06725 -0.0609679 0.0673 -0.0608659 0.06735 -0.060764 0.0674 -0.0606622 0.06745 -0.0605605 0.0675 -0.060459 0.06755 -0.0603576 0.0676 -0.0602564 0.06765 -0.0601553 0.0677 -0.0600543 0.06775 -0.0599535 0.0678 -0.0598529 0.06785 -0.0597523 0.0679 -0.059652 0.06795 -0.0595518 0.068 -0.0594517 0.06805 -0.0593518 0.0681 -0.059252 0.06815 -0.0591523 0.0682 -0.0590529 0.06825 -0.0589535 0.0683 -0.0588543 0.06835 -0.0587552 0.0684 -0.0586563 0.06845 -0.0585575 0.0685 -0.0584588 0.06855 -0.0583602 0.0686 -0.0582618 0.06865 -0.0581634 0.0687 -0.0580652 0.06875 -0.0579671 0.0688 -0.057869 0.06885 -0.0577711 0.0689 -0.0576733 0.06895 -0.0575755 0.069 -0.0574778 0.06905 -0.0573802 0.0691 -0.0572826 0.06915 -0.0571851 0.0692 -0.0570876 0.06925 -0.0569901 0.0693 -0.0568927 0.06935 -0.0567952 0.0694 -0.0566978 0.06945 -0.0566003 0.0695 -0.0565029 0.06955 -0.0564053 0.0696 -0.0563078 0.06965 -0.0562101 0.0697 -0.0561124 0.06975 -0.0560146 0.0698 -0.0559167 0.06985 -0.0558186 0.0699 -0.0557204 0.06995 -0.0556221 0.07 -0.0555235 0.07005 -0.0554248 0.0701 -0.0553258 0.07015 -0.0552266 0.0702 -0.0551271 0.07025 -0.0550273 0.0703 -0.0549272 0.07035 -0.0548268 0.0704 -0.054726 0.07045 -0.0546248 0.0705 -0.0545231 0.07055 -0.054421 0.0706 -0.0543184 0.07065 -0.0542153 0.0707 -0.0541115 0.07075 -0.0540072 0.0708 -0.0539022 0.07085 -0.0537965 0.0709 -0.05369 0.07095 -0.0535828 0.071 -0.0534747 0.07105 -0.0533656 0.0711 -0.0532556 0.07115 -0.0531445 0.0712 -0.0530323 0.07125 -0.052919 0.0713 -0.0528043 0.07135 -0.0526883 0.0714 -0.0525709 0.07145 -0.0524519 0.0715 -0.0523312 0.07155 -0.0522087 0.0716 -0.0520844 0.07165 -0.051958 0.0717 -0.0518294 0.07175 -0.0516985 0.0718 -0.0515651 0.07185 -0.051429 0.0719 -0.0512899 0.07195 -0.0511478 0.072 -0.0510022 0.07205 -0.050853 0.0721 -0.0506998 0.07215 -0.0505424 0.0722 -0.0503803 0.07225 -0.0502132 0.0723 -0.0500405 0.07235 -0.049862 0.0724 -0.0496768 0.07245 -0.0494845 0.0725 -0.0492844 0.07255 -0.0490756 0.0726 -0.0488573 0.07265 -0.0486285 0.0727 -0.048388 0.07275 -0.0481346 0.0728 -0.0478667 0.07285 -0.0475826 0.0729 -0.0472804 0.07295 -0.0469577 0.073 -0.0466119 0.07305 -0.0462398 0.0731 -0.0458378 0.07315 -0.0454013 0.0732 -0.0449253 0.07325 -0.0444035 0.0733 -0.0438283 0.07335 -0.0431906 0.0734 -0.0424792 0.07345 -0.0416805 0.0735 -0.0407776 0.07355 -0.0397495 0.0736 -0.0385701 0.07365 -0.0372066 0.0737 -0.0356184 0.07375 -0.0337546 0.0738 -0.0315529 0.07385 -0.0289384 0.0739 -0.0258255 0.07395 -0.022124 0.074 -0.0177555 0.07405 -0.0126844 0.0741 -0.00696771 0.07415 -0.000811622 0.0742 0.0053989 0.07425 0.0111487 0.0743 0.0159316 0.07435 0.0194098 0.0744 0.0214916 0.07445 0.0222946 0.0745 0.0220483 0.07455 0.0210041 0.0746 0.0193851 0.07465 0.0173682 0.0747 0.015086 0.07475 0.012635 0.0748 0.0100843 0.07485 0.00748372 0.0749 0.00486867 0.07495 0.00226452 0.075 -0.000310741 0.07505 -0.00284452 0.0751 -0.00532819 0.07515 -0.00775611 0.0752 -0.0101249 0.07525 -0.0124328 0.0753 -0.0146793 0.07535 -0.0168652 0.0754 -0.0189919 0.07545 -0.0210615 0.0755 -0.0230769 0.07555 -0.0250417 0.0756 -0.0269601 0.07565 -0.028837 0.0757 -0.0306782 0.07575 -0.0324899 0.0758 -0.0342792 0.07585 -0.0360536 0.0759 -0.037821 0.07595 -0.0395895 0.076 -0.0413666 0.07605 -0.0431593 0.0761 -0.0449727 0.07615 -0.0468097 0.0762 -0.0486693 0.07625 -0.0505456 0.0763 -0.0524268 0.07635 -0.0542945 0.0764 -0.0561236 0.07645 -0.0578844 0.0765 -0.059545 0.07655 -0.061076 0.0766 -0.0624543 0.07665 -0.0636663 0.0767 -0.0647092 0.07675 -0.0655894 0.0768 -0.0663206 0.07685 -0.0669202 0.0769 -0.0674069 0.07695 -0.0677985 0.077 -0.0681109 0.07705 -0.0683579 0.0771 -0.0685509 0.07715 -0.0686994 0.0772 -0.068811 0.07725 -0.068892 0.0773 -0.0689473 0.07735 -0.0689811 0.0774 -0.0689969 0.07745 -0.0689973 0.0775 -0.0689848 0.07755 -0.0689613 0.0776 -0.0689283 0.07765 -0.0688872 0.0777 -0.0688392 0.07775 -0.0687851 0.0778 -0.0687259 0.07785 -0.0686621 0.0779 -0.0685945 0.07795 -0.0685234 0.078 -0.0684493 0.07805 -0.0683726 0.0781 -0.0682936 0.07815 -0.0682125 0.0782 -0.0681296 0.07825 -0.0680451 0.0783 -0.0679591 0.07835 -0.0678718 0.0784 -0.0677833 0.07845 -0.0676937 0.0785 -0.0676032 0.07855 -0.0675118 0.0786 -0.0674195 0.07865 -0.0673265 0.0787 -0.0672327 0.07875 -0.0671384 0.0788 -0.0670434 0.07885 -0.0669478 0.0789 -0.0668517 0.07895 -0.0667552 0.079 -0.0666581 0.07905 -0.0665606 0.0791 -0.0664627 0.07915 -0.0663644 0.0792 -0.0662657 0.07925 -0.0661667 0.0793 -0.0660673 0.07935 -0.0659677 0.0794 -0.0658677 0.07945 -0.0657674 0.0795 -0.0656669 0.07955 -0.0655661 0.0796 -0.065465 0.07965 -0.0653637 0.0797 -0.0652622 0.07975 -0.0651605 0.0798 -0.0650586 0.07985 -0.0649566 0.0799 -0.0648543 0.07995 -0.0647519 0.08 -0.0646494 0.08005 -0.0645467 0.0801 -0.0644439 0.08015 -0.0643409 0.0802 -0.0642379 0.08025 -0.0641347 0.0803 -0.0640315 0.08035 -0.0639282 0.0804 -0.0638248 0.08045 -0.0637214 0.0805 -0.0636179 0.08055 -0.0635144 0.0806 -0.0634109 0.08065 -0.0633073 0.0807 -0.0632037 0.08075 -0.0631001 0.0808 -0.0629965 0.08085 -0.062893 0.0809 -0.0627894 0.08095 -0.0626859 0.081 -0.0625824 0.08105 -0.0624789 0.0811 -0.0623755 0.08115 -0.0622721 0.0812 -0.0621688 0.08125 -0.0620656 0.0813 -0.0619624 0.08135 -0.0618594 0.0814 -0.0617564 0.08145 -0.0616535 0.0815 -0.0615506 0.08155 -0.0614479 0.0816 -0.0613453 0.08165 -0.0612428 0.0817 -0.0611405 0.08175 -0.0610382 0.0818 -0.0609361 0.08185 -0.0608341 0.0819 -0.0607322 0.08195 -0.0606304 0.082 -0.0605288 0.08205 -0.0604273 0.0821 -0.060326 0.08215 -0.0602248 0.0822 -0.0601237 0.08225 -0.0600228 0.0823 -0.0599221 0.08235 -0.0598215 0.0824 -0.059721 0.08245 -0.0596207 0.0825 -0.0595205 0.08255 -0.0594205 0.0826 -0.0593206 0.08265 -0.0592209 0.0827 -0.0591213 0.08275 -0.0590218 0.0828 -0.0589225 0.08285 -0.0588233 0.0829 -0.0587243 0.08295 -0.0586254 0.083 -0.0585266 0.08305 -0.058428 0.0831 -0.0583295 0.08315 -0.058231 0.0832 -0.0581327 0.08325 -0.0580345 0.0833 -0.0579365 0.08335 -0.0578385 0.0834 -0.0577406 0.08345 -0.0576427 0.0835 -0.057545 0.08355 -0.0574473 0.0836 -0.0573497 0.08365 -0.0572521 0.0837 -0.0571546 0.08375 -0.0570571 0.0838 -0.0569597 0.08385 -0.0568623 0.0839 -0.0567648 0.08395 -0.0566674 0.084 -0.0565699 0.08405 -0.0564724 0.0841 -0.0563749 0.08415 -0.0562773 0.0842 -0.0561796 0.08425 -0.0560819 0.0843 -0.055984 0.08435 -0.0558861 0.0844 -0.055788 0.08445 -0.0556897 0.0845 -0.0555913 0.08455 -0.0554927 0.0846 -0.0553939 0.08465 -0.0552949 0.0847 -0.0551956 0.08475 -0.055096 0.0848 -0.0549961 0.08485 -0.0548959 0.0849 -0.0547954 0.08495 -0.0546944 0.085 -0.0545931 0.08505 -0.0544913 0.0851 -0.054389 0.08515 -0.0542863 0.0852 -0.054183 0.08525 -0.054079 0.0853 -0.0539745 0.08535 -0.0538693 0.0854 -0.0537633 0.08545 -0.0536566 0.0855 -0.0535491 0.08555 -0.0534407 0.0856 -0.0533314 0.08565 -0.053221 0.0857 -0.0531096 0.08575 -0.0529971 0.0858 -0.0528833 0.08585 -0.0527683 0.0859 -0.0526518 0.08595 -0.0525339 0.086 -0.0524144 0.08605 -0.0522932 0.0861 -0.0521701 0.08615 -0.0520452 0.0862 -0.0519181 0.08625 -0.0517888 0.0863 -0.0516572 0.08635 -0.0515229 0.0864 -0.0513859 0.08645 -0.0512459 0.0865 -0.0511027 0.08655 -0.050956 0.0866 -0.0508056 0.08665 -0.0506511 0.0867 -0.0504923 0.08675 -0.0503287 0.0868 -0.0501599 0.08685 -0.0499855 0.0869 -0.0498049 0.08695 -0.0496176 0.087 -0.0494229 0.08705 -0.0492202 0.0871 -0.0490085 0.08715 -0.0487871 0.0872 -0.0485547 0.08725 -0.0483104 0.0873 -0.0480526 0.08735 -0.0477798 0.0874 -0.0474903 0.08745 -0.047182 0.0875 -0.0468524 0.08755 -0.0464988 0.0876 -0.0461177 0.08765 -0.0457055 0.0877 -0.0452573 0.08775 -0.0447677 0.0878 -0.0442301 0.08785 -0.0436364 0.0879 -0.0429771 0.08795 -0.04224 0.088 -0.0414108 0.08805 -0.0404713 0.0881 -0.039399 0.08815 -0.038166 0.0882 -0.0367372 0.08825 -0.0350689 0.0883 -0.0331069 0.08835 -0.030785 0.0884 -0.0280246 0.08845 -0.0247376 0.0885 -0.0208351 0.08855 -0.0162479 0.0886 -0.0109633 0.08865 -0.00507896 0.0887 0.00114343 0.08875 0.00726843 0.0888 0.0127657 0.08885 0.0171665 0.0889 0.0202072 0.08895 0.0218699 0.089 0.0223183 0.08905 0.0217967 0.0891 0.0205512 0.08915 0.0187913 0.0892 0.0166792 0.08925 0.0143354 0.0893 0.0118469 0.08935 0.00927609 0.0894 0.00666772 0.08945 0.00405375 0.0895 0.00145697 0.08955 -0.00110649 0.0896 -0.0036254 0.08965 -0.00609216 0.0897 -0.0085019 0.08975 -0.0108518 0.0898 -0.0131405 0.08985 -0.015368 0.0899 -0.0175353 0.08995 -0.0196438 0.09 -0.0216962 0.09005 -0.0236954 0.0901 -0.0256452 0.09015 -0.0275501 0.0902 -0.0294152 0.09025 -0.0312465 0.0903 -0.0330504 0.09035 -0.0348343 0.0904 -0.0366056 0.09045 -0.0383725 0.0905 -0.0401429 0.09055 -0.0419242 0.0906 -0.0437228 0.09065 -0.0455435 0.0907 -0.0473878 0.09075 -0.0492535 0.0908 -0.051133 0.09085 -0.0530123 0.0909 -0.0548709 0.09095 -0.056682 0.091 -0.0584149 0.09105 -0.060038 0.0911 -0.0615234 0.09115 -0.0628508 0.0912 -0.0640098 0.09125 -0.0650008 0.0913 -0.0658328 0.09135 -0.066521 0.0914 -0.0670834 0.09145 -0.0675385 0.0915 -0.0679038 0.09155 -0.0681944 0.0916 -0.0684235 0.09165 -0.0686017 0.0917 -0.0687379 0.09175 -0.0688393 0.0918 -0.0689118 0.09185 -0.06896 0.0919 -0.0689878 0.09195 -0.0689985 0.092 -0.0689947 0.09205 -0.0689786 0.0921 -0.0689519 0.09215 -0.0689163 0.0922 -0.0688729 0.09225 -0.0688229 0.0923 -0.0687671 0.09235 -0.0687064 0.0924 -0.0686414 0.09245 -0.0685726 0.0925 -0.0685006 0.09255 -0.0684257 0.0926 -0.0683482 0.09265 -0.0682685 0.0927 -0.0681868 0.09275 -0.0681034 0.0928 -0.0680184 0.09285 -0.067932 0.0929 -0.0678443 0.09295 -0.0677555 0.093 -0.0676656 0.09305 -0.0675747 0.0931 -0.067483 0.09315 -0.0673905 0.0932 -0.0672973 0.09325 -0.0672033 0.0933 -0.0671088 0.09335 -0.0670136 0.0934 -0.0669179 0.09345 -0.0668216 0.0935 -0.0667249 0.09355 -0.0666277 0.0936 -0.0665301 0.09365 -0.0664321 0.0937 -0.0663337 0.09375 -0.0662349 0.0938 -0.0661357 0.09385 -0.0660363 0.0939 -0.0659365 0.09395 -0.0658364 0.094 -0.0657361 0.09405 -0.0656354 0.0941 -0.0655345 0.09415 -0.0654334 0.0942 -0.0653321 0.09425 -0.0652305 0.0943 -0.0651288 0.09435 -0.0650268 0.0944 -0.0649247 0.09445 -0.0648224 0.0945 -0.0647199 0.09455 -0.0646173 0.0946 -0.0645146 0.09465 -0.0644117 0.0947 -0.0643088 0.09475 -0.0642057 0.0948 -0.0641025 0.09485 -0.0639993 0.0949 -0.063896 0.09495 -0.0637926 0.095 -0.0636891 0.09505 -0.0635856 0.0951 -0.0634821 0.09515 -0.0633785 0.0952 -0.063275 0.09525 -0.0631714 0.0953 -0.0630678 0.09535 -0.0629642 0.0954 -0.0628606 0.09545 -0.0627571 0.0955 -0.0626536 0.09555 -0.0625501 0.0956 -0.0624466 0.09565 -0.0623432 0.0957 -0.0622399 0.09575 -0.0621366 0.0958 -0.0620334 0.09585 -0.0619303 0.0959 -0.0618272 0.09595 -0.0617242 0.096 -0.0616214 0.09605 -0.0615186 0.0961 -0.0614159 0.09615 -0.0613133 0.0962 -0.0612109 0.09625 -0.0611085 0.0963 -0.0610063 0.09635 -0.0609042 0.0964 -0.0608022 0.09645 -0.0607004 0.0965 -0.0605987 0.09655 -0.0604971 0.0966 -0.0603957 0.09665 -0.0602944 0.0967 -0.0601932 0.09675 -0.0600922 0.0968 -0.0599914 0.09685 -0.0598907 0.0969 -0.0597901 0.09695 -0.0596897 0.097 -0.0595894 0.09705 -0.0594893 0.0971 -0.0593893 0.09715 -0.0592895 0.0972 -0.0591898 0.09725 -0.0590902 0.0973 -0.0589908 0.09735 -0.0588915 0.0974 -0.0587924 0.09745 -0.0586934 0.0975 -0.0585946 0.09755 -0.0584958 0.0976 -0.0583972 0.09765 -0.0582987 0.0977 -0.0582003 0.09775 -0.0581021 0.0978 -0.0580039 0.09785 -0.0579059 0.0979 -0.0578079 0.09795 -0.05771 0.098 -0.0576122 0.09805 -0.0575145 0.0981 -0.0574168 0.09815 -0.0573193 0.0982 -0.0572217 0.09825 -0.0571242 0.0983 -0.0570267 0.09835 -0.0569293 0.0984 -0.0568318 0.09845 -0.0567344 0.0985 -0.056637 0.09855 -0.0565395 0.0986 -0.056442 0.09865 -0.0563444 0.0987 -0.0562468 0.09875 -0.0561491 0.0988 -0.0560514 0.09885 -0.0559535 0.0989 -0.0558555 0.09895 -0.0557573 0.099 -0.055659 0.09905 -0.0555606 0.0991 -0.0554619 0.09915 -0.055363 0.0992 -0.0552639 0.09925 -0.0551645 0.0993 -0.0550649 0.09935 -0.0549649 0.0994 -0.0548646 0.09945 -0.0547639 0.0995 -0.0546629 0.09955 -0.0545614 0.0996 -0.0544594 0.09965 -0.054357 0.0997 -0.0542541 0.09975 -0.0541506 0.0998 -0.0540465 0.09985 -0.0539417 0.0999 -0.0538363 0.09995 -0.0537301 0.1 -0.0536232 0.10005 -0.0535154 0.1001 -0.0534067 0.10015 -0.0532971 0.1002 -0.0531864 0.10025 -0.0530746 0.1003 -0.0529617 0.10035 -0.0528476 0.1004 -0.0527321 0.10045 -0.0526152 0.1005 -0.0524968 0.10055 -0.0523767 0.1006 -0.052255 0.10065 -0.0521313 0.1007 -0.0520057 0.10075 -0.051878 0.1008 -0.051748 0.10085 -0.0516155 0.1009 -0.0514805 0.10095 -0.0513425 0.101 -0.0512016 0.10105 -0.0510573 0.1011 -0.0509095 0.10115 -0.0507578 0.1012 -0.0506021 0.10125 -0.0504418 0.1013 -0.0502766 0.10135 -0.0501061 0.1014 -0.0499298 0.10145 -0.0497472 0.1015 -0.0495577 0.10155 -0.0493606 0.1016 -0.0491551 0.10165 -0.0489405 0.1017 -0.0487158 0.10175 -0.0484798 0.1018 -0.0482314 0.10185 -0.0479691 0.1019 -0.0476914 0.10195 -0.0473962 0.102 -0.0470815 0.10205 -0.0467447 0.1021 -0.0463829 0.10215 -0.0459926 0.1022 -0.0455697 0.10225 -0.0451092 0.1023 -0.0446054 0.10235 -0.0440512 0.1024 -0.0434382 0.10245 -0.042756 0.1025 -0.0419919 0.10255 -0.0411304 0.1026 -0.0401521 0.10265 -0.0390329 0.1027 -0.037743 0.10275 -0.0362446 0.1028 -0.034491 0.10285 -0.0324243 0.1029 -0.0299745 0.10295 -0.0270595 0.103 -0.0235895 0.10305 -0.019479 0.1031 -0.0146715 0.10315 -0.00918243 0.1032 -0.00315588 0.10325 0.00308982 0.1033 0.0090764 0.10335 0.0142743 0.1034 0.018267 0.10345 0.0208691 0.1035 0.0221295 0.10355 0.0222476 0.1036 0.0214749 0.10365 0.0200486 0.1037 0.0181635 0.10375 0.0159679 0.1038 0.0135709 0.10385 0.011051 0.1039 0.00846431 0.10395 0.00585125 0.104 0.00324055 0.10405 0.000652696 0.1041 -0.00189787 0.10415 -0.00440117 0.1042 -0.00685055 0.10425 -0.00924183 0.1043 -0.0115727 0.10435 -0.0138423 0.1044 -0.0160509 0.10445 -0.0181996 0.1045 -0.0202903 0.10455 -0.0223257 0.1046 -0.024309 0.10465 -0.0262443 0.1047 -0.0281362 0.10475 -0.0299901 0.1048 -0.0318121 0.10485 -0.033609 0.1049 -0.0353881 0.10495 -0.0371572 0.105 -0.0389243 0.10505 -0.0406973 0.1051 -0.0424834 0.10515 -0.0442886 0.1052 -0.0461166 0.10525 -0.0479681 0.1053 -0.0498392 0.10535 -0.0517204 0.1054 -0.0535957 0.10545 -0.0554426 0.1055 -0.0572328 0.10555 -0.0589347 0.1056 -0.0605176 0.10565 -0.0619554 0.1057 -0.0632308 0.10575 -0.0643369 0.1058 -0.0652769 0.10585 -0.0660622 0.1059 -0.0667091 0.10595 -0.0672361 0.106 -0.0676614 0.10605 -0.0680018 0.1061 -0.068272 0.10615 -0.0684841 0.1062 -0.0686483 0.10625 -0.068773 0.1063 -0.0688648 0.10635 -0.0689292 0.1064 -0.0689707 0.10645 -0.0689929 0.1065 -0.0689988 0.10655 -0.0689909 0.1066 -0.0689713 0.10665 -0.0689417 0.1067 -0.0689035 0.10675 -0.068858 0.1068 -0.0688061 0.10685 -0.0687487 0.1069 -0.0686866 0.10695 -0.0686203 0.107 -0.0685505 0.10705 -0.0684775 0.1071 -0.0684017 0.10715 -0.0683236 0.1072 -0.0682432 0.10725 -0.068161 0.1073 -0.068077 0.10735 -0.0679916 0.1074 -0.0679048 0.10745 -0.0678167 0.1075 -0.0677275 0.10755 -0.0676373 0.1076 -0.0675462 0.10765 -0.0674543 0.1077 -0.0673615 0.10775 -0.067268 0.1078 -0.0671739 0.10785 -0.0670791 0.1079 -0.0669838 0.10795 -0.0668879 0.108 -0.0667915 0.10805 -0.0666946 0.1081 -0.0665973 0.10815 -0.0664996 0.1082 -0.0664014 0.10825 -0.0663029 0.1083 -0.066204 0.10835 -0.0661047 0.1084 -0.0660052 0.10845 -0.0659053 0.1085 -0.0658051 0.10855 -0.0657047 0.1086 -0.065604 0.10865 -0.065503 0.1087 -0.0654018 0.10875 -0.0653004 0.1088 -0.0651988 0.10885 -0.065097 0.1089 -0.0649949 0.10895 -0.0648928 0.109 -0.0647904 0.10905 -0.0646879 0.1091 -0.0645853 0.10915 -0.0644825 0.1092 -0.0643796 0.10925 -0.0642766 0.1093 -0.0641735 0.10935 -0.0640703 0.1094 -0.063967 0.10945 -0.0638637 0.1095 -0.0637603 0.10955 -0.0636568 0.1096 -0.0635533 0.10965 -0.0634498 0.1097 -0.0633462 0.10975 -0.0632426 0.1098 -0.0631391 0.10985 -0.0630355 0.1099 -0.0629319 0.10995 -0.0628283 0.11 -0.0627248 0.11005 -0.0626213 0.1101 -0.0625178 0.11015 -0.0624143 0.1102 -0.062311 0.11025 -0.0622076 0.1103 -0.0621044 0.11035 -0.0620012 0.1104 -0.0618981 0.11045 -0.0617951 0.1105 -0.0616921 0.11055 -0.0615893 0.1106 -0.0614865 0.11065 -0.0613839 0.1107 -0.0612813 0.11075 -0.0611789 0.1108 -0.0610766 0.11085 -0.0609744 0.1109 -0.0608724 0.11095 -0.0607704 0.111 -0.0606686 0.11105 -0.060567 0.1111 -0.0604654 0.11115 -0.0603641 0.1112 -0.0602628 0.11125 -0.0601617 0.1113 -0.0600607 0.11135 -0.0599599 0.1114 -0.0598593 0.11145 -0.0597587 0.1115 -0.0596584 0.11155 -0.0595581 0.1116 -0.059458 0.11165 -0.0593581 0.1117 -0.0592583 0.11175 -0.0591587 0.1118 -0.0590592 0.11185 -0.0589598 0.1119 -0.0588606 0.11195 -0.0587615 0.112 -0.0586626 0.11205 -0.0585637 0.1121 -0.058465 0.11215 -0.0583665 0.1122 -0.058268 0.11225 -0.0581697 0.1123 -0.0580714 0.11235 -0.0579733 0.1124 -0.0578753 0.11245 -0.0577773 0.1125 -0.0576795 0.11255 -0.0575817 0.1126 -0.057484 0.11265 -0.0573864 0.1127 -0.0572888 0.11275 -0.0571913 0.1128 -0.0570938 0.11285 -0.0569963 0.1129 -0.0568989 0.11295 -0.0568014 0.113 -0.056704 0.11305 -0.0566065 0.1131 -0.0565091 0.11315 -0.0564115 0.1132 -0.056314 0.11325 -0.0562163 0.1133 -0.0561186 0.11335 -0.0560208 0.1134 -0.0559229 0.11345 -0.0558249 0.1135 -0.0557267 0.11355 -0.0556283 0.1136 -0.0555298 0.11365 -0.0554311 0.1137 -0.0553321 0.11375 -0.0552329 0.1138 -0.0551334 0.11385 -0.0550337 0.1139 -0.0549336 0.11395 -0.0548332 0.114 -0.0547324 0.11405 -0.0546312 0.1141 -0.0545296 0.11415 -0.0544275 0.1142 -0.054325 0.11425 -0.0542218 0.1143 -0.0541182 0.11435 -0.0540139 0.1144 -0.0539089 0.11445 -0.0538032 0.1145 -0.0536968 0.11455 -0.0535896 0.1146 -0.0534816 0.11465 -0.0533726 0.1147 -0.0532626 0.11475 -0.0531516 0.1148 -0.0530395 0.11485 -0.0529262 0.1149 -0.0528117 0.11495 -0.0526957 0.115 -0.0525784 0.11505 -0.0524595 0.1151 -0.0523389 0.11515 -0.0522166 0.1152 -0.0520924 0.11525 -0.0519661 0.1153 -0.0518377 0.11535 -0.0517069 0.1154 -0.0515737 0.11545 -0.0514377 0.1155 -0.0512989 0.11555 -0.0511569 0.1156 -0.0510116 0.11565 -0.0508626 0.1157 -0.0507097 0.11575 -0.0505525 0.1158 -0.0503907 0.11585 -0.050224 0.1159 -0.0500517 0.11595 -0.0498735 0.116 -0.0496888 0.11605 -0.049497 0.1161 -0.0492974 0.11615 -0.0490892 0.1162 -0.0488715 0.11625 -0.0486434 0.1163 -0.0484037 0.11635 -0.0481511 0.1164 -0.0478842 0.11645 -0.0476012 0.1165 -0.0473002 0.11655 -0.0469789 0.1166 -0.0466347 0.11665 -0.0462644 0.1167 -0.0458643 0.11675 -0.0454302 0.1168 -0.0449569 0.11685 -0.0444382 0.1169 -0.0438667 0.11695 -0.0432333 0.117 -0.042527 0.11705 -0.0417343 0.1171 -0.0408386 0.11715 -0.0398192 0.1172 -0.0386503 0.11725 -0.0372997 0.1173 -0.0357271 0.11735 -0.0338826 0.1174 -0.0317045 0.11745 -0.0291188 0.1175 -0.0260403 0.11755 -0.0223789 0.1176 -0.0180546 0.11765 -0.0130278 0.1177 -0.00734791 0.11775 -0.00121018 0.1178 0.00501141 0.11785 0.0108067 0.1179 0.0156636 0.11795 0.0192302 0.118 0.0213992 0.11805 0.0222774 0.1181 0.0220903 0.11815 0.0210898 0.1182 0.0195017 0.11825 0.0175057 0.1183 0.0152372 0.11835 0.0127946 0.1184 0.0102486 0.11845 0.00765003 0.1185 0.00503506 0.11855 0.00242962 0.1186 -0.000147909 0.11865 -0.00268462 0.1187 -0.00517168 0.11875 -0.00760327 0.1188 -0.00997587 0.11885 -0.0122877 0.1189 -0.0145381 0.11895 -0.0167278 0.119 -0.0188582 0.11905 -0.0209313 0.1191 -0.0229501 0.11915 -0.024918 0.1192 -0.0268392 0.11925 -0.0287186 0.1193 -0.0305619 0.11935 -0.0323753 0.1194 -0.0341658 0.11945 -0.0359409 0.1195 -0.0377086 0.11955 -0.0394767 0.1196 -0.0412531 0.11965 -0.0430446 0.1197 -0.0448566 0.11975 -0.046692 0.1198 -0.0485503 0.11985 -0.0504258 0.1199 -0.0523072 0.11995 -0.0541765 0.12 -0.0560089 0.12005 -0.0577749 0.1201 -0.0594429 0.12015 -0.0609829 0.1202 -0.0623714 0.12025 -0.0635942 0.1203 -0.0646477 0.12035 -0.065538 0.1204 -0.0662782 0.12045 -0.0668856 0.1205 -0.0673789 0.12055 -0.067776 0.1206 -0.0680931 0.12065 -0.0683439 0.1207 -0.0685401 0.12075 -0.0686911 0.1208 -0.0688049 0.12085 -0.0688876 0.1209 -0.0689444 0.12095 -0.0689795 0.121 -0.0689964 0.12105 -0.0689977 0.1211 -0.068986 0.12115 -0.0689631 0.1212 -0.0689306 0.12125 -0.06889 0.1213 -0.0688424 0.12135 -0.0687887 0.1214 -0.0687298 0.12145 -0.0686663 0.1215 -0.0685989 0.12155 -0.068528 0.1216 -0.0684541 0.12165 -0.0683776 0.1217 -0.0682987 0.12175 -0.0682178 0.1218 -0.068135 0.12185 -0.0680505 0.1219 -0.0679646 0.12195 -0.0678774 0.122 -0.067789 0.12205 -0.0676995 0.1221 -0.067609 0.12215 -0.0675176 0.1222 -0.0674254 0.12225 -0.0673324 0.1223 -0.0672387 0.12235 -0.0671444 0.1224 -0.0670494 0.12245 -0.0669539 0.1225 -0.0668579 0.12255 -0.0667613 0.1226 -0.0666643 0.12265 -0.0665668 0.1227 -0.066469 0.12275 -0.0663707 0.1228 -0.066272 0.12285 -0.066173 0.1229 -0.0660737 0.12295 -0.065974 0.123 -0.0658741 0.12305 -0.0657738 0.1231 -0.0656733 0.12315 -0.0655725 0.1232 -0.0654715 0.12325 -0.0653702 0.1233 -0.0652687 0.12335 -0.065167 0.1234 -0.0650651 0.12345 -0.0649631 0.1235 -0.0648608 0.12355 -0.0647584 0.1236 -0.0646559 0.12365 -0.0645532 0.1237 -0.0644504 0.12375 -0.0643475 0.1238 -0.0642444 0.12385 -0.0641413 0.1239 -0.0640381 0.12395 -0.0639348 0.124 -0.0638314 0.12405 -0.063728 0.1241 -0.0636245 0.12415 -0.063521 0.1242 -0.0634175 0.12425 -0.0633139 0.1243 -0.0632103 0.12435 -0.0631067 0.1244 -0.0630031 0.12445 -0.0628996 0.1245 -0.062796 0.12455 -0.0626925 0.1246 -0.062589 0.12465 -0.0624855 0.1247 -0.0623821 0.12475 -0.0622787 0.1248 -0.0621754 0.12485 -0.0620722 0.1249 -0.061969 0.12495 -0.0618659 0.125 -0.0617629 0.12505 -0.06166 0.1251 -0.0615572 0.12515 -0.0614545 0.1252 -0.0613519 0.12525 -0.0612494 0.1253 -0.061147 0.12535 -0.0610447 0.1254 -0.0609426 0.12545 -0.0608405 0.1255 -0.0607387 0.12555 -0.0606369 0.1256 -0.0605353 0.12565 -0.0604338 0.1257 -0.0603324 0.12575 -0.0602312 0.1258 -0.0601302 0.12585 -0.0600293 0.1259 -0.0599285 0.12595 -0.0598279 0.126 -0.0597274 0.12605 -0.0596271 0.1261 -0.0595269 0.12615 -0.0594268 0.1262 -0.059327 0.12625 -0.0592272 0.1263 -0.0591276 0.12635 -0.0590281 0.1264 -0.0589288 0.12645 -0.0588297 0.1265 -0.0587306 0.12655 -0.0586317 0.1266 -0.0585329 0.12665 -0.0584343 0.1267 -0.0583357 0.12675 -0.0582373 0.1268 -0.058139 0.12685 -0.0580408 0.1269 -0.0579427 0.12695 -0.0578447 0.127 -0.0577468 0.12705 -0.057649 0.1271 -0.0575512 0.12715 -0.0574535 0.1272 -0.0573559 0.12725 -0.0572584 0.1273 -0.0571608 0.12735 -0.0570634 0.1274 -0.0569659 0.12745 -0.0568685 0.1275 -0.056771 0.12755 -0.0566736 0.1276 -0.0565761 0.12765 -0.0564786 0.1277 -0.0563811 0.12775 -0.0562835 0.1278 -0.0561859 0.12785 -0.0560881 0.1279 -0.0559903 0.12795 -0.0558923 0.128 -0.0557942 0.12805 -0.055696 0.1281 -0.0555976 0.12815 -0.055499 0.1282 -0.0554002 0.12825 -0.0553012 0.1283 -0.0552019 0.12835 -0.0551023 0.1284 -0.0550025 0.12845 -0.0549023 0.1285 -0.0548018 0.12855 -0.0547009 0.1286 -0.0545996 0.12865 -0.0544978 0.1287 -0.0543956 0.12875 -0.0542928 0.1288 -0.0541895 0.12885 -0.0540857 0.1289 -0.0539812 0.12895 -0.053876 0.129 -0.0537701 0.12905 -0.0536635 0.1291 -0.053556 0.12915 -0.0534476 0.1292 -0.0533384 0.12925 -0.0532281 0.1293 -0.0531168 0.12935 -0.0530043 0.1294 -0.0528906 0.12945 -0.0527756 0.1295 -0.0526593 0.12955 -0.0525414 0.1296 -0.052422 0.12965 -0.0523009 0.1297 -0.052178 0.12975 -0.0520532 0.1298 -0.0519263 0.12985 -0.0517971 0.1299 -0.0516656 0.12995 -0.0515315 0.13 -0.0513947 0.13005 -0.0512549 0.1301 -0.0511119 0.13015 -0.0509655 0.1302 -0.0508153 0.13025 -0.0506611 0.1303 -0.0505025 0.13035 -0.0503392 0.1304 -0.0501708 0.13045 -0.0499968 0.1305 -0.0498166 0.13055 -0.0496297 0.1306 -0.0494356 0.13065 -0.0492333 0.1307 -0.0490223 0.13075 -0.0488015 0.1308 -0.0485699 0.13085 -0.0483263 0.1309 -0.0480694 0.13095 -0.0477977 0.131 -0.0475093 0.13105 -0.0472022 0.1311 -0.0468741 0.13115 -0.046522 0.1312 -0.0461429 0.13125 -0.0457327 0.1313 -0.045287 0.13135 -0.0448002 0.1314 -0.0442659 0.13145 -0.0436761 0.1315 -0.0430212 0.13155 -0.0422895 0.1316 -0.0414667 0.13165 -0.0405348 0.1317 -0.0394718 0.13175 -0.03825 0.1318 -0.0368348 0.13185 -0.0351833 0.1319 -0.0332418 0.13195 -0.030945 0.132 -0.0282151 0.13205 -0.0249643 0.1321 -0.0211034 0.13215 -0.016561 0.1322 -0.0113194 0.13225 -0.0054674 0.1323 0.000744799 0.13235 0.00689151 0.1324 0.0124443 0.13245 0.0169254 0.1325 0.0200556 0.13255 0.0218026 0.1326 0.0223214 0.13265 0.0218539 0.1327 0.0206479 0.13275 0.0189153 0.1328 0.0168217 0.13285 0.0144897 0.1329 0.0120083 0.13295 0.00944129 0.133 0.00683424 0.13305 0.00421986 0.1331 0.00162145 0.13315 -0.000944509 0.1332 -0.00346651 0.13325 -0.00593676 0.1333 -0.00835024 0.13335 -0.010704 0.1334 -0.0129966 0.13345 -0.015228 0.1335 -0.017399 0.13355 -0.0195113 0.1336 -0.0215671 0.13365 -0.0235696 0.1337 -0.0255224 0.13375 -0.02743 0.1338 -0.0292975 0.13385 -0.0311308 0.1339 -0.0329363 0.13395 -0.0347211 0.134 -0.0364931 0.13405 -0.03826 0.1341 -0.0400299 0.13415 -0.0418103 0.1342 -0.0436077 0.13425 -0.0454269 0.1343 -0.0472697 0.13435 -0.0491342 0.1344 -0.0510131 0.13445 -0.052893 0.1345 -0.0547536 0.13455 -0.0565687 0.1346 -0.0583075 0.13465 -0.0599385 0.1347 -0.0614334 0.13475 -0.0627712 0.1348 -0.0639411 0.13485 -0.0649426 0.1349 -0.0657843 0.13495 -0.0664811 0.135 -0.067051 0.13505 -0.0675124 0.1351 -0.0678829 0.13515 -0.0681779 0.1352 -0.0684105 0.13525 -0.0685917 0.1353 -0.0687303 0.13535 -0.0688338 0.1354 -0.0689079 0.13545 -0.0689575 0.1355 -0.0689866 0.13555 -0.0689983 0.1356 -0.0689954 0.13565 -0.06898 0.1357 -0.0689539 0.13575 -0.0689188 0.1358 -0.0688759 0.13585 -0.0688263 0.1359 -0.0687708 0.13595 -0.0687104 0.136 -0.0686457 0.13605 -0.0685771 0.1361 -0.0685053 0.13615 -0.0684305 0.1362 -0.0683532 0.13625 -0.0682737 0.1363 -0.0681921 0.13635 -0.0681088 0.1364 -0.0680239 0.13645 -0.0679375 0.1365 -0.0678499 0.13655 -0.0677612 0.1366 -0.0676713 0.13665 -0.0675806 0.1367 -0.0674889 0.13675 -0.0673964 0.1368 -0.0673032 0.13685 -0.0672093 0.1369 -0.0671148 0.13695 -0.0670197 0.137 -0.066924 0.13705 -0.0668278 0.1371 -0.0667311 0.13715 -0.0666339 0.1372 -0.0665363 0.13725 -0.0664383 0.1373 -0.0663399 0.13735 -0.0662412 0.1374 -0.0661421 0.13745 -0.0660426 0.1375 -0.0659428 0.13755 -0.0658428 0.1376 -0.0657424 0.13765 -0.0656418 0.1377 -0.065541 0.13775 -0.0654399 0.1378 -0.0653385 0.13785 -0.065237 0.1379 -0.0651352 0.13795 -0.0650333 0.138 -0.0649312 0.13805 -0.0648289 0.1381 -0.0647265 0.13815 -0.0646239 0.1382 -0.0645211 0.13825 -0.0644183 0.1383 -0.0643153 0.13835 -0.0642123 0.1384 -0.0641091 0.13845 -0.0640059 0.1385 -0.0639025 0.13855 -0.0637992 0.1386 -0.0636957 0.13865 -0.0635922 0.1387 -0.0634887 0.13875 -0.0633851 0.1388 -0.0632816 0.13885 -0.063178 0.1389 -0.0630744 0.13895 -0.0629708 0.139 -0.0628672 0.13905 -0.0627637 0.1391 -0.0626601 0.13915 -0.0625567 0.1392 -0.0624532 0.13925 -0.0623498 0.1393 -0.0622465 0.13935 -0.0621432 0.1394 -0.06204 0.13945 -0.0619368 0.1395 -0.0618338 0.13955 -0.0617308 0.1396 -0.0616279 0.13965 -0.0615251 0.1397 -0.0614224 0.13975 -0.0613199 0.1398 -0.0612174 0.13985 -0.061115 0.1399 -0.0610128 0.13995 -0.0609107 0.14 -0.0608087 0.14005 -0.0607069 0.1401 -0.0606052 0.14015 -0.0605036 0.1402 -0.0604021 0.14025 -0.0603008 0.1403 -0.0601997 0.14035 -0.0600987 0.1404 -0.0599978 0.14045 -0.0598971 0.1405 -0.0597965 0.14055 -0.0596961 0.1406 -0.0595958 0.14065 -0.0594956 0.1407 -0.0593956 0.14075 -0.0592958 0.1408 -0.0591961 0.14085 -0.0590966 0.1409 -0.0589971 0.14095 -0.0588979 0.141 -0.0587987 0.14105 -0.0586997 0.1411 -0.0586009 0.14115 -0.0585021 0.1412 -0.0584035 0.14125 -0.058305 0.1413 -0.0582066 0.14135 -0.0581083 0.1414 -0.0580102 0.14145 -0.0579121 0.1415 -0.0578141 0.14155 -0.0577163 0.1416 -0.0576185 0.14165 -0.0575207 0.1417 -0.0574231 0.14175 -0.0573255 0.1418 -0.0572279 0.14185 -0.0571304 0.1419 -0.0570329 0.14195 -0.0569355 0.142 -0.056838 0.14205 -0.0567406 0.1421 -0.0566432 0.14215 -0.0565457 0.1422 -0.0564482 0.14225 -0.0563506 0.1423 -0.056253 0.14235 -0.0561554 0.1424 -0.0560576 0.14245 -0.0559597 0.1425 -0.0558617 0.14255 -0.0557636 0.1426 -0.0556653 0.14265 -0.0555668 0.1427 -0.0554682 0.14275 -0.0553693 0.1428 -0.0552702 0.14285 -0.0551709 0.1429 -0.0550712 0.14295 -0.0549713 0.143 -0.054871 0.14305 -0.0547703 0.1431 -0.0546693 0.14315 -0.0545679 0.1432 -0.054466 0.14325 -0.0543636 0.1433 -0.0542607 0.14335 -0.0541572 0.1434 -0.0540531 0.14345 -0.0539484 0.1435 -0.053843 0.14355 -0.0537369 0.1436 -0.05363 0.14365 -0.0535223 0.1437 -0.0534136 0.14375 -0.0533041 0.1438 -0.0531935 0.14385 -0.0530818 0.1439 -0.0529689 0.14395 -0.0528549 0.144 -0.0527395 0.14405 -0.0526227 0.1441 -0.0525043 0.14415 -0.0523844 0.1442 -0.0522628 0.14425 -0.0521393 0.1443 -0.0520138 0.14435 -0.0518862 0.1444 -0.0517564 0.14445 -0.0516241 0.1445 -0.0514891 0.14455 -0.0513514 0.1446 -0.0512106 0.14465 -0.0510666 0.1447 -0.050919 0.14475 -0.0507676 0.1448 -0.0506121 0.14485 -0.0504521 0.1449 -0.0502872 0.14495 -0.0501171 0.145 -0.0499412 0.14505 -0.049759 0.1451 -0.0495699 0.14515 -0.0493733 0.1452 -0.0491685 0.14525 -0.0489545 0.1453 -0.0487304 0.14535 -0.0484952 0.1454 -0.0482476 0.14545 -0.0479863 0.1455 -0.0477095 0.14555 -0.0474156 0.1456 -0.0471022 0.14565 -0.0467669 0.1457 -0.0464068 0.14575 -0.0460184 0.1458 -0.0455977 0.14585 -0.0451398 0.1459 -0.0446389 0.14595 -0.0440882 0.146 -0.0434792 0.14605 -0.0428017 0.1461 -0.0420433 0.14615 -0.0411885 0.1462 -0.0402183 0.14625 -0.0391089 0.1463 -0.0378309 0.14635 -0.036347 0.1464 -0.0346113 0.14645 -0.0325665 0.1465 -0.0301435 0.14655 -0.0272607 0.1466 -0.0238287 0.14665 -0.0197612 0.1467 -0.0149986 0.14675 -0.00955029 0.1468 -0.00355042 0.14685 0.00269424 0.1469 0.00871338 0.14695 0.0139759 0.147 0.0180536 0.14705 0.0207449 0.1471 0.0220859 0.14715 0.0222693 0.1472 0.0215459 0.14725 0.0201549 0.1473 0.0182941 0.14735 0.0161147 0.1474 0.0137279 0.14745 0.0112139 0.1475 0.00863014 0.14755 0.0060178 0.1476 0.00340627 0.14765 0.000816476 0.1477 -0.0017368 0.14775 -0.00424334 0.1478 -0.0066963 0.14785 -0.00909137 0.1479 -0.0114261 0.14795 -0.0136996 0.148 -0.0159121 0.14805 -0.0180645 0.1481 -0.0201588 0.14815 -0.0221977 0.1482 -0.0241842 0.14825 -0.0261224 0.1483 -0.028017 0.14835 -0.0298731 0.1484 -0.0316969 0.14845 -0.0334952 0.1485 -0.0352752 0.14855 -0.0370447 0.1486 -0.0388117 0.14865 -0.0405841 0.1487 -0.0423692 0.14875 -0.044173 0.1488 -0.0459995 0.14885 -0.0478495 0.1489 -0.0497196 0.14895 -0.0516006 0.149 -0.0534769 0.14905 -0.0553264 0.1491 -0.0571211 0.14915 -0.0588296 0.1492 -0.0604209 0.14925 -0.0618685 0.1493 -0.0631546 0.14935 -0.0642715 0.1494 -0.0652218 0.14945 -0.0660166 0.1495 -0.0666717 0.14955 -0.0672058 0.1496 -0.067637 0.14965 -0.0679824 0.1497 -0.0682566 0.14975 -0.0684721 0.1498 -0.0686391 0.14985 -0.0687661 0.1499 -0.0688598 0.14995 -0.0689258 0.15 -0.0689687 0.15005 -0.068992 0.1501 -0.0689989 0.15015 -0.0689918 0.1502 -0.0689729 0.15025 -0.0689439 0.1503 -0.0689062 0.15035 -0.0688611 0.1504 -0.0688095 0.15045 -0.0687525 0.1505 -0.0686907 0.15055 -0.0686247 0.1506 -0.068555 0.15065 -0.0684822 0.1507 -0.0684066 0.15075 -0.0683286 0.1508 -0.0682484 0.15085 -0.0681663 0.1509 -0.0680824 0.15095 -0.0679971 0.151 -0.0679103 0.15105 -0.0678223 0.1511 -0.0677332 0.15115 -0.0676431 0.1512 -0.067552 0.15125 -0.0674601 0.1513 -0.0673674 0.15135 -0.067274 0.1514 -0.0671799 0.15145 -0.0670852 0.1515 -0.0669899 0.15155 -0.066894 0.1516 -0.0667977 0.15165 -0.0667008 0.1517 -0.0666035 0.15175 -0.0665058 0.1518 -0.0664077 0.15185 -0.0663091 0.1519 -0.0662103 0.15195 -0.0661111 0.152 -0.0660115 0.15205 -0.0659117 0.1521 -0.0658115 0.15215 -0.0657111 0.1522 -0.0656104 0.15225 -0.0655094 0.1523 -0.0654083 0.15235 -0.0653069 0.1524 -0.0652053 0.15245 -0.0651034 0.1525 -0.0650014 0.15255 -0.0648993 0.1526 -0.0647969 0.15265 -0.0646944 0.1527 -0.0645918 0.15275 -0.064489 0.1528 -0.0643862 0.15285 -0.0642832 0.1529 -0.0641801 0.15295 -0.0640769 0.153 -0.0639736 0.15305 -0.0638703 0.1531 -0.0637669 0.15315 -0.0636634 0.1532 -0.0635599 0.15325 -0.0634564 0.1533 -0.0633528 0.15335 -0.0632492 0.1534 -0.0631457 0.15345 -0.0630421 0.1535 -0.0629385 0.15355 -0.0628349 0.1536 -0.0627314 0.15365 -0.0626278 0.1537 -0.0625244 0.15375 -0.0624209 0.1538 -0.0623175 0.15385 -0.0622142 0.1539 -0.062111 0.15395 -0.0620078 0.154 -0.0619046 0.15405 -0.0618016 0.1541 -0.0616987 0.15415 -0.0615958 0.1542 -0.0614931 0.15425 -0.0613904 0.1543 -0.0612879 0.15435 -0.0611854 0.1544 -0.0610831 0.15445 -0.0609809 0.1545 -0.0608789 0.15455 -0.0607769 0.1546 -0.0606751 0.15465 -0.0605734 0.1547 -0.0604719 0.15475 -0.0603705 0.1548 -0.0602693 0.15485 -0.0601681 0.1549 -0.0600672 0.15495 -0.0599663 0.155 -0.0598657 0.15505 -0.0597651 0.1551 -0.0596647 0.15515 -0.0595645 0.1552 -0.0594644 0.15525 -0.0593645 0.1553 -0.0592647 0.15535 -0.059165 0.1554 -0.0590655 0.15545 -0.0589661 0.1555 -0.0588669 0.15555 -0.0587678 0.1556 -0.0586688 0.15565 -0.05857 0.1557 -0.0584713 0.15575 -0.0583727 0.1558 -0.0582743 0.15585 -0.0581759 0.1559 -0.0580777 0.15595 -0.0579796 0.156 -0.0578815 0.15605 -0.0577836 0.1561 -0.0576857 0.15615 -0.0575879 0.1562 -0.0574902 0.15625 -0.0573926 0.1563 -0.057295 0.15635 -0.0571975 0.1564 -0.0571 0.15645 -0.0570025 0.1565 -0.0569051 0.15655 -0.0568076 0.1566 -0.0567102 0.15665 -0.0566127 0.1567 -0.0565153 0.15675 -0.0564178 0.1568 -0.0563202 0.15685 -0.0562226 0.1569 -0.0561249 0.15695 -0.0560271 0.157 -0.0559291 0.15705 -0.0558311 0.1571 -0.0557329 0.15715 -0.0556346 0.1572 -0.0555361 0.15725 -0.0554374 0.1573 -0.0553384 0.15735 -0.0552392 0.1574 -0.0551398 0.15745 -0.0550401 0.1575 -0.05494 0.15755 -0.0548396 0.1576 -0.0547388 0.15765 -0.0546377 0.1577 -0.0545361 0.15775 -0.054434 0.1578 -0.0543315 0.15785 -0.0542284 0.1579 -0.0541248 0.15795 -0.0540205 0.158 -0.0539156 0.15805 -0.05381 0.1581 -0.0537036 0.15815 -0.0535965 0.1582 -0.0534885 0.15825 -0.0533795 0.1583 -0.0532697 0.15835 -0.0531587 0.1584 -0.0530467 0.15845 -0.0529335 0.1585 -0.052819 0.15855 -0.0527032 0.1586 -0.0525859 0.15865 -0.0524671 0.1587 -0.0523466 0.15875 -0.0522244 0.1588 -0.0521003 0.15885 -0.0519742 0.1589 -0.0518459 0.15895 -0.0517153 0.159 -0.0515822 0.15905 -0.0514465 0.1591 -0.0513078 0.15915 -0.051166 0.1592 -0.0510209 0.15925 -0.0508722 0.1593 -0.0507195 0.15935 -0.0505627 0.1594 -0.0504012 0.15945 -0.0502347 0.1595 -0.0500628 0.15955 -0.049885 0.1596 -0.0497008 0.15965 -0.0495094 0.1597 -0.0493103 0.15975 -0.0491027 0.1598 -0.0488857 0.15985 -0.0486582 0.1599 -0.0484193 0.15995 -0.0481676 0.16 -0.0479016 0.16005 -0.0476197 0.1601 -0.0473199 0.16015 -0.047 0.1602 -0.0466573 0.16025 -0.0462888 0.1603 -0.0458908 0.16035 -0.045459 0.1604 -0.0449884 0.16045 -0.0444727 0.1605 -0.0439048 0.16055 -0.0432756 0.1606 -0.0425743 0.16065 -0.0417876 0.1607 -0.0408991 0.16075 -0.0398882 0.1608 -0.0387297 0.16085 -0.0373918 0.1609 -0.0358348 0.16095 -0.0340093 0.161 -0.0318545 0.16105 -0.0292972 0.1611 -0.0262528 0.16115 -0.0226311 0.1612 -0.0183507 0.16125 -0.0133685 0.1613 -0.00772615 0.16135 -0.0016083 0.1614 0.00462223 0.16145 0.0104608 0.1615 0.0153903 0.16155 0.0190449 0.1616 0.0213015 0.16165 0.022256 0.1617 0.022129 0.16175 0.0211732 0.1618 0.0196166 0.16185 0.0176422 0.1619 0.0153877 0.16195 0.0129538 0.162 0.0104127 0.16205 0.00781629 0.1621 0.0052015 0.16215 0.00259483 0.1622 1.50943e-05 0.16225 -0.00252452 0.1623 -0.00501494 0.16235 -0.00745019 0.1624 -0.00982661 0.16245 -0.0121423 0.1625 -0.0143967 0.16255 -0.0165902 0.1626 -0.0187243 0.16265 -0.020801 0.1627 -0.0228231 0.16275 -0.0247941 0.1628 -0.0267182 0.16285 -0.0286001 0.1629 -0.0304455 0.16295 -0.0322606 0.163 -0.0340524 0.16305 -0.0358282 0.1631 -0.0375961 0.16315 -0.039364 0.1632 -0.0411396 0.16325 -0.0429299 0.1633 -0.0447405 0.16335 -0.0465745 0.1634 -0.0484314 0.16345 -0.0503061 0.1635 -0.0521876 0.16355 -0.0540583 0.1636 -0.0558939 0.16365 -0.0576651 0.1637 -0.0593402 0.16375 -0.0608891 0.1638 -0.0622879 0.16385 -0.0635214 0.1639 -0.0645856 0.16395 -0.0654859 0.164 -0.0662352 0.16405 -0.0668505 0.1641 -0.0673506 0.16415 -0.0677533 0.1642 -0.068075 0.16425 -0.0683296 0.1643 -0.068529 0.16435 -0.0686827 0.1644 -0.0687986 0.16445 -0.0688832 0.1645 -0.0689415 0.16455 -0.0689779 0.1646 -0.0689958 0.16465 -0.0689981 0.1647 -0.0689871 0.16475 -0.0689648 0.1648 -0.068933 0.16485 -0.0688928 0.1649 -0.0688456 0.16495 -0.0687923 0.165 -0.0687337 0.16505 -0.0686705 0.1651 -0.0686033 0.16515 -0.0685326 0.1652 -0.0684589 0.16525 -0.0683825 0.1653 -0.0683038 0.16535 -0.068223 0.1654 -0.0681403 0.16545 -0.0680559 0.1655 -0.0679701 0.16555 -0.067883 0.1656 -0.0677946 0.16565 -0.0677052 0.1657 -0.0676148 0.16575 -0.0675234 0.1658 -0.0674313 0.16585 -0.0673383 0.1659 -0.0672447 0.16595 -0.0671504 0.166 -0.0670555 0.16605 -0.06696 0.1661 -0.066864 0.16615 -0.0667675 0.1662 -0.0666705 0.16625 -0.0665731 0.1663 -0.0664752 0.16635 -0.066377 0.1664 -0.0662783 0.16645 -0.0661793 0.1665 -0.06608 0.16655 -0.0659804 0.1666 -0.0658804 0.16665 -0.0657802 0.1667 -0.0656797 0.16675 -0.0655789 0.1668 -0.0654779 0.16685 -0.0653766 0.1669 -0.0652752 0.16695 -0.0651735 0.167 -0.0650716 0.16705 -0.0649696 0.1671 -0.0648674 0.16715 -0.064765 0.1672 -0.0646624 0.16725 -0.0645598 0.1673 -0.064457 0.16735 -0.064354 0.1674 -0.064251 0.16745 -0.0641479 0.1675 -0.0640447 0.16755 -0.0639414 0.1676 -0.063838 0.16765 -0.0637346 0.1677 -0.0636311 0.16775 -0.0635276 0.1678 -0.0634241 0.16785 -0.0633205 0.1679 -0.0632169 0.16795 -0.0631133 0.168 -0.0630097 0.16805 -0.0629061 0.1681 -0.0628026 0.16815 -0.062699 0.1682 -0.0625955 0.16825 -0.0624921 0.1683 -0.0623887 0.16835 -0.0622853 0.1684 -0.062182 0.16845 -0.0620787 0.1685 -0.0619756 0.16855 -0.0618725 0.1686 -0.0617695 0.16865 -0.0616666 0.1687 -0.0615637 0.16875 -0.061461 0.1688 -0.0613584 0.16885 -0.0612559 0.1689 -0.0611535 0.16895 -0.0610512 0.169 -0.0609491 0.16905 -0.060847 0.1691 -0.0607451 0.16915 -0.0606434 0.1692 -0.0605417 0.16925 -0.0604402 0.1693 -0.0603389 0.16935 -0.0602377 0.1694 -0.0601366 0.16945 -0.0600357 0.1695 -0.0599349 0.16955 -0.0598343 0.1696 -0.0597338 0.16965 -0.0596334 0.1697 -0.0595333 0.16975 -0.0594332 0.1698 -0.0593333 0.16985 -0.0592336 0.1699 -0.0591339 0.16995 -0.0590345 0.17 -0.0589352 0.17005 -0.058836 0.1701 -0.0587369 0.17015 -0.058638 0.1702 -0.0585392 0.17025 -0.0584405 0.1703 -0.058342 0.17035 -0.0582436 0.1704 -0.0581453 0.17045 -0.058047 0.1705 -0.0579489 0.17055 -0.0578509 0.1706 -0.057753 0.17065 -0.0576552 0.1707 -0.0575574 0.17075 -0.0574598 0.1708 -0.0573621 0.17085 -0.0572646 0.1709 -0.057167 0.17095 -0.0570696 0.171 -0.0569721 0.17105 -0.0568747 0.1711 -0.0567772 0.17115 -0.0566798 0.1712 -0.0565823 0.17125 -0.0564848 0.1713 -0.0563873 0.17135 -0.0562897 0.1714 -0.0561921 0.17145 -0.0560943 0.1715 -0.0559965 0.17155 -0.0558986 0.1716 -0.0558005 0.17165 -0.0557023 0.1717 -0.0556039 0.17175 -0.0555053 0.1718 -0.0554065 0.17185 -0.0553075 0.1719 -0.0552082 0.17195 -0.0551087 0.172 -0.0550089 0.17205 -0.0549087 0.1721 -0.0548082 0.17215 -0.0547073 0.1722 -0.054606 0.17225 -0.0545043 0.1723 -0.0544021 0.17235 -0.0542994 0.1724 -0.0541961 0.17245 -0.0540923 0.1725 -0.0539878 0.17255 -0.0538827 0.1726 -0.0537769 0.17265 -0.0536703 0.1727 -0.0535629 0.17275 -0.0534546 0.1728 -0.0533454 0.17285 -0.0532351 0.1729 -0.0531239 0.17295 -0.0530115 0.173 -0.0528979 0.17305 -0.052783 0.1731 -0.0526667 0.17315 -0.052549 0.1732 -0.0524297 0.17325 -0.0523087 0.1733 -0.0521859 0.17335 -0.0520612 0.1734 -0.0519344 0.17345 -0.0518054 0.1735 -0.0516741 0.17355 -0.0515402 0.1736 -0.0514035 0.17365 -0.0512639 0.1737 -0.0511211 0.17375 -0.0509749 0.1738 -0.050825 0.17385 -0.050671 0.1739 -0.0505128 0.17395 -0.0503498 0.174 -0.0501817 0.17405 -0.050008 0.1741 -0.0498282 0.17415 -0.0496418 0.1742 -0.0494482 0.17425 -0.0492465 0.1743 -0.049036 0.17435 -0.0488158 0.1744 -0.048585 0.17445 -0.0483422 0.1745 -0.0480862 0.17455 -0.0478154 0.1746 -0.0475282 0.17465 -0.0472224 0.1747 -0.0468956 0.17475 -0.0465452 0.1748 -0.0461679 0.17485 -0.0457599 0.1749 -0.0453165 0.17495 -0.0448326 0.175 -0.0443015 0.17505 -0.0437155 0.1751 -0.0430651 0.17515 -0.0423387 0.1752 -0.0415221 0.17525 -0.0405978 0.1753 -0.0395439 0.17535 -0.0383331 0.1754 -0.0369315 0.17545 -0.0352965 0.1755 -0.0333753 0.17555 -0.0311033 0.1756 -0.0284035 0.17565 -0.0251886 0.1757 -0.021369 0.17575 -0.0168713 0.1758 -0.011673 0.17585 -0.00585431 0.1759 0.000345945 0.17595 0.00651217 0.176 0.0121184 0.17605 0.0166787 0.1761 0.0198984 0.17615 0.0217303 0.1762 0.0223205 0.17625 0.0219082 0.1763 0.0207423 0.17635 0.0190379 0.1764 0.0169632 0.17645 0.0146435 0.1765 0.0121695 0.17655 0.00960633 0.1766 0.00700074 0.17665 0.00438605 0.1767 0.00178606 0.17675 -0.000782349 0.1768 -0.00330742 0.17685 -0.00578114 0.1769 -0.00819833 0.17695 -0.0105559 0.177 -0.0128525 0.17705 -0.0150878 0.1771 -0.0172626 0.17715 -0.0193785 0.1772 -0.0214378 0.17725 -0.0234436 0.1773 -0.0253995 0.17735 -0.0273098 0.1774 -0.0291797 0.17745 -0.031015 0.1775 -0.032822 0.17755 -0.034608 0.1776 -0.0363805 0.17765 -0.0381475 0.1777 -0.039917 0.17775 -0.0416965 0.1778 -0.0434927 0.17785 -0.0453103 0.1779 -0.0471517 0.17795 -0.049015 0.178 -0.0508933 0.17805 -0.0527736 0.1781 -0.0546362 0.17815 -0.0564551 0.1782 -0.0581997 0.17825 -0.0598385 0.1783 -0.0613427 0.17835 -0.062691 0.1784 -0.0638717 0.17845 -0.0648837 0.1785 -0.0657352 0.17855 -0.0664407 0.1786 -0.0670181 0.17865 -0.0674859 0.1787 -0.0678617 0.17875 -0.0681611 0.1788 -0.0683973 0.17885 -0.0685815 0.1789 -0.0687226 0.17895 -0.0688281 0.179 -0.068904 0.17905 -0.068955 0.1791 -0.0689853 0.17915 -0.068998 0.1792 -0.068996 0.17925 -0.0689813 0.1793 -0.0689559 0.17935 -0.0689213 0.1794 -0.0688788 0.17945 -0.0688296 0.1795 -0.0687745 0.17955 -0.0687144 0.1796 -0.0686499 0.17965 -0.0685816 0.1797 -0.0685099 0.17975 -0.0684354 0.1798 -0.0683582 0.17985 -0.0682788 0.1799 -0.0681973 0.17995 -0.0681141 0.18 -0.0680293 0.18005 -0.0679431 0.1801 -0.0678555 0.18015 -0.0677668 0.1802 -0.0676771 0.18025 -0.0675864 0.1803 -0.0674948 0.18035 -0.0674024 0.1804 -0.0673092 0.18045 -0.0672153 0.1805 -0.0671209 0.18055 -0.0670258 0.1806 -0.0669301 0.18065 -0.0668339 0.1807 -0.0667373 0.18075 -0.0666401 0.1808 -0.0665426 0.18085 -0.0664446 0.1809 -0.0663462 0.18095 -0.0662475 0.181 -0.0661484 0.18105 -0.066049 0.1811 -0.0659492 0.18115 -0.0658492 0.1812 -0.0657488 0.18125 -0.0656483 0.1813 -0.0655474 0.18135 -0.0654463 0.1814 -0.065345 0.18145 -0.0652435 0.1815 -0.0651417 0.18155 -0.0650398 0.1816 -0.0649377 0.18165 -0.0648354 0.1817 -0.064733 0.18175 -0.0646304 0.1818 -0.0645277 0.18185 -0.0644248 0.1819 -0.0643219 0.18195 -0.0642188 0.182 -0.0641157 0.18205 -0.0640124 0.1821 -0.0639091 0.18215 -0.0638057 0.1822 -0.0637023 0.18225 -0.0635988 0.1823 -0.0634953 0.18235 -0.0633917 0.1824 -0.0632882 0.18245 -0.0631846 0.1825 -0.063081 0.18255 -0.0629774 0.1826 -0.0628738 0.18265 -0.0627703 0.1827 -0.0626667 0.18275 -0.0625632 0.1828 -0.0624598 0.18285 -0.0623564 0.1829 -0.062253 0.18295 -0.0621497 0.183 -0.0620465 0.18305 -0.0619434 0.1831 -0.0618403 0.18315 -0.0617373 0.1832 -0.0616345 0.18325 -0.0615317 0.1833 -0.061429 0.18335 -0.0613264 0.1834 -0.0612239 0.18345 -0.0611216 0.1835 -0.0610193 0.18355 -0.0609172 0.1836 -0.0608152 0.18365 -0.0607134 0.1837 -0.0606116 0.18375 -0.06051 0.1838 -0.0604086 0.18385 -0.0603073 0.1839 -0.0602061 0.18395 -0.0601051 0.184 -0.0600042 0.18405 -0.0599035 0.1841 -0.0598029 0.18415 -0.0597024 0.1842 -0.0596022 0.18425 -0.059502 0.1843 -0.059402 0.18435 -0.0593022 0.1844 -0.0592024 0.18445 -0.0591029 0.1845 -0.0590035 0.18455 -0.0589042 0.1846 -0.058805 0.18465 -0.058706 0.1847 -0.0586071 0.18475 -0.0585084 0.1848 -0.0584098 0.18485 -0.0583113 0.1849 -0.0582129 0.18495 -0.0581146 0.185 -0.0580164 0.18505 -0.0579183 0.1851 -0.0578204 0.18515 -0.0577225 0.1852 -0.0576247 0.18525 -0.0575269 0.1853 -0.0574293 0.18535 -0.0573317 0.1854 -0.0572341 0.18545 -0.0571366 0.1855 -0.0570391 0.18555 -0.0569417 0.1856 -0.0568442 0.18565 -0.0567468 0.1857 -0.0566494 0.18575 -0.0565519 0.1858 -0.0564544 0.18585 -0.0563569 0.1859 -0.0562593 0.18595 -0.0561616 0.186 -0.0560638 0.18605 -0.055966 0.1861 -0.055868 0.18615 -0.0557698 0.1862 -0.0556716 0.18625 -0.0555731 0.1863 -0.0554745 0.18635 -0.0553756 0.1864 -0.0552765 0.18645 -0.0551772 0.1865 -0.0550776 0.18655 -0.0549776 0.1866 -0.0548774 0.18665 -0.0547768 0.1867 -0.0546758 0.18675 -0.0545743 0.1868 -0.0544725 0.18685 -0.0543701 0.1869 -0.0542672 0.18695 -0.0541638 0.187 -0.0540598 0.18705 -0.0539551 0.1871 -0.0538498 0.18715 -0.0537437 0.1872 -0.0536368 0.18725 -0.0535292 0.1873 -0.0534206 0.18735 -0.0533111 0.1874 -0.0532005 0.18745 -0.0530889 0.1875 -0.0529762 0.18755 -0.0528622 0.1876 -0.0527469 0.18765 -0.0526301 0.1877 -0.0525119 0.18775 -0.0523921 0.1878 -0.0522706 0.18785 -0.0521472 0.1879 -0.0520219 0.18795 -0.0518944 0.188 -0.0517647 0.18805 -0.0516326 0.1881 -0.0514978 0.18815 -0.0513603 0.1882 -0.0512197 0.18825 -0.0510759 0.1883 -0.0509285 0.18835 -0.0507774 0.1884 -0.0506221 0.18845 -0.0504624 0.1885 -0.0502979 0.18855 -0.0501281 0.1886 -0.0499526 0.18865 -0.0497708 0.1887 -0.0495822 0.18875 -0.0493861 0.1888 -0.0491818 0.18885 -0.0489684 0.1889 -0.048745 0.18895 -0.0485105 0.189 -0.0482638 0.18905 -0.0480034 0.1891 -0.0477276 0.18915 -0.0474348 0.1892 -0.0471228 0.18925 -0.046789 0.1893 -0.0464305 0.18935 -0.046044 0.1894 -0.0456255 0.18945 -0.0451701 0.1895 -0.0446722 0.18955 -0.0441249 0.1896 -0.0435199 0.18965 -0.0428471 0.1897 -0.0420943 0.18975 -0.0412461 0.1898 -0.0402839 0.18985 -0.0391843 0.1899 -0.0379179 0.18995 -0.0364485 0.19 -0.0347303 0.19005 -0.0327072 0.1901 -0.0303105 0.19015 -0.0274596 0.1902 -0.0240654 0.19025 -0.0200405 0.1903 -0.0153229 0.19035 -0.00991585 0.1904 -0.00394391 0.19045 0.00229778 0.1905 0.00834726 0.19055 0.0136726 0.1906 0.0178345 0.19065 0.0206152 0.1907 0.0220375 0.19075 0.0222874 0.1908 0.0216142 0.19085 0.0202593 0.1909 0.0184234 0.19095 0.0162606 0.191 0.0138844 0.19105 0.0113765 0.1911 0.00879586 0.19115 0.00618436 0.1912 0.00357208 0.19125 0.000980406 0.1913 -0.00157554 0.19135 -0.00408529 0.1914 -0.00654182 0.19145 -0.00894066 0.1915 -0.0112793 0.19155 -0.0135567 0.1916 -0.015773 0.19165 -0.0179292 0.1917 -0.0200272 0.19175 -0.0220695 0.1918 -0.0240592 0.19185 -0.0260004 0.1919 -0.0278975 0.19195 -0.029756 0.192 -0.0315817 0.19205 -0.0333813 0.1921 -0.0351623 0.19215 -0.0369322 0.1922 -0.0386991 0.19225 -0.040471 0.1923 -0.042255 0.19235 -0.0440575 0.1924 -0.0458825 0.19245 -0.0477311 0.1925 -0.0496001 0.19255 -0.0514807 0.1926 -0.053358 0.19265 -0.05521 0.1927 -0.0570091 0.19275 -0.058724 0.1928 -0.0603236 0.19285 -0.061781 0.1929 -0.0630778 0.19295 -0.0642054 0.193 -0.0651661 0.19305 -0.0659703 0.1931 -0.0666338 0.19315 -0.067175 0.1932 -0.0676123 0.19325 -0.0679627 0.1933 -0.068241 0.19335 -0.0684599 0.1934 -0.0686298 0.19345 -0.0687591 0.1935 -0.0688547 0.19355 -0.0689224 0.1936 -0.0689665 0.19365 -0.068991 0.1937 -0.0689989 0.19375 -0.0689926 0.1938 -0.0689744 0.19385 -0.068946 0.1939 -0.0689088 0.19395 -0.0688641 0.194 -0.068813 0.19405 -0.0687563 0.1941 -0.0686947 0.19415 -0.068629 0.1942 -0.0685596 0.19425 -0.068487 0.1943 -0.0684115 0.19435 -0.0683336 0.1944 -0.0682536 0.19445 -0.0681716 0.1945 -0.0680878 0.19455 -0.0680025 0.1946 -0.0679159 0.19465 -0.067828 0.1947 -0.0677389 0.19475 -0.0676489 0.1948 -0.0675579 0.19485 -0.067466 0.1949 -0.0673734 0.19495 -0.06728 0.195 -0.0671859 0.19505 -0.0670912 0.1951 -0.066996 0.19515 -0.0669001 0.1952 -0.0668038 0.19525 -0.066707 0.1953 -0.0666097 0.19535 -0.066512 0.1954 -0.0664139 0.19545 -0.0663154 0.1955 -0.0662166 0.19555 -0.0661174 0.1956 -0.0660179 0.19565 -0.065918 0.1957 -0.0658179 0.19575 -0.0657175 0.1958 -0.0656168 0.19585 -0.0655159 0.1959 -0.0654147 0.19595 -0.0653133 0.196 -0.0652117 0.19605 -0.0651099 0.1961 -0.0650079 0.19615 -0.0649058 0.1962 -0.0648035 0.19625 -0.064701 0.1963 -0.0645984 0.19635 -0.0644956 0.1964 -0.0643927 0.19645 -0.0642897 0.1965 -0.0641866 0.19655 -0.0640835 0.1966 -0.0639802 0.19665 -0.0638769 0.1967 -0.0637735 0.19675 -0.06367 0.1968 -0.0635665 0.19685 -0.063463 0.1969 -0.0633594 0.19695 -0.0632558 0.197 -0.0631522 0.19705 -0.0630487 0.1971 -0.0629451 0.19715 -0.0628415 0.1972 -0.062738 0.19725 -0.0626344 0.1973 -0.062531 0.19735 -0.0624275 0.1974 -0.0623241 0.19745 -0.0622208 0.1975 -0.0621175 0.19755 -0.0620143 0.1976 -0.0619112 0.19765 -0.0618082 0.1977 -0.0617052 0.19775 -0.0616024 0.1978 -0.0614996 0.19785 -0.0613969 0.1979 -0.0612944 0.19795 -0.061192 0.198 -0.0610896 0.19805 -0.0609874 0.1981 -0.0608854 0.19815 -0.0607834 0.1982 -0.0606816 0.19825 -0.0605799 0.1983 -0.0604784 0.19835 -0.060377 0.1984 -0.0602757 0.19845 -0.0601746 0.1985 -0.0600736 0.19855 -0.0599728 0.1986 -0.0598721 0.19865 -0.0597715 0.1987 -0.0596711 0.19875 -0.0595709 0.1988 -0.0594708 0.19885 -0.0593708 0.1989 -0.059271 0.19895 -0.0591714 0.199 -0.0590718 0.19905 -0.0589725 0.1991 -0.0588732 0.19915 -0.0587741 0.1992 -0.0586751 0.19925 -0.0585763 0.1993 -0.0584776 0.19935 -0.058379 0.1994 -0.0582805 0.19945 -0.0581822 0.1995 -0.0580839 0.19955 -0.0579858 0.1996 -0.0578878 0.19965 -0.0577898 0.1997 -0.0576919 0.19975 -0.0575942 0.1998 -0.0574965 0.19985 -0.0573988 0.1999 -0.0573012 0.19995 -0.0572037 0.2 -0.0571062 0.20005 -0.0570087 0.2001 -0.0569113 0.20015 -0.0568138 0.2002 -0.0567164 0.20025 -0.0566189 0.2003 -0.0565215 0.20035 -0.056424 0.2004 -0.0563264 0.20045 -0.0562288 0.2005 -0.0561311 0.20055 -0.0560333 0.2006 -0.0559354 0.20065 -0.0558374 0.2007 -0.0557392 0.20075 -0.0556409 0.2008 -0.0555424 0.20085 -0.0554436 0.2009 -0.0553447 0.20095 -0.0552456 0.201 -0.0551461 0.20105 -0.0550464 0.2011 -0.0549464 0.20115 -0.054846 0.2012 -0.0547453 0.20125 -0.0546441 0.2013 -0.0545426 0.20135 -0.0544406 0.2014 -0.054338 0.20145 -0.054235 0.2015 -0.0541314 0.20155 -0.0540272 0.2016 -0.0539223 0.20165 -0.0538167 0.2017 -0.0537104 0.20175 -0.0536033 0.2018 -0.0534954 0.20185 -0.0533865 0.2019 -0.0532767 0.20195 -0.0531658 0.202 -0.0530539 0.20205 -0.0529407 0.2021 -0.0528263 0.20215 -0.0527106 0.2022 -0.0525934 0.20225 -0.0524747 0.2023 -0.0523544 0.20235 -0.0522323 0.2024 -0.0521083 0.20245 -0.0519823 0.2025 -0.0518542 0.20255 -0.0517237 0.2026 -0.0515908 0.20265 -0.0514552 0.2027 -0.0513167 0.20275 -0.0511752 0.2028 -0.0510303 0.20285 -0.0508818 0.2029 -0.0507294 0.20295 -0.0505728 0.203 -0.0504116 0.20305 -0.0502455 0.2031 -0.050074 0.20315 -0.0498965 0.2032 -0.0497127 0.20325 -0.0495218 0.2033 -0.0493232 0.20335 -0.0491162 0.2034 -0.0488998 0.20345 -0.0486731 0.2035 -0.0484349 0.20355 -0.048184 0.2036 -0.047919 0.20365 -0.0476382 0.2037 -0.0473396 0.20375 -0.047021 0.2038 -0.0466799 0.20385 -0.0463131 0.2039 -0.0459171 0.20395 -0.0454876 0.204 -0.0450196 0.20405 -0.044507 0.2041 -0.0439427 0.20415 -0.0433177 0.2042 -0.0426214 0.20425 -0.0418406 0.2043 -0.040959 0.20435 -0.0399567 0.2044 -0.0388085 0.20445 -0.0374831 0.2045 -0.0359413 0.20455 -0.0341346 0.2046 -0.0320028 0.20465 -0.0294736 0.2047 -0.0264629 0.20475 -0.0228805 0.2048 -0.018644 0.20485 -0.0137064 0.2049 -0.0081024 0.20495 -0.00200591 0.205 0.00423148 0.20505 0.0101111 0.2051 0.0151118 0.20515 0.0188539 0.2052 0.0211985 0.20525 0.0222301 0.2053 0.0221644 0.20535 0.0212542 0.2054 0.0197298 0.20545 0.0177775 0.2055 0.0155375 0.20555 0.0131126 0.2056 0.0105766 0.20565 0.00798248 0.2057 0.00536797 0.20575 0.00276016 0.2058 0.000178262 0.20585 -0.00236421 0.2059 -0.00485797 0.20595 -0.00729687 0.206 -0.00967711 0.20605 -0.0119967 0.2061 -0.014255 0.20615 -0.0164524 0.2062 -0.0185902 0.20625 -0.0206704 0.2063 -0.022696 0.20635 -0.0246701 0.2064 -0.026597 0.20645 -0.0284815 0.2065 -0.030329 0.20655 -0.0321458 0.2066 -0.0339388 0.20665 -0.0357155 0.2067 -0.0374836 0.20675 -0.0392513 0.2068 -0.0410262 0.20685 -0.0428154 0.2069 -0.0446246 0.20695 -0.046457 0.207 -0.0483125 0.20705 -0.0501864 0.2071 -0.0520679 0.20715 -0.0539399 0.2072 -0.0557786 0.20725 -0.0575548 0.2073 -0.059237 0.20735 -0.0607948 0.2074 -0.0622036 0.20745 -0.063448 0.2075 -0.0645228 0.20755 -0.0654333 0.2076 -0.0661917 0.20765 -0.066815 0.2077 -0.0673218 0.20775 -0.0677302 0.2078 -0.0680566 0.20785 -0.0683152 0.2079 -0.0685178 0.20795 -0.0686741 0.208 -0.0687923 0.20805 -0.0688786 0.2081 -0.0689385 0.20815 -0.0689761 0.2082 -0.0689951 0.20825 -0.0689983 0.2083 -0.0689881 0.20835 -0.0689666 0.2084 -0.0689353 0.20845 -0.0688956 0.2085 -0.0688488 0.20855 -0.0687959 0.2086 -0.0687376 0.20865 -0.0686746 0.2087 -0.0686077 0.20875 -0.0685372 0.2088 -0.0684637 0.20885 -0.0683875 0.2089 -0.0683089 0.20895 -0.0682282 0.209 -0.0681456 0.20905 -0.0680614 0.2091 -0.0679756 0.20915 -0.0678886 0.2092 -0.0678003 0.20925 -0.0677109 0.2093 -0.0676206 0.20935 -0.0675293 0.2094 -0.0674372 0.20945 -0.0673443 0.2095 -0.0672507 0.20955 -0.0671564 0.2096 -0.0670616 0.20965 -0.0669661 0.2097 -0.0668701 0.20975 -0.0667736 0.2098 -0.0666767 0.20985 -0.0665793 0.2099 -0.0664814 0.20995 -0.0663832 0.21 -0.0662846 0.21005 -0.0661857 0.2101 -0.0660864 0.21015 -0.0659867 0.2102 -0.0658868 0.21025 -0.0657866 0.2103 -0.0656861 0.21035 -0.0655853 0.2104 -0.0654843 0.21045 -0.0653831 0.2105 -0.0652816 0.21055 -0.06518 0.2106 -0.0650781 0.21065 -0.0649761 0.2107 -0.0648739 0.21075 -0.0647715 0.2108 -0.064669 0.21085 -0.0645663 0.2109 -0.0644635 0.21095 -0.0643606 0.211 -0.0642576 0.21105 -0.0641544 0.2111 -0.0640512 0.21115 -0.0639479 0.2112 -0.0638446 0.21125 -0.0637412 0.2113 -0.0636377 0.21135 -0.0635342 0.2114 -0.0634307 0.21145 -0.0633271 0.2115 -0.0632235 0.21155 -0.0631199 0.2116 -0.0630163 0.21165 -0.0629127 0.2117 -0.0628092 0.21175 -0.0627056 0.2118 -0.0626021 0.21185 -0.0624987 0.2119 -0.0623952 0.21195 -0.0622919 0.212 -0.0621886 0.21205 -0.0620853 0.2121 -0.0619821 0.21215 -0.061879 0.2122 -0.061776 0.21225 -0.0616731 0.2123 -0.0615703 0.21235 -0.0614675 0.2124 -0.0613649 0.21245 -0.0612624 0.2125 -0.06116 0.21255 -0.0610577 0.2126 -0.0609556 0.21265 -0.0608535 0.2127 -0.0607516 0.21275 -0.0606498 0.2128 -0.0605482 0.21285 -0.0604467 0.2129 -0.0603453 0.21295 -0.0602441 0.213 -0.060143 0.21305 -0.0600421 0.2131 -0.0599413 0.21315 -0.0598407 0.2132 -0.0597402 0.21325 -0.0596398 0.2133 -0.0595396 0.21335 -0.0594396 0.2134 -0.0593397 0.21345 -0.0592399 0.2135 -0.0591403 0.21355 -0.0590408 0.2136 -0.0589415 0.21365 -0.0588423 0.2137 -0.0587432 0.21375 -0.0586443 0.2138 -0.0585455 0.21385 -0.0584468 0.2139 -0.0583483 0.21395 -0.0582498 0.214 -0.0581515 0.21405 -0.0580533 0.2141 -0.0579552 0.21415 -0.0578572 0.2142 -0.0577593 0.21425 -0.0576614 0.2143 -0.0575637 0.21435 -0.057466 0.2144 -0.0573683 0.21445 -0.0572708 0.2145 -0.0571733 0.21455 -0.0570758 0.2146 -0.0569783 0.21465 -0.0568809 0.2147 -0.0567834 0.21475 -0.056686 0.2148 -0.0565885 0.21485 -0.056491 0.2149 -0.0563935 0.21495 -0.0562959 0.215 -0.0561983 0.21505 -0.0561006 0.2151 -0.0560027 0.21515 -0.0559048 0.2152 -0.0558067 0.21525 -0.0557085 0.2153 -0.0556101 0.21535 -0.0555116 0.2154 -0.0554128 0.21545 -0.0553138 0.2155 -0.0552146 0.21555 -0.055115 0.2156 -0.0550152 0.21565 -0.0549151 0.2157 -0.0548146 0.21575 -0.0547138 0.2158 -0.0546125 0.21585 -0.0545108 0.2159 -0.0544086 0.21595 -0.0543059 0.216 -0.0542027 0.21605 -0.0540989 0.2161 -0.0539945 0.21615 -0.0538894 0.2162 -0.0537836 0.21625 -0.0536771 0.2163 -0.0535697 0.21635 -0.0534615 0.2164 -0.0533523 0.21645 -0.0532422 0.2165 -0.053131 0.21655 -0.0530187 0.2166 -0.0529052 0.21665 -0.0527903 0.2167 -0.0526742 0.21675 -0.0525565 0.2168 -0.0524373 0.21685 -0.0523164 0.2169 -0.0521938 0.21695 -0.0520692 0.217 -0.0519426 0.21705 -0.0518137 0.2171 -0.0516825 0.21715 -0.0515488 0.2172 -0.0514123 0.21725 -0.0512729 0.2173 -0.0511303 0.21735 -0.0509843 0.2174 -0.0508346 0.21745 -0.050681 0.2175 -0.050523 0.21755 -0.0503603 0.2176 -0.0501926 0.21765 -0.0500192 0.2177 -0.0498399 0.21775 -0.0496539 0.2178 -0.0494607 0.21785 -0.0492596 0.2179 -0.0490497 0.21795 -0.0488302 0.218 -0.0486 0.21805 -0.048358 0.2181 -0.0481029 0.21815 -0.0478332 0.2182 -0.047547 0.21825 -0.0472424 0.2183 -0.0469171 0.21835 -0.0465683 0.2184 -0.0461928 0.21845 -0.0457868 0.2185 -0.0453459 0.21855 -0.0448647 0.2186 -0.0443368 0.21865 -0.0437546 0.2187 -0.0431086 0.21875 -0.0423875 0.2188 -0.0415771 0.21885 -0.0406602 0.2189 -0.0396153 0.21895 -0.0384155 0.219 -0.0370272 0.21905 -0.0354085 0.2191 -0.0335073 0.21915 -0.0312598 0.2192 -0.0285898 0.21925 -0.0254104 0.2193 -0.0216318 0.21935 -0.0171787 0.2194 -0.012024 0.21945 -0.00623961 0.2195 -5.30047e-05 0.21955 0.00613054 0.2196 0.0117882 0.21965 0.0164264 0.2197 0.0197355 0.21975 0.0216529 0.2198 0.0223155 0.21985 0.0219594 0.2199 0.0208347 0.21995 0.019159 0.22 0.0171037 0.22005 0.0147966 0.2201 0.0123302 0.22015 0.0097712 0.2202 0.0071672 0.22025 0.00455229 0.2203 0.00195081 0.22035 -0.000620011 0.2204 -0.00314811 0.22045 -0.00562528 0.2205 -0.00804619 0.22055 -0.0104076 0.2206 -0.0127081 0.22065 -0.0149473 0.2207 -0.0171259 0.22075 -0.0192455 0.2208 -0.0213084 0.22085 -0.0233174 0.2209 -0.0252764 0.22095 -0.0271895 0.221 -0.0290618 0.22105 -0.030899 0.2211 -0.0327077 0.22115 -0.0344947 0.2212 -0.0362679 0.22125 -0.038035 0.2213 -0.0398041 0.22135 -0.0415828 0.2214 -0.0433777 0.22145 -0.0451939 0.2215 -0.0470338 0.22155 -0.0488958 0.2216 -0.0507735 0.22165 -0.0526542 0.2217 -0.0545186 0.22175 -0.0563412 0.2218 -0.0580915 0.22185 -0.0597379 0.2219 -0.0612514 0.22195 -0.0626101 0.222 -0.0638016 0.22205 -0.0648242 0.2221 -0.0656855 0.22215 -0.0663998 0.2222 -0.0669848 0.22225 -0.067459 0.2223 -0.0678402 0.22235 -0.068144 0.2224 -0.0683839 0.22245 -0.0685711 0.2225 -0.0687148 0.22255 -0.0688224 0.2226 -0.0688999 0.22265 -0.0689524 0.2227 -0.0689839 0.22275 -0.0689977 0.2228 -0.0689965 0.22285 -0.0689825 0.2229 -0.0689578 0.22295 -0.0689237 0.223 -0.0688817 0.22305 -0.0688329 0.2231 -0.0687782 0.22315 -0.0687184 0.2232 -0.0686541 0.22325 -0.068586 0.2233 -0.0685146 0.22335 -0.0684402 0.2234 -0.0683632 0.22345 -0.0682839 0.2235 -0.0682026 0.22355 -0.0681195 0.2236 -0.0680348 0.22365 -0.0679486 0.2237 -0.0678611 0.22375 -0.0677725 0.2238 -0.0676828 0.22385 -0.0675922 0.2239 -0.0675006 0.22395 -0.0674083 0.224 -0.0673151 0.22405 -0.0672213 0.2241 -0.0671269 0.22415 -0.0670318 0.2242 -0.0669362 0.22425 -0.0668401 0.2243 -0.0667434 0.22435 -0.0666463 0.2244 -0.0665488 0.22445 -0.0664508 0.2245 -0.0663525 0.22455 -0.0662538 0.2246 -0.0661547 0.22465 -0.0660553 0.2247 -0.0659556 0.22475 -0.0658555 0.2248 -0.0657552 0.22485 -0.0656547 0.2249 -0.0655538 0.22495 -0.0654528 0.225 -0.0653515 0.22505 -0.0652499 0.2251 -0.0651482 0.22515 -0.0650463 0.2252 -0.0649442 0.22525 -0.0648419 0.2253 -0.0647395 0.22535 -0.0646369 0.2254 -0.0645342 0.22545 -0.0644314 0.2255 -0.0643284 0.22555 -0.0642254 0.2256 -0.0641222 0.22565 -0.064019 0.2257 -0.0639157 0.22575 -0.0638123 0.2258 -0.0637089 0.22585 -0.0636054 0.2259 -0.0635019 0.22595 -0.0633983 0.226 -0.0632948 0.22605 -0.0631912 0.2261 -0.0630876 0.22615 -0.062984 0.2262 -0.0628804 0.22625 -0.0627769 0.2263 -0.0626733 0.22635 -0.0625698 0.2264 -0.0624664 0.22645 -0.062363 0.2265 -0.0622596 0.22655 -0.0621563 0.2266 -0.0620531 0.22665 -0.0619499 0.2267 -0.0618469 0.22675 -0.0617439 0.2268 -0.061641 0.22685 -0.0615382 0.2269 -0.0614355 0.22695 -0.0613329 0.227 -0.0612304 0.22705 -0.0611281 0.2271 -0.0610258 0.22715 -0.0609237 0.2272 -0.0608217 0.22725 -0.0607198 0.2273 -0.0606181 0.22735 -0.0605165 0.2274 -0.0604151 0.22745 -0.0603137 0.2275 -0.0602126 0.22755 -0.0601115 0.2276 -0.0600106 0.22765 -0.0599099 0.2277 -0.0598093 0.22775 -0.0597088 0.2278 -0.0596085 0.22785 -0.0595084 0.2279 -0.0594084 0.22795 -0.0593085 0.228 -0.0592088 0.22805 -0.0591092 0.2281 -0.0590098 0.22815 -0.0589105 0.2282 -0.0588113 0.22825 -0.0587123 0.2283 -0.0586134 0.22835 -0.0585147 0.2284 -0.058416 0.22845 -0.0583175 0.2285 -0.0582191 0.22855 -0.0581208 0.2286 -0.0580227 0.22865 -0.0579246 0.2287 -0.0578266 0.22875 -0.0577287 0.2288 -0.0576309 0.22885 -0.0575332 0.2289 -0.0574355 0.22895 -0.0573379 0.229 -0.0572403 0.22905 -0.0571428 0.2291 -0.0570453 0.22915 -0.0569479 0.2292 -0.0568504 0.22925 -0.056753 0.2293 -0.0566556 0.22935 -0.0565581 0.2294 -0.0564606 0.22945 -0.0563631 0.2295 -0.0562655 0.22955 -0.0561678 0.2296 -0.05607 0.22965 -0.0559722 0.2297 -0.0558742 0.22975 -0.0557761 0.2298 -0.0556778 0.22985 -0.0555794 0.2299 -0.0554808 0.22995 -0.0553819 0.23 -0.0552829 0.23005 -0.0551835 0.2301 -0.0550839 0.23015 -0.054984 0.2302 -0.0548838 0.23025 -0.0547832 0.2303 -0.0546822 0.23035 -0.0545808 0.2304 -0.054479 0.23045 -0.0543766 0.2305 -0.0542738 0.23055 -0.0541704 0.2306 -0.0540664 0.23065 -0.0539618 0.2307 -0.0538565 0.23075 -0.0537505 0.2308 -0.0536437 0.23085 -0.053536 0.2309 -0.0534275 0.23095 -0.0533181 0.231 -0.0532076 0.23105 -0.0530961 0.2311 -0.0529834 0.23115 -0.0528695 0.2312 -0.0527542 0.23125 -0.0526376 0.2313 -0.0525195 0.23135 -0.0523998 0.2314 -0.0522784 0.23145 -0.0521551 0.2315 -0.0520299 0.23155 -0.0519026 0.2316 -0.051773 0.23165 -0.051641 0.2317 -0.0515065 0.23175 -0.0513691 0.2318 -0.0512287 0.23185 -0.0510851 0.2319 -0.050938 0.23195 -0.0507871 0.232 -0.0506321 0.23205 -0.0504727 0.2321 -0.0503085 0.23215 -0.0501391 0.2322 -0.0499639 0.23225 -0.0497826 0.2323 -0.0495944 0.23235 -0.0493988 0.2324 -0.049195 0.23245 -0.0489823 0.2325 -0.0487595 0.23255 -0.0485258 0.2326 -0.0482799 0.23265 -0.0480204 0.2327 -0.0477457 0.23275 -0.047454 0.2328 -0.0471432 0.23285 -0.0468109 0.2329 -0.0464541 0.23295 -0.0460696 0.233 -0.0456532 0.23305 -0.0452003 0.2331 -0.0447053 0.23315 -0.0441614 0.2332 -0.0435603 0.23325 -0.0428922 0.2333 -0.0421448 0.23335 -0.0413033 0.2334 -0.040349 0.23345 -0.0392589 0.2335 -0.0380042 0.23355 -0.0365489 0.2336 -0.0348482 0.23365 -0.0328464 0.2337 -0.0304757 0.23375 -0.0276564 0.2338 -0.0242994 0.23385 -0.020317 0.2339 -0.0156443 0.23395 -0.0102791 0.234 -0.00433624 0.23405 0.00190056 0.2341 0.00797818 0.23415 0.0133646 0.2342 0.0176097 0.23425 0.02048 0.2343 0.0219844 0.23435 0.0223017 0.2344 0.0216798 0.23445 0.0203618 0.2345 0.0185515 0.23455 0.0164057 0.2346 0.0140403 0.23465 0.0115389 0.2347 0.00896146 0.23475 0.00635092 0.2348 0.00373797 0.23485 0.00114448 0.2349 -0.00141409 0.23495 -0.00392703 0.235 -0.0063871 0.23505 -0.0087897 0.2351 -0.0111322 0.23515 -0.0134135 0.2352 -0.0156337 0.23525 -0.0177937 0.2353 -0.0198953 0.23535 -0.021941 0.2354 -0.023934 0.23545 -0.0258782 0.2355 -0.027778 0.23555 -0.0296387 0.2356 -0.0314663 0.23565 -0.0332674 0.2357 -0.0350493 0.23575 -0.0368197 0.2358 -0.0385866 0.23585 -0.0403579 0.2359 -0.0421409 0.23595 -0.0439421 0.236 -0.0457656 0.23605 -0.0476127 0.2361 -0.0494806 0.23615 -0.0513609 0.2362 -0.0532389 0.23625 -0.0550933 0.2363 -0.0568967 0.23635 -0.0586179 0.2364 -0.0602257 0.23645 -0.0616929 0.2365 -0.0630002 0.23655 -0.0641387 0.2366 -0.0651098 0.23665 -0.0659235 0.2367 -0.0665954 0.23675 -0.0671438 0.2368 -0.0675872 0.23685 -0.0679427 0.2369 -0.0682252 0.23695 -0.0684475 0.237 -0.0686203 0.23705 -0.0687519 0.2371 -0.0688495 0.23715 -0.0689188 0.2372 -0.0689644 0.23725 -0.06899 0.2373 -0.0689988 0.23735 -0.0689934 0.2374 -0.0689759 0.23745 -0.0689481 0.2375 -0.0689114 0.23755 -0.0688672 0.2376 -0.0688164 0.23765 -0.06876 0.2377 -0.0686988 0.23775 -0.0686333 0.2378 -0.0685641 0.23785 -0.0684917 0.2379 -0.0684164 0.23795 -0.0683387 0.238 -0.0682587 0.23805 -0.0681768 0.2381 -0.0680932 0.23815 -0.068008 0.2382 -0.0679214 0.23825 -0.0678336 0.2383 -0.0677446 0.23835 -0.0676546 0.2384 -0.0675637 0.23845 -0.0674719 0.2385 -0.0673793 0.23855 -0.0672859 0.2386 -0.0671919 0.23865 -0.0670973 0.2387 -0.067002 0.23875 -0.0669063 0.2388 -0.06681 0.23885 -0.0667132 0.2389 -0.0666159 0.23895 -0.0665183 0.239 -0.0664202 0.23905 -0.0663217 0.2391 -0.0662229 0.23915 -0.0661237 0.2392 -0.0660242 0.23925 -0.0659244 0.2393 -0.0658243 0.23935 -0.0657239 0.2394 -0.0656232 0.23945 -0.0655223 0.2395 -0.0654212 0.23955 -0.0653198 0.2396 -0.0652182 0.23965 -0.0651164 0.2397 -0.0650144 0.23975 -0.0649123 0.2398 -0.06481 0.23985 -0.0647075 0.2399 -0.0646049 0.23995 -0.0645021 0.24 -0.0643993 0.24005 -0.0642963 0.2401 -0.0641932 0.24015 -0.06409 0.2402 -0.0639868 0.24025 -0.0638834 0.2403 -0.06378 0.24035 -0.0636766 0.2404 -0.0635731 0.24045 -0.0634696 0.2405 -0.063366 0.24055 -0.0632624 0.2406 -0.0631588 0.24065 -0.0630553 0.2407 -0.0629517 0.24075 -0.0628481 0.2408 -0.0627445 0.24085 -0.062641 0.2409 -0.0625375 0.24095 -0.0624341 0.241 -0.0623307 0.24105 -0.0622274 0.2411 -0.0621241 0.24115 -0.0620209 0.2412 -0.0619178 0.24125 -0.0618147 0.2413 -0.0617118 0.24135 -0.0616089 0.2414 -0.0615061 0.24145 -0.0614035 0.2415 -0.0613009 0.24155 -0.0611985 0.2416 -0.0610961 0.24165 -0.0609939 0.2417 -0.0608919 0.24175 -0.0607899 0.2418 -0.0606881 0.24185 -0.0605864 0.2419 -0.0604848 0.24195 -0.0603834 0.242 -0.0602821 0.24205 -0.060181 0.2421 -0.06008 0.24215 -0.0599792 0.2422 -0.0598785 0.24225 -0.0597779 0.2423 -0.0596775 0.24235 -0.0595773 0.2424 -0.0594772 0.24245 -0.0593772 0.2425 -0.0592774 0.24255 -0.0591777 0.2426 -0.0590782 0.24265 -0.0589788 0.2427 -0.0588795 0.24275 -0.0587804 0.2428 -0.0586814 0.24285 -0.0585826 0.2429 -0.0584839 0.24295 -0.0583853 0.243 -0.0582868 0.24305 -0.0581884 0.2431 -0.0580902 0.24315 -0.057992 0.2432 -0.057894 0.24325 -0.057796 0.2433 -0.0576982 0.24335 -0.0576004 0.2434 -0.0575027 0.24345 -0.057405 0.2435 -0.0573074 0.24355 -0.0572099 0.2436 -0.0571124 0.24365 -0.0570149 0.2437 -0.0569175 0.24375 -0.05682 0.2438 -0.0567226 0.24385 -0.0566252 0.2439 -0.0565277 0.24395 -0.0564302 0.244 -0.0563326 0.24405 -0.056235 0.2441 -0.0561373 0.24415 -0.0560395 0.2442 -0.0559416 0.24425 -0.0558436 0.2443 -0.0557454 0.24435 -0.0556471 0.2444 -0.0555486 0.24445 -0.0554499 0.2445 -0.055351 0.24455 -0.0552519 0.2446 -0.0551525 0.24465 -0.0550528 0.2447 -0.0549528 0.24475 -0.0548524 0.2448 -0.0547517 0.24485 -0.0546506 0.2449 -0.0545491 0.24495 -0.0544471 0.245 -0.0543446 0.24505 -0.0542416 0.2451 -0.054138 0.24515 -0.0540338 0.2452 -0.053929 0.24525 -0.0538235 0.2453 -0.0537172 0.24535 -0.0536102 0.2454 -0.0535023 0.24545 -0.0533935 0.2455 -0.0532837 0.24555 -0.0531729 0.2456 -0.053061 0.24565 -0.052948 0.2457 -0.0528336 0.24575 -0.052718 0.2458 -0.0526009 0.24585 -0.0524823 0.2459 -0.0523621 0.24595 -0.0522401 0.246 -0.0521162 0.24605 -0.0519904 0.2461 -0.0518624 0.24615 -0.0517321 0.2462 -0.0515993 0.24625 -0.0514639 0.2463 -0.0513256 0.24635 -0.0511843 0.2464 -0.0510396 0.24645 -0.0508913 0.2465 -0.0507392 0.24655 -0.0505829 0.2466 -0.050422 0.24665 -0.0502562 0.2467 -0.0500851 0.24675 -0.049908 0.2468 -0.0497246 0.24685 -0.0495342 0.2469 -0.0493361 0.24695 -0.0491296 0.247 -0.0489139 0.24705 -0.0486878 0.2471 -0.0484504 0.24715 -0.0482004 0.2472 -0.0479363 0.24725 -0.0476566 0.2473 -0.0473592 0.24735 -0.047042 0.2474 -0.0467023 0.24745 -0.0463373 0.2475 -0.0459432 0.24755 -0.045516 0.2476 -0.0450506 0.24765 -0.0445411 0.2477 -0.0439803 0.24775 -0.0433595 0.2478 -0.0426681 0.24785 -0.0418931 0.2479 -0.0410185 0.24795 -0.0400246 0.248 -0.0388865 0.24805 -0.0375734 0.2481 -0.0360468 0.24815 -0.0342586 0.2482 -0.0321496 0.24825 -0.029648 0.2483 -0.0266706 0.24835 -0.0231274 0.2484 -0.0189345 0.24845 -0.0140416 0.2485 -0.00847659 0.24855 -0.0024029 0.2486 0.00383928 0.24865 0.00975785 0.2487 0.014828 0.24875 0.0186571 0.2488 0.0210902 0.24885 0.0221997 0.2489 0.0221964 0.24895 0.0213327 0.249 0.0198414 0.24905 0.0179116 0.2491 0.0156866 0.24915 0.013271 0.2492 0.0107403 0.24925 0.00814859 0.2493 0.00553448 0.24935 0.00292559 0.2494 0.000341591 0.24945 -0.00220371 0.2495 -0.00470078 0.24955 -0.0071433 0.2496 -0.00952736 0.24965 -0.0118508 0.2497 -0.014113 0.24975 -0.0163142 0.2498 -0.0184558 0.24985 -0.0205397 0.2499 -0.0225686 0.24995 -0.0245459 0.25 -0.0264756 brian2-2.5.4/brian2/tests/rallpack_data/ref_axon.x.neuron000066400000000000000000002623731445201106100233060ustar00rootroot000000000000000 -0.065 5e-05 -0.0651575 0.0001 -0.0653124 0.00015 -0.0654652 0.0002 -0.0656163 0.00025 -0.0657659 0.0003 -0.0659142 0.00035 -0.0660614 0.0004 -0.0662074 0.00045 -0.0663522 0.0005 -0.0664959 0.00055 -0.0666385 0.0006 -0.0667798 0.00065 -0.0669198 0.0007 -0.0670584 0.00075 -0.0671956 0.0008 -0.0673314 0.00085 -0.0674655 0.0009 -0.0675981 0.00095 -0.0677289 0.001 -0.067858 0.00105 -0.0679853 0.0011 -0.0681108 0.00115 -0.0682344 0.0012 -0.068356 0.00125 -0.0684757 0.0013 -0.0685934 0.00135 -0.068709 0.0014 -0.0688226 0.00145 -0.068934 0.0015 -0.0690434 0.00155 -0.0691506 0.0016 -0.0692556 0.00165 -0.0693585 0.0017 -0.0694591 0.00175 -0.0695576 0.0018 -0.0696538 0.00185 -0.0697478 0.0019 -0.0698395 0.00195 -0.0699289 0.002 -0.0700161 0.00205 -0.0701009 0.0021 -0.0701833 0.00215 -0.0702632 0.0022 -0.0703406 0.00225 -0.0704153 0.0023 -0.0704872 0.00235 -0.070556 0.0024 -0.0706214 0.00245 -0.0706831 0.0025 -0.0707406 0.00255 -0.0707933 0.0026 -0.0708404 0.00265 -0.0708808 0.0027 -0.0709134 0.00275 -0.0709365 0.0028 -0.0709483 0.00285 -0.0709462 0.0029 -0.0709273 0.00295 -0.0708878 0.003 -0.070823 0.00305 -0.0707271 0.0031 -0.0705931 0.00315 -0.0704119 0.0032 -0.0701727 0.00325 -0.0698619 0.0033 -0.0694627 0.00335 -0.0689543 0.0034 -0.0683112 0.00345 -0.0675016 0.0035 -0.066486 0.00355 -0.0652157 0.0036 -0.0636298 0.00365 -0.0616519 0.0037 -0.0591854 0.00375 -0.0561056 0.0038 -0.0522469 0.00385 -0.0473802 0.0039 -0.0411686 0.00395 -0.0330904 0.004 -0.0223354 0.00405 -0.00788677 0.0041 0.0103026 0.00415 0.0283645 0.0042 0.0398132 0.00425 0.0439189 0.0043 0.0445382 0.00435 0.0440554 0.0044 0.0431558 0.00445 0.0419918 0.0045 0.0406073 0.00455 0.0390257 0.0046 0.0372668 0.00465 0.03535 0.0047 0.0332948 0.00475 0.031121 0.0048 0.0288476 0.00485 0.026493 0.0049 0.0240743 0.00495 0.0216072 0.005 0.0191059 0.00505 0.0165828 0.0051 0.0140487 0.00515 0.0115129 0.0052 0.00898313 0.00525 0.00646556 0.0053 0.00396518 0.00535 0.00148576 0.0054 -0.000969999 0.00545 -0.00340035 0.0055 -0.00580441 0.00555 -0.0081821 0.0056 -0.0105341 0.00565 -0.0128619 0.0057 -0.0151677 0.00575 -0.0174547 0.0058 -0.0197275 0.00585 -0.0219919 0.0059 -0.0242558 0.00595 -0.0265296 0.006 -0.028827 0.00605 -0.0311659 0.0061 -0.0335693 0.00615 -0.036066 0.0062 -0.038692 0.00625 -0.0414895 0.0063 -0.0445053 0.00635 -0.0477839 0.0064 -0.0513541 0.00645 -0.0552043 0.0065 -0.0592483 0.00655 -0.0632943 0.0066 -0.0670551 0.00665 -0.0702332 0.0067 -0.0726505 0.00675 -0.0743173 0.0068 -0.0753814 0.00685 -0.0760272 0.0069 -0.0764088 0.00695 -0.0766322 0.007 -0.0767633 0.00705 -0.0768407 0.0071 -0.0768871 0.00715 -0.076915 0.0072 -0.076932 0.00725 -0.0769424 0.0073 -0.0769487 0.00735 -0.0769525 0.0074 -0.0769546 0.00745 -0.0769557 0.0075 -0.076956 0.00755 -0.076956 0.0076 -0.0769555 0.00765 -0.0769549 0.0077 -0.0769541 0.00775 -0.0769532 0.0078 -0.0769522 0.00785 -0.0769511 0.0079 -0.07695 0.00795 -0.0769488 0.008 -0.0769475 0.00805 -0.0769462 0.0081 -0.0769449 0.00815 -0.0769435 0.0082 -0.0769422 0.00825 -0.0769407 0.0083 -0.0769393 0.00835 -0.0769378 0.0084 -0.0769362 0.00845 -0.0769347 0.0085 -0.0769331 0.00855 -0.0769314 0.0086 -0.0769297 0.00865 -0.076928 0.0087 -0.0769263 0.00875 -0.0769245 0.0088 -0.0769227 0.00885 -0.0769208 0.0089 -0.0769189 0.00895 -0.076917 0.009 -0.076915 0.00905 -0.076913 0.0091 -0.0769109 0.00915 -0.0769088 0.0092 -0.0769066 0.00925 -0.0769044 0.0093 -0.0769022 0.00935 -0.0768999 0.0094 -0.0768976 0.00945 -0.0768952 0.0095 -0.0768927 0.00955 -0.0768902 0.0096 -0.0768877 0.00965 -0.0768851 0.0097 -0.0768824 0.00975 -0.0768797 0.0098 -0.0768769 0.00985 -0.0768741 0.0099 -0.0768712 0.00995 -0.0768683 0.01 -0.0768653 0.01005 -0.0768622 0.0101 -0.0768591 0.01015 -0.0768559 0.0102 -0.0768526 0.01025 -0.0768493 0.0103 -0.0768459 0.01035 -0.0768424 0.0104 -0.0768388 0.01045 -0.0768352 0.0105 -0.0768315 0.01055 -0.0768277 0.0106 -0.0768238 0.01065 -0.0768199 0.0107 -0.0768158 0.01075 -0.0768117 0.0108 -0.0768075 0.01085 -0.0768032 0.0109 -0.0767988 0.01095 -0.0767943 0.011 -0.0767897 0.01105 -0.0767851 0.0111 -0.0767803 0.01115 -0.0767754 0.0112 -0.0767704 0.01125 -0.0767653 0.0113 -0.0767601 0.01135 -0.0767548 0.0114 -0.0767494 0.01145 -0.0767439 0.0115 -0.0767383 0.01155 -0.0767325 0.0116 -0.0767266 0.01165 -0.0767206 0.0117 -0.0767145 0.01175 -0.0767083 0.0118 -0.0767019 0.01185 -0.0766954 0.0119 -0.0766888 0.01195 -0.076682 0.012 -0.0766751 0.01205 -0.0766681 0.0121 -0.0766609 0.01215 -0.0766535 0.0122 -0.0766461 0.01225 -0.0766385 0.0123 -0.0766307 0.01235 -0.0766228 0.0124 -0.0766147 0.01245 -0.0766065 0.0125 -0.0765982 0.01255 -0.0765896 0.0126 -0.0765809 0.01265 -0.0765721 0.0127 -0.0765631 0.01275 -0.0765539 0.0128 -0.0765446 0.01285 -0.0765351 0.0129 -0.0765254 0.01295 -0.0765156 0.013 -0.0765055 0.01305 -0.0764953 0.0131 -0.076485 0.01315 -0.0764744 0.0132 -0.0764637 0.01325 -0.0764528 0.0133 -0.0764417 0.01335 -0.0764304 0.0134 -0.076419 0.01345 -0.0764073 0.0135 -0.0763955 0.01355 -0.0763835 0.0136 -0.0763713 0.01365 -0.0763589 0.0137 -0.0763463 0.01375 -0.0763335 0.0138 -0.0763205 0.01385 -0.0763074 0.0139 -0.076294 0.01395 -0.0762804 0.014 -0.0762667 0.01405 -0.0762527 0.0141 -0.0762386 0.01415 -0.0762242 0.0142 -0.0762096 0.01425 -0.0761949 0.0143 -0.0761799 0.01435 -0.0761647 0.0144 -0.0761494 0.01445 -0.0761338 0.0145 -0.076118 0.01455 -0.076102 0.0146 -0.0760858 0.01465 -0.0760694 0.0147 -0.0760528 0.01475 -0.0760359 0.0148 -0.0760189 0.01485 -0.0760016 0.0149 -0.0759842 0.01495 -0.0759665 0.015 -0.0759486 0.01505 -0.0759305 0.0151 -0.0759122 0.01515 -0.0758937 0.0152 -0.0758749 0.01525 -0.075856 0.0153 -0.0758368 0.01535 -0.0758174 0.0154 -0.0757977 0.01545 -0.0757779 0.0155 -0.0757578 0.01555 -0.0757375 0.0156 -0.0757169 0.01565 -0.0756962 0.0157 -0.0756751 0.01575 -0.0756539 0.0158 -0.0756324 0.01585 -0.0756107 0.0159 -0.0755887 0.01595 -0.0755665 0.016 -0.075544 0.01605 -0.0755212 0.0161 -0.0754982 0.01615 -0.0754749 0.0162 -0.0754513 0.01625 -0.0754274 0.0163 -0.0754032 0.01635 -0.0753787 0.0164 -0.0753538 0.01645 -0.0753286 0.0165 -0.075303 0.01655 -0.075277 0.0166 -0.0752505 0.01665 -0.0752235 0.0167 -0.0751959 0.01675 -0.0751678 0.0168 -0.0751389 0.01685 -0.0751091 0.0169 -0.0750784 0.01695 -0.0750464 0.017 -0.075013 0.01705 -0.0749777 0.0171 -0.0749402 0.01715 -0.0748999 0.0172 -0.0748561 0.01725 -0.0748078 0.0173 -0.074754 0.01735 -0.0746932 0.0174 -0.0746236 0.01745 -0.074543 0.0175 -0.0744484 0.01755 -0.0743366 0.0176 -0.0742029 0.01765 -0.0740421 0.0177 -0.0738473 0.01775 -0.0736103 0.0178 -0.0733206 0.01785 -0.0729655 0.0179 -0.0725291 0.01795 -0.0719916 0.018 -0.0713289 0.01805 -0.0705108 0.0181 -0.0695001 0.01815 -0.0682506 0.0182 -0.0667047 0.01825 -0.0647907 0.0183 -0.0624182 0.01835 -0.0594722 0.0184 -0.0558024 0.01845 -0.0512055 0.0185 -0.0453902 0.01855 -0.0379075 0.0186 -0.0280261 0.01865 -0.0146203 0.0187 0.00329633 0.01875 0.0236412 0.0188 0.0388942 0.01885 0.0450639 0.0189 0.04623 0.01895 0.0459579 0.019 0.045269 0.01905 0.0443449 0.0191 0.0432194 0.01915 0.0419056 0.0192 0.0404147 0.01925 0.0387587 0.0193 0.0369518 0.01935 0.0350092 0.0194 0.0329471 0.01945 0.0307819 0.0195 0.0285298 0.01955 0.0262066 0.0196 0.0238269 0.01965 0.0214044 0.0197 0.0189512 0.01975 0.0164784 0.0198 0.0139954 0.01985 0.0115105 0.0199 0.00903047 0.01995 0.00656106 0.02 0.00410677 0.02005 0.00167107 0.0201 -0.00074351 0.02015 -0.00313531 0.0202 -0.00550343 0.02025 -0.00784774 0.0203 -0.0101688 0.02035 -0.0124678 0.0204 -0.0147469 0.02045 -0.0170089 0.0205 -0.0192579 0.02055 -0.0214993 0.0206 -0.0237403 0.02065 -0.0259906 0.0207 -0.0282627 0.02075 -0.0305732 0.0208 -0.0329435 0.02085 -0.035401 0.0209 -0.0379799 0.02095 -0.040721 0.021 -0.0436712 0.02105 -0.0468777 0.0211 -0.0503769 0.02115 -0.0541723 0.0212 -0.0582011 0.02125 -0.0622985 0.0213 -0.0661909 0.02135 -0.0695626 0.0214 -0.0721882 0.02145 -0.0740311 0.0215 -0.0752189 0.02155 -0.0759409 0.0216 -0.0763654 0.02165 -0.0766116 0.0217 -0.0767543 0.02175 -0.0768376 0.0218 -0.0768867 0.02185 -0.0769159 0.0219 -0.0769335 0.02195 -0.0769441 0.022 -0.0769504 0.02205 -0.0769541 0.0221 -0.0769562 0.02215 -0.0769572 0.0222 -0.0769575 0.02225 -0.0769574 0.0223 -0.076957 0.02235 -0.0769564 0.0224 -0.0769556 0.02245 -0.0769546 0.0225 -0.0769536 0.02255 -0.0769526 0.0226 -0.0769515 0.02265 -0.0769503 0.0227 -0.0769491 0.02275 -0.0769478 0.0228 -0.0769465 0.02285 -0.0769452 0.0229 -0.0769438 0.02295 -0.0769424 0.023 -0.076941 0.02305 -0.0769395 0.0231 -0.076938 0.02315 -0.0769365 0.0232 -0.0769349 0.02325 -0.0769333 0.0233 -0.0769317 0.02335 -0.07693 0.0234 -0.0769283 0.02345 -0.0769266 0.0235 -0.0769248 0.02355 -0.076923 0.0236 -0.0769211 0.02365 -0.0769192 0.0237 -0.0769173 0.02375 -0.0769153 0.0238 -0.0769133 0.02385 -0.0769112 0.0239 -0.0769091 0.02395 -0.076907 0.024 -0.0769048 0.02405 -0.0769025 0.0241 -0.0769002 0.02415 -0.0768979 0.0242 -0.0768955 0.02425 -0.076893 0.0243 -0.0768905 0.02435 -0.076888 0.0244 -0.0768854 0.02445 -0.0768827 0.0245 -0.07688 0.02455 -0.0768772 0.0246 -0.0768744 0.02465 -0.0768715 0.0247 -0.0768685 0.02475 -0.0768655 0.0248 -0.0768624 0.02485 -0.0768592 0.0249 -0.076856 0.02495 -0.0768527 0.025 -0.0768494 0.02505 -0.0768459 0.0251 -0.0768424 0.02515 -0.0768388 0.0252 -0.0768352 0.02525 -0.0768314 0.0253 -0.0768276 0.02535 -0.0768237 0.0254 -0.0768197 0.02545 -0.0768156 0.0255 -0.0768114 0.02555 -0.0768072 0.0256 -0.0768028 0.02565 -0.0767984 0.0257 -0.0767938 0.02575 -0.0767892 0.0258 -0.0767845 0.02585 -0.0767796 0.0259 -0.0767747 0.02595 -0.0767696 0.026 -0.0767645 0.02605 -0.0767592 0.0261 -0.0767538 0.02615 -0.0767483 0.0262 -0.0767427 0.02625 -0.076737 0.0263 -0.0767311 0.02635 -0.0767252 0.0264 -0.0767191 0.02645 -0.0767128 0.0265 -0.0767065 0.02655 -0.0767 0.0266 -0.0766934 0.02665 -0.0766866 0.0267 -0.0766797 0.02675 -0.0766727 0.0268 -0.0766655 0.02685 -0.0766582 0.0269 -0.0766508 0.02695 -0.0766432 0.027 -0.0766354 0.02705 -0.0766275 0.0271 -0.0766194 0.02715 -0.0766112 0.0272 -0.0766029 0.02725 -0.0765943 0.0273 -0.0765856 0.02735 -0.0765768 0.0274 -0.0765678 0.02745 -0.0765586 0.0275 -0.0765492 0.02755 -0.0765397 0.0276 -0.07653 0.02765 -0.0765201 0.0277 -0.0765101 0.02775 -0.0764999 0.0278 -0.0764895 0.02785 -0.0764789 0.0279 -0.0764681 0.02795 -0.0764572 0.028 -0.0764461 0.02805 -0.0764347 0.0281 -0.0764232 0.02815 -0.0764116 0.0282 -0.0763997 0.02825 -0.0763876 0.0283 -0.0763754 0.02835 -0.0763629 0.0284 -0.0763503 0.02845 -0.0763374 0.0285 -0.0763244 0.02855 -0.0763112 0.0286 -0.0762977 0.02865 -0.0762841 0.0287 -0.0762703 0.02875 -0.0762562 0.0288 -0.076242 0.02885 -0.0762275 0.0289 -0.0762129 0.02895 -0.076198 0.029 -0.076183 0.02905 -0.0761677 0.0291 -0.0761523 0.02915 -0.0761366 0.0292 -0.0761207 0.02925 -0.0761046 0.0293 -0.0760883 0.02935 -0.0760718 0.0294 -0.076055 0.02945 -0.0760381 0.0295 -0.0760209 0.02955 -0.0760035 0.0296 -0.0759859 0.02965 -0.0759681 0.0297 -0.0759501 0.02975 -0.0759318 0.0298 -0.0759134 0.02985 -0.0758947 0.0299 -0.0758758 0.02995 -0.0758566 0.03 -0.0758373 0.03005 -0.0758177 0.0301 -0.0757978 0.03015 -0.0757778 0.0302 -0.0757575 0.03025 -0.0757369 0.0303 -0.0757162 0.03035 -0.0756951 0.0304 -0.0756739 0.03045 -0.0756524 0.0305 -0.0756306 0.03055 -0.0756086 0.0306 -0.0755862 0.03065 -0.0755637 0.0307 -0.0755408 0.03075 -0.0755176 0.0308 -0.0754942 0.03085 -0.0754704 0.0309 -0.0754463 0.03095 -0.0754219 0.031 -0.075397 0.03105 -0.0753718 0.0311 -0.0753462 0.03115 -0.0753201 0.0312 -0.0752935 0.03125 -0.0752663 0.0313 -0.0752385 0.03135 -0.0752099 0.0314 -0.0751804 0.03145 -0.07515 0.0315 -0.0751182 0.03155 -0.075085 0.0316 -0.0750499 0.03165 -0.0750125 0.0317 -0.0749722 0.03175 -0.0749283 0.0318 -0.0748799 0.03185 -0.0748258 0.0319 -0.0747646 0.03195 -0.0746944 0.032 -0.074613 0.03205 -0.0745175 0.0321 -0.0744045 0.03215 -0.0742693 0.0322 -0.0741068 0.03225 -0.07391 0.0323 -0.0736705 0.03235 -0.0733782 0.0324 -0.07302 0.03245 -0.0725801 0.0325 -0.0720391 0.03255 -0.0713725 0.0326 -0.0705506 0.03265 -0.0695362 0.0327 -0.0682834 0.03275 -0.0667353 0.0328 -0.0648205 0.03285 -0.0624497 0.0329 -0.0595089 0.03295 -0.0558496 0.033 -0.0512709 0.03305 -0.0454852 0.0331 -0.0380499 0.03315 -0.0282445 0.0332 -0.0149568 0.03325 0.00281347 0.0333 0.0231109 0.03335 0.0385381 0.0334 0.0449037 0.03345 0.0461476 0.0335 0.0458937 0.03355 0.0452076 0.0336 0.044282 0.03365 0.0431534 0.0337 0.0418357 0.03375 0.0403403 0.0338 0.0386798 0.03385 0.0368683 0.0339 0.0349214 0.03395 0.0328553 0.034 0.0306865 0.03405 0.0284313 0.0341 0.0261053 0.03415 0.0237234 0.0342 0.0212991 0.03425 0.0188445 0.0343 0.0163707 0.03435 0.0138871 0.0344 0.0114018 0.03445 0.00892174 0.0345 0.00645248 0.03455 0.00399851 0.0346 0.00156327 0.03465 -0.000850747 0.0347 -0.00324191 0.03475 -0.00560937 0.0348 -0.007953 0.03485 -0.0102734 0.0349 -0.0125719 0.03495 -0.0148505 0.035 -0.0171122 0.03505 -0.0193611 0.0351 -0.0216027 0.03515 -0.0238443 0.0352 -0.0260957 0.03525 -0.0283696 0.0353 -0.030683 0.03535 -0.0330574 0.0354 -0.0355205 0.03545 -0.0381069 0.0355 -0.0408579 0.03555 -0.0438204 0.0356 -0.0470414 0.03565 -0.0505563 0.0357 -0.0543662 0.03575 -0.0584039 0.0358 -0.0624988 0.03585 -0.0663729 0.0359 -0.0697117 0.03595 -0.0722976 0.036 -0.0741039 0.03605 -0.075264 0.0361 -0.0759676 0.03615 -0.0763809 0.0362 -0.0766205 0.03625 -0.0767595 0.0363 -0.0768406 0.03635 -0.0768885 0.0364 -0.076917 0.03645 -0.0769341 0.0365 -0.0769444 0.03655 -0.0769506 0.0366 -0.0769542 0.03665 -0.0769562 0.0367 -0.0769572 0.03675 -0.0769575 0.0368 -0.0769574 0.03685 -0.0769569 0.0369 -0.0769563 0.03695 -0.0769555 0.037 -0.0769546 0.03705 -0.0769536 0.0371 -0.0769525 0.03715 -0.0769514 0.0372 -0.0769502 0.03725 -0.076949 0.0373 -0.0769477 0.03735 -0.0769464 0.0374 -0.0769451 0.03745 -0.0769437 0.0375 -0.0769423 0.03755 -0.0769409 0.0376 -0.0769394 0.03765 -0.0769379 0.0377 -0.0769364 0.03775 -0.0769348 0.0378 -0.0769332 0.03785 -0.0769316 0.0379 -0.0769299 0.03795 -0.0769282 0.038 -0.0769265 0.03805 -0.0769247 0.0381 -0.0769229 0.03815 -0.076921 0.0382 -0.0769191 0.03825 -0.0769172 0.0383 -0.0769152 0.03835 -0.0769131 0.0384 -0.0769111 0.03845 -0.076909 0.0385 -0.0769068 0.03855 -0.0769046 0.0386 -0.0769023 0.03865 -0.0769 0.0387 -0.0768977 0.03875 -0.0768953 0.0388 -0.0768928 0.03885 -0.0768903 0.0389 -0.0768878 0.03895 -0.0768852 0.039 -0.0768825 0.03905 -0.0768798 0.0391 -0.076877 0.03915 -0.0768741 0.0392 -0.0768712 0.03925 -0.0768683 0.0393 -0.0768652 0.03935 -0.0768621 0.0394 -0.076859 0.03945 -0.0768557 0.0395 -0.0768524 0.03955 -0.0768491 0.0396 -0.0768456 0.03965 -0.0768421 0.0397 -0.0768385 0.03975 -0.0768348 0.0398 -0.0768311 0.03985 -0.0768272 0.0399 -0.0768233 0.03995 -0.0768193 0.04 -0.0768152 0.04005 -0.076811 0.0401 -0.0768068 0.04015 -0.0768024 0.0402 -0.0767979 0.04025 -0.0767934 0.0403 -0.0767887 0.04035 -0.076784 0.0404 -0.0767791 0.04045 -0.0767741 0.0405 -0.0767691 0.04055 -0.0767639 0.0406 -0.0767586 0.04065 -0.0767532 0.0407 -0.0767477 0.04075 -0.0767421 0.0408 -0.0767363 0.04085 -0.0767304 0.0409 -0.0767245 0.04095 -0.0767183 0.041 -0.0767121 0.04105 -0.0767057 0.0411 -0.0766992 0.04115 -0.0766926 0.0412 -0.0766858 0.04125 -0.0766789 0.0413 -0.0766718 0.04135 -0.0766646 0.0414 -0.0766573 0.04145 -0.0766498 0.0415 -0.0766422 0.04155 -0.0766344 0.0416 -0.0766264 0.04165 -0.0766184 0.0417 -0.0766101 0.04175 -0.0766017 0.0418 -0.0765931 0.04185 -0.0765844 0.0419 -0.0765755 0.04195 -0.0765665 0.042 -0.0765573 0.04205 -0.0765479 0.0421 -0.0765383 0.04215 -0.0765286 0.0422 -0.0765187 0.04225 -0.0765086 0.0423 -0.0764983 0.04235 -0.0764879 0.0424 -0.0764773 0.04245 -0.0764665 0.0425 -0.0764555 0.04255 -0.0764444 0.0426 -0.076433 0.04265 -0.0764215 0.0427 -0.0764097 0.04275 -0.0763978 0.0428 -0.0763857 0.04285 -0.0763734 0.0429 -0.0763609 0.04295 -0.0763483 0.043 -0.0763354 0.04305 -0.0763223 0.0431 -0.076309 0.04315 -0.0762955 0.0432 -0.0762819 0.04325 -0.076268 0.0433 -0.0762539 0.04335 -0.0762396 0.0434 -0.0762251 0.04345 -0.0762105 0.0435 -0.0761956 0.04355 -0.0761805 0.0436 -0.0761652 0.04365 -0.0761496 0.0437 -0.0761339 0.04375 -0.076118 0.0438 -0.0761018 0.04385 -0.0760855 0.0439 -0.0760689 0.04395 -0.0760521 0.044 -0.0760351 0.04405 -0.0760179 0.0441 -0.0760005 0.04415 -0.0759829 0.0442 -0.075965 0.04425 -0.0759469 0.0443 -0.0759286 0.04435 -0.0759101 0.0444 -0.0758913 0.04445 -0.0758724 0.0445 -0.0758532 0.04455 -0.0758338 0.0446 -0.0758141 0.04465 -0.0757942 0.0447 -0.0757741 0.04475 -0.0757538 0.0448 -0.0757332 0.04485 -0.0757123 0.0449 -0.0756913 0.04495 -0.0756699 0.045 -0.0756484 0.04505 -0.0756265 0.0451 -0.0756044 0.04515 -0.075582 0.0452 -0.0755594 0.04525 -0.0755364 0.0453 -0.0755132 0.04535 -0.0754897 0.0454 -0.0754658 0.04545 -0.0754416 0.0455 -0.0754171 0.04555 -0.0753922 0.0456 -0.0753668 0.04565 -0.0753411 0.0457 -0.0753148 0.04575 -0.0752881 0.0458 -0.0752607 0.04585 -0.0752327 0.0459 -0.0752039 0.04595 -0.0751742 0.046 -0.0751433 0.04605 -0.0751112 0.0461 -0.0750775 0.04615 -0.0750417 0.0462 -0.0750035 0.04625 -0.0749623 0.0463 -0.0749171 0.04635 -0.0748672 0.0464 -0.0748111 0.04645 -0.0747475 0.0465 -0.0746742 0.04655 -0.0745889 0.0466 -0.0744886 0.04665 -0.0743695 0.0467 -0.0742269 0.04675 -0.0740549 0.0468 -0.0738464 0.04685 -0.0735924 0.0469 -0.073282 0.04695 -0.0729014 0.047 -0.0724338 0.04705 -0.0718584 0.0471 -0.0711493 0.04715 -0.0702748 0.0472 -0.0691952 0.04725 -0.0678619 0.0473 -0.0662138 0.04735 -0.0641751 0.0474 -0.0616499 0.04745 -0.0585153 0.0475 -0.0546101 0.04755 -0.0497126 0.0476 -0.0434991 0.04765 -0.0354611 0.0477 -0.0247683 0.04775 -0.0102255 0.0478 0.00873768 0.04785 0.0285001 0.0479 0.041245 0.04795 0.0455663 0.048 0.0461435 0.04805 0.0457172 0.0481 0.0449529 0.04815 0.0439654 0.0482 0.0427796 0.04825 0.041408 0.0483 0.0398622 0.04835 0.0381552 0.0484 0.0363018 0.04845 0.0343176 0.0485 0.0322191 0.04855 0.0300228 0.0486 0.0277448 0.04865 0.0254006 0.0487 0.0230046 0.04875 0.0205699 0.0488 0.0181084 0.04885 0.0156306 0.0489 0.0131455 0.04895 0.010661 0.049 0.00818354 0.04905 0.0057183 0.0491 0.00326949 0.04915 0.00084025 0.0492 -0.00156719 0.04925 -0.00395143 0.0493 -0.00631185 0.04935 -0.00864855 0.0494 -0.0109623 0.04945 -0.0132547 0.0495 -0.0155279 0.04955 -0.0177854 0.0496 -0.0200316 0.04965 -0.0222724 0.0497 -0.0245158 0.04975 -0.0267725 0.0498 -0.0290563 0.04985 -0.0313854 0.0499 -0.0337832 0.04995 -0.0362793 0.05 -0.0389103 0.05005 -0.0417195 0.0501 -0.0447547 0.05015 -0.0480609 0.0502 -0.0516663 0.05025 -0.0555555 0.0503 -0.0596335 0.05035 -0.0636952 0.0504 -0.0674405 0.05045 -0.070569 0.0505 -0.0729156 0.05055 -0.07451 0.0506 -0.0755138 0.05065 -0.0761153 0.0507 -0.0764666 0.05075 -0.0766702 0.0508 -0.0767884 0.05085 -0.0768576 0.0509 -0.0768985 0.05095 -0.076923 0.051 -0.0769377 0.05105 -0.0769466 0.0511 -0.0769519 0.05115 -0.076955 0.0512 -0.0769566 0.05125 -0.0769574 0.0513 -0.0769575 0.05135 -0.0769573 0.0514 -0.0769568 0.05145 -0.0769561 0.0515 -0.0769552 0.05155 -0.0769543 0.0516 -0.0769533 0.05165 -0.0769522 0.0517 -0.076951 0.05175 -0.0769498 0.0518 -0.0769486 0.05185 -0.0769473 0.0519 -0.076946 0.05195 -0.0769447 0.052 -0.0769433 0.05205 -0.0769419 0.0521 -0.0769405 0.05215 -0.076939 0.0522 -0.0769375 0.05225 -0.0769359 0.0523 -0.0769344 0.05235 -0.0769328 0.0524 -0.0769311 0.05245 -0.0769294 0.0525 -0.0769277 0.05255 -0.0769259 0.0526 -0.0769241 0.05265 -0.0769223 0.0527 -0.0769204 0.05275 -0.0769185 0.0528 -0.0769166 0.05285 -0.0769146 0.0529 -0.0769125 0.05295 -0.0769104 0.053 -0.0769083 0.05305 -0.0769061 0.0531 -0.0769039 0.05315 -0.0769017 0.0532 -0.0768993 0.05325 -0.076897 0.0533 -0.0768946 0.05335 -0.0768921 0.0534 -0.0768896 0.05345 -0.076887 0.0535 -0.0768844 0.05355 -0.0768817 0.0536 -0.0768789 0.05365 -0.0768761 0.0537 -0.0768733 0.05375 -0.0768703 0.0538 -0.0768674 0.05385 -0.0768643 0.0539 -0.0768612 0.05395 -0.076858 0.054 -0.0768547 0.05405 -0.0768514 0.0541 -0.076848 0.05415 -0.0768446 0.0542 -0.076841 0.05425 -0.0768374 0.0543 -0.0768337 0.05435 -0.0768299 0.0544 -0.076826 0.05445 -0.0768221 0.0545 -0.0768181 0.05455 -0.076814 0.0546 -0.0768097 0.05465 -0.0768054 0.0547 -0.076801 0.05475 -0.0767966 0.0548 -0.076792 0.05485 -0.0767873 0.0549 -0.0767825 0.05495 -0.0767776 0.055 -0.0767726 0.05505 -0.0767675 0.0551 -0.0767623 0.05515 -0.076757 0.0552 -0.0767516 0.05525 -0.076746 0.0553 -0.0767403 0.05535 -0.0767345 0.0554 -0.0767286 0.05545 -0.0767226 0.0555 -0.0767164 0.05555 -0.0767102 0.0556 -0.0767037 0.05565 -0.0766972 0.0557 -0.0766905 0.05575 -0.0766837 0.0558 -0.0766767 0.05585 -0.0766696 0.0559 -0.0766624 0.05595 -0.076655 0.056 -0.0766475 0.05605 -0.0766398 0.0561 -0.076632 0.05615 -0.076624 0.0562 -0.0766159 0.05625 -0.0766076 0.0563 -0.0765991 0.05635 -0.0765905 0.0564 -0.0765817 0.05645 -0.0765728 0.0565 -0.0765637 0.05655 -0.0765544 0.0566 -0.076545 0.05665 -0.0765354 0.0567 -0.0765256 0.05675 -0.0765156 0.0568 -0.0765055 0.05685 -0.0764952 0.0569 -0.0764847 0.05695 -0.076474 0.057 -0.0764632 0.05705 -0.0764521 0.0571 -0.0764409 0.05715 -0.0764295 0.0572 -0.0764179 0.05725 -0.0764061 0.0573 -0.0763942 0.05735 -0.076382 0.0574 -0.0763696 0.05745 -0.0763571 0.0575 -0.0763443 0.05755 -0.0763314 0.0576 -0.0763182 0.05765 -0.0763049 0.0577 -0.0762914 0.05775 -0.0762776 0.0578 -0.0762637 0.05785 -0.0762496 0.0579 -0.0762352 0.05795 -0.0762207 0.058 -0.0762059 0.05805 -0.076191 0.0581 -0.0761758 0.05815 -0.0761604 0.0582 -0.0761448 0.05825 -0.0761291 0.0583 -0.0761131 0.05835 -0.0760968 0.0584 -0.0760804 0.05845 -0.0760638 0.0585 -0.0760469 0.05855 -0.0760299 0.0586 -0.0760126 0.05865 -0.0759951 0.0587 -0.0759774 0.05875 -0.0759595 0.0588 -0.0759413 0.05885 -0.075923 0.0589 -0.0759044 0.05895 -0.0758855 0.059 -0.0758665 0.05905 -0.0758472 0.0591 -0.0758278 0.05915 -0.075808 0.0592 -0.0757881 0.05925 -0.0757679 0.0593 -0.0757475 0.05935 -0.0757268 0.0594 -0.0757059 0.05945 -0.0756847 0.0595 -0.0756633 0.05955 -0.0756417 0.0596 -0.0756197 0.05965 -0.0755976 0.0597 -0.0755751 0.05975 -0.0755524 0.0598 -0.0755293 0.05985 -0.075506 0.0599 -0.0754824 0.05995 -0.0754584 0.06 -0.0754341 0.06005 -0.0754095 0.0601 -0.0753844 0.06015 -0.075359 0.0602 -0.075333 0.06025 -0.0753067 0.0603 -0.0752797 0.06035 -0.0752522 0.0604 -0.0752239 0.06045 -0.0751948 0.0605 -0.0751648 0.06055 -0.0751336 0.0606 -0.075101 0.06065 -0.0750667 0.0607 -0.0750302 0.06075 -0.0749911 0.0608 -0.0749488 0.06085 -0.0749022 0.0609 -0.0748505 0.06095 -0.0747923 0.061 -0.0747259 0.06105 -0.0746492 0.0611 -0.0745596 0.06115 -0.0744539 0.0612 -0.074328 0.06125 -0.0741769 0.0613 -0.0739944 0.06135 -0.0737728 0.0614 -0.0735026 0.06145 -0.0731719 0.0615 -0.0727663 0.06155 -0.0722676 0.0616 -0.0716536 0.06165 -0.0708968 0.0617 -0.0699631 0.06175 -0.0688104 0.0618 -0.0673863 0.06185 -0.0656258 0.0619 -0.0634472 0.06195 -0.0607471 0.062 -0.0573925 0.06205 -0.0532062 0.0621 -0.0479408 0.06215 -0.0412265 0.0622 -0.0324713 0.06225 -0.020719 0.0623 -0.00476111 0.06235 0.0151489 0.0624 0.033499 0.06245 0.0432883 0.0625 0.0459542 0.06255 0.0460655 0.0626 0.0455065 0.06265 0.0446679 0.0627 0.0436171 0.06275 0.0423723 0.0628 0.0409451 0.06285 0.0393477 0.0629 0.0375936 0.06295 0.0356977 0.063 0.0336762 0.06305 0.0315455 0.0631 0.0293221 0.06315 0.0270219 0.0632 0.0246601 0.06325 0.0222506 0.0633 0.0198064 0.06335 0.0173387 0.0634 0.0148578 0.06345 0.0123721 0.0635 0.00988915 0.06355 0.00741494 0.0636 0.00495441 0.06365 0.00251139 0.0637 8.87274e-05 0.06375 -0.00231161 0.0638 -0.00468848 0.06385 -0.00704148 0.0639 -0.00937093 0.06395 -0.0116778 0.064 -0.013964 0.06405 -0.0162319 0.0641 -0.0184853 0.06415 -0.0207291 0.0642 -0.0229698 0.06425 -0.0252161 0.0643 -0.0274795 0.06435 -0.0297753 0.0644 -0.032123 0.06445 -0.0345479 0.0645 -0.0370817 0.06455 -0.0397633 0.0646 -0.0426376 0.06465 -0.0457527 0.0647 -0.0491504 0.06475 -0.0528487 0.0648 -0.0568116 0.06485 -0.0609119 0.0649 -0.06491 0.06495 -0.0684917 0.065 -0.0713854 0.06505 -0.0734864 0.0651 -0.0748765 0.06515 -0.0757359 0.0652 -0.0762457 0.06525 -0.0765422 0.0653 -0.076714 0.06535 -0.076814 0.0654 -0.0768727 0.06545 -0.0769075 0.0655 -0.0769284 0.06555 -0.076941 0.0656 -0.0769486 0.06565 -0.076953 0.0657 -0.0769556 0.06575 -0.0769569 0.0658 -0.0769575 0.06585 -0.0769575 0.0659 -0.0769571 0.06595 -0.0769566 0.066 -0.0769558 0.06605 -0.0769549 0.0661 -0.076954 0.06615 -0.0769529 0.0662 -0.0769518 0.06625 -0.0769507 0.0663 -0.0769495 0.06635 -0.0769482 0.0664 -0.0769469 0.06645 -0.0769456 0.0665 -0.0769443 0.06655 -0.0769429 0.0666 -0.0769415 0.06665 -0.07694 0.0667 -0.0769385 0.06675 -0.076937 0.0668 -0.0769355 0.06685 -0.0769339 0.0669 -0.0769322 0.06695 -0.0769306 0.067 -0.0769289 0.06705 -0.0769272 0.0671 -0.0769254 0.06715 -0.0769236 0.0672 -0.0769217 0.06725 -0.0769198 0.0673 -0.0769179 0.06735 -0.0769159 0.0674 -0.0769139 0.06745 -0.0769119 0.0675 -0.0769098 0.06755 -0.0769076 0.0676 -0.0769055 0.06765 -0.0769032 0.0677 -0.0769009 0.06775 -0.0768986 0.0678 -0.0768962 0.06785 -0.0768938 0.0679 -0.0768913 0.06795 -0.0768888 0.068 -0.0768862 0.06805 -0.0768835 0.0681 -0.0768808 0.06815 -0.0768781 0.0682 -0.0768752 0.06825 -0.0768724 0.0683 -0.0768694 0.06835 -0.0768664 0.0684 -0.0768633 0.06845 -0.0768602 0.0685 -0.076857 0.06855 -0.0768537 0.0686 -0.0768504 0.06865 -0.076847 0.0687 -0.0768435 0.06875 -0.0768399 0.0688 -0.0768362 0.06885 -0.0768325 0.0689 -0.0768287 0.06895 -0.0768248 0.069 -0.0768209 0.06905 -0.0768168 0.0691 -0.0768126 0.06915 -0.0768084 0.0692 -0.0768041 0.06925 -0.0767997 0.0693 -0.0767951 0.06935 -0.0767905 0.0694 -0.0767858 0.06945 -0.076781 0.0695 -0.0767761 0.06955 -0.076771 0.0696 -0.0767659 0.06965 -0.0767607 0.0697 -0.0767553 0.06975 -0.0767498 0.0698 -0.0767443 0.06985 -0.0767385 0.0699 -0.0767327 0.06995 -0.0767268 0.07 -0.0767207 0.07005 -0.0767145 0.0701 -0.0767082 0.07015 -0.0767017 0.0702 -0.0766951 0.07025 -0.0766884 0.0703 -0.0766815 0.07035 -0.0766745 0.0704 -0.0766674 0.07045 -0.0766601 0.0705 -0.0766527 0.07055 -0.0766451 0.0706 -0.0766374 0.07065 -0.0766295 0.0707 -0.0766215 0.07075 -0.0766133 0.0708 -0.0766049 0.07085 -0.0765964 0.0709 -0.0765878 0.07095 -0.076579 0.071 -0.07657 0.07105 -0.0765608 0.0711 -0.0765515 0.07115 -0.076542 0.0712 -0.0765323 0.07125 -0.0765225 0.0713 -0.0765125 0.07135 -0.0765023 0.0714 -0.0764919 0.07145 -0.0764814 0.0715 -0.0764707 0.07155 -0.0764597 0.0716 -0.0764487 0.07165 -0.0764374 0.0717 -0.0764259 0.07175 -0.0764143 0.0718 -0.0764024 0.07185 -0.0763904 0.0719 -0.0763782 0.07195 -0.0763657 0.072 -0.0763531 0.07205 -0.0763403 0.0721 -0.0763273 0.07215 -0.0763141 0.0722 -0.0763007 0.07225 -0.0762871 0.0723 -0.0762733 0.07235 -0.0762593 0.0724 -0.0762451 0.07245 -0.0762307 0.0725 -0.0762161 0.07255 -0.0762013 0.0726 -0.0761863 0.07265 -0.076171 0.0727 -0.0761556 0.07275 -0.0761399 0.0728 -0.0761241 0.07285 -0.076108 0.0729 -0.0760917 0.07295 -0.0760753 0.073 -0.0760586 0.07305 -0.0760416 0.0731 -0.0760245 0.07315 -0.0760072 0.0732 -0.0759896 0.07325 -0.0759718 0.0733 -0.0759538 0.07335 -0.0759356 0.0734 -0.0759172 0.07345 -0.0758985 0.0735 -0.0758796 0.07355 -0.0758605 0.0736 -0.0758412 0.07365 -0.0758216 0.0737 -0.0758018 0.07375 -0.0757818 0.0738 -0.0757615 0.07385 -0.075741 0.0739 -0.0757203 0.07395 -0.0756993 0.074 -0.0756781 0.07405 -0.0756566 0.0741 -0.0756349 0.07415 -0.0756129 0.0742 -0.0755906 0.07425 -0.075568 0.0743 -0.0755452 0.07435 -0.0755221 0.0744 -0.0754987 0.07445 -0.0754749 0.0745 -0.0754509 0.07455 -0.0754265 0.0746 -0.0754017 0.07465 -0.0753765 0.0747 -0.0753509 0.07475 -0.0753249 0.0748 -0.0752983 0.07485 -0.0752712 0.0749 -0.0752434 0.07495 -0.0752149 0.075 -0.0751856 0.07505 -0.0751552 0.0751 -0.0751236 0.07515 -0.0750905 0.0752 -0.0750555 0.07525 -0.0750183 0.0753 -0.0749783 0.07535 -0.0749347 0.0754 -0.0748867 0.07545 -0.0748331 0.0755 -0.0747726 0.07555 -0.0747032 0.0756 -0.0746228 0.07565 -0.0745285 0.0757 -0.074417 0.07575 -0.0742838 0.0758 -0.0741237 0.07585 -0.0739299 0.0759 -0.0736942 0.07595 -0.0734065 0.076 -0.0730542 0.07605 -0.0726216 0.0761 -0.0720896 0.07615 -0.0714342 0.0762 -0.0706263 0.07625 -0.0696292 0.0763 -0.068398 0.07635 -0.0668766 0.0764 -0.0649952 0.07645 -0.0626662 0.0765 -0.0597779 0.07655 -0.0561855 0.0766 -0.0516934 0.07665 -0.0460235 0.0767 -0.0387502 0.07675 -0.0291825 0.0768 -0.0162396 0.07685 0.00115205 0.0769 0.0214599 0.07695 0.0375921 0.077 0.0446316 0.07705 0.046123 0.0771 0.0459316 0.07715 0.0452696 0.0772 0.0443616 0.07725 0.0432489 0.0773 0.041946 0.07735 0.0404644 0.0774 0.0388165 0.07745 0.0370165 0.0775 0.0350797 0.07755 0.0330223 0.0776 0.0308609 0.07765 0.0286119 0.0777 0.0262909 0.07775 0.0239127 0.0778 0.0214911 0.07785 0.0190384 0.0779 0.0165656 0.07795 0.0140823 0.078 0.0115967 0.07805 0.0091159 0.0781 0.00664549 0.07815 0.00419005 0.0782 0.00175312 0.07825 -0.000662741 0.0783 -0.00305584 0.07835 -0.00542528 0.0784 -0.00777086 0.07845 -0.0100931 0.0785 -0.0123933 0.07855 -0.0146735 0.0786 -0.0169365 0.07865 -0.0191863 0.0787 -0.0214283 0.07875 -0.0236696 0.0788 -0.0259199 0.07885 -0.0281916 0.0789 -0.0305012 0.07895 -0.03287 0.079 -0.0353252 0.07905 -0.0379006 0.0791 -0.0406372 0.07915 -0.0435816 0.0792 -0.046781 0.07925 -0.0502724 0.0793 -0.0540607 0.07935 -0.0580853 0.0794 -0.0621845 0.07945 -0.0660873 0.0795 -0.0694774 0.07955 -0.0721254 0.0796 -0.0739889 0.07965 -0.0751925 0.0797 -0.0759251 0.07975 -0.0763561 0.0798 -0.0766061 0.07985 -0.0767511 0.0799 -0.0768357 0.07995 -0.0768855 0.08 -0.0769152 0.08005 -0.076933 0.0801 -0.0769438 0.08015 -0.0769502 0.0802 -0.076954 0.08025 -0.0769561 0.0803 -0.0769572 0.08035 -0.0769575 0.0804 -0.0769574 0.08045 -0.076957 0.0805 -0.0769564 0.08055 -0.0769556 0.0806 -0.0769546 0.08065 -0.0769537 0.0807 -0.0769526 0.08075 -0.0769515 0.0808 -0.0769503 0.08085 -0.0769491 0.0809 -0.0769478 0.08095 -0.0769465 0.081 -0.0769452 0.08105 -0.0769438 0.0811 -0.0769424 0.08115 -0.076941 0.0812 -0.0769396 0.08125 -0.0769381 0.0813 -0.0769365 0.08135 -0.076935 0.0814 -0.0769334 0.08145 -0.0769317 0.0815 -0.0769301 0.08155 -0.0769283 0.0816 -0.0769266 0.08165 -0.0769248 0.0817 -0.076923 0.08175 -0.0769211 0.0818 -0.0769192 0.08185 -0.0769173 0.0819 -0.0769153 0.08195 -0.0769133 0.082 -0.0769112 0.08205 -0.0769091 0.0821 -0.076907 0.08215 -0.0769048 0.0822 -0.0769025 0.08225 -0.0769002 0.0823 -0.0768979 0.08235 -0.0768955 0.0824 -0.076893 0.08245 -0.0768905 0.0825 -0.076888 0.08255 -0.0768854 0.0826 -0.0768827 0.08265 -0.07688 0.0827 -0.0768772 0.08275 -0.0768744 0.0828 -0.0768715 0.08285 -0.0768685 0.0829 -0.0768655 0.08295 -0.0768624 0.083 -0.0768592 0.08305 -0.076856 0.0831 -0.0768527 0.08315 -0.0768493 0.0832 -0.0768459 0.08325 -0.0768424 0.0833 -0.0768388 0.08335 -0.0768351 0.0834 -0.0768313 0.08345 -0.0768275 0.0835 -0.0768236 0.08355 -0.0768196 0.0836 -0.0768155 0.08365 -0.0768113 0.0837 -0.0768071 0.08375 -0.0768027 0.0838 -0.0767983 0.08385 -0.0767937 0.0839 -0.0767891 0.08395 -0.0767843 0.084 -0.0767795 0.08405 -0.0767745 0.0841 -0.0767695 0.08415 -0.0767643 0.0842 -0.076759 0.08425 -0.0767536 0.0843 -0.0767481 0.08435 -0.0767425 0.0844 -0.0767367 0.08445 -0.0767309 0.0845 -0.0767249 0.08455 -0.0767188 0.0846 -0.0767125 0.08465 -0.0767062 0.0847 -0.0766997 0.08475 -0.076693 0.0848 -0.0766863 0.08485 -0.0766794 0.0849 -0.0766723 0.08495 -0.0766651 0.085 -0.0766578 0.08505 -0.0766503 0.0851 -0.0766427 0.08515 -0.0766349 0.0852 -0.076627 0.08525 -0.0766189 0.0853 -0.0766107 0.08535 -0.0766023 0.0854 -0.0765938 0.08545 -0.076585 0.0855 -0.0765762 0.08555 -0.0765671 0.0856 -0.0765579 0.08565 -0.0765485 0.0857 -0.076539 0.08575 -0.0765293 0.0858 -0.0765194 0.08585 -0.0765093 0.0859 -0.0764991 0.08595 -0.0764887 0.086 -0.0764781 0.08605 -0.0764673 0.0861 -0.0764563 0.08615 -0.0764452 0.0862 -0.0764338 0.08625 -0.0764223 0.0863 -0.0764106 0.08635 -0.0763987 0.0864 -0.0763866 0.08645 -0.0763743 0.0865 -0.0763618 0.08655 -0.0763492 0.0866 -0.0763363 0.08665 -0.0763232 0.0867 -0.07631 0.08675 -0.0762965 0.0868 -0.0762828 0.08685 -0.076269 0.0869 -0.0762549 0.08695 -0.0762406 0.087 -0.0762262 0.08705 -0.0762115 0.0871 -0.0761966 0.08715 -0.0761815 0.0872 -0.0761662 0.08725 -0.0761507 0.0873 -0.076135 0.08735 -0.0761191 0.0874 -0.076103 0.08745 -0.0760866 0.0875 -0.0760701 0.08755 -0.0760533 0.0876 -0.0760363 0.08765 -0.0760191 0.0877 -0.0760017 0.08775 -0.0759841 0.0878 -0.0759662 0.08785 -0.0759482 0.0879 -0.0759299 0.08795 -0.0759114 0.088 -0.0758927 0.08805 -0.0758737 0.0881 -0.0758545 0.08815 -0.0758351 0.0882 -0.0758155 0.08825 -0.0757956 0.0883 -0.0757755 0.08835 -0.0757552 0.0884 -0.0757346 0.08845 -0.0757138 0.0885 -0.0756927 0.08855 -0.0756714 0.0886 -0.0756498 0.08865 -0.075628 0.0887 -0.0756059 0.08875 -0.0755836 0.0888 -0.075561 0.08885 -0.075538 0.0889 -0.0755148 0.08895 -0.0754913 0.089 -0.0754675 0.08905 -0.0754433 0.0891 -0.0754188 0.08915 -0.0753939 0.0892 -0.0753686 0.08925 -0.0753428 0.0893 -0.0753166 0.08935 -0.0752899 0.0894 -0.0752626 0.08945 -0.0752346 0.0895 -0.0752059 0.08955 -0.0751762 0.0896 -0.0751455 0.08965 -0.0751134 0.0897 -0.0750798 0.08975 -0.0750442 0.0898 -0.0750062 0.08985 -0.0749651 0.0899 -0.0749203 0.08995 -0.0748707 0.09 -0.0748151 0.09005 -0.0747519 0.0901 -0.0746794 0.09015 -0.074595 0.0902 -0.0744957 0.09025 -0.074378 0.0903 -0.074237 0.09035 -0.0740671 0.0904 -0.0738613 0.09045 -0.0736106 0.0905 -0.0733042 0.09055 -0.0729286 0.0906 -0.0724672 0.09065 -0.0718995 0.0907 -0.0712 0.09075 -0.0703373 0.0908 -0.0692724 0.09085 -0.0679573 0.0909 -0.0663318 0.09095 -0.0643211 0.091 -0.0618308 0.09105 -0.0587403 0.0911 -0.054891 0.09115 -0.0500665 0.0912 -0.0439514 0.09125 -0.0360528 0.0913 -0.0255661 0.09135 -0.0113102 0.0914 0.00740621 0.09145 0.0273525 0.0915 0.0407123 0.09155 0.0454471 0.0916 0.0461503 0.09165 0.0457574 0.0917 0.0450096 0.09175 0.0440355 0.0918 0.0428621 0.09185 0.0415022 0.0919 0.0399673 0.09195 0.0382704 0.092 0.0364259 0.09205 0.0344497 0.0921 0.0323581 0.09215 0.0301676 0.0922 0.0278945 0.09225 0.0255541 0.0923 0.023161 0.09235 0.0207284 0.0924 0.0182683 0.09245 0.0157912 0.0925 0.0133064 0.09255 0.0108217 0.0926 0.00834352 0.09265 0.00587734 0.0927 0.00342735 0.09275 0.000996762 0.0928 -0.00141214 0.09285 -0.00379792 0.0929 -0.0061599 0.09295 -0.00849812 0.093 -0.0108133 0.09305 -0.013107 0.0931 -0.0153814 0.09315 -0.0176398 0.0932 -0.0198865 0.09325 -0.0221275 0.0933 -0.0243705 0.09335 -0.0266259 0.0934 -0.0289075 0.09345 -0.031233 0.0935 -0.0336256 0.09355 -0.0361143 0.0936 -0.0387353 0.09365 -0.0415316 0.0937 -0.0445507 0.09375 -0.0478382 0.0938 -0.0514241 0.09385 -0.0552967 0.0939 -0.0593675 0.09395 -0.0634386 0.094 -0.0672142 0.09405 -0.0703896 0.0941 -0.0727879 0.09415 -0.0744269 0.0942 -0.0754629 0.09425 -0.0760853 0.0943 -0.0764492 0.09435 -0.0766601 0.0944 -0.0767825 0.09445 -0.0768541 0.0945 -0.0768965 0.09455 -0.0769218 0.0946 -0.076937 0.09465 -0.0769462 0.0947 -0.0769516 0.09475 -0.0769548 0.0948 -0.0769565 0.09485 -0.0769573 0.0949 -0.0769575 0.09495 -0.0769573 0.095 -0.0769568 0.09505 -0.0769561 0.0951 -0.0769553 0.09515 -0.0769543 0.0952 -0.0769533 0.09525 -0.0769522 0.0953 -0.0769511 0.09535 -0.0769499 0.0954 -0.0769487 0.09545 -0.0769474 0.0955 -0.0769461 0.09555 -0.0769448 0.0956 -0.0769434 0.09565 -0.076942 0.0957 -0.0769406 0.09575 -0.0769391 0.0958 -0.0769376 0.09585 -0.076936 0.0959 -0.0769345 0.09595 -0.0769329 0.096 -0.0769312 0.09605 -0.0769295 0.0961 -0.0769278 0.09615 -0.0769261 0.0962 -0.0769243 0.09625 -0.0769224 0.0963 -0.0769206 0.09635 -0.0769186 0.0964 -0.0769167 0.09645 -0.0769147 0.0965 -0.0769127 0.09655 -0.0769106 0.0966 -0.0769085 0.09665 -0.0769063 0.0967 -0.0769041 0.09675 -0.0769018 0.0968 -0.0768995 0.09685 -0.0768971 0.0969 -0.0768947 0.09695 -0.0768922 0.097 -0.0768897 0.09705 -0.0768872 0.0971 -0.0768845 0.09715 -0.0768819 0.0972 -0.0768791 0.09725 -0.0768763 0.0973 -0.0768735 0.09735 -0.0768705 0.0974 -0.0768676 0.09745 -0.0768645 0.0975 -0.0768614 0.09755 -0.0768582 0.0976 -0.076855 0.09765 -0.0768516 0.0977 -0.0768482 0.09775 -0.0768448 0.0978 -0.0768412 0.09785 -0.0768376 0.0979 -0.0768339 0.09795 -0.0768302 0.098 -0.0768263 0.09805 -0.0768224 0.0981 -0.0768183 0.09815 -0.0768142 0.0982 -0.07681 0.09825 -0.0768057 0.0983 -0.0768013 0.09835 -0.0767969 0.0984 -0.0767923 0.09845 -0.0767876 0.0985 -0.0767828 0.09855 -0.0767779 0.0986 -0.0767729 0.09865 -0.0767679 0.0987 -0.0767626 0.09875 -0.0767573 0.0988 -0.0767519 0.09885 -0.0767464 0.0989 -0.0767407 0.09895 -0.0767349 0.099 -0.076729 0.09905 -0.076723 0.0991 -0.0767168 0.09915 -0.0767106 0.0992 -0.0767042 0.09925 -0.0766976 0.0993 -0.0766909 0.09935 -0.0766841 0.0994 -0.0766772 0.09945 -0.0766701 0.0995 -0.0766629 0.09955 -0.0766555 0.0996 -0.076648 0.09965 -0.0766403 0.0997 -0.0766325 0.09975 -0.0766245 0.0998 -0.0766164 0.09985 -0.0766081 0.0999 -0.0765997 0.09995 -0.0765911 0.1 -0.0765823 0.10005 -0.0765734 0.1001 -0.0765643 0.10015 -0.076555 0.1002 -0.0765456 0.10025 -0.076536 0.1003 -0.0765262 0.10035 -0.0765163 0.1004 -0.0765061 0.10045 -0.0764958 0.1005 -0.0764854 0.10055 -0.0764747 0.1006 -0.0764639 0.10065 -0.0764528 0.1007 -0.0764416 0.10075 -0.0764302 0.1008 -0.0764187 0.10085 -0.0764069 0.1009 -0.0763949 0.10095 -0.0763828 0.101 -0.0763704 0.10105 -0.0763579 0.1011 -0.0763452 0.10115 -0.0763322 0.1012 -0.0763191 0.10125 -0.0763058 0.1013 -0.0762922 0.10135 -0.0762785 0.1014 -0.0762646 0.10145 -0.0762505 0.1015 -0.0762361 0.10155 -0.0762216 0.1016 -0.0762069 0.10165 -0.0761919 0.1017 -0.0761768 0.10175 -0.0761614 0.1018 -0.0761458 0.10185 -0.0761301 0.1019 -0.0761141 0.10195 -0.0760979 0.102 -0.0760815 0.10205 -0.0760649 0.1021 -0.076048 0.10215 -0.076031 0.1022 -0.0760137 0.10225 -0.0759962 0.1023 -0.0759785 0.10235 -0.0759606 0.1024 -0.0759425 0.10245 -0.0759241 0.1025 -0.0759056 0.10255 -0.0758868 0.1026 -0.0758677 0.10265 -0.0758485 0.1027 -0.075829 0.10275 -0.0758093 0.1028 -0.0757894 0.10285 -0.0757692 0.1029 -0.0757488 0.10295 -0.0757281 0.103 -0.0757072 0.10305 -0.0756861 0.1031 -0.0756647 0.10315 -0.0756431 0.1032 -0.0756212 0.10325 -0.075599 0.1033 -0.0755766 0.10335 -0.0755538 0.1034 -0.0755308 0.10345 -0.0755075 0.1035 -0.0754839 0.10355 -0.07546 0.1036 -0.0754357 0.10365 -0.075411 0.1037 -0.075386 0.10375 -0.0753606 0.1038 -0.0753347 0.10385 -0.0753084 0.1039 -0.0752815 0.10395 -0.0752539 0.104 -0.0752257 0.10405 -0.0751967 0.1041 -0.0751667 0.10415 -0.0751356 0.1042 -0.0751031 0.10425 -0.0750689 0.1043 -0.0750326 0.10435 -0.0749937 0.1044 -0.0749516 0.10445 -0.0749054 0.1045 -0.074854 0.10455 -0.0747963 0.1046 -0.0747304 0.10465 -0.0746544 0.1047 -0.0745658 0.10475 -0.0744612 0.1048 -0.0743367 0.10485 -0.0741874 0.1049 -0.0740071 0.10495 -0.0737883 0.105 -0.0735215 0.10505 -0.0731951 0.1051 -0.0727947 0.10515 -0.0723026 0.1052 -0.0716967 0.10525 -0.0709499 0.1053 -0.0700287 0.10535 -0.0688914 0.1054 -0.0674864 0.10545 -0.0657496 0.1055 -0.0636004 0.10555 -0.0609372 0.1056 -0.0576291 0.10565 -0.0535023 0.1057 -0.048315 0.10575 -0.0417077 0.1058 -0.0331066 0.10585 -0.021582 0.1059 -0.00591841 0.10595 0.0138347 0.106 0.0325446 0.10605 0.0429323 0.1061 0.0458956 0.10615 0.0460866 0.1062 0.0455518 0.10625 0.044728 0.1063 0.04369 0.10635 0.0424572 0.1064 0.0410413 0.10645 0.0394544 0.1065 0.0377098 0.10655 0.0358225 0.1066 0.0338085 0.10665 0.0316843 0.1067 0.0294663 0.10675 0.0271705 0.1068 0.0248122 0.10685 0.0224054 0.1069 0.019963 0.10695 0.0174965 0.107 0.0150161 0.10705 0.0125306 0.1071 0.0100472 0.10715 0.0075723 0.1072 0.00511077 0.10725 0.00266653 0.1073 0.000242507 0.10735 -0.0021593 0.1074 -0.00453768 0.10745 -0.00689221 0.1075 -0.00922314 0.10755 -0.0115314 0.1076 -0.0138188 0.10765 -0.0160878 0.1077 -0.0183421 0.10775 -0.0205863 0.1078 -0.0228269 0.10785 -0.0250725 0.1079 -0.0273345 0.10795 -0.0296276 0.108 -0.0319713 0.10805 -0.0343905 0.1081 -0.0369163 0.10815 -0.0395871 0.1082 -0.0424477 0.10825 -0.045546 0.1083 -0.0489248 0.10835 -0.0526043 0.1084 -0.0565529 0.10845 -0.0606505 0.1085 -0.0646641 0.10855 -0.0682817 0.1086 -0.0712245 0.10865 -0.0733753 0.1087 -0.0748058 0.10875 -0.0756933 0.1088 -0.0762207 0.10885 -0.0765278 0.1089 -0.0767056 0.10895 -0.0768091 0.109 -0.0768698 0.10905 -0.0769058 0.1091 -0.0769274 0.10915 -0.0769404 0.1092 -0.0769482 0.10925 -0.0769528 0.1093 -0.0769555 0.10935 -0.0769569 0.1094 -0.0769574 0.10945 -0.0769575 0.1095 -0.0769572 0.10955 -0.0769566 0.1096 -0.0769559 0.10965 -0.076955 0.1097 -0.076954 0.10975 -0.076953 0.1098 -0.0769519 0.10985 -0.0769507 0.1099 -0.0769495 0.10995 -0.0769483 0.11 -0.076947 0.11005 -0.0769457 0.1101 -0.0769444 0.11015 -0.076943 0.1102 -0.0769416 0.11025 -0.0769401 0.1103 -0.0769386 0.11035 -0.0769371 0.1104 -0.0769356 0.11045 -0.076934 0.1105 -0.0769323 0.11055 -0.0769307 0.1106 -0.076929 0.11065 -0.0769273 0.1107 -0.0769255 0.11075 -0.0769237 0.1108 -0.0769218 0.11085 -0.07692 0.1109 -0.076918 0.11095 -0.0769161 0.111 -0.0769141 0.11105 -0.076912 0.1111 -0.0769099 0.11115 -0.0769078 0.1112 -0.0769056 0.11125 -0.0769034 0.1113 -0.0769011 0.11135 -0.0768988 0.1114 -0.0768964 0.11145 -0.0768939 0.1115 -0.0768915 0.11155 -0.0768889 0.1116 -0.0768863 0.11165 -0.0768837 0.1117 -0.076881 0.11175 -0.0768782 0.1118 -0.0768754 0.11185 -0.0768725 0.1119 -0.0768696 0.11195 -0.0768666 0.112 -0.0768635 0.11205 -0.0768604 0.1121 -0.0768572 0.11215 -0.0768539 0.1122 -0.0768506 0.11225 -0.0768472 0.1123 -0.0768437 0.11235 -0.0768401 0.1124 -0.0768365 0.11245 -0.0768328 0.1125 -0.076829 0.11255 -0.0768251 0.1126 -0.0768211 0.11265 -0.0768171 0.1127 -0.0768129 0.11275 -0.0768087 0.1128 -0.0768044 0.11285 -0.0767999 0.1129 -0.0767954 0.11295 -0.0767908 0.113 -0.0767861 0.11305 -0.0767813 0.1131 -0.0767764 0.11315 -0.0767714 0.1132 -0.0767662 0.11325 -0.076761 0.1133 -0.0767557 0.11335 -0.0767502 0.1134 -0.0767446 0.11345 -0.0767389 0.1135 -0.0767331 0.11355 -0.0767272 0.1136 -0.0767211 0.11365 -0.0767149 0.1137 -0.0767086 0.11375 -0.0767021 0.1138 -0.0766956 0.11385 -0.0766888 0.1139 -0.076682 0.11395 -0.076675 0.114 -0.0766679 0.11405 -0.0766606 0.1141 -0.0766532 0.11415 -0.0766456 0.1142 -0.0766379 0.11425 -0.07663 0.1143 -0.076622 0.11435 -0.0766138 0.1144 -0.0766055 0.11445 -0.076597 0.1145 -0.0765883 0.11455 -0.0765795 0.1146 -0.0765705 0.11465 -0.0765614 0.1147 -0.0765521 0.11475 -0.0765426 0.1148 -0.076533 0.11485 -0.0765231 0.1149 -0.0765131 0.11495 -0.0765029 0.115 -0.0764926 0.11505 -0.0764821 0.1151 -0.0764713 0.11515 -0.0764604 0.1152 -0.0764494 0.11525 -0.0764381 0.1153 -0.0764266 0.11535 -0.076415 0.1154 -0.0764032 0.11545 -0.0763912 0.1155 -0.0763789 0.11555 -0.0763665 0.1156 -0.0763539 0.11565 -0.0763411 0.1157 -0.0763281 0.11575 -0.076315 0.1158 -0.0763016 0.11585 -0.076288 0.1159 -0.0762742 0.11595 -0.0762602 0.116 -0.076246 0.11605 -0.0762316 0.1161 -0.076217 0.11615 -0.0762022 0.1162 -0.0761872 0.11625 -0.076172 0.1163 -0.0761566 0.11635 -0.0761409 0.1164 -0.0761251 0.11645 -0.0761091 0.1165 -0.0760928 0.11655 -0.0760763 0.1166 -0.0760596 0.11665 -0.0760427 0.1167 -0.0760256 0.11675 -0.0760083 0.1168 -0.0759907 0.11685 -0.075973 0.1169 -0.075955 0.11695 -0.0759368 0.117 -0.0759184 0.11705 -0.0758997 0.1171 -0.0758808 0.11715 -0.0758618 0.1172 -0.0758424 0.11725 -0.0758229 0.1173 -0.0758031 0.11735 -0.0757831 0.1174 -0.0757628 0.11745 -0.0757424 0.1175 -0.0757216 0.11755 -0.0757007 0.1176 -0.0756794 0.11765 -0.075658 0.1177 -0.0756363 0.11775 -0.0756143 0.1178 -0.075592 0.11785 -0.0755695 0.1179 -0.0755467 0.11795 -0.0755236 0.118 -0.0755002 0.11805 -0.0754765 0.1181 -0.0754524 0.11815 -0.075428 0.1182 -0.0754033 0.11825 -0.0753781 0.1183 -0.0753526 0.11835 -0.0753265 0.1184 -0.0753 0.11845 -0.0752729 0.1185 -0.0752452 0.11855 -0.0752168 0.1186 -0.0751875 0.11865 -0.0751572 0.1187 -0.0751256 0.11875 -0.0750926 0.1188 -0.0750578 0.11885 -0.0750208 0.1189 -0.074981 0.11895 -0.0749376 0.119 -0.0748899 0.11905 -0.0748368 0.1191 -0.0747767 0.11915 -0.0747079 0.1192 -0.0746283 0.11925 -0.074535 0.1193 -0.0744247 0.11935 -0.074293 0.1194 -0.0741348 0.11945 -0.0739434 0.1195 -0.0737107 0.11955 -0.0734266 0.1196 -0.0730788 0.11965 -0.0726519 0.1197 -0.0721268 0.11975 -0.0714802 0.1198 -0.0706829 0.11985 -0.0696991 0.1199 -0.0684844 0.11995 -0.0669834 0.12 -0.0651273 0.12005 -0.0628298 0.1201 -0.0599811 0.12015 -0.0564386 0.1202 -0.052011 0.12025 -0.0464268 0.1203 -0.0392726 0.12035 -0.0298789 0.1204 -0.0171893 0.12045 -8.61952e-05 0.1205 0.0201962 0.12055 0.0368363 0.1206 0.0444062 0.12065 0.0461021 0.1207 0.0459625 0.12075 0.0453199 0.1208 0.0444258 0.12085 0.0433256 0.1209 0.0420344 0.12095 0.0405638 0.121 0.0389261 0.12105 0.0371354 0.1211 0.0352068 0.12115 0.0331566 0.1212 0.0310013 0.12125 0.0287574 0.1213 0.0264405 0.12135 0.0240656 0.1214 0.0216464 0.12145 0.0191953 0.1215 0.0167234 0.12155 0.0142405 0.1216 0.0117549 0.12165 0.00927361 0.1217 0.00680238 0.12175 0.00434588 0.1218 0.00190769 0.12185 -0.000509579 0.1219 -0.00290417 0.12195 -0.00527512 0.122 -0.00762222 0.12205 -0.00994594 0.1221 -0.0122475 0.12215 -0.0145288 0.1222 -0.0167928 0.12225 -0.0190433 0.1223 -0.0212856 0.12235 -0.0235268 0.1224 -0.0257761 0.12245 -0.028046 0.1225 -0.0303527 0.12255 -0.0327169 0.1226 -0.0351657 0.12265 -0.0377323 0.1227 -0.0404573 0.12275 -0.043387 0.1228 -0.0465689 0.12285 -0.0500411 0.1229 -0.0538113 0.12295 -0.0578245 0.123 -0.061926 0.12305 -0.0658508 0.1231 -0.0692821 0.12315 -0.0719808 0.1232 -0.0738918 0.12325 -0.075132 0.1233 -0.075889 0.12335 -0.076335 0.1234 -0.076594 0.12345 -0.076744 0.1235 -0.0768315 0.12355 -0.0768831 0.1236 -0.0769138 0.12365 -0.0769322 0.1237 -0.0769433 0.12375 -0.0769499 0.1238 -0.0769538 0.12385 -0.076956 0.1239 -0.0769571 0.12395 -0.0769575 0.124 -0.0769574 0.12405 -0.076957 0.1241 -0.0769564 0.12415 -0.0769556 0.1242 -0.0769547 0.12425 -0.0769537 0.1243 -0.0769527 0.12435 -0.0769515 0.1244 -0.0769504 0.12445 -0.0769492 0.1245 -0.0769479 0.12455 -0.0769466 0.1246 -0.0769453 0.12465 -0.0769439 0.1247 -0.0769425 0.12475 -0.0769411 0.1248 -0.0769396 0.12485 -0.0769382 0.1249 -0.0769366 0.12495 -0.0769351 0.125 -0.0769335 0.12505 -0.0769318 0.1251 -0.0769302 0.12515 -0.0769285 0.1252 -0.0769267 0.12525 -0.0769249 0.1253 -0.0769231 0.12535 -0.0769213 0.1254 -0.0769194 0.12545 -0.0769174 0.1255 -0.0769154 0.12555 -0.0769134 0.1256 -0.0769114 0.12565 -0.0769093 0.1257 -0.0769071 0.12575 -0.0769049 0.1258 -0.0769027 0.12585 -0.0769004 0.1259 -0.076898 0.12595 -0.0768956 0.126 -0.0768932 0.12605 -0.0768907 0.1261 -0.0768881 0.12615 -0.0768855 0.1262 -0.0768829 0.12625 -0.0768801 0.1263 -0.0768774 0.12635 -0.0768745 0.1264 -0.0768716 0.12645 -0.0768687 0.1265 -0.0768657 0.12655 -0.0768626 0.1266 -0.0768594 0.12665 -0.0768562 0.1267 -0.0768529 0.12675 -0.0768495 0.1268 -0.0768461 0.12685 -0.0768426 0.1269 -0.076839 0.12695 -0.0768353 0.127 -0.0768316 0.12705 -0.0768278 0.1271 -0.0768239 0.12715 -0.0768199 0.1272 -0.0768158 0.12725 -0.0768116 0.1273 -0.0768073 0.12735 -0.076803 0.1274 -0.0767985 0.12745 -0.076794 0.1275 -0.0767894 0.12755 -0.0767846 0.1276 -0.0767798 0.12765 -0.0767748 0.1277 -0.0767698 0.12775 -0.0767646 0.1278 -0.0767593 0.12785 -0.076754 0.1279 -0.0767485 0.12795 -0.0767428 0.128 -0.0767371 0.12805 -0.0767313 0.1281 -0.0767253 0.12815 -0.0767192 0.1282 -0.0767129 0.12825 -0.0767066 0.1283 -0.0767001 0.12835 -0.0766935 0.1284 -0.0766867 0.12845 -0.0766798 0.1285 -0.0766728 0.12855 -0.0766656 0.1286 -0.0766583 0.12865 -0.0766508 0.1287 -0.0766432 0.12875 -0.0766354 0.1288 -0.0766275 0.12885 -0.0766195 0.1289 -0.0766112 0.12895 -0.0766028 0.129 -0.0765943 0.12905 -0.0765856 0.1291 -0.0765767 0.12915 -0.0765677 0.1292 -0.0765585 0.12925 -0.0765491 0.1293 -0.0765396 0.12935 -0.0765299 0.1294 -0.07652 0.12945 -0.07651 0.1295 -0.0764997 0.12955 -0.0764893 0.1296 -0.0764787 0.12965 -0.076468 0.1297 -0.076457 0.12975 -0.0764459 0.1298 -0.0764345 0.12985 -0.076423 0.1299 -0.0764113 0.12995 -0.0763994 0.13 -0.0763874 0.13005 -0.0763751 0.1301 -0.0763626 0.13015 -0.07635 0.1302 -0.0763371 0.13025 -0.0763241 0.1303 -0.0763108 0.13035 -0.0762974 0.1304 -0.0762837 0.13045 -0.0762699 0.1305 -0.0762558 0.13055 -0.0762416 0.1306 -0.0762271 0.13065 -0.0762124 0.1307 -0.0761976 0.13075 -0.0761825 0.1308 -0.0761672 0.13085 -0.0761517 0.1309 -0.076136 0.13095 -0.0761201 0.131 -0.076104 0.13105 -0.0760877 0.1311 -0.0760711 0.13115 -0.0760544 0.1312 -0.0760374 0.13125 -0.0760202 0.1313 -0.0760028 0.13135 -0.0759852 0.1314 -0.0759674 0.13145 -0.0759493 0.1315 -0.0759311 0.13155 -0.0759126 0.1316 -0.0758939 0.13165 -0.0758749 0.1317 -0.0758557 0.13175 -0.0758364 0.1318 -0.0758167 0.13185 -0.0757969 0.1319 -0.0757768 0.13195 -0.0757565 0.132 -0.0757359 0.13205 -0.0757151 0.1321 -0.0756941 0.13215 -0.0756728 0.1322 -0.0756512 0.13225 -0.0756294 0.1323 -0.0756074 0.13235 -0.075585 0.1324 -0.0755624 0.13245 -0.0755395 0.1325 -0.0755163 0.13255 -0.0754928 0.1326 -0.075469 0.13265 -0.0754448 0.1327 -0.0754203 0.13275 -0.0753955 0.1328 -0.0753702 0.13285 -0.0753445 0.1329 -0.0753183 0.13295 -0.0752916 0.133 -0.0752644 0.13305 -0.0752364 0.1331 -0.0752077 0.13315 -0.0751781 0.1332 -0.0751475 0.13325 -0.0751155 0.1333 -0.075082 0.13335 -0.0750465 0.1334 -0.0750087 0.13345 -0.0749679 0.1335 -0.0749233 0.13355 -0.074874 0.1336 -0.0748188 0.13365 -0.0747562 0.1337 -0.0746843 0.13375 -0.0746008 0.1338 -0.0745026 0.13385 -0.0743861 0.1339 -0.0742468 0.13395 -0.0740789 0.134 -0.0738756 0.13405 -0.0736281 0.1341 -0.0733256 0.13415 -0.0729549 0.1342 -0.0724995 0.13425 -0.0719393 0.1343 -0.071249 0.13435 -0.0703978 0.1344 -0.0693471 0.13445 -0.0680495 0.1345 -0.0664459 0.13455 -0.0644623 0.1346 -0.0620058 0.13465 -0.0589577 0.1347 -0.0551625 0.13475 -0.0504081 0.1348 -0.0443874 0.13485 -0.0366223 0.1349 -0.0263324 0.13495 -0.0123533 0.135 0.00610967 0.13505 0.0261989 0.1351 0.0401528 0.13515 0.0453153 0.1352 0.0461534 0.13525 0.0457957 0.1353 0.0450644 0.13535 0.0441036 0.1354 0.0429425 0.13545 0.0415941 0.1355 0.04007 0.13555 0.038383 0.1356 0.0365475 0.13565 0.0345792 0.1357 0.0324944 0.13575 0.0303097 0.1358 0.0280414 0.13585 0.0257048 0.1359 0.0233147 0.13595 0.0208842 0.136 0.0184255 0.13605 0.0159493 0.1361 0.0134647 0.13615 0.0109797 0.1362 0.00850099 0.13625 0.00603392 0.1363 0.00358279 0.13635 0.00115089 0.1364 -0.00125945 0.13645 -0.00364673 0.1365 -0.00601022 0.13655 -0.00834994 0.1366 -0.0106666 0.13665 -0.0129616 0.1367 -0.0152372 0.13675 -0.0174964 0.1368 -0.0197437 0.13685 -0.0219848 0.1369 -0.0242274 0.13695 -0.0264817 0.137 -0.0287611 0.13705 -0.0310833 0.1371 -0.0334707 0.13715 -0.0359523 0.1372 -0.0385637 0.13725 -0.0413474 0.1373 -0.0443509 0.13735 -0.0476202 0.1374 -0.0511868 0.13745 -0.0550428 0.1375 -0.0591056 0.13755 -0.0631848 0.1376 -0.0669889 0.13765 -0.0702097 0.1377 -0.0726589 0.13775 -0.0743425 0.1378 -0.0754112 0.13785 -0.0760547 0.1379 -0.0764315 0.13795 -0.0766498 0.138 -0.0767765 0.13805 -0.0768506 0.1381 -0.0768944 0.13815 -0.0769205 0.1382 -0.0769362 0.13825 -0.0769457 0.1383 -0.0769514 0.13835 -0.0769547 0.1384 -0.0769565 0.13845 -0.0769573 0.1385 -0.0769575 0.13855 -0.0769573 0.1386 -0.0769568 0.13865 -0.0769562 0.1387 -0.0769553 0.13875 -0.0769544 0.1388 -0.0769534 0.13885 -0.0769523 0.1389 -0.0769512 0.13895 -0.07695 0.139 -0.0769488 0.13905 -0.0769475 0.1391 -0.0769462 0.13915 -0.0769449 0.1392 -0.0769435 0.13925 -0.0769421 0.1393 -0.0769407 0.13935 -0.0769392 0.1394 -0.0769377 0.13945 -0.0769361 0.1395 -0.0769346 0.13955 -0.076933 0.1396 -0.0769313 0.13965 -0.0769296 0.1397 -0.0769279 0.13975 -0.0769262 0.1398 -0.0769244 0.13985 -0.0769225 0.1399 -0.0769207 0.13995 -0.0769188 0.14 -0.0769168 0.14005 -0.0769148 0.1401 -0.0769128 0.14015 -0.0769107 0.1402 -0.0769086 0.14025 -0.0769064 0.1403 -0.0769042 0.14035 -0.0769019 0.1404 -0.0768996 0.14045 -0.0768973 0.1405 -0.0768949 0.14055 -0.0768924 0.1406 -0.0768899 0.14065 -0.0768873 0.1407 -0.0768847 0.14075 -0.076882 0.1408 -0.0768793 0.14085 -0.0768765 0.1409 -0.0768736 0.14095 -0.0768707 0.141 -0.0768677 0.14105 -0.0768647 0.1411 -0.0768616 0.14115 -0.0768584 0.1412 -0.0768552 0.14125 -0.0768519 0.1413 -0.0768485 0.14135 -0.076845 0.1414 -0.0768415 0.14145 -0.0768379 0.1415 -0.0768342 0.14155 -0.0768304 0.1416 -0.0768265 0.14165 -0.0768226 0.1417 -0.0768186 0.14175 -0.0768145 0.1418 -0.0768103 0.14185 -0.076806 0.1419 -0.0768016 0.14195 -0.0767971 0.142 -0.0767926 0.14205 -0.0767879 0.1421 -0.0767831 0.14215 -0.0767782 0.1422 -0.0767733 0.14225 -0.0767682 0.1423 -0.076763 0.14235 -0.0767577 0.1424 -0.0767523 0.14245 -0.0767467 0.1425 -0.0767411 0.14255 -0.0767353 0.1426 -0.0767294 0.14265 -0.0767234 0.1427 -0.0767172 0.14275 -0.076711 0.1428 -0.0767046 0.14285 -0.076698 0.1429 -0.0766914 0.14295 -0.0766846 0.143 -0.0766776 0.14305 -0.0766706 0.1431 -0.0766633 0.14315 -0.076656 0.1432 -0.0766485 0.14325 -0.0766408 0.1433 -0.076633 0.14335 -0.076625 0.1434 -0.0766169 0.14345 -0.0766086 0.1435 -0.0766002 0.14355 -0.0765916 0.1436 -0.0765829 0.14365 -0.0765739 0.1437 -0.0765649 0.14375 -0.0765556 0.1438 -0.0765462 0.14385 -0.0765366 0.1439 -0.0765268 0.14395 -0.0765169 0.144 -0.0765068 0.14405 -0.0764965 0.1441 -0.076486 0.14415 -0.0764754 0.1442 -0.0764646 0.14425 -0.0764535 0.1443 -0.0764424 0.14435 -0.076431 0.1444 -0.0764194 0.14445 -0.0764076 0.1445 -0.0763957 0.14455 -0.0763836 0.1446 -0.0763712 0.14465 -0.0763587 0.1447 -0.076346 0.14475 -0.0763331 0.1448 -0.0763199 0.14485 -0.0763066 0.1449 -0.0762931 0.14495 -0.0762794 0.145 -0.0762655 0.14505 -0.0762514 0.1451 -0.0762371 0.14515 -0.0762225 0.1452 -0.0762078 0.14525 -0.0761929 0.1453 -0.0761777 0.14535 -0.0761624 0.1454 -0.0761468 0.14545 -0.0761311 0.1455 -0.0761151 0.14555 -0.0760989 0.1456 -0.0760825 0.14565 -0.0760659 0.1457 -0.0760491 0.14575 -0.0760321 0.1458 -0.0760148 0.14585 -0.0759974 0.1459 -0.0759797 0.14595 -0.0759618 0.146 -0.0759437 0.14605 -0.0759253 0.1461 -0.0759067 0.14615 -0.075888 0.1462 -0.075869 0.14625 -0.0758497 0.1463 -0.0758303 0.14635 -0.0758106 0.1464 -0.0757906 0.14645 -0.0757705 0.1465 -0.0757501 0.14655 -0.0757295 0.1466 -0.0757086 0.14665 -0.0756875 0.1467 -0.0756661 0.14675 -0.0756445 0.1468 -0.0756226 0.14685 -0.0756004 0.1469 -0.075578 0.14695 -0.0755553 0.147 -0.0755323 0.14705 -0.075509 0.1471 -0.0754854 0.14715 -0.0754615 0.1472 -0.0754372 0.14725 -0.0754126 0.1473 -0.0753876 0.14735 -0.0753622 0.1474 -0.0753364 0.14745 -0.0753101 0.1475 -0.0752832 0.14755 -0.0752557 0.1476 -0.0752275 0.14765 -0.0751986 0.1477 -0.0751687 0.14775 -0.0751376 0.1478 -0.0751052 0.14785 -0.0750712 0.1479 -0.075035 0.14795 -0.0749963 0.148 -0.0749544 0.14805 -0.0749084 0.1481 -0.0748575 0.14815 -0.0748002 0.1482 -0.0747349 0.14825 -0.0746596 0.1483 -0.0745718 0.14835 -0.0744684 0.1484 -0.0743453 0.14845 -0.0741978 0.1485 -0.0740197 0.14855 -0.0738035 0.1486 -0.0735401 0.14865 -0.0732179 0.1487 -0.0728227 0.14875 -0.072337 0.1488 -0.0717391 0.14885 -0.0710023 0.1489 -0.0700933 0.14895 -0.0689712 0.149 -0.067585 0.14905 -0.0658715 0.1491 -0.0637513 0.14915 -0.0611244 0.1492 -0.057862 0.14925 -0.0537936 0.1493 -0.048683 0.14935 -0.0421803 0.1494 -0.0337294 0.14945 -0.0224266 0.1495 -0.00705544 0.14955 0.0125199 0.1496 0.0315537 0.14965 0.0425458 0.1497 0.0458273 0.14975 0.0461053 0.1498 0.045596 0.14985 0.0447871 0.1499 0.043762 0.14995 0.0425413 0.15 0.0411367 0.15005 0.0395603 0.1501 0.0378253 0.15015 0.0359466 0.1502 0.0339402 0.15025 0.0318225 0.1503 0.02961 0.15035 0.0273187 0.1504 0.0249639 0.15045 0.0225598 0.1505 0.0201193 0.15055 0.0176541 0.1506 0.0151743 0.15065 0.0126888 0.1507 0.0102051 0.15075 0.00772952 0.1508 0.00526701 0.15085 0.00282158 0.1509 0.000396195 0.15095 -0.00200707 0.151 -0.00438697 0.15105 -0.00674301 0.1511 -0.00907542 0.15115 -0.0113851 0.1512 -0.0136738 0.15125 -0.0159439 0.1513 -0.0181989 0.15135 -0.0204436 0.1514 -0.0226842 0.15145 -0.0249292 0.1515 -0.0271897 0.15155 -0.0294804 0.1516 -0.0318202 0.15165 -0.0342336 0.1517 -0.0367516 0.15175 -0.0394119 0.1518 -0.042259 0.15185 -0.0453408 0.1519 -0.0487008 0.15195 -0.0523613 0.152 -0.0562953 0.15205 -0.0603891 0.1521 -0.064417 0.15215 -0.0680691 0.1522 -0.0710606 0.15225 -0.0732614 0.1523 -0.074733 0.15235 -0.0756493 0.1524 -0.0761949 0.15245 -0.0765128 0.1525 -0.0766969 0.15255 -0.076804 0.1526 -0.0768668 0.15265 -0.076904 0.1527 -0.0769263 0.15275 -0.0769397 0.1528 -0.0769478 0.15285 -0.0769526 0.1529 -0.0769553 0.15295 -0.0769568 0.153 -0.0769574 0.15305 -0.0769575 0.1531 -0.0769572 0.15315 -0.0769567 0.1532 -0.0769559 0.15325 -0.0769551 0.1533 -0.0769541 0.15335 -0.0769531 0.1534 -0.076952 0.15345 -0.0769508 0.1535 -0.0769496 0.15355 -0.0769484 0.1536 -0.0769471 0.15365 -0.0769458 0.1537 -0.0769444 0.15375 -0.0769431 0.1538 -0.0769416 0.15385 -0.0769402 0.1539 -0.0769387 0.15395 -0.0769372 0.154 -0.0769357 0.15405 -0.0769341 0.1541 -0.0769325 0.15415 -0.0769308 0.1542 -0.0769291 0.15425 -0.0769274 0.1543 -0.0769256 0.15435 -0.0769238 0.1544 -0.076922 0.15445 -0.0769201 0.1545 -0.0769182 0.15455 -0.0769162 0.1546 -0.0769142 0.15465 -0.0769121 0.1547 -0.0769101 0.15475 -0.0769079 0.1548 -0.0769057 0.15485 -0.0769035 0.1549 -0.0769012 0.15495 -0.0768989 0.155 -0.0768965 0.15505 -0.0768941 0.1551 -0.0768916 0.15515 -0.0768891 0.1552 -0.0768865 0.15525 -0.0768839 0.1553 -0.0768812 0.15535 -0.0768784 0.1554 -0.0768756 0.15545 -0.0768727 0.1555 -0.0768698 0.15555 -0.0768668 0.1556 -0.0768637 0.15565 -0.0768606 0.1557 -0.0768574 0.15575 -0.0768541 0.1558 -0.0768508 0.15585 -0.0768474 0.1559 -0.0768439 0.15595 -0.0768404 0.156 -0.0768367 0.15605 -0.076833 0.1561 -0.0768292 0.15615 -0.0768253 0.1562 -0.0768214 0.15625 -0.0768173 0.1563 -0.0768132 0.15635 -0.076809 0.1564 -0.0768046 0.15645 -0.0768002 0.1565 -0.0767957 0.15655 -0.0767911 0.1566 -0.0767864 0.15665 -0.0767816 0.1567 -0.0767767 0.15675 -0.0767717 0.1568 -0.0767666 0.15685 -0.0767613 0.1569 -0.076756 0.15695 -0.0767505 0.157 -0.076745 0.15705 -0.0767393 0.1571 -0.0767335 0.15715 -0.0767275 0.1572 -0.0767215 0.15725 -0.0767153 0.1573 -0.076709 0.15735 -0.0767026 0.1574 -0.076696 0.15745 -0.0766893 0.1575 -0.0766824 0.15755 -0.0766754 0.1576 -0.0766683 0.15765 -0.0766611 0.1577 -0.0766536 0.15775 -0.0766461 0.1578 -0.0766384 0.15785 -0.0766305 0.1579 -0.0766225 0.15795 -0.0766143 0.158 -0.076606 0.15805 -0.0765975 0.1581 -0.0765889 0.15815 -0.0765801 0.1582 -0.0765711 0.15825 -0.076562 0.1583 -0.0765527 0.15835 -0.0765432 0.1584 -0.0765336 0.15845 -0.0765238 0.1585 -0.0765138 0.15855 -0.0765036 0.1586 -0.0764933 0.15865 -0.0764827 0.1587 -0.076472 0.15875 -0.0764611 0.1588 -0.0764501 0.15885 -0.0764388 0.1589 -0.0764274 0.15895 -0.0764157 0.159 -0.0764039 0.15905 -0.0763919 0.1591 -0.0763797 0.15915 -0.0763673 0.1592 -0.0763547 0.15925 -0.076342 0.1593 -0.076329 0.15935 -0.0763158 0.1594 -0.0763024 0.15945 -0.0762889 0.1595 -0.0762751 0.15955 -0.0762611 0.1596 -0.0762469 0.15965 -0.0762326 0.1597 -0.076218 0.15975 -0.0762032 0.1598 -0.0761882 0.15985 -0.076173 0.1599 -0.0761576 0.15995 -0.0761419 0.16 -0.0761261 0.16005 -0.0761101 0.1601 -0.0760938 0.16015 -0.0760774 0.1602 -0.0760607 0.16025 -0.0760438 0.1603 -0.0760267 0.16035 -0.0760094 0.1604 -0.0759919 0.16045 -0.0759741 0.1605 -0.0759561 0.16055 -0.0759379 0.1606 -0.0759195 0.16065 -0.0759009 0.1607 -0.0758821 0.16075 -0.075863 0.1608 -0.0758437 0.16085 -0.0758241 0.1609 -0.0758044 0.16095 -0.0757844 0.161 -0.0757641 0.16105 -0.0757437 0.1611 -0.075723 0.16115 -0.075702 0.1612 -0.0756808 0.16125 -0.0756594 0.1613 -0.0756376 0.16135 -0.0756157 0.1614 -0.0755934 0.16145 -0.0755709 0.1615 -0.0755481 0.16155 -0.0755251 0.1616 -0.0755017 0.16165 -0.075478 0.1617 -0.075454 0.16175 -0.0754296 0.1618 -0.0754049 0.16185 -0.0753797 0.1619 -0.0753542 0.16195 -0.0753282 0.162 -0.0753017 0.16205 -0.0752747 0.1621 -0.075247 0.16215 -0.0752186 0.1622 -0.0751894 0.16225 -0.0751591 0.1623 -0.0751277 0.16235 -0.0750948 0.1624 -0.0750601 0.16245 -0.0750232 0.1625 -0.0749836 0.16255 -0.0749405 0.1626 -0.0748931 0.16265 -0.0748403 0.1627 -0.0747807 0.16275 -0.0747126 0.1628 -0.0746337 0.16285 -0.0745414 0.1629 -0.0744323 0.16295 -0.0743021 0.163 -0.0741458 0.16305 -0.0739567 0.1631 -0.0737269 0.16315 -0.0734465 0.1632 -0.0731031 0.16325 -0.0726818 0.1633 -0.0721636 0.16335 -0.0715255 0.1634 -0.0707388 0.16345 -0.0697681 0.1635 -0.0685696 0.16355 -0.0670887 0.1636 -0.0652577 0.16365 -0.0629913 0.1637 -0.0601814 0.16375 -0.0566882 0.1638 -0.052324 0.16385 -0.0468237 0.1639 -0.039786 0.16395 -0.0305619 0.164 -0.0181204 0.16405 -0.00130892 0.1641 0.0189177 0.16415 0.0360395 0.1642 0.0441573 0.16425 0.0460749 0.1643 0.0459918 0.16435 0.0453692 0.1644 0.044489 0.16445 0.0434014 0.1645 0.0421221 0.16455 0.0406626 0.1646 0.0390351 0.16465 0.0372536 0.1647 0.0353334 0.16475 0.0332904 0.1648 0.0311414 0.16485 0.0289026 0.1649 0.0265899 0.16495 0.0242182 0.165 0.0218015 0.16505 0.0193521 0.1651 0.0168812 0.16515 0.0143988 0.1652 0.0119131 0.16525 0.00943135 0.1653 0.00695933 0.16535 0.00450177 0.1654 0.00206232 0.16545 -0.000356337 0.1655 -0.0027524 0.16555 -0.00512488 0.1656 -0.00747348 0.16565 -0.00979867 0.1657 -0.0121016 0.16575 -0.0143841 0.1658 -0.0166491 0.16585 -0.0189003 0.1659 -0.021143 0.16595 -0.0233839 0.166 -0.0256324 0.16605 -0.0279006 0.1661 -0.0302043 0.16615 -0.0325642 0.1662 -0.0350066 0.16625 -0.0375646 0.1663 -0.0402782 0.16635 -0.0431934 0.1664 -0.0463579 0.16645 -0.0498109 0.1665 -0.053563 0.16655 -0.0575642 0.1666 -0.0616669 0.16665 -0.0656124 0.1667 -0.0690838 0.16675 -0.0718329 0.1668 -0.073792 0.16685 -0.0750694 0.1669 -0.0758516 0.16695 -0.0763133 0.167 -0.0765814 0.16705 -0.0767367 0.1671 -0.0768273 0.16715 -0.0768805 0.1672 -0.0769122 0.16725 -0.0769312 0.1673 -0.0769427 0.16735 -0.0769496 0.1674 -0.0769536 0.16745 -0.0769559 0.1675 -0.0769571 0.16755 -0.0769575 0.1676 -0.0769574 0.16765 -0.0769571 0.1677 -0.0769564 0.16775 -0.0769557 0.1678 -0.0769548 0.16785 -0.0769538 0.1679 -0.0769527 0.16795 -0.0769516 0.168 -0.0769504 0.16805 -0.0769492 0.1681 -0.076948 0.16815 -0.0769467 0.1682 -0.0769454 0.16825 -0.076944 0.1683 -0.0769426 0.16835 -0.0769412 0.1684 -0.0769397 0.16845 -0.0769382 0.1685 -0.0769367 0.16855 -0.0769352 0.1686 -0.0769336 0.16865 -0.0769319 0.1687 -0.0769303 0.16875 -0.0769286 0.1688 -0.0769268 0.16885 -0.0769251 0.1689 -0.0769232 0.16895 -0.0769214 0.169 -0.0769195 0.16905 -0.0769176 0.1691 -0.0769156 0.16915 -0.0769136 0.1692 -0.0769115 0.16925 -0.0769094 0.1693 -0.0769072 0.16935 -0.076905 0.1694 -0.0769028 0.16945 -0.0769005 0.1695 -0.0768982 0.16955 -0.0768958 0.1696 -0.0768933 0.16965 -0.0768908 0.1697 -0.0768883 0.16975 -0.0768857 0.1698 -0.076883 0.16985 -0.0768803 0.1699 -0.0768775 0.16995 -0.0768747 0.17 -0.0768718 0.17005 -0.0768689 0.1701 -0.0768658 0.17015 -0.0768628 0.1702 -0.0768596 0.17025 -0.0768564 0.1703 -0.0768531 0.17035 -0.0768497 0.1704 -0.0768463 0.17045 -0.0768428 0.1705 -0.0768392 0.17055 -0.0768356 0.1706 -0.0768318 0.17065 -0.076828 0.1707 -0.0768241 0.17075 -0.0768201 0.1708 -0.076816 0.17085 -0.0768119 0.1709 -0.0768076 0.17095 -0.0768033 0.171 -0.0767988 0.17105 -0.0767943 0.1711 -0.0767897 0.17115 -0.0767849 0.1712 -0.0767801 0.17125 -0.0767751 0.1713 -0.0767701 0.17135 -0.0767649 0.1714 -0.0767597 0.17145 -0.0767543 0.1715 -0.0767488 0.17155 -0.0767432 0.1716 -0.0767375 0.17165 -0.0767316 0.1717 -0.0767257 0.17175 -0.0767196 0.1718 -0.0767133 0.17185 -0.076707 0.1719 -0.0767005 0.17195 -0.0766939 0.172 -0.0766871 0.17205 -0.0766803 0.1721 -0.0766732 0.17215 -0.0766661 0.1722 -0.0766588 0.17225 -0.0766513 0.1723 -0.0766437 0.17235 -0.0766359 0.1724 -0.076628 0.17245 -0.07662 0.1725 -0.0766118 0.17255 -0.0766034 0.1726 -0.0765949 0.17265 -0.0765862 0.1727 -0.0765773 0.17275 -0.0765683 0.1728 -0.0765591 0.17285 -0.0765497 0.1729 -0.0765402 0.17295 -0.0765305 0.173 -0.0765207 0.17305 -0.0765106 0.1731 -0.0765004 0.17315 -0.07649 0.1732 -0.0764794 0.17325 -0.0764687 0.1733 -0.0764577 0.17335 -0.0764466 0.1734 -0.0764353 0.17345 -0.0764238 0.1735 -0.0764121 0.17355 -0.0764002 0.1736 -0.0763881 0.17365 -0.0763759 0.1737 -0.0763634 0.17375 -0.0763508 0.1738 -0.0763379 0.17385 -0.0763249 0.1739 -0.0763117 0.17395 -0.0762982 0.174 -0.0762846 0.17405 -0.0762707 0.1741 -0.0762567 0.17415 -0.0762425 0.1742 -0.076228 0.17425 -0.0762134 0.1743 -0.0761985 0.17435 -0.0761835 0.1744 -0.0761682 0.17445 -0.0761527 0.1745 -0.076137 0.17455 -0.0761211 0.1746 -0.076105 0.17465 -0.0760887 0.1747 -0.0760722 0.17475 -0.0760554 0.1748 -0.0760385 0.17485 -0.0760213 0.1749 -0.0760039 0.17495 -0.0759863 0.175 -0.0759685 0.17505 -0.0759505 0.1751 -0.0759322 0.17515 -0.0759137 0.1752 -0.075895 0.17525 -0.0758761 0.1753 -0.075857 0.17535 -0.0758376 0.1754 -0.075818 0.17545 -0.0757982 0.1755 -0.0757781 0.17555 -0.0757578 0.1756 -0.0757372 0.17565 -0.0757164 0.1757 -0.0756954 0.17575 -0.0756741 0.1758 -0.0756526 0.17585 -0.0756308 0.1759 -0.0756088 0.17595 -0.0755864 0.176 -0.0755639 0.17605 -0.075541 0.1761 -0.0755178 0.17615 -0.0754943 0.1762 -0.0754705 0.17625 -0.0754464 0.1763 -0.0754219 0.17635 -0.0753971 0.1764 -0.0753718 0.17645 -0.0753461 0.1765 -0.07532 0.17655 -0.0752933 0.1766 -0.0752661 0.17665 -0.0752382 0.1767 -0.0752096 0.17675 -0.07518 0.1768 -0.0751494 0.17685 -0.0751176 0.1769 -0.0750842 0.17695 -0.0750489 0.177 -0.0750112 0.17705 -0.0749706 0.1771 -0.0749262 0.17715 -0.0748773 0.1772 -0.0748225 0.17725 -0.0747605 0.1773 -0.0746892 0.17735 -0.0746065 0.1774 -0.0745093 0.17745 -0.0743941 0.1775 -0.0742564 0.17755 -0.0740906 0.1776 -0.0738898 0.17765 -0.0736453 0.1777 -0.0733467 0.17775 -0.0729808 0.1778 -0.0725314 0.17785 -0.0719785 0.1779 -0.0712974 0.17795 -0.0704575 0.178 -0.0694208 0.17805 -0.0681406 0.1781 -0.0665584 0.17815 -0.0646016 0.1782 -0.0621784 0.17825 -0.0591722 0.1783 -0.0554301 0.17835 -0.0507446 0.1784 -0.0448165 0.17845 -0.0371817 0.1785 -0.0270836 0.17855 -0.013377 0.1786 0.00482233 0.17865 0.0250182 0.1787 0.0395546 0.17875 0.0451671 0.1788 0.0461524 0.17885 0.0458326 0.1789 0.0451183 0.17895 0.0441709 0.179 0.0430222 0.17905 0.0416853 0.1791 0.040172 0.17915 0.0384949 0.1792 0.0366685 0.17925 0.0347081 0.1793 0.0326303 0.17935 0.0304515 0.1794 0.028188 0.17945 0.0258553 0.1795 0.0234682 0.17955 0.0210399 0.1796 0.0185827 0.17965 0.0161073 0.1797 0.013623 0.17975 0.0111378 0.1798 0.00865851 0.17985 0.00619055 0.1799 0.00373831 0.17995 0.0013051 0.18 -0.00110666 0.18005 -0.00349544 0.1801 -0.00586046 0.18015 -0.00820168 0.1802 -0.0105198 0.18025 -0.0128161 0.1803 -0.0150928 0.18035 -0.017353 0.1804 -0.0196009 0.18045 -0.0218422 0.1805 -0.0240844 0.18055 -0.0263376 0.1806 -0.0286149 0.18065 -0.0309337 0.1807 -0.0333162 0.18075 -0.0357909 0.1808 -0.0383928 0.18085 -0.0411642 0.1809 -0.0441522 0.18095 -0.0474034 0.181 -0.0509507 0.18105 -0.0547898 0.1811 -0.0588438 0.18115 -0.0629299 0.1812 -0.0667611 0.18125 -0.0700266 0.1813 -0.0725268 0.18135 -0.0742556 0.1814 -0.0753577 0.18145 -0.0760231 0.1815 -0.0764131 0.18155 -0.0766392 0.1816 -0.0767703 0.18165 -0.076847 0.1817 -0.0768922 0.18175 -0.0769192 0.1818 -0.0769355 0.18185 -0.0769452 0.1819 -0.0769511 0.18195 -0.0769545 0.182 -0.0769564 0.18205 -0.0769573 0.1821 -0.0769575 0.18215 -0.0769574 0.1822 -0.0769569 0.18225 -0.0769562 0.1823 -0.0769554 0.18235 -0.0769545 0.1824 -0.0769535 0.18245 -0.0769524 0.1825 -0.0769513 0.18255 -0.0769501 0.1826 -0.0769488 0.18265 -0.0769476 0.1827 -0.0769463 0.18275 -0.076945 0.1828 -0.0769436 0.18285 -0.0769422 0.1829 -0.0769407 0.18295 -0.0769393 0.183 -0.0769378 0.18305 -0.0769362 0.1831 -0.0769347 0.18315 -0.0769331 0.1832 -0.0769314 0.18325 -0.0769297 0.1833 -0.076928 0.18335 -0.0769263 0.1834 -0.0769245 0.18345 -0.0769227 0.1835 -0.0769208 0.18355 -0.0769189 0.1836 -0.0769169 0.18365 -0.076915 0.1837 -0.0769129 0.18375 -0.0769108 0.1838 -0.0769087 0.18385 -0.0769066 0.1839 -0.0769043 0.18395 -0.0769021 0.184 -0.0768998 0.18405 -0.0768974 0.1841 -0.076895 0.18415 -0.0768926 0.1842 -0.0768901 0.18425 -0.0768875 0.1843 -0.0768849 0.18435 -0.0768822 0.1844 -0.0768795 0.18445 -0.0768767 0.1845 -0.0768738 0.18455 -0.0768709 0.1846 -0.0768679 0.18465 -0.0768649 0.1847 -0.0768618 0.18475 -0.0768586 0.1848 -0.0768554 0.18485 -0.0768521 0.1849 -0.0768487 0.18495 -0.0768452 0.185 -0.0768417 0.18505 -0.0768381 0.1851 -0.0768344 0.18515 -0.0768306 0.1852 -0.0768268 0.18525 -0.0768229 0.1853 -0.0768189 0.18535 -0.0768147 0.1854 -0.0768106 0.18545 -0.0768063 0.1855 -0.0768019 0.18555 -0.0767974 0.1856 -0.0767929 0.18565 -0.0767882 0.1857 -0.0767834 0.18575 -0.0767786 0.1858 -0.0767736 0.18585 -0.0767685 0.1859 -0.0767633 0.18595 -0.076758 0.186 -0.0767526 0.18605 -0.0767471 0.1861 -0.0767414 0.18615 -0.0767357 0.1862 -0.0767298 0.18625 -0.0767238 0.1863 -0.0767176 0.18635 -0.0767114 0.1864 -0.076705 0.18645 -0.0766985 0.1865 -0.0766918 0.18655 -0.076685 0.1866 -0.0766781 0.18665 -0.076671 0.1867 -0.0766638 0.18675 -0.0766564 0.1868 -0.0766489 0.18685 -0.0766413 0.1869 -0.0766335 0.18695 -0.0766255 0.187 -0.0766174 0.18705 -0.0766092 0.1871 -0.0766007 0.18715 -0.0765922 0.1872 -0.0765834 0.18725 -0.0765745 0.1873 -0.0765654 0.18735 -0.0765562 0.1874 -0.0765468 0.18745 -0.0765372 0.1875 -0.0765275 0.18755 -0.0765175 0.1876 -0.0765074 0.18765 -0.0764972 0.1877 -0.0764867 0.18775 -0.0764761 0.1878 -0.0764653 0.18785 -0.0764543 0.1879 -0.0764431 0.18795 -0.0764317 0.188 -0.0764201 0.18805 -0.0764084 0.1881 -0.0763965 0.18815 -0.0763843 0.1882 -0.076372 0.18825 -0.0763595 0.1883 -0.0763468 0.18835 -0.0763339 0.1884 -0.0763208 0.18845 -0.0763075 0.1885 -0.076294 0.18855 -0.0762803 0.1886 -0.0762664 0.18865 -0.0762523 0.1887 -0.076238 0.18875 -0.0762235 0.1888 -0.0762088 0.18885 -0.0761938 0.1889 -0.0761787 0.18895 -0.0761634 0.189 -0.0761478 0.18905 -0.0761321 0.1891 -0.0761161 0.18915 -0.0761 0.1892 -0.0760836 0.18925 -0.076067 0.1893 -0.0760502 0.18935 -0.0760332 0.1894 -0.0760159 0.18945 -0.0759985 0.1895 -0.0759808 0.18955 -0.0759629 0.1896 -0.0759448 0.18965 -0.0759265 0.1897 -0.0759079 0.18975 -0.0758892 0.1898 -0.0758702 0.18985 -0.075851 0.1899 -0.0758315 0.18995 -0.0758118 0.19 -0.0757919 0.19005 -0.0757718 0.1901 -0.0757514 0.19015 -0.0757308 0.1902 -0.0757099 0.19025 -0.0756888 0.1903 -0.0756674 0.19035 -0.0756458 0.1904 -0.075624 0.19045 -0.0756018 0.1905 -0.0755794 0.19055 -0.0755567 0.1906 -0.0755338 0.19065 -0.0755105 0.1907 -0.0754869 0.19075 -0.075463 0.1908 -0.0754388 0.19085 -0.0754142 0.1909 -0.0753892 0.19095 -0.0753639 0.191 -0.075338 0.19105 -0.0753117 0.1911 -0.0752849 0.19115 -0.0752575 0.1912 -0.0752294 0.19125 -0.0752005 0.1913 -0.0751706 0.19135 -0.0751396 0.1914 -0.0751073 0.19145 -0.0750734 0.1915 -0.0750374 0.19155 -0.0749988 0.1916 -0.0749572 0.19165 -0.0749115 0.1917 -0.0748609 0.19175 -0.074804 0.1918 -0.0747393 0.19185 -0.0746648 0.1919 -0.0745779 0.19195 -0.0744755 0.192 -0.0743538 0.19205 -0.074208 0.1921 -0.074032 0.19215 -0.0738186 0.1922 -0.0735585 0.19225 -0.0732404 0.1923 -0.0728504 0.19235 -0.072371 0.1924 -0.071781 0.19245 -0.0710539 0.1925 -0.0701571 0.19255 -0.0690499 0.1926 -0.0676823 0.19265 -0.0659918 0.1927 -0.0639003 0.19275 -0.0613091 0.1928 -0.0580917 0.19285 -0.0540808 0.1929 -0.0490454 0.19295 -0.042645 0.193 -0.0343407 0.19305 -0.0232546 0.1931 -0.00817363 0.19315 0.0112048 0.1932 0.0305262 0.19325 0.0421266 0.1933 0.045748 0.19335 0.0461212 0.1934 0.0456391 0.19345 0.0448454 0.1935 0.0438333 0.19355 0.0426246 0.1936 0.0412314 0.19365 0.0396655 0.1937 0.0379402 0.19375 0.0360702 0.1938 0.0340714 0.19385 0.0319603 0.1939 0.0297534 0.19395 0.0274666 0.194 0.0251155 0.19405 0.0227141 0.1941 0.0202756 0.19415 0.0178116 0.1942 0.0153324 0.19425 0.0128471 0.1943 0.0103631 0.19435 0.00788679 0.1944 0.00542332 0.19445 0.0029767 0.1945 0.000549974 0.19455 -0.00185474 0.1946 -0.00423615 0.19465 -0.00659371 0.1947 -0.00892761 0.19475 -0.0112387 0.1948 -0.0135286 0.19485 -0.0157998 0.1949 -0.0180557 0.19495 -0.0203008 0.195 -0.0225415 0.19505 -0.0247859 0.1951 -0.0270451 0.19515 -0.0293333 0.1952 -0.0316693 0.19525 -0.0340772 0.1953 -0.0365874 0.19535 -0.0392374 0.1954 -0.0420712 0.19545 -0.0451367 0.1955 -0.0484779 0.19555 -0.0521194 0.1956 -0.0560383 0.19565 -0.0601275 0.1957 -0.0641683 0.19575 -0.0678539 0.1958 -0.0708934 0.19585 -0.0731445 0.1959 -0.074658 0.19595 -0.0756038 0.196 -0.0761682 0.19605 -0.0764973 0.1961 -0.076688 0.19615 -0.0767987 0.1962 -0.0768637 0.19625 -0.0769022 0.1963 -0.0769252 0.19635 -0.0769391 0.1964 -0.0769474 0.19645 -0.0769524 0.1965 -0.0769552 0.19655 -0.0769567 0.1966 -0.0769574 0.19665 -0.0769575 0.1967 -0.0769572 0.19675 -0.0769567 0.1968 -0.076956 0.19685 -0.0769551 0.1969 -0.0769542 0.19695 -0.0769531 0.197 -0.076952 0.19705 -0.0769509 0.1971 -0.0769497 0.19715 -0.0769485 0.1972 -0.0769472 0.19725 -0.0769459 0.1973 -0.0769445 0.19735 -0.0769432 0.1974 -0.0769417 0.19745 -0.0769403 0.1975 -0.0769388 0.19755 -0.0769373 0.1976 -0.0769358 0.19765 -0.0769342 0.1977 -0.0769326 0.19775 -0.0769309 0.1978 -0.0769292 0.19785 -0.0769275 0.1979 -0.0769257 0.19795 -0.0769239 0.198 -0.0769221 0.19805 -0.0769202 0.1981 -0.0769183 0.19815 -0.0769163 0.1982 -0.0769143 0.19825 -0.0769123 0.1983 -0.0769102 0.19835 -0.0769081 0.1984 -0.0769059 0.19845 -0.0769036 0.1985 -0.0769014 0.19855 -0.0768991 0.1986 -0.0768967 0.19865 -0.0768943 0.1987 -0.0768918 0.19875 -0.0768893 0.1988 -0.0768867 0.19885 -0.076884 0.1989 -0.0768813 0.19895 -0.0768786 0.199 -0.0768758 0.19905 -0.0768729 0.1991 -0.07687 0.19915 -0.076867 0.1992 -0.0768639 0.19925 -0.0768608 0.1993 -0.0768576 0.19935 -0.0768544 0.1994 -0.076851 0.19945 -0.0768476 0.1995 -0.0768441 0.19955 -0.0768406 0.1996 -0.076837 0.19965 -0.0768332 0.1997 -0.0768295 0.19975 -0.0768256 0.1998 -0.0768216 0.19985 -0.0768176 0.1999 -0.0768134 0.19995 -0.0768092 0.2 -0.0768049 0.20005 -0.0768005 0.2001 -0.076796 0.20015 -0.0767914 0.2002 -0.0767867 0.20025 -0.0767819 0.2003 -0.076777 0.20035 -0.076772 0.2004 -0.0767669 0.20045 -0.0767617 0.2005 -0.0767563 0.20055 -0.0767509 0.2006 -0.0767453 0.20065 -0.0767396 0.2007 -0.0767338 0.20075 -0.0767279 0.2008 -0.0767219 0.20085 -0.0767157 0.2009 -0.0767094 0.20095 -0.076703 0.201 -0.0766964 0.20105 -0.0766897 0.2011 -0.0766829 0.20115 -0.0766759 0.2012 -0.0766688 0.20125 -0.0766615 0.2013 -0.0766541 0.20135 -0.0766466 0.2014 -0.0766389 0.20145 -0.076631 0.2015 -0.076623 0.20155 -0.0766149 0.2016 -0.0766066 0.20165 -0.0765981 0.2017 -0.0765894 0.20175 -0.0765807 0.2018 -0.0765717 0.20185 -0.0765626 0.2019 -0.0765533 0.20195 -0.0765438 0.202 -0.0765342 0.20205 -0.0765244 0.2021 -0.0765144 0.20215 -0.0765043 0.2022 -0.0764939 0.20225 -0.0764834 0.2023 -0.0764727 0.20235 -0.0764618 0.2024 -0.0764508 0.20245 -0.0764395 0.2025 -0.0764281 0.20255 -0.0764165 0.2026 -0.0764047 0.20265 -0.0763927 0.2027 -0.0763805 0.20275 -0.0763681 0.2028 -0.0763556 0.20285 -0.0763428 0.2029 -0.0763298 0.20295 -0.0763167 0.203 -0.0763033 0.20305 -0.0762897 0.2031 -0.076276 0.20315 -0.076262 0.2032 -0.0762478 0.20325 -0.0762335 0.2033 -0.0762189 0.20335 -0.0762041 0.2034 -0.0761891 0.20345 -0.076174 0.2035 -0.0761586 0.20355 -0.0761429 0.2036 -0.0761271 0.20365 -0.0761111 0.2037 -0.0760949 0.20375 -0.0760784 0.2038 -0.0760618 0.20385 -0.0760449 0.2039 -0.0760278 0.20395 -0.0760105 0.204 -0.075993 0.20405 -0.0759752 0.2041 -0.0759573 0.20415 -0.0759391 0.2042 -0.0759207 0.20425 -0.0759021 0.2043 -0.0758833 0.20435 -0.0758642 0.2044 -0.0758449 0.20445 -0.0758254 0.2045 -0.0758056 0.20455 -0.0757857 0.2046 -0.0757654 0.20465 -0.075745 0.2047 -0.0757243 0.20475 -0.0757033 0.2048 -0.0756822 0.20485 -0.0756607 0.2049 -0.075639 0.20495 -0.0756171 0.205 -0.0755949 0.20505 -0.0755724 0.2051 -0.0755496 0.20515 -0.0755265 0.2052 -0.0755032 0.20525 -0.0754795 0.2053 -0.0754555 0.20535 -0.0754312 0.2054 -0.0754064 0.20545 -0.0753814 0.2055 -0.0753558 0.20555 -0.0753299 0.2056 -0.0753034 0.20565 -0.0752764 0.2057 -0.0752488 0.20575 -0.0752204 0.2058 -0.0751912 0.20585 -0.0751611 0.2059 -0.0751297 0.20595 -0.0750969 0.206 -0.0750624 0.20605 -0.0750257 0.2061 -0.0749862 0.20615 -0.0749434 0.2062 -0.0748963 0.20625 -0.0748439 0.2063 -0.0747847 0.20635 -0.0747172 0.2064 -0.0746391 0.20645 -0.0745477 0.2065 -0.0744398 0.20655 -0.0743112 0.2066 -0.0741566 0.20665 -0.0739698 0.2067 -0.0737429 0.20675 -0.073466 0.2068 -0.0731271 0.20685 -0.0727113 0.2069 -0.0721999 0.20695 -0.0715702 0.207 -0.070794 0.20705 -0.0698362 0.2071 -0.0686537 0.20715 -0.0671926 0.2072 -0.0653862 0.20725 -0.0631505 0.2073 -0.060379 0.20735 -0.0569343 0.2074 -0.0526324 0.20745 -0.0472145 0.2075 -0.0402906 0.20755 -0.0312321 0.2076 -0.0190333 0.20765 -0.00251541 0.2077 0.0176269 0.20775 0.0352019 0.2078 0.0438834 0.20785 0.0460408 0.2079 0.0460191 0.20795 0.0454175 0.208 0.0445515 0.20805 0.0434765 0.2081 0.042209 0.20815 0.0407606 0.2082 0.0391435 0.20825 0.0373714 0.2083 0.0354594 0.20835 0.0334238 0.2084 0.031281 0.20845 0.0290475 0.2085 0.026739 0.20855 0.0243707 0.2086 0.0219564 0.20865 0.0195088 0.2087 0.017039 0.20875 0.014557 0.2088 0.0120713 0.20885 0.00958912 0.2089 0.00711633 0.20895 0.00465773 0.209 0.00221704 0.20905 -0.000203003 0.2091 -0.00260055 0.20915 -0.00497454 0.2092 -0.00732466 0.20925 -0.00965131 0.2093 -0.0119556 0.20935 -0.0142394 0.2094 -0.0165054 0.20945 -0.0187573 0.2095 -0.0210003 0.20955 -0.0232411 0.2096 -0.0254888 0.20965 -0.0277553 0.2097 -0.0300562 0.20975 -0.0324118 0.2098 -0.0348481 0.20985 -0.0373976 0.2099 -0.0400999 0.20995 -0.0430009 0.21 -0.0461482 0.21005 -0.0495821 0.2101 -0.0533158 0.21015 -0.0573042 0.2102 -0.0614071 0.21025 -0.065372 0.2103 -0.0688825 0.21035 -0.0716817 0.2104 -0.0736894 0.21045 -0.0750049 0.2105 -0.075813 0.21055 -0.0762907 0.2106 -0.0765683 0.21065 -0.0767291 0.2107 -0.0768228 0.21075 -0.0768779 0.2108 -0.0769107 0.21085 -0.0769303 0.2109 -0.0769421 0.21095 -0.0769492 0.211 -0.0769534 0.21105 -0.0769558 0.2111 -0.076957 0.21115 -0.0769575 0.2112 -0.0769575 0.21125 -0.0769571 0.2113 -0.0769565 0.21135 -0.0769557 0.2114 -0.0769548 0.21145 -0.0769538 0.2115 -0.0769528 0.21155 -0.0769517 0.2116 -0.0769505 0.21165 -0.0769493 0.2117 -0.0769481 0.21175 -0.0769468 0.2118 -0.0769455 0.21185 -0.0769441 0.2119 -0.0769427 0.21195 -0.0769413 0.212 -0.0769398 0.21205 -0.0769383 0.2121 -0.0769368 0.21215 -0.0769353 0.2122 -0.0769337 0.21225 -0.076932 0.2123 -0.0769304 0.21235 -0.0769287 0.2124 -0.0769269 0.21245 -0.0769252 0.2125 -0.0769234 0.21255 -0.0769215 0.2126 -0.0769196 0.21265 -0.0769177 0.2127 -0.0769157 0.21275 -0.0769137 0.2128 -0.0769116 0.21285 -0.0769095 0.2129 -0.0769074 0.21295 -0.0769052 0.213 -0.0769029 0.21305 -0.0769007 0.2131 -0.0768983 0.21315 -0.0768959 0.2132 -0.0768935 0.21325 -0.076891 0.2133 -0.0768885 0.21335 -0.0768859 0.2134 -0.0768832 0.21345 -0.0768805 0.2135 -0.0768777 0.21355 -0.0768749 0.2136 -0.076872 0.21365 -0.0768691 0.2137 -0.076866 0.21375 -0.076863 0.2138 -0.0768598 0.21385 -0.0768566 0.2139 -0.0768533 0.21395 -0.07685 0.214 -0.0768465 0.21405 -0.076843 0.2141 -0.0768395 0.21415 -0.0768358 0.2142 -0.0768321 0.21425 -0.0768283 0.2143 -0.0768244 0.21435 -0.0768204 0.2144 -0.0768163 0.21445 -0.0768121 0.2145 -0.0768079 0.21455 -0.0768036 0.2146 -0.0767991 0.21465 -0.0767946 0.2147 -0.07679 0.21475 -0.0767852 0.2148 -0.0767804 0.21485 -0.0767755 0.2149 -0.0767704 0.21495 -0.0767653 0.215 -0.07676 0.21505 -0.0767547 0.2151 -0.0767492 0.21515 -0.0767436 0.2152 -0.0767378 0.21525 -0.076732 0.2153 -0.076726 0.21535 -0.07672 0.2154 -0.0767137 0.21545 -0.0767074 0.2155 -0.0767009 0.21555 -0.0766943 0.2156 -0.0766876 0.21565 -0.0766807 0.2157 -0.0766737 0.21575 -0.0766665 0.2158 -0.0766592 0.21585 -0.0766518 0.2159 -0.0766442 0.21595 -0.0766364 0.216 -0.0766285 0.21605 -0.0766205 0.2161 -0.0766123 0.21615 -0.0766039 0.2162 -0.0765954 0.21625 -0.0765867 0.2163 -0.0765779 0.21635 -0.0765689 0.2164 -0.0765597 0.21645 -0.0765503 0.2165 -0.0765408 0.21655 -0.0765311 0.2166 -0.0765213 0.21665 -0.0765113 0.2167 -0.076501 0.21675 -0.0764907 0.2168 -0.0764801 0.21685 -0.0764693 0.2169 -0.0764584 0.21695 -0.0764473 0.217 -0.076436 0.21705 -0.0764245 0.2171 -0.0764128 0.21715 -0.076401 0.2172 -0.0763889 0.21725 -0.0763767 0.2173 -0.0763642 0.21735 -0.0763516 0.2174 -0.0763388 0.21745 -0.0763257 0.2175 -0.0763125 0.21755 -0.0762991 0.2176 -0.0762855 0.21765 -0.0762716 0.2177 -0.0762576 0.21775 -0.0762434 0.2178 -0.0762289 0.21785 -0.0762143 0.2179 -0.0761995 0.21795 -0.0761844 0.218 -0.0761692 0.21805 -0.0761537 0.2181 -0.076138 0.21815 -0.0761222 0.2182 -0.0761061 0.21825 -0.0760898 0.2183 -0.0760732 0.21835 -0.0760565 0.2184 -0.0760396 0.21845 -0.0760224 0.2185 -0.0760051 0.21855 -0.0759875 0.2186 -0.0759697 0.21865 -0.0759516 0.2187 -0.0759334 0.21875 -0.0759149 0.2188 -0.0758962 0.21885 -0.0758773 0.2189 -0.0758582 0.21895 -0.0758388 0.219 -0.0758192 0.21905 -0.0757994 0.2191 -0.0757794 0.21915 -0.0757591 0.2192 -0.0757385 0.21925 -0.0757178 0.2193 -0.0756968 0.21935 -0.0756755 0.2194 -0.075654 0.21945 -0.0756322 0.2195 -0.0756102 0.21955 -0.0755879 0.2196 -0.0755653 0.21965 -0.0755424 0.2197 -0.0755193 0.21975 -0.0754958 0.2198 -0.075472 0.21985 -0.0754479 0.2199 -0.0754235 0.21995 -0.0753987 0.22 -0.0753734 0.22005 -0.0753478 0.2201 -0.0753217 0.22015 -0.0752951 0.2202 -0.0752679 0.22025 -0.07524 0.2203 -0.0752114 0.22035 -0.0751819 0.2204 -0.0751514 0.22045 -0.0751197 0.2205 -0.0750864 0.22055 -0.0750512 0.2206 -0.0750137 0.22065 -0.0749732 0.2207 -0.0749292 0.22075 -0.0748806 0.2208 -0.0748262 0.22085 -0.0747647 0.2209 -0.0746941 0.22095 -0.0746121 0.221 -0.074516 0.22105 -0.0744021 0.2211 -0.074266 0.22115 -0.0741021 0.2212 -0.0739037 0.22125 -0.0736624 0.2213 -0.0733676 0.22135 -0.0730064 0.2214 -0.0725629 0.22145 -0.0720173 0.2215 -0.0713452 0.22155 -0.0705164 0.2216 -0.0694936 0.22165 -0.0682304 0.2217 -0.0666695 0.22175 -0.064739 0.2218 -0.0623487 0.22185 -0.0593837 0.2219 -0.0556939 0.22195 -0.0510762 0.222 -0.0452387 0.22205 -0.0377312 0.2221 -0.0278203 0.22215 -0.0143815 0.2222 0.00354571 0.22225 0.0238126 0.2223 0.0389168 0.22235 0.0450012 0.2224 0.046147 0.22245 0.0458682 0.2225 0.0451713 0.22255 0.0442374 0.2226 0.043101 0.22265 0.0417758 0.2227 0.0402734 0.22275 0.0386063 0.2228 0.0367889 0.22285 0.0348366 0.2229 0.0327658 0.22295 0.0305929 0.223 0.0283343 0.22305 0.0260056 0.2231 0.0236215 0.22315 0.0211955 0.2232 0.0187398 0.22325 0.0162652 0.2233 0.0137812 0.22335 0.0112959 0.2234 0.00881608 0.22345 0.00634726 0.2235 0.0038939 0.22355 0.0014594 0.2236 -0.000953782 0.22365 -0.00334405 0.2237 -0.00571059 0.22375 -0.00805331 0.2238 -0.0103728 0.22385 -0.0126705 0.2239 -0.0149484 0.22395 -0.0172095 0.224 -0.0194581 0.22405 -0.0216996 0.2241 -0.0239414 0.22415 -0.0261936 0.2242 -0.0284689 0.22425 -0.0307845 0.2243 -0.0331621 0.22435 -0.0356299 0.2244 -0.0382226 0.22445 -0.0409818 0.2245 -0.0439546 0.22455 -0.0471878 0.2246 -0.0507158 0.22465 -0.0545376 0.2247 -0.0585822 0.22475 -0.0626739 0.2248 -0.066531 0.22485 -0.0698404 0.2249 -0.0723915 0.22495 -0.0741662 0.225 -0.0753025 0.22505 -0.0759904 0.2251 -0.0763941 0.22515 -0.0766282 0.2252 -0.0767639 0.22525 -0.0768432 0.2253 -0.07689 0.22535 -0.0769179 0.2254 -0.0769347 0.22545 -0.0769448 0.2255 -0.0769508 0.22555 -0.0769543 0.2256 -0.0769563 0.22565 -0.0769572 0.2257 -0.0769575 0.22575 -0.0769574 0.2258 -0.0769569 0.22585 -0.0769563 0.2259 -0.0769555 0.22595 -0.0769545 0.226 -0.0769535 0.22605 -0.0769525 0.2261 -0.0769513 0.22615 -0.0769501 0.2262 -0.0769489 0.22625 -0.0769477 0.2263 -0.0769464 0.22635 -0.076945 0.2264 -0.0769437 0.22645 -0.0769423 0.2265 -0.0769408 0.22655 -0.0769394 0.2266 -0.0769379 0.22665 -0.0769363 0.2267 -0.0769348 0.22675 -0.0769332 0.2268 -0.0769315 0.22685 -0.0769299 0.2269 -0.0769281 0.22695 -0.0769264 0.227 -0.0769246 0.22705 -0.0769228 0.2271 -0.0769209 0.22715 -0.076919 0.2272 -0.0769171 0.22725 -0.0769151 0.2273 -0.0769131 0.22735 -0.076911 0.2274 -0.0769089 0.22745 -0.0769067 0.2275 -0.0769045 0.22755 -0.0769022 0.2276 -0.0768999 0.22765 -0.0768976 0.2277 -0.0768952 0.22775 -0.0768927 0.2278 -0.0768902 0.22785 -0.0768877 0.2279 -0.076885 0.22795 -0.0768824 0.228 -0.0768796 0.22805 -0.0768769 0.2281 -0.076874 0.22815 -0.0768711 0.2282 -0.0768681 0.22825 -0.0768651 0.2283 -0.076862 0.22835 -0.0768588 0.2284 -0.0768556 0.22845 -0.0768523 0.2285 -0.0768489 0.22855 -0.0768455 0.2286 -0.0768419 0.22865 -0.0768383 0.2287 -0.0768346 0.22875 -0.0768309 0.2288 -0.076827 0.22885 -0.0768231 0.2289 -0.0768191 0.22895 -0.076815 0.229 -0.0768108 0.22905 -0.0768065 0.2291 -0.0768022 0.22915 -0.0767977 0.2292 -0.0767932 0.22925 -0.0767885 0.2293 -0.0767837 0.22935 -0.0767789 0.2294 -0.0767739 0.22945 -0.0767688 0.2295 -0.0767636 0.22955 -0.0767584 0.2296 -0.076753 0.22965 -0.0767474 0.2297 -0.0767418 0.22975 -0.076736 0.2298 -0.0767302 0.22985 -0.0767242 0.2299 -0.076718 0.22995 -0.0767118 0.23 -0.0767054 0.23005 -0.0766989 0.2301 -0.0766922 0.23015 -0.0766855 0.2302 -0.0766785 0.23025 -0.0766715 0.2303 -0.0766643 0.23035 -0.0766569 0.2304 -0.0766494 0.23045 -0.0766418 0.2305 -0.076634 0.23055 -0.076626 0.2306 -0.0766179 0.23065 -0.0766097 0.2307 -0.0766013 0.23075 -0.0765927 0.2308 -0.076584 0.23085 -0.0765751 0.2309 -0.076566 0.23095 -0.0765568 0.231 -0.0765474 0.23105 -0.0765378 0.2311 -0.0765281 0.23115 -0.0765182 0.2312 -0.0765081 0.23125 -0.0764978 0.2313 -0.0764874 0.23135 -0.0764768 0.2314 -0.076466 0.23145 -0.076455 0.2315 -0.0764438 0.23155 -0.0764324 0.2316 -0.0764209 0.23165 -0.0764091 0.2317 -0.0763972 0.23175 -0.0763851 0.2318 -0.0763728 0.23185 -0.0763603 0.2319 -0.0763476 0.23195 -0.0763347 0.232 -0.0763216 0.23205 -0.0763083 0.2321 -0.0762948 0.23215 -0.0762812 0.2322 -0.0762673 0.23225 -0.0762532 0.2323 -0.0762389 0.23235 -0.0762244 0.2324 -0.0762097 0.23245 -0.0761948 0.2325 -0.0761797 0.23255 -0.0761644 0.2326 -0.0761488 0.23265 -0.0761331 0.2327 -0.0761172 0.23275 -0.076101 0.2328 -0.0760846 0.23285 -0.0760681 0.2329 -0.0760513 0.23295 -0.0760343 0.233 -0.076017 0.23305 -0.0759996 0.2331 -0.0759819 0.23315 -0.0759641 0.2332 -0.075946 0.23325 -0.0759277 0.2333 -0.0759091 0.23335 -0.0758904 0.2334 -0.0758714 0.23345 -0.0758522 0.2335 -0.0758327 0.23355 -0.0758131 0.2336 -0.0757932 0.23365 -0.0757731 0.2337 -0.0757527 0.23375 -0.0757321 0.2338 -0.0757112 0.23385 -0.0756902 0.2339 -0.0756688 0.23395 -0.0756472 0.234 -0.0756254 0.23405 -0.0756032 0.2341 -0.0755809 0.23415 -0.0755582 0.2342 -0.0755352 0.23425 -0.075512 0.2343 -0.0754884 0.23435 -0.0754646 0.2344 -0.0754403 0.23445 -0.0754158 0.2345 -0.0753908 0.23455 -0.0753655 0.2346 -0.0753397 0.23465 -0.0753134 0.2347 -0.0752866 0.23475 -0.0752592 0.2348 -0.0752312 0.23485 -0.0752023 0.2349 -0.0751725 0.23495 -0.0751417 0.235 -0.0751094 0.23505 -0.0750756 0.2351 -0.0750397 0.23515 -0.0750014 0.2352 -0.0749599 0.23525 -0.0749146 0.2353 -0.0748643 0.23535 -0.0748079 0.2354 -0.0747437 0.23545 -0.0746698 0.2355 -0.0745838 0.23555 -0.0744826 0.2356 -0.0743622 0.23565 -0.0742181 0.2357 -0.0740443 0.23575 -0.0738335 0.2358 -0.0735767 0.23585 -0.0732627 0.2359 -0.0728777 0.23595 -0.0724046 0.236 -0.0718224 0.23605 -0.0711049 0.2361 -0.07022 0.23615 -0.0691276 0.2362 -0.0677783 0.23625 -0.0661104 0.2363 -0.0640471 0.23635 -0.0614912 0.2364 -0.0583181 0.23645 -0.0543638 0.2365 -0.0494023 0.23655 -0.0431022 0.2366 -0.0349409 0.23665 -0.0240662 0.2367 -0.00927274 0.23675 0.00989159 0.2368 0.0294637 0.23685 0.0416733 0.2369 0.0456569 0.23695 0.0461343 0.237 0.045681 0.23705 0.0449028 0.2371 0.0439037 0.23715 0.0427072 0.2372 0.0413254 0.23725 0.0397701 0.2373 0.0380545 0.23735 0.0361933 0.2374 0.0342022 0.23745 0.0320978 0.2375 0.0298964 0.23755 0.0276143 0.2376 0.0252668 0.23765 0.0228682 0.2377 0.0204317 0.23775 0.017969 0.2378 0.0154906 0.23785 0.0130054 0.2379 0.0105211 0.23795 0.00804412 0.238 0.0055797 0.23805 0.00313191 0.2381 0.000703842 0.23815 -0.00170232 0.2382 -0.00408524 0.23825 -0.00644432 0.2383 -0.0087797 0.23835 -0.0110922 0.2384 -0.0133834 0.23845 -0.0156557 0.2385 -0.0179124 0.23855 -0.0201581 0.2386 -0.0223988 0.23865 -0.0246427 0.2387 -0.0269005 0.23875 -0.0291864 0.2388 -0.0315187 0.23885 -0.0339212 0.2389 -0.0364239 0.23895 -0.0390637 0.239 -0.0418844 0.23905 -0.0449337 0.2391 -0.0482563 0.23915 -0.0518787 0.2392 -0.055782 0.23925 -0.0598657 0.2393 -0.0639181 0.23935 -0.0676359 0.2394 -0.0707229 0.23945 -0.0730245 0.2395 -0.0745806 0.23955 -0.0755568 0.2396 -0.0761406 0.23965 -0.0764813 0.2397 -0.0766787 0.23975 -0.0767933 0.2398 -0.0768605 0.23985 -0.0769003 0.2399 -0.0769241 0.23995 -0.0769384 0.24 -0.076947 0.24005 -0.0769521 0.2401 -0.0769551 0.24015 -0.0769567 0.2402 -0.0769574 0.24025 -0.0769575 0.2403 -0.0769573 0.24035 -0.0769567 0.2404 -0.076956 0.24045 -0.0769552 0.2405 -0.0769542 0.24055 -0.0769532 0.2406 -0.0769521 0.24065 -0.076951 0.2407 -0.0769498 0.24075 -0.0769485 0.2408 -0.0769473 0.24085 -0.076946 0.2409 -0.0769446 0.24095 -0.0769432 0.241 -0.0769418 0.24105 -0.0769404 0.2411 -0.0769389 0.24115 -0.0769374 0.2412 -0.0769359 0.24125 -0.0769343 0.2413 -0.0769327 0.24135 -0.076931 0.2414 -0.0769293 0.24145 -0.0769276 0.2415 -0.0769258 0.24155 -0.076924 0.2416 -0.0769222 0.24165 -0.0769203 0.2417 -0.0769184 0.24175 -0.0769165 0.2418 -0.0769145 0.24185 -0.0769124 0.2419 -0.0769103 0.24195 -0.0769082 0.242 -0.076906 0.24205 -0.0769038 0.2421 -0.0769015 0.24215 -0.0768992 0.2422 -0.0768968 0.24225 -0.0768944 0.2423 -0.0768919 0.24235 -0.0768894 0.2424 -0.0768868 0.24245 -0.0768842 0.2425 -0.0768815 0.24255 -0.0768788 0.2426 -0.076876 0.24265 -0.0768731 0.2427 -0.0768702 0.24275 -0.0768672 0.2428 -0.0768641 0.24285 -0.076861 0.2429 -0.0768578 0.24295 -0.0768546 0.243 -0.0768512 0.24305 -0.0768478 0.2431 -0.0768444 0.24315 -0.0768408 0.2432 -0.0768372 0.24325 -0.0768335 0.2433 -0.0768297 0.24335 -0.0768258 0.2434 -0.0768219 0.24345 -0.0768178 0.2435 -0.0768137 0.24355 -0.0768095 0.2436 -0.0768052 0.24365 -0.0768008 0.2437 -0.0767963 0.24375 -0.0767917 0.2438 -0.076787 0.24385 -0.0767822 0.2439 -0.0767773 0.24395 -0.0767723 0.244 -0.0767672 0.24405 -0.076762 0.2441 -0.0767567 0.24415 -0.0767512 0.2442 -0.0767457 0.24425 -0.07674 0.2443 -0.0767342 0.24435 -0.0767283 0.2444 -0.0767223 0.24445 -0.0767161 0.2445 -0.0767098 0.24455 -0.0767034 0.2446 -0.0766968 0.24465 -0.0766901 0.2447 -0.0766833 0.24475 -0.0766763 0.2448 -0.0766692 0.24485 -0.076662 0.2449 -0.0766546 0.24495 -0.0766471 0.245 -0.0766394 0.24505 -0.0766315 0.2451 -0.0766235 0.24515 -0.0766154 0.2452 -0.0766071 0.24525 -0.0765986 0.2453 -0.07659 0.24535 -0.0765812 0.2454 -0.0765723 0.24545 -0.0765632 0.2455 -0.0765539 0.24555 -0.0765444 0.2456 -0.0765348 0.24565 -0.076525 0.2457 -0.076515 0.24575 -0.0765049 0.2458 -0.0764946 0.24585 -0.0764841 0.2459 -0.0764734 0.24595 -0.0764625 0.246 -0.0764515 0.24605 -0.0764403 0.2461 -0.0764288 0.24615 -0.0764172 0.2462 -0.0764054 0.24625 -0.0763935 0.2463 -0.0763813 0.24635 -0.0763689 0.2464 -0.0763564 0.24645 -0.0763436 0.2465 -0.0763306 0.24655 -0.0763175 0.2466 -0.0763041 0.24665 -0.0762906 0.2467 -0.0762769 0.24675 -0.0762629 0.2468 -0.0762487 0.24685 -0.0762344 0.2469 -0.0762198 0.24695 -0.0762051 0.247 -0.0761901 0.24705 -0.0761749 0.2471 -0.0761595 0.24715 -0.0761439 0.2472 -0.0761281 0.24725 -0.0761121 0.2473 -0.0760959 0.24735 -0.0760795 0.2474 -0.0760628 0.24745 -0.076046 0.2475 -0.0760289 0.24755 -0.0760116 0.2476 -0.0759941 0.24765 -0.0759764 0.2477 -0.0759584 0.24775 -0.0759403 0.2478 -0.0759219 0.24785 -0.0759033 0.2479 -0.0758845 0.24795 -0.0758654 0.248 -0.0758461 0.24805 -0.0758266 0.2481 -0.0758069 0.24815 -0.0757869 0.2482 -0.0757667 0.24825 -0.0757463 0.2483 -0.0757256 0.24835 -0.0757047 0.2484 -0.0756835 0.24845 -0.0756621 0.2485 -0.0756404 0.24855 -0.0756185 0.2486 -0.0755963 0.24865 -0.0755738 0.2487 -0.0755511 0.24875 -0.075528 0.2488 -0.0755047 0.24885 -0.075481 0.2489 -0.075457 0.24895 -0.0754327 0.249 -0.075408 0.24905 -0.075383 0.2491 -0.0753575 0.24915 -0.0753316 0.2492 -0.0753051 0.24925 -0.0752782 0.2493 -0.0752506 0.24935 -0.0752223 0.2494 -0.0751931 0.24945 -0.075163 0.2495 -0.0751318 0.24955 -0.0750991 0.2496 -0.0750646 0.24965 -0.0750281 0.2497 -0.0749888 0.24975 -0.0749462 0.2498 -0.0748994 0.24985 -0.0748474 0.2499 -0.0747887 0.24995 -0.0747218 0.25 -0.0746444 brian2-2.5.4/brian2/tests/rallpack_data/ref_branch.0000066400000000000000000003405171445201106100221560ustar00rootroot000000000000000.000000 -6.500000e-02 0.000050 -6.492059e-02 0.000100 -6.488556e-02 0.000150 -6.485399e-02 0.000200 -6.482296e-02 0.000250 -6.479203e-02 0.000300 -6.476116e-02 0.000350 -6.473033e-02 0.000400 -6.469953e-02 0.000450 -6.466877e-02 0.000500 -6.463806e-02 0.000550 -6.460738e-02 0.000600 -6.457674e-02 0.000650 -6.454613e-02 0.000700 -6.451557e-02 0.000750 -6.448504e-02 0.000800 -6.445455e-02 0.000850 -6.442410e-02 0.000900 -6.439369e-02 0.000950 -6.436332e-02 0.001000 -6.433298e-02 0.001050 -6.430268e-02 0.001100 -6.427242e-02 0.001150 -6.424220e-02 0.001200 -6.421201e-02 0.001250 -6.418186e-02 0.001300 -6.415175e-02 0.001350 -6.412168e-02 0.001400 -6.409165e-02 0.001450 -6.406165e-02 0.001500 -6.403169e-02 0.001550 -6.400177e-02 0.001600 -6.397188e-02 0.001650 -6.394204e-02 0.001700 -6.391223e-02 0.001750 -6.388245e-02 0.001800 -6.385272e-02 0.001850 -6.382302e-02 0.001900 -6.379336e-02 0.001950 -6.376373e-02 0.002000 -6.373415e-02 0.002050 -6.370460e-02 0.002100 -6.367508e-02 0.002150 -6.364561e-02 0.002200 -6.361617e-02 0.002250 -6.358676e-02 0.002300 -6.355740e-02 0.002350 -6.352807e-02 0.002400 -6.349877e-02 0.002450 -6.346952e-02 0.002500 -6.344030e-02 0.002550 -6.341111e-02 0.002600 -6.338197e-02 0.002650 -6.335286e-02 0.002700 -6.332378e-02 0.002750 -6.329474e-02 0.002800 -6.326574e-02 0.002850 -6.323678e-02 0.002900 -6.320785e-02 0.002950 -6.317896e-02 0.003000 -6.315010e-02 0.003050 -6.312128e-02 0.003100 -6.309249e-02 0.003150 -6.306374e-02 0.003200 -6.303503e-02 0.003250 -6.300635e-02 0.003300 -6.297771e-02 0.003350 -6.294911e-02 0.003400 -6.292054e-02 0.003450 -6.289200e-02 0.003500 -6.286351e-02 0.003550 -6.283504e-02 0.003600 -6.280662e-02 0.003650 -6.277822e-02 0.003700 -6.274987e-02 0.003750 -6.272155e-02 0.003800 -6.269326e-02 0.003850 -6.266501e-02 0.003900 -6.263680e-02 0.003950 -6.260862e-02 0.004000 -6.258047e-02 0.004050 -6.255236e-02 0.004100 -6.252429e-02 0.004150 -6.249625e-02 0.004200 -6.246824e-02 0.004250 -6.244028e-02 0.004300 -6.241234e-02 0.004350 -6.238444e-02 0.004400 -6.235658e-02 0.004450 -6.232875e-02 0.004500 -6.230095e-02 0.004550 -6.227319e-02 0.004600 -6.224547e-02 0.004650 -6.221778e-02 0.004700 -6.219012e-02 0.004750 -6.216250e-02 0.004800 -6.213491e-02 0.004850 -6.210736e-02 0.004900 -6.207984e-02 0.004950 -6.205236e-02 0.005000 -6.202491e-02 0.005050 -6.199749e-02 0.005100 -6.197011e-02 0.005150 -6.194277e-02 0.005200 -6.191545e-02 0.005250 -6.188818e-02 0.005300 -6.186093e-02 0.005350 -6.183372e-02 0.005400 -6.180654e-02 0.005450 -6.177940e-02 0.005500 -6.175229e-02 0.005550 -6.172522e-02 0.005600 -6.169818e-02 0.005650 -6.167117e-02 0.005700 -6.164420e-02 0.005750 -6.161726e-02 0.005800 -6.159035e-02 0.005850 -6.156348e-02 0.005900 -6.153664e-02 0.005950 -6.150983e-02 0.006000 -6.148306e-02 0.006050 -6.145632e-02 0.006100 -6.142962e-02 0.006150 -6.140295e-02 0.006200 -6.137631e-02 0.006250 -6.134970e-02 0.006300 -6.132313e-02 0.006350 -6.129659e-02 0.006400 -6.127009e-02 0.006450 -6.124362e-02 0.006500 -6.121718e-02 0.006550 -6.119077e-02 0.006600 -6.116440e-02 0.006650 -6.113806e-02 0.006700 -6.111175e-02 0.006750 -6.108548e-02 0.006800 -6.105923e-02 0.006850 -6.103302e-02 0.006900 -6.100685e-02 0.006950 -6.098071e-02 0.007000 -6.095459e-02 0.007050 -6.092852e-02 0.007100 -6.090247e-02 0.007150 -6.087646e-02 0.007200 -6.085048e-02 0.007250 -6.082453e-02 0.007300 -6.079861e-02 0.007350 -6.077273e-02 0.007400 -6.074688e-02 0.007450 -6.072106e-02 0.007500 -6.069527e-02 0.007550 -6.066952e-02 0.007600 -6.064380e-02 0.007650 -6.061811e-02 0.007700 -6.059245e-02 0.007750 -6.056682e-02 0.007800 -6.054123e-02 0.007850 -6.051567e-02 0.007900 -6.049014e-02 0.007950 -6.046464e-02 0.008000 -6.043917e-02 0.008050 -6.041374e-02 0.008100 -6.038834e-02 0.008150 -6.036297e-02 0.008200 -6.033763e-02 0.008250 -6.031232e-02 0.008300 -6.028705e-02 0.008350 -6.026180e-02 0.008400 -6.023659e-02 0.008450 -6.021141e-02 0.008500 -6.018626e-02 0.008550 -6.016114e-02 0.008600 -6.013605e-02 0.008650 -6.011100e-02 0.008700 -6.008597e-02 0.008750 -6.006098e-02 0.008800 -6.003602e-02 0.008850 -6.001109e-02 0.008900 -5.998619e-02 0.008950 -5.996132e-02 0.009000 -5.993648e-02 0.009050 -5.991167e-02 0.009100 -5.988690e-02 0.009150 -5.986215e-02 0.009200 -5.983744e-02 0.009250 -5.981276e-02 0.009300 -5.978811e-02 0.009350 -5.976349e-02 0.009400 -5.973890e-02 0.009450 -5.971434e-02 0.009500 -5.968981e-02 0.009550 -5.966531e-02 0.009600 -5.964084e-02 0.009650 -5.961640e-02 0.009700 -5.959200e-02 0.009750 -5.956762e-02 0.009800 -5.954328e-02 0.009850 -5.951896e-02 0.009900 -5.949468e-02 0.009950 -5.947042e-02 0.010000 -5.944620e-02 0.010050 -5.942200e-02 0.010100 -5.939784e-02 0.010150 -5.937371e-02 0.010200 -5.934960e-02 0.010250 -5.932553e-02 0.010300 -5.930149e-02 0.010350 -5.927747e-02 0.010400 -5.925349e-02 0.010450 -5.922954e-02 0.010500 -5.920561e-02 0.010550 -5.918172e-02 0.010600 -5.915786e-02 0.010650 -5.913402e-02 0.010700 -5.911022e-02 0.010750 -5.908645e-02 0.010800 -5.906270e-02 0.010850 -5.903899e-02 0.010900 -5.901530e-02 0.010950 -5.899165e-02 0.011000 -5.896802e-02 0.011050 -5.894442e-02 0.011100 -5.892086e-02 0.011150 -5.889732e-02 0.011200 -5.887381e-02 0.011250 -5.885033e-02 0.011300 -5.882688e-02 0.011350 -5.880346e-02 0.011400 -5.878007e-02 0.011450 -5.875671e-02 0.011500 -5.873338e-02 0.011550 -5.871007e-02 0.011600 -5.868680e-02 0.011650 -5.866355e-02 0.011700 -5.864034e-02 0.011750 -5.861715e-02 0.011800 -5.859399e-02 0.011850 -5.857086e-02 0.011900 -5.854776e-02 0.011950 -5.852469e-02 0.012000 -5.850165e-02 0.012050 -5.847864e-02 0.012100 -5.845565e-02 0.012150 -5.843269e-02 0.012200 -5.840977e-02 0.012250 -5.838687e-02 0.012300 -5.836400e-02 0.012350 -5.834115e-02 0.012400 -5.831834e-02 0.012450 -5.829556e-02 0.012500 -5.827280e-02 0.012550 -5.825007e-02 0.012600 -5.822737e-02 0.012650 -5.820470e-02 0.012700 -5.818206e-02 0.012750 -5.815944e-02 0.012800 -5.813686e-02 0.012850 -5.811430e-02 0.012900 -5.809177e-02 0.012950 -5.806927e-02 0.013000 -5.804679e-02 0.013050 -5.802435e-02 0.013100 -5.800193e-02 0.013150 -5.797954e-02 0.013200 -5.795718e-02 0.013250 -5.793484e-02 0.013300 -5.791254e-02 0.013350 -5.789026e-02 0.013400 -5.786801e-02 0.013450 -5.784579e-02 0.013500 -5.782359e-02 0.013550 -5.780143e-02 0.013600 -5.777929e-02 0.013650 -5.775718e-02 0.013700 -5.773509e-02 0.013750 -5.771304e-02 0.013800 -5.769101e-02 0.013850 -5.766901e-02 0.013900 -5.764703e-02 0.013950 -5.762509e-02 0.014000 -5.760317e-02 0.014050 -5.758128e-02 0.014100 -5.755941e-02 0.014150 -5.753757e-02 0.014200 -5.751576e-02 0.014250 -5.749398e-02 0.014300 -5.747223e-02 0.014350 -5.745050e-02 0.014400 -5.742880e-02 0.014450 -5.740712e-02 0.014500 -5.738548e-02 0.014550 -5.736386e-02 0.014600 -5.734227e-02 0.014650 -5.732070e-02 0.014700 -5.729916e-02 0.014750 -5.727765e-02 0.014800 -5.725616e-02 0.014850 -5.723471e-02 0.014900 -5.721328e-02 0.014950 -5.719187e-02 0.015000 -5.717049e-02 0.015050 -5.714914e-02 0.015100 -5.712782e-02 0.015150 -5.710652e-02 0.015200 -5.708525e-02 0.015250 -5.706400e-02 0.015300 -5.704279e-02 0.015350 -5.702160e-02 0.015400 -5.700043e-02 0.015450 -5.697929e-02 0.015500 -5.695818e-02 0.015550 -5.693709e-02 0.015600 -5.691603e-02 0.015650 -5.689500e-02 0.015700 -5.687399e-02 0.015750 -5.685301e-02 0.015800 -5.683206e-02 0.015850 -5.681113e-02 0.015900 -5.679023e-02 0.015950 -5.676935e-02 0.016000 -5.674850e-02 0.016050 -5.672768e-02 0.016100 -5.670688e-02 0.016150 -5.668611e-02 0.016200 -5.666536e-02 0.016250 -5.664464e-02 0.016300 -5.662395e-02 0.016350 -5.660328e-02 0.016400 -5.658264e-02 0.016450 -5.656202e-02 0.016500 -5.654143e-02 0.016550 -5.652087e-02 0.016600 -5.650033e-02 0.016650 -5.647981e-02 0.016700 -5.645933e-02 0.016750 -5.643886e-02 0.016800 -5.641842e-02 0.016850 -5.639801e-02 0.016900 -5.637763e-02 0.016950 -5.635727e-02 0.017000 -5.633693e-02 0.017050 -5.631662e-02 0.017100 -5.629634e-02 0.017150 -5.627608e-02 0.017200 -5.625585e-02 0.017250 -5.623564e-02 0.017300 -5.621545e-02 0.017350 -5.619530e-02 0.017400 -5.617516e-02 0.017450 -5.615506e-02 0.017500 -5.613497e-02 0.017550 -5.611492e-02 0.017600 -5.609488e-02 0.017650 -5.607488e-02 0.017700 -5.605489e-02 0.017750 -5.603494e-02 0.017800 -5.601500e-02 0.017850 -5.599510e-02 0.017900 -5.597521e-02 0.017950 -5.595536e-02 0.018000 -5.593552e-02 0.018050 -5.591571e-02 0.018100 -5.589593e-02 0.018150 -5.587617e-02 0.018200 -5.585644e-02 0.018250 -5.583673e-02 0.018300 -5.581704e-02 0.018350 -5.579738e-02 0.018400 -5.577775e-02 0.018450 -5.575814e-02 0.018500 -5.573855e-02 0.018550 -5.571899e-02 0.018600 -5.569945e-02 0.018650 -5.567994e-02 0.018700 -5.566045e-02 0.018750 -5.564098e-02 0.018800 -5.562154e-02 0.018850 -5.560213e-02 0.018900 -5.558273e-02 0.018950 -5.556337e-02 0.019000 -5.554402e-02 0.019050 -5.552471e-02 0.019100 -5.550541e-02 0.019150 -5.548614e-02 0.019200 -5.546689e-02 0.019250 -5.544767e-02 0.019300 -5.542847e-02 0.019350 -5.540929e-02 0.019400 -5.539014e-02 0.019450 -5.537102e-02 0.019500 -5.535191e-02 0.019550 -5.533283e-02 0.019600 -5.531378e-02 0.019650 -5.529475e-02 0.019700 -5.527574e-02 0.019750 -5.525676e-02 0.019800 -5.523780e-02 0.019850 -5.521886e-02 0.019900 -5.519995e-02 0.019950 -5.518106e-02 0.020000 -5.516219e-02 0.020050 -5.514335e-02 0.020100 -5.512453e-02 0.020150 -5.510573e-02 0.020200 -5.508696e-02 0.020250 -5.506822e-02 0.020300 -5.504949e-02 0.020350 -5.503079e-02 0.020400 -5.501211e-02 0.020450 -5.499346e-02 0.020500 -5.497483e-02 0.020550 -5.495622e-02 0.020600 -5.493763e-02 0.020650 -5.491907e-02 0.020700 -5.490053e-02 0.020750 -5.488202e-02 0.020800 -5.486352e-02 0.020850 -5.484506e-02 0.020900 -5.482661e-02 0.020950 -5.480819e-02 0.021000 -5.478979e-02 0.021050 -5.477141e-02 0.021100 -5.475306e-02 0.021150 -5.473472e-02 0.021200 -5.471642e-02 0.021250 -5.469813e-02 0.021300 -5.467987e-02 0.021350 -5.466163e-02 0.021400 -5.464341e-02 0.021450 -5.462522e-02 0.021500 -5.460705e-02 0.021550 -5.458890e-02 0.021600 -5.457077e-02 0.021650 -5.455267e-02 0.021700 -5.453459e-02 0.021750 -5.451653e-02 0.021800 -5.449849e-02 0.021850 -5.448048e-02 0.021900 -5.446249e-02 0.021950 -5.444452e-02 0.022000 -5.442658e-02 0.022050 -5.440865e-02 0.022100 -5.439075e-02 0.022150 -5.437287e-02 0.022200 -5.435502e-02 0.022250 -5.433718e-02 0.022300 -5.431937e-02 0.022350 -5.430158e-02 0.022400 -5.428382e-02 0.022450 -5.426607e-02 0.022500 -5.424835e-02 0.022550 -5.423065e-02 0.022600 -5.421297e-02 0.022650 -5.419531e-02 0.022700 -5.417768e-02 0.022750 -5.416007e-02 0.022800 -5.414248e-02 0.022850 -5.412491e-02 0.022900 -5.410736e-02 0.022950 -5.408984e-02 0.023000 -5.407233e-02 0.023050 -5.405485e-02 0.023100 -5.403739e-02 0.023150 -5.401996e-02 0.023200 -5.400254e-02 0.023250 -5.398515e-02 0.023300 -5.396778e-02 0.023350 -5.395043e-02 0.023400 -5.393310e-02 0.023450 -5.391579e-02 0.023500 -5.389851e-02 0.023550 -5.388124e-02 0.023600 -5.386400e-02 0.023650 -5.384678e-02 0.023700 -5.382958e-02 0.023750 -5.381240e-02 0.023800 -5.379525e-02 0.023850 -5.377811e-02 0.023900 -5.376100e-02 0.023950 -5.374391e-02 0.024000 -5.372684e-02 0.024050 -5.370979e-02 0.024100 -5.369276e-02 0.024150 -5.367575e-02 0.024200 -5.365877e-02 0.024250 -5.364180e-02 0.024300 -5.362486e-02 0.024350 -5.360794e-02 0.024400 -5.359104e-02 0.024450 -5.357416e-02 0.024500 -5.355730e-02 0.024550 -5.354046e-02 0.024600 -5.352365e-02 0.024650 -5.350685e-02 0.024700 -5.349008e-02 0.024750 -5.347332e-02 0.024800 -5.345659e-02 0.024850 -5.343988e-02 0.024900 -5.342319e-02 0.024950 -5.340652e-02 0.025000 -5.338987e-02 0.025050 -5.337324e-02 0.025100 -5.335664e-02 0.025150 -5.334005e-02 0.025200 -5.332348e-02 0.025250 -5.330694e-02 0.025300 -5.329041e-02 0.025350 -5.327391e-02 0.025400 -5.325743e-02 0.025450 -5.324096e-02 0.025500 -5.322452e-02 0.025550 -5.320810e-02 0.025600 -5.319170e-02 0.025650 -5.317532e-02 0.025700 -5.315896e-02 0.025750 -5.314262e-02 0.025800 -5.312630e-02 0.025850 -5.311000e-02 0.025900 -5.309372e-02 0.025950 -5.307746e-02 0.026000 -5.306122e-02 0.026050 -5.304501e-02 0.026100 -5.302881e-02 0.026150 -5.301263e-02 0.026200 -5.299648e-02 0.026250 -5.298034e-02 0.026300 -5.296422e-02 0.026350 -5.294813e-02 0.026400 -5.293205e-02 0.026450 -5.291599e-02 0.026500 -5.289996e-02 0.026550 -5.288394e-02 0.026600 -5.286794e-02 0.026650 -5.285197e-02 0.026700 -5.283601e-02 0.026750 -5.282008e-02 0.026800 -5.280416e-02 0.026850 -5.278826e-02 0.026900 -5.277239e-02 0.026950 -5.275653e-02 0.027000 -5.274069e-02 0.027050 -5.272488e-02 0.027100 -5.270908e-02 0.027150 -5.269330e-02 0.027200 -5.267754e-02 0.027250 -5.266180e-02 0.027300 -5.264608e-02 0.027350 -5.263039e-02 0.027400 -5.261471e-02 0.027450 -5.259905e-02 0.027500 -5.258341e-02 0.027550 -5.256779e-02 0.027600 -5.255218e-02 0.027650 -5.253660e-02 0.027700 -5.252104e-02 0.027750 -5.250550e-02 0.027800 -5.248997e-02 0.027850 -5.247447e-02 0.027900 -5.245899e-02 0.027950 -5.244352e-02 0.028000 -5.242808e-02 0.028050 -5.241265e-02 0.028100 -5.239724e-02 0.028150 -5.238185e-02 0.028200 -5.236648e-02 0.028250 -5.235113e-02 0.028300 -5.233580e-02 0.028350 -5.232049e-02 0.028400 -5.230520e-02 0.028450 -5.228993e-02 0.028500 -5.227467e-02 0.028550 -5.225944e-02 0.028600 -5.224422e-02 0.028650 -5.222902e-02 0.028700 -5.221385e-02 0.028750 -5.219869e-02 0.028800 -5.218355e-02 0.028850 -5.216843e-02 0.028900 -5.215332e-02 0.028950 -5.213824e-02 0.029000 -5.212317e-02 0.029050 -5.210813e-02 0.029100 -5.209310e-02 0.029150 -5.207809e-02 0.029200 -5.206311e-02 0.029250 -5.204813e-02 0.029300 -5.203318e-02 0.029350 -5.201825e-02 0.029400 -5.200333e-02 0.029450 -5.198844e-02 0.029500 -5.197356e-02 0.029550 -5.195870e-02 0.029600 -5.194386e-02 0.029650 -5.192904e-02 0.029700 -5.191424e-02 0.029750 -5.189945e-02 0.029800 -5.188469e-02 0.029850 -5.186994e-02 0.029900 -5.185521e-02 0.029950 -5.184050e-02 0.030000 -5.182580e-02 0.030050 -5.181113e-02 0.030100 -5.179647e-02 0.030150 -5.178184e-02 0.030200 -5.176722e-02 0.030250 -5.175261e-02 0.030300 -5.173803e-02 0.030350 -5.172347e-02 0.030400 -5.170892e-02 0.030450 -5.169439e-02 0.030500 -5.167988e-02 0.030550 -5.166539e-02 0.030600 -5.165092e-02 0.030650 -5.163646e-02 0.030700 -5.162202e-02 0.030750 -5.160760e-02 0.030800 -5.159320e-02 0.030850 -5.157882e-02 0.030900 -5.156445e-02 0.030950 -5.155010e-02 0.031000 -5.153577e-02 0.031050 -5.152146e-02 0.031100 -5.150717e-02 0.031150 -5.149289e-02 0.031200 -5.147863e-02 0.031250 -5.146439e-02 0.031300 -5.145017e-02 0.031350 -5.143596e-02 0.031400 -5.142178e-02 0.031450 -5.140761e-02 0.031500 -5.139346e-02 0.031550 -5.137932e-02 0.031600 -5.136520e-02 0.031650 -5.135111e-02 0.031700 -5.133703e-02 0.031750 -5.132296e-02 0.031800 -5.130892e-02 0.031850 -5.129489e-02 0.031900 -5.128088e-02 0.031950 -5.126688e-02 0.032000 -5.125291e-02 0.032050 -5.123895e-02 0.032100 -5.122501e-02 0.032150 -5.121108e-02 0.032200 -5.119718e-02 0.032250 -5.118329e-02 0.032300 -5.116941e-02 0.032350 -5.115556e-02 0.032400 -5.114172e-02 0.032450 -5.112790e-02 0.032500 -5.111410e-02 0.032550 -5.110032e-02 0.032600 -5.108655e-02 0.032650 -5.107280e-02 0.032700 -5.105906e-02 0.032750 -5.104535e-02 0.032800 -5.103165e-02 0.032850 -5.101797e-02 0.032900 -5.100430e-02 0.032950 -5.099065e-02 0.033000 -5.097702e-02 0.033050 -5.096341e-02 0.033100 -5.094981e-02 0.033150 -5.093623e-02 0.033200 -5.092267e-02 0.033250 -5.090912e-02 0.033300 -5.089559e-02 0.033350 -5.088208e-02 0.033400 -5.086858e-02 0.033450 -5.085511e-02 0.033500 -5.084164e-02 0.033550 -5.082820e-02 0.033600 -5.081477e-02 0.033650 -5.080136e-02 0.033700 -5.078797e-02 0.033750 -5.077459e-02 0.033800 -5.076123e-02 0.033850 -5.074788e-02 0.033900 -5.073455e-02 0.033950 -5.072124e-02 0.034000 -5.070795e-02 0.034050 -5.069467e-02 0.034100 -5.068141e-02 0.034150 -5.066816e-02 0.034200 -5.065494e-02 0.034250 -5.064173e-02 0.034300 -5.062853e-02 0.034350 -5.061535e-02 0.034400 -5.060219e-02 0.034450 -5.058904e-02 0.034500 -5.057591e-02 0.034550 -5.056280e-02 0.034600 -5.054971e-02 0.034650 -5.053662e-02 0.034700 -5.052356e-02 0.034750 -5.051051e-02 0.034800 -5.049748e-02 0.034850 -5.048447e-02 0.034900 -5.047147e-02 0.034950 -5.045849e-02 0.035000 -5.044552e-02 0.035050 -5.043257e-02 0.035100 -5.041963e-02 0.035150 -5.040672e-02 0.035200 -5.039382e-02 0.035250 -5.038093e-02 0.035300 -5.036806e-02 0.035350 -5.035521e-02 0.035400 -5.034237e-02 0.035450 -5.032955e-02 0.035500 -5.031674e-02 0.035550 -5.030396e-02 0.035600 -5.029118e-02 0.035650 -5.027842e-02 0.035700 -5.026568e-02 0.035750 -5.025296e-02 0.035800 -5.024025e-02 0.035850 -5.022755e-02 0.035900 -5.021488e-02 0.035950 -5.020221e-02 0.036000 -5.018957e-02 0.036050 -5.017694e-02 0.036100 -5.016432e-02 0.036150 -5.015173e-02 0.036200 -5.013914e-02 0.036250 -5.012657e-02 0.036300 -5.011402e-02 0.036350 -5.010149e-02 0.036400 -5.008897e-02 0.036450 -5.007646e-02 0.036500 -5.006397e-02 0.036550 -5.005150e-02 0.036600 -5.003904e-02 0.036650 -5.002660e-02 0.036700 -5.001417e-02 0.036750 -5.000176e-02 0.036800 -4.998937e-02 0.036850 -4.997699e-02 0.036900 -4.996462e-02 0.036950 -4.995227e-02 0.037000 -4.993994e-02 0.037050 -4.992762e-02 0.037100 -4.991532e-02 0.037150 -4.990303e-02 0.037200 -4.989076e-02 0.037250 -4.987850e-02 0.037300 -4.986626e-02 0.037350 -4.985403e-02 0.037400 -4.984182e-02 0.037450 -4.982962e-02 0.037500 -4.981744e-02 0.037550 -4.980528e-02 0.037600 -4.979313e-02 0.037650 -4.978099e-02 0.037700 -4.976887e-02 0.037750 -4.975677e-02 0.037800 -4.974468e-02 0.037850 -4.973260e-02 0.037900 -4.972055e-02 0.037950 -4.970850e-02 0.038000 -4.969647e-02 0.038050 -4.968446e-02 0.038100 -4.967246e-02 0.038150 -4.966047e-02 0.038200 -4.964850e-02 0.038250 -4.963655e-02 0.038300 -4.962461e-02 0.038350 -4.961269e-02 0.038400 -4.960078e-02 0.038450 -4.958888e-02 0.038500 -4.957700e-02 0.038550 -4.956514e-02 0.038600 -4.955329e-02 0.038650 -4.954145e-02 0.038700 -4.952963e-02 0.038750 -4.951782e-02 0.038800 -4.950603e-02 0.038850 -4.949426e-02 0.038900 -4.948249e-02 0.038950 -4.947075e-02 0.039000 -4.945902e-02 0.039050 -4.944730e-02 0.039100 -4.943559e-02 0.039150 -4.942391e-02 0.039200 -4.941223e-02 0.039250 -4.940057e-02 0.039300 -4.938893e-02 0.039350 -4.937730e-02 0.039400 -4.936568e-02 0.039450 -4.935408e-02 0.039500 -4.934249e-02 0.039550 -4.933092e-02 0.039600 -4.931937e-02 0.039650 -4.930782e-02 0.039700 -4.929629e-02 0.039750 -4.928478e-02 0.039800 -4.927328e-02 0.039850 -4.926179e-02 0.039900 -4.925032e-02 0.039950 -4.923887e-02 0.040000 -4.922742e-02 0.040050 -4.921599e-02 0.040100 -4.920458e-02 0.040150 -4.919318e-02 0.040200 -4.918179e-02 0.040250 -4.917042e-02 0.040300 -4.915907e-02 0.040350 -4.914772e-02 0.040400 -4.913639e-02 0.040450 -4.912508e-02 0.040500 -4.911378e-02 0.040550 -4.910249e-02 0.040600 -4.909122e-02 0.040650 -4.907996e-02 0.040700 -4.906872e-02 0.040750 -4.905749e-02 0.040800 -4.904627e-02 0.040850 -4.903507e-02 0.040900 -4.902388e-02 0.040950 -4.901271e-02 0.041000 -4.900155e-02 0.041050 -4.899040e-02 0.041100 -4.897927e-02 0.041150 -4.896815e-02 0.041200 -4.895705e-02 0.041250 -4.894596e-02 0.041300 -4.893488e-02 0.041350 -4.892382e-02 0.041400 -4.891277e-02 0.041450 -4.890173e-02 0.041500 -4.889071e-02 0.041550 -4.887970e-02 0.041600 -4.886871e-02 0.041650 -4.885773e-02 0.041700 -4.884676e-02 0.041750 -4.883581e-02 0.041800 -4.882487e-02 0.041850 -4.881394e-02 0.041900 -4.880303e-02 0.041950 -4.879213e-02 0.042000 -4.878125e-02 0.042050 -4.877038e-02 0.042100 -4.875952e-02 0.042150 -4.874868e-02 0.042200 -4.873785e-02 0.042250 -4.872703e-02 0.042300 -4.871623e-02 0.042350 -4.870544e-02 0.042400 -4.869466e-02 0.042450 -4.868390e-02 0.042500 -4.867315e-02 0.042550 -4.866241e-02 0.042600 -4.865169e-02 0.042650 -4.864098e-02 0.042700 -4.863028e-02 0.042750 -4.861960e-02 0.042800 -4.860893e-02 0.042850 -4.859828e-02 0.042900 -4.858764e-02 0.042950 -4.857701e-02 0.043000 -4.856639e-02 0.043050 -4.855579e-02 0.043100 -4.854520e-02 0.043150 -4.853462e-02 0.043200 -4.852406e-02 0.043250 -4.851351e-02 0.043300 -4.850297e-02 0.043350 -4.849245e-02 0.043400 -4.848194e-02 0.043450 -4.847144e-02 0.043500 -4.846096e-02 0.043550 -4.845049e-02 0.043600 -4.844003e-02 0.043650 -4.842959e-02 0.043700 -4.841915e-02 0.043750 -4.840873e-02 0.043800 -4.839833e-02 0.043850 -4.838794e-02 0.043900 -4.837756e-02 0.043950 -4.836719e-02 0.044000 -4.835684e-02 0.044050 -4.834649e-02 0.044100 -4.833617e-02 0.044150 -4.832585e-02 0.044200 -4.831555e-02 0.044250 -4.830526e-02 0.044300 -4.829498e-02 0.044350 -4.828472e-02 0.044400 -4.827447e-02 0.044450 -4.826423e-02 0.044500 -4.825401e-02 0.044550 -4.824379e-02 0.044600 -4.823360e-02 0.044650 -4.822341e-02 0.044700 -4.821323e-02 0.044750 -4.820307e-02 0.044800 -4.819292e-02 0.044850 -4.818279e-02 0.044900 -4.817266e-02 0.044950 -4.816255e-02 0.045000 -4.815246e-02 0.045050 -4.814237e-02 0.045100 -4.813230e-02 0.045150 -4.812224e-02 0.045200 -4.811219e-02 0.045250 -4.810215e-02 0.045300 -4.809213e-02 0.045350 -4.808212e-02 0.045400 -4.807212e-02 0.045450 -4.806214e-02 0.045500 -4.805217e-02 0.045550 -4.804221e-02 0.045600 -4.803226e-02 0.045650 -4.802232e-02 0.045700 -4.801240e-02 0.045750 -4.800249e-02 0.045800 -4.799259e-02 0.045850 -4.798271e-02 0.045900 -4.797283e-02 0.045950 -4.796297e-02 0.046000 -4.795312e-02 0.046050 -4.794329e-02 0.046100 -4.793346e-02 0.046150 -4.792365e-02 0.046200 -4.791385e-02 0.046250 -4.790406e-02 0.046300 -4.789429e-02 0.046350 -4.788452e-02 0.046400 -4.787477e-02 0.046450 -4.786503e-02 0.046500 -4.785531e-02 0.046550 -4.784559e-02 0.046600 -4.783589e-02 0.046650 -4.782620e-02 0.046700 -4.781652e-02 0.046750 -4.780686e-02 0.046800 -4.779720e-02 0.046850 -4.778756e-02 0.046900 -4.777793e-02 0.046950 -4.776831e-02 0.047000 -4.775871e-02 0.047050 -4.774912e-02 0.047100 -4.773953e-02 0.047150 -4.772996e-02 0.047200 -4.772041e-02 0.047250 -4.771086e-02 0.047300 -4.770133e-02 0.047350 -4.769181e-02 0.047400 -4.768230e-02 0.047450 -4.767280e-02 0.047500 -4.766331e-02 0.047550 -4.765384e-02 0.047600 -4.764437e-02 0.047650 -4.763492e-02 0.047700 -4.762548e-02 0.047750 -4.761606e-02 0.047800 -4.760664e-02 0.047850 -4.759724e-02 0.047900 -4.758785e-02 0.047950 -4.757847e-02 0.048000 -4.756910e-02 0.048050 -4.755974e-02 0.048100 -4.755039e-02 0.048150 -4.754106e-02 0.048200 -4.753174e-02 0.048250 -4.752243e-02 0.048300 -4.751313e-02 0.048350 -4.750384e-02 0.048400 -4.749457e-02 0.048450 -4.748531e-02 0.048500 -4.747605e-02 0.048550 -4.746681e-02 0.048600 -4.745758e-02 0.048650 -4.744837e-02 0.048700 -4.743916e-02 0.048750 -4.742997e-02 0.048800 -4.742078e-02 0.048850 -4.741161e-02 0.048900 -4.740245e-02 0.048950 -4.739330e-02 0.049000 -4.738417e-02 0.049050 -4.737504e-02 0.049100 -4.736593e-02 0.049150 -4.735682e-02 0.049200 -4.734773e-02 0.049250 -4.733865e-02 0.049300 -4.732958e-02 0.049350 -4.732052e-02 0.049400 -4.731148e-02 0.049450 -4.730244e-02 0.049500 -4.729342e-02 0.049550 -4.728441e-02 0.049600 -4.727541e-02 0.049650 -4.726642e-02 0.049700 -4.725744e-02 0.049750 -4.724847e-02 0.049800 -4.723951e-02 0.049850 -4.723057e-02 0.049900 -4.722164e-02 0.049950 -4.721271e-02 0.050000 -4.720380e-02 0.050050 -4.719490e-02 0.050100 -4.718601e-02 0.050150 -4.717713e-02 0.050200 -4.716827e-02 0.050250 -4.715941e-02 0.050300 -4.715056e-02 0.050350 -4.714173e-02 0.050400 -4.713291e-02 0.050450 -4.712410e-02 0.050500 -4.711530e-02 0.050550 -4.710651e-02 0.050600 -4.709773e-02 0.050650 -4.708896e-02 0.050700 -4.708020e-02 0.050750 -4.707146e-02 0.050800 -4.706272e-02 0.050850 -4.705400e-02 0.050900 -4.704528e-02 0.050950 -4.703658e-02 0.051000 -4.702789e-02 0.051050 -4.701921e-02 0.051100 -4.701054e-02 0.051150 -4.700188e-02 0.051200 -4.699323e-02 0.051250 -4.698459e-02 0.051300 -4.697597e-02 0.051350 -4.696735e-02 0.051400 -4.695875e-02 0.051450 -4.695015e-02 0.051500 -4.694157e-02 0.051550 -4.693300e-02 0.051600 -4.692443e-02 0.051650 -4.691588e-02 0.051700 -4.690734e-02 0.051750 -4.689881e-02 0.051800 -4.689029e-02 0.051850 -4.688178e-02 0.051900 -4.687329e-02 0.051950 -4.686480e-02 0.052000 -4.685632e-02 0.052050 -4.684786e-02 0.052100 -4.683940e-02 0.052150 -4.683095e-02 0.052200 -4.682252e-02 0.052250 -4.681409e-02 0.052300 -4.680568e-02 0.052350 -4.679728e-02 0.052400 -4.678889e-02 0.052450 -4.678050e-02 0.052500 -4.677213e-02 0.052550 -4.676377e-02 0.052600 -4.675542e-02 0.052650 -4.674708e-02 0.052700 -4.673875e-02 0.052750 -4.673043e-02 0.052800 -4.672212e-02 0.052850 -4.671382e-02 0.052900 -4.670553e-02 0.052950 -4.669726e-02 0.053000 -4.668899e-02 0.053050 -4.668073e-02 0.053100 -4.667248e-02 0.053150 -4.666425e-02 0.053200 -4.665602e-02 0.053250 -4.664780e-02 0.053300 -4.663960e-02 0.053350 -4.663140e-02 0.053400 -4.662322e-02 0.053450 -4.661504e-02 0.053500 -4.660688e-02 0.053550 -4.659872e-02 0.053600 -4.659058e-02 0.053650 -4.658244e-02 0.053700 -4.657432e-02 0.053750 -4.656621e-02 0.053800 -4.655810e-02 0.053850 -4.655001e-02 0.053900 -4.654192e-02 0.053950 -4.653385e-02 0.054000 -4.652579e-02 0.054050 -4.651773e-02 0.054100 -4.650969e-02 0.054150 -4.650166e-02 0.054200 -4.649363e-02 0.054250 -4.648562e-02 0.054300 -4.647762e-02 0.054350 -4.646962e-02 0.054400 -4.646164e-02 0.054450 -4.645367e-02 0.054500 -4.644570e-02 0.054550 -4.643775e-02 0.054600 -4.642981e-02 0.054650 -4.642187e-02 0.054700 -4.641395e-02 0.054750 -4.640604e-02 0.054800 -4.639813e-02 0.054850 -4.639024e-02 0.054900 -4.638236e-02 0.054950 -4.637448e-02 0.055000 -4.636662e-02 0.055050 -4.635876e-02 0.055100 -4.635092e-02 0.055150 -4.634308e-02 0.055200 -4.633526e-02 0.055250 -4.632744e-02 0.055300 -4.631964e-02 0.055350 -4.631184e-02 0.055400 -4.630405e-02 0.055450 -4.629628e-02 0.055500 -4.628851e-02 0.055550 -4.628075e-02 0.055600 -4.627301e-02 0.055650 -4.626527e-02 0.055700 -4.625754e-02 0.055750 -4.624982e-02 0.055800 -4.624211e-02 0.055850 -4.623441e-02 0.055900 -4.622672e-02 0.055950 -4.621905e-02 0.056000 -4.621137e-02 0.056050 -4.620371e-02 0.056100 -4.619606e-02 0.056150 -4.618842e-02 0.056200 -4.618079e-02 0.056250 -4.617317e-02 0.056300 -4.616555e-02 0.056350 -4.615795e-02 0.056400 -4.615036e-02 0.056450 -4.614277e-02 0.056500 -4.613520e-02 0.056550 -4.612763e-02 0.056600 -4.612008e-02 0.056650 -4.611253e-02 0.056700 -4.610499e-02 0.056750 -4.609746e-02 0.056800 -4.608995e-02 0.056850 -4.608244e-02 0.056900 -4.607494e-02 0.056950 -4.606745e-02 0.057000 -4.605997e-02 0.057050 -4.605249e-02 0.057100 -4.604503e-02 0.057150 -4.603758e-02 0.057200 -4.603014e-02 0.057250 -4.602270e-02 0.057300 -4.601528e-02 0.057350 -4.600786e-02 0.057400 -4.600045e-02 0.057450 -4.599306e-02 0.057500 -4.598567e-02 0.057550 -4.597829e-02 0.057600 -4.597092e-02 0.057650 -4.596356e-02 0.057700 -4.595621e-02 0.057750 -4.594887e-02 0.057800 -4.594154e-02 0.057850 -4.593421e-02 0.057900 -4.592690e-02 0.057950 -4.591959e-02 0.058000 -4.591230e-02 0.058050 -4.590501e-02 0.058100 -4.589773e-02 0.058150 -4.589046e-02 0.058200 -4.588320e-02 0.058250 -4.587595e-02 0.058300 -4.586871e-02 0.058350 -4.586148e-02 0.058400 -4.585425e-02 0.058450 -4.584704e-02 0.058500 -4.583983e-02 0.058550 -4.583264e-02 0.058600 -4.582545e-02 0.058650 -4.581827e-02 0.058700 -4.581110e-02 0.058750 -4.580394e-02 0.058800 -4.579679e-02 0.058850 -4.578965e-02 0.058900 -4.578251e-02 0.058950 -4.577539e-02 0.059000 -4.576827e-02 0.059050 -4.576116e-02 0.059100 -4.575407e-02 0.059150 -4.574698e-02 0.059200 -4.573990e-02 0.059250 -4.573282e-02 0.059300 -4.572576e-02 0.059350 -4.571871e-02 0.059400 -4.571166e-02 0.059450 -4.570463e-02 0.059500 -4.569760e-02 0.059550 -4.569058e-02 0.059600 -4.568357e-02 0.059650 -4.567657e-02 0.059700 -4.566958e-02 0.059750 -4.566259e-02 0.059800 -4.565562e-02 0.059850 -4.564865e-02 0.059900 -4.564169e-02 0.059950 -4.563474e-02 0.060000 -4.562780e-02 0.060050 -4.562087e-02 0.060100 -4.561395e-02 0.060150 -4.560703e-02 0.060200 -4.560013e-02 0.060250 -4.559323e-02 0.060300 -4.558634e-02 0.060350 -4.557946e-02 0.060400 -4.557259e-02 0.060450 -4.556573e-02 0.060500 -4.555887e-02 0.060550 -4.555203e-02 0.060600 -4.554519e-02 0.060650 -4.553836e-02 0.060700 -4.553154e-02 0.060750 -4.552473e-02 0.060800 -4.551793e-02 0.060850 -4.551114e-02 0.060900 -4.550435e-02 0.060950 -4.549757e-02 0.061000 -4.549080e-02 0.061050 -4.548404e-02 0.061100 -4.547729e-02 0.061150 -4.547055e-02 0.061200 -4.546381e-02 0.061250 -4.545708e-02 0.061300 -4.545037e-02 0.061350 -4.544366e-02 0.061400 -4.543695e-02 0.061450 -4.543026e-02 0.061500 -4.542358e-02 0.061550 -4.541690e-02 0.061600 -4.541023e-02 0.061650 -4.540357e-02 0.061700 -4.539692e-02 0.061750 -4.539028e-02 0.061800 -4.538364e-02 0.061850 -4.537702e-02 0.061900 -4.537040e-02 0.061950 -4.536379e-02 0.062000 -4.535719e-02 0.062050 -4.535059e-02 0.062100 -4.534401e-02 0.062150 -4.533743e-02 0.062200 -4.533086e-02 0.062250 -4.532430e-02 0.062300 -4.531775e-02 0.062350 -4.531120e-02 0.062400 -4.530467e-02 0.062450 -4.529814e-02 0.062500 -4.529162e-02 0.062550 -4.528511e-02 0.062600 -4.527860e-02 0.062650 -4.527211e-02 0.062700 -4.526562e-02 0.062750 -4.525914e-02 0.062800 -4.525267e-02 0.062850 -4.524621e-02 0.062900 -4.523975e-02 0.062950 -4.523331e-02 0.063000 -4.522687e-02 0.063050 -4.522044e-02 0.063100 -4.521401e-02 0.063150 -4.520760e-02 0.063200 -4.520119e-02 0.063250 -4.519479e-02 0.063300 -4.518840e-02 0.063350 -4.518202e-02 0.063400 -4.517564e-02 0.063450 -4.516928e-02 0.063500 -4.516292e-02 0.063550 -4.515657e-02 0.063600 -4.515023e-02 0.063650 -4.514389e-02 0.063700 -4.513756e-02 0.063750 -4.513124e-02 0.063800 -4.512493e-02 0.063850 -4.511863e-02 0.063900 -4.511233e-02 0.063950 -4.510605e-02 0.064000 -4.509977e-02 0.064050 -4.509349e-02 0.064100 -4.508723e-02 0.064150 -4.508097e-02 0.064200 -4.507472e-02 0.064250 -4.506848e-02 0.064300 -4.506225e-02 0.064350 -4.505603e-02 0.064400 -4.504981e-02 0.064450 -4.504360e-02 0.064500 -4.503740e-02 0.064550 -4.503120e-02 0.064600 -4.502502e-02 0.064650 -4.501884e-02 0.064700 -4.501267e-02 0.064750 -4.500650e-02 0.064800 -4.500035e-02 0.064850 -4.499420e-02 0.064900 -4.498806e-02 0.064950 -4.498193e-02 0.065000 -4.497580e-02 0.065050 -4.496969e-02 0.065100 -4.496358e-02 0.065150 -4.495747e-02 0.065200 -4.495138e-02 0.065250 -4.494529e-02 0.065300 -4.493921e-02 0.065350 -4.493314e-02 0.065400 -4.492708e-02 0.065450 -4.492102e-02 0.065500 -4.491497e-02 0.065550 -4.490893e-02 0.065600 -4.490290e-02 0.065650 -4.489687e-02 0.065700 -4.489085e-02 0.065750 -4.488484e-02 0.065800 -4.487884e-02 0.065850 -4.487284e-02 0.065900 -4.486686e-02 0.065950 -4.486087e-02 0.066000 -4.485490e-02 0.066050 -4.484893e-02 0.066100 -4.484298e-02 0.066150 -4.483702e-02 0.066200 -4.483108e-02 0.066250 -4.482514e-02 0.066300 -4.481922e-02 0.066350 -4.481329e-02 0.066400 -4.480738e-02 0.066450 -4.480147e-02 0.066500 -4.479557e-02 0.066550 -4.478968e-02 0.066600 -4.478380e-02 0.066650 -4.477792e-02 0.066700 -4.477205e-02 0.066750 -4.476619e-02 0.066800 -4.476033e-02 0.066850 -4.475448e-02 0.066900 -4.474864e-02 0.066950 -4.474281e-02 0.067000 -4.473698e-02 0.067050 -4.473116e-02 0.067100 -4.472535e-02 0.067150 -4.471955e-02 0.067200 -4.471375e-02 0.067250 -4.470796e-02 0.067300 -4.470218e-02 0.067350 -4.469640e-02 0.067400 -4.469064e-02 0.067450 -4.468488e-02 0.067500 -4.467912e-02 0.067550 -4.467337e-02 0.067600 -4.466764e-02 0.067650 -4.466190e-02 0.067700 -4.465618e-02 0.067750 -4.465046e-02 0.067800 -4.464475e-02 0.067850 -4.463905e-02 0.067900 -4.463335e-02 0.067950 -4.462766e-02 0.068000 -4.462198e-02 0.068050 -4.461630e-02 0.068100 -4.461063e-02 0.068150 -4.460497e-02 0.068200 -4.459932e-02 0.068250 -4.459367e-02 0.068300 -4.458803e-02 0.068350 -4.458240e-02 0.068400 -4.457677e-02 0.068450 -4.457116e-02 0.068500 -4.456554e-02 0.068550 -4.455994e-02 0.068600 -4.455434e-02 0.068650 -4.454875e-02 0.068700 -4.454317e-02 0.068750 -4.453759e-02 0.068800 -4.453202e-02 0.068850 -4.452646e-02 0.068900 -4.452090e-02 0.068950 -4.451535e-02 0.069000 -4.450981e-02 0.069050 -4.450428e-02 0.069100 -4.449875e-02 0.069150 -4.449323e-02 0.069200 -4.448771e-02 0.069250 -4.448221e-02 0.069300 -4.447670e-02 0.069350 -4.447121e-02 0.069400 -4.446572e-02 0.069450 -4.446024e-02 0.069500 -4.445477e-02 0.069550 -4.444930e-02 0.069600 -4.444385e-02 0.069650 -4.443839e-02 0.069700 -4.443295e-02 0.069750 -4.442751e-02 0.069800 -4.442208e-02 0.069850 -4.441665e-02 0.069900 -4.441123e-02 0.069950 -4.440582e-02 0.070000 -4.440042e-02 0.070050 -4.439502e-02 0.070100 -4.438962e-02 0.070150 -4.438424e-02 0.070200 -4.437886e-02 0.070250 -4.437349e-02 0.070300 -4.436813e-02 0.070350 -4.436277e-02 0.070400 -4.435742e-02 0.070450 -4.435207e-02 0.070500 -4.434673e-02 0.070550 -4.434140e-02 0.070600 -4.433608e-02 0.070650 -4.433076e-02 0.070700 -4.432545e-02 0.070750 -4.432014e-02 0.070800 -4.431485e-02 0.070850 -4.430955e-02 0.070900 -4.430427e-02 0.070950 -4.429899e-02 0.071000 -4.429372e-02 0.071050 -4.428845e-02 0.071100 -4.428320e-02 0.071150 -4.427794e-02 0.071200 -4.427270e-02 0.071250 -4.426746e-02 0.071300 -4.426223e-02 0.071350 -4.425700e-02 0.071400 -4.425178e-02 0.071450 -4.424657e-02 0.071500 -4.424136e-02 0.071550 -4.423616e-02 0.071600 -4.423097e-02 0.071650 -4.422578e-02 0.071700 -4.422060e-02 0.071750 -4.421543e-02 0.071800 -4.421026e-02 0.071850 -4.420510e-02 0.071900 -4.419995e-02 0.071950 -4.419480e-02 0.072000 -4.418966e-02 0.072050 -4.418452e-02 0.072100 -4.417939e-02 0.072150 -4.417427e-02 0.072200 -4.416916e-02 0.072250 -4.416405e-02 0.072300 -4.415894e-02 0.072350 -4.415385e-02 0.072400 -4.414876e-02 0.072450 -4.414367e-02 0.072500 -4.413859e-02 0.072550 -4.413352e-02 0.072600 -4.412846e-02 0.072650 -4.412340e-02 0.072700 -4.411835e-02 0.072750 -4.411330e-02 0.072800 -4.410826e-02 0.072850 -4.410323e-02 0.072900 -4.409820e-02 0.072950 -4.409318e-02 0.073000 -4.408817e-02 0.073050 -4.408316e-02 0.073100 -4.407815e-02 0.073150 -4.407316e-02 0.073200 -4.406817e-02 0.073250 -4.406319e-02 0.073300 -4.405821e-02 0.073350 -4.405324e-02 0.073400 -4.404827e-02 0.073450 -4.404332e-02 0.073500 -4.403836e-02 0.073550 -4.403342e-02 0.073600 -4.402848e-02 0.073650 -4.402354e-02 0.073700 -4.401862e-02 0.073750 -4.401369e-02 0.073800 -4.400878e-02 0.073850 -4.400387e-02 0.073900 -4.399897e-02 0.073950 -4.399407e-02 0.074000 -4.398918e-02 0.074050 -4.398429e-02 0.074100 -4.397942e-02 0.074150 -4.397454e-02 0.074200 -4.396968e-02 0.074250 -4.396482e-02 0.074300 -4.395996e-02 0.074350 -4.395511e-02 0.074400 -4.395027e-02 0.074450 -4.394544e-02 0.074500 -4.394061e-02 0.074550 -4.393578e-02 0.074600 -4.393096e-02 0.074650 -4.392615e-02 0.074700 -4.392135e-02 0.074750 -4.391655e-02 0.074800 -4.391175e-02 0.074850 -4.390696e-02 0.074900 -4.390218e-02 0.074950 -4.389741e-02 0.075000 -4.389264e-02 0.075050 -4.388787e-02 0.075100 -4.388311e-02 0.075150 -4.387836e-02 0.075200 -4.387362e-02 0.075250 -4.386888e-02 0.075300 -4.386414e-02 0.075350 -4.385941e-02 0.075400 -4.385469e-02 0.075450 -4.384997e-02 0.075500 -4.384526e-02 0.075550 -4.384056e-02 0.075600 -4.383586e-02 0.075650 -4.383117e-02 0.075700 -4.382648e-02 0.075750 -4.382180e-02 0.075800 -4.381712e-02 0.075850 -4.381245e-02 0.075900 -4.380779e-02 0.075950 -4.380313e-02 0.076000 -4.379848e-02 0.076050 -4.379383e-02 0.076100 -4.378919e-02 0.076150 -4.378456e-02 0.076200 -4.377993e-02 0.076250 -4.377530e-02 0.076300 -4.377069e-02 0.076350 -4.376607e-02 0.076400 -4.376147e-02 0.076450 -4.375687e-02 0.076500 -4.375227e-02 0.076550 -4.374769e-02 0.076600 -4.374310e-02 0.076650 -4.373852e-02 0.076700 -4.373395e-02 0.076750 -4.372939e-02 0.076800 -4.372483e-02 0.076850 -4.372027e-02 0.076900 -4.371572e-02 0.076950 -4.371118e-02 0.077000 -4.370664e-02 0.077050 -4.370211e-02 0.077100 -4.369759e-02 0.077150 -4.369307e-02 0.077200 -4.368855e-02 0.077250 -4.368404e-02 0.077300 -4.367954e-02 0.077350 -4.367504e-02 0.077400 -4.367055e-02 0.077450 -4.366606e-02 0.077500 -4.366158e-02 0.077550 -4.365711e-02 0.077600 -4.365264e-02 0.077650 -4.364817e-02 0.077700 -4.364371e-02 0.077750 -4.363926e-02 0.077800 -4.363481e-02 0.077850 -4.363037e-02 0.077900 -4.362593e-02 0.077950 -4.362150e-02 0.078000 -4.361708e-02 0.078050 -4.361266e-02 0.078100 -4.360824e-02 0.078150 -4.360383e-02 0.078200 -4.359943e-02 0.078250 -4.359503e-02 0.078300 -4.359064e-02 0.078350 -4.358625e-02 0.078400 -4.358187e-02 0.078450 -4.357750e-02 0.078500 -4.357313e-02 0.078550 -4.356876e-02 0.078600 -4.356440e-02 0.078650 -4.356005e-02 0.078700 -4.355570e-02 0.078750 -4.355136e-02 0.078800 -4.354702e-02 0.078850 -4.354269e-02 0.078900 -4.353836e-02 0.078950 -4.353404e-02 0.079000 -4.352972e-02 0.079050 -4.352541e-02 0.079100 -4.352111e-02 0.079150 -4.351681e-02 0.079200 -4.351251e-02 0.079250 -4.350822e-02 0.079300 -4.350394e-02 0.079350 -4.349966e-02 0.079400 -4.349539e-02 0.079450 -4.349112e-02 0.079500 -4.348686e-02 0.079550 -4.348260e-02 0.079600 -4.347835e-02 0.079650 -4.347410e-02 0.079700 -4.346986e-02 0.079750 -4.346562e-02 0.079800 -4.346139e-02 0.079850 -4.345717e-02 0.079900 -4.345295e-02 0.079950 -4.344873e-02 0.080000 -4.344452e-02 0.080050 -4.344032e-02 0.080100 -4.343612e-02 0.080150 -4.343193e-02 0.080200 -4.342774e-02 0.080250 -4.342355e-02 0.080300 -4.341938e-02 0.080350 -4.341520e-02 0.080400 -4.341104e-02 0.080450 -4.340687e-02 0.080500 -4.340272e-02 0.080550 -4.339856e-02 0.080600 -4.339442e-02 0.080650 -4.339028e-02 0.080700 -4.338614e-02 0.080750 -4.338201e-02 0.080800 -4.337788e-02 0.080850 -4.337376e-02 0.080900 -4.336965e-02 0.080950 -4.336553e-02 0.081000 -4.336143e-02 0.081050 -4.335733e-02 0.081100 -4.335323e-02 0.081150 -4.334914e-02 0.081200 -4.334506e-02 0.081250 -4.334098e-02 0.081300 -4.333690e-02 0.081350 -4.333283e-02 0.081400 -4.332877e-02 0.081450 -4.332471e-02 0.081500 -4.332065e-02 0.081550 -4.331660e-02 0.081600 -4.331256e-02 0.081650 -4.330852e-02 0.081700 -4.330449e-02 0.081750 -4.330046e-02 0.081800 -4.329643e-02 0.081850 -4.329241e-02 0.081900 -4.328840e-02 0.081950 -4.328439e-02 0.082000 -4.328039e-02 0.082050 -4.327639e-02 0.082100 -4.327239e-02 0.082150 -4.326840e-02 0.082200 -4.326442e-02 0.082250 -4.326044e-02 0.082300 -4.325647e-02 0.082350 -4.325250e-02 0.082400 -4.324853e-02 0.082450 -4.324457e-02 0.082500 -4.324062e-02 0.082550 -4.323667e-02 0.082600 -4.323272e-02 0.082650 -4.322878e-02 0.082700 -4.322485e-02 0.082750 -4.322092e-02 0.082800 -4.321699e-02 0.082850 -4.321307e-02 0.082900 -4.320916e-02 0.082950 -4.320525e-02 0.083000 -4.320134e-02 0.083050 -4.319744e-02 0.083100 -4.319355e-02 0.083150 -4.318966e-02 0.083200 -4.318577e-02 0.083250 -4.318189e-02 0.083300 -4.317801e-02 0.083350 -4.317414e-02 0.083400 -4.317028e-02 0.083450 -4.316641e-02 0.083500 -4.316256e-02 0.083550 -4.315870e-02 0.083600 -4.315486e-02 0.083650 -4.315102e-02 0.083700 -4.314718e-02 0.083750 -4.314335e-02 0.083800 -4.313952e-02 0.083850 -4.313569e-02 0.083900 -4.313188e-02 0.083950 -4.312806e-02 0.084000 -4.312425e-02 0.084050 -4.312045e-02 0.084100 -4.311665e-02 0.084150 -4.311285e-02 0.084200 -4.310906e-02 0.084250 -4.310528e-02 0.084300 -4.310150e-02 0.084350 -4.309772e-02 0.084400 -4.309395e-02 0.084450 -4.309019e-02 0.084500 -4.308642e-02 0.084550 -4.308267e-02 0.084600 -4.307892e-02 0.084650 -4.307517e-02 0.084700 -4.307142e-02 0.084750 -4.306769e-02 0.084800 -4.306395e-02 0.084850 -4.306022e-02 0.084900 -4.305650e-02 0.084950 -4.305278e-02 0.085000 -4.304907e-02 0.085050 -4.304536e-02 0.085100 -4.304165e-02 0.085150 -4.303795e-02 0.085200 -4.303425e-02 0.085250 -4.303056e-02 0.085300 -4.302687e-02 0.085350 -4.302319e-02 0.085400 -4.301951e-02 0.085450 -4.301584e-02 0.085500 -4.301217e-02 0.085550 -4.300851e-02 0.085600 -4.300485e-02 0.085650 -4.300119e-02 0.085700 -4.299754e-02 0.085750 -4.299390e-02 0.085800 -4.299025e-02 0.085850 -4.298662e-02 0.085900 -4.298299e-02 0.085950 -4.297936e-02 0.086000 -4.297573e-02 0.086050 -4.297212e-02 0.086100 -4.296850e-02 0.086150 -4.296489e-02 0.086200 -4.296129e-02 0.086250 -4.295769e-02 0.086300 -4.295409e-02 0.086350 -4.295050e-02 0.086400 -4.294691e-02 0.086450 -4.294333e-02 0.086500 -4.293975e-02 0.086550 -4.293618e-02 0.086600 -4.293261e-02 0.086650 -4.292904e-02 0.086700 -4.292548e-02 0.086750 -4.292193e-02 0.086800 -4.291838e-02 0.086850 -4.291483e-02 0.086900 -4.291129e-02 0.086950 -4.290775e-02 0.087000 -4.290421e-02 0.087050 -4.290068e-02 0.087100 -4.289716e-02 0.087150 -4.289364e-02 0.087200 -4.289012e-02 0.087250 -4.288661e-02 0.087300 -4.288310e-02 0.087350 -4.287960e-02 0.087400 -4.287610e-02 0.087450 -4.287261e-02 0.087500 -4.286912e-02 0.087550 -4.286563e-02 0.087600 -4.286215e-02 0.087650 -4.285868e-02 0.087700 -4.285520e-02 0.087750 -4.285174e-02 0.087800 -4.284827e-02 0.087850 -4.284481e-02 0.087900 -4.284136e-02 0.087950 -4.283791e-02 0.088000 -4.283446e-02 0.088050 -4.283102e-02 0.088100 -4.282758e-02 0.088150 -4.282415e-02 0.088200 -4.282072e-02 0.088250 -4.281729e-02 0.088300 -4.281387e-02 0.088350 -4.281045e-02 0.088400 -4.280704e-02 0.088450 -4.280363e-02 0.088500 -4.280023e-02 0.088550 -4.279683e-02 0.088600 -4.279344e-02 0.088650 -4.279005e-02 0.088700 -4.278666e-02 0.088750 -4.278328e-02 0.088800 -4.277990e-02 0.088850 -4.277652e-02 0.088900 -4.277315e-02 0.088950 -4.276979e-02 0.089000 -4.276643e-02 0.089050 -4.276307e-02 0.089100 -4.275972e-02 0.089150 -4.275637e-02 0.089200 -4.275302e-02 0.089250 -4.274968e-02 0.089300 -4.274635e-02 0.089350 -4.274302e-02 0.089400 -4.273969e-02 0.089450 -4.273636e-02 0.089500 -4.273304e-02 0.089550 -4.272973e-02 0.089600 -4.272642e-02 0.089650 -4.272311e-02 0.089700 -4.271981e-02 0.089750 -4.271651e-02 0.089800 -4.271321e-02 0.089850 -4.270992e-02 0.089900 -4.270664e-02 0.089950 -4.270335e-02 0.090000 -4.270007e-02 0.090050 -4.269680e-02 0.090100 -4.269353e-02 0.090150 -4.269026e-02 0.090200 -4.268700e-02 0.090250 -4.268374e-02 0.090300 -4.268049e-02 0.090350 -4.267724e-02 0.090400 -4.267399e-02 0.090450 -4.267075e-02 0.090500 -4.266752e-02 0.090550 -4.266428e-02 0.090600 -4.266105e-02 0.090650 -4.265783e-02 0.090700 -4.265461e-02 0.090750 -4.265139e-02 0.090800 -4.264817e-02 0.090850 -4.264496e-02 0.090900 -4.264176e-02 0.090950 -4.263856e-02 0.091000 -4.263536e-02 0.091050 -4.263217e-02 0.091100 -4.262898e-02 0.091150 -4.262579e-02 0.091200 -4.262261e-02 0.091250 -4.261943e-02 0.091300 -4.261626e-02 0.091350 -4.261309e-02 0.091400 -4.260992e-02 0.091450 -4.260676e-02 0.091500 -4.260361e-02 0.091550 -4.260045e-02 0.091600 -4.259730e-02 0.091650 -4.259416e-02 0.091700 -4.259101e-02 0.091750 -4.258788e-02 0.091800 -4.258474e-02 0.091850 -4.258161e-02 0.091900 -4.257848e-02 0.091950 -4.257536e-02 0.092000 -4.257224e-02 0.092050 -4.256913e-02 0.092100 -4.256602e-02 0.092150 -4.256291e-02 0.092200 -4.255981e-02 0.092250 -4.255671e-02 0.092300 -4.255361e-02 0.092350 -4.255052e-02 0.092400 -4.254744e-02 0.092450 -4.254435e-02 0.092500 -4.254127e-02 0.092550 -4.253820e-02 0.092600 -4.253512e-02 0.092650 -4.253206e-02 0.092700 -4.252899e-02 0.092750 -4.252593e-02 0.092800 -4.252287e-02 0.092850 -4.251982e-02 0.092900 -4.251677e-02 0.092950 -4.251373e-02 0.093000 -4.251069e-02 0.093050 -4.250765e-02 0.093100 -4.250461e-02 0.093150 -4.250158e-02 0.093200 -4.249856e-02 0.093250 -4.249554e-02 0.093300 -4.249252e-02 0.093350 -4.248950e-02 0.093400 -4.248649e-02 0.093450 -4.248348e-02 0.093500 -4.248048e-02 0.093550 -4.247748e-02 0.093600 -4.247448e-02 0.093650 -4.247149e-02 0.093700 -4.246850e-02 0.093750 -4.246552e-02 0.093800 -4.246254e-02 0.093850 -4.245956e-02 0.093900 -4.245658e-02 0.093950 -4.245361e-02 0.094000 -4.245065e-02 0.094050 -4.244768e-02 0.094100 -4.244473e-02 0.094150 -4.244177e-02 0.094200 -4.243882e-02 0.094250 -4.243587e-02 0.094300 -4.243293e-02 0.094350 -4.242999e-02 0.094400 -4.242705e-02 0.094450 -4.242412e-02 0.094500 -4.242119e-02 0.094550 -4.241826e-02 0.094600 -4.241534e-02 0.094650 -4.241242e-02 0.094700 -4.240951e-02 0.094750 -4.240659e-02 0.094800 -4.240369e-02 0.094850 -4.240078e-02 0.094900 -4.239788e-02 0.094950 -4.239498e-02 0.095000 -4.239209e-02 0.095050 -4.238920e-02 0.095100 -4.238632e-02 0.095150 -4.238343e-02 0.095200 -4.238056e-02 0.095250 -4.237768e-02 0.095300 -4.237481e-02 0.095350 -4.237194e-02 0.095400 -4.236908e-02 0.095450 -4.236622e-02 0.095500 -4.236336e-02 0.095550 -4.236050e-02 0.095600 -4.235765e-02 0.095650 -4.235481e-02 0.095700 -4.235197e-02 0.095750 -4.234913e-02 0.095800 -4.234629e-02 0.095850 -4.234346e-02 0.095900 -4.234063e-02 0.095950 -4.233780e-02 0.096000 -4.233498e-02 0.096050 -4.233216e-02 0.096100 -4.232935e-02 0.096150 -4.232654e-02 0.096200 -4.232373e-02 0.096250 -4.232093e-02 0.096300 -4.231813e-02 0.096350 -4.231533e-02 0.096400 -4.231253e-02 0.096450 -4.230974e-02 0.096500 -4.230696e-02 0.096550 -4.230417e-02 0.096600 -4.230139e-02 0.096650 -4.229862e-02 0.096700 -4.229585e-02 0.096750 -4.229308e-02 0.096800 -4.229031e-02 0.096850 -4.228755e-02 0.096900 -4.228479e-02 0.096950 -4.228203e-02 0.097000 -4.227928e-02 0.097050 -4.227653e-02 0.097100 -4.227379e-02 0.097150 -4.227105e-02 0.097200 -4.226831e-02 0.097250 -4.226557e-02 0.097300 -4.226284e-02 0.097350 -4.226011e-02 0.097400 -4.225739e-02 0.097450 -4.225467e-02 0.097500 -4.225195e-02 0.097550 -4.224923e-02 0.097600 -4.224652e-02 0.097650 -4.224382e-02 0.097700 -4.224111e-02 0.097750 -4.223841e-02 0.097800 -4.223571e-02 0.097850 -4.223302e-02 0.097900 -4.223033e-02 0.097950 -4.222764e-02 0.098000 -4.222496e-02 0.098050 -4.222228e-02 0.098100 -4.221960e-02 0.098150 -4.221692e-02 0.098200 -4.221425e-02 0.098250 -4.221159e-02 0.098300 -4.220892e-02 0.098350 -4.220626e-02 0.098400 -4.220360e-02 0.098450 -4.220095e-02 0.098500 -4.219830e-02 0.098550 -4.219565e-02 0.098600 -4.219301e-02 0.098650 -4.219037e-02 0.098700 -4.218773e-02 0.098750 -4.218510e-02 0.098800 -4.218246e-02 0.098850 -4.217984e-02 0.098900 -4.217721e-02 0.098950 -4.217459e-02 0.099000 -4.217197e-02 0.099050 -4.216936e-02 0.099100 -4.216675e-02 0.099150 -4.216414e-02 0.099200 -4.216153e-02 0.099250 -4.215893e-02 0.099300 -4.215633e-02 0.099350 -4.215374e-02 0.099400 -4.215115e-02 0.099450 -4.214856e-02 0.099500 -4.214597e-02 0.099550 -4.214339e-02 0.099600 -4.214081e-02 0.099650 -4.213824e-02 0.099700 -4.213566e-02 0.099750 -4.213310e-02 0.099800 -4.213053e-02 0.099850 -4.212797e-02 0.099900 -4.212541e-02 0.099950 -4.212285e-02 0.100000 -4.212030e-02 0.100050 -4.211775e-02 0.100100 -4.211520e-02 0.100150 -4.211266e-02 0.100200 -4.211012e-02 0.100250 -4.210758e-02 0.100300 -4.210505e-02 0.100350 -4.210251e-02 0.100400 -4.209999e-02 0.100450 -4.209746e-02 0.100500 -4.209494e-02 0.100550 -4.209242e-02 0.100600 -4.208991e-02 0.100650 -4.208739e-02 0.100700 -4.208489e-02 0.100750 -4.208238e-02 0.100800 -4.207988e-02 0.100850 -4.207738e-02 0.100900 -4.207488e-02 0.100950 -4.207239e-02 0.101000 -4.206990e-02 0.101050 -4.206741e-02 0.101100 -4.206493e-02 0.101150 -4.206245e-02 0.101200 -4.205997e-02 0.101250 -4.205749e-02 0.101300 -4.205502e-02 0.101350 -4.205255e-02 0.101400 -4.205009e-02 0.101450 -4.204763e-02 0.101500 -4.204517e-02 0.101550 -4.204271e-02 0.101600 -4.204026e-02 0.101650 -4.203781e-02 0.101700 -4.203536e-02 0.101750 -4.203292e-02 0.101800 -4.203048e-02 0.101850 -4.202804e-02 0.101900 -4.202560e-02 0.101950 -4.202317e-02 0.102000 -4.202074e-02 0.102050 -4.201832e-02 0.102100 -4.201589e-02 0.102150 -4.201348e-02 0.102200 -4.201106e-02 0.102250 -4.200864e-02 0.102300 -4.200623e-02 0.102350 -4.200383e-02 0.102400 -4.200142e-02 0.102450 -4.199902e-02 0.102500 -4.199662e-02 0.102550 -4.199423e-02 0.102600 -4.199183e-02 0.102650 -4.198944e-02 0.102700 -4.198706e-02 0.102750 -4.198467e-02 0.102800 -4.198229e-02 0.102850 -4.197992e-02 0.102900 -4.197754e-02 0.102950 -4.197517e-02 0.103000 -4.197280e-02 0.103050 -4.197044e-02 0.103100 -4.196807e-02 0.103150 -4.196571e-02 0.103200 -4.196336e-02 0.103250 -4.196100e-02 0.103300 -4.195865e-02 0.103350 -4.195630e-02 0.103400 -4.195396e-02 0.103450 -4.195162e-02 0.103500 -4.194928e-02 0.103550 -4.194694e-02 0.103600 -4.194461e-02 0.103650 -4.194228e-02 0.103700 -4.193995e-02 0.103750 -4.193762e-02 0.103800 -4.193530e-02 0.103850 -4.193298e-02 0.103900 -4.193067e-02 0.103950 -4.192835e-02 0.104000 -4.192604e-02 0.104050 -4.192374e-02 0.104100 -4.192143e-02 0.104150 -4.191913e-02 0.104200 -4.191683e-02 0.104250 -4.191454e-02 0.104300 -4.191224e-02 0.104350 -4.190995e-02 0.104400 -4.190767e-02 0.104450 -4.190538e-02 0.104500 -4.190310e-02 0.104550 -4.190082e-02 0.104600 -4.189854e-02 0.104650 -4.189627e-02 0.104700 -4.189400e-02 0.104750 -4.189173e-02 0.104800 -4.188947e-02 0.104850 -4.188721e-02 0.104900 -4.188495e-02 0.104950 -4.188269e-02 0.105000 -4.188044e-02 0.105050 -4.187819e-02 0.105100 -4.187594e-02 0.105150 -4.187370e-02 0.105200 -4.187146e-02 0.105250 -4.186922e-02 0.105300 -4.186698e-02 0.105350 -4.186475e-02 0.105400 -4.186252e-02 0.105450 -4.186029e-02 0.105500 -4.185806e-02 0.105550 -4.185584e-02 0.105600 -4.185362e-02 0.105650 -4.185140e-02 0.105700 -4.184919e-02 0.105750 -4.184698e-02 0.105800 -4.184477e-02 0.105850 -4.184256e-02 0.105900 -4.184036e-02 0.105950 -4.183816e-02 0.106000 -4.183596e-02 0.106050 -4.183377e-02 0.106100 -4.183158e-02 0.106150 -4.182939e-02 0.106200 -4.182720e-02 0.106250 -4.182502e-02 0.106300 -4.182283e-02 0.106350 -4.182066e-02 0.106400 -4.181848e-02 0.106450 -4.181631e-02 0.106500 -4.181414e-02 0.106550 -4.181197e-02 0.106600 -4.180981e-02 0.106650 -4.180764e-02 0.106700 -4.180548e-02 0.106750 -4.180333e-02 0.106800 -4.180117e-02 0.106850 -4.179902e-02 0.106900 -4.179687e-02 0.106950 -4.179473e-02 0.107000 -4.179258e-02 0.107050 -4.179044e-02 0.107100 -4.178830e-02 0.107150 -4.178617e-02 0.107200 -4.178404e-02 0.107250 -4.178191e-02 0.107300 -4.177978e-02 0.107350 -4.177766e-02 0.107400 -4.177553e-02 0.107450 -4.177341e-02 0.107500 -4.177130e-02 0.107550 -4.176918e-02 0.107600 -4.176707e-02 0.107650 -4.176496e-02 0.107700 -4.176286e-02 0.107750 -4.176075e-02 0.107800 -4.175865e-02 0.107850 -4.175655e-02 0.107900 -4.175446e-02 0.107950 -4.175237e-02 0.108000 -4.175028e-02 0.108050 -4.174819e-02 0.108100 -4.174610e-02 0.108150 -4.174402e-02 0.108200 -4.174194e-02 0.108250 -4.173986e-02 0.108300 -4.173779e-02 0.108350 -4.173572e-02 0.108400 -4.173365e-02 0.108450 -4.173158e-02 0.108500 -4.172951e-02 0.108550 -4.172745e-02 0.108600 -4.172539e-02 0.108650 -4.172334e-02 0.108700 -4.172128e-02 0.108750 -4.171923e-02 0.108800 -4.171718e-02 0.108850 -4.171514e-02 0.108900 -4.171309e-02 0.108950 -4.171105e-02 0.109000 -4.170901e-02 0.109050 -4.170698e-02 0.109100 -4.170494e-02 0.109150 -4.170291e-02 0.109200 -4.170088e-02 0.109250 -4.169886e-02 0.109300 -4.169683e-02 0.109350 -4.169481e-02 0.109400 -4.169279e-02 0.109450 -4.169078e-02 0.109500 -4.168876e-02 0.109550 -4.168675e-02 0.109600 -4.168474e-02 0.109650 -4.168274e-02 0.109700 -4.168073e-02 0.109750 -4.167873e-02 0.109800 -4.167674e-02 0.109850 -4.167474e-02 0.109900 -4.167275e-02 0.109950 -4.167076e-02 0.110000 -4.166877e-02 0.110050 -4.166678e-02 0.110100 -4.166480e-02 0.110150 -4.166282e-02 0.110200 -4.166084e-02 0.110250 -4.165886e-02 0.110300 -4.165689e-02 0.110350 -4.165492e-02 0.110400 -4.165295e-02 0.110450 -4.165098e-02 0.110500 -4.164902e-02 0.110550 -4.164706e-02 0.110600 -4.164510e-02 0.110650 -4.164314e-02 0.110700 -4.164119e-02 0.110750 -4.163924e-02 0.110800 -4.163729e-02 0.110850 -4.163534e-02 0.110900 -4.163340e-02 0.110950 -4.163145e-02 0.111000 -4.162952e-02 0.111050 -4.162758e-02 0.111100 -4.162564e-02 0.111150 -4.162371e-02 0.111200 -4.162178e-02 0.111250 -4.161986e-02 0.111300 -4.161793e-02 0.111350 -4.161601e-02 0.111400 -4.161409e-02 0.111450 -4.161217e-02 0.111500 -4.161025e-02 0.111550 -4.160834e-02 0.111600 -4.160643e-02 0.111650 -4.160452e-02 0.111700 -4.160262e-02 0.111750 -4.160071e-02 0.111800 -4.159881e-02 0.111850 -4.159691e-02 0.111900 -4.159502e-02 0.111950 -4.159312e-02 0.112000 -4.159123e-02 0.112050 -4.158934e-02 0.112100 -4.158746e-02 0.112150 -4.158557e-02 0.112200 -4.158369e-02 0.112250 -4.158181e-02 0.112300 -4.157993e-02 0.112350 -4.157806e-02 0.112400 -4.157619e-02 0.112450 -4.157432e-02 0.112500 -4.157245e-02 0.112550 -4.157058e-02 0.112600 -4.156872e-02 0.112650 -4.156686e-02 0.112700 -4.156500e-02 0.112750 -4.156314e-02 0.112800 -4.156129e-02 0.112850 -4.155944e-02 0.112900 -4.155759e-02 0.112950 -4.155574e-02 0.113000 -4.155390e-02 0.113050 -4.155205e-02 0.113100 -4.155021e-02 0.113150 -4.154838e-02 0.113200 -4.154654e-02 0.113250 -4.154471e-02 0.113300 -4.154288e-02 0.113350 -4.154105e-02 0.113400 -4.153922e-02 0.113450 -4.153740e-02 0.113500 -4.153558e-02 0.113550 -4.153376e-02 0.113600 -4.153194e-02 0.113650 -4.153012e-02 0.113700 -4.152831e-02 0.113750 -4.152650e-02 0.113800 -4.152469e-02 0.113850 -4.152289e-02 0.113900 -4.152108e-02 0.113950 -4.151928e-02 0.114000 -4.151748e-02 0.114050 -4.151568e-02 0.114100 -4.151389e-02 0.114150 -4.151210e-02 0.114200 -4.151031e-02 0.114250 -4.150852e-02 0.114300 -4.150673e-02 0.114350 -4.150495e-02 0.114400 -4.150317e-02 0.114450 -4.150139e-02 0.114500 -4.149961e-02 0.114550 -4.149784e-02 0.114600 -4.149607e-02 0.114650 -4.149430e-02 0.114700 -4.149253e-02 0.114750 -4.149076e-02 0.114800 -4.148900e-02 0.114850 -4.148724e-02 0.114900 -4.148548e-02 0.114950 -4.148372e-02 0.115000 -4.148197e-02 0.115050 -4.148021e-02 0.115100 -4.147846e-02 0.115150 -4.147671e-02 0.115200 -4.147497e-02 0.115250 -4.147322e-02 0.115300 -4.147148e-02 0.115350 -4.146974e-02 0.115400 -4.146801e-02 0.115450 -4.146627e-02 0.115500 -4.146454e-02 0.115550 -4.146281e-02 0.115600 -4.146108e-02 0.115650 -4.145935e-02 0.115700 -4.145763e-02 0.115750 -4.145591e-02 0.115800 -4.145419e-02 0.115850 -4.145247e-02 0.115900 -4.145075e-02 0.115950 -4.144904e-02 0.116000 -4.144733e-02 0.116050 -4.144562e-02 0.116100 -4.144391e-02 0.116150 -4.144220e-02 0.116200 -4.144050e-02 0.116250 -4.143880e-02 0.116300 -4.143710e-02 0.116350 -4.143541e-02 0.116400 -4.143371e-02 0.116450 -4.143202e-02 0.116500 -4.143033e-02 0.116550 -4.142864e-02 0.116600 -4.142695e-02 0.116650 -4.142527e-02 0.116700 -4.142359e-02 0.116750 -4.142191e-02 0.116800 -4.142023e-02 0.116850 -4.141856e-02 0.116900 -4.141688e-02 0.116950 -4.141521e-02 0.117000 -4.141354e-02 0.117050 -4.141188e-02 0.117100 -4.141021e-02 0.117150 -4.140855e-02 0.117200 -4.140689e-02 0.117250 -4.140523e-02 0.117300 -4.140357e-02 0.117350 -4.140192e-02 0.117400 -4.140026e-02 0.117450 -4.139861e-02 0.117500 -4.139696e-02 0.117550 -4.139532e-02 0.117600 -4.139367e-02 0.117650 -4.139203e-02 0.117700 -4.139039e-02 0.117750 -4.138875e-02 0.117800 -4.138712e-02 0.117850 -4.138548e-02 0.117900 -4.138385e-02 0.117950 -4.138222e-02 0.118000 -4.138059e-02 0.118050 -4.137897e-02 0.118100 -4.137734e-02 0.118150 -4.137572e-02 0.118200 -4.137410e-02 0.118250 -4.137248e-02 0.118300 -4.137087e-02 0.118350 -4.136925e-02 0.118400 -4.136764e-02 0.118450 -4.136603e-02 0.118500 -4.136442e-02 0.118550 -4.136282e-02 0.118600 -4.136121e-02 0.118650 -4.135961e-02 0.118700 -4.135801e-02 0.118750 -4.135642e-02 0.118800 -4.135482e-02 0.118850 -4.135323e-02 0.118900 -4.135163e-02 0.118950 -4.135004e-02 0.119000 -4.134846e-02 0.119050 -4.134687e-02 0.119100 -4.134529e-02 0.119150 -4.134371e-02 0.119200 -4.134213e-02 0.119250 -4.134055e-02 0.119300 -4.133897e-02 0.119350 -4.133740e-02 0.119400 -4.133583e-02 0.119450 -4.133426e-02 0.119500 -4.133269e-02 0.119550 -4.133112e-02 0.119600 -4.132956e-02 0.119650 -4.132799e-02 0.119700 -4.132643e-02 0.119750 -4.132488e-02 0.119800 -4.132332e-02 0.119850 -4.132177e-02 0.119900 -4.132021e-02 0.119950 -4.131866e-02 0.120000 -4.131711e-02 0.120050 -4.131557e-02 0.120100 -4.131402e-02 0.120150 -4.131248e-02 0.120200 -4.131094e-02 0.120250 -4.130940e-02 0.120300 -4.130786e-02 0.120350 -4.130633e-02 0.120400 -4.130479e-02 0.120450 -4.130326e-02 0.120500 -4.130173e-02 0.120550 -4.130021e-02 0.120600 -4.129868e-02 0.120650 -4.129716e-02 0.120700 -4.129564e-02 0.120750 -4.129412e-02 0.120800 -4.129260e-02 0.120850 -4.129108e-02 0.120900 -4.128957e-02 0.120950 -4.128806e-02 0.121000 -4.128655e-02 0.121050 -4.128504e-02 0.121100 -4.128353e-02 0.121150 -4.128203e-02 0.121200 -4.128052e-02 0.121250 -4.127902e-02 0.121300 -4.127752e-02 0.121350 -4.127603e-02 0.121400 -4.127453e-02 0.121450 -4.127304e-02 0.121500 -4.127155e-02 0.121550 -4.127006e-02 0.121600 -4.126857e-02 0.121650 -4.126708e-02 0.121700 -4.126560e-02 0.121750 -4.126411e-02 0.121800 -4.126263e-02 0.121850 -4.126116e-02 0.121900 -4.125968e-02 0.121950 -4.125820e-02 0.122000 -4.125673e-02 0.122050 -4.125526e-02 0.122100 -4.125379e-02 0.122150 -4.125232e-02 0.122200 -4.125086e-02 0.122250 -4.124939e-02 0.122300 -4.124793e-02 0.122350 -4.124647e-02 0.122400 -4.124501e-02 0.122450 -4.124356e-02 0.122500 -4.124210e-02 0.122550 -4.124065e-02 0.122600 -4.123920e-02 0.122650 -4.123775e-02 0.122700 -4.123630e-02 0.122750 -4.123485e-02 0.122800 -4.123341e-02 0.122850 -4.123197e-02 0.122900 -4.123053e-02 0.122950 -4.122909e-02 0.123000 -4.122765e-02 0.123050 -4.122622e-02 0.123100 -4.122478e-02 0.123150 -4.122335e-02 0.123200 -4.122192e-02 0.123250 -4.122050e-02 0.123300 -4.121907e-02 0.123350 -4.121765e-02 0.123400 -4.121622e-02 0.123450 -4.121480e-02 0.123500 -4.121338e-02 0.123550 -4.121197e-02 0.123600 -4.121055e-02 0.123650 -4.120914e-02 0.123700 -4.120773e-02 0.123750 -4.120632e-02 0.123800 -4.120491e-02 0.123850 -4.120350e-02 0.123900 -4.120210e-02 0.123950 -4.120069e-02 0.124000 -4.119929e-02 0.124050 -4.119789e-02 0.124100 -4.119650e-02 0.124150 -4.119510e-02 0.124200 -4.119371e-02 0.124250 -4.119231e-02 0.124300 -4.119092e-02 0.124350 -4.118953e-02 0.124400 -4.118815e-02 0.124450 -4.118676e-02 0.124500 -4.118538e-02 0.124550 -4.118399e-02 0.124600 -4.118261e-02 0.124650 -4.118124e-02 0.124700 -4.117986e-02 0.124750 -4.117848e-02 0.124800 -4.117711e-02 0.124850 -4.117574e-02 0.124900 -4.117437e-02 0.124950 -4.117300e-02 0.125000 -4.117163e-02 0.125050 -4.117027e-02 0.125100 -4.116890e-02 0.125150 -4.116754e-02 0.125200 -4.116618e-02 0.125250 -4.116483e-02 0.125300 -4.116347e-02 0.125350 -4.116211e-02 0.125400 -4.116076e-02 0.125450 -4.115941e-02 0.125500 -4.115806e-02 0.125550 -4.115671e-02 0.125600 -4.115537e-02 0.125650 -4.115402e-02 0.125700 -4.115268e-02 0.125750 -4.115134e-02 0.125800 -4.115000e-02 0.125850 -4.114866e-02 0.125900 -4.114732e-02 0.125950 -4.114599e-02 0.126000 -4.114466e-02 0.126050 -4.114333e-02 0.126100 -4.114200e-02 0.126150 -4.114067e-02 0.126200 -4.113934e-02 0.126250 -4.113802e-02 0.126300 -4.113669e-02 0.126350 -4.113537e-02 0.126400 -4.113405e-02 0.126450 -4.113273e-02 0.126500 -4.113142e-02 0.126550 -4.113010e-02 0.126600 -4.112879e-02 0.126650 -4.112748e-02 0.126700 -4.112617e-02 0.126750 -4.112486e-02 0.126800 -4.112355e-02 0.126850 -4.112225e-02 0.126900 -4.112095e-02 0.126950 -4.111965e-02 0.127000 -4.111835e-02 0.127050 -4.111705e-02 0.127100 -4.111575e-02 0.127150 -4.111446e-02 0.127200 -4.111316e-02 0.127250 -4.111187e-02 0.127300 -4.111058e-02 0.127350 -4.110929e-02 0.127400 -4.110800e-02 0.127450 -4.110672e-02 0.127500 -4.110543e-02 0.127550 -4.110415e-02 0.127600 -4.110287e-02 0.127650 -4.110159e-02 0.127700 -4.110032e-02 0.127750 -4.109904e-02 0.127800 -4.109777e-02 0.127850 -4.109649e-02 0.127900 -4.109522e-02 0.127950 -4.109395e-02 0.128000 -4.109268e-02 0.128050 -4.109142e-02 0.128100 -4.109015e-02 0.128150 -4.108889e-02 0.128200 -4.108763e-02 0.128250 -4.108637e-02 0.128300 -4.108511e-02 0.128350 -4.108385e-02 0.128400 -4.108260e-02 0.128450 -4.108134e-02 0.128500 -4.108009e-02 0.128550 -4.107884e-02 0.128600 -4.107759e-02 0.128650 -4.107634e-02 0.128700 -4.107510e-02 0.128750 -4.107385e-02 0.128800 -4.107261e-02 0.128850 -4.107137e-02 0.128900 -4.107013e-02 0.128950 -4.106889e-02 0.129000 -4.106766e-02 0.129050 -4.106642e-02 0.129100 -4.106519e-02 0.129150 -4.106396e-02 0.129200 -4.106273e-02 0.129250 -4.106150e-02 0.129300 -4.106027e-02 0.129350 -4.105904e-02 0.129400 -4.105782e-02 0.129450 -4.105660e-02 0.129500 -4.105538e-02 0.129550 -4.105416e-02 0.129600 -4.105294e-02 0.129650 -4.105172e-02 0.129700 -4.105051e-02 0.129750 -4.104929e-02 0.129800 -4.104808e-02 0.129850 -4.104687e-02 0.129900 -4.104566e-02 0.129950 -4.104445e-02 0.130000 -4.104325e-02 0.130050 -4.104204e-02 0.130100 -4.104084e-02 0.130150 -4.103964e-02 0.130200 -4.103844e-02 0.130250 -4.103724e-02 0.130300 -4.103604e-02 0.130350 -4.103485e-02 0.130400 -4.103365e-02 0.130450 -4.103246e-02 0.130500 -4.103127e-02 0.130550 -4.103008e-02 0.130600 -4.102889e-02 0.130650 -4.102770e-02 0.130700 -4.102652e-02 0.130750 -4.102534e-02 0.130800 -4.102415e-02 0.130850 -4.102297e-02 0.130900 -4.102179e-02 0.130950 -4.102062e-02 0.131000 -4.101944e-02 0.131050 -4.101826e-02 0.131100 -4.101709e-02 0.131150 -4.101592e-02 0.131200 -4.101475e-02 0.131250 -4.101358e-02 0.131300 -4.101241e-02 0.131350 -4.101125e-02 0.131400 -4.101008e-02 0.131450 -4.100892e-02 0.131500 -4.100776e-02 0.131550 -4.100660e-02 0.131600 -4.100544e-02 0.131650 -4.100428e-02 0.131700 -4.100313e-02 0.131750 -4.100197e-02 0.131800 -4.100082e-02 0.131850 -4.099967e-02 0.131900 -4.099852e-02 0.131950 -4.099737e-02 0.132000 -4.099622e-02 0.132050 -4.099507e-02 0.132100 -4.099393e-02 0.132150 -4.099279e-02 0.132200 -4.099165e-02 0.132250 -4.099051e-02 0.132300 -4.098937e-02 0.132350 -4.098823e-02 0.132400 -4.098709e-02 0.132450 -4.098596e-02 0.132500 -4.098483e-02 0.132550 -4.098369e-02 0.132600 -4.098256e-02 0.132650 -4.098144e-02 0.132700 -4.098031e-02 0.132750 -4.097918e-02 0.132800 -4.097806e-02 0.132850 -4.097694e-02 0.132900 -4.097581e-02 0.132950 -4.097469e-02 0.133000 -4.097357e-02 0.133050 -4.097246e-02 0.133100 -4.097134e-02 0.133150 -4.097023e-02 0.133200 -4.096911e-02 0.133250 -4.096800e-02 0.133300 -4.096689e-02 0.133350 -4.096578e-02 0.133400 -4.096467e-02 0.133450 -4.096357e-02 0.133500 -4.096246e-02 0.133550 -4.096136e-02 0.133600 -4.096026e-02 0.133650 -4.095916e-02 0.133700 -4.095806e-02 0.133750 -4.095696e-02 0.133800 -4.095586e-02 0.133850 -4.095477e-02 0.133900 -4.095367e-02 0.133950 -4.095258e-02 0.134000 -4.095149e-02 0.134050 -4.095040e-02 0.134100 -4.094931e-02 0.134150 -4.094822e-02 0.134200 -4.094714e-02 0.134250 -4.094605e-02 0.134300 -4.094497e-02 0.134350 -4.094389e-02 0.134400 -4.094281e-02 0.134450 -4.094173e-02 0.134500 -4.094065e-02 0.134550 -4.093957e-02 0.134600 -4.093850e-02 0.134650 -4.093742e-02 0.134700 -4.093635e-02 0.134750 -4.093528e-02 0.134800 -4.093421e-02 0.134850 -4.093314e-02 0.134900 -4.093208e-02 0.134950 -4.093101e-02 0.135000 -4.092995e-02 0.135050 -4.092888e-02 0.135100 -4.092782e-02 0.135150 -4.092676e-02 0.135200 -4.092570e-02 0.135250 -4.092464e-02 0.135300 -4.092359e-02 0.135350 -4.092253e-02 0.135400 -4.092148e-02 0.135450 -4.092043e-02 0.135500 -4.091938e-02 0.135550 -4.091833e-02 0.135600 -4.091728e-02 0.135650 -4.091623e-02 0.135700 -4.091518e-02 0.135750 -4.091414e-02 0.135800 -4.091310e-02 0.135850 -4.091205e-02 0.135900 -4.091101e-02 0.135950 -4.090997e-02 0.136000 -4.090894e-02 0.136050 -4.090790e-02 0.136100 -4.090686e-02 0.136150 -4.090583e-02 0.136200 -4.090480e-02 0.136250 -4.090377e-02 0.136300 -4.090274e-02 0.136350 -4.090171e-02 0.136400 -4.090068e-02 0.136450 -4.089965e-02 0.136500 -4.089863e-02 0.136550 -4.089760e-02 0.136600 -4.089658e-02 0.136650 -4.089556e-02 0.136700 -4.089454e-02 0.136750 -4.089352e-02 0.136800 -4.089250e-02 0.136850 -4.089149e-02 0.136900 -4.089047e-02 0.136950 -4.088946e-02 0.137000 -4.088845e-02 0.137050 -4.088743e-02 0.137100 -4.088642e-02 0.137150 -4.088542e-02 0.137200 -4.088441e-02 0.137250 -4.088340e-02 0.137300 -4.088240e-02 0.137350 -4.088139e-02 0.137400 -4.088039e-02 0.137450 -4.087939e-02 0.137500 -4.087839e-02 0.137550 -4.087739e-02 0.137600 -4.087639e-02 0.137650 -4.087540e-02 0.137700 -4.087440e-02 0.137750 -4.087341e-02 0.137800 -4.087242e-02 0.137850 -4.087143e-02 0.137900 -4.087044e-02 0.137950 -4.086945e-02 0.138000 -4.086846e-02 0.138050 -4.086747e-02 0.138100 -4.086649e-02 0.138150 -4.086551e-02 0.138200 -4.086452e-02 0.138250 -4.086354e-02 0.138300 -4.086256e-02 0.138350 -4.086158e-02 0.138400 -4.086061e-02 0.138450 -4.085963e-02 0.138500 -4.085865e-02 0.138550 -4.085768e-02 0.138600 -4.085671e-02 0.138650 -4.085574e-02 0.138700 -4.085477e-02 0.138750 -4.085380e-02 0.138800 -4.085283e-02 0.138850 -4.085186e-02 0.138900 -4.085090e-02 0.138950 -4.084993e-02 0.139000 -4.084897e-02 0.139050 -4.084801e-02 0.139100 -4.084705e-02 0.139150 -4.084609e-02 0.139200 -4.084513e-02 0.139250 -4.084417e-02 0.139300 -4.084322e-02 0.139350 -4.084226e-02 0.139400 -4.084131e-02 0.139450 -4.084036e-02 0.139500 -4.083940e-02 0.139550 -4.083845e-02 0.139600 -4.083751e-02 0.139650 -4.083656e-02 0.139700 -4.083561e-02 0.139750 -4.083467e-02 0.139800 -4.083372e-02 0.139850 -4.083278e-02 0.139900 -4.083184e-02 0.139950 -4.083090e-02 0.140000 -4.082996e-02 0.140050 -4.082902e-02 0.140100 -4.082808e-02 0.140150 -4.082715e-02 0.140200 -4.082621e-02 0.140250 -4.082528e-02 0.140300 -4.082435e-02 0.140350 -4.082342e-02 0.140400 -4.082249e-02 0.140450 -4.082156e-02 0.140500 -4.082063e-02 0.140550 -4.081970e-02 0.140600 -4.081878e-02 0.140650 -4.081785e-02 0.140700 -4.081693e-02 0.140750 -4.081601e-02 0.140800 -4.081509e-02 0.140850 -4.081417e-02 0.140900 -4.081325e-02 0.140950 -4.081233e-02 0.141000 -4.081142e-02 0.141050 -4.081050e-02 0.141100 -4.080959e-02 0.141150 -4.080868e-02 0.141200 -4.080776e-02 0.141250 -4.080685e-02 0.141300 -4.080595e-02 0.141350 -4.080504e-02 0.141400 -4.080413e-02 0.141450 -4.080322e-02 0.141500 -4.080232e-02 0.141550 -4.080142e-02 0.141600 -4.080051e-02 0.141650 -4.079961e-02 0.141700 -4.079871e-02 0.141750 -4.079781e-02 0.141800 -4.079691e-02 0.141850 -4.079602e-02 0.141900 -4.079512e-02 0.141950 -4.079423e-02 0.142000 -4.079333e-02 0.142050 -4.079244e-02 0.142100 -4.079155e-02 0.142150 -4.079066e-02 0.142200 -4.078977e-02 0.142250 -4.078888e-02 0.142300 -4.078800e-02 0.142350 -4.078711e-02 0.142400 -4.078623e-02 0.142450 -4.078534e-02 0.142500 -4.078446e-02 0.142550 -4.078358e-02 0.142600 -4.078270e-02 0.142650 -4.078182e-02 0.142700 -4.078094e-02 0.142750 -4.078007e-02 0.142800 -4.077919e-02 0.142850 -4.077832e-02 0.142900 -4.077744e-02 0.142950 -4.077657e-02 0.143000 -4.077570e-02 0.143050 -4.077483e-02 0.143100 -4.077396e-02 0.143150 -4.077309e-02 0.143200 -4.077222e-02 0.143250 -4.077136e-02 0.143300 -4.077049e-02 0.143350 -4.076963e-02 0.143400 -4.076877e-02 0.143450 -4.076790e-02 0.143500 -4.076704e-02 0.143550 -4.076618e-02 0.143600 -4.076533e-02 0.143650 -4.076447e-02 0.143700 -4.076361e-02 0.143750 -4.076276e-02 0.143800 -4.076190e-02 0.143850 -4.076105e-02 0.143900 -4.076020e-02 0.143950 -4.075935e-02 0.144000 -4.075850e-02 0.144050 -4.075765e-02 0.144100 -4.075680e-02 0.144150 -4.075595e-02 0.144200 -4.075511e-02 0.144250 -4.075426e-02 0.144300 -4.075342e-02 0.144350 -4.075258e-02 0.144400 -4.075174e-02 0.144450 -4.075090e-02 0.144500 -4.075006e-02 0.144550 -4.074922e-02 0.144600 -4.074838e-02 0.144650 -4.074754e-02 0.144700 -4.074671e-02 0.144750 -4.074587e-02 0.144800 -4.074504e-02 0.144850 -4.074421e-02 0.144900 -4.074338e-02 0.144950 -4.074255e-02 0.145000 -4.074172e-02 0.145050 -4.074089e-02 0.145100 -4.074007e-02 0.145150 -4.073924e-02 0.145200 -4.073841e-02 0.145250 -4.073759e-02 0.145300 -4.073677e-02 0.145350 -4.073595e-02 0.145400 -4.073513e-02 0.145450 -4.073431e-02 0.145500 -4.073349e-02 0.145550 -4.073267e-02 0.145600 -4.073185e-02 0.145650 -4.073104e-02 0.145700 -4.073022e-02 0.145750 -4.072941e-02 0.145800 -4.072860e-02 0.145850 -4.072779e-02 0.145900 -4.072698e-02 0.145950 -4.072617e-02 0.146000 -4.072536e-02 0.146050 -4.072455e-02 0.146100 -4.072374e-02 0.146150 -4.072294e-02 0.146200 -4.072213e-02 0.146250 -4.072133e-02 0.146300 -4.072053e-02 0.146350 -4.071973e-02 0.146400 -4.071893e-02 0.146450 -4.071813e-02 0.146500 -4.071733e-02 0.146550 -4.071653e-02 0.146600 -4.071573e-02 0.146650 -4.071494e-02 0.146700 -4.071414e-02 0.146750 -4.071335e-02 0.146800 -4.071256e-02 0.146850 -4.071177e-02 0.146900 -4.071098e-02 0.146950 -4.071019e-02 0.147000 -4.070940e-02 0.147050 -4.070861e-02 0.147100 -4.070783e-02 0.147150 -4.070704e-02 0.147200 -4.070626e-02 0.147250 -4.070547e-02 0.147300 -4.070469e-02 0.147350 -4.070391e-02 0.147400 -4.070313e-02 0.147450 -4.070235e-02 0.147500 -4.070157e-02 0.147550 -4.070079e-02 0.147600 -4.070001e-02 0.147650 -4.069924e-02 0.147700 -4.069846e-02 0.147750 -4.069769e-02 0.147800 -4.069692e-02 0.147850 -4.069614e-02 0.147900 -4.069537e-02 0.147950 -4.069460e-02 0.148000 -4.069383e-02 0.148050 -4.069307e-02 0.148100 -4.069230e-02 0.148150 -4.069153e-02 0.148200 -4.069077e-02 0.148250 -4.069000e-02 0.148300 -4.068924e-02 0.148350 -4.068848e-02 0.148400 -4.068772e-02 0.148450 -4.068696e-02 0.148500 -4.068620e-02 0.148550 -4.068544e-02 0.148600 -4.068468e-02 0.148650 -4.068392e-02 0.148700 -4.068317e-02 0.148750 -4.068241e-02 0.148800 -4.068166e-02 0.148850 -4.068091e-02 0.148900 -4.068016e-02 0.148950 -4.067940e-02 0.149000 -4.067865e-02 0.149050 -4.067791e-02 0.149100 -4.067716e-02 0.149150 -4.067641e-02 0.149200 -4.067566e-02 0.149250 -4.067492e-02 0.149300 -4.067417e-02 0.149350 -4.067343e-02 0.149400 -4.067269e-02 0.149450 -4.067195e-02 0.149500 -4.067121e-02 0.149550 -4.067047e-02 0.149600 -4.066973e-02 0.149650 -4.066899e-02 0.149700 -4.066825e-02 0.149750 -4.066752e-02 0.149800 -4.066678e-02 0.149850 -4.066605e-02 0.149900 -4.066531e-02 0.149950 -4.066458e-02 0.150000 -4.066385e-02 0.150050 -4.066312e-02 0.150100 -4.066239e-02 0.150150 -4.066166e-02 0.150200 -4.066093e-02 0.150250 -4.066021e-02 0.150300 -4.065948e-02 0.150350 -4.065875e-02 0.150400 -4.065803e-02 0.150450 -4.065731e-02 0.150500 -4.065658e-02 0.150550 -4.065586e-02 0.150600 -4.065514e-02 0.150650 -4.065442e-02 0.150700 -4.065370e-02 0.150750 -4.065299e-02 0.150800 -4.065227e-02 0.150850 -4.065155e-02 0.150900 -4.065084e-02 0.150950 -4.065012e-02 0.151000 -4.064941e-02 0.151050 -4.064870e-02 0.151100 -4.064799e-02 0.151150 -4.064727e-02 0.151200 -4.064656e-02 0.151250 -4.064586e-02 0.151300 -4.064515e-02 0.151350 -4.064444e-02 0.151400 -4.064373e-02 0.151450 -4.064303e-02 0.151500 -4.064232e-02 0.151550 -4.064162e-02 0.151600 -4.064092e-02 0.151650 -4.064022e-02 0.151700 -4.063951e-02 0.151750 -4.063881e-02 0.151800 -4.063812e-02 0.151850 -4.063742e-02 0.151900 -4.063672e-02 0.151950 -4.063602e-02 0.152000 -4.063533e-02 0.152050 -4.063463e-02 0.152100 -4.063394e-02 0.152150 -4.063324e-02 0.152200 -4.063255e-02 0.152250 -4.063186e-02 0.152300 -4.063117e-02 0.152350 -4.063048e-02 0.152400 -4.062979e-02 0.152450 -4.062910e-02 0.152500 -4.062842e-02 0.152550 -4.062773e-02 0.152600 -4.062704e-02 0.152650 -4.062636e-02 0.152700 -4.062568e-02 0.152750 -4.062499e-02 0.152800 -4.062431e-02 0.152850 -4.062363e-02 0.152900 -4.062295e-02 0.152950 -4.062227e-02 0.153000 -4.062159e-02 0.153050 -4.062091e-02 0.153100 -4.062024e-02 0.153150 -4.061956e-02 0.153200 -4.061888e-02 0.153250 -4.061821e-02 0.153300 -4.061754e-02 0.153350 -4.061686e-02 0.153400 -4.061619e-02 0.153450 -4.061552e-02 0.153500 -4.061485e-02 0.153550 -4.061418e-02 0.153600 -4.061351e-02 0.153650 -4.061285e-02 0.153700 -4.061218e-02 0.153750 -4.061151e-02 0.153800 -4.061085e-02 0.153850 -4.061018e-02 0.153900 -4.060952e-02 0.153950 -4.060886e-02 0.154000 -4.060819e-02 0.154050 -4.060753e-02 0.154100 -4.060687e-02 0.154150 -4.060621e-02 0.154200 -4.060556e-02 0.154250 -4.060490e-02 0.154300 -4.060424e-02 0.154350 -4.060358e-02 0.154400 -4.060293e-02 0.154450 -4.060227e-02 0.154500 -4.060162e-02 0.154550 -4.060097e-02 0.154600 -4.060032e-02 0.154650 -4.059967e-02 0.154700 -4.059901e-02 0.154750 -4.059837e-02 0.154800 -4.059772e-02 0.154850 -4.059707e-02 0.154900 -4.059642e-02 0.154950 -4.059577e-02 0.155000 -4.059513e-02 0.155050 -4.059448e-02 0.155100 -4.059384e-02 0.155150 -4.059320e-02 0.155200 -4.059256e-02 0.155250 -4.059191e-02 0.155300 -4.059127e-02 0.155350 -4.059063e-02 0.155400 -4.058999e-02 0.155450 -4.058936e-02 0.155500 -4.058872e-02 0.155550 -4.058808e-02 0.155600 -4.058745e-02 0.155650 -4.058681e-02 0.155700 -4.058618e-02 0.155750 -4.058554e-02 0.155800 -4.058491e-02 0.155850 -4.058428e-02 0.155900 -4.058365e-02 0.155950 -4.058302e-02 0.156000 -4.058239e-02 0.156050 -4.058176e-02 0.156100 -4.058113e-02 0.156150 -4.058050e-02 0.156200 -4.057988e-02 0.156250 -4.057925e-02 0.156300 -4.057862e-02 0.156350 -4.057800e-02 0.156400 -4.057738e-02 0.156450 -4.057675e-02 0.156500 -4.057613e-02 0.156550 -4.057551e-02 0.156600 -4.057489e-02 0.156650 -4.057427e-02 0.156700 -4.057365e-02 0.156750 -4.057304e-02 0.156800 -4.057242e-02 0.156850 -4.057180e-02 0.156900 -4.057119e-02 0.156950 -4.057057e-02 0.157000 -4.056996e-02 0.157050 -4.056934e-02 0.157100 -4.056873e-02 0.157150 -4.056812e-02 0.157200 -4.056751e-02 0.157250 -4.056690e-02 0.157300 -4.056629e-02 0.157350 -4.056568e-02 0.157400 -4.056507e-02 0.157450 -4.056447e-02 0.157500 -4.056386e-02 0.157550 -4.056325e-02 0.157600 -4.056265e-02 0.157650 -4.056204e-02 0.157700 -4.056144e-02 0.157750 -4.056084e-02 0.157800 -4.056024e-02 0.157850 -4.055964e-02 0.157900 -4.055903e-02 0.157950 -4.055844e-02 0.158000 -4.055784e-02 0.158050 -4.055724e-02 0.158100 -4.055664e-02 0.158150 -4.055604e-02 0.158200 -4.055545e-02 0.158250 -4.055485e-02 0.158300 -4.055426e-02 0.158350 -4.055366e-02 0.158400 -4.055307e-02 0.158450 -4.055248e-02 0.158500 -4.055189e-02 0.158550 -4.055130e-02 0.158600 -4.055071e-02 0.158650 -4.055012e-02 0.158700 -4.054953e-02 0.158750 -4.054894e-02 0.158800 -4.054835e-02 0.158850 -4.054777e-02 0.158900 -4.054718e-02 0.158950 -4.054660e-02 0.159000 -4.054601e-02 0.159050 -4.054543e-02 0.159100 -4.054485e-02 0.159150 -4.054427e-02 0.159200 -4.054368e-02 0.159250 -4.054310e-02 0.159300 -4.054252e-02 0.159350 -4.054195e-02 0.159400 -4.054137e-02 0.159450 -4.054079e-02 0.159500 -4.054021e-02 0.159550 -4.053964e-02 0.159600 -4.053906e-02 0.159650 -4.053849e-02 0.159700 -4.053791e-02 0.159750 -4.053734e-02 0.159800 -4.053677e-02 0.159850 -4.053619e-02 0.159900 -4.053562e-02 0.159950 -4.053505e-02 0.160000 -4.053448e-02 0.160050 -4.053391e-02 0.160100 -4.053335e-02 0.160150 -4.053278e-02 0.160200 -4.053221e-02 0.160250 -4.053165e-02 0.160300 -4.053108e-02 0.160350 -4.053052e-02 0.160400 -4.052995e-02 0.160450 -4.052939e-02 0.160500 -4.052883e-02 0.160550 -4.052826e-02 0.160600 -4.052770e-02 0.160650 -4.052714e-02 0.160700 -4.052658e-02 0.160750 -4.052602e-02 0.160800 -4.052546e-02 0.160850 -4.052491e-02 0.160900 -4.052435e-02 0.160950 -4.052379e-02 0.161000 -4.052324e-02 0.161050 -4.052268e-02 0.161100 -4.052213e-02 0.161150 -4.052158e-02 0.161200 -4.052102e-02 0.161250 -4.052047e-02 0.161300 -4.051992e-02 0.161350 -4.051937e-02 0.161400 -4.051882e-02 0.161450 -4.051827e-02 0.161500 -4.051772e-02 0.161550 -4.051717e-02 0.161600 -4.051662e-02 0.161650 -4.051608e-02 0.161700 -4.051553e-02 0.161750 -4.051499e-02 0.161800 -4.051444e-02 0.161850 -4.051390e-02 0.161900 -4.051335e-02 0.161950 -4.051281e-02 0.162000 -4.051227e-02 0.162050 -4.051173e-02 0.162100 -4.051119e-02 0.162150 -4.051065e-02 0.162200 -4.051011e-02 0.162250 -4.050957e-02 0.162300 -4.050903e-02 0.162350 -4.050850e-02 0.162400 -4.050796e-02 0.162450 -4.050742e-02 0.162500 -4.050689e-02 0.162550 -4.050635e-02 0.162600 -4.050582e-02 0.162650 -4.050529e-02 0.162700 -4.050475e-02 0.162750 -4.050422e-02 0.162800 -4.050369e-02 0.162850 -4.050316e-02 0.162900 -4.050263e-02 0.162950 -4.050210e-02 0.163000 -4.050157e-02 0.163050 -4.050104e-02 0.163100 -4.050052e-02 0.163150 -4.049999e-02 0.163200 -4.049947e-02 0.163250 -4.049894e-02 0.163300 -4.049842e-02 0.163350 -4.049789e-02 0.163400 -4.049737e-02 0.163450 -4.049685e-02 0.163500 -4.049632e-02 0.163550 -4.049580e-02 0.163600 -4.049528e-02 0.163650 -4.049476e-02 0.163700 -4.049424e-02 0.163750 -4.049372e-02 0.163800 -4.049321e-02 0.163850 -4.049269e-02 0.163900 -4.049217e-02 0.163950 -4.049166e-02 0.164000 -4.049114e-02 0.164050 -4.049062e-02 0.164100 -4.049011e-02 0.164150 -4.048960e-02 0.164200 -4.048908e-02 0.164250 -4.048857e-02 0.164300 -4.048806e-02 0.164350 -4.048755e-02 0.164400 -4.048704e-02 0.164450 -4.048653e-02 0.164500 -4.048602e-02 0.164550 -4.048551e-02 0.164600 -4.048500e-02 0.164650 -4.048450e-02 0.164700 -4.048399e-02 0.164750 -4.048348e-02 0.164800 -4.048298e-02 0.164850 -4.048247e-02 0.164900 -4.048197e-02 0.164950 -4.048147e-02 0.165000 -4.048096e-02 0.165050 -4.048046e-02 0.165100 -4.047996e-02 0.165150 -4.047946e-02 0.165200 -4.047896e-02 0.165250 -4.047846e-02 0.165300 -4.047796e-02 0.165350 -4.047746e-02 0.165400 -4.047696e-02 0.165450 -4.047647e-02 0.165500 -4.047597e-02 0.165550 -4.047548e-02 0.165600 -4.047498e-02 0.165650 -4.047449e-02 0.165700 -4.047399e-02 0.165750 -4.047350e-02 0.165800 -4.047301e-02 0.165850 -4.047251e-02 0.165900 -4.047202e-02 0.165950 -4.047153e-02 0.166000 -4.047104e-02 0.166050 -4.047055e-02 0.166100 -4.047006e-02 0.166150 -4.046957e-02 0.166200 -4.046908e-02 0.166250 -4.046860e-02 0.166300 -4.046811e-02 0.166350 -4.046762e-02 0.166400 -4.046714e-02 0.166450 -4.046665e-02 0.166500 -4.046617e-02 0.166550 -4.046569e-02 0.166600 -4.046520e-02 0.166650 -4.046472e-02 0.166700 -4.046424e-02 0.166750 -4.046376e-02 0.166800 -4.046328e-02 0.166850 -4.046280e-02 0.166900 -4.046232e-02 0.166950 -4.046184e-02 0.167000 -4.046136e-02 0.167050 -4.046088e-02 0.167100 -4.046041e-02 0.167150 -4.045993e-02 0.167200 -4.045945e-02 0.167250 -4.045898e-02 0.167300 -4.045850e-02 0.167350 -4.045803e-02 0.167400 -4.045756e-02 0.167450 -4.045708e-02 0.167500 -4.045661e-02 0.167550 -4.045614e-02 0.167600 -4.045567e-02 0.167650 -4.045520e-02 0.167700 -4.045473e-02 0.167750 -4.045426e-02 0.167800 -4.045379e-02 0.167850 -4.045332e-02 0.167900 -4.045285e-02 0.167950 -4.045239e-02 0.168000 -4.045192e-02 0.168050 -4.045145e-02 0.168100 -4.045099e-02 0.168150 -4.045052e-02 0.168200 -4.045006e-02 0.168250 -4.044960e-02 0.168300 -4.044913e-02 0.168350 -4.044867e-02 0.168400 -4.044821e-02 0.168450 -4.044775e-02 0.168500 -4.044729e-02 0.168550 -4.044683e-02 0.168600 -4.044637e-02 0.168650 -4.044591e-02 0.168700 -4.044545e-02 0.168750 -4.044499e-02 0.168800 -4.044454e-02 0.168850 -4.044408e-02 0.168900 -4.044362e-02 0.168950 -4.044317e-02 0.169000 -4.044271e-02 0.169050 -4.044226e-02 0.169100 -4.044181e-02 0.169150 -4.044135e-02 0.169200 -4.044090e-02 0.169250 -4.044045e-02 0.169300 -4.044000e-02 0.169350 -4.043954e-02 0.169400 -4.043909e-02 0.169450 -4.043864e-02 0.169500 -4.043820e-02 0.169550 -4.043775e-02 0.169600 -4.043730e-02 0.169650 -4.043685e-02 0.169700 -4.043640e-02 0.169750 -4.043596e-02 0.169800 -4.043551e-02 0.169850 -4.043507e-02 0.169900 -4.043462e-02 0.169950 -4.043418e-02 0.170000 -4.043373e-02 0.170050 -4.043329e-02 0.170100 -4.043285e-02 0.170150 -4.043241e-02 0.170200 -4.043196e-02 0.170250 -4.043152e-02 0.170300 -4.043108e-02 0.170350 -4.043064e-02 0.170400 -4.043020e-02 0.170450 -4.042977e-02 0.170500 -4.042933e-02 0.170550 -4.042889e-02 0.170600 -4.042845e-02 0.170650 -4.042802e-02 0.170700 -4.042758e-02 0.170750 -4.042714e-02 0.170800 -4.042671e-02 0.170850 -4.042628e-02 0.170900 -4.042584e-02 0.170950 -4.042541e-02 0.171000 -4.042498e-02 0.171050 -4.042454e-02 0.171100 -4.042411e-02 0.171150 -4.042368e-02 0.171200 -4.042325e-02 0.171250 -4.042282e-02 0.171300 -4.042239e-02 0.171350 -4.042196e-02 0.171400 -4.042153e-02 0.171450 -4.042111e-02 0.171500 -4.042068e-02 0.171550 -4.042025e-02 0.171600 -4.041982e-02 0.171650 -4.041940e-02 0.171700 -4.041897e-02 0.171750 -4.041855e-02 0.171800 -4.041812e-02 0.171850 -4.041770e-02 0.171900 -4.041728e-02 0.171950 -4.041686e-02 0.172000 -4.041643e-02 0.172050 -4.041601e-02 0.172100 -4.041559e-02 0.172150 -4.041517e-02 0.172200 -4.041475e-02 0.172250 -4.041433e-02 0.172300 -4.041391e-02 0.172350 -4.041349e-02 0.172400 -4.041308e-02 0.172450 -4.041266e-02 0.172500 -4.041224e-02 0.172550 -4.041183e-02 0.172600 -4.041141e-02 0.172650 -4.041099e-02 0.172700 -4.041058e-02 0.172750 -4.041017e-02 0.172800 -4.040975e-02 0.172850 -4.040934e-02 0.172900 -4.040893e-02 0.172950 -4.040851e-02 0.173000 -4.040810e-02 0.173050 -4.040769e-02 0.173100 -4.040728e-02 0.173150 -4.040687e-02 0.173200 -4.040646e-02 0.173250 -4.040605e-02 0.173300 -4.040564e-02 0.173350 -4.040524e-02 0.173400 -4.040483e-02 0.173450 -4.040442e-02 0.173500 -4.040401e-02 0.173550 -4.040361e-02 0.173600 -4.040320e-02 0.173650 -4.040280e-02 0.173700 -4.040239e-02 0.173750 -4.040199e-02 0.173800 -4.040159e-02 0.173850 -4.040118e-02 0.173900 -4.040078e-02 0.173950 -4.040038e-02 0.174000 -4.039998e-02 0.174050 -4.039958e-02 0.174100 -4.039918e-02 0.174150 -4.039878e-02 0.174200 -4.039838e-02 0.174250 -4.039798e-02 0.174300 -4.039758e-02 0.174350 -4.039718e-02 0.174400 -4.039678e-02 0.174450 -4.039639e-02 0.174500 -4.039599e-02 0.174550 -4.039559e-02 0.174600 -4.039520e-02 0.174650 -4.039480e-02 0.174700 -4.039441e-02 0.174750 -4.039402e-02 0.174800 -4.039362e-02 0.174850 -4.039323e-02 0.174900 -4.039284e-02 0.174950 -4.039244e-02 0.175000 -4.039205e-02 0.175050 -4.039166e-02 0.175100 -4.039127e-02 0.175150 -4.039088e-02 0.175200 -4.039049e-02 0.175250 -4.039010e-02 0.175300 -4.038971e-02 0.175350 -4.038933e-02 0.175400 -4.038894e-02 0.175450 -4.038855e-02 0.175500 -4.038816e-02 0.175550 -4.038778e-02 0.175600 -4.038739e-02 0.175650 -4.038701e-02 0.175700 -4.038662e-02 0.175750 -4.038624e-02 0.175800 -4.038585e-02 0.175850 -4.038547e-02 0.175900 -4.038509e-02 0.175950 -4.038471e-02 0.176000 -4.038432e-02 0.176050 -4.038394e-02 0.176100 -4.038356e-02 0.176150 -4.038318e-02 0.176200 -4.038280e-02 0.176250 -4.038242e-02 0.176300 -4.038204e-02 0.176350 -4.038166e-02 0.176400 -4.038129e-02 0.176450 -4.038091e-02 0.176500 -4.038053e-02 0.176550 -4.038015e-02 0.176600 -4.037978e-02 0.176650 -4.037940e-02 0.176700 -4.037903e-02 0.176750 -4.037865e-02 0.176800 -4.037828e-02 0.176850 -4.037790e-02 0.176900 -4.037753e-02 0.176950 -4.037716e-02 0.177000 -4.037679e-02 0.177050 -4.037641e-02 0.177100 -4.037604e-02 0.177150 -4.037567e-02 0.177200 -4.037530e-02 0.177250 -4.037493e-02 0.177300 -4.037456e-02 0.177350 -4.037419e-02 0.177400 -4.037382e-02 0.177450 -4.037345e-02 0.177500 -4.037309e-02 0.177550 -4.037272e-02 0.177600 -4.037235e-02 0.177650 -4.037199e-02 0.177700 -4.037162e-02 0.177750 -4.037125e-02 0.177800 -4.037089e-02 0.177850 -4.037052e-02 0.177900 -4.037016e-02 0.177950 -4.036980e-02 0.178000 -4.036943e-02 0.178050 -4.036907e-02 0.178100 -4.036871e-02 0.178150 -4.036835e-02 0.178200 -4.036798e-02 0.178250 -4.036762e-02 0.178300 -4.036726e-02 0.178350 -4.036690e-02 0.178400 -4.036654e-02 0.178450 -4.036618e-02 0.178500 -4.036583e-02 0.178550 -4.036547e-02 0.178600 -4.036511e-02 0.178650 -4.036475e-02 0.178700 -4.036440e-02 0.178750 -4.036404e-02 0.178800 -4.036368e-02 0.178850 -4.036333e-02 0.178900 -4.036297e-02 0.178950 -4.036262e-02 0.179000 -4.036226e-02 0.179050 -4.036191e-02 0.179100 -4.036156e-02 0.179150 -4.036120e-02 0.179200 -4.036085e-02 0.179250 -4.036050e-02 0.179300 -4.036015e-02 0.179350 -4.035980e-02 0.179400 -4.035944e-02 0.179450 -4.035909e-02 0.179500 -4.035874e-02 0.179550 -4.035839e-02 0.179600 -4.035805e-02 0.179650 -4.035770e-02 0.179700 -4.035735e-02 0.179750 -4.035700e-02 0.179800 -4.035665e-02 0.179850 -4.035631e-02 0.179900 -4.035596e-02 0.179950 -4.035561e-02 0.180000 -4.035527e-02 0.180050 -4.035492e-02 0.180100 -4.035458e-02 0.180150 -4.035424e-02 0.180200 -4.035389e-02 0.180250 -4.035355e-02 0.180300 -4.035321e-02 0.180350 -4.035286e-02 0.180400 -4.035252e-02 0.180450 -4.035218e-02 0.180500 -4.035184e-02 0.180550 -4.035150e-02 0.180600 -4.035116e-02 0.180650 -4.035082e-02 0.180700 -4.035048e-02 0.180750 -4.035014e-02 0.180800 -4.034980e-02 0.180850 -4.034946e-02 0.180900 -4.034912e-02 0.180950 -4.034879e-02 0.181000 -4.034845e-02 0.181050 -4.034811e-02 0.181100 -4.034778e-02 0.181150 -4.034744e-02 0.181200 -4.034710e-02 0.181250 -4.034677e-02 0.181300 -4.034644e-02 0.181350 -4.034610e-02 0.181400 -4.034577e-02 0.181450 -4.034543e-02 0.181500 -4.034510e-02 0.181550 -4.034477e-02 0.181600 -4.034444e-02 0.181650 -4.034411e-02 0.181700 -4.034377e-02 0.181750 -4.034344e-02 0.181800 -4.034311e-02 0.181850 -4.034278e-02 0.181900 -4.034245e-02 0.181950 -4.034212e-02 0.182000 -4.034180e-02 0.182050 -4.034147e-02 0.182100 -4.034114e-02 0.182150 -4.034081e-02 0.182200 -4.034049e-02 0.182250 -4.034016e-02 0.182300 -4.033983e-02 0.182350 -4.033951e-02 0.182400 -4.033918e-02 0.182450 -4.033886e-02 0.182500 -4.033853e-02 0.182550 -4.033821e-02 0.182600 -4.033788e-02 0.182650 -4.033756e-02 0.182700 -4.033724e-02 0.182750 -4.033691e-02 0.182800 -4.033659e-02 0.182850 -4.033627e-02 0.182900 -4.033595e-02 0.182950 -4.033563e-02 0.183000 -4.033531e-02 0.183050 -4.033499e-02 0.183100 -4.033467e-02 0.183150 -4.033435e-02 0.183200 -4.033403e-02 0.183250 -4.033371e-02 0.183300 -4.033339e-02 0.183350 -4.033307e-02 0.183400 -4.033276e-02 0.183450 -4.033244e-02 0.183500 -4.033212e-02 0.183550 -4.033181e-02 0.183600 -4.033149e-02 0.183650 -4.033118e-02 0.183700 -4.033086e-02 0.183750 -4.033055e-02 0.183800 -4.033023e-02 0.183850 -4.032992e-02 0.183900 -4.032961e-02 0.183950 -4.032929e-02 0.184000 -4.032898e-02 0.184050 -4.032867e-02 0.184100 -4.032836e-02 0.184150 -4.032804e-02 0.184200 -4.032773e-02 0.184250 -4.032742e-02 0.184300 -4.032711e-02 0.184350 -4.032680e-02 0.184400 -4.032649e-02 0.184450 -4.032618e-02 0.184500 -4.032587e-02 0.184550 -4.032557e-02 0.184600 -4.032526e-02 0.184650 -4.032495e-02 0.184700 -4.032464e-02 0.184750 -4.032434e-02 0.184800 -4.032403e-02 0.184850 -4.032372e-02 0.184900 -4.032342e-02 0.184950 -4.032311e-02 0.185000 -4.032281e-02 0.185050 -4.032250e-02 0.185100 -4.032220e-02 0.185150 -4.032190e-02 0.185200 -4.032159e-02 0.185250 -4.032129e-02 0.185300 -4.032099e-02 0.185350 -4.032068e-02 0.185400 -4.032038e-02 0.185450 -4.032008e-02 0.185500 -4.031978e-02 0.185550 -4.031948e-02 0.185600 -4.031918e-02 0.185650 -4.031888e-02 0.185700 -4.031858e-02 0.185750 -4.031828e-02 0.185800 -4.031798e-02 0.185850 -4.031768e-02 0.185900 -4.031738e-02 0.185950 -4.031709e-02 0.186000 -4.031679e-02 0.186050 -4.031649e-02 0.186100 -4.031619e-02 0.186150 -4.031590e-02 0.186200 -4.031560e-02 0.186250 -4.031531e-02 0.186300 -4.031501e-02 0.186350 -4.031472e-02 0.186400 -4.031442e-02 0.186450 -4.031413e-02 0.186500 -4.031383e-02 0.186550 -4.031354e-02 0.186600 -4.031325e-02 0.186650 -4.031296e-02 0.186700 -4.031266e-02 0.186750 -4.031237e-02 0.186800 -4.031208e-02 0.186850 -4.031179e-02 0.186900 -4.031150e-02 0.186950 -4.031121e-02 0.187000 -4.031092e-02 0.187050 -4.031063e-02 0.187100 -4.031034e-02 0.187150 -4.031005e-02 0.187200 -4.030976e-02 0.187250 -4.030947e-02 0.187300 -4.030919e-02 0.187350 -4.030890e-02 0.187400 -4.030861e-02 0.187450 -4.030832e-02 0.187500 -4.030804e-02 0.187550 -4.030775e-02 0.187600 -4.030747e-02 0.187650 -4.030718e-02 0.187700 -4.030689e-02 0.187750 -4.030661e-02 0.187800 -4.030633e-02 0.187850 -4.030604e-02 0.187900 -4.030576e-02 0.187950 -4.030548e-02 0.188000 -4.030519e-02 0.188050 -4.030491e-02 0.188100 -4.030463e-02 0.188150 -4.030435e-02 0.188200 -4.030406e-02 0.188250 -4.030378e-02 0.188300 -4.030350e-02 0.188350 -4.030322e-02 0.188400 -4.030294e-02 0.188450 -4.030266e-02 0.188500 -4.030238e-02 0.188550 -4.030210e-02 0.188600 -4.030182e-02 0.188650 -4.030155e-02 0.188700 -4.030127e-02 0.188750 -4.030099e-02 0.188800 -4.030071e-02 0.188850 -4.030044e-02 0.188900 -4.030016e-02 0.188950 -4.029988e-02 0.189000 -4.029961e-02 0.189050 -4.029933e-02 0.189100 -4.029906e-02 0.189150 -4.029878e-02 0.189200 -4.029851e-02 0.189250 -4.029823e-02 0.189300 -4.029796e-02 0.189350 -4.029769e-02 0.189400 -4.029741e-02 0.189450 -4.029714e-02 0.189500 -4.029687e-02 0.189550 -4.029660e-02 0.189600 -4.029632e-02 0.189650 -4.029605e-02 0.189700 -4.029578e-02 0.189750 -4.029551e-02 0.189800 -4.029524e-02 0.189850 -4.029497e-02 0.189900 -4.029470e-02 0.189950 -4.029443e-02 0.190000 -4.029416e-02 0.190050 -4.029389e-02 0.190100 -4.029362e-02 0.190150 -4.029336e-02 0.190200 -4.029309e-02 0.190250 -4.029282e-02 0.190300 -4.029255e-02 0.190350 -4.029229e-02 0.190400 -4.029202e-02 0.190450 -4.029175e-02 0.190500 -4.029149e-02 0.190550 -4.029122e-02 0.190600 -4.029096e-02 0.190650 -4.029069e-02 0.190700 -4.029043e-02 0.190750 -4.029016e-02 0.190800 -4.028990e-02 0.190850 -4.028964e-02 0.190900 -4.028937e-02 0.190950 -4.028911e-02 0.191000 -4.028885e-02 0.191050 -4.028859e-02 0.191100 -4.028833e-02 0.191150 -4.028806e-02 0.191200 -4.028780e-02 0.191250 -4.028754e-02 0.191300 -4.028728e-02 0.191350 -4.028702e-02 0.191400 -4.028676e-02 0.191450 -4.028650e-02 0.191500 -4.028624e-02 0.191550 -4.028598e-02 0.191600 -4.028572e-02 0.191650 -4.028547e-02 0.191700 -4.028521e-02 0.191750 -4.028495e-02 0.191800 -4.028469e-02 0.191850 -4.028444e-02 0.191900 -4.028418e-02 0.191950 -4.028392e-02 0.192000 -4.028367e-02 0.192050 -4.028341e-02 0.192100 -4.028316e-02 0.192150 -4.028290e-02 0.192200 -4.028265e-02 0.192250 -4.028239e-02 0.192300 -4.028214e-02 0.192350 -4.028189e-02 0.192400 -4.028163e-02 0.192450 -4.028138e-02 0.192500 -4.028113e-02 0.192550 -4.028087e-02 0.192600 -4.028062e-02 0.192650 -4.028037e-02 0.192700 -4.028012e-02 0.192750 -4.027987e-02 0.192800 -4.027962e-02 0.192850 -4.027937e-02 0.192900 -4.027911e-02 0.192950 -4.027886e-02 0.193000 -4.027862e-02 0.193050 -4.027837e-02 0.193100 -4.027812e-02 0.193150 -4.027787e-02 0.193200 -4.027762e-02 0.193250 -4.027737e-02 0.193300 -4.027712e-02 0.193350 -4.027688e-02 0.193400 -4.027663e-02 0.193450 -4.027638e-02 0.193500 -4.027614e-02 0.193550 -4.027589e-02 0.193600 -4.027564e-02 0.193650 -4.027540e-02 0.193700 -4.027515e-02 0.193750 -4.027491e-02 0.193800 -4.027466e-02 0.193850 -4.027442e-02 0.193900 -4.027417e-02 0.193950 -4.027393e-02 0.194000 -4.027369e-02 0.194050 -4.027344e-02 0.194100 -4.027320e-02 0.194150 -4.027296e-02 0.194200 -4.027272e-02 0.194250 -4.027247e-02 0.194300 -4.027223e-02 0.194350 -4.027199e-02 0.194400 -4.027175e-02 0.194450 -4.027151e-02 0.194500 -4.027127e-02 0.194550 -4.027103e-02 0.194600 -4.027079e-02 0.194650 -4.027055e-02 0.194700 -4.027031e-02 0.194750 -4.027007e-02 0.194800 -4.026983e-02 0.194850 -4.026959e-02 0.194900 -4.026936e-02 0.194950 -4.026912e-02 0.195000 -4.026888e-02 0.195050 -4.026864e-02 0.195100 -4.026841e-02 0.195150 -4.026817e-02 0.195200 -4.026793e-02 0.195250 -4.026770e-02 0.195300 -4.026746e-02 0.195350 -4.026723e-02 0.195400 -4.026699e-02 0.195450 -4.026676e-02 0.195500 -4.026652e-02 0.195550 -4.026629e-02 0.195600 -4.026605e-02 0.195650 -4.026582e-02 0.195700 -4.026559e-02 0.195750 -4.026535e-02 0.195800 -4.026512e-02 0.195850 -4.026489e-02 0.195900 -4.026466e-02 0.195950 -4.026442e-02 0.196000 -4.026419e-02 0.196050 -4.026396e-02 0.196100 -4.026373e-02 0.196150 -4.026350e-02 0.196200 -4.026327e-02 0.196250 -4.026304e-02 0.196300 -4.026281e-02 0.196350 -4.026258e-02 0.196400 -4.026235e-02 0.196450 -4.026212e-02 0.196500 -4.026189e-02 0.196550 -4.026166e-02 0.196600 -4.026144e-02 0.196650 -4.026121e-02 0.196700 -4.026098e-02 0.196750 -4.026075e-02 0.196800 -4.026053e-02 0.196850 -4.026030e-02 0.196900 -4.026007e-02 0.196950 -4.025985e-02 0.197000 -4.025962e-02 0.197050 -4.025939e-02 0.197100 -4.025917e-02 0.197150 -4.025894e-02 0.197200 -4.025872e-02 0.197250 -4.025850e-02 0.197300 -4.025827e-02 0.197350 -4.025805e-02 0.197400 -4.025782e-02 0.197450 -4.025760e-02 0.197500 -4.025738e-02 0.197550 -4.025715e-02 0.197600 -4.025693e-02 0.197650 -4.025671e-02 0.197700 -4.025649e-02 0.197750 -4.025627e-02 0.197800 -4.025604e-02 0.197850 -4.025582e-02 0.197900 -4.025560e-02 0.197950 -4.025538e-02 0.198000 -4.025516e-02 0.198050 -4.025494e-02 0.198100 -4.025472e-02 0.198150 -4.025450e-02 0.198200 -4.025428e-02 0.198250 -4.025406e-02 0.198300 -4.025384e-02 0.198350 -4.025363e-02 0.198400 -4.025341e-02 0.198450 -4.025319e-02 0.198500 -4.025297e-02 0.198550 -4.025276e-02 0.198600 -4.025254e-02 0.198650 -4.025232e-02 0.198700 -4.025211e-02 0.198750 -4.025189e-02 0.198800 -4.025167e-02 0.198850 -4.025146e-02 0.198900 -4.025124e-02 0.198950 -4.025103e-02 0.199000 -4.025081e-02 0.199050 -4.025060e-02 0.199100 -4.025038e-02 0.199150 -4.025017e-02 0.199200 -4.024996e-02 0.199250 -4.024974e-02 0.199300 -4.024953e-02 0.199350 -4.024932e-02 0.199400 -4.024910e-02 0.199450 -4.024889e-02 0.199500 -4.024868e-02 0.199550 -4.024847e-02 0.199600 -4.024825e-02 0.199650 -4.024804e-02 0.199700 -4.024783e-02 0.199750 -4.024762e-02 0.199800 -4.024741e-02 0.199850 -4.024720e-02 0.199900 -4.024699e-02 0.199950 -4.024678e-02 0.200000 -4.024657e-02 0.200050 -4.024636e-02 0.200100 -4.024615e-02 0.200150 -4.024594e-02 0.200200 -4.024573e-02 0.200250 -4.024553e-02 0.200300 -4.024532e-02 0.200350 -4.024511e-02 0.200400 -4.024490e-02 0.200450 -4.024470e-02 0.200500 -4.024449e-02 0.200550 -4.024428e-02 0.200600 -4.024408e-02 0.200650 -4.024387e-02 0.200700 -4.024366e-02 0.200750 -4.024346e-02 0.200800 -4.024325e-02 0.200850 -4.024305e-02 0.200900 -4.024284e-02 0.200950 -4.024264e-02 0.201000 -4.024243e-02 0.201050 -4.024223e-02 0.201100 -4.024203e-02 0.201150 -4.024182e-02 0.201200 -4.024162e-02 0.201250 -4.024141e-02 0.201300 -4.024121e-02 0.201350 -4.024101e-02 0.201400 -4.024081e-02 0.201450 -4.024060e-02 0.201500 -4.024040e-02 0.201550 -4.024020e-02 0.201600 -4.024000e-02 0.201650 -4.023980e-02 0.201700 -4.023960e-02 0.201750 -4.023940e-02 0.201800 -4.023920e-02 0.201850 -4.023900e-02 0.201900 -4.023880e-02 0.201950 -4.023860e-02 0.202000 -4.023840e-02 0.202050 -4.023820e-02 0.202100 -4.023800e-02 0.202150 -4.023780e-02 0.202200 -4.023760e-02 0.202250 -4.023741e-02 0.202300 -4.023721e-02 0.202350 -4.023701e-02 0.202400 -4.023681e-02 0.202450 -4.023662e-02 0.202500 -4.023642e-02 0.202550 -4.023622e-02 0.202600 -4.023603e-02 0.202650 -4.023583e-02 0.202700 -4.023563e-02 0.202750 -4.023544e-02 0.202800 -4.023524e-02 0.202850 -4.023505e-02 0.202900 -4.023485e-02 0.202950 -4.023466e-02 0.203000 -4.023446e-02 0.203050 -4.023427e-02 0.203100 -4.023407e-02 0.203150 -4.023388e-02 0.203200 -4.023369e-02 0.203250 -4.023349e-02 0.203300 -4.023330e-02 0.203350 -4.023311e-02 0.203400 -4.023292e-02 0.203450 -4.023272e-02 0.203500 -4.023253e-02 0.203550 -4.023234e-02 0.203600 -4.023215e-02 0.203650 -4.023196e-02 0.203700 -4.023177e-02 0.203750 -4.023158e-02 0.203800 -4.023138e-02 0.203850 -4.023119e-02 0.203900 -4.023100e-02 0.203950 -4.023081e-02 0.204000 -4.023062e-02 0.204050 -4.023044e-02 0.204100 -4.023025e-02 0.204150 -4.023006e-02 0.204200 -4.022987e-02 0.204250 -4.022968e-02 0.204300 -4.022949e-02 0.204350 -4.022930e-02 0.204400 -4.022912e-02 0.204450 -4.022893e-02 0.204500 -4.022874e-02 0.204550 -4.022855e-02 0.204600 -4.022837e-02 0.204650 -4.022818e-02 0.204700 -4.022799e-02 0.204750 -4.022781e-02 0.204800 -4.022762e-02 0.204850 -4.022744e-02 0.204900 -4.022725e-02 0.204950 -4.022707e-02 0.205000 -4.022688e-02 0.205050 -4.022670e-02 0.205100 -4.022651e-02 0.205150 -4.022633e-02 0.205200 -4.022614e-02 0.205250 -4.022596e-02 0.205300 -4.022578e-02 0.205350 -4.022559e-02 0.205400 -4.022541e-02 0.205450 -4.022523e-02 0.205500 -4.022504e-02 0.205550 -4.022486e-02 0.205600 -4.022468e-02 0.205650 -4.022450e-02 0.205700 -4.022432e-02 0.205750 -4.022413e-02 0.205800 -4.022395e-02 0.205850 -4.022377e-02 0.205900 -4.022359e-02 0.205950 -4.022341e-02 0.206000 -4.022323e-02 0.206050 -4.022305e-02 0.206100 -4.022287e-02 0.206150 -4.022269e-02 0.206200 -4.022251e-02 0.206250 -4.022233e-02 0.206300 -4.022215e-02 0.206350 -4.022197e-02 0.206400 -4.022180e-02 0.206450 -4.022162e-02 0.206500 -4.022144e-02 0.206550 -4.022126e-02 0.206600 -4.022108e-02 0.206650 -4.022091e-02 0.206700 -4.022073e-02 0.206750 -4.022055e-02 0.206800 -4.022037e-02 0.206850 -4.022020e-02 0.206900 -4.022002e-02 0.206950 -4.021985e-02 0.207000 -4.021967e-02 0.207050 -4.021949e-02 0.207100 -4.021932e-02 0.207150 -4.021914e-02 0.207200 -4.021897e-02 0.207250 -4.021879e-02 0.207300 -4.021862e-02 0.207350 -4.021844e-02 0.207400 -4.021827e-02 0.207450 -4.021810e-02 0.207500 -4.021792e-02 0.207550 -4.021775e-02 0.207600 -4.021758e-02 0.207650 -4.021740e-02 0.207700 -4.021723e-02 0.207750 -4.021706e-02 0.207800 -4.021688e-02 0.207850 -4.021671e-02 0.207900 -4.021654e-02 0.207950 -4.021637e-02 0.208000 -4.021620e-02 0.208050 -4.021603e-02 0.208100 -4.021585e-02 0.208150 -4.021568e-02 0.208200 -4.021551e-02 0.208250 -4.021534e-02 0.208300 -4.021517e-02 0.208350 -4.021500e-02 0.208400 -4.021483e-02 0.208450 -4.021466e-02 0.208500 -4.021449e-02 0.208550 -4.021432e-02 0.208600 -4.021415e-02 0.208650 -4.021399e-02 0.208700 -4.021382e-02 0.208750 -4.021365e-02 0.208800 -4.021348e-02 0.208850 -4.021331e-02 0.208900 -4.021314e-02 0.208950 -4.021298e-02 0.209000 -4.021281e-02 0.209050 -4.021264e-02 0.209100 -4.021248e-02 0.209150 -4.021231e-02 0.209200 -4.021214e-02 0.209250 -4.021198e-02 0.209300 -4.021181e-02 0.209350 -4.021164e-02 0.209400 -4.021148e-02 0.209450 -4.021131e-02 0.209500 -4.021115e-02 0.209550 -4.021098e-02 0.209600 -4.021082e-02 0.209650 -4.021065e-02 0.209700 -4.021049e-02 0.209750 -4.021032e-02 0.209800 -4.021016e-02 0.209850 -4.021000e-02 0.209900 -4.020983e-02 0.209950 -4.020967e-02 0.210000 -4.020951e-02 0.210050 -4.020934e-02 0.210100 -4.020918e-02 0.210150 -4.020902e-02 0.210200 -4.020886e-02 0.210250 -4.020869e-02 0.210300 -4.020853e-02 0.210350 -4.020837e-02 0.210400 -4.020821e-02 0.210450 -4.020805e-02 0.210500 -4.020789e-02 0.210550 -4.020772e-02 0.210600 -4.020756e-02 0.210650 -4.020740e-02 0.210700 -4.020724e-02 0.210750 -4.020708e-02 0.210800 -4.020692e-02 0.210850 -4.020676e-02 0.210900 -4.020660e-02 0.210950 -4.020644e-02 0.211000 -4.020628e-02 0.211050 -4.020613e-02 0.211100 -4.020597e-02 0.211150 -4.020581e-02 0.211200 -4.020565e-02 0.211250 -4.020549e-02 0.211300 -4.020533e-02 0.211350 -4.020518e-02 0.211400 -4.020502e-02 0.211450 -4.020486e-02 0.211500 -4.020470e-02 0.211550 -4.020455e-02 0.211600 -4.020439e-02 0.211650 -4.020423e-02 0.211700 -4.020408e-02 0.211750 -4.020392e-02 0.211800 -4.020376e-02 0.211850 -4.020361e-02 0.211900 -4.020345e-02 0.211950 -4.020330e-02 0.212000 -4.020314e-02 0.212050 -4.020299e-02 0.212100 -4.020283e-02 0.212150 -4.020268e-02 0.212200 -4.020252e-02 0.212250 -4.020237e-02 0.212300 -4.020221e-02 0.212350 -4.020206e-02 0.212400 -4.020191e-02 0.212450 -4.020175e-02 0.212500 -4.020160e-02 0.212550 -4.020145e-02 0.212600 -4.020129e-02 0.212650 -4.020114e-02 0.212700 -4.020099e-02 0.212750 -4.020084e-02 0.212800 -4.020068e-02 0.212850 -4.020053e-02 0.212900 -4.020038e-02 0.212950 -4.020023e-02 0.213000 -4.020008e-02 0.213050 -4.019993e-02 0.213100 -4.019977e-02 0.213150 -4.019962e-02 0.213200 -4.019947e-02 0.213250 -4.019932e-02 0.213300 -4.019917e-02 0.213350 -4.019902e-02 0.213400 -4.019887e-02 0.213450 -4.019872e-02 0.213500 -4.019857e-02 0.213550 -4.019842e-02 0.213600 -4.019827e-02 0.213650 -4.019813e-02 0.213700 -4.019798e-02 0.213750 -4.019783e-02 0.213800 -4.019768e-02 0.213850 -4.019753e-02 0.213900 -4.019738e-02 0.213950 -4.019724e-02 0.214000 -4.019709e-02 0.214050 -4.019694e-02 0.214100 -4.019679e-02 0.214150 -4.019665e-02 0.214200 -4.019650e-02 0.214250 -4.019635e-02 0.214300 -4.019621e-02 0.214350 -4.019606e-02 0.214400 -4.019591e-02 0.214450 -4.019577e-02 0.214500 -4.019562e-02 0.214550 -4.019548e-02 0.214600 -4.019533e-02 0.214650 -4.019518e-02 0.214700 -4.019504e-02 0.214750 -4.019489e-02 0.214800 -4.019475e-02 0.214850 -4.019461e-02 0.214900 -4.019446e-02 0.214950 -4.019432e-02 0.215000 -4.019417e-02 0.215050 -4.019403e-02 0.215100 -4.019389e-02 0.215150 -4.019374e-02 0.215200 -4.019360e-02 0.215250 -4.019346e-02 0.215300 -4.019331e-02 0.215350 -4.019317e-02 0.215400 -4.019303e-02 0.215450 -4.019288e-02 0.215500 -4.019274e-02 0.215550 -4.019260e-02 0.215600 -4.019246e-02 0.215650 -4.019232e-02 0.215700 -4.019217e-02 0.215750 -4.019203e-02 0.215800 -4.019189e-02 0.215850 -4.019175e-02 0.215900 -4.019161e-02 0.215950 -4.019147e-02 0.216000 -4.019133e-02 0.216050 -4.019119e-02 0.216100 -4.019105e-02 0.216150 -4.019091e-02 0.216200 -4.019077e-02 0.216250 -4.019063e-02 0.216300 -4.019049e-02 0.216350 -4.019035e-02 0.216400 -4.019021e-02 0.216450 -4.019007e-02 0.216500 -4.018993e-02 0.216550 -4.018980e-02 0.216600 -4.018966e-02 0.216650 -4.018952e-02 0.216700 -4.018938e-02 0.216750 -4.018924e-02 0.216800 -4.018911e-02 0.216850 -4.018897e-02 0.216900 -4.018883e-02 0.216950 -4.018869e-02 0.217000 -4.018856e-02 0.217050 -4.018842e-02 0.217100 -4.018828e-02 0.217150 -4.018815e-02 0.217200 -4.018801e-02 0.217250 -4.018787e-02 0.217300 -4.018774e-02 0.217350 -4.018760e-02 0.217400 -4.018747e-02 0.217450 -4.018733e-02 0.217500 -4.018720e-02 0.217550 -4.018706e-02 0.217600 -4.018693e-02 0.217650 -4.018679e-02 0.217700 -4.018666e-02 0.217750 -4.018652e-02 0.217800 -4.018639e-02 0.217850 -4.018625e-02 0.217900 -4.018612e-02 0.217950 -4.018599e-02 0.218000 -4.018585e-02 0.218050 -4.018572e-02 0.218100 -4.018558e-02 0.218150 -4.018545e-02 0.218200 -4.018532e-02 0.218250 -4.018519e-02 0.218300 -4.018505e-02 0.218350 -4.018492e-02 0.218400 -4.018479e-02 0.218450 -4.018466e-02 0.218500 -4.018452e-02 0.218550 -4.018439e-02 0.218600 -4.018426e-02 0.218650 -4.018413e-02 0.218700 -4.018400e-02 0.218750 -4.018387e-02 0.218800 -4.018374e-02 0.218850 -4.018361e-02 0.218900 -4.018347e-02 0.218950 -4.018334e-02 0.219000 -4.018321e-02 0.219050 -4.018308e-02 0.219100 -4.018295e-02 0.219150 -4.018282e-02 0.219200 -4.018269e-02 0.219250 -4.018256e-02 0.219300 -4.018243e-02 0.219350 -4.018231e-02 0.219400 -4.018218e-02 0.219450 -4.018205e-02 0.219500 -4.018192e-02 0.219550 -4.018179e-02 0.219600 -4.018166e-02 0.219650 -4.018153e-02 0.219700 -4.018141e-02 0.219750 -4.018128e-02 0.219800 -4.018115e-02 0.219850 -4.018102e-02 0.219900 -4.018090e-02 0.219950 -4.018077e-02 0.220000 -4.018064e-02 0.220050 -4.018051e-02 0.220100 -4.018039e-02 0.220150 -4.018026e-02 0.220200 -4.018013e-02 0.220250 -4.018001e-02 0.220300 -4.017988e-02 0.220350 -4.017976e-02 0.220400 -4.017963e-02 0.220450 -4.017950e-02 0.220500 -4.017938e-02 0.220550 -4.017925e-02 0.220600 -4.017913e-02 0.220650 -4.017900e-02 0.220700 -4.017888e-02 0.220750 -4.017875e-02 0.220800 -4.017863e-02 0.220850 -4.017850e-02 0.220900 -4.017838e-02 0.220950 -4.017826e-02 0.221000 -4.017813e-02 0.221050 -4.017801e-02 0.221100 -4.017788e-02 0.221150 -4.017776e-02 0.221200 -4.017764e-02 0.221250 -4.017751e-02 0.221300 -4.017739e-02 0.221350 -4.017727e-02 0.221400 -4.017715e-02 0.221450 -4.017702e-02 0.221500 -4.017690e-02 0.221550 -4.017678e-02 0.221600 -4.017666e-02 0.221650 -4.017653e-02 0.221700 -4.017641e-02 0.221750 -4.017629e-02 0.221800 -4.017617e-02 0.221850 -4.017605e-02 0.221900 -4.017593e-02 0.221950 -4.017581e-02 0.222000 -4.017568e-02 0.222050 -4.017556e-02 0.222100 -4.017544e-02 0.222150 -4.017532e-02 0.222200 -4.017520e-02 0.222250 -4.017508e-02 0.222300 -4.017496e-02 0.222350 -4.017484e-02 0.222400 -4.017472e-02 0.222450 -4.017460e-02 0.222500 -4.017448e-02 0.222550 -4.017436e-02 0.222600 -4.017424e-02 0.222650 -4.017413e-02 0.222700 -4.017401e-02 0.222750 -4.017389e-02 0.222800 -4.017377e-02 0.222850 -4.017365e-02 0.222900 -4.017353e-02 0.222950 -4.017342e-02 0.223000 -4.017330e-02 0.223050 -4.017318e-02 0.223100 -4.017306e-02 0.223150 -4.017294e-02 0.223200 -4.017283e-02 0.223250 -4.017271e-02 0.223300 -4.017259e-02 0.223350 -4.017248e-02 0.223400 -4.017236e-02 0.223450 -4.017224e-02 0.223500 -4.017213e-02 0.223550 -4.017201e-02 0.223600 -4.017189e-02 0.223650 -4.017178e-02 0.223700 -4.017166e-02 0.223750 -4.017155e-02 0.223800 -4.017143e-02 0.223850 -4.017131e-02 0.223900 -4.017120e-02 0.223950 -4.017108e-02 0.224000 -4.017097e-02 0.224050 -4.017085e-02 0.224100 -4.017074e-02 0.224150 -4.017063e-02 0.224200 -4.017051e-02 0.224250 -4.017040e-02 0.224300 -4.017028e-02 0.224350 -4.017017e-02 0.224400 -4.017005e-02 0.224450 -4.016994e-02 0.224500 -4.016983e-02 0.224550 -4.016971e-02 0.224600 -4.016960e-02 0.224650 -4.016949e-02 0.224700 -4.016937e-02 0.224750 -4.016926e-02 0.224800 -4.016915e-02 0.224850 -4.016904e-02 0.224900 -4.016892e-02 0.224950 -4.016881e-02 0.225000 -4.016870e-02 0.225050 -4.016859e-02 0.225100 -4.016848e-02 0.225150 -4.016836e-02 0.225200 -4.016825e-02 0.225250 -4.016814e-02 0.225300 -4.016803e-02 0.225350 -4.016792e-02 0.225400 -4.016781e-02 0.225450 -4.016770e-02 0.225500 -4.016758e-02 0.225550 -4.016747e-02 0.225600 -4.016736e-02 0.225650 -4.016725e-02 0.225700 -4.016714e-02 0.225750 -4.016703e-02 0.225800 -4.016692e-02 0.225850 -4.016681e-02 0.225900 -4.016670e-02 0.225950 -4.016659e-02 0.226000 -4.016648e-02 0.226050 -4.016638e-02 0.226100 -4.016627e-02 0.226150 -4.016616e-02 0.226200 -4.016605e-02 0.226250 -4.016594e-02 0.226300 -4.016583e-02 0.226350 -4.016572e-02 0.226400 -4.016561e-02 0.226450 -4.016551e-02 0.226500 -4.016540e-02 0.226550 -4.016529e-02 0.226600 -4.016518e-02 0.226650 -4.016507e-02 0.226700 -4.016497e-02 0.226750 -4.016486e-02 0.226800 -4.016475e-02 0.226850 -4.016465e-02 0.226900 -4.016454e-02 0.226950 -4.016443e-02 0.227000 -4.016432e-02 0.227050 -4.016422e-02 0.227100 -4.016411e-02 0.227150 -4.016401e-02 0.227200 -4.016390e-02 0.227250 -4.016379e-02 0.227300 -4.016369e-02 0.227350 -4.016358e-02 0.227400 -4.016348e-02 0.227450 -4.016337e-02 0.227500 -4.016327e-02 0.227550 -4.016316e-02 0.227600 -4.016305e-02 0.227650 -4.016295e-02 0.227700 -4.016284e-02 0.227750 -4.016274e-02 0.227800 -4.016264e-02 0.227850 -4.016253e-02 0.227900 -4.016243e-02 0.227950 -4.016232e-02 0.228000 -4.016222e-02 0.228050 -4.016211e-02 0.228100 -4.016201e-02 0.228150 -4.016191e-02 0.228200 -4.016180e-02 0.228250 -4.016170e-02 0.228300 -4.016160e-02 0.228350 -4.016149e-02 0.228400 -4.016139e-02 0.228450 -4.016129e-02 0.228500 -4.016118e-02 0.228550 -4.016108e-02 0.228600 -4.016098e-02 0.228650 -4.016088e-02 0.228700 -4.016077e-02 0.228750 -4.016067e-02 0.228800 -4.016057e-02 0.228850 -4.016047e-02 0.228900 -4.016037e-02 0.228950 -4.016027e-02 0.229000 -4.016016e-02 0.229050 -4.016006e-02 0.229100 -4.015996e-02 0.229150 -4.015986e-02 0.229200 -4.015976e-02 0.229250 -4.015966e-02 0.229300 -4.015956e-02 0.229350 -4.015946e-02 0.229400 -4.015936e-02 0.229450 -4.015926e-02 0.229500 -4.015916e-02 0.229550 -4.015906e-02 0.229600 -4.015896e-02 0.229650 -4.015886e-02 0.229700 -4.015876e-02 0.229750 -4.015866e-02 0.229800 -4.015856e-02 0.229850 -4.015846e-02 0.229900 -4.015836e-02 0.229950 -4.015826e-02 0.230000 -4.015816e-02 0.230050 -4.015806e-02 0.230100 -4.015796e-02 0.230150 -4.015786e-02 0.230200 -4.015777e-02 0.230250 -4.015767e-02 0.230300 -4.015757e-02 0.230350 -4.015747e-02 0.230400 -4.015737e-02 0.230450 -4.015727e-02 0.230500 -4.015718e-02 0.230550 -4.015708e-02 0.230600 -4.015698e-02 0.230650 -4.015688e-02 0.230700 -4.015679e-02 0.230750 -4.015669e-02 0.230800 -4.015659e-02 0.230850 -4.015650e-02 0.230900 -4.015640e-02 0.230950 -4.015630e-02 0.231000 -4.015621e-02 0.231050 -4.015611e-02 0.231100 -4.015601e-02 0.231150 -4.015592e-02 0.231200 -4.015582e-02 0.231250 -4.015573e-02 0.231300 -4.015563e-02 0.231350 -4.015553e-02 0.231400 -4.015544e-02 0.231450 -4.015534e-02 0.231500 -4.015525e-02 0.231550 -4.015515e-02 0.231600 -4.015506e-02 0.231650 -4.015496e-02 0.231700 -4.015487e-02 0.231750 -4.015477e-02 0.231800 -4.015468e-02 0.231850 -4.015458e-02 0.231900 -4.015449e-02 0.231950 -4.015439e-02 0.232000 -4.015430e-02 0.232050 -4.015421e-02 0.232100 -4.015411e-02 0.232150 -4.015402e-02 0.232200 -4.015392e-02 0.232250 -4.015383e-02 0.232300 -4.015374e-02 0.232350 -4.015364e-02 0.232400 -4.015355e-02 0.232450 -4.015346e-02 0.232500 -4.015336e-02 0.232550 -4.015327e-02 0.232600 -4.015318e-02 0.232650 -4.015309e-02 0.232700 -4.015299e-02 0.232750 -4.015290e-02 0.232800 -4.015281e-02 0.232850 -4.015272e-02 0.232900 -4.015263e-02 0.232950 -4.015253e-02 0.233000 -4.015244e-02 0.233050 -4.015235e-02 0.233100 -4.015226e-02 0.233150 -4.015217e-02 0.233200 -4.015208e-02 0.233250 -4.015198e-02 0.233300 -4.015189e-02 0.233350 -4.015180e-02 0.233400 -4.015171e-02 0.233450 -4.015162e-02 0.233500 -4.015153e-02 0.233550 -4.015144e-02 0.233600 -4.015135e-02 0.233650 -4.015126e-02 0.233700 -4.015117e-02 0.233750 -4.015108e-02 0.233800 -4.015099e-02 0.233850 -4.015090e-02 0.233900 -4.015081e-02 0.233950 -4.015072e-02 0.234000 -4.015063e-02 0.234050 -4.015054e-02 0.234100 -4.015045e-02 0.234150 -4.015036e-02 0.234200 -4.015027e-02 0.234250 -4.015018e-02 0.234300 -4.015009e-02 0.234350 -4.015000e-02 0.234400 -4.014992e-02 0.234450 -4.014983e-02 0.234500 -4.014974e-02 0.234550 -4.014965e-02 0.234600 -4.014956e-02 0.234650 -4.014947e-02 0.234700 -4.014939e-02 0.234750 -4.014930e-02 0.234800 -4.014921e-02 0.234850 -4.014912e-02 0.234900 -4.014903e-02 0.234950 -4.014895e-02 0.235000 -4.014886e-02 0.235050 -4.014877e-02 0.235100 -4.014869e-02 0.235150 -4.014860e-02 0.235200 -4.014851e-02 0.235250 -4.014842e-02 0.235300 -4.014834e-02 0.235350 -4.014825e-02 0.235400 -4.014817e-02 0.235450 -4.014808e-02 0.235500 -4.014799e-02 0.235550 -4.014791e-02 0.235600 -4.014782e-02 0.235650 -4.014773e-02 0.235700 -4.014765e-02 0.235750 -4.014756e-02 0.235800 -4.014748e-02 0.235850 -4.014739e-02 0.235900 -4.014731e-02 0.235950 -4.014722e-02 0.236000 -4.014714e-02 0.236050 -4.014705e-02 0.236100 -4.014697e-02 0.236150 -4.014688e-02 0.236200 -4.014680e-02 0.236250 -4.014671e-02 0.236300 -4.014663e-02 0.236350 -4.014654e-02 0.236400 -4.014646e-02 0.236450 -4.014637e-02 0.236500 -4.014629e-02 0.236550 -4.014621e-02 0.236600 -4.014612e-02 0.236650 -4.014604e-02 0.236700 -4.014595e-02 0.236750 -4.014587e-02 0.236800 -4.014579e-02 0.236850 -4.014570e-02 0.236900 -4.014562e-02 0.236950 -4.014554e-02 0.237000 -4.014545e-02 0.237050 -4.014537e-02 0.237100 -4.014529e-02 0.237150 -4.014520e-02 0.237200 -4.014512e-02 0.237250 -4.014504e-02 0.237300 -4.014496e-02 0.237350 -4.014487e-02 0.237400 -4.014479e-02 0.237450 -4.014471e-02 0.237500 -4.014463e-02 0.237550 -4.014455e-02 0.237600 -4.014446e-02 0.237650 -4.014438e-02 0.237700 -4.014430e-02 0.237750 -4.014422e-02 0.237800 -4.014414e-02 0.237850 -4.014406e-02 0.237900 -4.014398e-02 0.237950 -4.014389e-02 0.238000 -4.014381e-02 0.238050 -4.014373e-02 0.238100 -4.014365e-02 0.238150 -4.014357e-02 0.238200 -4.014349e-02 0.238250 -4.014341e-02 0.238300 -4.014333e-02 0.238350 -4.014325e-02 0.238400 -4.014317e-02 0.238450 -4.014309e-02 0.238500 -4.014301e-02 0.238550 -4.014293e-02 0.238600 -4.014285e-02 0.238650 -4.014277e-02 0.238700 -4.014269e-02 0.238750 -4.014261e-02 0.238800 -4.014253e-02 0.238850 -4.014245e-02 0.238900 -4.014237e-02 0.238950 -4.014229e-02 0.239000 -4.014221e-02 0.239050 -4.014213e-02 0.239100 -4.014206e-02 0.239150 -4.014198e-02 0.239200 -4.014190e-02 0.239250 -4.014182e-02 0.239300 -4.014174e-02 0.239350 -4.014166e-02 0.239400 -4.014158e-02 0.239450 -4.014151e-02 0.239500 -4.014143e-02 0.239550 -4.014135e-02 0.239600 -4.014127e-02 0.239650 -4.014119e-02 0.239700 -4.014112e-02 0.239750 -4.014104e-02 0.239800 -4.014096e-02 0.239850 -4.014088e-02 0.239900 -4.014081e-02 0.239950 -4.014073e-02 0.240000 -4.014065e-02 0.240050 -4.014058e-02 0.240100 -4.014050e-02 0.240150 -4.014042e-02 0.240200 -4.014035e-02 0.240250 -4.014027e-02 0.240300 -4.014019e-02 0.240350 -4.014012e-02 0.240400 -4.014004e-02 0.240450 -4.013996e-02 0.240500 -4.013989e-02 0.240550 -4.013981e-02 0.240600 -4.013973e-02 0.240650 -4.013966e-02 0.240700 -4.013958e-02 0.240750 -4.013951e-02 0.240800 -4.013943e-02 0.240850 -4.013936e-02 0.240900 -4.013928e-02 0.240950 -4.013921e-02 0.241000 -4.013913e-02 0.241050 -4.013906e-02 0.241100 -4.013898e-02 0.241150 -4.013891e-02 0.241200 -4.013883e-02 0.241250 -4.013876e-02 0.241300 -4.013868e-02 0.241350 -4.013861e-02 0.241400 -4.013853e-02 0.241450 -4.013846e-02 0.241500 -4.013838e-02 0.241550 -4.013831e-02 0.241600 -4.013824e-02 0.241650 -4.013816e-02 0.241700 -4.013809e-02 0.241750 -4.013801e-02 0.241800 -4.013794e-02 0.241850 -4.013787e-02 0.241900 -4.013779e-02 0.241950 -4.013772e-02 0.242000 -4.013765e-02 0.242050 -4.013757e-02 0.242100 -4.013750e-02 0.242150 -4.013743e-02 0.242200 -4.013735e-02 0.242250 -4.013728e-02 0.242300 -4.013721e-02 0.242350 -4.013714e-02 0.242400 -4.013706e-02 0.242450 -4.013699e-02 0.242500 -4.013692e-02 0.242550 -4.013685e-02 0.242600 -4.013677e-02 0.242650 -4.013670e-02 0.242700 -4.013663e-02 0.242750 -4.013656e-02 0.242800 -4.013649e-02 0.242850 -4.013641e-02 0.242900 -4.013634e-02 0.242950 -4.013627e-02 0.243000 -4.013620e-02 0.243050 -4.013613e-02 0.243100 -4.013606e-02 0.243150 -4.013598e-02 0.243200 -4.013591e-02 0.243250 -4.013584e-02 0.243300 -4.013577e-02 0.243350 -4.013570e-02 0.243400 -4.013563e-02 0.243450 -4.013556e-02 0.243500 -4.013549e-02 0.243550 -4.013542e-02 0.243600 -4.013535e-02 0.243650 -4.013528e-02 0.243700 -4.013521e-02 0.243750 -4.013514e-02 0.243800 -4.013507e-02 0.243850 -4.013500e-02 0.243900 -4.013493e-02 0.243950 -4.013486e-02 0.244000 -4.013479e-02 0.244050 -4.013472e-02 0.244100 -4.013465e-02 0.244150 -4.013458e-02 0.244200 -4.013451e-02 0.244250 -4.013444e-02 0.244300 -4.013437e-02 0.244350 -4.013430e-02 0.244400 -4.013423e-02 0.244450 -4.013416e-02 0.244500 -4.013409e-02 0.244550 -4.013402e-02 0.244600 -4.013396e-02 0.244650 -4.013389e-02 0.244700 -4.013382e-02 0.244750 -4.013375e-02 0.244800 -4.013368e-02 0.244850 -4.013361e-02 0.244900 -4.013355e-02 0.244950 -4.013348e-02 0.245000 -4.013341e-02 0.245050 -4.013334e-02 0.245100 -4.013327e-02 0.245150 -4.013321e-02 0.245200 -4.013314e-02 0.245250 -4.013307e-02 0.245300 -4.013300e-02 0.245350 -4.013294e-02 0.245400 -4.013287e-02 0.245450 -4.013280e-02 0.245500 -4.013273e-02 0.245550 -4.013267e-02 0.245600 -4.013260e-02 0.245650 -4.013253e-02 0.245700 -4.013247e-02 0.245750 -4.013240e-02 0.245800 -4.013233e-02 0.245850 -4.013227e-02 0.245900 -4.013220e-02 0.245950 -4.013213e-02 0.246000 -4.013207e-02 0.246050 -4.013200e-02 0.246100 -4.013193e-02 0.246150 -4.013187e-02 0.246200 -4.013180e-02 0.246250 -4.013174e-02 0.246300 -4.013167e-02 0.246350 -4.013160e-02 0.246400 -4.013154e-02 0.246450 -4.013147e-02 0.246500 -4.013141e-02 0.246550 -4.013134e-02 0.246600 -4.013128e-02 0.246650 -4.013121e-02 0.246700 -4.013115e-02 0.246750 -4.013108e-02 0.246800 -4.013102e-02 0.246850 -4.013095e-02 0.246900 -4.013089e-02 0.246950 -4.013082e-02 0.247000 -4.013076e-02 0.247050 -4.013069e-02 0.247100 -4.013063e-02 0.247150 -4.013056e-02 0.247200 -4.013050e-02 0.247250 -4.013043e-02 0.247300 -4.013037e-02 0.247350 -4.013031e-02 0.247400 -4.013024e-02 0.247450 -4.013018e-02 0.247500 -4.013011e-02 0.247550 -4.013005e-02 0.247600 -4.012999e-02 0.247650 -4.012992e-02 0.247700 -4.012986e-02 0.247750 -4.012980e-02 0.247800 -4.012973e-02 0.247850 -4.012967e-02 0.247900 -4.012961e-02 0.247950 -4.012954e-02 0.248000 -4.012948e-02 0.248050 -4.012942e-02 0.248100 -4.012935e-02 0.248150 -4.012929e-02 0.248200 -4.012923e-02 0.248250 -4.012916e-02 0.248300 -4.012910e-02 0.248350 -4.012904e-02 0.248400 -4.012898e-02 0.248450 -4.012891e-02 0.248500 -4.012885e-02 0.248550 -4.012879e-02 0.248600 -4.012873e-02 0.248650 -4.012867e-02 0.248700 -4.012860e-02 0.248750 -4.012854e-02 0.248800 -4.012848e-02 0.248850 -4.012842e-02 0.248900 -4.012836e-02 0.248950 -4.012829e-02 0.249000 -4.012823e-02 0.249050 -4.012817e-02 0.249100 -4.012811e-02 0.249150 -4.012805e-02 0.249200 -4.012799e-02 0.249250 -4.012793e-02 0.249300 -4.012787e-02 0.249350 -4.012780e-02 0.249400 -4.012774e-02 0.249450 -4.012768e-02 0.249500 -4.012762e-02 0.249550 -4.012756e-02 0.249600 -4.012750e-02 0.249650 -4.012744e-02 0.249700 -4.012738e-02 0.249750 -4.012732e-02 0.249800 -4.012726e-02 0.249850 -4.012720e-02 0.249900 -4.012714e-02 0.249950 -4.012708e-02 0.250000 -4.012702e-02 brian2-2.5.4/brian2/tests/rallpack_data/ref_branch.x000066400000000000000000003405171445201106100222660ustar00rootroot000000000000000.000000 -6.500000e-02 0.000050 -6.499076e-02 0.000100 -6.496373e-02 0.000150 -6.493333e-02 0.000200 -6.490246e-02 0.000250 -6.487156e-02 0.000300 -6.484069e-02 0.000350 -6.480986e-02 0.000400 -6.477907e-02 0.000450 -6.474831e-02 0.000500 -6.471759e-02 0.000550 -6.468691e-02 0.000600 -6.465627e-02 0.000650 -6.462567e-02 0.000700 -6.459510e-02 0.000750 -6.456458e-02 0.000800 -6.453409e-02 0.000850 -6.450364e-02 0.000900 -6.447323e-02 0.000950 -6.444285e-02 0.001000 -6.441251e-02 0.001050 -6.438222e-02 0.001100 -6.435195e-02 0.001150 -6.432173e-02 0.001200 -6.429155e-02 0.001250 -6.426140e-02 0.001300 -6.423129e-02 0.001350 -6.420122e-02 0.001400 -6.417118e-02 0.001450 -6.414119e-02 0.001500 -6.411123e-02 0.001550 -6.408130e-02 0.001600 -6.405142e-02 0.001650 -6.402157e-02 0.001700 -6.399176e-02 0.001750 -6.396199e-02 0.001800 -6.393225e-02 0.001850 -6.390255e-02 0.001900 -6.387289e-02 0.001950 -6.384327e-02 0.002000 -6.381368e-02 0.002050 -6.378413e-02 0.002100 -6.375462e-02 0.002150 -6.372514e-02 0.002200 -6.369570e-02 0.002250 -6.366630e-02 0.002300 -6.363693e-02 0.002350 -6.360760e-02 0.002400 -6.357831e-02 0.002450 -6.354905e-02 0.002500 -6.351983e-02 0.002550 -6.349065e-02 0.002600 -6.346150e-02 0.002650 -6.343239e-02 0.002700 -6.340332e-02 0.002750 -6.337428e-02 0.002800 -6.334528e-02 0.002850 -6.331631e-02 0.002900 -6.328738e-02 0.002950 -6.325849e-02 0.003000 -6.322963e-02 0.003050 -6.320081e-02 0.003100 -6.317203e-02 0.003150 -6.314328e-02 0.003200 -6.311457e-02 0.003250 -6.308589e-02 0.003300 -6.305725e-02 0.003350 -6.302864e-02 0.003400 -6.300007e-02 0.003450 -6.297154e-02 0.003500 -6.294304e-02 0.003550 -6.291458e-02 0.003600 -6.288615e-02 0.003650 -6.285776e-02 0.003700 -6.282940e-02 0.003750 -6.280108e-02 0.003800 -6.277280e-02 0.003850 -6.274455e-02 0.003900 -6.271633e-02 0.003950 -6.268815e-02 0.004000 -6.266001e-02 0.004050 -6.263190e-02 0.004100 -6.260382e-02 0.004150 -6.257578e-02 0.004200 -6.254778e-02 0.004250 -6.251981e-02 0.004300 -6.249188e-02 0.004350 -6.246398e-02 0.004400 -6.243611e-02 0.004450 -6.240828e-02 0.004500 -6.238049e-02 0.004550 -6.235273e-02 0.004600 -6.232500e-02 0.004650 -6.229731e-02 0.004700 -6.226966e-02 0.004750 -6.224203e-02 0.004800 -6.221445e-02 0.004850 -6.218690e-02 0.004900 -6.215938e-02 0.004950 -6.213189e-02 0.005000 -6.210444e-02 0.005050 -6.207703e-02 0.005100 -6.204965e-02 0.005150 -6.202230e-02 0.005200 -6.199499e-02 0.005250 -6.196771e-02 0.005300 -6.194047e-02 0.005350 -6.191325e-02 0.005400 -6.188608e-02 0.005450 -6.185894e-02 0.005500 -6.183183e-02 0.005550 -6.180475e-02 0.005600 -6.177771e-02 0.005650 -6.175071e-02 0.005700 -6.172373e-02 0.005750 -6.169679e-02 0.005800 -6.166989e-02 0.005850 -6.164301e-02 0.005900 -6.161617e-02 0.005950 -6.158937e-02 0.006000 -6.156260e-02 0.006050 -6.153586e-02 0.006100 -6.150915e-02 0.006150 -6.148248e-02 0.006200 -6.145584e-02 0.006250 -6.142924e-02 0.006300 -6.140267e-02 0.006350 -6.137613e-02 0.006400 -6.134962e-02 0.006450 -6.132315e-02 0.006500 -6.129671e-02 0.006550 -6.127031e-02 0.006600 -6.124393e-02 0.006650 -6.121759e-02 0.006700 -6.119129e-02 0.006750 -6.116501e-02 0.006800 -6.113877e-02 0.006850 -6.111256e-02 0.006900 -6.108638e-02 0.006950 -6.106024e-02 0.007000 -6.103413e-02 0.007050 -6.100805e-02 0.007100 -6.098201e-02 0.007150 -6.095599e-02 0.007200 -6.093001e-02 0.007250 -6.090406e-02 0.007300 -6.087815e-02 0.007350 -6.085226e-02 0.007400 -6.082641e-02 0.007450 -6.080060e-02 0.007500 -6.077481e-02 0.007550 -6.074905e-02 0.007600 -6.072333e-02 0.007650 -6.069764e-02 0.007700 -6.067199e-02 0.007750 -6.064636e-02 0.007800 -6.062077e-02 0.007850 -6.059520e-02 0.007900 -6.056967e-02 0.007950 -6.054418e-02 0.008000 -6.051871e-02 0.008050 -6.049328e-02 0.008100 -6.046787e-02 0.008150 -6.044250e-02 0.008200 -6.041716e-02 0.008250 -6.039186e-02 0.008300 -6.036658e-02 0.008350 -6.034134e-02 0.008400 -6.031612e-02 0.008450 -6.029094e-02 0.008500 -6.026579e-02 0.008550 -6.024067e-02 0.008600 -6.021559e-02 0.008650 -6.019053e-02 0.008700 -6.016551e-02 0.008750 -6.014051e-02 0.008800 -6.011555e-02 0.008850 -6.009062e-02 0.008900 -6.006572e-02 0.008950 -6.004085e-02 0.009000 -6.001602e-02 0.009050 -5.999121e-02 0.009100 -5.996643e-02 0.009150 -5.994169e-02 0.009200 -5.991698e-02 0.009250 -5.989229e-02 0.009300 -5.986764e-02 0.009350 -5.984302e-02 0.009400 -5.981843e-02 0.009450 -5.979387e-02 0.009500 -5.976934e-02 0.009550 -5.974484e-02 0.009600 -5.972038e-02 0.009650 -5.969594e-02 0.009700 -5.967153e-02 0.009750 -5.964716e-02 0.009800 -5.962281e-02 0.009850 -5.959850e-02 0.009900 -5.957421e-02 0.009950 -5.954996e-02 0.010000 -5.952573e-02 0.010050 -5.950154e-02 0.010100 -5.947738e-02 0.010150 -5.945324e-02 0.010200 -5.942914e-02 0.010250 -5.940507e-02 0.010300 -5.938102e-02 0.010350 -5.935701e-02 0.010400 -5.933303e-02 0.010450 -5.930907e-02 0.010500 -5.928515e-02 0.010550 -5.926126e-02 0.010600 -5.923739e-02 0.010650 -5.921356e-02 0.010700 -5.918976e-02 0.010750 -5.916598e-02 0.010800 -5.914224e-02 0.010850 -5.911852e-02 0.010900 -5.909484e-02 0.010950 -5.907118e-02 0.011000 -5.904755e-02 0.011050 -5.902396e-02 0.011100 -5.900039e-02 0.011150 -5.897685e-02 0.011200 -5.895335e-02 0.011250 -5.892987e-02 0.011300 -5.890642e-02 0.011350 -5.888300e-02 0.011400 -5.885961e-02 0.011450 -5.883624e-02 0.011500 -5.881291e-02 0.011550 -5.878961e-02 0.011600 -5.876633e-02 0.011650 -5.874309e-02 0.011700 -5.871987e-02 0.011750 -5.869669e-02 0.011800 -5.867353e-02 0.011850 -5.865040e-02 0.011900 -5.862730e-02 0.011950 -5.860423e-02 0.012000 -5.858118e-02 0.012050 -5.855817e-02 0.012100 -5.853519e-02 0.012150 -5.851223e-02 0.012200 -5.848930e-02 0.012250 -5.846640e-02 0.012300 -5.844353e-02 0.012350 -5.842069e-02 0.012400 -5.839788e-02 0.012450 -5.837509e-02 0.012500 -5.835233e-02 0.012550 -5.832961e-02 0.012600 -5.830691e-02 0.012650 -5.828424e-02 0.012700 -5.826159e-02 0.012750 -5.823898e-02 0.012800 -5.821639e-02 0.012850 -5.819383e-02 0.012900 -5.817130e-02 0.012950 -5.814880e-02 0.013000 -5.812633e-02 0.013050 -5.810388e-02 0.013100 -5.808146e-02 0.013150 -5.805907e-02 0.013200 -5.803671e-02 0.013250 -5.801438e-02 0.013300 -5.799207e-02 0.013350 -5.796979e-02 0.013400 -5.794755e-02 0.013450 -5.792532e-02 0.013500 -5.790313e-02 0.013550 -5.788096e-02 0.013600 -5.785882e-02 0.013650 -5.783671e-02 0.013700 -5.781463e-02 0.013750 -5.779257e-02 0.013800 -5.777054e-02 0.013850 -5.774854e-02 0.013900 -5.772657e-02 0.013950 -5.770462e-02 0.014000 -5.768270e-02 0.014050 -5.766081e-02 0.014100 -5.763895e-02 0.014150 -5.761711e-02 0.014200 -5.759530e-02 0.014250 -5.757352e-02 0.014300 -5.755176e-02 0.014350 -5.753003e-02 0.014400 -5.750833e-02 0.014450 -5.748666e-02 0.014500 -5.746501e-02 0.014550 -5.744339e-02 0.014600 -5.742180e-02 0.014650 -5.740024e-02 0.014700 -5.737870e-02 0.014750 -5.735718e-02 0.014800 -5.733570e-02 0.014850 -5.731424e-02 0.014900 -5.729281e-02 0.014950 -5.727141e-02 0.015000 -5.725003e-02 0.015050 -5.722868e-02 0.015100 -5.720735e-02 0.015150 -5.718606e-02 0.015200 -5.716478e-02 0.015250 -5.714354e-02 0.015300 -5.712232e-02 0.015350 -5.710113e-02 0.015400 -5.707997e-02 0.015450 -5.705883e-02 0.015500 -5.703771e-02 0.015550 -5.701663e-02 0.015600 -5.699557e-02 0.015650 -5.697454e-02 0.015700 -5.695353e-02 0.015750 -5.693255e-02 0.015800 -5.691159e-02 0.015850 -5.689067e-02 0.015900 -5.686976e-02 0.015950 -5.684889e-02 0.016000 -5.682804e-02 0.016050 -5.680721e-02 0.016100 -5.678642e-02 0.016150 -5.676565e-02 0.016200 -5.674490e-02 0.016250 -5.672418e-02 0.016300 -5.670348e-02 0.016350 -5.668282e-02 0.016400 -5.666217e-02 0.016450 -5.664156e-02 0.016500 -5.662097e-02 0.016550 -5.660040e-02 0.016600 -5.657986e-02 0.016650 -5.655935e-02 0.016700 -5.653886e-02 0.016750 -5.651840e-02 0.016800 -5.649796e-02 0.016850 -5.647755e-02 0.016900 -5.645716e-02 0.016950 -5.643680e-02 0.017000 -5.641647e-02 0.017050 -5.639616e-02 0.017100 -5.637587e-02 0.017150 -5.635561e-02 0.017200 -5.633538e-02 0.017250 -5.631517e-02 0.017300 -5.629499e-02 0.017350 -5.627483e-02 0.017400 -5.625470e-02 0.017450 -5.623459e-02 0.017500 -5.621451e-02 0.017550 -5.619445e-02 0.017600 -5.617442e-02 0.017650 -5.615441e-02 0.017700 -5.613443e-02 0.017750 -5.611447e-02 0.017800 -5.609454e-02 0.017850 -5.607463e-02 0.017900 -5.605475e-02 0.017950 -5.603489e-02 0.018000 -5.601506e-02 0.018050 -5.599525e-02 0.018100 -5.597547e-02 0.018150 -5.595571e-02 0.018200 -5.593597e-02 0.018250 -5.591626e-02 0.018300 -5.589658e-02 0.018350 -5.587692e-02 0.018400 -5.585728e-02 0.018450 -5.583767e-02 0.018500 -5.581808e-02 0.018550 -5.579852e-02 0.018600 -5.577899e-02 0.018650 -5.575947e-02 0.018700 -5.573998e-02 0.018750 -5.572052e-02 0.018800 -5.570108e-02 0.018850 -5.568166e-02 0.018900 -5.566227e-02 0.018950 -5.564290e-02 0.019000 -5.562356e-02 0.019050 -5.560424e-02 0.019100 -5.558495e-02 0.019150 -5.556567e-02 0.019200 -5.554643e-02 0.019250 -5.552720e-02 0.019300 -5.550800e-02 0.019350 -5.548883e-02 0.019400 -5.546968e-02 0.019450 -5.545055e-02 0.019500 -5.543145e-02 0.019550 -5.541237e-02 0.019600 -5.539331e-02 0.019650 -5.537428e-02 0.019700 -5.535528e-02 0.019750 -5.533629e-02 0.019800 -5.531733e-02 0.019850 -5.529840e-02 0.019900 -5.527948e-02 0.019950 -5.526059e-02 0.020000 -5.524173e-02 0.020050 -5.522288e-02 0.020100 -5.520407e-02 0.020150 -5.518527e-02 0.020200 -5.516650e-02 0.020250 -5.514775e-02 0.020300 -5.512903e-02 0.020350 -5.511032e-02 0.020400 -5.509165e-02 0.020450 -5.507299e-02 0.020500 -5.505436e-02 0.020550 -5.503575e-02 0.020600 -5.501717e-02 0.020650 -5.499860e-02 0.020700 -5.498007e-02 0.020750 -5.496155e-02 0.020800 -5.494306e-02 0.020850 -5.492459e-02 0.020900 -5.490614e-02 0.020950 -5.488772e-02 0.021000 -5.486932e-02 0.021050 -5.485094e-02 0.021100 -5.483259e-02 0.021150 -5.481426e-02 0.021200 -5.479595e-02 0.021250 -5.477767e-02 0.021300 -5.475940e-02 0.021350 -5.474116e-02 0.021400 -5.472295e-02 0.021450 -5.470475e-02 0.021500 -5.468658e-02 0.021550 -5.466843e-02 0.021600 -5.465031e-02 0.021650 -5.463220e-02 0.021700 -5.461412e-02 0.021750 -5.459606e-02 0.021800 -5.457803e-02 0.021850 -5.456002e-02 0.021900 -5.454202e-02 0.021950 -5.452406e-02 0.022000 -5.450611e-02 0.022050 -5.448819e-02 0.022100 -5.447029e-02 0.022150 -5.445241e-02 0.022200 -5.443455e-02 0.022250 -5.441672e-02 0.022300 -5.439891e-02 0.022350 -5.438112e-02 0.022400 -5.436335e-02 0.022450 -5.434561e-02 0.022500 -5.432788e-02 0.022550 -5.431018e-02 0.022600 -5.429250e-02 0.022650 -5.427485e-02 0.022700 -5.425721e-02 0.022750 -5.423960e-02 0.022800 -5.422201e-02 0.022850 -5.420444e-02 0.022900 -5.418690e-02 0.022950 -5.416937e-02 0.023000 -5.415187e-02 0.023050 -5.413439e-02 0.023100 -5.411693e-02 0.023150 -5.409949e-02 0.023200 -5.408208e-02 0.023250 -5.406468e-02 0.023300 -5.404731e-02 0.023350 -5.402996e-02 0.023400 -5.401263e-02 0.023450 -5.399533e-02 0.023500 -5.397804e-02 0.023550 -5.396078e-02 0.023600 -5.394353e-02 0.023650 -5.392631e-02 0.023700 -5.390912e-02 0.023750 -5.389194e-02 0.023800 -5.387478e-02 0.023850 -5.385765e-02 0.023900 -5.384053e-02 0.023950 -5.382344e-02 0.024000 -5.380637e-02 0.024050 -5.378932e-02 0.024100 -5.377230e-02 0.024150 -5.375529e-02 0.024200 -5.373830e-02 0.024250 -5.372134e-02 0.024300 -5.370440e-02 0.024350 -5.368748e-02 0.024400 -5.367057e-02 0.024450 -5.365369e-02 0.024500 -5.363684e-02 0.024550 -5.362000e-02 0.024600 -5.360318e-02 0.024650 -5.358639e-02 0.024700 -5.356961e-02 0.024750 -5.355286e-02 0.024800 -5.353613e-02 0.024850 -5.351942e-02 0.024900 -5.350272e-02 0.024950 -5.348605e-02 0.025000 -5.346941e-02 0.025050 -5.345278e-02 0.025100 -5.343617e-02 0.025150 -5.341958e-02 0.025200 -5.340302e-02 0.025250 -5.338647e-02 0.025300 -5.336995e-02 0.025350 -5.335344e-02 0.025400 -5.333696e-02 0.025450 -5.332050e-02 0.025500 -5.330406e-02 0.025550 -5.328763e-02 0.025600 -5.327123e-02 0.025650 -5.325485e-02 0.025700 -5.323849e-02 0.025750 -5.322215e-02 0.025800 -5.320583e-02 0.025850 -5.318953e-02 0.025900 -5.317326e-02 0.025950 -5.315700e-02 0.026000 -5.314076e-02 0.026050 -5.312454e-02 0.026100 -5.310835e-02 0.026150 -5.309217e-02 0.026200 -5.307601e-02 0.026250 -5.305987e-02 0.026300 -5.304376e-02 0.026350 -5.302766e-02 0.026400 -5.301158e-02 0.026450 -5.299553e-02 0.026500 -5.297949e-02 0.026550 -5.296348e-02 0.026600 -5.294748e-02 0.026650 -5.293150e-02 0.026700 -5.291555e-02 0.026750 -5.289961e-02 0.026800 -5.288369e-02 0.026850 -5.286780e-02 0.026900 -5.285192e-02 0.026950 -5.283606e-02 0.027000 -5.282023e-02 0.027050 -5.280441e-02 0.027100 -5.278861e-02 0.027150 -5.277284e-02 0.027200 -5.275708e-02 0.027250 -5.274134e-02 0.027300 -5.272562e-02 0.027350 -5.270992e-02 0.027400 -5.269424e-02 0.027450 -5.267858e-02 0.027500 -5.266294e-02 0.027550 -5.264732e-02 0.027600 -5.263172e-02 0.027650 -5.261614e-02 0.027700 -5.260058e-02 0.027750 -5.258503e-02 0.027800 -5.256951e-02 0.027850 -5.255401e-02 0.027900 -5.253852e-02 0.027950 -5.252306e-02 0.028000 -5.250761e-02 0.028050 -5.249218e-02 0.028100 -5.247678e-02 0.028150 -5.246139e-02 0.028200 -5.244602e-02 0.028250 -5.243067e-02 0.028300 -5.241534e-02 0.028350 -5.240003e-02 0.028400 -5.238473e-02 0.028450 -5.236946e-02 0.028500 -5.235421e-02 0.028550 -5.233897e-02 0.028600 -5.232376e-02 0.028650 -5.230856e-02 0.028700 -5.229338e-02 0.028750 -5.227822e-02 0.028800 -5.226308e-02 0.028850 -5.224796e-02 0.028900 -5.223286e-02 0.028950 -5.221778e-02 0.029000 -5.220271e-02 0.029050 -5.218766e-02 0.029100 -5.217264e-02 0.029150 -5.215763e-02 0.029200 -5.214264e-02 0.029250 -5.212767e-02 0.029300 -5.211272e-02 0.029350 -5.209778e-02 0.029400 -5.208287e-02 0.029450 -5.206797e-02 0.029500 -5.205310e-02 0.029550 -5.203824e-02 0.029600 -5.202340e-02 0.029650 -5.200857e-02 0.029700 -5.199377e-02 0.029750 -5.197899e-02 0.029800 -5.196422e-02 0.029850 -5.194947e-02 0.029900 -5.193474e-02 0.029950 -5.192003e-02 0.030000 -5.190534e-02 0.030050 -5.189066e-02 0.030100 -5.187601e-02 0.030150 -5.186137e-02 0.030200 -5.184675e-02 0.030250 -5.183215e-02 0.030300 -5.181757e-02 0.030350 -5.180300e-02 0.030400 -5.178846e-02 0.030450 -5.177393e-02 0.030500 -5.175942e-02 0.030550 -5.174493e-02 0.030600 -5.173045e-02 0.030650 -5.171600e-02 0.030700 -5.170156e-02 0.030750 -5.168714e-02 0.030800 -5.167274e-02 0.030850 -5.165835e-02 0.030900 -5.164399e-02 0.030950 -5.162964e-02 0.031000 -5.161531e-02 0.031050 -5.160100e-02 0.031100 -5.158670e-02 0.031150 -5.157243e-02 0.031200 -5.155817e-02 0.031250 -5.154393e-02 0.031300 -5.152970e-02 0.031350 -5.151550e-02 0.031400 -5.150131e-02 0.031450 -5.148714e-02 0.031500 -5.147299e-02 0.031550 -5.145886e-02 0.031600 -5.144474e-02 0.031650 -5.143064e-02 0.031700 -5.141656e-02 0.031750 -5.140250e-02 0.031800 -5.138845e-02 0.031850 -5.137442e-02 0.031900 -5.136041e-02 0.031950 -5.134642e-02 0.032000 -5.133244e-02 0.032050 -5.131848e-02 0.032100 -5.130454e-02 0.032150 -5.129062e-02 0.032200 -5.127671e-02 0.032250 -5.126282e-02 0.032300 -5.124895e-02 0.032350 -5.123510e-02 0.032400 -5.122126e-02 0.032450 -5.120744e-02 0.032500 -5.119364e-02 0.032550 -5.117985e-02 0.032600 -5.116608e-02 0.032650 -5.115233e-02 0.032700 -5.113860e-02 0.032750 -5.112488e-02 0.032800 -5.111118e-02 0.032850 -5.109750e-02 0.032900 -5.108384e-02 0.032950 -5.107019e-02 0.033000 -5.105656e-02 0.033050 -5.104294e-02 0.033100 -5.102935e-02 0.033150 -5.101577e-02 0.033200 -5.100220e-02 0.033250 -5.098866e-02 0.033300 -5.097513e-02 0.033350 -5.096161e-02 0.033400 -5.094812e-02 0.033450 -5.093464e-02 0.033500 -5.092118e-02 0.033550 -5.090773e-02 0.033600 -5.089431e-02 0.033650 -5.088090e-02 0.033700 -5.086750e-02 0.033750 -5.085412e-02 0.033800 -5.084076e-02 0.033850 -5.082742e-02 0.033900 -5.081409e-02 0.033950 -5.080078e-02 0.034000 -5.078748e-02 0.034050 -5.077421e-02 0.034100 -5.076094e-02 0.034150 -5.074770e-02 0.034200 -5.073447e-02 0.034250 -5.072126e-02 0.034300 -5.070806e-02 0.034350 -5.069489e-02 0.034400 -5.068172e-02 0.034450 -5.066858e-02 0.034500 -5.065545e-02 0.034550 -5.064234e-02 0.034600 -5.062924e-02 0.034650 -5.061616e-02 0.034700 -5.060310e-02 0.034750 -5.059005e-02 0.034800 -5.057702e-02 0.034850 -5.056400e-02 0.034900 -5.055100e-02 0.034950 -5.053802e-02 0.035000 -5.052505e-02 0.035050 -5.051210e-02 0.035100 -5.049917e-02 0.035150 -5.048625e-02 0.035200 -5.047335e-02 0.035250 -5.046047e-02 0.035300 -5.044760e-02 0.035350 -5.043474e-02 0.035400 -5.042191e-02 0.035450 -5.040909e-02 0.035500 -5.039628e-02 0.035550 -5.038349e-02 0.035600 -5.037072e-02 0.035650 -5.035796e-02 0.035700 -5.034522e-02 0.035750 -5.033249e-02 0.035800 -5.031978e-02 0.035850 -5.030709e-02 0.035900 -5.029441e-02 0.035950 -5.028175e-02 0.036000 -5.026910e-02 0.036050 -5.025647e-02 0.036100 -5.024386e-02 0.036150 -5.023126e-02 0.036200 -5.021868e-02 0.036250 -5.020611e-02 0.036300 -5.019356e-02 0.036350 -5.018102e-02 0.036400 -5.016850e-02 0.036450 -5.015600e-02 0.036500 -5.014351e-02 0.036550 -5.013104e-02 0.036600 -5.011858e-02 0.036650 -5.010614e-02 0.036700 -5.009371e-02 0.036750 -5.008130e-02 0.036800 -5.006890e-02 0.036850 -5.005652e-02 0.036900 -5.004416e-02 0.036950 -5.003181e-02 0.037000 -5.001947e-02 0.037050 -5.000715e-02 0.037100 -4.999485e-02 0.037150 -4.998256e-02 0.037200 -4.997029e-02 0.037250 -4.995804e-02 0.037300 -4.994579e-02 0.037350 -4.993357e-02 0.037400 -4.992136e-02 0.037450 -4.990916e-02 0.037500 -4.989698e-02 0.037550 -4.988481e-02 0.037600 -4.987266e-02 0.037650 -4.986053e-02 0.037700 -4.984841e-02 0.037750 -4.983630e-02 0.037800 -4.982421e-02 0.037850 -4.981214e-02 0.037900 -4.980008e-02 0.037950 -4.978804e-02 0.038000 -4.977601e-02 0.038050 -4.976399e-02 0.038100 -4.975199e-02 0.038150 -4.974001e-02 0.038200 -4.972804e-02 0.038250 -4.971609e-02 0.038300 -4.970415e-02 0.038350 -4.969222e-02 0.038400 -4.968031e-02 0.038450 -4.966842e-02 0.038500 -4.965654e-02 0.038550 -4.964467e-02 0.038600 -4.963282e-02 0.038650 -4.962099e-02 0.038700 -4.960916e-02 0.038750 -4.959736e-02 0.038800 -4.958557e-02 0.038850 -4.957379e-02 0.038900 -4.956203e-02 0.038950 -4.955028e-02 0.039000 -4.953855e-02 0.039050 -4.952683e-02 0.039100 -4.951513e-02 0.039150 -4.950344e-02 0.039200 -4.949177e-02 0.039250 -4.948011e-02 0.039300 -4.946846e-02 0.039350 -4.945683e-02 0.039400 -4.944522e-02 0.039450 -4.943362e-02 0.039500 -4.942203e-02 0.039550 -4.941046e-02 0.039600 -4.939890e-02 0.039650 -4.938736e-02 0.039700 -4.937583e-02 0.039750 -4.936431e-02 0.039800 -4.935281e-02 0.039850 -4.934133e-02 0.039900 -4.932986e-02 0.039950 -4.931840e-02 0.040000 -4.930696e-02 0.040050 -4.929553e-02 0.040100 -4.928411e-02 0.040150 -4.927271e-02 0.040200 -4.926133e-02 0.040250 -4.924996e-02 0.040300 -4.923860e-02 0.040350 -4.922726e-02 0.040400 -4.921593e-02 0.040450 -4.920461e-02 0.040500 -4.919331e-02 0.040550 -4.918203e-02 0.040600 -4.917076e-02 0.040650 -4.915950e-02 0.040700 -4.914825e-02 0.040750 -4.913702e-02 0.040800 -4.912581e-02 0.040850 -4.911460e-02 0.040900 -4.910342e-02 0.040950 -4.909224e-02 0.041000 -4.908108e-02 0.041050 -4.906994e-02 0.041100 -4.905880e-02 0.041150 -4.904769e-02 0.041200 -4.903658e-02 0.041250 -4.902549e-02 0.041300 -4.901441e-02 0.041350 -4.900335e-02 0.041400 -4.899230e-02 0.041450 -4.898127e-02 0.041500 -4.897025e-02 0.041550 -4.895924e-02 0.041600 -4.894824e-02 0.041650 -4.893726e-02 0.041700 -4.892630e-02 0.041750 -4.891534e-02 0.041800 -4.890441e-02 0.041850 -4.889348e-02 0.041900 -4.888257e-02 0.041950 -4.887167e-02 0.042000 -4.886078e-02 0.042050 -4.884991e-02 0.042100 -4.883906e-02 0.042150 -4.882821e-02 0.042200 -4.881738e-02 0.042250 -4.880656e-02 0.042300 -4.879576e-02 0.042350 -4.878497e-02 0.042400 -4.877420e-02 0.042450 -4.876343e-02 0.042500 -4.875268e-02 0.042550 -4.874195e-02 0.042600 -4.873122e-02 0.042650 -4.872052e-02 0.042700 -4.870982e-02 0.042750 -4.869914e-02 0.042800 -4.868847e-02 0.042850 -4.867781e-02 0.042900 -4.866717e-02 0.042950 -4.865654e-02 0.043000 -4.864593e-02 0.043050 -4.863532e-02 0.043100 -4.862473e-02 0.043150 -4.861416e-02 0.043200 -4.860359e-02 0.043250 -4.859304e-02 0.043300 -4.858251e-02 0.043350 -4.857198e-02 0.043400 -4.856147e-02 0.043450 -4.855098e-02 0.043500 -4.854049e-02 0.043550 -4.853002e-02 0.043600 -4.851956e-02 0.043650 -4.850912e-02 0.043700 -4.849869e-02 0.043750 -4.848827e-02 0.043800 -4.847786e-02 0.043850 -4.846747e-02 0.043900 -4.845709e-02 0.043950 -4.844672e-02 0.044000 -4.843637e-02 0.044050 -4.842603e-02 0.044100 -4.841570e-02 0.044150 -4.840539e-02 0.044200 -4.839509e-02 0.044250 -4.838480e-02 0.044300 -4.837452e-02 0.044350 -4.836426e-02 0.044400 -4.835401e-02 0.044450 -4.834377e-02 0.044500 -4.833354e-02 0.044550 -4.832333e-02 0.044600 -4.831313e-02 0.044650 -4.830294e-02 0.044700 -4.829277e-02 0.044750 -4.828261e-02 0.044800 -4.827246e-02 0.044850 -4.826232e-02 0.044900 -4.825220e-02 0.044950 -4.824209e-02 0.045000 -4.823199e-02 0.045050 -4.822191e-02 0.045100 -4.821183e-02 0.045150 -4.820177e-02 0.045200 -4.819172e-02 0.045250 -4.818169e-02 0.045300 -4.817167e-02 0.045350 -4.816166e-02 0.045400 -4.815166e-02 0.045450 -4.814167e-02 0.045500 -4.813170e-02 0.045550 -4.812174e-02 0.045600 -4.811179e-02 0.045650 -4.810186e-02 0.045700 -4.809193e-02 0.045750 -4.808202e-02 0.045800 -4.807213e-02 0.045850 -4.806224e-02 0.045900 -4.805237e-02 0.045950 -4.804251e-02 0.046000 -4.803266e-02 0.046050 -4.802282e-02 0.046100 -4.801300e-02 0.046150 -4.800318e-02 0.046200 -4.799338e-02 0.046250 -4.798360e-02 0.046300 -4.797382e-02 0.046350 -4.796406e-02 0.046400 -4.795431e-02 0.046450 -4.794457e-02 0.046500 -4.793484e-02 0.046550 -4.792513e-02 0.046600 -4.791543e-02 0.046650 -4.790574e-02 0.046700 -4.789606e-02 0.046750 -4.788639e-02 0.046800 -4.787674e-02 0.046850 -4.786710e-02 0.046900 -4.785747e-02 0.046950 -4.784785e-02 0.047000 -4.783824e-02 0.047050 -4.782865e-02 0.047100 -4.781907e-02 0.047150 -4.780950e-02 0.047200 -4.779994e-02 0.047250 -4.779040e-02 0.047300 -4.778086e-02 0.047350 -4.777134e-02 0.047400 -4.776183e-02 0.047450 -4.775233e-02 0.047500 -4.774285e-02 0.047550 -4.773337e-02 0.047600 -4.772391e-02 0.047650 -4.771446e-02 0.047700 -4.770502e-02 0.047750 -4.769559e-02 0.047800 -4.768618e-02 0.047850 -4.767677e-02 0.047900 -4.766738e-02 0.047950 -4.765800e-02 0.048000 -4.764863e-02 0.048050 -4.763928e-02 0.048100 -4.762993e-02 0.048150 -4.762060e-02 0.048200 -4.761127e-02 0.048250 -4.760196e-02 0.048300 -4.759267e-02 0.048350 -4.758338e-02 0.048400 -4.757410e-02 0.048450 -4.756484e-02 0.048500 -4.755559e-02 0.048550 -4.754635e-02 0.048600 -4.753712e-02 0.048650 -4.752790e-02 0.048700 -4.751870e-02 0.048750 -4.750950e-02 0.048800 -4.750032e-02 0.048850 -4.749115e-02 0.048900 -4.748199e-02 0.048950 -4.747284e-02 0.049000 -4.746370e-02 0.049050 -4.745458e-02 0.049100 -4.744546e-02 0.049150 -4.743636e-02 0.049200 -4.742727e-02 0.049250 -4.741819e-02 0.049300 -4.740912e-02 0.049350 -4.740006e-02 0.049400 -4.739101e-02 0.049450 -4.738198e-02 0.049500 -4.737295e-02 0.049550 -4.736394e-02 0.049600 -4.735494e-02 0.049650 -4.734595e-02 0.049700 -4.733697e-02 0.049750 -4.732801e-02 0.049800 -4.731905e-02 0.049850 -4.731010e-02 0.049900 -4.730117e-02 0.049950 -4.729225e-02 0.050000 -4.728334e-02 0.050050 -4.727444e-02 0.050100 -4.726555e-02 0.050150 -4.725667e-02 0.050200 -4.724780e-02 0.050250 -4.723895e-02 0.050300 -4.723010e-02 0.050350 -4.722127e-02 0.050400 -4.721244e-02 0.050450 -4.720363e-02 0.050500 -4.719483e-02 0.050550 -4.718604e-02 0.050600 -4.717726e-02 0.050650 -4.716849e-02 0.050700 -4.715974e-02 0.050750 -4.715099e-02 0.050800 -4.714226e-02 0.050850 -4.713353e-02 0.050900 -4.712482e-02 0.050950 -4.711612e-02 0.051000 -4.710742e-02 0.051050 -4.709874e-02 0.051100 -4.709007e-02 0.051150 -4.708142e-02 0.051200 -4.707277e-02 0.051250 -4.706413e-02 0.051300 -4.705550e-02 0.051350 -4.704689e-02 0.051400 -4.703828e-02 0.051450 -4.702969e-02 0.051500 -4.702110e-02 0.051550 -4.701253e-02 0.051600 -4.700397e-02 0.051650 -4.699542e-02 0.051700 -4.698688e-02 0.051750 -4.697835e-02 0.051800 -4.696983e-02 0.051850 -4.696132e-02 0.051900 -4.695282e-02 0.051950 -4.694433e-02 0.052000 -4.693586e-02 0.052050 -4.692739e-02 0.052100 -4.691893e-02 0.052150 -4.691049e-02 0.052200 -4.690205e-02 0.052250 -4.689363e-02 0.052300 -4.688522e-02 0.052350 -4.687681e-02 0.052400 -4.686842e-02 0.052450 -4.686004e-02 0.052500 -4.685167e-02 0.052550 -4.684331e-02 0.052600 -4.683495e-02 0.052650 -4.682661e-02 0.052700 -4.681828e-02 0.052750 -4.680997e-02 0.052800 -4.680166e-02 0.052850 -4.679336e-02 0.052900 -4.678507e-02 0.052950 -4.677679e-02 0.053000 -4.676852e-02 0.053050 -4.676027e-02 0.053100 -4.675202e-02 0.053150 -4.674378e-02 0.053200 -4.673556e-02 0.053250 -4.672734e-02 0.053300 -4.671913e-02 0.053350 -4.671094e-02 0.053400 -4.670275e-02 0.053450 -4.669458e-02 0.053500 -4.668641e-02 0.053550 -4.667826e-02 0.053600 -4.667011e-02 0.053650 -4.666198e-02 0.053700 -4.665386e-02 0.053750 -4.664574e-02 0.053800 -4.663764e-02 0.053850 -4.662954e-02 0.053900 -4.662146e-02 0.053950 -4.661339e-02 0.054000 -4.660532e-02 0.054050 -4.659727e-02 0.054100 -4.658923e-02 0.054150 -4.658119e-02 0.054200 -4.657317e-02 0.054250 -4.656516e-02 0.054300 -4.655715e-02 0.054350 -4.654916e-02 0.054400 -4.654118e-02 0.054450 -4.653320e-02 0.054500 -4.652524e-02 0.054550 -4.651729e-02 0.054600 -4.650934e-02 0.054650 -4.650141e-02 0.054700 -4.649349e-02 0.054750 -4.648557e-02 0.054800 -4.647767e-02 0.054850 -4.646977e-02 0.054900 -4.646189e-02 0.054950 -4.645402e-02 0.055000 -4.644615e-02 0.055050 -4.643830e-02 0.055100 -4.643045e-02 0.055150 -4.642262e-02 0.055200 -4.641479e-02 0.055250 -4.640698e-02 0.055300 -4.639917e-02 0.055350 -4.639137e-02 0.055400 -4.638359e-02 0.055450 -4.637581e-02 0.055500 -4.636805e-02 0.055550 -4.636029e-02 0.055600 -4.635254e-02 0.055650 -4.634480e-02 0.055700 -4.633708e-02 0.055750 -4.632936e-02 0.055800 -4.632165e-02 0.055850 -4.631395e-02 0.055900 -4.630626e-02 0.055950 -4.629858e-02 0.056000 -4.629091e-02 0.056050 -4.628325e-02 0.056100 -4.627560e-02 0.056150 -4.626796e-02 0.056200 -4.626032e-02 0.056250 -4.625270e-02 0.056300 -4.624509e-02 0.056350 -4.623749e-02 0.056400 -4.622989e-02 0.056450 -4.622231e-02 0.056500 -4.621473e-02 0.056550 -4.620717e-02 0.056600 -4.619961e-02 0.056650 -4.619206e-02 0.056700 -4.618453e-02 0.056750 -4.617700e-02 0.056800 -4.616948e-02 0.056850 -4.616197e-02 0.056900 -4.615447e-02 0.056950 -4.614698e-02 0.057000 -4.613950e-02 0.057050 -4.613203e-02 0.057100 -4.612457e-02 0.057150 -4.611711e-02 0.057200 -4.610967e-02 0.057250 -4.610224e-02 0.057300 -4.609481e-02 0.057350 -4.608740e-02 0.057400 -4.607999e-02 0.057450 -4.607259e-02 0.057500 -4.606520e-02 0.057550 -4.605783e-02 0.057600 -4.605046e-02 0.057650 -4.604310e-02 0.057700 -4.603574e-02 0.057750 -4.602840e-02 0.057800 -4.602107e-02 0.057850 -4.601375e-02 0.057900 -4.600643e-02 0.057950 -4.599913e-02 0.058000 -4.599183e-02 0.058050 -4.598454e-02 0.058100 -4.597727e-02 0.058150 -4.597000e-02 0.058200 -4.596274e-02 0.058250 -4.595549e-02 0.058300 -4.594824e-02 0.058350 -4.594101e-02 0.058400 -4.593379e-02 0.058450 -4.592657e-02 0.058500 -4.591937e-02 0.058550 -4.591217e-02 0.058600 -4.590498e-02 0.058650 -4.589781e-02 0.058700 -4.589064e-02 0.058750 -4.588348e-02 0.058800 -4.587632e-02 0.058850 -4.586918e-02 0.058900 -4.586205e-02 0.058950 -4.585492e-02 0.059000 -4.584781e-02 0.059050 -4.584070e-02 0.059100 -4.583360e-02 0.059150 -4.582651e-02 0.059200 -4.581943e-02 0.059250 -4.581236e-02 0.059300 -4.580530e-02 0.059350 -4.579824e-02 0.059400 -4.579120e-02 0.059450 -4.578416e-02 0.059500 -4.577713e-02 0.059550 -4.577011e-02 0.059600 -4.576310e-02 0.059650 -4.575610e-02 0.059700 -4.574911e-02 0.059750 -4.574213e-02 0.059800 -4.573515e-02 0.059850 -4.572819e-02 0.059900 -4.572123e-02 0.059950 -4.571428e-02 0.060000 -4.570734e-02 0.060050 -4.570041e-02 0.060100 -4.569348e-02 0.060150 -4.568657e-02 0.060200 -4.567966e-02 0.060250 -4.567277e-02 0.060300 -4.566588e-02 0.060350 -4.565900e-02 0.060400 -4.565213e-02 0.060450 -4.564526e-02 0.060500 -4.563841e-02 0.060550 -4.563156e-02 0.060600 -4.562473e-02 0.060650 -4.561790e-02 0.060700 -4.561108e-02 0.060750 -4.560427e-02 0.060800 -4.559746e-02 0.060850 -4.559067e-02 0.060900 -4.558388e-02 0.060950 -4.557711e-02 0.061000 -4.557034e-02 0.061050 -4.556358e-02 0.061100 -4.555683e-02 0.061150 -4.555008e-02 0.061200 -4.554335e-02 0.061250 -4.553662e-02 0.061300 -4.552990e-02 0.061350 -4.552319e-02 0.061400 -4.551649e-02 0.061450 -4.550980e-02 0.061500 -4.550311e-02 0.061550 -4.549643e-02 0.061600 -4.548977e-02 0.061650 -4.548311e-02 0.061700 -4.547646e-02 0.061750 -4.546981e-02 0.061800 -4.546318e-02 0.061850 -4.545655e-02 0.061900 -4.544993e-02 0.061950 -4.544332e-02 0.062000 -4.543672e-02 0.062050 -4.543013e-02 0.062100 -4.542354e-02 0.062150 -4.541696e-02 0.062200 -4.541040e-02 0.062250 -4.540384e-02 0.062300 -4.539728e-02 0.062350 -4.539074e-02 0.062400 -4.538420e-02 0.062450 -4.537767e-02 0.062500 -4.537115e-02 0.062550 -4.536464e-02 0.062600 -4.535814e-02 0.062650 -4.535164e-02 0.062700 -4.534516e-02 0.062750 -4.533868e-02 0.062800 -4.533221e-02 0.062850 -4.532574e-02 0.062900 -4.531929e-02 0.062950 -4.531284e-02 0.063000 -4.530640e-02 0.063050 -4.529997e-02 0.063100 -4.529355e-02 0.063150 -4.528713e-02 0.063200 -4.528073e-02 0.063250 -4.527433e-02 0.063300 -4.526794e-02 0.063350 -4.526155e-02 0.063400 -4.525518e-02 0.063450 -4.524881e-02 0.063500 -4.524245e-02 0.063550 -4.523610e-02 0.063600 -4.522976e-02 0.063650 -4.522343e-02 0.063700 -4.521710e-02 0.063750 -4.521078e-02 0.063800 -4.520447e-02 0.063850 -4.519816e-02 0.063900 -4.519187e-02 0.063950 -4.518558e-02 0.064000 -4.517930e-02 0.064050 -4.517303e-02 0.064100 -4.516676e-02 0.064150 -4.516051e-02 0.064200 -4.515426e-02 0.064250 -4.514802e-02 0.064300 -4.514179e-02 0.064350 -4.513556e-02 0.064400 -4.512934e-02 0.064450 -4.512313e-02 0.064500 -4.511693e-02 0.064550 -4.511074e-02 0.064600 -4.510455e-02 0.064650 -4.509837e-02 0.064700 -4.509220e-02 0.064750 -4.508604e-02 0.064800 -4.507988e-02 0.064850 -4.507374e-02 0.064900 -4.506760e-02 0.064950 -4.506146e-02 0.065000 -4.505534e-02 0.065050 -4.504922e-02 0.065100 -4.504311e-02 0.065150 -4.503701e-02 0.065200 -4.503092e-02 0.065250 -4.502483e-02 0.065300 -4.501875e-02 0.065350 -4.501268e-02 0.065400 -4.500661e-02 0.065450 -4.500056e-02 0.065500 -4.499451e-02 0.065550 -4.498847e-02 0.065600 -4.498243e-02 0.065650 -4.497641e-02 0.065700 -4.497039e-02 0.065750 -4.496438e-02 0.065800 -4.495838e-02 0.065850 -4.495238e-02 0.065900 -4.494639e-02 0.065950 -4.494041e-02 0.066000 -4.493444e-02 0.066050 -4.492847e-02 0.066100 -4.492251e-02 0.066150 -4.491656e-02 0.066200 -4.491062e-02 0.066250 -4.490468e-02 0.066300 -4.489875e-02 0.066350 -4.489283e-02 0.066400 -4.488692e-02 0.066450 -4.488101e-02 0.066500 -4.487511e-02 0.066550 -4.486922e-02 0.066600 -4.486333e-02 0.066650 -4.485745e-02 0.066700 -4.485158e-02 0.066750 -4.484572e-02 0.066800 -4.483987e-02 0.066850 -4.483402e-02 0.066900 -4.482818e-02 0.066950 -4.482234e-02 0.067000 -4.481652e-02 0.067050 -4.481070e-02 0.067100 -4.480489e-02 0.067150 -4.479908e-02 0.067200 -4.479329e-02 0.067250 -4.478750e-02 0.067300 -4.478171e-02 0.067350 -4.477594e-02 0.067400 -4.477017e-02 0.067450 -4.476441e-02 0.067500 -4.475866e-02 0.067550 -4.475291e-02 0.067600 -4.474717e-02 0.067650 -4.474144e-02 0.067700 -4.473571e-02 0.067750 -4.473000e-02 0.067800 -4.472428e-02 0.067850 -4.471858e-02 0.067900 -4.471288e-02 0.067950 -4.470719e-02 0.068000 -4.470151e-02 0.068050 -4.469584e-02 0.068100 -4.469017e-02 0.068150 -4.468451e-02 0.068200 -4.467886e-02 0.068250 -4.467321e-02 0.068300 -4.466757e-02 0.068350 -4.466194e-02 0.068400 -4.465631e-02 0.068450 -4.465069e-02 0.068500 -4.464508e-02 0.068550 -4.463947e-02 0.068600 -4.463388e-02 0.068650 -4.462829e-02 0.068700 -4.462270e-02 0.068750 -4.461713e-02 0.068800 -4.461156e-02 0.068850 -4.460599e-02 0.068900 -4.460044e-02 0.068950 -4.459489e-02 0.069000 -4.458935e-02 0.069050 -4.458381e-02 0.069100 -4.457828e-02 0.069150 -4.457276e-02 0.069200 -4.456725e-02 0.069250 -4.456174e-02 0.069300 -4.455624e-02 0.069350 -4.455075e-02 0.069400 -4.454526e-02 0.069450 -4.453978e-02 0.069500 -4.453431e-02 0.069550 -4.452884e-02 0.069600 -4.452338e-02 0.069650 -4.451793e-02 0.069700 -4.451248e-02 0.069750 -4.450704e-02 0.069800 -4.450161e-02 0.069850 -4.449619e-02 0.069900 -4.449077e-02 0.069950 -4.448536e-02 0.070000 -4.447995e-02 0.070050 -4.447455e-02 0.070100 -4.446916e-02 0.070150 -4.446377e-02 0.070200 -4.445840e-02 0.070250 -4.445303e-02 0.070300 -4.444766e-02 0.070350 -4.444230e-02 0.070400 -4.443695e-02 0.070450 -4.443161e-02 0.070500 -4.442627e-02 0.070550 -4.442094e-02 0.070600 -4.441561e-02 0.070650 -4.441029e-02 0.070700 -4.440498e-02 0.070750 -4.439968e-02 0.070800 -4.439438e-02 0.070850 -4.438909e-02 0.070900 -4.438380e-02 0.070950 -4.437853e-02 0.071000 -4.437325e-02 0.071050 -4.436799e-02 0.071100 -4.436273e-02 0.071150 -4.435748e-02 0.071200 -4.435223e-02 0.071250 -4.434699e-02 0.071300 -4.434176e-02 0.071350 -4.433654e-02 0.071400 -4.433132e-02 0.071450 -4.432610e-02 0.071500 -4.432090e-02 0.071550 -4.431570e-02 0.071600 -4.431051e-02 0.071650 -4.430532e-02 0.071700 -4.430014e-02 0.071750 -4.429496e-02 0.071800 -4.428980e-02 0.071850 -4.428464e-02 0.071900 -4.427948e-02 0.071950 -4.427433e-02 0.072000 -4.426919e-02 0.072050 -4.426406e-02 0.072100 -4.425893e-02 0.072150 -4.425381e-02 0.072200 -4.424869e-02 0.072250 -4.424358e-02 0.072300 -4.423848e-02 0.072350 -4.423338e-02 0.072400 -4.422829e-02 0.072450 -4.422321e-02 0.072500 -4.421813e-02 0.072550 -4.421306e-02 0.072600 -4.420799e-02 0.072650 -4.420293e-02 0.072700 -4.419788e-02 0.072750 -4.419284e-02 0.072800 -4.418780e-02 0.072850 -4.418276e-02 0.072900 -4.417774e-02 0.072950 -4.417271e-02 0.073000 -4.416770e-02 0.073050 -4.416269e-02 0.073100 -4.415769e-02 0.073150 -4.415269e-02 0.073200 -4.414770e-02 0.073250 -4.414272e-02 0.073300 -4.413774e-02 0.073350 -4.413277e-02 0.073400 -4.412781e-02 0.073450 -4.412285e-02 0.073500 -4.411790e-02 0.073550 -4.411295e-02 0.073600 -4.410801e-02 0.073650 -4.410308e-02 0.073700 -4.409815e-02 0.073750 -4.409323e-02 0.073800 -4.408831e-02 0.073850 -4.408340e-02 0.073900 -4.407850e-02 0.073950 -4.407360e-02 0.074000 -4.406871e-02 0.074050 -4.406383e-02 0.074100 -4.405895e-02 0.074150 -4.405408e-02 0.074200 -4.404921e-02 0.074250 -4.404435e-02 0.074300 -4.403950e-02 0.074350 -4.403465e-02 0.074400 -4.402981e-02 0.074450 -4.402497e-02 0.074500 -4.402014e-02 0.074550 -4.401532e-02 0.074600 -4.401050e-02 0.074650 -4.400569e-02 0.074700 -4.400088e-02 0.074750 -4.399608e-02 0.074800 -4.399129e-02 0.074850 -4.398650e-02 0.074900 -4.398172e-02 0.074950 -4.397694e-02 0.075000 -4.397217e-02 0.075050 -4.396741e-02 0.075100 -4.396265e-02 0.075150 -4.395790e-02 0.075200 -4.395315e-02 0.075250 -4.394841e-02 0.075300 -4.394368e-02 0.075350 -4.393895e-02 0.075400 -4.393423e-02 0.075450 -4.392951e-02 0.075500 -4.392480e-02 0.075550 -4.392009e-02 0.075600 -4.391539e-02 0.075650 -4.391070e-02 0.075700 -4.390601e-02 0.075750 -4.390133e-02 0.075800 -4.389666e-02 0.075850 -4.389199e-02 0.075900 -4.388732e-02 0.075950 -4.388266e-02 0.076000 -4.387801e-02 0.076050 -4.387337e-02 0.076100 -4.386873e-02 0.076150 -4.386409e-02 0.076200 -4.385946e-02 0.076250 -4.385484e-02 0.076300 -4.385022e-02 0.076350 -4.384561e-02 0.076400 -4.384100e-02 0.076450 -4.383640e-02 0.076500 -4.383181e-02 0.076550 -4.382722e-02 0.076600 -4.382264e-02 0.076650 -4.381806e-02 0.076700 -4.381349e-02 0.076750 -4.380892e-02 0.076800 -4.380436e-02 0.076850 -4.379981e-02 0.076900 -4.379526e-02 0.076950 -4.379072e-02 0.077000 -4.378618e-02 0.077050 -4.378165e-02 0.077100 -4.377712e-02 0.077150 -4.377260e-02 0.077200 -4.376809e-02 0.077250 -4.376358e-02 0.077300 -4.375907e-02 0.077350 -4.375458e-02 0.077400 -4.375008e-02 0.077450 -4.374560e-02 0.077500 -4.374112e-02 0.077550 -4.373664e-02 0.077600 -4.373217e-02 0.077650 -4.372771e-02 0.077700 -4.372325e-02 0.077750 -4.371879e-02 0.077800 -4.371435e-02 0.077850 -4.370990e-02 0.077900 -4.370547e-02 0.077950 -4.370104e-02 0.078000 -4.369661e-02 0.078050 -4.369219e-02 0.078100 -4.368778e-02 0.078150 -4.368337e-02 0.078200 -4.367897e-02 0.078250 -4.367457e-02 0.078300 -4.367018e-02 0.078350 -4.366579e-02 0.078400 -4.366141e-02 0.078450 -4.365703e-02 0.078500 -4.365266e-02 0.078550 -4.364830e-02 0.078600 -4.364394e-02 0.078650 -4.363958e-02 0.078700 -4.363523e-02 0.078750 -4.363089e-02 0.078800 -4.362655e-02 0.078850 -4.362222e-02 0.078900 -4.361789e-02 0.078950 -4.361357e-02 0.079000 -4.360926e-02 0.079050 -4.360495e-02 0.079100 -4.360064e-02 0.079150 -4.359634e-02 0.079200 -4.359205e-02 0.079250 -4.358776e-02 0.079300 -4.358347e-02 0.079350 -4.357920e-02 0.079400 -4.357492e-02 0.079450 -4.357065e-02 0.079500 -4.356639e-02 0.079550 -4.356213e-02 0.079600 -4.355788e-02 0.079650 -4.355364e-02 0.079700 -4.354939e-02 0.079750 -4.354516e-02 0.079800 -4.354093e-02 0.079850 -4.353670e-02 0.079900 -4.353248e-02 0.079950 -4.352827e-02 0.080000 -4.352406e-02 0.080050 -4.351985e-02 0.080100 -4.351566e-02 0.080150 -4.351146e-02 0.080200 -4.350727e-02 0.080250 -4.350309e-02 0.080300 -4.349891e-02 0.080350 -4.349474e-02 0.080400 -4.349057e-02 0.080450 -4.348641e-02 0.080500 -4.348225e-02 0.080550 -4.347810e-02 0.080600 -4.347395e-02 0.080650 -4.346981e-02 0.080700 -4.346567e-02 0.080750 -4.346154e-02 0.080800 -4.345742e-02 0.080850 -4.345330e-02 0.080900 -4.344918e-02 0.080950 -4.344507e-02 0.081000 -4.344096e-02 0.081050 -4.343686e-02 0.081100 -4.343277e-02 0.081150 -4.342868e-02 0.081200 -4.342459e-02 0.081250 -4.342051e-02 0.081300 -4.341644e-02 0.081350 -4.341237e-02 0.081400 -4.340830e-02 0.081450 -4.340424e-02 0.081500 -4.340019e-02 0.081550 -4.339614e-02 0.081600 -4.339210e-02 0.081650 -4.338806e-02 0.081700 -4.338402e-02 0.081750 -4.337999e-02 0.081800 -4.337597e-02 0.081850 -4.337195e-02 0.081900 -4.336793e-02 0.081950 -4.336392e-02 0.082000 -4.335992e-02 0.082050 -4.335592e-02 0.082100 -4.335193e-02 0.082150 -4.334794e-02 0.082200 -4.334395e-02 0.082250 -4.333997e-02 0.082300 -4.333600e-02 0.082350 -4.333203e-02 0.082400 -4.332807e-02 0.082450 -4.332411e-02 0.082500 -4.332015e-02 0.082550 -4.331620e-02 0.082600 -4.331226e-02 0.082650 -4.330832e-02 0.082700 -4.330438e-02 0.082750 -4.330045e-02 0.082800 -4.329653e-02 0.082850 -4.329261e-02 0.082900 -4.328869e-02 0.082950 -4.328478e-02 0.083000 -4.328088e-02 0.083050 -4.327698e-02 0.083100 -4.327308e-02 0.083150 -4.326919e-02 0.083200 -4.326531e-02 0.083250 -4.326143e-02 0.083300 -4.325755e-02 0.083350 -4.325368e-02 0.083400 -4.324981e-02 0.083450 -4.324595e-02 0.083500 -4.324209e-02 0.083550 -4.323824e-02 0.083600 -4.323439e-02 0.083650 -4.323055e-02 0.083700 -4.322671e-02 0.083750 -4.322288e-02 0.083800 -4.321905e-02 0.083850 -4.321523e-02 0.083900 -4.321141e-02 0.083950 -4.320760e-02 0.084000 -4.320379e-02 0.084050 -4.319998e-02 0.084100 -4.319618e-02 0.084150 -4.319239e-02 0.084200 -4.318860e-02 0.084250 -4.318481e-02 0.084300 -4.318103e-02 0.084350 -4.317726e-02 0.084400 -4.317349e-02 0.084450 -4.316972e-02 0.084500 -4.316596e-02 0.084550 -4.316220e-02 0.084600 -4.315845e-02 0.084650 -4.315470e-02 0.084700 -4.315096e-02 0.084750 -4.314722e-02 0.084800 -4.314349e-02 0.084850 -4.313976e-02 0.084900 -4.313604e-02 0.084950 -4.313232e-02 0.085000 -4.312860e-02 0.085050 -4.312489e-02 0.085100 -4.312118e-02 0.085150 -4.311748e-02 0.085200 -4.311379e-02 0.085250 -4.311010e-02 0.085300 -4.310641e-02 0.085350 -4.310273e-02 0.085400 -4.309905e-02 0.085450 -4.309537e-02 0.085500 -4.309171e-02 0.085550 -4.308804e-02 0.085600 -4.308438e-02 0.085650 -4.308073e-02 0.085700 -4.307708e-02 0.085750 -4.307343e-02 0.085800 -4.306979e-02 0.085850 -4.306615e-02 0.085900 -4.306252e-02 0.085950 -4.305889e-02 0.086000 -4.305527e-02 0.086050 -4.305165e-02 0.086100 -4.304804e-02 0.086150 -4.304443e-02 0.086200 -4.304082e-02 0.086250 -4.303722e-02 0.086300 -4.303363e-02 0.086350 -4.303003e-02 0.086400 -4.302645e-02 0.086450 -4.302286e-02 0.086500 -4.301929e-02 0.086550 -4.301571e-02 0.086600 -4.301214e-02 0.086650 -4.300858e-02 0.086700 -4.300502e-02 0.086750 -4.300146e-02 0.086800 -4.299791e-02 0.086850 -4.299436e-02 0.086900 -4.299082e-02 0.086950 -4.298728e-02 0.087000 -4.298375e-02 0.087050 -4.298022e-02 0.087100 -4.297670e-02 0.087150 -4.297317e-02 0.087200 -4.296966e-02 0.087250 -4.296615e-02 0.087300 -4.296264e-02 0.087350 -4.295914e-02 0.087400 -4.295564e-02 0.087450 -4.295214e-02 0.087500 -4.294865e-02 0.087550 -4.294517e-02 0.087600 -4.294169e-02 0.087650 -4.293821e-02 0.087700 -4.293474e-02 0.087750 -4.293127e-02 0.087800 -4.292781e-02 0.087850 -4.292435e-02 0.087900 -4.292089e-02 0.087950 -4.291744e-02 0.088000 -4.291399e-02 0.088050 -4.291055e-02 0.088100 -4.290712e-02 0.088150 -4.290368e-02 0.088200 -4.290025e-02 0.088250 -4.289683e-02 0.088300 -4.289341e-02 0.088350 -4.288999e-02 0.088400 -4.288658e-02 0.088450 -4.288317e-02 0.088500 -4.287977e-02 0.088550 -4.287637e-02 0.088600 -4.287297e-02 0.088650 -4.286958e-02 0.088700 -4.286619e-02 0.088750 -4.286281e-02 0.088800 -4.285943e-02 0.088850 -4.285606e-02 0.088900 -4.285269e-02 0.088950 -4.284932e-02 0.089000 -4.284596e-02 0.089050 -4.284261e-02 0.089100 -4.283925e-02 0.089150 -4.283590e-02 0.089200 -4.283256e-02 0.089250 -4.282922e-02 0.089300 -4.282588e-02 0.089350 -4.282255e-02 0.089400 -4.281922e-02 0.089450 -4.281590e-02 0.089500 -4.281258e-02 0.089550 -4.280926e-02 0.089600 -4.280595e-02 0.089650 -4.280264e-02 0.089700 -4.279934e-02 0.089750 -4.279604e-02 0.089800 -4.279275e-02 0.089850 -4.278946e-02 0.089900 -4.278617e-02 0.089950 -4.278289e-02 0.090000 -4.277961e-02 0.090050 -4.277634e-02 0.090100 -4.277307e-02 0.090150 -4.276980e-02 0.090200 -4.276654e-02 0.090250 -4.276328e-02 0.090300 -4.276003e-02 0.090350 -4.275678e-02 0.090400 -4.275353e-02 0.090450 -4.275029e-02 0.090500 -4.274705e-02 0.090550 -4.274382e-02 0.090600 -4.274059e-02 0.090650 -4.273736e-02 0.090700 -4.273414e-02 0.090750 -4.273092e-02 0.090800 -4.272771e-02 0.090850 -4.272450e-02 0.090900 -4.272129e-02 0.090950 -4.271809e-02 0.091000 -4.271490e-02 0.091050 -4.271170e-02 0.091100 -4.270851e-02 0.091150 -4.270533e-02 0.091200 -4.270215e-02 0.091250 -4.269897e-02 0.091300 -4.269579e-02 0.091350 -4.269263e-02 0.091400 -4.268946e-02 0.091450 -4.268630e-02 0.091500 -4.268314e-02 0.091550 -4.267999e-02 0.091600 -4.267684e-02 0.091650 -4.267369e-02 0.091700 -4.267055e-02 0.091750 -4.266741e-02 0.091800 -4.266428e-02 0.091850 -4.266115e-02 0.091900 -4.265802e-02 0.091950 -4.265490e-02 0.092000 -4.265178e-02 0.092050 -4.264866e-02 0.092100 -4.264555e-02 0.092150 -4.264245e-02 0.092200 -4.263934e-02 0.092250 -4.263625e-02 0.092300 -4.263315e-02 0.092350 -4.263006e-02 0.092400 -4.262697e-02 0.092450 -4.262389e-02 0.092500 -4.262081e-02 0.092550 -4.261773e-02 0.092600 -4.261466e-02 0.092650 -4.261159e-02 0.092700 -4.260853e-02 0.092750 -4.260547e-02 0.092800 -4.260241e-02 0.092850 -4.259936e-02 0.092900 -4.259631e-02 0.092950 -4.259326e-02 0.093000 -4.259022e-02 0.093050 -4.258718e-02 0.093100 -4.258415e-02 0.093150 -4.258112e-02 0.093200 -4.257809e-02 0.093250 -4.257507e-02 0.093300 -4.257205e-02 0.093350 -4.256904e-02 0.093400 -4.256603e-02 0.093450 -4.256302e-02 0.093500 -4.256001e-02 0.093550 -4.255701e-02 0.093600 -4.255402e-02 0.093650 -4.255103e-02 0.093700 -4.254804e-02 0.093750 -4.254505e-02 0.093800 -4.254207e-02 0.093850 -4.253909e-02 0.093900 -4.253612e-02 0.093950 -4.253315e-02 0.094000 -4.253018e-02 0.094050 -4.252722e-02 0.094100 -4.252426e-02 0.094150 -4.252131e-02 0.094200 -4.251835e-02 0.094250 -4.251541e-02 0.094300 -4.251246e-02 0.094350 -4.250952e-02 0.094400 -4.250658e-02 0.094450 -4.250365e-02 0.094500 -4.250072e-02 0.094550 -4.249780e-02 0.094600 -4.249487e-02 0.094650 -4.249196e-02 0.094700 -4.248904e-02 0.094750 -4.248613e-02 0.094800 -4.248322e-02 0.094850 -4.248032e-02 0.094900 -4.247742e-02 0.094950 -4.247452e-02 0.095000 -4.247163e-02 0.095050 -4.246874e-02 0.095100 -4.246585e-02 0.095150 -4.246297e-02 0.095200 -4.246009e-02 0.095250 -4.245722e-02 0.095300 -4.245434e-02 0.095350 -4.245148e-02 0.095400 -4.244861e-02 0.095450 -4.244575e-02 0.095500 -4.244289e-02 0.095550 -4.244004e-02 0.095600 -4.243719e-02 0.095650 -4.243434e-02 0.095700 -4.243150e-02 0.095750 -4.242866e-02 0.095800 -4.242582e-02 0.095850 -4.242299e-02 0.095900 -4.242016e-02 0.095950 -4.241734e-02 0.096000 -4.241452e-02 0.096050 -4.241170e-02 0.096100 -4.240888e-02 0.096150 -4.240607e-02 0.096200 -4.240327e-02 0.096250 -4.240046e-02 0.096300 -4.239766e-02 0.096350 -4.239486e-02 0.096400 -4.239207e-02 0.096450 -4.238928e-02 0.096500 -4.238649e-02 0.096550 -4.238371e-02 0.096600 -4.238093e-02 0.096650 -4.237815e-02 0.096700 -4.237538e-02 0.096750 -4.237261e-02 0.096800 -4.236985e-02 0.096850 -4.236708e-02 0.096900 -4.236432e-02 0.096950 -4.236157e-02 0.097000 -4.235882e-02 0.097050 -4.235607e-02 0.097100 -4.235332e-02 0.097150 -4.235058e-02 0.097200 -4.234784e-02 0.097250 -4.234511e-02 0.097300 -4.234238e-02 0.097350 -4.233965e-02 0.097400 -4.233692e-02 0.097450 -4.233420e-02 0.097500 -4.233148e-02 0.097550 -4.232877e-02 0.097600 -4.232606e-02 0.097650 -4.232335e-02 0.097700 -4.232065e-02 0.097750 -4.231795e-02 0.097800 -4.231525e-02 0.097850 -4.231255e-02 0.097900 -4.230986e-02 0.097950 -4.230718e-02 0.098000 -4.230449e-02 0.098050 -4.230181e-02 0.098100 -4.229913e-02 0.098150 -4.229646e-02 0.098200 -4.229379e-02 0.098250 -4.229112e-02 0.098300 -4.228846e-02 0.098350 -4.228580e-02 0.098400 -4.228314e-02 0.098450 -4.228048e-02 0.098500 -4.227783e-02 0.098550 -4.227519e-02 0.098600 -4.227254e-02 0.098650 -4.226990e-02 0.098700 -4.226726e-02 0.098750 -4.226463e-02 0.098800 -4.226200e-02 0.098850 -4.225937e-02 0.098900 -4.225675e-02 0.098950 -4.225413e-02 0.099000 -4.225151e-02 0.099050 -4.224889e-02 0.099100 -4.224628e-02 0.099150 -4.224367e-02 0.099200 -4.224107e-02 0.099250 -4.223847e-02 0.099300 -4.223587e-02 0.099350 -4.223327e-02 0.099400 -4.223068e-02 0.099450 -4.222809e-02 0.099500 -4.222551e-02 0.099550 -4.222293e-02 0.099600 -4.222035e-02 0.099650 -4.221777e-02 0.099700 -4.221520e-02 0.099750 -4.221263e-02 0.099800 -4.221006e-02 0.099850 -4.220750e-02 0.099900 -4.220494e-02 0.099950 -4.220239e-02 0.100000 -4.219983e-02 0.100050 -4.219728e-02 0.100100 -4.219474e-02 0.100150 -4.219219e-02 0.100200 -4.218965e-02 0.100250 -4.218711e-02 0.100300 -4.218458e-02 0.100350 -4.218205e-02 0.100400 -4.217952e-02 0.100450 -4.217700e-02 0.100500 -4.217448e-02 0.100550 -4.217196e-02 0.100600 -4.216944e-02 0.100650 -4.216693e-02 0.100700 -4.216442e-02 0.100750 -4.216192e-02 0.100800 -4.215941e-02 0.100850 -4.215691e-02 0.100900 -4.215442e-02 0.100950 -4.215192e-02 0.101000 -4.214943e-02 0.101050 -4.214695e-02 0.101100 -4.214446e-02 0.101150 -4.214198e-02 0.101200 -4.213950e-02 0.101250 -4.213703e-02 0.101300 -4.213456e-02 0.101350 -4.213209e-02 0.101400 -4.212962e-02 0.101450 -4.212716e-02 0.101500 -4.212470e-02 0.101550 -4.212225e-02 0.101600 -4.211979e-02 0.101650 -4.211734e-02 0.101700 -4.211490e-02 0.101750 -4.211245e-02 0.101800 -4.211001e-02 0.101850 -4.210757e-02 0.101900 -4.210514e-02 0.101950 -4.210271e-02 0.102000 -4.210028e-02 0.102050 -4.209785e-02 0.102100 -4.209543e-02 0.102150 -4.209301e-02 0.102200 -4.209059e-02 0.102250 -4.208818e-02 0.102300 -4.208577e-02 0.102350 -4.208336e-02 0.102400 -4.208096e-02 0.102450 -4.207856e-02 0.102500 -4.207616e-02 0.102550 -4.207376e-02 0.102600 -4.207137e-02 0.102650 -4.206898e-02 0.102700 -4.206659e-02 0.102750 -4.206421e-02 0.102800 -4.206183e-02 0.102850 -4.205945e-02 0.102900 -4.205708e-02 0.102950 -4.205471e-02 0.103000 -4.205234e-02 0.103050 -4.204997e-02 0.103100 -4.204761e-02 0.103150 -4.204525e-02 0.103200 -4.204289e-02 0.103250 -4.204054e-02 0.103300 -4.203819e-02 0.103350 -4.203584e-02 0.103400 -4.203349e-02 0.103450 -4.203115e-02 0.103500 -4.202881e-02 0.103550 -4.202648e-02 0.103600 -4.202414e-02 0.103650 -4.202181e-02 0.103700 -4.201948e-02 0.103750 -4.201716e-02 0.103800 -4.201484e-02 0.103850 -4.201252e-02 0.103900 -4.201020e-02 0.103950 -4.200789e-02 0.104000 -4.200558e-02 0.104050 -4.200327e-02 0.104100 -4.200097e-02 0.104150 -4.199867e-02 0.104200 -4.199637e-02 0.104250 -4.199407e-02 0.104300 -4.199178e-02 0.104350 -4.198949e-02 0.104400 -4.198720e-02 0.104450 -4.198492e-02 0.104500 -4.198263e-02 0.104550 -4.198036e-02 0.104600 -4.197808e-02 0.104650 -4.197581e-02 0.104700 -4.197354e-02 0.104750 -4.197127e-02 0.104800 -4.196900e-02 0.104850 -4.196674e-02 0.104900 -4.196448e-02 0.104950 -4.196223e-02 0.105000 -4.195998e-02 0.105050 -4.195772e-02 0.105100 -4.195548e-02 0.105150 -4.195323e-02 0.105200 -4.195099e-02 0.105250 -4.194875e-02 0.105300 -4.194652e-02 0.105350 -4.194428e-02 0.105400 -4.194205e-02 0.105450 -4.193982e-02 0.105500 -4.193760e-02 0.105550 -4.193538e-02 0.105600 -4.193316e-02 0.105650 -4.193094e-02 0.105700 -4.192872e-02 0.105750 -4.192651e-02 0.105800 -4.192430e-02 0.105850 -4.192210e-02 0.105900 -4.191990e-02 0.105950 -4.191770e-02 0.106000 -4.191550e-02 0.106050 -4.191330e-02 0.106100 -4.191111e-02 0.106150 -4.190892e-02 0.106200 -4.190673e-02 0.106250 -4.190455e-02 0.106300 -4.190237e-02 0.106350 -4.190019e-02 0.106400 -4.189802e-02 0.106450 -4.189584e-02 0.106500 -4.189367e-02 0.106550 -4.189151e-02 0.106600 -4.188934e-02 0.106650 -4.188718e-02 0.106700 -4.188502e-02 0.106750 -4.188286e-02 0.106800 -4.188071e-02 0.106850 -4.187856e-02 0.106900 -4.187641e-02 0.106950 -4.187426e-02 0.107000 -4.187212e-02 0.107050 -4.186998e-02 0.107100 -4.186784e-02 0.107150 -4.186570e-02 0.107200 -4.186357e-02 0.107250 -4.186144e-02 0.107300 -4.185931e-02 0.107350 -4.185719e-02 0.107400 -4.185507e-02 0.107450 -4.185295e-02 0.107500 -4.185083e-02 0.107550 -4.184872e-02 0.107600 -4.184661e-02 0.107650 -4.184450e-02 0.107700 -4.184239e-02 0.107750 -4.184029e-02 0.107800 -4.183819e-02 0.107850 -4.183609e-02 0.107900 -4.183399e-02 0.107950 -4.183190e-02 0.108000 -4.182981e-02 0.108050 -4.182772e-02 0.108100 -4.182564e-02 0.108150 -4.182355e-02 0.108200 -4.182147e-02 0.108250 -4.181940e-02 0.108300 -4.181732e-02 0.108350 -4.181525e-02 0.108400 -4.181318e-02 0.108450 -4.181111e-02 0.108500 -4.180905e-02 0.108550 -4.180699e-02 0.108600 -4.180493e-02 0.108650 -4.180287e-02 0.108700 -4.180082e-02 0.108750 -4.179877e-02 0.108800 -4.179672e-02 0.108850 -4.179467e-02 0.108900 -4.179263e-02 0.108950 -4.179059e-02 0.109000 -4.178855e-02 0.109050 -4.178651e-02 0.109100 -4.178448e-02 0.109150 -4.178245e-02 0.109200 -4.178042e-02 0.109250 -4.177839e-02 0.109300 -4.177637e-02 0.109350 -4.177435e-02 0.109400 -4.177233e-02 0.109450 -4.177031e-02 0.109500 -4.176830e-02 0.109550 -4.176629e-02 0.109600 -4.176428e-02 0.109650 -4.176227e-02 0.109700 -4.176027e-02 0.109750 -4.175827e-02 0.109800 -4.175627e-02 0.109850 -4.175427e-02 0.109900 -4.175228e-02 0.109950 -4.175029e-02 0.110000 -4.174830e-02 0.110050 -4.174632e-02 0.110100 -4.174433e-02 0.110150 -4.174235e-02 0.110200 -4.174037e-02 0.110250 -4.173840e-02 0.110300 -4.173642e-02 0.110350 -4.173445e-02 0.110400 -4.173248e-02 0.110450 -4.173052e-02 0.110500 -4.172855e-02 0.110550 -4.172659e-02 0.110600 -4.172463e-02 0.110650 -4.172268e-02 0.110700 -4.172072e-02 0.110750 -4.171877e-02 0.110800 -4.171682e-02 0.110850 -4.171488e-02 0.110900 -4.171293e-02 0.110950 -4.171099e-02 0.111000 -4.170905e-02 0.111050 -4.170711e-02 0.111100 -4.170518e-02 0.111150 -4.170325e-02 0.111200 -4.170132e-02 0.111250 -4.169939e-02 0.111300 -4.169747e-02 0.111350 -4.169554e-02 0.111400 -4.169362e-02 0.111450 -4.169171e-02 0.111500 -4.168979e-02 0.111550 -4.168788e-02 0.111600 -4.168597e-02 0.111650 -4.168406e-02 0.111700 -4.168215e-02 0.111750 -4.168025e-02 0.111800 -4.167835e-02 0.111850 -4.167645e-02 0.111900 -4.167455e-02 0.111950 -4.167266e-02 0.112000 -4.167077e-02 0.112050 -4.166888e-02 0.112100 -4.166699e-02 0.112150 -4.166511e-02 0.112200 -4.166323e-02 0.112250 -4.166135e-02 0.112300 -4.165947e-02 0.112350 -4.165759e-02 0.112400 -4.165572e-02 0.112450 -4.165385e-02 0.112500 -4.165198e-02 0.112550 -4.165012e-02 0.112600 -4.164825e-02 0.112650 -4.164639e-02 0.112700 -4.164453e-02 0.112750 -4.164268e-02 0.112800 -4.164082e-02 0.112850 -4.163897e-02 0.112900 -4.163712e-02 0.112950 -4.163528e-02 0.113000 -4.163343e-02 0.113050 -4.163159e-02 0.113100 -4.162975e-02 0.113150 -4.162791e-02 0.113200 -4.162608e-02 0.113250 -4.162424e-02 0.113300 -4.162241e-02 0.113350 -4.162058e-02 0.113400 -4.161876e-02 0.113450 -4.161693e-02 0.113500 -4.161511e-02 0.113550 -4.161329e-02 0.113600 -4.161147e-02 0.113650 -4.160966e-02 0.113700 -4.160785e-02 0.113750 -4.160604e-02 0.113800 -4.160423e-02 0.113850 -4.160242e-02 0.113900 -4.160062e-02 0.113950 -4.159882e-02 0.114000 -4.159702e-02 0.114050 -4.159522e-02 0.114100 -4.159342e-02 0.114150 -4.159163e-02 0.114200 -4.158984e-02 0.114250 -4.158805e-02 0.114300 -4.158627e-02 0.114350 -4.158448e-02 0.114400 -4.158270e-02 0.114450 -4.158092e-02 0.114500 -4.157915e-02 0.114550 -4.157737e-02 0.114600 -4.157560e-02 0.114650 -4.157383e-02 0.114700 -4.157206e-02 0.114750 -4.157030e-02 0.114800 -4.156853e-02 0.114850 -4.156677e-02 0.114900 -4.156501e-02 0.114950 -4.156326e-02 0.115000 -4.156150e-02 0.115050 -4.155975e-02 0.115100 -4.155800e-02 0.115150 -4.155625e-02 0.115200 -4.155450e-02 0.115250 -4.155276e-02 0.115300 -4.155102e-02 0.115350 -4.154928e-02 0.115400 -4.154754e-02 0.115450 -4.154581e-02 0.115500 -4.154407e-02 0.115550 -4.154234e-02 0.115600 -4.154061e-02 0.115650 -4.153889e-02 0.115700 -4.153716e-02 0.115750 -4.153544e-02 0.115800 -4.153372e-02 0.115850 -4.153200e-02 0.115900 -4.153029e-02 0.115950 -4.152857e-02 0.116000 -4.152686e-02 0.116050 -4.152515e-02 0.116100 -4.152345e-02 0.116150 -4.152174e-02 0.116200 -4.152004e-02 0.116250 -4.151834e-02 0.116300 -4.151664e-02 0.116350 -4.151494e-02 0.116400 -4.151325e-02 0.116450 -4.151155e-02 0.116500 -4.150986e-02 0.116550 -4.150818e-02 0.116600 -4.150649e-02 0.116650 -4.150481e-02 0.116700 -4.150312e-02 0.116750 -4.150144e-02 0.116800 -4.149977e-02 0.116850 -4.149809e-02 0.116900 -4.149642e-02 0.116950 -4.149475e-02 0.117000 -4.149308e-02 0.117050 -4.149141e-02 0.117100 -4.148975e-02 0.117150 -4.148808e-02 0.117200 -4.148642e-02 0.117250 -4.148476e-02 0.117300 -4.148311e-02 0.117350 -4.148145e-02 0.117400 -4.147980e-02 0.117450 -4.147815e-02 0.117500 -4.147650e-02 0.117550 -4.147485e-02 0.117600 -4.147321e-02 0.117650 -4.147157e-02 0.117700 -4.146993e-02 0.117750 -4.146829e-02 0.117800 -4.146665e-02 0.117850 -4.146502e-02 0.117900 -4.146339e-02 0.117950 -4.146176e-02 0.118000 -4.146013e-02 0.118050 -4.145850e-02 0.118100 -4.145688e-02 0.118150 -4.145526e-02 0.118200 -4.145364e-02 0.118250 -4.145202e-02 0.118300 -4.145040e-02 0.118350 -4.144879e-02 0.118400 -4.144718e-02 0.118450 -4.144557e-02 0.118500 -4.144396e-02 0.118550 -4.144235e-02 0.118600 -4.144075e-02 0.118650 -4.143915e-02 0.118700 -4.143755e-02 0.118750 -4.143595e-02 0.118800 -4.143435e-02 0.118850 -4.143276e-02 0.118900 -4.143117e-02 0.118950 -4.142958e-02 0.119000 -4.142799e-02 0.119050 -4.142641e-02 0.119100 -4.142482e-02 0.119150 -4.142324e-02 0.119200 -4.142166e-02 0.119250 -4.142008e-02 0.119300 -4.141851e-02 0.119350 -4.141693e-02 0.119400 -4.141536e-02 0.119450 -4.141379e-02 0.119500 -4.141222e-02 0.119550 -4.141066e-02 0.119600 -4.140909e-02 0.119650 -4.140753e-02 0.119700 -4.140597e-02 0.119750 -4.140441e-02 0.119800 -4.140286e-02 0.119850 -4.140130e-02 0.119900 -4.139975e-02 0.119950 -4.139820e-02 0.120000 -4.139665e-02 0.120050 -4.139510e-02 0.120100 -4.139356e-02 0.120150 -4.139201e-02 0.120200 -4.139047e-02 0.120250 -4.138893e-02 0.120300 -4.138740e-02 0.120350 -4.138586e-02 0.120400 -4.138433e-02 0.120450 -4.138280e-02 0.120500 -4.138127e-02 0.120550 -4.137974e-02 0.120600 -4.137822e-02 0.120650 -4.137669e-02 0.120700 -4.137517e-02 0.120750 -4.137365e-02 0.120800 -4.137213e-02 0.120850 -4.137062e-02 0.120900 -4.136910e-02 0.120950 -4.136759e-02 0.121000 -4.136608e-02 0.121050 -4.136457e-02 0.121100 -4.136307e-02 0.121150 -4.136156e-02 0.121200 -4.136006e-02 0.121250 -4.135856e-02 0.121300 -4.135706e-02 0.121350 -4.135556e-02 0.121400 -4.135407e-02 0.121450 -4.135257e-02 0.121500 -4.135108e-02 0.121550 -4.134959e-02 0.121600 -4.134810e-02 0.121650 -4.134662e-02 0.121700 -4.134513e-02 0.121750 -4.134365e-02 0.121800 -4.134217e-02 0.121850 -4.134069e-02 0.121900 -4.133921e-02 0.121950 -4.133774e-02 0.122000 -4.133627e-02 0.122050 -4.133479e-02 0.122100 -4.133333e-02 0.122150 -4.133186e-02 0.122200 -4.133039e-02 0.122250 -4.132893e-02 0.122300 -4.132747e-02 0.122350 -4.132601e-02 0.122400 -4.132455e-02 0.122450 -4.132309e-02 0.122500 -4.132164e-02 0.122550 -4.132018e-02 0.122600 -4.131873e-02 0.122650 -4.131728e-02 0.122700 -4.131584e-02 0.122750 -4.131439e-02 0.122800 -4.131295e-02 0.122850 -4.131150e-02 0.122900 -4.131006e-02 0.122950 -4.130862e-02 0.123000 -4.130719e-02 0.123050 -4.130575e-02 0.123100 -4.130432e-02 0.123150 -4.130289e-02 0.123200 -4.130146e-02 0.123250 -4.130003e-02 0.123300 -4.129861e-02 0.123350 -4.129718e-02 0.123400 -4.129576e-02 0.123450 -4.129434e-02 0.123500 -4.129292e-02 0.123550 -4.129150e-02 0.123600 -4.129009e-02 0.123650 -4.128867e-02 0.123700 -4.128726e-02 0.123750 -4.128585e-02 0.123800 -4.128444e-02 0.123850 -4.128304e-02 0.123900 -4.128163e-02 0.123950 -4.128023e-02 0.124000 -4.127883e-02 0.124050 -4.127743e-02 0.124100 -4.127603e-02 0.124150 -4.127463e-02 0.124200 -4.127324e-02 0.124250 -4.127185e-02 0.124300 -4.127046e-02 0.124350 -4.126907e-02 0.124400 -4.126768e-02 0.124450 -4.126630e-02 0.124500 -4.126491e-02 0.124550 -4.126353e-02 0.124600 -4.126215e-02 0.124650 -4.126077e-02 0.124700 -4.125939e-02 0.124750 -4.125802e-02 0.124800 -4.125664e-02 0.124850 -4.125527e-02 0.124900 -4.125390e-02 0.124950 -4.125253e-02 0.125000 -4.125117e-02 0.125050 -4.124980e-02 0.125100 -4.124844e-02 0.125150 -4.124708e-02 0.125200 -4.124572e-02 0.125250 -4.124436e-02 0.125300 -4.124300e-02 0.125350 -4.124165e-02 0.125400 -4.124030e-02 0.125450 -4.123895e-02 0.125500 -4.123760e-02 0.125550 -4.123625e-02 0.125600 -4.123490e-02 0.125650 -4.123356e-02 0.125700 -4.123221e-02 0.125750 -4.123087e-02 0.125800 -4.122953e-02 0.125850 -4.122819e-02 0.125900 -4.122686e-02 0.125950 -4.122552e-02 0.126000 -4.122419e-02 0.126050 -4.122286e-02 0.126100 -4.122153e-02 0.126150 -4.122020e-02 0.126200 -4.121888e-02 0.126250 -4.121755e-02 0.126300 -4.121623e-02 0.126350 -4.121491e-02 0.126400 -4.121359e-02 0.126450 -4.121227e-02 0.126500 -4.121095e-02 0.126550 -4.120964e-02 0.126600 -4.120833e-02 0.126650 -4.120701e-02 0.126700 -4.120570e-02 0.126750 -4.120440e-02 0.126800 -4.120309e-02 0.126850 -4.120179e-02 0.126900 -4.120048e-02 0.126950 -4.119918e-02 0.127000 -4.119788e-02 0.127050 -4.119658e-02 0.127100 -4.119529e-02 0.127150 -4.119399e-02 0.127200 -4.119270e-02 0.127250 -4.119140e-02 0.127300 -4.119011e-02 0.127350 -4.118883e-02 0.127400 -4.118754e-02 0.127450 -4.118625e-02 0.127500 -4.118497e-02 0.127550 -4.118369e-02 0.127600 -4.118241e-02 0.127650 -4.118113e-02 0.127700 -4.117985e-02 0.127750 -4.117857e-02 0.127800 -4.117730e-02 0.127850 -4.117603e-02 0.127900 -4.117476e-02 0.127950 -4.117349e-02 0.128000 -4.117222e-02 0.128050 -4.117095e-02 0.128100 -4.116969e-02 0.128150 -4.116842e-02 0.128200 -4.116716e-02 0.128250 -4.116590e-02 0.128300 -4.116464e-02 0.128350 -4.116339e-02 0.128400 -4.116213e-02 0.128450 -4.116088e-02 0.128500 -4.115963e-02 0.128550 -4.115838e-02 0.128600 -4.115713e-02 0.128650 -4.115588e-02 0.128700 -4.115463e-02 0.128750 -4.115339e-02 0.128800 -4.115215e-02 0.128850 -4.115091e-02 0.128900 -4.114967e-02 0.128950 -4.114843e-02 0.129000 -4.114719e-02 0.129050 -4.114596e-02 0.129100 -4.114472e-02 0.129150 -4.114349e-02 0.129200 -4.114226e-02 0.129250 -4.114103e-02 0.129300 -4.113980e-02 0.129350 -4.113858e-02 0.129400 -4.113735e-02 0.129450 -4.113613e-02 0.129500 -4.113491e-02 0.129550 -4.113369e-02 0.129600 -4.113247e-02 0.129650 -4.113126e-02 0.129700 -4.113004e-02 0.129750 -4.112883e-02 0.129800 -4.112761e-02 0.129850 -4.112640e-02 0.129900 -4.112520e-02 0.129950 -4.112399e-02 0.130000 -4.112278e-02 0.130050 -4.112158e-02 0.130100 -4.112037e-02 0.130150 -4.111917e-02 0.130200 -4.111797e-02 0.130250 -4.111677e-02 0.130300 -4.111558e-02 0.130350 -4.111438e-02 0.130400 -4.111319e-02 0.130450 -4.111199e-02 0.130500 -4.111080e-02 0.130550 -4.110961e-02 0.130600 -4.110843e-02 0.130650 -4.110724e-02 0.130700 -4.110605e-02 0.130750 -4.110487e-02 0.130800 -4.110369e-02 0.130850 -4.110251e-02 0.130900 -4.110133e-02 0.130950 -4.110015e-02 0.131000 -4.109897e-02 0.131050 -4.109780e-02 0.131100 -4.109663e-02 0.131150 -4.109545e-02 0.131200 -4.109428e-02 0.131250 -4.109312e-02 0.131300 -4.109195e-02 0.131350 -4.109078e-02 0.131400 -4.108962e-02 0.131450 -4.108845e-02 0.131500 -4.108729e-02 0.131550 -4.108613e-02 0.131600 -4.108497e-02 0.131650 -4.108382e-02 0.131700 -4.108266e-02 0.131750 -4.108151e-02 0.131800 -4.108035e-02 0.131850 -4.107920e-02 0.131900 -4.107805e-02 0.131950 -4.107690e-02 0.132000 -4.107576e-02 0.132050 -4.107461e-02 0.132100 -4.107347e-02 0.132150 -4.107232e-02 0.132200 -4.107118e-02 0.132250 -4.107004e-02 0.132300 -4.106890e-02 0.132350 -4.106776e-02 0.132400 -4.106663e-02 0.132450 -4.106549e-02 0.132500 -4.106436e-02 0.132550 -4.106323e-02 0.132600 -4.106210e-02 0.132650 -4.106097e-02 0.132700 -4.105984e-02 0.132750 -4.105872e-02 0.132800 -4.105759e-02 0.132850 -4.105647e-02 0.132900 -4.105535e-02 0.132950 -4.105423e-02 0.133000 -4.105311e-02 0.133050 -4.105199e-02 0.133100 -4.105088e-02 0.133150 -4.104976e-02 0.133200 -4.104865e-02 0.133250 -4.104754e-02 0.133300 -4.104643e-02 0.133350 -4.104532e-02 0.133400 -4.104421e-02 0.133450 -4.104310e-02 0.133500 -4.104200e-02 0.133550 -4.104089e-02 0.133600 -4.103979e-02 0.133650 -4.103869e-02 0.133700 -4.103759e-02 0.133750 -4.103649e-02 0.133800 -4.103540e-02 0.133850 -4.103430e-02 0.133900 -4.103321e-02 0.133950 -4.103211e-02 0.134000 -4.103102e-02 0.134050 -4.102993e-02 0.134100 -4.102884e-02 0.134150 -4.102776e-02 0.134200 -4.102667e-02 0.134250 -4.102559e-02 0.134300 -4.102450e-02 0.134350 -4.102342e-02 0.134400 -4.102234e-02 0.134450 -4.102126e-02 0.134500 -4.102018e-02 0.134550 -4.101911e-02 0.134600 -4.101803e-02 0.134650 -4.101696e-02 0.134700 -4.101589e-02 0.134750 -4.101482e-02 0.134800 -4.101375e-02 0.134850 -4.101268e-02 0.134900 -4.101161e-02 0.134950 -4.101055e-02 0.135000 -4.100948e-02 0.135050 -4.100842e-02 0.135100 -4.100736e-02 0.135150 -4.100630e-02 0.135200 -4.100524e-02 0.135250 -4.100418e-02 0.135300 -4.100312e-02 0.135350 -4.100207e-02 0.135400 -4.100101e-02 0.135450 -4.099996e-02 0.135500 -4.099891e-02 0.135550 -4.099786e-02 0.135600 -4.099681e-02 0.135650 -4.099577e-02 0.135700 -4.099472e-02 0.135750 -4.099367e-02 0.135800 -4.099263e-02 0.135850 -4.099159e-02 0.135900 -4.099055e-02 0.135950 -4.098951e-02 0.136000 -4.098847e-02 0.136050 -4.098743e-02 0.136100 -4.098640e-02 0.136150 -4.098536e-02 0.136200 -4.098433e-02 0.136250 -4.098330e-02 0.136300 -4.098227e-02 0.136350 -4.098124e-02 0.136400 -4.098021e-02 0.136450 -4.097919e-02 0.136500 -4.097816e-02 0.136550 -4.097714e-02 0.136600 -4.097612e-02 0.136650 -4.097509e-02 0.136700 -4.097407e-02 0.136750 -4.097306e-02 0.136800 -4.097204e-02 0.136850 -4.097102e-02 0.136900 -4.097001e-02 0.136950 -4.096899e-02 0.137000 -4.096798e-02 0.137050 -4.096697e-02 0.137100 -4.096596e-02 0.137150 -4.096495e-02 0.137200 -4.096394e-02 0.137250 -4.096294e-02 0.137300 -4.096193e-02 0.137350 -4.096093e-02 0.137400 -4.095993e-02 0.137450 -4.095893e-02 0.137500 -4.095793e-02 0.137550 -4.095693e-02 0.137600 -4.095593e-02 0.137650 -4.095493e-02 0.137700 -4.095394e-02 0.137750 -4.095294e-02 0.137800 -4.095195e-02 0.137850 -4.095096e-02 0.137900 -4.094997e-02 0.137950 -4.094898e-02 0.138000 -4.094800e-02 0.138050 -4.094701e-02 0.138100 -4.094602e-02 0.138150 -4.094504e-02 0.138200 -4.094406e-02 0.138250 -4.094308e-02 0.138300 -4.094210e-02 0.138350 -4.094112e-02 0.138400 -4.094014e-02 0.138450 -4.093916e-02 0.138500 -4.093819e-02 0.138550 -4.093721e-02 0.138600 -4.093624e-02 0.138650 -4.093527e-02 0.138700 -4.093430e-02 0.138750 -4.093333e-02 0.138800 -4.093236e-02 0.138850 -4.093140e-02 0.138900 -4.093043e-02 0.138950 -4.092947e-02 0.139000 -4.092850e-02 0.139050 -4.092754e-02 0.139100 -4.092658e-02 0.139150 -4.092562e-02 0.139200 -4.092466e-02 0.139250 -4.092371e-02 0.139300 -4.092275e-02 0.139350 -4.092180e-02 0.139400 -4.092084e-02 0.139450 -4.091989e-02 0.139500 -4.091894e-02 0.139550 -4.091799e-02 0.139600 -4.091704e-02 0.139650 -4.091609e-02 0.139700 -4.091515e-02 0.139750 -4.091420e-02 0.139800 -4.091326e-02 0.139850 -4.091231e-02 0.139900 -4.091137e-02 0.139950 -4.091043e-02 0.140000 -4.090949e-02 0.140050 -4.090856e-02 0.140100 -4.090762e-02 0.140150 -4.090668e-02 0.140200 -4.090575e-02 0.140250 -4.090481e-02 0.140300 -4.090388e-02 0.140350 -4.090295e-02 0.140400 -4.090202e-02 0.140450 -4.090109e-02 0.140500 -4.090017e-02 0.140550 -4.089924e-02 0.140600 -4.089831e-02 0.140650 -4.089739e-02 0.140700 -4.089647e-02 0.140750 -4.089554e-02 0.140800 -4.089462e-02 0.140850 -4.089370e-02 0.140900 -4.089279e-02 0.140950 -4.089187e-02 0.141000 -4.089095e-02 0.141050 -4.089004e-02 0.141100 -4.088912e-02 0.141150 -4.088821e-02 0.141200 -4.088730e-02 0.141250 -4.088639e-02 0.141300 -4.088548e-02 0.141350 -4.088457e-02 0.141400 -4.088367e-02 0.141450 -4.088276e-02 0.141500 -4.088185e-02 0.141550 -4.088095e-02 0.141600 -4.088005e-02 0.141650 -4.087915e-02 0.141700 -4.087825e-02 0.141750 -4.087735e-02 0.141800 -4.087645e-02 0.141850 -4.087555e-02 0.141900 -4.087466e-02 0.141950 -4.087376e-02 0.142000 -4.087287e-02 0.142050 -4.087198e-02 0.142100 -4.087109e-02 0.142150 -4.087020e-02 0.142200 -4.086931e-02 0.142250 -4.086842e-02 0.142300 -4.086753e-02 0.142350 -4.086665e-02 0.142400 -4.086576e-02 0.142450 -4.086488e-02 0.142500 -4.086400e-02 0.142550 -4.086311e-02 0.142600 -4.086223e-02 0.142650 -4.086136e-02 0.142700 -4.086048e-02 0.142750 -4.085960e-02 0.142800 -4.085872e-02 0.142850 -4.085785e-02 0.142900 -4.085698e-02 0.142950 -4.085610e-02 0.143000 -4.085523e-02 0.143050 -4.085436e-02 0.143100 -4.085349e-02 0.143150 -4.085263e-02 0.143200 -4.085176e-02 0.143250 -4.085089e-02 0.143300 -4.085003e-02 0.143350 -4.084916e-02 0.143400 -4.084830e-02 0.143450 -4.084744e-02 0.143500 -4.084658e-02 0.143550 -4.084572e-02 0.143600 -4.084486e-02 0.143650 -4.084400e-02 0.143700 -4.084315e-02 0.143750 -4.084229e-02 0.143800 -4.084144e-02 0.143850 -4.084058e-02 0.143900 -4.083973e-02 0.143950 -4.083888e-02 0.144000 -4.083803e-02 0.144050 -4.083718e-02 0.144100 -4.083633e-02 0.144150 -4.083549e-02 0.144200 -4.083464e-02 0.144250 -4.083380e-02 0.144300 -4.083295e-02 0.144350 -4.083211e-02 0.144400 -4.083127e-02 0.144450 -4.083043e-02 0.144500 -4.082959e-02 0.144550 -4.082875e-02 0.144600 -4.082792e-02 0.144650 -4.082708e-02 0.144700 -4.082624e-02 0.144750 -4.082541e-02 0.144800 -4.082458e-02 0.144850 -4.082374e-02 0.144900 -4.082291e-02 0.144950 -4.082208e-02 0.145000 -4.082125e-02 0.145050 -4.082043e-02 0.145100 -4.081960e-02 0.145150 -4.081877e-02 0.145200 -4.081795e-02 0.145250 -4.081713e-02 0.145300 -4.081630e-02 0.145350 -4.081548e-02 0.145400 -4.081466e-02 0.145450 -4.081384e-02 0.145500 -4.081302e-02 0.145550 -4.081220e-02 0.145600 -4.081139e-02 0.145650 -4.081057e-02 0.145700 -4.080976e-02 0.145750 -4.080894e-02 0.145800 -4.080813e-02 0.145850 -4.080732e-02 0.145900 -4.080651e-02 0.145950 -4.080570e-02 0.146000 -4.080489e-02 0.146050 -4.080409e-02 0.146100 -4.080328e-02 0.146150 -4.080247e-02 0.146200 -4.080167e-02 0.146250 -4.080087e-02 0.146300 -4.080006e-02 0.146350 -4.079926e-02 0.146400 -4.079846e-02 0.146450 -4.079766e-02 0.146500 -4.079686e-02 0.146550 -4.079607e-02 0.146600 -4.079527e-02 0.146650 -4.079447e-02 0.146700 -4.079368e-02 0.146750 -4.079289e-02 0.146800 -4.079209e-02 0.146850 -4.079130e-02 0.146900 -4.079051e-02 0.146950 -4.078972e-02 0.147000 -4.078893e-02 0.147050 -4.078815e-02 0.147100 -4.078736e-02 0.147150 -4.078657e-02 0.147200 -4.078579e-02 0.147250 -4.078501e-02 0.147300 -4.078422e-02 0.147350 -4.078344e-02 0.147400 -4.078266e-02 0.147450 -4.078188e-02 0.147500 -4.078110e-02 0.147550 -4.078033e-02 0.147600 -4.077955e-02 0.147650 -4.077877e-02 0.147700 -4.077800e-02 0.147750 -4.077722e-02 0.147800 -4.077645e-02 0.147850 -4.077568e-02 0.147900 -4.077491e-02 0.147950 -4.077414e-02 0.148000 -4.077337e-02 0.148050 -4.077260e-02 0.148100 -4.077183e-02 0.148150 -4.077107e-02 0.148200 -4.077030e-02 0.148250 -4.076954e-02 0.148300 -4.076878e-02 0.148350 -4.076801e-02 0.148400 -4.076725e-02 0.148450 -4.076649e-02 0.148500 -4.076573e-02 0.148550 -4.076497e-02 0.148600 -4.076422e-02 0.148650 -4.076346e-02 0.148700 -4.076270e-02 0.148750 -4.076195e-02 0.148800 -4.076120e-02 0.148850 -4.076044e-02 0.148900 -4.075969e-02 0.148950 -4.075894e-02 0.149000 -4.075819e-02 0.149050 -4.075744e-02 0.149100 -4.075669e-02 0.149150 -4.075595e-02 0.149200 -4.075520e-02 0.149250 -4.075445e-02 0.149300 -4.075371e-02 0.149350 -4.075297e-02 0.149400 -4.075222e-02 0.149450 -4.075148e-02 0.149500 -4.075074e-02 0.149550 -4.075000e-02 0.149600 -4.074926e-02 0.149650 -4.074852e-02 0.149700 -4.074779e-02 0.149750 -4.074705e-02 0.149800 -4.074632e-02 0.149850 -4.074558e-02 0.149900 -4.074485e-02 0.149950 -4.074412e-02 0.150000 -4.074338e-02 0.150050 -4.074265e-02 0.150100 -4.074192e-02 0.150150 -4.074120e-02 0.150200 -4.074047e-02 0.150250 -4.073974e-02 0.150300 -4.073901e-02 0.150350 -4.073829e-02 0.150400 -4.073757e-02 0.150450 -4.073684e-02 0.150500 -4.073612e-02 0.150550 -4.073540e-02 0.150600 -4.073468e-02 0.150650 -4.073396e-02 0.150700 -4.073324e-02 0.150750 -4.073252e-02 0.150800 -4.073180e-02 0.150850 -4.073109e-02 0.150900 -4.073037e-02 0.150950 -4.072966e-02 0.151000 -4.072894e-02 0.151050 -4.072823e-02 0.151100 -4.072752e-02 0.151150 -4.072681e-02 0.151200 -4.072610e-02 0.151250 -4.072539e-02 0.151300 -4.072468e-02 0.151350 -4.072398e-02 0.151400 -4.072327e-02 0.151450 -4.072256e-02 0.151500 -4.072186e-02 0.151550 -4.072116e-02 0.151600 -4.072045e-02 0.151650 -4.071975e-02 0.151700 -4.071905e-02 0.151750 -4.071835e-02 0.151800 -4.071765e-02 0.151850 -4.071695e-02 0.151900 -4.071625e-02 0.151950 -4.071556e-02 0.152000 -4.071486e-02 0.152050 -4.071417e-02 0.152100 -4.071347e-02 0.152150 -4.071278e-02 0.152200 -4.071209e-02 0.152250 -4.071140e-02 0.152300 -4.071070e-02 0.152350 -4.071002e-02 0.152400 -4.070933e-02 0.152450 -4.070864e-02 0.152500 -4.070795e-02 0.152550 -4.070726e-02 0.152600 -4.070658e-02 0.152650 -4.070589e-02 0.152700 -4.070521e-02 0.152750 -4.070453e-02 0.152800 -4.070385e-02 0.152850 -4.070316e-02 0.152900 -4.070248e-02 0.152950 -4.070180e-02 0.153000 -4.070113e-02 0.153050 -4.070045e-02 0.153100 -4.069977e-02 0.153150 -4.069910e-02 0.153200 -4.069842e-02 0.153250 -4.069775e-02 0.153300 -4.069707e-02 0.153350 -4.069640e-02 0.153400 -4.069573e-02 0.153450 -4.069506e-02 0.153500 -4.069439e-02 0.153550 -4.069372e-02 0.153600 -4.069305e-02 0.153650 -4.069238e-02 0.153700 -4.069171e-02 0.153750 -4.069105e-02 0.153800 -4.069038e-02 0.153850 -4.068972e-02 0.153900 -4.068905e-02 0.153950 -4.068839e-02 0.154000 -4.068773e-02 0.154050 -4.068707e-02 0.154100 -4.068641e-02 0.154150 -4.068575e-02 0.154200 -4.068509e-02 0.154250 -4.068443e-02 0.154300 -4.068378e-02 0.154350 -4.068312e-02 0.154400 -4.068246e-02 0.154450 -4.068181e-02 0.154500 -4.068116e-02 0.154550 -4.068050e-02 0.154600 -4.067985e-02 0.154650 -4.067920e-02 0.154700 -4.067855e-02 0.154750 -4.067790e-02 0.154800 -4.067725e-02 0.154850 -4.067660e-02 0.154900 -4.067596e-02 0.154950 -4.067531e-02 0.155000 -4.067466e-02 0.155050 -4.067402e-02 0.155100 -4.067338e-02 0.155150 -4.067273e-02 0.155200 -4.067209e-02 0.155250 -4.067145e-02 0.155300 -4.067081e-02 0.155350 -4.067017e-02 0.155400 -4.066953e-02 0.155450 -4.066889e-02 0.155500 -4.066825e-02 0.155550 -4.066762e-02 0.155600 -4.066698e-02 0.155650 -4.066635e-02 0.155700 -4.066571e-02 0.155750 -4.066508e-02 0.155800 -4.066444e-02 0.155850 -4.066381e-02 0.155900 -4.066318e-02 0.155950 -4.066255e-02 0.156000 -4.066192e-02 0.156050 -4.066129e-02 0.156100 -4.066066e-02 0.156150 -4.066004e-02 0.156200 -4.065941e-02 0.156250 -4.065878e-02 0.156300 -4.065816e-02 0.156350 -4.065754e-02 0.156400 -4.065691e-02 0.156450 -4.065629e-02 0.156500 -4.065567e-02 0.156550 -4.065505e-02 0.156600 -4.065443e-02 0.156650 -4.065381e-02 0.156700 -4.065319e-02 0.156750 -4.065257e-02 0.156800 -4.065195e-02 0.156850 -4.065134e-02 0.156900 -4.065072e-02 0.156950 -4.065011e-02 0.157000 -4.064949e-02 0.157050 -4.064888e-02 0.157100 -4.064827e-02 0.157150 -4.064766e-02 0.157200 -4.064704e-02 0.157250 -4.064643e-02 0.157300 -4.064582e-02 0.157350 -4.064522e-02 0.157400 -4.064461e-02 0.157450 -4.064400e-02 0.157500 -4.064339e-02 0.157550 -4.064279e-02 0.157600 -4.064218e-02 0.157650 -4.064158e-02 0.157700 -4.064098e-02 0.157750 -4.064037e-02 0.157800 -4.063977e-02 0.157850 -4.063917e-02 0.157900 -4.063857e-02 0.157950 -4.063797e-02 0.158000 -4.063737e-02 0.158050 -4.063677e-02 0.158100 -4.063618e-02 0.158150 -4.063558e-02 0.158200 -4.063498e-02 0.158250 -4.063439e-02 0.158300 -4.063379e-02 0.158350 -4.063320e-02 0.158400 -4.063261e-02 0.158450 -4.063201e-02 0.158500 -4.063142e-02 0.158550 -4.063083e-02 0.158600 -4.063024e-02 0.158650 -4.062965e-02 0.158700 -4.062906e-02 0.158750 -4.062848e-02 0.158800 -4.062789e-02 0.158850 -4.062730e-02 0.158900 -4.062672e-02 0.158950 -4.062613e-02 0.159000 -4.062555e-02 0.159050 -4.062497e-02 0.159100 -4.062438e-02 0.159150 -4.062380e-02 0.159200 -4.062322e-02 0.159250 -4.062264e-02 0.159300 -4.062206e-02 0.159350 -4.062148e-02 0.159400 -4.062090e-02 0.159450 -4.062032e-02 0.159500 -4.061975e-02 0.159550 -4.061917e-02 0.159600 -4.061860e-02 0.159650 -4.061802e-02 0.159700 -4.061745e-02 0.159750 -4.061687e-02 0.159800 -4.061630e-02 0.159850 -4.061573e-02 0.159900 -4.061516e-02 0.159950 -4.061459e-02 0.160000 -4.061402e-02 0.160050 -4.061345e-02 0.160100 -4.061288e-02 0.160150 -4.061231e-02 0.160200 -4.061175e-02 0.160250 -4.061118e-02 0.160300 -4.061062e-02 0.160350 -4.061005e-02 0.160400 -4.060949e-02 0.160450 -4.060892e-02 0.160500 -4.060836e-02 0.160550 -4.060780e-02 0.160600 -4.060724e-02 0.160650 -4.060668e-02 0.160700 -4.060612e-02 0.160750 -4.060556e-02 0.160800 -4.060500e-02 0.160850 -4.060444e-02 0.160900 -4.060389e-02 0.160950 -4.060333e-02 0.161000 -4.060277e-02 0.161050 -4.060222e-02 0.161100 -4.060166e-02 0.161150 -4.060111e-02 0.161200 -4.060056e-02 0.161250 -4.060001e-02 0.161300 -4.059945e-02 0.161350 -4.059890e-02 0.161400 -4.059835e-02 0.161450 -4.059780e-02 0.161500 -4.059725e-02 0.161550 -4.059671e-02 0.161600 -4.059616e-02 0.161650 -4.059561e-02 0.161700 -4.059507e-02 0.161750 -4.059452e-02 0.161800 -4.059398e-02 0.161850 -4.059343e-02 0.161900 -4.059289e-02 0.161950 -4.059235e-02 0.162000 -4.059181e-02 0.162050 -4.059126e-02 0.162100 -4.059072e-02 0.162150 -4.059018e-02 0.162200 -4.058964e-02 0.162250 -4.058911e-02 0.162300 -4.058857e-02 0.162350 -4.058803e-02 0.162400 -4.058749e-02 0.162450 -4.058696e-02 0.162500 -4.058642e-02 0.162550 -4.058589e-02 0.162600 -4.058535e-02 0.162650 -4.058482e-02 0.162700 -4.058429e-02 0.162750 -4.058376e-02 0.162800 -4.058323e-02 0.162850 -4.058270e-02 0.162900 -4.058217e-02 0.162950 -4.058164e-02 0.163000 -4.058111e-02 0.163050 -4.058058e-02 0.163100 -4.058005e-02 0.163150 -4.057953e-02 0.163200 -4.057900e-02 0.163250 -4.057848e-02 0.163300 -4.057795e-02 0.163350 -4.057743e-02 0.163400 -4.057690e-02 0.163450 -4.057638e-02 0.163500 -4.057586e-02 0.163550 -4.057534e-02 0.163600 -4.057482e-02 0.163650 -4.057430e-02 0.163700 -4.057378e-02 0.163750 -4.057326e-02 0.163800 -4.057274e-02 0.163850 -4.057222e-02 0.163900 -4.057171e-02 0.163950 -4.057119e-02 0.164000 -4.057067e-02 0.164050 -4.057016e-02 0.164100 -4.056965e-02 0.164150 -4.056913e-02 0.164200 -4.056862e-02 0.164250 -4.056811e-02 0.164300 -4.056760e-02 0.164350 -4.056708e-02 0.164400 -4.056657e-02 0.164450 -4.056606e-02 0.164500 -4.056556e-02 0.164550 -4.056505e-02 0.164600 -4.056454e-02 0.164650 -4.056403e-02 0.164700 -4.056353e-02 0.164750 -4.056302e-02 0.164800 -4.056251e-02 0.164850 -4.056201e-02 0.164900 -4.056151e-02 0.164950 -4.056100e-02 0.165000 -4.056050e-02 0.165050 -4.056000e-02 0.165100 -4.055950e-02 0.165150 -4.055899e-02 0.165200 -4.055849e-02 0.165250 -4.055799e-02 0.165300 -4.055750e-02 0.165350 -4.055700e-02 0.165400 -4.055650e-02 0.165450 -4.055600e-02 0.165500 -4.055551e-02 0.165550 -4.055501e-02 0.165600 -4.055452e-02 0.165650 -4.055402e-02 0.165700 -4.055353e-02 0.165750 -4.055303e-02 0.165800 -4.055254e-02 0.165850 -4.055205e-02 0.165900 -4.055156e-02 0.165950 -4.055107e-02 0.166000 -4.055058e-02 0.166050 -4.055009e-02 0.166100 -4.054960e-02 0.166150 -4.054911e-02 0.166200 -4.054862e-02 0.166250 -4.054813e-02 0.166300 -4.054765e-02 0.166350 -4.054716e-02 0.166400 -4.054667e-02 0.166450 -4.054619e-02 0.166500 -4.054571e-02 0.166550 -4.054522e-02 0.166600 -4.054474e-02 0.166650 -4.054426e-02 0.166700 -4.054377e-02 0.166750 -4.054329e-02 0.166800 -4.054281e-02 0.166850 -4.054233e-02 0.166900 -4.054185e-02 0.166950 -4.054137e-02 0.167000 -4.054090e-02 0.167050 -4.054042e-02 0.167100 -4.053994e-02 0.167150 -4.053946e-02 0.167200 -4.053899e-02 0.167250 -4.053851e-02 0.167300 -4.053804e-02 0.167350 -4.053756e-02 0.167400 -4.053709e-02 0.167450 -4.053662e-02 0.167500 -4.053615e-02 0.167550 -4.053567e-02 0.167600 -4.053520e-02 0.167650 -4.053473e-02 0.167700 -4.053426e-02 0.167750 -4.053379e-02 0.167800 -4.053332e-02 0.167850 -4.053286e-02 0.167900 -4.053239e-02 0.167950 -4.053192e-02 0.168000 -4.053146e-02 0.168050 -4.053099e-02 0.168100 -4.053052e-02 0.168150 -4.053006e-02 0.168200 -4.052960e-02 0.168250 -4.052913e-02 0.168300 -4.052867e-02 0.168350 -4.052821e-02 0.168400 -4.052775e-02 0.168450 -4.052728e-02 0.168500 -4.052682e-02 0.168550 -4.052636e-02 0.168600 -4.052590e-02 0.168650 -4.052544e-02 0.168700 -4.052499e-02 0.168750 -4.052453e-02 0.168800 -4.052407e-02 0.168850 -4.052361e-02 0.168900 -4.052316e-02 0.168950 -4.052270e-02 0.169000 -4.052225e-02 0.169050 -4.052179e-02 0.169100 -4.052134e-02 0.169150 -4.052089e-02 0.169200 -4.052043e-02 0.169250 -4.051998e-02 0.169300 -4.051953e-02 0.169350 -4.051908e-02 0.169400 -4.051863e-02 0.169450 -4.051818e-02 0.169500 -4.051773e-02 0.169550 -4.051728e-02 0.169600 -4.051683e-02 0.169650 -4.051639e-02 0.169700 -4.051594e-02 0.169750 -4.051549e-02 0.169800 -4.051505e-02 0.169850 -4.051460e-02 0.169900 -4.051416e-02 0.169950 -4.051371e-02 0.170000 -4.051327e-02 0.170050 -4.051283e-02 0.170100 -4.051238e-02 0.170150 -4.051194e-02 0.170200 -4.051150e-02 0.170250 -4.051106e-02 0.170300 -4.051062e-02 0.170350 -4.051018e-02 0.170400 -4.050974e-02 0.170450 -4.050930e-02 0.170500 -4.050886e-02 0.170550 -4.050842e-02 0.170600 -4.050799e-02 0.170650 -4.050755e-02 0.170700 -4.050711e-02 0.170750 -4.050668e-02 0.170800 -4.050624e-02 0.170850 -4.050581e-02 0.170900 -4.050538e-02 0.170950 -4.050494e-02 0.171000 -4.050451e-02 0.171050 -4.050408e-02 0.171100 -4.050365e-02 0.171150 -4.050322e-02 0.171200 -4.050278e-02 0.171250 -4.050235e-02 0.171300 -4.050193e-02 0.171350 -4.050150e-02 0.171400 -4.050107e-02 0.171450 -4.050064e-02 0.171500 -4.050021e-02 0.171550 -4.049979e-02 0.171600 -4.049936e-02 0.171650 -4.049893e-02 0.171700 -4.049851e-02 0.171750 -4.049808e-02 0.171800 -4.049766e-02 0.171850 -4.049724e-02 0.171900 -4.049681e-02 0.171950 -4.049639e-02 0.172000 -4.049597e-02 0.172050 -4.049555e-02 0.172100 -4.049513e-02 0.172150 -4.049471e-02 0.172200 -4.049429e-02 0.172250 -4.049387e-02 0.172300 -4.049345e-02 0.172350 -4.049303e-02 0.172400 -4.049261e-02 0.172450 -4.049219e-02 0.172500 -4.049178e-02 0.172550 -4.049136e-02 0.172600 -4.049095e-02 0.172650 -4.049053e-02 0.172700 -4.049012e-02 0.172750 -4.048970e-02 0.172800 -4.048929e-02 0.172850 -4.048887e-02 0.172900 -4.048846e-02 0.172950 -4.048805e-02 0.173000 -4.048764e-02 0.173050 -4.048723e-02 0.173100 -4.048682e-02 0.173150 -4.048641e-02 0.173200 -4.048600e-02 0.173250 -4.048559e-02 0.173300 -4.048518e-02 0.173350 -4.048477e-02 0.173400 -4.048436e-02 0.173450 -4.048396e-02 0.173500 -4.048355e-02 0.173550 -4.048314e-02 0.173600 -4.048274e-02 0.173650 -4.048233e-02 0.173700 -4.048193e-02 0.173750 -4.048152e-02 0.173800 -4.048112e-02 0.173850 -4.048072e-02 0.173900 -4.048032e-02 0.173950 -4.047991e-02 0.174000 -4.047951e-02 0.174050 -4.047911e-02 0.174100 -4.047871e-02 0.174150 -4.047831e-02 0.174200 -4.047791e-02 0.174250 -4.047751e-02 0.174300 -4.047711e-02 0.174350 -4.047672e-02 0.174400 -4.047632e-02 0.174450 -4.047592e-02 0.174500 -4.047553e-02 0.174550 -4.047513e-02 0.174600 -4.047473e-02 0.174650 -4.047434e-02 0.174700 -4.047394e-02 0.174750 -4.047355e-02 0.174800 -4.047316e-02 0.174850 -4.047276e-02 0.174900 -4.047237e-02 0.174950 -4.047198e-02 0.175000 -4.047159e-02 0.175050 -4.047120e-02 0.175100 -4.047081e-02 0.175150 -4.047042e-02 0.175200 -4.047003e-02 0.175250 -4.046964e-02 0.175300 -4.046925e-02 0.175350 -4.046886e-02 0.175400 -4.046847e-02 0.175450 -4.046809e-02 0.175500 -4.046770e-02 0.175550 -4.046731e-02 0.175600 -4.046693e-02 0.175650 -4.046654e-02 0.175700 -4.046616e-02 0.175750 -4.046577e-02 0.175800 -4.046539e-02 0.175850 -4.046501e-02 0.175900 -4.046462e-02 0.175950 -4.046424e-02 0.176000 -4.046386e-02 0.176050 -4.046348e-02 0.176100 -4.046310e-02 0.176150 -4.046272e-02 0.176200 -4.046234e-02 0.176250 -4.046196e-02 0.176300 -4.046158e-02 0.176350 -4.046120e-02 0.176400 -4.046082e-02 0.176450 -4.046044e-02 0.176500 -4.046007e-02 0.176550 -4.045969e-02 0.176600 -4.045931e-02 0.176650 -4.045894e-02 0.176700 -4.045856e-02 0.176750 -4.045819e-02 0.176800 -4.045781e-02 0.176850 -4.045744e-02 0.176900 -4.045707e-02 0.176950 -4.045669e-02 0.177000 -4.045632e-02 0.177050 -4.045595e-02 0.177100 -4.045558e-02 0.177150 -4.045521e-02 0.177200 -4.045484e-02 0.177250 -4.045447e-02 0.177300 -4.045410e-02 0.177350 -4.045373e-02 0.177400 -4.045336e-02 0.177450 -4.045299e-02 0.177500 -4.045262e-02 0.177550 -4.045225e-02 0.177600 -4.045189e-02 0.177650 -4.045152e-02 0.177700 -4.045115e-02 0.177750 -4.045079e-02 0.177800 -4.045042e-02 0.177850 -4.045006e-02 0.177900 -4.044970e-02 0.177950 -4.044933e-02 0.178000 -4.044897e-02 0.178050 -4.044861e-02 0.178100 -4.044824e-02 0.178150 -4.044788e-02 0.178200 -4.044752e-02 0.178250 -4.044716e-02 0.178300 -4.044680e-02 0.178350 -4.044644e-02 0.178400 -4.044608e-02 0.178450 -4.044572e-02 0.178500 -4.044536e-02 0.178550 -4.044500e-02 0.178600 -4.044464e-02 0.178650 -4.044429e-02 0.178700 -4.044393e-02 0.178750 -4.044357e-02 0.178800 -4.044322e-02 0.178850 -4.044286e-02 0.178900 -4.044251e-02 0.178950 -4.044215e-02 0.179000 -4.044180e-02 0.179050 -4.044144e-02 0.179100 -4.044109e-02 0.179150 -4.044074e-02 0.179200 -4.044039e-02 0.179250 -4.044003e-02 0.179300 -4.043968e-02 0.179350 -4.043933e-02 0.179400 -4.043898e-02 0.179450 -4.043863e-02 0.179500 -4.043828e-02 0.179550 -4.043793e-02 0.179600 -4.043758e-02 0.179650 -4.043723e-02 0.179700 -4.043688e-02 0.179750 -4.043654e-02 0.179800 -4.043619e-02 0.179850 -4.043584e-02 0.179900 -4.043550e-02 0.179950 -4.043515e-02 0.180000 -4.043480e-02 0.180050 -4.043446e-02 0.180100 -4.043411e-02 0.180150 -4.043377e-02 0.180200 -4.043343e-02 0.180250 -4.043308e-02 0.180300 -4.043274e-02 0.180350 -4.043240e-02 0.180400 -4.043206e-02 0.180450 -4.043171e-02 0.180500 -4.043137e-02 0.180550 -4.043103e-02 0.180600 -4.043069e-02 0.180650 -4.043035e-02 0.180700 -4.043001e-02 0.180750 -4.042967e-02 0.180800 -4.042933e-02 0.180850 -4.042900e-02 0.180900 -4.042866e-02 0.180950 -4.042832e-02 0.181000 -4.042798e-02 0.181050 -4.042765e-02 0.181100 -4.042731e-02 0.181150 -4.042697e-02 0.181200 -4.042664e-02 0.181250 -4.042630e-02 0.181300 -4.042597e-02 0.181350 -4.042564e-02 0.181400 -4.042530e-02 0.181450 -4.042497e-02 0.181500 -4.042464e-02 0.181550 -4.042430e-02 0.181600 -4.042397e-02 0.181650 -4.042364e-02 0.181700 -4.042331e-02 0.181750 -4.042298e-02 0.181800 -4.042265e-02 0.181850 -4.042232e-02 0.181900 -4.042199e-02 0.181950 -4.042166e-02 0.182000 -4.042133e-02 0.182050 -4.042100e-02 0.182100 -4.042067e-02 0.182150 -4.042035e-02 0.182200 -4.042002e-02 0.182250 -4.041969e-02 0.182300 -4.041937e-02 0.182350 -4.041904e-02 0.182400 -4.041872e-02 0.182450 -4.041839e-02 0.182500 -4.041807e-02 0.182550 -4.041774e-02 0.182600 -4.041742e-02 0.182650 -4.041710e-02 0.182700 -4.041677e-02 0.182750 -4.041645e-02 0.182800 -4.041613e-02 0.182850 -4.041581e-02 0.182900 -4.041548e-02 0.182950 -4.041516e-02 0.183000 -4.041484e-02 0.183050 -4.041452e-02 0.183100 -4.041420e-02 0.183150 -4.041388e-02 0.183200 -4.041356e-02 0.183250 -4.041325e-02 0.183300 -4.041293e-02 0.183350 -4.041261e-02 0.183400 -4.041229e-02 0.183450 -4.041198e-02 0.183500 -4.041166e-02 0.183550 -4.041134e-02 0.183600 -4.041103e-02 0.183650 -4.041071e-02 0.183700 -4.041040e-02 0.183750 -4.041008e-02 0.183800 -4.040977e-02 0.183850 -4.040945e-02 0.183900 -4.040914e-02 0.183950 -4.040883e-02 0.184000 -4.040851e-02 0.184050 -4.040820e-02 0.184100 -4.040789e-02 0.184150 -4.040758e-02 0.184200 -4.040727e-02 0.184250 -4.040696e-02 0.184300 -4.040665e-02 0.184350 -4.040634e-02 0.184400 -4.040603e-02 0.184450 -4.040572e-02 0.184500 -4.040541e-02 0.184550 -4.040510e-02 0.184600 -4.040479e-02 0.184650 -4.040449e-02 0.184700 -4.040418e-02 0.184750 -4.040387e-02 0.184800 -4.040357e-02 0.184850 -4.040326e-02 0.184900 -4.040295e-02 0.184950 -4.040265e-02 0.185000 -4.040234e-02 0.185050 -4.040204e-02 0.185100 -4.040173e-02 0.185150 -4.040143e-02 0.185200 -4.040113e-02 0.185250 -4.040082e-02 0.185300 -4.040052e-02 0.185350 -4.040022e-02 0.185400 -4.039992e-02 0.185450 -4.039962e-02 0.185500 -4.039931e-02 0.185550 -4.039901e-02 0.185600 -4.039871e-02 0.185650 -4.039841e-02 0.185700 -4.039811e-02 0.185750 -4.039781e-02 0.185800 -4.039752e-02 0.185850 -4.039722e-02 0.185900 -4.039692e-02 0.185950 -4.039662e-02 0.186000 -4.039632e-02 0.186050 -4.039603e-02 0.186100 -4.039573e-02 0.186150 -4.039543e-02 0.186200 -4.039514e-02 0.186250 -4.039484e-02 0.186300 -4.039455e-02 0.186350 -4.039425e-02 0.186400 -4.039396e-02 0.186450 -4.039366e-02 0.186500 -4.039337e-02 0.186550 -4.039308e-02 0.186600 -4.039278e-02 0.186650 -4.039249e-02 0.186700 -4.039220e-02 0.186750 -4.039191e-02 0.186800 -4.039162e-02 0.186850 -4.039132e-02 0.186900 -4.039103e-02 0.186950 -4.039074e-02 0.187000 -4.039045e-02 0.187050 -4.039016e-02 0.187100 -4.038987e-02 0.187150 -4.038958e-02 0.187200 -4.038930e-02 0.187250 -4.038901e-02 0.187300 -4.038872e-02 0.187350 -4.038843e-02 0.187400 -4.038815e-02 0.187450 -4.038786e-02 0.187500 -4.038757e-02 0.187550 -4.038729e-02 0.187600 -4.038700e-02 0.187650 -4.038671e-02 0.187700 -4.038643e-02 0.187750 -4.038615e-02 0.187800 -4.038586e-02 0.187850 -4.038558e-02 0.187900 -4.038529e-02 0.187950 -4.038501e-02 0.188000 -4.038473e-02 0.188050 -4.038444e-02 0.188100 -4.038416e-02 0.188150 -4.038388e-02 0.188200 -4.038360e-02 0.188250 -4.038332e-02 0.188300 -4.038304e-02 0.188350 -4.038276e-02 0.188400 -4.038248e-02 0.188450 -4.038220e-02 0.188500 -4.038192e-02 0.188550 -4.038164e-02 0.188600 -4.038136e-02 0.188650 -4.038108e-02 0.188700 -4.038080e-02 0.188750 -4.038053e-02 0.188800 -4.038025e-02 0.188850 -4.037997e-02 0.188900 -4.037969e-02 0.188950 -4.037942e-02 0.189000 -4.037914e-02 0.189050 -4.037887e-02 0.189100 -4.037859e-02 0.189150 -4.037832e-02 0.189200 -4.037804e-02 0.189250 -4.037777e-02 0.189300 -4.037749e-02 0.189350 -4.037722e-02 0.189400 -4.037695e-02 0.189450 -4.037667e-02 0.189500 -4.037640e-02 0.189550 -4.037613e-02 0.189600 -4.037586e-02 0.189650 -4.037559e-02 0.189700 -4.037532e-02 0.189750 -4.037505e-02 0.189800 -4.037477e-02 0.189850 -4.037450e-02 0.189900 -4.037423e-02 0.189950 -4.037397e-02 0.190000 -4.037370e-02 0.190050 -4.037343e-02 0.190100 -4.037316e-02 0.190150 -4.037289e-02 0.190200 -4.037262e-02 0.190250 -4.037236e-02 0.190300 -4.037209e-02 0.190350 -4.037182e-02 0.190400 -4.037156e-02 0.190450 -4.037129e-02 0.190500 -4.037102e-02 0.190550 -4.037076e-02 0.190600 -4.037049e-02 0.190650 -4.037023e-02 0.190700 -4.036996e-02 0.190750 -4.036970e-02 0.190800 -4.036944e-02 0.190850 -4.036917e-02 0.190900 -4.036891e-02 0.190950 -4.036865e-02 0.191000 -4.036838e-02 0.191050 -4.036812e-02 0.191100 -4.036786e-02 0.191150 -4.036760e-02 0.191200 -4.036734e-02 0.191250 -4.036708e-02 0.191300 -4.036682e-02 0.191350 -4.036656e-02 0.191400 -4.036630e-02 0.191450 -4.036604e-02 0.191500 -4.036578e-02 0.191550 -4.036552e-02 0.191600 -4.036526e-02 0.191650 -4.036500e-02 0.191700 -4.036474e-02 0.191750 -4.036449e-02 0.191800 -4.036423e-02 0.191850 -4.036397e-02 0.191900 -4.036372e-02 0.191950 -4.036346e-02 0.192000 -4.036320e-02 0.192050 -4.036295e-02 0.192100 -4.036269e-02 0.192150 -4.036244e-02 0.192200 -4.036218e-02 0.192250 -4.036193e-02 0.192300 -4.036167e-02 0.192350 -4.036142e-02 0.192400 -4.036117e-02 0.192450 -4.036091e-02 0.192500 -4.036066e-02 0.192550 -4.036041e-02 0.192600 -4.036016e-02 0.192650 -4.035990e-02 0.192700 -4.035965e-02 0.192750 -4.035940e-02 0.192800 -4.035915e-02 0.192850 -4.035890e-02 0.192900 -4.035865e-02 0.192950 -4.035840e-02 0.193000 -4.035815e-02 0.193050 -4.035790e-02 0.193100 -4.035765e-02 0.193150 -4.035740e-02 0.193200 -4.035715e-02 0.193250 -4.035691e-02 0.193300 -4.035666e-02 0.193350 -4.035641e-02 0.193400 -4.035616e-02 0.193450 -4.035592e-02 0.193500 -4.035567e-02 0.193550 -4.035542e-02 0.193600 -4.035518e-02 0.193650 -4.035493e-02 0.193700 -4.035469e-02 0.193750 -4.035444e-02 0.193800 -4.035420e-02 0.193850 -4.035395e-02 0.193900 -4.035371e-02 0.193950 -4.035347e-02 0.194000 -4.035322e-02 0.194050 -4.035298e-02 0.194100 -4.035274e-02 0.194150 -4.035249e-02 0.194200 -4.035225e-02 0.194250 -4.035201e-02 0.194300 -4.035177e-02 0.194350 -4.035153e-02 0.194400 -4.035128e-02 0.194450 -4.035104e-02 0.194500 -4.035080e-02 0.194550 -4.035056e-02 0.194600 -4.035032e-02 0.194650 -4.035008e-02 0.194700 -4.034984e-02 0.194750 -4.034961e-02 0.194800 -4.034937e-02 0.194850 -4.034913e-02 0.194900 -4.034889e-02 0.194950 -4.034865e-02 0.195000 -4.034842e-02 0.195050 -4.034818e-02 0.195100 -4.034794e-02 0.195150 -4.034770e-02 0.195200 -4.034747e-02 0.195250 -4.034723e-02 0.195300 -4.034700e-02 0.195350 -4.034676e-02 0.195400 -4.034653e-02 0.195450 -4.034629e-02 0.195500 -4.034606e-02 0.195550 -4.034582e-02 0.195600 -4.034559e-02 0.195650 -4.034535e-02 0.195700 -4.034512e-02 0.195750 -4.034489e-02 0.195800 -4.034466e-02 0.195850 -4.034442e-02 0.195900 -4.034419e-02 0.195950 -4.034396e-02 0.196000 -4.034373e-02 0.196050 -4.034350e-02 0.196100 -4.034327e-02 0.196150 -4.034303e-02 0.196200 -4.034280e-02 0.196250 -4.034257e-02 0.196300 -4.034234e-02 0.196350 -4.034211e-02 0.196400 -4.034188e-02 0.196450 -4.034166e-02 0.196500 -4.034143e-02 0.196550 -4.034120e-02 0.196600 -4.034097e-02 0.196650 -4.034074e-02 0.196700 -4.034052e-02 0.196750 -4.034029e-02 0.196800 -4.034006e-02 0.196850 -4.033983e-02 0.196900 -4.033961e-02 0.196950 -4.033938e-02 0.197000 -4.033916e-02 0.197050 -4.033893e-02 0.197100 -4.033870e-02 0.197150 -4.033848e-02 0.197200 -4.033825e-02 0.197250 -4.033803e-02 0.197300 -4.033781e-02 0.197350 -4.033758e-02 0.197400 -4.033736e-02 0.197450 -4.033713e-02 0.197500 -4.033691e-02 0.197550 -4.033669e-02 0.197600 -4.033647e-02 0.197650 -4.033624e-02 0.197700 -4.033602e-02 0.197750 -4.033580e-02 0.197800 -4.033558e-02 0.197850 -4.033536e-02 0.197900 -4.033514e-02 0.197950 -4.033492e-02 0.198000 -4.033470e-02 0.198050 -4.033448e-02 0.198100 -4.033426e-02 0.198150 -4.033404e-02 0.198200 -4.033382e-02 0.198250 -4.033360e-02 0.198300 -4.033338e-02 0.198350 -4.033316e-02 0.198400 -4.033294e-02 0.198450 -4.033273e-02 0.198500 -4.033251e-02 0.198550 -4.033229e-02 0.198600 -4.033207e-02 0.198650 -4.033186e-02 0.198700 -4.033164e-02 0.198750 -4.033142e-02 0.198800 -4.033121e-02 0.198850 -4.033099e-02 0.198900 -4.033078e-02 0.198950 -4.033056e-02 0.199000 -4.033035e-02 0.199050 -4.033013e-02 0.199100 -4.032992e-02 0.199150 -4.032970e-02 0.199200 -4.032949e-02 0.199250 -4.032928e-02 0.199300 -4.032906e-02 0.199350 -4.032885e-02 0.199400 -4.032864e-02 0.199450 -4.032843e-02 0.199500 -4.032821e-02 0.199550 -4.032800e-02 0.199600 -4.032779e-02 0.199650 -4.032758e-02 0.199700 -4.032737e-02 0.199750 -4.032716e-02 0.199800 -4.032695e-02 0.199850 -4.032673e-02 0.199900 -4.032652e-02 0.199950 -4.032631e-02 0.200000 -4.032611e-02 0.200050 -4.032590e-02 0.200100 -4.032569e-02 0.200150 -4.032548e-02 0.200200 -4.032527e-02 0.200250 -4.032506e-02 0.200300 -4.032485e-02 0.200350 -4.032465e-02 0.200400 -4.032444e-02 0.200450 -4.032423e-02 0.200500 -4.032402e-02 0.200550 -4.032382e-02 0.200600 -4.032361e-02 0.200650 -4.032340e-02 0.200700 -4.032320e-02 0.200750 -4.032299e-02 0.200800 -4.032279e-02 0.200850 -4.032258e-02 0.200900 -4.032238e-02 0.200950 -4.032217e-02 0.201000 -4.032197e-02 0.201050 -4.032176e-02 0.201100 -4.032156e-02 0.201150 -4.032136e-02 0.201200 -4.032115e-02 0.201250 -4.032095e-02 0.201300 -4.032075e-02 0.201350 -4.032054e-02 0.201400 -4.032034e-02 0.201450 -4.032014e-02 0.201500 -4.031994e-02 0.201550 -4.031974e-02 0.201600 -4.031954e-02 0.201650 -4.031933e-02 0.201700 -4.031913e-02 0.201750 -4.031893e-02 0.201800 -4.031873e-02 0.201850 -4.031853e-02 0.201900 -4.031833e-02 0.201950 -4.031813e-02 0.202000 -4.031793e-02 0.202050 -4.031773e-02 0.202100 -4.031754e-02 0.202150 -4.031734e-02 0.202200 -4.031714e-02 0.202250 -4.031694e-02 0.202300 -4.031674e-02 0.202350 -4.031654e-02 0.202400 -4.031635e-02 0.202450 -4.031615e-02 0.202500 -4.031595e-02 0.202550 -4.031576e-02 0.202600 -4.031556e-02 0.202650 -4.031536e-02 0.202700 -4.031517e-02 0.202750 -4.031497e-02 0.202800 -4.031478e-02 0.202850 -4.031458e-02 0.202900 -4.031439e-02 0.202950 -4.031419e-02 0.203000 -4.031400e-02 0.203050 -4.031380e-02 0.203100 -4.031361e-02 0.203150 -4.031342e-02 0.203200 -4.031322e-02 0.203250 -4.031303e-02 0.203300 -4.031284e-02 0.203350 -4.031264e-02 0.203400 -4.031245e-02 0.203450 -4.031226e-02 0.203500 -4.031207e-02 0.203550 -4.031188e-02 0.203600 -4.031168e-02 0.203650 -4.031149e-02 0.203700 -4.031130e-02 0.203750 -4.031111e-02 0.203800 -4.031092e-02 0.203850 -4.031073e-02 0.203900 -4.031054e-02 0.203950 -4.031035e-02 0.204000 -4.031016e-02 0.204050 -4.030997e-02 0.204100 -4.030978e-02 0.204150 -4.030959e-02 0.204200 -4.030940e-02 0.204250 -4.030922e-02 0.204300 -4.030903e-02 0.204350 -4.030884e-02 0.204400 -4.030865e-02 0.204450 -4.030846e-02 0.204500 -4.030828e-02 0.204550 -4.030809e-02 0.204600 -4.030790e-02 0.204650 -4.030772e-02 0.204700 -4.030753e-02 0.204750 -4.030734e-02 0.204800 -4.030716e-02 0.204850 -4.030697e-02 0.204900 -4.030679e-02 0.204950 -4.030660e-02 0.205000 -4.030642e-02 0.205050 -4.030623e-02 0.205100 -4.030605e-02 0.205150 -4.030586e-02 0.205200 -4.030568e-02 0.205250 -4.030550e-02 0.205300 -4.030531e-02 0.205350 -4.030513e-02 0.205400 -4.030495e-02 0.205450 -4.030476e-02 0.205500 -4.030458e-02 0.205550 -4.030440e-02 0.205600 -4.030421e-02 0.205650 -4.030403e-02 0.205700 -4.030385e-02 0.205750 -4.030367e-02 0.205800 -4.030349e-02 0.205850 -4.030331e-02 0.205900 -4.030313e-02 0.205950 -4.030295e-02 0.206000 -4.030277e-02 0.206050 -4.030259e-02 0.206100 -4.030241e-02 0.206150 -4.030223e-02 0.206200 -4.030205e-02 0.206250 -4.030187e-02 0.206300 -4.030169e-02 0.206350 -4.030151e-02 0.206400 -4.030133e-02 0.206450 -4.030115e-02 0.206500 -4.030097e-02 0.206550 -4.030080e-02 0.206600 -4.030062e-02 0.206650 -4.030044e-02 0.206700 -4.030026e-02 0.206750 -4.030009e-02 0.206800 -4.029991e-02 0.206850 -4.029973e-02 0.206900 -4.029956e-02 0.206950 -4.029938e-02 0.207000 -4.029920e-02 0.207050 -4.029903e-02 0.207100 -4.029885e-02 0.207150 -4.029868e-02 0.207200 -4.029850e-02 0.207250 -4.029833e-02 0.207300 -4.029815e-02 0.207350 -4.029798e-02 0.207400 -4.029781e-02 0.207450 -4.029763e-02 0.207500 -4.029746e-02 0.207550 -4.029728e-02 0.207600 -4.029711e-02 0.207650 -4.029694e-02 0.207700 -4.029676e-02 0.207750 -4.029659e-02 0.207800 -4.029642e-02 0.207850 -4.029625e-02 0.207900 -4.029608e-02 0.207950 -4.029590e-02 0.208000 -4.029573e-02 0.208050 -4.029556e-02 0.208100 -4.029539e-02 0.208150 -4.029522e-02 0.208200 -4.029505e-02 0.208250 -4.029488e-02 0.208300 -4.029471e-02 0.208350 -4.029454e-02 0.208400 -4.029437e-02 0.208450 -4.029420e-02 0.208500 -4.029403e-02 0.208550 -4.029386e-02 0.208600 -4.029369e-02 0.208650 -4.029352e-02 0.208700 -4.029335e-02 0.208750 -4.029318e-02 0.208800 -4.029302e-02 0.208850 -4.029285e-02 0.208900 -4.029268e-02 0.208950 -4.029251e-02 0.209000 -4.029234e-02 0.209050 -4.029218e-02 0.209100 -4.029201e-02 0.209150 -4.029184e-02 0.209200 -4.029168e-02 0.209250 -4.029151e-02 0.209300 -4.029135e-02 0.209350 -4.029118e-02 0.209400 -4.029101e-02 0.209450 -4.029085e-02 0.209500 -4.029068e-02 0.209550 -4.029052e-02 0.209600 -4.029035e-02 0.209650 -4.029019e-02 0.209700 -4.029002e-02 0.209750 -4.028986e-02 0.209800 -4.028970e-02 0.209850 -4.028953e-02 0.209900 -4.028937e-02 0.209950 -4.028920e-02 0.210000 -4.028904e-02 0.210050 -4.028888e-02 0.210100 -4.028872e-02 0.210150 -4.028855e-02 0.210200 -4.028839e-02 0.210250 -4.028823e-02 0.210300 -4.028807e-02 0.210350 -4.028790e-02 0.210400 -4.028774e-02 0.210450 -4.028758e-02 0.210500 -4.028742e-02 0.210550 -4.028726e-02 0.210600 -4.028710e-02 0.210650 -4.028694e-02 0.210700 -4.028678e-02 0.210750 -4.028662e-02 0.210800 -4.028646e-02 0.210850 -4.028630e-02 0.210900 -4.028614e-02 0.210950 -4.028598e-02 0.211000 -4.028582e-02 0.211050 -4.028566e-02 0.211100 -4.028550e-02 0.211150 -4.028534e-02 0.211200 -4.028518e-02 0.211250 -4.028503e-02 0.211300 -4.028487e-02 0.211350 -4.028471e-02 0.211400 -4.028455e-02 0.211450 -4.028440e-02 0.211500 -4.028424e-02 0.211550 -4.028408e-02 0.211600 -4.028392e-02 0.211650 -4.028377e-02 0.211700 -4.028361e-02 0.211750 -4.028346e-02 0.211800 -4.028330e-02 0.211850 -4.028314e-02 0.211900 -4.028299e-02 0.211950 -4.028283e-02 0.212000 -4.028268e-02 0.212050 -4.028252e-02 0.212100 -4.028237e-02 0.212150 -4.028221e-02 0.212200 -4.028206e-02 0.212250 -4.028190e-02 0.212300 -4.028175e-02 0.212350 -4.028160e-02 0.212400 -4.028144e-02 0.212450 -4.028129e-02 0.212500 -4.028113e-02 0.212550 -4.028098e-02 0.212600 -4.028083e-02 0.212650 -4.028068e-02 0.212700 -4.028052e-02 0.212750 -4.028037e-02 0.212800 -4.028022e-02 0.212850 -4.028007e-02 0.212900 -4.027992e-02 0.212950 -4.027976e-02 0.213000 -4.027961e-02 0.213050 -4.027946e-02 0.213100 -4.027931e-02 0.213150 -4.027916e-02 0.213200 -4.027901e-02 0.213250 -4.027886e-02 0.213300 -4.027871e-02 0.213350 -4.027856e-02 0.213400 -4.027841e-02 0.213450 -4.027826e-02 0.213500 -4.027811e-02 0.213550 -4.027796e-02 0.213600 -4.027781e-02 0.213650 -4.027766e-02 0.213700 -4.027751e-02 0.213750 -4.027736e-02 0.213800 -4.027721e-02 0.213850 -4.027707e-02 0.213900 -4.027692e-02 0.213950 -4.027677e-02 0.214000 -4.027662e-02 0.214050 -4.027648e-02 0.214100 -4.027633e-02 0.214150 -4.027618e-02 0.214200 -4.027603e-02 0.214250 -4.027589e-02 0.214300 -4.027574e-02 0.214350 -4.027559e-02 0.214400 -4.027545e-02 0.214450 -4.027530e-02 0.214500 -4.027516e-02 0.214550 -4.027501e-02 0.214600 -4.027487e-02 0.214650 -4.027472e-02 0.214700 -4.027457e-02 0.214750 -4.027443e-02 0.214800 -4.027428e-02 0.214850 -4.027414e-02 0.214900 -4.027400e-02 0.214950 -4.027385e-02 0.215000 -4.027371e-02 0.215050 -4.027356e-02 0.215100 -4.027342e-02 0.215150 -4.027328e-02 0.215200 -4.027313e-02 0.215250 -4.027299e-02 0.215300 -4.027285e-02 0.215350 -4.027270e-02 0.215400 -4.027256e-02 0.215450 -4.027242e-02 0.215500 -4.027228e-02 0.215550 -4.027214e-02 0.215600 -4.027199e-02 0.215650 -4.027185e-02 0.215700 -4.027171e-02 0.215750 -4.027157e-02 0.215800 -4.027143e-02 0.215850 -4.027129e-02 0.215900 -4.027115e-02 0.215950 -4.027100e-02 0.216000 -4.027086e-02 0.216050 -4.027072e-02 0.216100 -4.027058e-02 0.216150 -4.027044e-02 0.216200 -4.027030e-02 0.216250 -4.027016e-02 0.216300 -4.027003e-02 0.216350 -4.026989e-02 0.216400 -4.026975e-02 0.216450 -4.026961e-02 0.216500 -4.026947e-02 0.216550 -4.026933e-02 0.216600 -4.026919e-02 0.216650 -4.026905e-02 0.216700 -4.026892e-02 0.216750 -4.026878e-02 0.216800 -4.026864e-02 0.216850 -4.026850e-02 0.216900 -4.026837e-02 0.216950 -4.026823e-02 0.217000 -4.026809e-02 0.217050 -4.026795e-02 0.217100 -4.026782e-02 0.217150 -4.026768e-02 0.217200 -4.026754e-02 0.217250 -4.026741e-02 0.217300 -4.026727e-02 0.217350 -4.026714e-02 0.217400 -4.026700e-02 0.217450 -4.026687e-02 0.217500 -4.026673e-02 0.217550 -4.026660e-02 0.217600 -4.026646e-02 0.217650 -4.026633e-02 0.217700 -4.026619e-02 0.217750 -4.026606e-02 0.217800 -4.026592e-02 0.217850 -4.026579e-02 0.217900 -4.026565e-02 0.217950 -4.026552e-02 0.218000 -4.026539e-02 0.218050 -4.026525e-02 0.218100 -4.026512e-02 0.218150 -4.026499e-02 0.218200 -4.026485e-02 0.218250 -4.026472e-02 0.218300 -4.026459e-02 0.218350 -4.026446e-02 0.218400 -4.026432e-02 0.218450 -4.026419e-02 0.218500 -4.026406e-02 0.218550 -4.026393e-02 0.218600 -4.026380e-02 0.218650 -4.026366e-02 0.218700 -4.026353e-02 0.218750 -4.026340e-02 0.218800 -4.026327e-02 0.218850 -4.026314e-02 0.218900 -4.026301e-02 0.218950 -4.026288e-02 0.219000 -4.026275e-02 0.219050 -4.026262e-02 0.219100 -4.026249e-02 0.219150 -4.026236e-02 0.219200 -4.026223e-02 0.219250 -4.026210e-02 0.219300 -4.026197e-02 0.219350 -4.026184e-02 0.219400 -4.026171e-02 0.219450 -4.026158e-02 0.219500 -4.026145e-02 0.219550 -4.026133e-02 0.219600 -4.026120e-02 0.219650 -4.026107e-02 0.219700 -4.026094e-02 0.219750 -4.026081e-02 0.219800 -4.026069e-02 0.219850 -4.026056e-02 0.219900 -4.026043e-02 0.219950 -4.026030e-02 0.220000 -4.026018e-02 0.220050 -4.026005e-02 0.220100 -4.025992e-02 0.220150 -4.025980e-02 0.220200 -4.025967e-02 0.220250 -4.025954e-02 0.220300 -4.025942e-02 0.220350 -4.025929e-02 0.220400 -4.025916e-02 0.220450 -4.025904e-02 0.220500 -4.025891e-02 0.220550 -4.025879e-02 0.220600 -4.025866e-02 0.220650 -4.025854e-02 0.220700 -4.025841e-02 0.220750 -4.025829e-02 0.220800 -4.025816e-02 0.220850 -4.025804e-02 0.220900 -4.025791e-02 0.220950 -4.025779e-02 0.221000 -4.025767e-02 0.221050 -4.025754e-02 0.221100 -4.025742e-02 0.221150 -4.025730e-02 0.221200 -4.025717e-02 0.221250 -4.025705e-02 0.221300 -4.025693e-02 0.221350 -4.025680e-02 0.221400 -4.025668e-02 0.221450 -4.025656e-02 0.221500 -4.025644e-02 0.221550 -4.025631e-02 0.221600 -4.025619e-02 0.221650 -4.025607e-02 0.221700 -4.025595e-02 0.221750 -4.025583e-02 0.221800 -4.025570e-02 0.221850 -4.025558e-02 0.221900 -4.025546e-02 0.221950 -4.025534e-02 0.222000 -4.025522e-02 0.222050 -4.025510e-02 0.222100 -4.025498e-02 0.222150 -4.025486e-02 0.222200 -4.025474e-02 0.222250 -4.025462e-02 0.222300 -4.025450e-02 0.222350 -4.025438e-02 0.222400 -4.025426e-02 0.222450 -4.025414e-02 0.222500 -4.025402e-02 0.222550 -4.025390e-02 0.222600 -4.025378e-02 0.222650 -4.025366e-02 0.222700 -4.025354e-02 0.222750 -4.025342e-02 0.222800 -4.025331e-02 0.222850 -4.025319e-02 0.222900 -4.025307e-02 0.222950 -4.025295e-02 0.223000 -4.025283e-02 0.223050 -4.025271e-02 0.223100 -4.025260e-02 0.223150 -4.025248e-02 0.223200 -4.025236e-02 0.223250 -4.025224e-02 0.223300 -4.025213e-02 0.223350 -4.025201e-02 0.223400 -4.025189e-02 0.223450 -4.025178e-02 0.223500 -4.025166e-02 0.223550 -4.025154e-02 0.223600 -4.025143e-02 0.223650 -4.025131e-02 0.223700 -4.025120e-02 0.223750 -4.025108e-02 0.223800 -4.025097e-02 0.223850 -4.025085e-02 0.223900 -4.025073e-02 0.223950 -4.025062e-02 0.224000 -4.025050e-02 0.224050 -4.025039e-02 0.224100 -4.025027e-02 0.224150 -4.025016e-02 0.224200 -4.025005e-02 0.224250 -4.024993e-02 0.224300 -4.024982e-02 0.224350 -4.024970e-02 0.224400 -4.024959e-02 0.224450 -4.024948e-02 0.224500 -4.024936e-02 0.224550 -4.024925e-02 0.224600 -4.024914e-02 0.224650 -4.024902e-02 0.224700 -4.024891e-02 0.224750 -4.024880e-02 0.224800 -4.024868e-02 0.224850 -4.024857e-02 0.224900 -4.024846e-02 0.224950 -4.024835e-02 0.225000 -4.024823e-02 0.225050 -4.024812e-02 0.225100 -4.024801e-02 0.225150 -4.024790e-02 0.225200 -4.024779e-02 0.225250 -4.024768e-02 0.225300 -4.024756e-02 0.225350 -4.024745e-02 0.225400 -4.024734e-02 0.225450 -4.024723e-02 0.225500 -4.024712e-02 0.225550 -4.024701e-02 0.225600 -4.024690e-02 0.225650 -4.024679e-02 0.225700 -4.024668e-02 0.225750 -4.024657e-02 0.225800 -4.024646e-02 0.225850 -4.024635e-02 0.225900 -4.024624e-02 0.225950 -4.024613e-02 0.226000 -4.024602e-02 0.226050 -4.024591e-02 0.226100 -4.024580e-02 0.226150 -4.024569e-02 0.226200 -4.024558e-02 0.226250 -4.024547e-02 0.226300 -4.024537e-02 0.226350 -4.024526e-02 0.226400 -4.024515e-02 0.226450 -4.024504e-02 0.226500 -4.024493e-02 0.226550 -4.024483e-02 0.226600 -4.024472e-02 0.226650 -4.024461e-02 0.226700 -4.024450e-02 0.226750 -4.024439e-02 0.226800 -4.024429e-02 0.226850 -4.024418e-02 0.226900 -4.024407e-02 0.226950 -4.024397e-02 0.227000 -4.024386e-02 0.227050 -4.024375e-02 0.227100 -4.024365e-02 0.227150 -4.024354e-02 0.227200 -4.024343e-02 0.227250 -4.024333e-02 0.227300 -4.024322e-02 0.227350 -4.024312e-02 0.227400 -4.024301e-02 0.227450 -4.024291e-02 0.227500 -4.024280e-02 0.227550 -4.024269e-02 0.227600 -4.024259e-02 0.227650 -4.024248e-02 0.227700 -4.024238e-02 0.227750 -4.024228e-02 0.227800 -4.024217e-02 0.227850 -4.024207e-02 0.227900 -4.024196e-02 0.227950 -4.024186e-02 0.228000 -4.024175e-02 0.228050 -4.024165e-02 0.228100 -4.024155e-02 0.228150 -4.024144e-02 0.228200 -4.024134e-02 0.228250 -4.024124e-02 0.228300 -4.024113e-02 0.228350 -4.024103e-02 0.228400 -4.024093e-02 0.228450 -4.024082e-02 0.228500 -4.024072e-02 0.228550 -4.024062e-02 0.228600 -4.024051e-02 0.228650 -4.024041e-02 0.228700 -4.024031e-02 0.228750 -4.024021e-02 0.228800 -4.024011e-02 0.228850 -4.024000e-02 0.228900 -4.023990e-02 0.228950 -4.023980e-02 0.229000 -4.023970e-02 0.229050 -4.023960e-02 0.229100 -4.023950e-02 0.229150 -4.023940e-02 0.229200 -4.023929e-02 0.229250 -4.023919e-02 0.229300 -4.023909e-02 0.229350 -4.023899e-02 0.229400 -4.023889e-02 0.229450 -4.023879e-02 0.229500 -4.023869e-02 0.229550 -4.023859e-02 0.229600 -4.023849e-02 0.229650 -4.023839e-02 0.229700 -4.023829e-02 0.229750 -4.023819e-02 0.229800 -4.023809e-02 0.229850 -4.023799e-02 0.229900 -4.023789e-02 0.229950 -4.023779e-02 0.230000 -4.023770e-02 0.230050 -4.023760e-02 0.230100 -4.023750e-02 0.230150 -4.023740e-02 0.230200 -4.023730e-02 0.230250 -4.023720e-02 0.230300 -4.023710e-02 0.230350 -4.023701e-02 0.230400 -4.023691e-02 0.230450 -4.023681e-02 0.230500 -4.023671e-02 0.230550 -4.023661e-02 0.230600 -4.023652e-02 0.230650 -4.023642e-02 0.230700 -4.023632e-02 0.230750 -4.023623e-02 0.230800 -4.023613e-02 0.230850 -4.023603e-02 0.230900 -4.023593e-02 0.230950 -4.023584e-02 0.231000 -4.023574e-02 0.231050 -4.023564e-02 0.231100 -4.023555e-02 0.231150 -4.023545e-02 0.231200 -4.023536e-02 0.231250 -4.023526e-02 0.231300 -4.023516e-02 0.231350 -4.023507e-02 0.231400 -4.023497e-02 0.231450 -4.023488e-02 0.231500 -4.023478e-02 0.231550 -4.023469e-02 0.231600 -4.023459e-02 0.231650 -4.023450e-02 0.231700 -4.023440e-02 0.231750 -4.023431e-02 0.231800 -4.023421e-02 0.231850 -4.023412e-02 0.231900 -4.023402e-02 0.231950 -4.023393e-02 0.232000 -4.023384e-02 0.232050 -4.023374e-02 0.232100 -4.023365e-02 0.232150 -4.023355e-02 0.232200 -4.023346e-02 0.232250 -4.023337e-02 0.232300 -4.023327e-02 0.232350 -4.023318e-02 0.232400 -4.023309e-02 0.232450 -4.023299e-02 0.232500 -4.023290e-02 0.232550 -4.023281e-02 0.232600 -4.023271e-02 0.232650 -4.023262e-02 0.232700 -4.023253e-02 0.232750 -4.023244e-02 0.232800 -4.023234e-02 0.232850 -4.023225e-02 0.232900 -4.023216e-02 0.232950 -4.023207e-02 0.233000 -4.023198e-02 0.233050 -4.023188e-02 0.233100 -4.023179e-02 0.233150 -4.023170e-02 0.233200 -4.023161e-02 0.233250 -4.023152e-02 0.233300 -4.023143e-02 0.233350 -4.023134e-02 0.233400 -4.023125e-02 0.233450 -4.023115e-02 0.233500 -4.023106e-02 0.233550 -4.023097e-02 0.233600 -4.023088e-02 0.233650 -4.023079e-02 0.233700 -4.023070e-02 0.233750 -4.023061e-02 0.233800 -4.023052e-02 0.233850 -4.023043e-02 0.233900 -4.023034e-02 0.233950 -4.023025e-02 0.234000 -4.023016e-02 0.234050 -4.023007e-02 0.234100 -4.022998e-02 0.234150 -4.022990e-02 0.234200 -4.022981e-02 0.234250 -4.022972e-02 0.234300 -4.022963e-02 0.234350 -4.022954e-02 0.234400 -4.022945e-02 0.234450 -4.022936e-02 0.234500 -4.022927e-02 0.234550 -4.022919e-02 0.234600 -4.022910e-02 0.234650 -4.022901e-02 0.234700 -4.022892e-02 0.234750 -4.022883e-02 0.234800 -4.022875e-02 0.234850 -4.022866e-02 0.234900 -4.022857e-02 0.234950 -4.022848e-02 0.235000 -4.022840e-02 0.235050 -4.022831e-02 0.235100 -4.022822e-02 0.235150 -4.022813e-02 0.235200 -4.022805e-02 0.235250 -4.022796e-02 0.235300 -4.022787e-02 0.235350 -4.022779e-02 0.235400 -4.022770e-02 0.235450 -4.022761e-02 0.235500 -4.022753e-02 0.235550 -4.022744e-02 0.235600 -4.022736e-02 0.235650 -4.022727e-02 0.235700 -4.022718e-02 0.235750 -4.022710e-02 0.235800 -4.022701e-02 0.235850 -4.022693e-02 0.235900 -4.022684e-02 0.235950 -4.022676e-02 0.236000 -4.022667e-02 0.236050 -4.022659e-02 0.236100 -4.022650e-02 0.236150 -4.022642e-02 0.236200 -4.022633e-02 0.236250 -4.022625e-02 0.236300 -4.022616e-02 0.236350 -4.022608e-02 0.236400 -4.022599e-02 0.236450 -4.022591e-02 0.236500 -4.022582e-02 0.236550 -4.022574e-02 0.236600 -4.022566e-02 0.236650 -4.022557e-02 0.236700 -4.022549e-02 0.236750 -4.022541e-02 0.236800 -4.022532e-02 0.236850 -4.022524e-02 0.236900 -4.022515e-02 0.236950 -4.022507e-02 0.237000 -4.022499e-02 0.237050 -4.022491e-02 0.237100 -4.022482e-02 0.237150 -4.022474e-02 0.237200 -4.022466e-02 0.237250 -4.022457e-02 0.237300 -4.022449e-02 0.237350 -4.022441e-02 0.237400 -4.022433e-02 0.237450 -4.022425e-02 0.237500 -4.022416e-02 0.237550 -4.022408e-02 0.237600 -4.022400e-02 0.237650 -4.022392e-02 0.237700 -4.022384e-02 0.237750 -4.022375e-02 0.237800 -4.022367e-02 0.237850 -4.022359e-02 0.237900 -4.022351e-02 0.237950 -4.022343e-02 0.238000 -4.022335e-02 0.238050 -4.022327e-02 0.238100 -4.022319e-02 0.238150 -4.022311e-02 0.238200 -4.022302e-02 0.238250 -4.022294e-02 0.238300 -4.022286e-02 0.238350 -4.022278e-02 0.238400 -4.022270e-02 0.238450 -4.022262e-02 0.238500 -4.022254e-02 0.238550 -4.022246e-02 0.238600 -4.022238e-02 0.238650 -4.022230e-02 0.238700 -4.022222e-02 0.238750 -4.022214e-02 0.238800 -4.022206e-02 0.238850 -4.022199e-02 0.238900 -4.022191e-02 0.238950 -4.022183e-02 0.239000 -4.022175e-02 0.239050 -4.022167e-02 0.239100 -4.022159e-02 0.239150 -4.022151e-02 0.239200 -4.022143e-02 0.239250 -4.022135e-02 0.239300 -4.022128e-02 0.239350 -4.022120e-02 0.239400 -4.022112e-02 0.239450 -4.022104e-02 0.239500 -4.022096e-02 0.239550 -4.022089e-02 0.239600 -4.022081e-02 0.239650 -4.022073e-02 0.239700 -4.022065e-02 0.239750 -4.022057e-02 0.239800 -4.022050e-02 0.239850 -4.022042e-02 0.239900 -4.022034e-02 0.239950 -4.022026e-02 0.240000 -4.022019e-02 0.240050 -4.022011e-02 0.240100 -4.022003e-02 0.240150 -4.021996e-02 0.240200 -4.021988e-02 0.240250 -4.021980e-02 0.240300 -4.021973e-02 0.240350 -4.021965e-02 0.240400 -4.021957e-02 0.240450 -4.021950e-02 0.240500 -4.021942e-02 0.240550 -4.021935e-02 0.240600 -4.021927e-02 0.240650 -4.021919e-02 0.240700 -4.021912e-02 0.240750 -4.021904e-02 0.240800 -4.021897e-02 0.240850 -4.021889e-02 0.240900 -4.021882e-02 0.240950 -4.021874e-02 0.241000 -4.021867e-02 0.241050 -4.021859e-02 0.241100 -4.021852e-02 0.241150 -4.021844e-02 0.241200 -4.021837e-02 0.241250 -4.021829e-02 0.241300 -4.021822e-02 0.241350 -4.021814e-02 0.241400 -4.021807e-02 0.241450 -4.021799e-02 0.241500 -4.021792e-02 0.241550 -4.021784e-02 0.241600 -4.021777e-02 0.241650 -4.021770e-02 0.241700 -4.021762e-02 0.241750 -4.021755e-02 0.241800 -4.021748e-02 0.241850 -4.021740e-02 0.241900 -4.021733e-02 0.241950 -4.021725e-02 0.242000 -4.021718e-02 0.242050 -4.021711e-02 0.242100 -4.021703e-02 0.242150 -4.021696e-02 0.242200 -4.021689e-02 0.242250 -4.021682e-02 0.242300 -4.021674e-02 0.242350 -4.021667e-02 0.242400 -4.021660e-02 0.242450 -4.021653e-02 0.242500 -4.021645e-02 0.242550 -4.021638e-02 0.242600 -4.021631e-02 0.242650 -4.021624e-02 0.242700 -4.021616e-02 0.242750 -4.021609e-02 0.242800 -4.021602e-02 0.242850 -4.021595e-02 0.242900 -4.021588e-02 0.242950 -4.021581e-02 0.243000 -4.021573e-02 0.243050 -4.021566e-02 0.243100 -4.021559e-02 0.243150 -4.021552e-02 0.243200 -4.021545e-02 0.243250 -4.021538e-02 0.243300 -4.021531e-02 0.243350 -4.021524e-02 0.243400 -4.021516e-02 0.243450 -4.021509e-02 0.243500 -4.021502e-02 0.243550 -4.021495e-02 0.243600 -4.021488e-02 0.243650 -4.021481e-02 0.243700 -4.021474e-02 0.243750 -4.021467e-02 0.243800 -4.021460e-02 0.243850 -4.021453e-02 0.243900 -4.021446e-02 0.243950 -4.021439e-02 0.244000 -4.021432e-02 0.244050 -4.021425e-02 0.244100 -4.021418e-02 0.244150 -4.021411e-02 0.244200 -4.021404e-02 0.244250 -4.021397e-02 0.244300 -4.021390e-02 0.244350 -4.021384e-02 0.244400 -4.021377e-02 0.244450 -4.021370e-02 0.244500 -4.021363e-02 0.244550 -4.021356e-02 0.244600 -4.021349e-02 0.244650 -4.021342e-02 0.244700 -4.021335e-02 0.244750 -4.021329e-02 0.244800 -4.021322e-02 0.244850 -4.021315e-02 0.244900 -4.021308e-02 0.244950 -4.021301e-02 0.245000 -4.021294e-02 0.245050 -4.021288e-02 0.245100 -4.021281e-02 0.245150 -4.021274e-02 0.245200 -4.021267e-02 0.245250 -4.021261e-02 0.245300 -4.021254e-02 0.245350 -4.021247e-02 0.245400 -4.021240e-02 0.245450 -4.021234e-02 0.245500 -4.021227e-02 0.245550 -4.021220e-02 0.245600 -4.021213e-02 0.245650 -4.021207e-02 0.245700 -4.021200e-02 0.245750 -4.021193e-02 0.245800 -4.021187e-02 0.245850 -4.021180e-02 0.245900 -4.021173e-02 0.245950 -4.021167e-02 0.246000 -4.021160e-02 0.246050 -4.021154e-02 0.246100 -4.021147e-02 0.246150 -4.021140e-02 0.246200 -4.021134e-02 0.246250 -4.021127e-02 0.246300 -4.021121e-02 0.246350 -4.021114e-02 0.246400 -4.021107e-02 0.246450 -4.021101e-02 0.246500 -4.021094e-02 0.246550 -4.021088e-02 0.246600 -4.021081e-02 0.246650 -4.021075e-02 0.246700 -4.021068e-02 0.246750 -4.021062e-02 0.246800 -4.021055e-02 0.246850 -4.021049e-02 0.246900 -4.021042e-02 0.246950 -4.021036e-02 0.247000 -4.021029e-02 0.247050 -4.021023e-02 0.247100 -4.021016e-02 0.247150 -4.021010e-02 0.247200 -4.021003e-02 0.247250 -4.020997e-02 0.247300 -4.020990e-02 0.247350 -4.020984e-02 0.247400 -4.020978e-02 0.247450 -4.020971e-02 0.247500 -4.020965e-02 0.247550 -4.020958e-02 0.247600 -4.020952e-02 0.247650 -4.020946e-02 0.247700 -4.020939e-02 0.247750 -4.020933e-02 0.247800 -4.020927e-02 0.247850 -4.020920e-02 0.247900 -4.020914e-02 0.247950 -4.020908e-02 0.248000 -4.020901e-02 0.248050 -4.020895e-02 0.248100 -4.020889e-02 0.248150 -4.020883e-02 0.248200 -4.020876e-02 0.248250 -4.020870e-02 0.248300 -4.020864e-02 0.248350 -4.020857e-02 0.248400 -4.020851e-02 0.248450 -4.020845e-02 0.248500 -4.020839e-02 0.248550 -4.020832e-02 0.248600 -4.020826e-02 0.248650 -4.020820e-02 0.248700 -4.020814e-02 0.248750 -4.020808e-02 0.248800 -4.020801e-02 0.248850 -4.020795e-02 0.248900 -4.020789e-02 0.248950 -4.020783e-02 0.249000 -4.020777e-02 0.249050 -4.020771e-02 0.249100 -4.020765e-02 0.249150 -4.020758e-02 0.249200 -4.020752e-02 0.249250 -4.020746e-02 0.249300 -4.020740e-02 0.249350 -4.020734e-02 0.249400 -4.020728e-02 0.249450 -4.020722e-02 0.249500 -4.020716e-02 0.249550 -4.020710e-02 0.249600 -4.020704e-02 0.249650 -4.020697e-02 0.249700 -4.020691e-02 0.249750 -4.020685e-02 0.249800 -4.020679e-02 0.249850 -4.020673e-02 0.249900 -4.020667e-02 0.249950 -4.020661e-02 0.250000 -4.020655e-02 brian2-2.5.4/brian2/tests/rallpack_data/ref_cable.0000066400000000000000000003272041445201106100217650ustar00rootroot000000000000000.000000 -6.500000e-02 0.000050 -5.992262e-02 0.000100 -5.782250e-02 0.000150 -5.621305e-02 0.000200 -5.485792e-02 0.000250 -5.366553e-02 0.000300 -5.258887e-02 0.000350 -5.160003e-02 0.000400 -5.068078e-02 0.000450 -4.981848e-02 0.000500 -4.900392e-02 0.000550 -4.823013e-02 0.000600 -4.749171e-02 0.000650 -4.678435e-02 0.000700 -4.610457e-02 0.000750 -4.544948e-02 0.000800 -4.481668e-02 0.000850 -4.420413e-02 0.000900 -4.361009e-02 0.000950 -4.303305e-02 0.001000 -4.247172e-02 0.001050 -4.192493e-02 0.001100 -4.139168e-02 0.001150 -4.087107e-02 0.001200 -4.036230e-02 0.001250 -3.986464e-02 0.001300 -3.937745e-02 0.001350 -3.890014e-02 0.001400 -3.843218e-02 0.001450 -3.797307e-02 0.001500 -3.752237e-02 0.001550 -3.707969e-02 0.001600 -3.664463e-02 0.001650 -3.621685e-02 0.001700 -3.579603e-02 0.001750 -3.538187e-02 0.001800 -3.497410e-02 0.001850 -3.457246e-02 0.001900 -3.417671e-02 0.001950 -3.378661e-02 0.002000 -3.340197e-02 0.002050 -3.302258e-02 0.002100 -3.264826e-02 0.002150 -3.227883e-02 0.002200 -3.191413e-02 0.002250 -3.155399e-02 0.002300 -3.119829e-02 0.002350 -3.084686e-02 0.002400 -3.049960e-02 0.002450 -3.015636e-02 0.002500 -2.981704e-02 0.002550 -2.948150e-02 0.002600 -2.914965e-02 0.002650 -2.882139e-02 0.002700 -2.849663e-02 0.002750 -2.817525e-02 0.002800 -2.785719e-02 0.002850 -2.754233e-02 0.002900 -2.723063e-02 0.002950 -2.692198e-02 0.003000 -2.661633e-02 0.003050 -2.631358e-02 0.003100 -2.601368e-02 0.003150 -2.571657e-02 0.003200 -2.542217e-02 0.003250 -2.513042e-02 0.003300 -2.484128e-02 0.003350 -2.455467e-02 0.003400 -2.427055e-02 0.003450 -2.398886e-02 0.003500 -2.370956e-02 0.003550 -2.343257e-02 0.003600 -2.315790e-02 0.003650 -2.288545e-02 0.003700 -2.261520e-02 0.003750 -2.234710e-02 0.003800 -2.208112e-02 0.003850 -2.181721e-02 0.003900 -2.155533e-02 0.003950 -2.129545e-02 0.004000 -2.103753e-02 0.004050 -2.078153e-02 0.004100 -2.052742e-02 0.004150 -2.027517e-02 0.004200 -2.002474e-02 0.004250 -1.977612e-02 0.004300 -1.952924e-02 0.004350 -1.928410e-02 0.004400 -1.904067e-02 0.004450 -1.879891e-02 0.004500 -1.855880e-02 0.004550 -1.832032e-02 0.004600 -1.808342e-02 0.004650 -1.784811e-02 0.004700 -1.761433e-02 0.004750 -1.738208e-02 0.004800 -1.715133e-02 0.004850 -1.692205e-02 0.004900 -1.669423e-02 0.004950 -1.646784e-02 0.005000 -1.624285e-02 0.005050 -1.601927e-02 0.005100 -1.579704e-02 0.005150 -1.557618e-02 0.005200 -1.535663e-02 0.005250 -1.513841e-02 0.005300 -1.492146e-02 0.005350 -1.470581e-02 0.005400 -1.449139e-02 0.005450 -1.427823e-02 0.005500 -1.406629e-02 0.005550 -1.385555e-02 0.005600 -1.364599e-02 0.005650 -1.343762e-02 0.005700 -1.323040e-02 0.005750 -1.302431e-02 0.005800 -1.281937e-02 0.005850 -1.261553e-02 0.005900 -1.241279e-02 0.005950 -1.221112e-02 0.006000 -1.201053e-02 0.006050 -1.181101e-02 0.006100 -1.161251e-02 0.006150 -1.141505e-02 0.006200 -1.121860e-02 0.006250 -1.102316e-02 0.006300 -1.082870e-02 0.006350 -1.063523e-02 0.006400 -1.044271e-02 0.006450 -1.025116e-02 0.006500 -1.006054e-02 0.006550 -9.870869e-03 0.006600 -9.682103e-03 0.006650 -9.494249e-03 0.006700 -9.307284e-03 0.006750 -9.121220e-03 0.006800 -8.936023e-03 0.006850 -8.751702e-03 0.006900 -8.568217e-03 0.006950 -8.385606e-03 0.007000 -8.203811e-03 0.007050 -8.022852e-03 0.007100 -7.842693e-03 0.007150 -7.663358e-03 0.007200 -7.484809e-03 0.007250 -7.307056e-03 0.007300 -7.130075e-03 0.007350 -6.953881e-03 0.007400 -6.778428e-03 0.007450 -6.603740e-03 0.007500 -6.429795e-03 0.007550 -6.256596e-03 0.007600 -6.084110e-03 0.007650 -5.912346e-03 0.007700 -5.741302e-03 0.007750 -5.570962e-03 0.007800 -5.401318e-03 0.007850 -5.232346e-03 0.007900 -5.064072e-03 0.007950 -4.896480e-03 0.008000 -4.729550e-03 0.008050 -4.563263e-03 0.008100 -4.397648e-03 0.008150 -4.232666e-03 0.008200 -4.068335e-03 0.008250 -3.904618e-03 0.008300 -3.741546e-03 0.008350 -3.579079e-03 0.008400 -3.417230e-03 0.008450 -3.255982e-03 0.008500 -3.095341e-03 0.008550 -2.935285e-03 0.008600 -2.775826e-03 0.008650 -2.616943e-03 0.008700 -2.458633e-03 0.008750 -2.300895e-03 0.008800 -2.143723e-03 0.008850 -1.987096e-03 0.008900 -1.831034e-03 0.008950 -1.675519e-03 0.009000 -1.520531e-03 0.009050 -1.366084e-03 0.009100 -1.212177e-03 0.009150 -1.058777e-03 0.009200 -9.059165e-04 0.009250 -7.535570e-04 0.009300 -6.017056e-04 0.009350 -4.503694e-04 0.009400 -2.995326e-04 0.009450 -1.491873e-04 0.009500 6.748333e-07 0.009550 1.500389e-04 0.009600 2.989284e-04 0.009650 4.473512e-04 0.009700 5.952850e-04 0.009750 7.427684e-04 0.009800 8.897866e-04 0.009850 1.036332e-03 0.009900 1.182436e-03 0.009950 1.328099e-03 0.010000 1.473291e-03 0.010050 1.618065e-03 0.010100 1.762392e-03 0.010150 1.906294e-03 0.010200 2.049757e-03 0.010250 2.192796e-03 0.010300 2.335411e-03 0.010350 2.477619e-03 0.010400 2.619404e-03 0.010450 2.760782e-03 0.010500 2.901754e-03 0.010550 3.042318e-03 0.010600 3.182491e-03 0.010650 3.322274e-03 0.010700 3.461650e-03 0.010750 3.600644e-03 0.010800 3.739271e-03 0.010850 3.877493e-03 0.010900 4.015332e-03 0.010950 4.152820e-03 0.011000 4.289911e-03 0.011050 4.426644e-03 0.011100 4.562996e-03 0.011150 4.699005e-03 0.011200 4.834647e-03 0.011250 4.969932e-03 0.011300 5.104859e-03 0.011350 5.239452e-03 0.011400 5.373679e-03 0.011450 5.507572e-03 0.011500 5.641108e-03 0.011550 5.774317e-03 0.011600 5.907200e-03 0.011650 6.039726e-03 0.011700 6.171925e-03 0.011750 6.303806e-03 0.011800 6.435375e-03 0.011850 6.566589e-03 0.011900 6.697499e-03 0.011950 6.828075e-03 0.012000 6.958364e-03 0.012050 7.088310e-03 0.012100 7.217962e-03 0.012150 7.347303e-03 0.012200 7.476333e-03 0.012250 7.605068e-03 0.012300 7.733492e-03 0.012350 7.861614e-03 0.012400 7.989454e-03 0.012450 8.116985e-03 0.012500 8.244235e-03 0.012550 8.371189e-03 0.012600 8.497859e-03 0.012650 8.624233e-03 0.012700 8.750324e-03 0.012750 8.876143e-03 0.012800 9.001681e-03 0.012850 9.126923e-03 0.012900 9.251902e-03 0.012950 9.376614e-03 0.013000 9.501057e-03 0.013050 9.625205e-03 0.013100 9.749102e-03 0.013150 9.872727e-03 0.013200 9.996092e-03 0.013250 1.011919e-02 0.013300 1.024203e-02 0.013350 1.036461e-02 0.013400 1.048695e-02 0.013450 1.060901e-02 0.013500 1.073083e-02 0.013550 1.085238e-02 0.013600 1.097369e-02 0.013650 1.109476e-02 0.013700 1.121557e-02 0.013750 1.133615e-02 0.013800 1.145647e-02 0.013850 1.157654e-02 0.013900 1.169639e-02 0.013950 1.181599e-02 0.014000 1.193536e-02 0.014050 1.205447e-02 0.014100 1.217338e-02 0.014150 1.229202e-02 0.014200 1.241046e-02 0.014250 1.252865e-02 0.014300 1.264661e-02 0.014350 1.276435e-02 0.014400 1.288187e-02 0.014450 1.299915e-02 0.014500 1.311622e-02 0.014550 1.323306e-02 0.014600 1.334967e-02 0.014650 1.346607e-02 0.014700 1.358225e-02 0.014750 1.369820e-02 0.014800 1.381396e-02 0.014850 1.392949e-02 0.014900 1.404481e-02 0.014950 1.415991e-02 0.015000 1.427482e-02 0.015050 1.438949e-02 0.015100 1.450396e-02 0.015150 1.461825e-02 0.015200 1.473229e-02 0.015250 1.484615e-02 0.015300 1.495979e-02 0.015350 1.507325e-02 0.015400 1.518648e-02 0.015450 1.529953e-02 0.015500 1.541237e-02 0.015550 1.552502e-02 0.015600 1.563747e-02 0.015650 1.574970e-02 0.015700 1.586177e-02 0.015750 1.597362e-02 0.015800 1.608529e-02 0.015850 1.619675e-02 0.015900 1.630803e-02 0.015950 1.641912e-02 0.016000 1.653001e-02 0.016050 1.664073e-02 0.016100 1.675125e-02 0.016150 1.686157e-02 0.016200 1.697173e-02 0.016250 1.708169e-02 0.016300 1.719146e-02 0.016350 1.730106e-02 0.016400 1.741045e-02 0.016450 1.751969e-02 0.016500 1.762872e-02 0.016550 1.773758e-02 0.016600 1.784627e-02 0.016650 1.795478e-02 0.016700 1.806310e-02 0.016750 1.817124e-02 0.016800 1.827922e-02 0.016850 1.838702e-02 0.016900 1.849465e-02 0.016950 1.860209e-02 0.017000 1.870936e-02 0.017050 1.881648e-02 0.017100 1.892340e-02 0.017150 1.903016e-02 0.017200 1.913674e-02 0.017250 1.924316e-02 0.017300 1.934941e-02 0.017350 1.945550e-02 0.017400 1.956142e-02 0.017450 1.966715e-02 0.017500 1.977274e-02 0.017550 1.987815e-02 0.017600 1.998340e-02 0.017650 2.008849e-02 0.017700 2.019340e-02 0.017750 2.029816e-02 0.017800 2.040276e-02 0.017850 2.050720e-02 0.017900 2.061148e-02 0.017950 2.071558e-02 0.018000 2.081954e-02 0.018050 2.092333e-02 0.018100 2.102697e-02 0.018150 2.113044e-02 0.018200 2.123375e-02 0.018250 2.133692e-02 0.018300 2.143992e-02 0.018350 2.154277e-02 0.018400 2.164546e-02 0.018450 2.174799e-02 0.018500 2.185039e-02 0.018550 2.195262e-02 0.018600 2.205468e-02 0.018650 2.215663e-02 0.018700 2.225840e-02 0.018750 2.236002e-02 0.018800 2.246150e-02 0.018850 2.256281e-02 0.018900 2.266399e-02 0.018950 2.276500e-02 0.019000 2.286588e-02 0.019050 2.296660e-02 0.019100 2.306718e-02 0.019150 2.316760e-02 0.019200 2.326789e-02 0.019250 2.336802e-02 0.019300 2.346801e-02 0.019350 2.356786e-02 0.019400 2.366755e-02 0.019450 2.376711e-02 0.019500 2.386651e-02 0.019550 2.396577e-02 0.019600 2.406490e-02 0.019650 2.416388e-02 0.019700 2.426272e-02 0.019750 2.436142e-02 0.019800 2.445997e-02 0.019850 2.455837e-02 0.019900 2.465665e-02 0.019950 2.475478e-02 0.020000 2.485278e-02 0.020050 2.495061e-02 0.020100 2.504834e-02 0.020150 2.514591e-02 0.020200 2.524333e-02 0.020250 2.534064e-02 0.020300 2.543781e-02 0.020350 2.553482e-02 0.020400 2.563172e-02 0.020450 2.572845e-02 0.020500 2.582506e-02 0.020550 2.592154e-02 0.020600 2.601790e-02 0.020650 2.611409e-02 0.020700 2.621015e-02 0.020750 2.630610e-02 0.020800 2.640192e-02 0.020850 2.649759e-02 0.020900 2.659313e-02 0.020950 2.668852e-02 0.021000 2.678380e-02 0.021050 2.687894e-02 0.021100 2.697396e-02 0.021150 2.706882e-02 0.021200 2.716358e-02 0.021250 2.725819e-02 0.021300 2.735268e-02 0.021350 2.744703e-02 0.021400 2.754127e-02 0.021450 2.763535e-02 0.021500 2.772933e-02 0.021550 2.782315e-02 0.021600 2.791687e-02 0.021650 2.801047e-02 0.021700 2.810391e-02 0.021750 2.819723e-02 0.021800 2.829044e-02 0.021850 2.838352e-02 0.021900 2.847646e-02 0.021950 2.856927e-02 0.022000 2.866198e-02 0.022050 2.875453e-02 0.022100 2.884697e-02 0.022150 2.893931e-02 0.022200 2.903149e-02 0.022250 2.912356e-02 0.022300 2.921550e-02 0.022350 2.930732e-02 0.022400 2.939902e-02 0.022450 2.949059e-02 0.022500 2.958204e-02 0.022550 2.967337e-02 0.022600 2.976457e-02 0.022650 2.985565e-02 0.022700 2.994662e-02 0.022750 3.003744e-02 0.022800 3.012816e-02 0.022850 3.021876e-02 0.022900 3.030923e-02 0.022950 3.039958e-02 0.023000 3.048982e-02 0.023050 3.057993e-02 0.023100 3.066991e-02 0.023150 3.075979e-02 0.023200 3.084954e-02 0.023250 3.093917e-02 0.023300 3.102869e-02 0.023350 3.111807e-02 0.023400 3.120735e-02 0.023450 3.129651e-02 0.023500 3.138556e-02 0.023550 3.147449e-02 0.023600 3.156329e-02 0.023650 3.165198e-02 0.023700 3.174055e-02 0.023750 3.182900e-02 0.023800 3.191733e-02 0.023850 3.200556e-02 0.023900 3.209365e-02 0.023950 3.218164e-02 0.024000 3.226951e-02 0.024050 3.235727e-02 0.024100 3.244490e-02 0.024150 3.253244e-02 0.024200 3.261985e-02 0.024250 3.270715e-02 0.024300 3.279433e-02 0.024350 3.288139e-02 0.024400 3.296836e-02 0.024450 3.305518e-02 0.024500 3.314191e-02 0.024550 3.322853e-02 0.024600 3.331503e-02 0.024650 3.340142e-02 0.024700 3.348768e-02 0.024750 3.357385e-02 0.024800 3.365990e-02 0.024850 3.374583e-02 0.024900 3.383166e-02 0.024950 3.391737e-02 0.025000 3.400298e-02 0.025050 3.408847e-02 0.025100 3.417384e-02 0.025150 3.425913e-02 0.025200 3.434428e-02 0.025250 3.442932e-02 0.025300 3.451425e-02 0.025350 3.459909e-02 0.025400 3.468381e-02 0.025450 3.476842e-02 0.025500 3.485291e-02 0.025550 3.493731e-02 0.025600 3.502159e-02 0.025650 3.510575e-02 0.025700 3.518980e-02 0.025750 3.527377e-02 0.025800 3.535761e-02 0.025850 3.544134e-02 0.025900 3.552496e-02 0.025950 3.560849e-02 0.026000 3.569190e-02 0.026050 3.577520e-02 0.026100 3.585840e-02 0.026150 3.594149e-02 0.026200 3.602447e-02 0.026250 3.610735e-02 0.026300 3.619012e-02 0.026350 3.627278e-02 0.026400 3.635534e-02 0.026450 3.643778e-02 0.026500 3.652013e-02 0.026550 3.660237e-02 0.026600 3.668451e-02 0.026650 3.676653e-02 0.026700 3.684844e-02 0.026750 3.693027e-02 0.026800 3.701198e-02 0.026850 3.709359e-02 0.026900 3.717508e-02 0.026950 3.725649e-02 0.027000 3.733779e-02 0.027050 3.741897e-02 0.027100 3.750006e-02 0.027150 3.758103e-02 0.027200 3.766194e-02 0.027250 3.774270e-02 0.027300 3.782338e-02 0.027350 3.790393e-02 0.027400 3.798440e-02 0.027450 3.806476e-02 0.027500 3.814503e-02 0.027550 3.822518e-02 0.027600 3.830525e-02 0.027650 3.838519e-02 0.027700 3.846504e-02 0.027750 3.854480e-02 0.027800 3.862443e-02 0.027850 3.870398e-02 0.027900 3.878343e-02 0.027950 3.886277e-02 0.028000 3.894202e-02 0.028050 3.902115e-02 0.028100 3.910020e-02 0.028150 3.917914e-02 0.028200 3.925799e-02 0.028250 3.933671e-02 0.028300 3.941535e-02 0.028350 3.949390e-02 0.028400 3.957234e-02 0.028450 3.965067e-02 0.028500 3.972891e-02 0.028550 3.980706e-02 0.028600 3.988510e-02 0.028650 3.996303e-02 0.028700 4.004087e-02 0.028750 4.011861e-02 0.028800 4.019627e-02 0.028850 4.027381e-02 0.028900 4.035126e-02 0.028950 4.042862e-02 0.029000 4.050587e-02 0.029050 4.058301e-02 0.029100 4.066008e-02 0.029150 4.073704e-02 0.029200 4.081390e-02 0.029250 4.089065e-02 0.029300 4.096732e-02 0.029350 4.104390e-02 0.029400 4.112036e-02 0.029450 4.119674e-02 0.029500 4.127302e-02 0.029550 4.134922e-02 0.029600 4.142529e-02 0.029650 4.150127e-02 0.029700 4.157717e-02 0.029750 4.165298e-02 0.029800 4.172867e-02 0.029850 4.180427e-02 0.029900 4.187979e-02 0.029950 4.195519e-02 0.030000 4.203052e-02 0.030050 4.210573e-02 0.030100 4.218087e-02 0.030150 4.225592e-02 0.030200 4.233085e-02 0.030250 4.240569e-02 0.030300 4.248045e-02 0.030350 4.255511e-02 0.030400 4.262966e-02 0.030450 4.270412e-02 0.030500 4.277849e-02 0.030550 4.285278e-02 0.030600 4.292696e-02 0.030650 4.300104e-02 0.030700 4.307505e-02 0.030750 4.314895e-02 0.030800 4.322277e-02 0.030850 4.329647e-02 0.030900 4.337010e-02 0.030950 4.344365e-02 0.031000 4.351708e-02 0.031050 4.359042e-02 0.031100 4.366367e-02 0.031150 4.373684e-02 0.031200 4.380992e-02 0.031250 4.388289e-02 0.031300 4.395577e-02 0.031350 4.402857e-02 0.031400 4.410127e-02 0.031450 4.417388e-02 0.031500 4.424640e-02 0.031550 4.431883e-02 0.031600 4.439116e-02 0.031650 4.446341e-02 0.031700 4.453557e-02 0.031750 4.460763e-02 0.031800 4.467959e-02 0.031850 4.475146e-02 0.031900 4.482327e-02 0.031950 4.489497e-02 0.032000 4.496658e-02 0.032050 4.503811e-02 0.032100 4.510955e-02 0.032150 4.518089e-02 0.032200 4.525214e-02 0.032250 4.532329e-02 0.032300 4.539437e-02 0.032350 4.546535e-02 0.032400 4.553624e-02 0.032450 4.560704e-02 0.032500 4.567777e-02 0.032550 4.574839e-02 0.032600 4.581892e-02 0.032650 4.588937e-02 0.032700 4.595975e-02 0.032750 4.603002e-02 0.032800 4.610020e-02 0.032850 4.617029e-02 0.032900 4.624030e-02 0.032950 4.631021e-02 0.033000 4.638004e-02 0.033050 4.644979e-02 0.033100 4.651946e-02 0.033150 4.658902e-02 0.033200 4.665849e-02 0.033250 4.672789e-02 0.033300 4.679721e-02 0.033350 4.686641e-02 0.033400 4.693556e-02 0.033450 4.700459e-02 0.033500 4.707356e-02 0.033550 4.714243e-02 0.033600 4.721121e-02 0.033650 4.727992e-02 0.033700 4.734853e-02 0.033750 4.741706e-02 0.033800 4.748549e-02 0.033850 4.755385e-02 0.033900 4.762214e-02 0.033950 4.769031e-02 0.034000 4.775841e-02 0.034050 4.782643e-02 0.034100 4.789435e-02 0.034150 4.796219e-02 0.034200 4.802995e-02 0.034250 4.809763e-02 0.034300 4.816522e-02 0.034350 4.823271e-02 0.034400 4.830013e-02 0.034450 4.836747e-02 0.034500 4.843473e-02 0.034550 4.850188e-02 0.034600 4.856896e-02 0.034650 4.863596e-02 0.034700 4.870287e-02 0.034750 4.876971e-02 0.034800 4.883644e-02 0.034850 4.890311e-02 0.034900 4.896969e-02 0.034950 4.903618e-02 0.035000 4.910258e-02 0.035050 4.916891e-02 0.035100 4.923516e-02 0.035150 4.930131e-02 0.035200 4.936739e-02 0.035250 4.943340e-02 0.035300 4.949931e-02 0.035350 4.956514e-02 0.035400 4.963088e-02 0.035450 4.969655e-02 0.035500 4.976213e-02 0.035550 4.982763e-02 0.035600 4.989305e-02 0.035650 4.995840e-02 0.035700 5.002365e-02 0.035750 5.008882e-02 0.035800 5.015392e-02 0.035850 5.021892e-02 0.035900 5.028384e-02 0.035950 5.034871e-02 0.036000 5.041346e-02 0.036050 5.047815e-02 0.036100 5.054275e-02 0.036150 5.060728e-02 0.036200 5.067173e-02 0.036250 5.073609e-02 0.036300 5.080038e-02 0.036350 5.086457e-02 0.036400 5.092868e-02 0.036450 5.099273e-02 0.036500 5.105668e-02 0.036550 5.112055e-02 0.036600 5.118436e-02 0.036650 5.124808e-02 0.036700 5.131172e-02 0.036750 5.137528e-02 0.036800 5.143876e-02 0.036850 5.150217e-02 0.036900 5.156549e-02 0.036950 5.162872e-02 0.037000 5.169189e-02 0.037050 5.175498e-02 0.037100 5.181798e-02 0.037150 5.188090e-02 0.037200 5.194375e-02 0.037250 5.200652e-02 0.037300 5.206921e-02 0.037350 5.213181e-02 0.037400 5.219435e-02 0.037450 5.225682e-02 0.037500 5.231918e-02 0.037550 5.238148e-02 0.037600 5.244371e-02 0.037650 5.250585e-02 0.037700 5.256792e-02 0.037750 5.262990e-02 0.037800 5.269181e-02 0.037850 5.275365e-02 0.037900 5.281540e-02 0.037950 5.287708e-02 0.038000 5.293867e-02 0.038050 5.300021e-02 0.038100 5.306165e-02 0.038150 5.312301e-02 0.038200 5.318431e-02 0.038250 5.324553e-02 0.038300 5.330666e-02 0.038350 5.336772e-02 0.038400 5.342872e-02 0.038450 5.348963e-02 0.038500 5.355047e-02 0.038550 5.361122e-02 0.038600 5.367190e-02 0.038650 5.373251e-02 0.038700 5.379303e-02 0.038750 5.385349e-02 0.038800 5.391387e-02 0.038850 5.397418e-02 0.038900 5.403441e-02 0.038950 5.409456e-02 0.039000 5.415463e-02 0.039050 5.421463e-02 0.039100 5.427456e-02 0.039150 5.433441e-02 0.039200 5.439419e-02 0.039250 5.445390e-02 0.039300 5.451353e-02 0.039350 5.457306e-02 0.039400 5.463256e-02 0.039450 5.469197e-02 0.039500 5.475130e-02 0.039550 5.481056e-02 0.039600 5.486974e-02 0.039650 5.492885e-02 0.039700 5.498788e-02 0.039750 5.504683e-02 0.039800 5.510572e-02 0.039850 5.516451e-02 0.039900 5.522327e-02 0.039950 5.528194e-02 0.040000 5.534053e-02 0.040050 5.539903e-02 0.040100 5.545748e-02 0.040150 5.551588e-02 0.040200 5.557418e-02 0.040250 5.563240e-02 0.040300 5.569054e-02 0.040350 5.574865e-02 0.040400 5.580666e-02 0.040450 5.586458e-02 0.040500 5.592244e-02 0.040550 5.598023e-02 0.040600 5.603795e-02 0.040650 5.609557e-02 0.040700 5.615318e-02 0.040750 5.621068e-02 0.040800 5.626813e-02 0.040850 5.632548e-02 0.040900 5.638274e-02 0.040950 5.643998e-02 0.041000 5.649712e-02 0.041050 5.655419e-02 0.041100 5.661118e-02 0.041150 5.666813e-02 0.041200 5.672498e-02 0.041250 5.678177e-02 0.041300 5.683847e-02 0.041350 5.689512e-02 0.041400 5.695171e-02 0.041450 5.700822e-02 0.041500 5.706464e-02 0.041550 5.712101e-02 0.041600 5.717731e-02 0.041650 5.723353e-02 0.041700 5.728967e-02 0.041750 5.734575e-02 0.041800 5.740176e-02 0.041850 5.745770e-02 0.041900 5.751360e-02 0.041950 5.756939e-02 0.042000 5.762514e-02 0.042050 5.768079e-02 0.042100 5.773639e-02 0.042150 5.779191e-02 0.042200 5.784736e-02 0.042250 5.790273e-02 0.042300 5.795807e-02 0.042350 5.801331e-02 0.042400 5.806850e-02 0.042450 5.812361e-02 0.042500 5.817865e-02 0.042550 5.823362e-02 0.042600 5.828852e-02 0.042650 5.834334e-02 0.042700 5.839811e-02 0.042750 5.845282e-02 0.042800 5.850744e-02 0.042850 5.856200e-02 0.042900 5.861650e-02 0.042950 5.867092e-02 0.043000 5.872527e-02 0.043050 5.877955e-02 0.043100 5.883379e-02 0.043150 5.888792e-02 0.043200 5.894203e-02 0.043250 5.899605e-02 0.043300 5.904998e-02 0.043350 5.910387e-02 0.043400 5.915769e-02 0.043450 5.921143e-02 0.043500 5.926511e-02 0.043550 5.931873e-02 0.043600 5.937224e-02 0.043650 5.942575e-02 0.043700 5.947916e-02 0.043750 5.953251e-02 0.043800 5.958578e-02 0.043850 5.963898e-02 0.043900 5.969215e-02 0.043950 5.974523e-02 0.044000 5.979825e-02 0.044050 5.985118e-02 0.044100 5.990405e-02 0.044150 5.995689e-02 0.044200 6.000962e-02 0.044250 6.006231e-02 0.044300 6.011492e-02 0.044350 6.016747e-02 0.044400 6.021996e-02 0.044450 6.027238e-02 0.044500 6.032475e-02 0.044550 6.037704e-02 0.044600 6.042925e-02 0.044650 6.048142e-02 0.044700 6.053351e-02 0.044750 6.058554e-02 0.044800 6.063750e-02 0.044850 6.068938e-02 0.044900 6.074123e-02 0.044950 6.079298e-02 0.045000 6.084469e-02 0.045050 6.089634e-02 0.045100 6.094790e-02 0.045150 6.099941e-02 0.045200 6.105088e-02 0.045250 6.110226e-02 0.045300 6.115356e-02 0.045350 6.120482e-02 0.045400 6.125602e-02 0.045450 6.130714e-02 0.045500 6.135817e-02 0.045550 6.140920e-02 0.045600 6.146012e-02 0.045650 6.151099e-02 0.045700 6.156180e-02 0.045750 6.161255e-02 0.045800 6.166321e-02 0.045850 6.171384e-02 0.045900 6.176439e-02 0.045950 6.181487e-02 0.046000 6.186532e-02 0.046050 6.191567e-02 0.046100 6.196598e-02 0.046150 6.201621e-02 0.046200 6.206639e-02 0.046250 6.211650e-02 0.046300 6.216652e-02 0.046350 6.221654e-02 0.046400 6.226646e-02 0.046450 6.231633e-02 0.046500 6.236614e-02 0.046550 6.241588e-02 0.046600 6.246552e-02 0.046650 6.251515e-02 0.046700 6.256471e-02 0.046750 6.261418e-02 0.046800 6.266362e-02 0.046850 6.271299e-02 0.046900 6.276231e-02 0.046950 6.281154e-02 0.047000 6.286072e-02 0.047050 6.290985e-02 0.047100 6.295891e-02 0.047150 6.300788e-02 0.047200 6.305684e-02 0.047250 6.310573e-02 0.047300 6.315452e-02 0.047350 6.320328e-02 0.047400 6.325196e-02 0.047450 6.330060e-02 0.047500 6.334918e-02 0.047550 6.339769e-02 0.047600 6.344613e-02 0.047650 6.349453e-02 0.047700 6.354284e-02 0.047750 6.359111e-02 0.047800 6.363933e-02 0.047850 6.368748e-02 0.047900 6.373557e-02 0.047950 6.378359e-02 0.048000 6.383155e-02 0.048050 6.387947e-02 0.048100 6.392732e-02 0.048150 6.397509e-02 0.048200 6.402284e-02 0.048250 6.407049e-02 0.048300 6.411809e-02 0.048350 6.416565e-02 0.048400 6.421314e-02 0.048450 6.426056e-02 0.048500 6.430795e-02 0.048550 6.435526e-02 0.048600 6.440250e-02 0.048650 6.444970e-02 0.048700 6.449683e-02 0.048750 6.454391e-02 0.048800 6.459092e-02 0.048850 6.463789e-02 0.048900 6.468480e-02 0.048950 6.473163e-02 0.049000 6.477841e-02 0.049050 6.482513e-02 0.049100 6.487181e-02 0.049150 6.491840e-02 0.049200 6.496495e-02 0.049250 6.501147e-02 0.049300 6.505789e-02 0.049350 6.510425e-02 0.049400 6.515057e-02 0.049450 6.519684e-02 0.049500 6.524303e-02 0.049550 6.528918e-02 0.049600 6.533527e-02 0.049650 6.538131e-02 0.049700 6.542728e-02 0.049750 6.547316e-02 0.049800 6.551904e-02 0.049850 6.556483e-02 0.049900 6.561058e-02 0.049950 6.565625e-02 0.050000 6.570190e-02 0.050050 6.574746e-02 0.050100 6.579299e-02 0.050150 6.583844e-02 0.050200 6.588383e-02 0.050250 6.592918e-02 0.050300 6.597447e-02 0.050350 6.601966e-02 0.050400 6.606486e-02 0.050450 6.610999e-02 0.050500 6.615505e-02 0.050550 6.620005e-02 0.050600 6.624499e-02 0.050650 6.628987e-02 0.050700 6.633473e-02 0.050750 6.637952e-02 0.050800 6.642423e-02 0.050850 6.646889e-02 0.050900 6.651352e-02 0.050950 6.655807e-02 0.051000 6.660258e-02 0.051050 6.664701e-02 0.051100 6.669142e-02 0.051150 6.673573e-02 0.051200 6.678002e-02 0.051250 6.682425e-02 0.051300 6.686841e-02 0.051350 6.691252e-02 0.051400 6.695658e-02 0.051450 6.700058e-02 0.051500 6.704452e-02 0.051550 6.708842e-02 0.051600 6.713226e-02 0.051650 6.717604e-02 0.051700 6.721978e-02 0.051750 6.726345e-02 0.051800 6.730708e-02 0.051850 6.735063e-02 0.051900 6.739413e-02 0.051950 6.743759e-02 0.052000 6.748099e-02 0.052050 6.752434e-02 0.052100 6.756762e-02 0.052150 6.761089e-02 0.052200 6.765407e-02 0.052250 6.769722e-02 0.052300 6.774028e-02 0.052350 6.778332e-02 0.052400 6.782628e-02 0.052450 6.786921e-02 0.052500 6.791205e-02 0.052550 6.795486e-02 0.052600 6.799763e-02 0.052650 6.804034e-02 0.052700 6.808296e-02 0.052750 6.812557e-02 0.052800 6.816812e-02 0.052850 6.821060e-02 0.052900 6.825305e-02 0.052950 6.829542e-02 0.053000 6.833775e-02 0.053050 6.838003e-02 0.053100 6.842224e-02 0.053150 6.846442e-02 0.053200 6.850655e-02 0.053250 6.854861e-02 0.053300 6.859062e-02 0.053350 6.863260e-02 0.053400 6.867450e-02 0.053450 6.871638e-02 0.053500 6.875816e-02 0.053550 6.879991e-02 0.053600 6.884161e-02 0.053650 6.888327e-02 0.053700 6.892486e-02 0.053750 6.896639e-02 0.053800 6.900789e-02 0.053850 6.904934e-02 0.053900 6.909073e-02 0.053950 6.913203e-02 0.054000 6.917334e-02 0.054050 6.921456e-02 0.054100 6.925576e-02 0.054150 6.929688e-02 0.054200 6.933798e-02 0.054250 6.937901e-02 0.054300 6.941996e-02 0.054350 6.946091e-02 0.054400 6.950178e-02 0.054450 6.954261e-02 0.054500 6.958336e-02 0.054550 6.962410e-02 0.054600 6.966478e-02 0.054650 6.970537e-02 0.054700 6.974596e-02 0.054750 6.978647e-02 0.054800 6.982694e-02 0.054850 6.986735e-02 0.054900 6.990772e-02 0.054950 6.994803e-02 0.055000 6.998831e-02 0.055050 7.002854e-02 0.055100 7.006869e-02 0.055150 7.010879e-02 0.055200 7.014889e-02 0.055250 7.018887e-02 0.055300 7.022886e-02 0.055350 7.026877e-02 0.055400 7.030864e-02 0.055450 7.034844e-02 0.055500 7.038819e-02 0.055550 7.042791e-02 0.055600 7.046758e-02 0.055650 7.050719e-02 0.055700 7.054678e-02 0.055750 7.058629e-02 0.055800 7.062576e-02 0.055850 7.066517e-02 0.055900 7.070455e-02 0.055950 7.074386e-02 0.056000 7.078315e-02 0.056050 7.082234e-02 0.056100 7.086153e-02 0.056150 7.090066e-02 0.056200 7.093971e-02 0.056250 7.097876e-02 0.056300 7.101774e-02 0.056350 7.105667e-02 0.056400 7.109555e-02 0.056450 7.113440e-02 0.056500 7.117315e-02 0.056550 7.121189e-02 0.056600 7.125059e-02 0.056650 7.128921e-02 0.056700 7.132781e-02 0.056750 7.136635e-02 0.056800 7.140485e-02 0.056850 7.144329e-02 0.056900 7.148170e-02 0.056950 7.152004e-02 0.057000 7.155835e-02 0.057050 7.159661e-02 0.057100 7.163480e-02 0.057150 7.167297e-02 0.057200 7.171109e-02 0.057250 7.174915e-02 0.057300 7.178715e-02 0.057350 7.182512e-02 0.057400 7.186306e-02 0.057450 7.190092e-02 0.057500 7.193874e-02 0.057550 7.197652e-02 0.057600 7.201425e-02 0.057650 7.205196e-02 0.057700 7.208958e-02 0.057750 7.212717e-02 0.057800 7.216470e-02 0.057850 7.220222e-02 0.057900 7.223967e-02 0.057950 7.227708e-02 0.058000 7.231443e-02 0.058050 7.235175e-02 0.058100 7.238899e-02 0.058150 7.242622e-02 0.058200 7.246339e-02 0.058250 7.250050e-02 0.058300 7.253760e-02 0.058350 7.257462e-02 0.058400 7.261160e-02 0.058450 7.264856e-02 0.058500 7.268543e-02 0.058550 7.272230e-02 0.058600 7.275908e-02 0.058650 7.279584e-02 0.058700 7.283255e-02 0.058750 7.286921e-02 0.058800 7.290582e-02 0.058850 7.294240e-02 0.058900 7.297893e-02 0.058950 7.301539e-02 0.059000 7.305182e-02 0.059050 7.308821e-02 0.059100 7.312457e-02 0.059150 7.316086e-02 0.059200 7.319712e-02 0.059250 7.323332e-02 0.059300 7.326947e-02 0.059350 7.330559e-02 0.059400 7.334168e-02 0.059450 7.337768e-02 0.059500 7.341369e-02 0.059550 7.344962e-02 0.059600 7.348551e-02 0.059650 7.352135e-02 0.059700 7.355715e-02 0.059750 7.359289e-02 0.059800 7.362862e-02 0.059850 7.366429e-02 0.059900 7.369991e-02 0.059950 7.373550e-02 0.060000 7.377103e-02 0.060050 7.380652e-02 0.060100 7.384196e-02 0.060150 7.387737e-02 0.060200 7.391272e-02 0.060250 7.394803e-02 0.060300 7.398330e-02 0.060350 7.401853e-02 0.060400 7.405371e-02 0.060450 7.408885e-02 0.060500 7.412394e-02 0.060550 7.415899e-02 0.060600 7.419399e-02 0.060650 7.422896e-02 0.060700 7.426387e-02 0.060750 7.429875e-02 0.060800 7.433358e-02 0.060850 7.436836e-02 0.060900 7.440311e-02 0.060950 7.443781e-02 0.061000 7.447247e-02 0.061050 7.450708e-02 0.061100 7.454165e-02 0.061150 7.457618e-02 0.061200 7.461066e-02 0.061250 7.464511e-02 0.061300 7.467950e-02 0.061350 7.471386e-02 0.061400 7.474817e-02 0.061450 7.478244e-02 0.061500 7.481667e-02 0.061550 7.485085e-02 0.061600 7.488499e-02 0.061650 7.491909e-02 0.061700 7.495315e-02 0.061750 7.498716e-02 0.061800 7.502113e-02 0.061850 7.505506e-02 0.061900 7.508895e-02 0.061950 7.512279e-02 0.062000 7.515659e-02 0.062050 7.519035e-02 0.062100 7.522407e-02 0.062150 7.525774e-02 0.062200 7.529137e-02 0.062250 7.532496e-02 0.062300 7.535851e-02 0.062350 7.539202e-02 0.062400 7.542549e-02 0.062450 7.545891e-02 0.062500 7.549229e-02 0.062550 7.552563e-02 0.062600 7.555893e-02 0.062650 7.559218e-02 0.062700 7.562540e-02 0.062750 7.565857e-02 0.062800 7.569171e-02 0.062850 7.572480e-02 0.062900 7.575784e-02 0.062950 7.579085e-02 0.063000 7.582382e-02 0.063050 7.585675e-02 0.063100 7.588963e-02 0.063150 7.592248e-02 0.063200 7.595528e-02 0.063250 7.598804e-02 0.063300 7.602076e-02 0.063350 7.605344e-02 0.063400 7.608608e-02 0.063450 7.611868e-02 0.063500 7.615123e-02 0.063550 7.618375e-02 0.063600 7.621623e-02 0.063650 7.624866e-02 0.063700 7.628106e-02 0.063750 7.631341e-02 0.063800 7.634573e-02 0.063850 7.637800e-02 0.063900 7.641023e-02 0.063950 7.644243e-02 0.064000 7.647458e-02 0.064050 7.650669e-02 0.064100 7.653876e-02 0.064150 7.657080e-02 0.064200 7.660279e-02 0.064250 7.663474e-02 0.064300 7.666666e-02 0.064350 7.669853e-02 0.064400 7.673036e-02 0.064450 7.676215e-02 0.064500 7.679391e-02 0.064550 7.682562e-02 0.064600 7.685730e-02 0.064650 7.688893e-02 0.064700 7.692052e-02 0.064750 7.695208e-02 0.064800 7.698360e-02 0.064850 7.701508e-02 0.064900 7.704651e-02 0.064950 7.707791e-02 0.065000 7.710927e-02 0.065050 7.714059e-02 0.065100 7.717187e-02 0.065150 7.720311e-02 0.065200 7.723431e-02 0.065250 7.726548e-02 0.065300 7.729660e-02 0.065350 7.732769e-02 0.065400 7.735874e-02 0.065450 7.738974e-02 0.065500 7.742071e-02 0.065550 7.745165e-02 0.065600 7.748253e-02 0.065650 7.751339e-02 0.065700 7.754420e-02 0.065750 7.757498e-02 0.065800 7.760572e-02 0.065850 7.763642e-02 0.065900 7.766708e-02 0.065950 7.769770e-02 0.066000 7.772829e-02 0.066050 7.775883e-02 0.066100 7.778934e-02 0.066150 7.781981e-02 0.066200 7.785025e-02 0.066250 7.788064e-02 0.066300 7.791099e-02 0.066350 7.794132e-02 0.066400 7.797159e-02 0.066450 7.800184e-02 0.066500 7.803204e-02 0.066550 7.806221e-02 0.066600 7.809234e-02 0.066650 7.812243e-02 0.066700 7.815248e-02 0.066750 7.818250e-02 0.066800 7.821248e-02 0.066850 7.824242e-02 0.066900 7.827233e-02 0.066950 7.830220e-02 0.067000 7.833202e-02 0.067050 7.836181e-02 0.067100 7.839157e-02 0.067150 7.842129e-02 0.067200 7.845097e-02 0.067250 7.848061e-02 0.067300 7.851022e-02 0.067350 7.853979e-02 0.067400 7.856933e-02 0.067450 7.859882e-02 0.067500 7.862828e-02 0.067550 7.865770e-02 0.067600 7.868709e-02 0.067650 7.871644e-02 0.067700 7.874575e-02 0.067750 7.877502e-02 0.067800 7.880426e-02 0.067850 7.883346e-02 0.067900 7.886263e-02 0.067950 7.889176e-02 0.068000 7.892085e-02 0.068050 7.894991e-02 0.068100 7.897893e-02 0.068150 7.900792e-02 0.068200 7.903686e-02 0.068250 7.906578e-02 0.068300 7.909466e-02 0.068350 7.912349e-02 0.068400 7.915230e-02 0.068450 7.918106e-02 0.068500 7.920980e-02 0.068550 7.923849e-02 0.068600 7.926715e-02 0.068650 7.929578e-02 0.068700 7.932436e-02 0.068750 7.935291e-02 0.068800 7.938143e-02 0.068850 7.940991e-02 0.068900 7.943836e-02 0.068950 7.946677e-02 0.069000 7.949515e-02 0.069050 7.952348e-02 0.069100 7.955179e-02 0.069150 7.958006e-02 0.069200 7.960829e-02 0.069250 7.963649e-02 0.069300 7.966465e-02 0.069350 7.969278e-02 0.069400 7.972087e-02 0.069450 7.974893e-02 0.069500 7.977695e-02 0.069550 7.980494e-02 0.069600 7.983289e-02 0.069650 7.986081e-02 0.069700 7.988869e-02 0.069750 7.991654e-02 0.069800 7.994435e-02 0.069850 7.997213e-02 0.069900 7.999988e-02 0.069950 8.002758e-02 0.070000 8.005526e-02 0.070050 8.008290e-02 0.070100 8.011050e-02 0.070150 8.013807e-02 0.070200 8.016561e-02 0.070250 8.019311e-02 0.070300 8.022058e-02 0.070350 8.024801e-02 0.070400 8.027541e-02 0.070450 8.030278e-02 0.070500 8.033011e-02 0.070550 8.035740e-02 0.070600 8.038466e-02 0.070650 8.041189e-02 0.070700 8.043909e-02 0.070750 8.046625e-02 0.070800 8.049337e-02 0.070850 8.052047e-02 0.070900 8.054753e-02 0.070950 8.057455e-02 0.071000 8.060154e-02 0.071050 8.062850e-02 0.071100 8.065542e-02 0.071150 8.068231e-02 0.071200 8.070917e-02 0.071250 8.073599e-02 0.071300 8.076278e-02 0.071350 8.078954e-02 0.071400 8.081626e-02 0.071450 8.084294e-02 0.071500 8.086960e-02 0.071550 8.089622e-02 0.071600 8.092281e-02 0.071650 8.094937e-02 0.071700 8.097589e-02 0.071750 8.100238e-02 0.071800 8.102884e-02 0.071850 8.105526e-02 0.071900 8.108165e-02 0.071950 8.110801e-02 0.072000 8.113434e-02 0.072050 8.116063e-02 0.072100 8.118688e-02 0.072150 8.121311e-02 0.072200 8.123931e-02 0.072250 8.126547e-02 0.072300 8.129159e-02 0.072350 8.131769e-02 0.072400 8.134375e-02 0.072450 8.136978e-02 0.072500 8.139578e-02 0.072550 8.142175e-02 0.072600 8.144768e-02 0.072650 8.147358e-02 0.072700 8.149944e-02 0.072750 8.152528e-02 0.072800 8.155108e-02 0.072850 8.157685e-02 0.072900 8.160260e-02 0.072950 8.162830e-02 0.073000 8.165397e-02 0.073050 8.167962e-02 0.073100 8.170523e-02 0.073150 8.173081e-02 0.073200 8.175635e-02 0.073250 8.178187e-02 0.073300 8.180735e-02 0.073350 8.183280e-02 0.073400 8.185822e-02 0.073450 8.188361e-02 0.073500 8.190896e-02 0.073550 8.193429e-02 0.073600 8.195958e-02 0.073650 8.198484e-02 0.073700 8.201007e-02 0.073750 8.203527e-02 0.073800 8.206044e-02 0.073850 8.208557e-02 0.073900 8.211067e-02 0.073950 8.213574e-02 0.074000 8.216079e-02 0.074050 8.218580e-02 0.074100 8.221077e-02 0.074150 8.223572e-02 0.074200 8.226064e-02 0.074250 8.228552e-02 0.074300 8.231037e-02 0.074350 8.233520e-02 0.074400 8.235999e-02 0.074450 8.238475e-02 0.074500 8.240948e-02 0.074550 8.243418e-02 0.074600 8.245885e-02 0.074650 8.248348e-02 0.074700 8.250809e-02 0.074750 8.253266e-02 0.074800 8.255721e-02 0.074850 8.258172e-02 0.074900 8.260621e-02 0.074950 8.263066e-02 0.075000 8.265508e-02 0.075050 8.267947e-02 0.075100 8.270383e-02 0.075150 8.272817e-02 0.075200 8.275247e-02 0.075250 8.277674e-02 0.075300 8.280098e-02 0.075350 8.282519e-02 0.075400 8.284937e-02 0.075450 8.287352e-02 0.075500 8.289764e-02 0.075550 8.292172e-02 0.075600 8.294578e-02 0.075650 8.296981e-02 0.075700 8.299381e-02 0.075750 8.301778e-02 0.075800 8.304172e-02 0.075850 8.306563e-02 0.075900 8.308951e-02 0.075950 8.311336e-02 0.076000 8.313717e-02 0.076050 8.316096e-02 0.076100 8.318472e-02 0.076150 8.320845e-02 0.076200 8.323215e-02 0.076250 8.325582e-02 0.076300 8.327947e-02 0.076350 8.330308e-02 0.076400 8.332666e-02 0.076450 8.335022e-02 0.076500 8.337374e-02 0.076550 8.339723e-02 0.076600 8.342070e-02 0.076650 8.344414e-02 0.076700 8.346754e-02 0.076750 8.349092e-02 0.076800 8.351427e-02 0.076850 8.353758e-02 0.076900 8.356087e-02 0.076950 8.358413e-02 0.077000 8.360736e-02 0.077050 8.363057e-02 0.077100 8.365374e-02 0.077150 8.367689e-02 0.077200 8.370000e-02 0.077250 8.372309e-02 0.077300 8.374615e-02 0.077350 8.376917e-02 0.077400 8.379218e-02 0.077450 8.381515e-02 0.077500 8.383809e-02 0.077550 8.386100e-02 0.077600 8.388389e-02 0.077650 8.390674e-02 0.077700 8.392957e-02 0.077750 8.395237e-02 0.077800 8.397515e-02 0.077850 8.399789e-02 0.077900 8.402060e-02 0.077950 8.404329e-02 0.078000 8.406595e-02 0.078050 8.408858e-02 0.078100 8.411117e-02 0.078150 8.413375e-02 0.078200 8.415630e-02 0.078250 8.417881e-02 0.078300 8.420130e-02 0.078350 8.422376e-02 0.078400 8.424619e-02 0.078450 8.426860e-02 0.078500 8.429097e-02 0.078550 8.431332e-02 0.078600 8.433564e-02 0.078650 8.435793e-02 0.078700 8.438020e-02 0.078750 8.440244e-02 0.078800 8.442464e-02 0.078850 8.444683e-02 0.078900 8.446898e-02 0.078950 8.449111e-02 0.079000 8.451320e-02 0.079050 8.453528e-02 0.079100 8.455732e-02 0.079150 8.457933e-02 0.079200 8.460132e-02 0.079250 8.462328e-02 0.079300 8.464521e-02 0.079350 8.466712e-02 0.079400 8.468900e-02 0.079450 8.471085e-02 0.079500 8.473268e-02 0.079550 8.475447e-02 0.079600 8.477624e-02 0.079650 8.479798e-02 0.079700 8.481970e-02 0.079750 8.484139e-02 0.079800 8.486305e-02 0.079850 8.488468e-02 0.079900 8.490629e-02 0.079950 8.492787e-02 0.080000 8.494942e-02 0.080050 8.497095e-02 0.080100 8.499244e-02 0.080150 8.501392e-02 0.080200 8.503536e-02 0.080250 8.505678e-02 0.080300 8.507817e-02 0.080350 8.509954e-02 0.080400 8.512088e-02 0.080450 8.514219e-02 0.080500 8.516347e-02 0.080550 8.518473e-02 0.080600 8.520596e-02 0.080650 8.522717e-02 0.080700 8.524835e-02 0.080750 8.526950e-02 0.080800 8.529063e-02 0.080850 8.531173e-02 0.080900 8.533280e-02 0.080950 8.535385e-02 0.081000 8.537487e-02 0.081050 8.539586e-02 0.081100 8.541683e-02 0.081150 8.543777e-02 0.081200 8.545869e-02 0.081250 8.547958e-02 0.081300 8.550044e-02 0.081350 8.552128e-02 0.081400 8.554209e-02 0.081450 8.556287e-02 0.081500 8.558363e-02 0.081550 8.560437e-02 0.081600 8.562507e-02 0.081650 8.564576e-02 0.081700 8.566641e-02 0.081750 8.568704e-02 0.081800 8.570764e-02 0.081850 8.572822e-02 0.081900 8.574878e-02 0.081950 8.576931e-02 0.082000 8.578981e-02 0.082050 8.581028e-02 0.082100 8.583073e-02 0.082150 8.585116e-02 0.082200 8.587156e-02 0.082250 8.589193e-02 0.082300 8.591228e-02 0.082350 8.593260e-02 0.082400 8.595290e-02 0.082450 8.597317e-02 0.082500 8.599342e-02 0.082550 8.601364e-02 0.082600 8.603384e-02 0.082650 8.605401e-02 0.082700 8.607416e-02 0.082750 8.609428e-02 0.082800 8.611437e-02 0.082850 8.613444e-02 0.082900 8.615449e-02 0.082950 8.617451e-02 0.083000 8.619451e-02 0.083050 8.621448e-02 0.083100 8.623442e-02 0.083150 8.625434e-02 0.083200 8.627424e-02 0.083250 8.629411e-02 0.083300 8.631395e-02 0.083350 8.633377e-02 0.083400 8.635357e-02 0.083450 8.637334e-02 0.083500 8.639309e-02 0.083550 8.641281e-02 0.083600 8.643251e-02 0.083650 8.645218e-02 0.083700 8.647183e-02 0.083750 8.649145e-02 0.083800 8.651105e-02 0.083850 8.653063e-02 0.083900 8.655018e-02 0.083950 8.656971e-02 0.084000 8.658921e-02 0.084050 8.660868e-02 0.084100 8.662814e-02 0.084150 8.664757e-02 0.084200 8.666697e-02 0.084250 8.668635e-02 0.084300 8.670571e-02 0.084350 8.672504e-02 0.084400 8.674435e-02 0.084450 8.676363e-02 0.084500 8.678289e-02 0.084550 8.680213e-02 0.084600 8.682134e-02 0.084650 8.684052e-02 0.084700 8.685969e-02 0.084750 8.687883e-02 0.084800 8.689794e-02 0.084850 8.691704e-02 0.084900 8.693610e-02 0.084950 8.695515e-02 0.085000 8.697417e-02 0.085050 8.699316e-02 0.085100 8.701213e-02 0.085150 8.703109e-02 0.085200 8.705001e-02 0.085250 8.706891e-02 0.085300 8.708779e-02 0.085350 8.710664e-02 0.085400 8.712548e-02 0.085450 8.714428e-02 0.085500 8.716307e-02 0.085550 8.718183e-02 0.085600 8.720056e-02 0.085650 8.721928e-02 0.085700 8.723797e-02 0.085750 8.725664e-02 0.085800 8.727528e-02 0.085850 8.729390e-02 0.085900 8.731250e-02 0.085950 8.733107e-02 0.086000 8.734962e-02 0.086050 8.736815e-02 0.086100 8.738665e-02 0.086150 8.740513e-02 0.086200 8.742359e-02 0.086250 8.744203e-02 0.086300 8.746044e-02 0.086350 8.747883e-02 0.086400 8.749719e-02 0.086450 8.751553e-02 0.086500 8.753386e-02 0.086550 8.755215e-02 0.086600 8.757043e-02 0.086650 8.758868e-02 0.086700 8.760691e-02 0.086750 8.762512e-02 0.086800 8.764330e-02 0.086850 8.766146e-02 0.086900 8.767960e-02 0.086950 8.769771e-02 0.087000 8.771581e-02 0.087050 8.773388e-02 0.087100 8.775192e-02 0.087150 8.776995e-02 0.087200 8.778795e-02 0.087250 8.780593e-02 0.087300 8.782389e-02 0.087350 8.784182e-02 0.087400 8.785973e-02 0.087450 8.787762e-02 0.087500 8.789549e-02 0.087550 8.791334e-02 0.087600 8.793116e-02 0.087650 8.794896e-02 0.087700 8.796674e-02 0.087750 8.798450e-02 0.087800 8.800223e-02 0.087850 8.801994e-02 0.087900 8.803764e-02 0.087950 8.805530e-02 0.088000 8.807295e-02 0.088050 8.809057e-02 0.088100 8.810817e-02 0.088150 8.812575e-02 0.088200 8.814331e-02 0.088250 8.816085e-02 0.088300 8.817836e-02 0.088350 8.819585e-02 0.088400 8.821332e-02 0.088450 8.823077e-02 0.088500 8.824820e-02 0.088550 8.826560e-02 0.088600 8.828299e-02 0.088650 8.830035e-02 0.088700 8.831769e-02 0.088750 8.833501e-02 0.088800 8.835230e-02 0.088850 8.836958e-02 0.088900 8.838683e-02 0.088950 8.840406e-02 0.089000 8.842127e-02 0.089050 8.843846e-02 0.089100 8.845563e-02 0.089150 8.847278e-02 0.089200 8.848990e-02 0.089250 8.850700e-02 0.089300 8.852408e-02 0.089350 8.854115e-02 0.089400 8.855818e-02 0.089450 8.857520e-02 0.089500 8.859220e-02 0.089550 8.860917e-02 0.089600 8.862613e-02 0.089650 8.864306e-02 0.089700 8.865997e-02 0.089750 8.867686e-02 0.089800 8.869373e-02 0.089850 8.871058e-02 0.089900 8.872741e-02 0.089950 8.874421e-02 0.090000 8.876100e-02 0.090050 8.877776e-02 0.090100 8.879450e-02 0.090150 8.881123e-02 0.090200 8.882793e-02 0.090250 8.884461e-02 0.090300 8.886127e-02 0.090350 8.887791e-02 0.090400 8.889453e-02 0.090450 8.891113e-02 0.090500 8.892770e-02 0.090550 8.894426e-02 0.090600 8.896079e-02 0.090650 8.897731e-02 0.090700 8.899380e-02 0.090750 8.901028e-02 0.090800 8.902673e-02 0.090850 8.904316e-02 0.090900 8.905957e-02 0.090950 8.907596e-02 0.091000 8.909233e-02 0.091050 8.910869e-02 0.091100 8.912501e-02 0.091150 8.914132e-02 0.091200 8.915761e-02 0.091250 8.917388e-02 0.091300 8.919013e-02 0.091350 8.920636e-02 0.091400 8.922257e-02 0.091450 8.923876e-02 0.091500 8.925492e-02 0.091550 8.927107e-02 0.091600 8.928720e-02 0.091650 8.930330e-02 0.091700 8.931939e-02 0.091750 8.933546e-02 0.091800 8.935151e-02 0.091850 8.936753e-02 0.091900 8.938354e-02 0.091950 8.939952e-02 0.092000 8.941549e-02 0.092050 8.943144e-02 0.092100 8.944736e-02 0.092150 8.946327e-02 0.092200 8.947916e-02 0.092250 8.949503e-02 0.092300 8.951087e-02 0.092350 8.952670e-02 0.092400 8.954251e-02 0.092450 8.955830e-02 0.092500 8.957407e-02 0.092550 8.958981e-02 0.092600 8.960554e-02 0.092650 8.962125e-02 0.092700 8.963694e-02 0.092750 8.965261e-02 0.092800 8.966826e-02 0.092850 8.968389e-02 0.092900 8.969951e-02 0.092950 8.971510e-02 0.093000 8.973067e-02 0.093050 8.974622e-02 0.093100 8.976176e-02 0.093150 8.977727e-02 0.093200 8.979277e-02 0.093250 8.980824e-02 0.093300 8.982370e-02 0.093350 8.983913e-02 0.093400 8.985455e-02 0.093450 8.986995e-02 0.093500 8.988533e-02 0.093550 8.990069e-02 0.093600 8.991603e-02 0.093650 8.993135e-02 0.093700 8.994665e-02 0.093750 8.996194e-02 0.093800 8.997720e-02 0.093850 8.999245e-02 0.093900 9.000767e-02 0.093950 9.002288e-02 0.094000 9.003806e-02 0.094050 9.005323e-02 0.094100 9.006839e-02 0.094150 9.008351e-02 0.094200 9.009863e-02 0.094250 9.011372e-02 0.094300 9.012879e-02 0.094350 9.014385e-02 0.094400 9.015889e-02 0.094450 9.017391e-02 0.094500 9.018891e-02 0.094550 9.020389e-02 0.094600 9.021885e-02 0.094650 9.023379e-02 0.094700 9.024872e-02 0.094750 9.026362e-02 0.094800 9.027851e-02 0.094850 9.029338e-02 0.094900 9.030823e-02 0.094950 9.032306e-02 0.095000 9.033787e-02 0.095050 9.035267e-02 0.095100 9.036744e-02 0.095150 9.038220e-02 0.095200 9.039694e-02 0.095250 9.041166e-02 0.095300 9.042636e-02 0.095350 9.044105e-02 0.095400 9.045571e-02 0.095450 9.047036e-02 0.095500 9.048499e-02 0.095550 9.049960e-02 0.095600 9.051419e-02 0.095650 9.052876e-02 0.095700 9.054332e-02 0.095750 9.055786e-02 0.095800 9.057238e-02 0.095850 9.058688e-02 0.095900 9.060136e-02 0.095950 9.061583e-02 0.096000 9.063028e-02 0.096050 9.064470e-02 0.096100 9.065912e-02 0.096150 9.067351e-02 0.096200 9.068789e-02 0.096250 9.070224e-02 0.096300 9.071658e-02 0.096350 9.073090e-02 0.096400 9.074521e-02 0.096450 9.075949e-02 0.096500 9.077376e-02 0.096550 9.078801e-02 0.096600 9.080224e-02 0.096650 9.081646e-02 0.096700 9.083065e-02 0.096750 9.084483e-02 0.096800 9.085899e-02 0.096850 9.087314e-02 0.096900 9.088726e-02 0.096950 9.090137e-02 0.097000 9.091546e-02 0.097050 9.092953e-02 0.097100 9.094359e-02 0.097150 9.095763e-02 0.097200 9.097165e-02 0.097250 9.098565e-02 0.097300 9.099963e-02 0.097350 9.101360e-02 0.097400 9.102755e-02 0.097450 9.104148e-02 0.097500 9.105540e-02 0.097550 9.106930e-02 0.097600 9.108318e-02 0.097650 9.109704e-02 0.097700 9.111089e-02 0.097750 9.112472e-02 0.097800 9.113853e-02 0.097850 9.115232e-02 0.097900 9.116610e-02 0.097950 9.117986e-02 0.098000 9.119361e-02 0.098050 9.120733e-02 0.098100 9.122104e-02 0.098150 9.123473e-02 0.098200 9.124840e-02 0.098250 9.126206e-02 0.098300 9.127570e-02 0.098350 9.128932e-02 0.098400 9.130293e-02 0.098450 9.131652e-02 0.098500 9.133009e-02 0.098550 9.134364e-02 0.098600 9.135718e-02 0.098650 9.137070e-02 0.098700 9.138421e-02 0.098750 9.139769e-02 0.098800 9.141116e-02 0.098850 9.142462e-02 0.098900 9.143806e-02 0.098950 9.145148e-02 0.099000 9.146488e-02 0.099050 9.147827e-02 0.099100 9.149164e-02 0.099150 9.150499e-02 0.099200 9.151832e-02 0.099250 9.153165e-02 0.099300 9.154495e-02 0.099350 9.155824e-02 0.099400 9.157151e-02 0.099450 9.158476e-02 0.099500 9.159799e-02 0.099550 9.161121e-02 0.099600 9.162442e-02 0.099650 9.163761e-02 0.099700 9.165078e-02 0.099750 9.166393e-02 0.099800 9.167707e-02 0.099850 9.169019e-02 0.099900 9.170330e-02 0.099950 9.171639e-02 0.100000 9.172946e-02 0.100050 9.174251e-02 0.100100 9.175555e-02 0.100150 9.176858e-02 0.100200 9.178158e-02 0.100250 9.179458e-02 0.100300 9.180755e-02 0.100350 9.182051e-02 0.100400 9.183345e-02 0.100450 9.184638e-02 0.100500 9.185929e-02 0.100550 9.187218e-02 0.100600 9.188506e-02 0.100650 9.189792e-02 0.100700 9.191077e-02 0.100750 9.192360e-02 0.100800 9.193641e-02 0.100850 9.194921e-02 0.100900 9.196199e-02 0.100950 9.197475e-02 0.101000 9.198750e-02 0.101050 9.200024e-02 0.101100 9.201296e-02 0.101150 9.202566e-02 0.101200 9.203834e-02 0.101250 9.205101e-02 0.101300 9.206367e-02 0.101350 9.207631e-02 0.101400 9.208893e-02 0.101450 9.210153e-02 0.101500 9.211413e-02 0.101550 9.212670e-02 0.101600 9.213926e-02 0.101650 9.215181e-02 0.101700 9.216434e-02 0.101750 9.217685e-02 0.101800 9.218935e-02 0.101850 9.220183e-02 0.101900 9.221429e-02 0.101950 9.222674e-02 0.102000 9.223918e-02 0.102050 9.225160e-02 0.102100 9.226400e-02 0.102150 9.227639e-02 0.102200 9.228876e-02 0.102250 9.230112e-02 0.102300 9.231346e-02 0.102350 9.232579e-02 0.102400 9.233810e-02 0.102450 9.235039e-02 0.102500 9.236267e-02 0.102550 9.237494e-02 0.102600 9.238719e-02 0.102650 9.239942e-02 0.102700 9.241164e-02 0.102750 9.242384e-02 0.102800 9.243603e-02 0.102850 9.244821e-02 0.102900 9.246037e-02 0.102950 9.247251e-02 0.103000 9.248464e-02 0.103050 9.249675e-02 0.103100 9.250885e-02 0.103150 9.252093e-02 0.103200 9.253300e-02 0.103250 9.254505e-02 0.103300 9.255709e-02 0.103350 9.256911e-02 0.103400 9.258112e-02 0.103450 9.259311e-02 0.103500 9.260508e-02 0.103550 9.261705e-02 0.103600 9.262900e-02 0.103650 9.264093e-02 0.103700 9.265284e-02 0.103750 9.266475e-02 0.103800 9.267664e-02 0.103850 9.268851e-02 0.103900 9.270037e-02 0.103950 9.271221e-02 0.104000 9.272404e-02 0.104050 9.273585e-02 0.104100 9.274765e-02 0.104150 9.275944e-02 0.104200 9.277120e-02 0.104250 9.278296e-02 0.104300 9.279470e-02 0.104350 9.280643e-02 0.104400 9.281813e-02 0.104450 9.282983e-02 0.104500 9.284151e-02 0.104550 9.285318e-02 0.104600 9.286483e-02 0.104650 9.287647e-02 0.104700 9.288809e-02 0.104750 9.289970e-02 0.104800 9.291130e-02 0.104850 9.292287e-02 0.104900 9.293444e-02 0.104950 9.294599e-02 0.105000 9.295753e-02 0.105050 9.296905e-02 0.105100 9.298056e-02 0.105150 9.299205e-02 0.105200 9.300353e-02 0.105250 9.301500e-02 0.105300 9.302644e-02 0.105350 9.303788e-02 0.105400 9.304930e-02 0.105450 9.306071e-02 0.105500 9.307210e-02 0.105550 9.308348e-02 0.105600 9.309484e-02 0.105650 9.310619e-02 0.105700 9.311753e-02 0.105750 9.312885e-02 0.105800 9.314016e-02 0.105850 9.315146e-02 0.105900 9.316274e-02 0.105950 9.317400e-02 0.106000 9.318525e-02 0.106050 9.319649e-02 0.106100 9.320771e-02 0.106150 9.321892e-02 0.106200 9.323012e-02 0.106250 9.324130e-02 0.106300 9.325247e-02 0.106350 9.326362e-02 0.106400 9.327476e-02 0.106450 9.328589e-02 0.106500 9.329700e-02 0.106550 9.330810e-02 0.106600 9.331918e-02 0.106650 9.333025e-02 0.106700 9.334131e-02 0.106750 9.335235e-02 0.106800 9.336338e-02 0.106850 9.337439e-02 0.106900 9.338539e-02 0.106950 9.339638e-02 0.107000 9.340736e-02 0.107050 9.341832e-02 0.107100 9.342926e-02 0.107150 9.344020e-02 0.107200 9.345111e-02 0.107250 9.346202e-02 0.107300 9.347291e-02 0.107350 9.348379e-02 0.107400 9.349465e-02 0.107450 9.350550e-02 0.107500 9.351634e-02 0.107550 9.352717e-02 0.107600 9.353798e-02 0.107650 9.354877e-02 0.107700 9.355956e-02 0.107750 9.357033e-02 0.107800 9.358108e-02 0.107850 9.359182e-02 0.107900 9.360256e-02 0.107950 9.361327e-02 0.108000 9.362397e-02 0.108050 9.363466e-02 0.108100 9.364534e-02 0.108150 9.365600e-02 0.108200 9.366665e-02 0.108250 9.367729e-02 0.108300 9.368791e-02 0.108350 9.369852e-02 0.108400 9.370912e-02 0.108450 9.371970e-02 0.108500 9.373027e-02 0.108550 9.374083e-02 0.108600 9.375137e-02 0.108650 9.376190e-02 0.108700 9.377241e-02 0.108750 9.378292e-02 0.108800 9.379341e-02 0.108850 9.380389e-02 0.108900 9.381435e-02 0.108950 9.382480e-02 0.109000 9.383524e-02 0.109050 9.384567e-02 0.109100 9.385608e-02 0.109150 9.386648e-02 0.109200 9.387687e-02 0.109250 9.388724e-02 0.109300 9.389760e-02 0.109350 9.390795e-02 0.109400 9.391828e-02 0.109450 9.392861e-02 0.109500 9.393891e-02 0.109550 9.394921e-02 0.109600 9.395949e-02 0.109650 9.396976e-02 0.109700 9.398002e-02 0.109750 9.399027e-02 0.109800 9.400049e-02 0.109850 9.401072e-02 0.109900 9.402092e-02 0.109950 9.403112e-02 0.110000 9.404130e-02 0.110050 9.405146e-02 0.110100 9.406162e-02 0.110150 9.407176e-02 0.110200 9.408189e-02 0.110250 9.409201e-02 0.110300 9.410211e-02 0.110350 9.411221e-02 0.110400 9.412229e-02 0.110450 9.413235e-02 0.110500 9.414241e-02 0.110550 9.415245e-02 0.110600 9.416248e-02 0.110650 9.417249e-02 0.110700 9.418250e-02 0.110750 9.419249e-02 0.110800 9.420247e-02 0.110850 9.421244e-02 0.110900 9.422239e-02 0.110950 9.423233e-02 0.111000 9.424226e-02 0.111050 9.425218e-02 0.111100 9.426208e-02 0.111150 9.427198e-02 0.111200 9.428186e-02 0.111250 9.429172e-02 0.111300 9.430158e-02 0.111350 9.431142e-02 0.111400 9.432125e-02 0.111450 9.433107e-02 0.111500 9.434088e-02 0.111550 9.435067e-02 0.111600 9.436045e-02 0.111650 9.437022e-02 0.111700 9.437998e-02 0.111750 9.438972e-02 0.111800 9.439946e-02 0.111850 9.440918e-02 0.111900 9.441889e-02 0.111950 9.442858e-02 0.112000 9.443827e-02 0.112050 9.444794e-02 0.112100 9.445760e-02 0.112150 9.446725e-02 0.112200 9.447688e-02 0.112250 9.448651e-02 0.112300 9.449612e-02 0.112350 9.450572e-02 0.112400 9.451531e-02 0.112450 9.452488e-02 0.112500 9.453445e-02 0.112550 9.454400e-02 0.112600 9.455354e-02 0.112650 9.456307e-02 0.112700 9.457259e-02 0.112750 9.458209e-02 0.112800 9.459158e-02 0.112850 9.460106e-02 0.112900 9.461053e-02 0.112950 9.461998e-02 0.113000 9.462943e-02 0.113050 9.463887e-02 0.113100 9.464829e-02 0.113150 9.465770e-02 0.113200 9.466710e-02 0.113250 9.467648e-02 0.113300 9.468586e-02 0.113350 9.469522e-02 0.113400 9.470457e-02 0.113450 9.471391e-02 0.113500 9.472324e-02 0.113550 9.473255e-02 0.113600 9.474186e-02 0.113650 9.475115e-02 0.113700 9.476043e-02 0.113750 9.476970e-02 0.113800 9.477896e-02 0.113850 9.478821e-02 0.113900 9.479744e-02 0.113950 9.480667e-02 0.114000 9.481588e-02 0.114050 9.482508e-02 0.114100 9.483427e-02 0.114150 9.484344e-02 0.114200 9.485261e-02 0.114250 9.486176e-02 0.114300 9.487091e-02 0.114350 9.488004e-02 0.114400 9.488916e-02 0.114450 9.489827e-02 0.114500 9.490737e-02 0.114550 9.491645e-02 0.114600 9.492553e-02 0.114650 9.493459e-02 0.114700 9.494364e-02 0.114750 9.495268e-02 0.114800 9.496171e-02 0.114850 9.497073e-02 0.114900 9.497974e-02 0.114950 9.498873e-02 0.115000 9.499772e-02 0.115050 9.500669e-02 0.115100 9.501565e-02 0.115150 9.502460e-02 0.115200 9.503354e-02 0.115250 9.504247e-02 0.115300 9.505139e-02 0.115350 9.506030e-02 0.115400 9.506919e-02 0.115450 9.507808e-02 0.115500 9.508695e-02 0.115550 9.509581e-02 0.115600 9.510466e-02 0.115650 9.511350e-02 0.115700 9.512233e-02 0.115750 9.513115e-02 0.115800 9.513996e-02 0.115850 9.514875e-02 0.115900 9.515753e-02 0.115950 9.516631e-02 0.116000 9.517507e-02 0.116050 9.518382e-02 0.116100 9.519256e-02 0.116150 9.520130e-02 0.116200 9.521001e-02 0.116250 9.521872e-02 0.116300 9.522742e-02 0.116350 9.523610e-02 0.116400 9.524478e-02 0.116450 9.525344e-02 0.116500 9.526210e-02 0.116550 9.527074e-02 0.116600 9.527937e-02 0.116650 9.528800e-02 0.116700 9.529660e-02 0.116750 9.530521e-02 0.116800 9.531379e-02 0.116850 9.532237e-02 0.116900 9.533094e-02 0.116950 9.533950e-02 0.117000 9.534804e-02 0.117050 9.535658e-02 0.117100 9.536510e-02 0.117150 9.537362e-02 0.117200 9.538212e-02 0.117250 9.539062e-02 0.117300 9.539910e-02 0.117350 9.540757e-02 0.117400 9.541603e-02 0.117450 9.542448e-02 0.117500 9.543292e-02 0.117550 9.544135e-02 0.117600 9.544977e-02 0.117650 9.545818e-02 0.117700 9.546658e-02 0.117750 9.547496e-02 0.117800 9.548334e-02 0.117850 9.549171e-02 0.117900 9.550007e-02 0.117950 9.550841e-02 0.118000 9.551674e-02 0.118050 9.552507e-02 0.118100 9.553339e-02 0.118150 9.554169e-02 0.118200 9.554998e-02 0.118250 9.555826e-02 0.118300 9.556654e-02 0.118350 9.557480e-02 0.118400 9.558305e-02 0.118450 9.559130e-02 0.118500 9.559953e-02 0.118550 9.560775e-02 0.118600 9.561596e-02 0.118650 9.562416e-02 0.118700 9.563235e-02 0.118750 9.564053e-02 0.118800 9.564871e-02 0.118850 9.565687e-02 0.118900 9.566502e-02 0.118950 9.567316e-02 0.119000 9.568129e-02 0.119050 9.568941e-02 0.119100 9.569751e-02 0.119150 9.570561e-02 0.119200 9.571370e-02 0.119250 9.572178e-02 0.119300 9.572985e-02 0.119350 9.573791e-02 0.119400 9.574596e-02 0.119450 9.575399e-02 0.119500 9.576202e-02 0.119550 9.577004e-02 0.119600 9.577805e-02 0.119650 9.578605e-02 0.119700 9.579404e-02 0.119750 9.580202e-02 0.119800 9.580998e-02 0.119850 9.581794e-02 0.119900 9.582589e-02 0.119950 9.583383e-02 0.120000 9.584176e-02 0.120050 9.584968e-02 0.120100 9.585759e-02 0.120150 9.586549e-02 0.120200 9.587338e-02 0.120250 9.588125e-02 0.120300 9.588912e-02 0.120350 9.589698e-02 0.120400 9.590483e-02 0.120450 9.591267e-02 0.120500 9.592050e-02 0.120550 9.592832e-02 0.120600 9.593613e-02 0.120650 9.594394e-02 0.120700 9.595173e-02 0.120750 9.595951e-02 0.120800 9.596728e-02 0.120850 9.597504e-02 0.120900 9.598279e-02 0.120950 9.599054e-02 0.121000 9.599827e-02 0.121050 9.600600e-02 0.121100 9.601371e-02 0.121150 9.602141e-02 0.121200 9.602911e-02 0.121250 9.603679e-02 0.121300 9.604447e-02 0.121350 9.605213e-02 0.121400 9.605979e-02 0.121450 9.606743e-02 0.121500 9.607507e-02 0.121550 9.608270e-02 0.121600 9.609031e-02 0.121650 9.609793e-02 0.121700 9.610553e-02 0.121750 9.611311e-02 0.121800 9.612069e-02 0.121850 9.612827e-02 0.121900 9.613583e-02 0.121950 9.614338e-02 0.122000 9.615092e-02 0.122050 9.615845e-02 0.122100 9.616598e-02 0.122150 9.617349e-02 0.122200 9.618099e-02 0.122250 9.618849e-02 0.122300 9.619597e-02 0.122350 9.620345e-02 0.122400 9.621092e-02 0.122450 9.621838e-02 0.122500 9.622582e-02 0.122550 9.623326e-02 0.122600 9.624069e-02 0.122650 9.624811e-02 0.122700 9.625553e-02 0.122750 9.626293e-02 0.122800 9.627032e-02 0.122850 9.627770e-02 0.122900 9.628508e-02 0.122950 9.629244e-02 0.123000 9.629980e-02 0.123050 9.630714e-02 0.123100 9.631448e-02 0.123150 9.632181e-02 0.123200 9.632913e-02 0.123250 9.633644e-02 0.123300 9.634374e-02 0.123350 9.635103e-02 0.123400 9.635831e-02 0.123450 9.636559e-02 0.123500 9.637286e-02 0.123550 9.638011e-02 0.123600 9.638736e-02 0.123650 9.639459e-02 0.123700 9.640182e-02 0.123750 9.640904e-02 0.123800 9.641625e-02 0.123850 9.642345e-02 0.123900 9.643064e-02 0.123950 9.643783e-02 0.124000 9.644500e-02 0.124050 9.645217e-02 0.124100 9.645932e-02 0.124150 9.646647e-02 0.124200 9.647361e-02 0.124250 9.648074e-02 0.124300 9.648786e-02 0.124350 9.649497e-02 0.124400 9.650207e-02 0.124450 9.650917e-02 0.124500 9.651625e-02 0.124550 9.652333e-02 0.124600 9.653040e-02 0.124650 9.653746e-02 0.124700 9.654450e-02 0.124750 9.655155e-02 0.124800 9.655858e-02 0.124850 9.656560e-02 0.124900 9.657262e-02 0.124950 9.657962e-02 0.125000 9.658662e-02 0.125050 9.659361e-02 0.125100 9.660059e-02 0.125150 9.660756e-02 0.125200 9.661452e-02 0.125250 9.662148e-02 0.125300 9.662842e-02 0.125350 9.663536e-02 0.125400 9.664228e-02 0.125450 9.664920e-02 0.125500 9.665611e-02 0.125550 9.666301e-02 0.125600 9.666991e-02 0.125650 9.667679e-02 0.125700 9.668367e-02 0.125750 9.669053e-02 0.125800 9.669740e-02 0.125850 9.670424e-02 0.125900 9.671108e-02 0.125950 9.671792e-02 0.126000 9.672474e-02 0.126050 9.673156e-02 0.126100 9.673836e-02 0.126150 9.674517e-02 0.126200 9.675196e-02 0.126250 9.675874e-02 0.126300 9.676551e-02 0.126350 9.677228e-02 0.126400 9.677903e-02 0.126450 9.678578e-02 0.126500 9.679252e-02 0.126550 9.679925e-02 0.126600 9.680597e-02 0.126650 9.681269e-02 0.126700 9.681939e-02 0.126750 9.682609e-02 0.126800 9.683278e-02 0.126850 9.683946e-02 0.126900 9.684613e-02 0.126950 9.685280e-02 0.127000 9.685945e-02 0.127050 9.686610e-02 0.127100 9.687274e-02 0.127150 9.687937e-02 0.127200 9.688599e-02 0.127250 9.689261e-02 0.127300 9.689922e-02 0.127350 9.690581e-02 0.127400 9.691240e-02 0.127450 9.691898e-02 0.127500 9.692556e-02 0.127550 9.693212e-02 0.127600 9.693868e-02 0.127650 9.694523e-02 0.127700 9.695177e-02 0.127750 9.695830e-02 0.127800 9.696482e-02 0.127850 9.697134e-02 0.127900 9.697785e-02 0.127950 9.698435e-02 0.128000 9.699084e-02 0.128050 9.699732e-02 0.128100 9.700380e-02 0.128150 9.701026e-02 0.128200 9.701673e-02 0.128250 9.702318e-02 0.128300 9.702962e-02 0.128350 9.703605e-02 0.128400 9.704248e-02 0.128450 9.704890e-02 0.128500 9.705531e-02 0.128550 9.706171e-02 0.128600 9.706811e-02 0.128650 9.707450e-02 0.128700 9.708087e-02 0.128750 9.708725e-02 0.128800 9.709361e-02 0.128850 9.709996e-02 0.128900 9.710631e-02 0.128950 9.711265e-02 0.129000 9.711898e-02 0.129050 9.712530e-02 0.129100 9.713162e-02 0.129150 9.713793e-02 0.129200 9.714423e-02 0.129250 9.715052e-02 0.129300 9.715680e-02 0.129350 9.716308e-02 0.129400 9.716935e-02 0.129450 9.717561e-02 0.129500 9.718186e-02 0.129550 9.718811e-02 0.129600 9.719434e-02 0.129650 9.720057e-02 0.129700 9.720679e-02 0.129750 9.721301e-02 0.129800 9.721921e-02 0.129850 9.722541e-02 0.129900 9.723160e-02 0.129950 9.723778e-02 0.130000 9.724396e-02 0.130050 9.725013e-02 0.130100 9.725629e-02 0.130150 9.726244e-02 0.130200 9.726858e-02 0.130250 9.727472e-02 0.130300 9.728085e-02 0.130350 9.728697e-02 0.130400 9.729308e-02 0.130450 9.729919e-02 0.130500 9.730529e-02 0.130550 9.731138e-02 0.130600 9.731746e-02 0.130650 9.732354e-02 0.130700 9.732960e-02 0.130750 9.733566e-02 0.130800 9.734172e-02 0.130850 9.734776e-02 0.130900 9.735380e-02 0.130950 9.735983e-02 0.131000 9.736585e-02 0.131050 9.737187e-02 0.131100 9.737787e-02 0.131150 9.738387e-02 0.131200 9.738987e-02 0.131250 9.739585e-02 0.131300 9.740183e-02 0.131350 9.740780e-02 0.131400 9.741376e-02 0.131450 9.741972e-02 0.131500 9.742566e-02 0.131550 9.743160e-02 0.131600 9.743754e-02 0.131650 9.744346e-02 0.131700 9.744938e-02 0.131750 9.745529e-02 0.131800 9.746119e-02 0.131850 9.746709e-02 0.131900 9.747298e-02 0.131950 9.747886e-02 0.132000 9.748473e-02 0.132050 9.749060e-02 0.132100 9.749646e-02 0.132150 9.750231e-02 0.132200 9.750816e-02 0.132250 9.751399e-02 0.132300 9.751982e-02 0.132350 9.752564e-02 0.132400 9.753146e-02 0.132450 9.753727e-02 0.132500 9.754307e-02 0.132550 9.754886e-02 0.132600 9.755465e-02 0.132650 9.756043e-02 0.132700 9.756620e-02 0.132750 9.757197e-02 0.132800 9.757772e-02 0.132850 9.758347e-02 0.132900 9.758922e-02 0.132950 9.759495e-02 0.133000 9.760068e-02 0.133050 9.760640e-02 0.133100 9.761212e-02 0.133150 9.761782e-02 0.133200 9.762352e-02 0.133250 9.762922e-02 0.133300 9.763490e-02 0.133350 9.764058e-02 0.133400 9.764625e-02 0.133450 9.765192e-02 0.133500 9.765758e-02 0.133550 9.766323e-02 0.133600 9.766887e-02 0.133650 9.767451e-02 0.133700 9.768014e-02 0.133750 9.768576e-02 0.133800 9.769137e-02 0.133850 9.769698e-02 0.133900 9.770258e-02 0.133950 9.770818e-02 0.134000 9.771377e-02 0.134050 9.771935e-02 0.134100 9.772492e-02 0.134150 9.773049e-02 0.134200 9.773605e-02 0.134250 9.774160e-02 0.134300 9.774714e-02 0.134350 9.775268e-02 0.134400 9.775821e-02 0.134450 9.776374e-02 0.134500 9.776926e-02 0.134550 9.777477e-02 0.134600 9.778027e-02 0.134650 9.778577e-02 0.134700 9.779126e-02 0.134750 9.779674e-02 0.134800 9.780222e-02 0.134850 9.780769e-02 0.134900 9.781315e-02 0.134950 9.781861e-02 0.135000 9.782406e-02 0.135050 9.782950e-02 0.135100 9.783494e-02 0.135150 9.784037e-02 0.135200 9.784579e-02 0.135250 9.785120e-02 0.135300 9.785661e-02 0.135350 9.786201e-02 0.135400 9.786741e-02 0.135450 9.787280e-02 0.135500 9.787818e-02 0.135550 9.788355e-02 0.135600 9.788892e-02 0.135650 9.789428e-02 0.135700 9.789964e-02 0.135750 9.790499e-02 0.135800 9.791033e-02 0.135850 9.791566e-02 0.135900 9.792099e-02 0.135950 9.792631e-02 0.136000 9.793163e-02 0.136050 9.793694e-02 0.136100 9.794224e-02 0.136150 9.794753e-02 0.136200 9.795282e-02 0.136250 9.795810e-02 0.136300 9.796338e-02 0.136350 9.796865e-02 0.136400 9.797391e-02 0.136450 9.797916e-02 0.136500 9.798441e-02 0.136550 9.798965e-02 0.136600 9.799489e-02 0.136650 9.800012e-02 0.136700 9.800534e-02 0.136750 9.801056e-02 0.136800 9.801577e-02 0.136850 9.802097e-02 0.136900 9.802617e-02 0.136950 9.803136e-02 0.137000 9.803654e-02 0.137050 9.804172e-02 0.137100 9.804689e-02 0.137150 9.805205e-02 0.137200 9.805721e-02 0.137250 9.806236e-02 0.137300 9.806751e-02 0.137350 9.807265e-02 0.137400 9.807778e-02 0.137450 9.808290e-02 0.137500 9.808802e-02 0.137550 9.809314e-02 0.137600 9.809824e-02 0.137650 9.810334e-02 0.137700 9.810844e-02 0.137750 9.811352e-02 0.137800 9.811860e-02 0.137850 9.812368e-02 0.137900 9.812875e-02 0.137950 9.813381e-02 0.138000 9.813886e-02 0.138050 9.814391e-02 0.138100 9.814896e-02 0.138150 9.815399e-02 0.138200 9.815902e-02 0.138250 9.816405e-02 0.138300 9.816907e-02 0.138350 9.817408e-02 0.138400 9.817908e-02 0.138450 9.818408e-02 0.138500 9.818908e-02 0.138550 9.819406e-02 0.138600 9.819904e-02 0.138650 9.820402e-02 0.138700 9.820898e-02 0.138750 9.821395e-02 0.138800 9.821890e-02 0.138850 9.822385e-02 0.138900 9.822879e-02 0.138950 9.823373e-02 0.139000 9.823866e-02 0.139050 9.824359e-02 0.139100 9.824850e-02 0.139150 9.825342e-02 0.139200 9.825832e-02 0.139250 9.826322e-02 0.139300 9.826812e-02 0.139350 9.827301e-02 0.139400 9.827789e-02 0.139450 9.828276e-02 0.139500 9.828763e-02 0.139550 9.829250e-02 0.139600 9.829735e-02 0.139650 9.830220e-02 0.139700 9.830705e-02 0.139750 9.831189e-02 0.139800 9.831672e-02 0.139850 9.832155e-02 0.139900 9.832637e-02 0.139950 9.833119e-02 0.140000 9.833599e-02 0.140050 9.834080e-02 0.140100 9.834559e-02 0.140150 9.835039e-02 0.140200 9.835517e-02 0.140250 9.835995e-02 0.140300 9.836472e-02 0.140350 9.836949e-02 0.140400 9.837425e-02 0.140450 9.837901e-02 0.140500 9.838376e-02 0.140550 9.838850e-02 0.140600 9.839324e-02 0.140650 9.839797e-02 0.140700 9.840269e-02 0.140750 9.840741e-02 0.140800 9.841213e-02 0.140850 9.841684e-02 0.140900 9.842154e-02 0.140950 9.842623e-02 0.141000 9.843092e-02 0.141050 9.843561e-02 0.141100 9.844029e-02 0.141150 9.844496e-02 0.141200 9.844963e-02 0.141250 9.845429e-02 0.141300 9.845894e-02 0.141350 9.846359e-02 0.141400 9.846824e-02 0.141450 9.847287e-02 0.141500 9.847751e-02 0.141550 9.848213e-02 0.141600 9.848675e-02 0.141650 9.849137e-02 0.141700 9.849598e-02 0.141750 9.850058e-02 0.141800 9.850518e-02 0.141850 9.850977e-02 0.141900 9.851436e-02 0.141950 9.851894e-02 0.142000 9.852351e-02 0.142050 9.852808e-02 0.142100 9.853264e-02 0.142150 9.853720e-02 0.142200 9.854175e-02 0.142250 9.854630e-02 0.142300 9.855084e-02 0.142350 9.855537e-02 0.142400 9.855990e-02 0.142450 9.856442e-02 0.142500 9.856894e-02 0.142550 9.857345e-02 0.142600 9.857796e-02 0.142650 9.858246e-02 0.142700 9.858696e-02 0.142750 9.859145e-02 0.142800 9.859593e-02 0.142850 9.860041e-02 0.142900 9.860488e-02 0.142950 9.860935e-02 0.143000 9.861381e-02 0.143050 9.861827e-02 0.143100 9.862272e-02 0.143150 9.862716e-02 0.143200 9.863160e-02 0.143250 9.863603e-02 0.143300 9.864046e-02 0.143350 9.864488e-02 0.143400 9.864930e-02 0.143450 9.865371e-02 0.143500 9.865812e-02 0.143550 9.866252e-02 0.143600 9.866692e-02 0.143650 9.867131e-02 0.143700 9.867569e-02 0.143750 9.868007e-02 0.143800 9.868444e-02 0.143850 9.868881e-02 0.143900 9.869317e-02 0.143950 9.869753e-02 0.144000 9.870188e-02 0.144050 9.870623e-02 0.144100 9.871057e-02 0.144150 9.871490e-02 0.144200 9.871923e-02 0.144250 9.872356e-02 0.144300 9.872787e-02 0.144350 9.873219e-02 0.144400 9.873650e-02 0.144450 9.874080e-02 0.144500 9.874510e-02 0.144550 9.874939e-02 0.144600 9.875368e-02 0.144650 9.875796e-02 0.144700 9.876223e-02 0.144750 9.876650e-02 0.144800 9.877077e-02 0.144850 9.877503e-02 0.144900 9.877928e-02 0.144950 9.878353e-02 0.145000 9.878778e-02 0.145050 9.879201e-02 0.145100 9.879625e-02 0.145150 9.880048e-02 0.145200 9.880470e-02 0.145250 9.880892e-02 0.145300 9.881313e-02 0.145350 9.881734e-02 0.145400 9.882154e-02 0.145450 9.882573e-02 0.145500 9.882993e-02 0.145550 9.883411e-02 0.145600 9.883829e-02 0.145650 9.884247e-02 0.145700 9.884664e-02 0.145750 9.885080e-02 0.145800 9.885496e-02 0.145850 9.885912e-02 0.145900 9.886327e-02 0.145950 9.886741e-02 0.146000 9.887155e-02 0.146050 9.887569e-02 0.146100 9.887981e-02 0.146150 9.888394e-02 0.146200 9.888806e-02 0.146250 9.889217e-02 0.146300 9.889628e-02 0.146350 9.890038e-02 0.146400 9.890448e-02 0.146450 9.890857e-02 0.146500 9.891266e-02 0.146550 9.891674e-02 0.146600 9.892082e-02 0.146650 9.892489e-02 0.146700 9.892896e-02 0.146750 9.893302e-02 0.146800 9.893708e-02 0.146850 9.894113e-02 0.146900 9.894518e-02 0.146950 9.894922e-02 0.147000 9.895326e-02 0.147050 9.895729e-02 0.147100 9.896132e-02 0.147150 9.896534e-02 0.147200 9.896936e-02 0.147250 9.897337e-02 0.147300 9.897737e-02 0.147350 9.898138e-02 0.147400 9.898537e-02 0.147450 9.898936e-02 0.147500 9.899335e-02 0.147550 9.899733e-02 0.147600 9.900131e-02 0.147650 9.900528e-02 0.147700 9.900925e-02 0.147750 9.901321e-02 0.147800 9.901717e-02 0.147850 9.902112e-02 0.147900 9.902507e-02 0.147950 9.902901e-02 0.148000 9.903295e-02 0.148050 9.903688e-02 0.148100 9.904081e-02 0.148150 9.904473e-02 0.148200 9.904865e-02 0.148250 9.905256e-02 0.148300 9.905647e-02 0.148350 9.906037e-02 0.148400 9.906427e-02 0.148450 9.906816e-02 0.148500 9.907205e-02 0.148550 9.907594e-02 0.148600 9.907981e-02 0.148650 9.908369e-02 0.148700 9.908756e-02 0.148750 9.909142e-02 0.148800 9.909528e-02 0.148850 9.909913e-02 0.148900 9.910298e-02 0.148950 9.910683e-02 0.149000 9.911067e-02 0.149050 9.911450e-02 0.149100 9.911834e-02 0.149150 9.912216e-02 0.149200 9.912598e-02 0.149250 9.912980e-02 0.149300 9.913361e-02 0.149350 9.913742e-02 0.149400 9.914122e-02 0.149450 9.914502e-02 0.149500 9.914881e-02 0.149550 9.915260e-02 0.149600 9.915638e-02 0.149650 9.916016e-02 0.149700 9.916393e-02 0.149750 9.916770e-02 0.149800 9.917146e-02 0.149850 9.917522e-02 0.149900 9.917898e-02 0.149950 9.918273e-02 0.150000 9.918647e-02 0.150050 9.919021e-02 0.150100 9.919395e-02 0.150150 9.919768e-02 0.150200 9.920141e-02 0.150250 9.920513e-02 0.150300 9.920885e-02 0.150350 9.921256e-02 0.150400 9.921627e-02 0.150450 9.921997e-02 0.150500 9.922367e-02 0.150550 9.922736e-02 0.150600 9.923105e-02 0.150650 9.923474e-02 0.150700 9.923842e-02 0.150750 9.924209e-02 0.150800 9.924576e-02 0.150850 9.924943e-02 0.150900 9.925309e-02 0.150950 9.925675e-02 0.151000 9.926040e-02 0.151050 9.926405e-02 0.151100 9.926770e-02 0.151150 9.927133e-02 0.151200 9.927497e-02 0.151250 9.927860e-02 0.151300 9.928223e-02 0.151350 9.928585e-02 0.151400 9.928946e-02 0.151450 9.929307e-02 0.151500 9.929668e-02 0.151550 9.930028e-02 0.151600 9.930388e-02 0.151650 9.930748e-02 0.151700 9.931107e-02 0.151750 9.931465e-02 0.151800 9.931823e-02 0.151850 9.932181e-02 0.151900 9.932538e-02 0.151950 9.932895e-02 0.152000 9.933251e-02 0.152050 9.933607e-02 0.152100 9.933962e-02 0.152150 9.934317e-02 0.152200 9.934672e-02 0.152250 9.935026e-02 0.152300 9.935379e-02 0.152350 9.935732e-02 0.152400 9.936085e-02 0.152450 9.936437e-02 0.152500 9.936789e-02 0.152550 9.937141e-02 0.152600 9.937492e-02 0.152650 9.937842e-02 0.152700 9.938192e-02 0.152750 9.938542e-02 0.152800 9.938891e-02 0.152850 9.939240e-02 0.152900 9.939588e-02 0.152950 9.939936e-02 0.153000 9.940284e-02 0.153050 9.940631e-02 0.153100 9.940977e-02 0.153150 9.941323e-02 0.153200 9.941669e-02 0.153250 9.942014e-02 0.153300 9.942359e-02 0.153350 9.942704e-02 0.153400 9.943048e-02 0.153450 9.943391e-02 0.153500 9.943734e-02 0.153550 9.944077e-02 0.153600 9.944419e-02 0.153650 9.944761e-02 0.153700 9.945103e-02 0.153750 9.945444e-02 0.153800 9.945784e-02 0.153850 9.946125e-02 0.153900 9.946464e-02 0.153950 9.946804e-02 0.154000 9.947142e-02 0.154050 9.947481e-02 0.154100 9.947819e-02 0.154150 9.948157e-02 0.154200 9.948494e-02 0.154250 9.948831e-02 0.154300 9.949167e-02 0.154350 9.949503e-02 0.154400 9.949838e-02 0.154450 9.950173e-02 0.154500 9.950508e-02 0.154550 9.950842e-02 0.154600 9.951176e-02 0.154650 9.951510e-02 0.154700 9.951843e-02 0.154750 9.952175e-02 0.154800 9.952507e-02 0.154850 9.952839e-02 0.154900 9.953171e-02 0.154950 9.953501e-02 0.155000 9.953832e-02 0.155050 9.954162e-02 0.155100 9.954492e-02 0.155150 9.954821e-02 0.155200 9.955150e-02 0.155250 9.955478e-02 0.155300 9.955806e-02 0.155350 9.956134e-02 0.155400 9.956461e-02 0.155450 9.956788e-02 0.155500 9.957115e-02 0.155550 9.957441e-02 0.155600 9.957766e-02 0.155650 9.958091e-02 0.155700 9.958416e-02 0.155750 9.958741e-02 0.155800 9.959065e-02 0.155850 9.959388e-02 0.155900 9.959711e-02 0.155950 9.960034e-02 0.156000 9.960356e-02 0.156050 9.960678e-02 0.156100 9.961000e-02 0.156150 9.961321e-02 0.156200 9.961642e-02 0.156250 9.961962e-02 0.156300 9.962282e-02 0.156350 9.962602e-02 0.156400 9.962921e-02 0.156450 9.963240e-02 0.156500 9.963558e-02 0.156550 9.963876e-02 0.156600 9.964194e-02 0.156650 9.964511e-02 0.156700 9.964827e-02 0.156750 9.965144e-02 0.156800 9.965460e-02 0.156850 9.965775e-02 0.156900 9.966091e-02 0.156950 9.966405e-02 0.157000 9.966720e-02 0.157050 9.967034e-02 0.157100 9.967347e-02 0.157150 9.967661e-02 0.157200 9.967973e-02 0.157250 9.968286e-02 0.157300 9.968598e-02 0.157350 9.968910e-02 0.157400 9.969221e-02 0.157450 9.969532e-02 0.157500 9.969842e-02 0.157550 9.970152e-02 0.157600 9.970462e-02 0.157650 9.970771e-02 0.157700 9.971080e-02 0.157750 9.971389e-02 0.157800 9.971697e-02 0.157850 9.972005e-02 0.157900 9.972312e-02 0.157950 9.972619e-02 0.158000 9.972926e-02 0.158050 9.973232e-02 0.158100 9.973538e-02 0.158150 9.973844e-02 0.158200 9.974149e-02 0.158250 9.974453e-02 0.158300 9.974758e-02 0.158350 9.975062e-02 0.158400 9.975365e-02 0.158450 9.975669e-02 0.158500 9.975971e-02 0.158550 9.976274e-02 0.158600 9.976576e-02 0.158650 9.976878e-02 0.158700 9.977179e-02 0.158750 9.977480e-02 0.158800 9.977780e-02 0.158850 9.978081e-02 0.158900 9.978380e-02 0.158950 9.978680e-02 0.159000 9.978979e-02 0.159050 9.979278e-02 0.159100 9.979576e-02 0.159150 9.979874e-02 0.159200 9.980172e-02 0.159250 9.980469e-02 0.159300 9.980766e-02 0.159350 9.981062e-02 0.159400 9.981358e-02 0.159450 9.981654e-02 0.159500 9.981949e-02 0.159550 9.982244e-02 0.159600 9.982539e-02 0.159650 9.982833e-02 0.159700 9.983127e-02 0.159750 9.983420e-02 0.159800 9.983714e-02 0.159850 9.984006e-02 0.159900 9.984299e-02 0.159950 9.984591e-02 0.160000 9.984883e-02 0.160050 9.985174e-02 0.160100 9.985465e-02 0.160150 9.985755e-02 0.160200 9.986046e-02 0.160250 9.986335e-02 0.160300 9.986625e-02 0.160350 9.986914e-02 0.160400 9.987203e-02 0.160450 9.987491e-02 0.160500 9.987779e-02 0.160550 9.988067e-02 0.160600 9.988354e-02 0.160650 9.988641e-02 0.160700 9.988928e-02 0.160750 9.989214e-02 0.160800 9.989500e-02 0.160850 9.989786e-02 0.160900 9.990071e-02 0.160950 9.990356e-02 0.161000 9.990640e-02 0.161050 9.990924e-02 0.161100 9.991208e-02 0.161150 9.991492e-02 0.161200 9.991775e-02 0.161250 9.992057e-02 0.161300 9.992340e-02 0.161350 9.992622e-02 0.161400 9.992903e-02 0.161450 9.993185e-02 0.161500 9.993466e-02 0.161550 9.993746e-02 0.161600 9.994026e-02 0.161650 9.994306e-02 0.161700 9.994586e-02 0.161750 9.994865e-02 0.161800 9.995144e-02 0.161850 9.995422e-02 0.161900 9.995701e-02 0.161950 9.995978e-02 0.162000 9.996256e-02 0.162050 9.996533e-02 0.162100 9.996810e-02 0.162150 9.997086e-02 0.162200 9.997362e-02 0.162250 9.997638e-02 0.162300 9.997913e-02 0.162350 9.998188e-02 0.162400 9.998463e-02 0.162450 9.998738e-02 0.162500 9.999011e-02 0.162550 9.999285e-02 0.162600 9.999559e-02 0.162650 9.999832e-02 0.162700 1.000010e-01 0.162750 1.000038e-01 0.162800 1.000065e-01 0.162850 1.000092e-01 0.162900 1.000119e-01 0.162950 1.000146e-01 0.163000 1.000173e-01 0.163050 1.000200e-01 0.163100 1.000227e-01 0.163150 1.000254e-01 0.163200 1.000281e-01 0.163250 1.000308e-01 0.163300 1.000335e-01 0.163350 1.000362e-01 0.163400 1.000389e-01 0.163450 1.000415e-01 0.163500 1.000442e-01 0.163550 1.000469e-01 0.163600 1.000495e-01 0.163650 1.000522e-01 0.163700 1.000549e-01 0.163750 1.000575e-01 0.163800 1.000602e-01 0.163850 1.000628e-01 0.163900 1.000655e-01 0.163950 1.000681e-01 0.164000 1.000707e-01 0.164050 1.000734e-01 0.164100 1.000760e-01 0.164150 1.000786e-01 0.164200 1.000813e-01 0.164250 1.000839e-01 0.164300 1.000865e-01 0.164350 1.000891e-01 0.164400 1.000917e-01 0.164450 1.000944e-01 0.164500 1.000970e-01 0.164550 1.000996e-01 0.164600 1.001022e-01 0.164650 1.001048e-01 0.164700 1.001074e-01 0.164750 1.001099e-01 0.164800 1.001125e-01 0.164850 1.001151e-01 0.164900 1.001177e-01 0.164950 1.001203e-01 0.165000 1.001228e-01 0.165050 1.001254e-01 0.165100 1.001280e-01 0.165150 1.001305e-01 0.165200 1.001331e-01 0.165250 1.001357e-01 0.165300 1.001382e-01 0.165350 1.001408e-01 0.165400 1.001433e-01 0.165450 1.001459e-01 0.165500 1.001484e-01 0.165550 1.001509e-01 0.165600 1.001535e-01 0.165650 1.001560e-01 0.165700 1.001585e-01 0.165750 1.001611e-01 0.165800 1.001636e-01 0.165850 1.001661e-01 0.165900 1.001686e-01 0.165950 1.001711e-01 0.166000 1.001737e-01 0.166050 1.001762e-01 0.166100 1.001787e-01 0.166150 1.001812e-01 0.166200 1.001837e-01 0.166250 1.001862e-01 0.166300 1.001887e-01 0.166350 1.001911e-01 0.166400 1.001936e-01 0.166450 1.001961e-01 0.166500 1.001986e-01 0.166550 1.002011e-01 0.166600 1.002035e-01 0.166650 1.002060e-01 0.166700 1.002085e-01 0.166750 1.002109e-01 0.166800 1.002134e-01 0.166850 1.002159e-01 0.166900 1.002183e-01 0.166950 1.002208e-01 0.167000 1.002232e-01 0.167050 1.002257e-01 0.167100 1.002281e-01 0.167150 1.002305e-01 0.167200 1.002330e-01 0.167250 1.002354e-01 0.167300 1.002378e-01 0.167350 1.002403e-01 0.167400 1.002427e-01 0.167450 1.002451e-01 0.167500 1.002475e-01 0.167550 1.002499e-01 0.167600 1.002524e-01 0.167650 1.002548e-01 0.167700 1.002572e-01 0.167750 1.002596e-01 0.167800 1.002620e-01 0.167850 1.002644e-01 0.167900 1.002668e-01 0.167950 1.002692e-01 0.168000 1.002715e-01 0.168050 1.002739e-01 0.168100 1.002763e-01 0.168150 1.002787e-01 0.168200 1.002811e-01 0.168250 1.002834e-01 0.168300 1.002858e-01 0.168350 1.002882e-01 0.168400 1.002905e-01 0.168450 1.002929e-01 0.168500 1.002953e-01 0.168550 1.002976e-01 0.168600 1.003000e-01 0.168650 1.003023e-01 0.168700 1.003047e-01 0.168750 1.003070e-01 0.168800 1.003094e-01 0.168850 1.003117e-01 0.168900 1.003140e-01 0.168950 1.003164e-01 0.169000 1.003187e-01 0.169050 1.003210e-01 0.169100 1.003233e-01 0.169150 1.003257e-01 0.169200 1.003280e-01 0.169250 1.003303e-01 0.169300 1.003326e-01 0.169350 1.003349e-01 0.169400 1.003372e-01 0.169450 1.003395e-01 0.169500 1.003418e-01 0.169550 1.003441e-01 0.169600 1.003464e-01 0.169650 1.003487e-01 0.169700 1.003510e-01 0.169750 1.003533e-01 0.169800 1.003556e-01 0.169850 1.003578e-01 0.169900 1.003601e-01 0.169950 1.003624e-01 0.170000 1.003647e-01 0.170050 1.003669e-01 0.170100 1.003692e-01 0.170150 1.003715e-01 0.170200 1.003737e-01 0.170250 1.003760e-01 0.170300 1.003782e-01 0.170350 1.003805e-01 0.170400 1.003827e-01 0.170450 1.003850e-01 0.170500 1.003872e-01 0.170550 1.003895e-01 0.170600 1.003917e-01 0.170650 1.003939e-01 0.170700 1.003962e-01 0.170750 1.003984e-01 0.170800 1.004006e-01 0.170850 1.004029e-01 0.170900 1.004051e-01 0.170950 1.004073e-01 0.171000 1.004095e-01 0.171050 1.004117e-01 0.171100 1.004139e-01 0.171150 1.004161e-01 0.171200 1.004183e-01 0.171250 1.004205e-01 0.171300 1.004227e-01 0.171350 1.004249e-01 0.171400 1.004271e-01 0.171450 1.004293e-01 0.171500 1.004315e-01 0.171550 1.004337e-01 0.171600 1.004359e-01 0.171650 1.004381e-01 0.171700 1.004402e-01 0.171750 1.004424e-01 0.171800 1.004446e-01 0.171850 1.004468e-01 0.171900 1.004489e-01 0.171950 1.004511e-01 0.172000 1.004532e-01 0.172050 1.004554e-01 0.172100 1.004576e-01 0.172150 1.004597e-01 0.172200 1.004619e-01 0.172250 1.004640e-01 0.172300 1.004662e-01 0.172350 1.004683e-01 0.172400 1.004704e-01 0.172450 1.004726e-01 0.172500 1.004747e-01 0.172550 1.004768e-01 0.172600 1.004790e-01 0.172650 1.004811e-01 0.172700 1.004832e-01 0.172750 1.004853e-01 0.172800 1.004875e-01 0.172850 1.004896e-01 0.172900 1.004917e-01 0.172950 1.004938e-01 0.173000 1.004959e-01 0.173050 1.004980e-01 0.173100 1.005001e-01 0.173150 1.005022e-01 0.173200 1.005043e-01 0.173250 1.005064e-01 0.173300 1.005085e-01 0.173350 1.005106e-01 0.173400 1.005127e-01 0.173450 1.005147e-01 0.173500 1.005168e-01 0.173550 1.005189e-01 0.173600 1.005210e-01 0.173650 1.005231e-01 0.173700 1.005251e-01 0.173750 1.005272e-01 0.173800 1.005293e-01 0.173850 1.005313e-01 0.173900 1.005334e-01 0.173950 1.005354e-01 0.174000 1.005375e-01 0.174050 1.005396e-01 0.174100 1.005416e-01 0.174150 1.005436e-01 0.174200 1.005457e-01 0.174250 1.005477e-01 0.174300 1.005498e-01 0.174350 1.005518e-01 0.174400 1.005539e-01 0.174450 1.005559e-01 0.174500 1.005579e-01 0.174550 1.005599e-01 0.174600 1.005620e-01 0.174650 1.005640e-01 0.174700 1.005660e-01 0.174750 1.005680e-01 0.174800 1.005700e-01 0.174850 1.005721e-01 0.174900 1.005741e-01 0.174950 1.005761e-01 0.175000 1.005781e-01 0.175050 1.005801e-01 0.175100 1.005821e-01 0.175150 1.005841e-01 0.175200 1.005861e-01 0.175250 1.005881e-01 0.175300 1.005900e-01 0.175350 1.005920e-01 0.175400 1.005940e-01 0.175450 1.005960e-01 0.175500 1.005980e-01 0.175550 1.006000e-01 0.175600 1.006019e-01 0.175650 1.006039e-01 0.175700 1.006059e-01 0.175750 1.006078e-01 0.175800 1.006098e-01 0.175850 1.006118e-01 0.175900 1.006137e-01 0.175950 1.006157e-01 0.176000 1.006176e-01 0.176050 1.006196e-01 0.176100 1.006215e-01 0.176150 1.006235e-01 0.176200 1.006254e-01 0.176250 1.006274e-01 0.176300 1.006293e-01 0.176350 1.006313e-01 0.176400 1.006332e-01 0.176450 1.006351e-01 0.176500 1.006371e-01 0.176550 1.006390e-01 0.176600 1.006409e-01 0.176650 1.006428e-01 0.176700 1.006448e-01 0.176750 1.006467e-01 0.176800 1.006486e-01 0.176850 1.006505e-01 0.176900 1.006524e-01 0.176950 1.006543e-01 0.177000 1.006562e-01 0.177050 1.006581e-01 0.177100 1.006600e-01 0.177150 1.006619e-01 0.177200 1.006638e-01 0.177250 1.006657e-01 0.177300 1.006676e-01 0.177350 1.006695e-01 0.177400 1.006714e-01 0.177450 1.006733e-01 0.177500 1.006752e-01 0.177550 1.006771e-01 0.177600 1.006789e-01 0.177650 1.006808e-01 0.177700 1.006827e-01 0.177750 1.006846e-01 0.177800 1.006864e-01 0.177850 1.006883e-01 0.177900 1.006902e-01 0.177950 1.006920e-01 0.178000 1.006939e-01 0.178050 1.006957e-01 0.178100 1.006976e-01 0.178150 1.006994e-01 0.178200 1.007013e-01 0.178250 1.007031e-01 0.178300 1.007050e-01 0.178350 1.007068e-01 0.178400 1.007087e-01 0.178450 1.007105e-01 0.178500 1.007124e-01 0.178550 1.007142e-01 0.178600 1.007160e-01 0.178650 1.007179e-01 0.178700 1.007197e-01 0.178750 1.007215e-01 0.178800 1.007233e-01 0.178850 1.007251e-01 0.178900 1.007270e-01 0.178950 1.007288e-01 0.179000 1.007306e-01 0.179050 1.007324e-01 0.179100 1.007342e-01 0.179150 1.007360e-01 0.179200 1.007378e-01 0.179250 1.007396e-01 0.179300 1.007414e-01 0.179350 1.007432e-01 0.179400 1.007450e-01 0.179450 1.007468e-01 0.179500 1.007486e-01 0.179550 1.007504e-01 0.179600 1.007522e-01 0.179650 1.007540e-01 0.179700 1.007558e-01 0.179750 1.007575e-01 0.179800 1.007593e-01 0.179850 1.007611e-01 0.179900 1.007629e-01 0.179950 1.007646e-01 0.180000 1.007664e-01 0.180050 1.007682e-01 0.180100 1.007699e-01 0.180150 1.007717e-01 0.180200 1.007735e-01 0.180250 1.007752e-01 0.180300 1.007770e-01 0.180350 1.007787e-01 0.180400 1.007805e-01 0.180450 1.007822e-01 0.180500 1.007840e-01 0.180550 1.007857e-01 0.180600 1.007875e-01 0.180650 1.007892e-01 0.180700 1.007909e-01 0.180750 1.007927e-01 0.180800 1.007944e-01 0.180850 1.007961e-01 0.180900 1.007979e-01 0.180950 1.007996e-01 0.181000 1.008013e-01 0.181050 1.008030e-01 0.181100 1.008048e-01 0.181150 1.008065e-01 0.181200 1.008082e-01 0.181250 1.008099e-01 0.181300 1.008116e-01 0.181350 1.008133e-01 0.181400 1.008151e-01 0.181450 1.008168e-01 0.181500 1.008185e-01 0.181550 1.008202e-01 0.181600 1.008219e-01 0.181650 1.008236e-01 0.181700 1.008253e-01 0.181750 1.008270e-01 0.181800 1.008286e-01 0.181850 1.008303e-01 0.181900 1.008320e-01 0.181950 1.008337e-01 0.182000 1.008354e-01 0.182050 1.008371e-01 0.182100 1.008387e-01 0.182150 1.008404e-01 0.182200 1.008421e-01 0.182250 1.008438e-01 0.182300 1.008454e-01 0.182350 1.008471e-01 0.182400 1.008488e-01 0.182450 1.008504e-01 0.182500 1.008521e-01 0.182550 1.008538e-01 0.182600 1.008554e-01 0.182650 1.008571e-01 0.182700 1.008587e-01 0.182750 1.008604e-01 0.182800 1.008620e-01 0.182850 1.008637e-01 0.182900 1.008653e-01 0.182950 1.008670e-01 0.183000 1.008686e-01 0.183050 1.008702e-01 0.183100 1.008719e-01 0.183150 1.008735e-01 0.183200 1.008752e-01 0.183250 1.008768e-01 0.183300 1.008784e-01 0.183350 1.008800e-01 0.183400 1.008817e-01 0.183450 1.008833e-01 0.183500 1.008849e-01 0.183550 1.008865e-01 0.183600 1.008881e-01 0.183650 1.008898e-01 0.183700 1.008914e-01 0.183750 1.008930e-01 0.183800 1.008946e-01 0.183850 1.008962e-01 0.183900 1.008978e-01 0.183950 1.008994e-01 0.184000 1.009010e-01 0.184050 1.009026e-01 0.184100 1.009042e-01 0.184150 1.009058e-01 0.184200 1.009074e-01 0.184250 1.009090e-01 0.184300 1.009106e-01 0.184350 1.009122e-01 0.184400 1.009137e-01 0.184450 1.009153e-01 0.184500 1.009169e-01 0.184550 1.009185e-01 0.184600 1.009201e-01 0.184650 1.009216e-01 0.184700 1.009232e-01 0.184750 1.009248e-01 0.184800 1.009263e-01 0.184850 1.009279e-01 0.184900 1.009295e-01 0.184950 1.009310e-01 0.185000 1.009326e-01 0.185050 1.009342e-01 0.185100 1.009357e-01 0.185150 1.009373e-01 0.185200 1.009388e-01 0.185250 1.009404e-01 0.185300 1.009419e-01 0.185350 1.009435e-01 0.185400 1.009450e-01 0.185450 1.009466e-01 0.185500 1.009481e-01 0.185550 1.009497e-01 0.185600 1.009512e-01 0.185650 1.009527e-01 0.185700 1.009543e-01 0.185750 1.009558e-01 0.185800 1.009573e-01 0.185850 1.009588e-01 0.185900 1.009604e-01 0.185950 1.009619e-01 0.186000 1.009634e-01 0.186050 1.009649e-01 0.186100 1.009665e-01 0.186150 1.009680e-01 0.186200 1.009695e-01 0.186250 1.009710e-01 0.186300 1.009725e-01 0.186350 1.009740e-01 0.186400 1.009755e-01 0.186450 1.009770e-01 0.186500 1.009785e-01 0.186550 1.009800e-01 0.186600 1.009815e-01 0.186650 1.009830e-01 0.186700 1.009845e-01 0.186750 1.009860e-01 0.186800 1.009875e-01 0.186850 1.009890e-01 0.186900 1.009905e-01 0.186950 1.009920e-01 0.187000 1.009935e-01 0.187050 1.009950e-01 0.187100 1.009964e-01 0.187150 1.009979e-01 0.187200 1.009994e-01 0.187250 1.010009e-01 0.187300 1.010024e-01 0.187350 1.010038e-01 0.187400 1.010053e-01 0.187450 1.010068e-01 0.187500 1.010082e-01 0.187550 1.010097e-01 0.187600 1.010112e-01 0.187650 1.010126e-01 0.187700 1.010141e-01 0.187750 1.010155e-01 0.187800 1.010170e-01 0.187850 1.010184e-01 0.187900 1.010199e-01 0.187950 1.010213e-01 0.188000 1.010228e-01 0.188050 1.010242e-01 0.188100 1.010257e-01 0.188150 1.010271e-01 0.188200 1.010286e-01 0.188250 1.010300e-01 0.188300 1.010315e-01 0.188350 1.010329e-01 0.188400 1.010343e-01 0.188450 1.010358e-01 0.188500 1.010372e-01 0.188550 1.010386e-01 0.188600 1.010400e-01 0.188650 1.010415e-01 0.188700 1.010429e-01 0.188750 1.010443e-01 0.188800 1.010457e-01 0.188850 1.010471e-01 0.188900 1.010486e-01 0.188950 1.010500e-01 0.189000 1.010514e-01 0.189050 1.010528e-01 0.189100 1.010542e-01 0.189150 1.010556e-01 0.189200 1.010570e-01 0.189250 1.010584e-01 0.189300 1.010598e-01 0.189350 1.010612e-01 0.189400 1.010626e-01 0.189450 1.010640e-01 0.189500 1.010654e-01 0.189550 1.010668e-01 0.189600 1.010682e-01 0.189650 1.010696e-01 0.189700 1.010710e-01 0.189750 1.010724e-01 0.189800 1.010738e-01 0.189850 1.010751e-01 0.189900 1.010765e-01 0.189950 1.010779e-01 0.190000 1.010793e-01 0.190050 1.010807e-01 0.190100 1.010820e-01 0.190150 1.010834e-01 0.190200 1.010848e-01 0.190250 1.010861e-01 0.190300 1.010875e-01 0.190350 1.010889e-01 0.190400 1.010902e-01 0.190450 1.010916e-01 0.190500 1.010930e-01 0.190550 1.010943e-01 0.190600 1.010957e-01 0.190650 1.010970e-01 0.190700 1.010984e-01 0.190750 1.010997e-01 0.190800 1.011011e-01 0.190850 1.011024e-01 0.190900 1.011038e-01 0.190950 1.011051e-01 0.191000 1.011065e-01 0.191050 1.011078e-01 0.191100 1.011092e-01 0.191150 1.011105e-01 0.191200 1.011118e-01 0.191250 1.011132e-01 0.191300 1.011145e-01 0.191350 1.011158e-01 0.191400 1.011172e-01 0.191450 1.011185e-01 0.191500 1.011198e-01 0.191550 1.011211e-01 0.191600 1.011225e-01 0.191650 1.011238e-01 0.191700 1.011251e-01 0.191750 1.011264e-01 0.191800 1.011277e-01 0.191850 1.011291e-01 0.191900 1.011304e-01 0.191950 1.011317e-01 0.192000 1.011330e-01 0.192050 1.011343e-01 0.192100 1.011356e-01 0.192150 1.011369e-01 0.192200 1.011382e-01 0.192250 1.011395e-01 0.192300 1.011408e-01 0.192350 1.011421e-01 0.192400 1.011434e-01 0.192450 1.011447e-01 0.192500 1.011460e-01 0.192550 1.011473e-01 0.192600 1.011486e-01 0.192650 1.011499e-01 0.192700 1.011512e-01 0.192750 1.011525e-01 0.192800 1.011537e-01 0.192850 1.011550e-01 0.192900 1.011563e-01 0.192950 1.011576e-01 0.193000 1.011589e-01 0.193050 1.011601e-01 0.193100 1.011614e-01 0.193150 1.011627e-01 0.193200 1.011640e-01 0.193250 1.011652e-01 0.193300 1.011665e-01 0.193350 1.011678e-01 0.193400 1.011690e-01 0.193450 1.011703e-01 0.193500 1.011716e-01 0.193550 1.011728e-01 0.193600 1.011741e-01 0.193650 1.011753e-01 0.193700 1.011766e-01 0.193750 1.011779e-01 0.193800 1.011791e-01 0.193850 1.011804e-01 0.193900 1.011816e-01 0.193950 1.011829e-01 0.194000 1.011841e-01 0.194050 1.011854e-01 0.194100 1.011866e-01 0.194150 1.011878e-01 0.194200 1.011891e-01 0.194250 1.011903e-01 0.194300 1.011916e-01 0.194350 1.011928e-01 0.194400 1.011940e-01 0.194450 1.011953e-01 0.194500 1.011965e-01 0.194550 1.011977e-01 0.194600 1.011989e-01 0.194650 1.012002e-01 0.194700 1.012014e-01 0.194750 1.012026e-01 0.194800 1.012038e-01 0.194850 1.012051e-01 0.194900 1.012063e-01 0.194950 1.012075e-01 0.195000 1.012087e-01 0.195050 1.012099e-01 0.195100 1.012111e-01 0.195150 1.012124e-01 0.195200 1.012136e-01 0.195250 1.012148e-01 0.195300 1.012160e-01 0.195350 1.012172e-01 0.195400 1.012184e-01 0.195450 1.012196e-01 0.195500 1.012208e-01 0.195550 1.012220e-01 0.195600 1.012232e-01 0.195650 1.012244e-01 0.195700 1.012256e-01 0.195750 1.012268e-01 0.195800 1.012280e-01 0.195850 1.012292e-01 0.195900 1.012303e-01 0.195950 1.012315e-01 0.196000 1.012327e-01 0.196050 1.012339e-01 0.196100 1.012351e-01 0.196150 1.012363e-01 0.196200 1.012374e-01 0.196250 1.012386e-01 0.196300 1.012398e-01 0.196350 1.012410e-01 0.196400 1.012422e-01 0.196450 1.012433e-01 0.196500 1.012445e-01 0.196550 1.012457e-01 0.196600 1.012468e-01 0.196650 1.012480e-01 0.196700 1.012492e-01 0.196750 1.012503e-01 0.196800 1.012515e-01 0.196850 1.012527e-01 0.196900 1.012538e-01 0.196950 1.012550e-01 0.197000 1.012561e-01 0.197050 1.012573e-01 0.197100 1.012584e-01 0.197150 1.012596e-01 0.197200 1.012607e-01 0.197250 1.012619e-01 0.197300 1.012630e-01 0.197350 1.012642e-01 0.197400 1.012653e-01 0.197450 1.012665e-01 0.197500 1.012676e-01 0.197550 1.012688e-01 0.197600 1.012699e-01 0.197650 1.012710e-01 0.197700 1.012722e-01 0.197750 1.012733e-01 0.197800 1.012744e-01 0.197850 1.012756e-01 0.197900 1.012767e-01 0.197950 1.012778e-01 0.198000 1.012790e-01 0.198050 1.012801e-01 0.198100 1.012812e-01 0.198150 1.012823e-01 0.198200 1.012835e-01 0.198250 1.012846e-01 0.198300 1.012857e-01 0.198350 1.012868e-01 0.198400 1.012879e-01 0.198450 1.012890e-01 0.198500 1.012902e-01 0.198550 1.012913e-01 0.198600 1.012924e-01 0.198650 1.012935e-01 0.198700 1.012946e-01 0.198750 1.012957e-01 0.198800 1.012968e-01 0.198850 1.012979e-01 0.198900 1.012990e-01 0.198950 1.013001e-01 0.199000 1.013012e-01 0.199050 1.013023e-01 0.199100 1.013034e-01 0.199150 1.013045e-01 0.199200 1.013056e-01 0.199250 1.013067e-01 0.199300 1.013078e-01 0.199350 1.013089e-01 0.199400 1.013100e-01 0.199450 1.013111e-01 0.199500 1.013122e-01 0.199550 1.013132e-01 0.199600 1.013143e-01 0.199650 1.013154e-01 0.199700 1.013165e-01 0.199750 1.013176e-01 0.199800 1.013186e-01 0.199850 1.013197e-01 0.199900 1.013208e-01 0.199950 1.013219e-01 0.200000 1.013229e-01 0.200050 1.013240e-01 0.200100 1.013251e-01 0.200150 1.013262e-01 0.200200 1.013272e-01 0.200250 1.013283e-01 0.200300 1.013294e-01 0.200350 1.013304e-01 0.200400 1.013315e-01 0.200450 1.013325e-01 0.200500 1.013336e-01 0.200550 1.013347e-01 0.200600 1.013357e-01 0.200650 1.013368e-01 0.200700 1.013378e-01 0.200750 1.013389e-01 0.200800 1.013399e-01 0.200850 1.013410e-01 0.200900 1.013420e-01 0.200950 1.013431e-01 0.201000 1.013441e-01 0.201050 1.013452e-01 0.201100 1.013462e-01 0.201150 1.013473e-01 0.201200 1.013483e-01 0.201250 1.013493e-01 0.201300 1.013504e-01 0.201350 1.013514e-01 0.201400 1.013524e-01 0.201450 1.013535e-01 0.201500 1.013545e-01 0.201550 1.013556e-01 0.201600 1.013566e-01 0.201650 1.013576e-01 0.201700 1.013586e-01 0.201750 1.013597e-01 0.201800 1.013607e-01 0.201850 1.013617e-01 0.201900 1.013627e-01 0.201950 1.013638e-01 0.202000 1.013648e-01 0.202050 1.013658e-01 0.202100 1.013668e-01 0.202150 1.013678e-01 0.202200 1.013689e-01 0.202250 1.013699e-01 0.202300 1.013709e-01 0.202350 1.013719e-01 0.202400 1.013729e-01 0.202450 1.013739e-01 0.202500 1.013749e-01 0.202550 1.013759e-01 0.202600 1.013769e-01 0.202650 1.013779e-01 0.202700 1.013789e-01 0.202750 1.013799e-01 0.202800 1.013809e-01 0.202850 1.013819e-01 0.202900 1.013829e-01 0.202950 1.013839e-01 0.203000 1.013849e-01 0.203050 1.013859e-01 0.203100 1.013869e-01 0.203150 1.013879e-01 0.203200 1.013889e-01 0.203250 1.013899e-01 0.203300 1.013909e-01 0.203350 1.013919e-01 0.203400 1.013929e-01 0.203450 1.013938e-01 0.203500 1.013948e-01 0.203550 1.013958e-01 0.203600 1.013968e-01 0.203650 1.013978e-01 0.203700 1.013987e-01 0.203750 1.013997e-01 0.203800 1.014007e-01 0.203850 1.014017e-01 0.203900 1.014026e-01 0.203950 1.014036e-01 0.204000 1.014046e-01 0.204050 1.014056e-01 0.204100 1.014065e-01 0.204150 1.014075e-01 0.204200 1.014085e-01 0.204250 1.014094e-01 0.204300 1.014104e-01 0.204350 1.014113e-01 0.204400 1.014123e-01 0.204450 1.014133e-01 0.204500 1.014142e-01 0.204550 1.014152e-01 0.204600 1.014161e-01 0.204650 1.014171e-01 0.204700 1.014180e-01 0.204750 1.014190e-01 0.204800 1.014200e-01 0.204850 1.014209e-01 0.204900 1.014219e-01 0.204950 1.014228e-01 0.205000 1.014237e-01 0.205050 1.014247e-01 0.205100 1.014256e-01 0.205150 1.014266e-01 0.205200 1.014275e-01 0.205250 1.014285e-01 0.205300 1.014294e-01 0.205350 1.014303e-01 0.205400 1.014313e-01 0.205450 1.014322e-01 0.205500 1.014332e-01 0.205550 1.014341e-01 0.205600 1.014350e-01 0.205650 1.014360e-01 0.205700 1.014369e-01 0.205750 1.014378e-01 0.205800 1.014387e-01 0.205850 1.014397e-01 0.205900 1.014406e-01 0.205950 1.014415e-01 0.206000 1.014424e-01 0.206050 1.014434e-01 0.206100 1.014443e-01 0.206150 1.014452e-01 0.206200 1.014461e-01 0.206250 1.014470e-01 0.206300 1.014480e-01 0.206350 1.014489e-01 0.206400 1.014498e-01 0.206450 1.014507e-01 0.206500 1.014516e-01 0.206550 1.014525e-01 0.206600 1.014534e-01 0.206650 1.014543e-01 0.206700 1.014553e-01 0.206750 1.014562e-01 0.206800 1.014571e-01 0.206850 1.014580e-01 0.206900 1.014589e-01 0.206950 1.014598e-01 0.207000 1.014607e-01 0.207050 1.014616e-01 0.207100 1.014625e-01 0.207150 1.014634e-01 0.207200 1.014643e-01 0.207250 1.014652e-01 0.207300 1.014661e-01 0.207350 1.014669e-01 0.207400 1.014678e-01 0.207450 1.014687e-01 0.207500 1.014696e-01 0.207550 1.014705e-01 0.207600 1.014714e-01 0.207650 1.014723e-01 0.207700 1.014732e-01 0.207750 1.014741e-01 0.207800 1.014749e-01 0.207850 1.014758e-01 0.207900 1.014767e-01 0.207950 1.014776e-01 0.208000 1.014785e-01 0.208050 1.014793e-01 0.208100 1.014802e-01 0.208150 1.014811e-01 0.208200 1.014820e-01 0.208250 1.014828e-01 0.208300 1.014837e-01 0.208350 1.014846e-01 0.208400 1.014854e-01 0.208450 1.014863e-01 0.208500 1.014872e-01 0.208550 1.014880e-01 0.208600 1.014889e-01 0.208650 1.014898e-01 0.208700 1.014906e-01 0.208750 1.014915e-01 0.208800 1.014924e-01 0.208850 1.014932e-01 0.208900 1.014941e-01 0.208950 1.014949e-01 0.209000 1.014958e-01 0.209050 1.014967e-01 0.209100 1.014975e-01 0.209150 1.014984e-01 0.209200 1.014992e-01 0.209250 1.015001e-01 0.209300 1.015009e-01 0.209350 1.015018e-01 0.209400 1.015026e-01 0.209450 1.015035e-01 0.209500 1.015043e-01 0.209550 1.015052e-01 0.209600 1.015060e-01 0.209650 1.015068e-01 0.209700 1.015077e-01 0.209750 1.015085e-01 0.209800 1.015094e-01 0.209850 1.015102e-01 0.209900 1.015110e-01 0.209950 1.015119e-01 0.210000 1.015127e-01 0.210050 1.015135e-01 0.210100 1.015144e-01 0.210150 1.015152e-01 0.210200 1.015160e-01 0.210250 1.015169e-01 0.210300 1.015177e-01 0.210350 1.015185e-01 0.210400 1.015194e-01 0.210450 1.015202e-01 0.210500 1.015210e-01 0.210550 1.015218e-01 0.210600 1.015227e-01 0.210650 1.015235e-01 0.210700 1.015243e-01 0.210750 1.015251e-01 0.210800 1.015259e-01 0.210850 1.015268e-01 0.210900 1.015276e-01 0.210950 1.015284e-01 0.211000 1.015292e-01 0.211050 1.015300e-01 0.211100 1.015308e-01 0.211150 1.015316e-01 0.211200 1.015325e-01 0.211250 1.015333e-01 0.211300 1.015341e-01 0.211350 1.015349e-01 0.211400 1.015357e-01 0.211450 1.015365e-01 0.211500 1.015373e-01 0.211550 1.015381e-01 0.211600 1.015389e-01 0.211650 1.015397e-01 0.211700 1.015405e-01 0.211750 1.015413e-01 0.211800 1.015421e-01 0.211850 1.015429e-01 0.211900 1.015437e-01 0.211950 1.015445e-01 0.212000 1.015453e-01 0.212050 1.015461e-01 0.212100 1.015469e-01 0.212150 1.015477e-01 0.212200 1.015485e-01 0.212250 1.015493e-01 0.212300 1.015500e-01 0.212350 1.015508e-01 0.212400 1.015516e-01 0.212450 1.015524e-01 0.212500 1.015532e-01 0.212550 1.015540e-01 0.212600 1.015548e-01 0.212650 1.015555e-01 0.212700 1.015563e-01 0.212750 1.015571e-01 0.212800 1.015579e-01 0.212850 1.015587e-01 0.212900 1.015594e-01 0.212950 1.015602e-01 0.213000 1.015610e-01 0.213050 1.015618e-01 0.213100 1.015625e-01 0.213150 1.015633e-01 0.213200 1.015641e-01 0.213250 1.015648e-01 0.213300 1.015656e-01 0.213350 1.015664e-01 0.213400 1.015672e-01 0.213450 1.015679e-01 0.213500 1.015687e-01 0.213550 1.015695e-01 0.213600 1.015702e-01 0.213650 1.015710e-01 0.213700 1.015717e-01 0.213750 1.015725e-01 0.213800 1.015733e-01 0.213850 1.015740e-01 0.213900 1.015748e-01 0.213950 1.015755e-01 0.214000 1.015763e-01 0.214050 1.015770e-01 0.214100 1.015778e-01 0.214150 1.015786e-01 0.214200 1.015793e-01 0.214250 1.015801e-01 0.214300 1.015808e-01 0.214350 1.015816e-01 0.214400 1.015823e-01 0.214450 1.015831e-01 0.214500 1.015838e-01 0.214550 1.015845e-01 0.214600 1.015853e-01 0.214650 1.015860e-01 0.214700 1.015868e-01 0.214750 1.015875e-01 0.214800 1.015883e-01 0.214850 1.015890e-01 0.214900 1.015897e-01 0.214950 1.015905e-01 0.215000 1.015912e-01 0.215050 1.015920e-01 0.215100 1.015927e-01 0.215150 1.015934e-01 0.215200 1.015942e-01 0.215250 1.015949e-01 0.215300 1.015956e-01 0.215350 1.015964e-01 0.215400 1.015971e-01 0.215450 1.015978e-01 0.215500 1.015985e-01 0.215550 1.015993e-01 0.215600 1.016000e-01 0.215650 1.016007e-01 0.215700 1.016014e-01 0.215750 1.016022e-01 0.215800 1.016029e-01 0.215850 1.016036e-01 0.215900 1.016043e-01 0.215950 1.016051e-01 0.216000 1.016058e-01 0.216050 1.016065e-01 0.216100 1.016072e-01 0.216150 1.016079e-01 0.216200 1.016086e-01 0.216250 1.016094e-01 0.216300 1.016101e-01 0.216350 1.016108e-01 0.216400 1.016115e-01 0.216450 1.016122e-01 0.216500 1.016129e-01 0.216550 1.016136e-01 0.216600 1.016143e-01 0.216650 1.016150e-01 0.216700 1.016158e-01 0.216750 1.016165e-01 0.216800 1.016172e-01 0.216850 1.016179e-01 0.216900 1.016186e-01 0.216950 1.016193e-01 0.217000 1.016200e-01 0.217050 1.016207e-01 0.217100 1.016214e-01 0.217150 1.016221e-01 0.217200 1.016228e-01 0.217250 1.016235e-01 0.217300 1.016242e-01 0.217350 1.016249e-01 0.217400 1.016256e-01 0.217450 1.016262e-01 0.217500 1.016269e-01 0.217550 1.016276e-01 0.217600 1.016283e-01 0.217650 1.016290e-01 0.217700 1.016297e-01 0.217750 1.016304e-01 0.217800 1.016311e-01 0.217850 1.016318e-01 0.217900 1.016325e-01 0.217950 1.016331e-01 0.218000 1.016338e-01 0.218050 1.016345e-01 0.218100 1.016352e-01 0.218150 1.016359e-01 0.218200 1.016366e-01 0.218250 1.016372e-01 0.218300 1.016379e-01 0.218350 1.016386e-01 0.218400 1.016393e-01 0.218450 1.016399e-01 0.218500 1.016406e-01 0.218550 1.016413e-01 0.218600 1.016420e-01 0.218650 1.016426e-01 0.218700 1.016433e-01 0.218750 1.016440e-01 0.218800 1.016447e-01 0.218850 1.016453e-01 0.218900 1.016460e-01 0.218950 1.016467e-01 0.219000 1.016473e-01 0.219050 1.016480e-01 0.219100 1.016487e-01 0.219150 1.016493e-01 0.219200 1.016500e-01 0.219250 1.016507e-01 0.219300 1.016513e-01 0.219350 1.016520e-01 0.219400 1.016526e-01 0.219450 1.016533e-01 0.219500 1.016540e-01 0.219550 1.016546e-01 0.219600 1.016553e-01 0.219650 1.016559e-01 0.219700 1.016566e-01 0.219750 1.016572e-01 0.219800 1.016579e-01 0.219850 1.016585e-01 0.219900 1.016592e-01 0.219950 1.016599e-01 0.220000 1.016605e-01 0.220050 1.016612e-01 0.220100 1.016618e-01 0.220150 1.016624e-01 0.220200 1.016631e-01 0.220250 1.016637e-01 0.220300 1.016644e-01 0.220350 1.016650e-01 0.220400 1.016657e-01 0.220450 1.016663e-01 0.220500 1.016670e-01 0.220550 1.016676e-01 0.220600 1.016682e-01 0.220650 1.016689e-01 0.220700 1.016695e-01 0.220750 1.016702e-01 0.220800 1.016708e-01 0.220850 1.016714e-01 0.220900 1.016721e-01 0.220950 1.016727e-01 0.221000 1.016733e-01 0.221050 1.016740e-01 0.221100 1.016746e-01 0.221150 1.016752e-01 0.221200 1.016759e-01 0.221250 1.016765e-01 0.221300 1.016771e-01 0.221350 1.016778e-01 0.221400 1.016784e-01 0.221450 1.016790e-01 0.221500 1.016797e-01 0.221550 1.016803e-01 0.221600 1.016809e-01 0.221650 1.016815e-01 0.221700 1.016822e-01 0.221750 1.016828e-01 0.221800 1.016834e-01 0.221850 1.016840e-01 0.221900 1.016846e-01 0.221950 1.016853e-01 0.222000 1.016859e-01 0.222050 1.016865e-01 0.222100 1.016871e-01 0.222150 1.016877e-01 0.222200 1.016883e-01 0.222250 1.016890e-01 0.222300 1.016896e-01 0.222350 1.016902e-01 0.222400 1.016908e-01 0.222450 1.016914e-01 0.222500 1.016920e-01 0.222550 1.016926e-01 0.222600 1.016932e-01 0.222650 1.016939e-01 0.222700 1.016945e-01 0.222750 1.016951e-01 0.222800 1.016957e-01 0.222850 1.016963e-01 0.222900 1.016969e-01 0.222950 1.016975e-01 0.223000 1.016981e-01 0.223050 1.016987e-01 0.223100 1.016993e-01 0.223150 1.016999e-01 0.223200 1.017005e-01 0.223250 1.017011e-01 0.223300 1.017017e-01 0.223350 1.017023e-01 0.223400 1.017029e-01 0.223450 1.017035e-01 0.223500 1.017041e-01 0.223550 1.017047e-01 0.223600 1.017053e-01 0.223650 1.017059e-01 0.223700 1.017065e-01 0.223750 1.017071e-01 0.223800 1.017077e-01 0.223850 1.017082e-01 0.223900 1.017088e-01 0.223950 1.017094e-01 0.224000 1.017100e-01 0.224050 1.017106e-01 0.224100 1.017112e-01 0.224150 1.017118e-01 0.224200 1.017124e-01 0.224250 1.017130e-01 0.224300 1.017135e-01 0.224350 1.017141e-01 0.224400 1.017147e-01 0.224450 1.017153e-01 0.224500 1.017159e-01 0.224550 1.017164e-01 0.224600 1.017170e-01 0.224650 1.017176e-01 0.224700 1.017182e-01 0.224750 1.017188e-01 0.224800 1.017193e-01 0.224850 1.017199e-01 0.224900 1.017205e-01 0.224950 1.017211e-01 0.225000 1.017216e-01 0.225050 1.017222e-01 0.225100 1.017228e-01 0.225150 1.017234e-01 0.225200 1.017239e-01 0.225250 1.017245e-01 0.225300 1.017251e-01 0.225350 1.017256e-01 0.225400 1.017262e-01 0.225450 1.017268e-01 0.225500 1.017273e-01 0.225550 1.017279e-01 0.225600 1.017285e-01 0.225650 1.017290e-01 0.225700 1.017296e-01 0.225750 1.017302e-01 0.225800 1.017307e-01 0.225850 1.017313e-01 0.225900 1.017319e-01 0.225950 1.017324e-01 0.226000 1.017330e-01 0.226050 1.017335e-01 0.226100 1.017341e-01 0.226150 1.017347e-01 0.226200 1.017352e-01 0.226250 1.017358e-01 0.226300 1.017363e-01 0.226350 1.017369e-01 0.226400 1.017374e-01 0.226450 1.017380e-01 0.226500 1.017385e-01 0.226550 1.017391e-01 0.226600 1.017396e-01 0.226650 1.017402e-01 0.226700 1.017408e-01 0.226750 1.017413e-01 0.226800 1.017418e-01 0.226850 1.017424e-01 0.226900 1.017429e-01 0.226950 1.017435e-01 0.227000 1.017440e-01 0.227050 1.017446e-01 0.227100 1.017451e-01 0.227150 1.017457e-01 0.227200 1.017462e-01 0.227250 1.017468e-01 0.227300 1.017473e-01 0.227350 1.017478e-01 0.227400 1.017484e-01 0.227450 1.017489e-01 0.227500 1.017495e-01 0.227550 1.017500e-01 0.227600 1.017505e-01 0.227650 1.017511e-01 0.227700 1.017516e-01 0.227750 1.017522e-01 0.227800 1.017527e-01 0.227850 1.017532e-01 0.227900 1.017538e-01 0.227950 1.017543e-01 0.228000 1.017548e-01 0.228050 1.017554e-01 0.228100 1.017559e-01 0.228150 1.017564e-01 0.228200 1.017569e-01 0.228250 1.017575e-01 0.228300 1.017580e-01 0.228350 1.017585e-01 0.228400 1.017591e-01 0.228450 1.017596e-01 0.228500 1.017601e-01 0.228550 1.017606e-01 0.228600 1.017612e-01 0.228650 1.017617e-01 0.228700 1.017622e-01 0.228750 1.017627e-01 0.228800 1.017633e-01 0.228850 1.017638e-01 0.228900 1.017643e-01 0.228950 1.017648e-01 0.229000 1.017653e-01 0.229050 1.017659e-01 0.229100 1.017664e-01 0.229150 1.017669e-01 0.229200 1.017674e-01 0.229250 1.017679e-01 0.229300 1.017684e-01 0.229350 1.017690e-01 0.229400 1.017695e-01 0.229450 1.017700e-01 0.229500 1.017705e-01 0.229550 1.017710e-01 0.229600 1.017715e-01 0.229650 1.017720e-01 0.229700 1.017725e-01 0.229750 1.017731e-01 0.229800 1.017736e-01 0.229850 1.017741e-01 0.229900 1.017746e-01 0.229950 1.017751e-01 0.230000 1.017756e-01 0.230050 1.017761e-01 0.230100 1.017766e-01 0.230150 1.017771e-01 0.230200 1.017776e-01 0.230250 1.017781e-01 0.230300 1.017786e-01 0.230350 1.017791e-01 0.230400 1.017796e-01 0.230450 1.017801e-01 0.230500 1.017806e-01 0.230550 1.017811e-01 0.230600 1.017816e-01 0.230650 1.017821e-01 0.230700 1.017826e-01 0.230750 1.017831e-01 0.230800 1.017836e-01 0.230850 1.017841e-01 0.230900 1.017846e-01 0.230950 1.017851e-01 0.231000 1.017856e-01 0.231050 1.017861e-01 0.231100 1.017866e-01 0.231150 1.017871e-01 0.231200 1.017876e-01 0.231250 1.017881e-01 0.231300 1.017886e-01 0.231350 1.017890e-01 0.231400 1.017895e-01 0.231450 1.017900e-01 0.231500 1.017905e-01 0.231550 1.017910e-01 0.231600 1.017915e-01 0.231650 1.017920e-01 0.231700 1.017925e-01 0.231750 1.017929e-01 0.231800 1.017934e-01 0.231850 1.017939e-01 0.231900 1.017944e-01 0.231950 1.017949e-01 0.232000 1.017954e-01 0.232050 1.017958e-01 0.232100 1.017963e-01 0.232150 1.017968e-01 0.232200 1.017973e-01 0.232250 1.017978e-01 0.232300 1.017982e-01 0.232350 1.017987e-01 0.232400 1.017992e-01 0.232450 1.017997e-01 0.232500 1.018002e-01 0.232550 1.018006e-01 0.232600 1.018011e-01 0.232650 1.018016e-01 0.232700 1.018021e-01 0.232750 1.018025e-01 0.232800 1.018030e-01 0.232850 1.018035e-01 0.232900 1.018039e-01 0.232950 1.018044e-01 0.233000 1.018049e-01 0.233050 1.018054e-01 0.233100 1.018058e-01 0.233150 1.018063e-01 0.233200 1.018068e-01 0.233250 1.018072e-01 0.233300 1.018077e-01 0.233350 1.018082e-01 0.233400 1.018086e-01 0.233450 1.018091e-01 0.233500 1.018096e-01 0.233550 1.018100e-01 0.233600 1.018105e-01 0.233650 1.018109e-01 0.233700 1.018114e-01 0.233750 1.018119e-01 0.233800 1.018123e-01 0.233850 1.018128e-01 0.233900 1.018132e-01 0.233950 1.018137e-01 0.234000 1.018142e-01 0.234050 1.018146e-01 0.234100 1.018151e-01 0.234150 1.018155e-01 0.234200 1.018160e-01 0.234250 1.018164e-01 0.234300 1.018169e-01 0.234350 1.018174e-01 0.234400 1.018178e-01 0.234450 1.018183e-01 0.234500 1.018187e-01 0.234550 1.018192e-01 0.234600 1.018196e-01 0.234650 1.018201e-01 0.234700 1.018205e-01 0.234750 1.018210e-01 0.234800 1.018214e-01 0.234850 1.018219e-01 0.234900 1.018223e-01 0.234950 1.018228e-01 0.235000 1.018232e-01 0.235050 1.018237e-01 0.235100 1.018241e-01 0.235150 1.018246e-01 0.235200 1.018250e-01 0.235250 1.018254e-01 0.235300 1.018259e-01 0.235350 1.018263e-01 0.235400 1.018268e-01 0.235450 1.018272e-01 0.235500 1.018277e-01 0.235550 1.018281e-01 0.235600 1.018285e-01 0.235650 1.018290e-01 0.235700 1.018294e-01 0.235750 1.018299e-01 0.235800 1.018303e-01 0.235850 1.018307e-01 0.235900 1.018312e-01 0.235950 1.018316e-01 0.236000 1.018320e-01 0.236050 1.018325e-01 0.236100 1.018329e-01 0.236150 1.018334e-01 0.236200 1.018338e-01 0.236250 1.018342e-01 0.236300 1.018347e-01 0.236350 1.018351e-01 0.236400 1.018355e-01 0.236450 1.018359e-01 0.236500 1.018364e-01 0.236550 1.018368e-01 0.236600 1.018372e-01 0.236650 1.018377e-01 0.236700 1.018381e-01 0.236750 1.018385e-01 0.236800 1.018390e-01 0.236850 1.018394e-01 0.236900 1.018398e-01 0.236950 1.018402e-01 0.237000 1.018407e-01 0.237050 1.018411e-01 0.237100 1.018415e-01 0.237150 1.018419e-01 0.237200 1.018424e-01 0.237250 1.018428e-01 0.237300 1.018432e-01 0.237350 1.018436e-01 0.237400 1.018440e-01 0.237450 1.018445e-01 0.237500 1.018449e-01 0.237550 1.018453e-01 0.237600 1.018457e-01 0.237650 1.018461e-01 0.237700 1.018466e-01 0.237750 1.018470e-01 0.237800 1.018474e-01 0.237850 1.018478e-01 0.237900 1.018482e-01 0.237950 1.018486e-01 0.238000 1.018491e-01 0.238050 1.018495e-01 0.238100 1.018499e-01 0.238150 1.018503e-01 0.238200 1.018507e-01 0.238250 1.018511e-01 0.238300 1.018515e-01 0.238350 1.018519e-01 0.238400 1.018524e-01 0.238450 1.018528e-01 0.238500 1.018532e-01 0.238550 1.018536e-01 0.238600 1.018540e-01 0.238650 1.018544e-01 0.238700 1.018548e-01 0.238750 1.018552e-01 0.238800 1.018556e-01 0.238850 1.018560e-01 0.238900 1.018564e-01 0.238950 1.018568e-01 0.239000 1.018573e-01 0.239050 1.018577e-01 0.239100 1.018581e-01 0.239150 1.018585e-01 0.239200 1.018589e-01 0.239250 1.018593e-01 0.239300 1.018597e-01 0.239350 1.018601e-01 0.239400 1.018605e-01 0.239450 1.018609e-01 0.239500 1.018613e-01 0.239550 1.018617e-01 0.239600 1.018621e-01 0.239650 1.018625e-01 0.239700 1.018629e-01 0.239750 1.018633e-01 0.239800 1.018637e-01 0.239850 1.018641e-01 0.239900 1.018645e-01 0.239950 1.018648e-01 0.240000 1.018652e-01 0.240050 1.018656e-01 0.240100 1.018660e-01 0.240150 1.018664e-01 0.240200 1.018668e-01 0.240250 1.018672e-01 0.240300 1.018676e-01 0.240350 1.018680e-01 0.240400 1.018684e-01 0.240450 1.018688e-01 0.240500 1.018692e-01 0.240550 1.018696e-01 0.240600 1.018699e-01 0.240650 1.018703e-01 0.240700 1.018707e-01 0.240750 1.018711e-01 0.240800 1.018715e-01 0.240850 1.018719e-01 0.240900 1.018723e-01 0.240950 1.018726e-01 0.241000 1.018730e-01 0.241050 1.018734e-01 0.241100 1.018738e-01 0.241150 1.018742e-01 0.241200 1.018746e-01 0.241250 1.018750e-01 0.241300 1.018753e-01 0.241350 1.018757e-01 0.241400 1.018761e-01 0.241450 1.018765e-01 0.241500 1.018769e-01 0.241550 1.018772e-01 0.241600 1.018776e-01 0.241650 1.018780e-01 0.241700 1.018784e-01 0.241750 1.018788e-01 0.241800 1.018791e-01 0.241850 1.018795e-01 0.241900 1.018799e-01 0.241950 1.018803e-01 0.242000 1.018806e-01 0.242050 1.018810e-01 0.242100 1.018814e-01 0.242150 1.018818e-01 0.242200 1.018821e-01 0.242250 1.018825e-01 0.242300 1.018829e-01 0.242350 1.018832e-01 0.242400 1.018836e-01 0.242450 1.018840e-01 0.242500 1.018844e-01 0.242550 1.018847e-01 0.242600 1.018851e-01 0.242650 1.018855e-01 0.242700 1.018858e-01 0.242750 1.018862e-01 0.242800 1.018866e-01 0.242850 1.018869e-01 0.242900 1.018873e-01 0.242950 1.018877e-01 0.243000 1.018880e-01 0.243050 1.018884e-01 0.243100 1.018888e-01 0.243150 1.018891e-01 0.243200 1.018895e-01 0.243250 1.018899e-01 0.243300 1.018902e-01 0.243350 1.018906e-01 0.243400 1.018910e-01 0.243450 1.018913e-01 0.243500 1.018917e-01 0.243550 1.018920e-01 0.243600 1.018924e-01 0.243650 1.018928e-01 0.243700 1.018931e-01 0.243750 1.018935e-01 0.243800 1.018938e-01 0.243850 1.018942e-01 0.243900 1.018946e-01 0.243950 1.018949e-01 0.244000 1.018953e-01 0.244050 1.018956e-01 0.244100 1.018960e-01 0.244150 1.018963e-01 0.244200 1.018967e-01 0.244250 1.018971e-01 0.244300 1.018974e-01 0.244350 1.018978e-01 0.244400 1.018981e-01 0.244450 1.018985e-01 0.244500 1.018988e-01 0.244550 1.018992e-01 0.244600 1.018995e-01 0.244650 1.018999e-01 0.244700 1.019002e-01 0.244750 1.019006e-01 0.244800 1.019009e-01 0.244850 1.019013e-01 0.244900 1.019016e-01 0.244950 1.019020e-01 0.245000 1.019023e-01 0.245050 1.019027e-01 0.245100 1.019030e-01 0.245150 1.019034e-01 0.245200 1.019037e-01 0.245250 1.019041e-01 0.245300 1.019044e-01 0.245350 1.019048e-01 0.245400 1.019051e-01 0.245450 1.019054e-01 0.245500 1.019058e-01 0.245550 1.019061e-01 0.245600 1.019065e-01 0.245650 1.019068e-01 0.245700 1.019072e-01 0.245750 1.019075e-01 0.245800 1.019078e-01 0.245850 1.019082e-01 0.245900 1.019085e-01 0.245950 1.019089e-01 0.246000 1.019092e-01 0.246050 1.019095e-01 0.246100 1.019099e-01 0.246150 1.019102e-01 0.246200 1.019106e-01 0.246250 1.019109e-01 0.246300 1.019112e-01 0.246350 1.019116e-01 0.246400 1.019119e-01 0.246450 1.019122e-01 0.246500 1.019126e-01 0.246550 1.019129e-01 0.246600 1.019132e-01 0.246650 1.019136e-01 0.246700 1.019139e-01 0.246750 1.019142e-01 0.246800 1.019146e-01 0.246850 1.019149e-01 0.246900 1.019152e-01 0.246950 1.019156e-01 0.247000 1.019159e-01 0.247050 1.019162e-01 0.247100 1.019166e-01 0.247150 1.019169e-01 0.247200 1.019172e-01 0.247250 1.019176e-01 0.247300 1.019179e-01 0.247350 1.019182e-01 0.247400 1.019185e-01 0.247450 1.019189e-01 0.247500 1.019192e-01 0.247550 1.019195e-01 0.247600 1.019199e-01 0.247650 1.019202e-01 0.247700 1.019205e-01 0.247750 1.019208e-01 0.247800 1.019212e-01 0.247850 1.019215e-01 0.247900 1.019218e-01 0.247950 1.019221e-01 0.248000 1.019224e-01 0.248050 1.019228e-01 0.248100 1.019231e-01 0.248150 1.019234e-01 0.248200 1.019237e-01 0.248250 1.019241e-01 0.248300 1.019244e-01 0.248350 1.019247e-01 0.248400 1.019250e-01 0.248450 1.019253e-01 0.248500 1.019257e-01 0.248550 1.019260e-01 0.248600 1.019263e-01 0.248650 1.019266e-01 0.248700 1.019269e-01 0.248750 1.019272e-01 0.248800 1.019276e-01 0.248850 1.019279e-01 0.248900 1.019282e-01 0.248950 1.019285e-01 0.249000 1.019288e-01 0.249050 1.019291e-01 0.249100 1.019295e-01 0.249150 1.019298e-01 0.249200 1.019301e-01 0.249250 1.019304e-01 0.249300 1.019307e-01 0.249350 1.019310e-01 0.249400 1.019313e-01 0.249450 1.019316e-01 0.249500 1.019320e-01 0.249550 1.019323e-01 0.249600 1.019326e-01 0.249650 1.019329e-01 0.249700 1.019332e-01 0.249750 1.019335e-01 0.249800 1.019338e-01 0.249850 1.019341e-01 0.249900 1.019344e-01 0.249950 1.019347e-01 0.250000 1.019351e-01 brian2-2.5.4/brian2/tests/rallpack_data/ref_cable.x000066400000000000000000003304451445201106100220760ustar00rootroot000000000000000.000000 -6.500000e-02 0.000050 -6.500000e-02 0.000100 -6.500000e-02 0.000150 -6.500000e-02 0.000200 -6.500000e-02 0.000250 -6.500000e-02 0.000300 -6.500000e-02 0.000350 -6.500000e-02 0.000400 -6.500000e-02 0.000450 -6.500000e-02 0.000500 -6.500000e-02 0.000550 -6.500000e-02 0.000600 -6.500000e-02 0.000650 -6.500000e-02 0.000700 -6.500000e-02 0.000750 -6.500000e-02 0.000800 -6.499999e-02 0.000850 -6.499999e-02 0.000900 -6.499997e-02 0.000950 -6.499995e-02 0.001000 -6.499991e-02 0.001050 -6.499985e-02 0.001100 -6.499975e-02 0.001150 -6.499961e-02 0.001200 -6.499940e-02 0.001250 -6.499912e-02 0.001300 -6.499873e-02 0.001350 -6.499823e-02 0.001400 -6.499758e-02 0.001450 -6.499675e-02 0.001500 -6.499573e-02 0.001550 -6.499447e-02 0.001600 -6.499295e-02 0.001650 -6.499113e-02 0.001700 -6.498898e-02 0.001750 -6.498646e-02 0.001800 -6.498354e-02 0.001850 -6.498019e-02 0.001900 -6.497637e-02 0.001950 -6.497204e-02 0.002000 -6.496717e-02 0.002050 -6.496173e-02 0.002100 -6.495569e-02 0.002150 -6.494901e-02 0.002200 -6.494166e-02 0.002250 -6.493361e-02 0.002300 -6.492484e-02 0.002350 -6.491531e-02 0.002400 -6.490500e-02 0.002450 -6.489389e-02 0.002500 -6.488195e-02 0.002550 -6.486916e-02 0.002600 -6.485551e-02 0.002650 -6.484096e-02 0.002700 -6.482550e-02 0.002750 -6.480912e-02 0.002800 -6.479180e-02 0.002850 -6.477353e-02 0.002900 -6.475429e-02 0.002950 -6.473407e-02 0.003000 -6.471287e-02 0.003050 -6.469066e-02 0.003100 -6.466745e-02 0.003150 -6.464323e-02 0.003200 -6.461799e-02 0.003250 -6.459173e-02 0.003300 -6.456443e-02 0.003350 -6.453611e-02 0.003400 -6.450675e-02 0.003450 -6.447636e-02 0.003500 -6.444493e-02 0.003550 -6.441246e-02 0.003600 -6.437896e-02 0.003650 -6.434443e-02 0.003700 -6.430887e-02 0.003750 -6.427227e-02 0.003800 -6.423465e-02 0.003850 -6.419601e-02 0.003900 -6.415635e-02 0.003950 -6.411568e-02 0.004000 -6.407400e-02 0.004050 -6.403132e-02 0.004100 -6.398765e-02 0.004150 -6.394298e-02 0.004200 -6.389734e-02 0.004250 -6.385071e-02 0.004300 -6.380312e-02 0.004350 -6.375457e-02 0.004400 -6.370507e-02 0.004450 -6.365462e-02 0.004500 -6.360323e-02 0.004550 -6.355093e-02 0.004600 -6.349769e-02 0.004650 -6.344355e-02 0.004700 -6.338851e-02 0.004750 -6.333257e-02 0.004800 -6.327577e-02 0.004850 -6.321809e-02 0.004900 -6.315953e-02 0.004950 -6.310013e-02 0.005000 -6.303988e-02 0.005050 -6.297880e-02 0.005100 -6.291689e-02 0.005150 -6.285418e-02 0.005200 -6.279065e-02 0.005250 -6.272632e-02 0.005300 -6.266122e-02 0.005350 -6.259534e-02 0.005400 -6.252870e-02 0.005450 -6.246130e-02 0.005500 -6.239315e-02 0.005550 -6.232428e-02 0.005600 -6.225467e-02 0.005650 -6.218435e-02 0.005700 -6.211332e-02 0.005750 -6.204160e-02 0.005800 -6.196918e-02 0.005850 -6.189610e-02 0.005900 -6.182234e-02 0.005950 -6.174793e-02 0.006000 -6.167287e-02 0.006050 -6.159717e-02 0.006100 -6.152084e-02 0.006150 -6.144390e-02 0.006200 -6.136634e-02 0.006250 -6.128818e-02 0.006300 -6.120942e-02 0.006350 -6.113009e-02 0.006400 -6.105018e-02 0.006450 -6.096970e-02 0.006500 -6.088867e-02 0.006550 -6.080709e-02 0.006600 -6.072497e-02 0.006650 -6.064232e-02 0.006700 -6.055915e-02 0.006750 -6.047546e-02 0.006800 -6.039127e-02 0.006850 -6.030658e-02 0.006900 -6.022140e-02 0.006950 -6.013574e-02 0.007000 -6.004961e-02 0.007050 -5.996302e-02 0.007100 -5.987597e-02 0.007150 -5.978846e-02 0.007200 -5.970052e-02 0.007250 -5.961214e-02 0.007300 -5.952333e-02 0.007350 -5.943411e-02 0.007400 -5.934447e-02 0.007450 -5.925443e-02 0.007500 -5.916399e-02 0.007550 -5.907316e-02 0.007600 -5.898195e-02 0.007650 -5.889036e-02 0.007700 -5.879840e-02 0.007750 -5.870608e-02 0.007800 -5.861340e-02 0.007850 -5.852038e-02 0.007900 -5.842701e-02 0.007950 -5.833330e-02 0.008000 -5.823927e-02 0.008050 -5.814490e-02 0.008100 -5.805023e-02 0.008150 -5.795523e-02 0.008200 -5.785993e-02 0.008250 -5.776434e-02 0.008300 -5.766845e-02 0.008350 -5.757227e-02 0.008400 -5.747581e-02 0.008450 -5.737907e-02 0.008500 -5.728206e-02 0.008550 -5.718479e-02 0.008600 -5.708725e-02 0.008650 -5.698947e-02 0.008700 -5.689143e-02 0.008750 -5.679314e-02 0.008800 -5.669462e-02 0.008850 -5.659586e-02 0.008900 -5.649688e-02 0.008950 -5.639767e-02 0.009000 -5.629825e-02 0.009050 -5.619860e-02 0.009100 -5.609875e-02 0.009150 -5.599870e-02 0.009200 -5.589844e-02 0.009250 -5.579799e-02 0.009300 -5.569734e-02 0.009350 -5.559652e-02 0.009400 -5.549550e-02 0.009450 -5.539432e-02 0.009500 -5.529295e-02 0.009550 -5.519142e-02 0.009600 -5.508973e-02 0.009650 -5.498787e-02 0.009700 -5.488585e-02 0.009750 -5.478369e-02 0.009800 -5.468137e-02 0.009850 -5.457891e-02 0.009900 -5.447630e-02 0.009950 -5.437356e-02 0.010000 -5.427069e-02 0.010050 -5.416769e-02 0.010100 -5.406456e-02 0.010150 -5.396130e-02 0.010200 -5.385793e-02 0.010250 -5.375444e-02 0.010300 -5.365085e-02 0.010350 -5.354713e-02 0.010400 -5.344331e-02 0.010450 -5.333940e-02 0.010500 -5.323538e-02 0.010550 -5.313127e-02 0.010600 -5.302706e-02 0.010650 -5.292277e-02 0.010700 -5.281839e-02 0.010750 -5.271392e-02 0.010800 -5.260937e-02 0.010850 -5.250475e-02 0.010900 -5.240005e-02 0.010950 -5.229528e-02 0.011000 -5.219044e-02 0.011050 -5.208553e-02 0.011100 -5.198056e-02 0.011150 -5.187553e-02 0.011200 -5.177044e-02 0.011250 -5.166530e-02 0.011300 -5.156010e-02 0.011350 -5.145485e-02 0.011400 -5.134956e-02 0.011450 -5.124422e-02 0.011500 -5.113882e-02 0.011550 -5.103340e-02 0.011600 -5.092794e-02 0.011650 -5.082244e-02 0.011700 -5.071691e-02 0.011750 -5.061134e-02 0.011800 -5.050575e-02 0.011850 -5.040013e-02 0.011900 -5.029449e-02 0.011950 -5.018882e-02 0.012000 -5.008313e-02 0.012050 -4.997742e-02 0.012100 -4.987170e-02 0.012150 -4.976596e-02 0.012200 -4.966021e-02 0.012250 -4.955445e-02 0.012300 -4.944868e-02 0.012350 -4.934291e-02 0.012400 -4.923713e-02 0.012450 -4.913134e-02 0.012500 -4.902556e-02 0.012550 -4.891978e-02 0.012600 -4.881400e-02 0.012650 -4.870822e-02 0.012700 -4.860245e-02 0.012750 -4.849669e-02 0.012800 -4.839093e-02 0.012850 -4.828520e-02 0.012900 -4.817947e-02 0.012950 -4.807375e-02 0.013000 -4.796805e-02 0.013050 -4.786237e-02 0.013100 -4.775670e-02 0.013150 -4.765106e-02 0.013200 -4.754544e-02 0.013250 -4.743984e-02 0.013300 -4.733427e-02 0.013350 -4.722873e-02 0.013400 -4.712320e-02 0.013450 -4.701771e-02 0.013500 -4.691225e-02 0.013550 -4.680682e-02 0.013600 -4.670144e-02 0.013650 -4.659607e-02 0.013700 -4.649075e-02 0.013750 -4.638546e-02 0.013800 -4.628021e-02 0.013850 -4.617500e-02 0.013900 -4.606983e-02 0.013950 -4.596470e-02 0.014000 -4.585961e-02 0.014050 -4.575456e-02 0.014100 -4.564957e-02 0.014150 -4.554462e-02 0.014200 -4.543971e-02 0.014250 -4.533486e-02 0.014300 -4.523005e-02 0.014350 -4.512529e-02 0.014400 -4.502059e-02 0.014450 -4.491593e-02 0.014500 -4.481133e-02 0.014550 -4.470679e-02 0.014600 -4.460230e-02 0.014650 -4.449787e-02 0.014700 -4.439349e-02 0.014750 -4.428917e-02 0.014800 -4.418491e-02 0.014850 -4.408072e-02 0.014900 -4.397658e-02 0.014950 -4.387250e-02 0.015000 -4.376848e-02 0.015050 -4.366453e-02 0.015100 -4.356065e-02 0.015150 -4.345683e-02 0.015200 -4.335307e-02 0.015250 -4.324938e-02 0.015300 -4.314576e-02 0.015350 -4.304220e-02 0.015400 -4.293872e-02 0.015450 -4.283530e-02 0.015500 -4.273195e-02 0.015550 -4.262868e-02 0.015600 -4.252548e-02 0.015650 -4.242234e-02 0.015700 -4.231929e-02 0.015750 -4.221631e-02 0.015800 -4.211340e-02 0.015850 -4.201056e-02 0.015900 -4.190780e-02 0.015950 -4.180512e-02 0.016000 -4.170251e-02 0.016050 -4.159999e-02 0.016100 -4.149753e-02 0.016150 -4.139516e-02 0.016200 -4.129287e-02 0.016250 -4.119066e-02 0.016300 -4.108852e-02 0.016350 -4.098647e-02 0.016400 -4.088450e-02 0.016450 -4.078261e-02 0.016500 -4.068080e-02 0.016550 -4.057908e-02 0.016600 -4.047744e-02 0.016650 -4.037588e-02 0.016700 -4.027441e-02 0.016750 -4.017302e-02 0.016800 -4.007172e-02 0.016850 -3.997050e-02 0.016900 -3.986937e-02 0.016950 -3.976833e-02 0.017000 -3.966737e-02 0.017050 -3.956650e-02 0.017100 -3.946572e-02 0.017150 -3.936503e-02 0.017200 -3.926442e-02 0.017250 -3.916391e-02 0.017300 -3.906348e-02 0.017350 -3.896315e-02 0.017400 -3.886290e-02 0.017450 -3.876274e-02 0.017500 -3.866268e-02 0.017550 -3.856271e-02 0.017600 -3.846282e-02 0.017650 -3.836303e-02 0.017700 -3.826334e-02 0.017750 -3.816373e-02 0.017800 -3.806422e-02 0.017850 -3.796479e-02 0.017900 -3.786547e-02 0.017950 -3.776624e-02 0.018000 -3.766710e-02 0.018050 -3.756806e-02 0.018100 -3.746911e-02 0.018150 -3.737026e-02 0.018200 -3.727150e-02 0.018250 -3.717283e-02 0.018300 -3.707427e-02 0.018350 -3.697580e-02 0.018400 -3.687742e-02 0.018450 -3.677914e-02 0.018500 -3.668096e-02 0.018550 -3.658287e-02 0.018600 -3.648489e-02 0.018650 -3.638699e-02 0.018700 -3.628920e-02 0.018750 -3.619151e-02 0.018800 -3.609391e-02 0.018850 -3.599641e-02 0.018900 -3.589901e-02 0.018950 -3.580171e-02 0.019000 -3.570450e-02 0.019050 -3.560740e-02 0.019100 -3.551040e-02 0.019150 -3.541349e-02 0.019200 -3.531668e-02 0.019250 -3.521997e-02 0.019300 -3.512337e-02 0.019350 -3.502686e-02 0.019400 -3.493045e-02 0.019450 -3.483415e-02 0.019500 -3.473794e-02 0.019550 -3.464183e-02 0.019600 -3.454583e-02 0.019650 -3.444993e-02 0.019700 -3.435412e-02 0.019750 -3.425842e-02 0.019800 -3.416282e-02 0.019850 -3.406732e-02 0.019900 -3.397192e-02 0.019950 -3.387662e-02 0.020000 -3.378143e-02 0.020050 -3.368634e-02 0.020100 -3.359135e-02 0.020150 -3.349646e-02 0.020200 -3.340167e-02 0.020250 -3.330698e-02 0.020300 -3.321240e-02 0.020350 -3.311792e-02 0.020400 -3.302355e-02 0.020450 -3.292927e-02 0.020500 -3.283510e-02 0.020550 -3.274103e-02 0.020600 -3.264706e-02 0.020650 -3.255320e-02 0.020700 -3.245944e-02 0.020750 -3.236578e-02 0.020800 -3.227222e-02 0.020850 -3.217877e-02 0.020900 -3.208542e-02 0.020950 -3.199218e-02 0.021000 -3.189903e-02 0.021050 -3.180599e-02 0.021100 -3.171306e-02 0.021150 -3.162023e-02 0.021200 -3.152750e-02 0.021250 -3.143487e-02 0.021300 -3.134235e-02 0.021350 -3.124993e-02 0.021400 -3.115762e-02 0.021450 -3.106540e-02 0.021500 -3.097330e-02 0.021550 -3.088129e-02 0.021600 -3.078939e-02 0.021650 -3.069760e-02 0.021700 -3.060590e-02 0.021750 -3.051431e-02 0.021800 -3.042283e-02 0.021850 -3.033145e-02 0.021900 -3.024017e-02 0.021950 -3.014900e-02 0.022000 -3.005793e-02 0.022050 -2.996696e-02 0.022100 -2.987610e-02 0.022150 -2.978534e-02 0.022200 -2.969468e-02 0.022250 -2.960414e-02 0.022300 -2.951369e-02 0.022350 -2.942334e-02 0.022400 -2.933311e-02 0.022450 -2.924297e-02 0.022500 -2.915294e-02 0.022550 -2.906301e-02 0.022600 -2.897319e-02 0.022650 -2.888347e-02 0.022700 -2.879385e-02 0.022750 -2.870434e-02 0.022800 -2.861493e-02 0.022850 -2.852562e-02 0.022900 -2.843642e-02 0.022950 -2.834732e-02 0.023000 -2.825833e-02 0.023050 -2.816944e-02 0.023100 -2.808064e-02 0.023150 -2.799196e-02 0.023200 -2.790338e-02 0.023250 -2.781490e-02 0.023300 -2.772653e-02 0.023350 -2.763826e-02 0.023400 -2.755010e-02 0.023450 -2.746203e-02 0.023500 -2.737407e-02 0.023550 -2.728621e-02 0.023600 -2.719846e-02 0.023650 -2.711081e-02 0.023700 -2.702327e-02 0.023750 -2.693582e-02 0.023800 -2.684848e-02 0.023850 -2.676124e-02 0.023900 -2.667411e-02 0.023950 -2.658707e-02 0.024000 -2.650015e-02 0.024050 -2.641332e-02 0.024100 -2.632660e-02 0.024150 -2.623997e-02 0.024200 -2.615346e-02 0.024250 -2.606704e-02 0.024300 -2.598073e-02 0.024350 -2.589452e-02 0.024400 -2.580841e-02 0.024450 -2.572241e-02 0.024500 -2.563650e-02 0.024550 -2.555070e-02 0.024600 -2.546500e-02 0.024650 -2.537940e-02 0.024700 -2.529391e-02 0.024750 -2.520851e-02 0.024800 -2.512322e-02 0.024850 -2.503804e-02 0.024900 -2.495295e-02 0.024950 -2.486796e-02 0.025000 -2.478308e-02 0.025050 -2.469830e-02 0.025100 -2.461362e-02 0.025150 -2.452904e-02 0.025200 -2.444456e-02 0.025250 -2.436018e-02 0.025300 -2.427591e-02 0.025350 -2.419174e-02 0.025400 -2.410766e-02 0.025450 -2.402370e-02 0.025500 -2.393982e-02 0.025550 -2.385605e-02 0.025600 -2.377239e-02 0.025650 -2.368882e-02 0.025700 -2.360535e-02 0.025750 -2.352199e-02 0.025800 -2.343872e-02 0.025850 -2.335556e-02 0.025900 -2.327249e-02 0.025950 -2.318952e-02 0.026000 -2.310666e-02 0.026050 -2.302390e-02 0.026100 -2.294123e-02 0.026150 -2.285867e-02 0.026200 -2.277620e-02 0.026250 -2.269384e-02 0.026300 -2.261158e-02 0.026350 -2.252941e-02 0.026400 -2.244735e-02 0.026450 -2.236538e-02 0.026500 -2.228351e-02 0.026550 -2.220175e-02 0.026600 -2.212008e-02 0.026650 -2.203851e-02 0.026700 -2.195704e-02 0.026750 -2.187567e-02 0.026800 -2.179440e-02 0.026850 -2.171323e-02 0.026900 -2.163215e-02 0.026950 -2.155118e-02 0.027000 -2.147030e-02 0.027050 -2.138952e-02 0.027100 -2.130884e-02 0.027150 -2.122826e-02 0.027200 -2.114778e-02 0.027250 -2.106739e-02 0.027300 -2.098710e-02 0.027350 -2.090691e-02 0.027400 -2.082682e-02 0.027450 -2.074683e-02 0.027500 -2.066693e-02 0.027550 -2.058713e-02 0.027600 -2.050744e-02 0.027650 -2.042783e-02 0.027700 -2.034832e-02 0.027750 -2.026891e-02 0.027800 -2.018960e-02 0.027850 -2.011039e-02 0.027900 -2.003127e-02 0.027950 -1.995225e-02 0.028000 -1.987332e-02 0.028050 -1.979450e-02 0.028100 -1.971576e-02 0.028150 -1.963713e-02 0.028200 -1.955859e-02 0.028250 -1.948015e-02 0.028300 -1.940181e-02 0.028350 -1.932355e-02 0.028400 -1.924540e-02 0.028450 -1.916734e-02 0.028500 -1.908938e-02 0.028550 -1.901151e-02 0.028600 -1.893374e-02 0.028650 -1.885607e-02 0.028700 -1.877848e-02 0.028750 -1.870101e-02 0.028800 -1.862361e-02 0.028850 -1.854632e-02 0.028900 -1.846912e-02 0.028950 -1.839201e-02 0.029000 -1.831501e-02 0.029050 -1.823809e-02 0.029100 -1.816127e-02 0.029150 -1.808454e-02 0.029200 -1.800791e-02 0.029250 -1.793138e-02 0.029300 -1.785493e-02 0.029350 -1.777858e-02 0.029400 -1.770232e-02 0.029450 -1.762617e-02 0.029500 -1.755010e-02 0.029550 -1.747413e-02 0.029600 -1.739825e-02 0.029650 -1.732246e-02 0.029700 -1.724677e-02 0.029750 -1.717117e-02 0.029800 -1.709567e-02 0.029850 -1.702025e-02 0.029900 -1.694493e-02 0.029950 -1.686970e-02 0.030000 -1.679457e-02 0.030050 -1.671953e-02 0.030100 -1.664458e-02 0.030150 -1.656972e-02 0.030200 -1.649496e-02 0.030250 -1.642029e-02 0.030300 -1.634571e-02 0.030350 -1.627122e-02 0.030400 -1.619683e-02 0.030450 -1.612252e-02 0.030500 -1.604831e-02 0.030550 -1.597419e-02 0.030600 -1.590017e-02 0.030650 -1.582622e-02 0.030700 -1.575238e-02 0.030750 -1.567863e-02 0.030800 -1.560497e-02 0.030850 -1.553139e-02 0.030900 -1.545791e-02 0.030950 -1.538453e-02 0.031000 -1.531123e-02 0.031050 -1.523802e-02 0.031100 -1.516490e-02 0.031150 -1.509187e-02 0.031200 -1.501894e-02 0.031250 -1.494609e-02 0.031300 -1.487333e-02 0.031350 -1.480067e-02 0.031400 -1.472810e-02 0.031450 -1.465561e-02 0.031500 -1.458321e-02 0.031550 -1.451090e-02 0.031600 -1.443869e-02 0.031650 -1.436656e-02 0.031700 -1.429452e-02 0.031750 -1.422257e-02 0.031800 -1.415071e-02 0.031850 -1.407894e-02 0.031900 -1.400726e-02 0.031950 -1.393566e-02 0.032000 -1.386416e-02 0.032050 -1.379275e-02 0.032100 -1.372142e-02 0.032150 -1.365018e-02 0.032200 -1.357903e-02 0.032250 -1.350797e-02 0.032300 -1.343700e-02 0.032350 -1.336611e-02 0.032400 -1.329532e-02 0.032450 -1.322460e-02 0.032500 -1.315398e-02 0.032550 -1.308345e-02 0.032600 -1.301300e-02 0.032650 -1.294264e-02 0.032700 -1.287237e-02 0.032750 -1.280219e-02 0.032800 -1.273208e-02 0.032850 -1.266208e-02 0.032900 -1.259216e-02 0.032950 -1.252231e-02 0.033000 -1.245257e-02 0.033050 -1.238290e-02 0.033100 -1.231333e-02 0.033150 -1.224384e-02 0.033200 -1.217443e-02 0.033250 -1.210512e-02 0.033300 -1.203588e-02 0.033350 -1.196674e-02 0.033400 -1.189768e-02 0.033450 -1.182870e-02 0.033500 -1.175982e-02 0.033550 -1.169101e-02 0.033600 -1.162229e-02 0.033650 -1.155366e-02 0.033700 -1.148512e-02 0.033750 -1.141665e-02 0.033800 -1.134828e-02 0.033850 -1.127999e-02 0.033900 -1.121178e-02 0.033950 -1.114366e-02 0.034000 -1.107563e-02 0.034050 -1.100767e-02 0.034100 -1.093980e-02 0.034150 -1.087202e-02 0.034200 -1.080432e-02 0.034250 -1.073671e-02 0.034300 -1.066918e-02 0.034350 -1.060173e-02 0.034400 -1.053437e-02 0.034450 -1.046709e-02 0.034500 -1.039989e-02 0.034550 -1.033279e-02 0.034600 -1.026576e-02 0.034650 -1.019881e-02 0.034700 -1.013195e-02 0.034750 -1.006517e-02 0.034800 -9.998483e-03 0.034850 -9.931861e-03 0.034900 -9.865335e-03 0.034950 -9.798885e-03 0.035000 -9.732524e-03 0.035050 -9.666247e-03 0.035100 -9.600050e-03 0.035150 -9.533935e-03 0.035200 -9.467899e-03 0.035250 -9.401948e-03 0.035300 -9.336079e-03 0.035350 -9.270289e-03 0.035400 -9.204589e-03 0.035450 -9.138965e-03 0.035500 -9.073421e-03 0.035550 -9.007960e-03 0.035600 -8.942580e-03 0.035650 -8.877289e-03 0.035700 -8.812071e-03 0.035750 -8.746936e-03 0.035800 -8.681882e-03 0.035850 -8.616906e-03 0.035900 -8.552021e-03 0.035950 -8.487207e-03 0.036000 -8.422474e-03 0.036050 -8.357826e-03 0.036100 -8.293262e-03 0.036150 -8.228769e-03 0.036200 -8.164364e-03 0.036250 -8.100036e-03 0.036300 -8.035786e-03 0.036350 -7.971623e-03 0.036400 -7.907528e-03 0.036450 -7.843522e-03 0.036500 -7.779594e-03 0.036550 -7.715744e-03 0.036600 -7.651978e-03 0.036650 -7.588291e-03 0.036700 -7.524679e-03 0.036750 -7.461147e-03 0.036800 -7.397697e-03 0.036850 -7.334321e-03 0.036900 -7.271031e-03 0.036950 -7.207819e-03 0.037000 -7.144685e-03 0.037050 -7.081628e-03 0.037100 -7.018648e-03 0.037150 -6.955749e-03 0.037200 -6.892929e-03 0.037250 -6.830182e-03 0.037300 -6.767520e-03 0.037350 -6.704932e-03 0.037400 -6.642422e-03 0.037450 -6.579992e-03 0.037500 -6.517641e-03 0.037550 -6.455363e-03 0.037600 -6.393163e-03 0.037650 -6.331042e-03 0.037700 -6.269005e-03 0.037750 -6.207041e-03 0.037800 -6.145151e-03 0.037850 -6.083338e-03 0.037900 -6.021605e-03 0.037950 -5.959950e-03 0.038000 -5.898372e-03 0.038050 -5.836869e-03 0.038100 -5.775444e-03 0.038150 -5.714096e-03 0.038200 -5.652820e-03 0.038250 -5.591619e-03 0.038300 -5.530501e-03 0.038350 -5.469457e-03 0.038400 -5.408492e-03 0.038450 -5.347601e-03 0.038500 -5.286783e-03 0.038550 -5.226044e-03 0.038600 -5.165375e-03 0.038650 -5.104787e-03 0.038700 -5.044271e-03 0.038750 -4.983832e-03 0.038800 -4.923474e-03 0.038850 -4.863188e-03 0.038900 -4.802975e-03 0.038950 -4.742839e-03 0.039000 -4.682778e-03 0.039050 -4.622790e-03 0.039100 -4.562877e-03 0.039150 -4.503044e-03 0.039200 -4.443278e-03 0.039250 -4.383592e-03 0.039300 -4.323976e-03 0.039350 -4.264441e-03 0.039400 -4.204976e-03 0.039450 -4.145586e-03 0.039500 -4.086267e-03 0.039550 -4.027024e-03 0.039600 -3.967855e-03 0.039650 -3.908764e-03 0.039700 -3.849743e-03 0.039750 -3.790796e-03 0.039800 -3.731923e-03 0.039850 -3.673121e-03 0.039900 -3.614393e-03 0.039950 -3.555735e-03 0.040000 -3.497164e-03 0.040050 -3.438654e-03 0.040100 -3.380217e-03 0.040150 -3.321858e-03 0.040200 -3.263569e-03 0.040250 -3.205352e-03 0.040300 -3.147208e-03 0.040350 -3.089141e-03 0.040400 -3.031140e-03 0.040450 -2.973214e-03 0.040500 -2.915363e-03 0.040550 -2.857578e-03 0.040600 -2.799875e-03 0.040650 -2.742234e-03 0.040700 -2.684674e-03 0.040750 -2.627181e-03 0.040800 -2.569757e-03 0.040850 -2.512409e-03 0.040900 -2.455129e-03 0.040950 -2.397920e-03 0.041000 -2.340786e-03 0.041050 -2.283723e-03 0.041100 -2.226725e-03 0.041150 -2.169805e-03 0.041200 -2.112957e-03 0.041250 -2.056176e-03 0.041300 -1.999468e-03 0.041350 -1.942833e-03 0.041400 -1.886260e-03 0.041450 -1.829770e-03 0.041500 -1.773338e-03 0.041550 -1.716982e-03 0.041600 -1.660699e-03 0.041650 -1.604479e-03 0.041700 -1.548339e-03 0.041750 -1.492261e-03 0.041800 -1.436262e-03 0.041850 -1.380326e-03 0.041900 -1.324462e-03 0.041950 -1.268669e-03 0.042000 -1.212942e-03 0.042050 -1.157283e-03 0.042100 -1.101697e-03 0.042150 -1.046187e-03 0.042200 -9.907314e-04 0.042250 -9.353526e-04 0.042300 -8.800411e-04 0.042350 -8.248003e-04 0.042400 -7.696312e-04 0.042450 -7.145306e-04 0.042500 -6.594973e-04 0.042550 -6.045304e-04 0.042600 -5.496346e-04 0.042650 -4.948030e-04 0.042700 -4.400461e-04 0.042750 -3.853553e-04 0.042800 -3.307338e-04 0.042850 -2.761787e-04 0.042900 -2.216945e-04 0.042950 -1.672760e-04 0.043000 -1.129224e-04 0.043050 -5.864153e-05 0.043100 -4.425858e-06 0.043150 4.972549e-05 0.043200 1.038063e-04 0.043250 1.578132e-04 0.043300 2.117625e-04 0.043350 2.656342e-04 0.043400 3.194457e-04 0.043450 3.731856e-04 0.043500 4.268627e-04 0.043550 4.804675e-04 0.043600 5.340097e-04 0.043650 5.874916e-04 0.043700 6.408956e-04 0.043750 6.942337e-04 0.043800 7.475112e-04 0.043850 8.007241e-04 0.043900 8.538633e-04 0.043950 9.069375e-04 0.044000 9.599466e-04 0.044050 1.012889e-03 0.044100 1.065769e-03 0.044150 1.118574e-03 0.044200 1.171321e-03 0.044250 1.224006e-03 0.044300 1.276613e-03 0.044350 1.329165e-03 0.044400 1.381643e-03 0.044450 1.434061e-03 0.044500 1.486413e-03 0.044550 1.538695e-03 0.044600 1.590919e-03 0.044650 1.643076e-03 0.044700 1.695155e-03 0.044750 1.747187e-03 0.044800 1.799150e-03 0.044850 1.851041e-03 0.044900 1.902876e-03 0.044950 1.954633e-03 0.045000 2.006334e-03 0.045050 2.057973e-03 0.045100 2.109550e-03 0.045150 2.161050e-03 0.045200 2.212496e-03 0.045250 2.263877e-03 0.045300 2.315189e-03 0.045350 2.366445e-03 0.045400 2.417625e-03 0.045450 2.468752e-03 0.045500 2.519809e-03 0.045550 2.570800e-03 0.045600 2.621731e-03 0.045650 2.672607e-03 0.045700 2.723408e-03 0.045750 2.774144e-03 0.045800 2.824823e-03 0.045850 2.875435e-03 0.045900 2.925986e-03 0.045950 2.976476e-03 0.046000 3.026903e-03 0.046050 3.077262e-03 0.046100 3.127560e-03 0.046150 3.177796e-03 0.046200 3.227971e-03 0.046250 3.278088e-03 0.046300 3.328134e-03 0.046350 3.378118e-03 0.046400 3.428037e-03 0.046450 3.477900e-03 0.046500 3.527703e-03 0.046550 3.577436e-03 0.046600 3.627113e-03 0.046650 3.676719e-03 0.046700 3.726269e-03 0.046750 3.775758e-03 0.046800 3.825187e-03 0.046850 3.874548e-03 0.046900 3.923850e-03 0.046950 3.973092e-03 0.047000 4.022274e-03 0.047050 4.071390e-03 0.047100 4.120452e-03 0.047150 4.169448e-03 0.047200 4.218379e-03 0.047250 4.267255e-03 0.047300 4.316064e-03 0.047350 4.364821e-03 0.047400 4.413508e-03 0.047450 4.462137e-03 0.047500 4.510708e-03 0.047550 4.559216e-03 0.047600 4.607660e-03 0.047650 4.656054e-03 0.047700 4.704381e-03 0.047750 4.752648e-03 0.047800 4.800848e-03 0.047850 4.848998e-03 0.047900 4.897080e-03 0.047950 4.945106e-03 0.048000 4.993073e-03 0.048050 5.040981e-03 0.048100 5.088825e-03 0.048150 5.136613e-03 0.048200 5.184338e-03 0.048250 5.232004e-03 0.048300 5.279610e-03 0.048350 5.327160e-03 0.048400 5.374652e-03 0.048450 5.422074e-03 0.048500 5.469448e-03 0.048550 5.516757e-03 0.048600 5.564014e-03 0.048650 5.611203e-03 0.048700 5.658337e-03 0.048750 5.705411e-03 0.048800 5.752427e-03 0.048850 5.799388e-03 0.048900 5.846281e-03 0.048950 5.893122e-03 0.049000 5.939905e-03 0.049050 5.986627e-03 0.049100 6.033296e-03 0.049150 6.079898e-03 0.049200 6.126449e-03 0.049250 6.172947e-03 0.049300 6.219374e-03 0.049350 6.265751e-03 0.049400 6.312062e-03 0.049450 6.358319e-03 0.049500 6.404524e-03 0.049550 6.450665e-03 0.049600 6.496750e-03 0.049650 6.542781e-03 0.049700 6.588747e-03 0.049750 6.634659e-03 0.049800 6.680519e-03 0.049850 6.726315e-03 0.049900 6.772056e-03 0.049950 6.817741e-03 0.050000 6.863367e-03 0.050050 6.908941e-03 0.050100 6.954449e-03 0.050150 6.999896e-03 0.050200 7.045303e-03 0.050250 7.090641e-03 0.050300 7.135934e-03 0.050350 7.181159e-03 0.050400 7.226336e-03 0.050450 7.271446e-03 0.050500 7.316514e-03 0.050550 7.361508e-03 0.050600 7.406459e-03 0.050650 7.451350e-03 0.050700 7.496188e-03 0.050750 7.540971e-03 0.050800 7.585696e-03 0.050850 7.630362e-03 0.050900 7.674972e-03 0.050950 7.719526e-03 0.051000 7.764025e-03 0.051050 7.808472e-03 0.051100 7.852865e-03 0.051150 7.897194e-03 0.051200 7.941472e-03 0.051250 7.985691e-03 0.051300 8.029862e-03 0.051350 8.073977e-03 0.051400 8.118034e-03 0.051450 8.162035e-03 0.051500 8.205982e-03 0.051550 8.249874e-03 0.051600 8.293711e-03 0.051650 8.337497e-03 0.051700 8.381224e-03 0.051750 8.424895e-03 0.051800 8.468517e-03 0.051850 8.512082e-03 0.051900 8.555592e-03 0.051950 8.599053e-03 0.052000 8.642456e-03 0.052050 8.685799e-03 0.052100 8.729090e-03 0.052150 8.772333e-03 0.052200 8.815523e-03 0.052250 8.858642e-03 0.052300 8.901722e-03 0.052350 8.944751e-03 0.052400 8.987717e-03 0.052450 9.030630e-03 0.052500 9.073495e-03 0.052550 9.116308e-03 0.052600 9.159060e-03 0.052650 9.201765e-03 0.052700 9.244412e-03 0.052750 9.287011e-03 0.052800 9.329556e-03 0.052850 9.372043e-03 0.052900 9.414476e-03 0.052950 9.456860e-03 0.053000 9.499190e-03 0.053050 9.541465e-03 0.053100 9.583688e-03 0.053150 9.625862e-03 0.053200 9.667984e-03 0.053250 9.710046e-03 0.053300 9.752062e-03 0.053350 9.794026e-03 0.053400 9.835934e-03 0.053450 9.877790e-03 0.053500 9.919594e-03 0.053550 9.961343e-03 0.053600 1.000304e-02 0.053650 1.004469e-02 0.053700 1.008629e-02 0.053750 1.012783e-02 0.053800 1.016932e-02 0.053850 1.021076e-02 0.053900 1.025215e-02 0.053950 1.029349e-02 0.054000 1.033477e-02 0.054050 1.037601e-02 0.054100 1.041719e-02 0.054150 1.045832e-02 0.054200 1.049939e-02 0.054250 1.054042e-02 0.054300 1.058140e-02 0.054350 1.062233e-02 0.054400 1.066320e-02 0.054450 1.070403e-02 0.054500 1.074480e-02 0.054550 1.078552e-02 0.054600 1.082619e-02 0.054650 1.086681e-02 0.054700 1.090738e-02 0.054750 1.094790e-02 0.054800 1.098837e-02 0.054850 1.102878e-02 0.054900 1.106916e-02 0.054950 1.110947e-02 0.055000 1.114973e-02 0.055050 1.118995e-02 0.055100 1.123011e-02 0.055150 1.127023e-02 0.055200 1.131029e-02 0.055250 1.135030e-02 0.055300 1.139027e-02 0.055350 1.143019e-02 0.055400 1.147005e-02 0.055450 1.150987e-02 0.055500 1.154963e-02 0.055550 1.158934e-02 0.055600 1.162901e-02 0.055650 1.166863e-02 0.055700 1.170820e-02 0.055750 1.174772e-02 0.055800 1.178719e-02 0.055850 1.182660e-02 0.055900 1.186598e-02 0.055950 1.190529e-02 0.056000 1.194456e-02 0.056050 1.198379e-02 0.056100 1.202296e-02 0.056150 1.206209e-02 0.056200 1.210116e-02 0.056250 1.214019e-02 0.056300 1.217917e-02 0.056350 1.221810e-02 0.056400 1.225698e-02 0.056450 1.229581e-02 0.056500 1.233460e-02 0.056550 1.237333e-02 0.056600 1.241201e-02 0.056650 1.245066e-02 0.056700 1.248924e-02 0.056750 1.252779e-02 0.056800 1.256628e-02 0.056850 1.260473e-02 0.056900 1.264312e-02 0.056950 1.268147e-02 0.057000 1.271977e-02 0.057050 1.275803e-02 0.057100 1.279623e-02 0.057150 1.283439e-02 0.057200 1.287251e-02 0.057250 1.291057e-02 0.057300 1.294858e-02 0.057350 1.298655e-02 0.057400 1.302447e-02 0.057450 1.306235e-02 0.057500 1.310017e-02 0.057550 1.313795e-02 0.057600 1.317568e-02 0.057650 1.321337e-02 0.057700 1.325101e-02 0.057750 1.328860e-02 0.057800 1.332614e-02 0.057850 1.336364e-02 0.057900 1.340108e-02 0.057950 1.343849e-02 0.058000 1.347584e-02 0.058050 1.351315e-02 0.058100 1.355042e-02 0.058150 1.358763e-02 0.058200 1.362480e-02 0.058250 1.366193e-02 0.058300 1.369901e-02 0.058350 1.373604e-02 0.058400 1.377302e-02 0.058450 1.380997e-02 0.058500 1.384686e-02 0.058550 1.388370e-02 0.058600 1.392050e-02 0.058650 1.395725e-02 0.058700 1.399397e-02 0.058750 1.403062e-02 0.058800 1.406724e-02 0.058850 1.410381e-02 0.058900 1.414034e-02 0.058950 1.417682e-02 0.059000 1.421325e-02 0.059050 1.424964e-02 0.059100 1.428598e-02 0.059150 1.432228e-02 0.059200 1.435853e-02 0.059250 1.439474e-02 0.059300 1.443090e-02 0.059350 1.446702e-02 0.059400 1.450309e-02 0.059450 1.453911e-02 0.059500 1.457510e-02 0.059550 1.461104e-02 0.059600 1.464693e-02 0.059650 1.468277e-02 0.059700 1.471857e-02 0.059750 1.475433e-02 0.059800 1.479005e-02 0.059850 1.482571e-02 0.059900 1.486133e-02 0.059950 1.489691e-02 0.060000 1.493245e-02 0.060050 1.496794e-02 0.060100 1.500338e-02 0.060150 1.503879e-02 0.060200 1.507414e-02 0.060250 1.510946e-02 0.060300 1.514472e-02 0.060350 1.517995e-02 0.060400 1.521513e-02 0.060450 1.525026e-02 0.060500 1.528536e-02 0.060550 1.532041e-02 0.060600 1.535542e-02 0.060650 1.539038e-02 0.060700 1.542529e-02 0.060750 1.546017e-02 0.060800 1.549500e-02 0.060850 1.552979e-02 0.060900 1.556453e-02 0.060950 1.559923e-02 0.061000 1.563389e-02 0.061050 1.566850e-02 0.061100 1.570307e-02 0.061150 1.573760e-02 0.061200 1.577208e-02 0.061250 1.580652e-02 0.061300 1.584092e-02 0.061350 1.587528e-02 0.061400 1.590959e-02 0.061450 1.594386e-02 0.061500 1.597809e-02 0.061550 1.601227e-02 0.061600 1.604642e-02 0.061650 1.608051e-02 0.061700 1.611457e-02 0.061750 1.614858e-02 0.061800 1.618255e-02 0.061850 1.621648e-02 0.061900 1.625036e-02 0.061950 1.628421e-02 0.062000 1.631801e-02 0.062050 1.635177e-02 0.062100 1.638548e-02 0.062150 1.641916e-02 0.062200 1.645280e-02 0.062250 1.648638e-02 0.062300 1.651993e-02 0.062350 1.655344e-02 0.062400 1.658691e-02 0.062450 1.662033e-02 0.062500 1.665371e-02 0.062550 1.668705e-02 0.062600 1.672035e-02 0.062650 1.675360e-02 0.062700 1.678682e-02 0.062750 1.681999e-02 0.062800 1.685312e-02 0.062850 1.688622e-02 0.062900 1.691926e-02 0.062950 1.695227e-02 0.063000 1.698524e-02 0.063050 1.701816e-02 0.063100 1.705105e-02 0.063150 1.708389e-02 0.063200 1.711669e-02 0.063250 1.714946e-02 0.063300 1.718218e-02 0.063350 1.721486e-02 0.063400 1.724750e-02 0.063450 1.728010e-02 0.063500 1.731265e-02 0.063550 1.734517e-02 0.063600 1.737764e-02 0.063650 1.741008e-02 0.063700 1.744247e-02 0.063750 1.747483e-02 0.063800 1.750715e-02 0.063850 1.753942e-02 0.063900 1.757165e-02 0.063950 1.760385e-02 0.064000 1.763600e-02 0.064050 1.766811e-02 0.064100 1.770019e-02 0.064150 1.773222e-02 0.064200 1.776421e-02 0.064250 1.779616e-02 0.064300 1.782808e-02 0.064350 1.785995e-02 0.064400 1.789178e-02 0.064450 1.792357e-02 0.064500 1.795533e-02 0.064550 1.798704e-02 0.064600 1.801872e-02 0.064650 1.805035e-02 0.064700 1.808195e-02 0.064750 1.811350e-02 0.064800 1.814502e-02 0.064850 1.817650e-02 0.064900 1.820793e-02 0.064950 1.823933e-02 0.065000 1.827069e-02 0.065050 1.830201e-02 0.065100 1.833329e-02 0.065150 1.836453e-02 0.065200 1.839573e-02 0.065250 1.842690e-02 0.065300 1.845802e-02 0.065350 1.848911e-02 0.065400 1.852015e-02 0.065450 1.855116e-02 0.065500 1.858213e-02 0.065550 1.861306e-02 0.065600 1.864396e-02 0.065650 1.867481e-02 0.065700 1.870563e-02 0.065750 1.873640e-02 0.065800 1.876714e-02 0.065850 1.879783e-02 0.065900 1.882850e-02 0.065950 1.885912e-02 0.066000 1.888971e-02 0.066050 1.892025e-02 0.066100 1.895076e-02 0.066150 1.898123e-02 0.066200 1.901166e-02 0.066250 1.904206e-02 0.066300 1.907242e-02 0.066350 1.910274e-02 0.066400 1.913302e-02 0.066450 1.916326e-02 0.066500 1.919346e-02 0.066550 1.922363e-02 0.066600 1.925376e-02 0.066650 1.928385e-02 0.066700 1.931390e-02 0.066750 1.934392e-02 0.066800 1.937390e-02 0.066850 1.940384e-02 0.066900 1.943375e-02 0.066950 1.946362e-02 0.067000 1.949344e-02 0.067050 1.952323e-02 0.067100 1.955299e-02 0.067150 1.958271e-02 0.067200 1.961239e-02 0.067250 1.964203e-02 0.067300 1.967164e-02 0.067350 1.970120e-02 0.067400 1.973074e-02 0.067450 1.976023e-02 0.067500 1.978970e-02 0.067550 1.981912e-02 0.067600 1.984851e-02 0.067650 1.987785e-02 0.067700 1.990717e-02 0.067750 1.993644e-02 0.067800 1.996568e-02 0.067850 1.999488e-02 0.067900 2.002405e-02 0.067950 2.005318e-02 0.068000 2.008227e-02 0.068050 2.011132e-02 0.068100 2.014035e-02 0.068150 2.016933e-02 0.068200 2.019828e-02 0.068250 2.022719e-02 0.068300 2.025607e-02 0.068350 2.028491e-02 0.068400 2.031371e-02 0.068450 2.034248e-02 0.068500 2.037121e-02 0.068550 2.039991e-02 0.068600 2.042857e-02 0.068650 2.045719e-02 0.068700 2.048578e-02 0.068750 2.051433e-02 0.068800 2.054285e-02 0.068850 2.057133e-02 0.068900 2.059978e-02 0.068950 2.062819e-02 0.069000 2.065656e-02 0.069050 2.068491e-02 0.069100 2.071321e-02 0.069150 2.074148e-02 0.069200 2.076971e-02 0.069250 2.079791e-02 0.069300 2.082607e-02 0.069350 2.085420e-02 0.069400 2.088229e-02 0.069450 2.091035e-02 0.069500 2.093837e-02 0.069550 2.096636e-02 0.069600 2.099431e-02 0.069650 2.102223e-02 0.069700 2.105010e-02 0.069750 2.107796e-02 0.069800 2.110577e-02 0.069850 2.113355e-02 0.069900 2.116129e-02 0.069950 2.118900e-02 0.070000 2.121668e-02 0.070050 2.124431e-02 0.070100 2.127192e-02 0.070150 2.129949e-02 0.070200 2.132702e-02 0.070250 2.135453e-02 0.070300 2.138200e-02 0.070350 2.140943e-02 0.070400 2.143683e-02 0.070450 2.146420e-02 0.070500 2.149152e-02 0.070550 2.151882e-02 0.070600 2.154608e-02 0.070650 2.157331e-02 0.070700 2.160051e-02 0.070750 2.162766e-02 0.070800 2.165479e-02 0.070850 2.168188e-02 0.070900 2.170894e-02 0.070950 2.173597e-02 0.071000 2.176296e-02 0.071050 2.178991e-02 0.071100 2.181683e-02 0.071150 2.184373e-02 0.071200 2.187058e-02 0.071250 2.189741e-02 0.071300 2.192420e-02 0.071350 2.195095e-02 0.071400 2.197767e-02 0.071450 2.200436e-02 0.071500 2.203102e-02 0.071550 2.205764e-02 0.071600 2.208423e-02 0.071650 2.211079e-02 0.071700 2.213731e-02 0.071750 2.216380e-02 0.071800 2.219026e-02 0.071850 2.221668e-02 0.071900 2.224307e-02 0.071950 2.226943e-02 0.072000 2.229575e-02 0.072050 2.232204e-02 0.072100 2.234830e-02 0.072150 2.237453e-02 0.072200 2.240072e-02 0.072250 2.242688e-02 0.072300 2.245301e-02 0.072350 2.247911e-02 0.072400 2.250517e-02 0.072450 2.253120e-02 0.072500 2.255720e-02 0.072550 2.258316e-02 0.072600 2.260909e-02 0.072650 2.263499e-02 0.072700 2.266086e-02 0.072750 2.268670e-02 0.072800 2.271250e-02 0.072850 2.273827e-02 0.072900 2.276401e-02 0.072950 2.278972e-02 0.073000 2.281540e-02 0.073050 2.284103e-02 0.073100 2.286664e-02 0.073150 2.289222e-02 0.073200 2.291777e-02 0.073250 2.294329e-02 0.073300 2.296877e-02 0.073350 2.299422e-02 0.073400 2.301964e-02 0.073450 2.304503e-02 0.073500 2.307038e-02 0.073550 2.309571e-02 0.073600 2.312100e-02 0.073650 2.314626e-02 0.073700 2.317149e-02 0.073750 2.319669e-02 0.073800 2.322185e-02 0.073850 2.324699e-02 0.073900 2.327209e-02 0.073950 2.329716e-02 0.074000 2.332220e-02 0.074050 2.334721e-02 0.074100 2.337219e-02 0.074150 2.339713e-02 0.074200 2.342206e-02 0.074250 2.344694e-02 0.074300 2.347179e-02 0.074350 2.349662e-02 0.074400 2.352141e-02 0.074450 2.354617e-02 0.074500 2.357090e-02 0.074550 2.359559e-02 0.074600 2.362026e-02 0.074650 2.364489e-02 0.074700 2.366950e-02 0.074750 2.369408e-02 0.074800 2.371863e-02 0.074850 2.374314e-02 0.074900 2.376763e-02 0.074950 2.379207e-02 0.075000 2.381650e-02 0.075050 2.384089e-02 0.075100 2.386525e-02 0.075150 2.388959e-02 0.075200 2.391389e-02 0.075250 2.393815e-02 0.075300 2.396240e-02 0.075350 2.398661e-02 0.075400 2.401078e-02 0.075450 2.403493e-02 0.075500 2.405905e-02 0.075550 2.408315e-02 0.075600 2.410720e-02 0.075650 2.413122e-02 0.075700 2.415522e-02 0.075750 2.417920e-02 0.075800 2.420313e-02 0.075850 2.422705e-02 0.075900 2.425092e-02 0.075950 2.427477e-02 0.076000 2.429859e-02 0.076050 2.432238e-02 0.076100 2.434614e-02 0.076150 2.436987e-02 0.076200 2.439357e-02 0.076250 2.441724e-02 0.076300 2.444089e-02 0.076350 2.446450e-02 0.076400 2.448808e-02 0.076450 2.451163e-02 0.076500 2.453515e-02 0.076550 2.455865e-02 0.076600 2.458212e-02 0.076650 2.460555e-02 0.076700 2.462895e-02 0.076750 2.465233e-02 0.076800 2.467568e-02 0.076850 2.469900e-02 0.076900 2.472230e-02 0.076950 2.474555e-02 0.077000 2.476878e-02 0.077050 2.479198e-02 0.077100 2.481516e-02 0.077150 2.483830e-02 0.077200 2.486142e-02 0.077250 2.488451e-02 0.077300 2.490756e-02 0.077350 2.493059e-02 0.077400 2.495359e-02 0.077450 2.497656e-02 0.077500 2.499951e-02 0.077550 2.502243e-02 0.077600 2.504531e-02 0.077650 2.506816e-02 0.077700 2.509099e-02 0.077750 2.511379e-02 0.077800 2.513657e-02 0.077850 2.515931e-02 0.077900 2.518202e-02 0.077950 2.520471e-02 0.078000 2.522736e-02 0.078050 2.525000e-02 0.078100 2.527260e-02 0.078150 2.529517e-02 0.078200 2.531771e-02 0.078250 2.534023e-02 0.078300 2.536272e-02 0.078350 2.538518e-02 0.078400 2.540761e-02 0.078450 2.543002e-02 0.078500 2.545239e-02 0.078550 2.547474e-02 0.078600 2.549705e-02 0.078650 2.551936e-02 0.078700 2.554162e-02 0.078750 2.556385e-02 0.078800 2.558607e-02 0.078850 2.560825e-02 0.078900 2.563041e-02 0.078950 2.565253e-02 0.079000 2.567463e-02 0.079050 2.569669e-02 0.079100 2.571874e-02 0.079150 2.574075e-02 0.079200 2.576274e-02 0.079250 2.578470e-02 0.079300 2.580664e-02 0.079350 2.582854e-02 0.079400 2.585041e-02 0.079450 2.587227e-02 0.079500 2.589410e-02 0.079550 2.591589e-02 0.079600 2.593766e-02 0.079650 2.595940e-02 0.079700 2.598112e-02 0.079750 2.600280e-02 0.079800 2.602447e-02 0.079850 2.604610e-02 0.079900 2.606770e-02 0.079950 2.608928e-02 0.080000 2.611084e-02 0.080050 2.613236e-02 0.080100 2.615386e-02 0.080150 2.617534e-02 0.080200 2.619678e-02 0.080250 2.621820e-02 0.080300 2.623959e-02 0.080350 2.626096e-02 0.080400 2.628229e-02 0.080450 2.630360e-02 0.080500 2.632489e-02 0.080550 2.634614e-02 0.080600 2.636738e-02 0.080650 2.638859e-02 0.080700 2.640977e-02 0.080750 2.643092e-02 0.080800 2.645204e-02 0.080850 2.647314e-02 0.080900 2.649422e-02 0.080950 2.651526e-02 0.081000 2.653628e-02 0.081050 2.655728e-02 0.081100 2.657825e-02 0.081150 2.659919e-02 0.081200 2.662010e-02 0.081250 2.664099e-02 0.081300 2.666185e-02 0.081350 2.668269e-02 0.081400 2.670350e-02 0.081450 2.672428e-02 0.081500 2.674504e-02 0.081550 2.676578e-02 0.081600 2.678649e-02 0.081650 2.680717e-02 0.081700 2.682783e-02 0.081750 2.684846e-02 0.081800 2.686906e-02 0.081850 2.688963e-02 0.081900 2.691019e-02 0.081950 2.693073e-02 0.082000 2.695123e-02 0.082050 2.697171e-02 0.082100 2.699216e-02 0.082150 2.701258e-02 0.082200 2.703297e-02 0.082250 2.705335e-02 0.082300 2.707370e-02 0.082350 2.709402e-02 0.082400 2.711432e-02 0.082450 2.713459e-02 0.082500 2.715484e-02 0.082550 2.717506e-02 0.082600 2.719526e-02 0.082650 2.721543e-02 0.082700 2.723557e-02 0.082750 2.725569e-02 0.082800 2.727579e-02 0.082850 2.729585e-02 0.082900 2.731591e-02 0.082950 2.733593e-02 0.083000 2.735592e-02 0.083050 2.737589e-02 0.083100 2.739583e-02 0.083150 2.741576e-02 0.083200 2.743565e-02 0.083250 2.745552e-02 0.083300 2.747536e-02 0.083350 2.749518e-02 0.083400 2.751499e-02 0.083450 2.753476e-02 0.083500 2.755450e-02 0.083550 2.757423e-02 0.083600 2.759393e-02 0.083650 2.761359e-02 0.083700 2.763325e-02 0.083750 2.765287e-02 0.083800 2.767247e-02 0.083850 2.769204e-02 0.083900 2.771159e-02 0.083950 2.773112e-02 0.084000 2.775062e-02 0.084050 2.777010e-02 0.084100 2.778955e-02 0.084150 2.780898e-02 0.084200 2.782838e-02 0.084250 2.784777e-02 0.084300 2.786712e-02 0.084350 2.788646e-02 0.084400 2.790576e-02 0.084450 2.792505e-02 0.084500 2.794430e-02 0.084550 2.796354e-02 0.084600 2.798276e-02 0.084650 2.800194e-02 0.084700 2.802110e-02 0.084750 2.804024e-02 0.084800 2.805936e-02 0.084850 2.807846e-02 0.084900 2.809752e-02 0.084950 2.811656e-02 0.085000 2.813558e-02 0.085050 2.815458e-02 0.085100 2.817356e-02 0.085150 2.819250e-02 0.085200 2.821143e-02 0.085250 2.823033e-02 0.085300 2.824920e-02 0.085350 2.826806e-02 0.085400 2.828688e-02 0.085450 2.830570e-02 0.085500 2.832448e-02 0.085550 2.834324e-02 0.085600 2.836198e-02 0.085650 2.838069e-02 0.085700 2.839938e-02 0.085750 2.841805e-02 0.085800 2.843669e-02 0.085850 2.845532e-02 0.085900 2.847391e-02 0.085950 2.849248e-02 0.086000 2.851103e-02 0.086050 2.852956e-02 0.086100 2.854807e-02 0.086150 2.856655e-02 0.086200 2.858500e-02 0.086250 2.860344e-02 0.086300 2.862186e-02 0.086350 2.864024e-02 0.086400 2.865861e-02 0.086450 2.867696e-02 0.086500 2.869527e-02 0.086550 2.871357e-02 0.086600 2.873184e-02 0.086650 2.875009e-02 0.086700 2.876833e-02 0.086750 2.878653e-02 0.086800 2.880471e-02 0.086850 2.882287e-02 0.086900 2.884101e-02 0.086950 2.885913e-02 0.087000 2.887722e-02 0.087050 2.889529e-02 0.087100 2.891334e-02 0.087150 2.893136e-02 0.087200 2.894937e-02 0.087250 2.896734e-02 0.087300 2.898531e-02 0.087350 2.900323e-02 0.087400 2.902115e-02 0.087450 2.903903e-02 0.087500 2.905691e-02 0.087550 2.907476e-02 0.087600 2.909258e-02 0.087650 2.911038e-02 0.087700 2.912816e-02 0.087750 2.914591e-02 0.087800 2.916365e-02 0.087850 2.918136e-02 0.087900 2.919905e-02 0.087950 2.921673e-02 0.088000 2.923437e-02 0.088050 2.925199e-02 0.088100 2.926959e-02 0.088150 2.928717e-02 0.088200 2.930473e-02 0.088250 2.932226e-02 0.088300 2.933979e-02 0.088350 2.935727e-02 0.088400 2.937474e-02 0.088450 2.939219e-02 0.088500 2.940962e-02 0.088550 2.942703e-02 0.088600 2.944440e-02 0.088650 2.946176e-02 0.088700 2.947911e-02 0.088750 2.949642e-02 0.088800 2.951372e-02 0.088850 2.953099e-02 0.088900 2.954824e-02 0.088950 2.956549e-02 0.089000 2.958270e-02 0.089050 2.959988e-02 0.089100 2.961705e-02 0.089150 2.963420e-02 0.089200 2.965132e-02 0.089250 2.966842e-02 0.089300 2.968551e-02 0.089350 2.970256e-02 0.089400 2.971960e-02 0.089450 2.973662e-02 0.089500 2.975362e-02 0.089550 2.977059e-02 0.089600 2.978754e-02 0.089650 2.980448e-02 0.089700 2.982139e-02 0.089750 2.983828e-02 0.089800 2.985515e-02 0.089850 2.987200e-02 0.089900 2.988882e-02 0.089950 2.990564e-02 0.090000 2.992242e-02 0.090050 2.993918e-02 0.090100 2.995592e-02 0.090150 2.997265e-02 0.090200 2.998935e-02 0.090250 3.000602e-02 0.090300 3.002269e-02 0.090350 3.003933e-02 0.090400 3.005594e-02 0.090450 3.007254e-02 0.090500 3.008912e-02 0.090550 3.010568e-02 0.090600 3.012221e-02 0.090650 3.013873e-02 0.090700 3.015522e-02 0.090750 3.017169e-02 0.090800 3.018815e-02 0.090850 3.020458e-02 0.090900 3.022099e-02 0.090950 3.023738e-02 0.091000 3.025375e-02 0.091050 3.027010e-02 0.091100 3.028643e-02 0.091150 3.030274e-02 0.091200 3.031903e-02 0.091250 3.033530e-02 0.091300 3.035155e-02 0.091350 3.036778e-02 0.091400 3.038399e-02 0.091450 3.040017e-02 0.091500 3.041634e-02 0.091550 3.043249e-02 0.091600 3.044862e-02 0.091650 3.046472e-02 0.091700 3.048081e-02 0.091750 3.049687e-02 0.091800 3.051292e-02 0.091850 3.052894e-02 0.091900 3.054496e-02 0.091950 3.056094e-02 0.092000 3.057691e-02 0.092050 3.059286e-02 0.092100 3.060879e-02 0.092150 3.062469e-02 0.092200 3.064057e-02 0.092250 3.065644e-02 0.092300 3.067229e-02 0.092350 3.068812e-02 0.092400 3.070393e-02 0.092450 3.071971e-02 0.092500 3.073548e-02 0.092550 3.075123e-02 0.092600 3.076695e-02 0.092650 3.078267e-02 0.092700 3.079836e-02 0.092750 3.081403e-02 0.092800 3.082968e-02 0.092850 3.084530e-02 0.092900 3.086092e-02 0.092950 3.087651e-02 0.093000 3.089209e-02 0.093050 3.090763e-02 0.093100 3.092318e-02 0.093150 3.093869e-02 0.093200 3.095418e-02 0.093250 3.096966e-02 0.093300 3.098511e-02 0.093350 3.100056e-02 0.093400 3.101597e-02 0.093450 3.103137e-02 0.093500 3.104675e-02 0.093550 3.106210e-02 0.093600 3.107744e-02 0.093650 3.109277e-02 0.093700 3.110807e-02 0.093750 3.112336e-02 0.093800 3.113861e-02 0.093850 3.115386e-02 0.093900 3.116908e-02 0.093950 3.118430e-02 0.094000 3.119949e-02 0.094050 3.121465e-02 0.094100 3.122980e-02 0.094150 3.124493e-02 0.094200 3.126005e-02 0.094250 3.127514e-02 0.094300 3.129021e-02 0.094350 3.130526e-02 0.094400 3.132031e-02 0.094450 3.133532e-02 0.094500 3.135032e-02 0.094550 3.136531e-02 0.094600 3.138027e-02 0.094650 3.139520e-02 0.094700 3.141013e-02 0.094750 3.142504e-02 0.094800 3.143993e-02 0.094850 3.145480e-02 0.094900 3.146964e-02 0.094950 3.148448e-02 0.095000 3.149929e-02 0.095050 3.151408e-02 0.095100 3.152886e-02 0.095150 3.154361e-02 0.095200 3.155835e-02 0.095250 3.157307e-02 0.095300 3.158778e-02 0.095350 3.160247e-02 0.095400 3.161713e-02 0.095450 3.163178e-02 0.095500 3.164640e-02 0.095550 3.166101e-02 0.095600 3.167560e-02 0.095650 3.169018e-02 0.095700 3.170474e-02 0.095750 3.171927e-02 0.095800 3.173380e-02 0.095850 3.174829e-02 0.095900 3.176278e-02 0.095950 3.177724e-02 0.096000 3.179169e-02 0.096050 3.180612e-02 0.096100 3.182053e-02 0.096150 3.183492e-02 0.096200 3.184930e-02 0.096250 3.186366e-02 0.096300 3.187800e-02 0.096350 3.189232e-02 0.096400 3.190662e-02 0.096450 3.192091e-02 0.096500 3.193517e-02 0.096550 3.194942e-02 0.096600 3.196366e-02 0.096650 3.197787e-02 0.096700 3.199207e-02 0.096750 3.200625e-02 0.096800 3.202041e-02 0.096850 3.203455e-02 0.096900 3.204868e-02 0.096950 3.206278e-02 0.097000 3.207688e-02 0.097050 3.209095e-02 0.097100 3.210500e-02 0.097150 3.211904e-02 0.097200 3.213306e-02 0.097250 3.214706e-02 0.097300 3.216105e-02 0.097350 3.217502e-02 0.097400 3.218897e-02 0.097450 3.220290e-02 0.097500 3.221682e-02 0.097550 3.223071e-02 0.097600 3.224460e-02 0.097650 3.225846e-02 0.097700 3.227230e-02 0.097750 3.228613e-02 0.097800 3.229994e-02 0.097850 3.231374e-02 0.097900 3.232752e-02 0.097950 3.234128e-02 0.098000 3.235502e-02 0.098050 3.236874e-02 0.098100 3.238245e-02 0.098150 3.239614e-02 0.098200 3.240982e-02 0.098250 3.242348e-02 0.098300 3.243712e-02 0.098350 3.245074e-02 0.098400 3.246435e-02 0.098450 3.247793e-02 0.098500 3.249151e-02 0.098550 3.250506e-02 0.098600 3.251860e-02 0.098650 3.253212e-02 0.098700 3.254562e-02 0.098750 3.255911e-02 0.098800 3.257258e-02 0.098850 3.258603e-02 0.098900 3.259947e-02 0.098950 3.261289e-02 0.099000 3.262630e-02 0.099050 3.263968e-02 0.099100 3.265305e-02 0.099150 3.266640e-02 0.099200 3.267974e-02 0.099250 3.269306e-02 0.099300 3.270637e-02 0.099350 3.271965e-02 0.099400 3.273292e-02 0.099450 3.274617e-02 0.099500 3.275941e-02 0.099550 3.277263e-02 0.099600 3.278584e-02 0.099650 3.279902e-02 0.099700 3.281219e-02 0.099750 3.282535e-02 0.099800 3.283849e-02 0.099850 3.285161e-02 0.099900 3.286471e-02 0.099950 3.287780e-02 0.100000 3.289087e-02 0.100050 3.290393e-02 0.100100 3.291697e-02 0.100150 3.292999e-02 0.100200 3.294300e-02 0.100250 3.295599e-02 0.100300 3.296896e-02 0.100350 3.298192e-02 0.100400 3.299487e-02 0.100450 3.300779e-02 0.100500 3.302070e-02 0.100550 3.303360e-02 0.100600 3.304648e-02 0.100650 3.305934e-02 0.100700 3.307218e-02 0.100750 3.308501e-02 0.100800 3.309782e-02 0.100850 3.311062e-02 0.100900 3.312340e-02 0.100950 3.313617e-02 0.101000 3.314892e-02 0.101050 3.316165e-02 0.101100 3.317437e-02 0.101150 3.318707e-02 0.101200 3.319976e-02 0.101250 3.321243e-02 0.101300 3.322508e-02 0.101350 3.323772e-02 0.101400 3.325035e-02 0.101450 3.326295e-02 0.101500 3.327554e-02 0.101550 3.328812e-02 0.101600 3.330068e-02 0.101650 3.331322e-02 0.101700 3.332575e-02 0.101750 3.333826e-02 0.101800 3.335076e-02 0.101850 3.336324e-02 0.101900 3.337571e-02 0.101950 3.338816e-02 0.102000 3.340059e-02 0.102050 3.341301e-02 0.102100 3.342542e-02 0.102150 3.343781e-02 0.102200 3.345018e-02 0.102250 3.346253e-02 0.102300 3.347488e-02 0.102350 3.348720e-02 0.102400 3.349952e-02 0.102450 3.351181e-02 0.102500 3.352409e-02 0.102550 3.353636e-02 0.102600 3.354861e-02 0.102650 3.356084e-02 0.102700 3.357306e-02 0.102750 3.358526e-02 0.102800 3.359745e-02 0.102850 3.360962e-02 0.102900 3.362178e-02 0.102950 3.363393e-02 0.103000 3.364605e-02 0.103050 3.365817e-02 0.103100 3.367026e-02 0.103150 3.368235e-02 0.103200 3.369441e-02 0.103250 3.370647e-02 0.103300 3.371850e-02 0.103350 3.373053e-02 0.103400 3.374253e-02 0.103450 3.375453e-02 0.103500 3.376650e-02 0.103550 3.377847e-02 0.103600 3.379041e-02 0.103650 3.380235e-02 0.103700 3.381426e-02 0.103750 3.382616e-02 0.103800 3.383805e-02 0.103850 3.384993e-02 0.103900 3.386178e-02 0.103950 3.387363e-02 0.104000 3.388546e-02 0.104050 3.389727e-02 0.104100 3.390907e-02 0.104150 3.392085e-02 0.104200 3.393262e-02 0.104250 3.394438e-02 0.104300 3.395612e-02 0.104350 3.396784e-02 0.104400 3.397955e-02 0.104450 3.399125e-02 0.104500 3.400293e-02 0.104550 3.401460e-02 0.104600 3.402625e-02 0.104650 3.403789e-02 0.104700 3.404951e-02 0.104750 3.406112e-02 0.104800 3.407271e-02 0.104850 3.408429e-02 0.104900 3.409586e-02 0.104950 3.410741e-02 0.105000 3.411894e-02 0.105050 3.413047e-02 0.105100 3.414197e-02 0.105150 3.415347e-02 0.105200 3.416495e-02 0.105250 3.417641e-02 0.105300 3.418786e-02 0.105350 3.419930e-02 0.105400 3.421072e-02 0.105450 3.422212e-02 0.105500 3.423352e-02 0.105550 3.424490e-02 0.105600 3.425626e-02 0.105650 3.426761e-02 0.105700 3.427895e-02 0.105750 3.429027e-02 0.105800 3.430158e-02 0.105850 3.431287e-02 0.105900 3.432415e-02 0.105950 3.433542e-02 0.106000 3.434667e-02 0.106050 3.435791e-02 0.106100 3.436913e-02 0.106150 3.438034e-02 0.106200 3.439153e-02 0.106250 3.440272e-02 0.106300 3.441388e-02 0.106350 3.442504e-02 0.106400 3.443618e-02 0.106450 3.444730e-02 0.106500 3.445841e-02 0.106550 3.446951e-02 0.106600 3.448060e-02 0.106650 3.449167e-02 0.106700 3.450272e-02 0.106750 3.451376e-02 0.106800 3.452479e-02 0.106850 3.453581e-02 0.106900 3.454681e-02 0.106950 3.455780e-02 0.107000 3.456877e-02 0.107050 3.457973e-02 0.107100 3.459068e-02 0.107150 3.460161e-02 0.107200 3.461253e-02 0.107250 3.462343e-02 0.107300 3.463433e-02 0.107350 3.464520e-02 0.107400 3.465607e-02 0.107450 3.466692e-02 0.107500 3.467776e-02 0.107550 3.468858e-02 0.107600 3.469939e-02 0.107650 3.471019e-02 0.107700 3.472097e-02 0.107750 3.473174e-02 0.107800 3.474250e-02 0.107850 3.475324e-02 0.107900 3.476397e-02 0.107950 3.477469e-02 0.108000 3.478539e-02 0.108050 3.479608e-02 0.108100 3.480675e-02 0.108150 3.481742e-02 0.108200 3.482807e-02 0.108250 3.483870e-02 0.108300 3.484932e-02 0.108350 3.485993e-02 0.108400 3.487053e-02 0.108450 3.488111e-02 0.108500 3.489168e-02 0.108550 3.490224e-02 0.108600 3.491278e-02 0.108650 3.492331e-02 0.108700 3.493383e-02 0.108750 3.494434e-02 0.108800 3.495483e-02 0.108850 3.496530e-02 0.108900 3.497577e-02 0.108950 3.498622e-02 0.109000 3.499666e-02 0.109050 3.500708e-02 0.109100 3.501750e-02 0.109150 3.502790e-02 0.109200 3.503828e-02 0.109250 3.504866e-02 0.109300 3.505902e-02 0.109350 3.506936e-02 0.109400 3.507970e-02 0.109450 3.509002e-02 0.109500 3.510033e-02 0.109550 3.511063e-02 0.109600 3.512091e-02 0.109650 3.513118e-02 0.109700 3.514144e-02 0.109750 3.515168e-02 0.109800 3.516191e-02 0.109850 3.517213e-02 0.109900 3.518234e-02 0.109950 3.519253e-02 0.110000 3.520271e-02 0.110050 3.521288e-02 0.110100 3.522304e-02 0.110150 3.523318e-02 0.110200 3.524331e-02 0.110250 3.525343e-02 0.110300 3.526353e-02 0.110350 3.527362e-02 0.110400 3.528370e-02 0.110450 3.529377e-02 0.110500 3.530382e-02 0.110550 3.531386e-02 0.110600 3.532389e-02 0.110650 3.533391e-02 0.110700 3.534392e-02 0.110750 3.535391e-02 0.110800 3.536389e-02 0.110850 3.537385e-02 0.110900 3.538381e-02 0.110950 3.539375e-02 0.111000 3.540368e-02 0.111050 3.541360e-02 0.111100 3.542350e-02 0.111150 3.543339e-02 0.111200 3.544327e-02 0.111250 3.545314e-02 0.111300 3.546300e-02 0.111350 3.547284e-02 0.111400 3.548267e-02 0.111450 3.549249e-02 0.111500 3.550229e-02 0.111550 3.551209e-02 0.111600 3.552187e-02 0.111650 3.553164e-02 0.111700 3.554140e-02 0.111750 3.555114e-02 0.111800 3.556087e-02 0.111850 3.557059e-02 0.111900 3.558030e-02 0.111950 3.559000e-02 0.112000 3.559968e-02 0.112050 3.560936e-02 0.112100 3.561901e-02 0.112150 3.562866e-02 0.112200 3.563830e-02 0.112250 3.564792e-02 0.112300 3.565754e-02 0.112350 3.566713e-02 0.112400 3.567672e-02 0.112450 3.568630e-02 0.112500 3.569586e-02 0.112550 3.570541e-02 0.112600 3.571495e-02 0.112650 3.572448e-02 0.112700 3.573400e-02 0.112750 3.574350e-02 0.112800 3.575300e-02 0.112850 3.576248e-02 0.112900 3.577194e-02 0.112950 3.578140e-02 0.113000 3.579085e-02 0.113050 3.580028e-02 0.113100 3.580970e-02 0.113150 3.581911e-02 0.113200 3.582851e-02 0.113250 3.583790e-02 0.113300 3.584727e-02 0.113350 3.585663e-02 0.113400 3.586599e-02 0.113450 3.587532e-02 0.113500 3.588465e-02 0.113550 3.589397e-02 0.113600 3.590327e-02 0.113650 3.591257e-02 0.113700 3.592185e-02 0.113750 3.593112e-02 0.113800 3.594038e-02 0.113850 3.594962e-02 0.113900 3.595886e-02 0.113950 3.596808e-02 0.114000 3.597729e-02 0.114050 3.598649e-02 0.114100 3.599568e-02 0.114150 3.600486e-02 0.114200 3.601403e-02 0.114250 3.602318e-02 0.114300 3.603232e-02 0.114350 3.604145e-02 0.114400 3.605058e-02 0.114450 3.605968e-02 0.114500 3.606878e-02 0.114550 3.607787e-02 0.114600 3.608694e-02 0.114650 3.609601e-02 0.114700 3.610506e-02 0.114750 3.611410e-02 0.114800 3.612313e-02 0.114850 3.613215e-02 0.114900 3.614115e-02 0.114950 3.615015e-02 0.115000 3.615913e-02 0.115050 3.616811e-02 0.115100 3.617707e-02 0.115150 3.618602e-02 0.115200 3.619496e-02 0.115250 3.620389e-02 0.115300 3.621281e-02 0.115350 3.622171e-02 0.115400 3.623061e-02 0.115450 3.623949e-02 0.115500 3.624836e-02 0.115550 3.625723e-02 0.115600 3.626608e-02 0.115650 3.627492e-02 0.115700 3.628375e-02 0.115750 3.629256e-02 0.115800 3.630137e-02 0.115850 3.631017e-02 0.115900 3.631895e-02 0.115950 3.632772e-02 0.116000 3.633649e-02 0.116050 3.634524e-02 0.116100 3.635398e-02 0.116150 3.636271e-02 0.116200 3.637143e-02 0.116250 3.638014e-02 0.116300 3.638883e-02 0.116350 3.639752e-02 0.116400 3.640620e-02 0.116450 3.641486e-02 0.116500 3.642351e-02 0.116550 3.643216e-02 0.116600 3.644079e-02 0.116650 3.644941e-02 0.116700 3.645802e-02 0.116750 3.646662e-02 0.116800 3.647521e-02 0.116850 3.648379e-02 0.116900 3.649236e-02 0.116950 3.650091e-02 0.117000 3.650946e-02 0.117050 3.651799e-02 0.117100 3.652652e-02 0.117150 3.653503e-02 0.117200 3.654354e-02 0.117250 3.655203e-02 0.117300 3.656051e-02 0.117350 3.656899e-02 0.117400 3.657745e-02 0.117450 3.658590e-02 0.117500 3.659434e-02 0.117550 3.660277e-02 0.117600 3.661119e-02 0.117650 3.661960e-02 0.117700 3.662799e-02 0.117750 3.663638e-02 0.117800 3.664476e-02 0.117850 3.665312e-02 0.117900 3.666148e-02 0.117950 3.666983e-02 0.118000 3.667816e-02 0.118050 3.668649e-02 0.118100 3.669480e-02 0.118150 3.670311e-02 0.118200 3.671140e-02 0.118250 3.671968e-02 0.118300 3.672796e-02 0.118350 3.673622e-02 0.118400 3.674447e-02 0.118450 3.675271e-02 0.118500 3.676094e-02 0.118550 3.676917e-02 0.118600 3.677738e-02 0.118650 3.678558e-02 0.118700 3.679377e-02 0.118750 3.680195e-02 0.118800 3.681012e-02 0.118850 3.681828e-02 0.118900 3.682643e-02 0.118950 3.683457e-02 0.119000 3.684270e-02 0.119050 3.685082e-02 0.119100 3.685893e-02 0.119150 3.686703e-02 0.119200 3.687512e-02 0.119250 3.688319e-02 0.119300 3.689126e-02 0.119350 3.689932e-02 0.119400 3.690737e-02 0.119450 3.691541e-02 0.119500 3.692344e-02 0.119550 3.693146e-02 0.119600 3.693947e-02 0.119650 3.694746e-02 0.119700 3.695545e-02 0.119750 3.696343e-02 0.119800 3.697140e-02 0.119850 3.697936e-02 0.119900 3.698731e-02 0.119950 3.699525e-02 0.120000 3.700317e-02 0.120050 3.701109e-02 0.120100 3.701900e-02 0.120150 3.702690e-02 0.120200 3.703479e-02 0.120250 3.704267e-02 0.120300 3.705054e-02 0.120350 3.705840e-02 0.120400 3.706625e-02 0.120450 3.707409e-02 0.120500 3.708192e-02 0.120550 3.708974e-02 0.120600 3.709755e-02 0.120650 3.710535e-02 0.120700 3.711314e-02 0.120750 3.712092e-02 0.120800 3.712870e-02 0.120850 3.713646e-02 0.120900 3.714421e-02 0.120950 3.715195e-02 0.121000 3.715969e-02 0.121050 3.716741e-02 0.121100 3.717512e-02 0.121150 3.718283e-02 0.121200 3.719052e-02 0.121250 3.719821e-02 0.121300 3.720588e-02 0.121350 3.721355e-02 0.121400 3.722120e-02 0.121450 3.722885e-02 0.121500 3.723649e-02 0.121550 3.724412e-02 0.121600 3.725173e-02 0.121650 3.725934e-02 0.121700 3.726694e-02 0.121750 3.727453e-02 0.121800 3.728211e-02 0.121850 3.728968e-02 0.121900 3.729724e-02 0.121950 3.730479e-02 0.122000 3.731234e-02 0.122050 3.731987e-02 0.122100 3.732739e-02 0.122150 3.733490e-02 0.122200 3.734241e-02 0.122250 3.734991e-02 0.122300 3.735739e-02 0.122350 3.736487e-02 0.122400 3.737233e-02 0.122450 3.737979e-02 0.122500 3.738724e-02 0.122550 3.739468e-02 0.122600 3.740211e-02 0.122650 3.740953e-02 0.122700 3.741694e-02 0.122750 3.742434e-02 0.122800 3.743174e-02 0.122850 3.743912e-02 0.122900 3.744649e-02 0.122950 3.745386e-02 0.123000 3.746121e-02 0.123050 3.746856e-02 0.123100 3.747590e-02 0.123150 3.748323e-02 0.123200 3.749055e-02 0.123250 3.749786e-02 0.123300 3.750516e-02 0.123350 3.751245e-02 0.123400 3.751973e-02 0.123450 3.752701e-02 0.123500 3.753427e-02 0.123550 3.754153e-02 0.123600 3.754877e-02 0.123650 3.755601e-02 0.123700 3.756324e-02 0.123750 3.757046e-02 0.123800 3.757767e-02 0.123850 3.758487e-02 0.123900 3.759206e-02 0.123950 3.759924e-02 0.124000 3.760642e-02 0.124050 3.761358e-02 0.124100 3.762074e-02 0.124150 3.762789e-02 0.124200 3.763503e-02 0.124250 3.764216e-02 0.124300 3.764928e-02 0.124350 3.765639e-02 0.124400 3.766349e-02 0.124450 3.767058e-02 0.124500 3.767767e-02 0.124550 3.768475e-02 0.124600 3.769181e-02 0.124650 3.769887e-02 0.124700 3.770592e-02 0.124750 3.771296e-02 0.124800 3.772000e-02 0.124850 3.772702e-02 0.124900 3.773403e-02 0.124950 3.774104e-02 0.125000 3.774804e-02 0.125050 3.775502e-02 0.125100 3.776200e-02 0.125150 3.776898e-02 0.125200 3.777594e-02 0.125250 3.778289e-02 0.125300 3.778984e-02 0.125350 3.779677e-02 0.125400 3.780370e-02 0.125450 3.781062e-02 0.125500 3.781753e-02 0.125550 3.782443e-02 0.125600 3.783132e-02 0.125650 3.783821e-02 0.125700 3.784508e-02 0.125750 3.785195e-02 0.125800 3.785881e-02 0.125850 3.786566e-02 0.125900 3.787250e-02 0.125950 3.787933e-02 0.126000 3.788616e-02 0.126050 3.789297e-02 0.126100 3.789978e-02 0.126150 3.790658e-02 0.126200 3.791337e-02 0.126250 3.792015e-02 0.126300 3.792693e-02 0.126350 3.793369e-02 0.126400 3.794045e-02 0.126450 3.794720e-02 0.126500 3.795393e-02 0.126550 3.796067e-02 0.126600 3.796739e-02 0.126650 3.797410e-02 0.126700 3.798081e-02 0.126750 3.798751e-02 0.126800 3.799420e-02 0.126850 3.800088e-02 0.126900 3.800755e-02 0.126950 3.801421e-02 0.127000 3.802087e-02 0.127050 3.802752e-02 0.127100 3.803416e-02 0.127150 3.804079e-02 0.127200 3.804741e-02 0.127250 3.805403e-02 0.127300 3.806063e-02 0.127350 3.806723e-02 0.127400 3.807382e-02 0.127450 3.808040e-02 0.127500 3.808697e-02 0.127550 3.809354e-02 0.127600 3.810010e-02 0.127650 3.810664e-02 0.127700 3.811318e-02 0.127750 3.811972e-02 0.127800 3.812624e-02 0.127850 3.813276e-02 0.127900 3.813926e-02 0.127950 3.814576e-02 0.128000 3.815226e-02 0.128050 3.815874e-02 0.128100 3.816521e-02 0.128150 3.817168e-02 0.128200 3.817814e-02 0.128250 3.818459e-02 0.128300 3.819104e-02 0.128350 3.819747e-02 0.128400 3.820390e-02 0.128450 3.821032e-02 0.128500 3.821673e-02 0.128550 3.822313e-02 0.128600 3.822952e-02 0.128650 3.823591e-02 0.128700 3.824229e-02 0.128750 3.824866e-02 0.128800 3.825502e-02 0.128850 3.826138e-02 0.128900 3.826773e-02 0.128950 3.827407e-02 0.129000 3.828040e-02 0.129050 3.828672e-02 0.129100 3.829304e-02 0.129150 3.829934e-02 0.129200 3.830564e-02 0.129250 3.831193e-02 0.129300 3.831822e-02 0.129350 3.832450e-02 0.129400 3.833076e-02 0.129450 3.833702e-02 0.129500 3.834328e-02 0.129550 3.834952e-02 0.129600 3.835576e-02 0.129650 3.836199e-02 0.129700 3.836821e-02 0.129750 3.837442e-02 0.129800 3.838063e-02 0.129850 3.838683e-02 0.129900 3.839302e-02 0.129950 3.839920e-02 0.130000 3.840537e-02 0.130050 3.841154e-02 0.130100 3.841770e-02 0.130150 3.842385e-02 0.130200 3.843000e-02 0.130250 3.843613e-02 0.130300 3.844226e-02 0.130350 3.844838e-02 0.130400 3.845450e-02 0.130450 3.846060e-02 0.130500 3.846670e-02 0.130550 3.847279e-02 0.130600 3.847888e-02 0.130650 3.848495e-02 0.130700 3.849102e-02 0.130750 3.849708e-02 0.130800 3.850313e-02 0.130850 3.850918e-02 0.130900 3.851521e-02 0.130950 3.852125e-02 0.131000 3.852727e-02 0.131050 3.853328e-02 0.131100 3.853929e-02 0.131150 3.854529e-02 0.131200 3.855128e-02 0.131250 3.855727e-02 0.131300 3.856325e-02 0.131350 3.856921e-02 0.131400 3.857518e-02 0.131450 3.858113e-02 0.131500 3.858708e-02 0.131550 3.859302e-02 0.131600 3.859895e-02 0.131650 3.860488e-02 0.131700 3.861080e-02 0.131750 3.861671e-02 0.131800 3.862261e-02 0.131850 3.862851e-02 0.131900 3.863440e-02 0.131950 3.864028e-02 0.132000 3.864615e-02 0.132050 3.865202e-02 0.132100 3.865788e-02 0.132150 3.866373e-02 0.132200 3.866957e-02 0.132250 3.867541e-02 0.132300 3.868124e-02 0.132350 3.868706e-02 0.132400 3.869288e-02 0.132450 3.869869e-02 0.132500 3.870449e-02 0.132550 3.871028e-02 0.132600 3.871607e-02 0.132650 3.872185e-02 0.132700 3.872762e-02 0.132750 3.873338e-02 0.132800 3.873914e-02 0.132850 3.874489e-02 0.132900 3.875063e-02 0.132950 3.875637e-02 0.133000 3.876210e-02 0.133050 3.876782e-02 0.133100 3.877353e-02 0.133150 3.877924e-02 0.133200 3.878494e-02 0.133250 3.879063e-02 0.133300 3.879632e-02 0.133350 3.880200e-02 0.133400 3.880767e-02 0.133450 3.881334e-02 0.133500 3.881899e-02 0.133550 3.882464e-02 0.133600 3.883029e-02 0.133650 3.883592e-02 0.133700 3.884155e-02 0.133750 3.884717e-02 0.133800 3.885279e-02 0.133850 3.885840e-02 0.133900 3.886400e-02 0.133950 3.886959e-02 0.134000 3.887518e-02 0.134050 3.888076e-02 0.134100 3.888634e-02 0.134150 3.889190e-02 0.134200 3.889746e-02 0.134250 3.890301e-02 0.134300 3.890856e-02 0.134350 3.891410e-02 0.134400 3.891963e-02 0.134450 3.892516e-02 0.134500 3.893067e-02 0.134550 3.893618e-02 0.134600 3.894169e-02 0.134650 3.894718e-02 0.134700 3.895267e-02 0.134750 3.895816e-02 0.134800 3.896364e-02 0.134850 3.896911e-02 0.134900 3.897457e-02 0.134950 3.898002e-02 0.135000 3.898547e-02 0.135050 3.899092e-02 0.135100 3.899635e-02 0.135150 3.900178e-02 0.135200 3.900720e-02 0.135250 3.901262e-02 0.135300 3.901803e-02 0.135350 3.902343e-02 0.135400 3.902883e-02 0.135450 3.903421e-02 0.135500 3.903960e-02 0.135550 3.904497e-02 0.135600 3.905034e-02 0.135650 3.905570e-02 0.135700 3.906105e-02 0.135750 3.906640e-02 0.135800 3.907174e-02 0.135850 3.907708e-02 0.135900 3.908241e-02 0.135950 3.908773e-02 0.136000 3.909304e-02 0.136050 3.909835e-02 0.136100 3.910365e-02 0.136150 3.910895e-02 0.136200 3.911424e-02 0.136250 3.911952e-02 0.136300 3.912479e-02 0.136350 3.913006e-02 0.136400 3.913532e-02 0.136450 3.914058e-02 0.136500 3.914583e-02 0.136550 3.915107e-02 0.136600 3.915631e-02 0.136650 3.916154e-02 0.136700 3.916676e-02 0.136750 3.917197e-02 0.136800 3.917718e-02 0.136850 3.918239e-02 0.136900 3.918759e-02 0.136950 3.919277e-02 0.137000 3.919796e-02 0.137050 3.920314e-02 0.137100 3.920831e-02 0.137150 3.921347e-02 0.137200 3.921863e-02 0.137250 3.922378e-02 0.137300 3.922892e-02 0.137350 3.923406e-02 0.137400 3.923919e-02 0.137450 3.924432e-02 0.137500 3.924944e-02 0.137550 3.925455e-02 0.137600 3.925966e-02 0.137650 3.926476e-02 0.137700 3.926985e-02 0.137750 3.927494e-02 0.137800 3.928002e-02 0.137850 3.928509e-02 0.137900 3.929016e-02 0.137950 3.929522e-02 0.138000 3.930028e-02 0.138050 3.930533e-02 0.138100 3.931037e-02 0.138150 3.931541e-02 0.138200 3.932044e-02 0.138250 3.932546e-02 0.138300 3.933048e-02 0.138350 3.933549e-02 0.138400 3.934050e-02 0.138450 3.934550e-02 0.138500 3.935049e-02 0.138550 3.935548e-02 0.138600 3.936046e-02 0.138650 3.936543e-02 0.138700 3.937040e-02 0.138750 3.937536e-02 0.138800 3.938032e-02 0.138850 3.938527e-02 0.138900 3.939021e-02 0.138950 3.939515e-02 0.139000 3.940008e-02 0.139050 3.940500e-02 0.139100 3.940992e-02 0.139150 3.941483e-02 0.139200 3.941974e-02 0.139250 3.942464e-02 0.139300 3.942953e-02 0.139350 3.943442e-02 0.139400 3.943930e-02 0.139450 3.944418e-02 0.139500 3.944905e-02 0.139550 3.945391e-02 0.139600 3.945877e-02 0.139650 3.946362e-02 0.139700 3.946847e-02 0.139750 3.947331e-02 0.139800 3.947814e-02 0.139850 3.948297e-02 0.139900 3.948779e-02 0.139950 3.949260e-02 0.140000 3.949741e-02 0.140050 3.950221e-02 0.140100 3.950701e-02 0.140150 3.951180e-02 0.140200 3.951659e-02 0.140250 3.952137e-02 0.140300 3.952614e-02 0.140350 3.953091e-02 0.140400 3.953567e-02 0.140450 3.954042e-02 0.140500 3.954517e-02 0.140550 3.954992e-02 0.140600 3.955465e-02 0.140650 3.955938e-02 0.140700 3.956411e-02 0.140750 3.956883e-02 0.140800 3.957354e-02 0.140850 3.957825e-02 0.140900 3.958295e-02 0.140950 3.958765e-02 0.141000 3.959234e-02 0.141050 3.959702e-02 0.141100 3.960170e-02 0.141150 3.960638e-02 0.141200 3.961104e-02 0.141250 3.961570e-02 0.141300 3.962036e-02 0.141350 3.962501e-02 0.141400 3.962965e-02 0.141450 3.963429e-02 0.141500 3.963892e-02 0.141550 3.964355e-02 0.141600 3.964817e-02 0.141650 3.965278e-02 0.141700 3.965739e-02 0.141750 3.966200e-02 0.141800 3.966659e-02 0.141850 3.967119e-02 0.141900 3.967577e-02 0.141950 3.968035e-02 0.142000 3.968493e-02 0.142050 3.968950e-02 0.142100 3.969406e-02 0.142150 3.969862e-02 0.142200 3.970317e-02 0.142250 3.970771e-02 0.142300 3.971225e-02 0.142350 3.971679e-02 0.142400 3.972132e-02 0.142450 3.972584e-02 0.142500 3.973036e-02 0.142550 3.973487e-02 0.142600 3.973938e-02 0.142650 3.974388e-02 0.142700 3.974837e-02 0.142750 3.975286e-02 0.142800 3.975735e-02 0.142850 3.976182e-02 0.142900 3.976630e-02 0.142950 3.977076e-02 0.143000 3.977523e-02 0.143050 3.977968e-02 0.143100 3.978413e-02 0.143150 3.978858e-02 0.143200 3.979302e-02 0.143250 3.979745e-02 0.143300 3.980188e-02 0.143350 3.980630e-02 0.143400 3.981072e-02 0.143450 3.981513e-02 0.143500 3.981954e-02 0.143550 3.982394e-02 0.143600 3.982833e-02 0.143650 3.983272e-02 0.143700 3.983711e-02 0.143750 3.984148e-02 0.143800 3.984586e-02 0.143850 3.985023e-02 0.143900 3.985459e-02 0.143950 3.985895e-02 0.144000 3.986330e-02 0.144050 3.986764e-02 0.144100 3.987198e-02 0.144150 3.987632e-02 0.144200 3.988065e-02 0.144250 3.988497e-02 0.144300 3.988929e-02 0.144350 3.989361e-02 0.144400 3.989791e-02 0.144450 3.990222e-02 0.144500 3.990651e-02 0.144550 3.991081e-02 0.144600 3.991509e-02 0.144650 3.991937e-02 0.144700 3.992365e-02 0.144750 3.992792e-02 0.144800 3.993218e-02 0.144850 3.993644e-02 0.144900 3.994070e-02 0.144950 3.994495e-02 0.145000 3.994919e-02 0.145050 3.995343e-02 0.145100 3.995766e-02 0.145150 3.996189e-02 0.145200 3.996612e-02 0.145250 3.997033e-02 0.145300 3.997454e-02 0.145350 3.997875e-02 0.145400 3.998295e-02 0.145450 3.998715e-02 0.145500 3.999134e-02 0.145550 3.999553e-02 0.145600 3.999971e-02 0.145650 4.000388e-02 0.145700 4.000805e-02 0.145750 4.001222e-02 0.145800 4.001638e-02 0.145850 4.002053e-02 0.145900 4.002468e-02 0.145950 4.002883e-02 0.146000 4.003297e-02 0.146050 4.003710e-02 0.146100 4.004123e-02 0.146150 4.004535e-02 0.146200 4.004947e-02 0.146250 4.005359e-02 0.146300 4.005769e-02 0.146350 4.006180e-02 0.146400 4.006590e-02 0.146450 4.006999e-02 0.146500 4.007408e-02 0.146550 4.007816e-02 0.146600 4.008224e-02 0.146650 4.008631e-02 0.146700 4.009038e-02 0.146750 4.009444e-02 0.146800 4.009850e-02 0.146850 4.010255e-02 0.146900 4.010660e-02 0.146950 4.011064e-02 0.147000 4.011467e-02 0.147050 4.011871e-02 0.147100 4.012273e-02 0.147150 4.012676e-02 0.147200 4.013077e-02 0.147250 4.013478e-02 0.147300 4.013879e-02 0.147350 4.014279e-02 0.147400 4.014679e-02 0.147450 4.015078e-02 0.147500 4.015477e-02 0.147550 4.015875e-02 0.147600 4.016273e-02 0.147650 4.016670e-02 0.147700 4.017066e-02 0.147750 4.017463e-02 0.147800 4.017858e-02 0.147850 4.018254e-02 0.147900 4.018649e-02 0.147950 4.019043e-02 0.148000 4.019436e-02 0.148050 4.019830e-02 0.148100 4.020222e-02 0.148150 4.020615e-02 0.148200 4.021006e-02 0.148250 4.021398e-02 0.148300 4.021788e-02 0.148350 4.022179e-02 0.148400 4.022569e-02 0.148450 4.022958e-02 0.148500 4.023347e-02 0.148550 4.023735e-02 0.148600 4.024123e-02 0.148650 4.024510e-02 0.148700 4.024897e-02 0.148750 4.025284e-02 0.148800 4.025670e-02 0.148850 4.026055e-02 0.148900 4.026440e-02 0.148950 4.026824e-02 0.149000 4.027209e-02 0.149050 4.027592e-02 0.149100 4.027975e-02 0.149150 4.028358e-02 0.149200 4.028740e-02 0.149250 4.029121e-02 0.149300 4.029503e-02 0.149350 4.029883e-02 0.149400 4.030263e-02 0.149450 4.030643e-02 0.149500 4.031022e-02 0.149550 4.031401e-02 0.149600 4.031779e-02 0.149650 4.032157e-02 0.149700 4.032535e-02 0.149750 4.032911e-02 0.149800 4.033288e-02 0.149850 4.033664e-02 0.149900 4.034039e-02 0.149950 4.034414e-02 0.150000 4.034789e-02 0.150050 4.035163e-02 0.150100 4.035536e-02 0.150150 4.035910e-02 0.150200 4.036282e-02 0.150250 4.036655e-02 0.150300 4.037026e-02 0.150350 4.037397e-02 0.150400 4.037768e-02 0.150450 4.038139e-02 0.150500 4.038508e-02 0.150550 4.038878e-02 0.150600 4.039247e-02 0.150650 4.039615e-02 0.150700 4.039983e-02 0.150750 4.040351e-02 0.150800 4.040718e-02 0.150850 4.041085e-02 0.150900 4.041451e-02 0.150950 4.041817e-02 0.151000 4.042182e-02 0.151050 4.042547e-02 0.151100 4.042911e-02 0.151150 4.043275e-02 0.151200 4.043639e-02 0.151250 4.044002e-02 0.151300 4.044364e-02 0.151350 4.044726e-02 0.151400 4.045088e-02 0.151450 4.045449e-02 0.151500 4.045810e-02 0.151550 4.046170e-02 0.151600 4.046530e-02 0.151650 4.046889e-02 0.151700 4.047248e-02 0.151750 4.047607e-02 0.151800 4.047965e-02 0.151850 4.048322e-02 0.151900 4.048680e-02 0.151950 4.049036e-02 0.152000 4.049393e-02 0.152050 4.049748e-02 0.152100 4.050104e-02 0.152150 4.050459e-02 0.152200 4.050813e-02 0.152250 4.051167e-02 0.152300 4.051521e-02 0.152350 4.051874e-02 0.152400 4.052227e-02 0.152450 4.052579e-02 0.152500 4.052931e-02 0.152550 4.053282e-02 0.152600 4.053633e-02 0.152650 4.053984e-02 0.152700 4.054334e-02 0.152750 4.054683e-02 0.152800 4.055033e-02 0.152850 4.055381e-02 0.152900 4.055730e-02 0.152950 4.056078e-02 0.153000 4.056425e-02 0.153050 4.056772e-02 0.153100 4.057119e-02 0.153150 4.057465e-02 0.153200 4.057811e-02 0.153250 4.058156e-02 0.153300 4.058501e-02 0.153350 4.058845e-02 0.153400 4.059189e-02 0.153450 4.059533e-02 0.153500 4.059876e-02 0.153550 4.060219e-02 0.153600 4.060561e-02 0.153650 4.060903e-02 0.153700 4.061244e-02 0.153750 4.061585e-02 0.153800 4.061926e-02 0.153850 4.062266e-02 0.153900 4.062606e-02 0.153950 4.062945e-02 0.154000 4.063284e-02 0.154050 4.063623e-02 0.154100 4.063961e-02 0.154150 4.064298e-02 0.154200 4.064635e-02 0.154250 4.064972e-02 0.154300 4.065309e-02 0.154350 4.065644e-02 0.154400 4.065980e-02 0.154450 4.066315e-02 0.154500 4.066650e-02 0.154550 4.066984e-02 0.154600 4.067318e-02 0.154650 4.067651e-02 0.154700 4.067984e-02 0.154750 4.068317e-02 0.154800 4.068649e-02 0.154850 4.068981e-02 0.154900 4.069312e-02 0.154950 4.069643e-02 0.155000 4.069974e-02 0.155050 4.070304e-02 0.155100 4.070633e-02 0.155150 4.070963e-02 0.155200 4.071292e-02 0.155250 4.071620e-02 0.155300 4.071948e-02 0.155350 4.072276e-02 0.155400 4.072603e-02 0.155450 4.072930e-02 0.155500 4.073256e-02 0.155550 4.073582e-02 0.155600 4.073908e-02 0.155650 4.074233e-02 0.155700 4.074558e-02 0.155750 4.074882e-02 0.155800 4.075206e-02 0.155850 4.075530e-02 0.155900 4.075853e-02 0.155950 4.076176e-02 0.156000 4.076498e-02 0.156050 4.076820e-02 0.156100 4.077142e-02 0.156150 4.077463e-02 0.156200 4.077784e-02 0.156250 4.078104e-02 0.156300 4.078424e-02 0.156350 4.078743e-02 0.156400 4.079063e-02 0.156450 4.079381e-02 0.156500 4.079700e-02 0.156550 4.080018e-02 0.156600 4.080335e-02 0.156650 4.080652e-02 0.156700 4.080969e-02 0.156750 4.081285e-02 0.156800 4.081601e-02 0.156850 4.081917e-02 0.156900 4.082232e-02 0.156950 4.082547e-02 0.157000 4.082861e-02 0.157050 4.083175e-02 0.157100 4.083489e-02 0.157150 4.083802e-02 0.157200 4.084115e-02 0.157250 4.084428e-02 0.157300 4.084740e-02 0.157350 4.085051e-02 0.157400 4.085362e-02 0.157450 4.085673e-02 0.157500 4.085984e-02 0.157550 4.086294e-02 0.157600 4.086604e-02 0.157650 4.086913e-02 0.157700 4.087222e-02 0.157750 4.087531e-02 0.157800 4.087839e-02 0.157850 4.088147e-02 0.157900 4.088454e-02 0.157950 4.088761e-02 0.158000 4.089068e-02 0.158050 4.089374e-02 0.158100 4.089680e-02 0.158150 4.089985e-02 0.158200 4.090290e-02 0.158250 4.090595e-02 0.158300 4.090899e-02 0.158350 4.091203e-02 0.158400 4.091507e-02 0.158450 4.091810e-02 0.158500 4.092113e-02 0.158550 4.092415e-02 0.158600 4.092718e-02 0.158650 4.093019e-02 0.158700 4.093321e-02 0.158750 4.093621e-02 0.158800 4.093922e-02 0.158850 4.094222e-02 0.158900 4.094522e-02 0.158950 4.094822e-02 0.159000 4.095121e-02 0.159050 4.095419e-02 0.159100 4.095718e-02 0.159150 4.096016e-02 0.159200 4.096313e-02 0.159250 4.096610e-02 0.159300 4.096907e-02 0.159350 4.097204e-02 0.159400 4.097500e-02 0.159450 4.097795e-02 0.159500 4.098091e-02 0.159550 4.098386e-02 0.159600 4.098680e-02 0.159650 4.098975e-02 0.159700 4.099269e-02 0.159750 4.099562e-02 0.159800 4.099855e-02 0.159850 4.100148e-02 0.159900 4.100440e-02 0.159950 4.100732e-02 0.160000 4.101024e-02 0.160050 4.101315e-02 0.160100 4.101606e-02 0.160150 4.101897e-02 0.160200 4.102187e-02 0.160250 4.102477e-02 0.160300 4.102767e-02 0.160350 4.103056e-02 0.160400 4.103345e-02 0.160450 4.103633e-02 0.160500 4.103921e-02 0.160550 4.104209e-02 0.160600 4.104496e-02 0.160650 4.104783e-02 0.160700 4.105070e-02 0.160750 4.105356e-02 0.160800 4.105642e-02 0.160850 4.105927e-02 0.160900 4.106213e-02 0.160950 4.106497e-02 0.161000 4.106782e-02 0.161050 4.107066e-02 0.161100 4.107350e-02 0.161150 4.107633e-02 0.161200 4.107916e-02 0.161250 4.108199e-02 0.161300 4.108481e-02 0.161350 4.108763e-02 0.161400 4.109045e-02 0.161450 4.109326e-02 0.161500 4.109607e-02 0.161550 4.109888e-02 0.161600 4.110168e-02 0.161650 4.110448e-02 0.161700 4.110728e-02 0.161750 4.111007e-02 0.161800 4.111286e-02 0.161850 4.111564e-02 0.161900 4.111842e-02 0.161950 4.112120e-02 0.162000 4.112398e-02 0.162050 4.112675e-02 0.162100 4.112951e-02 0.162150 4.113228e-02 0.162200 4.113504e-02 0.162250 4.113780e-02 0.162300 4.114055e-02 0.162350 4.114330e-02 0.162400 4.114605e-02 0.162450 4.114879e-02 0.162500 4.115153e-02 0.162550 4.115427e-02 0.162600 4.115700e-02 0.162650 4.115973e-02 0.162700 4.116246e-02 0.162750 4.116518e-02 0.162800 4.116790e-02 0.162850 4.117062e-02 0.162900 4.117333e-02 0.162950 4.117604e-02 0.163000 4.117874e-02 0.163050 4.118145e-02 0.163100 4.118415e-02 0.163150 4.118684e-02 0.163200 4.118954e-02 0.163250 4.119222e-02 0.163300 4.119491e-02 0.163350 4.119759e-02 0.163400 4.120027e-02 0.163450 4.120295e-02 0.163500 4.120562e-02 0.163550 4.120829e-02 0.163600 4.121096e-02 0.163650 4.121362e-02 0.163700 4.121628e-02 0.163750 4.121893e-02 0.163800 4.122159e-02 0.163850 4.122423e-02 0.163900 4.122688e-02 0.163950 4.122952e-02 0.164000 4.123216e-02 0.164050 4.123480e-02 0.164100 4.123743e-02 0.164150 4.124006e-02 0.164200 4.124269e-02 0.164250 4.124531e-02 0.164300 4.124793e-02 0.164350 4.125055e-02 0.164400 4.125316e-02 0.164450 4.125577e-02 0.164500 4.125837e-02 0.164550 4.126098e-02 0.164600 4.126358e-02 0.164650 4.126617e-02 0.164700 4.126877e-02 0.164750 4.127136e-02 0.164800 4.127394e-02 0.164850 4.127653e-02 0.164900 4.127911e-02 0.164950 4.128169e-02 0.165000 4.128426e-02 0.165050 4.128683e-02 0.165100 4.128940e-02 0.165150 4.129196e-02 0.165200 4.129453e-02 0.165250 4.129708e-02 0.165300 4.129964e-02 0.165350 4.130219e-02 0.165400 4.130474e-02 0.165450 4.130728e-02 0.165500 4.130983e-02 0.165550 4.131236e-02 0.165600 4.131490e-02 0.165650 4.131743e-02 0.165700 4.131996e-02 0.165750 4.132249e-02 0.165800 4.132501e-02 0.165850 4.132753e-02 0.165900 4.133005e-02 0.165950 4.133256e-02 0.166000 4.133507e-02 0.166050 4.133758e-02 0.166100 4.134008e-02 0.166150 4.134259e-02 0.166200 4.134508e-02 0.166250 4.134758e-02 0.166300 4.135007e-02 0.166350 4.135256e-02 0.166400 4.135504e-02 0.166450 4.135753e-02 0.166500 4.136001e-02 0.166550 4.136248e-02 0.166600 4.136496e-02 0.166650 4.136743e-02 0.166700 4.136989e-02 0.166750 4.137236e-02 0.166800 4.137482e-02 0.166850 4.137728e-02 0.166900 4.137973e-02 0.166950 4.138218e-02 0.167000 4.138463e-02 0.167050 4.138708e-02 0.167100 4.138952e-02 0.167150 4.139196e-02 0.167200 4.139439e-02 0.167250 4.139683e-02 0.167300 4.139926e-02 0.167350 4.140169e-02 0.167400 4.140411e-02 0.167450 4.140653e-02 0.167500 4.140895e-02 0.167550 4.141136e-02 0.167600 4.141378e-02 0.167650 4.141619e-02 0.167700 4.141859e-02 0.167750 4.142099e-02 0.167800 4.142339e-02 0.167850 4.142579e-02 0.167900 4.142819e-02 0.167950 4.143058e-02 0.168000 4.143296e-02 0.168050 4.143535e-02 0.168100 4.143773e-02 0.168150 4.144011e-02 0.168200 4.144249e-02 0.168250 4.144486e-02 0.168300 4.144723e-02 0.168350 4.144960e-02 0.168400 4.145196e-02 0.168450 4.145432e-02 0.168500 4.145668e-02 0.168550 4.145904e-02 0.168600 4.146139e-02 0.168650 4.146374e-02 0.168700 4.146609e-02 0.168750 4.146843e-02 0.168800 4.147077e-02 0.168850 4.147311e-02 0.168900 4.147544e-02 0.168950 4.147778e-02 0.169000 4.148011e-02 0.169050 4.148243e-02 0.169100 4.148476e-02 0.169150 4.148708e-02 0.169200 4.148939e-02 0.169250 4.149171e-02 0.169300 4.149402e-02 0.169350 4.149633e-02 0.169400 4.149863e-02 0.169450 4.150094e-02 0.169500 4.150324e-02 0.169550 4.150553e-02 0.169600 4.150783e-02 0.169650 4.151012e-02 0.169700 4.151241e-02 0.169750 4.151470e-02 0.169800 4.151698e-02 0.169850 4.151926e-02 0.169900 4.152154e-02 0.169950 4.152381e-02 0.170000 4.152608e-02 0.170050 4.152835e-02 0.170100 4.153062e-02 0.170150 4.153288e-02 0.170200 4.153514e-02 0.170250 4.153740e-02 0.170300 4.153965e-02 0.170350 4.154190e-02 0.170400 4.154415e-02 0.170450 4.154640e-02 0.170500 4.154864e-02 0.170550 4.155088e-02 0.170600 4.155312e-02 0.170650 4.155536e-02 0.170700 4.155759e-02 0.170750 4.155982e-02 0.170800 4.156204e-02 0.170850 4.156427e-02 0.170900 4.156649e-02 0.170950 4.156871e-02 0.171000 4.157092e-02 0.171050 4.157314e-02 0.171100 4.157535e-02 0.171150 4.157755e-02 0.171200 4.157976e-02 0.171250 4.158196e-02 0.171300 4.158416e-02 0.171350 4.158636e-02 0.171400 4.158855e-02 0.171450 4.159074e-02 0.171500 4.159293e-02 0.171550 4.159511e-02 0.171600 4.159730e-02 0.171650 4.159948e-02 0.171700 4.160165e-02 0.171750 4.160383e-02 0.171800 4.160600e-02 0.171850 4.160817e-02 0.171900 4.161033e-02 0.171950 4.161250e-02 0.172000 4.161466e-02 0.172050 4.161682e-02 0.172100 4.161897e-02 0.172150 4.162112e-02 0.172200 4.162327e-02 0.172250 4.162542e-02 0.172300 4.162757e-02 0.172350 4.162971e-02 0.172400 4.163185e-02 0.172450 4.163398e-02 0.172500 4.163612e-02 0.172550 4.163825e-02 0.172600 4.164038e-02 0.172650 4.164250e-02 0.172700 4.164463e-02 0.172750 4.164675e-02 0.172800 4.164887e-02 0.172850 4.165098e-02 0.172900 4.165310e-02 0.172950 4.165521e-02 0.173000 4.165731e-02 0.173050 4.165942e-02 0.173100 4.166152e-02 0.173150 4.166362e-02 0.173200 4.166572e-02 0.173250 4.166781e-02 0.173300 4.166990e-02 0.173350 4.167199e-02 0.173400 4.167408e-02 0.173450 4.167616e-02 0.173500 4.167824e-02 0.173550 4.168032e-02 0.173600 4.168240e-02 0.173650 4.168447e-02 0.173700 4.168654e-02 0.173750 4.168861e-02 0.173800 4.169068e-02 0.173850 4.169274e-02 0.173900 4.169480e-02 0.173950 4.169686e-02 0.174000 4.169891e-02 0.174050 4.170097e-02 0.174100 4.170302e-02 0.174150 4.170507e-02 0.174200 4.170711e-02 0.174250 4.170915e-02 0.174300 4.171119e-02 0.174350 4.171323e-02 0.174400 4.171527e-02 0.174450 4.171730e-02 0.174500 4.171933e-02 0.174550 4.172136e-02 0.174600 4.172338e-02 0.174650 4.172540e-02 0.174700 4.172742e-02 0.174750 4.172944e-02 0.174800 4.173145e-02 0.174850 4.173347e-02 0.174900 4.173548e-02 0.174950 4.173748e-02 0.175000 4.173949e-02 0.175050 4.174149e-02 0.175100 4.174349e-02 0.175150 4.174549e-02 0.175200 4.174748e-02 0.175250 4.174948e-02 0.175300 4.175146e-02 0.175350 4.175345e-02 0.175400 4.175544e-02 0.175450 4.175742e-02 0.175500 4.175940e-02 0.175550 4.176138e-02 0.175600 4.176335e-02 0.175650 4.176532e-02 0.175700 4.176729e-02 0.175750 4.176926e-02 0.175800 4.177123e-02 0.175850 4.177319e-02 0.175900 4.177515e-02 0.175950 4.177711e-02 0.176000 4.177906e-02 0.176050 4.178101e-02 0.176100 4.178296e-02 0.176150 4.178491e-02 0.176200 4.178686e-02 0.176250 4.178880e-02 0.176300 4.179074e-02 0.176350 4.179268e-02 0.176400 4.179462e-02 0.176450 4.179655e-02 0.176500 4.179848e-02 0.176550 4.180041e-02 0.176600 4.180233e-02 0.176650 4.180426e-02 0.176700 4.180618e-02 0.176750 4.180810e-02 0.176800 4.181001e-02 0.176850 4.181193e-02 0.176900 4.181384e-02 0.176950 4.181575e-02 0.177000 4.181766e-02 0.177050 4.181956e-02 0.177100 4.182146e-02 0.177150 4.182336e-02 0.177200 4.182526e-02 0.177250 4.182716e-02 0.177300 4.182905e-02 0.177350 4.183094e-02 0.177400 4.183283e-02 0.177450 4.183471e-02 0.177500 4.183660e-02 0.177550 4.183848e-02 0.177600 4.184036e-02 0.177650 4.184223e-02 0.177700 4.184411e-02 0.177750 4.184598e-02 0.177800 4.184785e-02 0.177850 4.184971e-02 0.177900 4.185158e-02 0.177950 4.185344e-02 0.178000 4.185530e-02 0.178050 4.185716e-02 0.178100 4.185901e-02 0.178150 4.186087e-02 0.178200 4.186272e-02 0.178250 4.186456e-02 0.178300 4.186641e-02 0.178350 4.186825e-02 0.178400 4.187010e-02 0.178450 4.187193e-02 0.178500 4.187377e-02 0.178550 4.187561e-02 0.178600 4.187744e-02 0.178650 4.187927e-02 0.178700 4.188110e-02 0.178750 4.188292e-02 0.178800 4.188474e-02 0.178850 4.188656e-02 0.178900 4.188838e-02 0.178950 4.189020e-02 0.179000 4.189201e-02 0.179050 4.189382e-02 0.179100 4.189563e-02 0.179150 4.189744e-02 0.179200 4.189925e-02 0.179250 4.190105e-02 0.179300 4.190285e-02 0.179350 4.190465e-02 0.179400 4.190644e-02 0.179450 4.190824e-02 0.179500 4.191003e-02 0.179550 4.191182e-02 0.179600 4.191360e-02 0.179650 4.191539e-02 0.179700 4.191717e-02 0.179750 4.191895e-02 0.179800 4.192073e-02 0.179850 4.192251e-02 0.179900 4.192428e-02 0.179950 4.192605e-02 0.180000 4.192782e-02 0.180050 4.192959e-02 0.180100 4.193135e-02 0.180150 4.193311e-02 0.180200 4.193487e-02 0.180250 4.193663e-02 0.180300 4.193839e-02 0.180350 4.194014e-02 0.180400 4.194189e-02 0.180450 4.194364e-02 0.180500 4.194539e-02 0.180550 4.194714e-02 0.180600 4.194888e-02 0.180650 4.195062e-02 0.180700 4.195236e-02 0.180750 4.195409e-02 0.180800 4.195583e-02 0.180850 4.195756e-02 0.180900 4.195929e-02 0.180950 4.196102e-02 0.181000 4.196274e-02 0.181050 4.196447e-02 0.181100 4.196619e-02 0.181150 4.196791e-02 0.181200 4.196962e-02 0.181250 4.197134e-02 0.181300 4.197305e-02 0.181350 4.197476e-02 0.181400 4.197647e-02 0.181450 4.197817e-02 0.181500 4.197988e-02 0.181550 4.198158e-02 0.181600 4.198328e-02 0.181650 4.198498e-02 0.181700 4.198667e-02 0.181750 4.198837e-02 0.181800 4.199006e-02 0.181850 4.199175e-02 0.181900 4.199343e-02 0.181950 4.199512e-02 0.182000 4.199680e-02 0.182050 4.199848e-02 0.182100 4.200016e-02 0.182150 4.200184e-02 0.182200 4.200351e-02 0.182250 4.200519e-02 0.182300 4.200686e-02 0.182350 4.200852e-02 0.182400 4.201019e-02 0.182450 4.201185e-02 0.182500 4.201352e-02 0.182550 4.201518e-02 0.182600 4.201683e-02 0.182650 4.201849e-02 0.182700 4.202014e-02 0.182750 4.202180e-02 0.182800 4.202344e-02 0.182850 4.202509e-02 0.182900 4.202674e-02 0.182950 4.202838e-02 0.183000 4.203002e-02 0.183050 4.203166e-02 0.183100 4.203330e-02 0.183150 4.203493e-02 0.183200 4.203657e-02 0.183250 4.203820e-02 0.183300 4.203983e-02 0.183350 4.204145e-02 0.183400 4.204308e-02 0.183450 4.204470e-02 0.183500 4.204632e-02 0.183550 4.204794e-02 0.183600 4.204956e-02 0.183650 4.205117e-02 0.183700 4.205279e-02 0.183750 4.205440e-02 0.183800 4.205601e-02 0.183850 4.205761e-02 0.183900 4.205922e-02 0.183950 4.206082e-02 0.184000 4.206242e-02 0.184050 4.206402e-02 0.184100 4.206562e-02 0.184150 4.206721e-02 0.184200 4.206880e-02 0.184250 4.207040e-02 0.184300 4.207198e-02 0.184350 4.207357e-02 0.184400 4.207516e-02 0.184450 4.207674e-02 0.184500 4.207832e-02 0.184550 4.207990e-02 0.184600 4.208148e-02 0.184650 4.208305e-02 0.184700 4.208462e-02 0.184750 4.208619e-02 0.184800 4.208776e-02 0.184850 4.208933e-02 0.184900 4.209090e-02 0.184950 4.209246e-02 0.185000 4.209402e-02 0.185050 4.209558e-02 0.185100 4.209714e-02 0.185150 4.209869e-02 0.185200 4.210025e-02 0.185250 4.210180e-02 0.185300 4.210335e-02 0.185350 4.210490e-02 0.185400 4.210644e-02 0.185450 4.210798e-02 0.185500 4.210953e-02 0.185550 4.211107e-02 0.185600 4.211260e-02 0.185650 4.211414e-02 0.185700 4.211567e-02 0.185750 4.211721e-02 0.185800 4.211874e-02 0.185850 4.212027e-02 0.185900 4.212179e-02 0.185950 4.212332e-02 0.186000 4.212484e-02 0.186050 4.212636e-02 0.186100 4.212788e-02 0.186150 4.212940e-02 0.186200 4.213091e-02 0.186250 4.213243e-02 0.186300 4.213394e-02 0.186350 4.213545e-02 0.186400 4.213695e-02 0.186450 4.213846e-02 0.186500 4.213996e-02 0.186550 4.214146e-02 0.186600 4.214296e-02 0.186650 4.214446e-02 0.186700 4.214596e-02 0.186750 4.214745e-02 0.186800 4.214895e-02 0.186850 4.215044e-02 0.186900 4.215193e-02 0.186950 4.215341e-02 0.187000 4.215490e-02 0.187050 4.215638e-02 0.187100 4.215786e-02 0.187150 4.215934e-02 0.187200 4.216082e-02 0.187250 4.216230e-02 0.187300 4.216377e-02 0.187350 4.216524e-02 0.187400 4.216671e-02 0.187450 4.216818e-02 0.187500 4.216965e-02 0.187550 4.217111e-02 0.187600 4.217258e-02 0.187650 4.217404e-02 0.187700 4.217550e-02 0.187750 4.217695e-02 0.187800 4.217841e-02 0.187850 4.217986e-02 0.187900 4.218132e-02 0.187950 4.218277e-02 0.188000 4.218421e-02 0.188050 4.218566e-02 0.188100 4.218711e-02 0.188150 4.218855e-02 0.188200 4.218999e-02 0.188250 4.219143e-02 0.188300 4.219287e-02 0.188350 4.219430e-02 0.188400 4.219574e-02 0.188450 4.219717e-02 0.188500 4.219860e-02 0.188550 4.220003e-02 0.188600 4.220146e-02 0.188650 4.220288e-02 0.188700 4.220430e-02 0.188750 4.220573e-02 0.188800 4.220715e-02 0.188850 4.220856e-02 0.188900 4.220998e-02 0.188950 4.221139e-02 0.189000 4.221281e-02 0.189050 4.221422e-02 0.189100 4.221563e-02 0.189150 4.221703e-02 0.189200 4.221844e-02 0.189250 4.221984e-02 0.189300 4.222125e-02 0.189350 4.222265e-02 0.189400 4.222405e-02 0.189450 4.222544e-02 0.189500 4.222684e-02 0.189550 4.222823e-02 0.189600 4.222962e-02 0.189650 4.223101e-02 0.189700 4.223240e-02 0.189750 4.223379e-02 0.189800 4.223517e-02 0.189850 4.223655e-02 0.189900 4.223794e-02 0.189950 4.223932e-02 0.190000 4.224069e-02 0.190050 4.224207e-02 0.190100 4.224344e-02 0.190150 4.224482e-02 0.190200 4.224619e-02 0.190250 4.224756e-02 0.190300 4.224892e-02 0.190350 4.225029e-02 0.190400 4.225165e-02 0.190450 4.225302e-02 0.190500 4.225438e-02 0.190550 4.225574e-02 0.190600 4.225709e-02 0.190650 4.225845e-02 0.190700 4.225980e-02 0.190750 4.226115e-02 0.190800 4.226251e-02 0.190850 4.226385e-02 0.190900 4.226520e-02 0.190950 4.226655e-02 0.191000 4.226789e-02 0.191050 4.226923e-02 0.191100 4.227057e-02 0.191150 4.227191e-02 0.191200 4.227325e-02 0.191250 4.227458e-02 0.191300 4.227592e-02 0.191350 4.227725e-02 0.191400 4.227858e-02 0.191450 4.227991e-02 0.191500 4.228124e-02 0.191550 4.228256e-02 0.191600 4.228389e-02 0.191650 4.228521e-02 0.191700 4.228653e-02 0.191750 4.228785e-02 0.191800 4.228916e-02 0.191850 4.229048e-02 0.191900 4.229179e-02 0.191950 4.229311e-02 0.192000 4.229442e-02 0.192050 4.229573e-02 0.192100 4.229703e-02 0.192150 4.229834e-02 0.192200 4.229964e-02 0.192250 4.230095e-02 0.192300 4.230225e-02 0.192350 4.230355e-02 0.192400 4.230484e-02 0.192450 4.230614e-02 0.192500 4.230743e-02 0.192550 4.230873e-02 0.192600 4.231002e-02 0.192650 4.231131e-02 0.192700 4.231259e-02 0.192750 4.231388e-02 0.192800 4.231517e-02 0.192850 4.231645e-02 0.192900 4.231773e-02 0.192950 4.231901e-02 0.193000 4.232029e-02 0.193050 4.232156e-02 0.193100 4.232284e-02 0.193150 4.232411e-02 0.193200 4.232539e-02 0.193250 4.232666e-02 0.193300 4.232792e-02 0.193350 4.232919e-02 0.193400 4.233046e-02 0.193450 4.233172e-02 0.193500 4.233298e-02 0.193550 4.233424e-02 0.193600 4.233550e-02 0.193650 4.233676e-02 0.193700 4.233802e-02 0.193750 4.233927e-02 0.193800 4.234052e-02 0.193850 4.234178e-02 0.193900 4.234303e-02 0.193950 4.234427e-02 0.194000 4.234552e-02 0.194050 4.234677e-02 0.194100 4.234801e-02 0.194150 4.234925e-02 0.194200 4.235049e-02 0.194250 4.235173e-02 0.194300 4.235297e-02 0.194350 4.235420e-02 0.194400 4.235544e-02 0.194450 4.235667e-02 0.194500 4.235790e-02 0.194550 4.235913e-02 0.194600 4.236036e-02 0.194650 4.236159e-02 0.194700 4.236281e-02 0.194750 4.236404e-02 0.194800 4.236526e-02 0.194850 4.236648e-02 0.194900 4.236770e-02 0.194950 4.236891e-02 0.195000 4.237013e-02 0.195050 4.237134e-02 0.195100 4.237256e-02 0.195150 4.237377e-02 0.195200 4.237498e-02 0.195250 4.237619e-02 0.195300 4.237739e-02 0.195350 4.237860e-02 0.195400 4.237980e-02 0.195450 4.238101e-02 0.195500 4.238221e-02 0.195550 4.238341e-02 0.195600 4.238460e-02 0.195650 4.238580e-02 0.195700 4.238700e-02 0.195750 4.238819e-02 0.195800 4.238938e-02 0.195850 4.239057e-02 0.195900 4.239176e-02 0.195950 4.239295e-02 0.196000 4.239413e-02 0.196050 4.239532e-02 0.196100 4.239650e-02 0.196150 4.239768e-02 0.196200 4.239886e-02 0.196250 4.240004e-02 0.196300 4.240122e-02 0.196350 4.240239e-02 0.196400 4.240357e-02 0.196450 4.240474e-02 0.196500 4.240591e-02 0.196550 4.240708e-02 0.196600 4.240825e-02 0.196650 4.240941e-02 0.196700 4.241058e-02 0.196750 4.241174e-02 0.196800 4.241291e-02 0.196850 4.241407e-02 0.196900 4.241523e-02 0.196950 4.241639e-02 0.197000 4.241754e-02 0.197050 4.241870e-02 0.197100 4.241985e-02 0.197150 4.242100e-02 0.197200 4.242215e-02 0.197250 4.242330e-02 0.197300 4.242445e-02 0.197350 4.242560e-02 0.197400 4.242674e-02 0.197450 4.242789e-02 0.197500 4.242903e-02 0.197550 4.243017e-02 0.197600 4.243131e-02 0.197650 4.243245e-02 0.197700 4.243358e-02 0.197750 4.243472e-02 0.197800 4.243585e-02 0.197850 4.243698e-02 0.197900 4.243812e-02 0.197950 4.243925e-02 0.198000 4.244037e-02 0.198050 4.244150e-02 0.198100 4.244263e-02 0.198150 4.244375e-02 0.198200 4.244487e-02 0.198250 4.244599e-02 0.198300 4.244711e-02 0.198350 4.244823e-02 0.198400 4.244935e-02 0.198450 4.245046e-02 0.198500 4.245158e-02 0.198550 4.245269e-02 0.198600 4.245380e-02 0.198650 4.245491e-02 0.198700 4.245602e-02 0.198750 4.245713e-02 0.198800 4.245823e-02 0.198850 4.245934e-02 0.198900 4.246044e-02 0.198950 4.246154e-02 0.199000 4.246264e-02 0.199050 4.246374e-02 0.199100 4.246484e-02 0.199150 4.246593e-02 0.199200 4.246703e-02 0.199250 4.246812e-02 0.199300 4.246921e-02 0.199350 4.247030e-02 0.199400 4.247139e-02 0.199450 4.247248e-02 0.199500 4.247357e-02 0.199550 4.247465e-02 0.199600 4.247574e-02 0.199650 4.247682e-02 0.199700 4.247790e-02 0.199750 4.247898e-02 0.199800 4.248006e-02 0.199850 4.248114e-02 0.199900 4.248221e-02 0.199950 4.248329e-02 0.200000 4.248436e-02 0.200050 4.248543e-02 0.200100 4.248650e-02 0.200150 4.248757e-02 0.200200 4.248864e-02 0.200250 4.248970e-02 0.200300 4.249077e-02 0.200350 4.249183e-02 0.200400 4.249290e-02 0.200450 4.249396e-02 0.200500 4.249502e-02 0.200550 4.249607e-02 0.200600 4.249713e-02 0.200650 4.249819e-02 0.200700 4.249924e-02 0.200750 4.250029e-02 0.200800 4.250135e-02 0.200850 4.250240e-02 0.200900 4.250345e-02 0.200950 4.250449e-02 0.201000 4.250554e-02 0.201050 4.250659e-02 0.201100 4.250763e-02 0.201150 4.250867e-02 0.201200 4.250971e-02 0.201250 4.251075e-02 0.201300 4.251179e-02 0.201350 4.251283e-02 0.201400 4.251387e-02 0.201450 4.251490e-02 0.201500 4.251593e-02 0.201550 4.251697e-02 0.201600 4.251800e-02 0.201650 4.251903e-02 0.201700 4.252006e-02 0.201750 4.252108e-02 0.201800 4.252211e-02 0.201850 4.252313e-02 0.201900 4.252416e-02 0.201950 4.252518e-02 0.202000 4.252620e-02 0.202050 4.252722e-02 0.202100 4.252824e-02 0.202150 4.252925e-02 0.202200 4.253027e-02 0.202250 4.253128e-02 0.202300 4.253230e-02 0.202350 4.253331e-02 0.202400 4.253432e-02 0.202450 4.253533e-02 0.202500 4.253634e-02 0.202550 4.253734e-02 0.202600 4.253835e-02 0.202650 4.253935e-02 0.202700 4.254036e-02 0.202750 4.254136e-02 0.202800 4.254236e-02 0.202850 4.254336e-02 0.202900 4.254436e-02 0.202950 4.254535e-02 0.203000 4.254635e-02 0.203050 4.254734e-02 0.203100 4.254834e-02 0.203150 4.254933e-02 0.203200 4.255032e-02 0.203250 4.255131e-02 0.203300 4.255229e-02 0.203350 4.255328e-02 0.203400 4.255427e-02 0.203450 4.255525e-02 0.203500 4.255623e-02 0.203550 4.255722e-02 0.203600 4.255820e-02 0.203650 4.255918e-02 0.203700 4.256016e-02 0.203750 4.256113e-02 0.203800 4.256211e-02 0.203850 4.256308e-02 0.203900 4.256406e-02 0.203950 4.256503e-02 0.204000 4.256600e-02 0.204050 4.256697e-02 0.204100 4.256794e-02 0.204150 4.256890e-02 0.204200 4.256987e-02 0.204250 4.257084e-02 0.204300 4.257180e-02 0.204350 4.257276e-02 0.204400 4.257372e-02 0.204450 4.257468e-02 0.204500 4.257564e-02 0.204550 4.257660e-02 0.204600 4.257756e-02 0.204650 4.257851e-02 0.204700 4.257947e-02 0.204750 4.258042e-02 0.204800 4.258137e-02 0.204850 4.258232e-02 0.204900 4.258327e-02 0.204950 4.258422e-02 0.205000 4.258517e-02 0.205050 4.258611e-02 0.205100 4.258706e-02 0.205150 4.258800e-02 0.205200 4.258894e-02 0.205250 4.258988e-02 0.205300 4.259082e-02 0.205350 4.259176e-02 0.205400 4.259270e-02 0.205450 4.259363e-02 0.205500 4.259457e-02 0.205550 4.259550e-02 0.205600 4.259644e-02 0.205650 4.259737e-02 0.205700 4.259830e-02 0.205750 4.259923e-02 0.205800 4.260016e-02 0.205850 4.260108e-02 0.205900 4.260201e-02 0.205950 4.260293e-02 0.206000 4.260386e-02 0.206050 4.260478e-02 0.206100 4.260570e-02 0.206150 4.260662e-02 0.206200 4.260754e-02 0.206250 4.260846e-02 0.206300 4.260938e-02 0.206350 4.261029e-02 0.206400 4.261121e-02 0.206450 4.261212e-02 0.206500 4.261303e-02 0.206550 4.261394e-02 0.206600 4.261485e-02 0.206650 4.261576e-02 0.206700 4.261667e-02 0.206750 4.261757e-02 0.206800 4.261848e-02 0.206850 4.261938e-02 0.206900 4.262029e-02 0.206950 4.262119e-02 0.207000 4.262209e-02 0.207050 4.262299e-02 0.207100 4.262389e-02 0.207150 4.262478e-02 0.207200 4.262568e-02 0.207250 4.262658e-02 0.207300 4.262747e-02 0.207350 4.262836e-02 0.207400 4.262925e-02 0.207450 4.263015e-02 0.207500 4.263104e-02 0.207550 4.263192e-02 0.207600 4.263281e-02 0.207650 4.263370e-02 0.207700 4.263458e-02 0.207750 4.263547e-02 0.207800 4.263635e-02 0.207850 4.263723e-02 0.207900 4.263811e-02 0.207950 4.263899e-02 0.208000 4.263987e-02 0.208050 4.264075e-02 0.208100 4.264162e-02 0.208150 4.264250e-02 0.208200 4.264337e-02 0.208250 4.264425e-02 0.208300 4.264512e-02 0.208350 4.264599e-02 0.208400 4.264686e-02 0.208450 4.264773e-02 0.208500 4.264860e-02 0.208550 4.264946e-02 0.208600 4.265033e-02 0.208650 4.265119e-02 0.208700 4.265206e-02 0.208750 4.265292e-02 0.208800 4.265378e-02 0.208850 4.265464e-02 0.208900 4.265550e-02 0.208950 4.265636e-02 0.209000 4.265721e-02 0.209050 4.265807e-02 0.209100 4.265892e-02 0.209150 4.265978e-02 0.209200 4.266063e-02 0.209250 4.266148e-02 0.209300 4.266233e-02 0.209350 4.266318e-02 0.209400 4.266403e-02 0.209450 4.266488e-02 0.209500 4.266572e-02 0.209550 4.266657e-02 0.209600 4.266741e-02 0.209650 4.266825e-02 0.209700 4.266910e-02 0.209750 4.266994e-02 0.209800 4.267078e-02 0.209850 4.267162e-02 0.209900 4.267245e-02 0.209950 4.267329e-02 0.210000 4.267413e-02 0.210050 4.267496e-02 0.210100 4.267579e-02 0.210150 4.267663e-02 0.210200 4.267746e-02 0.210250 4.267829e-02 0.210300 4.267912e-02 0.210350 4.267995e-02 0.210400 4.268077e-02 0.210450 4.268160e-02 0.210500 4.268243e-02 0.210550 4.268325e-02 0.210600 4.268407e-02 0.210650 4.268490e-02 0.210700 4.268572e-02 0.210750 4.268654e-02 0.210800 4.268736e-02 0.210850 4.268817e-02 0.210900 4.268899e-02 0.210950 4.268981e-02 0.211000 4.269062e-02 0.211050 4.269144e-02 0.211100 4.269225e-02 0.211150 4.269306e-02 0.211200 4.269387e-02 0.211250 4.269468e-02 0.211300 4.269549e-02 0.211350 4.269630e-02 0.211400 4.269711e-02 0.211450 4.269791e-02 0.211500 4.269872e-02 0.211550 4.269952e-02 0.211600 4.270032e-02 0.211650 4.270113e-02 0.211700 4.270193e-02 0.211750 4.270273e-02 0.211800 4.270353e-02 0.211850 4.270432e-02 0.211900 4.270512e-02 0.211950 4.270592e-02 0.212000 4.270671e-02 0.212050 4.270751e-02 0.212100 4.270830e-02 0.212150 4.270909e-02 0.212200 4.270988e-02 0.212250 4.271067e-02 0.212300 4.271146e-02 0.212350 4.271225e-02 0.212400 4.271304e-02 0.212450 4.271382e-02 0.212500 4.271461e-02 0.212550 4.271539e-02 0.212600 4.271617e-02 0.212650 4.271696e-02 0.212700 4.271774e-02 0.212750 4.271852e-02 0.212800 4.271930e-02 0.212850 4.272007e-02 0.212900 4.272085e-02 0.212950 4.272163e-02 0.213000 4.272240e-02 0.213050 4.272318e-02 0.213100 4.272395e-02 0.213150 4.272472e-02 0.213200 4.272549e-02 0.213250 4.272627e-02 0.213300 4.272703e-02 0.213350 4.272780e-02 0.213400 4.272857e-02 0.213450 4.272934e-02 0.213500 4.273010e-02 0.213550 4.273087e-02 0.213600 4.273163e-02 0.213650 4.273239e-02 0.213700 4.273316e-02 0.213750 4.273392e-02 0.213800 4.273468e-02 0.213850 4.273544e-02 0.213900 4.273619e-02 0.213950 4.273695e-02 0.214000 4.273771e-02 0.214050 4.273846e-02 0.214100 4.273922e-02 0.214150 4.273997e-02 0.214200 4.274072e-02 0.214250 4.274147e-02 0.214300 4.274222e-02 0.214350 4.274297e-02 0.214400 4.274372e-02 0.214450 4.274447e-02 0.214500 4.274522e-02 0.214550 4.274596e-02 0.214600 4.274671e-02 0.214650 4.274745e-02 0.214700 4.274820e-02 0.214750 4.274894e-02 0.214800 4.274968e-02 0.214850 4.275042e-02 0.214900 4.275116e-02 0.214950 4.275190e-02 0.215000 4.275263e-02 0.215050 4.275337e-02 0.215100 4.275411e-02 0.215150 4.275484e-02 0.215200 4.275557e-02 0.215250 4.275631e-02 0.215300 4.275704e-02 0.215350 4.275777e-02 0.215400 4.275850e-02 0.215450 4.275923e-02 0.215500 4.275996e-02 0.215550 4.276069e-02 0.215600 4.276141e-02 0.215650 4.276214e-02 0.215700 4.276286e-02 0.215750 4.276359e-02 0.215800 4.276431e-02 0.215850 4.276503e-02 0.215900 4.276575e-02 0.215950 4.276647e-02 0.216000 4.276719e-02 0.216050 4.276791e-02 0.216100 4.276863e-02 0.216150 4.276934e-02 0.216200 4.277006e-02 0.216250 4.277078e-02 0.216300 4.277149e-02 0.216350 4.277220e-02 0.216400 4.277291e-02 0.216450 4.277363e-02 0.216500 4.277434e-02 0.216550 4.277505e-02 0.216600 4.277575e-02 0.216650 4.277646e-02 0.216700 4.277717e-02 0.216750 4.277787e-02 0.216800 4.277858e-02 0.216850 4.277928e-02 0.216900 4.277999e-02 0.216950 4.278069e-02 0.217000 4.278139e-02 0.217050 4.278209e-02 0.217100 4.278279e-02 0.217150 4.278349e-02 0.217200 4.278419e-02 0.217250 4.278489e-02 0.217300 4.278558e-02 0.217350 4.278628e-02 0.217400 4.278697e-02 0.217450 4.278766e-02 0.217500 4.278836e-02 0.217550 4.278905e-02 0.217600 4.278974e-02 0.217650 4.279043e-02 0.217700 4.279112e-02 0.217750 4.279181e-02 0.217800 4.279250e-02 0.217850 4.279318e-02 0.217900 4.279387e-02 0.217950 4.279455e-02 0.218000 4.279524e-02 0.218050 4.279592e-02 0.218100 4.279660e-02 0.218150 4.279729e-02 0.218200 4.279797e-02 0.218250 4.279865e-02 0.218300 4.279933e-02 0.218350 4.280000e-02 0.218400 4.280068e-02 0.218450 4.280136e-02 0.218500 4.280203e-02 0.218550 4.280271e-02 0.218600 4.280338e-02 0.218650 4.280406e-02 0.218700 4.280473e-02 0.218750 4.280540e-02 0.218800 4.280607e-02 0.218850 4.280674e-02 0.218900 4.280741e-02 0.218950 4.280808e-02 0.219000 4.280874e-02 0.219050 4.280941e-02 0.219100 4.281008e-02 0.219150 4.281074e-02 0.219200 4.281141e-02 0.219250 4.281207e-02 0.219300 4.281273e-02 0.219350 4.281339e-02 0.219400 4.281405e-02 0.219450 4.281471e-02 0.219500 4.281537e-02 0.219550 4.281603e-02 0.219600 4.281669e-02 0.219650 4.281734e-02 0.219700 4.281800e-02 0.219750 4.281865e-02 0.219800 4.281931e-02 0.219850 4.281996e-02 0.219900 4.282061e-02 0.219950 4.282127e-02 0.220000 4.282192e-02 0.220050 4.282257e-02 0.220100 4.282322e-02 0.220150 4.282386e-02 0.220200 4.282451e-02 0.220250 4.282516e-02 0.220300 4.282581e-02 0.220350 4.282645e-02 0.220400 4.282709e-02 0.220450 4.282774e-02 0.220500 4.282838e-02 0.220550 4.282902e-02 0.220600 4.282966e-02 0.220650 4.283030e-02 0.220700 4.283094e-02 0.220750 4.283158e-02 0.220800 4.283222e-02 0.220850 4.283286e-02 0.220900 4.283349e-02 0.220950 4.283413e-02 0.221000 4.283476e-02 0.221050 4.283540e-02 0.221100 4.283603e-02 0.221150 4.283666e-02 0.221200 4.283730e-02 0.221250 4.283793e-02 0.221300 4.283856e-02 0.221350 4.283919e-02 0.221400 4.283981e-02 0.221450 4.284044e-02 0.221500 4.284107e-02 0.221550 4.284169e-02 0.221600 4.284232e-02 0.221650 4.284294e-02 0.221700 4.284357e-02 0.221750 4.284419e-02 0.221800 4.284481e-02 0.221850 4.284543e-02 0.221900 4.284606e-02 0.221950 4.284668e-02 0.222000 4.284729e-02 0.222050 4.284791e-02 0.222100 4.284853e-02 0.222150 4.284915e-02 0.222200 4.284976e-02 0.222250 4.285038e-02 0.222300 4.285099e-02 0.222350 4.285161e-02 0.222400 4.285222e-02 0.222450 4.285283e-02 0.222500 4.285344e-02 0.222550 4.285405e-02 0.222600 4.285466e-02 0.222650 4.285527e-02 0.222700 4.285588e-02 0.222750 4.285649e-02 0.222800 4.285710e-02 0.222850 4.285770e-02 0.222900 4.285831e-02 0.222950 4.285891e-02 0.223000 4.285952e-02 0.223050 4.286012e-02 0.223100 4.286072e-02 0.223150 4.286132e-02 0.223200 4.286192e-02 0.223250 4.286252e-02 0.223300 4.286312e-02 0.223350 4.286372e-02 0.223400 4.286432e-02 0.223450 4.286492e-02 0.223500 4.286551e-02 0.223550 4.286611e-02 0.223600 4.286670e-02 0.223650 4.286730e-02 0.223700 4.286789e-02 0.223750 4.286848e-02 0.223800 4.286907e-02 0.223850 4.286967e-02 0.223900 4.287026e-02 0.223950 4.287085e-02 0.224000 4.287143e-02 0.224050 4.287202e-02 0.224100 4.287261e-02 0.224150 4.287320e-02 0.224200 4.287378e-02 0.224250 4.287437e-02 0.224300 4.287495e-02 0.224350 4.287554e-02 0.224400 4.287612e-02 0.224450 4.287670e-02 0.224500 4.287728e-02 0.224550 4.287786e-02 0.224600 4.287844e-02 0.224650 4.287902e-02 0.224700 4.287960e-02 0.224750 4.288018e-02 0.224800 4.288076e-02 0.224850 4.288133e-02 0.224900 4.288191e-02 0.224950 4.288248e-02 0.225000 4.288306e-02 0.225050 4.288363e-02 0.225100 4.288421e-02 0.225150 4.288478e-02 0.225200 4.288535e-02 0.225250 4.288592e-02 0.225300 4.288649e-02 0.225350 4.288706e-02 0.225400 4.288763e-02 0.225450 4.288820e-02 0.225500 4.288876e-02 0.225550 4.288933e-02 0.225600 4.288990e-02 0.225650 4.289046e-02 0.225700 4.289103e-02 0.225750 4.289159e-02 0.225800 4.289215e-02 0.225850 4.289271e-02 0.225900 4.289328e-02 0.225950 4.289384e-02 0.226000 4.289440e-02 0.226050 4.289496e-02 0.226100 4.289552e-02 0.226150 4.289607e-02 0.226200 4.289663e-02 0.226250 4.289719e-02 0.226300 4.289774e-02 0.226350 4.289830e-02 0.226400 4.289885e-02 0.226450 4.289941e-02 0.226500 4.289996e-02 0.226550 4.290051e-02 0.226600 4.290106e-02 0.226650 4.290162e-02 0.226700 4.290217e-02 0.226750 4.290272e-02 0.226800 4.290327e-02 0.226850 4.290381e-02 0.226900 4.290436e-02 0.226950 4.290491e-02 0.227000 4.290545e-02 0.227050 4.290600e-02 0.227100 4.290655e-02 0.227150 4.290709e-02 0.227200 4.290763e-02 0.227250 4.290818e-02 0.227300 4.290872e-02 0.227350 4.290926e-02 0.227400 4.290980e-02 0.227450 4.291034e-02 0.227500 4.291088e-02 0.227550 4.291142e-02 0.227600 4.291196e-02 0.227650 4.291250e-02 0.227700 4.291303e-02 0.227750 4.291357e-02 0.227800 4.291410e-02 0.227850 4.291464e-02 0.227900 4.291517e-02 0.227950 4.291571e-02 0.228000 4.291624e-02 0.228050 4.291677e-02 0.228100 4.291730e-02 0.228150 4.291783e-02 0.228200 4.291836e-02 0.228250 4.291889e-02 0.228300 4.291942e-02 0.228350 4.291995e-02 0.228400 4.292048e-02 0.228450 4.292101e-02 0.228500 4.292153e-02 0.228550 4.292206e-02 0.228600 4.292258e-02 0.228650 4.292311e-02 0.228700 4.292363e-02 0.228750 4.292415e-02 0.228800 4.292468e-02 0.228850 4.292520e-02 0.228900 4.292572e-02 0.228950 4.292624e-02 0.229000 4.292676e-02 0.229050 4.292728e-02 0.229100 4.292780e-02 0.229150 4.292831e-02 0.229200 4.292883e-02 0.229250 4.292935e-02 0.229300 4.292986e-02 0.229350 4.293038e-02 0.229400 4.293089e-02 0.229450 4.293141e-02 0.229500 4.293192e-02 0.229550 4.293243e-02 0.229600 4.293294e-02 0.229650 4.293346e-02 0.229700 4.293397e-02 0.229750 4.293448e-02 0.229800 4.293499e-02 0.229850 4.293549e-02 0.229900 4.293600e-02 0.229950 4.293651e-02 0.230000 4.293702e-02 0.230050 4.293752e-02 0.230100 4.293803e-02 0.230150 4.293853e-02 0.230200 4.293904e-02 0.230250 4.293954e-02 0.230300 4.294004e-02 0.230350 4.294055e-02 0.230400 4.294105e-02 0.230450 4.294155e-02 0.230500 4.294205e-02 0.230550 4.294255e-02 0.230600 4.294305e-02 0.230650 4.294355e-02 0.230700 4.294405e-02 0.230750 4.294454e-02 0.230800 4.294504e-02 0.230850 4.294554e-02 0.230900 4.294603e-02 0.230950 4.294653e-02 0.231000 4.294702e-02 0.231050 4.294752e-02 0.231100 4.294801e-02 0.231150 4.294850e-02 0.231200 4.294899e-02 0.231250 4.294948e-02 0.231300 4.294998e-02 0.231350 4.295047e-02 0.231400 4.295095e-02 0.231450 4.295144e-02 0.231500 4.295193e-02 0.231550 4.295242e-02 0.231600 4.295291e-02 0.231650 4.295339e-02 0.231700 4.295388e-02 0.231750 4.295436e-02 0.231800 4.295485e-02 0.231850 4.295533e-02 0.231900 4.295582e-02 0.231950 4.295630e-02 0.232000 4.295678e-02 0.232050 4.295726e-02 0.232100 4.295774e-02 0.232150 4.295822e-02 0.232200 4.295870e-02 0.232250 4.295918e-02 0.232300 4.295966e-02 0.232350 4.296014e-02 0.232400 4.296062e-02 0.232450 4.296109e-02 0.232500 4.296157e-02 0.232550 4.296204e-02 0.232600 4.296252e-02 0.232650 4.296299e-02 0.232700 4.296347e-02 0.232750 4.296394e-02 0.232800 4.296441e-02 0.232850 4.296489e-02 0.232900 4.296536e-02 0.232950 4.296583e-02 0.233000 4.296630e-02 0.233050 4.296677e-02 0.233100 4.296724e-02 0.233150 4.296771e-02 0.233200 4.296817e-02 0.233250 4.296864e-02 0.233300 4.296911e-02 0.233350 4.296957e-02 0.233400 4.297004e-02 0.233450 4.297050e-02 0.233500 4.297097e-02 0.233550 4.297143e-02 0.233600 4.297190e-02 0.233650 4.297236e-02 0.233700 4.297282e-02 0.233750 4.297328e-02 0.233800 4.297374e-02 0.233850 4.297420e-02 0.233900 4.297466e-02 0.233950 4.297512e-02 0.234000 4.297558e-02 0.234050 4.297604e-02 0.234100 4.297650e-02 0.234150 4.297695e-02 0.234200 4.297741e-02 0.234250 4.297787e-02 0.234300 4.297832e-02 0.234350 4.297878e-02 0.234400 4.297923e-02 0.234450 4.297968e-02 0.234500 4.298014e-02 0.234550 4.298059e-02 0.234600 4.298104e-02 0.234650 4.298149e-02 0.234700 4.298194e-02 0.234750 4.298239e-02 0.234800 4.298284e-02 0.234850 4.298329e-02 0.234900 4.298374e-02 0.234950 4.298419e-02 0.235000 4.298463e-02 0.235050 4.298508e-02 0.235100 4.298553e-02 0.235150 4.298597e-02 0.235200 4.298642e-02 0.235250 4.298686e-02 0.235300 4.298731e-02 0.235350 4.298775e-02 0.235400 4.298819e-02 0.235450 4.298863e-02 0.235500 4.298908e-02 0.235550 4.298952e-02 0.235600 4.298996e-02 0.235650 4.299040e-02 0.235700 4.299084e-02 0.235750 4.299128e-02 0.235800 4.299172e-02 0.235850 4.299215e-02 0.235900 4.299259e-02 0.235950 4.299303e-02 0.236000 4.299346e-02 0.236050 4.299390e-02 0.236100 4.299433e-02 0.236150 4.299477e-02 0.236200 4.299520e-02 0.236250 4.299564e-02 0.236300 4.299607e-02 0.236350 4.299650e-02 0.236400 4.299693e-02 0.236450 4.299737e-02 0.236500 4.299780e-02 0.236550 4.299823e-02 0.236600 4.299866e-02 0.236650 4.299909e-02 0.236700 4.299951e-02 0.236750 4.299994e-02 0.236800 4.300037e-02 0.236850 4.300080e-02 0.236900 4.300122e-02 0.236950 4.300165e-02 0.237000 4.300208e-02 0.237050 4.300250e-02 0.237100 4.300293e-02 0.237150 4.300335e-02 0.237200 4.300377e-02 0.237250 4.300420e-02 0.237300 4.300462e-02 0.237350 4.300504e-02 0.237400 4.300546e-02 0.237450 4.300588e-02 0.237500 4.300630e-02 0.237550 4.300672e-02 0.237600 4.300714e-02 0.237650 4.300756e-02 0.237700 4.300798e-02 0.237750 4.300839e-02 0.237800 4.300881e-02 0.237850 4.300923e-02 0.237900 4.300964e-02 0.237950 4.301006e-02 0.238000 4.301048e-02 0.238050 4.301089e-02 0.238100 4.301130e-02 0.238150 4.301172e-02 0.238200 4.301213e-02 0.238250 4.301254e-02 0.238300 4.301295e-02 0.238350 4.301337e-02 0.238400 4.301378e-02 0.238450 4.301419e-02 0.238500 4.301460e-02 0.238550 4.301501e-02 0.238600 4.301541e-02 0.238650 4.301582e-02 0.238700 4.301623e-02 0.238750 4.301664e-02 0.238800 4.301704e-02 0.238850 4.301745e-02 0.238900 4.301786e-02 0.238950 4.301826e-02 0.239000 4.301867e-02 0.239050 4.301907e-02 0.239100 4.301947e-02 0.239150 4.301988e-02 0.239200 4.302028e-02 0.239250 4.302068e-02 0.239300 4.302108e-02 0.239350 4.302149e-02 0.239400 4.302189e-02 0.239450 4.302229e-02 0.239500 4.302269e-02 0.239550 4.302309e-02 0.239600 4.302348e-02 0.239650 4.302388e-02 0.239700 4.302428e-02 0.239750 4.302468e-02 0.239800 4.302507e-02 0.239850 4.302547e-02 0.239900 4.302587e-02 0.239950 4.302626e-02 0.240000 4.302666e-02 0.240050 4.302705e-02 0.240100 4.302744e-02 0.240150 4.302784e-02 0.240200 4.302823e-02 0.240250 4.302862e-02 0.240300 4.302901e-02 0.240350 4.302941e-02 0.240400 4.302980e-02 0.240450 4.303019e-02 0.240500 4.303058e-02 0.240550 4.303097e-02 0.240600 4.303136e-02 0.240650 4.303174e-02 0.240700 4.303213e-02 0.240750 4.303252e-02 0.240800 4.303291e-02 0.240850 4.303329e-02 0.240900 4.303368e-02 0.240950 4.303406e-02 0.241000 4.303445e-02 0.241050 4.303483e-02 0.241100 4.303522e-02 0.241150 4.303560e-02 0.241200 4.303598e-02 0.241250 4.303637e-02 0.241300 4.303675e-02 0.241350 4.303713e-02 0.241400 4.303751e-02 0.241450 4.303789e-02 0.241500 4.303827e-02 0.241550 4.303865e-02 0.241600 4.303903e-02 0.241650 4.303941e-02 0.241700 4.303979e-02 0.241750 4.304017e-02 0.241800 4.304054e-02 0.241850 4.304092e-02 0.241900 4.304130e-02 0.241950 4.304167e-02 0.242000 4.304205e-02 0.242050 4.304242e-02 0.242100 4.304280e-02 0.242150 4.304317e-02 0.242200 4.304355e-02 0.242250 4.304392e-02 0.242300 4.304429e-02 0.242350 4.304466e-02 0.242400 4.304504e-02 0.242450 4.304541e-02 0.242500 4.304578e-02 0.242550 4.304615e-02 0.242600 4.304652e-02 0.242650 4.304689e-02 0.242700 4.304726e-02 0.242750 4.304763e-02 0.242800 4.304799e-02 0.242850 4.304836e-02 0.242900 4.304873e-02 0.242950 4.304909e-02 0.243000 4.304946e-02 0.243050 4.304983e-02 0.243100 4.305019e-02 0.243150 4.305056e-02 0.243200 4.305092e-02 0.243250 4.305129e-02 0.243300 4.305165e-02 0.243350 4.305201e-02 0.243400 4.305237e-02 0.243450 4.305274e-02 0.243500 4.305310e-02 0.243550 4.305346e-02 0.243600 4.305382e-02 0.243650 4.305418e-02 0.243700 4.305454e-02 0.243750 4.305490e-02 0.243800 4.305526e-02 0.243850 4.305562e-02 0.243900 4.305598e-02 0.243950 4.305633e-02 0.244000 4.305669e-02 0.244050 4.305705e-02 0.244100 4.305740e-02 0.244150 4.305776e-02 0.244200 4.305811e-02 0.244250 4.305847e-02 0.244300 4.305882e-02 0.244350 4.305918e-02 0.244400 4.305953e-02 0.244450 4.305988e-02 0.244500 4.306024e-02 0.244550 4.306059e-02 0.244600 4.306094e-02 0.244650 4.306129e-02 0.244700 4.306164e-02 0.244750 4.306199e-02 0.244800 4.306234e-02 0.244850 4.306269e-02 0.244900 4.306304e-02 0.244950 4.306339e-02 0.245000 4.306374e-02 0.245050 4.306409e-02 0.245100 4.306444e-02 0.245150 4.306478e-02 0.245200 4.306513e-02 0.245250 4.306548e-02 0.245300 4.306582e-02 0.245350 4.306617e-02 0.245400 4.306651e-02 0.245450 4.306686e-02 0.245500 4.306720e-02 0.245550 4.306754e-02 0.245600 4.306789e-02 0.245650 4.306823e-02 0.245700 4.306857e-02 0.245750 4.306891e-02 0.245800 4.306926e-02 0.245850 4.306960e-02 0.245900 4.306994e-02 0.245950 4.307028e-02 0.246000 4.307062e-02 0.246050 4.307096e-02 0.246100 4.307130e-02 0.246150 4.307163e-02 0.246200 4.307197e-02 0.246250 4.307231e-02 0.246300 4.307265e-02 0.246350 4.307298e-02 0.246400 4.307332e-02 0.246450 4.307366e-02 0.246500 4.307399e-02 0.246550 4.307433e-02 0.246600 4.307466e-02 0.246650 4.307500e-02 0.246700 4.307533e-02 0.246750 4.307566e-02 0.246800 4.307600e-02 0.246850 4.307633e-02 0.246900 4.307666e-02 0.246950 4.307699e-02 0.247000 4.307732e-02 0.247050 4.307766e-02 0.247100 4.307799e-02 0.247150 4.307832e-02 0.247200 4.307865e-02 0.247250 4.307898e-02 0.247300 4.307930e-02 0.247350 4.307963e-02 0.247400 4.307996e-02 0.247450 4.308029e-02 0.247500 4.308062e-02 0.247550 4.308094e-02 0.247600 4.308127e-02 0.247650 4.308160e-02 0.247700 4.308192e-02 0.247750 4.308225e-02 0.247800 4.308257e-02 0.247850 4.308290e-02 0.247900 4.308322e-02 0.247950 4.308354e-02 0.248000 4.308387e-02 0.248050 4.308419e-02 0.248100 4.308451e-02 0.248150 4.308483e-02 0.248200 4.308515e-02 0.248250 4.308548e-02 0.248300 4.308580e-02 0.248350 4.308612e-02 0.248400 4.308644e-02 0.248450 4.308676e-02 0.248500 4.308708e-02 0.248550 4.308739e-02 0.248600 4.308771e-02 0.248650 4.308803e-02 0.248700 4.308835e-02 0.248750 4.308867e-02 0.248800 4.308898e-02 0.248850 4.308930e-02 0.248900 4.308961e-02 0.248950 4.308993e-02 0.249000 4.309025e-02 0.249050 4.309056e-02 0.249100 4.309087e-02 0.249150 4.309119e-02 0.249200 4.309150e-02 0.249250 4.309182e-02 0.249300 4.309213e-02 0.249350 4.309244e-02 0.249400 4.309275e-02 0.249450 4.309306e-02 0.249500 4.309338e-02 0.249550 4.309369e-02 0.249600 4.309400e-02 0.249650 4.309431e-02 0.249700 4.309462e-02 0.249750 4.309493e-02 0.249800 4.309524e-02 0.249850 4.309554e-02 0.249900 4.309585e-02 0.249950 4.309616e-02 0.250000 4.309647e-02 brian2-2.5.4/brian2/tests/test_GSL.py000066400000000000000000000506431445201106100172560ustar00rootroot00000000000000import functools import pytest from brian2 import * from brian2.codegen.runtime.GSLcython_rt import IntegrationError from brian2.core.preferences import PreferenceError from brian2.stateupdaters.base import UnsupportedEquationsException from brian2.tests.utils import exc_isinstance max_difference = 0.1 * mV pytestmark = pytest.mark.gsl def skip_if_not_implemented(func): @functools.wraps(func) def wrapped(): try: func() except (BrianObjectException, NotImplementedError) as exc: if not ( isinstance(exc, NotImplementedError) or isinstance(exc.__cause__, NotImplementedError) ): raise pytest.skip("GSL support for numpy has not been implemented yet") return wrapped @pytest.mark.standalone_compatible @skip_if_not_implemented def test_GSL_stateupdater_basic(): # just the adaptive_threshold example: run for exponential_euler and GSL and see # if results are comparable (same amount of spikes and spikes > 0) eqs = """ dv/dt = -v/(10*ms) : volt dvt/dt = (10*mV-vt)/(15*ms) : volt """ reset = """ v = 0*mV vt += 3*mV """ neurons_conventional = NeuronGroup( 1, model=eqs, reset=reset, threshold="v>vt", method="exponential_euler" ) neurons_GSL = NeuronGroup(1, model=eqs, reset=reset, threshold="v>vt", method="gsl") neurons_conventional.vt = 10 * mV neurons_GSL.vt = 10 * mV # 50 'different' neurons so no neuron spikes more than once per dt P = SpikeGeneratorGroup(1, [0] * 50, np.arange(50) / 50.0 * 100 * ms) C_conventional = Synapses(P, neurons_conventional, on_pre="v += 3*mV") C_GSL = Synapses(P, neurons_GSL, on_pre="v += 3*mV") C_conventional.connect() C_GSL.connect() SM_conventional = SpikeMonitor(neurons_conventional, variables="v") SM_GSL = SpikeMonitor(neurons_GSL, variables="v") net = Network( neurons_conventional, neurons_GSL, P, C_conventional, C_GSL, SM_conventional, SM_GSL, ) net.run(100 * ms) assert ( SM_conventional.num_spikes > 0 ), "simulation should produce spiking, but no spikes monitored" assert SM_conventional.num_spikes == SM_GSL.num_spikes, ( "GSL_statupdater produced different number of spikes than integration with ", "exponential euler", ) @pytest.mark.standalone_compatible @skip_if_not_implemented def test_GSL_different_clocks(): vt = 10 * mV eqs = "dv/dt = -v/(10*ms) : volt" neurons = NeuronGroup(1, model=eqs, threshold="v>vt", method="gsl", dt=0.2 * ms) # for this test just check if it compiles run(0 * ms) @pytest.mark.standalone_compatible @skip_if_not_implemented def test_GSL_default_function(): # phase_locking example tau = 20 * ms n = 100 b = 1.2 # constant current mean, the modulation varies freq = 10 * Hz eqs = """ dv/dt = (-v + a * sin(2 * pi * freq * t) + b) / tau : 1 a : 1 """ vrand = rand(n) neurons_conventional = NeuronGroup( n, model=eqs, threshold="v > 1", reset="v = 0", method="exponential_euler" ) neurons_GSL = NeuronGroup( n, model=eqs, threshold="v > 1", reset="v = 0", method="gsl" ) neurons_conventional.v = vrand neurons_GSL.v = vrand neurons_conventional.a = "0.05 + 0.7*i/n" neurons_GSL.a = "0.05 + 0.7*i/n" trace_conventional = StateMonitor(neurons_conventional, "v", record=50) trace_GSL = StateMonitor(neurons_GSL, "v", record=50) net = Network(neurons_conventional, neurons_GSL, trace_conventional, trace_GSL) net.run(10 * ms) assert ( max(trace_conventional.v[0] - trace_GSL.v[0]) < max_difference / mV ), "difference between conventional and GSL output is larger than max_difference" @pytest.mark.standalone_compatible @skip_if_not_implemented def test_GSL_user_defined_function(): # phase_locking example with user_defined sin eqs = """ dv/dt = (-v + a * sin(2 * pi * freq * t) + b) / tau : 1 a : 1 """ @implementation( "cpp", """ double user_sin(double phase) { return sin(phase); } """, ) @implementation( "cython", """ cdef double user_sin(double phase): return sin(phase) """, ) @check_units(phase=1, result=1) def user_sin(phase): raise Exception tau = 20 * ms n = 100 b = 1.2 # constant current mean, the modulation varies freq = 10 * Hz eqs = """ dv/dt = (-v + a * user_sin(2 * pi * freq * t) + b) / tau : 1 a : 1 """ vrand = rand(n) neurons_conventional = NeuronGroup( n, model=eqs, threshold="v > 1", reset="v = 0", method="exponential_euler" ) neurons_GSL = NeuronGroup( n, model=eqs, threshold="v > 1", reset="v = 0", method="gsl" ) neurons_conventional.v = vrand neurons_GSL.v = vrand neurons_conventional.a = "0.05 + 0.7*i/n" neurons_GSL.a = "0.05 + 0.7*i/n" trace_conventional = StateMonitor(neurons_conventional, "v", record=50) trace_GSL = StateMonitor(neurons_GSL, "v", record=50) net = Network(neurons_conventional, neurons_GSL, trace_conventional, trace_GSL) net.run(10 * ms) assert ( max(trace_conventional.v[0] - trace_GSL.v[0]) < max_difference / mV ), "difference between conventional and GSL output is larger than max_difference" # assert not all(trace_conventional.v[0]==trace_GSL.v[0]), \ # ('output of GSL stateupdater is exactly the same as Brians stateupdater (unlikely to be right)') @pytest.mark.standalone_compatible @skip_if_not_implemented def test_GSL_x_variable(): neurons = NeuronGroup( 2, "dx/dt = 300*Hz : 1", threshold="x>1", reset="x=0", method="gsl" ) # just testing compilation run(0 * ms) @pytest.mark.codegen_independent def test_GSL_failing_directory(): def set_dir(arg): prefs.GSL.directory = arg with pytest.raises(PreferenceError): set_dir(1) with pytest.raises(PreferenceError): set_dir("/usr/") with pytest.raises(PreferenceError): set_dir("/usr/blablabla/") @pytest.mark.codegen_independent @skip_if_not_implemented def test_GSL_stochastic(): tau = 20 * ms sigma = 0.015 eqs = """ dx/dt = (1.1 - x) / tau + sigma * (2 / tau)**.5 * xi : 1 """ neuron = NeuronGroup(1, eqs, method="gsl") net = Network(neuron) with pytest.raises(BrianObjectException) as exc: net.run(0 * ms, namespace={"tau": tau, "sigma": sigma}) assert exc_isinstance( exc, UnsupportedEquationsException, raise_not_implemented=True ) @pytest.mark.standalone_compatible @skip_if_not_implemented def test_GSL_error_dimension_mismatch_unit(): eqs = """ dv/dt = (v0 - v)/(10*ms) : volt v0 : volt """ options = {"absolute_error_per_variable": {"v": 1 * nS}} neuron = NeuronGroup( 1, eqs, threshold="v > 10*mV", reset="v = 0*mV", method="gsl", method_options=options, ) net = Network(neuron) with pytest.raises(BrianObjectException) as exc: net.run(0 * ms, namespace={}) assert exc_isinstance(exc, DimensionMismatchError, raise_not_implemented=True) @pytest.mark.standalone_compatible @skip_if_not_implemented def test_GSL_error_dimension_mismatch_dimensionless1(): eqs = """ dv/dt = (v0 - v)/(10*ms) : 1 v0 : 1 """ options = {"absolute_error_per_variable": {"v": 1 * mV}} neuron = NeuronGroup( 1, eqs, threshold="v > 10", reset="v = 0", method="gsl", method_options=options ) net = Network(neuron) with pytest.raises(BrianObjectException) as exc: net.run(0 * ms, namespace={}) assert exc_isinstance(exc, DimensionMismatchError, raise_not_implemented=True) @pytest.mark.standalone_compatible @skip_if_not_implemented def test_GSL_error_dimension_mismatch_dimensionless2(): eqs = """ dv/dt = (v0 - v)/(10*ms) : volt v0 : volt """ options = {"absolute_error_per_variable": {"v": 1e-3}} neuron = NeuronGroup( 1, eqs, threshold="v > 10*mV", reset="v = 0*mV", method="gsl", method_options=options, ) net = Network(neuron) with pytest.raises(BrianObjectException) as exc: net.run(0 * ms, namespace={}) assert exc_isinstance(exc, DimensionMismatchError, raise_not_implemented=True) @pytest.mark.standalone_compatible @skip_if_not_implemented def test_GSL_error_nonexisting_variable(): eqs = """ dv/dt = (v0 - v)/(10*ms) : volt v0 : volt """ options = {"absolute_error_per_variable": {"dummy": 1e-3 * mV}} neuron = NeuronGroup( 1, eqs, threshold="v > 10*mV", reset="v = 0*mV", method="gsl", method_options=options, ) net = Network(neuron) with pytest.raises(BrianObjectException) as exc: net.run(0 * ms, namespace={}) assert exc_isinstance(exc, KeyError, raise_not_implemented=True) @pytest.mark.standalone_compatible @skip_if_not_implemented def test_GSL_error_incorrect_error_format(): eqs = """ dv/dt = (v0 - v)/(10*ms) : volt v0 : volt """ options = {"absolute_error_per_variable": object()} neuron = NeuronGroup( 1, eqs, threshold="v > 10*mV", reset="v = 0*mV", method="gsl", method_options=options, ) net = Network(neuron) options2 = {"absolute_error": "not a float"} neuron2 = NeuronGroup( 1, eqs, threshold="v > 10*mV", reset="v = 0*mV", method="gsl", method_options=options2, ) net2 = Network(neuron2) with pytest.raises(BrianObjectException) as exc: net.run(0 * ms, namespace={}) assert exc_isinstance(exc, TypeError, raise_not_implemented=True) with pytest.raises(BrianObjectException) as exc: net2.run(0 * ms, namespace={}) assert exc_isinstance(exc, TypeError, raise_not_implemented=True) @pytest.mark.standalone_compatible @skip_if_not_implemented def test_GSL_error_nonODE_variable(): eqs = """ dv/dt = (v0 - v)/(10*ms) : volt v0 : volt """ options = {"absolute_error_per_variable": {"v0": 1e-3 * mV}} neuron = NeuronGroup( 1, eqs, threshold="v > 10*mV", reset="v = 0*mV", method="gsl", method_options=options, ) net = Network(neuron) with pytest.raises(BrianObjectException) as exc: net.run(0 * ms, namespace={}) assert exc_isinstance(exc, KeyError, raise_not_implemented=True) @pytest.mark.standalone_compatible @skip_if_not_implemented def test_GSL_error_bounds(): runtime = 50 * ms error1 = 1e-2 * volt error2 = 1e-4 * volt error3 = 1e-6 * volt # default error eqs = """ dv/dt = (stimulus(t) + -v)/(.1*ms) : volt """ stimulus = TimedArray(rand(int(runtime / (10 * ms))) * 3 * volt, dt=5 * ms) neuron1 = NeuronGroup( 1, model=eqs, reset="v=0*mV", threshold="v>10*volt", method="gsl", method_options={"absolute_error_per_variable": {"v": error1}}, dt=1 * ms, ) neuron2 = NeuronGroup( 1, model=eqs, reset="v=0*mV", threshold="v>10*volt", method="gsl", method_options={"absolute_error_per_variable": {"v": error2}}, dt=1 * ms, ) neuron3 = NeuronGroup( 1, model=eqs, reset="v=0*mV", threshold="v>10*volt", method="gsl", method_options={"absolute_error_per_variable": {}}, dt=1 * ms, ) # Uses default error neuron_control = NeuronGroup(1, model=eqs, method="linear", dt=1 * ms) mon1 = StateMonitor(neuron1, "v", record=True) mon2 = StateMonitor(neuron2, "v", record=True) mon3 = StateMonitor(neuron3, "v", record=True) mon_control = StateMonitor(neuron_control, "v", record=True) run(runtime) err1 = abs(mon1.v[0] - mon_control.v[0]) err2 = abs(mon2.v[0] - mon_control.v[0]) err3 = abs(mon3.v[0] - mon_control.v[0]) assert ( max(err1) < error1 ), f"Error bound exceeded, error bound: {error1:e}, obtained error: {max(err1):e}" assert max(err2) < error2, "Error bound exceeded" assert max(err3) < error3, "Error bound exceeded" assert max(err1) > max( err2 ), "The simulation with smaller error bound produced a bigger maximum error" assert max(err2) > max( err3 ), "The simulation with smaller error bound produced a bigger maximum error" @pytest.mark.standalone_compatible @skip_if_not_implemented def test_GSL_non_autonomous(): eqs = """dv/dt = sin(2*pi*freq*t)/ms : 1 freq : Hz""" neuron = NeuronGroup(10, eqs, method="gsl") neuron.freq = "i*10*Hz + 10*Hz" neuron2 = NeuronGroup(10, eqs, method="euler") neuron2.freq = "i*10*Hz + 10*Hz" mon = StateMonitor(neuron, "v", record=True) mon2 = StateMonitor(neuron2, "v", record=True) run(20 * ms) abs_err = np.abs(mon.v.T - mon2.v.T) max_allowed = 1000 * np.finfo(prefs.core.default_float_dtype).eps assert np.max(abs_err) < max_allowed @pytest.mark.standalone_compatible @skip_if_not_implemented def test_GSL_non_autonomous(): eqs = """dv/dt = sin(2*pi*freq*t)/ms : 1 freq : Hz""" neuron = NeuronGroup(10, eqs, method="gsl") neuron.freq = "i*10*Hz + 10*Hz" neuron2 = NeuronGroup(10, eqs, method="euler") neuron2.freq = "i*10*Hz + 10*Hz" mon = StateMonitor(neuron, "v", record=True) mon2 = StateMonitor(neuron2, "v", record=True) run(20 * ms) abs_err = np.abs(mon.v.T - mon2.v.T) max_allowed = 1000 * np.finfo(prefs.core.default_float_dtype).eps assert np.max(abs_err) < max_allowed @pytest.mark.standalone_compatible @skip_if_not_implemented def test_GSL_refractory(): eqs = """dv/dt = 99.99*Hz : 1 (unless refractory)""" neuron = NeuronGroup( 1, eqs, method="gsl", threshold="v>1", reset="v=0", refractory=3 * ms ) neuron2 = NeuronGroup( 1, eqs, method="euler", threshold="v>1", reset="v=0", refractory=3 * ms ) mon = SpikeMonitor(neuron, "v") mon2 = SpikeMonitor(neuron2, "v") run(20 * ms) assert mon.count[0] == mon2.count[0] @skip_if_not_implemented def test_GSL_save_step_count(): eqs = """ dv/dt = -v/(.1*ms) : volt """ neuron = NeuronGroup( 1, model=eqs, method="gsl", method_options={"save_step_count": True}, dt=1 * ms ) run(0 * ms) mon = StateMonitor(neuron, "_step_count", record=True, when="end") run(10 * ms) assert mon._step_count[0][0] > 0, "Monitor did not save GSL step count" HH_namespace = { "Cm": 1 * ufarad * cm**-2, "gl": 5e-5 * siemens * cm**-2, "El": -65 * mV, "EK": -90 * mV, "ENa": 50 * mV, "g_na": 100 * msiemens * cm**-2, "g_kd": 30 * msiemens * cm**-2, "VT": -63 * mV, } HH_eqs = Equations( """ dv/dt = (gl*(El-v) - g_na*(m*m*m)*h*(v-ENa) - g_kd*(n*n*n*n)*(v-EK) + I)/Cm : volt dm/dt = 0.32*(mV**-1)*(13.*mV-v+VT)/ (exp((13.*mV-v+VT)/(4.*mV))-1.)/ms*(1-m)-0.28*(mV**-1)*(v-VT-40.*mV)/ (exp((v-VT-40.*mV)/(5.*mV))-1.)/ms*m : 1 dn/dt = 0.032*(mV**-1)*(15.*mV-v+VT)/ (exp((15.*mV-v+VT)/(5.*mV))-1.)/ms*(1.-n)-.5*exp((10.*mV-v+VT)/(40.*mV))/ms*n : 1 dh/dt = 0.128*exp((17.*mV-v+VT)/(18.*mV))/ms*(1.-h)-4./(1+exp((40.*mV-v+VT)/(5.*mV)))/ms*h : 1 I : amp/metre**2 """ ) @pytest.mark.standalone_compatible @skip_if_not_implemented def test_GSL_fixed_timestep_big_dt_small_error(): # should raise integration error neuron = NeuronGroup( 1, model=HH_eqs, threshold="v > -40*mV", refractory="v > -40*mV", method="gsl", method_options={"adaptable_timestep": False, "absolute_error": 1e-12}, dt=0.001 * ms, namespace=HH_namespace, ) neuron.I = 0.7 * nA / (20000 * umetre**2) neuron.v = HH_namespace["El"] net = Network(neuron) with pytest.raises((BrianObjectException, RuntimeError)): net.run(10 * ms) @pytest.mark.codegen_independent @skip_if_not_implemented def test_GSL_internal_variable(): with pytest.raises(SyntaxError): Equations("d_p/dt = 300*Hz : 1") @pytest.mark.standalone_compatible @skip_if_not_implemented def test_GSL_method_options_neurongroup(): neuron1 = NeuronGroup( 1, model="dp/dt = 300*Hz : 1", method="gsl", method_options={"adaptable_timestep": True}, ) neuron2 = NeuronGroup( 1, model="dp/dt = 300*Hz : 1", method="gsl", method_options={"adaptable_timestep": False}, ) run(0 * ms) assert "if (gsl_odeiv2_driver_apply_fixed_step" not in str( neuron1.state_updater.codeobj.code ), "This neuron should not call gsl_odeiv2_driver_apply_fixed_step()" assert "if (gsl_odeiv2_driver_apply_fixed_step" in str( neuron2.state_updater.codeobj.code ), "This neuron should call gsl_odeiv2_driver_apply_fixed_step()" @pytest.mark.standalone_compatible @skip_if_not_implemented def test_GSL_method_options_spatialneuron(): morpho = Soma(30 * um) eqs = """ Im = g * v : amp/meter**2 dg/dt = siemens/metre**2/second : siemens/metre**2 """ neuron1 = SpatialNeuron( morphology=morpho, model=eqs, Cm=1 * uF / cm**2, Ri=100 * ohm * cm, method="gsl_rkf45", method_options={"adaptable_timestep": True}, ) neuron2 = SpatialNeuron( morphology=morpho, model=eqs, Cm=1 * uF / cm**2, Ri=100 * ohm * cm, method="gsl_rkf45", method_options={"adaptable_timestep": False}, ) run(0 * ms) assert "if (gsl_odeiv2_driver_apply_fixed_step" not in str( neuron1.state_updater.codeobj.code ), "This neuron should not call gsl_odeiv2_driver_apply_fixed_step()" assert "if (gsl_odeiv2_driver_apply_fixed_step" in str( neuron2.state_updater.codeobj.code ), "This neuron should call gsl_odeiv2_driver_apply_fixed_step()" @skip_if_not_implemented def test_GSL_method_options_synapses(): N = 1000 taum = 10 * ms taupre = 20 * ms taupost = taupre Ee = 0 * mV vt = -54 * mV vr = -60 * mV El = -74 * mV taue = 5 * ms F = 15 * Hz gmax = 0.01 dApre = 0.01 dApost = -dApre * taupre / taupost * 1.05 dApost *= gmax dApre *= gmax eqs_neurons = """ dv/dt = (ge * (Ee-vr) + El - v) / taum : volt dge/dt = -ge / taue : 1 """ poisson_input = PoissonGroup(N, rates=F) neurons = NeuronGroup( 1, eqs_neurons, threshold="v>vt", reset="v = vr", method="gsl_rkf45" ) S1 = Synapses( poisson_input, neurons, """ w : 1 dApre/dt = -Apre / taupre : 1 (clock-driven) dApost/dt = -Apost / taupost : 1 (clock-driven) """, method="gsl_rkf45", method_options={"adaptable_timestep": True}, ) S2 = Synapses( poisson_input, neurons, """ w : 1 dApre/dt = -Apre / taupre : 1 (clock-driven) dApost/dt = -Apost / taupost : 1 (clock-driven) """, method="gsl_rkf45", method_options={"adaptable_timestep": False}, ) run(0 * ms) assert "if (gsl_odeiv2_driver_apply_fixed_step" not in str( S1.state_updater.codeobj.code ), "This state_updater should not call gsl_odeiv2_driver_apply_fixed_step()" assert "if (gsl_odeiv2_driver_apply_fixed_step" in str( S2.state_updater.codeobj.code ), "This state_updater should call gsl_odeiv2_driver_apply_fixed_step()" if __name__ == "__main__": from _pytest.outcomes import Skipped for t in [ test_GSL_stateupdater_basic, test_GSL_different_clocks, test_GSL_default_function, test_GSL_user_defined_function, test_GSL_x_variable, test_GSL_failing_directory, test_GSL_stochastic, test_GSL_error_dimension_mismatch_unit, test_GSL_error_dimension_mismatch_dimensionless1, test_GSL_error_dimension_mismatch_dimensionless2, test_GSL_error_nonexisting_variable, test_GSL_error_incorrect_error_format, test_GSL_error_nonODE_variable, test_GSL_error_bounds, test_GSL_non_autonomous, test_GSL_refractory, test_GSL_save_step_count, test_GSL_fixed_timestep_big_dt_small_error, test_GSL_method_options_neurongroup, test_GSL_method_options_spatialneuron, test_GSL_method_options_synapses, ]: try: t() except Skipped as ex: print(f"Skipped: {t.__name__} ({str(ex)})") brian2-2.5.4/brian2/tests/test_base.py000066400000000000000000000055011445201106100175340ustar00rootroot00000000000000import pytest from numpy.testing import assert_equal from brian2 import * from brian2.devices.device import reinit_and_delete from brian2.tests.utils import assert_allclose class DerivedBrianObject(BrianObject): def __init__(self, name="derivedbrianobject*"): super().__init__(name=name) def __str__(self): return self.name __repr__ = __str__ @pytest.mark.codegen_independent def test_base(): x = DerivedBrianObject("x") y = DerivedBrianObject("y") assert_equal(x.when, "start") assert_equal(x.order, 0) assert_equal(len(x.contained_objects), 0) with pytest.raises(AttributeError): setattr(x, "contained_objects", []) x.contained_objects.append(y) assert_equal(len(x.contained_objects), 1) assert x.contained_objects[0] is y assert_equal(x.active, True) assert_equal(y.active, True) y.active = False assert_equal(x.active, True) assert_equal(y.active, False) y.active = True assert_equal(x.active, True) assert_equal(y.active, True) x.active = False assert_equal(x.active, False) assert_equal(y.active, False) @pytest.mark.codegen_independent def test_names(): obj = BrianObject() obj2 = BrianObject() obj3 = DerivedBrianObject() assert_equal(obj.name, "brianobject") assert_equal(obj2.name, "brianobject_1") assert_equal(obj3.name, "derivedbrianobject") @pytest.mark.codegen_independent def test_duplicate_names(): # duplicate names are allowed, as long as they are not part of the # same network obj1 = BrianObject(name="name1") obj2 = BrianObject(name="name2") obj3 = BrianObject(name="name") obj4 = BrianObject(name="name") net = Network(obj1, obj2) # all is good net.run(0 * ms) net = Network(obj3, obj4) with pytest.raises(ValueError): net.run(0 * ms) @pytest.mark.standalone_compatible @pytest.mark.multiple_runs def test_active_flag(): G = NeuronGroup(1, "dv/dt = 1/ms : 1") mon = StateMonitor(G, "v", record=0) mon.active = False run(1 * ms) mon.active = True G.active = False run(1 * ms) device.build(direct_call=False, **device.build_options) # Monitor should start recording at 1ms # Neurongroup should not integrate after 1ms (but should have integrated before) assert_allclose(mon[0].t[0], 1 * ms) assert_allclose(mon[0].v, 1.0) @pytest.mark.codegen_independent def test_version(): # Check that determining the Brian version works correctly import brian2 version = brian2.__version__ assert version.startswith("2.") # Check that the version tuple is correct version_tuple = brian2.__version_tuple__ assert version_tuple == tuple(int(i) for i in version.split(".")[:3]) if __name__ == "__main__": test_base() test_names() test_duplicate_names() test_active_flag() brian2-2.5.4/brian2/tests/test_clocks.py000066400000000000000000000034761445201106100201110ustar00rootroot00000000000000import pytest from numpy.testing import assert_array_equal, assert_equal from brian2 import * from brian2.utils.logger import catch_logs @pytest.mark.codegen_independent def test_clock_attributes(): clock = Clock(dt=1 * ms) assert_array_equal(clock.t, 0 * second) assert_array_equal(clock.timestep, 0) assert_array_equal(clock.dt, 1 * ms) @pytest.mark.codegen_independent def test_clock_dt_change(): clock = Clock(dt=1 * ms) # at time 0s, all dt changes should be allowed clock.dt = 0.75 * ms clock._set_t_update_dt() clock.dt = 2.5 * ms clock._set_t_update_dt() clock.dt = 1 * ms clock._set_t_update_dt() # at 0.1ms only changes that are still representable as an integer of the # current time 1s are allowed clock.dt = 0.1 * ms clock._set_t_update_dt() clock.dt = 0.05 * ms clock._set_t_update_dt(target_t=0.1 * ms) clock.dt = 0.1 * ms clock._set_t_update_dt(target_t=0.1 * ms) clock.dt = 0.3 * ms with pytest.raises(ValueError): clock._set_t_update_dt(target_t=0.1 * ms) @pytest.mark.codegen_independent def test_defaultclock(): defaultclock.dt = 1 * ms assert_equal(defaultclock.dt, 1 * ms) assert defaultclock.name == "defaultclock" @pytest.mark.codegen_independent def test_set_interval_warning(): clock = Clock(dt=0.1 * ms) with catch_logs() as logs: clock.set_interval(0 * second, 1000 * second) # no problem assert len(logs) == 0 with catch_logs() as logs: clock.set_interval(0 * second, 10000000000 * second) # too long assert len(logs) == 1 assert logs[0][1].endswith("many_timesteps") if __name__ == "__main__": test_clock_attributes() restore_initial_state() test_clock_dt_change() restore_initial_state() test_defaultclock() test_set_interval_warning() brian2-2.5.4/brian2/tests/test_codegen.py000066400000000000000000000546411445201106100202370ustar00rootroot00000000000000import json import os import platform import socket from collections import namedtuple import numpy as np import pytest from brian2 import _cache_dirs_and_extensions, clear_cache, prefs from brian2.codegen.codeobject import CodeObject from brian2.codegen.cpp_prefs import compiler_supports_c99, get_compiler_and_args from brian2.codegen.optimisation import optimise_statements from brian2.codegen.runtime.cython_rt import CythonCodeObject from brian2.codegen.statements import Statement from brian2.codegen.translation import ( analyse_identifiers, get_identifiers_recursively, make_statements, parse_statement, ) from brian2.core.functions import DEFAULT_CONSTANTS, DEFAULT_FUNCTIONS, Function from brian2.core.variables import ArrayVariable, Constant, Subexpression, Variable from brian2.devices.device import auto_target, device from brian2.parsing.sympytools import str_to_sympy, sympy_to_str from brian2.units import ms, second from brian2.units.fundamentalunits import Unit from brian2.utils.logger import catch_logs FakeGroup = namedtuple("FakeGroup", ["variables"]) @pytest.mark.codegen_independent def test_auto_target(): # very basic test that the "auto" codegen target is useable assert issubclass(auto_target(), CodeObject) @pytest.mark.codegen_independent def test_analyse_identifiers(): """ Test that the analyse_identifiers function works on a simple clear example. """ code = """ a = b+c d = e+f """ known = { "b": Variable(name="b"), "c": Variable(name="c"), "d": Variable(name="d"), "g": Variable(name="g"), } defined, used_known, dependent = analyse_identifiers(code, known) assert "a" in defined # There might be an additional constant added by the # loop-invariant optimisation assert used_known == {"b", "c", "d"} assert dependent == {"e", "f"} @pytest.mark.codegen_independent def test_get_identifiers_recursively(): """ Test finding identifiers including subexpressions. """ variables = { "sub1": Subexpression( name="sub1", dtype=np.float32, expr="sub2 * z", owner=FakeGroup(variables={}), device=None, ), "sub2": Subexpression( name="sub2", dtype=np.float32, expr="5 + y", owner=FakeGroup(variables={}), device=None, ), "x": Variable(name="x"), } identifiers = get_identifiers_recursively(["_x = sub1 + x"], variables) assert identifiers == {"x", "_x", "y", "z", "sub1", "sub2"} @pytest.mark.codegen_independent def test_write_to_subexpression(): variables = { "a": Subexpression( name="a", dtype=np.float32, owner=FakeGroup(variables={}), device=None, expr="2*z", ), "z": Variable(name="z"), } # Writing to a subexpression is not allowed code = "a = z" with pytest.raises(SyntaxError): make_statements(code, variables, np.float32) @pytest.mark.codegen_independent def test_repeated_subexpressions(): variables = { "a": Subexpression( name="a", dtype=np.float32, owner=FakeGroup(variables={}), device=None, expr="2*z", ), "x": Variable(name="x"), "y": Variable(name="y"), "z": Variable(name="z"), } # subexpression a (referring to z) is used twice, but can be reused the # second time (no change to z) code = """ x = a y = a """ scalar_stmts, vector_stmts = make_statements(code, variables, np.float32) assert len(scalar_stmts) == 0 assert [stmt.var for stmt in vector_stmts] == ["a", "x", "y"] assert vector_stmts[0].constant code = """ x = a z *= 2 """ scalar_stmts, vector_stmts = make_statements(code, variables, np.float32) assert len(scalar_stmts) == 0 assert [stmt.var for stmt in vector_stmts] == ["a", "x", "z"] # Note that we currently do not mark the subexpression as constant in this # case, because its use after the "z *=2" line would actually redefine it. # Our algorithm is currently not smart enough to detect that it is actually # not used afterwards # a refers to z, therefore we have to redefine a after z changed, and a # cannot be constant code = """ x = a z *= 2 y = a """ scalar_stmts, vector_stmts = make_statements(code, variables, np.float32) assert len(scalar_stmts) == 0 assert [stmt.var for stmt in vector_stmts] == ["a", "x", "z", "a", "y"] assert not any(stmt.constant for stmt in vector_stmts) @pytest.mark.codegen_independent def test_nested_subexpressions(): """ This test checks that code translation works with nested subexpressions. """ code = """ x = a + b + c c = 1 x = a + b + c d = 1 x = a + b + c """ variables = { "a": Subexpression( name="a", dtype=np.float32, owner=FakeGroup(variables={}), device=None, expr="b*b+d", ), "b": Subexpression( name="b", dtype=np.float32, owner=FakeGroup(variables={}), device=None, expr="c*c*c", ), "c": Variable(name="c"), "d": Variable(name="d"), } scalar_stmts, vector_stmts = make_statements(code, variables, np.float32) assert len(scalar_stmts) == 0 evalorder = "".join(stmt.var for stmt in vector_stmts) # This is the order that variables ought to be evaluated in (note that # previously this test did not expect the last "b" evaluation, because its # value did not change (c was not changed). We have since removed this # subexpression caching, because it did not seem to apply in practical # use cases) assert evalorder == "baxcbaxdbax" @pytest.mark.codegen_independent def test_apply_loop_invariant_optimisation(): variables = { "v": Variable("v", scalar=False), "w": Variable("w", scalar=False), "dt": Constant("dt", dimensions=second.dim, value=0.1 * ms), "tau": Constant("tau", dimensions=second.dim, value=10 * ms), "exp": DEFAULT_FUNCTIONS["exp"], } statements = [ Statement("v", "=", "dt*w*exp(-dt/tau)/tau + v*exp(-dt/tau)", "", np.float32), Statement("w", "=", "w*exp(-dt/tau)", "", np.float32), ] scalar, vector = optimise_statements([], statements, variables) # The optimisation should pull out at least exp(-dt / tau) assert len(scalar) >= 1 assert np.issubdtype(scalar[0].dtype, np.floating) assert scalar[0].var == "_lio_1" assert len(vector) == 2 assert all("_lio_" in stmt.expr for stmt in vector) @pytest.mark.codegen_independent def test_apply_loop_invariant_optimisation_integer(): variables = { "v": Variable("v", scalar=False), "N": Constant("N", 10), "b": Variable("b", scalar=True, dtype=int), "c": Variable("c", scalar=True, dtype=int), "d": Variable("d", scalar=True, dtype=int), "y": Variable("y", scalar=True, dtype=float), "z": Variable("z", scalar=True, dtype=float), "w": Variable("w", scalar=True, dtype=float), } statements = [ Statement("v", "=", "v % (2*3*N)", "", np.float32), # integer version doesn't get rewritten but float version does Statement("a", ":=", "b//(c//d)", "", int), Statement("x", ":=", "y/(z/w)", "", float), ] scalar, vector = optimise_statements([], statements, variables) assert len(scalar) == 3 assert np.issubdtype(scalar[0].dtype, np.signedinteger) assert scalar[0].var == "_lio_1" expr = scalar[0].expr.replace(" ", "") assert expr == "6*N" or expr == "N*6" assert np.issubdtype(scalar[1].dtype, np.signedinteger) assert scalar[1].var == "_lio_2" expr = scalar[1].expr.replace(" ", "") assert expr == "b//(c//d)" assert np.issubdtype(scalar[2].dtype, np.floating) assert scalar[2].var == "_lio_3" expr = scalar[2].expr.replace(" ", "") assert expr == "(y*w)/z" or expr == "(w*y)/z" @pytest.mark.codegen_independent def test_apply_loop_invariant_optimisation_boolean(): variables = { "v1": Variable("v1", scalar=False), "v2": Variable("v2", scalar=False), "N": Constant("N", 10), "b": Variable("b", scalar=True, dtype=bool), "c": Variable("c", scalar=True, dtype=bool), "int": DEFAULT_FUNCTIONS["int"], "foo": Function( lambda x: None, arg_units=[Unit(1)], return_unit=Unit(1), arg_types=["boolean"], return_type="float", stateless=False, ), } # The calls for "foo" cannot be pulled out, since foo is marked as stateful statements = [ Statement("v1", "=", "1.0*int(b and c)", "", np.float32), Statement("v1", "=", "1.0*foo(b and c)", "", np.float32), Statement("v2", "=", "int(not b and True)", "", np.float32), Statement("v2", "=", "foo(not b and True)", "", np.float32), ] scalar, vector = optimise_statements([], statements, variables) assert len(scalar) == 4 assert scalar[0].expr == "1.0 * int(b and c)" assert scalar[1].expr == "b and c" assert scalar[2].expr == "int((not b) and True)" assert scalar[3].expr == "(not b) and True" assert len(vector) == 4 assert vector[0].expr == "_lio_1" assert vector[1].expr == "foo(_lio_2)" assert vector[2].expr == "_lio_3" assert vector[3].expr == "foo(_lio_4)" @pytest.mark.codegen_independent def test_apply_loop_invariant_optimisation_no_optimisation(): variables = { "v1": Variable("v1", scalar=False), "v2": Variable("v2", scalar=False), "N": Constant("N", 10), "s1": Variable("s1", scalar=True, dtype=float), "s2": Variable("s2", scalar=True, dtype=float), "rand": DEFAULT_FUNCTIONS["rand"], } statements = [ # This should not be simplified to 0! Statement("v1", "=", "rand() - rand()", "", float), Statement("v1", "=", "3*rand() - 3*rand()", "", float), Statement("v1", "=", "3*rand() - ((1+2)*rand())", "", float), # This should not pull out rand()*N Statement("v1", "=", "s1*rand()*N", "", float), Statement("v1", "=", "s2*rand()*N", "", float), # This is not important mathematically, but it would change the numbers # that are generated Statement("v1", "=", "0*rand()*N", "", float), Statement("v1", "=", "0/rand()*N", "", float), ] scalar, vector = optimise_statements([], statements, variables) for vs in vector[:3]: assert ( vs.expr.count("rand()") == 2 ), f"Expression should still contain two rand() calls, but got {str(vs)}" for vs in vector[3:]: assert ( vs.expr.count("rand()") == 1 ), f"Expression should still contain a rand() call, but got {str(vs)}" @pytest.mark.codegen_independent def test_apply_loop_invariant_optimisation_simplification(): variables = { "v1": Variable("v1", scalar=False), "v2": Variable("v2", scalar=False), "i1": Variable("i1", scalar=False, dtype=int), "N": Constant("N", 10), } statements = [ # Should be simplified to 0.0 Statement("v1", "=", "v1 - v1", "", float), Statement("v1", "=", "N*v1 - N*v1", "", float), Statement("v1", "=", "v1*N * 0", "", float), Statement("v1", "=", "v1 * 0", "", float), Statement("v1", "=", "v1 * 0.0", "", float), Statement("v1", "=", "0.0 / (v1*N)", "", float), # Should be simplified to 0 Statement("i1", "=", "i1*N * 0", "", int), Statement("i1", "=", "0 * i1", "", int), Statement("i1", "=", "0 * i1*N", "", int), Statement("i1", "=", "i1 * 0", "", int), # Should be simplified to v1*N Statement("v2", "=", "0 + v1*N", "", float), Statement("v2", "=", "v1*N + 0.0", "", float), Statement("v2", "=", "v1*N - 0", "", float), Statement("v2", "=", "v1*N - 0.0", "", float), Statement("v2", "=", "1 * v1*N", "", float), Statement("v2", "=", "1.0 * v1*N", "", float), Statement("v2", "=", "v1*N / 1.0", "", float), Statement("v2", "=", "v1*N / 1", "", float), # Should be simplified to i1 Statement("i1", "=", "i1*1", "", int), Statement("i1", "=", "i1//1", "", int), Statement("i1", "=", "i1+0", "", int), Statement("i1", "=", "0+i1", "", int), Statement("i1", "=", "i1-0", "", int), # Should *not* be simplified (because it would change the type, # important for integer division, for example) Statement("v1", "=", "i1*1.0", "", float), Statement("v1", "=", "1.0*i1", "", float), Statement("v1", "=", "i1/1.0", "", float), Statement("v1", "=", "i1/1", "", float), Statement("v1", "=", "i1+0.0", "", float), Statement("v1", "=", "0.0+i1", "", float), Statement("v1", "=", "i1-0.0", "", float), ## Should *not* be simplified, flooring division by 1 changes the value Statement("v1", "=", "v2//1.0", "", float), Statement("i1", "=", "i1//1.0", "", float), # changes type ] scalar, vector = optimise_statements([], statements, variables) assert len(scalar) == 0 for s in vector[:6]: assert s.expr == "0.0" for s in vector[6:10]: assert s.expr == "0", s.expr # integer for s in vector[10:18]: expr = s.expr.replace(" ", "") assert expr == "v1*N" or expr == "N*v1" for s in vector[18:23]: expr = s.expr.replace(" ", "") assert expr == "i1" for s in vector[23:27]: expr = s.expr.replace(" ", "") assert expr == "1.0*i1" or expr == "i1*1.0" or expr == "i1/1.0" for s in vector[27:30]: expr = s.expr.replace(" ", "") assert expr == "0.0+i1" or expr == "i1+0.0" for s in vector[30:31]: expr = s.expr.replace(" ", "") assert expr == "v2//1.0" or expr == "v2//1" for s in vector[31:]: expr = s.expr.replace(" ", "") assert expr == "i1//1.0" @pytest.mark.codegen_independent def test_apply_loop_invariant_optimisation_constant_evaluation(): variables = { "v1": Variable("v1", scalar=False), "v2": Variable("v2", scalar=False), "i1": Variable("i1", scalar=False, dtype=int), "N": Constant("N", 10), "s1": Variable("s1", scalar=True, dtype=float), "s2": Variable("s2", scalar=True, dtype=float), "exp": DEFAULT_FUNCTIONS["exp"], } statements = [ Statement("v1", "=", "v1 * (1 + 2 + 3)", "", float), Statement("v1", "=", "exp(N)*v1", "", float), Statement("v1", "=", "exp(0)*v1", "", float), ] scalar, vector = optimise_statements([], statements, variables) # exp(N) should be pulled out of the vector statements, the rest should be # evaluated in place assert len(scalar) == 1 assert scalar[0].expr == "exp(N)" assert len(vector) == 3 expr = vector[0].expr.replace(" ", "") assert expr == "_lio_1*v1" or "v1*_lio_1" expr = vector[1].expr.replace(" ", "") assert expr == "6.0*v1" or "v1*6.0" assert vector[2].expr == "v1" @pytest.mark.codegen_independent def test_automatic_augmented_assignments(): # We test that statements that could be rewritten as augmented assignments # are correctly rewritten (using sympy to test for symbolic equality) variables = { "x": ArrayVariable("x", owner=None, size=10, device=device), "y": ArrayVariable("y", owner=None, size=10, device=device), "z": ArrayVariable("y", owner=None, size=10, device=device), "b": ArrayVariable("b", owner=None, size=10, dtype=bool, device=device), "clip": DEFAULT_FUNCTIONS["clip"], "inf": DEFAULT_CONSTANTS["inf"], } statements = [ # examples that should be rewritten # Note that using our approach, we will never get -= or /= but always # the equivalent += or *= statements ("x = x + 1.0", "x += 1.0"), ("x = 2.0 * x", "x *= 2.0"), ("x = x - 3.0", "x += -3.0"), ("x = x/2.0", "x *= 0.5"), ("x = y + (x + 1.0)", "x += y + 1.0"), ("x = x + x", "x *= 2.0"), ("x = x + y + z", "x += y + z"), ("x = x + y + z", "x += y + z"), # examples that should not be rewritten ("x = 1.0/x", "x = 1.0/x"), ("x = 1.0", "x = 1.0"), ("x = 2.0*(x + 1.0)", "x = 2.0*(x + 1.0)"), ("x = clip(x + y, 0.0, inf)", "x = clip(x + y, 0.0, inf)"), ("b = b or False", "b = b or False"), ] for orig, rewritten in statements: scalar, vector = make_statements(orig, variables, np.float32) try: # we augment the assertion error with the original statement assert ( len(scalar) == 0 ), f"Did not expect any scalar statements but got {str(scalar)}" assert ( len(vector) == 1 ), f"Did expect a single statement but got {str(vector)}" statement = vector[0] expected_var, expected_op, expected_expr, _ = parse_statement(rewritten) assert ( expected_var == statement.var ), f"expected write to variable {expected_var}, not to {statement.var}" assert ( expected_op == statement.op ), f"expected operation {expected_op}, not {statement.op}" # Compare the two expressions using sympy to allow for different order etc. sympy_expected = str_to_sympy(expected_expr) sympy_actual = str_to_sympy(statement.expr) assert sympy_expected == sympy_actual, ( f"RHS expressions '{sympy_to_str(sympy_expected)}' and" f" '{sympy_to_str(sympy_actual)}' are not identical" ) except AssertionError as ex: raise AssertionError( f"Transformation for statement '{orig}' gave an unexpected result: {ex}" ) def test_clear_cache(): target = prefs.codegen.target if target == "numpy": assert "numpy" not in _cache_dirs_and_extensions with pytest.raises(ValueError): clear_cache("numpy") else: assert target in _cache_dirs_and_extensions cache_dir, _ = _cache_dirs_and_extensions[target] # Create a file that should not be there fname = os.path.join(cache_dir, "some_file.py") open(fname, "w").close() # clear_cache should refuse to clear the directory with pytest.raises(IOError): clear_cache(target) os.remove(fname) @pytest.mark.skipif( platform.system() == "Windows", reason="CC and CXX variables are ignored on Windows.", ) def test_compiler_error(): # In particular on OSX with clang in a conda environment, compilation might fail. # Switching to a system gcc might help in such cases. Make sure that the error # message mentions that. old_CC = os.environ.get("CC", None) old_CXX = os.environ.get("CXX", None) os.environ.update({"CC": "non-existing-compiler", "CXX": "non-existing-compiler++"}) try: with catch_logs() as l: assert not CythonCodeObject.is_available() assert len(l) > 0 # There are additional warnings about compiler flags last_warning = l[-1] assert last_warning[1].endswith(".failed_compile_test") assert "CC" in last_warning[2] and "CXX" in last_warning[2] finally: if old_CC: os.environ["CC"] = old_CC else: del os.environ["CC"] if old_CXX: os.environ["CXX"] = old_CXX else: del os.environ["CXX"] def test_compiler_c99(): # On a user's computer, we do not know whether the compiler actually # has C99 support, so we just check whether the test does not raise an # error # The compiler check previously created spurious '-.o' files (see #1348) if os.path.exists("-.o"): os.remove("-.o") c99_support = compiler_supports_c99() assert not os.path.exists("-.o") # On our Azure test server we know that the compilers support C99 if os.environ.get("AGENT_OS", ""): assert c99_support def test_cpp_flags_support(): from distutils.ccompiler import get_default_compiler from brian2.codegen.cpp_prefs import _compiler_flag_compatibility _compiler_flag_compatibility.clear() # make sure cache is empty compiler = get_default_compiler() if compiler == "msvc": pytest.skip("No flag support check for msvc") old_prefs = prefs["codegen.cpp.extra_compile_args"] # Should always be supported prefs["codegen.cpp.extra_compile_args"] = ["-w"] _, compile_args = get_compiler_and_args() assert compile_args == prefs["codegen.cpp.extra_compile_args"] # Should never be supported and raise a warning prefs["codegen.cpp.extra_compile_args"] = ["-invalidxyz"] with catch_logs() as l: _, compile_args = get_compiler_and_args() assert len(l) == 1 and l[0][0] == "WARNING" assert compile_args == [] prefs["codegen.cpp.extra_compile_args"] = old_prefs @pytest.mark.skipif( platform.system() != "Windows", reason="MSVC flags are only relevant on Windows" ) @pytest.mark.skipif( prefs["codegen.target"] == "numpy", reason="Test only relevant for compiled code" ) def test_msvc_flags(): # Very basic test that flags are stored to disk import brian2.codegen.cpp_prefs as cpp_prefs user_dir = os.path.join(os.path.expanduser("~"), ".brian") flag_file = os.path.join(user_dir, "cpu_flags.txt") assert len(cpp_prefs.msvc_arch_flag) assert os.path.exists(flag_file) with open(flag_file, encoding="utf-8") as f: previously_stored_flags = json.load(f) hostname = socket.gethostname() assert hostname in previously_stored_flags assert len(previously_stored_flags[hostname]) if __name__ == "__main__": test_auto_target() test_analyse_identifiers() test_get_identifiers_recursively() test_write_to_subexpression() test_repeated_subexpressions() test_nested_subexpressions() test_apply_loop_invariant_optimisation() test_apply_loop_invariant_optimisation_integer() test_apply_loop_invariant_optimisation_boolean() test_apply_loop_invariant_optimisation_no_optimisation() test_apply_loop_invariant_optimisation_simplification() test_apply_loop_invariant_optimisation_constant_evaluation() test_automatic_augmented_assignments() test_clear_cache() test_msvc_flags() brian2-2.5.4/brian2/tests/test_codestrings.py000066400000000000000000000063761445201106100211610ustar00rootroot00000000000000import numpy as np import pytest import sympy from numpy.testing import assert_equal import brian2 from brian2 import ( DimensionMismatchError, Expression, Hz, Statements, get_dimensions, ms, mV, second, volt, ) from brian2.core.preferences import prefs from brian2.utils.logger import catch_logs def sympy_equals(expr1, expr2): """ Test that whether two string expressions are equal using sympy, allowing e.g. for ``sympy_equals("x * x", "x ** 2") == True``. """ s_expr1 = sympy.nsimplify(sympy.sympify(expr1).expand()) s_expr2 = sympy.nsimplify(sympy.sympify(expr2).expand()) return s_expr1 == s_expr2 @pytest.mark.codegen_independent def test_expr_creation(): """ Test creating expressions. """ expr = Expression("v > 5 * mV") assert expr.code == "v > 5 * mV" assert ( "v" in expr.identifiers and "mV" in expr.identifiers and not "V" in expr.identifiers ) with pytest.raises(SyntaxError): Expression("v 5 * mV") @pytest.mark.codegen_independent def test_split_stochastic(): tau = 5 * ms expr = Expression("(-v + I) / tau") # No stochastic part assert expr.split_stochastic() == (expr, None) # No non-stochastic part -- note that it should return 0 and not None expr = Expression("sigma*xi/tau**.5") non_stochastic, stochastic = expr.split_stochastic() assert sympy_equals(non_stochastic.code, 0) assert "xi" in stochastic assert len(stochastic) == 1 assert sympy_equals(stochastic["xi"].code, "sigma/tau**.5") expr = Expression("(-v + I) / tau + sigma*xi/tau**.5") non_stochastic, stochastic = expr.split_stochastic() assert "xi" in stochastic assert len(stochastic) == 1 assert sympy_equals(non_stochastic.code, "(-v + I) / tau") assert sympy_equals(stochastic["xi"].code, "sigma/tau**.5") expr = Expression("(-v + I) / tau + sigma*xi_1/tau**.5 + xi_2*sigma2/sqrt(tau_2)") non_stochastic, stochastic = expr.split_stochastic() assert set(stochastic.keys()) == {"xi_1", "xi_2"} assert sympy_equals(non_stochastic.code, "(-v + I) / tau") assert sympy_equals(stochastic["xi_1"].code, "sigma/tau**.5") assert sympy_equals(stochastic["xi_2"].code, "sigma2/tau_2**.5") expr = Expression("-v / tau + 1 / xi") with pytest.raises(ValueError): expr.split_stochastic() @pytest.mark.codegen_independent def test_str_repr(): """ Test the string representation of expressions and statements. Assumes that __str__ returns the complete expression/statement string and __repr__ a string of the form "Expression(...)" or "Statements(...)" that can be evaluated. """ expr_string = "(v - I)/ tau" expr = Expression(expr_string) # use sympy to check for equivalence of expressions (terms may have be # re-arranged by sympy) assert sympy_equals(expr_string, str(expr)) assert sympy_equals(expr_string, eval(repr(expr)).code) # Use exact string equivalence for statements statement_string = "v += w" statement = Statements(statement_string) assert str(statement) == "v += w" assert repr(statement) == "Statements('v += w')" if __name__ == "__main__": test_expr_creation() test_split_stochastic() test_str_repr() brian2-2.5.4/brian2/tests/test_complex_examples.py000066400000000000000000000017171445201106100221740ustar00rootroot00000000000000import pytest from brian2 import * from brian2.devices.device import reinit_and_delete @pytest.mark.standalone_compatible def test_cuba(): taum = 20 * ms taue = 5 * ms taui = 10 * ms Vt = -50 * mV Vr = -60 * mV El = -49 * mV eqs = """ dv/dt = (ge+gi-(v-El))/taum : volt (unless refractory) dge/dt = -ge/taue : volt dgi/dt = -gi/taui : volt """ P = NeuronGroup(4000, eqs, threshold="v>Vt", reset="v = Vr", refractory=5 * ms) P.v = "Vr + rand() * (Vt - Vr)" P.ge = 0 * mV P.gi = 0 * mV we = (60 * 0.27 / 10) * mV # excitatory synaptic weight (voltage) wi = (-20 * 4.5 / 10) * mV # inhibitory synaptic weight Ce = Synapses(P, P, on_pre="ge += we") Ci = Synapses(P, P, on_pre="gi += wi") Ce.connect("i<3200", p=0.02) Ci.connect("i>=3200", p=0.02) s_mon = SpikeMonitor(P) run(10 * ms) assert len(Ce) > 0 assert len(Ci) > 0 if __name__ == "__main__": test_cuba() brian2-2.5.4/brian2/tests/test_cpp_standalone.py000066400000000000000000000552231445201106100216220ustar00rootroot00000000000000import os import tempfile import pytest from numpy.testing import assert_equal from brian2 import * from brian2.devices.device import reinit_and_delete, reset_device, set_device from brian2.tests.utils import assert_allclose from brian2.utils.logger import catch_logs @pytest.mark.cpp_standalone @pytest.mark.standalone_only def test_cpp_standalone(): set_device("cpp_standalone", build_on_run=False) ##### Define the model tau = 1 * ms eqs = """ dV/dt = (-40*mV-V)/tau : volt (unless refractory) """ threshold = "V>-50*mV" reset = "V=-60*mV" refractory = 5 * ms N = 1000 G = NeuronGroup( N, eqs, reset=reset, threshold=threshold, refractory=refractory, name="gp" ) G.V = "-i*mV" M = SpikeMonitor(G) S = Synapses(G, G, "w : volt", on_pre="V += w") S.connect("abs(i-j)<5 and i!=j") S.w = 0.5 * mV S.delay = "0*ms" net = Network(G, M, S) net.run(100 * ms) device.build(directory=None, with_output=False) # we do an approximate equality here because depending on minor details of how it was compiled, the results # may be slightly different (if -ffast-math is on) assert len(M.i) >= 17000 and len(M.i) <= 18000 assert len(M.t) == len(M.i) assert M.t[0] == 0.0 reset_device() @pytest.mark.cpp_standalone @pytest.mark.standalone_only def test_multiple_connects(): set_device("cpp_standalone", build_on_run=False) G = NeuronGroup(10, "v:1") S = Synapses(G, G, "w:1") S.connect(i=[0], j=[0]) S.connect(i=[1], j=[1]) run(0 * ms) device.build(directory=None, with_output=False) assert len(S) == 2 and len(S.w[:]) == 2 reset_device() @pytest.mark.cpp_standalone @pytest.mark.standalone_only def test_device_cache_synapses(): # Check that we can ask for known synaptic information at runtime set_device("cpp_standalone", build_on_run=False) G = NeuronGroup(10, "v:1") S = Synapses(G, G, "w:1") S.connect(i=[0], j=[0]) assert len(S) == 1 assert_equal(S.i[:], [0]) assert_equal(S.j[:], [0]) S.connect(i=[1], j=[1]) assert len(S) == 2 assert_equal(S.i[:], [0, 1]) assert_equal(S.j[:], [0, 1]) S.connect(p=0.1) # We can't know anything about synapses anymore with pytest.raises(NotImplementedError): len(S) with pytest.raises(NotImplementedError): S.i[:] with pytest.raises(NotImplementedError): S.j[:] S.connect(i=[1], j=[1]) # Synapses are still "unknown" due to the previous p=0.1 call with pytest.raises(NotImplementedError): len(S) with pytest.raises(NotImplementedError): S.i[:] with pytest.raises(NotImplementedError): S.j[:] @pytest.mark.cpp_standalone @pytest.mark.standalone_only def test_storing_loading(): set_device("cpp_standalone", build_on_run=False) G = NeuronGroup( 10, """ v : volt x : 1 n : integer b : boolean """, ) v = np.arange(10) * volt x = np.arange(10, 20) n = np.arange(20, 30) b = np.array([True, False]).repeat(5) G.v = v G.x = x G.n = n G.b = b S = Synapses( G, G, """ v_syn : volt x_syn : 1 n_syn : integer b_syn : boolean """, ) S.connect(j="i") S.v_syn = v S.x_syn = x S.n_syn = n S.b_syn = b run(0 * ms) device.build(directory=None, with_output=False) assert_allclose(G.v[:], v) assert_allclose(S.v_syn[:], v) assert_allclose(G.x[:], x) assert_allclose(S.x_syn[:], x) assert_allclose(G.n[:], n) assert_allclose(S.n_syn[:], n) assert_allclose(G.b[:], b) assert_allclose(S.b_syn[:], b) reset_device() @pytest.mark.cpp_standalone @pytest.mark.standalone_only @pytest.mark.openmp def test_openmp_consistency(): previous_device = get_device() n_cells = 100 n_recorded = 10 numpy.random.seed(42) taum = 20 * ms taus = 5 * ms Vt = -50 * mV Vr = -60 * mV El = -49 * mV fac = 60 * 0.27 / 10 gmax = 20 * fac dApre = 0.01 taupre = 20 * ms taupost = taupre dApost = -dApre * taupre / taupost * 1.05 dApost *= 0.1 * gmax dApre *= 0.1 * gmax connectivity = numpy.random.randn(n_cells, n_cells) sources = numpy.random.randint(0, n_cells - 1, 10 * n_cells) # Only use one spike per time step (to rule out that a single source neuron # has more than one spike in a time step) times = ( numpy.random.choice(numpy.arange(10 * n_cells), 10 * n_cells, replace=False) * ms ) v_init = Vr + numpy.random.rand(n_cells) * (Vt - Vr) eqs = Equations( """ dv/dt = (g-(v-El))/taum : volt dg/dt = -g/taus : volt """ ) results = {} for n_threads, devicename in [ (0, "runtime"), (0, "cpp_standalone"), (1, "cpp_standalone"), (2, "cpp_standalone"), (3, "cpp_standalone"), (4, "cpp_standalone"), ]: set_device(devicename, build_on_run=False, with_output=False) Synapses.__instances__().clear() if devicename == "cpp_standalone": reinit_and_delete() prefs.devices.cpp_standalone.openmp_threads = n_threads P = NeuronGroup( n_cells, model=eqs, threshold="v>Vt", reset="v=Vr", refractory=5 * ms ) Q = SpikeGeneratorGroup(n_cells, sources, times) P.v = v_init P.g = 0 * mV S = Synapses( P, P, model=""" dApre/dt=-Apre/taupre : 1 (event-driven) dApost/dt=-Apost/taupost : 1 (event-driven) w : 1 """, pre=""" g += w*mV Apre += dApre w = w + Apost """, post=""" Apost += dApost w = w + Apre """, ) S.connect() S.w = fac * connectivity.flatten() T = Synapses(Q, P, model="w : 1", on_pre="g += w*mV") T.connect(j="i") T.w = 10 * fac spike_mon = SpikeMonitor(P) rate_mon = PopulationRateMonitor(P) state_mon = StateMonitor(S, "w", record=np.arange(n_recorded), dt=0.1 * second) v_mon = StateMonitor(P, "v", record=np.arange(n_recorded)) run(0.2 * second, report="text") if devicename == "cpp_standalone": device.build(directory=None, with_output=False) results[n_threads, devicename] = {} results[n_threads, devicename]["w"] = state_mon.w results[n_threads, devicename]["v"] = v_mon.v results[n_threads, devicename]["s"] = spike_mon.num_spikes results[n_threads, devicename]["r"] = rate_mon.rate[:] for key1, key2 in [ ((0, "runtime"), (0, "cpp_standalone")), ((1, "cpp_standalone"), (0, "cpp_standalone")), ((2, "cpp_standalone"), (0, "cpp_standalone")), ((3, "cpp_standalone"), (0, "cpp_standalone")), ((4, "cpp_standalone"), (0, "cpp_standalone")), ]: assert_allclose(results[key1]["w"], results[key2]["w"]) assert_allclose(results[key1]["v"], results[key2]["v"]) assert_allclose(results[key1]["r"], results[key2]["r"]) assert_allclose(results[key1]["s"], results[key2]["s"]) reset_device(previous_device) @pytest.mark.cpp_standalone @pytest.mark.standalone_only def test_duplicate_names_across_nets(): set_device("cpp_standalone", build_on_run=False) # In standalone mode, names have to be globally unique, not just unique # per network obj1 = BrianObject(name="name1") obj2 = BrianObject(name="name2") obj3 = BrianObject(name="name3") obj4 = BrianObject(name="name1") net1 = Network(obj1, obj2) net2 = Network(obj3, obj4) net1.run(0 * ms) net2.run(0 * ms) with pytest.raises(ValueError): device.build() reset_device() @pytest.mark.cpp_standalone @pytest.mark.standalone_only @pytest.mark.openmp def test_openmp_scalar_writes(): # Test that writing to a scalar variable only is done once in an OpenMP # setting (see github issue #551) set_device("cpp_standalone", build_on_run=False) prefs.devices.cpp_standalone.openmp_threads = 4 G = NeuronGroup(10, "s : 1 (shared)") G.run_regularly("s += 1") run(defaultclock.dt) device.build(directory=None, with_output=False) assert_equal(G.s[:], 1.0) reset_device() @pytest.mark.cpp_standalone @pytest.mark.standalone_only def test_time_after_run(): set_device("cpp_standalone", build_on_run=False) # Check that the clock and network time after a run is correct, even if we # have not actually run the code yet (via build) G = NeuronGroup(10, "dv/dt = -v/(10*ms) : 1") net = Network(G) assert_allclose(defaultclock.dt, 0.1 * ms) assert_allclose(defaultclock.t, 0.0 * ms) assert_allclose(G.t, 0.0 * ms) assert_allclose(net.t, 0.0 * ms) net.run(10 * ms) assert_allclose(defaultclock.t, 10.0 * ms) assert_allclose(G.t, 10.0 * ms) assert_allclose(net.t, 10.0 * ms) net.run(10 * ms) assert_allclose(defaultclock.t, 20.0 * ms) assert_allclose(G.t, 20.0 * ms) assert_allclose(net.t, 20.0 * ms) device.build(directory=None, with_output=False) # Everything should of course still be accessible assert_allclose(defaultclock.t, 20.0 * ms) assert_allclose(G.t, 20.0 * ms) assert_allclose(net.t, 20.0 * ms) reset_device() @pytest.mark.cpp_standalone @pytest.mark.standalone_only def test_array_cache(): # Check that variables are only accessible from Python when they should be set_device("cpp_standalone", build_on_run=False) G = NeuronGroup( 10, """ dv/dt = -v / (10*ms) : 1 w : 1 x : 1 y : 1 z : 1 (shared) """, threshold="v>1", ) S = Synapses(G, G, "weight: 1", on_pre="w += weight") S.connect(p=0.2) S.weight = 7 # All neurongroup values should be known assert_allclose(G.v, 0) assert_allclose(G.w, 0) assert_allclose(G.x, 0) assert_allclose(G.y, 0) assert_allclose(G.z, 0) assert_allclose(G.i, np.arange(10)) # But the synaptic variable is not -- we don't know the number of synapses with pytest.raises(NotImplementedError): S.weight[:] # Setting variables with explicit values should not change anything G.v = np.arange(10) + 1 G.w = 2 G.y = 5 G.z = 7 assert_allclose(G.v, np.arange(10) + 1) assert_allclose(G.w, 2) assert_allclose(G.y, 5) assert_allclose(G.z, 7) # But setting with code should invalidate them G.x = "i*2" with pytest.raises(NotImplementedError): G.x[:] # Make sure that the array cache does not allow to use incorrectly sized # values to pass with pytest.raises(ValueError): setattr(G, "w", [0, 2]) with pytest.raises(ValueError): G.w.__setitem__(slice(0, 4), [0, 2]) run(10 * ms) # v is now no longer known without running the network with pytest.raises(NotImplementedError): G.v[:] # Neither is w, it is updated in the synapse with pytest.raises(NotImplementedError): G.w[:] # However, no code touches y or z assert_allclose(G.y, 5) assert_allclose(G.z, 7) # i is read-only anyway assert_allclose(G.i, np.arange(10)) # After actually running the network, everything should be accessible device.build(directory=None, with_output=False) assert all(G.v > 0) assert all(G.w > 0) assert_allclose(G.x, np.arange(10) * 2) assert_allclose(G.y, 5) assert_allclose(G.z, 7) assert_allclose(G.i, np.arange(10)) assert_allclose(S.weight, 7) @pytest.mark.cpp_standalone @pytest.mark.standalone_only def test_run_with_debug(): # We just want to make sure that it works for now (i.e. not fails with a # compilation or runtime error), capturing the output is actually # a bit involved to get right. set_device("cpp_standalone", build_on_run=True, debug=True, directory=None) group = NeuronGroup(1, "v: 1", threshold="False") syn = Synapses(group, group, on_pre="v += 1") syn.connect() mon = SpikeMonitor(group) run(defaultclock.dt) @pytest.mark.cpp_standalone @pytest.mark.standalone_only def test_run_with_synapses_and_profile(): set_device("cpp_standalone", build_on_run=True, directory=None) group = NeuronGroup(1, "v: 1", threshold="False", reset="") syn = Synapses(group, group, on_pre="v += 1") syn.connect() mon = SpikeMonitor(group) run(defaultclock.dt, profile=True) @pytest.mark.cpp_standalone @pytest.mark.standalone_only def test_changing_profile_arg(): set_device("cpp_standalone", build_on_run=False) G = NeuronGroup(10000, "v : 1") op1 = G.run_regularly("v = exp(-v)", name="op1") op2 = G.run_regularly("v = exp(-v)", name="op2") op3 = G.run_regularly("v = exp(-v)", name="op3") op4 = G.run_regularly("v = exp(-v)", name="op4") # Op 1 is active only during the first profiled run # Op 2 is active during both profiled runs # Op 3 is active only during the second profiled run # Op 4 is never active (only during the unprofiled run) op1.active = True op2.active = True op3.active = False op4.active = False run(1000 * defaultclock.dt, profile=True) op1.active = True op2.active = True op3.active = True op4.active = True run(1000 * defaultclock.dt, profile=False) op1.active = False op2.active = True op3.active = True op4.active = False run(1000 * defaultclock.dt, profile=True) device.build(directory=None, with_output=False) profiling_dict = dict(magic_network.profiling_info) # Note that for now, C++ standalone creates a new CodeObject for every run, # which is most of the time unnecessary (this is partly due to the way we # handle constants: they are included as literals in the code but they can # change between runs). Therefore, the profiling info is potentially # difficult to interpret assert len(profiling_dict) == 4 # 2 during first run, 2 during last run # The two code objects that were executed during the first run assert ( "op1_codeobject" in profiling_dict and profiling_dict["op1_codeobject"] > 0 * second ) assert ( "op2_codeobject" in profiling_dict and profiling_dict["op2_codeobject"] > 0 * second ) # Four code objects were executed during the second run, but no profiling # information was saved for name in [ "op1_codeobject_1", "op2_codeobject_1", "op3_codeobject", "op4_codeobject", ]: assert name not in profiling_dict # Two code objects were exectued during the third run assert ( "op2_codeobject_2" in profiling_dict and profiling_dict["op2_codeobject_2"] > 0 * second ) assert ( "op3_codeobject_1" in profiling_dict and profiling_dict["op3_codeobject_1"] > 0 * second ) @pytest.mark.cpp_standalone @pytest.mark.standalone_only def test_profile_via_set_device_arg(): set_device("cpp_standalone", build_on_run=False, profile=True) G = NeuronGroup(10000, "v : 1") op1 = G.run_regularly("v = exp(-v)", name="op1") op2 = G.run_regularly("v = exp(-v)", name="op2") op3 = G.run_regularly("v = exp(-v)", name="op3") op4 = G.run_regularly("v = exp(-v)", name="op4") # Op 1 is active only during the first profiled run # Op 2 is active during both profiled runs # Op 3 is active only during the second profiled run # Op 4 is never active (only during the unprofiled run) op1.active = True op2.active = True op3.active = False op4.active = False run(1000 * defaultclock.dt) # Should use profile=True via set_device op1.active = True op2.active = True op3.active = True op4.active = True run(1000 * defaultclock.dt, profile=False) op1.active = False op2.active = True op3.active = True op4.active = False run(1000 * defaultclock.dt, profile=True) device.build(directory=None, with_output=False) profiling_dict = dict(magic_network.profiling_info) # Note that for now, C++ standalone creates a new CodeObject for every run, # which is most of the time unnecessary (this is partly due to the way we # handle constants: they are included as literals in the code but they can # change between runs). Therefore, the profiling info is potentially # difficult to interpret assert len(profiling_dict) == 4 # 2 during first run, 2 during last run # The two code objects that were executed during the first run assert ( "op1_codeobject" in profiling_dict and profiling_dict["op1_codeobject"] > 0 * second ) assert ( "op2_codeobject" in profiling_dict and profiling_dict["op2_codeobject"] > 0 * second ) # Four code objects were executed during the second run, but no profiling # information was saved for name in [ "op1_codeobject_1", "op2_codeobject_1", "op3_codeobject", "op4_codeobject", ]: assert name not in profiling_dict # Two code objects were exectued during the third run assert ( "op2_codeobject_2" in profiling_dict and profiling_dict["op2_codeobject_2"] > 0 * second ) assert ( "op3_codeobject_1" in profiling_dict and profiling_dict["op3_codeobject_1"] > 0 * second ) @pytest.mark.cpp_standalone @pytest.mark.standalone_only def test_delete_code_data(): set_device("cpp_standalone", build_on_run=True, directory=None) group = NeuronGroup(10, "dv/dt = -v / (10*ms) : volt", method="exact") group.v = np.arange(10) * mV # uses the static array mechanism run(defaultclock.dt) results_dir = os.path.join(device.project_dir, "results") assert os.path.exists(results_dir) and os.path.isdir(results_dir) # There should be 3 files for the clock, 2 for the neurongroup (index + v), # and the "last_run_info.txt" file assert len(os.listdir(results_dir)) == 6 device.delete(data=True, code=False, directory=False) assert os.path.exists(results_dir) and os.path.isdir(results_dir) assert len(os.listdir(results_dir)) == 0 assert len(os.listdir(os.path.join(device.project_dir, "static_arrays"))) > 0 assert len(os.listdir(os.path.join(device.project_dir, "code_objects"))) > 0 device.delete(data=False, code=True, directory=False) assert len(os.listdir(os.path.join(device.project_dir, "static_arrays"))) == 0 assert len(os.listdir(os.path.join(device.project_dir, "code_objects"))) == 0 @pytest.mark.cpp_standalone @pytest.mark.standalone_only def test_delete_directory(): set_device("cpp_standalone", build_on_run=True, directory=None) group = NeuronGroup(10, "dv/dt = -v / (10*ms) : volt", method="exact") group.v = np.arange(10) * mV # uses the static array mechanism run(defaultclock.dt) # Add a new file dummy_file = os.path.join(device.project_dir, "results", "dummy.txt") open(dummy_file, "w").flush() assert os.path.isfile(dummy_file) with catch_logs() as logs: device.delete(directory=True) assert len(logs) == 1 assert os.path.isfile(dummy_file) with catch_logs() as logs: device.delete(directory=True, force=True) assert len(logs) == 0 # everything should be deleted assert not os.path.exists(device.project_dir) @pytest.mark.cpp_standalone @pytest.mark.standalone_only def test_multiple_standalone_runs(): # see github issue #1189 set_device("cpp_standalone", directory=None) network = Network() Pe = NeuronGroup(1, "v : 1", threshold="False") C_ee = Synapses(Pe, Pe, on_pre="v += 1") C_ee.connect() network.add(Pe, C_ee) network.run(defaultclock.dt) device.reinit() device.activate(directory=None) network2 = Network() Pe = NeuronGroup(1, "v : 1", threshold="False") C_ee = Synapses(Pe, Pe, on_pre="v += 1") C_ee.connect() network2.add(Pe, C_ee) network2.run(defaultclock.dt) @pytest.mark.cpp_standalone @pytest.mark.standalone_only def test_continued_standalone_runs(): # see github issue #1237 set_device("cpp_standalone", build_on_run=False) source = SpikeGeneratorGroup(1, [0], [0] * ms) target = NeuronGroup(1, "v : 1") C_ee = Synapses(source, target, on_pre="v += 1", delay=2 * ms) C_ee.connect() run(1 * ms) # Spike has not been delivered yet run(2 * ms) device.build(directory=None) assert target.v[0] == 1 # Make sure the spike got delivered @pytest.mark.cpp_standalone @pytest.mark.standalone_only def test_constant_replacement(): # see github issue #1276 set_device("cpp_standalone") x = 42 G = NeuronGroup(1, "y : 1") G.y = "x" run(0 * ms) assert G.y[0] == 42.0 @pytest.mark.cpp_standalone @pytest.mark.standalone_only def test_header_file_inclusion(): set_device("cpp_standalone", directory=None, debug=True) with tempfile.TemporaryDirectory() as tmpdir: with open(os.path.join(tmpdir, "foo.h"), "w") as f: f.write( """ namespace brian_test_namespace { extern double test_variable; } """ ) with open(os.path.join(tmpdir, "foo.cpp"), "w") as f: f.write( """ namespace brian_test_namespace { double test_variable = 42; } """ ) @implementation( "cpp", """ double brian_function(int index) { using namespace brian_test_namespace; return test_variable * index; } """, headers=['"foo.h"'], sources=[os.path.join(tmpdir, "foo.cpp")], include_dirs=[tmpdir], ) @check_units(index=1, result=1) def brian_function(index): raise NotImplementedError() # Use the function in a somewhat convoluted way that exposes errors in the # code generation process G = PoissonGroup(5, rates="brian_function(i)*Hz") S = Synapses(G, G, "rate_copy : Hz") S.connect(j="i") S.run_regularly("rate_copy = rates_pre") run(defaultclock.dt) assert_allclose(S.rate_copy[:], np.arange(len(G)) * 42 * Hz) if __name__ == "__main__": for t in [ test_cpp_standalone, test_multiple_connects, test_storing_loading, test_openmp_consistency, test_duplicate_names_across_nets, test_openmp_scalar_writes, test_time_after_run, test_array_cache, test_run_with_debug, test_changing_profile_arg, test_profile_via_set_device_arg, test_delete_code_data, test_delete_directory, test_multiple_standalone_runs, test_header_file_inclusion, ]: t() reinit_and_delete() brian2-2.5.4/brian2/tests/test_devices.py000066400000000000000000000067741445201106100202610ustar00rootroot00000000000000import numpy as np import pytest from numpy.testing import assert_equal from brian2.core.magic import run from brian2.devices.device import ( Device, RuntimeDevice, all_devices, get_device, reset_device, runtime_device, set_device, ) from brian2.groups.neurongroup import NeuronGroup from brian2.units import ms class ATestDevice(Device): def activate(self, build_on_run, **kwargs): super().activate(build_on_run, **kwargs) self.build_on_run = build_on_run self._options = kwargs # These functions are needed during the setup of the defaultclock def get_value(self, var): return np.array([0.0001]) def add_array(self, var): pass def init_with_zeros(self, var, dtype): pass def fill_with_array(self, var, arr): pass @pytest.mark.codegen_independent def test_set_reset_device_implicit(): from brian2.devices import device_module old_prev_devices = list(device_module.previous_devices) device_module.previous_devices = [] test_device1 = ATestDevice() all_devices["test1"] = test_device1 test_device2 = ATestDevice() all_devices["test2"] = test_device2 set_device("test1", build_on_run=False, my_opt=1) set_device("test2", build_on_run=True, my_opt=2) assert get_device() is test_device2 assert get_device()._options["my_opt"] == 2 assert get_device().build_on_run reset_device() assert get_device() is test_device1 assert get_device()._options["my_opt"] == 1 assert not get_device().build_on_run reset_device() assert get_device() is runtime_device reset_device() # If there is no previous device, will reset to runtime device assert get_device() is runtime_device del all_devices["test1"] del all_devices["test2"] device_module.previous_devices = old_prev_devices @pytest.mark.codegen_independent def test_set_reset_device_explicit(): original_device = get_device() test_device1 = ATestDevice() all_devices["test1"] = test_device1 test_device2 = ATestDevice() all_devices["test2"] = test_device2 test_device3 = ATestDevice() all_devices["test3"] = test_device3 set_device("test1", build_on_run=False, my_opt=1) set_device("test2", build_on_run=True, my_opt=2) set_device("test3", build_on_run=False, my_opt=3) reset_device("test1") # Directly jump back to the first device assert get_device() is test_device1 assert get_device()._options["my_opt"] == 1 assert not get_device().build_on_run del all_devices["test1"] del all_devices["test2"] del all_devices["test3"] reset_device(original_device) @pytest.mark.skipif( not isinstance(get_device(), RuntimeDevice), reason="Getting/setting random number state only supported for runtime device.", ) def test_get_set_random_generator_state(): group = NeuronGroup(10, "dv/dt = -v/(10*ms) + (10*ms)**-0.5*xi : 1", method="euler") group.v = "rand()" run(10 * ms) assert np.var(group.v) > 0 # very basic test for randomness ;) old_v = np.array(group.v) random_state = get_device().get_random_state() group.v = "rand()" run(10 * ms) assert np.var(group.v - old_v) > 0 # just checking for *some* difference old_v = np.array(group.v) get_device().set_random_state(random_state) group.v = "rand()" run(10 * ms) assert_equal(group.v, old_v) if __name__ == "__main__": test_set_reset_device_implicit() test_set_reset_device_explicit() test_get_set_random_generator_state() brian2-2.5.4/brian2/tests/test_equations.py000066400000000000000000000530121445201106100206320ustar00rootroot00000000000000import sys import numpy as np try: from IPython.lib.pretty import pprint except ImportError: pprint = None import pytest from brian2 import Equations, Expression, Hz, Unit, farad, metre, ms, mV, second, volt from brian2.core.namespace import DEFAULT_UNITS from brian2.equations.equations import ( BOOLEAN, DIFFERENTIAL_EQUATION, FLOAT, INTEGER, PARAMETER, SUBEXPRESSION, EquationError, SingleEquation, check_identifier_basic, check_identifier_constants, check_identifier_functions, check_identifier_reserved, check_identifier_units, dimensions_and_type_from_string, extract_constant_subexpressions, parse_string_equations, ) from brian2.equations.refractory import check_identifier_refractory from brian2.groups.group import Group from brian2.units.fundamentalunits import ( DIMENSIONLESS, DimensionMismatchError, get_dimensions, ) # a simple Group for testing class SimpleGroup(Group): def __init__(self, variables, namespace=None): self.variables = variables self.namespace = namespace @pytest.mark.codegen_independent def test_utility_functions(): unit_namespace = DEFAULT_UNITS # Some simple tests whether the namespace returned by # get_default_namespace() makes sense assert "volt" in unit_namespace assert "ms" in unit_namespace assert unit_namespace["ms"] is ms assert unit_namespace["ms"] is unit_namespace["msecond"] for unit in unit_namespace.values(): assert isinstance(unit, Unit) assert dimensions_and_type_from_string("second") == (second.dim, FLOAT) assert dimensions_and_type_from_string("1") == (DIMENSIONLESS, FLOAT) assert dimensions_and_type_from_string("volt") == (volt.dim, FLOAT) assert dimensions_and_type_from_string("second ** -1") == (Hz.dim, FLOAT) assert dimensions_and_type_from_string("farad / metre**2") == ( (farad / metre**2).dim, FLOAT, ) assert dimensions_and_type_from_string("boolean") == (DIMENSIONLESS, BOOLEAN) assert dimensions_and_type_from_string("integer") == (DIMENSIONLESS, INTEGER) with pytest.raises(ValueError): dimensions_and_type_from_string("metr / second") with pytest.raises(ValueError): dimensions_and_type_from_string("metre **") with pytest.raises(ValueError): dimensions_and_type_from_string("5") with pytest.raises(ValueError): dimensions_and_type_from_string("2 / second") # Only the use of base units is allowed with pytest.raises(ValueError): dimensions_and_type_from_string("farad / cm**2") @pytest.mark.codegen_independent def test_identifier_checks(): legal_identifiers = ["v", "Vm", "V", "x", "ge", "g_i", "a2", "gaba_123"] illegal_identifiers = ["_v", "1v", "ü", "ge!", "v.x", "for", "else", "if"] for identifier in legal_identifiers: try: check_identifier_basic(identifier) check_identifier_reserved(identifier) except ValueError as ex: raise AssertionError( f'check complained about identifier "{identifier}": {ex}' ) for identifier in illegal_identifiers: with pytest.raises(SyntaxError): check_identifier_basic(identifier) for identifier in ("t", "dt", "xi", "i", "N"): with pytest.raises(SyntaxError): check_identifier_reserved(identifier) for identifier in ("not_refractory", "refractory", "refractory_until"): with pytest.raises(SyntaxError): check_identifier_refractory(identifier) for identifier in ("exp", "sin", "sqrt"): with pytest.raises(SyntaxError): check_identifier_functions(identifier) for identifier in ("e", "pi", "inf"): with pytest.raises(SyntaxError): check_identifier_constants(identifier) for identifier in ("volt", "second", "mV", "nA"): with pytest.raises(SyntaxError): check_identifier_units(identifier) # Check identifier registry assert check_identifier_basic in Equations.identifier_checks assert check_identifier_reserved in Equations.identifier_checks assert check_identifier_refractory in Equations.identifier_checks assert check_identifier_functions in Equations.identifier_checks assert check_identifier_constants in Equations.identifier_checks assert check_identifier_units in Equations.identifier_checks # Set up a dummy identifier check that disallows the variable name # gaba_123 (that is otherwise valid) def disallow_gaba_123(identifier): if identifier == "gaba_123": raise SyntaxError("I do not like this name") Equations.check_identifier("gaba_123") old_checks = set(Equations.identifier_checks) Equations.register_identifier_check(disallow_gaba_123) with pytest.raises(SyntaxError): Equations.check_identifier("gaba_123") Equations.identifier_checks = old_checks # registering a non-function should not work with pytest.raises(ValueError): Equations.register_identifier_check("no function") @pytest.mark.codegen_independent def test_parse_equations(): """Test the parsing of equation strings""" # A simple equation eqs = parse_string_equations("dv/dt = -v / tau : 1") assert len(eqs) == 1 and "v" in eqs and eqs["v"].type == DIFFERENTIAL_EQUATION assert eqs["v"].dim is DIMENSIONLESS # A complex one eqs = parse_string_equations( """ dv/dt = -(v + ge + # excitatory conductance I # external current )/ tau : volt dge/dt = -ge / tau_ge : volt I = sin(2 * pi * f * t) : volt f : Hz (constant) b : boolean n : integer """ ) assert len(eqs) == 6 assert "v" in eqs and eqs["v"].type == DIFFERENTIAL_EQUATION assert "ge" in eqs and eqs["ge"].type == DIFFERENTIAL_EQUATION assert "I" in eqs and eqs["I"].type == SUBEXPRESSION assert "f" in eqs and eqs["f"].type == PARAMETER assert "b" in eqs and eqs["b"].type == PARAMETER assert "n" in eqs and eqs["n"].type == PARAMETER assert eqs["f"].var_type == FLOAT assert eqs["b"].var_type == BOOLEAN assert eqs["n"].var_type == INTEGER assert eqs["v"].dim is volt.dim assert eqs["ge"].dim is volt.dim assert eqs["I"].dim is volt.dim assert eqs["f"].dim is Hz.dim assert eqs["v"].flags == [] assert eqs["ge"].flags == [] assert eqs["I"].flags == [] assert eqs["f"].flags == ["constant"] duplicate_eqs = """ dv/dt = -v / tau : 1 v = 2 * t : 1 """ with pytest.raises(EquationError): parse_string_equations(duplicate_eqs) parse_error_eqs = [ """ dv/d = -v / tau : 1 x = 2 * t : 1 """, """ dv/dt = -v / tau : 1 : volt x = 2 * t : 1 """, "dv/dt = -v / tau : 2 * volt", "dv/dt = v / second : boolean", ] for error_eqs in parse_error_eqs: with pytest.raises((ValueError, EquationError, TypeError)): parse_string_equations(error_eqs) @pytest.mark.codegen_independent def test_correct_replacements(): """Test replacing variables via keyword arguments""" # replace a variable name with a new name eqs = Equations("dv/dt = -v / tau : 1", v="V") # Correct left hand side assert ("V" in eqs) and not ("v" in eqs) # Correct right hand side assert ("V" in eqs["V"].identifiers) and not ("v" in eqs["V"].identifiers) # replace a variable name with a value eqs = Equations("dv/dt = -v / tau : 1", tau=10 * ms) assert not "tau" in eqs["v"].identifiers @pytest.mark.codegen_independent def test_wrong_replacements(): """Tests for replacements that should not work""" # Replacing a variable name with an illegal new name with pytest.raises(SyntaxError): Equations("dv/dt = -v / tau : 1", v="illegal name") with pytest.raises(SyntaxError): Equations("dv/dt = -v / tau : 1", v="_reserved") with pytest.raises(SyntaxError): Equations("dv/dt = -v / tau : 1", v="t") # Replacing a variable name with a value that already exists with pytest.raises(EquationError): Equations( """ dv/dt = -v / tau : 1 dx/dt = -x / tau : 1 """, v="x", ) # Replacing a model variable name with a value with pytest.raises(ValueError): Equations("dv/dt = -v / tau : 1", v=3 * mV) # Replacing with an illegal value with pytest.raises(SyntaxError): Equations("dv/dt = -v/tau : 1", tau=np.arange(5)) @pytest.mark.codegen_independent def test_substitute(): # Check that Equations.substitute returns an independent copy eqs = Equations("dx/dt = x : 1") eqs2 = eqs.substitute(x="y") # First equation should be unaffected assert len(eqs) == 1 and "x" in eqs assert eqs["x"].expr == Expression("x") # Second equation should have x substituted by y assert len(eqs2) == 1 and "y" in eqs2 assert eqs2["y"].expr == Expression("y") @pytest.mark.codegen_independent def test_construction_errors(): """ Test that the Equations constructor raises errors correctly """ # parse error with pytest.raises(EquationError): Equations("dv/dt = -v / tau volt") with pytest.raises(EquationError): Equations("dv/dt = -v / tau : volt second") # incorrect unit definition with pytest.raises(EquationError): Equations("dv/dt = -v / tau : mvolt") with pytest.raises(EquationError): Equations("dv/dt = -v / tau : voltage") with pytest.raises(EquationError): Equations("dv/dt = -v / tau : 1.0*volt") # Only a single string or a list of SingleEquation objects is allowed with pytest.raises(TypeError): Equations(None) with pytest.raises(TypeError): Equations(42) with pytest.raises(TypeError): Equations(["dv/dt = -v / tau : volt"]) # duplicate variable names with pytest.raises(EquationError): Equations( """ dv/dt = -v / tau : volt v = 2 * t/second * volt : volt """ ) eqs = [ SingleEquation( DIFFERENTIAL_EQUATION, "v", volt.dim, expr=Expression("-v / tau") ), SingleEquation( SUBEXPRESSION, "v", volt.dim, expr=Expression("2 * t/second * volt") ), ] with pytest.raises(EquationError): Equations(eqs) # illegal variable names with pytest.raises(SyntaxError): Equations("ddt/dt = -dt / tau : volt") with pytest.raises(SyntaxError): Equations("dt/dt = -t / tau : volt") with pytest.raises(SyntaxError): Equations("dxi/dt = -xi / tau : volt") with pytest.raises(SyntaxError): Equations("for : volt") with pytest.raises((EquationError, SyntaxError)): Equations("d1a/dt = -1a / tau : volt") with pytest.raises(SyntaxError): Equations("d_x/dt = -_x / tau : volt") # xi in a subexpression with pytest.raises(EquationError): Equations( """ dv/dt = -(v + I) / (5 * ms) : volt I = second**-1*xi**-2*volt : volt """ ) # more than one xi with pytest.raises(EquationError): Equations( """ dv/dt = -v / tau + xi/tau**.5 : volt dx/dt = -x / tau + 2*xi/tau : volt tau : second """ ) # using not-allowed flags eqs = Equations("dv/dt = -v / (5 * ms) : volt (flag)") eqs.check_flags({DIFFERENTIAL_EQUATION: ["flag"]}) # allow this flag with pytest.raises(ValueError): eqs.check_flags({DIFFERENTIAL_EQUATION: []}) with pytest.raises(ValueError): eqs.check_flags({}) with pytest.raises(ValueError): eqs.check_flags({SUBEXPRESSION: ["flag"]}) with pytest.raises(ValueError): eqs.check_flags({DIFFERENTIAL_EQUATION: ["otherflag"]}) eqs = Equations("dv/dt = -v / (5 * ms) : volt (flag1, flag2)") eqs.check_flags({DIFFERENTIAL_EQUATION: ["flag1", "flag2"]}) # allow both flags # Don't allow the two flags in combination with pytest.raises(ValueError): eqs.check_flags( {DIFFERENTIAL_EQUATION: ["flag1", "flag2"]}, incompatible_flags=[("flag1", "flag2")], ) eqs = Equations( """ dv/dt = -v / (5 * ms) : volt (flag1) dw/dt = -w / (5 * ms) : volt (flag2) """ ) # They should be allowed when used independently eqs.check_flags( {DIFFERENTIAL_EQUATION: ["flag1", "flag2"]}, incompatible_flags=[("flag1", "flag2")], ) # Circular subexpression with pytest.raises(ValueError): Equations( """ dv/dt = -(v + w) / (10 * ms) : 1 w = 2 * x : 1 x = 3 * w : 1 """ ) # Boolean/integer differential equations with pytest.raises(TypeError): Equations("dv/dt = -v / (10*ms) : boolean") with pytest.raises(TypeError): Equations("dv/dt = -v / (10*ms) : integer") @pytest.mark.codegen_independent def test_unit_checking(): # dummy Variable class class S: def __init__(self, dimensions): self.dim = get_dimensions(dimensions) # inconsistent unit for a differential equation eqs = Equations("dv/dt = -v : volt") group = SimpleGroup({"v": S(volt)}) with pytest.raises(DimensionMismatchError): eqs.check_units(group, {}) eqs = Equations("dv/dt = -v / tau: volt") group = SimpleGroup(namespace={"tau": 5 * mV}, variables={"v": S(volt)}) with pytest.raises(DimensionMismatchError): eqs.check_units(group, {}) group = SimpleGroup(namespace={"I": 3 * second}, variables={"v": S(volt)}) eqs = Equations("dv/dt = -(v + I) / (5 * ms): volt") with pytest.raises(DimensionMismatchError): eqs.check_units(group, {}) eqs = Equations( """ dv/dt = -(v + I) / (5 * ms): volt I : second """ ) group = SimpleGroup(variables={"v": S(volt), "I": S(second)}, namespace={}) with pytest.raises(DimensionMismatchError): eqs.check_units(group, {}) # inconsistent unit for a subexpression eqs = Equations( """ dv/dt = -v / (5 * ms) : volt I = 2 * v : amp """ ) group = SimpleGroup(variables={"v": S(volt), "I": S(second)}, namespace={}) with pytest.raises(DimensionMismatchError): eqs.check_units(group, {}) @pytest.mark.codegen_independent def test_properties(): """ Test accessing the various properties of equation objects """ tau = 10 * ms eqs = Equations( """ dv/dt = -(v + I)/ tau : volt I = sin(2 * 22/7. * f * t)* volt : volt f = freq * Hz: Hz freq : 1 """ ) assert ( len(eqs.diff_eq_expressions) == 1 and eqs.diff_eq_expressions[0][0] == "v" and isinstance(eqs.diff_eq_expressions[0][1], Expression) ) assert eqs.diff_eq_names == {"v"} assert ( len(eqs.eq_expressions) == 3 and {name for name, _ in eqs.eq_expressions} == {"v", "I", "f"} and all((isinstance(expr, Expression) for _, expr in eqs.eq_expressions)) ) assert len(eqs.eq_names) == 3 and eqs.eq_names == {"v", "I", "f"} assert set(eqs.keys()) == {"v", "I", "f", "freq"} # test that the equations object is iterable itself assert all(isinstance(eq, SingleEquation) for eq in eqs.values()) assert all(isinstance(eq, str) for eq in eqs) assert ( len(eqs.ordered) == 4 and all(isinstance(eq, SingleEquation) for eq in eqs.ordered) and [eq.varname for eq in eqs.ordered] == ["f", "I", "v", "freq"] ) assert [eq.unit for eq in eqs.ordered] == [Hz, volt, volt, 1] assert eqs.names == {"v", "I", "f", "freq"} assert eqs.parameter_names == {"freq"} assert eqs.subexpr_names == {"I", "f"} dimensions = eqs.dimensions assert set(dimensions.keys()) == {"v", "I", "f", "freq"} assert dimensions["v"] is volt.dim assert dimensions["I"] is volt.dim assert dimensions["f"] is Hz.dim assert dimensions["freq"] is DIMENSIONLESS assert eqs.names == set(eqs.dimensions.keys()) assert eqs.identifiers == {"tau", "volt", "Hz", "sin", "t"} # stochastic equations assert len(eqs.stochastic_variables) == 0 assert eqs.stochastic_type is None eqs = Equations("""dv/dt = -v / tau + 0.1*second**-.5*xi : 1""") assert eqs.stochastic_variables == {"xi"} assert eqs.stochastic_type == "additive" eqs = Equations( "dv/dt = -v / tau + 0.1*second**-.5*xi_1 + 0.1*second**-.5*xi_2: 1" ) assert eqs.stochastic_variables == {"xi_1", "xi_2"} assert eqs.stochastic_type == "additive" eqs = Equations("dv/dt = -v / tau + 0.1*second**-1.5*xi*t : 1") assert eqs.stochastic_type == "multiplicative" eqs = Equations("dv/dt = -v / tau + 0.1*second**-1.5*xi*v : 1") assert eqs.stochastic_type == "multiplicative" @pytest.mark.codegen_independent def test_concatenation(): eqs1 = Equations( """ dv/dt = -(v + I) / tau : volt I = sin(2*pi*freq*t) : volt freq : Hz """ ) # Concatenate two equation objects eqs2 = Equations("dv/dt = -(v + I) / tau : volt") + Equations( """ I = sin(2*pi*freq*t) : volt freq : Hz """ ) # Concatenate using "in-place" addition (which is not actually in-place) eqs3 = Equations("dv/dt = -(v + I) / tau : volt") eqs3 += Equations( """ I = sin(2*pi*freq*t) : volt freq : Hz """ ) # Concatenate with a string (will be parsed first) eqs4 = Equations("dv/dt = -(v + I) / tau : volt") eqs4 += """I = sin(2*pi*freq*t) : volt freq : Hz""" # Concatenating with something that is not a string should not work with pytest.raises(TypeError): eqs4 + 5 # The string representation is canonical, therefore it should be identical # in all cases assert str(eqs1) == str(eqs2) assert str(eqs2) == str(eqs3) assert str(eqs3) == str(eqs4) @pytest.mark.codegen_independent def test_extract_subexpressions(): eqs = Equations( """ dv/dt = -v / (10*ms) : 1 s1 = 2*v : 1 s2 = -v : 1 (constant over dt) """ ) variable, constant = extract_constant_subexpressions(eqs) assert [var in variable for var in ["v", "s1", "s2"]] assert variable["s1"].type == SUBEXPRESSION assert variable["s2"].type == PARAMETER assert constant["s2"].type == SUBEXPRESSION @pytest.mark.codegen_independent def test_repeated_construction(): eqs1 = Equations("dx/dt = x : 1") eqs2 = Equations("dx/dt = x : 1", x="y") assert len(eqs1) == 1 assert "x" in eqs1 assert eqs1["x"].expr == Expression("x") assert len(eqs2) == 1 assert "y" in eqs2 assert eqs2["y"].expr == Expression("y") @pytest.mark.codegen_independent def test_str_repr(): """ Test the string representation (only that it does not throw errors). """ tau = 10 * ms eqs = Equations( """ dv/dt = -(v + I)/ tau : volt (unless refractory) I = sin(2 * 22/7. * f * t)* volt : volt f : Hz """ ) assert len(str(eqs)) > 0 assert len(repr(eqs)) > 0 # Test str and repr of SingleEquations explicitly (might already have been # called by Equations for eq in eqs.values(): assert (len(str(eq))) > 0 assert (len(repr(eq))) > 0 @pytest.mark.codegen_independent def test_dependency_calculation(): eqs = Equations( """ dv/dt = I_m / C_m : volt I_m = I_ext + I_pas : amp I_ext = 1*nA + sin(2*pi*100*Hz*t)*nA : amp I_pas = g_L*(E_L - v) : amp """ ) deps = eqs.dependencies assert set(deps.keys()) == {"v", "I_m", "I_ext", "I_pas"} # v depends directly on I_m, on I_ext and I_pas via I_m, and on v via I_m -> I_pas assert len(deps["v"]) == 4 assert {d.equation.varname for d in deps["v"]} == {"I_m", "I_ext", "I_pas", "v"} expected_via = { "I_m": (), "I_pas": ("I_m",), "I_ext": ("I_m",), "v": ("I_m", "I_pas"), } assert all([d.via == expected_via[d.equation.varname] for d in deps["v"]]) # I_m depends directly on I_ext and I_pas, and on v via I_pas assert len(deps["I_m"]) == 3 assert {d.equation.varname for d in deps["I_m"]} == {"I_ext", "I_pas", "v"} expected_via = {"I_ext": (), "I_pas": (), "v": ("I_pas",)} assert all([d.via == expected_via[d.equation.varname] for d in deps["I_m"]]) # I_ext does not depend on anything assert len(deps["I_ext"]) == 0 # I_pas depends on v directly assert len(deps["I_pas"]) == 1 assert deps["I_pas"][0].equation.varname == "v" assert deps["I_pas"][0].via == () @pytest.mark.codegen_independent @pytest.mark.skipif(pprint is None, reason="ipython is not installed") def test_ipython_pprint(): from io import StringIO eqs = Equations( """ dv/dt = -(v + I)/ tau : volt (unless refractory) I = sin(2 * 22/7. * f * t)* volt : volt f : Hz """ ) # Test ipython's pretty printing old_stdout = sys.stdout string_output = StringIO() sys.stdout = string_output pprint(eqs) assert len(string_output.getvalue()) > 0 sys.stdout = old_stdout if __name__ == "__main__": test_utility_functions() test_identifier_checks() test_parse_equations() test_correct_replacements() test_substitute() test_wrong_replacements() test_construction_errors() test_concatenation() test_unit_checking() test_properties() test_extract_subexpressions() test_repeated_construction() test_str_repr() brian2-2.5.4/brian2/tests/test_functions.py000066400000000000000000000777301445201106100206470ustar00rootroot00000000000000import os import pytest from numpy.testing import assert_equal from brian2 import * from brian2.codegen.codeobject import CodeObject from brian2.codegen.cpp_prefs import compiler_supports_c99 from brian2.codegen.generators import CodeGenerator from brian2.core.functions import timestep from brian2.devices import RuntimeDevice from brian2.parsing.sympytools import str_to_sympy, sympy_to_str from brian2.tests.utils import assert_allclose, exc_isinstance from brian2.utils.logger import catch_logs @pytest.mark.codegen_independent def test_constants_sympy(): """ Make sure that symbolic constants are understood correctly by sympy """ assert sympy_to_str(str_to_sympy("1.0/inf")) == "0" assert sympy_to_str(str_to_sympy("sin(pi)")) == "0" assert sympy_to_str(str_to_sympy("log(e)")) == "1" @pytest.mark.standalone_compatible def test_constants_values(): """ Make sure that symbolic constants use the correct values in code """ G = NeuronGroup(3, "v : 1") G.v[0] = "pi" G.v[1] = "e" G.v[2] = "inf" run(0 * ms) assert_allclose(G.v[:], [np.pi, np.e, np.inf]) def int_(x): return array(x, dtype=int) int_.__name__ = "int" @pytest.mark.parametrize( "func,needs_c99_support", [ (cos, False), (tan, False), (sinh, False), (cosh, False), (tanh, False), (arcsin, False), (arccos, False), (arctan, False), (log, False), (log10, False), (log1p, True), (exp, False), (np.sqrt, False), (expm1, True), (exprel, True), (np.ceil, False), (np.floor, False), (np.sign, False), (int_, False), ], ) @pytest.mark.standalone_compatible def test_math_functions(func, needs_c99_support): """ Test that math functions give the same result, regardless of whether used directly or in generated Python or C++ code. """ if not get_device() == RuntimeDevice or prefs.codegen.target != "numpy": if needs_c99_support and not compiler_supports_c99(): pytest.skip('Support for function "{}" needs a compiler with C99 support.') test_array = np.array([-1, -0.5, 0, 0.5, 1]) with catch_logs() as _: # Let's suppress warnings about illegal values # Calculate the result directly numpy_result = func(test_array) # Calculate the result in a somewhat complicated way by using a # subexpression in a NeuronGroup if func.__name__ == "absolute": # we want to use the name abs instead of absolute func_name = "abs" else: func_name = func.__name__ G = NeuronGroup( len(test_array), f"""func = {func_name}(variable) : 1 variable : 1""", ) G.variable = test_array mon = StateMonitor(G, "func", record=True) net = Network(G, mon) net.run(defaultclock.dt) assert_allclose( numpy_result, mon.func_.flatten(), err_msg=f"Function {func.__name__} did not return the correct values", ) @pytest.mark.standalone_compatible @pytest.mark.parametrize("func,operator", [(np.power, "**"), (np.mod, "%")]) def test_math_operators(func, operator): default_dt = defaultclock.dt test_array = np.array([-1, -0.5, 0, 0.5, 1]) # Functions/operators scalar = 3 # Calculate the result directly numpy_result = func(test_array, scalar) # Calculate the result in a somewhat complicated way by using a # subexpression in a NeuronGroup G = NeuronGroup( len(test_array), f"""func = variable {operator} scalar : 1 variable : 1""", ) G.variable = test_array mon = StateMonitor(G, "func", record=True) net = Network(G, mon) net.run(defaultclock.dt) assert_allclose( numpy_result, mon.func_.flatten(), err_msg=f"Function {func.__name__} did not return the correct values", ) @pytest.mark.standalone_compatible def test_clip(): G = NeuronGroup( 4, """ clipexpr1 = clip(integer_var1, 0, 1) : integer clipexpr2 = clip(integer_var2, -0.5, 1.5) : integer clipexpr3 = clip(float_var1, 0, 1) : 1 clipexpr4 = clip(float_var2, -0.5, 1.5) : 1 integer_var1 : integer integer_var2 : integer float_var1 : 1 float_var2 : 1 """, ) G.integer_var1 = [0, 1, -1, 2] G.integer_var2 = [0, 1, -1, 2] G.float_var1 = [0.0, 1.0, -1.0, 2.0] G.float_var2 = [0.0, 1.0, -1.0, 2.0] s_mon = StateMonitor( G, ["clipexpr1", "clipexpr2", "clipexpr3", "clipexpr4"], record=True ) run(defaultclock.dt) assert_equal(s_mon.clipexpr1.flatten(), [0, 1, 0, 1]) assert_equal(s_mon.clipexpr2.flatten(), [0, 1, 0, 1]) assert_allclose(s_mon.clipexpr3.flatten(), [0, 1, 0, 1]) assert_allclose(s_mon.clipexpr4.flatten(), [0, 1, -0.5, 1.5]) @pytest.mark.standalone_compatible def test_bool_to_int(): # Test that boolean expressions and variables are correctly converted into # integers G = NeuronGroup( 2, """ intexpr1 = int(bool_var) : integer intexpr2 = int(float_var > 1.0) : integer bool_var : boolean float_var : 1 """, ) G.bool_var = [True, False] G.float_var = [2.0, 0.5] s_mon = StateMonitor(G, ["intexpr1", "intexpr2"], record=True) run(defaultclock.dt) assert_equal(s_mon.intexpr1.flatten(), [1, 0]) assert_equal(s_mon.intexpr2.flatten(), [1, 0]) @pytest.mark.codegen_independent def test_timestep_function(): dt = defaultclock.dt_ # Check that multiples of dt end up in the correct time step t = np.arange(100000) * dt assert_equal(timestep(t, dt), np.arange(100000)) # Scalar values should stay scalar ts = timestep(0.0005, 0.0001) assert np.isscalar(ts) and ts == 5 # Length-1 arrays should stay arrays ts = timestep(np.array([0.0005]), 0.0001) assert ts.shape == (1,) and ts == 5 @pytest.mark.standalone_compatible def test_timestep_function_during_run(): group = NeuronGroup( 2, """ref_t : second ts = timestep(ref_t, dt) + timestep(t, dt) : integer""", ) group.ref_t = [-1e4 * second, 5 * defaultclock.dt] mon = StateMonitor(group, "ts", record=True) run(5 * defaultclock.dt) assert all(mon.ts[0] <= -1e4) assert_equal(mon.ts[1], [5, 6, 7, 8, 9]) @pytest.mark.standalone_compatible def test_user_defined_function(): @implementation( "cpp", """ inline double usersin(double x) { return sin(x); } """, ) @implementation( "cython", """ cdef double usersin(double x): return sin(x) """, ) @check_units(x=1, result=1) def usersin(x): return np.sin(x) default_dt = defaultclock.dt test_array = np.array([0, 1, 2, 3]) G = NeuronGroup( len(test_array), """ func = usersin(variable) : 1 variable : 1 """, ) G.variable = test_array mon = StateMonitor(G, "func", record=True) run(default_dt) assert_allclose(np.sin(test_array), mon.func_.flatten()) def test_user_defined_function_units(): """ Test the preparation of functions for use in code with check_units. """ prefs.codegen.target = "numpy" if prefs.codegen.target != "numpy": pytest.skip("numpy-only test") def nothing_specified(x, y, z): return x * (y + z) no_result_unit = check_units(x=1, y=second, z=second)(nothing_specified) all_specified = check_units(x=1, y=second, z=second, result=second)( nothing_specified ) consistent_units = check_units(x=None, y=None, z="y", result=lambda x, y, z: x * y)( nothing_specified ) G = NeuronGroup( 1, """ a : 1 b : second c : second """, namespace={ "nothing_specified": nothing_specified, "no_result_unit": no_result_unit, "all_specified": all_specified, "consistent_units": consistent_units, }, ) net = Network(G) net.run(0 * ms) # make sure we have a clock and therefore a t G.c = "all_specified(a, b, t)" G.c = "consistent_units(a, b, t)" with pytest.raises(ValueError): setattr(G, "c", "no_result_unit(a, b, t)") with pytest.raises(KeyError): setattr(G, "c", "nothing_specified(a, b, t)") with pytest.raises(DimensionMismatchError): setattr(G, "a", "all_specified(a, b, t)") with pytest.raises(DimensionMismatchError): setattr(G, "a", "all_specified(b, a, t)") with pytest.raises(DimensionMismatchError): setattr(G, "a", "consistent_units(a, b, t)") with pytest.raises(DimensionMismatchError): setattr(G, "a", "consistent_units(b, a, t)") def test_simple_user_defined_function(): # Make sure that it's possible to use a Python function directly, without # additional wrapping @check_units(x=1, result=1) def usersin(x): return np.sin(x) usersin.stateless = True default_dt = defaultclock.dt test_array = np.array([0, 1, 2, 3]) G = NeuronGroup( len(test_array), """func = usersin(variable) : 1 variable : 1""", codeobj_class=NumpyCodeObject, ) G.variable = test_array mon = StateMonitor(G, "func", record=True, codeobj_class=NumpyCodeObject) net = Network(G, mon) net.run(default_dt) assert_allclose(np.sin(test_array), mon.func_.flatten()) def test_manual_user_defined_function(): if prefs.codegen.target != "numpy": pytest.skip("numpy-only test") default_dt = defaultclock.dt # User defined function without any decorators def foo(x, y): return x + y + 3 * volt orig_foo = foo # Since the function is not annotated with check units, we need to specify # both the units of the arguments and the return unit with pytest.raises(ValueError): Function(foo, return_unit=volt) with pytest.raises(ValueError): Function(foo, arg_units=[volt, volt]) # If the function uses the string syntax for "same units", it needs to # specify the names of the arguments with pytest.raises(TypeError): Function(foo, arg_units=[volt, "x"]) with pytest.raises(TypeError): Function(foo, arg_units=[volt, "x"], arg_names=["x"]) # Needs two entries foo = Function(foo, arg_units=[volt, volt], return_unit=volt) assert foo(1 * volt, 2 * volt) == 6 * volt # a can be any unit, b and c need to be the same unit def bar(a, b, c): return a * (b + c) bar = Function( bar, arg_units=[None, None, "b"], arg_names=["a", "b", "c"], return_unit=lambda a, b, c: a * b, ) assert bar(2, 3 * volt, 5 * volt) == 16 * volt assert bar(2 * amp, 3 * volt, 5 * volt) == 16 * watt assert bar(2 * volt, 3 * amp, 5 * amp) == 16 * watt with pytest.raises(DimensionMismatchError): bar(2, 3 * volt, 5 * amp) # Incorrect argument units group = NeuronGroup( 1, """ dv/dt = foo(x, y)/ms : volt x : 1 y : 1 """, ) net = Network(group) with pytest.raises(BrianObjectException) as exc: net.run(0 * ms, namespace={"foo": foo}) assert exc_isinstance(exc, DimensionMismatchError) # Incorrect output unit group = NeuronGroup( 1, """ dv/dt = foo(x, y)/ms : 1 x : volt y : volt """, ) net = Network(group) with pytest.raises(BrianObjectException) as exc: net.run(0 * ms, namespace={"foo": foo}) assert exc_isinstance(exc, DimensionMismatchError) G = NeuronGroup( 1, """ func = foo(x, y) : volt x : volt y : volt """, ) G.x = 1 * volt G.y = 2 * volt mon = StateMonitor(G, "func", record=True) net = Network(G, mon) net.run(default_dt) assert mon[0].func == [6] * volt # discard units foo.implementations.add_numpy_implementation(orig_foo, discard_units=True) G = NeuronGroup( 1, """ func = foo(x, y) : volt x : volt y : volt """, ) G.x = 1 * volt G.y = 2 * volt mon = StateMonitor(G, "func", record=True) net = Network(G, mon) net.run(default_dt) assert mon[0].func == [6] * volt @pytest.mark.cpp_standalone @pytest.mark.standalone_only def test_manual_user_defined_function_cpp_standalone_compiler_args(): set_device("cpp_standalone", directory=None) @implementation( "cpp", """ static inline double foo(const double x, const double y) { return x + y + _THREE; } """, # just check whether we can specify the supported compiler args, # only the define macro is actually used headers=[], sources=[], libraries=[], include_dirs=[], library_dirs=[], runtime_library_dirs=[], define_macros=[("_THREE", "3")], ) @check_units(x=volt, y=volt, result=volt) def foo(x, y): return x + y + 3 * volt G = NeuronGroup( 1, """ func = foo(x, y) : volt x : volt y : volt """, ) G.x = 1 * volt G.y = 2 * volt mon = StateMonitor(G, "func", record=True) net = Network(G, mon) net.run(defaultclock.dt) assert mon[0].func == [6] * volt @pytest.mark.cpp_standalone @pytest.mark.standalone_only def test_manual_user_defined_function_cpp_standalone_wrong_compiler_args1(): set_device("cpp_standalone", directory=None) @implementation( "cpp", """ static inline double foo(const double x, const double y) { return x + y + _THREE; } """, some_arg=[], ) # non-existing argument @check_units(x=volt, y=volt, result=volt) def foo(x, y): return x + y + 3 * volt G = NeuronGroup( 1, """ func = foo(x, y) : volt x : volt y : volt """, ) mon = StateMonitor(G, "func", record=True) net = Network(G, mon) with pytest.raises(BrianObjectException) as exc: net.run(defaultclock.dt, namespace={"foo": foo}) assert exc_isinstance(exc, ValueError) @pytest.mark.cpp_standalone @pytest.mark.standalone_only def test_manual_user_defined_function_cpp_standalone_wrong_compiler_args2(): set_device("cpp_standalone", directory=None) @implementation( "cpp", """ static inline double foo(const double x, const double y) { return x + y + _THREE; } """, headers="", ) # existing argument, wrong value type @check_units(x=volt, y=volt, result=volt) def foo(x, y): return x + y + 3 * volt G = NeuronGroup( 1, """ func = foo(x, y) : volt x : volt y : volt """, ) mon = StateMonitor(G, "func", record=True) net = Network(G, mon) with pytest.raises(BrianObjectException) as exc: net.run(defaultclock.dt, namespace={"foo": foo}) assert exc_isinstance(exc, TypeError) def test_manual_user_defined_function_cython_compiler_args(): if prefs.codegen.target != "cython": pytest.skip("Cython-only test") @implementation( "cython", """ cdef double foo(double x, const double y): return x + y + 3 """, # just check whether we can specify the supported compiler args, libraries=[], include_dirs=[], library_dirs=[], runtime_library_dirs=[], ) @check_units(x=volt, y=volt, result=volt) def foo(x, y): return x + y + 3 * volt G = NeuronGroup( 1, """ func = foo(x, y) : volt x : volt y : volt""", ) G.x = 1 * volt G.y = 2 * volt mon = StateMonitor(G, "func", record=True) net = Network(G, mon) net.run(defaultclock.dt) assert mon[0].func == [6] * volt def test_manual_user_defined_function_cython_wrong_compiler_args1(): if prefs.codegen.target != "cython": pytest.skip("Cython-only test") @implementation( "cython", """ cdef double foo(double x, const double y): return x + y + 3 """, some_arg=[], ) # non-existing argument @check_units(x=volt, y=volt, result=volt) def foo(x, y): return x + y + 3 * volt G = NeuronGroup( 1, """ func = foo(x, y) : volt x : volt y : volt """, ) mon = StateMonitor(G, "func", record=True) net = Network(G, mon) with pytest.raises(BrianObjectException) as exc: net.run(defaultclock.dt, namespace={"foo": foo}) assert exc_isinstance(exc, ValueError) def test_manual_user_defined_function_cython_wrong_compiler_args2(): if prefs.codegen.target != "cython": pytest.skip("Cython-only test") @implementation( "cython", """ cdef double foo(double x, const double y): return x + y + 3 """, libraries="cstdio", ) # existing argument, wrong value type @check_units(x=volt, y=volt, result=volt) def foo(x, y): return x + y + 3 * volt G = NeuronGroup( 1, """ func = foo(x, y) : volt x : volt y : volt """, ) mon = StateMonitor(G, "func", record=True) net = Network(G, mon) with pytest.raises(BrianObjectException) as exc: net.run(defaultclock.dt, namespace={"foo": foo}) assert exc_isinstance(exc, TypeError) def test_external_function_cython(): if prefs.codegen.target != "cython": pytest.skip("Cython-only test") this_dir = os.path.abspath(os.path.dirname(__file__)) @implementation( "cython", "from func_def_cython cimport foo", sources=[os.path.join(this_dir, "func_def_cython.pyx")], ) @check_units(x=volt, y=volt, result=volt) def foo(x, y): return x + y + 3 * volt G = NeuronGroup( 1, """ func = foo(x, y) : volt x : volt y : volt """, ) G.x = 1 * volt G.y = 2 * volt mon = StateMonitor(G, "func", record=True) net = Network(G, mon) net.run(defaultclock.dt) assert mon[0].func == [6] * volt @pytest.mark.cpp_standalone @pytest.mark.standalone_only def test_external_function_cpp_standalone(): set_device("cpp_standalone", directory=None) this_dir = os.path.abspath(os.path.dirname(__file__)) @implementation( "cpp", "//all code in func_def_cpp.cpp", headers=['"func_def_cpp.h"'], include_dirs=[this_dir], sources=[os.path.join(this_dir, "func_def_cpp.cpp")], ) @check_units(x=volt, y=volt, result=volt) def foo(x, y): return x + y + 3 * volt G = NeuronGroup( 1, """ func = foo(x, y) : volt x : volt y : volt """, ) G.x = 1 * volt G.y = 2 * volt mon = StateMonitor(G, "func", record=True) net = Network(G, mon) net.run(defaultclock.dt) assert mon[0].func == [6] * volt @pytest.mark.codegen_independent def test_user_defined_function_discarding_units(): # A function with units that should discard units also inside the function @implementation("numpy", discard_units=True) @check_units(v=volt, result=volt) def foo(v): return v + 3 * volt # this normally raises an error for unitless v assert foo(5 * volt) == 8 * volt # Test the function that is used during a run assert foo.implementations[NumpyCodeObject].get_code(None)(5) == 8 @pytest.mark.codegen_independent def test_user_defined_function_discarding_units_2(): # Add a numpy implementation explicitly (as in TimedArray) unit = volt @check_units(v=volt, result=unit) def foo(v): return v + 3 * unit # this normally raises an error for unitless v foo = Function(pyfunc=foo) def unitless_foo(v): return v + 3 foo.implementations.add_implementation("numpy", code=unitless_foo) assert foo(5 * volt) == 8 * volt # Test the function that is used during a run assert foo.implementations[NumpyCodeObject].get_code(None)(5) == 8 @pytest.mark.codegen_independent def test_function_implementation_container(): import brian2.codegen.targets as targets class ACodeGenerator(CodeGenerator): class_name = "A Language" class BCodeGenerator(CodeGenerator): class_name = "B Language" class ACodeObject(CodeObject): generator_class = ACodeGenerator class_name = "A" class A2CodeObject(CodeObject): generator_class = ACodeGenerator class_name = "A2" class BCodeObject(CodeObject): generator_class = BCodeGenerator class_name = "B" # Register the code generation targets _previous_codegen_targets = set(targets.codegen_targets) targets.codegen_targets = {ACodeObject, BCodeObject} @check_units(x=volt, result=volt) def foo(x): return x f = Function(foo) container = f.implementations # inserting into the container with a CodeGenerator class container.add_implementation(BCodeGenerator, code="implementation B language") assert container[BCodeGenerator].get_code(None) == "implementation B language" # inserting into the container with a CodeObject class container.add_implementation(ACodeObject, code="implementation A CodeObject") assert container[ACodeObject].get_code(None) == "implementation A CodeObject" # inserting into the container with a name of a CodeGenerator container.add_implementation("A Language", "implementation A Language") assert container["A Language"].get_code(None) == "implementation A Language" assert container[ACodeGenerator].get_code(None) == "implementation A Language" assert container[A2CodeObject].get_code(None) == "implementation A Language" # inserting into the container with a name of a CodeObject container.add_implementation("B", "implementation B CodeObject") assert container["B"].get_code(None) == "implementation B CodeObject" assert container[BCodeObject].get_code(None) == "implementation B CodeObject" with pytest.raises(KeyError): container["unknown"] # some basic dictionary properties assert len(container) == 4 assert {key for key in container} == { "A Language", "B", ACodeObject, BCodeGenerator, } # Restore the previous codegeneration targets targets.codegen_targets = _previous_codegen_targets def test_function_dependencies_cython(): if prefs.codegen.target != "cython": pytest.skip("cython-only test") @implementation( "cython", """ cdef float foo(float x): return 42*0.001 """, ) @check_units(x=volt, result=volt) def foo(x): return 42 * mV # Second function with an independent implementation for numpy and an # implementation for C++ that makes use of the previous function. @implementation( "cython", """ cdef float bar(float x): return 2*foo(x) """, dependencies={"foo": foo}, ) @check_units(x=volt, result=volt) def bar(x): return 84 * mV G = NeuronGroup(5, "v : volt") G.run_regularly("v = bar(v)") net = Network(G) net.run(defaultclock.dt) assert_allclose(G.v_[:], 84 * 0.001) def test_function_dependencies_cython_rename(): if prefs.codegen.target != "cython": pytest.skip("cython-only test") @implementation( "cython", """ cdef float _foo(float x): return 42*0.001 """, name="_foo", ) @check_units(x=volt, result=volt) def foo(x): return 42 * mV # Second function with an independent implementation for numpy and an # implementation for C++ that makes use of the previous function. @implementation( "cython", """ cdef float bar(float x): return 2*my_foo(x) """, dependencies={"my_foo": foo}, ) @check_units(x=volt, result=volt) def bar(x): return 84 * mV G = NeuronGroup(5, "v : volt") G.run_regularly("v = bar(v)") net = Network(G) net.run(defaultclock.dt) assert_allclose(G.v_[:], 84 * 0.001) def test_function_dependencies_numpy(): if prefs.codegen.target != "numpy": pytest.skip("numpy-only test") @implementation( "cpp", """ float foo(float x) { return 42*0.001; }""", ) @check_units(x=volt, result=volt) def foo(x): return 42 * mV # Second function with an independent implementation for C++ and an # implementation for numpy that makes use of the previous function. # Note that we don't need to use the explicit dependencies mechanism for # numpy, since the Python function stores a reference to the referenced # function directly @implementation( "cpp", """ float bar(float x) { return 84*0.001; }""", ) @check_units(x=volt, result=volt) def bar(x): return 2 * foo(x) G = NeuronGroup(5, "v : volt") G.run_regularly("v = bar(v)") net = Network(G) net.run(defaultclock.dt) assert_allclose(G.v_[:], 84 * 0.001) @pytest.mark.standalone_compatible def test_repeated_function_dependencies(): # each of the binomial functions adds randn as a depency, see #988 test_neuron = NeuronGroup( 1, "x : 1", namespace={ "bino_1": BinomialFunction(10, 0.5), "bino_2": BinomialFunction(10, 0.6), }, ) test_neuron.x = "bino_1()+bino_2()" run(0 * ms) @pytest.mark.standalone_compatible def test_binomial(): binomial_f_approximated = BinomialFunction(100, 0.1, approximate=True) binomial_f = BinomialFunction(100, 0.1, approximate=False) # Just check that it does not raise an error and that it produces some # values G = NeuronGroup( 1, """ x : 1 y : 1 """, ) G.run_regularly( """ x = binomial_f_approximated() y = binomial_f() """ ) mon = StateMonitor(G, ["x", "y"], record=0) run(1 * ms) assert np.var(mon[0].x) > 0 assert np.var(mon[0].y) > 0 @pytest.mark.standalone_compatible def test_poisson(): # Just check that it does not raise an error and that it produces some # values G = NeuronGroup( 5, """ l : 1 x : integer y : integer z : integer """, ) G.l = [0, 1, 5, 15, 25] G.run_regularly( """ x = poisson(l) y = poisson(5) z = poisson(0) """ ) mon = StateMonitor(G, ["x", "y", "z"], record=True) run(100 * defaultclock.dt) assert_equal(mon.x[0], 0) assert all(np.var(mon.x[1:], axis=1) > 0) assert all(np.var(mon.y, axis=1) > 0) assert_equal(mon.z, 0) def test_declare_types(): if prefs.codegen.target != "numpy": pytest.skip("numpy-only test") @declare_types(a="integer", b="float", result="highest") def f(a, b): return a * b assert f._arg_types == ["integer", "float"] assert f._return_type == "highest" @declare_types(b="float") def f(a, b, c): return a * b * c assert f._arg_types == ["any", "float", "any"] assert f._return_type == "float" def bad_argtype(): @declare_types(b="floating") def f(a, b, c): return a * b * c with pytest.raises(ValueError): bad_argtype() def bad_argname(): @declare_types(d="floating") def f(a, b, c): return a * b * c with pytest.raises(ValueError): bad_argname() @check_units(a=volt, b=1) @declare_types(a="float", b="integer") def f(a, b): return a * b @declare_types(a="float", b="integer") @check_units(a=volt, b=1) def f(a, b): return a * b def bad_units(): @declare_types(a="integer", b="float") @check_units(a=volt, b=1, result=volt) def f(a, b): return a * b eqs = """ dv/dt = f(v, 1)/second : 1 """ G = NeuronGroup(1, eqs) Network(G).run(1 * ms) with pytest.raises(BrianObjectException) as exc: bad_units() assert exc_isinstance(exc, TypeError) def bad_type(): @implementation("numpy", discard_units=True) @declare_types(a="float", result="float") @check_units(a=1, result=1) def f(a): return a eqs = """ a : integer dv/dt = f(a)*v/second : 1 """ G = NeuronGroup(1, eqs) Network(G).run(1 * ms) with pytest.raises(BrianObjectException) as exc: bad_type() assert exc_isinstance(exc, TypeError) def test_multiple_stateless_function_calls(): # Check that expressions such as rand() + rand() (which might be incorrectly # simplified to 2*rand()) raise an error G = NeuronGroup(1, "dv/dt = (rand() - rand())/second : 1") net = Network(G) with pytest.raises(BrianObjectException) as exc: net.run(0 * ms) assert exc_isinstance(exc, NotImplementedError) G2 = NeuronGroup(1, "v:1", threshold="v>1", reset="v=rand() - rand()") net2 = Network(G2) with pytest.raises(BrianObjectException) as exc: net2.run(0 * ms) assert exc_isinstance(exc, NotImplementedError) G3 = NeuronGroup(1, "v:1") G3.run_regularly("v = rand() - rand()") net3 = Network(G3) with pytest.raises(BrianObjectException) as exc: net3.run(0 * ms) assert exc_isinstance(exc, NotImplementedError) G4 = NeuronGroup(1, "x : 1") # Verify that synaptic equations are checked as well, see #1146 S = Synapses(G4, G4, "dy/dt = (rand() - rand())/second : 1 (clock-driven)") S.connect() net = Network(G4, S) with pytest.raises(BrianObjectException) as exc: net.run(0 * ms) assert exc_isinstance(exc, NotImplementedError) @pytest.mark.codegen_independent def test_parse_dimension_errors(): from brian2.parsing.expressions import parse_expression_dimensions @check_units(x=1, result=1) def foo(x): return x # Function call with keyword arguments with pytest.raises(ValueError): parse_expression_dimensions("foo(a=1, b=2)", {"foo": foo}) # Unknown function with pytest.raises(SyntaxError): parse_expression_dimensions("bar(1, 2)", {"foo": foo}) # Function without unit definition with pytest.raises(ValueError): parse_expression_dimensions("bar(1, 2)", {"bar": lambda x, y: x + y}) # Function with wrong number of arguments with pytest.raises(SyntaxError): parse_expression_dimensions("foo(1, 2)", {"foo": foo}) if __name__ == "__main__": # prefs.codegen.target = 'numpy' import time from _pytest.outcomes import Skipped from brian2 import prefs for f in [ test_constants_sympy, test_constants_values, test_math_functions, test_clip, test_bool_to_int, test_timestep_function, test_timestep_function_during_run, test_user_defined_function, test_user_defined_function_units, test_simple_user_defined_function, test_manual_user_defined_function, test_external_function_cython, test_user_defined_function_discarding_units, test_user_defined_function_discarding_units_2, test_function_implementation_container, test_function_dependencies_numpy, test_function_dependencies_cython, test_function_dependencies_cython_rename, test_repeated_function_dependencies, test_binomial, test_poisson, test_declare_types, test_multiple_stateless_function_calls, ]: try: start = time.time() f() print("Test", f.__name__, "took", time.time() - start) except Skipped as e: print("Skipping test", f.__name__, e) brian2-2.5.4/brian2/tests/test_logger.py000066400000000000000000000065741445201106100201140ustar00rootroot00000000000000import multiprocessing import os import pytest from brian2.core.preferences import prefs from brian2.utils.logger import BrianLogger, get_logger logger = get_logger("brian2.tests.test_logger") @pytest.mark.codegen_independent def test_file_logging(): BrianLogger.initialize() logger.error("error message xxx") logger.warn("warning message xxx") logger.info("info message xxx") logger.debug("debug message xxx") logger.diagnostic("diagnostic message xxx") BrianLogger.file_handler.flush() # By default, only >= debug messages should show up assert os.path.isfile(BrianLogger.tmp_log) with open(BrianLogger.tmp_log, encoding="utf-8") as f: log_content = f.readlines() for level, line in zip(["error", "warning", "info", "debug"], log_content[-4:]): assert "brian2.tests.test_logger" in line assert f"{level} message xxx" in line assert level.upper() in line @pytest.mark.codegen_independent def test_file_logging_special_characters(): BrianLogger.initialize() # Test logging with special characters that could occur in log messages and # require UTF-8 special_chars = "→ ≠ ≤ ≥ ← ∞ µ ∝ ∂ ∅" logger.debug(special_chars) BrianLogger.file_handler.flush() assert os.path.isfile(BrianLogger.tmp_log) with open(BrianLogger.tmp_log, encoding="utf-8") as f: log_content = f.readlines() last_line = log_content[-1] assert "brian2.tests.test_logger" in last_line assert special_chars in last_line def run_in_process(x): logger.info(f"subprocess info message {x}") def run_in_process_with_logger(x): prefs.logging.delete_log_on_exit = False BrianLogger.initialize() logger.info(f"subprocess info message {x}") BrianLogger.file_handler.flush() return BrianLogger.tmp_log @pytest.mark.codegen_independent def test_file_logging_multiprocessing(): logger.info("info message before multiprocessing") with multiprocessing.Pool() as p: p.map(run_in_process, range(3)) BrianLogger.file_handler.flush() assert os.path.isfile(BrianLogger.tmp_log) with open(BrianLogger.tmp_log, encoding="utf-8") as f: log_content = f.readlines() # The subprocesses should not have written to the log file assert "info message before multiprocessing" in log_content[-1] @pytest.mark.codegen_independent def test_file_logging_multiprocessing_with_loggers(): logger.info("info message before multiprocessing") with multiprocessing.Pool() as p: log_files = p.map(run_in_process_with_logger, range(3)) BrianLogger.file_handler.flush() assert os.path.isfile(BrianLogger.tmp_log) with open(BrianLogger.tmp_log, encoding="utf-8") as f: log_content = f.readlines() # The subprocesses should not have written to the main log file assert "info message before multiprocessing" in log_content[-1] # Each subprocess should have their own log file for x, log_file in enumerate(log_files): assert os.path.isfile(log_file) with open(log_file, encoding="utf-8") as f: log_content = f.readlines() assert f"subprocess info message {x}" in log_content[-1] prefs.logging.delete_log_on_exit = True if __name__ == "__main__": test_file_logging() test_file_logging_special_characters() test_file_logging_multiprocessing() test_file_logging_multiprocessing_with_loggers() brian2-2.5.4/brian2/tests/test_memory.py000066400000000000000000000110501445201106100201260ustar00rootroot00000000000000import numpy as np import pytest from numpy.testing import assert_equal from brian2.memory.dynamicarray import DynamicArray, DynamicArray1D @pytest.mark.codegen_independent def test_dynamic_array_1d_access(): da = DynamicArray1D(10) da[:] = np.arange(10) assert da[7] == 7 assert len(da) == 10 assert da.shape == (10,) assert len(str(da)) assert len(repr(da)) da[:] += 1 da.data[:] += 1 assert all(da[:] == (np.arange(10) + 2)) @pytest.mark.codegen_independent def test_dynamic_array_1d_resize_up_down(): for numpy_resize in [True, False]: da = DynamicArray1D(10, use_numpy_resize=numpy_resize, refcheck=False) da[:] = np.arange(10) da.resize(15) assert len(da) == 15 assert da.shape == (15,) assert all(da[10:] == 0) assert all(da[:10] == np.arange(10)) da.resize(5) assert len(da) == 5 assert da.shape == (5,) assert all(da[:] == np.arange(5)) @pytest.mark.codegen_independent def test_dynamic_array_1d_resize_down_up(): for numpy_resize in [True, False]: da = DynamicArray1D(10, use_numpy_resize=numpy_resize) da[:] = np.arange(10) da.resize(5) assert len(da) == 5 assert da.shape == (5,) assert all(da[:5] == np.arange(5)) da.resize(10) assert len(da) == 10 assert da.shape == (10,) assert all(da[:5] == np.arange(5)) assert all(da[5:] == 0) @pytest.mark.codegen_independent def test_dynamic_array_1d_shrink(): for numpy_resize in [True, False]: da = DynamicArray1D(10, use_numpy_resize=numpy_resize, refcheck=False) da[:] = np.arange(10) da.shrink(5) assert len(da) == 5 assert all(da[:] == np.arange(5)) # After using shrink, the underlying array should have changed assert len(da._data) == 5 @pytest.mark.codegen_independent def test_dynamic_array_2d_access(): da = DynamicArray1D((10, 20)) da[:, :] = np.arange(200).reshape((10, 20)) assert da[5, 10] == 5 * 20 + 10 assert da.shape == (10, 20) assert len(str(da)) assert len(repr(da)) da[:] += 1 da.data[:] += 1 assert_equal(da[:, :], np.arange(200).reshape((10, 20)) + 2) @pytest.mark.codegen_independent def test_dynamic_array_2d_resize_up_down(): for numpy_resize in [True, False]: da = DynamicArray((10, 20), use_numpy_resize=numpy_resize, refcheck=False) da[:, :] = np.arange(200).reshape((10, 20)) da.resize((15, 20)) assert da.shape == (15, 20) assert_equal(da[10:, :], np.zeros((5, 20))) assert_equal(da[:10, :], np.arange(200).reshape((10, 20))) da.resize((15, 25)) assert da.shape == (15, 25) assert_equal(da[:10, 20:], np.zeros((10, 5))) assert_equal(da[:10, :20], np.arange(200).reshape((10, 20))) da.resize((10, 20)) assert da.shape == (10, 20) assert_equal(da[:, :], np.arange(200).reshape((10, 20))) @pytest.mark.codegen_independent def test_dynamic_array_2d_resize_down_up(): for numpy_resize in [True, False]: da = DynamicArray((10, 20), use_numpy_resize=numpy_resize, refcheck=False) da[:, :] = np.arange(200).reshape((10, 20)) da.resize((5, 20)) assert da.shape == (5, 20) assert_equal(da, np.arange(100).reshape((5, 20))) da.resize((5, 15)) assert da.shape == (5, 15) for row_idx, row in enumerate(da): assert_equal(row, 20 * row_idx + np.arange(15)) da.resize((10, 20)) assert da.shape == (10, 20) for row_idx, row in enumerate(da[:5, :15]): assert_equal(row, 20 * row_idx + np.arange(15)) assert_equal(da[5:, 15:], 0) @pytest.mark.codegen_independent def test_dynamic_array_2d_shrink(): for numpy_resize in [True, False]: da = DynamicArray((10, 20), use_numpy_resize=numpy_resize, refcheck=False) da[:, :] = np.arange(200).reshape((10, 20)) da.shrink((5, 15)) assert da.shape == (5, 15) # After using shrink, the underlying array should have changed assert da._data.shape == (5, 15) assert_equal( da[:, :], np.arange(15).reshape((1, 15)) + 20 * np.arange(5).reshape((5, 1)) ) if __name__ == "__main__": test_dynamic_array_1d_access() test_dynamic_array_1d_resize_up_down() test_dynamic_array_1d_resize_down_up() test_dynamic_array_1d_shrink() test_dynamic_array_2d_access() test_dynamic_array_2d_resize_up_down() test_dynamic_array_2d_resize_down_up() test_dynamic_array_2d_shrink() brian2-2.5.4/brian2/tests/test_monitor.py000066400000000000000000000624051445201106100203170ustar00rootroot00000000000000import logging import tempfile import uuid import pytest from numpy.testing import assert_array_equal from brian2 import * from brian2.devices.cpp_standalone.device import CPPStandaloneDevice from brian2.tests.utils import assert_allclose from brian2.utils.logger import catch_logs @pytest.mark.standalone_compatible def test_spike_monitor(): G_without_threshold = NeuronGroup(5, "x : 1") G = NeuronGroup( 3, """ dv/dt = rate : 1 rate: Hz """, threshold="v>1", reset="v=0", ) # We don't use 100 and 1000Hz, because then the membrane potential would # be exactly at 1 after 10 resp. 100 timesteps. Due to floating point # issues this will not be exact, G.rate = [101, 0, 1001] * Hz mon = SpikeMonitor(G) with pytest.raises(ValueError): SpikeMonitor(G, order=1) # need to specify 'when' as well with pytest.raises(ValueError) as ex: SpikeMonitor(G_without_threshold) assert "threshold" in str(ex) # Creating a SpikeMonitor for a Synapses object should not work S = Synapses(G, G, on_pre="v += 0") S.connect() with pytest.raises(TypeError): SpikeMonitor(S) run(10 * ms) spike_trains = mon.spike_trains() assert_allclose(mon.t[mon.i == 0], [9.9] * ms) assert len(mon.t[mon.i == 1]) == 0 assert_allclose(mon.t[mon.i == 2], np.arange(10) * ms + 0.9 * ms) assert_allclose(mon.t_[mon.i == 0], np.array([9.9 * float(ms)])) assert len(mon.t_[mon.i == 1]) == 0 assert_allclose(mon.t_[mon.i == 2], (np.arange(10) + 0.9) * float(ms)) assert_allclose(spike_trains[0], [9.9] * ms) assert len(spike_trains[1]) == 0 assert_allclose(spike_trains[2], np.arange(10) * ms + 0.9 * ms) assert_array_equal(mon.count, np.array([1, 0, 10])) i, t = mon.it i_, t_ = mon.it_ assert_array_equal(i, mon.i) assert_array_equal(i, i_) assert_array_equal(t, mon.t) assert_array_equal(t_, mon.t_) with pytest.raises(KeyError): spike_trains[3] with pytest.raises(KeyError): spike_trains[-1] with pytest.raises(KeyError): spike_trains["string"] # Check that indexing into the VariableView works (this fails if we do not # update the N variable correctly) assert_allclose(mon.t[:5], [0.9, 1.9, 2.9, 3.9, 4.9] * ms) def test_spike_monitor_indexing(): generator = SpikeGeneratorGroup(3, [0, 0, 0, 1], [0, 1, 2, 1] * ms) mon = SpikeMonitor(generator) run(3 * ms) assert_array_equal(mon.t["i == 1"], [1] * ms) assert_array_equal(mon.t[mon.i == 1], [1] * ms) assert_array_equal(mon.i[mon.t > 1.5 * ms], [0] * ms) assert_array_equal(mon.i["t > 1.5 * ms"], [0] * ms) @pytest.mark.standalone_compatible def test_spike_monitor_variables(): G = NeuronGroup( 3, """ dv/dt = rate : 1 rate : Hz prev_spikes : integer """, threshold="v>1", reset="v=0; prev_spikes += 1", ) # We don't use 100 and 1000Hz, because then the membrane potential would # be exactly at 1 after 10 resp. 100 timesteps. Due to floating point # issues this will not be exact, G.rate = [101, 0, 1001] * Hz mon1 = SpikeMonitor(G, variables="prev_spikes") mon2 = SpikeMonitor(G, variables="prev_spikes", when="after_resets") run(10 * ms) all_values = mon1.all_values() prev_spikes_values = mon1.values("prev_spikes") assert_array_equal(mon1.prev_spikes[mon1.i == 0], [0]) assert_array_equal(prev_spikes_values[0], [0]) assert_array_equal(all_values["prev_spikes"][0], [0]) assert_array_equal(mon1.prev_spikes[mon1.i == 1], []) assert_array_equal(prev_spikes_values[1], []) assert_array_equal(all_values["prev_spikes"][1], []) assert_array_equal(mon1.prev_spikes[mon1.i == 2], np.arange(10)) assert_array_equal(prev_spikes_values[2], np.arange(10)) assert_array_equal(all_values["prev_spikes"][2], np.arange(10)) assert_array_equal(mon2.prev_spikes[mon2.i == 0], [1]) assert_array_equal(mon2.prev_spikes[mon2.i == 1], []) assert_array_equal(mon2.prev_spikes[mon2.i == 2], np.arange(10) + 1) @pytest.mark.standalone_compatible def test_spike_monitor_get_states(): G = NeuronGroup( 3, """dv/dt = rate : 1 rate : Hz prev_spikes : integer""", threshold="v>1", reset="v=0; prev_spikes += 1", ) # We don't use 100 and 1000Hz, because then the membrane potential would # be exactly at 1 after 10 resp. 100 timesteps. Due to floating point # issues this will not be exact, G.rate = [101, 0, 1001] * Hz mon1 = SpikeMonitor(G, variables="prev_spikes") run(10 * ms) all_states = mon1.get_states() assert set(all_states.keys()) == {"count", "i", "t", "prev_spikes", "N"} assert_array_equal(all_states["count"], mon1.count[:]) assert_array_equal(all_states["i"], mon1.i[:]) assert_array_equal(all_states["t"], mon1.t[:]) assert_array_equal(all_states["prev_spikes"], mon1.prev_spikes[:]) assert_array_equal(all_states["N"], mon1.N) @pytest.mark.standalone_compatible def test_spike_monitor_subgroups(): G = NeuronGroup(6, """do_spike : boolean""", threshold="do_spike") G.do_spike = [True, False, False, False, True, True] spikes_all = SpikeMonitor(G) spikes_1 = SpikeMonitor(G[:2]) spikes_2 = SpikeMonitor(G[2:4]) spikes_3 = SpikeMonitor(G[4:]) run(defaultclock.dt) assert_allclose(spikes_all.i, [0, 4, 5]) assert_allclose(spikes_all.t, [0, 0, 0] * ms) assert_allclose(spikes_1.i, [0]) assert_allclose(spikes_1.t, [0] * ms) assert len(spikes_2.i) == 0 assert len(spikes_2.t) == 0 assert_allclose(spikes_3.i, [0, 1]) # recorded spike indices are relative assert_allclose(spikes_3.t, [0, 0] * ms) def test_spike_monitor_bug_824(): # See github issue #824 if prefs.codegen.target != "numpy": pytest.skip("numpy-only test") G = NeuronGroup(6, """do_spike : boolean""", threshold="do_spike") G.do_spike = [True, False, False, True, False, False] spikes_1 = SpikeMonitor(G[:3]) spikes_2 = SpikeMonitor(G[3:]) run(4 * defaultclock.dt) assert_array_equal(spikes_1.count, [4, 0, 0]) assert_array_equal(spikes_2.count, [4, 0, 0]) @pytest.mark.standalone_compatible def test_event_monitor(): G = NeuronGroup( 3, """ dv/dt = rate : 1 rate: Hz """, events={"my_event": "v>1"}, ) G.run_on_event("my_event", "v=0") # We don't use 100 and 1000Hz, because then the membrane potential would # be exactly at 1 after 10 resp. 100 timesteps. Due to floating point # issues this will not be exact, G.rate = [101, 0, 1001] * Hz mon = EventMonitor(G, "my_event") net = Network(G, mon) net.run(10 * ms) event_trains = mon.event_trains() assert_allclose(mon.t[mon.i == 0], [9.9] * ms) assert len(mon.t[mon.i == 1]) == 0 assert_allclose(mon.t[mon.i == 2], np.arange(10) * ms + 0.9 * ms) assert_allclose(mon.t_[mon.i == 0], np.array([9.9 * float(ms)])) assert len(mon.t_[mon.i == 1]) == 0 assert_allclose(mon.t_[mon.i == 2], (np.arange(10) + 0.9) * float(ms)) assert_allclose(event_trains[0], [9.9] * ms) assert len(event_trains[1]) == 0 assert_allclose(event_trains[2], np.arange(10) * ms + 0.9 * ms) assert_array_equal(mon.count, np.array([1, 0, 10])) i, t = mon.it i_, t_ = mon.it_ assert_array_equal(i, mon.i) assert_array_equal(i, i_) assert_array_equal(t, mon.t) assert_array_equal(t_, mon.t_) with pytest.raises(KeyError): event_trains[3] with pytest.raises(KeyError): event_trains[-1] with pytest.raises(KeyError): event_trains["string"] @pytest.mark.standalone_compatible def test_event_monitor_no_record(): # Check that you can switch off recording spike times/indices G = NeuronGroup( 3, """ dv/dt = rate : 1 rate: Hz """, events={"my_event": "v>1"}, threshold="v>1", reset="v=0", ) # We don't use 100 and 1000Hz, because then the membrane potential would # be exactly at 1 after 10 resp. 100 timesteps. Due to floating point # issues this will not be exact, G.rate = [101, 0, 1001] * Hz event_mon = EventMonitor(G, "my_event", record=False) event_mon2 = EventMonitor(G, "my_event", variables="rate", record=False) spike_mon = SpikeMonitor(G, record=False) spike_mon2 = SpikeMonitor(G, variables="rate", record=False) net = Network(G, event_mon, event_mon2, spike_mon, spike_mon2) net.run(10 * ms) # i and t should not be there assert "i" not in event_mon.variables assert "t" not in event_mon.variables assert "i" not in spike_mon.variables assert "t" not in spike_mon.variables assert_array_equal(event_mon.count, np.array([1, 0, 10])) assert_array_equal(spike_mon.count, np.array([1, 0, 10])) assert spike_mon.num_spikes == sum(spike_mon.count) assert event_mon.num_events == sum(event_mon.count) # Other variables should still have been recorded assert len(spike_mon2.rate) == spike_mon.num_spikes assert len(event_mon2.rate) == event_mon.num_events @pytest.mark.standalone_compatible def test_spike_trains(): # An arbitrary combination of indices that has been shown to sort in an # unstable way with quicksort and therefore lead to out-of-order values # in the dictionary returned by spike_trains() of several neurons (see #725) generator = SpikeGeneratorGroup( 10, [6, 1, 2, 4, 6, 9, 1, 4, 7, 8, 0, 6, 3, 6, 4, 8, 9, 2, 5, 3], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] * ms, dt=1 * ms, ) monitor = SpikeMonitor(generator) run(19.1 * ms) trains = monitor.spike_trains() for idx, spike_times in trains.items(): assert all( np.diff(spike_times) > 0 * ms ), f"Spike times for neuron {int(idx)} are not sorted" def test_synapses_state_monitor(): G = NeuronGroup(2, "") S = Synapses(G, G, "w: siemens") S.connect(True) S.w = "j*nS" # record from a Synapses object (all synapses connecting to neuron 1) synapse_mon = StateMonitor(S, "w", record=S[:, 1]) synapse_mon2 = StateMonitor(S, "w", record=S["j==1"]) net = Network(G, S, synapse_mon, synapse_mon2) net.run(10 * ms) # Synaptic variables assert_allclose(synapse_mon[S[0, 1]].w, 1 * nS) assert_allclose(synapse_mon.w[1], 1 * nS) assert_allclose(synapse_mon2[S[0, 1]].w, 1 * nS) assert_allclose(synapse_mon2[S["i==0 and j==1"]].w, 1 * nS) assert_allclose(synapse_mon2.w[1], 1 * nS) @pytest.mark.standalone_compatible def test_state_monitor(): # Unique name to get the warning even for repeated runs of the test unique_name = f"neurongroup_{str(uuid.uuid4()).replace('-', '_')}" # Check that all kinds of variables can be recorded G = NeuronGroup( 2, """ dv/dt = -v / (10*ms) : 1 f = clip(v, 0.1, 0.9) : 1 rate: Hz """, threshold="v>1", reset="v=0", refractory=2 * ms, name=unique_name, ) G.rate = [100, 1000] * Hz G.v = 1 S = Synapses(G, G, "w: siemens") S.connect(True) S.w = "j*nS" # A bit peculiar, but in principle one should be allowed to record # nothing except for the time nothing_mon = StateMonitor(G, [], record=True) no_record = StateMonitor(G, "v", record=False) # Use a single StateMonitor v_mon = StateMonitor(G, "v", record=True) v_mon1 = StateMonitor(G, "v", record=[1]) # Use a StateMonitor for specified variables multi_mon = StateMonitor(G, ["v", "f", "rate"], record=True) multi_mon1 = StateMonitor(G, ["v", "f", "rate"], record=[1]) # Use a StateMonitor recording everything all_mon = StateMonitor(G, True, record=True) # Record synapses with explicit indices (the only way allowed in standalone) synapse_mon = StateMonitor(S, "w", record=np.arange(len(G) ** 2)) run(10 * ms) # Check time recordings assert_array_equal(nothing_mon.t, v_mon.t) assert_array_equal(nothing_mon.t_, np.asarray(nothing_mon.t)) assert_array_equal(nothing_mon.t_, v_mon.t_) assert_allclose(nothing_mon.t, np.arange(len(nothing_mon.t)) * defaultclock.dt) assert_array_equal(no_record.t, v_mon.t) # Check v recording assert_allclose(v_mon.v.T, np.exp(np.tile(-v_mon.t, (2, 1)).T / (10 * ms))) assert_allclose(v_mon.v_.T, np.exp(np.tile(-v_mon.t_, (2, 1)).T / float(10 * ms))) assert_array_equal(v_mon.v, multi_mon.v) assert_array_equal(v_mon.v_, multi_mon.v_) assert_array_equal(v_mon.v, all_mon.v) assert_array_equal(v_mon.v[1:2], v_mon1.v) assert_array_equal(multi_mon.v[1:2], multi_mon1.v) assert len(no_record.v) == 0 # Other variables assert_array_equal( multi_mon.rate_.T, np.tile(np.atleast_2d(G.rate_), (multi_mon.rate.shape[1], 1)) ) assert_array_equal(multi_mon.rate[1:2], multi_mon1.rate) assert_allclose(np.clip(multi_mon.v, 0.1, 0.9), multi_mon.f) assert_allclose(np.clip(multi_mon1.v, 0.1, 0.9), multi_mon1.f) assert all( all_mon[0].not_refractory[:] == True ), "not_refractory should be True, but got(not_refractory, v):%s " % str( (all_mon.not_refractory, all_mon.v) ) # Synapses assert_allclose( synapse_mon.w[:], np.tile(S.j[:] * nS, (synapse_mon.w[:].shape[1], 1)).T ) @pytest.mark.standalone_compatible @pytest.mark.multiple_runs def test_state_monitor_record_single_timestep(): G = NeuronGroup(1, "dv/dt = -v/(5*ms) : 1") G.v = 1 mon = StateMonitor(G, "v", record=True) # Recording before a run should not work with pytest.raises(TypeError): mon.record_single_timestep() run(0.5 * ms) mon.record_single_timestep() device.build(direct_call=False, **device.build_options) assert mon.t[0] == 0 * ms assert mon[0].v[0] == 1 assert_allclose(mon.t[-1], 0.5 * ms) assert len(mon.t) == 6 assert mon[0].v[-1] == G.v @pytest.mark.standalone_compatible def test_state_monitor_bigger_dt(): # Check that it is possible to record with a slower clock, i.e. bigger dt G = NeuronGroup(1, "dv/dt = -v/(5*ms) : 1", method="exact") G.v = 1 mon = StateMonitor(G, "v", record=True) slow_mon = StateMonitor(G, "v", record=True, dt=defaultclock.dt * 5) run(defaultclock.dt * 11) assert len(mon.t) == len(mon.v[0]) == 11 assert len(slow_mon.t) == len(slow_mon.v[0]) == 3 for timepoint in [0, 5, 10]: assert mon.t[timepoint] == slow_mon.t[timepoint // 5] assert mon.v[0, timepoint] == slow_mon.v[0, timepoint // 5] @pytest.mark.standalone_compatible def test_state_monitor_indexing(): # Check indexing semantics G = NeuronGroup(10, "v:volt") G.v = np.arange(10) * volt mon = StateMonitor(G, "v", record=[5, 6, 7]) run(2 * defaultclock.dt) assert_array_equal(mon.v, np.array([[5, 5], [6, 6], [7, 7]]) * volt) assert_array_equal(mon.v_, np.array([[5, 5], [6, 6], [7, 7]])) assert_array_equal(mon[5].v, mon.v[0]) assert_array_equal(mon[7].v, mon.v[2]) assert_array_equal(mon[[5, 7]].v, mon.v[[0, 2]]) assert_array_equal(mon[np.array([5, 7])].v, mon.v[[0, 2]]) assert_allclose(mon.t[1:], Quantity([defaultclock.dt])) with pytest.raises(IndexError): mon[8] with pytest.raises(TypeError): mon["string"] with pytest.raises(TypeError): mon[5.0] with pytest.raises(TypeError): mon[[5.0, 6.0]] @pytest.mark.standalone_compatible def test_state_monitor_get_states(): G = NeuronGroup( 2, """ dv/dt = -v / (10*ms) : 1 f = clip(v, 0.1, 0.9) : 1 rate: Hz """, threshold="v>1", reset="v=0", refractory=2 * ms, ) G.rate = [100, 1000] * Hz G.v = 1 mon = StateMonitor(G, ["v", "f", "rate"], record=True) run(10 * defaultclock.dt) all_states = mon.get_states() assert set(all_states.keys()) == {"v", "f", "rate", "t", "N"} assert_array_equal(all_states["v"].T, mon.v[:]) assert_array_equal(all_states["f"].T, mon.f[:]) assert_array_equal(all_states["rate"].T, mon.rate[:]) assert_array_equal(all_states["t"], mon.t[:]) assert_array_equal(all_states["N"], mon.N) @pytest.mark.standalone_compatible def test_state_monitor_resize(): # Test for issue #518 (cython did not resize the Variable object) G = NeuronGroup(2, "v : 1") mon = StateMonitor(G, "v", record=True) defaultclock.dt = 0.1 * ms run(1 * ms) # On standalone, the size information of the variables is only updated # after the variable has been accessed, so we can not only check the size # information of the variables object assert len(mon.t) == 10 assert mon.v.shape == (2, 10) assert mon.variables["t"].size == 10 # Note that the internally stored variable has the transposed shape of the # variable that is visible to the user assert mon.variables["v"].size == (10, 2) @pytest.mark.standalone_compatible def test_state_monitor_synapses(): # Check that recording from synapses works correctly G = NeuronGroup(5, "v : 1", threshold="False", reset="v = 0") S1 = Synapses(G, G, "w : 1", on_pre="v_post += w") S1.run_regularly("w += 1") S1.connect(i=[0, 1], j=[2, 3]) S1.w = "i" # We can check the record argument even in standalone mode, since we created # the synapses with an array of indices of known length with catch_logs() as l: S1_mon = StateMonitor(S1, "w", record=[0, 1]) assert len(l) == 0 with pytest.raises(IndexError): StateMonitor(S1, "w", record=[0, 2]) S2 = Synapses(G, G, "w : 1", on_pre="v_post += w") S2.run_regularly("w += 1") S2.connect("i==j") # Not yet executed for standalone mode S2.w = "i" with catch_logs() as l: S2_mon = StateMonitor(S2, "w", record=[0, 4]) if isinstance(get_device(), CPPStandaloneDevice): assert len(l) == 1 assert l[0][0] == "WARNING" assert l[0][1].endswith(".cannot_check_statemonitor_indices") else: assert len(l) == 0 run(2 * defaultclock.dt) assert_array_equal(S1_mon.w[:], [[0, 1], [1, 2]]) assert_array_equal(S2_mon.w[:], [[0, 1], [4, 5]]) @pytest.mark.codegen_independent def test_statemonitor_namespace(): # Make sure that StateMonitor is correctly inheriting its source's namespace G = NeuronGroup(2, "x = i + y : integer", namespace={"y": 3}) mon = StateMonitor(G, "x", record=True) run(defaultclock.dt, namespace={}) assert_array_equal(mon.x, [[3], [4]]) @pytest.mark.standalone_compatible def test_rate_monitor_1(): G = NeuronGroup(5, "v : 1", threshold="v>1") # no reset G.v = 1.1 # All neurons spike every time step rate_mon = PopulationRateMonitor(G) run(10 * defaultclock.dt) assert_allclose(rate_mon.t, np.arange(10) * defaultclock.dt) assert_allclose(rate_mon.t_, np.arange(10) * defaultclock.dt_) assert_allclose(rate_mon.t, np.arange(10) * defaultclock.dt) assert_allclose(rate_mon.rate, np.ones(10) / defaultclock.dt) assert_allclose(rate_mon.rate_, np.asarray(np.ones(10) / defaultclock.dt_)) # Check that indexing into the VariableView works (this fails if we do not # update the N variable correctly) assert_allclose(rate_mon.t[:5], np.arange(5) * defaultclock.dt) @pytest.mark.standalone_compatible def test_rate_monitor_2(): G = NeuronGroup(10, "v : 1", threshold="v>1") # no reset G.v["i<5"] = 1.1 # Half of the neurons fire every time step rate_mon = PopulationRateMonitor(G) net = Network(G, rate_mon) net.run(10 * defaultclock.dt) assert_allclose(rate_mon.rate, 0.5 * np.ones(10) / defaultclock.dt) assert_allclose(rate_mon.rate_, 0.5 * np.asarray(np.ones(10) / defaultclock.dt_)) @pytest.mark.codegen_independent def test_rate_monitor_smoothed_rate(): # Test the filter response by having a single spiking neuron G = SpikeGeneratorGroup(1, [0], [1] * ms) r_mon = PopulationRateMonitor(G) run(3 * ms) index = int(np.round(1 * ms / defaultclock.dt)) except_index = np.array([idx for idx in range(len(r_mon.rate)) if idx != index]) assert_array_equal(r_mon.rate[except_index], 0 * Hz) assert_allclose(r_mon.rate[index], 1 / defaultclock.dt) ### Flat window # Using a flat window of size = dt should not change anything assert_allclose(r_mon.rate, r_mon.smooth_rate(window="flat", width=defaultclock.dt)) smoothed = r_mon.smooth_rate(window="flat", width=5 * defaultclock.dt) assert_array_equal(smoothed[: index - 2], 0 * Hz) assert_array_equal(smoothed[index + 3 :], 0 * Hz) assert_allclose(smoothed[index - 2 : index + 3], 0.2 / defaultclock.dt) with catch_logs(log_level=logging.INFO): smoothed2 = r_mon.smooth_rate(window="flat", width=5.4 * defaultclock.dt) assert_array_equal(smoothed, smoothed2) ### Gaussian window width = 5 * defaultclock.dt smoothed = r_mon.smooth_rate(window="gaussian", width=width) # 0 outside of window assert_array_equal(smoothed[: index - 10], 0 * Hz) assert_array_equal(smoothed[index + 11 :], 0 * Hz) # Gaussian around the spike gaussian = np.exp( -((r_mon.t[index - 10 : index + 11] - 1 * ms) ** 2) / (2 * width**2) ) gaussian /= sum(gaussian) assert_allclose(smoothed[index - 10 : index + 11], 1 / defaultclock.dt * gaussian) ### Arbitrary window window = np.ones(5) smoothed_flat = r_mon.smooth_rate(window="flat", width=5 * defaultclock.dt) smoothed_custom = r_mon.smooth_rate(window=window) assert_allclose(smoothed_flat, smoothed_custom) @pytest.mark.codegen_independent def test_rate_monitor_smoothed_rate_incorrect(): # Test the filter response by having a single spiking neuron G = SpikeGeneratorGroup(1, [0], [1] * ms) r_mon = PopulationRateMonitor(G) run(2 * ms) with pytest.raises(TypeError): r_mon.smooth_rate(window="flat") # no width with pytest.raises(TypeError): r_mon.smooth_rate(window=np.ones(5), width=1 * ms) with pytest.raises(NotImplementedError): r_mon.smooth_rate(window="unknown", width=1 * ms) with pytest.raises(TypeError): r_mon.smooth_rate(window=object()) with pytest.raises(TypeError): r_mon.smooth_rate(window=np.ones(5, 2)) with pytest.raises(TypeError): r_mon.smooth_rate(window=np.ones(4)) # even number @pytest.mark.standalone_compatible def test_rate_monitor_get_states(): G = NeuronGroup(5, "v : 1", threshold="v>1") # no reset G.v = 1.1 # All neurons spike every time step rate_mon = PopulationRateMonitor(G) run(10 * defaultclock.dt) all_states = rate_mon.get_states() assert set(all_states.keys()) == {"rate", "t", "N"} assert_array_equal(all_states["rate"], rate_mon.rate[:]) assert_array_equal(all_states["t"], rate_mon.t[:]) assert_array_equal(all_states["N"], rate_mon.N) @pytest.mark.standalone_compatible def test_rate_monitor_subgroups(): old_dt = defaultclock.dt defaultclock.dt = 0.01 * ms G = NeuronGroup( 4, """ dv/dt = rate : 1 rate : Hz """, threshold="v>0.999", reset="v=0", ) G.rate = [100, 200, 400, 800] * Hz rate_all = PopulationRateMonitor(G) rate_1 = PopulationRateMonitor(G[:2]) rate_2 = PopulationRateMonitor(G[2:]) run(1 * second) assert_allclose(mean(G.rate_[:]), mean(rate_all.rate_[:])) assert_allclose(mean(G.rate_[:2]), mean(rate_1.rate_[:])) assert_allclose(mean(G.rate_[2:]), mean(rate_2.rate_[:])) defaultclock.dt = old_dt @pytest.mark.standalone_compatible def test_rate_monitor_subgroups_2(): G = NeuronGroup(6, """do_spike : boolean""", threshold="do_spike") G.do_spike = [True, False, False, False, True, True] rate_all = PopulationRateMonitor(G) rate_1 = PopulationRateMonitor(G[:2]) rate_2 = PopulationRateMonitor(G[2:4]) rate_3 = PopulationRateMonitor(G[4:]) run(2 * defaultclock.dt) assert_allclose(rate_all.rate, 0.5 / defaultclock.dt) assert_allclose(rate_1.rate, 0.5 / defaultclock.dt) assert_allclose(rate_2.rate, 0 * Hz) assert_allclose(rate_3.rate, 1 / defaultclock.dt) @pytest.mark.codegen_independent def test_monitor_str_repr(): # Basic test that string representations are not empty G = NeuronGroup(2, "dv/dt = -v/(10*ms) : 1", threshold="v>1", reset="v=0") spike_mon = SpikeMonitor(G) state_mon = StateMonitor(G, "v", record=True) rate_mon = PopulationRateMonitor(G) for obj in [spike_mon, state_mon, rate_mon]: assert len(str(obj)) assert len(repr(obj)) if __name__ == "__main__": from _pytest.outcomes import Skipped test_spike_monitor() test_spike_monitor_indexing() test_spike_monitor_get_states() test_spike_monitor_subgroups() try: test_spike_monitor_bug_824() except Skipped: pass test_spike_monitor_variables() test_event_monitor() test_event_monitor_no_record() test_spike_trains() test_synapses_state_monitor() test_state_monitor() test_state_monitor_record_single_timestep() test_state_monitor_get_states() test_state_monitor_indexing() test_state_monitor_resize() test_rate_monitor_1() test_rate_monitor_2() test_rate_monitor_smoothed_rate() test_rate_monitor_smoothed_rate_incorrect() test_rate_monitor_get_states() test_rate_monitor_subgroups() test_rate_monitor_subgroups_2() test_monitor_str_repr() brian2-2.5.4/brian2/tests/test_morphology.py000066400000000000000000001676751445201106100210460ustar00rootroot00000000000000import os import tempfile import pytest from numpy.testing import assert_equal from brian2 import numpy as np from brian2.spatialneuron import * from brian2.tests.utils import assert_allclose from brian2.units import DimensionMismatchError, cm, second, um @pytest.mark.codegen_independent def test_attributes_soma(): soma = Soma(diameter=10 * um) assert isinstance(soma, Morphology) # Single compartment assert soma.n == 1 assert soma.total_sections == 1 assert soma.total_compartments == 1 with pytest.raises(TypeError): len(soma) # ambiguous # Compartment attributes assert_equal(soma.diameter, [10] * um) assert_equal(soma.length, [10] * um) assert_equal(soma.distance, [0] * um) assert_equal(soma.end_distance, 0 * um) assert soma.r_length_1 > 1 * cm assert soma.r_length_2 > 1 * cm assert_equal(soma.area, np.pi * soma.diameter**2) assert_allclose(soma.volume, 1.0 / 6.0 * np.pi * (10 * um) ** 3) # No coordinates were specified assert soma.start_x is None assert soma.start_y is None assert soma.start_z is None assert soma.x is None assert soma.y is None assert soma.z is None assert soma.end_x is None assert soma.end_y is None assert soma.end_z is None @pytest.mark.codegen_independent def test_attributes_soma_coordinates(): # Specify only one of the coordinates xyz = {"x", "y", "z"} for coord in xyz: kwds = {coord: 5 * um} soma = Soma(diameter=10 * um, **kwds) # Length shouldn't change (not defined by coordinates but by the diameter) assert_equal(soma.length, [10] * um) assert_equal(soma.distance, [0] * um) # Coordinates should be specified now, with 0 values for the other # coordinates for other_coord in xyz - {coord}: assert_equal(getattr(soma, f"start_{other_coord}"), [0] * um) assert_equal(getattr(soma, other_coord), [0] * um) assert_equal(getattr(soma, f"end_{other_coord}"), [0] * um) assert_equal(getattr(soma, f"start_{coord}"), [5] * um) assert_equal(getattr(soma, coord), [5] * um) assert_equal(getattr(soma, f"end_{coord}"), [5] * um) # Specify all coordinates soma = Soma(diameter=10 * um, x=1 * um, y=2 * um, z=3 * um) # Length shouldn't change (not defined by coordinates but by the diameter) assert_equal(soma.length, [10] * um) assert_equal(soma.distance, [0] * um) assert_equal(soma.start_x, 1 * um) assert_equal(soma.x, 1 * um) assert_equal(soma.end_x, 1 * um) assert_equal(soma.start_y, 2 * um) assert_equal(soma.y, 2 * um) assert_equal(soma.end_y, 2 * um) assert_equal(soma.start_z, 3 * um) assert_equal(soma.z, 3 * um) assert_equal(soma.end_z, 3 * um) @pytest.mark.codegen_independent def test_attributes_cylinder(): n = 10 cylinder = Cylinder(n=n, diameter=10 * um, length=200 * um) assert isinstance(cylinder, Morphology) # Single section with 10 compartments assert cylinder.n == n assert cylinder.total_sections == 1 assert cylinder.total_compartments == n with pytest.raises(TypeError): len(cylinder) # ambiguous # Compartment attributes assert_equal(cylinder.diameter, np.ones(n) * 10 * um) assert_equal(cylinder.length, np.ones(n) * 20 * um) assert_equal(cylinder.distance, np.arange(n) * 20 * um + 10 * um) assert_equal(cylinder.end_distance, 200 * um) # TODO: r_length assert_allclose(cylinder.area, np.pi * cylinder.diameter * cylinder.length) assert_allclose( cylinder.volume, 1.0 / 4.0 * np.pi * cylinder.diameter**2 * cylinder.length ) # No coordinates were specified assert cylinder.start_x is None assert cylinder.start_y is None assert cylinder.start_z is None assert cylinder.x is None assert cylinder.y is None assert cylinder.z is None assert cylinder.end_x is None assert cylinder.end_y is None assert cylinder.end_z is None @pytest.mark.codegen_independent def test_attributes_cylinder_coordinates(): # Specify only the end-point of the section n = 10 # Specify only one of the coordinates xyz = {"x", "y", "z"} for coord in xyz: kwds = {coord: [0, 200] * um} cylinder = Cylinder(n=n, diameter=10 * um, **kwds) assert_equal(cylinder.diameter, np.ones(n) * 10 * um) assert_equal(cylinder.length, np.ones(n) * 20 * um) assert_equal(cylinder.distance, np.arange(n) * 20 * um + 10 * um) assert_equal(cylinder.end_distance, 200 * um) # Coordinates should be specified now, with 0 values for the other # coordinates for other_coord in xyz - {coord}: assert_equal(getattr(cylinder, f"start_{other_coord}"), np.zeros(n) * um) assert_equal(getattr(cylinder, other_coord), np.zeros(n) * um) assert_equal(getattr(cylinder, f"end_{other_coord}"), np.zeros(n) * um) assert_equal(getattr(cylinder, f"start_{coord}"), np.arange(n) * 20 * um) assert_equal(getattr(cylinder, coord), np.arange(n) * 20 * um + 10 * um) assert_equal( getattr(cylinder, f"end_{coord}"), np.arange(n) * 20 * um + 20 * um ) # Specify all coordinates val = [0, 200.0 / np.sqrt(3.0)] * um cylinder = Cylinder(n=n, diameter=10 * um, x=val, y=val, z=val) assert_equal(cylinder.diameter, np.ones(n) * 10 * um) assert_allclose(cylinder.length, np.ones(n) * 20 * um) assert_allclose(cylinder.distance, np.arange(n) * 20 * um + 10 * um) assert_allclose(cylinder.end_distance, 200 * um) for coord in ["x", "y", "z"]: assert_allclose(getattr(cylinder, f"start_{coord}"), np.arange(n) * val[1] / n) assert_allclose( getattr(cylinder, coord), np.arange(n) * val[1] / n + 0.5 * val[1] / n ) assert_allclose( getattr(cylinder, f"end_{coord}"), np.arange(n) * val[1] / n + val[1] / n ) @pytest.mark.codegen_independent def test_attributes_section(): n = 10 # No difference to a cylinder sec = Section(n=n, diameter=np.ones(n + 1) * 10 * um, length=np.ones(n) * 20 * um) cyl = Cylinder(n=1, diameter=10 * um, length=0 * um) # dummy cylinder cyl.child = sec assert isinstance(sec, Morphology) # Single section with 10 compartments assert sec.n == n assert sec.total_sections == 1 assert sec.total_compartments == n with pytest.raises(TypeError): len(sec) # ambiguous # Compartment attributes assert_allclose(sec.diameter, np.ones(n) * 10 * um) assert_allclose(sec.length, np.ones(n) * 20 * um) assert_allclose(sec.distance, np.arange(n) * 20 * um + 10 * um) assert_allclose(sec.end_distance, 200 * um) # TODO: r_length assert_allclose( sec.area, np.pi * 0.5 * (sec.start_diameter + sec.end_diameter) * sec.length ) assert_allclose(sec.volume, 1.0 / 4.0 * np.pi * sec.diameter**2 * sec.length) # No coordinates were specified assert sec.start_x is None assert sec.start_y is None assert sec.start_z is None assert sec.x is None assert sec.y is None assert sec.z is None assert sec.end_x is None assert sec.end_y is None assert sec.end_z is None @pytest.mark.codegen_independent def test_attributes_section_coordinates_single(): # Specify only the end-point of the section (no difference to cylinder) n = 10 # Specify only one of the coordinates xyz = {"x", "y", "z"} for coord in xyz: kwds = {coord: np.linspace(0 * um, 200 * um, n + 1)} sec = Section(n=n, diameter=np.ones(n + 1) * 10 * um, **kwds) cyl = Cylinder(n=1, diameter=10 * um, length=0 * um) # dummy cylinder cyl.child = sec assert_equal(sec.diameter, np.ones(n) * 10 * um) assert_equal(sec.length, np.ones(n) * 20 * um) assert_equal(sec.distance, np.arange(n) * 20 * um + 10 * um) assert_equal(sec.end_distance, 200 * um) # Coordinates should be specified now, with 0 values for the other # coordinates for other_coord in xyz - {coord}: assert_equal(getattr(sec, f"start_{other_coord}"), np.zeros(n) * um) assert_equal(getattr(sec, other_coord), np.zeros(n) * um) assert_equal(getattr(sec, f"end_{other_coord}"), np.zeros(n) * um) assert_equal(getattr(sec, f"start_{coord}"), np.arange(n) * 20 * um) assert_equal(getattr(sec, coord), np.arange(n) * 20 * um + 10 * um) assert_equal(getattr(sec, f"end_{coord}"), np.arange(n) * 20 * um + 20 * um) # Specify all coordinates val = 200.0 / np.sqrt(3.0) * um sec = Section( n=n, diameter=np.ones(n + 1) * 10 * um, x=np.linspace(0 * um, val, n + 1), y=np.linspace(0 * um, val, n + 1), z=np.linspace(0 * um, val, n + 1), ) cyl = Cylinder(n=1, diameter=10 * um, length=0 * um) cyl.child = sec assert_equal(sec.diameter, np.ones(n) * 10 * um) assert_allclose(sec.length, np.ones(n) * 20 * um) assert_allclose(sec.distance, np.arange(n) * 20 * um + 10 * um) assert_allclose(sec.end_distance, 200 * um) for coord in ["x", "y", "z"]: assert_allclose(getattr(sec, f"start_{coord}"), np.arange(n) * val / n) assert_allclose(getattr(sec, coord), np.arange(n) * val / n + 0.5 * val / n) assert_allclose(getattr(sec, f"end_{coord}"), np.arange(n) * val / n + val / n) @pytest.mark.codegen_independent def test_attributes_section_coordinates_all(): n = 3 # Specify all coordinates sec = Section( n=n, diameter=[10, 10, 10, 10] * um, x=[10, 11, 11, 11] * um, y=[100, 100, 101, 101] * um, z=[1000, 1000, 1000, 1001] * um, ) assert_equal(sec.diameter, np.ones(n) * 10 * um) assert_allclose(sec.length, np.ones(n) * um) assert_allclose(sec.distance, np.arange(n) * um + 0.5 * um) assert_allclose(sec.end_distance, 3 * um) assert_allclose(sec.start_x, [10, 11, 11] * um) assert_allclose(sec.x, [10.5, 11, 11] * um) assert_allclose(sec.end_x, [11, 11, 11] * um) assert_allclose(sec.start_y, [100, 100, 101] * um) assert_allclose(sec.y, [100, 100.5, 101] * um) assert_allclose(sec.end_y, [100, 101, 101] * um) assert_allclose(sec.start_z, [1000, 1000, 1000] * um) assert_allclose(sec.z, [1000, 1000, 1000.5] * um) assert_allclose(sec.end_z, [1000, 1000, 1001] * um) # Specify varying diameters sec = Section( n=n, diameter=[20, 10, 5, 2.5] * um, x=[0, 1, 1, 1] * um, y=[0, 0, 1, 1] * um, z=[0, 0, 0, 1] * um, ) assert_allclose(sec.start_diameter, [20, 10, 5] * um) # diameter at midpoint assert_allclose(sec.diameter, 0.5 * (sec.start_diameter + sec.end_diameter)) assert_allclose(sec.end_diameter, [10, 5, 2.5] * um) # TODO: Check area and volume def _check_tree_cables(morphology, coordinates=False): # number of compartments per section assert morphology.n == 10 assert morphology["1"].n == 5 assert morphology["2"].n == 5 assert morphology["21"].n == 5 assert morphology["22"].n == 5 # number of compartments per subtree assert morphology.total_compartments == 30 assert morphology["1"].total_compartments == 5 assert morphology["2"].total_compartments == 15 assert morphology["21"].total_compartments == 5 assert morphology["22"].total_compartments == 5 # number of sections per subtree assert morphology.total_sections == 5 assert morphology["1"].total_sections == 1 assert morphology["2"].total_sections == 3 assert morphology["21"].total_sections == 1 assert morphology["22"].total_sections == 1 # Check that distances (= distance to root at electrical midpoint) # correctly follow the tree structure assert_allclose(morphology.distance, np.arange(10) * 10 * um + 5 * um) assert_allclose( morphology["2"].distance, 100 * um + np.arange(5) * 10 * um + 5 * um ) assert_allclose( morphology["21"].distance, 150 * um + np.arange(5) * 10 * um + 5 * um ) assert_allclose(morphology.end_distance, 100 * um) assert_allclose(morphology["1"].end_distance, 200 * um) assert_allclose(morphology["2"].end_distance, 150 * um) assert_allclose(morphology["21"].end_distance, 200 * um) assert_allclose(morphology["22"].end_distance, 200 * um) # Check that section diameters are correctly inherited from the parent # sections assert_allclose(morphology["1"].start_diameter, [10, 8, 6, 4, 2] * um) assert_allclose(morphology["22"].start_diameter, [5, 4, 3, 2, 1] * um) if coordinates: # Coordinates should be absolute # section: cable assert_allclose(morphology.start_x, np.arange(10) * 10 * um) assert_allclose(morphology.x, np.arange(10) * 10 * um + 5 * um) assert_allclose(morphology.end_x, np.arange(10) * 10 * um + 10 * um) assert_allclose(morphology.y, np.zeros(10) * um) assert_allclose(morphology.z, np.zeros(10) * um) # section: cable['1'] step = 20 / np.sqrt(2) * um assert_allclose(morphology["1"].start_x, 100 * um + np.arange(5) * step) assert_allclose(morphology["1"].x, 100 * um + np.arange(5) * step + step / 2) assert_allclose(morphology["1"].end_x, 100 * um + np.arange(5) * step + step) assert_allclose(morphology["1"].start_y, np.arange(5) * step) assert_allclose(morphology["1"].y, np.arange(5) * step + step / 2) assert_allclose(morphology["1"].end_y, np.arange(5) * step + step) assert_allclose(morphology["1"].z, np.zeros(5) * um) # section: cable['2'] step = 10 / np.sqrt(2) * um assert_allclose(morphology["2"].start_x, 100 * um + np.arange(5) * step) assert_allclose(morphology["2"].x, 100 * um + np.arange(5) * step + step / 2) assert_allclose(morphology["2"].end_x, 100 * um + np.arange(5) * step + step) assert_allclose(morphology["2"].start_y, -np.arange(5) * step) assert_allclose(morphology["2"].y, -(np.arange(5) * step + step / 2)) assert_allclose(morphology["2"].end_y, -(np.arange(5) * step + step)) assert_allclose(morphology["2"].z, np.zeros(5) * um) # section: cable ['21'] step = 10 / np.sqrt(2) * um assert_allclose( morphology["21"].start_x, 100 * um + 50 / np.sqrt(2) * um + np.arange(5) * step, ) assert_allclose( morphology["21"].x, 100 * um + 50 / np.sqrt(2) * um + np.arange(5) * step + step / 2, ) assert_allclose( morphology["21"].end_x, 100 * um + 50 / np.sqrt(2) * um + np.arange(5) * step + step, ) assert_allclose(morphology["21"].start_y, -np.ones(5) * 50 / np.sqrt(2) * um) assert_allclose(morphology["21"].y, -np.ones(5) * 50 / np.sqrt(2) * um) assert_allclose(morphology["21"].end_y, -np.ones(5) * 50 / np.sqrt(2) * um) assert_allclose(morphology["21"].start_z, np.arange(5) * step) assert_allclose(morphology["21"].z, np.arange(5) * step + step / 2) assert_allclose(morphology["21"].end_z, np.arange(5) * step + step) # section: cable['22'] step = 10 / np.sqrt(2) * um assert_allclose( morphology["22"].start_x, 100 * um + 50 / np.sqrt(2) * um + np.arange(5) * step, ) assert_allclose( morphology["22"].x, 100 * um + 50 / np.sqrt(2) * um + np.arange(5) * step + step / 2, ) assert_allclose( morphology["22"].end_x, 100 * um + 50 / np.sqrt(2) * um + np.arange(5) * step + step, ) assert_allclose(morphology["22"].start_y, -np.ones(5) * 50 / np.sqrt(2) * um) assert_allclose(morphology["22"].y, -np.ones(5) * 50 / np.sqrt(2) * um) assert_allclose(morphology["22"].end_y, -np.ones(5) * 50 / np.sqrt(2) * um) assert_allclose(morphology["22"].start_z, -np.arange(5) * step) assert_allclose(morphology["22"].z, -(np.arange(5) * step + step / 2)) assert_allclose(morphology["22"].end_z, -(np.arange(5) * step + step)) @pytest.mark.codegen_independent def test_tree_cables_schematic(): cable = Cylinder(n=10, diameter=10 * um, length=100 * um) cable.L = Section( n=5, diameter=[10, 8, 6, 4, 2, 0] * um, length=np.ones(5) * 20 * um ) # tapering truncated cones cable.R = Cylinder(n=5, diameter=5 * um, length=50 * um) cable.RL = Cylinder(n=5, diameter=2.5 * um, length=50 * um) cable.RR = Section( n=5, diameter=[5, 4, 3, 2, 1, 0] * um, length=np.ones(5) * 10 * um ) _check_tree_cables(cable) @pytest.mark.codegen_independent def test_tree_cables_coordinates(): # The lengths of the sections should be identical to the previous test cable = Cylinder(n=10, x=[0, 100] * um, diameter=10 * um) cable.L = Section( n=5, diameter=[10, 8, 6, 4, 2, 0] * um, x=np.linspace(0, 100, 6) / np.sqrt(2) * um, y=np.linspace(0, 100, 6) / np.sqrt(2) * um, ) cable.R = Cylinder( n=5, diameter=5 * um, x=[0, 50] * um / np.sqrt(2), y=[0, -50] * um / np.sqrt(2) ) cable.RL = Cylinder( n=5, diameter=2.5 * um, x=[0, 50] * um / np.sqrt(2), z=[0, 50] * um / np.sqrt(2) ) cable.RR = Section( n=5, diameter=[5, 4, 3, 2, 1, 0] * um, x=np.linspace(0, 50, 6) * um / np.sqrt(2), z=np.linspace(0, -50, 6) * um / np.sqrt(2), ) _check_tree_cables(cable, coordinates=True) @pytest.mark.codegen_independent def test_tree_cables_from_points(): # The coordinates should be identical to the previous test # fmt: off points = [ # cable (1, None, 0, 0, 0, 10, -1), (2, None, 10, 0, 0, 10, 1), (3, None, 20, 0, 0, 10, 2), (4, None, 30, 0, 0, 10, 3), (5, None, 40, 0, 0, 10, 4), (6, None, 50, 0, 0, 10, 5), (7, None, 60, 0, 0, 10, 6), (8, None, 70, 0, 0, 10, 7), (9, None, 80, 0, 0, 10, 8), (10, None, 90, 0, 0, 10, 9), (11, None, 100, 0, 0, 10, 10), # cable.L (using automatic names) (12, None, 100+20/np.sqrt(2), 20/np.sqrt(2), 0, 8 , 11), (13, None, 100+40/np.sqrt(2), 40/np.sqrt(2), 0, 6 , 12), (14, None, 100+60/np.sqrt(2), 60/np.sqrt(2), 0, 4 , 13), (15, None, 100+80/np.sqrt(2), 80/np.sqrt(2), 0, 2 , 14), (16, None, 100+100/np.sqrt(2), 100/np.sqrt(2), 0, 0 , 15), # cable.R (using automatic names) (17, None, 100+10/np.sqrt(2), -10/np.sqrt(2), 0, 5 , 11), (18, None, 100+20/np.sqrt(2), -20/np.sqrt(2), 0, 5 , 17), (19, None, 100+30/np.sqrt(2), -30/np.sqrt(2), 0, 5 , 18), (20, None, 100+40/np.sqrt(2), -40/np.sqrt(2), 0, 5 , 19), (21, None, 100+50/np.sqrt(2), -50/np.sqrt(2), 0, 5 , 20), # cable.RL (using explicit names) (22, 'L' , 100+60/np.sqrt(2), -50/np.sqrt(2), 10/np.sqrt(2), 2.5, 21), (23, 'L' , 100+70/np.sqrt(2), -50/np.sqrt(2), 20/np.sqrt(2), 2.5, 22), (24, 'L' , 100+80/np.sqrt(2), -50/np.sqrt(2), 30/np.sqrt(2), 2.5, 23), (25, 'L' , 100+90/np.sqrt(2), -50/np.sqrt(2), 40/np.sqrt(2), 2.5, 24), (26, 'L' , 100+100/np.sqrt(2), -50/np.sqrt(2), 50/np.sqrt(2), 2.5, 25), # cable.RR (using explicit names) (27, 'R' , 100+60/np.sqrt(2), -50/np.sqrt(2), -10/np.sqrt(2), 4, 21), (28, 'R' , 100+70/np.sqrt(2), -50/np.sqrt(2), -20/np.sqrt(2), 3, 27), (29, 'R' , 100+80/np.sqrt(2), -50/np.sqrt(2), -30/np.sqrt(2), 2, 28), (30, 'R' , 100+90/np.sqrt(2), -50/np.sqrt(2), -40/np.sqrt(2), 1, 29), (31, 'R' , 100+100/np.sqrt(2), -50/np.sqrt(2), -50/np.sqrt(2), 0, 30), ] # fmt: on cable = Morphology.from_points(points) # Check that the names are used assert cable.L.n == 5 assert cable.R.n == 5 assert cable.RL.n == 5 assert cable.RR.n == 5 _check_tree_cables(cable, coordinates=True) def test_tree_cables_from_swc(): swc_content = """ # Test file 1 0 0 0 0 5 -1 2 0 10 0 0 5 1 3 0 20 0 0 5 2 4 0 30 0 0 5 3 5 0 40 0 0 5 4 6 0 50 0 0 5 5 7 0 60 0 0 5 6 8 0 70 0 0 5 7 9 0 80 0 0 5 8 10 0 90 0 0 5 9 11 0 100 0 0 5 10 12 2 114.14213562373095 14.142135623730949 0 4 11 13 2 128.2842712474619 28.284271247461898 0 3 12 14 2 142.42640687119285 42.426406871192846 0 2 13 15 2 156.5685424949238 56.568542494923797 0 1 14 16 2 170.71067811865476 70.710678118654741 0 0 15 17 2 107.07106781186548 -7.0710678118654746 0 2.5 11 18 2 114.14213562373095 -14.142135623730949 0 2.5 17 19 2 121.21320343559643 -21.213203435596423 0 2.5 18 20 2 128.2842712474619 -28.284271247461898 0 2.5 19 21 2 135.35533905932738 -35.35533905932737 0 2.5 20 22 2 142.42640687119285 -35.35533905932737 7.0710678118654746 1.25 21 23 2 149.49747468305833 -35.35533905932737 14.142135623730949 1.25 22 24 2 156.5685424949238 -35.35533905932737 21.213203435596423 1.25 23 25 2 163.63961030678928 -35.35533905932737 28.284271247461898 1.25 24 26 2 170.71067811865476 -35.35533905932737 35.35533905932737 1.25 25 27 2 142.42640687119285 -35.35533905932737 -7.0710678118654746 2 21 28 2 149.49747468305833 -35.35533905932737 -14.142135623730949 1.5 27 29 2 156.5685424949238 -35.35533905932737 -21.213203435596423 1 28 30 2 163.63961030678928 -35.35533905932737 -28.284271247461898 0.5 29 31 2 170.71067811865476 -35.35533905932737 -35.35533905932737 0 30 """ tmp_filename = tempfile.mktemp("cable_morphology.swc") with open(tmp_filename, "w") as f: f.write(swc_content) cable = Morphology.from_file(tmp_filename) os.remove(tmp_filename) _check_tree_cables(cable, coordinates=True) def _check_tree_soma(morphology, coordinates=False, use_cylinders=True): # number of compartments per section assert morphology.n == 1 assert morphology["1"].n == 5 assert morphology["2"].n == 5 # number of compartments per subtree assert morphology.total_compartments == 11 assert morphology["1"].total_compartments == 5 assert morphology["2"].total_compartments == 5 # number of sections per subtree assert morphology.total_sections == 3 assert morphology["1"].total_sections == 1 assert morphology["2"].total_sections == 1 assert_allclose(morphology.diameter, [30] * um) # Check that distances (= distance to root at midpoint) # correctly follow the tree structure # Note that the soma does add nothing to the distance assert_equal(morphology.distance, 0 * um) assert_allclose(morphology["1"].distance, np.arange(5) * 20 * um + 10 * um) assert_allclose(morphology["2"].distance, np.arange(5) * 10 * um + 5 * um) assert_allclose(morphology.end_distance, 0 * um) assert_allclose(morphology["1"].end_distance, 100 * um) assert_allclose(morphology["2"].end_distance, 50 * um) assert_allclose(morphology.diameter, 30 * um) assert_allclose(morphology["1"].start_diameter, [8, 8, 6, 4, 2] * um) assert_allclose(morphology["1"].diameter, [8, 7, 5, 3, 1] * um) assert_allclose(morphology["1"].end_diameter, [8, 6, 4, 2, 0] * um) assert_allclose(morphology["2"].start_diameter, np.ones(5) * 5 * um) assert_allclose(morphology["2"].diameter, np.ones(5) * 5 * um) assert_allclose(morphology["2"].end_diameter, np.ones(5) * 5 * um) if coordinates: # Coordinates should be absolute # section: soma assert_allclose(morphology.start_x, 100 * um) assert_allclose(morphology.x, 100 * um) assert_allclose(morphology.end_x, 100 * um) assert_allclose(morphology.y, 0 * um) assert_allclose(morphology.z, 0 * um) # section: cable['1'] step = 20 / np.sqrt(2) * um assert_allclose(morphology["1"].start_x, 100 * um + np.arange(5) * step) assert_allclose(morphology["1"].x, 100 * um + np.arange(5) * step + step / 2) assert_allclose(morphology["1"].end_x, 100 * um + np.arange(5) * step + step) assert_allclose(morphology["1"].start_y, np.arange(5) * step) assert_allclose(morphology["1"].y, np.arange(5) * step + step / 2) assert_allclose(morphology["1"].end_y, np.arange(5) * step + step) assert_allclose(morphology["1"].z, np.zeros(5) * um) # section: cable['2'] step = 10 / np.sqrt(2) * um assert_allclose(morphology["2"].start_x, 100 * um + np.arange(5) * step) if use_cylinders: assert_allclose( morphology["2"].x, 100 * um + np.arange(5) * step + step / 2 ) assert_allclose(morphology["2"].end_x, 100 * um + np.arange(5) * step + step) assert_allclose(morphology["2"].start_y, -np.arange(5) * step) if use_cylinders: assert_allclose(morphology["2"].y, -(np.arange(5) * step + step / 2)) assert_allclose(morphology["2"].end_y, -(np.arange(5) * step + step)) if use_cylinders: assert_allclose(morphology["2"].z, np.zeros(5) * um) @pytest.mark.codegen_independent def test_tree_soma_schematic(): soma = Soma(diameter=30 * um) soma.L = Section( n=5, diameter=[8, 8, 6, 4, 2, 0] * um, length=np.ones(5) * 20 * um ) # tapering truncated cones soma.R = Cylinder(n=5, diameter=5 * um, length=50 * um) _check_tree_soma(soma) @pytest.mark.codegen_independent def test_tree_soma_coordinates(): soma = Soma(diameter=30 * um, x=100 * um) soma.L = Section( n=5, diameter=[8, 8, 6, 4, 2, 0] * um, x=np.linspace(0, 100, 6) / np.sqrt(2) * um, y=np.linspace(0, 100, 6) / np.sqrt(2) * um, ) # tapering truncated cones soma.R = Cylinder( n=5, diameter=5 * um, x=[0, 50] * um / np.sqrt(2), y=[0, -50] * um / np.sqrt(2) ) _check_tree_soma(soma, coordinates=True) @pytest.mark.codegen_independent def test_tree_soma_from_points(): # The coordinates should be identical to the previous test # fmt: on points = [ # soma (1, "soma", 100, 0, 0, 30, -1), # soma.L (2, "L", 100 + 20 / np.sqrt(2), 20 / np.sqrt(2), 0, 8, 1), (3, "L", 100 + 40 / np.sqrt(2), 40 / np.sqrt(2), 0, 6, 2), (4, "L", 100 + 60 / np.sqrt(2), 60 / np.sqrt(2), 0, 4, 3), (5, "L", 100 + 80 / np.sqrt(2), 80 / np.sqrt(2), 0, 2, 4), (6, "L", 100 + 100 / np.sqrt(2), 100 / np.sqrt(2), 0, 0, 5), # soma.R (7, "R", 100 + 10 / np.sqrt(2), -10 / np.sqrt(2), 0, 5, 1), (8, "R", 100 + 20 / np.sqrt(2), -20 / np.sqrt(2), 0, 5, 7), (9, "R", 100 + 30 / np.sqrt(2), -30 / np.sqrt(2), 0, 5, 8), (10, "R", 100 + 40 / np.sqrt(2), -40 / np.sqrt(2), 0, 5, 9), (11, "R", 100 + 50 / np.sqrt(2), -50 / np.sqrt(2), 0, 5, 10), ] # fmt: on cable = Morphology.from_points(points) _check_tree_soma(cable, coordinates=True, use_cylinders=False) @pytest.mark.codegen_independent def test_tree_soma_from_points_3_point_soma(): # The coordinates should be identical to the previous test # fmt: off points = [ # soma (1, 'soma', 100, 0, 0, 30, -1), (2, 'soma', 100, 15, 0, 30, 1), (3, 'soma', 100, -15, 0, 30, 1), # soma.L (4, 'L' , 100+20/np.sqrt(2), 20/np.sqrt(2), 0, 8 , 1), (5, 'L' , 100+40/np.sqrt(2), 40/np.sqrt(2), 0, 6 , 4), (6, 'L' , 100+60/np.sqrt(2), 60/np.sqrt(2), 0, 4 , 5), (7, 'L' , 100+80/np.sqrt(2), 80/np.sqrt(2), 0, 2 , 6), (8, 'L' , 100+100/np.sqrt(2), 100/np.sqrt(2), 0, 0 , 7), # soma.R (9, 'R' , 100+10/np.sqrt(2), -10/np.sqrt(2), 0, 5 , 1), (10, 'R' , 100+20/np.sqrt(2), -20/np.sqrt(2), 0, 5 , 9), (11, 'R' , 100+30/np.sqrt(2), -30/np.sqrt(2), 0, 5 , 10), (12, 'R' , 100+40/np.sqrt(2), -40/np.sqrt(2), 0, 5 , 11), (13, 'R' , 100+50/np.sqrt(2), -50/np.sqrt(2), 0, 5 , 12), ] # fmt: on cable = Morphology.from_points(points) _check_tree_soma(cable, coordinates=True, use_cylinders=False) # The first compartment should be a spherical soma! assert isinstance(cable, Soma) @pytest.mark.codegen_independent def test_tree_soma_from_points_3_point_soma_incorrect(): # Inconsistent diameters # fmt: off points = [ # soma (1, 'soma', 100, 0, 0, 30, -1), (2, 'soma', 100, 15, 0, 28, 1), (3, 'soma', 100, -15, 0, 30, 1), # soma.L (4, 'L' , 100+20/np.sqrt(2), 20/np.sqrt(2), 0, 8 , 1), (5, 'L' , 100+40/np.sqrt(2), 40/np.sqrt(2), 0, 6 , 4), (6, 'L' , 100+60/np.sqrt(2), 60/np.sqrt(2), 0, 4 , 5), (7, 'L' , 100+80/np.sqrt(2), 80/np.sqrt(2), 0, 2 , 6), (8, 'L' , 100+100/np.sqrt(2), 100/np.sqrt(2), 0, 0 , 7) ] # fmt: on with pytest.raises(ValueError): Morphology.from_points(points) # Inconsistent coordinates # fmt: off points = [ # soma (1, 'soma', 100, 0, 0, 30, -1), (2, 'soma', 100, 15, 0, 30, 1), (3, 'soma', 100, -16, 0, 30, 1), # soma.L (4, 'L', 100 + 20 / np.sqrt(2), 20 / np.sqrt(2), 0, 8, 1), (5, 'L', 100 + 40 / np.sqrt(2), 40 / np.sqrt(2), 0, 6, 4), (6, 'L', 100 + 60 / np.sqrt(2), 60 / np.sqrt(2), 0, 4, 5), (7, 'L', 100 + 80 / np.sqrt(2), 80 / np.sqrt(2), 0, 2, 6), (8, 'L', 100 + 100 / np.sqrt(2), 100 / np.sqrt(2), 0, 0, 7) ] # fmt: on with pytest.raises(ValueError): Morphology.from_points(points) @pytest.mark.codegen_independent def test_tree_soma_from_swc(): swc_content = """ # Test file 1 1 100 0 0 15 -1 2 2 114.14213562373095 14.142135623730949 0 4 1 3 2 128.2842712474619 28.284271247461898 0 3 2 4 2 142.42640687119285 42.426406871192846 0 2 3 5 2 156.5685424949238 56.568542494923797 0 1 4 6 2 170.71067811865476 70.710678118654741 0 0 5 7 2 107.07106781186548 -7.0710678118654746 0 2.5 1 8 2 114.14213562373095 -14.142135623730949 0 2.5 7 9 2 121.21320343559643 -21.213203435596423 0 2.5 8 10 2 128.2842712474619 -28.284271247461898 0 2.5 9 11 2 135.35533905932738 -35.35533905932737 0 2.5 10 """ tmp_filename = tempfile.mktemp("cable_morphology.swc") with open(tmp_filename, "w") as f: f.write(swc_content) soma = Morphology.from_file(tmp_filename) os.remove(tmp_filename) _check_tree_soma(soma, coordinates=True, use_cylinders=False) @pytest.mark.codegen_independent def test_tree_soma_from_swc_3_point_soma(): swc_content = """ # Test file 1 1 100 0 0 15 -1 2 1 100 15 0 15 1 3 1 100 -15 0 15 1 4 2 114.14213562373095 14.142135623730949 0 4 1 5 2 128.2842712474619 28.284271247461898 0 3 4 6 2 142.42640687119285 42.426406871192846 0 2 5 7 2 156.5685424949238 56.568542494923797 0 1 6 8 2 170.71067811865476 70.710678118654741 0 0 7 9 2 107.07106781186548 -7.0710678118654746 0 2.5 1 10 2 114.14213562373095 -14.142135623730949 0 2.5 9 11 2 121.21320343559643 -21.213203435596423 0 2.5 10 12 2 128.2842712474619 -28.284271247461898 0 2.5 11 13 2 135.35533905932738 -35.35533905932737 0 2.5 12 """ tmp_filename = tempfile.mktemp("cable_morphology.swc") with open(tmp_filename, "w") as f: f.write(swc_content) soma = Morphology.from_file(tmp_filename) os.remove(tmp_filename) _check_tree_soma(soma, coordinates=True, use_cylinders=False) @pytest.mark.codegen_independent def test_construction_incorrect_arguments(): ### Morphology dummy_self = Soma(10 * um) # To allow testing of Morphology.__init__ with pytest.raises(TypeError): Morphology.__init__(dummy_self, n=1.5) with pytest.raises(ValueError): Morphology.__init__(dummy_self, n=0) with pytest.raises(TypeError): Morphology.__init__(dummy_self, "filename.swc") ### Soma with pytest.raises(DimensionMismatchError): Soma(10) with pytest.raises(TypeError): Soma([10, 20] * um) with pytest.raises(TypeError): Soma(x=[10, 20] * um) with pytest.raises(TypeError): Soma(y=[10, 20] * um) with pytest.raises(TypeError): Soma(z=[10, 20] * um) with pytest.raises(DimensionMismatchError): Soma(x=10) with pytest.raises(DimensionMismatchError): Soma(y=10) with pytest.raises(DimensionMismatchError): Soma(z=10) ### Cylinder # Diameter can only be single value with pytest.raises(TypeError): Cylinder(n=3, diameter=[10, 20] * um, length=100 * um) with pytest.raises(TypeError): Cylinder(n=3, diameter=[10, 20, 30] * um, length=100 * um) with pytest.raises(TypeError): Cylinder(n=3, diameter=np.ones(3, 2) * um, length=100 * um) # Length can only be single value with pytest.raises(TypeError): Cylinder(n=3, diameter=10 * um, length=[10, 20] * um) with pytest.raises(TypeError): Cylinder(n=3, diameter=10 * um, length=[10, 20, 30] * um) with pytest.raises(TypeError): Cylinder(n=3, diameter=10 * um, length=np.ones(3, 2) * um) # Coordinates have to be two values with pytest.raises(TypeError): Cylinder(n=3, diameter=10 * um, x=[10] * um) with pytest.raises(TypeError): Cylinder(n=3, diameter=10 * um, x=[10, 20, 30] * um) with pytest.raises(TypeError): Cylinder(n=3, diameter=10 * um, y=[10] * um) with pytest.raises(TypeError): Cylinder(n=3, diameter=10 * um, y=[10, 20, 30] * um) with pytest.raises(TypeError): Cylinder(n=3, diameter=10 * um, z=[10] * um) with pytest.raises(TypeError): Cylinder(n=3, diameter=10 * um, z=[10, 20, 30] * um) # Need either coordinates or lengths with pytest.raises(TypeError): Cylinder(n=3, diameter=10 * um) # But not both with pytest.raises(TypeError): Cylinder(n=3, diameter=10 * um, length=30 * um, x=[0, 30] * um) ### Section # Diameter have to be n+1 values with pytest.raises(TypeError): Section(n=3, diameter=10 * um, length=np.ones(3) * 10 * um) with pytest.raises(TypeError): Section(n=3, diameter=[10, 20, 30] * um, length=np.ones(3) * 10 * um) with pytest.raises(TypeError): Section(n=3, diameter=np.ones(4, 2) * um, length=np.ones(3) * 10 * um) # Length have to be n values with pytest.raises(TypeError): Section(n=3, diameter=np.ones(4) * 10 * um, length=10 * um) with pytest.raises(TypeError): Section(n=3, diameter=np.ones(4) * 10 * um, length=[10, 20] * um) with pytest.raises(TypeError): Section(n=3, diameter=np.ones(4) * 10 * um, length=np.ones(3, 2) * um) # Coordinates have to be n+1 values with pytest.raises(TypeError): Section(n=3, diameter=np.ones(4) * 10 * um, x=10 * um) with pytest.raises(TypeError): Section(n=3, diameter=np.ones(4) * 10 * um, x=[10, 20, 30] * um) with pytest.raises(TypeError): Section(n=3, diameter=np.ones(4) * 10 * um, y=10 * um) with pytest.raises(TypeError): Section(n=3, diameter=np.ones(4) * 10 * um, y=[10, 20, 30] * um) with pytest.raises(TypeError): Section(n=3, diameter=np.ones(4) * 10 * um, z=10 * um) with pytest.raises(TypeError): Section(n=3, diameter=np.ones(4) * 10 * um, z=[10, 20, 30] * um) # Need either coordinates or lengths with pytest.raises(TypeError): Section(n=3, diameter=np.ones(4) * 10 * um) # But not both with pytest.raises(TypeError): Section( n=3, diameter=np.ones(4) * 10 * um, length=[10, 20, 30] * um, x=[0, 10, 20, 30] * um, ) @pytest.mark.codegen_independent def test_from_points_minimal(): points = [(1, "soma", 10, 20, 30, 30, -1)] morph = Morphology.from_points(points) assert morph.total_compartments == 1 assert_allclose(morph.diameter, 30 * um) assert_allclose(morph.x, 10 * um) assert_allclose(morph.y, 20 * um) assert_allclose(morph.z, 30 * um) @pytest.mark.codegen_independent def test_from_points_incorrect(): # The coordinates should be identical to the previous test # fmt: off points = [ (1, None, 0, 0, 0, 10, -1), (2, None, 10, 0, 0, 10, 1), (2, None, 20, 0, 0, 10, 2), ] points2 = [ (1, None, 0, 0, 0, 10, -1), (2, None, 10, 0, 0, 10, 1), (3, None, 20, 0, 0, 10, 3), ] points3 = [ (1, None, 0, 0, 0, 10, -1), (2, None, 10, 0, 0, 10, 1), (3, None, 20, 0, 0, 10, 4), ] points4 = [ (1, 0, 0, 0, 10, -1), (2, 10, 0, 0, 10, 1), (3, 20, 0, 0, 10, 2), ] with pytest.raises(ValueError): Morphology.from_points(points) with pytest.raises(ValueError): Morphology.from_points(points2) with pytest.raises(ValueError): Morphology.from_points(points3) with pytest.raises(ValueError): Morphology.from_points(points4) @pytest.mark.codegen_independent def test_subtree_deletion(): soma = Soma(diameter=30 * um) first_dendrite = Cylinder(n=5, diameter=5 * um, length=50 * um) second_dendrite = Cylinder(n=5, diameter=5 * um, length=50 * um) second_dendrite.L = Cylinder(n=5, diameter=5 * um, length=50 * um) second_dendrite.R = Cylinder(n=5, diameter=5 * um, length=50 * um) soma.dend1 = first_dendrite soma.dend2 = second_dendrite soma.dend3 = Cylinder(n=5, diameter=5 * um, length=50 * um) soma.dend3.L = Cylinder(n=5, diameter=5 * um, length=50 * um) soma.dend3.L.L = Cylinder(n=5, diameter=5 * um, length=50 * um) assert soma.total_compartments == 36 del soma.dend1 assert soma.total_compartments == 31 with pytest.raises(AttributeError): soma.dend1 with pytest.raises(AttributeError): delattr(soma, "dend1") with pytest.raises(AttributeError): soma.__delitem__("dend1") assert first_dendrite not in soma.children del soma["dend2"] assert soma.total_compartments == 16 with pytest.raises(AttributeError): soma.dend2 assert second_dendrite not in soma.children del soma.dend3.LL assert soma.total_compartments == 11 with pytest.raises(AttributeError): soma.dend3.LL with pytest.raises(AttributeError): soma.dend3.L.L @pytest.mark.codegen_independent def test_subgroup_indices(): morpho = Soma(diameter=30 * um) morpho.L = Cylinder(length=10 * um, diameter=1 * um, n=10) morpho.LL = Cylinder(length=5 * um, diameter=2 * um, n=5) morpho.right = Cylinder(length=3 * um, diameter=1 * um, n=7) assert_equal(morpho.LL.indices[:], [11, 12, 13, 14, 15]) assert_equal(morpho.L.indices[3 * um : 5 * um], [4, 5]) assert_equal( morpho.L.indices[3 * um : 5 * um], morpho.L[3 * um : 5 * um].indices[:] ) assert_equal(morpho.L.indices[: 5 * um], [1, 2, 3, 4, 5]) assert_equal(morpho.L.indices[3 * um :], [4, 5, 6, 7, 8, 9, 10]) assert_equal(morpho.L.indices[3.5 * um], 4) assert_equal(morpho.L.indices[3 * um], 4) assert_equal(morpho.L.indices[3.9 * um], 4) assert_equal(morpho.L.indices[3], 4) assert_equal(morpho.L.indices[-1], 10) assert_equal(morpho.L.indices[3:5], [4, 5]) assert_equal(morpho.L.indices[3:], [4, 5, 6, 7, 8, 9, 10]) assert_equal(morpho.L.indices[:5], [1, 2, 3, 4, 5]) @pytest.mark.codegen_independent def test_subgroup_attributes(): morpho = Soma(diameter=30 * um) morpho.L = Cylinder(length=10 * um, diameter=1 * um, n=10) morpho.LL = Cylinder(x=[0, 5] * um, diameter=2 * um, n=5) morpho.right = Cylinder(length=3 * um, diameter=1 * um, n=7) # # Getting a single compartment by index assert_allclose(morpho.L[2].area, morpho.L.area[2]) assert_allclose(morpho.L[2].volume, morpho.L.volume[2]) assert_allclose(morpho.L[2].length, morpho.L.length[2]) assert_allclose(morpho.L[2].r_length_1, morpho.L.r_length_1[2]) assert_allclose(morpho.L[2].r_length_2, morpho.L.r_length_2[2]) assert_allclose(morpho.L[2].distance, morpho.L.distance[2]) assert_allclose(morpho.L[2].diameter, morpho.L.diameter[2]) assert morpho.L[2].x is None assert morpho.L[2].y is None assert morpho.L[2].z is None assert morpho.L[2].start_x is None assert morpho.L[2].start_y is None assert morpho.L[2].start_z is None assert morpho.L[2].end_x is None assert morpho.L[2].end_y is None assert morpho.L[2].end_z is None # # Getting a single compartment by position assert_allclose(morpho.LL[1.5 * um].area, morpho.LL.area[1]) assert_allclose(morpho.LL[1.5 * um].volume, morpho.LL.volume[1]) assert_allclose(morpho.LL[1.5 * um].length, morpho.LL.length[1]) assert_allclose(morpho.LL[1.5 * um].r_length_1, morpho.LL.r_length_1[1]) assert_allclose(morpho.LL[1.5 * um].r_length_2, morpho.LL.r_length_2[1]) assert_allclose(morpho.LL[1.5 * um].distance, morpho.LL.distance[1]) assert_allclose(morpho.LL[1.5 * um].diameter, morpho.LL.diameter[1]) assert_allclose(morpho.LL[1.5 * um].x, morpho.LL.x[1]) assert_allclose(morpho.LL[1.5 * um].y, morpho.LL.y[1]) assert_allclose(morpho.LL[1.5 * um].z, morpho.LL.z[1]) assert_allclose(morpho.LL[1.5 * um].start_x, morpho.LL.start_x[1]) assert_allclose(morpho.LL[1.5 * um].start_y, morpho.LL.start_y[1]) assert_allclose(morpho.LL[1.5 * um].start_z, morpho.LL.start_z[1]) assert_allclose(morpho.LL[1.5 * um].end_x, morpho.LL.end_x[1]) assert_allclose(morpho.LL[1.5 * um].end_y, morpho.LL.end_y[1]) assert_allclose(morpho.LL[1.5 * um].end_z, morpho.LL.end_z[1]) # Getting several compartments by indices assert_allclose(morpho.right[3:6].area, morpho.right.area[3:6]) assert_allclose(morpho.right[3:6].volume, morpho.right.volume[3:6]) assert_allclose(morpho.right[3:6].length, morpho.right.length[3:6]) assert_allclose(morpho.right[3:6].r_length_1, morpho.right.r_length_1[3:6]) assert_allclose(morpho.right[3:6].r_length_2, morpho.right.r_length_2[3:6]) assert_allclose(morpho.right[3:6].distance, morpho.right.distance[3:6]) assert_allclose(morpho.right[3:6].diameter, morpho.right.diameter[3:6]) assert morpho.right[3:6].x is None assert morpho.right[3:6].y is None assert morpho.right[3:6].z is None assert morpho.right[3:6].start_x is None assert morpho.right[3:6].start_y is None assert morpho.right[3:6].start_z is None assert morpho.right[3:6].end_x is None assert morpho.right[3:6].end_y is None assert morpho.right[3:6].end_z is None # Getting several compartments by position assert_allclose(morpho.L[3 * um : 5 * um].distance, [3.5, 4.5] * um) assert_allclose(morpho.L[3.5 * um : 4.5 * um].distance, [3.5, 4.5] * um) @pytest.mark.codegen_independent def test_subgroup_incorrect(): # Incorrect indexing morpho = Soma(diameter=30 * um) morpho.L = Cylinder(length=10 * um, diameter=1 * um, n=10) morpho.LL = Cylinder(length=5 * um, diameter=2 * um, n=5) morpho.right = Cylinder(length=3 * um, diameter=1 * um, n=7) # Non-existing branch with pytest.raises(AttributeError): morpho.axon # Incorrect indexing # wrong units or mixing units with pytest.raises(TypeError): morpho.L[3 * second : 5 * second] with pytest.raises(TypeError): morpho.L[3.4:5.3] with pytest.raises(TypeError): morpho.L[3 : 5 * um] with pytest.raises(TypeError): morpho.L[3 * um : 5] # providing a step with pytest.raises(TypeError): morpho.L[3 * um : 5 * um : 2 * um] with pytest.raises(TypeError): morpho.L[3:5:2] # incorrect type with pytest.raises(TypeError): morpho.L[object()] # out of range with pytest.raises(IndexError): morpho.L[-10 * um] with pytest.raises(IndexError): morpho.L[15 * um] with pytest.raises(IndexError): morpho.L[10] @pytest.mark.codegen_independent def test_topology(): soma = Soma(diameter=30 * um) soma.L = Section( n=5, diameter=[10, 8, 6, 4, 2, 0] * um, length=np.ones(5) * 20 * um ) # tapering truncated cones soma.R = Cylinder(n=10, diameter=5 * um, length=50 * um) soma.R.left = Cylinder(n=10, diameter=2.5 * um, length=50 * um) soma.R.right = Section( n=5, diameter=[5, 4, 3, 2, 1, 0] * um, length=np.ones(5) * 10 * um ) str_topology = str(soma.topology()) lines = [l for l in str_topology.split("\n") if len(l.strip())] assert len(lines) == 5 # one line for each section for line, name in zip(lines, ["root", ".L", ".R", ".R.left", "R.right"]): assert name in line @pytest.mark.codegen_independent def test_copy_section_soma(): soma = Soma(diameter=30 * um) soma_copy = soma.copy_section() assert soma_copy.diameter[0] == 30 * um assert soma_copy.x is None assert soma_copy.y is None assert soma_copy.z is None assert soma_copy.type == "soma" soma = Soma(diameter=30 * um, x=5 * um, z=-10 * um) soma_copy = soma.copy_section() assert soma_copy.diameter[0] == 30 * um assert_allclose(soma_copy.x[0], 5 * um) assert_allclose(soma_copy.y[0], 0 * um) assert_allclose(soma_copy.z[0], -10 * um) assert soma_copy.type == "soma" @pytest.mark.codegen_independent def test_copy_section_section(): # No coordinates sec = Section( diameter=[10, 5, 4, 3, 2, 1] * um, n=5, length=np.ones(5) * 10 * um, type="dend" ) sec_copy = sec.copy_section() assert_allclose(sec_copy.start_diameter, sec.start_diameter) assert_allclose(sec_copy.end_diameter, sec.end_diameter) assert_allclose(sec_copy.length, sec.length) assert sec_copy.n == sec.n assert sec_copy.x is None assert sec_copy.y is None assert sec_copy.z is None assert sec_copy.type == "dend" # With coordinates sec = Section( diameter=[10, 5, 4, 3, 2, 1] * um, n=5, x=[0, 1, 2, 3, 4, 5] * um, y=[0, -1, -2, -3, -4, -5] * um, ) sec_copy = sec.copy_section() assert_allclose(sec_copy.start_diameter, sec.start_diameter) assert_allclose(sec_copy.end_diameter, sec.end_diameter) assert_allclose(sec_copy.length, sec.length) assert sec_copy.n == sec.n assert_allclose(sec_copy.x, sec.x) assert_allclose(sec_copy.y, sec.y) assert_allclose(sec_copy.z, sec.z) assert sec_copy.type is None @pytest.mark.codegen_independent def test_copy_section_cylinder(): # no coordinates sec = Section( diameter=[10, 5, 4, 3, 2, 1] * um, n=5, length=np.ones(5) * 20 * um, type="dend" ) sec_copy = sec.copy_section() assert_allclose(sec_copy.end_diameter, sec.end_diameter) assert_allclose(sec_copy.length, sec.length) assert sec_copy.n == sec.n assert sec_copy.x is None assert sec_copy.y is None assert sec_copy.z is None assert sec_copy.type == "dend" # with coordinates sec = Section( diameter=[10, 5, 4, 3, 2, 1] * um, n=5, x=[0, 1, 2, 3, 4, 5] * um, y=[0, -1, -2, -3, -4, -5] * um, ) sec_copy = sec.copy_section() assert_allclose(sec_copy.end_diameter, sec.end_diameter) assert_allclose(sec_copy.length, sec.length) assert sec_copy.n == sec.n assert_allclose(sec_copy.x, sec.x) assert_allclose(sec_copy.y, sec.y) assert_allclose(sec_copy.z, sec.z) assert sec_copy.type is None def _check_length_coord_consistency(morph_with_coords): if not isinstance(morph_with_coords, Soma): vectors = np.diff(morph_with_coords.coordinates, axis=0) calculated_length = np.sqrt(np.sum(vectors**2, axis=1)) assert_allclose(calculated_length, morph_with_coords.length) for child in morph_with_coords.children: _check_length_coord_consistency(child) @pytest.mark.codegen_independent def test_generate_coordinates_deterministic(): morph = Soma(diameter=30 * um) morph.L = Section( n=5, diameter=[10, 8, 6, 4, 2, 0] * um, length=np.ones(5) * 20 * um ) # tapering truncated cones morph.R = Cylinder(n=10, diameter=5 * um, length=50 * um) morph.R.left = Cylinder(n=10, diameter=2.5 * um, length=50 * um) morph.R.right = Section( n=5, diameter=[5, 4, 3, 2, 1, 0] * um, length=np.ones(5) * 10 * um ) morph_with_coords = morph.generate_coordinates() assert morph_with_coords.total_compartments == morph.total_compartments assert morph_with_coords.total_sections == morph.total_sections for new, old in [ (morph_with_coords, morph), (morph_with_coords.L, morph.L), (morph_with_coords.R, morph.R), (morph_with_coords.R.left, morph.R.left), (morph_with_coords.R.right, morph.R.right), ]: assert new.n == old.n assert_allclose(new.length, old.length) assert_allclose(new.diameter, old.diameter) # The morphology should be in the x/y plane assert_equal(new.z, 0 * um) _check_length_coord_consistency(morph_with_coords) @pytest.mark.codegen_independent def test_generate_coordinates_random_sections(): morph = Soma(diameter=30 * um) morph.L = Section( n=5, diameter=[10, 8, 6, 4, 2, 0] * um, length=np.ones(5) * 20 * um ) # tapering truncated cones morph.R = Cylinder(n=10, diameter=5 * um, length=50 * um) morph.R.left = Cylinder(n=10, diameter=2.5 * um, length=50 * um) morph.R.right = Section( n=5, diameter=[5, 4, 3, 2, 1, 0] * um, length=np.ones(5) * 10 * um ) morph_with_coords = morph.generate_coordinates(section_randomness=25) assert morph_with_coords.total_compartments == morph.total_compartments assert morph_with_coords.total_sections == morph.total_sections for new, old in [ (morph_with_coords, morph), (morph_with_coords.L, morph.L), (morph_with_coords.R, morph.R), (morph_with_coords.R.left, morph.R.left), (morph_with_coords.R.right, morph.R.right), ]: assert new.n == old.n assert_allclose(new.length, old.length) assert_allclose(new.diameter, old.diameter) _check_length_coord_consistency(morph_with_coords) @pytest.mark.codegen_independent def test_generate_coordinates_random_compartments(): morph = Soma(diameter=30 * um) morph.L = Section( n=5, diameter=[10, 8, 6, 4, 2, 0] * um, length=np.ones(5) * 20 * um ) # tapering truncated cones morph.R = Cylinder(n=10, diameter=5 * um, length=50 * um) morph.R.left = Cylinder(n=10, diameter=2.5 * um, length=50 * um) morph.R.right = Section( n=5, diameter=[5, 4, 3, 2, 1, 0] * um, length=np.ones(5) * 10 * um ) morph_with_coords = morph.generate_coordinates(compartment_randomness=15) assert morph_with_coords.total_compartments == morph.total_compartments assert morph_with_coords.total_sections == morph.total_sections for new, old in [ (morph_with_coords, morph), (morph_with_coords.L, morph.L), (morph_with_coords.R, morph.R), (morph_with_coords.R.left, morph.R.left), (morph_with_coords.R.right, morph.R.right), ]: assert new.n == old.n assert_allclose(new.length, old.length) assert_allclose(new.diameter, old.diameter) _check_length_coord_consistency(morph_with_coords) @pytest.mark.codegen_independent def test_generate_coordinates_random_all(): morph = Soma(diameter=30 * um) morph.L = Section( n=5, diameter=[10, 8, 6, 4, 2, 0] * um, length=np.ones(5) * 20 * um ) # tapering truncated cones morph.R = Cylinder(n=10, diameter=5 * um, length=50 * um) morph.R.left = Cylinder(n=10, diameter=2.5 * um, length=50 * um) morph.R.right = Section( n=5, diameter=[5, 4, 3, 2, 1, 0] * um, length=np.ones(5) * 10 * um ) morph_with_coords = morph.generate_coordinates( section_randomness=25, compartment_randomness=15 ) assert morph_with_coords.total_compartments == morph.total_compartments assert morph_with_coords.total_sections == morph.total_sections for new, old in [ (morph_with_coords, morph), (morph_with_coords.L, morph.L), (morph_with_coords.R, morph.R), (morph_with_coords.R.left, morph.R.left), (morph_with_coords.R.right, morph.R.right), ]: assert new.n == old.n assert_allclose(new.length, old.length) assert_allclose(new.diameter, old.diameter) _check_length_coord_consistency(morph_with_coords) @pytest.mark.codegen_independent def test_generate_coordinates_no_overwrite(): morph = Soma(diameter=30 * um) morph.L = Section( n=5, diameter=[10, 8, 6, 4, 2, 0] * um, length=np.ones(5) * 20 * um ) # tapering truncated cones morph.R = Cylinder(n=10, diameter=5 * um, length=50 * um) morph.R.left = Cylinder(n=10, diameter=2.5 * um, length=50 * um) morph.R.right = Section( n=5, diameter=[5, 4, 3, 2, 1, 0] * um, length=np.ones(5) * 10 * um ) morph_with_coords = morph.generate_coordinates(compartment_randomness=15) # This should not change anything because the morphology already has coordinates! morph_with_coords2 = morph_with_coords.generate_coordinates( section_randomness=25, compartment_randomness=15 ) for new, old in [ (morph_with_coords2, morph_with_coords), (morph_with_coords2.L, morph_with_coords.L), (morph_with_coords2.R, morph_with_coords.R), (morph_with_coords2.R.left, morph_with_coords.R.left), (morph_with_coords2.R.right, morph_with_coords.R.right), ]: assert new.n == old.n assert_allclose(new.length, old.length) assert_allclose(new.diameter, old.diameter) assert_allclose(new.x, old.x) assert_allclose(new.y, old.y) assert_allclose(new.z, old.z) @pytest.mark.codegen_independent def test_generate_coordinates_overwrite(): morph = Soma(diameter=30 * um) morph.L = Section( n=5, diameter=[10, 8, 6, 4, 2, 0] * um, length=np.ones(5) * 20 * um ) # tapering truncated cones morph.R = Cylinder(n=10, diameter=5 * um, length=50 * um) morph.R.left = Cylinder(n=10, diameter=2.5 * um, length=50 * um) morph.R.right = Section( n=5, diameter=[5, 4, 3, 2, 1, 0] * um, length=np.ones(5) * 10 * um ) morph_with_coords = morph.generate_coordinates(compartment_randomness=15) # This should change things since we explicitly ask for it morph_with_coords2 = morph_with_coords.generate_coordinates( section_randomness=25, compartment_randomness=15, overwrite_existing=True ) for new, old in [ # ignore the root compartment (morph_with_coords2.L, morph_with_coords.L), (morph_with_coords2.R, morph_with_coords.R), (morph_with_coords2.R.left, morph_with_coords.R.left), (morph_with_coords2.R.right, morph_with_coords.R.right), ]: assert new.n == old.n assert_allclose(new.length, old.length) assert_allclose(new.diameter, old.diameter) assert all(np.abs(new.x - old.x) > 0) assert all(np.abs(new.y - old.y) > 0) assert all(np.abs(new.z - old.z) > 0) _check_length_coord_consistency(morph_with_coords2) @pytest.mark.codegen_independent def test_generate_coordinates_mixed_overwrite(): morph = Soma(diameter=30 * um) morph.L = Section( n=5, diameter=[10, 8, 6, 4, 2, 0] * um, length=np.ones(5) * 20 * um ) # tapering truncated cones morph.R = Cylinder(n=10, diameter=5 * um, length=50 * um) morph_with_coords = morph.generate_coordinates( section_randomness=25, compartment_randomness=15 ) # The following just returns a copy, as all coordinates are already # specified morph_copy = morph_with_coords.generate_coordinates() # Add new sections that do not yet have coordinates morph_with_coords.R.left = Cylinder(n=10, diameter=2.5 * um, length=50 * um) morph_with_coords.R.right = Section( n=5, diameter=[5, 4, 3, 2, 1, 0] * um, length=np.ones(5) * 10 * um ) # This should change things since we explicitly ask for it morph_with_coords2 = morph_with_coords.generate_coordinates( section_randomness=25, compartment_randomness=15 ) for new, old in [ (morph_with_coords2, morph_with_coords), (morph_with_coords2.L, morph_with_coords.L), (morph_with_coords2.R, morph_with_coords.R), ]: assert new.n == old.n assert_allclose(new.length, old.length) assert_allclose(new.diameter, old.diameter) assert_allclose(new.x, old.x) assert_allclose(new.y, old.y) assert_allclose(new.z, old.z) assert morph_with_coords.R.left.x is None assert len(morph_with_coords2.R.left.x) == morph_with_coords2.R.left.n _check_length_coord_consistency(morph_with_coords2) @pytest.mark.codegen_independent def test_str_repr(): # A very basic test, make sure that the str/repr functions return # something and do not raise an error for morph in [ Soma(diameter=30 * um), Soma(diameter=30 * um, x=5 * um, y=10 * um), Cylinder(n=5, diameter=10 * um, length=50 * um), Cylinder(n=5, diameter=10 * um, x=[0, 50] * um), Section( n=5, diameter=[2.5, 5, 10, 5, 10, 5] * um, length=[10, 20, 5, 5, 10] * um ), Section( n=5, diameter=[2.5, 5, 10, 5, 10, 5] * um, x=[0, 10, 30, 35, 40, 50] * um ), ]: assert len(repr(morph)) > 0 assert len(str(morph)) > 0 morph = Soma(30 * um) assert len(repr(morph.children)) > 0 assert len(str(morph.children)) > 0 morph.axon = Cylinder(1 * um, n=10, length=100 * um) morph.dend = Cylinder(1 * um, n=10, length=50 * um) assert len(repr(morph.children)) > 0 assert len(str(morph.children)) > 0 if __name__ == "__main__": test_attributes_soma() test_attributes_soma_coordinates() test_attributes_cylinder() test_attributes_cylinder_coordinates() test_attributes_section() test_attributes_section_coordinates_single() test_attributes_section_coordinates_all() test_tree_cables_schematic() test_tree_cables_coordinates() test_tree_cables_from_points() test_tree_cables_from_swc() test_tree_soma_schematic() test_tree_soma_coordinates() test_tree_soma_from_points() test_tree_soma_from_points_3_point_soma() test_tree_soma_from_points_3_point_soma_incorrect() test_tree_soma_from_swc() test_tree_soma_from_swc_3_point_soma() test_construction_incorrect_arguments() test_from_points_minimal() test_from_points_incorrect() test_subtree_deletion() test_subgroup_indices() test_subgroup_attributes() test_subgroup_incorrect() test_topology() test_copy_section_soma() test_copy_section_section() test_copy_section_cylinder() test_generate_coordinates_deterministic() test_generate_coordinates_random_sections() test_generate_coordinates_random_compartments() test_generate_coordinates_random_all() test_generate_coordinates_no_overwrite() test_generate_coordinates_overwrite() test_generate_coordinates_mixed_overwrite() test_str_repr() brian2-2.5.4/brian2/tests/test_namespaces.py000066400000000000000000000127461445201106100207520ustar00rootroot00000000000000import uuid import numpy import pytest import sympy from brian2.core.namespace import get_local_namespace from brian2.core.variables import Constant from brian2.groups.group import Group from brian2.units import second, volt from brian2.units.fundamentalunits import Unit from brian2.units.stdunits import Hz, ms, mV from brian2.units.unitsafefunctions import exp, log, sin from brian2.utils.logger import catch_logs # a simple Group for testing class SimpleGroup(Group): def __init__(self, variables, namespace=None): self.variables = variables # We use a unique name to get repeated warnings Group.__init__( self, namespace=namespace, name=f"simplegroup_{str(uuid.uuid4()).replace('-', '_')}", ) def _assert_one_warning(l): assert len(l) == 1, f"expected one warning got {len(l)}" assert l[0][0] == "WARNING", f"expected a WARNING, got {l[0][0]} instead" @pytest.mark.codegen_independent def test_default_content(): """ Test that the default namespace contains standard units and functions. """ group = Group() # Units assert group._resolve("second", {}).get_value_with_unit() == second assert group._resolve("volt", {}).get_value_with_unit() == volt assert group._resolve("ms", {}).get_value_with_unit() == ms assert group._resolve("Hz", {}).get_value_with_unit() == Hz assert group._resolve("mV", {}).get_value_with_unit() == mV # Functions assert group._resolve("sin", {}).pyfunc == sin assert group._resolve("log", {}).pyfunc == log assert group._resolve("exp", {}).pyfunc == exp # Constants assert group._resolve("e", {}).sympy_obj == sympy.E assert group._resolve("e", {}).get_value() == numpy.e assert group._resolve("pi", {}).sympy_obj == sympy.pi assert group._resolve("pi", {}).get_value() == numpy.pi assert group._resolve("inf", {}).sympy_obj == sympy.oo assert group._resolve("inf", {}).get_value() == numpy.inf @pytest.mark.codegen_independent def test_explicit_namespace(): """Test resolution with an explicitly provided namespace""" group = SimpleGroup(namespace={"variable": 42}, variables={}) # Explicitly provided with catch_logs() as l: assert group._resolve("variable", {}).get_value_with_unit() == 42 assert len(l) == 0 # Value from an explicit run namespace with catch_logs() as l: assert ( group._resolve( "yet_another_var", run_namespace={"yet_another_var": 17} ).get_value_with_unit() == 17 ) assert len(l) == 0 @pytest.mark.codegen_independent def test_errors(): # No explicit namespace group = SimpleGroup(namespace=None, variables={}) with pytest.raises(KeyError): group._resolve("nonexisting_variable", {}) # Empty explicit namespace group = SimpleGroup(namespace={}, variables={}) with pytest.raises(KeyError): group._resolve("nonexisting_variable", {}) # Illegal name with pytest.raises(ValueError): SimpleGroup(namespace={"_illegal": 3.0}, variables={}) @pytest.mark.codegen_independent def test_resolution(): # implicit namespace tau = 10 * ms group = SimpleGroup(namespace=None, variables={}) namespace = get_local_namespace(level=0) resolved = group.resolve_all( ["tau", "ms"], namespace, user_identifiers=["tau", "ms"] ) assert len(resolved) == 2 assert isinstance(resolved, dict) assert resolved["tau"].get_value_with_unit() == tau assert resolved["ms"].get_value_with_unit() == ms del tau # explicit namespace group = SimpleGroup(namespace={"tau": 20 * ms}, variables={}) namespace = get_local_namespace(level=0) resolved = group.resolve_all(["tau", "ms"], namespace, ["tau", "ms"]) assert len(resolved) == 2 assert resolved["tau"].get_value_with_unit() == 20 * ms @pytest.mark.codegen_independent def test_warning(): from brian2.core.functions import DEFAULT_FUNCTIONS from brian2.units.stdunits import cm as brian_cm # Name in external namespace clashes with unit/function name exp = 23 cm = 42 group = SimpleGroup(namespace=None, variables={}) namespace = get_local_namespace(level=0) with catch_logs() as l: resolved = group.resolve_all(["exp"], namespace)["exp"] assert resolved == DEFAULT_FUNCTIONS["exp"] assert len(l) == 1, f"got warnings: {str(l)}" assert l[0][1].endswith(".resolution_conflict") with catch_logs() as l: resolved = group.resolve_all(["cm"], namespace)["cm"] assert resolved.get_value_with_unit() == brian_cm assert len(l) == 1, f"got warnings: {str(l)}" assert l[0][1].endswith(".resolution_conflict") @pytest.mark.codegen_independent def test_warning_internal_variables(): group1 = SimpleGroup(namespace=None, variables={"N": Constant("N", 5)}) group2 = SimpleGroup(namespace=None, variables={"N": Constant("N", 7)}) with catch_logs() as l: group1.resolve_all(["N"], run_namespace={"N": 5}) # should not raise a warning assert len(l) == 0, f"got warnings: {str(l)}" with catch_logs() as l: group2.resolve_all(["N"], run_namespace={"N": 5}) # should raise a warning assert len(l) == 1, f"got warnings: {str(l)}" assert l[0][1].endswith(".resolution_conflict") if __name__ == "__main__": test_default_content() test_explicit_namespace() test_errors() test_resolution() test_warning() test_warning_internal_variables() brian2-2.5.4/brian2/tests/test_network.py000066400000000000000000001530561445201106100203240ustar00rootroot00000000000000import copy import logging import os import tempfile import uuid import weakref import numpy as np import pytest from numpy.testing import assert_array_equal, assert_equal from brian2 import ( BrianLogger, BrianObject, Clock, Hz, MagicError, MagicNetwork, Network, NetworkOperation, NeuronGroup, PoissonGroup, PopulationRateMonitor, Quantity, SpikeGeneratorGroup, SpikeMonitor, StateMonitor, Synapses, TimedArray, collect, defaultclock, magic_network, ms, network_operation, prefs, profiling_summary, restore, run, second, start_scope, stop, store, us, ) from brian2.core.network import schedule_propagation_offset, scheduling_summary from brian2.devices.device import ( Device, RuntimeDevice, all_devices, device, get_device, reinit_and_delete, reset_device, set_device, ) from brian2.tests.utils import assert_allclose from brian2.utils.logger import catch_logs @pytest.mark.codegen_independent def test_incorrect_network_use(): """Test some wrong uses of `Network` and `MagicNetwork`""" with pytest.raises(TypeError): Network(name="mynet", anotherkwd="does not exist") with pytest.raises(TypeError): Network("not a BrianObject") net = Network() with pytest.raises(TypeError): net.add("not a BrianObject") with pytest.raises(ValueError): MagicNetwork() G = NeuronGroup(10, "v:1") net.add(G) with pytest.raises(TypeError): net.remove(object()) with pytest.raises(MagicError): magic_network.add(G) with pytest.raises(MagicError): magic_network.remove(G) @pytest.mark.codegen_independent def test_network_contains(): """ Test `Network.__contains__`. """ G = NeuronGroup(1, "v:1", name="mygroup") net = Network(G) assert "mygroup" in net assert "neurongroup" not in net @pytest.mark.codegen_independent def test_empty_network(): # Check that an empty network functions correctly net = Network() net.run(1 * second) class Counter(BrianObject): add_to_magic_network = True def __init__(self, **kwds): super().__init__(**kwds) self.count = 0 self.state = {"state": 0} def get_states(self, *args, **kwds): return dict(self.state) def set_states(self, values, *args, **kwds): for k, v in values.items(): self.state[k] = v def run(self): self.count += 1 class CounterWithContained(Counter): add_to_magic_network = True def __init__(self, **kwds): super().__init__(**kwds) self.sub_counter = Counter() self.contained_objects.append(self.sub_counter) @pytest.mark.codegen_independent def test_network_single_object(): # Check that a network with a single object functions correctly x = Counter() net = Network(x) net.run(1 * ms) assert_equal(x.count, 10) @pytest.mark.codegen_independent def test_network_two_objects(): # Check that a network with two objects and the same clock function correctly x = Counter(order=5) y = Counter(order=6) net = Network() net.add([x, [y]]) # check that a funky way of adding objects work correctly net.run(1 * ms) assert_equal(len(net.objects), 2) assert_equal(x.count, 10) assert_equal(y.count, 10) @pytest.mark.codegen_independent def test_network_from_dict(): # Check that a network from a dictionary works x = Counter() y = Counter() d = dict(a=x, b=y) net = Network() net.add(d) net.run(1 * ms) assert_equal(len(net.objects), 2) assert_equal(x.count, 10) assert_equal(y.count, 10) class NameLister(BrianObject): add_to_magic_network = True updates = [] def __init__(self, **kwds): super().__init__(**kwds) def run(self): NameLister.updates.append(self.name) @pytest.mark.codegen_independent def test_network_different_clocks(): NameLister.updates[:] = [] # Check that a network with two different clocks functions correctly x = NameLister(name="x", dt=0.1 * ms, order=0) y = NameLister(name="y", dt=1 * ms, order=1) net = Network(x, y) net.run(100 * second + defaultclock.dt, report="text") updates = "".join(NameLister.updates)[2:] # ignore the first time step assert updates == ("xxxxxxxxxxy" * 100000) @pytest.mark.codegen_independent def test_network_different_when(): # Check that a network with different when attributes functions correctly NameLister.updates[:] = [] x = NameLister(name="x", when="start") y = NameLister(name="y", when="end") net = Network(x, y) net.run(0.3 * ms) assert_equal("".join(NameLister.updates), "xyxyxy") @pytest.mark.codegen_independent def test_network_default_schedule(): net = Network() assert net.schedule == [ "start", "groups", "thresholds", "synapses", "resets", "end", ] # Set the preference and check that the change is taken into account prefs.core.network.default_schedule = list( reversed(["start", "groups", "thresholds", "synapses", "resets", "end"]) ) assert net.schedule == list( reversed(["start", "groups", "thresholds", "synapses", "resets", "end"]) ) @pytest.mark.codegen_independent def test_network_schedule_change(): # Check that a changed schedule is taken into account correctly NameLister.updates[:] = [] x = NameLister(name="x", when="thresholds") y = NameLister(name="y", when="resets") net = Network(x, y) net.run(0.3 * ms) assert_equal("".join(NameLister.updates), "xyxyxy") NameLister.updates[:] = [] net.schedule = ["start", "groups", "synapses", "resets", "thresholds", "end"] net.run(0.3 * ms) assert_equal("".join(NameLister.updates), "yxyxyx") @pytest.mark.codegen_independent def test_network_before_after_schedule(): # Test that before... and after... slot names can be used NameLister.updates[:] = [] x = NameLister(name="x", when="before_resets") y = NameLister(name="y", when="after_thresholds") net = Network(x, y) net.schedule = ["thresholds", "resets", "end"] net.run(0.3 * ms) assert_equal("".join(NameLister.updates), "yxyxyx") @pytest.mark.codegen_independent def test_network_custom_slots(): # Check that custom slots can be inserted into the schedule NameLister.updates[:] = [] x = NameLister(name="x", when="thresholds") y = NameLister(name="y", when="in_between") z = NameLister(name="z", when="resets") net = Network(x, y, z) net.schedule = [ "start", "groups", "thresholds", "in_between", "synapses", "resets", "end", ] net.run(0.3 * ms) assert_equal("".join(NameLister.updates), "xyzxyzxyz") @pytest.mark.codegen_independent def test_network_incorrect_schedule(): # Test that incorrect arguments provided to schedule raise errors net = Network() # net.schedule = object() with pytest.raises(TypeError): setattr(net, "schedule", object()) # net.schedule = 1 with pytest.raises(TypeError): setattr(net, "schedule", 1) # net.schedule = {'slot1', 'slot2'} with pytest.raises(TypeError): setattr(net, "schedule", {"slot1", "slot2"}) # net.schedule = ['slot', 1] with pytest.raises(TypeError): setattr(net, "schedule", ["slot", 1]) # net.schedule = ['start', 'after_start'] with pytest.raises(ValueError): setattr(net, "schedule", ["start", "after_start"]) # net.schedule = ['before_start', 'start'] with pytest.raises(ValueError): setattr(net, "schedule", ["before_start", "start"]) @pytest.mark.codegen_independent def test_schedule_warning(): previous_device = get_device() from uuid import uuid4 # TestDevice1 supports arbitrary schedules, TestDevice2 does not class TestDevice1(Device): # These functions are needed during the setup of the defaultclock def get_value(self, var): return np.array([0.0001]) def add_array(self, var): pass def init_with_zeros(self, var, dtype): pass def fill_with_array(self, var, arr): pass class TestDevice2(TestDevice1): def __init__(self): super().__init__() self.network_schedule = [ "start", "groups", "synapses", "thresholds", "resets", "end", ] # Unique names are important for getting the warnings again for multiple # runs of the test suite name1 = f"testdevice_{str(uuid4())}" name2 = f"testdevice_{str(uuid4())}" all_devices[name1] = TestDevice1() all_devices[name2] = TestDevice2() set_device(name1) assert schedule_propagation_offset() == 0 * ms net = Network() assert schedule_propagation_offset(net) == 0 * ms # Any schedule should work net.schedule = list(reversed(net.schedule)) with catch_logs() as l: net.run(0 * ms) assert len(l) == 0, "did not expect a warning" assert schedule_propagation_offset(net) == defaultclock.dt set_device(name2) assert schedule_propagation_offset() == defaultclock.dt # Using the correct schedule should work net.schedule = ["start", "groups", "synapses", "thresholds", "resets", "end"] with catch_logs() as l: net.run(0 * ms) assert len(l) == 0, "did not expect a warning" assert schedule_propagation_offset(net) == defaultclock.dt # Using another (e.g. the default) schedule should raise a warning net.schedule = None with catch_logs() as l: net.run(0 * ms) assert len(l) == 1 and l[0][1].endswith("schedule_conflict") reset_device(previous_device) @pytest.mark.codegen_independent def test_scheduling_summary_magic(): basename = f"name{str(uuid.uuid4()).replace('-', '_')}" group = NeuronGroup( 10, "dv/dt = -v/(10*ms) : 1", threshold="v>1", reset="v=1", name=basename ) group.run_regularly("v = rand()", dt=defaultclock.dt * 10, when="end") state_mon = StateMonitor(group, "v", record=True, name=f"{basename}_sm") inactive_state_mon = StateMonitor( group, "v", record=True, name=f"{basename}_sm_ia", when="after_end" ) inactive_state_mon.active = False summary_before = scheduling_summary() assert [entry.name for entry in summary_before.entries] == [ f"{basename}_sm", f"{basename}_stateupdater", f"{basename}_spike_thresholder", f"{basename}_spike_resetter", f"{basename}_run_regularly", f"{basename}_sm_ia", ] assert [entry.when for entry in summary_before.entries] == [ "start", "groups", "thresholds", "resets", "end", "after_end", ] assert [entry.dt for entry in summary_before.entries] == [ defaultclock.dt, defaultclock.dt, defaultclock.dt, defaultclock.dt, defaultclock.dt * 10, defaultclock.dt, ] assert [entry.active for entry in summary_before.entries] == [ True, True, True, True, True, False, ] assert len(str(summary_before)) assert len(summary_before._repr_html_()) run(defaultclock.dt) summary_after = scheduling_summary() assert str(summary_after) == str(summary_before) assert summary_after._repr_html_() == summary_before._repr_html_() @pytest.mark.codegen_independent def test_scheduling_summary(): basename = f"name{str(uuid.uuid4()).replace('-', '_')}" group = NeuronGroup( 10, "dv/dt = -v/(10*ms) : 1", threshold="v>1", reset="v=1", name=basename ) group.run_regularly("v = rand()", dt=defaultclock.dt * 10, when="end") state_mon = StateMonitor(group, "v", record=True, name=f"{basename}_sm") inactive_state_mon = StateMonitor( group, "v", record=True, name=f"{basename}_sm_ia", when="after_end" ) inactive_state_mon.active = False @network_operation(name=f"{basename}_net_op", when="before_end") def foo(): pass net = Network(group, state_mon, inactive_state_mon, foo) summary_before = scheduling_summary(net) assert [entry.name for entry in summary_before.entries] == [ f"{basename}_sm", f"{basename}_stateupdater", f"{basename}_spike_thresholder", f"{basename}_spike_resetter", f"{basename}_net_op", f"{basename}_run_regularly", f"{basename}_sm_ia", ] assert [entry.when for entry in summary_before.entries] == [ "start", "groups", "thresholds", "resets", "before_end", "end", "after_end", ] assert [entry.dt for entry in summary_before.entries] == [ defaultclock.dt, defaultclock.dt, defaultclock.dt, defaultclock.dt, defaultclock.dt, defaultclock.dt * 10, defaultclock.dt, ] assert [entry.active for entry in summary_before.entries] == [ True, True, True, True, True, True, False, ] assert len(str(summary_before)) assert len(summary_before._repr_html_()) run(defaultclock.dt) summary_after = scheduling_summary(net) assert str(summary_after) == str(summary_before) assert summary_after._repr_html_() == summary_before._repr_html_() class Preparer(BrianObject): add_to_magic_network = True def __init__(self, **kwds): super().__init__(**kwds) self.did_reinit = False self.did_pre_run = False self.did_post_run = False def reinit(self, level=0): self.did_reinit = True def before_run(self, namespace=None, level=0): self.did_pre_run = True def after_run(self): self.did_post_run = True @pytest.mark.codegen_independent def test_magic_network(): # test that magic network functions correctly x = Counter() y = Counter() run(10 * ms) assert_equal(x.count, 100) assert_equal(y.count, 100) assert len(repr(magic_network)) # very basic test... assert len(str(magic_network)) # very basic test... class Stopper(BrianObject): add_to_magic_network = True def __init__(self, stoptime, stopfunc, **kwds): super().__init__(**kwds) self.stoptime = stoptime self.stopfunc = stopfunc def run(self): self.stoptime -= 1 if self.stoptime <= 0: self.stopfunc() @pytest.mark.codegen_independent def test_network_stop(): # test that Network.stop and global stop() work correctly net = Network() x = Stopper(10, net.stop) net.add(x) net.run(10 * ms) assert_equal(defaultclock.t, 1 * ms) x = Stopper(10, stop) net = Network(x) net.run(10 * ms) assert_equal(defaultclock.t, 1 * ms) @pytest.mark.codegen_independent def test_network_operations(): # test NetworkOperation and network_operation seq = [] def f1(): seq.append("a") op1 = NetworkOperation(f1, when="start", order=1) @network_operation def f2(): seq.append("b") @network_operation(when="end", order=1) def f3(): seq.append("c") # In complex frameworks, network operations might be object methods that # access some common data class Container: def __init__(self): self.g1_data = "B" self.g2_data = "C" def g1(self): seq.append(self.g1_data) def g2(self): seq.append(self.g2_data) c = Container() c_op1 = NetworkOperation(c.g1) c_op2 = NetworkOperation(c.g2, when="end", order=1) net = Network(op1, f2, f3, c_op1, c_op2) net.run(1 * ms) assert_equal("".join(seq), "bBacC" * 10) @pytest.mark.codegen_independent def test_incorrect_network_operations(): # Network operations with more than one argument are not allowed def func(x, y): pass class Container: def func(self, x, y): pass c = Container() with pytest.raises(TypeError): NetworkOperation(func) with pytest.raises(TypeError): NetworkOperation(c.func) # Incorrect use of @network_operation -- it does not work on an instance # method try: class Container: @network_operation def func(self): pass raise AssertionError("expected a TypeError") except TypeError: pass # this is what we expected @pytest.mark.codegen_independent def test_network_operations_name(): # test NetworkOperation name input seq = [] def f1(): seq.append("a") def f2(): seq.append("b") def x(): pass op = NetworkOperation(lambda: x) assert_equal(op.name, "networkoperation") op0 = NetworkOperation(lambda: x, name="named_network") assert_equal(op0.name, "named_network") op1 = NetworkOperation(f1, name="networkoperation_1") op2 = NetworkOperation(f1, name="networkoperation_3") op3 = NetworkOperation(f2, name="networkoperation_2") net = Network(op1, op2, op3) net.run(1 * ms) assert_equal("".join(seq), "aba" * 10) @pytest.mark.codegen_independent def test_network_active_flag(): # test that the BrianObject.active flag is recognised by Network.run x = Counter() y = Counter() y.active = False run(1 * ms) assert_equal(x.count, 10) assert_equal(y.count, 0) @pytest.mark.standalone_compatible @pytest.mark.multiple_runs def test_spikes_after_deactivating(): # Make sure that a spike in the last time step gets cleared. See #1319 always_spike = NeuronGroup(1, "", threshold="True", reset="") spike_mon = SpikeMonitor(always_spike) run(defaultclock.dt) always_spike.active = False run(defaultclock.dt) device.build(direct_call=False, **device.build_options) assert_equal(spike_mon.t[:], [0] * second) @pytest.mark.codegen_independent def test_network_t(): # test that Network.t works as expected x = Counter(dt=1 * ms) y = Counter(dt=2 * ms) net = Network(x, y) net.run(4 * ms) assert_equal(net.t, 4 * ms) net.run(1 * ms) assert_equal(net.t, 5 * ms) assert_equal(x.count, 5) assert_equal(y.count, 3) net.run(0.5 * ms) # should only update x assert_equal(net.t, 5.5 * ms) assert_equal(x.count, 6) assert_equal(y.count, 3) net.run(0.5 * ms) # shouldn't do anything assert_equal(net.t, 6 * ms) assert_equal(x.count, 6) assert_equal(y.count, 3) net.run(0.5 * ms) # should update x and y assert_equal(net.t, 6.5 * ms) assert_equal(x.count, 7) assert_equal(y.count, 4) del x, y, net # now test with magic run x = Counter(dt=1 * ms) y = Counter(dt=2 * ms) run(4 * ms) assert_equal(magic_network.t, 4 * ms) assert_equal(x.count, 4) assert_equal(y.count, 2) run(4 * ms) assert_equal(magic_network.t, 8 * ms) assert_equal(x.count, 8) assert_equal(y.count, 4) run(1 * ms) assert_equal(magic_network.t, 9 * ms) assert_equal(x.count, 9) assert_equal(y.count, 5) @pytest.mark.codegen_independent def test_incorrect_dt_defaultclock(): defaultclock.dt = 0.5 * ms G = NeuronGroup(1, "dv/dt = -v / (10*ms) : 1") net = Network(G) net.run(0.5 * ms) defaultclock.dt = 1 * ms with pytest.raises(ValueError): net.run(0 * ms) @pytest.mark.codegen_independent def test_incorrect_dt_custom_clock(): clock = Clock(dt=0.5 * ms) G = NeuronGroup(1, "dv/dt = -v / (10*ms) : 1", clock=clock) net = Network(G) net.run(0.5 * ms) clock.dt = 1 * ms with pytest.raises(ValueError): net.run(0 * ms) @pytest.mark.codegen_independent def test_network_remove(): x = Counter() y = Counter() net = Network(x, y) net.remove(y) net.run(1 * ms) assert_equal(x.count, 10) assert_equal(y.count, 0) # the relevance of this test is when we use weakref.proxy objects in # Network.objects, we should be able to add and remove these from # the Network just as much as the original objects # TODO: Does this test make sense now that Network does not store weak # references by default? for obj in copy.copy(net.objects): net.remove(obj) net.run(1 * ms) assert_equal(x.count, 10) assert_equal(y.count, 0) @pytest.mark.codegen_independent def test_contained_objects(): obj = CounterWithContained() net = Network(obj) # The contained object should not be stored explicitly assert len(net.objects) == 1 # It should be accessible via the network interface, though assert len(net) == 2 net.run(defaultclock.dt) # The contained object should be executed during the run assert obj.count == 1 assert obj.sub_counter.count == 1 # contained objects should be accessible via get_states/set_states states = net.get_states() assert len(states) == 2 assert set(states.keys()) == {obj.name, obj.sub_counter.name} assert set(states[obj.name].keys()) == {"state"} assert set(states[obj.sub_counter.name].keys()) == {"state"} net[obj.name].set_states({"state": 1}) net[obj.sub_counter.name].set_states({"state": 2}) net.remove(obj) assert len(net.objects) == 0 assert len(net) == 0 assert len(net.get_states()) == 0 net.run(defaultclock.dt) assert obj.count == 1 assert obj.sub_counter.count == 1 class NoninvalidatingCounter(Counter): add_to_magic_network = True invalidates_magic_network = False @pytest.mark.codegen_independent def test_invalid_magic_network(): x = Counter() run(1 * ms) assert_equal(x.count, 10) y = Counter() try: run(1 * ms) raise AssertionError("Expected a MagicError") except MagicError: pass # this is expected del x, y x = Counter() run(1 * ms) y = NoninvalidatingCounter() run(1 * ms) assert_equal(x.count, 20) assert_equal(y.count, 10) del y run(1 * ms) assert_equal(x.count, 30) del x x = Counter() run(1 * ms) assert_equal(magic_network.t, 1 * ms) del x x = Counter() y = Counter() run(1 * ms) assert_equal(x.count, 10) assert_equal(y.count, 10) @pytest.mark.codegen_independent def test_multiple_networks_invalid(): x = Counter() net = Network(x) net.run(1 * ms) try: run(1 * ms) raise AssertionError("Expected a RuntimeError") except RuntimeError: pass # this is expected try: net2 = Network(x) raise AssertionError("Expected a RuntimeError") except RuntimeError: pass # this is expected @pytest.mark.codegen_independent def test_magic_weak_reference(): """ Test that holding a weak reference to an object does not make it get simulated.""" G1 = NeuronGroup(1, "v:1") # this object should not be included G2 = weakref.ref(NeuronGroup(1, "v:1")) with catch_logs(log_level=logging.DEBUG) as l: run(1 * ms) # Check the debug messages for the number of included objects magic_objects = [ msg[2] for msg in l if msg[1] == "brian2.core.magic.magic_objects" ][0] assert "2 objects" in magic_objects, f"Unexpected log message: {magic_objects}" @pytest.mark.codegen_independent def test_magic_unused_object(): """Test that creating unused objects does not affect the magic system.""" def create_group(): # Produce two objects but return only one G1 = NeuronGroup(1, "v:1") # no Thresholder or Resetter G2 = NeuronGroup(1, "v:1") # This object should be garbage collected return G1 G = create_group() with catch_logs(log_level=logging.DEBUG) as l: run(1 * ms) # Check the debug messages for the number of included objects magic_objects = [ msg[2] for msg in l if msg[1] == "brian2.core.magic.magic_objects" ][0] assert "2 objects" in magic_objects, f"Unexpected log message: {magic_objects}" @pytest.mark.codegen_independent def test_network_access(): x = Counter(name="counter") net = Network(x) assert len(net) == 1 assert len(repr(net)) # very basic test... assert len(str(net)) # very basic test... # accessing objects assert net["counter"] is x with pytest.raises(TypeError): net[123] with pytest.raises(TypeError): net[1:3] with pytest.raises(KeyError): net["non-existing"] objects = [obj for obj in net] assert set(objects) == set(net.objects) # deleting objects del net["counter"] with pytest.raises(TypeError): net.__delitem__(123) with pytest.raises(TypeError): net.__delitem__(slice(1, 3)) with pytest.raises(KeyError): net.__delitem__("counter") @pytest.mark.codegen_independent def test_dependency_check(): def create_net(): G = NeuronGroup(10, "v: 1", threshold="False") dependent_objects = [ StateMonitor(G, "v", record=True), SpikeMonitor(G), PopulationRateMonitor(G), Synapses(G, G, on_pre="v+=1"), ] return dependent_objects dependent_objects = create_net() # Trying to simulate the monitors/synapses without the group should fail for obj in dependent_objects: with pytest.raises(ValueError): Network(obj).run(0 * ms) # simulation with a magic network should work when we have an explicit # reference to one of the objects, but the object should be inactive and # we should get a warning assert all(obj.active for obj in dependent_objects) for obj in dependent_objects: # obj is our explicit reference with catch_logs() as l: run(0 * ms) dependency_warnings = [ msg[2] for msg in l if msg[1] == "brian2.core.magic.dependency_warning" ] assert len(dependency_warnings) == 1 assert not obj.active def test_loop(): """ Somewhat realistic test with a loop of magic networks """ def run_simulation(): G = NeuronGroup(10, "dv/dt = -v / (10*ms) : 1", reset="v=0", threshold="v>1") G.v = np.linspace(0, 1, 10) run(1 * ms) # We return potentially problematic references to a VariableView return G.v # First run with catch_logs(log_level=logging.DEBUG) as l: v = run_simulation() assert v[0] == 0 and 0 < v[-1] < 1 # Check the debug messages for the number of included objects magic_objects = [ msg[2] for msg in l if msg[1] == "brian2.core.magic.magic_objects" ][0] assert "4 objects" in magic_objects # Second run with catch_logs(log_level=logging.DEBUG) as l: v = run_simulation() assert v[0] == 0 and 0 < v[-1] < 1 # Check the debug messages for the number of included objects magic_objects = [ msg[2] for msg in l if msg[1] == "brian2.core.magic.magic_objects" ][0] assert "4 objects" in magic_objects @pytest.mark.codegen_independent def test_magic_collect(): """ Make sure all expected objects are collected in a magic network """ P = PoissonGroup(10, rates=100 * Hz) G = NeuronGroup(10, "v:1", threshold="False") S = Synapses(G, G, "") state_mon = StateMonitor(G, "v", record=True) spike_mon = SpikeMonitor(G) rate_mon = PopulationRateMonitor(G) objects = collect() assert len(objects) == 6, f"expected {int(6)} objects, got {len(objects)}" import sys from contextlib import contextmanager from io import BytesIO, StringIO @contextmanager def captured_output(): new_out, new_err = StringIO(), StringIO() old_out, old_err = sys.stdout, sys.stderr try: sys.stdout, sys.stderr = new_out, new_err yield sys.stdout, sys.stderr finally: sys.stdout, sys.stderr = old_out, old_err @pytest.mark.codegen_independent def test_progress_report(): """ Very basic test of progress reporting """ G = NeuronGroup(1, "") net = Network(G) # No output with captured_output() as (out, err): net.run(1 * ms, report=None) # There should be at least two lines of output out, err = out.getvalue(), err.getvalue() assert len(out) == 0 and len(err) == 0 with captured_output() as (out, err): net.run(1 * ms) # There should be at least two lines of output out, err = out.getvalue(), err.getvalue() assert len(out) == 0 and len(err) == 0 # Progress should go to stdout with captured_output() as (out, err): net.run(1 * ms, report="text") # There should be at least two lines of output out, err = out.getvalue(), err.getvalue() assert len(out.split("\n")) >= 2 and len(err) == 0 with captured_output() as (out, err): net.run(1 * ms, report="stdout") # There should be at least two lines of output out, err = out.getvalue(), err.getvalue() assert len(out.split("\n")) >= 2 and len(err) == 0 # Progress should go to stderr with captured_output() as (out, err): net.run(1 * ms, report="stderr") # There should be at least two lines of output out, err = out.getvalue(), err.getvalue() assert len(err.split("\n")) >= 2 and len(out) == 0 # Custom function calls = [] def capture_progress(elapsed, complete, start, duration): calls.append((elapsed, complete, start, duration)) with captured_output() as (out, err): net.run(1 * ms, report=capture_progress) out, err = out.getvalue(), err.getvalue() assert len(err) == 0 and len(out) == 0 # There should be at least a call for the start and the end assert len(calls) >= 2 and calls[0][1] == 0.0 and calls[-1][1] == 1.0 @pytest.mark.codegen_independent def test_progress_report_incorrect(): """ Test wrong use of the report option """ G = NeuronGroup(1, "") net = Network(G) with pytest.raises(ValueError): net.run(1 * ms, report="unknown") with pytest.raises(TypeError): net.run(1 * ms, report=object()) @pytest.mark.standalone_compatible @pytest.mark.multiple_runs def test_multiple_runs_report_standalone(): group = NeuronGroup(1, "dv/dt = 1*Hz : 1") run(1 * ms, report="text") run(1 * ms) device.build(direct_call=False, **device.build_options) @pytest.mark.standalone_compatible @pytest.mark.multiple_runs def test_multiple_runs_report_standalone_2(): group = NeuronGroup(1, "dv/dt = 1*Hz : 1") run(1 * ms) run(1 * ms, report="text") device.build(direct_call=False, **device.build_options) @pytest.mark.standalone_compatible @pytest.mark.multiple_runs def test_multiple_runs_report_standalone_3(): group = NeuronGroup(1, "dv/dt = 1*Hz : 1") run(1 * ms, report="text") run(1 * ms, report="text") device.build(direct_call=False, **device.build_options) # This tests a specific limitation of the C++ standalone mode (cannot mix # multiple report methods) @pytest.mark.cpp_standalone @pytest.mark.standalone_only def test_multiple_runs_report_standalone_incorrect(): set_device("cpp_standalone", build_on_run=False) group = NeuronGroup(1, "dv/dt = 1*Hz : 1") run(1 * ms, report="text") with pytest.raises(NotImplementedError): run(1 * ms, report="stderr") @pytest.mark.codegen_independent def test_store_restore(): source = NeuronGroup( 10, """dv/dt = rates : 1 rates : Hz""", threshold="v>1", reset="v=0", ) source.rates = "i*100*Hz" target = NeuronGroup(10, "v:1") synapses = Synapses(source, target, model="w:1", on_pre="v+=w") synapses.connect(j="i") synapses.w = "i*1.0" synapses.delay = "i*ms" state_mon = StateMonitor(target, "v", record=True) spike_mon = SpikeMonitor(source) net = Network(source, target, synapses, state_mon, spike_mon) net.store() # default time slot net.run(10 * ms) net.store("second") net.run(10 * ms) v_values = state_mon.v[:, :] spike_indices, spike_times = spike_mon.it_ net.restore() # Go back to beginning assert defaultclock.t == 0 * ms assert net.t == 0 * ms net.run(20 * ms) assert_equal(v_values, state_mon.v[:, :]) assert_equal(spike_indices, spike_mon.i[:]) assert_equal(spike_times, spike_mon.t_[:]) # Go back to middle net.restore("second") assert defaultclock.t == 10 * ms assert net.t == 10 * ms net.run(10 * ms) assert_equal(v_values, state_mon.v[:, :]) assert_equal(spike_indices, spike_mon.i[:]) assert_equal(spike_times, spike_mon.t_[:]) # Go back again (see github issue #681) net.restore("second") assert defaultclock.t == 10 * ms assert net.t == 10 * ms @pytest.mark.codegen_independent def test_store_restore_to_file(): filename = tempfile.mktemp(suffix="state", prefix="brian_test") source = NeuronGroup( 10, """ dv/dt = rates : 1 rates : Hz """, threshold="v>1", reset="v=0", ) source.rates = "i*100*Hz" target = NeuronGroup(10, "v:1") synapses = Synapses(source, target, model="w:1", on_pre="v+=w") synapses.connect(j="i") synapses.w = "i*1.0" synapses.delay = "i*ms" state_mon = StateMonitor(target, "v", record=True) spike_mon = SpikeMonitor(source) net = Network(source, target, synapses, state_mon, spike_mon) net.store(filename=filename) # default time slot net.run(10 * ms) net.store("second", filename=filename) net.run(10 * ms) v_values = state_mon.v[:, :] spike_indices, spike_times = spike_mon.it_ net.restore(filename=filename) # Go back to beginning assert defaultclock.t == 0 * ms assert net.t == 0 * ms net.run(20 * ms) assert_equal(v_values, state_mon.v[:, :]) assert_equal(spike_indices, spike_mon.i[:]) assert_equal(spike_times, spike_mon.t_[:]) # Go back to middle net.restore("second", filename=filename) assert defaultclock.t == 10 * ms assert net.t == 10 * ms net.run(10 * ms) assert_equal(v_values, state_mon.v[:, :]) assert_equal(spike_indices, spike_mon.i[:]) assert_equal(spike_times, spike_mon.t_[:]) try: os.remove(filename) except OSError: pass @pytest.mark.codegen_independent def test_store_restore_to_file_new_objects(): # A more realistic test where the objects are completely re-created filename = tempfile.mktemp(suffix="state", prefix="brian_test") def create_net(): # Use a bit of a complicated spike and connection pattern with # heterogeneous delays # Note: it is important that all objects have the same name, this would # be the case if we were running this in a new process but to not rely # on garbage collection we will assign explicit names here source = SpikeGeneratorGroup( 5, np.arange(5).repeat(3), [3, 4, 1, 2, 3, 7, 5, 4, 1, 0, 5, 9, 7, 8, 9] * ms, name="source", ) target = NeuronGroup(10, "v:1", name="target") synapses = Synapses(source, target, model="w:1", on_pre="v+=w", name="synapses") synapses.connect("j>=i") synapses.w = "i*1.0 + j*2.0" synapses.delay = "(5-i)*ms" state_mon = StateMonitor(target, "v", record=True, name="statemonitor") input_spikes = SpikeMonitor(source, name="input_spikes") net = Network(source, target, synapses, state_mon, input_spikes) return net net = create_net() net.store(filename=filename) # default time slot net.run(5 * ms) net.store("second", filename=filename) net.run(5 * ms) input_spike_indices = np.array(net["input_spikes"].i) input_spike_times = Quantity(net["input_spikes"].t, copy=True) v_values_full_sim = Quantity(net["statemonitor"].v[:, :], copy=True) net = create_net() net.restore(filename=filename) # Go back to beginning net.run(10 * ms) assert_equal(input_spike_indices, net["input_spikes"].i) assert_equal(input_spike_times, net["input_spikes"].t) assert_equal(v_values_full_sim, net["statemonitor"].v[:, :]) net = create_net() net.restore("second", filename=filename) # Go back to middle net.run(5 * ms) assert_equal(input_spike_indices, net["input_spikes"].i) assert_equal(input_spike_times, net["input_spikes"].t) assert_equal(v_values_full_sim, net["statemonitor"].v[:, :]) try: os.remove(filename) except OSError: pass @pytest.mark.codegen_independent def test_store_restore_to_file_differing_nets(): # Check that the store/restore mechanism is not used with differing # networks filename = tempfile.mktemp(suffix="state", prefix="brian_test") source = SpikeGeneratorGroup( 5, [0, 1, 2, 3, 4], [0, 1, 2, 3, 4] * ms, name="source_1" ) mon = SpikeMonitor(source, name="monitor") net = Network(source, mon) net.store(filename=filename) source_2 = SpikeGeneratorGroup( 5, [0, 1, 2, 3, 4], [0, 1, 2, 3, 4] * ms, name="source_2" ) mon = SpikeMonitor(source_2, name="monitor") net = Network(source_2, mon) with pytest.raises(KeyError): net.restore(filename=filename) net = Network(source) # Without the monitor with pytest.raises(KeyError): net.restore(filename=filename) @pytest.mark.codegen_independent def test_store_restore_magic(): source = NeuronGroup( 10, """ dv/dt = rates : 1 rates : Hz """, threshold="v>1", reset="v=0", ) source.rates = "i*100*Hz" target = NeuronGroup(10, "v:1") synapses = Synapses(source, target, model="w:1", on_pre="v+=w") synapses.connect(j="i") synapses.w = "i*1.0" synapses.delay = "i*ms" state_mon = StateMonitor(target, "v", record=True) spike_mon = SpikeMonitor(source) store() # default time slot run(10 * ms) store("second") run(10 * ms) v_values = state_mon.v[:, :] spike_indices, spike_times = spike_mon.it_ restore() # Go back to beginning assert magic_network.t == 0 * ms run(20 * ms) assert defaultclock.t == 20 * ms assert_equal(v_values, state_mon.v[:, :]) assert_equal(spike_indices, spike_mon.i[:]) assert_equal(spike_times, spike_mon.t_[:]) # Go back to middle restore("second") assert magic_network.t == 10 * ms run(10 * ms) assert defaultclock.t == 20 * ms assert_equal(v_values, state_mon.v[:, :]) assert_equal(spike_indices, spike_mon.i[:]) assert_equal(spike_times, spike_mon.t_[:]) @pytest.mark.codegen_independent def test_store_restore_magic_to_file(): filename = tempfile.mktemp(suffix="state", prefix="brian_test") source = NeuronGroup( 10, """ dv/dt = rates : 1 rates : Hz """, threshold="v>1", reset="v=0", ) source.rates = "i*100*Hz" target = NeuronGroup(10, "v:1") synapses = Synapses(source, target, model="w:1", on_pre="v+=w") synapses.connect(j="i") synapses.w = "i*1.0" synapses.delay = "i*ms" state_mon = StateMonitor(target, "v", record=True) spike_mon = SpikeMonitor(source) store(filename=filename) # default time slot run(10 * ms) store("second", filename=filename) run(10 * ms) v_values = state_mon.v[:, :] spike_indices, spike_times = spike_mon.it_ restore(filename=filename) # Go back to beginning assert magic_network.t == 0 * ms run(20 * ms) assert defaultclock.t == 20 * ms assert_equal(v_values, state_mon.v[:, :]) assert_equal(spike_indices, spike_mon.i[:]) assert_equal(spike_times, spike_mon.t_[:]) # Go back to middle restore("second", filename=filename) assert magic_network.t == 10 * ms run(10 * ms) assert defaultclock.t == 20 * ms assert_equal(v_values, state_mon.v[:, :]) assert_equal(spike_indices, spike_mon.i[:]) assert_equal(spike_times, spike_mon.t_[:]) try: os.remove(filename) except OSError: pass @pytest.mark.codegen_independent def test_store_restore_spikequeue(): # See github issue #938 source = SpikeGeneratorGroup(1, [0], [0] * ms) target = NeuronGroup(1, "v : 1") conn = Synapses(source, target, on_pre="v += 1", delay=2 * defaultclock.dt) conn.connect() run(defaultclock.dt) # Spike is not yet delivered store() run(2 * defaultclock.dt) assert target.v[0] == 1 restore() run(2 * defaultclock.dt) assert target.v[0] == 1 restore() run(2 * defaultclock.dt) assert target.v[0] == 1 @pytest.mark.skipif( not isinstance(get_device(), RuntimeDevice), reason="Getting/setting random number state only supported for runtime device.", ) def test_restore_with_random_state(): group = NeuronGroup(10, "dv/dt = -v/(10*ms) + (10*ms)**-0.5*xi : 1", method="euler") group.v = "rand()" mon = StateMonitor(group, "v", record=True) store() run(10 * ms) old_v = np.array(group.v) restore() # Random state is not restored run(10 * ms) assert np.var(old_v - group.v) > 0 # very basic test for difference restore(restore_random_state=True) # Random state is restored run(10 * ms) assert_equal(old_v, group.v) @pytest.mark.codegen_independent def test_store_restore_restore_synapses(): group = NeuronGroup(10, "x : 1", threshold="False", reset="", name="group") synapses = Synapses(group, group, on_pre="x += 1", name="synapses") synapses.connect(i=[1, 3, 5], j=[6, 4, 2]) net = Network(group, synapses) tmp_file = tempfile.mktemp() net.store(filename=tmp_file) # clear up del net del synapses del group # Recreate the network without connecting the synapses group = NeuronGroup(10, "x: 1", threshold="False", reset="", name="group") synapses = Synapses(group, group, "", on_pre="x += 1", name="synapses") net = Network(group, synapses) try: net.restore(filename=tmp_file) assert len(synapses) == 3 assert_array_equal(synapses.i, [1, 3, 5]) assert_array_equal(synapses.j, [6, 4, 2]) # Tunning the network should not raise an error, despite the lack # of Synapses.connect net.run(0 * ms) finally: os.remove(tmp_file) @pytest.mark.codegen_independent def test_defaultclock_dt_changes(): BrianLogger.suppress_name("resolution_conflict") for dt in [0.1 * ms, 0.01 * ms, 0.5 * ms, 1 * ms, 3.3 * ms]: defaultclock.dt = dt G = NeuronGroup(1, "v:1") mon = StateMonitor(G, "v", record=True) net = Network(G, mon) net.run(2 * dt) assert_equal(mon.t[:], [0, dt / ms] * ms) @pytest.mark.standalone_compatible @pytest.mark.multiple_runs def test_dt_changes_between_runs(): defaultclock.dt = 0.1 * ms G = NeuronGroup(1, "v:1") mon = StateMonitor(G, "v", record=True) run(0.5 * ms) defaultclock.dt = 0.5 * ms run(0.5 * ms) defaultclock.dt = 0.1 * ms run(0.5 * ms) device.build(direct_call=False, **device.build_options) assert len(mon.t[:]) == 5 + 1 + 5 assert_allclose( mon.t[:], [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 1.0, 1.1, 1.2, 1.3, 1.4] * ms ) @pytest.mark.codegen_independent def test_dt_restore(): defaultclock.dt = 0.5 * ms G = NeuronGroup(1, "dv/dt = -v/(10*ms) : 1") mon = StateMonitor(G, "v", record=True) net = Network(G, mon) net.store() net.run(1 * ms) assert_equal(mon.t[:], [0, 0.5] * ms) defaultclock.dt = 1 * ms net.run(2 * ms) assert_equal(mon.t[:], [0, 0.5, 1, 2] * ms) net.restore() assert_equal(mon.t[:], []) net.run(1 * ms) assert defaultclock.dt == 0.5 * ms assert_equal(mon.t[:], [0, 0.5] * ms) @pytest.mark.codegen_independent def test_continuation(): defaultclock.dt = 1 * ms G = NeuronGroup(1, "dv/dt = -v / (10*ms) : 1") G.v = 1 mon = StateMonitor(G, "v", record=True) net = Network(G, mon) net.run(2 * ms) # Run the same simulation but with two runs that use sub-dt run times G2 = NeuronGroup(1, "dv/dt = -v / (10*ms) : 1") G2.v = 1 mon2 = StateMonitor(G2, "v", record=True) net2 = Network(G2, mon2) net2.run(0.5 * ms) net2.run(1.5 * ms) assert_equal(mon.t[:], mon2.t[:]) assert_equal(mon.v[:], mon2.v[:]) @pytest.mark.codegen_independent def test_get_set_states(): G = NeuronGroup(10, "v:1", name="a_neurongroup") G.v = "i" net = Network(G) states1 = net.get_states() states2 = magic_network.get_states() states3 = net.get_states(read_only_variables=False) assert ( set(states1.keys()) == set(states2.keys()) == set(states3.keys()) == {"a_neurongroup"} ) assert ( set(states1["a_neurongroup"].keys()) == set(states2["a_neurongroup"].keys()) == {"i", "dt", "N", "t", "v", "t_in_timesteps"} ) assert set(states3["a_neurongroup"]) == {"v"} # Try re-setting the state G.v = 0 net.set_states(states3) assert_equal(G.v, np.arange(10)) @pytest.mark.codegen_independent def test_multiple_runs_defaultclock(): defaultclock.dt = 0.1 * ms G = NeuronGroup(1, "dv/dt = -v / (10*ms) : 1") net = Network(G) net.run(0.5 * ms) # The new dt is not compatible with the previous time but it should not # raise an error because we start a new simulation at time 0 defaultclock.dt = 1 * ms G = NeuronGroup(1, "dv/dt = -v / (10*ms) : 1") net = Network(G) net.run(1 * ms) @pytest.mark.codegen_independent def test_multiple_runs_defaultclock_incorrect(): defaultclock.dt = 0.1 * ms G = NeuronGroup(1, "dv/dt = -v / (10*ms) : 1") net = Network(G) net.run(0.5 * ms) # The new dt is not compatible with the previous time since we cannot # continue at 0.5ms with a dt of 1ms defaultclock.dt = 1 * ms with pytest.raises(ValueError): net.run(1 * ms) @pytest.mark.standalone_compatible def test_profile(): G = NeuronGroup( 10, "dv/dt = -v / (10*ms) : 1", threshold="v>1", reset="v=0", name="profile_test", ) G.v = 1.1 net = Network(G) net.run(1 * ms, profile=True) # The should be four simulated CodeObjects, one for the group and one each # for state update, threshold and reset + 1 for the clock info = net.profiling_info info_dict = dict(info) # Standalone does not include the NeuronGroup object (which is not doing # anything during the run) in the profiling information, while runtime # does assert 3 <= len(info) <= 4 assert len(info) == 3 or "profile_test" in info_dict for obj in ["stateupdater", "spike_thresholder", "spike_resetter"]: name = f"profile_test_{obj}" assert name in info_dict or f"{name}_codeobject" in info_dict assert all([t >= 0 * second for _, t in info]) @pytest.mark.standalone_compatible def test_profile_off(): G = NeuronGroup( 10, "dv/dt = -v / (10*ms) : 1", threshold="v>1", reset="v=0", name="profile_test", ) net = Network(G) net.run(1 * ms, profile=False) with pytest.raises(ValueError): profiling_summary(net) @pytest.mark.codegen_independent def test_profile_ipython_html(): G = NeuronGroup( 10, "dv/dt = -v / (10*ms) : 1", threshold="v>1", reset="v=0", name="profile_test", ) G.v = 1.1 net = Network(G) net.run(1 * ms, profile=True) summary = profiling_summary(net) assert len(summary._repr_html_()) @pytest.mark.codegen_independent def test_magic_scope(): """ Check that `start_scope` works as expected. """ G1 = NeuronGroup(1, "v:1", name="G1") G2 = NeuronGroup(1, "v:1", name="G2") objs1 = {obj.name for obj in collect()} start_scope() G3 = NeuronGroup(1, "v:1", name="G3") G4 = NeuronGroup(1, "v:1", name="G4") objs2 = {obj.name for obj in collect()} assert objs1 == {"G1", "G2"} assert objs2 == {"G3", "G4"} @pytest.mark.standalone_compatible def test_runtime_rounding(): # Test that runtime and standalone round in the same way, see github issue # #695 for details defaultclock.dt = 20.000000000020002 * us G = NeuronGroup(1, "v:1") mon = StateMonitor(G, "v", record=True) run(defaultclock.dt * 250) assert len(mon.t) == 250 @pytest.mark.codegen_independent def test_small_runs(): # One long run and multiple small runs should give the same results group_1 = NeuronGroup(10, "dv/dt = -v / (10*ms) : 1") group_1.v = "(i + 1) / N" mon_1 = StateMonitor(group_1, "v", record=True) net_1 = Network(group_1, mon_1) net_1.run(1 * second) group_2 = NeuronGroup(10, "dv/dt = -v / (10*ms) : 1") group_2.v = "(i + 1) / N" mon_2 = StateMonitor(group_2, "v", record=True) net_2 = Network(group_2, mon_2) runtime = 1 * ms while True: runtime *= 3 runtime = min([runtime, 1 * second - net_2.t]) net_2.run(runtime) if net_2.t >= 1 * second: break assert_allclose(mon_1.t_[:], mon_2.t_[:]) assert_allclose(mon_1.v_[:], mon_2.v_[:]) @pytest.mark.codegen_independent def test_both_equal(): # check all objects added by Network.add() also have their contained_objects added to 'Network' tau = 10 * ms diff_eqn = """dv/dt = (1-v)/tau : 1""" chg_code = """v = 2*v""" Ng = NeuronGroup(1, diff_eqn, method="exact") M1 = StateMonitor(Ng, "v", record=True) netObj = Network(Ng, M1) Ng.run_regularly(chg_code, dt=20 * ms) netObj.run(100 * ms) start_scope() Ng = NeuronGroup(1, diff_eqn, method="exact") M2 = StateMonitor(Ng, "v", record=True) Ng.run_regularly(chg_code, dt=20 * ms) run(100 * ms) assert (M1.v == M2.v).all() @pytest.mark.standalone_compatible @pytest.mark.multiple_runs def test_long_run(): defaultclock.dt = 0.1 * ms group = NeuronGroup(1, "x : 1") group.run_regularly("x += 1") # Timesteps are internally stored as 64bit integers, but previous versions # converted them into 32bit integers along the way. We'll make sure that # this is not the case and everything runs fine. To not actually run such a # long simulation we run a single huge time step start_step = 2**31 - 5 defaultclock.dt = 0.1 * ms start_time = start_step * defaultclock.dt defaultclock.dt = start_time run(start_time) # A single, *very* long time step defaultclock.dt = 0.1 * ms run(6 * defaultclock.dt) device.build(direct_call=False, **device.build_options) assert group.x == 7 @pytest.mark.codegen_independent def test_long_run_dt_change(): # Check that the dt check is not too restrictive, see issue #730 for details group = NeuronGroup(1, "") # does nothing... defaultclock.dt = 0.1 * ms run(100 * second) # print profiling_summary() defaultclock.dt = 0.01 * ms run(1 * second) @pytest.mark.standalone_compatible @pytest.mark.multiple_runs def test_multiple_runs_constant_change(): const_v = 1 group = NeuronGroup(1, "v = const_v : 1") mon = StateMonitor(group, "v", record=0) run(defaultclock.dt) const_v = 2 run(defaultclock.dt) device.build(direct_call=False, **device.build_options) assert_equal(mon.v[0], [1, 2]) @pytest.mark.standalone_compatible @pytest.mark.multiple_runs def test_multiple_runs_function_change(): inp = TimedArray([1, 2], dt=defaultclock.dt) group = NeuronGroup(1, "v = inp(t) : 1") mon = StateMonitor(group, "v", record=0) run(2 * defaultclock.dt) inp = TimedArray([0, 0, 3, 4], dt=defaultclock.dt) run(2 * defaultclock.dt) device.build(direct_call=False, **device.build_options) assert_equal(mon.v[0], [1, 2, 3, 4]) if __name__ == "__main__": BrianLogger.log_level_warn() for t in [ test_incorrect_network_use, test_network_contains, test_empty_network, test_network_single_object, test_network_two_objects, test_network_from_dict, test_network_different_clocks, test_network_different_when, test_network_default_schedule, test_network_schedule_change, test_network_before_after_schedule, test_network_custom_slots, test_network_incorrect_schedule, test_schedule_warning, test_scheduling_summary_magic, test_scheduling_summary, test_magic_network, test_network_stop, test_network_operations, test_incorrect_network_operations, test_network_operations_name, test_network_active_flag, test_network_t, test_incorrect_dt_defaultclock, test_incorrect_dt_custom_clock, test_network_remove, test_magic_weak_reference, test_magic_unused_object, test_invalid_magic_network, test_multiple_networks_invalid, test_network_access, test_loop, test_magic_collect, test_progress_report, test_progress_report_incorrect, test_multiple_runs_report_standalone, test_multiple_runs_report_standalone_2, test_multiple_runs_report_standalone_3, test_multiple_runs_report_standalone_incorrect, test_store_restore, test_store_restore_to_file, test_store_restore_to_file_new_objects, test_store_restore_to_file_differing_nets, test_store_restore_magic, test_store_restore_magic_to_file, test_store_restore_spikequeue, test_store_restore_restore_synapses, test_defaultclock_dt_changes, test_dt_changes_between_runs, test_dt_restore, test_continuation, test_get_set_states, test_multiple_runs_defaultclock, test_multiple_runs_defaultclock_incorrect, test_profile, test_profile_off, test_profile_ipython_html, test_magic_scope, test_runtime_rounding, test_small_runs, test_both_equal, test_long_run, test_long_run_dt_change, test_multiple_runs_constant_change, test_multiple_runs_function_change, ]: set_device(all_devices["runtime"]) t() reinit_and_delete() brian2-2.5.4/brian2/tests/test_neurongroup.py000066400000000000000000002026221445201106100212100ustar00rootroot00000000000000import uuid import numpy as np import pytest import sympy from numpy.testing import assert_equal, assert_raises from brian2.core.base import BrianObjectException from brian2.core.clocks import defaultclock from brian2.core.magic import run from brian2.core.network import Network from brian2.core.preferences import prefs from brian2.core.variables import linked_var from brian2.devices.device import device, seed from brian2.equations.equations import Equations from brian2.groups.group import get_dtype from brian2.groups.neurongroup import NeuronGroup from brian2.monitors.statemonitor import StateMonitor from brian2.synapses.synapses import Synapses from brian2.tests.utils import assert_allclose, exc_isinstance from brian2.units.allunits import second, volt from brian2.units.fundamentalunits import DimensionMismatchError, have_same_dimensions from brian2.units.stdunits import Hz, ms, mV from brian2.units.unitsafefunctions import linspace from brian2.utils.logger import catch_logs @pytest.mark.codegen_independent def test_creation(): """ A basic test that creating a NeuronGroup works. """ G = NeuronGroup(42, model="dv/dt = -v/(10*ms) : 1", reset="v=0", threshold="v>1") assert len(G) == 42 # Test some error conditions # -------------------------- # Model equations as first argument (no number of neurons) with pytest.raises(TypeError): NeuronGroup("dv/dt = 5*Hz : 1", 1) # Not a number as first argument with pytest.raises(TypeError): NeuronGroup(object(), "dv/dt = 5*Hz : 1") # Illegal number with pytest.raises(ValueError): NeuronGroup(0, "dv/dt = 5*Hz : 1") # neither string nor Equations object as model description with pytest.raises(TypeError): NeuronGroup(1, object()) @pytest.mark.codegen_independent def test_integer_variables_and_mod(): """ Test that integer operations and variable definitions work. """ n = 10 eqs = """ dv/dt = (a+b+j+k)/second : 1 j = i%n : integer k = i//n : integer a = v%(i+1) : 1 b = v%(2*v) : 1 """ G = NeuronGroup(100, eqs) G.v = np.random.rand(len(G)) run(1 * ms) assert_equal(G.j[:], G.i[:] % n) assert_equal(G.k[:], G.i[:] // n) assert_equal(G.a[:], G.v[:] % (G.i[:] + 1)) @pytest.mark.codegen_independent def test_variables(): """ Test the correct creation of the variables dictionary. """ G = NeuronGroup(1, "dv/dt = -v/(10*ms) : 1") assert all((x in G.variables) for x in ["v", "t", "dt", "t_in_timesteps"]) assert "not_refractory" not in G.variables and "lastspike" not in G.variables G = NeuronGroup(1, "dv/dt = -v/tau + xi*tau**-0.5: 1") assert not "tau" in G.variables and "xi" in G.variables # NeuronGroup with refractoriness G = NeuronGroup(1, "dv/dt = -v/(10*ms) : 1", refractory=5 * ms) assert "not_refractory" in G.variables and "lastspike" in G.variables @pytest.mark.codegen_independent def test_variableview_calculations(): # Check that you can directly calculate with "variable views" G = NeuronGroup( 10, """ x : 1 y : volt idx : integer """, ) G.x = np.arange(10) G.y = np.arange(10)[::-1] * mV G.idx = np.arange(10, dtype=int) assert_allclose(G.x * G.y, np.arange(10) * np.arange(10)[::-1] * mV) assert_allclose(-G.x, -np.arange(10)) assert_allclose(-G.y, -np.arange(10)[::-1] * mV) assert_allclose(3 * G.x, 3 * np.arange(10)) assert_allclose(3 * G.y, 3 * np.arange(10)[::-1] * mV) assert_allclose(G.x * 3, 3 * np.arange(10)) assert_allclose(G.y * 3, 3 * np.arange(10)[::-1] * mV) assert_allclose(G.x / 2.0, np.arange(10) / 2.0) assert_allclose(G.y / 2, np.arange(10)[::-1] * mV / 2) assert_equal(G.idx % 2, np.arange(10, dtype=int) % 2) assert_allclose(G.x + 2, 2 + np.arange(10)) assert_allclose(G.y + 2 * mV, 2 * mV + np.arange(10)[::-1] * mV) assert_allclose(2 + G.x, 2 + np.arange(10)) assert_allclose(2 * mV + G.y, 2 * mV + np.arange(10)[::-1] * mV) assert_allclose(G.x - 2, np.arange(10) - 2) assert_allclose(G.y - 2 * mV, np.arange(10)[::-1] * mV - 2 * mV) assert_allclose(2 - G.x, 2 - np.arange(10)) assert_allclose(2 * mV - G.y, 2 * mV - np.arange(10)[::-1] * mV) assert_allclose(G.x**2, np.arange(10) ** 2) assert_allclose(G.y**2, (np.arange(10)[::-1] * mV) ** 2) assert_allclose(2**G.x, 2 ** np.arange(10)) # incorrect units with pytest.raises(DimensionMismatchError): G.x + G.y with pytest.raises(DimensionMismatchError): G.x[:] + G.y with pytest.raises(DimensionMismatchError): G.x + G.y[:] with pytest.raises(DimensionMismatchError): G.x + 3 * mV with pytest.raises(DimensionMismatchError): 3 * mV + G.x with pytest.raises(DimensionMismatchError): G.y + 3 with pytest.raises(DimensionMismatchError): 3 + G.y with pytest.raises(TypeError): 2**G.y # raising to a power with units @pytest.mark.codegen_independent def test_variableview_inplace_calculations(): # Check that you can directly do in-place calculation with "variable views" G = NeuronGroup( 10, """ x : 1 y : volt """, ) x_vals = np.arange(10) y_vals = np.arange(10)[::-1] * mV G.x[:] = x_vals G.y[:] = y_vals # Addition G.x += 1 G.y += 1 * mV assert_allclose(G.x[:], x_vals + 1) assert_allclose(G.y[:], y_vals + 1 * mV) G.y_ += float(1 * mV) assert_allclose(G.y[:], y_vals + 2 * mV) with pytest.raises(DimensionMismatchError): G.x += 1 * mV with pytest.raises(DimensionMismatchError): G.y += 1 with pytest.raises(DimensionMismatchError): G.y += 1 * ms G.x[:] = x_vals G.y[:] = y_vals # Subtraction G.x -= 1 G.y -= 1 * mV assert_allclose(G.x[:], x_vals - 1) assert_allclose(G.y[:], y_vals - 1 * mV) G.y_ -= float(1 * mV) assert_allclose(G.y[:], y_vals - 2 * mV) with pytest.raises(DimensionMismatchError): G.x -= 1 * mV with pytest.raises(DimensionMismatchError): G.y -= 1 with pytest.raises(DimensionMismatchError): G.y -= 1 * ms G.x[:] = x_vals G.y[:] = y_vals # Multiplication G.x *= 2 G.y *= 2 assert_allclose(G.x[:], x_vals * 2) assert_allclose(G.y[:], y_vals * 2) with pytest.raises(DimensionMismatchError): G.x *= 2 * mV with pytest.raises(DimensionMismatchError): G.y *= 1 * mV G.x[:] = x_vals G.y[:] = y_vals # Division G.x /= 2 G.y /= 2 assert_allclose(G.x[:], x_vals / 2) assert_allclose(G.y[:], y_vals / 2) with pytest.raises(DimensionMismatchError): G.x /= 2 * mV with pytest.raises(DimensionMismatchError): G.y /= 1 * mV G.x[:] = x_vals G.y[:] = y_vals # Floor division G.x //= 2 # This is very sensitive to rounding issues, so increase the value a bit G.y += 0.01 * mV G.y //= 0.001 assert_allclose(G.x[:], x_vals // 2) assert_allclose(G.y[:], y_vals // 0.001) with pytest.raises(DimensionMismatchError): G.x //= 2 * mV with pytest.raises(DimensionMismatchError): G.y //= 1 * mV G.x[:] = x_vals G.y[:] = y_vals # Modulo G.x %= 2 G.y %= 3.3 * mV assert_allclose(G.x[:], x_vals % 2) assert_allclose(G.y[:], y_vals % (3.3 * mV)) with pytest.raises(DimensionMismatchError): G.y %= 2 with pytest.raises(DimensionMismatchError): G.y %= 2 * ms G.x[:] = x_vals G.y[:] = y_vals # Power G.x **= 2 assert_allclose(G.x[:], x_vals**2) with pytest.raises(DimensionMismatchError): G.y **= 2 @pytest.mark.standalone_compatible def test_stochastic_variable(): """ Test that a NeuronGroup with a stochastic variable can be simulated. Only makes sure no error occurs. """ tau = 10 * ms G = NeuronGroup(1, "dv/dt = -v/tau + xi*tau**-0.5: 1") run(defaultclock.dt) @pytest.mark.standalone_compatible def test_stochastic_variable_multiplicative(): """ Test that a NeuronGroup with multiplicative noise can be simulated. Only makes sure no error occurs. """ mu = 0.5 / second # drift sigma = 0.1 / second # diffusion G = NeuronGroup( 1, "dX/dt = (mu - 0.5*second*sigma**2)*X + X*sigma*xi*second**.5: 1" ) run(defaultclock.dt) def test_scalar_variable(): """ Test the correct handling of scalar variables """ tau = 10 * ms G = NeuronGroup( 10, """ E_L : volt (shared) s2 : 1 (shared) dv/dt = (E_L - v) / tau : volt """, ) # Setting should work in these ways G.E_L = -70 * mV assert_allclose(G.E_L[:], -70 * mV) G.E_L[:] = -60 * mV assert_allclose(G.E_L[:], -60 * mV) G.E_L = "E_L + s2*mV - 10*mV" assert_allclose(G.E_L[:], -70 * mV) G.E_L[:] = "-75*mV" assert_allclose(G.E_L[:], -75 * mV) net = Network(G) net.run(defaultclock.dt) @pytest.mark.standalone_compatible def test_referred_scalar_variable(): """ Test the correct handling of referred scalar variables in subexpressions """ G = NeuronGroup( 10, """ out = sin(2*pi*t*freq) + x: 1 x : 1 freq : Hz (shared) """, ) G.freq = 1 * Hz G.x = np.arange(10) G2 = NeuronGroup(10, "") G2.variables.add_reference("out", G) run(0.25 * second) assert_allclose(G2.out[:], np.arange(10) + 1) @pytest.mark.standalone_compatible def test_linked_variable_correct(): """ Test correct uses of linked variables. """ tau = 10 * ms G1 = NeuronGroup(10, "dv/dt = -v / tau : volt") G1.v = linspace(0 * mV, 20 * mV, 10) G2 = NeuronGroup(10, "v : volt (linked)") G2.v = linked_var(G1.v) mon1 = StateMonitor(G1, "v", record=True) mon2 = StateMonitor(G2, "v", record=True) run(10 * ms) assert_allclose(mon1.v[:, :], mon2.v[:, :]) # Make sure that printing the variable values works assert len(str(G2.v)) > 0 assert len(repr(G2.v)) > 0 assert len(str(G2.v[:])) > 0 assert len(repr(G2.v[:])) > 0 @pytest.mark.codegen_independent def test_linked_variable_incorrect(): """ Test incorrect uses of linked variables. """ G1 = NeuronGroup( 10, """ x : volt y : 1 """, ) G2 = NeuronGroup(20, """x: volt""") G3 = NeuronGroup( 10, """ l : volt (linked) not_linked : volt """, ) # incorrect unit with pytest.raises(DimensionMismatchError): setattr(G3, "l", linked_var(G1.y)) # incorrect group size with pytest.raises(ValueError): setattr(G3, "l", linked_var(G2.x)) # incorrect use of linked_var with pytest.raises(ValueError): setattr(G3, "l", linked_var(G1.x, "x")) with pytest.raises(ValueError): setattr(G3, "l", linked_var(G1)) # Not a linked variable with pytest.raises(TypeError): setattr(G3, "not_linked", linked_var(G1.x)) @pytest.mark.standalone_compatible def test_linked_variable_scalar(): """ Test linked variable from a size 1 group. """ G1 = NeuronGroup(1, "dx/dt = -x / (10*ms) : 1") G2 = NeuronGroup( 10, """ dy/dt = (-y + x) / (20*ms) : 1 x : 1 (linked) """, ) G1.x = 1 G2.y = np.linspace(0, 1, 10) G2.x = linked_var(G1.x) mon = StateMonitor(G2, "y", record=True) # We don't test anything for now, except that it runs without raising an # error run(defaultclock.dt) # Make sure that printing the variable values works assert len(str(G2.x)) > 0 assert len(repr(G2.x)) > 0 assert len(str(G2.x[:])) > 0 assert len(repr(G2.x[:])) > 0 assert np.isscalar(G2.x[:]) # Check that subgroups work correctly (see github issue #916) sg1 = G2[:5] sg2 = G2[5:] assert sg1.x == G2.x assert sg2.x == G2.x @pytest.mark.codegen_independent def test_linked_variable_indexed(): """ Test linking a variable with an index specified as an array """ G = NeuronGroup( 10, """ x : 1 y : 1 (linked) """, ) G.x = np.arange(10) * 0.1 G.y = linked_var(G.x, index=np.arange(10)[::-1]) # G.y should refer to an inverted version of G.x assert_allclose(G.y[:], np.arange(10)[::-1] * 0.1) @pytest.mark.codegen_independent def test_linked_variable_repeat(): """ Test a "repeat"-like connection between two groups of different size """ G1 = NeuronGroup(5, "w : 1") G2 = NeuronGroup(10, "v : 1 (linked)") G2.v = linked_var(G1.w, index=np.arange(5).repeat(2)) G1.w = np.arange(5) * 0.1 assert_allclose(G2.v[:], np.arange(5).repeat(2) * 0.1) @pytest.mark.codegen_independent def test_linked_double_linked1(): """ Linked to a linked variable, without indices """ G1 = NeuronGroup(10, "x : 1") G2 = NeuronGroup(10, "y : 1 (linked)") G2.y = linked_var(G1.x) G3 = NeuronGroup(10, "z: 1 (linked)") G3.z = linked_var(G2.y) G1.x = np.arange(10) assert_allclose(G3.z[:], np.arange(10)) @pytest.mark.codegen_independent def test_linked_double_linked2(): """ Linked to a linked variable, first without indices, second with indices """ G1 = NeuronGroup(5, "x : 1") G2 = NeuronGroup(5, "y : 1 (linked)") G2.y = linked_var(G1.x) G3 = NeuronGroup(10, "z: 1 (linked)") G3.z = linked_var(G2.y, index=np.arange(5).repeat(2)) G1.x = np.arange(5) * 0.1 assert_allclose(G3.z[:], np.arange(5).repeat(2) * 0.1) @pytest.mark.codegen_independent def test_linked_double_linked3(): """ Linked to a linked variable, first with indices, second without indices """ G1 = NeuronGroup(5, "x : 1") G2 = NeuronGroup(10, "y : 1 (linked)") G2.y = linked_var(G1.x, index=np.arange(5).repeat(2)) G3 = NeuronGroup(10, "z: 1 (linked)") G3.z = linked_var(G2.y) G1.x = np.arange(5) * 0.1 assert_allclose(G3.z[:], np.arange(5).repeat(2) * 0.1) @pytest.mark.codegen_independent def test_linked_double_linked4(): """ Linked to a linked variable, both use indices """ G1 = NeuronGroup(5, "x : 1") G2 = NeuronGroup(10, "y : 1 (linked)") G2.y = linked_var(G1.x, index=np.arange(5).repeat(2)) G3 = NeuronGroup(10, "z: 1 (linked)") G3.z = linked_var(G2.y, index=np.arange(10)[::-1]) G1.x = np.arange(5) * 0.1 assert_allclose(G3.z[:], np.arange(5).repeat(2)[::-1] * 0.1) @pytest.mark.codegen_independent def test_linked_triple_linked(): """ Link to a linked variable that links to a linked variable, all use indices """ G1 = NeuronGroup(2, "a : 1") G2 = NeuronGroup(4, "b : 1 (linked)") G2.b = linked_var(G1.a, index=np.arange(2).repeat(2)) G3 = NeuronGroup(4, "c: 1 (linked)") G3.c = linked_var(G2.b, index=np.arange(4)[::-1]) G4 = NeuronGroup(8, "d: 1 (linked)") G4.d = linked_var(G3.c, index=np.arange(4).repeat(2)) G1.a = np.arange(2) * 0.1 assert_allclose(G4.d[:], np.arange(2).repeat(2)[::-1].repeat(2) * 0.1) @pytest.mark.codegen_independent def test_linked_subgroup(): """ Test linking a variable from a subgroup """ G1 = NeuronGroup(10, "x : 1") G1.x = np.arange(10) * 0.1 G2 = G1[3:8] G3 = NeuronGroup(5, "y:1 (linked)") G3.y = linked_var(G2.x) assert_allclose(G3.y[:], (np.arange(5) + 3) * 0.1) @pytest.mark.codegen_independent def test_linked_subgroup2(): """ Test linking a variable from a subgroup with indexing """ G1 = NeuronGroup(10, "x : 1") G1.x = np.arange(10) * 0.1 G2 = G1[3:8] G3 = NeuronGroup(10, "y:1 (linked)") G3.y = linked_var(G2.x, index=np.arange(5).repeat(2)) assert_allclose(G3.y[:], (np.arange(5) + 3).repeat(2) * 0.1) @pytest.mark.standalone_compatible def test_linked_subexpression(): """ Test a subexpression referring to a linked variable. """ G = NeuronGroup(2, "dv/dt = 100*Hz : 1", threshold="v>1", reset="v=0") G.v = [0, 0.5] G2 = NeuronGroup( 10, """ I = clip(x, 0, inf) : 1 x : 1 (linked) """, ) G2.x = linked_var(G.v, index=np.array([0, 1]).repeat(5)) mon = StateMonitor(G2, "I", record=True) run(5 * ms) # Due to the linking, the first 5 and the second 5 recorded I vectors should # be identical assert all(all(mon[i].I == mon[0].I) for i in range(5)) assert all(all(mon[i + 5].I == mon[5].I) for i in range(5)) @pytest.mark.standalone_compatible def test_linked_subexpression_2(): """ Test a linked variable referring to a subexpression without indices """ G = NeuronGroup( 2, """ dv/dt = 100*Hz : 1 I = clip(v, 0, inf) : 1 """, threshold="v>1", reset="v=0", ) G.v = [0, 0.5] G2 = NeuronGroup(2, """I_l : 1 (linked) """) G2.I_l = linked_var(G.I) mon1 = StateMonitor(G, "I", record=True) mon = StateMonitor(G2, "I_l", record=True) run(5 * ms) assert all(mon[0].I_l == mon1[0].I) assert all(mon[1].I_l == mon1[1].I) @pytest.mark.standalone_compatible def test_linked_subexpression_3(): """ Test a linked variable referring to a subexpression with indices """ G = NeuronGroup( 2, """ dv/dt = 100*Hz : 1 I = clip(v, 0, inf) : 1 """, threshold="v>1", reset="v=0", ) G.v = [0, 0.5] G2 = NeuronGroup(10, """I_l : 1 (linked) """) G2.I_l = linked_var(G.I, index=np.array([0, 1]).repeat(5)) mon1 = StateMonitor(G, "I", record=True) mon = StateMonitor(G2, "I_l", record=True) run(5 * ms) # Due to the linking, the first 5 and the second 5 recorded I vectors should # refer to the assert all(all(mon[i].I_l == mon1[0].I) for i in range(5)) assert all(all(mon[i + 5].I_l == mon1[1].I) for i in range(5)) def test_linked_subexpression_synapse(): """ Test a complicated setup (not unlikely when using brian hears) """ G = NeuronGroup(2, "dv/dt = 100*Hz : 1", threshold="v>1", reset="v=0") G.v = [0, 0.5] G2 = NeuronGroup( 10, """ I = clip(x, 0, inf) : 1 x : 1 (linked) """, ) # This will not be able to include references to `I` as `I_pre` etc., since # the indirect indexing would have to change depending on the synapses G2.x = linked_var(G.v, index=np.array([0, 1]).repeat(5)) S = Synapses(G2, G2, "") S.connect("i==j") assert "I" not in S.variables assert "I_pre" not in S.variables assert "I_post" not in S.variables assert "x" not in S.variables assert "x_pre" not in S.variables assert "x_post" not in S.variables @pytest.mark.codegen_independent def test_linked_variable_indexed_incorrect(): """ Test errors when providing incorrect index arrays """ G = NeuronGroup( 10, """ x : 1 y : 1 (linked) """, ) G.x = np.arange(10) * 0.1 with pytest.raises(TypeError): setattr(G, "y", linked_var(G.x, index=np.arange(10) * 1.0)) with pytest.raises(TypeError): setattr(G, "y", linked_var(G.x, index=np.arange(10).reshape(5, 2))) with pytest.raises(TypeError): setattr(G, "y", linked_var(G.x, index=np.arange(5))) with pytest.raises(ValueError): setattr(G, "y", linked_var(G.x, index=np.arange(10) - 1)) with pytest.raises(ValueError): setattr(G, "y", linked_var(G.x, index=np.arange(10) + 1)) @pytest.mark.codegen_independent def test_linked_synapses(): """ Test linking to a synaptic variable (should raise an error). """ G = NeuronGroup(10, "") S = Synapses(G, G, "w:1") S.connect() G2 = NeuronGroup(100, "x : 1 (linked)") with pytest.raises(NotImplementedError): setattr(G2, "x", linked_var(S, "w")) @pytest.mark.standalone_compatible def test_linked_var_in_reset(): G1 = NeuronGroup(3, "x:1") G2 = NeuronGroup( 3, """ x_linked : 1 (linked) y:1 """, threshold="y>1", reset="y=0; x_linked += 1", ) G2.x_linked = linked_var(G1, "x") G2.y = [0, 1.1, 0] # In this context, x_linked should not be considered as a scalar variable # and therefore the reset statement should be allowed run(3 * defaultclock.dt) assert_allclose(G1.x[:], [0, 1, 0]) @pytest.mark.standalone_compatible def test_linked_var_in_reset_size_1(): G1 = NeuronGroup(1, "x:1") G2 = NeuronGroup( 1, """ x_linked : 1 (linked) y:1 """, threshold="y>1", reset="y=0; x_linked += 1", ) G2.x_linked = linked_var(G1, "x") G2.y = 1.1 # In this context, x_linked should not be considered as a scalar variable # and therefore the reset statement should be allowed run(3 * defaultclock.dt) assert_allclose(G1.x[:], 1) @pytest.mark.codegen_independent def test_linked_var_in_reset_incorrect(): # Raise an error if a scalar variable (linked variable from a group of size # 1 is set in a reset statement of a group with size > 1) G1 = NeuronGroup(1, "x:1") G2 = NeuronGroup( 2, """ x_linked : 1 (linked) y:1 """, threshold="y>1", reset="y=0; x_linked += 1", ) G2.x_linked = linked_var(G1, "x") G2.y = 1.1 net = Network(G1, G2) # It is not well-defined what x_linked +=1 means in this context # (as for any other shared variable) with pytest.raises(BrianObjectException) as exc: net.run(0 * ms) assert exc_isinstance(exc, SyntaxError) @pytest.mark.codegen_independent def test_incomplete_namespace(): """ Test that the namespace does not have to be complete at creation time. """ # This uses tau which is not defined yet (explicit namespace) G = NeuronGroup(1, "dv/dt = -v/tau : 1", namespace={}) G.namespace["tau"] = 10 * ms net = Network(G) net.run(0 * ms) # This uses tau which is not defined yet (implicit namespace) G = NeuronGroup(1, "dv/dt = -v/tau : 1") tau = 10 * ms net = Network(G) net.run(0 * ms) @pytest.mark.codegen_independent def test_namespace_errors(): # model equations use unknown identifier G = NeuronGroup(1, "dv/dt = -v/tau : 1") net = Network(G) with pytest.raises(BrianObjectException) as exc: net.run(1 * ms) assert exc_isinstance(exc, KeyError) # reset uses unknown identifier G = NeuronGroup(1, "dv/dt = -v/tau : 1", threshold="False", reset="v = v_r") net = Network(G) with pytest.raises(BrianObjectException) as exc: net.run(1 * ms) assert exc_isinstance(exc, KeyError) # threshold uses unknown identifier G = NeuronGroup(1, "dv/dt = -v/tau : 1", threshold="v > v_th") net = Network(G) with pytest.raises(BrianObjectException) as exc: net.run(1 * ms) assert exc_isinstance(exc, KeyError) @pytest.mark.codegen_independent def test_namespace_warnings(): G = NeuronGroup( 1, """ x : 1 y : 1 """, # unique names to get warnings every time: name=f"neurongroup_{str(uuid.uuid4()).replace('-', '_')}", ) # conflicting variable in namespace y = 5 with catch_logs() as l: G.x = "y" assert len(l) == 1, f"got {str(l)} as warnings" assert l[0][1].endswith(".resolution_conflict") del y # conflicting variables with special meaning i = 5 N = 3 with catch_logs() as l: G.x = "i // N" assert len(l) == 2, f"got {str(l)} as warnings" assert l[0][1].endswith(".resolution_conflict") assert l[1][1].endswith(".resolution_conflict") del i del N # conflicting variables in equations y = 5 * Hz G = NeuronGroup( 1, """ y : Hz dx/dt = y : 1 """, # unique names to get warnings every time: name=f"neurongroup_{str(uuid.uuid4()).replace('-', '_')}", ) net = Network(G) with catch_logs() as l: net.run(0 * ms) assert len(l) == 1, f"got {str(l)} as warnings" assert l[0][1].endswith(".resolution_conflict") del y i = 5 # i is referring to the neuron number: G = NeuronGroup( 1, "dx/dt = i*Hz : 1", # unique names to get warnings every time: name=f"neurongroup_{str(uuid.uuid4()).replace('-', '_')}", ) net = Network(G) with catch_logs() as l: net.run(0 * ms) assert len(l) == 1, f"got {str(l)} as warnings" assert l[0][1].endswith(".resolution_conflict") del i # Variables that are used internally but not in equations should not raise # a warning N = 3 i = 5 dt = 1 * ms G = NeuronGroup( 1, "dx/dt = x/(10*ms) : 1", # unique names to get warnings every time: name=f"neurongroup_{str(uuid.uuid4()).replace('-', '_')}", ) net = Network(G) with catch_logs() as l: net.run(0 * ms) assert len(l) == 0, f"got {str(l)} as warnings" @pytest.mark.standalone_compatible def test_threshold_reset(): """ Test that threshold and reset work in the expected way. """ # Membrane potential does not change by itself G = NeuronGroup(3, "dv/dt = 0 / second : 1", threshold="v > 1", reset="v=0.5") G.v = np.array([0, 1, 2]) run(defaultclock.dt) assert_allclose(G.v[:], np.array([0, 1, 0.5])) with catch_logs() as logs: G = NeuronGroup(1, "v : 1", threshold="True") assert len(logs) == 1 assert logs[0][0] == "WARNING" and logs[0][1].endswith("only_threshold") with catch_logs() as logs: G = NeuronGroup(1, "v : 1", threshold="True", reset="") assert len(logs) == 0 with catch_logs() as logs: G = NeuronGroup(1, "v : 1", threshold="True", refractory=1 * ms) assert len(logs) == 0 @pytest.mark.codegen_independent def test_unit_errors_threshold_reset(): """ Test that unit errors in thresholds and resets are detected. """ # Unit error in threshold group = NeuronGroup(1, "dv/dt = -v/(10*ms) : 1", threshold="v > -20*mV") with pytest.raises(BrianObjectException) as exc: Network(group).run(0 * ms) assert exc_isinstance(exc, DimensionMismatchError) # Unit error in reset group = NeuronGroup( 1, "dv/dt = -v/(10*ms) : 1", threshold="True", reset="v = -65*mV" ) with pytest.raises(BrianObjectException) as exc: Network(group).run(0 * ms) assert exc_isinstance(exc, DimensionMismatchError) # More complicated unit reset with an intermediate variable # This should pass group = NeuronGroup( 1, "dv/dt = -v/(10*ms) : 1", threshold="False", reset="""temp_var = -65 v = temp_var""", ) run(0 * ms) # throw in an empty line (should still pass) group = NeuronGroup( 1, "dv/dt = -v/(10*ms) : 1", threshold="False", reset="""temp_var = -65 v = temp_var""", ) run(0 * ms) # This should fail group = NeuronGroup( 1, "dv/dt = -v/(10*ms) : 1", threshold="False", reset="""temp_var = -65*mV v = temp_var""", ) with pytest.raises(BrianObjectException) as exc: Network(group).run(0 * ms) assert exc_isinstance(exc, DimensionMismatchError) # Resets with an in-place modification # This should work group = NeuronGroup( 1, "dv/dt = -v/(10*ms) : 1", threshold="False", reset="""v /= 2""" ) run(0 * ms) # This should fail group = NeuronGroup( 1, "dv/dt = -v/(10*ms) : 1", threshold="False", reset="""v -= 60*mV""" ) with pytest.raises(BrianObjectException) as ecx: Network(group).run(0 * ms) assert exc_isinstance(exc, DimensionMismatchError) @pytest.mark.codegen_independent def test_syntax_errors(): """ Test that syntax errors are already caught at initialization time. For equations this is already tested in test_equations """ # We do not specify the exact type of exception here: Python throws a # SyntaxError while C++ results in a ValueError # Syntax error in threshold group = NeuronGroup(1, "dv/dt = 5*Hz : 1", threshold=">1") with pytest.raises(Exception): Network(group).run(0 * ms) # Syntax error in reset group = NeuronGroup(1, "dv/dt = 5*Hz : 1", threshold="True", reset="0") with pytest.raises(Exception): Network(group).run(0 * ms) @pytest.mark.codegen_independent def test_custom_events(): G = NeuronGroup( 2, """ event_time1 : second event_time2 : second """, events={ "event1": "t>=i*ms and t=(i+1)*ms and t<(i+1)*ms+dt", }, ) G.run_on_event("event1", "event_time1 = t") G.run_on_event("event2", "event_time2 = t") net = Network(G) net.run(2.1 * ms) assert_allclose(G.event_time1[:], [0, 1] * ms) assert_allclose(G.event_time2[:], [1, 2] * ms) def test_custom_events_schedule(): # In the same time step: event2 will be checked and its code executed # before event1 is checked and its code executed G = NeuronGroup( 2, """ x : 1 event_time : second """, events={"event1": "x>0", "event2": "t>=(i+1)*ms and t<(i+1)*ms+dt"}, ) G.set_event_schedule("event1", when="after_resets") G.run_on_event("event2", "x = 1", when="resets") G.run_on_event( "event1", """ event_time = t x = 0 """, when="after_resets", order=1, ) net = Network(G) net.run(2.1 * ms) assert_allclose(G.event_time[:], [1, 2] * ms) @pytest.mark.codegen_independent def test_incorrect_custom_event_definition(): # Incorrect event name with pytest.raises(TypeError): NeuronGroup(1, "", events={"1event": "True"}) # duplicate definition of 'spike' event with pytest.raises(ValueError): NeuronGroup(1, "", threshold="True", events={"spike": "False"}) # not a threshold G = NeuronGroup(1, "", events={"my_event": 10 * mV}) with pytest.raises(BrianObjectException) as exc: Network(G).run(0 * ms) assert exc_isinstance(exc, TypeError) # schedule for a non-existing event G = NeuronGroup(1, "", threshold="False", events={"my_event": "True"}) with pytest.raises(ValueError): G.set_event_schedule("another_event") # code for a non-existing event with pytest.raises(ValueError): G.run_on_event("another_event", "") def test_state_variables(): """ Test the setting and accessing of state variables. """ G = NeuronGroup(10, "v : volt") # The variable N should be always present assert G.N == 10 # But it should be read-only with pytest.raises(TypeError): G.__setattr__("N", 20) with pytest.raises(TypeError): G.__setattr__("N_", 20) G.v = -70 * mV with pytest.raises(DimensionMismatchError): G.__setattr__("v", -70) G.v_ = float(-70 * mV) assert_allclose(G.v[:], -70 * mV) G.v = -70 * mV + np.arange(10) * mV assert_allclose(G.v[:], -70 * mV + np.arange(10) * mV) G.v = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] * volt assert_allclose(G.v[:], np.arange(10) * volt) # incorrect size with pytest.raises(ValueError): G.__setattr__("v", [0, 1] * volt) with pytest.raises(ValueError): G.__setattr__("v", np.arange(11) * volt) G.v = -70 * mV # Numpy methods should be able to deal with state variables # (discarding units) assert_allclose(np.mean(G.v), float(-70 * mV)) # Getting the content should return a Quantity object which then natively # supports numpy functions that access a method assert_allclose(np.mean(G.v[:]), -70 * mV) # You should also be able to set variables with a string G.v = "-70*mV + i*mV" assert_allclose(G.v[0], -70 * mV) assert_allclose(G.v[9], -61 * mV) assert_allclose(G.v[:], -70 * mV + np.arange(10) * mV) # And it should raise an unit error if the units are incorrect with pytest.raises(DimensionMismatchError): G.__setattr__("v", "70 + i") with pytest.raises(DimensionMismatchError): G.__setattr__("v", "70 + i*mV") # Calculating with state variables should work too # With units assert all(G.v - G.v == 0) assert all(G.v - G.v[:] == 0 * mV) assert all(G.v[:] - G.v == 0 * mV) assert all(G.v + 70 * mV == G.v[:] + 70 * mV) assert all(70 * mV + G.v == G.v[:] + 70 * mV) assert all(G.v + G.v == 2 * G.v) assert all(G.v / 2.0 == 0.5 * G.v) assert all(1.0 / G.v == 1.0 / G.v[:]) assert_allclose((-G.v)[:], -G.v[:]) assert_allclose((+G.v)[:], G.v[:]) # Without units assert all(G.v_ - G.v_ == 0) assert all(G.v_ - G.v_[:] == 0) assert all(G.v_[:] - G.v_ == 0) assert all(G.v_ + float(70 * mV) == G.v_[:] + float(70 * mV)) assert all(float(70 * mV) + G.v_ == G.v_[:] + float(70 * mV)) assert all(G.v_ + G.v_ == 2 * G.v_) assert all(G.v_ / 2.0 == 0.5 * G.v_) assert all(1.0 / G.v_ == 1.0 / G.v_[:]) assert_allclose((-G.v)[:], -G.v[:]) assert_allclose((+G.v)[:], G.v[:]) # And in-place modification should work as well G.v += 10 * mV G.v -= 10 * mV G.v *= 2 G.v /= 2.0 # with unit checking with pytest.raises(DimensionMismatchError): G.v.__iadd__(3 * second) with pytest.raises(DimensionMismatchError): G.v.__iadd__(3) with pytest.raises(DimensionMismatchError): G.v.__imul__(3 * second) # in-place modification with strings should not work with pytest.raises(TypeError): G.v.__iadd__("string") with pytest.raises(TypeError): G.v.__imul__("string") with pytest.raises(TypeError): G.v.__idiv__("string") with pytest.raises(TypeError): G.v.__isub__("string") @pytest.mark.codegen_independent def test_state_variable_access(): G = NeuronGroup(10, "v:volt") G.v = np.arange(10) * volt assert_allclose(np.asarray(G.v[:]), np.arange(10)) assert have_same_dimensions(G.v[:], volt) assert_allclose(np.asarray(G.v[:]), G.v_[:]) # Accessing single elements, slices and arrays assert G.v[5] == 5 * volt assert G.v_[5] == 5 assert_allclose(G.v[:5], np.arange(5) * volt) assert_allclose(G.v_[:5], np.arange(5)) assert_allclose(G.v[[0, 5]], [0, 5] * volt) assert_allclose(G.v_[[0, 5]], np.array([0, 5])) # Illegal indexing with pytest.raises(IndexError): G.v[0, 0] with pytest.raises(IndexError): G.v_[0, 0] with pytest.raises(TypeError): G.v[object()] with pytest.raises(TypeError): G.v_[object()] # A string representation should not raise any error assert len(str(G.v)) assert len(repr(G.v)) assert len(str(G.v_)) assert len(repr(G.v_)) def test_state_variable_access_strings(): G = NeuronGroup( 10, """ v : volt dv_ref/dt = -v_ref/(10*ms) : 1 (unless refractory) """, threshold="v_ref>1", reset="v_ref=1", refractory=1 * ms, ) G.v = np.arange(10) * volt # Indexing with strings assert G.v["i==2"] == G.v[2] assert G.v_["i==2"] == G.v_[2] assert_allclose(G.v["v >= 3*volt"], G.v[3:]) assert_allclose(G.v_["v >= 3*volt"], G.v_[3:]) # Should also check for units with pytest.raises(DimensionMismatchError): G.v["v >= 3"] with pytest.raises(DimensionMismatchError): G.v["v >= 3*second"] @pytest.mark.standalone_compatible def test_state_variable_set_strings(): # Instead of overwriting the same variable over and over, we have one # variable for each assignment so that we can test everything in the end # for standalone. G = NeuronGroup( 10, """ v1 : volt v2 : volt v3 : volt v4 : volt v5 : volt v6 : volt v7 : volt v7b : volt v7c : volt v8 : volt v9 : volt v9b : volt v9c : volt v10 : volt v11 : volt dv_ref/dt = -v_ref/(10*ms) : 1 (unless refractory) """, threshold="v_ref>1", reset="v_ref=1", refractory=1 * ms, ) # Setting with strings # -------------------- # String value referring to i G.v1 = "2*i*volt" # String value referring to i G.v1[:5] = "3*i*volt" # Conditional write variable G.v_ref = "2*i" G.v2 = np.arange(10) * volt # String value referring to a state variable G.v2 = "2*v2" G.v2[:5] = "2*v2" G.v3 = np.arange(10) * volt # String value referring to state variables, i, and an external variable ext = 5 * volt G.v3 = "v3 + ext + (N + i)*volt" G.v4 = np.arange(10) * volt G.v4[:5] = "v4 + ext + (N + i)*volt" G.v5 = "v5 + randn()*volt" # only check that it doesn't raise an error G.v5[:5] = "v5 + randn()*volt" # only check that it doesn't raise an error G.v6 = np.arange(10) * volt # String index using a random number G.v6["rand() <= 1"] = 0 * mV G.v7 = np.arange(10) * volt # String index referring to i and setting to a scalar value G.v7["i>=5"] = 0 * mV G.v7b = np.arange(10) * volt # String index referring to i and setting to a scalar value (no effect) G.v7b["i>=10"] = 0 * mV G.v7c = np.arange(10) * volt # String index referring to i and setting to a scalar value (no effect) G.v7c["False"] = 0 * mV G.v8[:5] = np.arange(5) * volt # String index referring to a state variable G.v8["v8<3*volt"] = 0 * mV # String index referring to state variables, i, and an external variable ext = 2 * volt G.v8["v8>=ext and i==(N-6)"] = 0 * mV G.v9 = np.arange(10) * volt # Strings for both condition and values G.v9["i>=5"] = "v9*2" G.v9["v9<5*volt"] = "3*i*volt" G.v9b = np.arange(10) * volt # Strings for both condition and values (no effect) G.v9b["i<0"] = "v9 + 100*volt" G.v9c = np.arange(10) * volt # Strings for both condition and values (no effect) G.v9c["False"] = "v9 + 100*volt" G.v10 = np.arange(10) * volt G.v10["i<=5"] = "(100 + rand())*volt" # string assignment to scalars G.v11[0] = "1*volt" G.v11[1] = "(1 + i)*volt" G.v11[2] = "v11 + 3*volt" G.v11[3] = "inf*volt" G.v11[4] = "rand()*volt" run(0 * ms) assert_allclose(G.v1[:], [0, 3, 6, 9, 12, 10, 12, 14, 16, 18] * volt) assert_allclose(G.v_ref[:], 2 * np.arange(10)) assert_allclose(G.v2[:], [0, 4, 8, 12, 16, 10, 12, 14, 16, 18] * volt) assert_allclose(G.v3[:], 2 * np.arange(10) * volt + 15 * volt) assert_allclose(G.v4[:], [15, 17, 19, 21, 23, 5, 6, 7, 8, 9] * volt) assert_allclose(G.v6[:], np.zeros(10) * volt) assert_allclose(G.v7[:], [0, 1, 2, 3, 4, 0, 0, 0, 0, 0] * volt) assert_allclose(G.v7b[:], np.arange(10) * volt) assert_allclose(G.v7c[:], np.arange(10) * volt) assert_allclose(G.v8[:], [0, 0, 0, 3, 0, 0, 0, 0, 0, 0] * volt) assert_allclose(G.v9[:], [0, 3, 6, 9, 12, 10, 12, 14, 16, 18] * volt) assert_allclose(G.v9b[:], np.arange(10) * volt) assert_allclose(G.v9c[:], np.arange(10) * volt) assert_allclose(G.v10[6:], np.arange(4) * volt + 6 * volt) # unchanged assert all(G.v10[:6] >= 100 * volt) assert all(G.v10[:6] <= 101 * volt) assert np.var(G.v10_[:6]) > 0 assert_allclose(G.v11[:3], [1, 2, 3] * volt) assert np.isinf(G.v11_[3]) @pytest.mark.codegen_independent def test_unknown_state_variables(): # Test how setting attribute names that do not correspond to a state # variable are handled G = NeuronGroup(10, "v : 1") with pytest.raises(AttributeError): setattr(G, "unknown", 42) # Creating a new private attribute should be fine G._unknown = 42 assert G._unknown == 42 # Explicitly create the attribute G.add_attribute("unknown") G.unknown = 42 assert G.unknown == 42 @pytest.mark.codegen_independent def test_subexpression(): G = NeuronGroup( 10, """ dv/dt = freq : 1 freq : Hz array : 1 expr = 2*freq + array*Hz : Hz""", ) G.freq = "10*i*Hz" G.array = 5 assert_allclose(G.expr[:], 2 * 10 * np.arange(10) * Hz + 5 * Hz) @pytest.mark.codegen_independent def test_subexpression_with_constant(): g = 2 G = NeuronGroup( 1, """ x : 1 I = x*g : 1 """, ) G.x = 1 assert_allclose(G.I[:], np.array([2])) # Subexpressions that refer to external variables are tricky, see github # issue #313 for details # Comparisons assert G.I == 2 assert G.I >= 1 assert G.I > 1 assert G.I < 3 assert G.I <= 3 assert G.I != 3 # arithmetic operations assert G.I + 1 == 3 assert 1 + G.I == 3 assert G.I * 1 == 2 assert 1 * G.I == 2 assert G.I - 1 == 1 assert 3 - G.I == 1 assert G.I / 1 == 2 assert G.I // 1 == 2.0 assert 1.0 / G.I == 0.5 assert 1 // G.I == 0 assert +G.I == 2 assert -G.I == -2 # other operations assert len(G.I) == 1 # These will not work with pytest.raises(KeyError): np.array(G.I) with pytest.raises(KeyError): np.mean(G.I) # But these should assert_allclose(np.array(G.I[:]), G.I[:]) assert np.mean(G.I[:]) == 2 # This will work but display a text, advising to use G.I[:] instead of # G.I assert len(str(G.I)) assert len(repr(G.I)) @pytest.mark.codegen_independent def test_scalar_parameter_access(): G = NeuronGroup( 10, """ dv/dt = freq : 1 freq : Hz (shared) number : 1 (shared) array : 1 """, ) # Try setting a scalar variable G.freq = 100 * Hz assert_allclose(G.freq[:], 100 * Hz) G.freq[:] = 200 * Hz assert_allclose(G.freq[:], 200 * Hz) G.freq = "freq - 50*Hz + number*Hz" assert_allclose(G.freq[:], 150 * Hz) G.freq[:] = "50*Hz" assert_allclose(G.freq[:], 50 * Hz) # Check the second method of accessing that works assert_allclose(np.asanyarray(G.freq), 50 * Hz) # Check error messages with pytest.raises(IndexError): G.freq[0] with pytest.raises(IndexError): G.freq[1] with pytest.raises(IndexError): G.freq[0:1] with pytest.raises(IndexError): G.freq["i>5"] with pytest.raises(ValueError): G.freq.set_item(slice(None), [0, 1] * Hz) with pytest.raises(IndexError): G.freq.set_item(0, 100 * Hz) with pytest.raises(IndexError): G.freq.set_item(1, 100 * Hz) with pytest.raises(IndexError): G.freq.set_item("i>5", 100 * Hz) @pytest.mark.codegen_independent def test_scalar_subexpression(): G = NeuronGroup( 10, """ dv/dt = freq : 1 freq : Hz (shared) number : 1 (shared) array : 1 sub = freq + number*Hz : Hz (shared) """, ) G.freq = 100 * Hz G.number = 50 assert G.sub[:] == 150 * Hz with pytest.raises(SyntaxError): NeuronGroup( 10, """ dv/dt = freq : 1 freq : Hz (shared) array : 1 sub = freq + array*Hz : Hz (shared) """, ) # A scalar subexpresion cannot refer to implicitly vectorized functions group = NeuronGroup( 10, """x : 1 sub = rand() : 1 (shared)""", ) group.run_regularly("x = sub") net = Network(group) with pytest.raises(BrianObjectException) as exc: net.run(0 * ms) assert exc_isinstance(exc, SyntaxError) @pytest.mark.standalone_compatible def test_sim_with_scalar_variable(): G = NeuronGroup( 10, """ tau : second (shared) dv/dt = -v/tau : 1 """, method="exact", ) G.tau = 10 * ms G.v = "1.0*i/N" run(1 * ms) assert_allclose(G.v[:], np.exp(-0.1) * np.linspace(0, 1, 10, endpoint=False)) @pytest.mark.standalone_compatible def test_sim_with_scalar_subexpression(): G = NeuronGroup( 10, """ tau = 10*ms : second (shared) dv/dt = -v/tau : 1 """, method="exact", ) G.v = "1.0*i/N" run(1 * ms) assert_allclose(G.v[:], np.exp(-0.1) * np.linspace(0, 1, 10, endpoint=False)) @pytest.mark.standalone_compatible def test_constant_variable_subexpression(): G = NeuronGroup( 10, """ dv1/dt = -v1**2 / (10*ms) : 1 dv2/dt = -v_const**2 / (10*ms) : 1 dv3/dt = -v_var**2 / (10*ms) : 1 dv4/dt = -v_noflag**2 / (10*ms) : 1 v_const = v2 : 1 (constant over dt) v_var = v3 : 1 v_noflag = v4 : 1 """, method="rk2", ) G.v1 = "1.0*i/N" G.v2 = "1.0*i/N" G.v3 = "1.0*i/N" G.v4 = "1.0*i/N" run(10 * ms) # "variable over dt" subexpressions are directly inserted into the equation assert_allclose(G.v3[:], G.v1[:]) assert_allclose(G.v4[:], G.v1[:]) # "constant over dt" subexpressions will keep a fixed value over the time # step and therefore give a slightly different result for multi-step # methods assert np.sum((G.v2 - G.v1) ** 2) > 1e-10 @pytest.mark.codegen_independent def test_constant_subexpression_order(): G = NeuronGroup( 10, """ dv/dt = -v / (10*ms) : 1 s1 = v : 1 (constant over dt) s2 = 2*s3 : 1 (constant over dt) s3 = 1 + s1 : 1 (constant over dt) """, ) run(0 * ms) code_lines = G.subexpression_updater.abstract_code.split("\n") assert code_lines[0].startswith("s1") assert code_lines[1].startswith("s3") assert code_lines[2].startswith("s2") @pytest.mark.codegen_independent def test_subexpression_checks(): group = NeuronGroup( 1, """ dv/dt = -v / (10*ms) : volt y = rand() : 1 (constant over dt) z = 17*v**2 : volt**2 """, ) # This should all be fine net = Network(group) net.run(0 * ms) # The following should raise an error group = NeuronGroup( 1, """ dv/dt = -v / (10*ms) : volt y = rand() : 1 z = 17*v**2 : volt**2 """, ) net = Network(group) with pytest.raises(BrianObjectException) as exc: net.run(0 * ms) assert exc_isinstance(exc, SyntaxError) @pytest.mark.codegen_independent def test_repr(): G = NeuronGroup( 10, """ dv/dt = -(v + Inp) / tau : volt Inp = sin(2*pi*freq*t) : volt freq : Hz """, ) # Test that string/LaTeX representations do not raise errors for func in [str, repr, sympy.latex]: assert len(func(G)) assert "textbackslash" not in func(G) # for LaTeX, see #1296 assert len(func(G.equations)) assert "textbackslash" not in func(G.equations) for eq in G.equations.values(): assert len(func(eq)) @pytest.mark.codegen_independent def test_ipython_html(): G = NeuronGroup( 10, """ dv/dt = -(v + Inp) / tau : volt Inp = sin(2*pi*freq*t) : volt freq : Hz """, ) # Test that HTML representation in IPython does not raise errors assert len(G._repr_html_()) @pytest.mark.codegen_independent def test_indices(): G = NeuronGroup(10, "v : 1") G.v = "i" ext_var = 5 assert_allclose(G.indices[:], G.i[:]) assert_allclose(G.indices[5:], G.indices["i >= 5"]) assert_allclose(G.indices[5:], G.indices["i >= ext_var"]) assert_allclose(G.indices["v >= 5"], np.nonzero(G.v >= 5)[0]) # We should not accept "None" as an index, because in numpy this stands for # "new axis". In fact, x[0, None] is used in matplotlib to check whether # something behaves as a numpy array -- if NeuronGroup accepts None as an # index, then synaptic variables will allow indexing in such a way. This # makes plotting in matplotlib 1.5.1 fail with a non-obivous error # See https://groups.google.com/d/msg/briansupport/yRA4PHKAvN8/cClOEUlOAQAJ with pytest.raises(TypeError): G.indices.__getitem__(None) @pytest.mark.codegen_independent def test_get_dtype(): """ Check the utility function get_dtype """ eqs = Equations( """ dv/dt = -v / (10*ms) : volt x : 1 b : boolean n : integer """ ) # Test standard dtypes assert get_dtype(eqs["v"]) == prefs["core.default_float_dtype"] assert get_dtype(eqs["x"]) == prefs["core.default_float_dtype"] assert get_dtype(eqs["n"]) == prefs["core.default_integer_dtype"] assert get_dtype(eqs["b"]) == bool # Test a changed default (float) dtype assert get_dtype(eqs["v"], np.float32) == np.float32, get_dtype( eqs["v"], np.float32 ) assert get_dtype(eqs["x"], np.float32) == np.float32 # integer and boolean variables should be unaffected assert get_dtype(eqs["n"]) == prefs["core.default_integer_dtype"] assert get_dtype(eqs["b"]) == bool # Explicitly provide a dtype for some variables dtypes = {"v": np.float32, "x": np.float64, "n": np.int64} for varname in dtypes: assert get_dtype(eqs[varname], dtypes) == dtypes[varname] # Not setting some dtypes should use the standard dtypes dtypes = {"n": np.int64} assert get_dtype(eqs["n"], dtypes) == np.int64 assert get_dtype(eqs["v"], dtypes) == prefs["core.default_float_dtype"] # Test that incorrect types raise an error # incorrect general dtype with pytest.raises(TypeError): get_dtype(eqs["v"], np.int32) # incorrect specific types with pytest.raises(TypeError): get_dtype(eqs["v"], {"v": np.int32}) with pytest.raises(TypeError): get_dtype(eqs["n"], {"n": np.float32}) with pytest.raises(TypeError): get_dtype(eqs["b"], {"b": np.int32}) def test_aliasing_in_statements(): """ Test an issue around variables aliasing other variables (#259) """ if prefs.codegen.target != "numpy": pytest.skip("numpy-only test") runner_code = """x_1 = x_0 x_0 = -1""" g = NeuronGroup( 1, model=""" x_0 : 1 x_1 : 1 """, ) g.run_regularly(runner_code) net = Network(g) net.run(defaultclock.dt) assert_allclose(g.x_0_[:], np.array([-1])) assert_allclose(g.x_1_[:], np.array([0])) @pytest.mark.codegen_independent def test_get_states(): G = NeuronGroup( 10, """ v : volt x : 1 subexpr = x + v/volt : 1 subexpr2 = x*volt + v : volt """, ) G.v = "i*volt" G.x = "10*i" states_units = G.get_states(["v", "x", "subexpr", "subexpr2"], units=True) states = G.get_states(["v", "x", "subexpr", "subexpr2"], units=False) assert len(states_units) == len(states) == 4 assert_allclose(states_units["v"], np.arange(10) * volt) assert_allclose(states_units["x"], 10 * np.arange(10)) assert_allclose(states_units["subexpr"], 11 * np.arange(10)) assert_allclose(states_units["subexpr2"], 11 * np.arange(10) * volt) assert_allclose(states["v"], np.arange(10)) assert_allclose(states["x"], 10 * np.arange(10)) assert_allclose(states["subexpr"], 11 * np.arange(10)) assert_allclose(states["subexpr2"], 11 * np.arange(10)) all_states = G.get_states(units=True) assert set(all_states.keys()) == {"v", "x", "N", "t", "dt", "i", "t_in_timesteps"} all_states = G.get_states(units=True, subexpressions=True) assert set(all_states.keys()) == { "v", "x", "N", "t", "dt", "i", "t_in_timesteps", "subexpr", "subexpr2", } @pytest.mark.codegen_independent def test_set_states(): G = NeuronGroup( 10, """ v : volt x : 1 subexpr = x + v/volt : 1 subexpr2 = x*volt + v : volt """, ) G.v = "i*volt" G.x = "10*i" with pytest.raises(ValueError): G.set_states({"v": np.arange(2, 11) * volt}, units=True) # we test if function prevents from setting read_only variables with pytest.raises(TypeError): G.set_states({"N": 1}) with pytest.raises(DimensionMismatchError): G.set_states({"x": np.arange(2, 12) * volt}, units=True) with pytest.raises(DimensionMismatchError): G.set_states({"v": np.arange(2, 12)}, units=True) G.set_states({"v": np.arange(2, 12)}, units=False) assert_allclose(G.v, np.arange(2, 12) * volt) G.set_states({"v": np.arange(2, 12) * volt}, units=True) assert_allclose(G.v, np.arange(2, 12) * volt) G.set_states({"x": np.arange(2, 12)}, units=False) assert_allclose(G.x, np.arange(2, 12)) G.set_states({"x": np.arange(2, 12)}, units=True) assert_allclose(G.x, np.arange(2, 12)) @pytest.mark.codegen_independent def test_get_states_pandas(): try: import pandas as pd except ImportError: pytest.skip("Cannot test export to Pandas data frame, Pandas is not installed.") G = NeuronGroup( 10, """ v : volt x : 1 subexpr = x + v/volt : 1 subexpr2 = x*volt + v : volt """, ) G.v = "i*volt" G.x = "10*i" with pytest.raises(NotImplementedError): G.get_states(["v", "x", "subexpr", "subexpr2"], units=True, format="pandas") states = G.get_states( ["v", "x", "subexpr", "subexpr2"], units=False, format="pandas" ) assert_allclose(states["v"].values, np.arange(10)) assert_allclose(states["x"].values, 10 * np.arange(10)) assert_allclose(states["subexpr"].values, 11 * np.arange(10)) assert_allclose(states["subexpr2"].values, 11 * np.arange(10)) all_states = G.get_states(units=False, format="pandas") assert set(all_states.columns) == {"v", "x", "N", "t", "dt", "i", "t_in_timesteps"} all_states = G.get_states(units=False, subexpressions=True, format="pandas") assert set(all_states.columns) == { "v", "x", "N", "t", "dt", "i", "t_in_timesteps", "subexpr", "subexpr2", } @pytest.mark.codegen_independent def test_set_states_pandas(): try: import pandas as pd except ImportError: pytest.skip("Cannot test export to Pandas data frame, Pandas is not installed.") G = NeuronGroup( 10, """ v : volt x : 1 subexpr = x + v/volt : 1 subexpr2 = x*volt + v : volt """, ) G.v = "i*volt" G.x = "10*i" df = pd.DataFrame(np.arange(2, 11), columns=["v"]) with pytest.raises(NotImplementedError): G.set_states(df, units=True, format="pandas") with pytest.raises(ValueError): G.set_states(df, units=False, format="pandas") # we test if function prevents from setting read_only variables df = pd.DataFrame(np.array([1]), columns=["N"]) with pytest.raises(TypeError): G.set_states(df, units=False, format="pandas") df = pd.DataFrame(np.vstack((np.arange(2, 12), np.arange(2, 12))).T) df.columns = ["v", "x"] G.set_states(df, units=False, format="pandas") assert_allclose(G.v, np.arange(2, 12) * volt) assert_allclose(G.x, np.arange(2, 12)) def test_random_vector_values(): # Make sure that the new "loop-invariant optimisation" does not pull out # the random number generation and therefore makes all neurons receiving # the same values tau = 10 * ms G = NeuronGroup(100, "dv/dt = -v / tau + xi*tau**-0.5: 1") G.v[:] = "rand()" assert np.var(G.v[:]) > 0 G.v[:] = 0 net = Network(G) net.run(defaultclock.dt) assert np.var(G.v[:]) > 0 @pytest.mark.standalone_compatible def test_random_values_random_seed(): G = NeuronGroup( 100, """ v1 : 1 v2 : 1 """, ) seed() G.v1 = "rand() + randn()" seed() G.v2 = "rand() + randn()" run(0 * ms) # for standalone assert np.var(G.v1[:]) > 0 assert np.var(G.v2[:]) > 0 assert np.var(G.v1[:] - G.v2[:]) > 0 @pytest.mark.standalone_compatible def test_random_values_fixed_seed(): G = NeuronGroup( 100, """ v1 : 1 v2 : 1 """, ) seed(12345678) G.v1 = "rand() + randn()" seed(12345678) G.v2 = "rand() + randn()" run(0 * ms) # for standalone assert np.var(G.v1[:]) > 0 assert np.var(G.v2[:]) > 0 assert_allclose(G.v1[:], G.v2[:]) @pytest.mark.standalone_compatible @pytest.mark.multiple_runs def test_random_values_fixed_and_random(): G = NeuronGroup(10, "dv/dt = -v/(10*ms) + 0.1*xi/sqrt(ms) : 1", method="euler") mon = StateMonitor(G, "v", record=True) # first run seed(13579) G.v = "rand()" seed() run(2 * defaultclock.dt) # second run seed(13579) G.v = "rand()" seed() run(2 * defaultclock.dt) device.build(direct_call=False, **device.build_options) first_run_values = np.array(mon.v[:, [0, 1]]) second_run_values = np.array(mon.v[:, [2, 3]]) # First time step should be identical (same seed) assert all(abs(first_run_values[:, 0] - second_run_values[:, 0]) < 0.0001) # Increase in second time step should be different (random seed) assert all( abs( (first_run_values[:, 1] - first_run_values[:, 0]) - (second_run_values[:, 1] - second_run_values[:, 0]) ) > 1e-6 ) @pytest.mark.codegen_independent def test_no_code(): # Make sure that we are not unncessarily creating code objects for a state # updater that has nothing to do group_1 = NeuronGroup(10, "v: 1", threshold="False") # The refractory argument will automatically add a statement for each time # step, so we'll need a state updater here group_2 = NeuronGroup(10, "v: 1", threshold="False", refractory=2 * ms) run(0 * ms) assert len(group_1.state_updater.code_objects) == 0 assert group_1.state_updater.codeobj is None assert len(group_2.state_updater.code_objects) == 1 assert group_2.state_updater.codeobj is not None @pytest.mark.standalone_compatible def test_run_regularly_scheduling(): G = NeuronGroup( 1, """ v1 : 1 v2 : 1 v3 : 1 """, ) G.run_regularly("v1 += 1") G.run_regularly("v2 = v1", when="end") G.run_regularly("v3 = v1", when="before_start") run(2 * defaultclock.dt) assert_allclose(G.v1[:], 2) assert_allclose(G.v2[:], 2) assert_allclose(G.v3[:], 1) @pytest.mark.standalone_compatible def test_run_regularly_scheduling_2(): # This form is relevant for Brian2GeNN, where we are not allowed to change # the "when" attribute, but can change the order. G = NeuronGroup( 1, """ v1 : 1 v2 : 1 v3 : 1 """, ) # The order should be: # 0: 'v3 = v1' # 1: monitor 1 (v1) # 2: v1 += 1 # 3: monitor 2 (v1) # 4: monitor 3 (v2) # 5: v2 = v1 # 6: monitor 4 (v2) mon_1 = StateMonitor(G, "v1", record=0, order=1) mon_2 = StateMonitor(G, "v1", record=0, order=3) mon_3 = StateMonitor(G, "v2", record=0, order=4) mon_4 = StateMonitor(G, "v2", record=0, order=6) G.run_regularly("v3 = v1", order=0) G.run_regularly("v1 += 1", order=2) G.run_regularly("v2 = v1", order=5) run(2 * defaultclock.dt) assert_allclose(G.v1[:], 2) assert_allclose(G.v2[:], 2) assert_allclose(G.v3[:], 1) assert_allclose(mon_1.v1[0], [0, 1]) assert_allclose(mon_2.v1[0], [1, 2]) assert_allclose(mon_3.v2[0], [0, 1]) assert_allclose(mon_4.v2[0], [1, 2]) @pytest.mark.standalone_compatible def test_run_regularly_dt(): G = NeuronGroup(1, "v : 1") G.run_regularly("v += 1", dt=2 * defaultclock.dt) M = StateMonitor(G, "v", record=0, when="end") run(10 * defaultclock.dt) assert_allclose(G.v[:], 5) assert_allclose(np.diff(M.v[0]), np.tile([0, 1], 5)[:-1]) @pytest.mark.standalone_compatible def test_run_regularly_shared(): # Check that shared variables are handled correctly in run_regularly # operations. See brian-team/brian2genn#113 model = Equations( """ individual_var: 1 shared_var: 1 (shared) individual_var_i: integer shared_var_i: integer (shared) individual_var_b: boolean shared_var_b: boolean (shared) """ ) G = NeuronGroup(10, model) G.run_regularly( """ shared_var = 1.0 shared_var_i = 2 shared_var_b = True individual_var = 1.0 individual_var_i = 2 individual_var_b = True """, dt=defaultclock.dt, ) run(defaultclock.dt) assert_equal(G.shared_var[:], 1.0) assert_equal(G.individual_var[:], np.ones(10)) assert_equal(G.shared_var_i[:], 2) assert_equal(G.individual_var_i[:], 2 * np.ones(10, dtype=int)) assert_equal(G.shared_var_b[:], True) assert_equal(G.individual_var_b[:], np.ones(10, dtype=bool)) @pytest.mark.standalone_compatible def test_semantics_floor_division(): # See github issues #815 and #661 G = NeuronGroup( 11, """ a : integer b : integer x : 1 y : 1 fvalue : 1 ivalue : integer """, dtype={"a": np.int32, "b": np.int64, "x": float, "y": float}, ) int_values = np.arange(-5, 6) float_values = np.arange(-5.0, 6.0, dtype=np.float64) G.ivalue = int_values G.fvalue = float_values with catch_logs() as l: G.run_regularly( """ a = ivalue//3 b = ivalue//3 x = fvalue//3 y = fvalue//3 """ ) run(defaultclock.dt) assert len(l) == 0 assert_equal(G.a[:], int_values // 3) assert_equal(G.b[:], int_values // 3) assert_allclose(G.x[:], float_values // 3) assert_allclose(G.y[:], float_values // 3) @pytest.mark.standalone_compatible def test_semantics_floating_point_division(): # See github issues #815 and #661 G = NeuronGroup( 11, """ x1 : 1 x2 : 1 y1 : 1 y2 : 1 fvalue : 1 ivalue : integer """, dtype={"a": np.int32, "b": np.int64, "x": float, "y": float}, ) int_values = np.arange(-5, 6) float_values = np.arange(-5.0, 6.0, dtype=np.float64) G.ivalue = int_values G.fvalue = float_values with catch_logs() as l: G.run_regularly( """ x1 = ivalue/3 x2 = fvalue/3 y1 = ivalue/3 y2 = fvalue/3 """ ) run(defaultclock.dt) assert_allclose(G.x1[:], int_values / 3) assert_allclose(G.y1[:], int_values / 3) assert_allclose(G.x2[:], float_values / 3) assert_allclose(G.y2[:], float_values / 3) @pytest.mark.standalone_compatible def test_semantics_mod(): # See github issues #815 and #661 G = NeuronGroup( 11, """ a : integer b : integer x : 1 y : 1 fvalue : 1 ivalue : integer """, dtype={"a": np.int32, "b": np.int64, "x": float, "y": float}, ) int_values = np.arange(-5, 6) float_values = np.arange(-5.0, 6.0, dtype=np.float64) G.ivalue = int_values G.fvalue = float_values with catch_logs() as l: G.run_regularly( """ a = ivalue % 3 b = ivalue % 3 x = fvalue % 3 y = fvalue % 3 """ ) run(defaultclock.dt) assert len(l) == 0 assert_equal(G.a[:], int_values % 3) assert_equal(G.b[:], int_values % 3) assert_allclose(G.x[:], float_values % 3) assert_allclose(G.y[:], float_values % 3) if __name__ == "__main__": test_set_states() test_creation() test_integer_variables_and_mod() test_variables() test_variableview_calculations() test_scalar_variable() test_referred_scalar_variable() test_linked_variable_correct() test_linked_variable_incorrect() test_linked_variable_scalar() test_linked_variable_indexed() test_linked_variable_repeat() test_linked_double_linked1() test_linked_double_linked2() test_linked_double_linked3() test_linked_double_linked4() test_linked_triple_linked() test_linked_subgroup() test_linked_subgroup2() test_linked_subexpression() test_linked_subexpression_2() test_linked_subexpression_3() test_linked_subexpression_synapse() test_linked_variable_indexed_incorrect() test_linked_synapses() test_linked_var_in_reset() test_linked_var_in_reset_size_1() test_linked_var_in_reset_incorrect() test_stochastic_variable() test_stochastic_variable_multiplicative() test_threshold_reset() test_unit_errors_threshold_reset() test_custom_events() test_custom_events_schedule() test_incorrect_custom_event_definition() test_incomplete_namespace() test_namespace_errors() test_namespace_warnings() test_syntax_errors() test_state_variables() test_state_variable_access() test_state_variable_access_strings() test_state_variable_set_strings() test_unknown_state_variables() test_subexpression() test_subexpression_with_constant() test_scalar_parameter_access() test_scalar_subexpression() test_sim_with_scalar_variable() test_sim_with_scalar_subexpression() test_constant_variable_subexpression() test_constant_subexpression_order() test_subexpression_checks() test_indices() test_repr() test_ipython_html() test_get_dtype() if prefs.codegen.target == "numpy": test_aliasing_in_statements() test_get_states() test_set_states() test_get_states_pandas() test_set_states_pandas() test_random_vector_values() test_random_values_random_seed() test_random_values_fixed_seed() test_random_values_fixed_and_random() test_no_code() test_run_regularly_scheduling() test_run_regularly_scheduling_2() test_run_regularly_dt() test_semantics_floor_division() test_semantics_floating_point_division() test_semantics_mod() brian2-2.5.4/brian2/tests/test_numpy_codegen.py000066400000000000000000000010611445201106100214530ustar00rootroot00000000000000import pytest from brian2 import * def test_error_message(): if prefs.codegen.target != "numpy": pytest.skip("numpy-only test") @check_units(x=1, result=1) def foo(x): raise ValueError() G = NeuronGroup(1, "v : 1") G.run_regularly("v = foo(3)") with pytest.raises(BrianObjectException) as exc: run(defaultclock.dt) # The actual code line should be mentioned in the error message exc.match("v = foo(3)") if __name__ == "__main__": prefs.codegen.target = "numpy" test_error_message() brian2-2.5.4/brian2/tests/test_parsing.py000066400000000000000000000405771445201106100203010ustar00rootroot00000000000000""" Tests the brian2.parsing package """ from collections import namedtuple import numpy as np import pytest from brian2 import Function from brian2.codegen.generators.cpp_generator import CPPCodeGenerator from brian2.core.functions import DEFAULT_FUNCTIONS from brian2.core.preferences import prefs from brian2.core.variables import Constant from brian2.groups.group import Group from brian2.parsing.dependencies import abstract_code_dependencies from brian2.parsing.expressions import ( _get_value_from_expression, is_boolean_expression, parse_expression_dimensions, ) from brian2.parsing.functions import ( abstract_code_from_function, extract_abstract_code_functions, substitute_abstract_code_functions, ) from brian2.parsing.rendering import CPPNodeRenderer, NodeRenderer, NumpyNodeRenderer from brian2.parsing.sympytools import str_to_sympy, sympy_to_str from brian2.tests.utils import assert_allclose from brian2.units import ( DimensionMismatchError, Unit, amp, get_unit, have_same_dimensions, volt, ) from brian2.units.fundamentalunits import DIMENSIONLESS, Dimension from brian2.utils.logger import std_silent from brian2.utils.stringtools import deindent, get_identifiers # a simple Group for testing class SimpleGroup(Group): def __init__(self, variables, namespace=None): self.variables = variables self.namespace = namespace TEST_EXPRESSIONS = """ a+b+c*d-f+g-(b+d)-(a-c) a**b**2 a**(b**2) (a**b)**2 a*(b+c*(a+b)*(a-(c*d))) a/b/c-a/(b/c) 10//n n//10 n//m 10/n 10.0/n n/10 n/10.0 n/m ab a>=b a==b a!=b a+1 1+a 1+3 a>0.5 and b>0.5 a>0.5 and b>0.5 or c>0.5 a>0.5 and b>0.5 or not c>0.5 2%4 -1%4 2.3%5.6 2.3%5 -1.2%3.4 17e-12 42e17 """ def parse_expressions(renderer, evaluator, numvalues=10): exprs = [ ([m for m in get_identifiers(l) if len(m) == 1], [], l.strip()) for l in TEST_EXPRESSIONS.split("\n") if l.strip() ] i, imod = 1, 33 for varids, funcids, expr in exprs: pexpr = renderer.render_expr(expr) n = 0 for _ in range(numvalues): # assign some random values ns = {} for v in varids: if v in ["n", "m"]: # integer values ns[v] = i else: ns[v] = float(i) / imod i = i % imod + 1 r1 = eval(expr.replace("&", " and ").replace("|", " or "), ns) n += 1 r2 = evaluator(pexpr, ns) try: # Use all close because we can introduce small numerical # difference through sympy's rearrangements assert_allclose(r1, r2, atol=1e-8) except AssertionError as e: raise AssertionError( f"In expression {str(expr)} translated to {str(pexpr)} {str(e)}" ) def numpy_evaluator(expr, userns): ns = {} # exec 'from numpy import logical_not' in ns ns["logical_not"] = np.logical_not ns.update(**userns) for k in userns: if not k.startswith("_"): ns[k] = np.array([userns[k]]) try: x = eval(expr, ns) except Exception as e: raise ValueError( f"Could not evaluate numpy expression {expr} exception {str(e)}" ) if isinstance(x, np.ndarray): return x[0] else: return x @pytest.mark.codegen_independent def test_parse_expressions_python(): parse_expressions(NodeRenderer(), eval) @pytest.mark.codegen_independent def test_parse_expressions_numpy(): parse_expressions(NumpyNodeRenderer(), numpy_evaluator) @pytest.mark.codegen_independent def test_parse_expressions_sympy(): # sympy is about symbolic calculation, the string returned by the renderer # contains "Symbol('a')" etc. so we cannot simply evaluate it in a # namespace. # We therefore use a different approach: Convert the expression to a # sympy expression via str_to_sympy (uses the SympyNodeRenderer internally), # then convert it back to a string via sympy_to_str and evaluate it class SympyRenderer: def render_expr(self, expr): return str_to_sympy(expr) def evaluator(expr, ns): expr = sympy_to_str(expr) ns = dict(ns) # Add the floor function which is used to implement floor division ns["floor"] = DEFAULT_FUNCTIONS["floor"] return eval(expr, ns) parse_expressions(SympyRenderer(), evaluator) @pytest.mark.codegen_independent def test_abstract_code_dependencies(): code = """ a = b+c d = b+c a = func_a() a = func_b() a = x+d """ known_vars = {"a", "b", "c"} known_funcs = {"func_a"} res = abstract_code_dependencies(code, known_vars, known_funcs) expected_res = dict( all=[ "a", "b", "c", "d", "x", "func_a", "func_b", ], read=["b", "c", "d", "x"], write=["a", "d"], funcs=["func_a", "func_b"], known_all=["a", "b", "c", "func_a"], known_read=["b", "c"], known_write=["a"], known_funcs=["func_a"], unknown_read=["d", "x"], unknown_write=["d"], unknown_funcs=["func_b"], undefined_read=["x"], newly_defined=["d"], ) for k, v in expected_res.items(): if not getattr(res, k) == set(v): raise AssertionError( f"For '{k}' result is {getattr(res, k)} expected {set(v)}" ) @pytest.mark.codegen_independent def test_is_boolean_expression(): # dummy "Variable" class Var = namedtuple("Var", ["is_boolean"]) # dummy function object class Func: def __init__(self, returns_bool=False): self._returns_bool = returns_bool # variables / functions a = Constant("a", value=True) b = Constant("b", value=False) c = Constant("c", value=5) f = Func(returns_bool=True) g = Func(returns_bool=False) s1 = Var(is_boolean=True) s2 = Var(is_boolean=False) variables = {"a": a, "b": b, "c": c, "f": f, "g": g, "s1": s1, "s2": s2} EVF = [ (True, "a or b"), (False, "c"), (False, "s2"), (False, "g(s1)"), (True, "s2 > c"), (True, "c > 5"), (True, "True"), (True, "a=b)"), (False, "a+b"), (True, "f(c)"), (False, "g(c)"), ( True, "f(c) or a= b*c)"), (DimensionMismatchError, "a or b 30))", "|", "or"), ("int((v > 30) & (w < 20))", "&", "and"), ("x +* 3", "", ""), ] for expr, expected_1, expected_2 in expr_expected: try: nr.render_expr(expr) raise AssertionError(f"Excepted {expr} to raise a SyntaxError.") except SyntaxError as exc: message = str(exc) assert expected_1 in message assert expected_2 in message @pytest.mark.codegen_independent def test_sympy_infinity(): # See github issue #1061 assert sympy_to_str(str_to_sympy("inf")) == "inf" assert sympy_to_str(str_to_sympy("-inf")) == "-inf" if __name__ == "__main__": from _pytest.outcomes import Skipped test_parse_expressions_python() test_parse_expressions_numpy() try: test_parse_expressions_cpp() except Skipped: pass test_parse_expressions_sympy() test_abstract_code_dependencies() test_is_boolean_expression() test_parse_expression_unit() test_value_from_expression() test_abstract_code_from_function() test_extract_abstract_code_functions() test_substitute_abstract_code_functions() test_sympytools() test_error_messages() test_sympy_infinity() brian2-2.5.4/brian2/tests/test_poissongroup.py000066400000000000000000000077511445201106100214020ustar00rootroot00000000000000import uuid import pytest from numpy.testing import assert_equal from brian2 import * from brian2.core.network import schedule_propagation_offset from brian2.devices.device import reinit_and_delete from brian2.tests.utils import exc_isinstance from brian2.utils.logger import catch_logs @pytest.mark.standalone_compatible def test_single_rates(): # Specifying single rates P0 = PoissonGroup(10, 0 * Hz) Pfull = PoissonGroup(10, 1.0 / defaultclock.dt) # Basic properties assert len(P0) == len(Pfull) == 10 assert len(repr(P0)) and len(str(P0)) spikes_P0 = SpikeMonitor(P0) spikes_Pfull = SpikeMonitor(Pfull) run(2 * defaultclock.dt) assert_equal(spikes_P0.count, np.zeros(len(P0))) assert_equal(spikes_Pfull.count, 2 * np.ones(len(P0))) @pytest.mark.standalone_compatible def test_rate_arrays(): P = PoissonGroup(2, np.array([0, 1.0 / defaultclock.dt]) * Hz) spikes = SpikeMonitor(P) run(2 * defaultclock.dt) assert_equal(spikes.count, np.array([0, 2])) @pytest.mark.codegen_independent def test_rate_unit_check(): with pytest.raises(DimensionMismatchError): PoissonGroup(1, np.array([1, 2])) with pytest.raises(DimensionMismatchError): PoissonGroup(1, np.array([1, 2]) * ms) P = PoissonGroup(1, "i*mV") net = Network(P) with pytest.raises(BrianObjectException) as exc: net.run(0 * ms) assert exc_isinstance(exc, DimensionMismatchError) P = PoissonGroup(1, "i") net = Network(P) with pytest.raises(BrianObjectException) as exc: net.run(0 * ms) assert exc_isinstance(exc, DimensionMismatchError) @pytest.mark.standalone_compatible def test_time_dependent_rate(): # The following two groups should show the same behaviour timed_array = TimedArray( np.array([[0, 0], [1.0 / defaultclock.dt, 0]]) * Hz, dt=1 * ms ) group_1 = PoissonGroup(2, rates="timed_array(t, i)") group_2 = PoissonGroup(2, rates="int(i==0)*int(t>1*ms-dt/2)*(1/dt)") spikes_1 = SpikeMonitor(group_1) spikes_2 = SpikeMonitor(group_2) run(2 * ms) assert_equal(spikes_1.count, np.array([int(round(1 * ms / defaultclock.dt)), 0])) assert_equal(spikes_2.count, np.array([int(round(1 * ms / defaultclock.dt)), 0])) assert sum(spikes_1.t < 1 * ms) == 0 assert sum(spikes_2.t < 1 * ms) == 0 @pytest.mark.standalone_compatible def test_propagation(): # Using a PoissonGroup as a source for Synapses should work as expected P = PoissonGroup(2, np.array([0, 1.0 / defaultclock.dt]) * Hz) G = NeuronGroup(2, "v:1") S = Synapses(P, G, on_pre="v+=1") S.connect(j="i") run(2 * defaultclock.dt + schedule_propagation_offset()) assert_equal(G.v[:], np.array([0.0, 2.0])) @pytest.mark.standalone_compatible def test_poissongroup_subgroup(): # It should be possible to take a subgroup of a PoissonGroup P = PoissonGroup(4, [0, 0, 0, 0] * Hz) P1 = P[:2] P2 = P[2:] P2.rates = 1.0 / defaultclock.dt G = NeuronGroup(4, "v:1") S1 = Synapses(P1, G[:2], on_pre="v+=1") S1.connect(j="i") S2 = Synapses(P2, G[2:], on_pre="v+=1") S2.connect(j="i") run(2 * defaultclock.dt + schedule_propagation_offset()) assert_equal(G.v[:], np.array([0.0, 0.0, 2.0, 2.0])) @pytest.mark.codegen_independent def test_poissongroup_namespace(): rate_const = 0 * Hz P = PoissonGroup( 1, rates="rate_const", namespace={"rate_const": 1 / defaultclock.dt}, name=f"poissongroup_{uuid.uuid4().hex}", ) P2 = PoissonGroup(1, rates="rate_const") mon = SpikeMonitor(P) mon2 = SpikeMonitor(P2) with catch_logs() as l: run(2 * defaultclock.dt) assert len(l) == 1 assert l[0][1].endswith("resolution_conflict") assert mon.num_spikes == 2 assert mon2.num_spikes == 0 if __name__ == "__main__": test_single_rates() test_rate_arrays() test_rate_unit_check() test_time_dependent_rate() test_propagation() test_poissongroup_subgroup() test_poissongroup_namespace() brian2-2.5.4/brian2/tests/test_poissoninput.py000066400000000000000000000067461445201106100214100ustar00rootroot00000000000000import pytest from numpy.testing import assert_equal from brian2 import * from brian2.core.network import schedule_propagation_offset from brian2.devices.device import reinit_and_delete from brian2.tests.utils import assert_allclose, exc_isinstance @pytest.mark.standalone_compatible def test_poissoninput(): # Test extreme cases and do a very basic test of an intermediate case, we # don't want tests to be stochastic G = NeuronGroup( 10, """ x : volt y : volt y2 : volt z : volt z2 : volt w : 1 """, ) G.w = 0.5 never_update = PoissonInput(G, "x", 100, 0 * Hz, weight=1 * volt) always_update = PoissonInput(G, "y", 50, 1 / defaultclock.dt, weight=2 * volt) always_update2 = PoissonInput( G, "y2", 50, 1 / defaultclock.dt, weight="1*volt + 1*volt" ) sometimes_update = PoissonInput(G, "z", 10000, 50 * Hz, weight=0.5 * volt) sometimes_update2 = PoissonInput(G, "z2", 10000, 50 * Hz, weight="w*volt") assert_equal(never_update.rate, 0 * Hz) assert_equal(never_update.N, 100) assert_equal(always_update.rate, 1 / defaultclock.dt) assert_equal(always_update.N, 50) assert_equal(sometimes_update.rate, 50 * Hz) assert_equal(sometimes_update.N, 10000) mon = StateMonitor(G, ["x", "y", "y2", "z", "z2"], record=True, when="end") run(1 * ms) assert_equal(0, mon.x[:]) assert_equal( np.tile((1 + np.arange(mon.y[:].shape[1])) * 50 * 2 * volt, (10, 1)), mon.y[:] ) assert_equal( np.tile((1 + np.arange(mon.y[:].shape[1])) * 50 * 2 * volt, (10, 1)), mon.y2[:] ) assert all(np.var(np.diff(mon.z[:]), axis=1) > 0) # variability over time assert all(np.var(mon.z[:], axis=0) > 0) # variability over neurons assert all(np.var(np.diff(mon.z2[:]), axis=1) > 0) # variability over time assert all(np.var(mon.z2[:], axis=0) > 0) # variability over neurons @pytest.mark.codegen_independent def test_poissoninput_errors(): # Targeting non-existing variable G = NeuronGroup( 10, """ x : volt y : 1 """, ) with pytest.raises(KeyError): PoissonInput(G, "z", 100, 100 * Hz, weight=1.0) # Incorrect units with pytest.raises(DimensionMismatchError): PoissonInput(G, "x", 100, 100 * Hz, weight=1.0) with pytest.raises(DimensionMismatchError): PoissonInput(G, "y", 100, 100 * Hz, weight=1.0 * volt) # dt change old_dt = defaultclock.dt inp = PoissonInput(G, "x", 100, 100 * Hz, weight=1 * volt) defaultclock.dt = 2 * old_dt net = Network(collect()) with pytest.raises(BrianObjectException) as exc: net.run(0 * ms) assert exc_isinstance(exc, NotImplementedError) defaultclock.dt = old_dt @pytest.mark.standalone_compatible def test_poissoninput_refractory(): eqs = """ dv/dt = 0/second : 1 (unless refractory) """ G = NeuronGroup( 10, eqs, reset="v=0", threshold="v>4.5", refractory=5 * defaultclock.dt ) # Will increase the value by 1.0 at each time step P = PoissonInput(G, "v", 1, 1 / defaultclock.dt, weight=1.0) mon = StateMonitor(G, "v", record=5) run(10 * defaultclock.dt) expected = np.arange(10, dtype=float) expected[6 - int(schedule_propagation_offset() / defaultclock.dt) :] = 0 assert_allclose(mon[5].v[:], expected) if __name__ == "__main__": test_poissoninput() reinit_and_delete() test_poissoninput_errors() test_poissoninput_refractory() brian2-2.5.4/brian2/tests/test_preferences.py000066400000000000000000000247141445201106100211320ustar00rootroot00000000000000from io import StringIO import pytest from numpy import float32, float64 from numpy.testing import assert_equal from brian2 import amp, restore_initial_state, volt from brian2.core.preferences import ( BrianGlobalPreferences, BrianGlobalPreferencesView, BrianPreference, DefaultValidator, PreferenceError, ) @pytest.mark.codegen_independent def test_defaultvalidator(): # Test that the default validator checks the class validator = DefaultValidator(5) assert validator(3) assert not validator("3") validator = DefaultValidator("astring") assert validator("another") assert not validator(3) # test that the default validator checks the units validator = DefaultValidator(3 * volt) assert validator(2 * volt) assert not validator(1 * amp) @pytest.mark.codegen_independent def test_brianpreference(): # check default args pref = BrianPreference(1.0 / 3, "docs") assert not pref.validator(1) assert pref.docs == "docs" assert pref.default == 1.0 / 3 assert pref.representor(pref.default) == repr(1.0 / 3) @pytest.mark.codegen_independent def test_preference_name_checking(): """ Test that you cannot set illegal preference names. """ gp = BrianGlobalPreferences() # Name that starts with an underscore with pytest.raises(PreferenceError): gp.register_preferences( "dummy", "dummy doc", _notalegalname=BrianPreference(True, "some preference"), ) # Name that clashes with a method name with pytest.raises(PreferenceError): gp.register_preferences( "dummy", "dummy doc", update=BrianPreference(True, "some preference") ) gp.register_preferences( "a", "dummy doc", b=BrianPreference(True, "some preference") ) # Trying to register a subcategory that would shadow a preference with pytest.raises(PreferenceError): gp.register_preferences( "a.b", "dummy doc", name=BrianPreference(True, "some preference") ) gp.register_preferences( "b.c", "dummy doc", name=BrianPreference(True, "some preference") ) # Trying to register a preference that clashes with an existing category with pytest.raises(PreferenceError): gp.register_preferences( "b", "dummy doc", c=BrianPreference(True, "some preference") ) @pytest.mark.codegen_independent def test_brianglobalpreferences(): # test that pre-setting a nonexistent preference in a subsequently # existing base name raises an error at the correct point gp = BrianGlobalPreferences() # This shouldn't work, in user code only registered preferences can be set with pytest.raises(PreferenceError): gp.__setitem__("a.b", 5) # This uses the method that is used when reading preferences from a file gp._set_preference("a.b", 5) gp._set_preference("a.c", 5) with pytest.raises(PreferenceError): gp.register_preferences("a", "docs for a", b=BrianPreference(5, "docs for b")) # test that post-setting a nonexistent preference in an existing base # name raises an error gp = BrianGlobalPreferences() gp.register_preferences("a", "docs for a", b=BrianPreference(5, "docs for b")) with pytest.raises(PreferenceError): gp.__setitem__("a.c", 5) # Test pre and post-setting some correct names but valid and invalid values gp = BrianGlobalPreferences() gp._set_preference("a.b", 5) gp.register_preferences( "a", "docs for a", b=BrianPreference(5, "docs for b"), c=BrianPreference(1 * volt, "docs for c"), d=BrianPreference(0, "docs for d", validator=lambda x: x >= 0), e=BrianPreference(float64, "docs for e", representor=lambda x: x.__name__), ) assert gp["a.c"] == 1 * volt gp["a.c"] = 2 * volt with pytest.raises(PreferenceError): gp.__setitem__("a.c", 3 * amp) gp["a.d"] = 2.0 with pytest.raises(PreferenceError): gp.__setitem__("a.d", -1) gp["a.e"] = float32 with pytest.raises(PreferenceError): gp.__setitem__("a.e", 0) # test backup and restore gp._backup() gp["a.d"] = 10 assert gp["a.d"] == 10 gp._restore() assert gp["a.d"] == 2.0 # test that documentation and as_file generation runs without error, but # don't test for values because we might change the organisation of it assert len(gp.get_documentation()) gp.as_file gp.defaults_as_file # test that reading a preference file works as expected pref_file = StringIO( """ # a comment a.b = 10 [a] c = 5*volt d = 1 e = float64 """ ) gp.read_preference_file(pref_file) assert gp["a.b"] == 10 assert gp["a.c"] == 5 * volt assert gp["a.d"] == 1 assert gp["a.e"] == float64 # test that reading a badly formatted prefs file fails pref_file = StringIO( """ [a b = 10 """ ) with pytest.raises(PreferenceError): gp.read_preference_file(pref_file) # test that reading a well formatted prefs file with an invalid value fails pref_file = StringIO( """ a.b = 'oh no, not a string' """ ) with pytest.raises(PreferenceError): gp.read_preference_file(pref_file) # assert that writing the prefs to a file and loading them gives the # same values gp = BrianGlobalPreferences() gp.register_preferences( "a", "docs for a", b=BrianPreference(5, "docs for b"), ) gp._backup() gp["a.b"] = 10 str_modified = gp.as_file str_defaults = gp.defaults_as_file gp["a.b"] = 15 gp.read_preference_file(StringIO(str_modified)) assert gp["a.b"] == 10 gp.read_preference_file(StringIO(str_defaults)) assert gp["a.b"] == 5 # check that load_preferences works, but nothing about its values gp = BrianGlobalPreferences() gp.load_preferences() # Check that resetting to default preferences works gp = BrianGlobalPreferences() gp.register_preferences("a", "docs for a", b=BrianPreference(5, "docs for b")) assert gp["a.b"] == 5 gp["a.b"] = 7 assert gp["a.b"] == 7 gp.reset_to_defaults() assert gp["a.b"] == 5 @pytest.mark.codegen_independent def test_preference_name_access(): """ Test various ways of accessing preferences """ gp = BrianGlobalPreferences() gp.register_preferences( "main", "main category", name=BrianPreference(True, "some preference") ) gp.register_preferences( "main.sub", "subcategory", name2=BrianPreference(True, "some preference") ) gp.register_preferences("main.sub_no_pref", "subcategory without preference") gp.register_preferences( "main.sub_no_pref.sub", "deep subcategory", name=BrianPreference(True, "some preference"), ) # Keyword based access assert gp["main.name"] assert gp["main.sub.name2"] assert gp["main.sub_no_pref.sub.name"] gp["main.name"] = False gp["main.sub.name2"] = False gp["main.sub_no_pref.sub.name"] = False # Attribute based access assert not gp.main.name # we set it to False above assert not gp.main.sub.name2 assert not gp.main.sub_no_pref.sub.name gp.main.name = True gp.main.sub.name2 = True gp.main.sub_no_pref.sub.name = True # Mixed access assert gp.main["name"] assert gp["main"].name assert gp.main["sub"].name2 assert gp["main"].sub["name2"] # Accessing categories assert isinstance(gp["main"], BrianGlobalPreferencesView) assert isinstance(gp["main.sub"], BrianGlobalPreferencesView) assert isinstance(gp.main, BrianGlobalPreferencesView) assert isinstance(gp.main.sub, BrianGlobalPreferencesView) # Setting categories shouldn't work with pytest.raises(PreferenceError): gp.__setitem__("main", None) with pytest.raises(PreferenceError): gp.__setattr__("main", None) with pytest.raises(PreferenceError): gp.main.__setitem__("sub", None) with pytest.raises(PreferenceError): gp.main.__setattr__("sub", None) # Neither should deleting categories or preferences with pytest.raises(PreferenceError): gp.__delitem__("main") with pytest.raises(PreferenceError): gp.__delattr__("main") with pytest.raises(PreferenceError): gp.main.__delitem__("name") with pytest.raises(PreferenceError): gp.main.__delattr__("name") with pytest.raises(PreferenceError): gp.main.__delitem__("sub") with pytest.raises(PreferenceError): gp.main.__delattr__("sub") # Errors for accessing non-existing preferences with pytest.raises(KeyError): gp["main.doesnotexist"] with pytest.raises(KeyError): gp["nonexisting.name"] with pytest.raises(KeyError): gp.main.doesnotexist with pytest.raises(KeyError): gp.nonexisting.name # Check dictionary functionality for name, value in gp.items(): assert gp[name] == value for name, value in gp.main.items(): assert gp.main[name] == value assert len(gp) == 3 # three preferences in total assert len(gp["main"]) == 3 # all preferences are in the main category assert len(gp["main.sub"]) == 1 # one preference in main.sub assert "main.name" in gp assert "name" in gp["main"] assert "name2" in gp["main.sub"] assert not "name" in gp["main.sub"] gp["main.name"] = True gp.update({"main.name": False}) assert not gp["main.name"] gp.main.update({"name": True}) assert gp["main.name"] # Class based functionality assert "main" in dir(gp) assert "sub" in dir(gp.main) assert "name" in dir(gp.main) # Check that the fiddling with getattr and setattr did not destroy the # access to standard attributes assert len(gp.prefs) assert gp.main._basename == "main" @pytest.mark.codegen_independent def test_str_repr(): # Just test whether str and repr do not throw an error and return something gp = BrianGlobalPreferences() gp.register_preferences( "main", "main category", name=BrianPreference(True, "some preference") ) assert len(str(gp)) assert len(repr(gp)) assert len(str(gp.main)) assert len(repr(gp.main)) if __name__ == "__main__": for t in [ test_defaultvalidator, test_brianpreference, test_brianglobalpreferences, test_preference_name_checking, test_preference_name_access, ]: t() restore_initial_state() brian2-2.5.4/brian2/tests/test_refractory.py000066400000000000000000000271301445201106100210040ustar00rootroot00000000000000from collections import Counter import pytest from numpy.testing import assert_equal from brian2 import * from brian2.core.functions import timestep from brian2.devices.device import reinit_and_delete from brian2.equations.refractory import add_refractoriness from brian2.tests.utils import assert_allclose, exc_isinstance from brian2.utils.logger import catch_logs @pytest.mark.codegen_independent def test_add_refractoriness(): eqs = Equations( """ dv/dt = -x*v/second : volt (unless refractory) dw/dt = -w/second : amp x : 1 """ ) # only make sure it does not throw an error eqs = add_refractoriness(eqs) # Check that the parameters were added assert "not_refractory" in eqs assert "lastspike" in eqs @pytest.mark.codegen_independent def test_missing_refractory_warning(): # Forgotten refractory argument with catch_logs() as l: group = NeuronGroup( 1, "dv/dt = -v / (10*ms) : 1 (unless refractory)", threshold="v > 1", reset="v = 0", ) assert len(l) == 1 assert l[0][0] == "WARNING" and l[0][1].endswith("no_refractory") @pytest.mark.standalone_compatible def test_refractoriness_basic(): G = NeuronGroup( 1, """ dv/dt = 99.999*Hz : 1 (unless refractory) dw/dt = 99.999*Hz : 1 """, threshold="v>1", reset="v=0;w=0", refractory=5 * ms, ) # It should take 10ms to reach the threshold, then v should stay at 0 # for 5ms, while w continues to increase mon = StateMonitor(G, ["v", "w"], record=True, when="end") run(20 * ms) # No difference before the spike assert_allclose( mon[0].v[: timestep(10 * ms, defaultclock.dt)], mon[0].w[: timestep(10 * ms, defaultclock.dt)], ) # v is not updated during refractoriness in_refractoriness = mon[0].v[ timestep(10 * ms, defaultclock.dt) : timestep(15 * ms, defaultclock.dt) ] assert_equal(in_refractoriness, np.zeros_like(in_refractoriness)) # w should evolve as before assert_allclose( mon[0].w[: timestep(5 * ms, defaultclock.dt)], mon[0].w[ timestep(10 * ms, defaultclock.dt) + 1 : timestep(15 * ms, defaultclock.dt) + 1 ], ) assert np.all( mon[0].w[ timestep(10 * ms, defaultclock.dt) + 1 : timestep(15 * ms, defaultclock.dt) + 1 ] > 0 ) # After refractoriness, v should increase again assert np.all( mon[0].v[ timestep(15 * ms, defaultclock.dt) : timestep(20 * ms, defaultclock.dt) ] > 0 ) @pytest.mark.standalone_compatible @pytest.mark.parametrize( "ref_time", [ "5*ms", "(t-lastspike + 1e-3*dt) < 5*ms", "time_since_spike + 1e-3*dt < 5*ms", "ref_subexpression", "(t-lastspike + 1e-3*dt) < ref", "ref", "ref_no_unit*ms", ], ) def test_refractoriness_variables(ref_time): # Try a string evaluating to a quantity, and an explicit boolean # condition -- all should do the same thing G = NeuronGroup( 1, """ dv/dt = 99.999*Hz : 1 (unless refractory) dw/dt = 99.999*Hz : 1 ref : second ref_no_unit : 1 time_since_spike = (t - lastspike) +1e-3*dt : second ref_subexpression = (t - lastspike + 1e-3*dt) < ref : boolean """, threshold="v>1", reset="v=0;w=0", refractory=ref_time, dtype={ "ref": defaultclock.variables["t"].dtype, "ref_no_unit": defaultclock.variables["t"].dtype, "lastspike": defaultclock.variables["t"].dtype, "time_since_spike": defaultclock.variables["t"].dtype, }, ) G.ref = 5 * ms G.ref_no_unit = 5 # It should take 10ms to reach the threshold, then v should stay at 0 # for 5ms, while w continues to increase mon = StateMonitor(G, ["v", "w"], record=True, when="end") run(20 * ms) try: # No difference before the spike assert_allclose( mon[0].v[: timestep(10 * ms, defaultclock.dt)], mon[0].w[: timestep(10 * ms, defaultclock.dt)], ) # v is not updated during refractoriness in_refractoriness = mon[0].v[ timestep(10 * ms, defaultclock.dt) : timestep(15 * ms, defaultclock.dt) ] assert_allclose(in_refractoriness, np.zeros_like(in_refractoriness)) # w should evolve as before assert_allclose( mon[0].w[: timestep(5 * ms, defaultclock.dt)], mon[0].w[ timestep(10 * ms, defaultclock.dt) + 1 : timestep(15 * ms, defaultclock.dt) + 1 ], ) assert np.all( mon[0].w[ timestep(10 * ms, defaultclock.dt) + 1 : timestep(15 * ms, defaultclock.dt) + 1 ] > 0 ) # After refractoriness, v should increase again assert np.all( mon[0].v[ timestep(15 * ms, defaultclock.dt) : timestep(20 * ms, defaultclock.dt) ] > 0 ) except AssertionError as ex: raise raise AssertionError( f"Assertion failed when using {ref_time!r} as refractory argument:\n{ex}" ) @pytest.mark.standalone_compatible def test_refractoriness_threshold_basic(): G = NeuronGroup( 1, """ dv/dt = 199.99*Hz : 1 """, threshold="v > 1", reset="v=0", refractory=10 * ms, ) # The neuron should spike after 5ms but then not spike for the next # 10ms. The state variable should continue to integrate so there should # be a spike after 15ms spike_mon = SpikeMonitor(G) run(16 * ms) assert_allclose(spike_mon.t, [5, 15] * ms) @pytest.mark.standalone_compatible def test_refractoriness_repeated(): # Create a group that spikes whenever it can group = NeuronGroup(1, "", threshold="True", refractory=10 * defaultclock.dt) spike_mon = SpikeMonitor(group) run(10000 * defaultclock.dt) assert spike_mon.t[0] == 0 * ms assert_allclose(np.diff(spike_mon.t), 10 * defaultclock.dt) @pytest.mark.standalone_compatible def test_refractoriness_repeated_legacy(): if prefs.core.default_float_dtype == np.float32: pytest.skip( "Not testing legacy refractory mechanism with single precision floats." ) # Switch on behaviour from versions <= 2.1.2 prefs.legacy.refractory_timing = True # Create a group that spikes whenever it can group = NeuronGroup(1, "", threshold="True", refractory=10 * defaultclock.dt) spike_mon = SpikeMonitor(group) run(10000 * defaultclock.dt) assert spike_mon.t[0] == 0 * ms # Empirical values from running with earlier Brian versions assert_allclose( np.diff(spike_mon.t)[:10], [1.1, 1, 1.1, 1, 1.1, 1.1, 1.1, 1.1, 1, 1.1] * ms ) steps = Counter(np.diff(np.int_(np.round(spike_mon.t / defaultclock.dt)))) assert len(steps) == 2 and steps[10] == 899 and steps[11] == 91 prefs.legacy.refractory_timing = False @pytest.mark.standalone_compatible @pytest.mark.parametrize( "ref_time", [ 10 * ms, "10*ms", "timestep(t-lastspike, dt) < timestep(10*ms, dt)", "timestep(t-lastspike, dt) < timestep(ref, dt)", "ref", "ref_no_unit*ms", ], ) def test_refractoriness_threshold(ref_time): # Try a quantity, a string evaluating to a quantity, and an explicit boolean # condition -- all should do the same thing G = NeuronGroup( 1, """ dv/dt = 199.999*Hz : 1 ref : second ref_no_unit : 1 """, threshold="v > 1", reset="v=0", refractory=ref_time, dtype={ "ref": defaultclock.variables["t"].dtype, "ref_no_unit": defaultclock.variables["t"].dtype, }, ) G.ref = 10 * ms G.ref_no_unit = 10 # The neuron should spike after 5ms but then not spike for the next # 10ms. The state variable should continue to integrate so there should # be a spike after 15ms spike_mon = SpikeMonitor(G) run(16 * ms) assert_allclose(spike_mon.t, [5, 15] * ms) @pytest.mark.codegen_independent def test_refractoriness_types(): # make sure that using a wrong type of refractoriness does not work group = NeuronGroup(1, "", refractory="3*Hz") with pytest.raises(BrianObjectException) as exc: Network(group).run(0 * ms) assert exc_isinstance(exc, TypeError) group = NeuronGroup(1, "ref: Hz", refractory="ref") with pytest.raises(BrianObjectException) as exc: Network(group).run(0 * ms) assert exc_isinstance(exc, TypeError) group = NeuronGroup(1, "", refractory="3") with pytest.raises(BrianObjectException) as exc: Network(group).run(0 * ms) assert exc_isinstance(exc, TypeError) group = NeuronGroup(1, "ref: 1", refractory="ref") with pytest.raises(BrianObjectException) as exc: Network(group).run(0 * ms) assert exc_isinstance(exc, TypeError) @pytest.mark.codegen_independent def test_conditional_write_set(): """ Test that the conditional_write attribute is set correctly """ G = NeuronGroup( 1, """ dv/dt = 10*Hz : 1 (unless refractory) dw/dt = 10*Hz : 1 """, refractory=2 * ms, ) assert G.variables["v"].conditional_write is G.variables["not_refractory"] assert G.variables["w"].conditional_write is None @pytest.mark.standalone_compatible def test_conditional_write_behaviour(): H = NeuronGroup(1, "v:1", threshold="v>-1") tau = 1 * ms eqs = """ dv/dt = (2-v)/tau : 1 (unless refractory) dx/dt = 0/tau : 1 (unless refractory) dy/dt = 0/tau : 1 """ reset = """ v = 0 x -= 0.05 y -= 0.05 """ G = NeuronGroup(1, eqs, threshold="v>1", reset=reset, refractory=1 * ms) Sx = Synapses(H, G, on_pre="x += dt*100*Hz") Sx.connect(True) Sy = Synapses(H, G, on_pre="y += dt*100*Hz") Sy.connect(True) M = StateMonitor(G, variables=True, record=True) run(10 * ms) assert G.x[0] < 0.2 assert G.y[0] > 0.2 assert G.v[0] < 1.1 @pytest.mark.standalone_compatible def test_conditional_write_automatic_and_manual(): source = NeuronGroup(1, "", threshold="True") # spiking all the time target = NeuronGroup( 2, """ dv/dt = 0/ms : 1 (unless refractory) dw/dt = 0/ms : 1 """, threshold="t == 0*ms", refractory="False", ) # only refractory for the first time step # Cell is spiking/refractory only in the first time step syn = Synapses( source, target, on_pre=""" v += 1 w += 1 * int(not_refractory_post) """, ) syn.connect() mon = StateMonitor(target, ["v", "w"], record=True, when="end") run(2 * defaultclock.dt) # Synapse should not have been effective in the first time step assert_allclose(mon.v[:, 0], 0) assert_allclose(mon.v[:, 1], 1) assert_allclose(mon.w[:, 0], 0) assert_allclose(mon.w[:, 1], 1) if __name__ == "__main__": test_add_refractoriness() test_missing_refractory_warning() test_refractoriness_basic() test_refractoriness_variables() test_refractoriness_threshold() test_refractoriness_threshold_basic() test_refractoriness_repeated() test_refractoriness_repeated_legacy() test_refractoriness_types() test_conditional_write_set() test_conditional_write_behaviour() test_conditional_write_automatic_and_manual() brian2-2.5.4/brian2/tests/test_spatialneuron.py000066400000000000000000001055671445201106100215230ustar00rootroot00000000000000import itertools import os import pytest from numpy.testing import assert_equal from brian2 import * from brian2.devices.device import reinit_and_delete from brian2.tests.utils import assert_allclose try: import scipy except ImportError: scipy = None numpy_needs_scipy = pytest.mark.skipif( # Using condition string, since we cannot yet know # prefs.codegen.target at module import time "prefs.codegen.target == 'numpy' and not scipy", reason="multi-compartmental models need scipy to run with numpy", ) @pytest.mark.codegen_independent @numpy_needs_scipy def test_custom_events(): # Set (could be moved in a setup) EL = -65 * mV gL = 0.0003 * siemens / cm**2 ev = """ Im = gL * (EL - v) : amp/meter**2 event_time1 : second """ # Create a three compartments morphology morpho = Soma(diameter=10 * um) morpho.dend1 = Cylinder(n=1, diameter=1 * um, length=10 * um) morpho.dend2 = Cylinder(n=1, diameter=1 * um, length=10 * um) G = SpatialNeuron( morphology=morpho, model=ev, events={"event1": "t>=i*ms and t= neuron.diffusion_state_updater._starts[:].flat ) # Check that length and distances make sense assert_allclose(sum(morpho.L.length), 10 * um) assert_allclose(morpho.L.distance, (0.5 + np.arange(10)) * um) assert_allclose(sum(morpho.LL.length), 5 * um) assert_allclose(morpho.LL.distance, (10 + 0.5 + np.arange(5)) * um) assert_allclose(sum(morpho.LR.length), 5 * um) assert_allclose(morpho.LR.distance, (10 + 0.25 + np.arange(10) * 0.5) * um) assert_allclose(sum(morpho.right.length), 3 * um) assert_allclose(morpho.right.distance, (0.5 + np.arange(7)) * 3.0 / 7.0 * um) assert_allclose(sum(morpho.right.nextone.length), 2 * um) assert_allclose( morpho.right.nextone.distance, 3 * um + (0.5 + np.arange(3)) * 2.0 / 3.0 * um ) @pytest.mark.codegen_independent def test_construction_coordinates(): # Same as test_construction, but uses coordinates instead of lengths to # set up everything # Note that all coordinates here are relative to the origin of the # respective cylinder BrianLogger.suppress_name("resolution_conflict") morpho = Soma(diameter=30 * um) morpho.L = Cylinder(x=[0, 10] * um, diameter=1 * um, n=10) morpho.LL = Cylinder(y=[0, 5] * um, diameter=2 * um, n=5) morpho.LR = Cylinder(z=[0, 5] * um, diameter=2 * um, n=10) morpho.right = Cylinder( x=[0, sqrt(2) * 1.5] * um, y=[0, sqrt(2) * 1.5] * um, diameter=1 * um, n=7 ) morpho.right.nextone = Cylinder( y=[0, sqrt(2)] * um, z=[0, sqrt(2)] * um, diameter=1 * um, n=3 ) gL = 1e-4 * siemens / cm**2 EL = -70 * mV eqs = """ Im=gL*(EL-v) : amp/meter**2 I : meter (point current) """ # Check units of currents with pytest.raises(DimensionMismatchError): SpatialNeuron(morphology=morpho, model=eqs) eqs = """ Im=gL*(EL-v) : amp/meter**2 """ neuron = SpatialNeuron( morphology=morpho, model=eqs, Cm=1 * uF / cm**2, Ri=100 * ohm * cm ) # Test initialization of values neuron.LL.v = EL assert_allclose(neuron.L.main.v, 0 * mV) assert_allclose(neuron.LL.v, EL) neuron.LL[1 * um : 3 * um].v = 0 * mV assert_allclose(neuron.LL.v, Quantity([EL, 0 * mV, 0 * mV, EL, EL])) assert_allclose(neuron.Cm, 1 * uF / cm**2) # Test morphological variables assert_allclose(neuron.L.main.x, morpho.L.x) assert_allclose(neuron.LL.main.x, morpho.LL.x) assert_allclose(neuron.right.main.x, morpho.right.x) assert_allclose(neuron.L.main.distance, morpho.L.distance) # assert_allclose(neuron.L.main.diameter, morpho.L.diameter) assert_allclose(neuron.L.main.area, morpho.L.area) assert_allclose(neuron.L.main.length, morpho.L.length) # Check basic consistency of the flattened representation assert all( neuron.diffusion_state_updater._ends[:].flat >= neuron.diffusion_state_updater._starts[:].flat ) # Check that length and distances make sense assert_allclose(sum(morpho.L.length), 10 * um) assert_allclose(morpho.L.distance, (0.5 + np.arange(10)) * um) assert_allclose(sum(morpho.LL.length), 5 * um) assert_allclose(morpho.LL.distance, (10 + 0.5 + np.arange(5)) * um) assert_allclose(sum(morpho.LR.length), 5 * um) assert_allclose(morpho.LR.distance, (10 + 0.25 + np.arange(10) * 0.5) * um) assert_allclose(sum(morpho.right.length), 3 * um) assert_allclose(morpho.right.distance, (0.5 + np.arange(7)) * 3.0 / 7.0 * um) assert_allclose(sum(morpho.right.nextone.length), 2 * um) assert_allclose( morpho.right.nextone.distance, 3 * um + (0.5 + np.arange(3)) * 2.0 / 3.0 * um ) @pytest.mark.long @numpy_needs_scipy def test_infinitecable(): """ Test simulation of an infinite cable vs. theory for current pulse (Green function) """ BrianLogger.suppress_name("resolution_conflict") defaultclock.dt = 0.001 * ms # Morphology diameter = 1 * um Cm = 1 * uF / cm**2 Ri = 100 * ohm * cm N = 500 morpho = Cylinder(diameter=diameter, length=3 * mm, n=N) # Passive channels gL = 1e-4 * siemens / cm**2 eqs = """ Im=-gL*v : amp/meter**2 I : amp (point current) """ neuron = SpatialNeuron(morphology=morpho, model=eqs, Cm=Cm, Ri=Ri) # Monitors mon = StateMonitor(neuron, "v", record=N / 2 - 20) neuron.I[len(neuron) // 2] = 1 * nA # injecting in the middle run(0.02 * ms) neuron.I = 0 * amp run(3 * ms) t = mon.t v = mon[N // 2 - 20].v # Theory (incorrect near cable ends) x = 20 * morpho.length[0] la = neuron.space_constant[0] taum = Cm / gL # membrane time constant theory = ( 1.0 / (la * Cm * pi * diameter) * sqrt(taum / (4 * pi * (t + defaultclock.dt))) * exp( -(t + defaultclock.dt) / taum - taum / (4 * (t + defaultclock.dt)) * (x / la) ** 2 ) ) theory = theory * 1 * nA * 0.02 * ms assert_allclose( v[t > 0.5 * ms], theory[t > 0.5 * ms], atol=float(6.32 * uvolt) ) # high error tolerance (not exact because not infinite cable) @pytest.mark.standalone_compatible @numpy_needs_scipy def test_finitecable(): """ Test simulation of short cylinder vs. theory for constant current. """ BrianLogger.suppress_name("resolution_conflict") defaultclock.dt = 0.01 * ms # Morphology diameter = 1 * um length = 300 * um Cm = 1 * uF / cm**2 Ri = 150 * ohm * cm N = 200 morpho = Cylinder(diameter=diameter, length=length, n=N) # Passive channels gL = 1e-4 * siemens / cm**2 EL = -70 * mV eqs = """ Im=gL*(EL-v) : amp/meter**2 I : amp (point current) """ neuron = SpatialNeuron(morphology=morpho, model=eqs, Cm=Cm, Ri=Ri) neuron.v = EL neuron.I[0] = 0.02 * nA # injecting at the left end run(100 * ms) # Theory x = neuron.distance v = neuron.v la = neuron.space_constant[0] ra = la * 4 * Ri / (pi * diameter**2) theory = EL + ra * neuron.I[0] * cosh((length - x) / la) / sinh(length / la) assert_allclose(v - EL, theory - EL, atol=1e-6) @pytest.mark.standalone_compatible @numpy_needs_scipy def test_rallpack1(): """ Rallpack 1 """ if prefs.core.default_float_dtype is np.float32: pytest.skip("Need double precision for this test") defaultclock.dt = 0.05 * ms # Morphology diameter = 1 * um length = 1 * mm Cm = 1 * uF / cm**2 Ri = 100 * ohm * cm N = 1000 morpho = Cylinder(diameter=diameter, length=length, n=N) # Passive channels gL = 1.0 / (40000 * ohm * cm**2) EL = -65 * mV eqs = """ Im = gL*(EL - v) : amp/meter**2 I : amp (point current, constant) """ neuron = SpatialNeuron(morphology=morpho, model=eqs, Cm=Cm, Ri=Ri) neuron.v = EL neuron.I[0] = 0.1 * nA # injecting at the left end # Record at the two ends mon = StateMonitor(neuron, "v", record=[0, 999], when="start", dt=0.05 * ms) run(250 * ms + defaultclock.dt) # Load the theoretical results basedir = os.path.join(os.path.abspath(os.path.dirname(__file__)), "rallpack_data") data_0 = np.loadtxt(os.path.join(basedir, "ref_cable.0")) data_x = np.loadtxt(os.path.join(basedir, "ref_cable.x")) scale_0 = max(data_0[:, 1] * volt) - min(data_0[:, 1] * volt) scale_x = max(data_x[:, 1] * volt) - min(data_x[:, 1] * volt) squared_diff_0 = (data_0[:, 1] * volt - mon[0].v) ** 2 squared_diff_x = (data_x[:, 1] * volt - mon[999].v) ** 2 rel_RMS_0 = sqrt(mean(squared_diff_0)) / scale_0 rel_RMS_x = sqrt(mean(squared_diff_x)) / scale_x max_rel_0 = sqrt(max(squared_diff_0)) / scale_0 max_rel_x = sqrt(max(squared_diff_x)) / scale_x # sanity check: times are the same assert_allclose(mon.t / second, data_0[:, 0]) assert_allclose(mon.t / second, data_x[:, 0]) # RMS error should be < 0.1%, maximum error along the curve should be < 0.5% assert 100 * rel_RMS_0 < 0.1 assert 100 * rel_RMS_x < 0.1 assert 100 * max_rel_0 < 0.5 assert 100 * max_rel_x < 0.5 @pytest.mark.standalone_compatible @numpy_needs_scipy def test_rallpack2(): """ Rallpack 2 """ if prefs.core.default_float_dtype is np.float32: pytest.skip("Need double precision for this test") defaultclock.dt = 0.1 * ms # Morphology diameter = 32 * um length = 16 * um Cm = 1 * uF / cm**2 Ri = 100 * ohm * cm # Construct binary tree according to Rall's formula morpho = Cylinder(n=1, diameter=diameter, y=[0, float(length)] * meter) endpoints = {morpho} for depth in range(1, 10): diameter /= 2.0 ** (1.0 / 3.0) length /= 2.0 ** (2.0 / 3.0) new_endpoints = set() for endpoint in endpoints: new_L = Cylinder(n=1, diameter=diameter, length=length) new_R = Cylinder(n=1, diameter=diameter, length=length) new_endpoints.add(new_L) new_endpoints.add(new_R) endpoint.L = new_L endpoint.R = new_R endpoints = new_endpoints # Passive channels gL = 1.0 / (40000 * ohm * cm**2) EL = -65 * mV eqs = """ Im = gL*(EL - v) : amp/meter**2 I : amp (point current, constant) """ neuron = SpatialNeuron(morphology=morpho, model=eqs, Cm=Cm, Ri=Ri, method="rk4") neuron.v = EL neuron.I[0] = 0.1 * nA # injecting at the origin endpoint_indices = [endpoint.indices[0] for endpoint in endpoints] mon = StateMonitor( neuron, "v", record=[0] + endpoint_indices, when="start", dt=0.1 * ms ) run(250 * ms + defaultclock.dt) # Load the theoretical results basedir = os.path.join(os.path.abspath(os.path.dirname(__file__)), "rallpack_data") # Only use very second time step, since we run with 0.1ms instead of 0.05ms data_0 = np.loadtxt(os.path.join(basedir, "ref_branch.0"))[::2] data_x = np.loadtxt(os.path.join(basedir, "ref_branch.x"))[::2] # sanity check: times are the same assert_allclose(mon.t / second, data_0[:, 0]) assert_allclose(mon.t / second, data_x[:, 0]) # Check that all endpoints are the same: for endpoint in endpoints: assert_allclose(mon[endpoint].v, mon[endpoint[0]].v) scale_0 = max(data_0[:, 1] * volt) - min(data_0[:, 1] * volt) scale_x = max(data_x[:, 1] * volt) - min(data_x[:, 1] * volt) squared_diff_0 = (data_0[:, 1] * volt - mon[0].v) ** 2 # One endpoint squared_diff_x = (data_x[:, 1] * volt - mon[endpoint_indices[0]].v) ** 2 rel_RMS_0 = sqrt(mean(squared_diff_0)) / scale_0 rel_RMS_x = sqrt(mean(squared_diff_x)) / scale_x max_rel_0 = sqrt(max(squared_diff_0)) / scale_0 max_rel_x = sqrt(max(squared_diff_x)) / scale_x # RMS error should be < 0.25%, maximum error along the curve should be < 0.5% assert 100 * rel_RMS_0 < 0.25 assert 100 * rel_RMS_x < 0.25 assert 100 * max_rel_0 < 0.5 assert 100 * max_rel_x < 0.5 @pytest.mark.standalone_compatible @pytest.mark.long @numpy_needs_scipy def test_rallpack3(): """ Rallpack 3 """ if prefs.core.default_float_dtype is np.float32: pytest.skip("Need double precision for this test") defaultclock.dt = 1 * usecond # Morphology diameter = 1 * um length = 1 * mm N = 1000 morpho = Cylinder(diameter=diameter, length=length, n=N) # Passive properties gl = 1.0 / (40000 * ohm * cm**2) El = -65 * mV Cm = 1 * uF / cm**2 Ri = 100 * ohm * cm # Active properties ENa = 50 * mV EK = -77 * mV gNa = 120 * msiemens / cm**2 gK = 36 * msiemens / cm**2 eqs = """ Im = gl * (El-v) + gNa * m**3 * h * (ENa-v) + gK * n**4 * (EK-v) : amp/meter**2 dm/dt = alpham * (1-m) - betam * m : 1 dn/dt = alphan * (1-n) - betan * n : 1 dh/dt = alphah * (1-h) - betah * h : 1 v_shifted = v - El : volt alpham = (0.1/mV) * (-v_shifted+25*mV) / (exp((-v_shifted+25*mV) / (10*mV)) - 1)/ms : Hz betam = 4 * exp(-v_shifted/(18*mV))/ms : Hz alphah = 0.07 * exp(-v_shifted/(20*mV))/ms : Hz betah = 1/(exp((-v_shifted+30*mV) / (10*mV)) + 1)/ms : Hz alphan = (0.01/mV) * (-v_shifted+10*mV) / (exp((-v_shifted+10*mV) / (10*mV)) - 1)/ms : Hz betan = 0.125*exp(-v_shifted/(80*mV))/ms : Hz I : amp (point current, constant) """ axon = SpatialNeuron( morphology=morpho, model=eqs, Cm=Cm, Ri=Ri, method="exponential_euler" ) axon.v = El # Pre-calculated equilibrium values at v = El axon.m = 0.0529324852572 axon.n = 0.317676914061 axon.h = 0.596120753508 axon.I[0] = 0.1 * nA # injecting at the left end # Record at the two ends mon = StateMonitor(axon, "v", record=[0, 999], when="start", dt=0.05 * ms) run(250 * ms + defaultclock.dt) # Load the theoretical results basedir = os.path.join(os.path.abspath(os.path.dirname(__file__)), "rallpack_data") data_0 = np.loadtxt(os.path.join(basedir, "ref_axon.0.neuron")) data_x = np.loadtxt(os.path.join(basedir, "ref_axon.x.neuron")) # sanity check: times are the same assert_allclose(mon.t / second, data_0[:, 0]) assert_allclose(mon.t / second, data_x[:, 0]) scale_0 = max(data_0[:, 1] * volt) - min(data_0[:, 1] * volt) scale_x = max(data_x[:, 1] * volt) - min(data_x[:, 1] * volt) squared_diff_0 = (data_0[:, 1] * volt - mon[0].v) ** 2 squared_diff_x = (data_x[:, 1] * volt - mon[999].v) ** 2 rel_RMS_0 = sqrt(mean(squared_diff_0)) / scale_0 rel_RMS_x = sqrt(mean(squared_diff_x)) / scale_x max_rel_0 = sqrt(max(squared_diff_0)) / scale_0 max_rel_x = sqrt(max(squared_diff_x)) / scale_x # RMS error should be < 0.1%, maximum error along the curve should be < 0.5% # Note that this is much stricter than the original Rallpack evaluation, but # with the 1us time step, the voltage traces are extremely similar assert 100 * rel_RMS_0 < 0.1 assert 100 * rel_RMS_x < 0.1 assert 100 * max_rel_0 < 0.5 assert 100 * max_rel_x < 0.5 @pytest.mark.standalone_compatible @numpy_needs_scipy def test_rall(): """ Test simulation of a cylinder plus two branches, with diameters according to Rall's formula """ BrianLogger.suppress_name("resolution_conflict") defaultclock.dt = 0.01 * ms # Passive channels gL = 1e-4 * siemens / cm**2 EL = -70 * mV # Morphology diameter = 1 * um length = 300 * um Cm = 1 * uF / cm**2 Ri = 150 * ohm * cm N = 500 rm = 1 / (gL * pi * diameter) # membrane resistance per unit length ra = (4 * Ri) / (pi * diameter**2) # axial resistance per unit length la = sqrt(rm / ra) # space length morpho = Cylinder(diameter=diameter, length=length, n=N) d1 = 0.5 * um L1 = 200 * um rm = 1 / (gL * pi * d1) # membrane resistance per unit length ra = (4 * Ri) / (pi * d1**2) # axial resistance per unit length l1 = sqrt(rm / ra) # space length morpho.L = Cylinder(diameter=d1, length=L1, n=N) d2 = (diameter**1.5 - d1**1.5) ** (1.0 / 1.5) rm = 1 / (gL * pi * d2) # membrane resistance per unit length ra = (4 * Ri) / (pi * d2**2) # axial resistance per unit length l2 = sqrt(rm / ra) # space length L2 = (L1 / l1) * l2 morpho.R = Cylinder(diameter=d2, length=L2, n=N) eqs = """ Im=gL*(EL-v) : amp/meter**2 I : amp (point current) """ neuron = SpatialNeuron(morphology=morpho, model=eqs, Cm=Cm, Ri=Ri) neuron.v = EL neuron.I[0] = 0.02 * nA # injecting at the left end run(100 * ms) # Check space constant calculation assert_allclose(la, neuron.space_constant[0]) assert_allclose(l1, neuron.L.space_constant[0]) assert_allclose(l2, neuron.R.space_constant[0]) # Theory x = neuron.main.distance ra = la * 4 * Ri / (pi * diameter**2) l = length / la + L1 / l1 theory = EL + ra * neuron.I[0] * cosh(l - x / la) / sinh(l) v = neuron.main.v assert_allclose(v - EL, theory - EL, atol=2e-6) x = neuron.L.distance theory = EL + ra * neuron.I[0] * cosh( l - neuron.main.distance[-1] / la - (x - neuron.main.distance[-1]) / l1 ) / sinh(l) v = neuron.L.v assert_allclose(v - EL, theory - EL, atol=2e-6) x = neuron.R.distance theory = EL + ra * neuron.I[0] * cosh( l - neuron.main.distance[-1] / la - (x - neuron.main.distance[-1]) / l2 ) / sinh(l) v = neuron.R.v assert_allclose(v - EL, theory - EL, atol=2e-6) @pytest.mark.standalone_compatible @numpy_needs_scipy def test_basic_diffusion(): # A very basic test that shows that propagation is working in a very basic # sense, testing all morphological classes defaultclock.dt = 0.01 * ms EL = -70 * mV gL = 1e-4 * siemens / cm**2 target = -10 * mV eqs = """ Im = gL*(EL-v) + gClamp*(target-v): amp/meter**2 gClamp : siemens/meter**2 """ morph = Soma(diameter=30 * um) morph.axon = Cylinder(n=10, diameter=10 * um, length=100 * um) morph.dend = Section( n=10, diameter=[10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0.1] * um, length=np.ones(10) * 10 * um, ) neuron = SpatialNeuron(morph, eqs) neuron.v = EL neuron.axon.gClamp[0] = 100 * siemens / cm**2 mon = StateMonitor(neuron, "v", record=True) run(0.25 * ms) assert all(abs(mon.v[:, -1] / mV + 10) < 0.25), mon.v[:, -1] / mV @pytest.mark.codegen_independent def test_allowed_integration(): morph = Soma(diameter=30 * um) EL = -70 * mV gL = 1e-4 * siemens / cm**2 ENa = 115 * mV gNa = 120 * msiemens / cm**2 VT = -50.4 * mV DeltaT = 2 * mV ENMDA = 0.0 * mV @check_units(voltage=volt, result=volt) def user_fun(voltage): return voltage # could be an arbitrary function and is therefore unsafe allowed_eqs = [ "Im = gL*(EL-v) : amp/meter**2", """ Im = gl * (El-v) + gNa * m**3 * h * (ENa-v) : amp/meter**2 dm/dt = alpham * (1-m) - betam * m : 1 dh/dt = alphah * (1-h) - betah * h : 1 alpham = (0.1/mV) * (-v+25*mV) / (exp((-v+25*mV) / (10*mV)) - 1)/ms : Hz betam = 4 * exp(-v/(18*mV))/ms : Hz alphah = 0.07 * exp(-v/(20*mV))/ms : Hz betah = 1/(exp((-v+30*mV) / (10*mV)) + 1)/ms : Hz """, """ Im = gl * (El-v) : amp/meter**2 I_ext = 1*nA + sin(2*pi*100*Hz*t)*nA : amp (point current) """, """ Im = I_leak + I_spike : amp/meter**2 I_leak = gL*(EL - v) : amp/meter**2 I_spike = gL*DeltaT*exp((v - VT)/DeltaT): amp/meter**2 (constant over dt) """, """ Im = gL*(EL-v) : amp/meter**2 I_NMDA = gNMDA*(ENMDA-v)*Mgblock : amp (point current) gNMDA : siemens Mgblock = 1./(1. + exp(-0.062*v/mV)/3.57) : 1 (constant over dt) """, "Im = gL*(EL - v) + gL*DeltaT*exp((v - VT)/DeltaT) : amp/meter**2", """ Im = I_leak + I_spike : amp/meter**2 I_leak = gL*(EL - v) : amp/meter**2 I_spike = gL*DeltaT*exp((v - VT)/DeltaT): amp/meter**2 """, """ Im = gL*(EL-v) : amp/meter**2 I_NMDA = gNMDA*(ENMDA-v)*Mgblock : amp (point current) gNMDA : siemens Mgblock = 1./(1. + exp(-0.062*v/mV)/3.57) : 1 """, ] forbidden_eqs = [ """Im = gl * (El-v + user_fun(v)) : amp/meter**2""", """Im = gl * clip(El-v, -100*mV, 100*mV) : amp/meter**2""", ] for eqs in allowed_eqs: # Should not raise an error neuron = SpatialNeuron(morph, eqs) for eqs in forbidden_eqs: # Should raise an error with pytest.raises(TypeError): SpatialNeuron(morph, eqs) @pytest.mark.codegen_independent def test_spatialneuron_indexing(): sec = Cylinder(length=50 * um, diameter=10 * um, n=1) sec.sec1 = Cylinder(length=50 * um, diameter=10 * um, n=2) sec.sec1.sec11 = Cylinder(length=50 * um, diameter=10 * um, n=4) sec.sec1.sec12 = Cylinder(length=50 * um, diameter=10 * um, n=8) sec.sec2 = Cylinder(length=50 * um, diameter=10 * um, n=16) sec.sec2.sec21 = Cylinder(length=50 * um, diameter=10 * um, n=32) neuron = SpatialNeuron(sec, "Im = 0*amp/meter**2 : amp/meter**2") neuron.v = "i*volt" # Accessing indices/variables of a subtree refers to the full subtree assert len(neuron.indices[:]) == 1 + 2 + 4 + 8 + 16 + 32 assert len(neuron.sec1.indices[:]) == 2 + 4 + 8 assert len(neuron.sec1.sec11.indices[:]) == 4 assert len(neuron.sec1.sec12.indices[:]) == 8 assert len(neuron.sec2.indices[:]) == 16 + 32 assert len(neuron.sec2.sec21.indices[:]) == 32 assert len(neuron.v[:]) == 1 + 2 + 4 + 8 + 16 + 32 assert len(neuron.sec1.v[:]) == 2 + 4 + 8 assert len(neuron.sec1.sec11.v[:]) == 4 assert len(neuron.sec1.sec12.v[:]) == 8 assert len(neuron.sec2.v[:]) == 16 + 32 assert len(neuron.sec2.sec21.v[:]) == 32 # Accessing indices/variables with ".main" only refers to the section assert len(neuron.main.indices[:]) == 1 assert len(neuron.sec1.main.indices[:]) == 2 assert len(neuron.sec1.sec11.main.indices[:]) == 4 assert len(neuron.sec1.sec12.main.indices[:]) == 8 assert len(neuron.sec2.main.indices[:]) == 16 assert len(neuron.sec2.sec21.main.indices[:]) == 32 assert len(neuron.main.v[:]) == 1 assert len(neuron.sec1.main.v[:]) == 2 assert len(neuron.sec1.sec11.main.v[:]) == 4 assert len(neuron.sec1.sec12.main.v[:]) == 8 assert len(neuron.sec2.main.v[:]) == 16 assert len(neuron.sec2.sec21.main.v[:]) == 32 # Accessing subgroups assert len(neuron[0].indices[:]) == 1 assert len(neuron[0 * um : 50 * um].indices[:]) == 1 assert len(neuron[0:1].indices[:]) == 1 assert len(neuron[sec.sec2.indices[:]]) == 16 assert len(neuron[sec.sec2]) == 16 assert_equal(neuron.sec1.sec11.v, [3, 4, 5, 6] * volt) assert_equal(neuron.sec1.sec11[1].v, neuron.sec1.sec11.v[1]) assert_equal(neuron.sec1.sec11[1:3].v, neuron.sec1.sec11.v[1:3]) assert_equal(neuron.sec1.sec11[1:3].v, [4, 5] * volt) @pytest.mark.codegen_independent def test_tree_index_consistency(): # Test all possible trees with depth 3 and a maximum of 3 branches subtree # (a total of 84 trees) # This tests whether the indices (i.e. where the compartments are placed in # the overall flattened 1D structure) make sense: for the `SpatialSubgroup` # mechanism to work correctly, each subtree has to have contiguous indices. # Separate subtrees should of course have non-overlapping indices. for tree_description in itertools.product( [1, 2, 3], # children of root [0, 1, 2, 3], # children of first branch [0, 1, 2, 3], # children of second branch [0, 1, 2, 3], # children of third branch ): sec = Cylinder(length=50 * um, diameter=10 * um, n=1) root_children = tree_description[0] if not all([tree_description[x] == 0 for x in range(root_children + 1, 4)]): # skip redundant descriptions (differing number of branches in a # subtree that does not exist) continue # Create a tree according to the description for idx in range(root_children): setattr( sec, f"sec{int(idx + 1)}", Cylinder(length=50 * um, diameter=10 * um, n=2 * (idx + 1)), ) for child in range(root_children): subsec = getattr(sec, f"sec{int(child + 1)}") subsec_children = tree_description[child + 1] for idx in range(subsec_children): setattr( subsec, f"sec{int(child + 1)}{int(idx + 1)}", Cylinder(length=50 * um, diameter=10 * um, n=1 + (child + 1) * idx), ) neuron = SpatialNeuron(sec, "Im = 0*amp/meter**2 : amp/meter**2") # Check the indicies for the full neuron: assert_equal(neuron.indices[:], np.arange(sec.total_compartments)) all_subsec_indices = [] for child in range(root_children): subsec = getattr(neuron, f"sec{int(child + 1)}") sub_indices = set(subsec.main.indices[:]) subsec_children = tree_description[child + 1] for idx in range(subsec_children): subsubsec = getattr(subsec, f"sec{int(child + 1)}{int(idx + 1)}") sub_indices |= set(subsubsec.main.indices[:]) # The indices for a full subtree should be the union of the indices # for all subsections within that subtree assert sub_indices == set(subsec.indices[:]) all_subsec_indices.extend(subsec.indices[:]) # Separate subtrees should not overlap assert len(all_subsec_indices) == len(set(all_subsec_indices)) @pytest.mark.codegen_independent def test_spatialneuron_subtree_assignment(): sec = Cylinder(length=50 * um, diameter=10 * um, n=2) sec.sec1 = Cylinder(length=50 * um, diameter=10 * um, n=2) sec.sec1.sec11 = Cylinder(length=50 * um, diameter=10 * um, n=2) sec.sec1.sec12 = Cylinder(length=50 * um, diameter=10 * um, n=2) sec.sec2 = Cylinder(length=50 * um, diameter=10 * um, n=2) sec.sec2.sec21 = Cylinder(length=50 * um, diameter=10 * um, n=2) neuron = SpatialNeuron(sec, "Im = 0*amp/meter**2 : amp/meter**2") neuron.v = 1 * volt assert_allclose(neuron.v[:], np.ones(12) * volt) neuron.sec1.v += 1 * volt assert_allclose(neuron.main.v[:], np.ones(2) * volt) assert_allclose(neuron.sec1.v[:], np.ones(6) * 2 * volt) assert_allclose(neuron.sec1.main.v[:], np.ones(2) * 2 * volt) assert_allclose(neuron.sec1.sec11.v[:], np.ones(2) * 2 * volt) assert_allclose(neuron.sec1.sec12.v[:], np.ones(2) * 2 * volt) assert_allclose(neuron.sec2.v[:], np.ones(4) * volt) neuron.sec2.v = 5 * volt assert_allclose(neuron.sec2.v[:], np.ones(4) * 5 * volt) assert_allclose(neuron.sec2.main.v[:], np.ones(2) * 5 * volt) assert_allclose(neuron.sec2.sec21.v[:], np.ones(2) * 5 * volt) @pytest.mark.codegen_independent def test_spatialneuron_morphology_assignment(): sec = Cylinder(length=50 * um, diameter=10 * um, n=2) sec.sec1 = Cylinder(length=50 * um, diameter=10 * um, n=2) sec.sec1.sec11 = Cylinder(length=50 * um, diameter=10 * um, n=2) sec.sec1.sec12 = Cylinder(length=50 * um, diameter=10 * um, n=2) sec.sec2 = Cylinder(length=50 * um, diameter=10 * um, n=2) sec.sec2.sec21 = Cylinder(length=50 * um, diameter=10 * um, n=2) neuron = SpatialNeuron(sec, "Im = 0*amp/meter**2 : amp/meter**2") neuron.v[sec.sec1.sec11] = 1 * volt assert_allclose(neuron.sec1.sec11.v[:], np.ones(2) * volt) assert_allclose(neuron.sec1.sec12.v[:], np.zeros(2) * volt) assert_allclose(neuron.sec1.main.v[:], np.zeros(2) * volt) assert_allclose(neuron.main.v[:], np.zeros(2) * volt) assert_allclose(neuron.sec2.v[:], np.zeros(4) * volt) neuron.v[sec.sec2[25 * um :]] = 2 * volt neuron.v[sec.sec1[: 25 * um]] = 3 * volt assert_allclose(neuron.main.v[:], np.zeros(2) * volt) assert_allclose(neuron.sec2.main.v[:], [0, 2] * volt) assert_allclose(neuron.sec2.sec21.v[:], np.zeros(2) * volt) assert_allclose(neuron.sec1.main.v[:], [3, 0] * volt) assert_allclose(neuron.sec1.sec11.v[:], np.ones(2) * volt) assert_allclose(neuron.sec1.sec12.v[:], np.zeros(2) * volt) @pytest.mark.standalone_compatible @pytest.mark.multiple_runs @numpy_needs_scipy def test_spatialneuron_capacitive_currents(): if prefs.core.default_float_dtype is np.float32: pytest.skip("Need double precision for this test") defaultclock.dt = 0.1 * ms morpho = Cylinder(x=[0, 10] * cm, diameter=2 * 238 * um, n=200, type="axon") El = 10.613 * mV ENa = 115 * mV EK = -12 * mV gl = 0.3 * msiemens / cm**2 gNa0 = 120 * msiemens / cm**2 gK = 36 * msiemens / cm**2 # Typical equations eqs = """ # The same equations for the whole neuron, but possibly different parameter values # distributed transmembrane current Im = gl * (El-v) + gNa * m**3 * h * (ENa-v) + gK * n**4 * (EK-v) : amp/meter**2 I : amp (point current) # applied current dm/dt = alpham * (1-m) - betam * m : 1 dn/dt = alphan * (1-n) - betan * n : 1 dh/dt = alphah * (1-h) - betah * h : 1 alpham = (0.1/mV) * (-v+25*mV) / (exp((-v+25*mV) / (10*mV)) - 1)/ms : Hz betam = 4 * exp(-v/(18*mV))/ms : Hz alphah = 0.07 * exp(-v/(20*mV))/ms : Hz betah = 1/(exp((-v+30*mV) / (10*mV)) + 1)/ms : Hz alphan = (0.01/mV) * (-v+10*mV) / (exp((-v+10*mV) / (10*mV)) - 1)/ms : Hz betan = 0.125*exp(-v/(80*mV))/ms : Hz gNa : siemens/meter**2 """ neuron = SpatialNeuron( morphology=morpho, model=eqs, Cm=1 * uF / cm**2, Ri=35.4 * ohm * cm, method="exponential_euler", ) mon = StateMonitor(neuron, ["Im", "Ic"], record=True, when="end") run(10 * ms) neuron.I[0] = 1 * uA # current injection at one end run(3 * ms) neuron.I = 0 * amp run(10 * ms) device.build(direct_call=False, **device.build_options) assert_allclose( (mon.Im - mon.Ic).sum(axis=0) / (mA / cm**2), np.zeros(230), atol=1e6 ) @pytest.mark.codegen_independent def test_point_current(): soma = Soma(10 * um) eqs = """Im = 0*nA/cm**2 : amp/meter**2 I1 = 1*nA : amp (point current) I2 = 1*nA : amp (point current, constant over dt)""" neuron = SpatialNeuron(soma, eqs) assert "I1/area" in neuron.equations["Im"].expr.code assert "I2/area" in neuron.equations["Im"].expr.code # see issue #1160 @pytest.mark.standalone_compatible @pytest.mark.multiple_runs @numpy_needs_scipy def test_spatialneuron_threshold_location(): morpho = Soma(10 * um) morpho.axon = Cylinder(1 * um, n=2, length=20 * um) model = """ Im = 0*nA/cm**2 : amp/meter**2 should_spike : boolean (constant) """ neuron = SpatialNeuron( morpho, model, threshold_location=morpho.axon[15 * um], threshold="should_spike" ) # Different variants that should do the same thing neuron2 = SpatialNeuron( morpho, model, threshold_location=morpho.axon.indices[15 * um], threshold="should_spike", ) neuron3 = SpatialNeuron( morpho, model, threshold_location=2, threshold="should_spike" ) # Cannot use multiple compartments with pytest.raises(AttributeError): SpatialNeuron( morpho, model, threshold_location=[2, 3], threshold="should_spike" ) with pytest.raises(AttributeError): SpatialNeuron( morpho, model, threshold_location=morpho.axon[5 * um : 15 * um], threshold="should_spike", ) neurons = [neuron, neuron2, neuron3] monitors = [SpikeMonitor(n) for n in neurons] net = Network(neurons, monitors) for n in neurons: n.should_spike = True # all compartments want to spike net.run(defaultclock.dt) for n in neurons: n.should_spike = False # no compartment wants to spike net.run(defaultclock.dt) for n in neurons: n.should_spike = [False, False, True] net.run(defaultclock.dt) for n in neurons: n.should_spike = [True, True, False] net.run(defaultclock.dt) device.build(direct_call=False, **device.build_options) for mon in monitors: assert len(mon.i) == 2 assert all(mon.i == 2) assert_allclose(mon.t, [0 * ms, 2 * defaultclock.dt]) @pytest.mark.standalone_compatible @numpy_needs_scipy def test_spatialneuron_timedarray(): # See GitHub issue 1427 ta = TimedArray([0, 1] * nA, dt=1 * ms) morpho = Soma(diameter=10 * um) neuron = SpatialNeuron(morpho, "Im = ta(t)/area : amp/meter**2", method="euler") mon = StateMonitor(neuron, "v", record=0, when="after_groups") run(2 * ms) assert_allclose( np.diff(mon.v_[0]), np.r_[ np.zeros(9), np.array( np.ones(10) * 1 * nA / neuron.area[0] / neuron.Cm * defaultclock.dt ), ], ) if __name__ == "__main__": test_custom_events() test_construction() test_construction_coordinates() test_infinitecable() test_finitecable() test_rallpack1() test_rallpack2() test_rallpack3() test_rall() test_basic_diffusion() test_allowed_integration() test_spatialneuron_indexing() test_tree_index_consistency() test_spatialneuron_subtree_assignment() test_spatialneuron_morphology_assignment() test_spatialneuron_capacitive_currents() test_spatialneuron_timedarray() brian2-2.5.4/brian2/tests/test_spikegenerator.py000066400000000000000000000362601445201106100216520ustar00rootroot00000000000000""" Tests for `SpikeGeneratorGroup` """ import os import tempfile import pytest from numpy.testing import assert_array_equal, assert_equal from brian2 import * from brian2.core.network import schedule_propagation_offset from brian2.devices.device import reinit_and_delete from brian2.tests.utils import assert_allclose, exc_isinstance from brian2.utils.logger import catch_logs @pytest.mark.standalone_compatible def test_spikegenerator_connected(): """ Test that `SpikeGeneratorGroup` connects properly. """ G = NeuronGroup(10, "v:1") mon = StateMonitor(G, "v", record=True, when="end") indices = np.array([3, 2, 1, 1, 4, 5]) times = np.array([6, 5, 4, 3, 3, 1]) * ms SG = SpikeGeneratorGroup(10, indices, times) S = Synapses(SG, G, on_pre="v+=1") S.connect(j="i") run(7 * ms) # The following neurons should not receive any spikes for idx in [0, 6, 7, 8, 9]: assert all(mon[idx].v == 0) offset = schedule_propagation_offset() # The following neurons should receive a single spike for idx, time in zip([2, 3, 4, 5], [5, 6, 3, 1] * ms): assert all(mon[idx].v[mon.t < time + offset] == 0) assert all(mon[idx].v[mon.t >= time + offset] == 1) # This neuron receives two spikes assert all(mon[1].v[mon.t < 3 * ms + offset] == 0) assert all(mon[1].v[(mon.t >= 3 * ms + offset) & (mon.t < 4 * ms + offset)] == 1) assert all(mon[1].v[(mon.t >= 4 * ms + offset)] == 2) @pytest.mark.standalone_compatible def test_spikegenerator_basic(): """ Basic test for `SpikeGeneratorGroup`. """ indices = np.array([3, 2, 1, 1, 2, 3, 3, 2, 1]) times = np.array([1, 4, 4, 3, 2, 4, 2, 3, 2]) * ms SG = SpikeGeneratorGroup(5, indices, times) s_mon = SpikeMonitor(SG) run(5 * ms) _compare_spikes(5, indices, times, s_mon) @pytest.mark.standalone_compatible def test_spikegenerator_basic_sorted(): """ Basic test for `SpikeGeneratorGroup` with already sorted spike events. """ indices = np.array([3, 1, 2, 3, 1, 2, 1, 2, 3]) times = np.array([1, 2, 2, 2, 3, 3, 4, 4, 4]) * ms SG = SpikeGeneratorGroup(5, indices, times) s_mon = SpikeMonitor(SG) run(5 * ms) _compare_spikes(5, indices, times, s_mon) @pytest.mark.standalone_compatible def test_spikegenerator_basic_sorted_with_sorted(): """ Basic test for `SpikeGeneratorGroup` with already sorted spike events. """ indices = np.array([3, 1, 2, 3, 1, 2, 1, 2, 3]) times = np.array([1, 2, 2, 2, 3, 3, 4, 4, 4]) * ms SG = SpikeGeneratorGroup(5, indices, times, sorted=True) s_mon = SpikeMonitor(SG) run(5 * ms) _compare_spikes(5, indices, times, s_mon) @pytest.mark.standalone_compatible def test_spikegenerator_period(): """ Basic test for `SpikeGeneratorGroup`. """ indices = np.array([3, 2, 1, 1, 2, 3, 3, 2, 1]) times = np.array([1, 4, 4, 3, 2, 4, 2, 3, 2]) * ms SG = SpikeGeneratorGroup(5, indices, times, period=5 * ms) s_mon = SpikeMonitor(SG) run(10 * ms) for idx in range(5): generator_spikes = sorted( [(idx, time) for time in times[indices == idx]] + [(idx, time + 5 * ms) for time in times[indices == idx]] ) recorded_spikes = sorted([(idx, time) for time in s_mon.t[s_mon.i == idx]]) assert_allclose(generator_spikes, recorded_spikes) @pytest.mark.codegen_independent def test_spikegenerator_extreme_period(): """ Basic test for `SpikeGeneratorGroup`. """ indices = np.array([0, 1, 2]) times = np.array([0, 1, 2]) * ms SG = SpikeGeneratorGroup(5, indices, times, period=1e6 * second) s_mon = SpikeMonitor(SG) with catch_logs() as l: run(10 * ms) assert_equal(s_mon.i, np.array([0, 1, 2])) assert_allclose(s_mon.t, [0, 1, 2] * ms) assert len(l) == 1 and l[0][1].endswith("spikegenerator_long_period") @pytest.mark.standalone_compatible def test_spikegenerator_period_rounding(): # See discussion in PR #1042 # The last spike will be considered to be in the time step *after* 1s, due # to the way our rounding works. Although probably not what the user # expects, this should therefore raise an error. In previous versions of # Brian, this did not raise any error but silently discarded the spike. with pytest.raises(ValueError): SpikeGeneratorGroup( 1, [0, 0, 0], [0 * ms, 0.9 * ms, 0.99999 * ms], period=1 * ms, dt=0.1 * ms ) # This should also raise a ValueError, since the last two spikes fall into # the same bin s = SpikeGeneratorGroup( 1, [0, 0, 0], [0 * ms, 0.9 * ms, 0.96 * ms], period=1 * ms, dt=0.1 * ms ) net = Network(s) with pytest.raises(BrianObjectException) as exc: net.run(0 * ms) assert exc_isinstance(exc, ValueError) def test_spikegenerator_period_repeat(): """ Basic test for `SpikeGeneratorGroup`. """ indices = np.zeros(10) times = arange(0, 1, 0.1) * ms rec = np.rec.fromarrays([times, indices], names=["t", "i"]) rec.sort() sorted_times = np.ascontiguousarray(rec.t) * 1000 sorted_indices = np.ascontiguousarray(rec.i) SG = SpikeGeneratorGroup(1, indices, times, period=1 * ms) s_mon = SpikeMonitor(SG) net = Network(SG, s_mon) rate = PopulationRateMonitor(SG) for idx in range(5): net.run(1 * ms) assert (idx + 1) * len(SG.spike_time) == s_mon.num_spikes def _compare_spikes( N, indices, times, recorded, start_time=0 * ms, end_time=1e100 * second ): for idx in range(N): generator_spikes = sorted([(idx, time) for time in times[indices == idx]]) recorded_spikes = sorted( [ (idx, time) for time in recorded.t[recorded.i == idx] if time >= start_time and time < end_time ] ) assert_allclose(generator_spikes, recorded_spikes) @pytest.mark.standalone_compatible @pytest.mark.multiple_runs def test_spikegenerator_change_spikes(): indices1 = np.array([3, 2, 1, 1, 2, 3, 3, 2, 1]) times1 = np.array([1, 4, 4, 3, 2, 4, 2, 3, 2]) * ms SG = SpikeGeneratorGroup(5, indices1, times1) s_mon = SpikeMonitor(SG) net = Network(SG, s_mon) net.run(5 * ms) indices2 = np.array([3, 2, 1, 1, 2, 3, 3, 2, 1, 3, 3, 3, 1, 2]) times2 = ( np.array([1, 4, 4, 3, 2, 4, 2, 3, 2, 4.5, 4.7, 4.8, 4.5, 4.7]) * ms + 5 * ms ) SG.set_spikes(indices2, times2) net.run(5 * ms) indices3 = np.array([4, 1, 0]) times3 = np.array([1, 3, 4]) * ms + 10 * ms SG.set_spikes(indices3, times3) net.run(5 * ms) device.build(direct_call=False, **device.build_options) _compare_spikes(5, indices1, times1, s_mon, 0 * ms, 5 * ms) _compare_spikes(5, indices2, times2, s_mon, 5 * ms, 10 * ms) _compare_spikes(5, indices3, times3, s_mon, 10 * ms) @pytest.mark.standalone_compatible @pytest.mark.multiple_runs def test_spikegenerator_change_period(): """ Basic test for `SpikeGeneratorGroup`. """ indices1 = np.array([3, 2, 1, 1, 2, 3, 3, 2, 1]) times1 = np.array([1, 4, 4, 3, 2, 4, 2, 3, 2]) * ms SG = SpikeGeneratorGroup(5, indices1, times1, period=5 * ms) s_mon = SpikeMonitor(SG) net = Network(SG, s_mon) net.run(10 * ms) indices2 = np.array([3, 2, 1, 1, 2, 3, 3, 2, 1, 3, 3, 3, 1, 2]) times2 = ( np.array([1, 4, 4, 3, 2, 4, 2, 3, 2, 4.5, 4.7, 4.8, 4.5, 4.7]) * ms + 10 * ms ) SG.set_spikes(indices2, times2) net.run(10 * ms) # period should no longer be in effect device.build(direct_call=False, **device.build_options) _compare_spikes( 5, np.hstack([indices1, indices1]), np.hstack([times1, times1 + 5 * ms]), s_mon, 0 * ms, 10 * ms, ) _compare_spikes(5, indices2, times2, s_mon, 10 * ms) @pytest.mark.codegen_independent def test_spikegenerator_incorrect_values(): with pytest.raises(TypeError): SpikeGeneratorGroup(0, [], [] * second) # Floating point value for N with pytest.raises(TypeError): SpikeGeneratorGroup(1.5, [], [] * second) # Negative index with pytest.raises(ValueError): SpikeGeneratorGroup(5, [0, 3, -1], [0, 1, 2] * ms) # Too high index with pytest.raises(ValueError): SpikeGeneratorGroup(5, [0, 5, 1], [0, 1, 2] * ms) # Negative time with pytest.raises(ValueError): SpikeGeneratorGroup(5, [0, 1, 2], [0, -1, 2] * ms) @pytest.mark.codegen_independent def test_spikegenerator_incorrect_period(): """ Test that you cannot provide incorrect period arguments or combine inconsistent period and dt arguments. """ # Period is negative with pytest.raises(ValueError): SpikeGeneratorGroup(1, [], [] * second, period=-1 * ms) # Period is smaller than the highest spike time with pytest.raises(ValueError): SpikeGeneratorGroup(1, [0], [2] * ms, period=1 * ms) # Period is not an integer multiple of dt SG = SpikeGeneratorGroup(1, [], [] * second, period=1.25 * ms, dt=0.1 * ms) net = Network(SG) with pytest.raises(BrianObjectException) as exc: net.run(0 * ms) assert exc_isinstance(exc, NotImplementedError) SG = SpikeGeneratorGroup(1, [], [] * second, period=0.101 * ms, dt=0.1 * ms) net = Network(SG) with pytest.raises(BrianObjectException) as exc: net.run(0 * ms) assert exc_isinstance(exc, NotImplementedError) SG = SpikeGeneratorGroup(1, [], [] * second, period=3.333 * ms, dt=0.1 * ms) net = Network(SG) with pytest.raises(BrianObjectException) as exc: net.run(0 * ms) assert exc_isinstance(exc, NotImplementedError) # This should not raise an error (see #1041) SG = SpikeGeneratorGroup(1, [], [] * ms, period=150 * ms, dt=0.1 * ms) net = Network(SG) net.run(0 * ms) # Period is smaller than dt SG = SpikeGeneratorGroup(1, [], [] * second, period=1 * ms, dt=2 * ms) net = Network(SG) with pytest.raises(BrianObjectException) as exc: net.run(0 * ms) assert exc_isinstance(exc, ValueError) def test_spikegenerator_rounding(): # all spikes should fall into the first time bin indices = np.arange(100) times = np.linspace(0, 0.1, 100, endpoint=False) * ms SG = SpikeGeneratorGroup(100, indices, times, dt=0.1 * ms) mon = SpikeMonitor(SG) net = Network(SG, mon) net.run(0.1 * ms) assert_equal(mon.count, np.ones(100)) # all spikes should fall in separate bins dt = 0.1 * ms indices = np.zeros(10000) times = np.arange(10000) * dt SG = SpikeGeneratorGroup(1, indices, times, dt=dt) target = NeuronGroup( 1, "count : 1", threshold="True", reset="count=0" ) # set count to zero at every time step syn = Synapses(SG, target, on_pre="count+=1") syn.connect() mon = StateMonitor(target, "count", record=0, when="end") net = Network(SG, target, syn, mon) # change the schedule so that resets are processed before synapses net.schedule = ["start", "groups", "thresholds", "resets", "synapses", "end"] net.run(10000 * dt) assert_equal(mon[0].count, np.ones(10000)) @pytest.mark.standalone_compatible @pytest.mark.long def test_spikegenerator_rounding_long(): # all spikes should fall in separate bins dt = 0.1 * ms N = 1000000 indices = np.zeros(N) times = np.arange(N) * dt SG = SpikeGeneratorGroup(1, indices, times, dt=dt) target = NeuronGroup(1, "count : 1") syn = Synapses(SG, target, on_pre="count+=1") syn.connect() spikes = SpikeMonitor(SG) mon = StateMonitor(target, "count", record=0, when="end") run(N * dt, report="text") assert spikes.count[0] == N, f"expected {int(N)} spikes, got {int(spikes.count[0])}" assert all(np.diff(mon[0].count[:]) == 1) @pytest.mark.standalone_compatible @pytest.mark.long def test_spikegenerator_rounding_period(): # all spikes should fall in separate bins dt = 0.1 * ms N = 100 repeats = 10000 indices = np.zeros(N) times = np.arange(N) * dt SG = SpikeGeneratorGroup(1, indices, times, dt=dt, period=N * dt) target = NeuronGroup(1, "count : 1") syn = Synapses(SG, target, on_pre="count+=1") syn.connect() spikes = SpikeMonitor(SG) mon = StateMonitor(target, "count", record=0, when="end") run(N * repeats * dt, report="text") # print np.int_(np.round(spikes.t/dt)) assert_equal(spikes.count[0], N * repeats) assert all(np.diff(mon[0].count[:]) == 1) @pytest.mark.codegen_independent def test_spikegenerator_multiple_spikes_per_bin(): # Multiple spikes per bin are of course fine if they don't belong to the # same neuron SG = SpikeGeneratorGroup(2, [0, 1], [0, 0.05] * ms, dt=0.1 * ms) net = Network(SG) net.run(0 * ms) # This should raise an error SG = SpikeGeneratorGroup(2, [0, 0], [0, 0.05] * ms, dt=0.1 * ms) net = Network(SG) with pytest.raises(BrianObjectException) as exc: net.run(0 * ms) print(exc.value.__cause__) assert exc_isinstance(exc, ValueError) # More complicated scenario where dt changes between runs defaultclock.dt = 0.1 * ms SG = SpikeGeneratorGroup(2, [0, 0], [0.05, 0.15] * ms) net = Network(SG) net.run(0 * ms) # all is fine defaultclock.dt = 0.2 * ms # Now the two spikes fall into the same bin with pytest.raises(BrianObjectException) as exc: net.run(0 * ms) assert exc_isinstance(exc, ValueError) @pytest.mark.standalone_compatible @pytest.mark.multiple_runs def test_spikegenerator_multiple_runs(): indices = np.zeros(5) times = np.arange(5) * ms spike_gen = SpikeGeneratorGroup(1, indices, times) # all good spike_mon = SpikeMonitor(spike_gen) run(5 * ms) # Setting the same spike times again should not do anything, since they are # before the start of the current simulation spike_gen.set_spikes(indices, times) # however, a warning should be raised with catch_logs() as l: run(5 * ms) device.build(direct_call=False, **device.build_options) assert len(l) == 1 and l[0][1].endswith("ignored_spikes") assert spike_mon.num_spikes == 5 def test_spikegenerator_restore(): # Check whether SpikeGeneratorGroup works with store/restore # See github issue #1084 gen = SpikeGeneratorGroup(1, [0, 0, 0], [0, 1, 2] * ms) mon = SpikeMonitor(gen) store() run(3 * ms) assert_array_equal(mon.i, [0, 0, 0]) assert_allclose(mon.t, [0, 1, 2] * ms) restore() run(3 * ms) assert_array_equal(mon.i, [0, 0, 0]) assert_allclose(mon.t, [0, 1, 2] * ms) if __name__ == "__main__": import time start = time.time() test_spikegenerator_connected() test_spikegenerator_basic() test_spikegenerator_basic_sorted() test_spikegenerator_basic_sorted_with_sorted() test_spikegenerator_period() test_spikegenerator_period_rounding() test_spikegenerator_extreme_period() test_spikegenerator_period_repeat() test_spikegenerator_change_spikes() test_spikegenerator_change_period() test_spikegenerator_incorrect_values() test_spikegenerator_incorrect_period() test_spikegenerator_rounding() test_spikegenerator_rounding_long() test_spikegenerator_rounding_period() test_spikegenerator_multiple_spikes_per_bin() test_spikegenerator_multiple_runs() test_spikegenerator_restore() print("Tests took", time.time() - start) brian2-2.5.4/brian2/tests/test_spikequeue.py000066400000000000000000000036531445201106100210100ustar00rootroot00000000000000import numpy as np import pytest from numpy.testing import assert_equal from brian2.memory.dynamicarray import DynamicArray1D from brian2.synapses.spikequeue import SpikeQueue from brian2.units.stdunits import ms def create_all_to_all(N, dt): """ Return a tuple containing `synapses` and `delays` in the form that is needed for the `SpikeQueue` initializer. Every synapse has a delay depending on the presynaptic neuron. """ data = np.repeat(np.arange(N, dtype=np.int32), N) delays = DynamicArray1D(data.shape, dtype=np.float64) delays[:] = data * dt synapses = data return synapses, delays def create_one_to_one(N, dt): """ Return a tuple containing `synapses` and `delays` in the form that is needed for the `SpikeQueue` initializer. Every synapse has a delay depending on the presynaptic neuron. """ data = np.arange(N, dtype=np.int32) delays = DynamicArray1D(data.shape, dtype=np.float64) delays[:] = data * dt synapses = data return synapses, delays @pytest.mark.codegen_independent def test_spikequeue(): N = 100 dt = float(0.1 * ms) synapses, delays = create_one_to_one(N, dt) queue = SpikeQueue(source_start=0, source_end=N) queue.prepare(delays[:], dt, synapses) queue.push(np.arange(N, dtype=np.int32)) for i in range(N): assert_equal(queue.peek(), np.array([i])) queue.advance() for i in range(N): assert_equal(queue.peek(), np.array([])) queue.advance() synapses, delays = create_all_to_all(N, dt) queue = SpikeQueue(source_start=0, source_end=N) queue.prepare(delays[:], dt, synapses) queue.push(np.arange(N * N, dtype=np.int32)) for i in range(N): assert_equal(queue.peek(), i * N + np.arange(N)) queue.advance() for i in range(N): assert_equal(queue.peek(), np.array([])) queue.advance() if __name__ == "__main__": test_spikequeue() brian2-2.5.4/brian2/tests/test_stateupdaters.py000066400000000000000000001007271445201106100215200ustar00rootroot00000000000000import logging import re import pytest from numpy.testing import assert_equal from brian2 import * from brian2.core.variables import ArrayVariable, Constant, Variable from brian2.stateupdaters.base import UnsupportedEquationsException from brian2.tests.utils import assert_allclose, exc_isinstance from brian2.utils.logger import catch_logs @pytest.mark.codegen_independent def test_explicit_stateupdater_parsing(): """ Test the parsing of explicit state updater descriptions. """ # These are valid descriptions and should not raise errors updater = ExplicitStateUpdater("x_new = x + dt * f(x, t)") updater(Equations("dv/dt = -v / tau : 1")) updater = ExplicitStateUpdater( """ x2 = x + dt * f(x, t) x_new = x2 """ ) updater(Equations("dv/dt = -v / tau : 1")) updater = ExplicitStateUpdater( """ x1 = g(x, t) * dW x2 = x + dt * f(x, t) x_new = x1 + x2 """, stochastic="multiplicative", ) updater(Equations("dv/dt = -v / tau + v * xi * tau**-.5: 1")) updater = ExplicitStateUpdater( """ x_support = x + dt*f(x, t) + dt**.5 * g(x, t) g_support = g(x_support, t) k = 1/(2*dt**.5)*(g_support - g(x, t))*(dW**2) x_new = x + dt*f(x,t) + g(x, t) * dW + k """, stochastic="multiplicative", ) updater(Equations("dv/dt = -v / tau + v * xi * tau**-.5: 1")) # Examples of failed parsing # No x_new = ... statement with pytest.raises(SyntaxError): ExplicitStateUpdater("x = x + dt * f(x, t)") # Not an assigment with pytest.raises(SyntaxError): ExplicitStateUpdater( """ 2 * x x_new = x + dt * f(x, t) """ ) # doesn't separate into stochastic and non-stochastic part updater = ExplicitStateUpdater("x_new = x + dt * f(x, t) * g(x, t) * dW") with pytest.raises(ValueError): updater(Equations("")) @pytest.mark.codegen_independent def test_non_autonomous_equations(): # Check that non-autonmous equations are handled correctly in multi-step # updates updater = ExplicitStateUpdater("x_new = f(x, t + 0.5*dt)") update_step = updater(Equations("dv/dt = t : 1")) # Not a valid equation but... # very crude test assert "0.5*dt" in update_step @pytest.mark.codegen_independent def test_str_repr(): """ Assure that __str__ and __repr__ do not raise errors """ for integrator in [linear, euler, rk2, rk4]: assert len(str(integrator)) assert len(repr(integrator)) @pytest.mark.codegen_independent def test_multiple_noise_variables_basic(): # Very basic test, only to make sure that stochastic state updaters handle # multiple noise variables at all eqs = Equations( """ dv/dt = -v / (10*ms) + xi_1 * ms ** -.5 : 1 dw/dt = -w / (10*ms) + xi_2 * ms ** -.5 : 1 """ ) for method in [euler, heun, milstein]: code = method(eqs, {}) assert "xi_1" in code assert "xi_2" in code def test_multiple_noise_variables_extended(): # Some actual simulations with multiple noise variables eqs = """ dx/dt = y : 1 dy/dt = - 1*ms**-1*y - 40*ms**-2*x : Hz """ all_eqs_noise = [ """ dx/dt = y : 1 dy/dt = noise_factor*ms**-1.5*xi_1 + noise_factor*ms**-1.5*xi_2 - 1*ms**-1*y - 40*ms**-2*x : Hz """, """ dx/dt = y + noise_factor*ms**-0.5*xi_1: 1 dy/dt = noise_factor*ms**-1.5*xi_2 - 1*ms**-1*y - 40*ms**-2*x : Hz """, ] G = NeuronGroup(2, eqs, method="euler") G.x = [0.5, 1] G.y = [0, 0.5] * Hz mon = StateMonitor(G, ["x", "y"], record=True) net = Network(G, mon) net.run(10 * ms) no_noise_x, no_noise_y = mon.x[:], mon.y[:] for eqs_noise in all_eqs_noise: for method_name, method in [("euler", euler), ("heun", heun)]: with catch_logs("WARNING"): G = NeuronGroup(2, eqs_noise, method=method) G.x = [0.5, 1] G.y = [0, 0.5] * Hz mon = StateMonitor(G, ["x", "y"], record=True) net = Network(G, mon) # We run it deterministically, but still we'd detect major errors (e.g. # non-stochastic terms that are added twice, see #330 net.run(10 * ms, namespace={"noise_factor": 0.0}) assert_allclose( mon.x[:], no_noise_x, err_msg=f"Method {method_name} gave incorrect results", ) assert_allclose( mon.y[:], no_noise_y, err_msg=f"Method {method_name} gave incorrect results", ) def test_multiple_noise_variables_deterministic_noise(fake_randn_randn_fixture): all_eqs = [ """ dx/dt = y : 1 dy/dt = -y / (10*ms) + dt**-.5*0.5*ms**-1.5 + dt**-.5*0.5*ms**-1.5: Hz """, """ dx/dt = y + dt**-.5*0.5*ms**-0.5: 1 dy/dt = -y / (10*ms) + dt**-.5*0.5 * ms**-1.5 : Hz """, ] all_eqs_noise = [ """ dx/dt = y : 1 dy/dt = -y / (10*ms) + xi_1 * ms**-1.5 + xi_2 * ms**-1.5: Hz """, """ dx/dt = y + xi_1*ms**-0.5: 1 dy/dt = -y / (10*ms) + xi_2 * ms**-1.5 : Hz """, ] for eqs, eqs_noise in zip(all_eqs, all_eqs_noise): G = NeuronGroup(2, eqs, method="euler") G.x = [5, 17] G.y = [25, 5] * Hz mon = StateMonitor(G, ["x", "y"], record=True) net = Network(G, mon) net.run(10 * ms) no_noise_x, no_noise_y = mon.x[:], mon.y[:] for method_name, method in [("euler", euler), ("heun", heun)]: with catch_logs("WARNING"): G = NeuronGroup(2, eqs_noise, method=method) G.x = [5, 17] G.y = [25, 5] * Hz mon = StateMonitor(G, ["x", "y"], record=True) net = Network(G, mon) net.run(10 * ms) assert_allclose( mon.x[:], no_noise_x, err_msg=f"Method {method_name} gave incorrect results", ) assert_allclose( mon.y[:], no_noise_y, err_msg=f"Method {method_name} gave incorrect results", ) @pytest.mark.codegen_independent def test_multiplicative_noise(): # Noise is not multiplicative (constant over time step) ta = TimedArray([0, 1], dt=defaultclock.dt * 10) Eq = Equations("dv/dt = ta(t)*xi*(5*ms)**-0.5 :1") group = NeuronGroup(1, Eq, method="euler") net = Network(group) net.run(0 * ms) # no error # Noise is multiplicative (multiplied with time-varying variable) Eq1 = Equations("dv/dt = v*xi*(5*ms)**-0.5 :1") group1 = NeuronGroup(1, Eq1, method="euler") net1 = Network(group1) with pytest.raises(BrianObjectException) as exc: net1.run(0 * ms) assert exc_isinstance(exc, UnsupportedEquationsException) # Noise is multiplicative (multiplied with time) Eq2 = Equations("dv/dt = (t/ms)*xi*(5*ms)**-0.5 :1") group2 = NeuronGroup(1, Eq2, method="euler") net2 = Network(group2) with pytest.raises(BrianObjectException) as exc: net2.run(0 * ms) assert exc_isinstance(exc, UnsupportedEquationsException) # Noise is multiplicative (multiplied with time-varying variable) Eq3 = Equations( """ dv/dt = w*xi*(5*ms)**-0.5 :1 dw/dt = -w/(10*ms) : 1 """ ) group3 = NeuronGroup(1, Eq3, method="euler") net3 = Network(group3) with pytest.raises(BrianObjectException) as exc: net3.run(0 * ms) assert exc_isinstance(exc, UnsupportedEquationsException) # One of the equations has multiplicative noise Eq4 = Equations( """ dv/dt = xi_1*(5*ms)**-0.5 : 1 dw/dt = (t/ms)*xi_2*(5*ms)**-0.5 :1 """ ) group4 = NeuronGroup(1, Eq4, method="euler") net4 = Network(group4) with pytest.raises(BrianObjectException) as exc: net4.run(0 * ms) assert exc_isinstance(exc, UnsupportedEquationsException) # One of the equations has multiplicative noise Eq5 = Equations( """ dv/dt = xi_1*(5*ms)**-0.5 : 1 dw/dt = v*xi_2*(5*ms)**-0.5 :1 """ ) group5 = NeuronGroup(1, Eq5, method="euler") net5 = Network(group4) with pytest.raises(BrianObjectException) as exc: net5.run(0 * ms) assert exc_isinstance(exc, UnsupportedEquationsException) def test_pure_noise_deterministic(fake_randn_randn_fixture): sigma = 3.0 eqs = Equations("dx/dt = sigma*xi/sqrt(ms) : 1") dt = 0.1 * ms for method in ["euler", "heun", "milstein"]: G = NeuronGroup(1, eqs, dt=dt, method=method) run(10 * dt) assert_allclose( G.x, sqrt(dt) * sigma * 0.5 / sqrt(1 * ms) * 10, err_msg=f"method {method} did not give the expected result", ) @pytest.mark.codegen_independent def test_temporary_variables(): """ Make sure that the code does the distinction between temporary variables in the state updater description and external variables used in the equations. """ # Use a variable name that is used in the state updater description k_2 = 5 eqs = Equations("dv/dt = -(v + k_2)/(10*ms) : 1") converted = rk4(eqs) # Use a non-problematic name k_var = 5 eqs = Equations("dv/dt = -(v + k_var)/(10*ms) : 1") converted2 = rk4(eqs) # Make sure that the two formulations result in the same code assert converted == converted2.replace("k_var", "k_2") @pytest.mark.codegen_independent def test_temporary_variables2(): """ Make sure that the code does the distinction between temporary variables in the state updater description and external variables used in the equations. """ tau = 10 * ms # Use a variable name that is used in the state updater description k = 5 eqs = Equations("dv/dt = -v/tau + k*xi*tau**-0.5: 1") converted = milstein(eqs) # Use a non-problematic name k_var = 5 eqs = Equations("dv/dt = -v/tau + k_var*xi*tau**-0.5: 1") converted2 = milstein(eqs) # Make sure that the two formulations result in the same code assert converted == converted2.replace("k_var", "k") @pytest.mark.codegen_independent def test_integrator_code(): """ Check whether the returned abstract code is as expected. """ # A very simple example where the abstract code should always look the same eqs = Equations("dv/dt = -v / (1 * second) : 1") # Only test very basic stuff (expected number of lines and last line) for integrator, lines in zip([linear, euler, rk2, rk4], [2, 2, 3, 6]): code_lines = integrator(eqs).split("\n") err_msg = ( f"Returned code for integrator {integrator.__class__.__name__} had" f" {len(code_lines)} lines instead of {int(lines)}" ) assert len(code_lines) == lines, err_msg assert code_lines[-1] == "v = _v" # Make sure that it isn't a problem to use 'x', 'f' and 'g' as variable # names, even though they are also used in state updater descriptions. # The resulting code should be identical when replacing x by x0 (and ..._x by # ..._x0) for varname in ["x", "f", "g"]: # We use a very similar names here to avoid slightly re-arranged # expressions due to alphabetical sorting of terms in # multiplications, etc. eqs_v = Equations(f"d{varname}0/dt = -{varname}0 / (1 * second) : 1") eqs_var = Equations(f"d{varname}/dt = -{varname} / (1 * second) : 1") for integrator in [linear, euler, rk2, rk4]: code_v = integrator(eqs_v) code_var = integrator(eqs_var) # Re-substitute the variable names in the output code_var = re.sub(rf"\b{varname}\b", f"{varname}0", code_var) code_var = re.sub(rf"\b(\w*)_{varname}\b", rf"\1_{varname}0", code_var) assert code_var == code_v, f"'{code_var}' does not match '{code_v}'" @pytest.mark.codegen_independent def test_integrator_code2(): """ Test integration for a simple model with several state variables. """ eqs = Equations( """ dv/dt=(ge+gi-v)/tau : volt dge/dt=-ge/taue : volt dgi/dt=-gi/taui : volt """ ) euler_integration = euler(eqs) lines = sorted(euler_integration.split("\n")) # Do a very basic check that the right variables are used in every line for varname, line in zip(["_ge", "_gi", "_v", "ge", "gi", "v"], lines): assert line.startswith( f"{varname} = " ), f'line "{line}" does not start with {varname}' for variables, line in zip( [ ["dt", "ge", "taue"], ["dt", "gi", "taui"], ["dt", "ge", "gi", "v", "tau"], ["_ge"], ["_gi"], ["_v"], ], lines, ): rhs = line.split("=")[1] for variable in variables: assert variable in rhs, f'{variable} not in RHS: "{rhs}"' @pytest.mark.codegen_independent def test_illegal_calls(): eqs = Equations("dv/dt = -v / (10*ms) : 1") clock = Clock(dt=0.1 * ms) variables = { "v": ArrayVariable( name="name", size=10, owner=None, device=None, dtype=np.float64, constant=False, ), "t": clock.variables["t"], "dt": clock.variables["dt"], } with pytest.raises(TypeError): StateUpdateMethod.apply_stateupdater(eqs, variables, object()) with pytest.raises(TypeError): StateUpdateMethod.apply_stateupdater( eqs, variables, group_name="my_name", method=object() ) with pytest.raises(TypeError): StateUpdateMethod.apply_stateupdater(eqs, variables, [object(), "euler"]) with pytest.raises(TypeError): StateUpdateMethod.apply_stateupdater( eqs, variables, group_name="my_name", method=[object(), "euler"] ) def check_integration(eqs, variables, can_integrate): # can_integrate maps integrators to True/False/None # True/False means that the integrator should/should not integrate the equations # None means that it *might* integrate the equations (only needed for the # exact integration, since it can depend on the sympy version) for integrator, able in can_integrate.items(): try: integrator(eqs, variables) if able is False: raise AssertionError( "Should not be able to integrate these " f"equations (equations: '{eqs}') with " f"integrator {integrator.__class__.__name__}" ) except UnsupportedEquationsException: if able is True: raise AssertionError( "Should be able to integrate these " f"equations (equations: '{eqs}') with " f"integrator {integrator.__class__.__name__}" ) @pytest.mark.codegen_independent def test_priority(): updater = ExplicitStateUpdater("x_new = x + dt * f(x, t)") # Equations that work for the state updater eqs = Equations("dv/dt = -v / (10*ms) : 1") clock = Clock(dt=0.1 * ms) variables = { "v": ArrayVariable( name="name", size=10, owner=None, device=None, dtype=np.float64, constant=False, ), "w": ArrayVariable( name="name", size=10, owner=None, device=None, dtype=np.float64, constant=False, ), "t": clock.variables["t"], "dt": clock.variables["dt"], } updater(eqs, variables) # should not raise an error # External parameter in the coefficient, linear integration should work param = 1 eqs = Equations("dv/dt = -param * v / (10*ms) : 1") updater(eqs, variables) # should not raise an error can_integrate = { linear: True, euler: True, exponential_euler: True, rk2: True, rk4: True, heun: True, milstein: True, } check_integration(eqs, variables, can_integrate) # Constant equation, should work for all except linear (see #1010) param = 1 eqs = Equations( """dv/dt = 10*Hz : 1 dw/dt = -v/(10*ms) : 1""" ) updater(eqs, variables) # should not raise an error can_integrate = { linear: None, euler: True, exponential_euler: True, rk2: True, rk4: True, heun: True, milstein: True, } check_integration(eqs, variables, can_integrate) # Equations resulting in complex linear solution for older versions of sympy eqs = Equations( """ dv/dt = (ge+gi-(v+49*mV))/(20*ms) : volt dge/dt = -ge/(5*ms) : volt dgi/dt = Dgi/(5*ms) : volt dDgi/dt = ((-2./5) * Dgi - (1./5**2)*gi)/(10*ms) : volt """ ) can_integrate = { linear: None, euler: True, exponential_euler: True, rk2: True, rk4: True, heun: True, milstein: True, } check_integration(eqs, variables, can_integrate) # Equation with additive noise eqs = Equations("dv/dt = -v / (10*ms) + xi/(10*ms)**.5 : 1") with pytest.raises(UnsupportedEquationsException): updater(eqs, variables) can_integrate = { linear: False, euler: True, exponential_euler: False, rk2: False, rk4: False, heun: True, milstein: True, } check_integration(eqs, variables, can_integrate) # Equation with multiplicative noise eqs = Equations("dv/dt = -v / (10*ms) + v*xi/(10*ms)**.5 : 1") with pytest.raises(UnsupportedEquationsException): updater(eqs, variables) can_integrate = { linear: False, euler: False, exponential_euler: False, rk2: False, rk4: False, heun: True, milstein: True, } check_integration(eqs, variables, can_integrate) @pytest.mark.codegen_independent def test_registration(): """ Test state updater registration. """ # Save state before tests before = dict(StateUpdateMethod.stateupdaters) lazy_updater = ExplicitStateUpdater("x_new = x") StateUpdateMethod.register("lazy", lazy_updater) # Trying to register again with pytest.raises(ValueError): StateUpdateMethod.register("lazy", lazy_updater) # Trying to register something that is not a state updater with pytest.raises(ValueError): StateUpdateMethod.register("foo", "just a string") # Trying to register with an invalid index with pytest.raises(TypeError): StateUpdateMethod.register("foo", lazy_updater, index="not an index") # reset to state before the test StateUpdateMethod.stateupdaters = before @pytest.mark.codegen_independent def test_determination(): """ Test the determination of suitable state updaters. """ # To save some typing apply_stateupdater = StateUpdateMethod.apply_stateupdater eqs = Equations("dv/dt = -v / (10*ms) : 1") # Just make sure that state updaters know about the two state variables variables = {"v": Variable(name="v"), "w": Variable(name="w")} # all methods should work for these equations. # First, specify them explicitly (using the object) for integrator in ( linear, euler, exponential_euler, # TODO: Removed "independent" here due to the issue in sympy 0.7.4 rk2, rk4, heun, milstein, ): with catch_logs() as logs: returned = apply_stateupdater(eqs, variables, method=integrator) assert ( len(logs) == 0 ), f"Got {len(logs)} unexpected warnings: {str([l[2] for l in logs])}" # Equation with multiplicative noise, only milstein and heun should work eqs = Equations("dv/dt = -v / (10*ms) + v*xi*second**-.5: 1") for integrator in (linear, independent, euler, exponential_euler, rk2, rk4): with pytest.raises(UnsupportedEquationsException): apply_stateupdater(eqs, variables, integrator) for integrator in (heun, milstein): with catch_logs() as logs: returned = apply_stateupdater(eqs, variables, method=integrator) assert ( len(logs) == 0 ), f"Got {len(logs)} unexpected warnings: {str([l[2] for l in logs])}" # Arbitrary functions (converting equations into abstract code) should # always work my_stateupdater = lambda eqs, vars, options: "x_new = x" with catch_logs() as logs: returned = apply_stateupdater(eqs, variables, method=my_stateupdater) # No warning here assert len(logs) == 0 # Specification with names eqs = Equations("dv/dt = -v / (10*ms) : 1") for name, integrator in [ ("exact", exact), ("linear", linear), ("euler", euler), # ('independent', independent), #TODO: Removed "independent" here due to the issue in sympy 0.7.4 ("exponential_euler", exponential_euler), ("rk2", rk2), ("rk4", rk4), ("heun", heun), ("milstein", milstein), ]: with catch_logs() as logs: returned = apply_stateupdater(eqs, variables, method=name) # No warning here assert len(logs) == 0 # Now all except heun and milstein should refuse to work eqs = Equations("dv/dt = -v / (10*ms) + v*xi*second**-.5: 1") for name in [ "linear", "exact", "independent", "euler", "exponential_euler", "rk2", "rk4", ]: with pytest.raises(UnsupportedEquationsException): apply_stateupdater(eqs, variables, method=name) # milstein should work with catch_logs() as logs: apply_stateupdater(eqs, variables, method="milstein") assert len(logs) == 0 # heun should work with catch_logs() as logs: apply_stateupdater(eqs, variables, method="heun") assert len(logs) == 0 # non-existing name with pytest.raises(ValueError): apply_stateupdater(eqs, variables, method="does_not_exist") # Automatic state updater choice should return linear for linear equations, # euler for non-linear, non-stochastic equations and equations with # additive noise, heun for equations with multiplicative noise # Because it is somewhat fragile, the "independent" state updater is not # included in this list all_methods = ["linear", "exact", "exponential_euler", "euler", "heun", "milstein"] eqs = Equations("dv/dt = -v / (10*ms) : 1") with catch_logs(log_level=logging.INFO) as logs: apply_stateupdater(eqs, variables, all_methods) assert len(logs) == 1 assert ("linear" in logs[0][2]) or ("exact" in logs[0][2]) # This is conditionally linear eqs = Equations( """dv/dt = -(v + w**2)/ (10*ms) : 1 dw/dt = -w/ (10*ms) : 1""" ) with catch_logs(log_level=logging.INFO) as logs: apply_stateupdater(eqs, variables, all_methods) assert len(logs) == 1 assert "exponential_euler" in logs[0][2] # # Do not test for now # eqs = Equations('dv/dt = sin(t) / (10*ms) : 1') # assert apply_stateupdater(eqs, variables) is independent eqs = Equations("dv/dt = -sqrt(v) / (10*ms) : 1") with catch_logs(log_level=logging.INFO) as logs: apply_stateupdater(eqs, variables, all_methods) assert len(logs) == 1 assert "'euler'" in logs[0][2] eqs = Equations("dv/dt = -v / (10*ms) + 0.1*second**-.5*xi: 1") with catch_logs(log_level=logging.INFO) as logs: apply_stateupdater(eqs, variables, all_methods) assert len(logs) == 1 assert "'euler'" in logs[0][2] eqs = Equations("dv/dt = -v / (10*ms) + v*0.1*second**-.5*xi: 1") with catch_logs(log_level=logging.INFO) as logs: apply_stateupdater(eqs, variables, all_methods) assert len(logs) == 1 assert "'heun'" in logs[0][2] @pytest.mark.standalone_compatible def test_subexpressions_basic(): """ Make sure that the integration of a (non-stochastic) differential equation does not depend on whether it's formulated using subexpressions. """ # no subexpression eqs1 = "dv/dt = (-v + sin(2*pi*100*Hz*t)) / (10*ms) : 1" # same with subexpression eqs2 = """dv/dt = I / (10*ms) : 1 I = -v + sin(2*pi*100*Hz*t): 1""" method = "euler" G1 = NeuronGroup(1, eqs1, method=method) G1.v = 1 G2 = NeuronGroup(1, eqs2, method=method) G2.v = 1 mon1 = StateMonitor(G1, "v", record=True) mon2 = StateMonitor(G2, "v", record=True) run(10 * ms) assert_equal(mon1.v, mon2.v, f"Results for method {method} differed!") def test_subexpressions(): """ Make sure that the integration of a (non-stochastic) differential equation does not depend on whether it's formulated using subexpressions. """ # no subexpression eqs1 = "dv/dt = (-v + sin(2*pi*100*Hz*t)) / (10*ms) : 1" # same with subexpression eqs2 = """dv/dt = I / (10*ms) : 1 I = -v + sin(2*pi*100*Hz*t): 1""" methods = [ "exponential_euler", "rk2", "rk4", ] # euler is tested in test_subexpressions_basic for method in methods: G1 = NeuronGroup(1, eqs1, method=method) G1.v = 1 G2 = NeuronGroup(1, eqs2, method=method) G2.v = 1 mon1 = StateMonitor(G1, "v", record=True) mon2 = StateMonitor(G2, "v", record=True) net = Network(G1, mon1, G2, mon2) net.run(10 * ms) assert_equal(mon1.v, mon2.v, f"Results for method {method} differed!") @pytest.mark.codegen_independent def test_locally_constant_check(): default_dt = defaultclock.dt # The linear state update can handle additive time-dependent functions # (e.g. a TimedArray) but only if it can be safely assumed that the function # is constant over a single time check ta0 = TimedArray(np.array([1]), dt=default_dt) # ok ta1 = TimedArray(np.array([1]), dt=2 * default_dt) # ok ta2 = TimedArray(np.array([1]), dt=default_dt / 2) # not ok ta3 = TimedArray(np.array([1]), dt=default_dt * 1.5) # not ok for ta_func, ok in zip([ta0, ta1, ta2, ta3], [True, True, False, False]): # additive G = NeuronGroup( 1, "dv/dt = -v/(10*ms) + ta(t)*Hz : 1", method="exact", namespace={"ta": ta_func}, ) net = Network(G) if ok: # This should work net.run(0 * ms) else: # This should not with catch_logs(): with pytest.raises(BrianObjectException) as exc: net.run(0 * ms) assert exc.errisinstance(UnsupportedEquationsException) # multiplicative G = NeuronGroup( 1, "dv/dt = -v*ta(t)/(10*ms) : 1", method="exact", namespace={"ta": ta_func} ) net = Network(G) if ok: # This should work net.run(0 * ms) else: # This should not with catch_logs(): with pytest.raises(BrianObjectException) as exc: net.run(0 * ms) assert exc.errisinstance(UnsupportedEquationsException) # If the argument is more than just "t", we cannot guarantee that it is # actually locally constant G = NeuronGroup( 1, "dv/dt = -v*ta(t/2.0)/(10*ms) : 1", method="exact", namespace={"ta": ta0} ) net = Network(G) with pytest.raises(BrianObjectException) as exc: net.run(0 * ms) assert exc_isinstance(exc, UnsupportedEquationsException) # Arbitrary functions are not constant over a time step G = NeuronGroup(1, "dv/dt = -v/(10*ms) + sin(2*pi*100*Hz*t)*Hz : 1", method="exact") net = Network(G) with pytest.raises(BrianObjectException) as exc: net.run(0 * ms) assert exc_isinstance(exc, UnsupportedEquationsException) # Stateful functions aren't either G = NeuronGroup(1, "dv/dt = -v/(10*ms) + rand()*Hz : 1", method="exact") net = Network(G) with pytest.raises(BrianObjectException) as exc: net.run(0 * ms) assert exc_isinstance(exc, UnsupportedEquationsException) # Neither is "t" itself G = NeuronGroup(1, "dv/dt = -v/(10*ms) + t/second**2 : 1", method="exact") net = Network(G) with pytest.raises(BrianObjectException) as exc: net.run(0 * ms) assert exc_isinstance(exc, UnsupportedEquationsException) # But if the argument is not referring to t, all should be well G = NeuronGroup( 1, "dv/dt = -v/(10*ms) + sin(2*pi*100*Hz*5*second)*Hz : 1", method="exact" ) net = Network(G) net.run(0 * ms) def test_refractory(): # Compare integration with and without the addition of refractoriness -- # note that the cell here is not spiking, so it should never be in the # refractory period and therefore the results should be exactly identical # with and without (unless refractory) eqs_base = "dv/dt = -v/(10*ms) : 1" for method in [ "linear", "exact", "independent", "euler", "exponential_euler", "rk2", "rk4", ]: G_no_ref = NeuronGroup(10, eqs_base, method=method) G_no_ref.v = "(i+1)/11." G_ref = NeuronGroup( 10, f"{eqs_base}(unless refractory)", refractory=1 * ms, method=method ) G_ref.v = "(i+1)/11." net = Network(G_ref, G_no_ref) net.run(10 * ms) assert_allclose( G_no_ref.v[:], G_ref.v[:], err_msg="Results with and without refractoriness differ for method %s." % method, ) def test_refractory_stochastic(fake_randn_randn_fixture): eqs_base = "dv/dt = -v/(10*ms) + second**-.5*xi : 1" for method in ["euler", "heun", "milstein"]: G_no_ref = NeuronGroup(10, eqs_base, method=method) G_no_ref.v = "(i+1)/11." G_ref = NeuronGroup( 10, f"{eqs_base} (unless refractory)", refractory=1 * ms, method=method ) G_ref.v = "(i+1)/11." net = Network(G_ref, G_no_ref) net.run(10 * ms) assert_allclose( G_no_ref.v[:], G_ref.v[:], err_msg="Results with and without refractoriness differ for method %s." % method, ) @pytest.mark.standalone_compatible def test_check_for_invalid_values_linear_integrator(): # A differential equation that cannot be solved by the linear # integrator should return nan values to warn the user, and not silently # return incorrect values. See discussion on # https://github.com/brian-team/brian2/issues/626 a = 0.0 / ms b = 1.0 / ms c = -0.5 / ms d = -0.1 / ms eqs = """ dx/dt = a * x + b * y : 1 dy/dt = c * x + d * y : 1 """ G = NeuronGroup( 1, eqs, threshold="x > 100", reset="x = 0", method="exact", method_options={"simplify": False}, ) G.x = 1 BrianLogger._log_messages.clear() # because the log message is set to be shown only once with catch_logs() as clog: try: run(1 * ms) # this check allows for the possibility that we improve the linear # integrator in the future so that it can handle this equation if numpy.isnan(G.x[0]): assert "invalid_values" in repr(clog) else: assert G.x[0] != 0 except BrianObjectException as exc: assert isinstance(exc.__cause__, UnsupportedEquationsException) if __name__ == "__main__": import time from brian2 import prefs start = time.time() test_determination() test_explicit_stateupdater_parsing() test_non_autonomous_equations() test_str_repr() test_multiplicative_noise() test_multiple_noise_variables_basic() test_multiple_noise_variables_extended() test_temporary_variables() test_temporary_variables2() test_integrator_code() test_integrator_code2() test_illegal_calls() test_priority() test_registration() test_subexpressions() test_locally_constant_check() test_refractory() # # Need the fake random number generator from tests/conftest.py # test_refractory_stochastic() # test_multiple_noise_variables_deterministic_noise() # test_pure_noise_deterministic() test_check_for_invalid_values_linear_integrator() print("Tests took", time.time() - start) brian2-2.5.4/brian2/tests/test_subgroup.py000066400000000000000000000607661445201106100205060ustar00rootroot00000000000000import pytest from numpy.testing import assert_array_equal, assert_equal from brian2 import * from brian2.core.network import schedule_propagation_offset from brian2.devices.device import reinit_and_delete from brian2.tests.utils import assert_allclose from brian2.utils.logger import catch_logs @pytest.mark.codegen_independent def test_str_repr(): """ Test the string representation of a subgroup. """ G = NeuronGroup(10, "v:1") SG = G[5:8] # very basic test, only make sure no error is raised assert len(str(SG)) assert len(repr(SG)) def test_state_variables(): """ Test the setting and accessing of state variables in subgroups. """ G = NeuronGroup(10, "v : volt") SG = G[4:9] with pytest.raises(DimensionMismatchError): SG.__setattr__("v", -70) SG.v_ = float(-80 * mV) assert_allclose(G.v, np.array([0, 0, 0, 0, -80, -80, -80, -80, -80, 0]) * mV) assert_allclose(SG.v, np.array([-80, -80, -80, -80, -80]) * mV) assert_allclose( G.v_, np.array([0, 0, 0, 0, -80, -80, -80, -80, -80, 0]) * float(mV) ) assert_allclose(SG.v_, np.array([-80, -80, -80, -80, -80]) * float(mV)) # You should also be able to set variables with a string SG.v = "v + i*mV" assert_allclose(SG.v[0], -80 * mV) assert_allclose(SG.v[4], -76 * mV) assert_allclose(G.v[4:9], -80 * mV + np.arange(5) * mV) # Calculating with state variables should work too assert all(G.v[4:9] - SG.v == 0) # And in-place modification should work as well SG.v += 10 * mV assert_allclose(G.v[4:9], -70 * mV + np.arange(5) * mV) SG.v *= 2 assert_allclose(G.v[4:9], 2 * (-70 * mV + np.arange(5) * mV)) # with unit checking with pytest.raises(DimensionMismatchError): SG.v.__iadd__(3 * second) with pytest.raises(DimensionMismatchError): SG.v.__iadd__(3) with pytest.raises(DimensionMismatchError): SG.v.__imul__(3 * second) # Indexing with subgroups assert_equal(G.v[SG], SG.v[:]) @pytest.mark.standalone_compatible def test_state_variables_simple(): G = NeuronGroup( 10, """ a : 1 b : 1 c : 1 d : 1 """, ) SG = G[3:7] SG.a = 1 SG.a["i == 0"] = 2 SG.b = "i" SG.b["i == 3"] = "i * 2" SG.c = np.arange(3, 7) SG.d[1:2] = 4 SG.d[2:4] = [1, 2] run(0 * ms) assert_equal(G.a[:], [0, 0, 0, 2, 1, 1, 1, 0, 0, 0]) assert_equal(G.b[:], [0, 0, 0, 0, 1, 2, 6, 0, 0, 0]) assert_equal(G.c[:], [0, 0, 0, 3, 4, 5, 6, 0, 0, 0]) assert_equal(G.d[:], [0, 0, 0, 0, 4, 1, 2, 0, 0, 0]) def test_state_variables_string_indices(): """ Test accessing subgroups with string indices. """ G = NeuronGroup(10, "v : volt") SG = G[4:9] assert len(SG.v["i>3"]) == 1 G.v = np.arange(10) * mV assert len(SG.v["v>7.5*mV"]) == 1 # Combined string indexing and assignment SG.v["i > 3"] = "i*10*mV" assert_allclose(G.v[:], [0, 1, 2, 3, 4, 5, 6, 7, 40, 9] * mV) @pytest.mark.codegen_independent def test_state_variables_group_as_index(): G = NeuronGroup(10, "v : 1") SG = G[4:9] G.v[SG] = 1 assert_equal(G.v[:], np.array([0, 0, 0, 0, 1, 1, 1, 1, 1, 0])) G.v = 1 G.v[SG] = "2*v" assert_equal(G.v[:], np.array([1, 1, 1, 1, 2, 2, 2, 2, 2, 1])) @pytest.mark.codegen_independent def test_state_variables_group_as_index_problematic(): G = NeuronGroup(10, "v : 1") SG = G[4:9] G.v = 1 tests = [("i", 1), ("N", 1), ("N + i", 2), ("v", 0)] for value, n_warnings in tests: with catch_logs() as l: G.v.__setitem__(SG, value) assert ( len(l) == n_warnings ), f"expected {int(n_warnings)}, got {len(l)} warnings" assert all( [entry[1].endswith("ambiguous_string_expression") for entry in l] ) @pytest.mark.standalone_compatible def test_state_monitor(): G = NeuronGroup(10, "v : volt") G.v = np.arange(10) * volt SG = G[5:] mon_all = StateMonitor(SG, "v", record=True) mon_0 = StateMonitor(SG, "v", record=0) run(defaultclock.dt) assert_allclose(mon_0[0].v, mon_all[0].v) assert_allclose(mon_0[0].v, np.array([5]) * volt) assert_allclose(mon_all.v.flatten(), np.arange(5, 10) * volt) with pytest.raises(IndexError): mon_all[5] def test_shared_variable(): """Make sure that shared variables work with subgroups""" G = NeuronGroup(10, "v : volt (shared)") G.v = 1 * volt SG = G[5:] assert SG.v == 1 * volt @pytest.mark.standalone_compatible def test_synapse_creation(): G1 = NeuronGroup(10, "") G2 = NeuronGroup(20, "") SG1 = G1[:5] SG2 = G2[10:] S = Synapses(SG1, SG2) S.connect(i=2, j=2) # Should correspond to (2, 12) S.connect("i==2 and j==5") # Should correspond to (2, 15) run(0 * ms) # for standalone # Internally, the "real" neuron indices should be used assert_equal(S._synaptic_pre[:], np.array([2, 2])) assert_equal(S._synaptic_post[:], np.array([12, 15])) # For the user, the subgroup-relative indices should be presented assert_equal(S.i[:], np.array([2, 2])) assert_equal(S.j[:], np.array([2, 5])) # N_incoming and N_outgoing should also be correct assert all(S.N_outgoing[2, :] == 2) assert all(S.N_incoming[:, 2] == 1) assert all(S.N_incoming[:, 5] == 1) @pytest.mark.standalone_compatible def test_synapse_creation_state_vars(): G1 = NeuronGroup(10, "v : 1") G2 = NeuronGroup(20, "v : 1") G1.v = "i" G2.v = "10 + i" SG1 = G1[:5] SG2 = G2[10:] # connect based on pre-/postsynaptic state variables S2 = Synapses(SG1, SG2, "w:1") S2.connect("v_pre > 2") S3 = Synapses(SG1, SG2, "w:1") S3.connect("v_post < 25") S4 = Synapses(SG2, SG1, "w:1") S4.connect("v_post > 2") S5 = Synapses(SG2, SG1, "w:1") S5.connect("v_pre < 25") run(0 * ms) # for standalone assert len(S2) == 2 * len(SG2), str(len(S2)) assert all(S2.v_pre[:] > 2) assert len(S3) == 5 * len(SG1), f"{len(S3)} != {5 * len(SG1)} " assert all(S3.v_post[:] < 25) assert len(S4) == 2 * len(SG2), str(len(S4)) assert all(S4.v_post[:] > 2) assert len(S5) == 5 * len(SG1), f"{len(53)} != {5 * len(SG1)} " assert all(S5.v_pre[:] < 25) @pytest.mark.standalone_compatible def test_synapse_creation_generator(): G1 = NeuronGroup(10, "v:1") G2 = NeuronGroup(20, "v:1") G1.v = "i" G2.v = "10 + i" SG1 = G1[:5] SG2 = G2[10:] S = Synapses(SG1, SG2, "w:1") S.connect(j="i*2 + k for k in range(2)") # diverging connections # connect based on pre-/postsynaptic state variables S2 = Synapses(SG1, SG2, "w:1") S2.connect(j="k for k in range(N_post) if v_pre > 2") S3 = Synapses(SG1, SG2, "w:1") S3.connect(j="k for k in range(N_post) if v_post < 25") S4 = Synapses(SG2, SG1, "w:1") S4.connect(j="k for k in range(N_post) if v_post > 2") S5 = Synapses(SG2, SG1, "w:1") S5.connect(j="k for k in range(N_post) if v_pre < 25") run(0 * ms) # for standalone # Internally, the "real" neuron indices should be used assert_equal(S._synaptic_pre[:], np.arange(5).repeat(2)) assert_equal(S._synaptic_post[:], np.arange(10) + 10) # For the user, the subgroup-relative indices should be presented assert_equal(S.i[:], np.arange(5).repeat(2)) assert_equal(S.j[:], np.arange(10)) # N_incoming and N_outgoing should also be correct assert all(S.N_outgoing[:] == 2) assert all(S.N_incoming[:] == 1) assert len(S2) == 2 * len(SG2), str(len(S2)) assert all(S2.v_pre[:] > 2) assert len(S3) == 5 * len(SG1), f"{len(S3)} != {5 * len(SG1)} " assert all(S3.v_post[:] < 25) assert len(S4) == 2 * len(SG2), str(len(S4)) assert all(S4.v_post[:] > 2) assert len(S5) == 5 * len(SG1), f"{len(S5)} != {5 * len(SG1)} " assert all(S5.v_pre[:] < 25) @pytest.mark.standalone_compatible def test_synapse_creation_generator_multiple_synapses(): G1 = NeuronGroup(10, "v:1") G2 = NeuronGroup(20, "v:1") G1.v = "i" G2.v = "10 + i" SG1 = G1[:5] SG2 = G2[10:] S1 = Synapses(SG1, SG2) S1.connect(j="k for k in range(N_post)", n="i") S2 = Synapses(SG1, SG2) S2.connect(j="k for k in range(N_post)", n="j") S3 = Synapses(SG2, SG1) S3.connect(j="k for k in range(N_post)", n="i") S4 = Synapses(SG2, SG1) S4.connect(j="k for k in range(N_post)", n="j") S5 = Synapses(SG1, SG2) S5.connect(j="k for k in range(N_post)", n="i+j") S6 = Synapses(SG2, SG1) S6.connect(j="k for k in range(N_post)", n="i+j") S7 = Synapses(SG1, SG2) S7.connect(j="k for k in range(N_post)", n="int(v_pre>2)*2") S8 = Synapses(SG2, SG1) S8.connect(j="k for k in range(N_post)", n="int(v_post>2)*2") S9 = Synapses(SG1, SG2) S9.connect(j="k for k in range(N_post)", n="int(v_post>22)*2") S10 = Synapses(SG2, SG1) S10.connect(j="k for k in range(N_post)", n="int(v_pre>22)*2") run(0 * ms) # for standalone # straightforward loop instead of doing something clever... for source in range(len(SG1)): assert_equal(S1.j[source, :], np.arange(len(SG2)).repeat(source)) assert_equal(S2.j[source, :], np.arange(len(SG2)).repeat(np.arange(len(SG2)))) assert_equal(S3.i[:, source], np.arange(len(SG2)).repeat(np.arange(len(SG2)))) assert_equal(S4.i[:, source], np.arange(len(SG2)).repeat(source)) assert_equal( S5.j[source, :], np.arange(len(SG2)).repeat(np.arange(len(SG2)) + source) ) assert_equal( S6.i[:, source], np.arange(len(SG2)).repeat(np.arange(len(SG2)) + source) ) if source > 2: assert_equal(S7.j[source, :], np.arange(len(SG2)).repeat(2)) assert_equal(S8.i[:, source], np.arange(len(SG2)).repeat(2)) else: assert len(S7.j[source, :]) == 0 assert len(S8.i[:, source]) == 0 assert_equal(S9.j[source, :], np.arange(3, len(SG2)).repeat(2)) assert_equal(S10.i[:, source], np.arange(3, len(SG2)).repeat(2)) @pytest.mark.standalone_compatible def test_synapse_creation_generator_complex_ranges(): G1 = NeuronGroup(10, "v:1") G2 = NeuronGroup(20, "v:1") G1.v = "i" G2.v = "10 + i" SG1 = G1[:5] SG2 = G2[10:] S = Synapses(SG1, SG2) S.connect(j="i+k for k in range(N_post-i)") # Connect to all j>i # connect based on pre-/postsynaptic state variables S2 = Synapses(SG1, SG2) S2.connect(j="k for k in range(N_post * int(v_pre > 2))") # connect based on pre-/postsynaptic state variables S3 = Synapses(SG2, SG1) S3.connect(j="k for k in range(N_post * int(v_pre > 22))") run(0 * ms) # for standalone for syn_source in range(5): # Internally, the "real" neuron indices should be used assert_equal( S._synaptic_post[syn_source, :], 10 + syn_source + np.arange(10 - syn_source), ) # For the user, the subgroup-relative indices should be presented assert_equal(S.j[syn_source, :], syn_source + np.arange(10 - syn_source)) assert len(S2) == 2 * len(SG2), str(len(S2)) assert all(S2.v_pre[:] > 2) assert len(S3) == 7 * len(SG1), str(len(S3)) assert all(S3.v_pre[:] > 22) @pytest.mark.standalone_compatible def test_synapse_creation_generator_random(): G1 = NeuronGroup(10, "v:1") G2 = NeuronGroup(20, "v:1") G1.v = "i" G2.v = "10 + i" SG1 = G1[:5] SG2 = G2[10:] # connect based on pre-/postsynaptic state variables S2 = Synapses(SG1, SG2) S2.connect(j="k for k in sample(N_post, p=1.0*int(v_pre > 2))") S3 = Synapses(SG2, SG1) S3.connect(j="k for k in sample(N_post, p=1.0*int(v_pre > 22))") run(0 * ms) # for standalone assert len(S2) == 2 * len(SG2), str(len(S2)) assert all(S2.v_pre[:] > 2) assert len(S3) == 7 * len(SG1), str(len(S3)) assert all(S3.v_pre[:] > 22) def test_synapse_access(): G1 = NeuronGroup(10, "v:1") G1.v = "i" G2 = NeuronGroup(20, "v:1") G2.v = "i" SG1 = G1[:5] SG2 = G2[10:] S = Synapses(SG1, SG2, "w:1") S.connect(True) S.w["j == 0"] = 5 assert all(S.w["j==0"] == 5) S.w[2, 2] = 7 assert all(S.w["i==2 and j==2"] == 7) S.w = "2*j" assert all(S.w[:, 1] == 2) assert len(S.w[:, 10]) == 0 assert len(S.w["j==10"]) == 0 # Test referencing pre- and postsynaptic variables assert_equal(S.w[2:, :], S.w["v_pre >= 2"]) assert_equal(S.w[:, :5], S.w["v_post < 15"]) S.w = "v_post" assert_equal(S.w[:], S.j[:] + 10) S.w = "v_post + v_pre" assert_equal(S.w[:], S.j[:] + 10 + S.i[:]) # Test using subgroups as indices assert len(S) == len(S.w[SG1, SG2]) assert_equal(S.w[SG1, 1], S.w[:, 1]) assert_equal(S.w[1, SG2], S.w[1, :]) assert len(S.w[SG1, 10]) == 0 def test_synapses_access_subgroups(): G1 = NeuronGroup(5, "x:1") G2 = NeuronGroup(10, "y:1") SG1 = G1[2:5] SG2 = G2[4:9] S = Synapses(G1, G2, "w:1") S.connect() S.w[SG1, SG2] = 1 assert_equal(S.w["(i>=2 and i<5) and (j>=4 and j<9)"], 1) assert_equal(S.w["not ((i>=2 and i<5) and (j>=4 and j<9))"], 0) S.w = 0 S.w[SG1, :] = 1 assert_equal(S.w["i>=2 and i<5"], 1) assert_equal(S.w["not (i>=2 and i<5)"], 0) S.w = 0 S.w[:, SG2] = 1 assert_equal(S.w["j>=4 and j<9"], 1) assert_equal(S.w["not (j>=4 and j<9)"], 0) @pytest.mark.codegen_independent def test_synapses_access_subgroups_problematic(): G1 = NeuronGroup(5, "x:1") G2 = NeuronGroup(10, "y:1") SG1 = G1[2:5] SG2 = G2[4:9] S = Synapses(G1, G2, "w:1") S.connect() # Note that "j" is not ambiguous, because the equivalent in the target group # is called "i" (this previously raised a warning) tests = [ ((SG1, slice(None)), "i", 1), ((SG1, slice(None)), "i + N_pre", 2), ((SG1, slice(None)), "N_pre", 1), ((slice(None), SG2), "j", 0), ((slice(None), SG2), "N_post", 1), ((slice(None), SG2), "N", 1), ((SG1, SG2), "i", 1), ((SG1, SG2), "i + j", 1), ((SG1, SG2), "N_pre", 1), ((SG1, SG2), "j", 0), ((SG1, SG2), "N_post", 1), ((SG1, SG2), "N", 1), # These should not raise a warning ((SG1, SG2), "w", 0), ((SG1, SG2), "x_pre", 0), ((SG1, SG2), "y_post", 0), ((SG1, SG2), "y", 0), ] for item, value, n_warnings in tests: with catch_logs() as l: S.w.__setitem__(item, value) assert ( len(l) == n_warnings ), f"expected {int(n_warnings)}, got {len(l)} warnings" assert all( [entry[1].endswith("ambiguous_string_expression") for entry in l] ) @pytest.mark.standalone_compatible def test_subgroup_summed_variable(): # Check in particular that only neurons targeted are reset to 0 (see github issue #925) source = NeuronGroup(1, "") target = NeuronGroup(5, "Iin : 1") target.Iin = 10 target1 = target[1:2] target2 = target[3:] syn1 = Synapses(source, target1, "Iin_post = 5 : 1 (summed)") syn1.connect(True) syn2 = Synapses(source, target2, "Iin_post = 1 : 1 (summed)") syn2.connect(True) run(2 * defaultclock.dt) assert_array_equal(target.Iin, [10, 5, 10, 1, 1]) def test_subexpression_references(): """ Assure that subexpressions in targeted groups are handled correctly. """ G = NeuronGroup( 10, """ v : 1 v2 = 2*v : 1 """, ) G.v = np.arange(10) SG1 = G[:5] SG2 = G[5:] S1 = Synapses( SG1, SG2, """ w : 1 u = v2_post + 1 : 1 x = v2_pre + 1 : 1 """, ) S1.connect("i==(5-1-j)") assert_equal(S1.i[:], np.arange(5)) assert_equal(S1.j[:], np.arange(5)[::-1]) assert_equal(S1.u[:], np.arange(10)[:-6:-1] * 2 + 1) assert_equal(S1.x[:], np.arange(5) * 2 + 1) S2 = Synapses( G, SG2, """ w : 1 u = v2_post + 1 : 1 x = v2_pre + 1 : 1 """, ) S2.connect("i==(5-1-j)") assert_equal(S2.i[:], np.arange(5)) assert_equal(S2.j[:], np.arange(5)[::-1]) assert_equal(S2.u[:], np.arange(10)[:-6:-1] * 2 + 1) assert_equal(S2.x[:], np.arange(5) * 2 + 1) S3 = Synapses( SG1, G, """ w : 1 u = v2_post + 1 : 1 x = v2_pre + 1 : 1 """, ) S3.connect("i==(10-1-j)") assert_equal(S3.i[:], np.arange(5)) assert_equal(S3.j[:], np.arange(10)[:-6:-1]) assert_equal(S3.u[:], np.arange(10)[:-6:-1] * 2 + 1) assert_equal(S3.x[:], np.arange(5) * 2 + 1) def test_subexpression_no_references(): """ Assure that subexpressions are handled correctly, even when the subgroups are created on-the-fly. """ G = NeuronGroup( 10, """ v : 1 v2 = 2*v : 1 """, ) G.v = np.arange(10) assert_equal(G[5:].v2, np.arange(5, 10) * 2) S1 = Synapses( G[:5], G[5:], """ w : 1 u = v2_post + 1 : 1 x = v2_pre + 1 : 1 """, ) S1.connect("i==(5-1-j)") assert_equal(S1.i[:], np.arange(5)) assert_equal(S1.j[:], np.arange(5)[::-1]) assert_equal(S1.u[:], np.arange(10)[:-6:-1] * 2 + 1) assert_equal(S1.x[:], np.arange(5) * 2 + 1) S2 = Synapses( G, G[5:], """ w : 1 u = v2_post + 1 : 1 x = v2_pre + 1 : 1 """, ) S2.connect("i==(5-1-j)") assert_equal(S2.i[:], np.arange(5)) assert_equal(S2.j[:], np.arange(5)[::-1]) assert_equal(S2.u[:], np.arange(10)[:-6:-1] * 2 + 1) assert_equal(S2.x[:], np.arange(5) * 2 + 1) S3 = Synapses( G[:5], G, """ w : 1 u = v2_post + 1 : 1 x = v2_pre + 1 : 1 """, ) S3.connect("i==(10-1-j)") assert_equal(S3.i[:], np.arange(5)) assert_equal(S3.j[:], np.arange(10)[:-6:-1]) assert_equal(S3.u[:], np.arange(10)[:-6:-1] * 2 + 1) assert_equal(S3.x[:], np.arange(5) * 2 + 1) @pytest.mark.standalone_compatible def test_synaptic_propagation(): G1 = NeuronGroup(10, "v:1", threshold="v>1", reset="v=0") G1.v["i%2==1"] = 1.1 # odd numbers should spike G2 = NeuronGroup(20, "v:1") SG1 = G1[1:6] SG2 = G2[10:] S = Synapses(SG1, SG2, on_pre="v+=1") S.connect("i==j") run(defaultclock.dt + schedule_propagation_offset()) expected = np.zeros(len(G2)) # Neurons 1, 3, 5 spiked and are connected to 10, 12, 14 expected[[10, 12, 14]] = 1 assert_equal(np.asarray(G2.v).flatten(), expected) @pytest.mark.standalone_compatible def test_synaptic_propagation_2(): # This tests for the bug in github issue #461 source = NeuronGroup(100, "", threshold="True") sub_source = source[99:] target = NeuronGroup(1, "v:1") syn = Synapses(sub_source, target, on_pre="v+=1") syn.connect() run(defaultclock.dt + schedule_propagation_offset()) assert target.v[0] == 1.0 @pytest.mark.standalone_compatible def test_run_regularly(): # See github issue #922 group = NeuronGroup(10, "v: integer") # Full group group.run_regularly("v += 16") # Subgroup with explicit reference subgroup = group[:2] subgroup.run_regularly("v += 8") # Subgroup with explicit reference and reference for run_regularly operation subgroup2 = group[2:4] updater = subgroup2.run_regularly("v += 4") # Subgroup without reference group[4:6].run_regularly("v += 2") # Subgroup without reference, with reference for run_regularly operation updater2 = group[6:8].run_regularly("v += 1") run(defaultclock.dt) assert_array_equal(group.v, [24, 24, 20, 20, 18, 18, 17, 17, 16, 16]) @pytest.mark.standalone_compatible def test_spike_monitor(): G = NeuronGroup(10, "v:1", threshold="v>1", reset="v=0") G.v[0] = 1.1 G.v[2] = 1.1 G.v[5] = 1.1 SG = G[3:] SG2 = G[:3] s_mon = SpikeMonitor(G) sub_s_mon = SpikeMonitor(SG) sub_s_mon2 = SpikeMonitor(SG2) run(defaultclock.dt) assert_equal(s_mon.i, np.array([0, 2, 5])) assert_equal(s_mon.t_, np.zeros(3)) assert_equal(sub_s_mon.i, np.array([2])) assert_equal(sub_s_mon.t_, np.zeros(1)) assert_equal(sub_s_mon2.i, np.array([0, 2])) assert_equal(sub_s_mon2.t_, np.zeros(2)) expected = np.zeros(10, dtype=int) expected[[0, 2, 5]] = 1 assert_equal(s_mon.count, expected) expected = np.zeros(7, dtype=int) expected[[2]] = 1 assert_equal(sub_s_mon.count, expected) assert_equal(sub_s_mon2.count, np.array([1, 0, 1])) @pytest.mark.codegen_independent def test_wrong_indexing(): G = NeuronGroup(10, "v:1") with pytest.raises(TypeError): G["string"] with pytest.raises(IndexError): G[10] with pytest.raises(IndexError): G[10:] with pytest.raises(IndexError): G[::2] with pytest.raises(IndexError): G[3:2] with pytest.raises(IndexError): G[[5, 4, 3]] with pytest.raises(IndexError): G[[2, 4, 6]] with pytest.raises(IndexError): G[[-1, 0, 1]] with pytest.raises(IndexError): G[[9, 10, 11]] with pytest.raises(IndexError): G[[9, 10]] with pytest.raises(IndexError): G[[10, 11]] with pytest.raises(TypeError): G[[2.5, 3.5, 4.5]] @pytest.mark.codegen_independent def test_alternative_indexing(): G = NeuronGroup(10, "v : integer") G.v = "i" assert_equal(G[-3:].v, np.array([7, 8, 9])) assert_equal(G[3].v, np.array([3])) assert_equal(G[[3, 4, 5]].v, np.array([3, 4, 5])) def test_no_reference_1(): """ Using subgroups without keeping an explicit reference. Basic access. """ G = NeuronGroup(10, "v:1") G.v = np.arange(10) assert_equal(G[:5].v[:], G.v[:5]) @pytest.mark.standalone_compatible def test_no_reference_2(): """ Using subgroups without keeping an explicit reference. Monitors """ G = NeuronGroup(2, "v:1", threshold="v>1", reset="v=0") G.v = [0, 1.1] state_mon = StateMonitor(G[:1], "v", record=True) spike_mon = SpikeMonitor(G[1:]) rate_mon = PopulationRateMonitor(G[:2]) run(2 * defaultclock.dt) assert_equal(state_mon[0].v[:], np.zeros(2)) assert_equal(spike_mon.i[:], np.array([0])) assert_equal(spike_mon.t[:], np.array([0]) * second) assert_equal(rate_mon.rate[:], np.array([0.5, 0]) / defaultclock.dt) @pytest.mark.standalone_compatible def test_no_reference_3(): """ Using subgroups without keeping an explicit reference. Monitors """ G = NeuronGroup(2, "v:1", threshold="v>1", reset="v=0") G.v = [1.1, 0] S = Synapses(G[:1], G[1:], on_pre="v+=1") S.connect() run(defaultclock.dt + schedule_propagation_offset()) assert_equal(G.v[:], np.array([0, 1])) @pytest.mark.standalone_compatible def test_no_reference_4(): """ Using subgroups without keeping an explicit reference. Synapses """ G1 = NeuronGroup(10, "v:1", threshold="v>1", reset="v=0") G1.v["i%2==1"] = 1.1 # odd numbers should spike G2 = NeuronGroup(20, "v:1") S = Synapses(G1[1:6], G2[10:], on_pre="v+=1") S.connect("i==j") run(defaultclock.dt + schedule_propagation_offset()) expected = np.zeros(len(G2)) # Neurons 1, 3, 5 spiked and are connected to 10, 12, 14 expected[[10, 12, 14]] = 1 assert_equal(np.asarray(G2.v).flatten(), expected) def test_recursive_subgroup(): """ Create a subgroup of a subgroup """ G = NeuronGroup(10, "v : 1") G.v = "i" SG = G[3:8] SG2 = SG[2:4] assert_equal(SG2.v[:], np.array([5, 6])) assert_equal(SG2.v[:], SG.v[2:4]) assert SG2.source.name == G.name if __name__ == "__main__": test_str_repr() test_state_variables() test_state_variables_simple() test_state_variables_string_indices() test_state_variables_group_as_index() test_state_variables_group_as_index_problematic() test_state_monitor() test_shared_variable() test_synapse_creation() test_synapse_creation_state_vars() test_synapse_creation_generator() test_synapse_creation_generator_complex_ranges() test_synapse_creation_generator_random() test_synapse_creation_generator_multiple_synapses() test_synapse_access() test_synapses_access_subgroups() test_synapses_access_subgroups_problematic() test_subgroup_summed_variable() test_subexpression_references() test_subexpression_no_references() test_synaptic_propagation() test_synaptic_propagation_2() test_run_regularly() test_spike_monitor() test_wrong_indexing() test_no_reference_1() test_no_reference_2() test_no_reference_3() test_no_reference_4() test_recursive_subgroup() brian2-2.5.4/brian2/tests/test_synapses.py000066400000000000000000003463711445201106100205040ustar00rootroot00000000000000import logging import uuid import pytest import sympy from numpy.testing import assert_array_equal, assert_equal from brian2 import * from brian2.codegen.generators import NumpyCodeGenerator from brian2.codegen.permutation_analysis import ( OrderDependenceError, check_for_order_independence, ) from brian2.codegen.translation import make_statements from brian2.core.functions import DEFAULT_FUNCTIONS from brian2.core.network import schedule_propagation_offset from brian2.core.variables import ArrayVariable, Constant, variables_by_owner from brian2.devices.cpp_standalone.device import CPPStandaloneDevice from brian2.devices.device import all_devices, get_device, reinit_and_delete from brian2.equations.equations import EquationError from brian2.stateupdaters.base import UnsupportedEquationsException from brian2.synapses.parse_synaptic_generator_syntax import parse_synapse_generator from brian2.tests.utils import assert_allclose, exc_isinstance from brian2.utils.logger import catch_logs from brian2.utils.stringtools import deindent, get_identifiers, indent, word_substitute def _compare(synapses, expected): conn_matrix = np.zeros((len(synapses.source), len(synapses.target)), dtype=np.int32) for _i, _j in zip(synapses.i[:], synapses.j[:]): conn_matrix[_i, _j] += 1 assert_equal(conn_matrix, expected) # also compare the correct numbers of incoming and outgoing synapses incoming = conn_matrix.sum(axis=0) outgoing = conn_matrix.sum(axis=1) assert all( synapses.N_outgoing[:] == outgoing[synapses.i[:]] ), "N_outgoing returned an incorrect value" assert_array_equal( synapses.N_outgoing_pre, outgoing ), "N_outgoing_pre returned an incorrect value" assert all( synapses.N_incoming[:] == incoming[synapses.j[:]] ), "N_incoming returned an incorrect value" assert_array_equal( synapses.N_incoming_post, incoming ), "N_incoming_post returned an incorrect value" # Compare the "synapse number" if it exists if synapses.multisynaptic_index is not None: # Build an array of synapse numbers by counting the number of times # a source/target combination exists synapse_numbers = np.zeros_like(synapses.i[:]) numbers = {} for _i, (source, target) in enumerate(zip(synapses.i[:], synapses.j[:])): number = numbers.get((source, target), 0) synapse_numbers[_i] = number numbers[(source, target)] = number + 1 assert all( synapses.state(synapses.multisynaptic_index)[:] == synapse_numbers ), "synapse_number returned an incorrect value" @pytest.mark.codegen_independent def test_creation(): """ A basic test that creating a Synapses object works. """ G = NeuronGroup(42, "v: 1", threshold="False") S = Synapses(G, G, "w:1", on_pre="v+=w") # We store weakref proxys, so we can't directly compare the objects assert S.source.name == S.target.name == G.name assert len(S) == 0 S = Synapses(G, model="w:1", on_pre="v+=w") assert S.source.name == S.target.name == G.name @pytest.mark.codegen_independent def test_creation_errors(): G = NeuronGroup(42, "v: 1", threshold="False") # Check that the old Synapses(..., connect=...) syntax raises an error with pytest.raises(TypeError): Synapses(G, G, "w:1", on_pre="v+=w", connect=True) # Check that using pre and on_pre (resp. post/on_post) at the same time # raises an error with pytest.raises(TypeError): Synapses(G, G, "w:1", pre="v+=w", on_pre="v+=w", connect=True) with pytest.raises(TypeError): Synapses(G, G, "w:1", post="v+=w", on_post="v+=w", connect=True) @pytest.mark.codegen_independent def test_connect_errors(): G = NeuronGroup(42, "") S = Synapses(G, G) # Not a boolean condition with pytest.raises(TypeError): S.connect("i*2") # Unit error with pytest.raises(DimensionMismatchError): S.connect("i > 3*mV") # Syntax error with pytest.raises(SyntaxError): S.connect("sin(3, 4) > 1") # Unit error in p argument with pytest.raises(TypeError): S.connect("1*mV") # Syntax error in p argument with pytest.raises(SyntaxError): S.connect(p="sin(3, 4)") @pytest.mark.codegen_independent def test_name_clashes(): # Using identical names for synaptic and pre- or post-synaptic variables # is confusing and should be forbidden G1 = NeuronGroup(1, "a : 1") G2 = NeuronGroup(1, "b : 1") with pytest.raises(ValueError): Synapses(G1, G2, "a : 1") with pytest.raises(ValueError): Synapses(G1, G2, "b : 1") # Using _pre or _post as variable names is confusing (even if it is non- # ambiguous in unconnected NeuronGroups) with pytest.raises(ValueError): Synapses(G1, G2, "x_pre : 1") with pytest.raises(ValueError): Synapses(G1, G2, "x_post : 1") with pytest.raises(ValueError): Synapses(G1, G2, "x_pre = 1 : 1") with pytest.raises(ValueError): Synapses(G1, G2, "x_post = 1 : 1") with pytest.raises(ValueError): NeuronGroup(1, "x_pre : 1") with pytest.raises(ValueError): NeuronGroup(1, "x_post : 1") with pytest.raises(ValueError): NeuronGroup(1, "x_pre = 1 : 1") with pytest.raises(ValueError): NeuronGroup(1, "x_post = 1 : 1") # this should all be ok Synapses(G1, G2, "c : 1") Synapses(G1, G2, "a_syn : 1") Synapses(G1, G2, "b_syn : 1") @pytest.mark.standalone_compatible def test_incoming_outgoing(): """ Test the count of outgoing/incoming synapses per neuron. (It will be also automatically tested for all connection patterns that use the above _compare function for testing) """ G1 = NeuronGroup(5, "") G2 = NeuronGroup(5, "") S = Synapses(G1, G2, "") S.connect(i=[0, 0, 0, 1, 1, 2], j=[0, 1, 2, 1, 2, 3]) run(0 * ms) # to make this work for standalone # First source neuron has 3 outgoing synapses, the second 2, the third 1 assert all(S.N_outgoing[0, :] == 3) assert all(S.N_outgoing[1, :] == 2) assert all(S.N_outgoing[2, :] == 1) assert all(S.N_outgoing[3:, :] == 0) assert_array_equal(S.N_outgoing_pre, [3, 2, 1, 0, 0]) # First target neuron receives 1 input, the second+third each 2, the fourth receives 1 assert all(S.N_incoming[:, 0] == 1) assert all(S.N_incoming[:, 1] == 2) assert all(S.N_incoming[:, 2] == 2) assert all(S.N_incoming[:, 3] == 1) assert all(S.N_incoming[:, 4:] == 0) assert_array_equal(S.N_incoming_post, [1, 2, 2, 1, 0]) @pytest.mark.standalone_compatible def test_connection_arrays(): """ Test connecting synapses with explictly given arrays """ G = NeuronGroup(42, "") G2 = NeuronGroup(17, "") # one-to-one expected1 = np.eye(len(G2)) S1 = Synapses(G2) S1.connect(i=np.arange(len(G2)), j=np.arange(len(G2))) # full expected2 = np.ones((len(G), len(G2))) S2 = Synapses(G, G2) X, Y = np.meshgrid(np.arange(len(G)), np.arange(len(G2))) S2.connect(i=X.flatten(), j=Y.flatten()) # Multiple synapses expected3 = np.zeros((len(G), len(G2))) expected3[3, 3] = 2 S3 = Synapses(G, G2) S3.connect(i=[3, 3], j=[3, 3]) run(0 * ms) # for standalone _compare(S1, expected1) _compare(S2, expected2) _compare(S3, expected3) # Incorrect usage S = Synapses(G, G2) with pytest.raises(TypeError): S.connect(i=[1.1, 2.2], j=[1.1, 2.2]) with pytest.raises(TypeError): S.connect(i=[1, 2], j="string") with pytest.raises(TypeError): S.connect(i=[1, 2], j=[1, 2], n="i") with pytest.raises(TypeError): S.connect([1, 2]) with pytest.raises(ValueError): S.connect(i=[1, 2, 3], j=[1, 2]) with pytest.raises(ValueError): S.connect(i=np.ones((3, 3), dtype=np.int32), j=np.ones((3, 1), dtype=np.int32)) with pytest.raises(IndexError): S.connect(i=[41, 42], j=[0, 1]) # source index > max with pytest.raises(IndexError): S.connect(i=[0, 1], j=[16, 17]) # target index > max with pytest.raises(IndexError): S.connect(i=[0, -1], j=[0, 1]) # source index < 0 with pytest.raises(IndexError): S.connect(i=[0, 1], j=[0, -1]) # target index < 0 with pytest.raises(ValueError): S.connect("i==j", j=np.arange(10)) with pytest.raises(TypeError): S.connect("i==j", n=object()) with pytest.raises(TypeError): S.connect("i==j", p=object()) with pytest.raises(TypeError): S.connect(object()) @pytest.mark.standalone_compatible def test_connection_string_deterministic_full(): G = NeuronGroup(17, "") G2 = NeuronGroup(4, "") # Full connection expected_full = np.ones((len(G), len(G2))) S1 = Synapses(G, G2, "") S1.connect(True) S2 = Synapses(G, G2, "") S2.connect("True") run(0 * ms) # for standalone _compare(S1, expected_full) _compare(S2, expected_full) @pytest.mark.standalone_compatible def test_connection_string_deterministic_full_no_self(): G = NeuronGroup(17, "v : 1") G.v = "i" G2 = NeuronGroup(4, "v : 1") G2.v = "17 + i" # Full connection without self-connections expected_no_self = np.ones((len(G), len(G))) - np.eye(len(G)) S1 = Synapses(G, G) S1.connect("i != j") S2 = Synapses(G, G) S2.connect("v_pre != v_post") S3 = Synapses(G, G) S3.connect(condition="i != j") run(0 * ms) # for standalone _compare(S1, expected_no_self) _compare(S2, expected_no_self) _compare(S3, expected_no_self) @pytest.mark.standalone_compatible def test_connection_string_deterministic_full_one_to_one(): G = NeuronGroup(17, "v : 1") G.v = "i" G2 = NeuronGroup(4, "v : 1") G2.v = "17 + i" # One-to-one connectivity expected_one_to_one = np.eye(len(G)) S1 = Synapses(G, G) S1.connect("i == j") S2 = Synapses(G, G) S2.connect("v_pre == v_post") S3 = Synapses( G, G, """ sub_1 = v_pre : 1 sub_2 = v_post : 1 w:1 """, ) S3.connect("sub_1 == sub_2") S4 = Synapses(G, G) S4.connect(j="i") run(0 * ms) # for standalone _compare(S1, expected_one_to_one) _compare(S2, expected_one_to_one) _compare(S3, expected_one_to_one) _compare(S4, expected_one_to_one) @pytest.mark.standalone_compatible def test_connection_string_deterministic_full_custom(): G = NeuronGroup(17, "") G2 = NeuronGroup(4, "") # Everything except for the upper [2, 2] quadrant number = 2 expected_custom = np.ones((len(G), len(G))) expected_custom[:number, :number] = 0 S1 = Synapses(G, G) S1.connect("(i >= number) or (j >= number)") S2 = Synapses(G, G) S2.connect( "(i >= explicit_number) or (j >= explicit_number)", namespace={"explicit_number": number}, ) # check that this mistaken syntax raises an error with pytest.raises(ValueError): S2.connect("k for k in range(1)") # check that trying to connect to a neuron outside the range raises an error if get_device() == all_devices["runtime"]: with pytest.raises(BrianObjectException) as exc: S2.connect(j="20") assert exc_isinstance(exc, IndexError) run(0 * ms) # for standalone _compare(S1, expected_custom) _compare(S2, expected_custom) @pytest.mark.standalone_compatible def test_connection_string_deterministic_multiple_and(): # In Brian versions 2.1.0-2.1.2, this fails on the numpy target # See github issue 900 group = NeuronGroup(10, "") synapses = Synapses(group, group) synapses.connect("i>=5 and i<10 and j>=5") run(0 * ms) # for standalone assert len(synapses) == 25 @pytest.mark.standalone_compatible def test_connection_random_with_condition(): G = NeuronGroup(4, "") S1 = Synapses(G, G) S1.connect("i!=j", p=0.0) S2 = Synapses(G, G) S2.connect("i!=j", p=1.0) expected2 = np.ones((len(G), len(G))) - np.eye(len(G)) S3 = Synapses(G, G) S3.connect("i>=2", p=0.0) S4 = Synapses(G, G) S4.connect("i>=2", p=1.0) expected4 = np.zeros((len(G), len(G))) expected4[2, :] = 1 expected4[3, :] = 1 S5 = Synapses(G, G) S5.connect("j<2", p=0.0) S6 = Synapses(G, G) S6.connect("j<2", p=1.0) expected6 = np.zeros((len(G), len(G))) expected6[:, 0] = 1 expected6[:, 1] = 1 with catch_logs() as _: # Ignore warnings about empty synapses run(0 * ms) # for standalone assert len(S1) == 0 _compare(S2, expected2) assert len(S3) == 0 _compare(S4, expected4) assert len(S5) == 0 _compare(S6, expected6) @pytest.mark.standalone_compatible @pytest.mark.long def test_connection_random_with_condition_2(): G = NeuronGroup(4, "") # Just checking that everything works in principle (we can't check the # actual connections) S7 = Synapses(G, G) S7.connect("i!=j", p=0.01) S8 = Synapses(G, G) S8.connect("i!=j", p=0.03) S9 = Synapses(G, G) S9.connect("i!=j", p=0.3) S10 = Synapses(G, G) S10.connect("i>=2", p=0.01) S11 = Synapses(G, G) S11.connect("i>=2", p=0.03) S12 = Synapses(G, G) S12.connect("i>=2", p=0.3) S13 = Synapses(G, G) S13.connect("j>=2", p=0.01) S14 = Synapses(G, G) S14.connect("j>=2", p=0.03) S15 = Synapses(G, G) S15.connect("j>=2", p=0.3) S16 = Synapses(G, G) S16.connect("i!=j", p="i*0.1") S17 = Synapses(G, G) S17.connect("i!=j", p="j*0.1") # Forces the use of the "jump algorithm" big_group = NeuronGroup(10000, "") S18 = Synapses(big_group, big_group) S18.connect("i != j", p=0.001) # See github issue #835 -- this failed when using numpy S19 = Synapses(big_group, big_group) S19.connect("i < int(N_post*0.5)", p=0.001) with catch_logs() as _: # Ignore warnings about empty synapses run(0 * ms) # for standalone assert not any(S7.i == S7.j) assert not any(S8.i == S8.j) assert not any(S9.i == S9.j) assert all(S10.i >= 2) assert all(S11.i >= 2) assert all(S12.i >= 2) assert all(S13.j >= 2) assert all(S14.j >= 2) assert all(S15.j >= 2) assert not any(S16.i == 0) assert not any(S17.j == 0) @pytest.mark.standalone_compatible def test_connection_random_with_indices(): """ Test random connections. """ G = NeuronGroup(4, "") G2 = NeuronGroup(7, "") S1 = Synapses(G, G2) S1.connect(i=0, j=0, p=0.0) expected1 = np.zeros((len(G), len(G2))) S2 = Synapses(G, G2) S2.connect(i=0, j=0, p=1.0) expected2 = np.zeros((len(G), len(G2))) expected2[0, 0] = 1 S3 = Synapses(G, G2) S3.connect(i=[0, 1], j=[0, 2], p=1.0) expected3 = np.zeros((len(G), len(G2))) expected3[0, 0] = 1 expected3[1, 2] = 1 # Just checking that it works in principle S4 = Synapses(G, G) S4.connect(i=0, j=0, p=0.01) S5 = Synapses(G, G) S5.connect(i=[0, 1], j=[0, 2], p=0.01) S6 = Synapses(G, G) S6.connect(i=0, j=0, p=0.03) S7 = Synapses(G, G) S7.connect(i=[0, 1], j=[0, 2], p=0.03) S8 = Synapses(G, G) S8.connect(i=0, j=0, p=0.3) S9 = Synapses(G, G) S9.connect(i=[0, 1], j=[0, 2], p=0.3) with catch_logs() as _: # Ignore warnings about empty synapses run(0 * ms) # for standalone _compare(S1, expected1) _compare(S2, expected2) _compare(S3, expected3) assert 0 <= len(S4) <= 1 assert 0 <= len(S5) <= 2 assert 0 <= len(S6) <= 1 assert 0 <= len(S7) <= 2 assert 0 <= len(S8) <= 1 assert 0 <= len(S9) <= 2 @pytest.mark.standalone_compatible def test_connection_random_without_condition(): G = NeuronGroup( 4, """ v: 1 x : integer """, ) G.x = "i" G2 = NeuronGroup( 7, """ v: 1 y : 1 """, ) G2.y = "1.0*i/N" S1 = Synapses(G, G2) S1.connect(True, p=0.0) S2 = Synapses(G, G2) S2.connect(True, p=1.0) # Just make sure using values between 0 and 1 work in principle S3 = Synapses(G, G2) S3.connect(True, p=0.3) # Use pre-/post-synaptic variables for "stochastic" connections that are # actually deterministic S4 = Synapses(G, G2) S4.connect(True, p="int(x_pre==2)*1.0") # Use pre-/post-synaptic variables for "stochastic" connections that are # actually deterministic S5 = Synapses(G, G2) S5.connect(True, p="int(x_pre==2 and y_post > 0.5)*1.0") with catch_logs() as _: # Ignore warnings about empty synapses run(0 * ms) # for standalone _compare(S1, np.zeros((len(G), len(G2)))) _compare(S2, np.ones((len(G), len(G2)))) assert 0 <= len(S3) <= len(G) * len(G2) assert len(S4) == 7 assert_equal(S4.i, np.ones(7) * 2) assert_equal(S4.j, np.arange(7)) assert len(S5) == 3 assert_equal(S5.i, np.ones(3) * 2) assert_equal(S5.j, np.arange(3) + 4) @pytest.mark.standalone_compatible def test_connection_multiple_synapses(): """ Test multiple synapses per connection. """ G = NeuronGroup(42, "v: 1") G.v = "i" G2 = NeuronGroup(17, "v: 1") G2.v = "i" S1 = Synapses(G, G2) S1.connect(True, n=0) S2 = Synapses(G, G2) S2.connect(True, n=2) S3 = Synapses(G, G2) S3.connect(True, n="j") S4 = Synapses(G, G2) S4.connect(True, n="i") S5 = Synapses(G, G2) S5.connect(True, n="int(i>j)*2") S6 = Synapses(G, G2) S6.connect(True, n="int(v_pre>v_post)*2") with catch_logs() as _: # Ignore warnings about empty synapses run(0 * ms) # for standalone assert len(S1) == 0 _compare(S2, 2 * np.ones((len(G), len(G2)))) _compare(S3, np.arange(len(G2)).reshape(1, len(G2)).repeat(len(G), axis=0)) _compare(S4, np.arange(len(G)).reshape(len(G), 1).repeat(len(G2), axis=1)) expected = np.zeros((len(G), len(G2)), dtype=np.int32) for source in range(len(G)): expected[source, :source] = 2 _compare(S5, expected) _compare(S6, expected) def test_state_variable_assignment(): """ Assign values to state variables in various ways """ G = NeuronGroup(10, "v: volt") G.v = "i*mV" S = Synapses(G, G, "w:volt") S.connect(True) # with unit checking assignment_expected = [ (5 * mV, np.ones(100) * 5 * mV), (7 * mV, np.ones(100) * 7 * mV), (S.i[:] * mV, S.i[:] * np.ones(100) * mV), ("5*mV", np.ones(100) * 5 * mV), ("i*mV", np.ones(100) * S.i[:] * mV), ("i*mV +j*mV", S.i[:] * mV + S.j[:] * mV), # reference to pre- and postsynaptic state variables ("v_pre", S.i[:] * mV), ("v_post", S.j[:] * mV), # ('i*mV + j*mV + k*mV', S.i[:]*mV + S.j[:]*mV + S.k[:]*mV) #not supported yet ] for assignment, expected in assignment_expected: S.w = 0 * volt S.w = assignment assert_allclose( S.w[:], expected, err_msg="Assigning %r gave incorrect result" % assignment ) S.w = 0 * volt S.w[:] = assignment assert_allclose( S.w[:], expected, err_msg="Assigning %r gave incorrect result" % assignment ) # without unit checking assignment_expected = [ (5, np.ones(100) * 5 * volt), (7, np.ones(100) * 7 * volt), (S.i[:], S.i[:] * np.ones(100) * volt), ("5", np.ones(100) * 5 * volt), ("i", np.ones(100) * S.i[:] * volt), ("i +j", S.i[:] * volt + S.j[:] * volt), # ('i + j + k', S.i[:]*volt + S.j[:]*volt + S.k[:]*volt) #not supported yet ] for assignment, expected in assignment_expected: S.w = 0 * volt S.w_ = assignment assert_allclose( S.w[:], expected, err_msg="Assigning %r gave incorrect result" % assignment ) S.w = 0 * volt S.w_[:] = assignment assert_allclose( S.w[:], expected, err_msg="Assigning %r gave incorrect result" % assignment ) def test_state_variable_indexing(): G1 = NeuronGroup(5, "v:volt") G1.v = "i*mV" G2 = NeuronGroup(7, "v:volt") G2.v = "10*mV + i*mV" S = Synapses(G1, G2, "w:1", multisynaptic_index="k") S.connect(True, n=2) S.w[:, :, 0] = "5*i + j" S.w[:, :, 1] = "35 + 5*i + j" # Slicing assert len(S.w[:]) == len(S.w[:, :]) == len(S.w[:, :, :]) == len(G1) * len(G2) * 2 assert len(S.w[0:, 0:]) == len(S.w[0:, 0:, 0:]) == len(G1) * len(G2) * 2 assert len(S.w[0::2, 0:]) == 3 * len(G2) * 2 assert len(S.w[0, :]) == len(S.w[0, :, :]) == len(G2) * 2 assert len(S.w[0:2, :]) == len(S.w[0:2, :, :]) == 2 * len(G2) * 2 assert len(S.w[:2, :]) == len(S.w[:2, :, :]) == 2 * len(G2) * 2 assert len(S.w[0:4:2, :]) == len(S.w[0:4:2, :, :]) == 2 * len(G2) * 2 assert len(S.w[:4:2, :]) == len(S.w[:4:2, :, :]) == 2 * len(G2) * 2 assert len(S.w[:, 0]) == len(S.w[:, 0, :]) == len(G1) * 2 assert len(S.w[:, 0:2]) == len(S.w[:, 0:2, :]) == 2 * len(G1) * 2 assert len(S.w[:, :2]) == len(S.w[:, :2, :]) == 2 * len(G1) * 2 assert len(S.w[:, 0:4:2]) == len(S.w[:, 0:4:2, :]) == 2 * len(G1) * 2 assert len(S.w[:, :4:2]) == len(S.w[:, :4:2, :]) == 2 * len(G1) * 2 assert len(S.w[:, :, 0]) == len(G1) * len(G2) assert len(S.w[:, :, 0:2]) == len(G1) * len(G2) * 2 assert len(S.w[:, :, :2]) == len(G1) * len(G2) * 2 assert len(S.w[:, :, 0:2:2]) == len(G1) * len(G2) assert len(S.w[:, :, :2:2]) == len(G1) * len(G2) # 1d indexing is directly indexing synapses! assert len(S.w[:]) == len(S.w[0:]) assert len(S.w[[0, 1]]) == len(S.w[3:5]) == 2 assert len(S.w[:]) == len(S.w[np.arange(len(G1) * len(G2) * 2)]) assert S.w[3] == S.w[np.int32(3)] == S.w[np.int64(3)] # See issue #888 # Array-indexing (not yet supported for synapse index) assert_equal(S.w[:, 0:3], S.w[:, [0, 1, 2]]) assert_equal(S.w[:, 0:3], S.w[np.arange(len(G1)), [0, 1, 2]]) # string-based indexing assert_equal(S.w[0:3, :], S.w["i<3"]) assert_equal(S.w[:, 0:3], S.w["j<3"]) assert_equal(S.w[:, :, 0], S.w["k == 0"]) assert_equal(S.w[0:3, :], S.w["v_pre < 2.5*mV"]) assert_equal(S.w[:, 0:3], S.w["v_post < 12.5*mV"]) # invalid indices with pytest.raises(IndexError): S.w.__getitem__((1, 2, 3, 4)) with pytest.raises(IndexError): S.w.__getitem__(object()) with pytest.raises(IndexError): S.w.__getitem__(1.5) def test_indices(): G = NeuronGroup(10, "v : 1") S = Synapses(G, G, "") S.connect() G.v = "i" assert_equal(S.indices[:], np.arange(10 * 10)) assert len(S.indices[5, :]) == 10 assert_equal(S.indices["v_pre >=5"], S.indices[5:, :]) assert_equal(S.indices["j >=5"], S.indices[:, 5:]) def test_subexpression_references(): """ Assure that subexpressions in targeted groups are handled correctly. """ G = NeuronGroup( 10, """ v : 1 v2 = 2*v : 1 """, ) G.v = np.arange(10) S = Synapses( G, G, """ w : 1 u = v2_post + 1 : 1 x = v2_pre + 1 : 1 """, ) S.connect("i==(10-1-j)") assert_equal(S.u[:], np.arange(10)[::-1] * 2 + 1) assert_equal(S.x[:], np.arange(10) * 2 + 1) @pytest.mark.standalone_compatible def test_constant_variable_subexpression_in_synapses(): G = NeuronGroup(10, "") S = Synapses( G, G, """ dv1/dt = -v1**2 / (10*ms) : 1 (clock-driven) dv2/dt = -v_const**2 / (10*ms) : 1 (clock-driven) dv3/dt = -v_var**2 / (10*ms) : 1 (clock-driven) dv4/dt = -v_noflag**2 / (10*ms) : 1 (clock-driven) v_const = v2 : 1 (constant over dt) v_var = v3 : 1 v_noflag = v4 : 1 """, method="rk2", ) S.connect(j="i") S.v1 = "1.0*i/N" S.v2 = "1.0*i/N" S.v3 = "1.0*i/N" S.v4 = "1.0*i/N" run(10 * ms) # "variable over dt" subexpressions are directly inserted into the equation assert_allclose(S.v3[:], S.v1[:]) assert_allclose(S.v4[:], S.v1[:]) # "constant over dt" subexpressions will keep a fixed value over the time # step and therefore give a slightly different result for multi-step # methods assert np.sum((S.v2 - S.v1) ** 2) > 1e-10 @pytest.mark.standalone_compatible def test_nested_subexpression_references(): """ Assure that subexpressions in targeted groups are handled correctly. """ G = NeuronGroup( 10, """ v : 1 v2 = 2*v : 1 v3 = 1.5*v2 : 1 """, threshold="v>=5", ) G2 = NeuronGroup(10, "v : 1") G.v = np.arange(10) S = Synapses(G, G2, on_pre="v_post += v3_pre") S.connect(j="i") run(defaultclock.dt) assert_allclose(G2.v[:5], 0.0) assert_allclose(G2.v[5:], (5 + np.arange(5)) * 3) @pytest.mark.codegen_independent def test_equations_unit_check(): group = NeuronGroup(1, "v : volt", threshold="True") syn = Synapses( group, group, """ sub1 = 3 : 1 sub2 = sub1 + 1*mV : volt """, on_pre="v += sub2", ) syn.connect() net = Network(group, syn) with pytest.raises(BrianObjectException) as exc: net.run(0 * ms) assert exc_isinstance(exc, DimensionMismatchError) def test_delay_specification(): # By default delays are state variables (i.e. arrays), but if they are # specified in the initializer, they are scalars. G = NeuronGroup(10, "x : meter", threshold="False") G.x = "i*mmeter" # Array delay S = Synapses(G, G, "w:1", on_pre="v+=w") S.connect(j="i") assert len(S.delay[:]) == len(G) S.delay = "i*ms" assert_allclose(S.delay[:], np.arange(len(G)) * ms) velocity = 1 * meter / second S.delay = "abs(x_pre - (N_post-j)*mmeter)/velocity" assert_allclose(S.delay[:], abs(G.x - (10 - G.i) * mmeter) / velocity) S.delay = 5 * ms assert_allclose(S.delay[:], np.ones(len(G)) * 5 * ms) # Setting delays without units S.delay_ = float(7 * ms) assert_allclose(S.delay[:], np.ones(len(G)) * 7 * ms) # Scalar delay S = Synapses(G, G, "w:1", on_pre="v+=w", delay=5 * ms) assert_allclose(S.delay[:], 5 * ms) S.connect(j="i") S.delay = "3*ms" assert_allclose(S.delay[:], 3 * ms) S.delay = 10 * ms assert_allclose(S.delay[:], 10 * ms) # Without units S.delay_ = float(20 * ms) assert_allclose(S.delay[:], 20 * ms) # Invalid arguments with pytest.raises(DimensionMismatchError): Synapses(G, G, "w:1", on_pre="v+=w", delay=5 * mV) with pytest.raises(TypeError): Synapses(G, G, "w:1", on_pre="v+=w", delay=object()) with pytest.raises(ValueError): Synapses(G, G, "w:1", delay=5 * ms) with pytest.raises(ValueError): Synapses(G, G, "w:1", on_pre="v+=w", delay={"post": 5 * ms}) def test_delays_pathways(): G = NeuronGroup(10, "x: meter", threshold="False") G.x = "i*mmeter" # Array delay S = Synapses(G, G, "w:1", on_pre={"pre1": "v+=w", "pre2": "v+=w"}, on_post="v-=w") S.connect(j="i") assert len(S.pre1.delay[:]) == len(G) assert len(S.pre2.delay[:]) == len(G) assert len(S.post.delay[:]) == len(G) S.pre1.delay = "i*ms" S.pre2.delay = "j*ms" velocity = 1 * meter / second S.post.delay = "abs(x_pre - (N_post-j)*mmeter)/velocity" assert_allclose(S.pre1.delay[:], np.arange(len(G)) * ms) assert_allclose(S.pre2.delay[:], np.arange(len(G)) * ms) assert_allclose(S.post.delay[:], abs(G.x - (10 - G.i) * mmeter) / velocity) S.pre1.delay = 5 * ms S.pre2.delay = 10 * ms S.post.delay = 1 * ms assert_allclose(S.pre1.delay[:], np.ones(len(G)) * 5 * ms) assert_allclose(S.pre2.delay[:], np.ones(len(G)) * 10 * ms) assert_allclose(S.post.delay[:], np.ones(len(G)) * 1 * ms) # Indexing with strings assert len(S.pre1.delay["j<5"]) == 5 assert_allclose(S.pre1.delay["j<5"], 5 * ms) # Indexing with 2d indices assert len(S.post.delay[[3, 4], :]) == 2 assert_allclose(S.post.delay[[3, 4], :], 1 * ms) assert len(S.pre2.delay[:, 7]) == 1 assert_allclose(S.pre2.delay[:, 7], 10 * ms) assert len(S.pre1.delay[[1, 2], [1, 2]]) == 2 assert_allclose(S.pre1.delay[[1, 2], [1, 2]], 5 * ms) # Scalar delay S = Synapses( G, G, "w:1", on_pre={"pre1": "v+=w", "pre2": "v+=w"}, on_post="v-=w", delay={"pre1": 5 * ms, "post": 1 * ms}, ) assert_allclose(S.pre1.delay[:], 5 * ms) assert_allclose(S.post.delay[:], 1 * ms) S.connect(j="i") assert len(S.pre2.delay[:]) == len(G) S.pre1.delay = 10 * ms assert_allclose(S.pre1.delay[:], 10 * ms) S.post.delay = "3*ms" assert_allclose(S.post.delay[:], 3 * ms) def test_delays_pathways_subgroups(): G = NeuronGroup(10, "x: meter", threshold="False") G.x = "i*mmeter" # Array delay S = Synapses( G[:5], G[5:], "w:1", on_pre={"pre1": "v+=w", "pre2": "v+=w"}, on_post="v-=w" ) S.connect(j="i") assert len(S.pre1.delay[:]) == 5 assert len(S.pre2.delay[:]) == 5 assert len(S.post.delay[:]) == 5 S.pre1.delay = "i*ms" S.pre2.delay = "j*ms" velocity = 1 * meter / second S.post.delay = "abs(x_pre - (N_post-j)*mmeter)/velocity" assert_allclose(S.pre1.delay[:], np.arange(5) * ms) assert_allclose(S.pre2.delay[:], np.arange(5) * ms) assert_allclose(S.post.delay[:], abs(G[:5].x - (5 - G[:5].i) * mmeter) / velocity) S.pre1.delay = 5 * ms S.pre2.delay = 10 * ms S.post.delay = 1 * ms assert_allclose(S.pre1.delay[:], np.ones(5) * 5 * ms) assert_allclose(S.pre2.delay[:], np.ones(5) * 10 * ms) assert_allclose(S.post.delay[:], np.ones(5) * 1 * ms) @pytest.mark.codegen_independent def test_pre_before_post(): # The pre pathway should be executed before the post pathway G = NeuronGroup( 1, """ x : 1 y : 1 """, threshold="True", ) S = Synapses(G, G, "", on_pre="x=1; y=1", on_post="x=2") S.connect() run(defaultclock.dt) # Both pathways should have been executed, but post should have overriden # the x value (because it was executed later) assert G.x == 2 assert G.y == 1 @pytest.mark.standalone_compatible def test_pre_post_simple(): # Test that pre and post still work correctly G1 = SpikeGeneratorGroup(1, [0], [1] * ms) G2 = SpikeGeneratorGroup(1, [0], [2] * ms) with catch_logs() as l: S = Synapses( G1, G2, """ pre_value : 1 post_value : 1 """, pre="pre_value +=1", post="post_value +=2", ) S.connect() syn_mon = StateMonitor(S, ["pre_value", "post_value"], record=[0], when="end") run(3 * ms) offset = schedule_propagation_offset() assert_allclose(syn_mon.pre_value[0][syn_mon.t < 1 * ms + offset], 0) assert_allclose(syn_mon.pre_value[0][syn_mon.t >= 1 * ms + offset], 1) assert_allclose(syn_mon.post_value[0][syn_mon.t < 2 * ms + offset], 0) assert_allclose(syn_mon.post_value[0][syn_mon.t >= 2 * ms + offset], 2) @pytest.mark.standalone_compatible def test_transmission_simple(): source = SpikeGeneratorGroup(2, [0, 1], [2, 1] * ms) target = NeuronGroup(2, "v : 1") syn = Synapses(source, target, on_pre="v += 1") syn.connect(j="i") mon = StateMonitor(target, "v", record=True, when="end") run(2.5 * ms) offset = schedule_propagation_offset() assert_allclose(mon[0].v[mon.t < 2 * ms + offset], 0.0) assert_allclose(mon[0].v[mon.t >= 2 * ms + offset], 1.0) assert_allclose(mon[1].v[mon.t < 1 * ms + offset], 0.0) assert_allclose(mon[1].v[mon.t >= 1 * ms + offset], 1.0) @pytest.mark.standalone_compatible def test_transmission_custom_event(): source = NeuronGroup( 2, "", events={ "custom": ( "timestep(t,dt)>=timestep((2-i)*ms, dt) " "and timestep(t,dt)= 2 * ms], 1.0) assert_allclose(mon[1].v[mon.t < 1 * ms], 0.0) assert_allclose(mon[1].v[mon.t >= 1 * ms], 1.0) @pytest.mark.codegen_independent def test_invalid_custom_event(): group1 = NeuronGroup( 2, "v : 1", events={ "custom": ( "timestep(t,dt)>=timesteep((2-i)*ms,dt) " "and timestep(t, dt)= 0.5 * ms + offset - defaultclock.dt / 2], 1) assert_allclose(mon[1].v[mon.t < 1.5 * ms + offset - defaultclock.dt / 2], 0) assert_allclose(mon[1].v[mon.t >= 1.5 * ms + offset - defaultclock.dt / 2], 1) @pytest.mark.standalone_compatible def test_transmission_scalar_delay_different_clocks(): inp = SpikeGeneratorGroup( 2, [0, 1], [0, 1] * ms, dt=0.5 * ms, # give the group a unique name to always # get a 'fresh' warning name="sg_%d" % uuid.uuid4(), ) target = NeuronGroup(2, "v:1", dt=0.1 * ms) S = Synapses(inp, target, on_pre="v+=1", delay=0.5 * ms) S.connect(j="i") mon = StateMonitor(target, "v", record=True, when="end") if get_device() == all_devices["runtime"]: # We should get a warning when using inconsistent dts with catch_logs() as l: run(2 * ms) assert len(l) == 1, "expected a warning, got %d" % len(l) assert l[0][1].endswith("synapses_dt_mismatch") run(0 * ms) assert_allclose(mon[0].v[mon.t < 0.5 * ms], 0) assert_allclose(mon[0].v[mon.t >= 0.5 * ms], 1) assert_allclose(mon[1].v[mon.t < 1.5 * ms], 0) assert_allclose(mon[1].v[mon.t >= 1.5 * ms], 1) @pytest.mark.standalone_compatible def test_transmission_boolean_variable(): source = SpikeGeneratorGroup(4, [0, 1, 2, 3], [2, 1, 2, 1] * ms) target = NeuronGroup(4, "v : 1") syn = Synapses(source, target, "use : boolean (constant)", on_pre="v += int(use)") syn.connect(j="i") syn.use = "i<2" mon = StateMonitor(target, "v", record=True, when="end") run(2.5 * ms) offset = schedule_propagation_offset() assert_allclose(mon[0].v[mon.t < 2 * ms + offset], 0.0) assert_allclose(mon[0].v[mon.t >= 2 * ms + offset], 1.0) assert_allclose(mon[1].v[mon.t < 1 * ms + offset], 0.0) assert_allclose(mon[1].v[mon.t >= 1 * ms + offset], 1.0) assert_allclose(mon[2].v, 0.0) assert_allclose(mon[3].v, 0.0) @pytest.mark.codegen_independent def test_clocks(): """ Make sure that a `Synapse` object uses the correct clocks. """ source_dt = 0.05 * ms target_dt = 0.1 * ms synapse_dt = 0.2 * ms source = NeuronGroup(1, "v:1", dt=source_dt, threshold="False") target = NeuronGroup(1, "v:1", dt=target_dt, threshold="False") synapse = Synapses( source, target, "w:1", on_pre="v+=1", on_post="v+=1", dt=synapse_dt ) synapse.connect() assert synapse.pre.clock is source.clock assert synapse.post.clock is target.clock assert synapse.pre._clock.dt == source_dt assert synapse.post._clock.dt == target_dt assert synapse._clock.dt == synapse_dt def test_equations_with_clocks(): """ Make sure that dt of a `Synapse` object is correctly resolved. """ source_dt = 0.1 * ms synapse_dt = 1 * ms source_target = NeuronGroup(1, "v:1", dt=source_dt, threshold="False") synapse = Synapses( source_target, source_target, "dw/dt = 1/ms : 1 (clock-driven)", dt=synapse_dt, method="euler", ) synapse.connect() synapse.w = 0 run(1 * ms) assert synapse.w[0] == 1 def test_changed_dt_spikes_in_queue(): defaultclock.dt = 0.5 * ms G1 = NeuronGroup(1, "v:1", threshold="v>1", reset="v=0") G1.v = 1.1 G2 = NeuronGroup(10, "v:1", threshold="v>1", reset="v=0") S = Synapses(G1, G2, on_pre="v+=1.1") S.connect(True) S.delay = "j*ms" mon = SpikeMonitor(G2) net = Network(G1, G2, S, mon) net.run(5 * ms) defaultclock.dt = 1 * ms net.run(3 * ms) defaultclock.dt = 0.1 * ms net.run(2 * ms) # Spikes should have delays of 0, 1, 2, ... ms and always # trigger a spike one dt later expected = [ 0.5, 1.5, 2.5, 3.5, 4.5, # dt=0.5ms 6, 7, 8, # dt = 1ms 8.1, 9.1, # dt=0.1ms ] * ms assert_allclose(mon.t[:], expected) @pytest.mark.codegen_independent def test_no_synapses(): # Synaptic pathway but no synapses G1 = NeuronGroup(1, "", threshold="True") G2 = NeuronGroup(1, "v:1") S = Synapses(G1, G2, on_pre="v+=1") net = Network(G1, G2, S) with pytest.raises(BrianObjectException) as exc: net.run(0 * ms) assert exc_isinstance(exc, TypeError) @pytest.mark.codegen_independent def test_no_synapses_variable_write(): # Synaptic pathway but no synapses G1 = NeuronGroup(1, "", threshold="True") G2 = NeuronGroup(1, "v:1") S = Synapses(G1, G2, "w : 1", on_pre="v+=w") # Setting synaptic variables before calling connect is not allowed with pytest.raises(TypeError): setattr(S, "w", 1) with pytest.raises(TypeError): setattr(S, "delay", 1 * ms) @pytest.mark.standalone_compatible def test_summed_variable(): source = NeuronGroup(2, "v : volt", threshold="v>1*volt", reset="v=0*volt") source.v = 1.1 * volt # will spike immediately target = NeuronGroup(2, "v : volt") S = Synapses( source, target, """ w : volt x : volt v_post = 2*x : volt (summed) """, on_pre="x+=w", multisynaptic_index="k", ) S.connect("i==j", n=2) S.w["k == 0"] = "i*volt" S.w["k == 1"] = "(i + 0.5)*volt" net = Network(source, target, S) net.run(1 * ms) # v of the target should be the sum of the two weights assert_allclose(target.v, np.array([1.0, 5.0]) * volt) @pytest.mark.standalone_compatible def test_summed_variable_pre_and_post(): G1 = NeuronGroup( 4, """ neuron_var : 1 syn_sum : 1 neuron_sum : 1 """, ) G1.neuron_var = "i" G2 = NeuronGroup( 4, """ neuron_var : 1 syn_sum : 1 neuron_sum : 1 """, ) G2.neuron_var = "i+4" synapses = Synapses( G1, G2, """ syn_var : 1 neuron_sum_pre = neuron_var_post : 1 (summed) syn_sum_pre = syn_var : 1 (summed) neuron_sum_post = neuron_var_pre : 1 (summed) syn_sum_post = syn_var : 1 (summed) """, ) # The first three cells in G1 connect to the first cell in G2 # The remaining three cells of G2 all connect to the last cell of G1 synapses.connect(i=[0, 1, 2, 3, 3, 3], j=[0, 0, 0, 1, 2, 3]) synapses.syn_var = [0, 1, 2, 3, 4, 5] run(defaultclock.dt) assert_allclose(G1.syn_sum[:], [0, 1, 2, 12]) assert_allclose(G1.neuron_sum[:], [4, 4, 4, 18]) assert_allclose(G2.syn_sum[:], [3, 3, 4, 5]) assert_allclose(G2.neuron_sum[:], [3, 3, 3, 3]) @pytest.mark.standalone_compatible def test_summed_variable_differing_group_size(): G1 = NeuronGroup(2, "var : 1", name="G1") G2 = NeuronGroup(10, "var : 1", name="G2") G2.var[:5] = 1 G2.var[5:] = 10 syn1 = Synapses( G1, G2, """ syn_var : 1 var_pre = syn_var + var_post : 1 (summed) """, ) syn1.connect(i=0, j=[0, 1, 2, 3, 4]) syn1.connect(i=1, j=[5, 6, 7, 8, 9]) syn1.syn_var = np.arange(10) # The same in the other direction G3 = NeuronGroup(10, "var : 1", name="G3") G4 = NeuronGroup(2, "var : 1", name="G4") G3.var[:5] = 1 G3.var[5:] = 10 syn2 = Synapses( G3, G4, """ syn_var : 1 var_post = syn_var + var_pre : 1 (summed) """, ) syn2.connect(i=[0, 1, 2, 3, 4], j=0) syn2.connect(i=[5, 6, 7, 8, 9], j=1) syn2.syn_var = np.arange(10) run(defaultclock.dt) assert_allclose(G1.var[0], 5 * 1 + 0 + 1 + 2 + 3 + 4) assert_allclose(G1.var[1], 5 * 10 + 5 + 6 + 7 + 8 + 9) assert_allclose(G4.var[0], 5 * 1 + 0 + 1 + 2 + 3 + 4) assert_allclose(G4.var[1], 5 * 10 + 5 + 6 + 7 + 8 + 9) def test_summed_variable_errors(): G = NeuronGroup( 10, """ dv/dt = -v / (10*ms) : volt sub = 2*v : volt p : volt """, threshold="False", reset="", ) # Using the (summed) flag for a differential equation or a parameter with pytest.raises(ValueError): Synapses(G, G, """dp_post/dt = -p_post / (10*ms) : volt (summed)""") with pytest.raises(ValueError): Synapses(G, G, """p_post : volt (summed)""") # Using the (summed) flag for a variable name without _pre or _post suffix with pytest.raises(ValueError): Synapses(G, G, """p = 3*volt : volt (summed)""") # Using the name of a variable that does not exist with pytest.raises(ValueError): Synapses(G, G, """q_post = 3*volt : volt (summed)""") # Target equation is not a parameter with pytest.raises(ValueError): Synapses(G, G, """sub_post = 3*volt : volt (summed)""") with pytest.raises(ValueError): Synapses(G, G, """v_post = 3*volt : volt (summed)""") # Unit mismatch between synapses and target with pytest.raises(DimensionMismatchError): Synapses(G, G, """p_post = 3*second : second (summed)""") # Two summed variable equations targetting the same variable with pytest.raises(ValueError): Synapses( G, G, """ p_post = 3*volt : volt (summed) p_pre = 3*volt : volt (summed) """, ) # Summed variable referring to an event-driven variable with pytest.raises(EquationError) as ex: Synapses( G, G, """ ds/dt = -s/(3*ms) : volt (event-driven) p_post = s : volt (summed) """, on_pre="s += 1*mV", ) assert "'p_post'" in str(ex.value) and "'s'" in str(ex.value) # Indirect dependency with pytest.raises(EquationError) as ex: Synapses( G, G, """ ds/dt = -s/(3*ms) : volt (event-driven) x = s : volt y = x : volt p_post = y : 1 (summed) """, on_pre="s += 1*mV", ) assert "'p_post'" in str(ex.value) and "'s'" in str(ex.value) assert "'x'" in str(ex.value) and "'y'" in str(ex.value) with pytest.raises(BrianObjectException) as ex: S = Synapses( G, G, """ y : siemens p_post = y : volt (summed) """, ) run(0 * ms) assert isinstance(ex.value.__cause__, DimensionMismatchError) @pytest.mark.codegen_independent def test_multiple_summed_variables(): # See github issue #766 source = NeuronGroup(1, "") target = NeuronGroup(10, "v : 1") syn1 = Synapses(source, target, "v_post = 1 : 1 (summed)") syn1.connect() syn2 = Synapses(source, target, "v_post = 1 : 1 (summed)") syn2.connect() net = Network(collect()) with pytest.raises(NotImplementedError): net.run(0 * ms) @pytest.mark.standalone_compatible def test_summed_variables_subgroups(): source = NeuronGroup(1, "") target = NeuronGroup(10, "v : 1") subgroup1 = target[:6] subgroup2 = target[6:] syn1 = Synapses(source, subgroup1, "v_post = 1 : 1 (summed)") syn1.connect(n=2) syn2 = Synapses(source, subgroup2, "v_post = 1 : 1 (summed)") syn2.connect() run(defaultclock.dt) assert_allclose(target.v[:6], 2 * np.ones(6)) assert_allclose(target.v[6:], 1 * np.ones(4)) @pytest.mark.codegen_independent def test_summed_variables_overlapping_subgroups(): # See github issue #766 source = NeuronGroup(1, "") target = NeuronGroup(10, "v : 1") # overlapping subgroups subgroup1 = target[:7] subgroup2 = target[6:] syn1 = Synapses(source, subgroup1, "v_post = 1 : 1 (summed)") syn1.connect(n=2) syn2 = Synapses(source, subgroup2, "v_post = 1 : 1 (summed)") syn2.connect() net = Network(collect()) with pytest.raises(NotImplementedError): net.run(0 * ms) @pytest.mark.codegen_independent def test_summed_variables_linked_variables(): source = NeuronGroup(1, "") target1 = NeuronGroup(10, "v : 1") target2 = NeuronGroup(10, "v : 1 (linked)") target2.v = linked_var(target1.v) # Seemingly independent targets, but the variable is the same syn1 = Synapses(source, target1, "v_post = 1 : 1 (summed)") syn1.connect() syn2 = Synapses(source, target2, "v_post = 1 : 1 (summed)") syn2.connect() net = Network(collect()) with pytest.raises(NotImplementedError): net.run(0 * ms) def test_scalar_parameter_access(): G = NeuronGroup( 10, """ v : 1 scalar : Hz (shared) """, threshold="False", ) S = Synapses( G, G, """ w : 1 s : Hz (shared) number : 1 (shared) """, on_pre="v+=w*number", ) S.connect() # Try setting a scalar variable S.s = 100 * Hz assert_allclose(S.s[:], 100 * Hz) S.s[:] = 200 * Hz assert_allclose(S.s[:], 200 * Hz) S.s = "s - 50*Hz + number*Hz" assert_allclose(S.s[:], 150 * Hz) S.s[:] = "50*Hz" assert_allclose(S.s[:], 50 * Hz) # Set a postsynaptic scalar variable S.scalar_post = 100 * Hz assert_allclose(G.scalar[:], 100 * Hz) S.scalar_post[:] = 100 * Hz assert_allclose(G.scalar[:], 100 * Hz) # Check the second method of accessing that works assert_allclose(np.asanyarray(S.s), 50 * Hz) # Check error messages with pytest.raises(IndexError): S.s[0] with pytest.raises(IndexError): S.s[1] with pytest.raises(IndexError): S.s[0:1] with pytest.raises(IndexError): S.s["i>5"] with pytest.raises(ValueError): S.s.set_item(slice(None), [0, 1] * Hz) with pytest.raises(IndexError): S.s.set_item(0, 100 * Hz) with pytest.raises(IndexError): S.s.set_item(1, 100 * Hz) with pytest.raises(IndexError): S.s.set_item("i>5", 100 * Hz) def test_scalar_subexpression(): G = NeuronGroup( 10, """ v : 1 number : 1 (shared) """, threshold="False", ) S = Synapses( G, G, """ s : 1 (shared) sub = number_post + s : 1 (shared) """, on_pre="v+=s", ) S.connect() S.s = 100 G.number = 50 assert S.sub[:] == 150 with pytest.raises(SyntaxError): Synapses( G, G, """ s : 1 (shared) sub = v_post + s : 1 (shared) """, on_pre="v+=s", ) @pytest.mark.standalone_compatible def test_sim_with_scalar_variable(): inp = SpikeGeneratorGroup(2, [0, 1], [0, 0] * ms) out = NeuronGroup(2, "v : 1") syn = Synapses( inp, out, """ w : 1 s : 1 (shared) """, on_pre="v += s + w", ) syn.connect(j="i") syn.w = [1, 2] syn.s = 5 run(2 * defaultclock.dt) assert_allclose(out.v[:], [6, 7]) @pytest.mark.standalone_compatible def test_sim_with_scalar_subexpression(): inp = SpikeGeneratorGroup(2, [0, 1], [0, 0] * ms) out = NeuronGroup(2, "v : 1") syn = Synapses( inp, out, """ w : 1 s = 5 : 1 (shared) """, on_pre="v += s + w", ) syn.connect(j="i") syn.w = [1, 2] run(2 * defaultclock.dt) assert_allclose(out.v[:], [6, 7]) @pytest.mark.standalone_compatible def test_sim_with_constant_subexpression(): inp = SpikeGeneratorGroup(2, [0, 1], [0, 0] * ms) out = NeuronGroup(2, "v : 1") syn = Synapses( inp, out, """ w : 1 s = 5 : 1 (constant over dt) """, on_pre="v += s + w", ) syn.connect(j="i") syn.w = [1, 2] run(2 * defaultclock.dt) assert_allclose(out.v[:], [6, 7]) @pytest.mark.standalone_compatible def test_external_variables(): # Make sure that external variables are correctly resolved source = SpikeGeneratorGroup(1, [0], [0] * ms) target = NeuronGroup(1, "v:1") w_var = 1 amplitude = 2 syn = Synapses(source, target, "w=w_var : 1", on_pre="v+=amplitude*w") syn.connect() run(defaultclock.dt) assert target.v[0] == 2 @pytest.mark.standalone_compatible def test_event_driven(): # Fake example, where the synapse is actually not changing the state of the # postsynaptic neuron, the pre- and post spiketrains are regular spike # trains with different rates pre = NeuronGroup( 2, """ dv/dt = rate : 1 rate : Hz """, threshold="v>1", reset="v=0", ) pre.rate = [1000, 1500] * Hz post = NeuronGroup( 2, """ dv/dt = rate : 1 rate : Hz """, threshold="v>1", reset="v=0", ) post.rate = [1100, 1400] * Hz # event-driven formulation taupre = 20 * ms taupost = taupre gmax = 0.01 dApre = 0.01 dApost = -dApre * taupre / taupost * 1.05 dApost *= gmax dApre *= gmax # event-driven S1 = Synapses( pre, post, """ w : 1 dApre/dt = -Apre/taupre : 1 (event-driven) dApost/dt = -Apost/taupost : 1 (event-driven) """, on_pre=""" Apre += dApre w = clip(w+Apost, 0, gmax) """, on_post=""" Apost += dApost w = clip(w+Apre, 0, gmax) """, ) S1.connect(j="i") # not event-driven S2 = Synapses( pre, post, """ w : 1 Apre : 1 Apost : 1 lastupdate : second """, on_pre=""" Apre=Apre*exp((lastupdate-t)/taupre)+dApre Apost=Apost*exp((lastupdate-t)/taupost) w = clip(w+Apost, 0, gmax) lastupdate = t """, on_post=""" Apre=Apre*exp((lastupdate-t)/taupre) Apost=Apost*exp((lastupdate-t)/taupost) +dApost w = clip(w+Apre, 0, gmax) lastupdate = t """, ) S2.connect(j="i") S1.w = 0.5 * gmax S2.w = 0.5 * gmax run(25 * ms) # The two formulations should yield identical results assert_allclose(S1.w[:], S2.w[:]) @pytest.mark.codegen_independent def test_event_driven_dependency_checks(): dummy = NeuronGroup(1, "", threshold="False", reset="") # Dependency on parameter syn = Synapses( dummy, dummy, """ da/dt = (a-b) / (5*ms): 1 (event-driven) b : 1""", on_pre="b+=1", ) syn.connect() # Dependency on parameter via subexpression syn2 = Synapses( dummy, dummy, """ da/dt = (a-b) / (5*ms): 1 (event-driven) b = c : 1 c : 1""", on_pre="c+=1", ) syn2.connect() run(0 * ms) @pytest.mark.codegen_independent def test_event_driven_dependency_error(): stim = SpikeGeneratorGroup(1, [0], [0] * ms, period=5 * ms) syn = Synapses( stim, stim, """ da/dt = -a / (5*ms) : 1 (event-driven) db/dt = -b / (5*ms) : 1 (event-driven) dc/dt = a*b / (5*ms) : 1 (event-driven)""", on_pre="a+=1", ) syn.connect() net = Network(collect()) with pytest.raises(BrianObjectException) as exc: net.run(0 * ms) assert exc_isinstance(exc, UnsupportedEquationsException) @pytest.mark.codegen_independent def test_event_driven_dependency_error2(): stim = SpikeGeneratorGroup(1, [0], [0] * ms, period=5 * ms) tau = 5 * ms with pytest.raises(EquationError) as exc: syn = Synapses( stim, stim, """ da/dt = -a / (5*ms) : 1 (clock-driven) db/dt = -b / (5*ms) : 1 (clock-driven) dc/dt = a*b / (5*ms) : 1 (event-driven) """, on_pre="a+=1", ) assert "'c'" in str(exc.value) and ( "'a'" in str(exc.value) or "'b'" in str(exc.value) ) # Indirect dependency with pytest.raises(EquationError) as exc: syn = Synapses( stim, stim, """ da/dt = -a / (5*ms) : 1 (clock-driven) b = a : 1 dc/dt = b / (5*ms) : 1 (event-driven) """, on_pre="a+=1", ) assert ( "'c'" in str(exc.value) and "'a'" in str(exc.value) and "'b'" in str(exc.value) ) @pytest.mark.codegen_independent def test_event_driven_dependency_error3(): P = NeuronGroup(10, "dv/dt = -v/(10*ms) : volt") with pytest.raises(EquationError) as ex: Synapses( P, P, """ ds/dt = -s/(3*ms) : 1 (event-driven) df/dt = f*s/(5*ms) : 1 (clock-driven) """, on_pre="s += 1", ) assert "'s'" in str(ex.value) and "'f'" in str(ex.value) # Indirect dependency with pytest.raises(EquationError) as ex: Synapses( P, P, """ ds/dt = -s/(3*ms) : 1 (event-driven) x = s : 1 y = x : 1 df/dt = f*y/(5*ms) : 1 (clock-driven) """, on_pre="s += 1", ) assert "'s'" in str(ex.value) and "'f'" in str(ex.value) assert "'x'" in str(ex.value) and "'y'" in str(ex.value) @pytest.mark.codegen_independent def test_repr(): G = NeuronGroup(1, "v: volt", threshold="False") S = Synapses( G, G, """ w : 1 dApre/dt = -Apre/taupre : 1 (event-driven) dApost/dt = -Apost/taupost : 1 (event-driven) """, on_pre=""" Apre += dApre w = clip(w+Apost, 0, gmax) """, on_post=""" Apost += dApost w = clip(w+Apre, 0, gmax) """, ) # Test that string/LaTeX representations do not raise errors for func in [str, repr, sympy.latex]: assert len(func(S.equations)) @pytest.mark.codegen_independent def test_pre_post_variables(): G = NeuronGroup(10, "v : 1", threshold="False") G2 = NeuronGroup( 10, """ v : 1 w : 1 """, threshold="False", ) S = Synapses(G, G2, "x : 1") # Check for the most important variables for var in [ "v_pre", "v", "v_post", "w", "w_post", "x", "N_pre", "N_post", "N_incoming", "N_outgoing", "i", "j", "t", "dt", ]: assert var in S.variables # Check that postsynaptic variables without suffix refer to the correct # variable assert S.variables["v"] is S.variables["v_post"] assert S.variables["w"] is S.variables["w_post"] # Check that internal pre-/post-synaptic variables are not accessible assert "_spikespace_pre" not in S.variables assert "_spikespace" not in S.variables assert "_spikespace_post" not in S.variables @pytest.mark.codegen_independent def test_variables_by_owner(): # Test the `variables_by_owner` convenience function G = NeuronGroup(10, "v : 1") G2 = NeuronGroup( 10, """ v : 1 w : 1 """, ) S = Synapses(G, G2, "x : 1") # Check that the variables returned as owned by the pre/post groups are the # variables stored in the respective groups. We only compare the `Variable` # objects, as the names may be different (e.g. ``v_post`` vs. ``v``) G_variables = { key: value for key, value in G.variables.items() if value.owner.name == G.name } # exclude dt G2_variables = { key: value for key, value in G2.variables.items() if value.owner.name == G2.name } assert set(G_variables.values()) == set(variables_by_owner(S.variables, G).values()) assert set(G2_variables.values()) == set( variables_by_owner(S.variables, G2).values() ) assert len(set(variables_by_owner(S.variables, S)) & set(G_variables.values())) == 0 assert ( len(set(variables_by_owner(S.variables, S)) & set(G2_variables.values())) == 0 ) # Just test a few examples for synaptic variables assert all( varname in variables_by_owner(S.variables, S) for varname in ["x", "N", "N_incoming", "N_outgoing"] ) @pytest.mark.codegen_independent def check_permutation_code(code): from collections import defaultdict vars = get_identifiers(code) indices = defaultdict(lambda: "_idx") for var in vars: if var.endswith("_syn"): indices[var] = "_idx" elif var.endswith("_pre"): indices[var] = "_presynaptic_idx" elif var.endswith("_post"): indices[var] = "_postsynaptic_idx" elif var.endswith("_const"): indices[var] = "0" variables = dict() variables.update(DEFAULT_FUNCTIONS) for var in indices: if var.endswith("_const"): variables[var] = Constant(var, 42, owner=device) else: variables[var] = ArrayVariable(var, None, 10, device) variables["_presynaptic_idx"] = ArrayVariable(var, None, 10, device) variables["_postsynaptic_idx"] = ArrayVariable(var, None, 10, device) scalar_statements, vector_statements = make_statements(code, variables, float64) check_for_order_independence(vector_statements, variables, indices) def numerically_check_permutation_code(code): # numerically checks that a code block used in the test below is permutation-independent by creating a # presynaptic and postsynaptic group of 3 neurons each, and a full connectivity matrix between them, then # repeatedly filling in random values for each of the variables, and checking for several random shuffles of # the synapse order that the result doesn't depend on it. This is a sort of test of the test itself, to make # sure we didn't accidentally assign a good/bad example to the wrong class. code = deindent(code) from collections import defaultdict vars = get_identifiers(code) indices = defaultdict(lambda: "_idx") vals = {} for var in vars: if var.endswith("_syn"): indices[var] = "_idx" vals[var] = zeros(9) elif var.endswith("_pre"): indices[var] = "_presynaptic_idx" vals[var] = zeros(3) elif var.endswith("_post"): indices[var] = "_postsynaptic_idx" vals[var] = zeros(3) elif var.endswith("_shared"): indices[var] = "0" vals[var] = zeros(1) elif var.endswith("_const"): indices[var] = "0" vals[var] = 42 subs = { var: var + "[" + idx + "]" for var, idx in indices.items() if not var.endswith("_const") } code = word_substitute(code, subs) code = f""" from numpy import * from numpy.random import rand, randn for _idx in shuffled_indices: _presynaptic_idx = presyn[_idx] _postsynaptic_idx = postsyn[_idx] {indent(code)} """ ns = vals.copy() ns["shuffled_indices"] = arange(9) ns["presyn"] = arange(9) % 3 ns["postsyn"] = arange(9) / 3 for _ in range(10): origvals = {} for k, v in vals.items(): if not k.endswith("_const"): v[:] = randn(len(v)) origvals[k] = v.copy() exec(code, ns) endvals = {} for k, v in vals.items(): endvals[k] = copy(v) for _ in range(10): for k, v in vals.items(): if not k.endswith("_const"): v[:] = origvals[k] shuffle(ns["shuffled_indices"]) exec(code, ns) for k, v in vals.items(): try: assert_allclose(v, endvals[k]) except AssertionError: raise OrderDependenceError() SANITY_CHECK_PERMUTATION_ANALYSIS_EXAMPLE = False permutation_analysis_good_examples = [ "v_post += w_syn", "v_post *= w_syn", "v_post = v_post + w_syn", "v_post = v_post * w_syn", "v_post = w_syn * v_post", "v_post += 1", "v_post = 1", "v_post = c_const", "v_post = x_shared", "v_post += v_post # NOT_UFUNC_AT_VECTORISABLE", "v_post += c_const", "v_post += x_shared", #'v_post += w_syn*v_post', # this is a hard one (it is good for w*v but bad for w+v) "v_post += sin(-v_post) # NOT_UFUNC_AT_VECTORISABLE", "v_post += u_post", "v_post += w_syn*v_pre", "v_post += sin(-v_post) # NOT_UFUNC_AT_VECTORISABLE", "v_post -= sin(v_post) # NOT_UFUNC_AT_VECTORISABLE", "v_post += v_pre", "v_pre += v_post", "v_pre += c_const", "v_pre += x_shared", "w_syn = v_pre", "w_syn = a_syn", "w_syn += a_syn", "w_syn *= a_syn", "w_syn -= a_syn", "w_syn /= a_syn", "w_syn += 1", "w_syn += c_const", "w_syn += x_shared", "w_syn *= 2", "w_syn *= c_const", "w_syn *= x_shared", """ w_syn = a_syn a_syn += 1 """, """ w_syn = a_syn a_syn += c_const """, """ w_syn = a_syn a_syn += x_shared """, "v_post *= 2", "v_post *= w_syn", """ v_pre = 0 w_syn = v_pre """, """ v_pre = c_const w_syn = v_pre """, """ v_pre = x_shared w_syn = v_pre """, """ ge_syn += w_syn Apre_syn += 3 w_syn = clip(w_syn + Apost_syn, 0, 10) """, """ ge_syn += w_syn Apre_syn += c_const w_syn = clip(w_syn + Apost_syn, 0, 10) """, """ ge_syn += w_syn Apre_syn += x_shared w_syn = clip(w_syn + Apost_syn, 0, 10) """, """ a_syn = v_pre v_post += a_syn """, """ v_post += v_post # NOT_UFUNC_AT_VECTORISABLE v_post += v_post """, """ v_post += 1 x = v_post """, ] permutation_analysis_bad_examples = [ "v_pre = w_syn", "v_post = v_pre", "v_post = w_syn", "v_post += w_syn+v_post", "v_post += rand()", # rand() has state, and therefore this is order dependent """ a_syn = v_post v_post += w_syn """, """ x = w_syn v_pre = x """, """ x = v_pre v_post = x """, """ v_post += v_pre v_pre += v_post """, """ b_syn = v_post v_post += a_syn """, """ v_post += w_syn u_post += v_post """, """ v_post += 1 w_syn = v_post """, ] @pytest.mark.codegen_independent def test_permutation_analysis(): # Examples that should work for example in permutation_analysis_good_examples: if SANITY_CHECK_PERMUTATION_ANALYSIS_EXAMPLE: try: numerically_check_permutation_code(example) except OrderDependenceError: raise AssertionError( "Test unexpectedly raised a numerical " "OrderDependenceError on these " "statements:\n" + example ) try: check_permutation_code(example) except OrderDependenceError: raise AssertionError( "Test unexpectedly raised an " "OrderDependenceError on these " "statements:\n" + example ) for example in permutation_analysis_bad_examples: if SANITY_CHECK_PERMUTATION_ANALYSIS_EXAMPLE: try: with pytest.raises(OrderDependenceError): numerically_check_permutation_code(example) except AssertionError: raise AssertionError( "Order dependence not raised numerically for example: " + example ) try: with pytest.raises(OrderDependenceError): check_permutation_code(example) except AssertionError: raise AssertionError("Order dependence not raised for example: " + example) @pytest.mark.standalone_compatible def test_vectorisation(): source = NeuronGroup(10, "v : 1", threshold="v>1") target = NeuronGroup( 10, """ x : 1 y : 1 """, ) syn = Synapses( source, target, "w_syn : 1", on_pre=""" v_pre += w_syn x_post = y_post """, ) syn.connect() syn.w_syn = 1 source.v["i<5"] = 2 target.y = "i" run(defaultclock.dt) assert_allclose(source.v[:5], 12) assert_allclose(source.v[5:], 0) assert_allclose(target.x[:], target.y[:]) @pytest.mark.standalone_compatible def test_vectorisation_STDP_like(): # Test the use of pre- and post-synaptic traces that are stored in the # pre/post group instead of in the synapses w_max = 10 neurons = NeuronGroup( 6, """ dv/dt = rate : 1 ge : 1 rate : Hz dA/dt = -A/(1*ms) : 1 """, threshold="v>1", reset="v=0", ) # Note that the synapse does not actually increase the target v, we want # to have simple control about when neurons spike. Also, we separate the # "depression" and "facilitation" completely. The example also uses # subgroups, which should complicate things further. # This test should try to capture the spirit of indexing in such a use case, # it simply compares the results to fixed pre-calculated values syn = Synapses( neurons[:3], neurons[3:], """ w_dep : 1 w_fac : 1 """, on_pre=""" ge_post += w_dep - w_fac A_pre += 1 w_dep = clip(w_dep + A_post, 0, w_max) """, on_post=""" A_post += 1 w_fac = clip(w_fac + A_pre, 0, w_max) """, ) syn.connect() neurons.rate = 1000 * Hz neurons.v = "abs(3-i)*0.1 + 0.7" run(2 * ms) # Make sure that this test is invariant to synapse order indices = np.argsort( np.array(list(zip(syn.i[:], syn.j[:])), dtype=[("i", "=j") summed_conn.x = "i" run(defaultclock.dt) assert_array_equal(conn.w[:], [10, 10, 9, 7, 4]) @pytest.mark.codegen_independent def test_synapse_generator_syntax(): parsed = parse_synapse_generator("k for k in sample(1, N, p=p) if abs(i-k)<10") assert parsed["element"] == "k" assert parsed["inner_variable"] == "k" assert parsed["iterator_func"] == "sample" assert parsed["iterator_kwds"]["low"] == "1" assert parsed["iterator_kwds"]["high"] == "N" assert parsed["iterator_kwds"]["step"] == "1" assert parsed["iterator_kwds"]["p"] == "p" assert parsed["iterator_kwds"]["size"] is None assert parsed["iterator_kwds"]["sample_size"] == "random" assert parsed["if_expression"] == "abs(i - k) < 10" parsed = parse_synapse_generator("k for k in sample(N, size=5) if abs(i-k)<10") assert parsed["element"] == "k" assert parsed["inner_variable"] == "k" assert parsed["iterator_func"] == "sample" assert parsed["iterator_kwds"]["low"] == "0" assert parsed["iterator_kwds"]["high"] == "N" assert parsed["iterator_kwds"]["step"] == "1" assert parsed["iterator_kwds"]["p"] is None assert parsed["iterator_kwds"]["size"] == "5" assert parsed["iterator_kwds"]["sample_size"] == "fixed" assert parsed["if_expression"] == "abs(i - k) < 10" parsed = parse_synapse_generator("k+1 for k in range(i-100, i+100, 2)") assert parsed["element"] == "k + 1" assert parsed["inner_variable"] == "k" assert parsed["iterator_func"] == "range" assert parsed["iterator_kwds"]["low"] == "i - 100" assert parsed["iterator_kwds"]["high"] == "i + 100" assert parsed["iterator_kwds"]["step"] == "2" assert parsed["if_expression"] == "True" with pytest.raises(SyntaxError): parse_synapse_generator("mad rubbish") with pytest.raises(SyntaxError): parse_synapse_generator("k+1") with pytest.raises(SyntaxError): parse_synapse_generator("k for k in range()") with pytest.raises(SyntaxError): parse_synapse_generator("k for k in range(1,2,3,4)") with pytest.raises(SyntaxError): parse_synapse_generator("k for k in range(1,2,3) if ") with pytest.raises(SyntaxError): parse_synapse_generator("k[1:3] for k in range(1,2,3)") with pytest.raises(SyntaxError): parse_synapse_generator("k for k in x") with pytest.raises(SyntaxError): parse_synapse_generator("k for k in x[1:5]") with pytest.raises(SyntaxError): parse_synapse_generator("k for k in sample()") with pytest.raises(SyntaxError): parse_synapse_generator("k for k in sample(N, p=0.1, size=5)") with pytest.raises(SyntaxError): parse_synapse_generator("k for k in sample(N, q=0.1)") def test_synapse_generator_out_of_range(): G = NeuronGroup(16, "v : 1") G2 = NeuronGroup(4, "v : 1") G2.v = "16 + i" S1 = Synapses(G, G2, "") with pytest.raises(BrianObjectException) as exc: S1.connect(j="k for k in range(0, N_post*2)") exc.errisinstance(IndexError) # This should be fine S2 = Synapses(G, G, "") S2.connect(j="i+k for k in range(0, 5) if i <= N_post-5") expected = np.zeros((len(G), len(G))) expected[np.triu_indices(len(G))] = 1 expected[np.triu_indices(len(G), 5)] = 0 expected[len(G) - 4 :, :] = 0 _compare(S2, expected) # This should be fine (see #1037) S2 = Synapses(G, G, "") S2.connect(j="i+k for k in range(0, 5) if i <= N_post-5 and rand() <= 1") _compare(S2, expected) # This could in principle be fine, but we cannot test the condition without # accessing the post-synaptic variable outside of its range. By analyzing # the post-synaptic condition, we could find out that the value of this # variable is actually irrelevant, but that makes things too complicated. S3 = Synapses(G, G, "") with pytest.raises(BrianObjectException) as exc: S3.connect(j="i+k for k in range(0, 5) if i <= N_post-5 and v_post >= 0") assert exc_isinstance(exc, IndexError) assert "outside allowed range" in str(exc.value.__cause__) @pytest.mark.standalone_compatible def test_synapse_generator_deterministic(): # Same as "test_connection_string_deterministic" but using the generator # syntax G = NeuronGroup(16, "v : 1") G.v = "i" G2 = NeuronGroup(4, "v : 1") G2.v = "16 + i" # Full connection expected_full = np.ones((len(G), len(G2))) S1 = Synapses(G, G2) S1.connect(j="k for k in range(N_post)") # Full connection without self-connections expected_no_self = np.ones((len(G), len(G))) - np.eye(len(G)) S2 = Synapses(G, G) S2.connect(j="k for k in range(N_post) if k != i") S3 = Synapses(G, G) # slightly confusing with j on the RHS, but it should work... S3.connect(j="k for k in range(N_post) if j != i") S4 = Synapses(G, G) S4.connect(j="k for k in range(N_post) if v_post != v_pre") # One-to-one connectivity expected_one_to_one = np.eye(len(G)) S5 = Synapses(G, G) S5.connect(j="k for k in range(N_post) if k == i") # inefficient S6 = Synapses(G, G) # slightly confusing with j on the RHS, but it should work... S6.connect(j="k for k in range(N_post) if j == i") # inefficient S7 = Synapses(G, G) S7.connect(j="k for k in range(N_post) if v_pre == v_post") # inefficient S8 = Synapses(G, G) S8.connect(j="i for _ in range(1)") # efficient S9 = Synapses(G, G) S9.connect(j="i") # short form of the above with catch_logs() as _: # Ignore warnings about empty synapses run(0 * ms) # for standalone _compare(S1, expected_full) _compare(S2, expected_no_self) _compare(S3, expected_no_self) _compare(S4, expected_no_self) _compare(S5, expected_one_to_one) _compare(S6, expected_one_to_one) _compare(S7, expected_one_to_one) _compare(S8, expected_one_to_one) _compare(S9, expected_one_to_one) @pytest.mark.standalone_compatible def test_synapse_generator_deterministic_over_postsynaptic(): # Same as "test_connection_string_deterministic" but using the generator # syntax and iterating over post-synaptic variables G = NeuronGroup(16, "v : 1") G.v = "i" G2 = NeuronGroup(4, "v : 1") G2.v = "16 + i" # Full connection expected_full = np.ones((len(G), len(G2))) S1 = Synapses(G, G2) S1.connect(i="k for k in range(N_pre)") # Full connection without self-connections expected_no_self = np.ones((len(G), len(G))) - np.eye(len(G)) S2 = Synapses(G, G) S2.connect(i="k for k in range(N_pre) if k != j") S3 = Synapses(G, G) # slightly confusing with i on the RHS, but it should work... S3.connect(i="k for k in range(N_pre) if i != j") S4 = Synapses(G, G) S4.connect(j="k for k in range(N_pre) if v_pre != v_post") # One-to-one connectivity expected_one_to_one = np.eye(len(G)) S5 = Synapses(G, G) S5.connect(i="k for k in range(N_pre) if k == j") # inefficient S6 = Synapses(G, G) # slightly confusing with j on the RHS, but it should work... S6.connect(i="k for k in range(N_pre) if i == j") # inefficient S7 = Synapses(G, G) S7.connect(i="k for k in range(N_pre) if v_pre == v_post") # inefficient S8 = Synapses(G, G) S8.connect(i="j for _ in range(1)") # efficient S9 = Synapses(G, G) S9.connect(i="j") # short form of the above with catch_logs() as _: # Ignore warnings about empty synapses run(0 * ms) # for standalone _compare(S1, expected_full) _compare(S2, expected_no_self) _compare(S3, expected_no_self) _compare(S4, expected_no_self) _compare(S5, expected_one_to_one) _compare(S6, expected_one_to_one) _compare(S7, expected_one_to_one) _compare(S8, expected_one_to_one) _compare(S9, expected_one_to_one) @pytest.mark.standalone_compatible @pytest.mark.long def test_synapse_generator_deterministic_2(): # Same as "test_connection_string_deterministic" but using the generator # syntax G = NeuronGroup(16, "") G2 = NeuronGroup(4, "") # A few more tests of deterministic connections where the generator syntax # is particularly useful # Ring structure S10 = Synapses(G, G) S10.connect(j="(i + (-1)**k) % N_post for k in range(2)") expected_ring = np.zeros((len(G), len(G)), dtype=np.int32) expected_ring[np.arange(15), np.arange(15) + 1] = 1 # Next cell expected_ring[np.arange(1, 16), np.arange(15)] = 1 # Previous cell expected_ring[[0, 15], [15, 0]] = 1 # wrap around the ring # Diverging connection pattern S11 = Synapses(G2, G) S11.connect(j="i*4 + k for k in range(4)") expected_diverging = np.zeros((len(G2), len(G)), dtype=np.int32) for source in range(4): expected_diverging[source, np.arange(4) + source * 4] = 1 # Diverging connection pattern within population (no self-connections) S11b = Synapses(G2, G2) S11b.connect(j="k for k in range(i-3, i+4) if i!=k", skip_if_invalid=True) expected_diverging_b = np.zeros((len(G2), len(G2)), dtype=np.int32) for source in range(len(G2)): expected_diverging_b[ source, np.clip(np.arange(-3, 4) + source, 0, len(G2) - 1) ] = 1 expected_diverging_b[source, source] = 0 # Converging connection pattern S12 = Synapses(G, G2) S12.connect(j="int(i/4)") expected_converging = np.zeros((len(G), len(G2)), dtype=np.int32) for target in range(4): expected_converging[np.arange(4) + target * 4, target] = 1 # skip if invalid S13 = Synapses(G2, G2) S13.connect(j="i+(-1)**k for k in range(2)", skip_if_invalid=True) expected_offdiagonal = np.zeros((len(G2), len(G2)), dtype=np.int32) expected_offdiagonal[np.arange(len(G2) - 1), np.arange(len(G2) - 1) + 1] = 1 expected_offdiagonal[np.arange(len(G2) - 1) + 1, np.arange(len(G2) - 1)] = 1 # Converging connection pattern with restriction S14 = Synapses(G, G2) S14.connect(j="int(i/4) if i % 2 == 0") expected_converging_restricted = np.zeros((len(G), len(G2)), dtype=np.int32) for target in range(4): expected_converging_restricted[np.arange(4, step=2) + target * 4, target] = 1 # Connecting to post indices >= source index expected_diagonal = np.zeros((len(G), len(G)), dtype=np.int32) expected_diagonal[np.triu_indices(len(G))] = 1 S15 = Synapses(G, G) S15.connect(j="i + k for k in range(0, N_post-i)") S15b = Synapses(G, G) S15b.connect(j="i + k for k in range(0, N_post)", skip_if_invalid=True) S15c = Synapses(G, G) S15c.connect(j="i + k for k in range(0, N_post) if j < N_post") S15d = Synapses(G, G) S15d.connect(j="i + k for k in range(0, N_post) if i + k < N_post") with catch_logs() as _: # Ignore warnings about empty synapses run(0 * ms) # for standalone _compare(S10, expected_ring) _compare(S11, expected_diverging) _compare(S11b, expected_diverging_b) _compare(S12, expected_converging) _compare(S13, expected_offdiagonal) _compare(S14, expected_converging_restricted) _compare(S15, expected_diagonal) _compare(S15b, expected_diagonal) _compare(S15c, expected_diagonal) _compare(S15d, expected_diagonal) @pytest.mark.standalone_compatible def test_synapse_generator_random(): # The same tests as test_connection_random_without_condition, but using # the generator syntax G = NeuronGroup(4, "x : integer") G.x = "i" G2 = NeuronGroup(7, "") S1 = Synapses(G, G2) S1.connect(j="k for k in sample(N_post, p=0)") S2 = Synapses(G, G2) S2.connect(j="k for k in sample(N_post, p=1)") # Just make sure using values between 0 and 1 work in principle S3 = Synapses(G, G2) S3.connect(j="k for k in sample(N_post, p=0.3)") # Use pre-/post-synaptic variables for "stochastic" connections that are # actually deterministic S4 = Synapses(G, G2) S4.connect(j="k for k in sample(N_post, p=int(x_pre==2)*1.0)") with catch_logs() as _: # Ignore warnings about empty synapses run(0 * ms) # for standalone assert len(S1) == 0 _compare(S2, np.ones((len(G), len(G2)))) assert 0 <= len(S3) <= len(G) * len(G2) assert len(S4) == 7 assert_equal(S4.i, np.ones(7) * 2) assert_equal(S4.j, np.arange(7)) @pytest.mark.standalone_compatible def test_synapse_generator_random_over_postsynaptic(): # The same tests as test_connection_random_without_condition, but using # the generator syntax and iterating over post-synaptic neurons G = NeuronGroup(4, "") G2 = NeuronGroup(7, "y : 1") G2.y = "i" S1 = Synapses(G, G2) S1.connect(i="k for k in sample(N_pre, p=0)") S2 = Synapses(G, G2) S2.connect(i="k for k in sample(N_pre, p=1)") # Just make sure using values between 0 and 1 work in principle S3 = Synapses(G, G2) S3.connect(i="k for k in sample(N_pre, p=0.3)") # Use pre-/post-synaptic variables for "stochastic" connections that are # actually deterministic S4 = Synapses(G, G2) S4.connect(i="k for k in sample(N_pre, p=int(y_post==2)*1.0)") with catch_logs() as _: # Ignore warnings about empty synapses run(0 * ms) # for standalone assert len(S1) == 0 _compare(S2, np.ones((len(G), len(G2)))) assert 0 <= len(S3) <= len(G) * len(G2) assert len(S4) == 4 assert_equal(S4.i, np.arange(4)) assert_equal(S4.j, np.ones(4) * 2) @pytest.mark.standalone_compatible def test_synapse_generator_random_positive_steps(): # Test generator with sampling from stepped ranges (e.g. all even numbers) G = NeuronGroup(4, "x : integer") G.x = "i" G2 = NeuronGroup(7, "") S1 = Synapses(G, G2) S1.connect(j="k for k in sample(2, N_post, 2, p=0)") S2 = Synapses(G, G2) S2.connect(j="k for k in sample(2, N_post, 2, p=1)") # Just make sure using values between 0 and 1 work in principle (note that # 0.25 is the cutoff between the general method and the "jump method", so # we test a value above and below S3 = Synapses(G, G2) S3.connect(j="k for k in sample(2, N_post, 2, p=0.2)") S3b = Synapses(G, G2) S3b.connect(j="k for k in sample(2, N_post, 2, p=0.3)") # Use pre-/post-synaptic variables for "stochastic" connections that are # actually deterministic S4 = Synapses(G, G2) S4.connect(j="k for k in sample(2, N_post, 2, p=int(x_pre==2)*1.0)") with catch_logs() as _: # Ignore warnings about empty synapses run(0 * ms) # for standalone assert len(S1) == 0 S2_comp = np.zeros((len(G), len(G2))) S2_comp[:, 2::2] = 1 _compare(S2, S2_comp) assert 0 <= len(S3) <= len(G) * 3 assert all(S3.j[:] % 2 == 0) assert all(S3.j >= 2) assert 0 <= len(S3b) <= len(G) * 3 assert all(S3b.j[:] % 2 == 0) assert all(S3b.j >= 2) assert len(S4) == 3 assert_equal(S4.i, np.ones(3) * 2) assert_equal(S4.j, np.arange(2, 7, 2)) @pytest.mark.standalone_compatible def test_synapse_generator_random_negative_steps(): # Test generator with sampling from stepped ranges (e.g. all even numbers) # going backwards G = NeuronGroup(4, "x : integer") G.x = "i" G2 = NeuronGroup(7, "") S1 = Synapses(G, G2) S1.connect(j="k for k in sample(N_post-1, 0, -2, p=0)") S2 = Synapses(G, G2) S2.connect(j="k for k in sample(N_post-1, 0, -2, p=1)") # Just make sure using values between 0 and 1 work in principle (note that # 0.25 is the cutoff between the general method and the "jump method", so # we test a value above and below S3 = Synapses(G, G2) S3.connect(j="k for k in sample(N_post-1, 0, -2, p=0.2)") S3b = Synapses(G, G2) S3b.connect(j="k for k in sample(N_post-1, 0, -2, p=0.3)") # Use pre-/post-synaptic variables for "stochastic" connections that are # actually deterministic S4 = Synapses(G, G2) S4.connect(j="k for k in sample(N_post-1, 0, -2, p=int(x_pre==2)*1.0)") with catch_logs() as _: # Ignore warnings about empty synapses run(0 * ms) # for standalone assert len(S1) == 0 S2_comp = np.zeros((len(G), len(G2))) S2_comp[:, 2::2] = 1 _compare(S2, S2_comp) assert 0 <= len(S3) <= len(G) * 3 assert all(S3.j[:] % 2 == 0) assert all(S3.j >= 2) assert 0 <= len(S3b) <= len(G) * 3 assert all(S3b.j[:] % 2 == 0) assert all(S3b.j >= 2) assert len(S4) == 3 assert_array_equal(S4.i, np.ones(3) * 2) assert_array_equal(S4.j, [6, 4, 2]) @pytest.mark.standalone_compatible def test_synapse_generator_fixed_random(): # Random samples with fixed size G = NeuronGroup(4, "x : integer") G.x = "i" G2 = NeuronGroup(7, "") S1 = Synapses(G, G2) S1.connect(j="k for k in sample(N_post, size=0)") S2 = Synapses(G, G2) S2.connect(j="k for k in sample(N_post, size=N_post)") S3 = Synapses(G, G2) S3.connect(j="k for k in sample(N_post, size=3)") # Use pre-/post-synaptic variables for "stochastic" connections that are # actually deterministic S4 = Synapses(G, G2) S4.connect(j="k for k in sample(N_post, size=int(x_pre==2)*N_post)") with catch_logs() as _: # Ignore warnings about empty synapses run(0 * ms) # for standalone assert len(S1) == 0 _compare(S2, np.ones((len(G), len(G2)))) # Each neuron should have 3 outgoing connections assert_array_equal(S3.N_outgoing_pre, np.ones(4) * 3) # Synapses should be sorted and unique for source_idx in range(4): assert len(set(S3.j[source_idx, :])) == 3 assert all(S3.j[source_idx, :] == sorted(S3.j[source_idx, :])) assert len(S4) == 7 assert_equal(S4.i, np.ones(7) * 2) assert_equal(S4.j, np.arange(7)) @pytest.mark.standalone_compatible def test_synapse_generator_fixed_random_over_postsynaptic(): # Random samples with fixed size, iterating over post-synaptic neurons G = NeuronGroup(4, "") G2 = NeuronGroup(7, "y : integer") G2.y = "i" S1 = Synapses(G, G2) S1.connect(i="k for k in sample(N_pre, size=0)") S2 = Synapses(G, G2) S2.connect(i="k for k in sample(N_pre, size=N_pre)") S3 = Synapses(G, G2) S3.connect(i="k for k in sample(N_pre, size=3)") # Use pre-/post-synaptic variables for "stochastic" connections that are # actually deterministic S4 = Synapses(G, G2) S4.connect(i="k for k in sample(N_pre, size=int(y_post==2)*N_pre)") with catch_logs() as _: # Ignore warnings about empty synapses run(0 * ms) # for standalone assert len(S1) == 0 _compare(S2, np.ones((len(G), len(G2)))) # Each neuron should have 3 incoming connections assert_array_equal(S3.N_incoming_post, np.ones(7) * 3) # Synapses should be sorted and unique for target_idx in range(7): assert len(set(S3.i[:, target_idx])) == 3 assert all(S3.i[:, target_idx] == sorted(S3.i[:, target_idx])) assert len(S4) == 4 assert_equal(S4.j, np.ones(4) * 2) assert_equal(S4.i, np.arange(4)) @pytest.mark.standalone_compatible def test_synapse_generator_fixed_random_positive_steps(): # Test generator with fixed-size sampling from stepped ranges (e.g. all # even numbers) G = NeuronGroup(4, "x : integer") G.x = "i" G2 = NeuronGroup(7, "") S1 = Synapses(G, G2) S1.connect(j="k for k in sample(2, N_post, 2, size=0)") S2 = Synapses(G, G2) S2.connect(j="k for k in sample(2, N_post, 2, size=3)") # Just make sure using values between 0 and 1 work in principle S3 = Synapses(G, G2) S3.connect(j="k for k in sample(2, N_post, 2, size=2)") # Use pre-/post-synaptic variables for "stochastic" connections that are # actually deterministic S4 = Synapses(G, G2) S4.connect(j="k for k in sample(2, N_post, 2, size=int(x_pre==2)*3)") with catch_logs() as _: # Ignore warnings about empty synapses run(0 * ms) # for standalone assert len(S1) == 0 S2_comp = np.zeros((len(G), len(G2))) S2_comp[:, 2::2] = 1 _compare(S2, S2_comp) assert len(S3) == len(G) * 2 assert all(S3.N_outgoing_pre == 2) assert all(S3.j[:] % 2 == 0) assert all(S3.j >= 2) assert all([len(S3.j[x, :]) == len(set(S3.j[x, :])) for x in range(len(G))]) assert len(S4) == 3 assert_equal(S4.i, np.ones(3) * 2) assert_equal(S4.j, np.arange(2, 7, 2)) @pytest.mark.standalone_compatible def test_synapse_generator_fixed_random_negative_steps(): # Test generator with fixed-size sampling from stepped ranges (e.g. all # even numbers) going backwards G = NeuronGroup(4, "x : integer") G.x = "i" G2 = NeuronGroup(7, "") S1 = Synapses(G, G2) S1.connect(j="k for k in sample(N_post-1, 0, -2, size=0)") S2 = Synapses(G, G2) S2.connect(j="k for k in sample(N_post-1, 0, -2, size=3)") # Just make sure using intermediate values between 0 and 1 work in principle S3 = Synapses(G, G2) S3.connect(j="k for k in sample(N_post-1, 0, -2, size=2)") # Use pre-/post-synaptic variables for "stochastic" connections that are # actually deterministic S4 = Synapses(G, G2, "w:1") S4.connect(j="k for k in sample(N_post-1, 0, -2, size=int(x_pre==2)*3)") with catch_logs() as _: # Ignore warnings about empty synapses run(0 * ms) # for standalone assert len(S1) == 0 S2_comp = np.zeros((len(G), len(G2))) S2_comp[:, 2::2] = 1 _compare(S2, S2_comp) assert len(S3) == len(G) * 2 assert all(S3.N_outgoing_pre == 2) assert all(S3.j[:] % 2 == 0) assert all(S3.j >= 2) assert all([len(S3.j[x, :]) == len(set(S3.j[x, :])) for x in range(len(G))]) assert len(S4) == 3 assert_equal(S4.i, np.ones(3) * 2) assert_equal(S4.j, np.arange(6, 0, -2)) @pytest.mark.standalone_compatible def test_synapse_generator_fixed_random_error1(): G = NeuronGroup(5, "") G2 = NeuronGroup(7, "") S = Synapses(G, G2) with pytest.raises((BrianObjectException, IndexError, RuntimeError)): # Won't work for i=4 S.connect(j="k for k in sample(N_post, size=i+4)") run(0 * ms) # for standalone @pytest.mark.standalone_compatible def test_synapse_generator_fixed_random_error2(): G = NeuronGroup(5, "") G2 = NeuronGroup(7, "") S = Synapses(G, G2) with pytest.raises((BrianObjectException, IndexError, RuntimeError)): # Won't work for i=4 S.connect(j="k for k in sample(N_post, size=3-i)") run(0 * ms) # for standalone @pytest.mark.standalone_compatible def test_synapse_generator_fixed_random_skip_if_invalid(): G = NeuronGroup(5, "") G2 = NeuronGroup(7, "") S1 = Synapses(G, G2) S2 = Synapses(G, G2) # > N_post for i=4 S1.connect(j="k for k in sample(N_post, size=i+4)", skip_if_invalid=True) # < 0 for i=4 S2.connect(j="k for k in sample(N_post, size=3-i)", skip_if_invalid=True) run(0 * ms) # for standalone assert_array_equal(S1.N_outgoing_pre, [4, 5, 6, 7, 7]) assert_array_equal(S2.N_outgoing_pre, [3, 2, 1, 0, 0]) @pytest.mark.standalone_compatible def test_synapse_generator_random_with_condition(): G = NeuronGroup(4, "") S1 = Synapses(G, G) S1.connect(j="k for k in sample(N_post, p=0) if i != k") S2 = Synapses(G, G) S2.connect(j="k for k in sample(N_post, p=1) if i != k") expected2 = np.ones((len(G), len(G))) - np.eye(len(G)) S3 = Synapses(G, G) S3.connect(j="k for k in sample(N_post, p=0) if i >= 2") S4 = Synapses(G, G) S4.connect(j="k for k in sample(N_post, p=1.0) if i >= 2") expected4 = np.zeros((len(G), len(G))) expected4[2, :] = 1 expected4[3, :] = 1 S5 = Synapses(G, G) S5.connect(j="k for k in sample(N_post, p=0) if j < 2") # inefficient S6 = Synapses(G, G) S6.connect(j="k for k in sample(2, p=0)") # better S7 = Synapses(G, G) expected7 = np.zeros((len(G), len(G))) expected7[:, 0] = 1 expected7[:, 1] = 1 S7.connect(j="k for k in sample(N_post, p=1.0) if j < 2") # inefficient S8 = Synapses(G, G) S8.connect(j="k for k in sample(2, p=1.0)") # better with catch_logs() as _: # Ignore warnings about empty synapses run(0 * ms) # for standalone assert len(S1) == 0 _compare(S2, expected2) assert len(S3) == 0 _compare(S4, expected4) assert len(S5) == 0 assert len(S6) == 0 _compare(S7, expected7) _compare(S8, expected7) @pytest.mark.standalone_compatible @pytest.mark.long def test_synapse_generator_random_with_condition_2(): G = NeuronGroup(4, "") # Just checking that everything works in principle (we can't check the # actual connections) S9 = Synapses(G, G) S9.connect(j="k for k in sample(N_post, p=0.001) if i != k") S10 = Synapses(G, G) S10.connect(j="k for k in sample(N_post, p=0.03) if i != k") S11 = Synapses(G, G) S11.connect(j="k for k in sample(N_post, p=0.1) if i != k") S12 = Synapses(G, G) S12.connect(j="k for k in sample(N_post, p=0.9) if i != k") S13 = Synapses(G, G) S13.connect(j="k for k in sample(N_post, p=0.001) if i >= 2") S14 = Synapses(G, G) S14.connect(j="k for k in sample(N_post, p=0.03) if i >= 2") S15 = Synapses(G, G) S15.connect(j="k for k in sample(N_post, p=0.1) if i >= 2") S16 = Synapses(G, G) S16.connect(j="k for k in sample(N_post, p=0.9) if i >= 2") S17 = Synapses(G, G) S17.connect(j="k for k in sample(N_post, p=0.001) if j < 2") S18 = Synapses(G, G) S18.connect(j="k for k in sample(N_post, p=0.03) if j < 2") S19 = Synapses(G, G) S19.connect(j="k for k in sample(N_post, p=0.1) if j < 2") S20 = Synapses(G, G) S20.connect(j="k for k in sample(N_post, p=0.9) if j < 2") S21 = Synapses(G, G) S21.connect(j="k for k in sample(2, p=0.001)") S22 = Synapses(G, G) S22.connect(j="k for k in sample(2, p=0.03)") S23 = Synapses(G, G) S23.connect(j="k for k in sample(2, p=0.1)") S24 = Synapses(G, G) S24.connect(j="k for k in sample(2, p=0.9)") # Some more tests specific to the generator syntax S25 = Synapses(G, G) S25.connect(j="i+1 for _ in sample(1, p=0.5) if i < N_post-1") S26 = Synapses(G, G) S26.connect(j="i+k for k in sample(N_post-i, p=0.5)") with catch_logs() as _: # Ignore warnings about empty synapses run(0 * ms) # for standalone assert not any(S9.i == S9.j) assert 0 <= len(S9) <= len(G) * (len(G) - 1) assert not any(S10.i == S10.j) assert 0 <= len(S10) <= len(G) * (len(G) - 1) assert not any(S11.i == S11.j) assert 0 <= len(S11) <= len(G) * (len(G) - 1) assert not any(S12.i == S12.j) assert 0 <= len(S12) <= len(G) * (len(G) - 1) assert all(S13.i[:] >= 2) assert 0 <= len(S13) <= len(G) * (len(G) - 1) assert all(S14.i[:] >= 2) assert 0 <= len(S14) <= len(G) * (len(G) - 1) assert all(S15.i[:] >= 2) assert 0 <= len(S15) <= len(G) * (len(G) - 1) assert all(S16.i[:] >= 2) assert 0 <= len(S16) <= len(G) * (len(G) - 1) assert all(S17.j[:] < 2) assert 0 <= len(S17) <= len(G) * (len(G) - 1) assert all(S18.j[:] < 2) assert 0 <= len(S18) <= len(G) * (len(G) - 1) assert all(S19.j[:] < 2) assert 0 <= len(S19) <= len(G) * (len(G) - 1) assert all(S20.j[:] < 2) assert 0 <= len(S20) <= len(G) * (len(G) - 1) assert all(S21.j[:] < 2) assert 0 <= len(S21) <= len(G) * (len(G) - 1) assert all(S22.j[:] < 2) assert 0 <= len(S22) <= len(G) * (len(G) - 1) assert all(S23.j[:] < 2) assert 0 <= len(S23) <= len(G) * (len(G) - 1) assert all(S24.j[:] < 2) assert 0 <= len(S24) <= len(G) * (len(G) - 1) assert 0 <= len(S25) <= len(G) assert_equal(S25.j[:], S25.i[:] + 1) assert 0 <= len(S26) <= (1 + len(G)) * (len(G) / 2) assert all(S26.j[:] >= S26.i[:]) @pytest.mark.standalone_compatible def test_synapses_refractory(): source = NeuronGroup(10, "", threshold="True") target = NeuronGroup( 10, "dv/dt = 0/second : 1 (unless refractory)", threshold="i>=5", refractory=defaultclock.dt, ) S = Synapses(source, target, on_pre="v += 1") S.connect(j="i") run(defaultclock.dt + schedule_propagation_offset()) assert_allclose(target.v[:5], 1) assert_allclose(target.v[5:], 0) @pytest.mark.standalone_compatible def test_synapses_refractory_rand(): source = NeuronGroup(10, "", threshold="True") target = NeuronGroup( 10, "dv/dt = 0/second : 1 (unless refractory)", threshold="i>=5", refractory=defaultclock.dt, ) S = Synapses(source, target, on_pre="v += rand()") S.connect(j="i") with catch_logs() as _: # Currently, rand() is a stateful function (we do not make use of # _vectorisation_idx yet to make random numbers completely # reproducible), which will lead to a warning, since the result depends # on the order of execution. run(defaultclock.dt + schedule_propagation_offset()) assert all(target.v[:5] > 0) assert_allclose(target.v[5:], 0) @pytest.mark.codegen_independent def test_synapse_generator_range_noint(): # arguments to `range` should only be integers (issue #781) G = NeuronGroup(42, "") S = Synapses(G, G) msg = ( r"The '{}' argument of the range function was .+, but it needs to be an" r" integer\." ) with pytest.raises(TypeError, match=msg.format("high")): S.connect(j="k for k in range(42.0)") with pytest.raises(TypeError, match=msg.format("low")): S.connect(j="k for k in range(0.0, 42)") with pytest.raises(TypeError, match=msg.format("high")): S.connect(j="k for k in range(0, 42.0)") with pytest.raises(TypeError, match=msg.format("step")): S.connect(j="k for k in range(0, 42, 1.0)") with pytest.raises(TypeError, match=msg.format("low")): S.connect(j="k for k in range(True, 42)") with pytest.raises(TypeError, match=msg.format("high")): S.connect(j="k for k in range(0, True)") with pytest.raises(TypeError, match=msg.format("step")): S.connect(j="k for k in range(0, 42, True)") @pytest.mark.codegen_independent def test_missing_lastupdate_error_syn_pathway(): G = NeuronGroup(1, "v : 1", threshold="False") S = Synapses(G, G, on_pre="v += exp(-lastupdate/dt)") S.connect() with pytest.raises(BrianObjectException) as exc: run(0 * ms) assert exc_isinstance(exc, KeyError) assert "lastupdate = t" in str(exc.value.__cause__) assert "lastupdate : second" in str(exc.value.__cause__) @pytest.mark.codegen_independent def test_missing_lastupdate_error_run_regularly(): G = NeuronGroup(1, "v : 1") S = Synapses(G, G) S.connect() S.run_regularly("v += exp(-lastupdate/dt") with pytest.raises(BrianObjectException) as exc: run(0 * ms) assert exc_isinstance(exc, KeyError) assert "lastupdate = t" in str(exc.value.__cause__) assert "lastupdate : second" in str(exc.value.__cause__) @pytest.mark.codegen_independent def test_synaptic_subgroups(): source = NeuronGroup(5, "") target = NeuronGroup(3, "") syn = Synapses(source, target) syn.connect() assert len(syn) == 15 from_3 = syn[3, :] assert len(from_3) == 3 assert all(syn.i[from_3] == 3) assert_array_equal(syn.j[from_3], np.arange(3)) to_2 = syn[:, 2] assert len(to_2) == 5 assert all(syn.j[to_2] == 2) assert_array_equal(syn.i[to_2], np.arange(5)) mixed = syn[1:3, :2] assert len(mixed) == 4 connections = {(i, j) for i, j in zip(syn.i[mixed], syn.j[mixed])} assert connections == {(1, 0), (1, 1), (2, 0), (2, 1)} @pytest.mark.codegen_independent def test_incorrect_connect_N_incoming_outgoing(): # See github issue #1227 source = NeuronGroup(5, "") target = NeuronGroup(3, "") syn = Synapses(source, target) with pytest.raises(ValueError) as ex: syn.connect("N_incoming < 5") assert "N_incoming" in str(ex) with pytest.raises(ValueError) as ex: syn.connect("N_outgoing < 5") assert "N_outgoing" in str(ex) @pytest.mark.codegen_independent def test_setting_from_weight_matrix(): # fully connected weight matrix # weights[source_index, target_index] weights = np.array([[1, 2, 3], [4, 5, 6]]) source = NeuronGroup(2, "") target = NeuronGroup(3, "") syn = Synapses(source, target, "w : 1") syn.connect() syn.w[:] = weights.flatten() for (i, j), w in np.ndenumerate(weights): assert all(syn.w[i, j] == weights[i, j]) if __name__ == "__main__": SANITY_CHECK_PERMUTATION_ANALYSIS_EXAMPLE = True # prefs.codegen.target = 'numpy' # prefs._backup() import time from _pytest.outcomes import Skipped from brian2 import prefs start = time.time() test_creation() test_name_clashes() test_incoming_outgoing() test_connection_string_deterministic_full() test_connection_string_deterministic_full_no_self() test_connection_string_deterministic_full_one_to_one() test_connection_string_deterministic_full_custom() test_connection_string_deterministic_multiple_and() test_connection_random_with_condition() test_connection_random_with_condition_2() test_connection_random_without_condition() test_connection_random_with_indices() test_connection_multiple_synapses() test_connection_arrays() reinit_and_delete() test_state_variable_assignment() test_state_variable_indexing() test_indices() test_subexpression_references() test_nested_subexpression_references() test_constant_variable_subexpression_in_synapses() test_equations_unit_check() test_delay_specification() test_delays_pathways() test_delays_pathways_subgroups() test_pre_before_post() test_pre_post_simple() test_transmission_simple() test_transmission_custom_event() test_invalid_custom_event() test_transmission() test_transmission_all_to_one_heterogeneous_delays() test_transmission_one_to_all_heterogeneous_delays() test_transmission_scalar_delay() test_transmission_scalar_delay_different_clocks() test_transmission_boolean_variable() test_clocks() test_changed_dt_spikes_in_queue() test_no_synapses() test_no_synapses_variable_write() test_summed_variable() test_summed_variable_pre_and_post() test_summed_variable_differing_group_size() test_summed_variable_errors() test_multiple_summed_variables() test_summed_variables_subgroups() test_summed_variables_overlapping_subgroups() test_summed_variables_linked_variables() test_scalar_parameter_access() test_scalar_subexpression() test_sim_with_scalar_variable() test_sim_with_scalar_subexpression() test_sim_with_constant_subexpression() test_external_variables() test_event_driven() test_event_driven_dependency_error() test_event_driven_dependency_error2() test_event_driven_dependency_error3() test_repr() test_pre_post_variables() test_variables_by_owner() test_permutation_analysis() test_vectorisation() test_vectorisation_STDP_like() test_synaptic_equations() test_synapse_with_run_regularly() test_synapses_to_synapses() test_synapses_to_synapses_statevar_access() test_synapses_to_synapses_different_sizes() test_synapses_to_synapses_summed_variable() try: test_ufunc_at_vectorisation() test_fallback_loop_and_stateless_func() except Skipped: print("Skipping numpy-only test") test_synapse_generator_syntax() test_synapse_generator_out_of_range() test_synapse_generator_deterministic() test_synapse_generator_deterministic_2() test_synapse_generator_random() test_synapse_generator_random_with_condition() test_synapse_generator_random_with_condition_2() test_synapses_refractory() test_synapses_refractory_rand() test_synapse_generator_range_noint() test_missing_lastupdate_error_syn_pathway() test_missing_lastupdate_error_run_regularly() test_synaptic_subgroups() test_incorrect_connect_N_incoming_outgoing() test_setting_from_weight_matrix() print("Tests took", time.time() - start) brian2-2.5.4/brian2/tests/test_templates/000077500000000000000000000000001445201106100202455ustar00rootroot00000000000000brian2-2.5.4/brian2/tests/test_templates/__init__.py000066400000000000000000000000001445201106100223440ustar00rootroot00000000000000brian2-2.5.4/brian2/tests/test_templates/fake_package_1/000077500000000000000000000000001445201106100230465ustar00rootroot00000000000000brian2-2.5.4/brian2/tests/test_templates/fake_package_1/__init__.py000066400000000000000000000000001445201106100251450ustar00rootroot00000000000000brian2-2.5.4/brian2/tests/test_templates/fake_package_1/templates/000077500000000000000000000000001445201106100250445ustar00rootroot00000000000000brian2-2.5.4/brian2/tests/test_templates/fake_package_1/templates/A.txt000066400000000000000000000001321445201106100257610ustar00rootroot00000000000000{% extends 'B.txt' %} {% block main %} A1 {{ super() }} {{ f() }} {{ g() }} {% endblock %}brian2-2.5.4/brian2/tests/test_templates/fake_package_1/templates/B.txt000066400000000000000000000000421445201106100257620ustar00rootroot00000000000000{% block main %} B1 {% endblock %}brian2-2.5.4/brian2/tests/test_templates/fake_package_1/templates/C.txt000066400000000000000000000001071445201106100257650ustar00rootroot00000000000000{% extends 'D.txt' %} {% block main %} C1 {{ super() }} {% endblock %} brian2-2.5.4/brian2/tests/test_templates/fake_package_1/templates/D.txt000066400000000000000000000000431445201106100257650ustar00rootroot00000000000000{% block main %} D1 {% endblock %} brian2-2.5.4/brian2/tests/test_templates/fake_package_2/000077500000000000000000000000001445201106100230475ustar00rootroot00000000000000brian2-2.5.4/brian2/tests/test_templates/fake_package_2/__init__.py000066400000000000000000000000001445201106100251460ustar00rootroot00000000000000brian2-2.5.4/brian2/tests/test_templates/fake_package_2/templates/000077500000000000000000000000001445201106100250455ustar00rootroot00000000000000brian2-2.5.4/brian2/tests/test_templates/fake_package_2/templates/A.txt000066400000000000000000000000271445201106100257650ustar00rootroot00000000000000A2 {{ f() }} {{ g() }} brian2-2.5.4/brian2/tests/test_templates/fake_package_2/templates/D.txt000066400000000000000000000000431445201106100257660ustar00rootroot00000000000000{% block main %} D2 {% endblock %} brian2-2.5.4/brian2/tests/test_templates/test_templates.py000066400000000000000000000021771445201106100236630ustar00rootroot00000000000000import pytest from brian2 import * from brian2.codegen.templates import Templater @pytest.mark.codegen_independent def test_templates(): T1 = Templater( "brian2.tests.test_templates.fake_package_1", env_globals={"f": lambda: "F1", "g": lambda: "G1"}, extension=".txt", ) T2 = T1.derive( "brian2.tests.test_templates.fake_package_2", env_globals={"f": lambda: "F2"} ) ns = {} for i, T in enumerate([T1, T2], start=1): for c in ["A", "B", "C", "D"]: ns[c + str(i)] = getattr(T, c)("", "") # for k, v in ns.items(): # print k, v assert "A1" in ns["A1"] assert "B1" in ns["A1"] assert "F1" in ns["A1"] assert "G1" in ns["A1"] assert "A2" in ns["A2"] assert "F2" in ns["A2"] assert "G1" in ns["A2"] assert "B1" not in ns["A2"] assert "B1" in ns["B1"] assert "B1" in ns["B2"] assert "C1" in ns["C1"] assert "D1" in ns["C1"] assert "C1" in ns["C2"] assert "D2" in ns["C2"] assert "D1" not in ns["C2"] assert "D1" in ns["D1"] assert "D2" in ns["D2"] if __name__ == "__main__": test_templates() brian2-2.5.4/brian2/tests/test_thresholder.py000066400000000000000000000012631445201106100211460ustar00rootroot00000000000000import pytest from numpy.testing import assert_equal from brian2 import * from brian2.devices.device import reinit_and_delete @pytest.mark.standalone_compatible def test_simple_threshold(): G = NeuronGroup(4, "v : 1", threshold="v > 1") G.v = [1.5, 0, 3, -1] s_mon = SpikeMonitor(G) run(defaultclock.dt) assert_equal(s_mon.count, np.array([1, 0, 1, 0])) @pytest.mark.standalone_compatible def test_scalar_threshold(): c = 2 G = NeuronGroup(4, "", threshold="c > 1") s_mon = SpikeMonitor(G) run(defaultclock.dt) assert_equal(s_mon.count, np.array([1, 1, 1, 1])) if __name__ == "__main__": test_simple_threshold() test_scalar_threshold() brian2-2.5.4/brian2/tests/test_timedarray.py000066400000000000000000000117251445201106100207700ustar00rootroot00000000000000import pytest from brian2 import * from brian2.devices.device import reinit_and_delete from brian2.tests.utils import assert_allclose from brian2.utils.caching import _hashable @pytest.mark.codegen_independent def test_timedarray_direct_use(): ta = TimedArray(np.linspace(0, 10, 11), 1 * ms) assert ta(-1 * ms) == 0 assert ta(5 * ms) == 5 assert ta(10 * ms) == 10 assert ta(15 * ms) == 10 ta = TimedArray(np.linspace(0, 10, 11) * amp, 1 * ms) assert ta(-1 * ms) == 0 * amp assert ta(5 * ms) == 5 * amp assert ta(10 * ms) == 10 * amp assert ta(15 * ms) == 10 * amp ta2d = TimedArray((np.linspace(0, 11, 12) * amp).reshape(4, 3), 1 * ms) assert ta2d(-1 * ms, 0) == 0 * amp assert ta2d(0 * ms, 0) == 0 * amp assert ta2d(0 * ms, 1) == 1 * amp assert ta2d(1 * ms, 1) == 4 * amp assert_allclose(ta2d(1 * ms, [0, 1, 2]), [3, 4, 5] * amp) assert_allclose(ta2d(15 * ms, [0, 1, 2]), [9, 10, 11] * amp) @pytest.mark.standalone_compatible def test_timedarray_semantics(): # Make sure that timed arrays are interpreted as specifying the values # between t and t+dt (not between t-dt/2 and t+dt/2 as in Brian1) ta = TimedArray(array([0, 1]), dt=0.4 * ms) G = NeuronGroup(1, "value = ta(t) : 1", dt=0.1 * ms) mon = StateMonitor(G, "value", record=0) run(0.8 * ms) assert_allclose(mon[0].value, [0, 0, 0, 0, 1, 1, 1, 1]) assert_allclose(mon[0].value, ta(mon.t)) @pytest.mark.standalone_compatible def test_timedarray_no_units(): ta = TimedArray(np.arange(10), dt=0.1 * ms) G = NeuronGroup(1, "value = ta(t) + 1: 1", dt=0.1 * ms) mon = StateMonitor(G, "value", record=True, dt=0.1 * ms) run(1.1 * ms) assert_allclose(mon[0].value_, np.clip(np.arange(len(mon[0].t)), 0, 9) + 1) @pytest.mark.standalone_compatible def test_timedarray_with_units(): ta = TimedArray(np.arange(10) * amp, dt=0.1 * ms) G = NeuronGroup(1, "value = ta(t) + 2*nA: amp", dt=0.1 * ms) mon = StateMonitor(G, "value", record=True, dt=0.1 * ms) run(1.1 * ms) assert_allclose( mon[0].value, np.clip(np.arange(len(mon[0].t)), 0, 9) * amp + 2 * nA ) @pytest.mark.standalone_compatible def test_timedarray_2d(): # 4 time steps, 3 neurons ta2d = TimedArray(np.arange(12).reshape(4, 3), dt=0.1 * ms) G = NeuronGroup(3, "value = ta2d(t, i) + 1: 1", dt=0.1 * ms) mon = StateMonitor(G, "value", record=True, dt=0.1 * ms) run(0.5 * ms) assert_allclose(mon[0].value_, np.array([0, 3, 6, 9, 9]) + 1) assert_allclose(mon[1].value_, np.array([1, 4, 7, 10, 10]) + 1) assert_allclose(mon[2].value_, np.array([2, 5, 8, 11, 11]) + 1) @pytest.mark.codegen_independent def test_timedarray_incorrect_use(): ta = TimedArray(np.linspace(0, 10, 11), 1 * ms) ta2d = TimedArray((np.linspace(0, 11, 12) * amp).reshape(4, 3), 1 * ms) G = NeuronGroup(3, "I : amp") with pytest.raises(ValueError): setattr(G, "I", "ta2d(t)*amp") with pytest.raises(ValueError): setattr(G, "I", "ta(t, i)*amp") with pytest.raises(ValueError): setattr(G, "I", "ta()*amp") with pytest.raises(ValueError): setattr(G, "I", "ta*amp") @pytest.mark.standalone_compatible def test_timedarray_no_upsampling(): # Test a TimedArray where no upsampling is necessary because the monitor's # dt is bigger than the TimedArray's ta = TimedArray(np.arange(10), dt=0.01 * ms) G = NeuronGroup(1, "value = ta(t): 1", dt=0.1 * ms) mon = StateMonitor(G, "value", record=True, dt=1 * ms) run(2.1 * ms) assert_allclose(mon[0].value, [0, 9, 9]) # @pytest.mark.standalone_compatible # see FIXME comment below def test_long_timedarray(): """ Use a very long timedarray (with a big dt), where the upsampling can lead to integer overflow. """ ta = TimedArray(np.arange(16385), dt=1 * second) G = NeuronGroup(1, "value = ta(t) : 1") mon = StateMonitor(G, "value", record=True) net = Network(G, mon) # We'll start the simulation close to the critical boundary # FIXME: setting the time like this does not work for standalone net.t_ = float(16384 * second - 5 * ms) net.run(10 * ms) assert_allclose(mon[0].value[mon.t < 16384 * second], 16383) assert_allclose(mon[0].value[mon.t >= 16384 * second], 16384) def test_timedarray_repeated_use(): # Check that recreating a TimedArray with different values does work # correctly (no issues with caching) values = np.array([[1, 2, 3], [2, 4, 6]]) for run_idx in range(2): ta = TimedArray(values[run_idx], dt=defaultclock.dt, name="ta") G = NeuronGroup(1, "dx/dt = ta(t)/dt : 1", name="G") run(3 * defaultclock.dt) assert G.x[0] == 6 * (run_idx + 1) if __name__ == "__main__": test_timedarray_direct_use() test_timedarray_semantics() test_timedarray_no_units() test_timedarray_with_units() test_timedarray_2d() test_timedarray_incorrect_use() test_timedarray_no_upsampling() test_long_timedarray() test_timedarray_repeated_use() brian2-2.5.4/brian2/tests/test_units.py000066400000000000000000001463341445201106100177760ustar00rootroot00000000000000import itertools import pickle import warnings import numpy as np import pytest from numpy.testing import assert_equal import brian2 from brian2.core.preferences import prefs from brian2.tests.utils import assert_allclose from brian2.units.allunits import * from brian2.units.fundamentalunits import ( DIMENSIONLESS, UFUNCS_DIMENSIONLESS, UFUNCS_DIMENSIONLESS_TWOARGS, UFUNCS_INTEGERS, UFUNCS_LOGICAL, DimensionMismatchError, Quantity, Unit, check_units, fail_for_dimension_mismatch, get_dimensions, get_or_create_dimension, get_unit, have_same_dimensions, in_unit, is_dimensionless, is_scalar_type, ) from brian2.units.stdunits import Hz, cm, kHz, mM, ms, mV, nA, nS # To work around an issue in matplotlib 1.3.1 (see # https://github.com/matplotlib/matplotlib/pull/2591), we make `ravel` # return a unitless array and emit a warning explaining the issue. use_matplotlib_units_fix = False try: import matplotlib if matplotlib.__version__ == "1.3.1": use_matplotlib_units_fix = True except ImportError: pass def assert_quantity(q, values, unit): assert isinstance(q, Quantity) or ( have_same_dimensions(unit, 1) and (values.shape == () or isinstance(q, np.ndarray)) ), q assert_allclose(np.asarray(q), values) assert have_same_dimensions( q, unit ), f"Dimension mismatch: ({get_dimensions(q)}) ({get_dimensions(unit)})" @pytest.mark.codegen_independent def test_construction(): """Test the construction of quantity objects""" q = 500 * ms assert_quantity(q, 0.5, second) q = np.float64(500) * ms assert_quantity(q, 0.5, second) q = np.array(500) * ms assert_quantity(q, 0.5, second) q = np.array([500, 1000]) * ms assert_quantity(q, np.array([0.5, 1]), second) q = Quantity(500) assert_quantity(q, 500, 1) q = Quantity(500, dim=second.dim) assert_quantity(q, 500, second) q = Quantity([0.5, 1], dim=second.dim) assert_quantity(q, np.array([0.5, 1]), second) q = Quantity(np.array([0.5, 1]), dim=second.dim) assert_quantity(q, np.array([0.5, 1]), second) q = Quantity([500 * ms, 1 * second]) assert_quantity(q, np.array([0.5, 1]), second) q = Quantity.with_dimensions(np.array([0.5, 1]), second=1) assert_quantity(q, np.array([0.5, 1]), second) q = [0.5, 1] * second assert_quantity(q, np.array([0.5, 1]), second) # dimensionless quantities q = Quantity([1, 2, 3]) assert_quantity(q, np.array([1, 2, 3]), Unit(1)) q = Quantity(np.array([1, 2, 3])) assert_quantity(q, np.array([1, 2, 3]), Unit(1)) q = Quantity([]) assert_quantity(q, np.array([]), Unit(1)) # copying/referencing a quantity q1 = Quantity.with_dimensions(np.array([0.5, 1]), second=1) q2 = Quantity(q1) # no copy assert_quantity(q2, np.asarray(q1), q1) q2[0] = 3 * second assert_equal(q1[0], 3 * second) q1 = Quantity.with_dimensions(np.array([0.5, 1]), second=1) q2 = Quantity(q1, copy=True) # copy assert_quantity(q2, np.asarray(q1), q1) q2[0] = 3 * second assert_equal(q1[0], 0.5 * second) # Illegal constructor calls with pytest.raises(TypeError): Quantity([500 * ms, 1]) with pytest.raises(TypeError): Quantity(["some", "nonsense"]) with pytest.raises(DimensionMismatchError): Quantity([500 * ms, 1 * volt]) @pytest.mark.codegen_independent def test_get_dimensions(): """ Test various ways of getting/comparing the dimensions of a quantity. """ q = 500 * ms assert get_dimensions(q) is get_or_create_dimension(q.dimensions._dims) assert get_dimensions(q) is q.dimensions assert q.has_same_dimensions(3 * second) dims = q.dimensions assert_equal(dims.get_dimension("time"), 1.0) assert_equal(dims.get_dimension("length"), 0) assert get_dimensions(5) is DIMENSIONLESS assert get_dimensions(5.0) is DIMENSIONLESS assert get_dimensions(np.array(5, dtype=np.int32)) is DIMENSIONLESS assert get_dimensions(np.array(5.0)) is DIMENSIONLESS assert get_dimensions(np.float32(5.0)) is DIMENSIONLESS assert get_dimensions(np.float64(5.0)) is DIMENSIONLESS assert is_scalar_type(5) assert is_scalar_type(5.0) assert is_scalar_type(np.array(5, dtype=np.int32)) assert is_scalar_type(np.array(5.0)) assert is_scalar_type(np.float32(5.0)) assert is_scalar_type(np.float64(5.0)) with pytest.raises(TypeError): get_dimensions("a string") # wrong number of indices with pytest.raises(TypeError): get_or_create_dimension([1, 2, 3, 4, 5, 6]) # not a sequence with pytest.raises(TypeError): get_or_create_dimension(42) @pytest.mark.codegen_independent def test_display(): """ Test displaying a quantity in different units """ assert_equal(in_unit(3 * volt, mvolt), "3000. mV") assert_equal(in_unit(10 * mV, ohm * amp), "0.01 ohm A") with pytest.raises(DimensionMismatchError): in_unit(10 * nS, ohm) # A bit artificial... assert_equal(in_unit(10.0, Unit(10.0, scale=1)), "1.0") @pytest.mark.codegen_independent def test_scale(): # Check that unit scaling is implemented correctly from brian2.core.namespace import DEFAULT_UNITS siprefixes = { "y": 1e-24, "z": 1e-21, "a": 1e-18, "f": 1e-15, "p": 1e-12, "n": 1e-9, "u": 1e-6, "m": 1e-3, "": 1.0, "k": 1e3, "M": 1e6, "G": 1e9, "T": 1e12, "P": 1e15, "E": 1e18, "Z": 1e21, "Y": 1e24, } for prefix in siprefixes: if prefix in ["c", "d", "da", "h"]: continue scaled_unit = DEFAULT_UNITS[f"{prefix}meter"] assert_allclose(float(scaled_unit), siprefixes[prefix]) assert_allclose(5 * scaled_unit / meter, 5 * siprefixes[prefix]) scaled_unit = DEFAULT_UNITS[f"{prefix}meter2"] assert_allclose(float(scaled_unit), siprefixes[prefix] ** 2) assert_allclose(5 * scaled_unit / meter2, 5 * siprefixes[prefix] ** 2) scaled_unit = DEFAULT_UNITS[f"{prefix}meter3"] assert_allclose(float(scaled_unit), siprefixes[prefix] ** 3) assert_allclose(5 * scaled_unit / meter3, 5 * siprefixes[prefix] ** 3) # liter, gram, and molar are special, they are not base units with a # value of one, even though they do not have any prefix for unit, factor in [ ("liter", 1e-3), ("litre", 1e-3), ("gram", 1e-3), ("gramme", 1e-3), ("molar", 1e3), ]: base_unit = DEFAULT_UNITS[unit] scaled_unit = DEFAULT_UNITS[prefix + unit] assert_allclose(float(scaled_unit), siprefixes[prefix] * factor) assert_allclose(5 * scaled_unit / base_unit, 5 * siprefixes[prefix]) @pytest.mark.codegen_independent def test_pickling(): """ Test pickling of units. """ for q in [ 500 * mV, 500 * mV / mV, np.arange(10) * mV, np.arange(12).reshape(4, 3) * mV / ms, ]: pickled = pickle.dumps(q) unpickled = pickle.loads(pickled) assert isinstance(unpickled, type(q)) assert have_same_dimensions(unpickled, q) assert_equal(unpickled, q) @pytest.mark.codegen_independent def test_dimension_singletons(): # Make sure that Dimension objects are singletons, even when pickled volt_dim = get_or_create_dimension((2, 1, -3, -1, 0, 0, 0)) assert volt.dim is volt_dim import pickle pickled_dim = pickle.dumps(volt_dim) unpickled_dim = pickle.loads(pickled_dim) assert unpickled_dim is volt_dim assert unpickled_dim is volt.dim @pytest.mark.codegen_independent def test_str_repr(): """ Test that str representations do not raise any errors and that repr fullfills eval(repr(x)) == x. Also test generating LaTeX representations via sympy. """ import sympy from numpy import array # necessary for evaluating repr units_which_should_exist = [ metre, meter, kilogram, kilogramme, second, amp, kelvin, mole, candle, radian, steradian, hertz, newton, pascal, joule, watt, coulomb, volt, farad, ohm, siemens, weber, tesla, henry, lumen, lux, becquerel, gray, sievert, katal, gram, gramme, molar, liter, litre, ] # scaled versions of all these units should exist (we just check farad as an example) some_scaled_units = [ Yfarad, Zfarad, Efarad, Pfarad, Tfarad, Gfarad, Mfarad, kfarad, hfarad, dafarad, dfarad, cfarad, mfarad, ufarad, nfarad, pfarad, ffarad, afarad, zfarad, yfarad, ] # some powered units powered_units = [cmetre2, Yfarad3] # Combined units complex_units = [ (kgram * metre2) / (amp * second3), 5 * (kgram * metre2) / (amp * second3), metre * second**-1, 10 * metre * second**-1, array([1, 2, 3]) * kmetre / second, np.ones(3) * nS / cm**2, # Made-up unit: Unit( 1, dim=get_or_create_dimension(length=5, time=2), dispname="O", latexname=r"\Omega", ), 8000 * umetre**3, [0.0001, 10000] * umetre**3, 1 / metre, 1 / (coulomb * metre**2), Unit(1) / second, 3.0 * mM, 5 * mole / liter, 7 * liter / meter3, 1 / second**2, volt**-2, (volt**2) ** -1, (1 / second) / meter, 1 / (1 / second), ] unitless = [second / second, 5 * second / second, Unit(1)] for u in itertools.chain( units_which_should_exist, some_scaled_units, powered_units, complex_units, unitless, ): assert len(str(u)) > 0 if not is_dimensionless(u): assert len(sympy.latex(u)) assert get_dimensions(eval(repr(u))) == get_dimensions(u) assert_allclose(eval(repr(u)), u) for ar in [np.arange(10000) * mV, np.arange(100).reshape(10, 10) * mV]: latex_str = sympy.latex(ar) assert 0 < len(latex_str) < 1000 # arbitrary threshold, but see #1425 # test the `DIMENSIONLESS` object assert str(DIMENSIONLESS) == "1" assert repr(DIMENSIONLESS) == "Dimension()" # test DimensionMismatchError (only that it works without raising an error for error in [ DimensionMismatchError("A description"), DimensionMismatchError("A description", DIMENSIONLESS), DimensionMismatchError("A description", DIMENSIONLESS, second.dim), ]: assert len(str(error)) assert len(repr(error)) @pytest.mark.codegen_independent def test_format_quantity(): # Avoid that the default f-string (or .format call) discards units when used without # a format spec q = 0.5 * ms assert f"{q}" == f"{q!s}" == str(q) assert f"{q:g}" == f"{float(q)}" @pytest.mark.codegen_independent def test_slicing(): # Slicing and indexing, setting items quantity = np.reshape(np.arange(6), (2, 3)) * mV assert_equal(quantity[:], quantity) assert_equal(quantity[0], np.asarray(quantity)[0] * volt) assert_equal(quantity[0:1], np.asarray(quantity)[0:1] * volt) assert_equal(quantity[0, 1], np.asarray(quantity)[0, 1] * volt) assert_equal(quantity[0:1, 1:], np.asarray(quantity)[0:1, 1:] * volt) bool_matrix = np.array([[True, False, False], [False, False, True]]) assert_equal(quantity[bool_matrix], np.asarray(quantity)[bool_matrix] * volt) @pytest.mark.codegen_independent def test_setting(): quantity = np.reshape(np.arange(6), (2, 3)) * mV quantity[0, 1] = 10 * mV assert quantity[0, 1] == 10 * mV quantity[:, 1] = 20 * mV assert np.all(quantity[:, 1] == 20 * mV) quantity[1, :] = np.ones((1, 3)) * volt assert np.all(quantity[1, :] == 1 * volt) # Setting to zero should work without units as well quantity[1, 2] = 0 assert quantity[1, 2] == 0 * mV def set_to_value(key, value): quantity[key] = value with pytest.raises(DimensionMismatchError): set_to_value(0, 1) with pytest.raises(DimensionMismatchError): set_to_value(0, 1 * second) with pytest.raises(DimensionMismatchError): set_to_value((slice(2), slice(3)), np.ones((2, 3))) @pytest.mark.codegen_independent def test_multiplication_division(): quantities = [3 * mV, np.array([1, 2]) * mV, np.ones((3, 3)) * mV] q2 = 5 * second for q in quantities: # Scalars and array scalars assert_quantity(q / 3, np.asarray(q) / 3, volt) assert_quantity(3 / q, 3 / np.asarray(q), 1 / volt) assert_quantity(q * 3, np.asarray(q) * 3, volt) assert_quantity(3 * q, 3 * np.asarray(q), volt) assert_quantity(q / np.float64(3), np.asarray(q) / 3, volt) assert_quantity(np.float64(3) / q, 3 / np.asarray(q), 1 / volt) assert_quantity(q * np.float64(3), np.asarray(q) * 3, volt) assert_quantity(np.float64(3) * q, 3 * np.asarray(q), volt) assert_quantity(q / np.array(3), np.asarray(q) / 3, volt) assert_quantity(np.array(3) / q, 3 / np.asarray(q), 1 / volt) assert_quantity(q * np.array(3), np.asarray(q) * 3, volt) assert_quantity(np.array(3) * q, 3 * np.asarray(q), volt) # (unitless) arrays assert_quantity(q / np.array([3]), np.asarray(q) / 3, volt) assert_quantity(np.array([3]) / q, 3 / np.asarray(q), 1 / volt) assert_quantity(q * np.array([3]), np.asarray(q) * 3, volt) assert_quantity(np.array([3]) * q, 3 * np.asarray(q), volt) # arrays with units assert_quantity(q / q, np.asarray(q) / np.asarray(q), 1) assert_quantity(q * q, np.asarray(q) ** 2, volt**2) assert_quantity(q / q2, np.asarray(q) / np.asarray(q2), volt / second) assert_quantity(q2 / q, np.asarray(q2) / np.asarray(q), second / volt) assert_quantity(q * q2, np.asarray(q) * np.asarray(q2), volt * second) # using unsupported objects should fail with pytest.raises(TypeError): q / "string" with pytest.raises(TypeError): "string" / q with pytest.raises(TypeError): "string" * q with pytest.raises(TypeError): q * "string" @pytest.mark.codegen_independent def test_addition_subtraction(): quantities = [3 * mV, np.array([1, 2]) * mV, np.ones((3, 3)) * mV] q2 = 5 * volt for q in quantities: # arrays with units assert_quantity(q + q, np.asarray(q) + np.asarray(q), volt) assert_quantity(q - q, 0, volt) assert_quantity(q + q2, np.asarray(q) + np.asarray(q2), volt) assert_quantity(q2 + q, np.asarray(q2) + np.asarray(q), volt) assert_quantity(q - q2, np.asarray(q) - np.asarray(q2), volt) assert_quantity(q2 - q, np.asarray(q2) - np.asarray(q), volt) # mismatching units with pytest.raises(DimensionMismatchError): q + 5 * second with pytest.raises(DimensionMismatchError): 5 * second + q with pytest.raises(DimensionMismatchError): q - 5 * second with pytest.raises(DimensionMismatchError): 5 * second - q # scalar with pytest.raises(DimensionMismatchError): q + 5 with pytest.raises(DimensionMismatchError): 5 + q with pytest.raises(DimensionMismatchError): q + np.float64(5) with pytest.raises(DimensionMismatchError): np.float64(5) + q with pytest.raises(DimensionMismatchError): q - 5 with pytest.raises(DimensionMismatchError): 5 - q with pytest.raises(DimensionMismatchError): q - np.float64(5) with pytest.raises(DimensionMismatchError): np.float64(5) - q # unitless array with pytest.raises(DimensionMismatchError): q + np.array([5]) with pytest.raises(DimensionMismatchError): np.array([5]) + q with pytest.raises(DimensionMismatchError): q + np.array([5], dtype=np.float64) with pytest.raises(DimensionMismatchError): np.array([5], dtype=np.float64) + q with pytest.raises(DimensionMismatchError): q - np.array([5]) with pytest.raises(DimensionMismatchError): np.array([5]) - q with pytest.raises(DimensionMismatchError): q - np.array([5], dtype=np.float64) with pytest.raises(DimensionMismatchError): np.array([5], dtype=np.float64) - q # Check that operations with 0 work assert_quantity(q + 0, np.asarray(q), volt) assert_quantity(0 + q, np.asarray(q), volt) assert_quantity(q - 0, np.asarray(q), volt) assert_quantity(0 - q, -np.asarray(q), volt) assert_quantity(q + np.float64(0), np.asarray(q), volt) assert_quantity(np.float64(0) + q, np.asarray(q), volt) assert_quantity(q - np.float64(0), np.asarray(q), volt) assert_quantity(np.float64(0) - q, -np.asarray(q), volt) # using unsupported objects should fail with pytest.raises(TypeError): "string" + q with pytest.raises(TypeError): q + "string" with pytest.raises(TypeError): q - "string" with pytest.raises(TypeError): "string" - q @pytest.mark.codegen_independent def test_unary_operations(): from operator import neg, pos for op in [neg, pos]: for x in [2, np.array([2]), np.array([1, 2])]: assert_quantity(op(x * kilogram), op(x), kilogram) @pytest.mark.codegen_independent def test_binary_operations(): """Test whether binary operations work when they should and raise DimensionMismatchErrors when they should. Does not test for the actual result. """ from operator import add, eq, ge, gt, le, lt, ne, sub def assert_operations_work(a, b): try: # Test python builtins tryops = [add, sub, lt, le, gt, ge, eq, ne] for op in tryops: op(a, b) op(b, a) # Test equivalent numpy functions numpy_funcs = [ np.add, np.subtract, np.less, np.less_equal, np.greater, np.greater_equal, np.equal, np.not_equal, np.maximum, np.minimum, ] for numpy_func in numpy_funcs: numpy_func(a, b) numpy_func(b, a) except DimensionMismatchError as ex: raise AssertionError(f"Operation raised unexpected exception: {ex}") def assert_operations_do_not_work(a, b): # Test python builtins tryops = [add, sub, lt, le, gt, ge, eq, ne] for op in tryops: with pytest.raises(DimensionMismatchError): op(a, b) with pytest.raises(DimensionMismatchError): op(b, a) # Test equivalent numpy functions numpy_funcs = [ np.add, np.subtract, np.less, np.less_equal, np.greater, np.greater_equal, np.equal, np.not_equal, np.maximum, np.minimum, ] for numpy_func in numpy_funcs: with pytest.raises(DimensionMismatchError): numpy_func(a, b) with pytest.raises(DimensionMismatchError): numpy_func(b, a) # # Check that consistent units work # # unit arrays a = 1 * kilogram for b in [2 * kilogram, np.array([2]) * kilogram, np.array([1, 2]) * kilogram]: assert_operations_work(a, b) # dimensionless units and scalars a = 1 for b in [ 2 * kilogram / kilogram, np.array([2]) * kilogram / kilogram, np.array([1, 2]) * kilogram / kilogram, ]: assert_operations_work(a, b) # dimensionless units and unitless arrays a = np.array([1]) for b in [ 2 * kilogram / kilogram, np.array([2]) * kilogram / kilogram, np.array([1, 2]) * kilogram / kilogram, ]: assert_operations_work(a, b) # # Check that inconsistent units do not work # # unit arrays a = np.array([1]) * second for b in [2 * kilogram, np.array([2]) * kilogram, np.array([1, 2]) * kilogram]: assert_operations_do_not_work(a, b) # unitless array a = np.array([1]) for b in [2 * kilogram, np.array([2]) * kilogram, np.array([1, 2]) * kilogram]: assert_operations_do_not_work(a, b) # scalar a = 1 for b in [2 * kilogram, np.array([2]) * kilogram, np.array([1, 2]) * kilogram]: assert_operations_do_not_work(a, b) # Check that comparisons with inf/-inf always work values = [ 2 * kilogram / kilogram, 2 * kilogram, np.array([2]) * kilogram, np.array([1, 2]) * kilogram, ] for value in values: assert np.all(value < np.inf) assert np.all(np.inf > value) assert np.all(value <= np.inf) assert np.all(np.inf >= value) assert np.all(value != np.inf) assert np.all(np.inf != value) assert np.all(value >= -np.inf) assert np.all(-np.inf <= value) assert np.all(value > -np.inf) assert np.all(-np.inf < value) @pytest.mark.codegen_independent def test_power(): """ Test raising quantities to a power. """ values = [2 * kilogram, np.array([2]) * kilogram, np.array([1, 2]) * kilogram] for value in values: assert_quantity(value**3, np.asarray(value) ** 3, kilogram**3) # Test raising to a dimensionless quantity assert_quantity( value ** (3 * volt / volt), np.asarray(value) ** 3, kilogram**3 ) with pytest.raises(DimensionMismatchError): value ** (2 * volt) with pytest.raises(TypeError): value ** np.array([2, 3]) @pytest.mark.codegen_independent def test_inplace_operations(): q = np.arange(10) * volt q_orig = q.copy() q_id = id(q) q *= 2 assert np.all(q == 2 * q_orig) and id(q) == q_id q /= 2 assert np.all(q == q_orig) and id(q) == q_id q += 1 * volt assert np.all(q == q_orig + 1 * volt) and id(q) == q_id q -= 1 * volt assert np.all(q == q_orig) and id(q) == q_id q **= 2 assert np.all(q == q_orig**2) and id(q) == q_id q **= 0.5 assert np.all(q == q_orig) and id(q) == q_id def illegal_add(q2): q = np.arange(10) * volt q += q2 with pytest.raises(DimensionMismatchError): illegal_add(1 * second) with pytest.raises(DimensionMismatchError): illegal_add(1) def illegal_sub(q2): q = np.arange(10) * volt q -= q2 with pytest.raises(DimensionMismatchError): illegal_add(1 * second) with pytest.raises(DimensionMismatchError): illegal_add(1) def illegal_pow(q2): q = np.arange(10) * volt q **= q2 with pytest.raises(DimensionMismatchError): illegal_pow(1 * volt) with pytest.raises(TypeError): illegal_pow(np.arange(10)) # inplace operations with unsupported objects should fail for inplace_op in [ q.__iadd__, q.__isub__, q.__imul__, q.__idiv__, q.__itruediv__, q.__ifloordiv__, q.__imod__, q.__ipow__, ]: try: result = inplace_op("string") # if it doesn't fail with an error, it should return NotImplemented assert result == NotImplemented except TypeError: pass # raised on numpy >= 0.10 # make sure that inplace operations do not work on units/dimensions at all for inplace_op in [ volt.__iadd__, volt.__isub__, volt.__imul__, volt.__idiv__, volt.__itruediv__, volt.__ifloordiv__, volt.__imod__, volt.__ipow__, ]: with pytest.raises(TypeError): inplace_op(volt) for inplace_op in [ volt.dimensions.__imul__, volt.dimensions.__idiv__, volt.dimensions.__itruediv__, volt.dimensions.__ipow__, ]: with pytest.raises(TypeError): inplace_op(volt.dimensions) @pytest.mark.codegen_independent def test_unit_discarding_functions(): """ Test functions that discard units. """ from brian2.units.unitsafefunctions import ones_like, zeros_like values = [3 * mV, np.array([1, 2]) * mV, np.arange(12).reshape(3, 4) * mV] for value in values: assert_equal(np.sign(value), np.sign(np.asarray(value))) assert_equal(zeros_like(value), np.zeros_like(np.asarray(value))) assert_equal(ones_like(value), np.ones_like(np.asarray(value))) # Calling non-zero on a 0d array is deprecated, don't test it: if value.ndim > 0: assert_equal(np.nonzero(value), np.nonzero(np.asarray(value))) @pytest.mark.codegen_independent def test_unitsafe_functions(): """ Test the unitsafe functions wrapping their numpy counterparts. """ from brian2.units.unitsafefunctions import ( arccos, arccosh, arcsin, arcsinh, arctan, arctanh, cos, cosh, exp, log, sin, sinh, tan, tanh, ) # All functions with their numpy counterparts funcs = [ (sin, np.sin), (sinh, np.sinh), (arcsin, np.arcsin), (arcsinh, np.arcsinh), (cos, np.cos), (cosh, np.cosh), (arccos, np.arccos), (arccosh, np.arccosh), (tan, np.tan), (tanh, np.tanh), (arctan, np.arctan), (arctanh, np.arctanh), (log, np.log), (exp, np.exp), ] unitless_values = [ 3 * mV / mV, np.array([1, 2]) * mV / mV, np.ones((3, 3)) * mV / mV, ] numpy_values = [3, np.array([1, 2]), np.ones((3, 3))] unit_values = [3 * mV, np.array([1, 2]) * mV, np.ones((3, 3)) * mV] for func, np_func in funcs: # make sure these functions raise errors when run on values with dimensions for val in unit_values: with pytest.raises(DimensionMismatchError): func(val) # make sure the functions are equivalent to their numpy counterparts # when run on unitless values while ignoring warnings about invalid # values or divisions by zero with warnings.catch_warnings(): warnings.simplefilter("ignore") for val in unitless_values: assert_equal(func(val), np_func(val)) for val in numpy_values: assert_equal(func(val), np_func(val)) @pytest.mark.codegen_independent def test_special_case_numpy_functions(): """ Test a couple of functions/methods that need special treatment. """ from brian2.units.unitsafefunctions import diagonal, dot, ravel, trace, where quadratic_matrix = np.reshape(np.arange(9), (3, 3)) * mV # Temporarily suppress warnings related to the matplotlib 1.3 bug with warnings.catch_warnings(): warnings.simplefilter("ignore") # Check that function and method do the same thing assert_equal(ravel(quadratic_matrix), quadratic_matrix.ravel()) # Check that function gives the same result as on unitless arrays assert_equal( np.asarray(ravel(quadratic_matrix)), ravel(np.asarray(quadratic_matrix)) ) # Check that the function gives the same results as the original numpy # function assert_equal( np.ravel(np.asarray(quadratic_matrix)), ravel(np.asarray(quadratic_matrix)) ) # Do the same checks for diagonal, trace and dot assert_equal(diagonal(quadratic_matrix), quadratic_matrix.diagonal()) assert_equal( np.asarray(diagonal(quadratic_matrix)), diagonal(np.asarray(quadratic_matrix)) ) assert_equal( np.diagonal(np.asarray(quadratic_matrix)), diagonal(np.asarray(quadratic_matrix)), ) assert_equal(trace(quadratic_matrix), quadratic_matrix.trace()) assert_equal( np.asarray(trace(quadratic_matrix)), trace(np.asarray(quadratic_matrix)) ) assert_equal( np.trace(np.asarray(quadratic_matrix)), trace(np.asarray(quadratic_matrix)) ) assert_equal( dot(quadratic_matrix, quadratic_matrix), quadratic_matrix.dot(quadratic_matrix) ) assert_equal( np.asarray(dot(quadratic_matrix, quadratic_matrix)), dot(np.asarray(quadratic_matrix), np.asarray(quadratic_matrix)), ) assert_equal( np.dot(np.asarray(quadratic_matrix), np.asarray(quadratic_matrix)), dot(np.asarray(quadratic_matrix), np.asarray(quadratic_matrix)), ) assert_equal( np.asarray(quadratic_matrix.prod()), np.asarray(quadratic_matrix).prod() ) assert_equal( np.asarray(quadratic_matrix.prod(axis=0)), np.asarray(quadratic_matrix).prod(axis=0), ) # Check for correct units if use_matplotlib_units_fix: assert have_same_dimensions(1, ravel(quadratic_matrix)) else: assert have_same_dimensions(quadratic_matrix, ravel(quadratic_matrix)) assert have_same_dimensions(quadratic_matrix, trace(quadratic_matrix)) assert have_same_dimensions(quadratic_matrix, diagonal(quadratic_matrix)) assert have_same_dimensions( quadratic_matrix[0] ** 2, dot(quadratic_matrix, quadratic_matrix) ) assert have_same_dimensions( quadratic_matrix.prod(axis=0), quadratic_matrix[0] ** quadratic_matrix.shape[0] ) # check the where function # pure numpy array cond = [True, False, False] ar1 = np.array([1, 2, 3]) ar2 = np.array([4, 5, 6]) assert_equal(np.where(cond), where(cond)) assert_equal(np.where(cond, ar1, ar2), where(cond, ar1, ar2)) # dimensionless quantity assert_equal( np.where(cond, ar1, ar2), np.asarray(where(cond, ar1 * mV / mV, ar2 * mV / mV)) ) # quantity with dimensions ar1 = ar1 * mV ar2 = ar2 * mV assert_equal( np.where(cond, np.asarray(ar1), np.asarray(ar2)), np.asarray(where(cond, ar1, ar2)), ) # Check some error cases with pytest.raises(ValueError): where(cond, ar1) with pytest.raises(TypeError): where(cond, ar1, ar1, ar2) with pytest.raises(DimensionMismatchError): where(cond, ar1, ar1 / ms) # Check setasflat (for numpy < 1.7) if hasattr(Quantity, "setasflat"): a = np.arange(10) * mV b = np.ones(10).reshape(5, 2) * volt c = np.ones(10).reshape(5, 2) * second with pytest.raises(DimensionMismatchError): a.setasflat(c) a.setasflat(b) assert_equal(a.flatten(), b.flatten()) # Check cumprod a = np.arange(1, 10) * mV / mV assert_equal(a.cumprod(), np.asarray(a).cumprod()) with pytest.raises(TypeError): (np.arange(1, 5) * mV).cumprod() # Functions that should not change units @pytest.mark.codegen_independent def test_numpy_functions_same_dimensions(): values = [np.array([1, 2]), np.ones((3, 3))] units = [volt, second, siemens, mV, kHz] # numpy functions keep_dim_funcs = [ np.abs, np.cumsum, np.max, np.mean, np.min, np.negative, np.ptp, np.round, np.squeeze, np.std, np.sum, np.transpose, ] for value, unit in itertools.product(values, units): q_ar = value * unit for func in keep_dim_funcs: test_ar = func(q_ar) if not get_dimensions(test_ar) is q_ar.dim: raise AssertionError( f"'{func.__name__}' failed on {q_ar!r} -- dim was " f"{q_ar.dim}, is now {get_dimensions(test_ar)}." ) # Python builtins should work on one-dimensional arrays value = np.arange(5) builtins = [abs, max, min, sum] for unit in units: q_ar = value * unit for func in builtins: test_ar = func(q_ar) if not get_dimensions(test_ar) is q_ar.dim: raise AssertionError( f"'{func.__name__}' failed on {q_ar!r} -- dim " f"was {q_ar.dim}, is now " f"{get_dimensions(test_ar)}" ) @pytest.mark.codegen_independent def test_numpy_functions_indices(): """ Check numpy functions that return indices. """ values = [np.array([-4, 3, -2, 1, 0]), np.ones((3, 3)), np.array([17])] units = [volt, second, siemens, mV, kHz] # numpy functions keep_dim_funcs = [np.argmin, np.argmax, np.argsort, np.nonzero] for value, unit in itertools.product(values, units): q_ar = value * unit for func in keep_dim_funcs: test_ar = func(q_ar) # Compare it to the result on the same value without units comparison_ar = func(value) assert_equal( test_ar, comparison_ar, ( "function %s returned an incorrect result when used on quantities " % func.__name__ ), ) @pytest.mark.codegen_independent def test_numpy_functions_dimensionless(): """ Test that numpy functions that should work on dimensionless quantities only work dimensionless arrays and return the correct result. """ unitless_values = [3, np.array([-4, 3, -1, 2]), np.ones((3, 3))] unit_values = [3 * mV, np.array([-4, 3, -1, 2]) * mV, np.ones((3, 3)) * mV] with warnings.catch_warnings(): # ignore division by 0 warnings warnings.simplefilter("ignore", RuntimeWarning) for value in unitless_values: for ufunc in UFUNCS_DIMENSIONLESS: result_unitless = eval(f"np.{ufunc}(value)") result_array = eval(f"np.{ufunc}(np.array(value))") assert isinstance( result_unitless, (np.ndarray, np.number) ) and not isinstance(result_unitless, Quantity) assert_equal(result_unitless, result_array) for ufunc in UFUNCS_DIMENSIONLESS_TWOARGS: result_unitless = eval(f"np.{ufunc}(value, value)") result_array = eval(f"np.{ufunc}(np.array(value), np.array(value))") assert isinstance( result_unitless, (np.ndarray, np.number) ) and not isinstance(result_unitless, Quantity) assert_equal(result_unitless, result_array) for value, unitless_value in zip(unit_values, unitless_values): for ufunc in UFUNCS_DIMENSIONLESS: with pytest.raises(DimensionMismatchError): eval(f"np.{ufunc}(value)", globals(), {"value": value}) for ufunc in UFUNCS_DIMENSIONLESS_TWOARGS: with pytest.raises(DimensionMismatchError): eval( f"np.{ufunc}(value1, value2)", globals(), {"value1": value, "value2": unitless_value}, ) with pytest.raises(DimensionMismatchError): eval( f"np.{ufunc}(value2, value1)", globals(), {"value1": value, "value2": unitless_value}, ) with pytest.raises(DimensionMismatchError): eval(f"np.{ufunc}(value, value)", globals(), {"value": value}) @pytest.mark.codegen_independent def test_numpy_functions_change_dimensions(): """ Test some numpy functions that change the dimensions of the quantity. """ unit_values = [np.array([1, 2]) * mV, np.ones((3, 3)) * 2 * mV] for value in unit_values: assert_quantity(np.var(value), np.var(np.array(value)), volt**2) assert_quantity(np.square(value), np.square(np.array(value)), volt**2) assert_quantity(np.sqrt(value), np.sqrt(np.array(value)), volt**0.5) assert_quantity( np.reciprocal(value), np.reciprocal(np.array(value)), 1.0 / volt ) @pytest.mark.codegen_independent def test_numpy_functions_matmul(): """ Check support for matmul and the ``@`` operator. """ no_units_eye = np.eye(3) with_units_eye = no_units_eye * Mohm matrix_no_units = np.arange(9).reshape((3, 3)) matrix_units = matrix_no_units * nA # First operand with units assert_allclose(no_units_eye @ matrix_units, matrix_units) assert have_same_dimensions(no_units_eye @ matrix_units, matrix_units) assert_allclose(np.matmul(no_units_eye, matrix_units), matrix_units) assert have_same_dimensions(np.matmul(no_units_eye, matrix_units), matrix_units) # Second operand with units assert_allclose(with_units_eye @ matrix_no_units, matrix_no_units * Mohm) assert have_same_dimensions( with_units_eye @ matrix_no_units, matrix_no_units * Mohm ) assert_allclose(np.matmul(with_units_eye, matrix_no_units), matrix_no_units * Mohm) assert have_same_dimensions( np.matmul(with_units_eye, matrix_no_units), matrix_no_units * Mohm ) # Both operands with units assert_allclose( with_units_eye @ matrix_units, no_units_eye @ matrix_no_units * nA * Mohm ) assert have_same_dimensions(with_units_eye @ matrix_units, nA * Mohm) assert_allclose( np.matmul(with_units_eye, matrix_units), np.matmul(no_units_eye, matrix_no_units) * nA * Mohm, ) assert have_same_dimensions(np.matmul(with_units_eye, matrix_units), nA * Mohm) @pytest.mark.codegen_independent def test_numpy_functions_typeerror(): """ Assures that certain numpy functions raise a TypeError when called on quantities. """ unitless_values = [ 3 * mV / mV, np.array([1, 2]) * mV / mV, np.ones((3, 3)) * mV / mV, ] unit_values = [3 * mV, np.array([1, 2]) * mV, np.ones((3, 3)) * mV] for value in unitless_values + unit_values: for ufunc in UFUNCS_INTEGERS: if ufunc == "invert": # only takes one argument with pytest.raises(TypeError): eval(f"np.{ufunc}(value)", globals(), {"value": value}) else: with pytest.raises(TypeError): eval(f"np.{ufunc}(value, value)", globals(), {"value": value}) @pytest.mark.codegen_independent def test_numpy_functions_logical(): """ Assure that logical numpy functions work on all quantities and return unitless boolean arrays. """ unit_values1 = [3 * mV, np.array([1, 2]) * mV, np.ones((3, 3)) * mV] unit_values2 = [3 * second, np.array([1, 2]) * second, np.ones((3, 3)) * second] for ufunc in UFUNCS_LOGICAL: for value1, value2 in zip(unit_values1, unit_values2): try: # one argument result_units = eval(f"np.{ufunc}(value1)") result_array = eval(f"np.{ufunc}(np.array(value1))") except (ValueError, TypeError): # two arguments result_units = eval(f"np.{ufunc}(value1, value2)") result_array = eval(f"np.{ufunc}(np.array(value1), np.array(value2))") # assert that comparing to a string results in "NotImplemented" or an error try: result = eval(f'np.{ufunc}(value1, "a string")') assert result == NotImplemented except (ValueError, TypeError): pass # raised on numpy >= 0.10 try: result = eval(f'np.{ufunc}("a string", value1)') assert result == NotImplemented except (ValueError, TypeError): pass # raised on numpy >= 0.10 assert not isinstance(result_units, Quantity) assert_equal(result_units, result_array) @pytest.mark.codegen_independent def test_arange_linspace(): # For dimensionless values, the unit-safe functions should give the same results assert_equal(brian2.arange(5), np.arange(5)) assert_equal(brian2.arange(1, 5), np.arange(1, 5)) assert_equal(brian2.arange(10, step=2), np.arange(10, step=2)) assert_equal(brian2.arange(0, 5, 0.5), np.arange(0, 5, 0.5)) assert_equal(brian2.linspace(0, 1), np.linspace(0, 1)) assert_equal(brian2.linspace(0, 1, 10), np.linspace(0, 1, 10)) # Make sure units are checked with pytest.raises(DimensionMismatchError): brian2.arange(1 * mV, 5) with pytest.raises(DimensionMismatchError): brian2.arange(1 * mV, 5 * mV) with pytest.raises(DimensionMismatchError): brian2.arange(1, 5 * mV) with pytest.raises(DimensionMismatchError): brian2.arange(1 * mV, 5 * ms) with pytest.raises(DimensionMismatchError): brian2.arange(1 * mV, 5 * mV, step=1 * ms) with pytest.raises(DimensionMismatchError): brian2.arange(1 * ms, 5 * mV) # Check correct functioning with units assert_quantity( brian2.arange(5 * mV, step=1 * mV), float(mV) * np.arange(5, step=1), mV ) assert_quantity( brian2.arange(1 * mV, 5 * mV, 1 * mV), float(mV) * np.arange(1, 5, 1), mV ) assert_quantity(brian2.linspace(1 * mV, 2 * mV), float(mV) * np.linspace(1, 2), mV) # Check errors for arange with incorrect numbers of arguments/duplicate arguments with pytest.raises(TypeError): brian2.arange() with pytest.raises(TypeError): brian2.arange(0, 5, 1, 0) with pytest.raises(TypeError): brian2.arange(0, stop=1) with pytest.raises(TypeError): brian2.arange(0, 5, stop=1) with pytest.raises(TypeError): brian2.arange(0, 5, start=1) with pytest.raises(TypeError): brian2.arange(0, 5, 1, start=1) with pytest.raises(TypeError): brian2.arange(0, 5, 1, stop=2) with pytest.raises(TypeError): brian2.arange(0, 5, 1, step=2) @pytest.mark.codegen_independent def test_list(): """ Test converting to and from a list. """ values = [3 * mV, np.array([1, 2]) * mV, np.arange(12).reshape(4, 3) * mV] for value in values: l = value.tolist() from_list = Quantity(l) assert have_same_dimensions(from_list, value) assert_equal(from_list, value) @pytest.mark.codegen_independent def test_check_units(): """ Test the check_units decorator """ @check_units(v=volt) def a_function(v, x): """ v has to have units of volt, x can have any (or no) unit. """ pass # Try correct units a_function(3 * mV, 5 * second) a_function(5 * volt, "something") a_function([1, 2, 3] * volt, None) # lists that can be converted should also work a_function([1 * volt, 2 * volt, 3 * volt], None) # Strings and None are also allowed to pass a_function("a string", None) a_function(None, None) # Try incorrect units with pytest.raises(DimensionMismatchError): a_function(5 * second, None) with pytest.raises(DimensionMismatchError): a_function(5, None) with pytest.raises(TypeError): a_function(object(), None) with pytest.raises(TypeError): a_function([1, 2 * volt, 3], None) @check_units(result=second) def b_function(return_second): """ Return a value in seconds if return_second is True, otherwise return a value in volt. """ if return_second: return 5 * second else: return 3 * volt # Should work (returns second) b_function(True) # Should fail (returns volt) with pytest.raises(DimensionMismatchError): b_function(False) @check_units(a=bool, b=1, result=bool) def c_function(a, b): if a: return b > 0 else: return b assert c_function(True, 1) assert not c_function(True, -1) with pytest.raises(TypeError): c_function(1, 1) with pytest.raises(TypeError): c_function(1 * mV, 1) with pytest.raises(TypeError): c_function(False, 1) @pytest.mark.codegen_independent def test_get_unit(): """ Test get_unit """ values = [ (volt.dim, volt), (mV.dim, volt), ((amp / metre**2).dim, amp / metre**2), ] for dim, expected_unit in values: unit = get_unit(dim) assert isinstance(unit, Unit) assert unit == expected_unit assert float(unit) == 1.0 @pytest.mark.codegen_independent def test_get_best_unit(): # get_best_unit should not check all values for long arrays, since it is # a function used for display purposes only. Instead, only the first and # last few values should matter (see github issue #966) long_ar = np.ones(10000) * siemens long_ar[:10] = 1 * nS long_ar[-10:] = 2 * nS values = [ (np.arange(10) * mV, mV), ([0.001, 0.002, 0.003] * second, ms), (long_ar, nS), ] for ar, expected_unit in values: assert ar.get_best_unit() is expected_unit assert str(expected_unit) in ar.in_best_unit() @pytest.mark.codegen_independent def test_switching_off_unit_checks(): """ Check switching off unit checks (used for external functions). """ import brian2.units.fundamentalunits as fundamentalunits x = 3 * second y = 5 * volt with pytest.raises(DimensionMismatchError): x + y fundamentalunits.unit_checking = False # Now it should work assert np.asarray(x + y) == np.array(8) assert have_same_dimensions(x, y) assert x.has_same_dimensions(y) fundamentalunits.unit_checking = True @pytest.mark.codegen_independent def test_fail_for_dimension_mismatch(): """ Test the fail_for_dimension_mismatch function. """ # examples that should not raise an error dim1, dim2 = fail_for_dimension_mismatch(3) assert dim1 is DIMENSIONLESS assert dim2 is DIMENSIONLESS dim1, dim2 = fail_for_dimension_mismatch(3 * volt / volt) assert dim1 is DIMENSIONLESS assert dim2 is DIMENSIONLESS dim1, dim2 = fail_for_dimension_mismatch(3 * volt / volt, 7) assert dim1 is DIMENSIONLESS assert dim2 is DIMENSIONLESS dim1, dim2 = fail_for_dimension_mismatch(3 * volt, 5 * volt) assert dim1 is volt.dim assert dim2 is volt.dim # examples that should raise an error with pytest.raises(DimensionMismatchError): fail_for_dimension_mismatch(6 * volt) with pytest.raises(DimensionMismatchError): fail_for_dimension_mismatch(6 * volt, 5 * second) @pytest.mark.codegen_independent def test_deepcopy(): d = {"x": 1 * second} from copy import deepcopy d_copy = deepcopy(d) assert d_copy["x"] == 1 * second d_copy["x"] += 1 * second assert d_copy["x"] == 2 * second assert d["x"] == 1 * second @pytest.mark.codegen_independent def test_inplace_on_scalars(): # We want "copy semantics" for in-place operations on scalar quantities # in the same way as for Python scalars for scalar in [3 * mV, 3 * mV / mV]: scalar_reference = scalar scalar_copy = Quantity(scalar, copy=True) scalar += scalar_copy assert_equal(scalar_copy, scalar_reference) scalar *= 1.5 assert_equal(scalar_copy, scalar_reference) scalar /= 2 assert_equal(scalar_copy, scalar_reference) # also check that it worked correctly for the scalar itself assert_allclose(scalar, (scalar_copy + scalar_copy) * 1.5 / 2) # For arrays, it should use reference semantics for vector in [[3] * mV, [3] * mV / mV]: vector_reference = vector vector_copy = Quantity(vector, copy=True) vector += vector_copy assert_equal(vector, vector_reference) vector *= 1.5 assert_equal(vector, vector_reference) vector /= 2 assert_equal(vector, vector_reference) # also check that it worked correctly for the vector itself assert_allclose(vector, (vector_copy + vector_copy) * 1.5 / 2) def test_units_vs_quantities(): # Unit objects should stay Unit objects under certain operations # (important e.g. in the unit definition of Equations, where only units but # not quantities are allowed) assert isinstance(meter**2, Unit) assert isinstance(meter**-1, Unit) assert isinstance(meter**0.5, Unit) assert isinstance(meter / second, Unit) assert isinstance(amp / meter**2, Unit) assert isinstance(1 / meter, Unit) assert isinstance(1.0 / meter, Unit) # Using the unconventional type(x) == y since we want to test that # e.g. meter**2 stays a Unit and does not become a Quantity however Unit # inherits from Quantity and therefore both would pass the isinstance test assert type(2 / meter) == Quantity assert type(2 * meter) == Quantity assert type(meter + meter) == Quantity assert type(meter - meter) == Quantity @pytest.mark.codegen_independent def test_all_units_list(): from brian2.units.allunits import all_units assert meter in all_units assert volt in all_units assert cm in all_units assert Hz in all_units assert all(isinstance(u, Unit) for u in all_units) @pytest.mark.codegen_independent def test_constants(): import brian2.units.constants as constants # Check that the expected names exist and have the correct dimensions assert constants.avogadro_constant.dim == (1 / mole).dim assert constants.boltzmann_constant.dim == (joule / kelvin).dim assert constants.electric_constant.dim == (farad / meter).dim assert constants.electron_mass.dim == kilogram.dim assert constants.elementary_charge.dim == coulomb.dim assert constants.faraday_constant.dim == (coulomb / mole).dim assert constants.gas_constant.dim == (joule / mole / kelvin).dim assert constants.magnetic_constant.dim == (newton / amp2).dim assert constants.molar_mass_constant.dim == (kilogram / mole).dim assert constants.zero_celsius.dim == kelvin.dim # Check the consistency between a few constants assert_allclose( constants.gas_constant, constants.avogadro_constant * constants.boltzmann_constant, ) assert_allclose( constants.faraday_constant, constants.avogadro_constant * constants.elementary_charge, ) if __name__ == "__main__": test_construction() test_get_dimensions() test_display() test_scale() test_power() test_pickling() test_str_repr() test_slicing() test_setting() test_multiplication_division() test_addition_subtraction() test_unary_operations() test_binary_operations() test_inplace_operations() test_unit_discarding_functions() test_unitsafe_functions() test_special_case_numpy_functions() test_numpy_functions_same_dimensions() test_numpy_functions_indices() test_numpy_functions_dimensionless() test_numpy_functions_change_dimensions() test_numpy_functions_typeerror() test_numpy_functions_logical() test_arange_linspace() test_list() test_check_units() test_get_unit() test_get_best_unit() test_switching_off_unit_checks() test_fail_for_dimension_mismatch() test_deepcopy() test_inplace_on_scalars() test_units_vs_quantities() test_all_units_list() test_constants() brian2-2.5.4/brian2/tests/test_utils.py000066400000000000000000000017261445201106100177670ustar00rootroot00000000000000import builtins import pytest from brian2.utils.environment import running_from_ipython from brian2.utils.stringtools import SpellChecker @pytest.mark.codegen_independent def test_environment(): """ Test information about the environment we are running under. """ if hasattr(builtins, "__IPYTHON__"): testing_under_ipython = True del builtins.__IPYTHON__ else: testing_under_ipython = False assert not running_from_ipython() builtins.__IPYTHON__ = True assert running_from_ipython() if not testing_under_ipython: del builtins.__IPYTHON__ @pytest.mark.codegen_independent def test_spell_check(): checker = SpellChecker(["vm", "alpha", "beta"]) assert checker.suggest("Vm") == {"vm"} assert checker.suggest("alphas") == {"alpha"} assert checker.suggest("bta") == {"beta"} assert checker.suggest("gamma") == set() if __name__ == "__main__": test_environment() test_spell_check() brian2-2.5.4/brian2/tests/test_variables.py000066400000000000000000000050261445201106100205740ustar00rootroot00000000000000""" Some basic tests for the `Variable` system """ from collections import namedtuple import numpy as np import pytest from brian2.core.preferences import prefs from brian2.core.variables import * from brian2.units.allunits import second from brian2.units.fundamentalunits import Unit @pytest.mark.codegen_independent def test_construction_errors(): # Boolean variable that isn't dimensionless with pytest.raises(ValueError): Variable(name="name", dimensions=second.dim, dtype=bool) # Dynamic array variable that is constant but not constant in size with pytest.raises(ValueError): DynamicArrayVariable( name="name", owner=None, size=0, device=None, constant=True, needs_reference_update=True, ) @pytest.mark.codegen_independent def test_str_repr(): # Basic test that the str/repr methods work FakeGroup = namedtuple("G", ["name"]) group = FakeGroup(name="groupname") variables = [ Variable(name="name", dimensions=second.dim), Constant(name="name", dimensions=second.dim, value=1.0), AuxiliaryVariable(name="name", dimensions=second.dim), ArrayVariable( name="name", dimensions=second.dim, owner=None, size=10, device=None ), DynamicArrayVariable( name="name", dimensions=second.dim, owner=None, size=0, device=None ), Subexpression( name="sub", dimensions=second.dim, expr="a+b", owner=group, device=None ), ] for var in variables: assert len(str(var)) # The repr value should contain the name of the class assert len(repr(var)) and var.__class__.__name__ in repr(var) @pytest.mark.codegen_independent def test_dtype_str(): FakeGroup = namedtuple("G", ["name"]) group = FakeGroup(name="groupname") for d in ["int32", "int64", "float32", "float64", "bool", "int", "float"]: nd = np.dtype(d) for var in [ Constant(name="name", value=np.zeros(1, dtype=nd)[0]), AuxiliaryVariable(name="name", dtype=nd), ArrayVariable(name="name", owner=None, size=10, device=None, dtype=nd), DynamicArrayVariable( name="name", owner=None, dtype=nd, size=0, device=None ), Subexpression(name="sub", expr="a+b", owner=group, device=None, dtype=nd), ]: assert var.dtype_str.startswith(d) if __name__ == "__main__": test_construction_errors() test_str_repr() test_dtype_str() brian2-2.5.4/brian2/tests/utils.py000066400000000000000000000044521445201106100167270ustar00rootroot00000000000000import numpy as np from numpy.testing import assert_allclose as numpy_allclose from brian2 import prefs from brian2.units.fundamentalunits import have_same_dimensions def assert_allclose(actual, desired, rtol=4.5e8, atol=0, **kwds): """ Thin wrapper around numpy's `~numpy.testing.utils.assert_allclose` function. The tolerance depends on the floating point precision as defined by the `core.default_float_dtype` preference. Parameters ---------- actual : `numpy.ndarray` The results to check. desired : `numpy.ndarray` The expected results. rtol : float, optional The relative tolerance which will be multiplied with the machine epsilon of the type set as `core.default_float_type`. atol : float, optional The absolute tolerance """ assert have_same_dimensions(actual, desired) eps = np.finfo(prefs["core.default_float_dtype"]).eps rtol = eps * rtol numpy_allclose( np.asarray(actual), np.asarray(desired), rtol=rtol, atol=atol, **kwds ) def exc_isinstance(exc_info, expected_exception, raise_not_implemented=False): """ Simple helper function as an alternative to calling `~.pytest.ExceptionInfo.errisinstance` which will take into account all the "causing" exceptions in an exception chain. Parameters ---------- exc_info : `pytest.ExceptionInfo` or `Exception` The exception info as returned by `pytest.raises`. expected_exception : `type` The expected exception class raise_not_implemented : bool, optional Whether to re-raise a `NotImplementedError` – necessary for tests that should be skipped with ``@skip_if_not_implemented``. Defaults to ``False``. Returns ------- correct_exception : bool Whether the exception itself or one of the causing exceptions is of the expected type. """ if exc_info is None: return False if hasattr(exc_info, "value"): exc_info = exc_info.value if isinstance(exc_info, expected_exception): return True elif raise_not_implemented and isinstance(exc_info, NotImplementedError): raise exc_info return exc_isinstance( exc_info.__cause__, expected_exception, raise_not_implemented=raise_not_implemented, ) brian2-2.5.4/brian2/units/000077500000000000000000000000001445201106100152105ustar00rootroot00000000000000brian2-2.5.4/brian2/units/__init__.py000066400000000000000000000117141445201106100173250ustar00rootroot00000000000000""" The unit system. """ # isort:skip_file from .allunits import ( # basic units pamp, namp, uamp, mamp, amp, kamp, Mamp, Gamp, Tamp, kelvin, kilogram, # silly to have mkilogram, etc... pmetre, nmetre, umetre, mmetre, metre, kmetre, Mmetre, Gmetre, Tmetre, pmeter, nmeter, umeter, mmeter, meter, kmeter, Mmeter, Gmeter, Tmeter, cmetre, cmeter, # quite commonly used psecond, nsecond, usecond, msecond, second, ksecond, Msecond, Gsecond, Tsecond, pmole, nmole, umole, mmole, mole, kmole, Mmole, Gmole, Tmole, # derived units pcoulomb, ncoulomb, ucoulomb, mcoulomb, coulomb, kcoulomb, Mcoulomb, Gcoulomb, Tcoulomb, pfarad, nfarad, ufarad, mfarad, farad, kfarad, Mfarad, Gfarad, Tfarad, pgram, ngram, ugram, mgram, gram, kgram, Mgram, Ggram, Tgram, pgramme, ngramme, ugramme, mgramme, gramme, kgramme, Mgramme, Ggramme, Tgramme, phertz, nhertz, uhertz, mhertz, hertz, khertz, Mhertz, Ghertz, Thertz, pjoule, njoule, ujoule, mjoule, joule, kjoule, Mjoule, Gjoule, Tjoule, pmolar, nmolar, umolar, mmolar, molar, kmolar, Mmolar, Gmolar, Tmolar, pliter, nliter, uliter, mliter, liter, kliter, Mliter, Gliter, Tliter, plitre, nlitre, ulitre, mlitre, litre, klitre, Mlitre, Glitre, Tlitre, ppascal, npascal, upascal, mpascal, pascal, kpascal, Mpascal, Gpascal, Tpascal, pohm, nohm, uohm, mohm, ohm, kohm, Mohm, Gohm, Tohm, psiemens, nsiemens, usiemens, msiemens, siemens, ksiemens, Msiemens, Gsiemens, Tsiemens, pvolt, nvolt, uvolt, mvolt, volt, kvolt, Mvolt, Gvolt, Tvolt, pwatt, nwatt, uwatt, mwatt, watt, kwatt, Mwatt, Gwatt, Twatt, ) from .unitsafefunctions import * from .unitsafefunctions import __all__ as unitsafefunctions_all from .fundamentalunits import * from .fundamentalunits import __all__ as fundamentalunits_all from .stdunits import * from .stdunits import __all__ as stdunits_all __all__ = [ "pamp", "namp", "uamp", "mamp", "amp", "kamp", "Mamp", "Gamp", "Tamp", "kelvin", "kilogram", "pmetre", "nmetre", "umetre", "mmetre", "metre", "kmetre", "Mmetre", "Gmetre", "Tmetre", "pmeter", "nmeter", "umeter", "mmeter", "meter", "kmeter", "Mmeter", "Gmeter", "Tmeter", "cmetre", "cmeter", "psecond", "nsecond", "usecond", "msecond", "second", "ksecond", "Msecond", "Gsecond", "Tsecond", "pmole", "nmole", "umole", "mmole", "mole", "kmole", "Mmole", "Gmole", "Tmole", # derived units "pcoulomb", "ncoulomb", "ucoulomb", "mcoulomb", "coulomb", "kcoulomb", "Mcoulomb", "Gcoulomb", "Tcoulomb", "pfarad", "nfarad", "ufarad", "mfarad", "farad", "kfarad", "Mfarad", "Gfarad", "Tfarad", "pgram", "ngram", "ugram", "mgram", "gram", "kgram", "Mgram", "Ggram", "Tgram", "pgramme", "ngramme", "ugramme", "mgramme", "gramme", "kgramme", "Mgramme", "Ggramme", "Tgramme", "phertz", "nhertz", "uhertz", "mhertz", "hertz", "khertz", "Mhertz", "Ghertz", "Thertz", "pjoule", "njoule", "ujoule", "mjoule", "joule", "kjoule", "Mjoule", "Gjoule", "Tjoule", "pmolar", "nmolar", "umolar", "mmolar", "molar", "kmolar", "Mmolar", "Gmolar", "Tmolar", "pliter", "nliter", "uliter", "mliter", "liter", "kliter", "Mliter", "Gliter", "Tliter", "plitre", "nlitre", "ulitre", "mlitre", "litre", "klitre", "Mlitre", "Glitre", "Tlitre", "ppascal", "npascal", "upascal", "mpascal", "pascal", "kpascal", "Mpascal", "Gpascal", "Tpascal", "pohm", "nohm", "uohm", "mohm", "ohm", "kohm", "Mohm", "Gohm", "Tohm", "psiemens", "nsiemens", "usiemens", "msiemens", "siemens", "ksiemens", "Msiemens", "Gsiemens", "Tsiemens", "pvolt", "nvolt", "uvolt", "mvolt", "volt", "kvolt", "Mvolt", "Gvolt", "Tvolt", "pwatt", "nwatt", "uwatt", "mwatt", "watt", "kwatt", "Mwatt", "Gwatt", "Twatt", ] __all__.extend(unitsafefunctions_all) __all__.extend(fundamentalunits_all) __all__.extend(stdunits_all) brian2-2.5.4/brian2/units/allunits.py000066400000000000000000007747201445201106100174360ustar00rootroot00000000000000""" THIS FILE IS AUTOMATICALLY GENERATED BY A STATIC CODE GENERATION TOOL DO NOT EDIT BY HAND Instead edit the template: dev/tools/static_codegen/units_template.py """ # fmt: off # flake8: noqa import itertools from .fundamentalunits import ( Unit, additional_unit_register, get_or_create_dimension, standard_unit_register, ) __all__ = [ "metre", "meter", "kilogram", "second", "amp", "ampere", "kelvin", "mole", "mol", "candle", "kilogramme", "gram", "gramme", "molar", "radian", "steradian", "hertz", "newton", "pascal", "joule", "watt", "coulomb", "volt", "farad", "ohm", "siemens", "weber", "tesla", "henry", "lumen", "lux", "becquerel", "gray", "sievert", "katal", "ametre", "cmetre", "Zmetre", "Pmetre", "dmetre", "Gmetre", "fmetre", "hmetre", "dametre", "mmetre", "nmetre", "pmetre", "umetre", "Tmetre", "ymetre", "Emetre", "zmetre", "Mmetre", "kmetre", "Ymetre", "ameter", "cmeter", "Zmeter", "Pmeter", "dmeter", "Gmeter", "fmeter", "hmeter", "dameter", "mmeter", "nmeter", "pmeter", "umeter", "Tmeter", "ymeter", "Emeter", "zmeter", "Mmeter", "kmeter", "Ymeter", "asecond", "csecond", "Zsecond", "Psecond", "dsecond", "Gsecond", "fsecond", "hsecond", "dasecond", "msecond", "nsecond", "psecond", "usecond", "Tsecond", "ysecond", "Esecond", "zsecond", "Msecond", "ksecond", "Ysecond", "aamp", "camp", "Zamp", "Pamp", "damp", "Gamp", "famp", "hamp", "daamp", "mamp", "namp", "pamp", "uamp", "Tamp", "yamp", "Eamp", "zamp", "Mamp", "kamp", "Yamp", "aampere", "campere", "Zampere", "Pampere", "dampere", "Gampere", "fampere", "hampere", "daampere", "mampere", "nampere", "pampere", "uampere", "Tampere", "yampere", "Eampere", "zampere", "Mampere", "kampere", "Yampere", "amole", "cmole", "Zmole", "Pmole", "dmole", "Gmole", "fmole", "hmole", "damole", "mmole", "nmole", "pmole", "umole", "Tmole", "ymole", "Emole", "zmole", "Mmole", "kmole", "Ymole", "amol", "cmol", "Zmol", "Pmol", "dmol", "Gmol", "fmol", "hmol", "damol", "mmol", "nmol", "pmol", "umol", "Tmol", "ymol", "Emol", "zmol", "Mmol", "kmol", "Ymol", "acandle", "ccandle", "Zcandle", "Pcandle", "dcandle", "Gcandle", "fcandle", "hcandle", "dacandle", "mcandle", "ncandle", "pcandle", "ucandle", "Tcandle", "ycandle", "Ecandle", "zcandle", "Mcandle", "kcandle", "Ycandle", "agram", "cgram", "Zgram", "Pgram", "dgram", "Ggram", "fgram", "hgram", "dagram", "mgram", "ngram", "pgram", "ugram", "Tgram", "ygram", "Egram", "zgram", "Mgram", "kgram", "Ygram", "agramme", "cgramme", "Zgramme", "Pgramme", "dgramme", "Ggramme", "fgramme", "hgramme", "dagramme", "mgramme", "ngramme", "pgramme", "ugramme", "Tgramme", "ygramme", "Egramme", "zgramme", "Mgramme", "kgramme", "Ygramme", "amolar", "cmolar", "Zmolar", "Pmolar", "dmolar", "Gmolar", "fmolar", "hmolar", "damolar", "mmolar", "nmolar", "pmolar", "umolar", "Tmolar", "ymolar", "Emolar", "zmolar", "Mmolar", "kmolar", "Ymolar", "aradian", "cradian", "Zradian", "Pradian", "dradian", "Gradian", "fradian", "hradian", "daradian", "mradian", "nradian", "pradian", "uradian", "Tradian", "yradian", "Eradian", "zradian", "Mradian", "kradian", "Yradian", "asteradian", "csteradian", "Zsteradian", "Psteradian", "dsteradian", "Gsteradian", "fsteradian", "hsteradian", "dasteradian", "msteradian", "nsteradian", "psteradian", "usteradian", "Tsteradian", "ysteradian", "Esteradian", "zsteradian", "Msteradian", "ksteradian", "Ysteradian", "ahertz", "chertz", "Zhertz", "Phertz", "dhertz", "Ghertz", "fhertz", "hhertz", "dahertz", "mhertz", "nhertz", "phertz", "uhertz", "Thertz", "yhertz", "Ehertz", "zhertz", "Mhertz", "khertz", "Yhertz", "anewton", "cnewton", "Znewton", "Pnewton", "dnewton", "Gnewton", "fnewton", "hnewton", "danewton", "mnewton", "nnewton", "pnewton", "unewton", "Tnewton", "ynewton", "Enewton", "znewton", "Mnewton", "knewton", "Ynewton", "apascal", "cpascal", "Zpascal", "Ppascal", "dpascal", "Gpascal", "fpascal", "hpascal", "dapascal", "mpascal", "npascal", "ppascal", "upascal", "Tpascal", "ypascal", "Epascal", "zpascal", "Mpascal", "kpascal", "Ypascal", "ajoule", "cjoule", "Zjoule", "Pjoule", "djoule", "Gjoule", "fjoule", "hjoule", "dajoule", "mjoule", "njoule", "pjoule", "ujoule", "Tjoule", "yjoule", "Ejoule", "zjoule", "Mjoule", "kjoule", "Yjoule", "awatt", "cwatt", "Zwatt", "Pwatt", "dwatt", "Gwatt", "fwatt", "hwatt", "dawatt", "mwatt", "nwatt", "pwatt", "uwatt", "Twatt", "ywatt", "Ewatt", "zwatt", "Mwatt", "kwatt", "Ywatt", "acoulomb", "ccoulomb", "Zcoulomb", "Pcoulomb", "dcoulomb", "Gcoulomb", "fcoulomb", "hcoulomb", "dacoulomb", "mcoulomb", "ncoulomb", "pcoulomb", "ucoulomb", "Tcoulomb", "ycoulomb", "Ecoulomb", "zcoulomb", "Mcoulomb", "kcoulomb", "Ycoulomb", "avolt", "cvolt", "Zvolt", "Pvolt", "dvolt", "Gvolt", "fvolt", "hvolt", "davolt", "mvolt", "nvolt", "pvolt", "uvolt", "Tvolt", "yvolt", "Evolt", "zvolt", "Mvolt", "kvolt", "Yvolt", "afarad", "cfarad", "Zfarad", "Pfarad", "dfarad", "Gfarad", "ffarad", "hfarad", "dafarad", "mfarad", "nfarad", "pfarad", "ufarad", "Tfarad", "yfarad", "Efarad", "zfarad", "Mfarad", "kfarad", "Yfarad", "aohm", "cohm", "Zohm", "Pohm", "dohm", "Gohm", "fohm", "hohm", "daohm", "mohm", "nohm", "pohm", "uohm", "Tohm", "yohm", "Eohm", "zohm", "Mohm", "kohm", "Yohm", "asiemens", "csiemens", "Zsiemens", "Psiemens", "dsiemens", "Gsiemens", "fsiemens", "hsiemens", "dasiemens", "msiemens", "nsiemens", "psiemens", "usiemens", "Tsiemens", "ysiemens", "Esiemens", "zsiemens", "Msiemens", "ksiemens", "Ysiemens", "aweber", "cweber", "Zweber", "Pweber", "dweber", "Gweber", "fweber", "hweber", "daweber", "mweber", "nweber", "pweber", "uweber", "Tweber", "yweber", "Eweber", "zweber", "Mweber", "kweber", "Yweber", "atesla", "ctesla", "Ztesla", "Ptesla", "dtesla", "Gtesla", "ftesla", "htesla", "datesla", "mtesla", "ntesla", "ptesla", "utesla", "Ttesla", "ytesla", "Etesla", "ztesla", "Mtesla", "ktesla", "Ytesla", "ahenry", "chenry", "Zhenry", "Phenry", "dhenry", "Ghenry", "fhenry", "hhenry", "dahenry", "mhenry", "nhenry", "phenry", "uhenry", "Thenry", "yhenry", "Ehenry", "zhenry", "Mhenry", "khenry", "Yhenry", "alumen", "clumen", "Zlumen", "Plumen", "dlumen", "Glumen", "flumen", "hlumen", "dalumen", "mlumen", "nlumen", "plumen", "ulumen", "Tlumen", "ylumen", "Elumen", "zlumen", "Mlumen", "klumen", "Ylumen", "alux", "clux", "Zlux", "Plux", "dlux", "Glux", "flux", "hlux", "dalux", "mlux", "nlux", "plux", "ulux", "Tlux", "ylux", "Elux", "zlux", "Mlux", "klux", "Ylux", "abecquerel", "cbecquerel", "Zbecquerel", "Pbecquerel", "dbecquerel", "Gbecquerel", "fbecquerel", "hbecquerel", "dabecquerel", "mbecquerel", "nbecquerel", "pbecquerel", "ubecquerel", "Tbecquerel", "ybecquerel", "Ebecquerel", "zbecquerel", "Mbecquerel", "kbecquerel", "Ybecquerel", "agray", "cgray", "Zgray", "Pgray", "dgray", "Ggray", "fgray", "hgray", "dagray", "mgray", "ngray", "pgray", "ugray", "Tgray", "ygray", "Egray", "zgray", "Mgray", "kgray", "Ygray", "asievert", "csievert", "Zsievert", "Psievert", "dsievert", "Gsievert", "fsievert", "hsievert", "dasievert", "msievert", "nsievert", "psievert", "usievert", "Tsievert", "ysievert", "Esievert", "zsievert", "Msievert", "ksievert", "Ysievert", "akatal", "ckatal", "Zkatal", "Pkatal", "dkatal", "Gkatal", "fkatal", "hkatal", "dakatal", "mkatal", "nkatal", "pkatal", "ukatal", "Tkatal", "ykatal", "Ekatal", "zkatal", "Mkatal", "kkatal", "Ykatal", "metre2", "metre3", "meter2", "meter3", "kilogram2", "kilogram3", "second2", "second3", "amp2", "amp3", "ampere2", "ampere3", "kelvin2", "kelvin3", "mole2", "mole3", "mol2", "mol3", "candle2", "candle3", "kilogramme2", "kilogramme3", "gram2", "gram3", "gramme2", "gramme3", "molar2", "molar3", "radian2", "radian3", "steradian2", "steradian3", "hertz2", "hertz3", "newton2", "newton3", "pascal2", "pascal3", "joule2", "joule3", "watt2", "watt3", "coulomb2", "coulomb3", "volt2", "volt3", "farad2", "farad3", "ohm2", "ohm3", "siemens2", "siemens3", "weber2", "weber3", "tesla2", "tesla3", "henry2", "henry3", "lumen2", "lumen3", "lux2", "lux3", "becquerel2", "becquerel3", "gray2", "gray3", "sievert2", "sievert3", "katal2", "katal3", "ametre2", "ametre3", "cmetre2", "cmetre3", "Zmetre2", "Zmetre3", "Pmetre2", "Pmetre3", "dmetre2", "dmetre3", "Gmetre2", "Gmetre3", "fmetre2", "fmetre3", "hmetre2", "hmetre3", "dametre2", "dametre3", "mmetre2", "mmetre3", "nmetre2", "nmetre3", "pmetre2", "pmetre3", "umetre2", "umetre3", "Tmetre2", "Tmetre3", "ymetre2", "ymetre3", "Emetre2", "Emetre3", "zmetre2", "zmetre3", "Mmetre2", "Mmetre3", "kmetre2", "kmetre3", "Ymetre2", "Ymetre3", "ameter2", "ameter3", "cmeter2", "cmeter3", "Zmeter2", "Zmeter3", "Pmeter2", "Pmeter3", "dmeter2", "dmeter3", "Gmeter2", "Gmeter3", "fmeter2", "fmeter3", "hmeter2", "hmeter3", "dameter2", "dameter3", "mmeter2", "mmeter3", "nmeter2", "nmeter3", "pmeter2", "pmeter3", "umeter2", "umeter3", "Tmeter2", "Tmeter3", "ymeter2", "ymeter3", "Emeter2", "Emeter3", "zmeter2", "zmeter3", "Mmeter2", "Mmeter3", "kmeter2", "kmeter3", "Ymeter2", "Ymeter3", "asecond2", "asecond3", "csecond2", "csecond3", "Zsecond2", "Zsecond3", "Psecond2", "Psecond3", "dsecond2", "dsecond3", "Gsecond2", "Gsecond3", "fsecond2", "fsecond3", "hsecond2", "hsecond3", "dasecond2", "dasecond3", "msecond2", "msecond3", "nsecond2", "nsecond3", "psecond2", "psecond3", "usecond2", "usecond3", "Tsecond2", "Tsecond3", "ysecond2", "ysecond3", "Esecond2", "Esecond3", "zsecond2", "zsecond3", "Msecond2", "Msecond3", "ksecond2", "ksecond3", "Ysecond2", "Ysecond3", "aamp2", "aamp3", "camp2", "camp3", "Zamp2", "Zamp3", "Pamp2", "Pamp3", "damp2", "damp3", "Gamp2", "Gamp3", "famp2", "famp3", "hamp2", "hamp3", "daamp2", "daamp3", "mamp2", "mamp3", "namp2", "namp3", "pamp2", "pamp3", "uamp2", "uamp3", "Tamp2", "Tamp3", "yamp2", "yamp3", "Eamp2", "Eamp3", "zamp2", "zamp3", "Mamp2", "Mamp3", "kamp2", "kamp3", "Yamp2", "Yamp3", "aampere2", "aampere3", "campere2", "campere3", "Zampere2", "Zampere3", "Pampere2", "Pampere3", "dampere2", "dampere3", "Gampere2", "Gampere3", "fampere2", "fampere3", "hampere2", "hampere3", "daampere2", "daampere3", "mampere2", "mampere3", "nampere2", "nampere3", "pampere2", "pampere3", "uampere2", "uampere3", "Tampere2", "Tampere3", "yampere2", "yampere3", "Eampere2", "Eampere3", "zampere2", "zampere3", "Mampere2", "Mampere3", "kampere2", "kampere3", "Yampere2", "Yampere3", "amole2", "amole3", "cmole2", "cmole3", "Zmole2", "Zmole3", "Pmole2", "Pmole3", "dmole2", "dmole3", "Gmole2", "Gmole3", "fmole2", "fmole3", "hmole2", "hmole3", "damole2", "damole3", "mmole2", "mmole3", "nmole2", "nmole3", "pmole2", "pmole3", "umole2", "umole3", "Tmole2", "Tmole3", "ymole2", "ymole3", "Emole2", "Emole3", "zmole2", "zmole3", "Mmole2", "Mmole3", "kmole2", "kmole3", "Ymole2", "Ymole3", "amol2", "amol3", "cmol2", "cmol3", "Zmol2", "Zmol3", "Pmol2", "Pmol3", "dmol2", "dmol3", "Gmol2", "Gmol3", "fmol2", "fmol3", "hmol2", "hmol3", "damol2", "damol3", "mmol2", "mmol3", "nmol2", "nmol3", "pmol2", "pmol3", "umol2", "umol3", "Tmol2", "Tmol3", "ymol2", "ymol3", "Emol2", "Emol3", "zmol2", "zmol3", "Mmol2", "Mmol3", "kmol2", "kmol3", "Ymol2", "Ymol3", "acandle2", "acandle3", "ccandle2", "ccandle3", "Zcandle2", "Zcandle3", "Pcandle2", "Pcandle3", "dcandle2", "dcandle3", "Gcandle2", "Gcandle3", "fcandle2", "fcandle3", "hcandle2", "hcandle3", "dacandle2", "dacandle3", "mcandle2", "mcandle3", "ncandle2", "ncandle3", "pcandle2", "pcandle3", "ucandle2", "ucandle3", "Tcandle2", "Tcandle3", "ycandle2", "ycandle3", "Ecandle2", "Ecandle3", "zcandle2", "zcandle3", "Mcandle2", "Mcandle3", "kcandle2", "kcandle3", "Ycandle2", "Ycandle3", "agram2", "agram3", "cgram2", "cgram3", "Zgram2", "Zgram3", "Pgram2", "Pgram3", "dgram2", "dgram3", "Ggram2", "Ggram3", "fgram2", "fgram3", "hgram2", "hgram3", "dagram2", "dagram3", "mgram2", "mgram3", "ngram2", "ngram3", "pgram2", "pgram3", "ugram2", "ugram3", "Tgram2", "Tgram3", "ygram2", "ygram3", "Egram2", "Egram3", "zgram2", "zgram3", "Mgram2", "Mgram3", "kgram2", "kgram3", "Ygram2", "Ygram3", "agramme2", "agramme3", "cgramme2", "cgramme3", "Zgramme2", "Zgramme3", "Pgramme2", "Pgramme3", "dgramme2", "dgramme3", "Ggramme2", "Ggramme3", "fgramme2", "fgramme3", "hgramme2", "hgramme3", "dagramme2", "dagramme3", "mgramme2", "mgramme3", "ngramme2", "ngramme3", "pgramme2", "pgramme3", "ugramme2", "ugramme3", "Tgramme2", "Tgramme3", "ygramme2", "ygramme3", "Egramme2", "Egramme3", "zgramme2", "zgramme3", "Mgramme2", "Mgramme3", "kgramme2", "kgramme3", "Ygramme2", "Ygramme3", "amolar2", "amolar3", "cmolar2", "cmolar3", "Zmolar2", "Zmolar3", "Pmolar2", "Pmolar3", "dmolar2", "dmolar3", "Gmolar2", "Gmolar3", "fmolar2", "fmolar3", "hmolar2", "hmolar3", "damolar2", "damolar3", "mmolar2", "mmolar3", "nmolar2", "nmolar3", "pmolar2", "pmolar3", "umolar2", "umolar3", "Tmolar2", "Tmolar3", "ymolar2", "ymolar3", "Emolar2", "Emolar3", "zmolar2", "zmolar3", "Mmolar2", "Mmolar3", "kmolar2", "kmolar3", "Ymolar2", "Ymolar3", "aradian2", "aradian3", "cradian2", "cradian3", "Zradian2", "Zradian3", "Pradian2", "Pradian3", "dradian2", "dradian3", "Gradian2", "Gradian3", "fradian2", "fradian3", "hradian2", "hradian3", "daradian2", "daradian3", "mradian2", "mradian3", "nradian2", "nradian3", "pradian2", "pradian3", "uradian2", "uradian3", "Tradian2", "Tradian3", "yradian2", "yradian3", "Eradian2", "Eradian3", "zradian2", "zradian3", "Mradian2", "Mradian3", "kradian2", "kradian3", "Yradian2", "Yradian3", "asteradian2", "asteradian3", "csteradian2", "csteradian3", "Zsteradian2", "Zsteradian3", "Psteradian2", "Psteradian3", "dsteradian2", "dsteradian3", "Gsteradian2", "Gsteradian3", "fsteradian2", "fsteradian3", "hsteradian2", "hsteradian3", "dasteradian2", "dasteradian3", "msteradian2", "msteradian3", "nsteradian2", "nsteradian3", "psteradian2", "psteradian3", "usteradian2", "usteradian3", "Tsteradian2", "Tsteradian3", "ysteradian2", "ysteradian3", "Esteradian2", "Esteradian3", "zsteradian2", "zsteradian3", "Msteradian2", "Msteradian3", "ksteradian2", "ksteradian3", "Ysteradian2", "Ysteradian3", "ahertz2", "ahertz3", "chertz2", "chertz3", "Zhertz2", "Zhertz3", "Phertz2", "Phertz3", "dhertz2", "dhertz3", "Ghertz2", "Ghertz3", "fhertz2", "fhertz3", "hhertz2", "hhertz3", "dahertz2", "dahertz3", "mhertz2", "mhertz3", "nhertz2", "nhertz3", "phertz2", "phertz3", "uhertz2", "uhertz3", "Thertz2", "Thertz3", "yhertz2", "yhertz3", "Ehertz2", "Ehertz3", "zhertz2", "zhertz3", "Mhertz2", "Mhertz3", "khertz2", "khertz3", "Yhertz2", "Yhertz3", "anewton2", "anewton3", "cnewton2", "cnewton3", "Znewton2", "Znewton3", "Pnewton2", "Pnewton3", "dnewton2", "dnewton3", "Gnewton2", "Gnewton3", "fnewton2", "fnewton3", "hnewton2", "hnewton3", "danewton2", "danewton3", "mnewton2", "mnewton3", "nnewton2", "nnewton3", "pnewton2", "pnewton3", "unewton2", "unewton3", "Tnewton2", "Tnewton3", "ynewton2", "ynewton3", "Enewton2", "Enewton3", "znewton2", "znewton3", "Mnewton2", "Mnewton3", "knewton2", "knewton3", "Ynewton2", "Ynewton3", "apascal2", "apascal3", "cpascal2", "cpascal3", "Zpascal2", "Zpascal3", "Ppascal2", "Ppascal3", "dpascal2", "dpascal3", "Gpascal2", "Gpascal3", "fpascal2", "fpascal3", "hpascal2", "hpascal3", "dapascal2", "dapascal3", "mpascal2", "mpascal3", "npascal2", "npascal3", "ppascal2", "ppascal3", "upascal2", "upascal3", "Tpascal2", "Tpascal3", "ypascal2", "ypascal3", "Epascal2", "Epascal3", "zpascal2", "zpascal3", "Mpascal2", "Mpascal3", "kpascal2", "kpascal3", "Ypascal2", "Ypascal3", "ajoule2", "ajoule3", "cjoule2", "cjoule3", "Zjoule2", "Zjoule3", "Pjoule2", "Pjoule3", "djoule2", "djoule3", "Gjoule2", "Gjoule3", "fjoule2", "fjoule3", "hjoule2", "hjoule3", "dajoule2", "dajoule3", "mjoule2", "mjoule3", "njoule2", "njoule3", "pjoule2", "pjoule3", "ujoule2", "ujoule3", "Tjoule2", "Tjoule3", "yjoule2", "yjoule3", "Ejoule2", "Ejoule3", "zjoule2", "zjoule3", "Mjoule2", "Mjoule3", "kjoule2", "kjoule3", "Yjoule2", "Yjoule3", "awatt2", "awatt3", "cwatt2", "cwatt3", "Zwatt2", "Zwatt3", "Pwatt2", "Pwatt3", "dwatt2", "dwatt3", "Gwatt2", "Gwatt3", "fwatt2", "fwatt3", "hwatt2", "hwatt3", "dawatt2", "dawatt3", "mwatt2", "mwatt3", "nwatt2", "nwatt3", "pwatt2", "pwatt3", "uwatt2", "uwatt3", "Twatt2", "Twatt3", "ywatt2", "ywatt3", "Ewatt2", "Ewatt3", "zwatt2", "zwatt3", "Mwatt2", "Mwatt3", "kwatt2", "kwatt3", "Ywatt2", "Ywatt3", "acoulomb2", "acoulomb3", "ccoulomb2", "ccoulomb3", "Zcoulomb2", "Zcoulomb3", "Pcoulomb2", "Pcoulomb3", "dcoulomb2", "dcoulomb3", "Gcoulomb2", "Gcoulomb3", "fcoulomb2", "fcoulomb3", "hcoulomb2", "hcoulomb3", "dacoulomb2", "dacoulomb3", "mcoulomb2", "mcoulomb3", "ncoulomb2", "ncoulomb3", "pcoulomb2", "pcoulomb3", "ucoulomb2", "ucoulomb3", "Tcoulomb2", "Tcoulomb3", "ycoulomb2", "ycoulomb3", "Ecoulomb2", "Ecoulomb3", "zcoulomb2", "zcoulomb3", "Mcoulomb2", "Mcoulomb3", "kcoulomb2", "kcoulomb3", "Ycoulomb2", "Ycoulomb3", "avolt2", "avolt3", "cvolt2", "cvolt3", "Zvolt2", "Zvolt3", "Pvolt2", "Pvolt3", "dvolt2", "dvolt3", "Gvolt2", "Gvolt3", "fvolt2", "fvolt3", "hvolt2", "hvolt3", "davolt2", "davolt3", "mvolt2", "mvolt3", "nvolt2", "nvolt3", "pvolt2", "pvolt3", "uvolt2", "uvolt3", "Tvolt2", "Tvolt3", "yvolt2", "yvolt3", "Evolt2", "Evolt3", "zvolt2", "zvolt3", "Mvolt2", "Mvolt3", "kvolt2", "kvolt3", "Yvolt2", "Yvolt3", "afarad2", "afarad3", "cfarad2", "cfarad3", "Zfarad2", "Zfarad3", "Pfarad2", "Pfarad3", "dfarad2", "dfarad3", "Gfarad2", "Gfarad3", "ffarad2", "ffarad3", "hfarad2", "hfarad3", "dafarad2", "dafarad3", "mfarad2", "mfarad3", "nfarad2", "nfarad3", "pfarad2", "pfarad3", "ufarad2", "ufarad3", "Tfarad2", "Tfarad3", "yfarad2", "yfarad3", "Efarad2", "Efarad3", "zfarad2", "zfarad3", "Mfarad2", "Mfarad3", "kfarad2", "kfarad3", "Yfarad2", "Yfarad3", "aohm2", "aohm3", "cohm2", "cohm3", "Zohm2", "Zohm3", "Pohm2", "Pohm3", "dohm2", "dohm3", "Gohm2", "Gohm3", "fohm2", "fohm3", "hohm2", "hohm3", "daohm2", "daohm3", "mohm2", "mohm3", "nohm2", "nohm3", "pohm2", "pohm3", "uohm2", "uohm3", "Tohm2", "Tohm3", "yohm2", "yohm3", "Eohm2", "Eohm3", "zohm2", "zohm3", "Mohm2", "Mohm3", "kohm2", "kohm3", "Yohm2", "Yohm3", "asiemens2", "asiemens3", "csiemens2", "csiemens3", "Zsiemens2", "Zsiemens3", "Psiemens2", "Psiemens3", "dsiemens2", "dsiemens3", "Gsiemens2", "Gsiemens3", "fsiemens2", "fsiemens3", "hsiemens2", "hsiemens3", "dasiemens2", "dasiemens3", "msiemens2", "msiemens3", "nsiemens2", "nsiemens3", "psiemens2", "psiemens3", "usiemens2", "usiemens3", "Tsiemens2", "Tsiemens3", "ysiemens2", "ysiemens3", "Esiemens2", "Esiemens3", "zsiemens2", "zsiemens3", "Msiemens2", "Msiemens3", "ksiemens2", "ksiemens3", "Ysiemens2", "Ysiemens3", "aweber2", "aweber3", "cweber2", "cweber3", "Zweber2", "Zweber3", "Pweber2", "Pweber3", "dweber2", "dweber3", "Gweber2", "Gweber3", "fweber2", "fweber3", "hweber2", "hweber3", "daweber2", "daweber3", "mweber2", "mweber3", "nweber2", "nweber3", "pweber2", "pweber3", "uweber2", "uweber3", "Tweber2", "Tweber3", "yweber2", "yweber3", "Eweber2", "Eweber3", "zweber2", "zweber3", "Mweber2", "Mweber3", "kweber2", "kweber3", "Yweber2", "Yweber3", "atesla2", "atesla3", "ctesla2", "ctesla3", "Ztesla2", "Ztesla3", "Ptesla2", "Ptesla3", "dtesla2", "dtesla3", "Gtesla2", "Gtesla3", "ftesla2", "ftesla3", "htesla2", "htesla3", "datesla2", "datesla3", "mtesla2", "mtesla3", "ntesla2", "ntesla3", "ptesla2", "ptesla3", "utesla2", "utesla3", "Ttesla2", "Ttesla3", "ytesla2", "ytesla3", "Etesla2", "Etesla3", "ztesla2", "ztesla3", "Mtesla2", "Mtesla3", "ktesla2", "ktesla3", "Ytesla2", "Ytesla3", "ahenry2", "ahenry3", "chenry2", "chenry3", "Zhenry2", "Zhenry3", "Phenry2", "Phenry3", "dhenry2", "dhenry3", "Ghenry2", "Ghenry3", "fhenry2", "fhenry3", "hhenry2", "hhenry3", "dahenry2", "dahenry3", "mhenry2", "mhenry3", "nhenry2", "nhenry3", "phenry2", "phenry3", "uhenry2", "uhenry3", "Thenry2", "Thenry3", "yhenry2", "yhenry3", "Ehenry2", "Ehenry3", "zhenry2", "zhenry3", "Mhenry2", "Mhenry3", "khenry2", "khenry3", "Yhenry2", "Yhenry3", "alumen2", "alumen3", "clumen2", "clumen3", "Zlumen2", "Zlumen3", "Plumen2", "Plumen3", "dlumen2", "dlumen3", "Glumen2", "Glumen3", "flumen2", "flumen3", "hlumen2", "hlumen3", "dalumen2", "dalumen3", "mlumen2", "mlumen3", "nlumen2", "nlumen3", "plumen2", "plumen3", "ulumen2", "ulumen3", "Tlumen2", "Tlumen3", "ylumen2", "ylumen3", "Elumen2", "Elumen3", "zlumen2", "zlumen3", "Mlumen2", "Mlumen3", "klumen2", "klumen3", "Ylumen2", "Ylumen3", "alux2", "alux3", "clux2", "clux3", "Zlux2", "Zlux3", "Plux2", "Plux3", "dlux2", "dlux3", "Glux2", "Glux3", "flux2", "flux3", "hlux2", "hlux3", "dalux2", "dalux3", "mlux2", "mlux3", "nlux2", "nlux3", "plux2", "plux3", "ulux2", "ulux3", "Tlux2", "Tlux3", "ylux2", "ylux3", "Elux2", "Elux3", "zlux2", "zlux3", "Mlux2", "Mlux3", "klux2", "klux3", "Ylux2", "Ylux3", "abecquerel2", "abecquerel3", "cbecquerel2", "cbecquerel3", "Zbecquerel2", "Zbecquerel3", "Pbecquerel2", "Pbecquerel3", "dbecquerel2", "dbecquerel3", "Gbecquerel2", "Gbecquerel3", "fbecquerel2", "fbecquerel3", "hbecquerel2", "hbecquerel3", "dabecquerel2", "dabecquerel3", "mbecquerel2", "mbecquerel3", "nbecquerel2", "nbecquerel3", "pbecquerel2", "pbecquerel3", "ubecquerel2", "ubecquerel3", "Tbecquerel2", "Tbecquerel3", "ybecquerel2", "ybecquerel3", "Ebecquerel2", "Ebecquerel3", "zbecquerel2", "zbecquerel3", "Mbecquerel2", "Mbecquerel3", "kbecquerel2", "kbecquerel3", "Ybecquerel2", "Ybecquerel3", "agray2", "agray3", "cgray2", "cgray3", "Zgray2", "Zgray3", "Pgray2", "Pgray3", "dgray2", "dgray3", "Ggray2", "Ggray3", "fgray2", "fgray3", "hgray2", "hgray3", "dagray2", "dagray3", "mgray2", "mgray3", "ngray2", "ngray3", "pgray2", "pgray3", "ugray2", "ugray3", "Tgray2", "Tgray3", "ygray2", "ygray3", "Egray2", "Egray3", "zgray2", "zgray3", "Mgray2", "Mgray3", "kgray2", "kgray3", "Ygray2", "Ygray3", "asievert2", "asievert3", "csievert2", "csievert3", "Zsievert2", "Zsievert3", "Psievert2", "Psievert3", "dsievert2", "dsievert3", "Gsievert2", "Gsievert3", "fsievert2", "fsievert3", "hsievert2", "hsievert3", "dasievert2", "dasievert3", "msievert2", "msievert3", "nsievert2", "nsievert3", "psievert2", "psievert3", "usievert2", "usievert3", "Tsievert2", "Tsievert3", "ysievert2", "ysievert3", "Esievert2", "Esievert3", "zsievert2", "zsievert3", "Msievert2", "Msievert3", "ksievert2", "ksievert3", "Ysievert2", "Ysievert3", "akatal2", "akatal3", "ckatal2", "ckatal3", "Zkatal2", "Zkatal3", "Pkatal2", "Pkatal3", "dkatal2", "dkatal3", "Gkatal2", "Gkatal3", "fkatal2", "fkatal3", "hkatal2", "hkatal3", "dakatal2", "dakatal3", "mkatal2", "mkatal3", "nkatal2", "nkatal3", "pkatal2", "pkatal3", "ukatal2", "ukatal3", "Tkatal2", "Tkatal3", "ykatal2", "ykatal3", "Ekatal2", "Ekatal3", "zkatal2", "zkatal3", "Mkatal2", "Mkatal3", "kkatal2", "kkatal3", "Ykatal2", "Ykatal3", "liter", "aliter", "liter", "cliter", "Zliter", "Pliter", "dliter", "Gliter", "fliter", "hliter", "daliter", "mliter", "nliter", "pliter", "uliter", "Tliter", "yliter", "Eliter", "zliter", "Mliter", "kliter", "Yliter", "litre", "alitre", "litre", "clitre", "Zlitre", "Plitre", "dlitre", "Glitre", "flitre", "hlitre", "dalitre", "mlitre", "nlitre", "plitre", "ulitre", "Tlitre", "ylitre", "Elitre", "zlitre", "Mlitre", "klitre", "Ylitre", "celsius" # Dummy object raising an error ] Unit.automatically_register_units = False #### FUNDAMENTAL UNITS metre = Unit.create(get_or_create_dimension(m=1), "metre", "m") meter = Unit.create(get_or_create_dimension(m=1), "meter", "m") # Liter has a scale of 10^-3, since 1 l = 1 dm^3 = 10^-3 m^3 liter = Unit.create(dim=(meter**3).dim, name="liter", dispname="l", scale=-3) litre = Unit.create(dim=(meter**3).dim, name="litre", dispname="l", scale=-3) kilogram = Unit.create(get_or_create_dimension(kg=1), "kilogram", "kg") kilogramme = Unit.create(get_or_create_dimension(kg=1), "kilogramme", "kg") gram = Unit.create(dim=kilogram.dim, name="gram", dispname="g", scale=-3) gramme = Unit.create(dim=kilogram.dim, name="gramme", dispname="g", scale=-3) second = Unit.create(get_or_create_dimension(s=1), "second", "s") amp = Unit.create(get_or_create_dimension(A=1), "amp", "A") ampere = Unit.create(get_or_create_dimension(A=1), "ampere", "A") kelvin = Unit.create(get_or_create_dimension(K=1), "kelvin", "K") mole = Unit.create(get_or_create_dimension(mol=1), "mole", "mol") mol = Unit.create(get_or_create_dimension(mol=1), "mol", "mol") # Molar has a scale of 10^3, since 1 M = 1 mol/l = 1000 mol/m^3 molar = Unit.create((mole/liter).dim, name="molar", dispname="M", scale=3) candle = Unit.create(get_or_create_dimension(candle=1), "candle", "cd") fundamental_units = [metre, meter, gram, second, amp, kelvin, mole, candle] radian = Unit.create(get_or_create_dimension(), "radian", "rad") steradian = Unit.create(get_or_create_dimension(), "steradian", "sr") hertz = Unit.create(get_or_create_dimension(s= -1), "hertz", "Hz") newton = Unit.create(get_or_create_dimension(m=1, kg=1, s=-2), "newton", "N") pascal = Unit.create(get_or_create_dimension(m= -1, kg=1, s=-2), "pascal", "Pa") joule = Unit.create(get_or_create_dimension(m=2, kg=1, s=-2), "joule", "J") watt = Unit.create(get_or_create_dimension(m=2, kg=1, s=-3), "watt", "W") coulomb = Unit.create(get_or_create_dimension(s=1, A=1), "coulomb", "C") volt = Unit.create(get_or_create_dimension(m=2, kg=1, s=-3, A=-1), "volt", "V") farad = Unit.create(get_or_create_dimension(m= -2, kg=-1, s=4, A=2), "farad", "F") ohm = Unit.create(get_or_create_dimension(m=2, kg=1, s= -3, A=-2), "ohm", "ohm") siemens = Unit.create(get_or_create_dimension(m= -2, kg=-1, s=3, A=2), "siemens", "S") weber = Unit.create(get_or_create_dimension(m=2, kg=1, s=-2, A=-1), "weber", "Wb") tesla = Unit.create(get_or_create_dimension(kg=1, s=-2, A=-1), "tesla", "T") henry = Unit.create(get_or_create_dimension(m=2, kg=1, s=-2, A=-2), "henry", "H") lumen = Unit.create(get_or_create_dimension(cd=1), "lumen", "lm") lux = Unit.create(get_or_create_dimension(m=-2, cd=1), "lux", "lx") becquerel = Unit.create(get_or_create_dimension(s=-1), "becquerel", "Bq") gray = Unit.create(get_or_create_dimension(m=2, s=-2), "gray", "Gy") sievert = Unit.create(get_or_create_dimension(m=2, s=-2), "sievert", "Sv") katal = Unit.create(get_or_create_dimension(s=-1, mol=1), "katal", "kat") ######### SCALED BASE UNITS ########### ametre = Unit.create_scaled_unit(metre, "a") cmetre = Unit.create_scaled_unit(metre, "c") Zmetre = Unit.create_scaled_unit(metre, "Z") Pmetre = Unit.create_scaled_unit(metre, "P") dmetre = Unit.create_scaled_unit(metre, "d") Gmetre = Unit.create_scaled_unit(metre, "G") fmetre = Unit.create_scaled_unit(metre, "f") hmetre = Unit.create_scaled_unit(metre, "h") dametre = Unit.create_scaled_unit(metre, "da") mmetre = Unit.create_scaled_unit(metre, "m") nmetre = Unit.create_scaled_unit(metre, "n") pmetre = Unit.create_scaled_unit(metre, "p") umetre = Unit.create_scaled_unit(metre, "u") Tmetre = Unit.create_scaled_unit(metre, "T") ymetre = Unit.create_scaled_unit(metre, "y") Emetre = Unit.create_scaled_unit(metre, "E") zmetre = Unit.create_scaled_unit(metre, "z") Mmetre = Unit.create_scaled_unit(metre, "M") kmetre = Unit.create_scaled_unit(metre, "k") Ymetre = Unit.create_scaled_unit(metre, "Y") ameter = Unit.create_scaled_unit(meter, "a") cmeter = Unit.create_scaled_unit(meter, "c") Zmeter = Unit.create_scaled_unit(meter, "Z") Pmeter = Unit.create_scaled_unit(meter, "P") dmeter = Unit.create_scaled_unit(meter, "d") Gmeter = Unit.create_scaled_unit(meter, "G") fmeter = Unit.create_scaled_unit(meter, "f") hmeter = Unit.create_scaled_unit(meter, "h") dameter = Unit.create_scaled_unit(meter, "da") mmeter = Unit.create_scaled_unit(meter, "m") nmeter = Unit.create_scaled_unit(meter, "n") pmeter = Unit.create_scaled_unit(meter, "p") umeter = Unit.create_scaled_unit(meter, "u") Tmeter = Unit.create_scaled_unit(meter, "T") ymeter = Unit.create_scaled_unit(meter, "y") Emeter = Unit.create_scaled_unit(meter, "E") zmeter = Unit.create_scaled_unit(meter, "z") Mmeter = Unit.create_scaled_unit(meter, "M") kmeter = Unit.create_scaled_unit(meter, "k") Ymeter = Unit.create_scaled_unit(meter, "Y") asecond = Unit.create_scaled_unit(second, "a") csecond = Unit.create_scaled_unit(second, "c") Zsecond = Unit.create_scaled_unit(second, "Z") Psecond = Unit.create_scaled_unit(second, "P") dsecond = Unit.create_scaled_unit(second, "d") Gsecond = Unit.create_scaled_unit(second, "G") fsecond = Unit.create_scaled_unit(second, "f") hsecond = Unit.create_scaled_unit(second, "h") dasecond = Unit.create_scaled_unit(second, "da") msecond = Unit.create_scaled_unit(second, "m") nsecond = Unit.create_scaled_unit(second, "n") psecond = Unit.create_scaled_unit(second, "p") usecond = Unit.create_scaled_unit(second, "u") Tsecond = Unit.create_scaled_unit(second, "T") ysecond = Unit.create_scaled_unit(second, "y") Esecond = Unit.create_scaled_unit(second, "E") zsecond = Unit.create_scaled_unit(second, "z") Msecond = Unit.create_scaled_unit(second, "M") ksecond = Unit.create_scaled_unit(second, "k") Ysecond = Unit.create_scaled_unit(second, "Y") aamp = Unit.create_scaled_unit(amp, "a") camp = Unit.create_scaled_unit(amp, "c") Zamp = Unit.create_scaled_unit(amp, "Z") Pamp = Unit.create_scaled_unit(amp, "P") damp = Unit.create_scaled_unit(amp, "d") Gamp = Unit.create_scaled_unit(amp, "G") famp = Unit.create_scaled_unit(amp, "f") hamp = Unit.create_scaled_unit(amp, "h") daamp = Unit.create_scaled_unit(amp, "da") mamp = Unit.create_scaled_unit(amp, "m") namp = Unit.create_scaled_unit(amp, "n") pamp = Unit.create_scaled_unit(amp, "p") uamp = Unit.create_scaled_unit(amp, "u") Tamp = Unit.create_scaled_unit(amp, "T") yamp = Unit.create_scaled_unit(amp, "y") Eamp = Unit.create_scaled_unit(amp, "E") zamp = Unit.create_scaled_unit(amp, "z") Mamp = Unit.create_scaled_unit(amp, "M") kamp = Unit.create_scaled_unit(amp, "k") Yamp = Unit.create_scaled_unit(amp, "Y") aampere = Unit.create_scaled_unit(ampere, "a") campere = Unit.create_scaled_unit(ampere, "c") Zampere = Unit.create_scaled_unit(ampere, "Z") Pampere = Unit.create_scaled_unit(ampere, "P") dampere = Unit.create_scaled_unit(ampere, "d") Gampere = Unit.create_scaled_unit(ampere, "G") fampere = Unit.create_scaled_unit(ampere, "f") hampere = Unit.create_scaled_unit(ampere, "h") daampere = Unit.create_scaled_unit(ampere, "da") mampere = Unit.create_scaled_unit(ampere, "m") nampere = Unit.create_scaled_unit(ampere, "n") pampere = Unit.create_scaled_unit(ampere, "p") uampere = Unit.create_scaled_unit(ampere, "u") Tampere = Unit.create_scaled_unit(ampere, "T") yampere = Unit.create_scaled_unit(ampere, "y") Eampere = Unit.create_scaled_unit(ampere, "E") zampere = Unit.create_scaled_unit(ampere, "z") Mampere = Unit.create_scaled_unit(ampere, "M") kampere = Unit.create_scaled_unit(ampere, "k") Yampere = Unit.create_scaled_unit(ampere, "Y") amole = Unit.create_scaled_unit(mole, "a") cmole = Unit.create_scaled_unit(mole, "c") Zmole = Unit.create_scaled_unit(mole, "Z") Pmole = Unit.create_scaled_unit(mole, "P") dmole = Unit.create_scaled_unit(mole, "d") Gmole = Unit.create_scaled_unit(mole, "G") fmole = Unit.create_scaled_unit(mole, "f") hmole = Unit.create_scaled_unit(mole, "h") damole = Unit.create_scaled_unit(mole, "da") mmole = Unit.create_scaled_unit(mole, "m") nmole = Unit.create_scaled_unit(mole, "n") pmole = Unit.create_scaled_unit(mole, "p") umole = Unit.create_scaled_unit(mole, "u") Tmole = Unit.create_scaled_unit(mole, "T") ymole = Unit.create_scaled_unit(mole, "y") Emole = Unit.create_scaled_unit(mole, "E") zmole = Unit.create_scaled_unit(mole, "z") Mmole = Unit.create_scaled_unit(mole, "M") kmole = Unit.create_scaled_unit(mole, "k") Ymole = Unit.create_scaled_unit(mole, "Y") amol = Unit.create_scaled_unit(mol, "a") cmol = Unit.create_scaled_unit(mol, "c") Zmol = Unit.create_scaled_unit(mol, "Z") Pmol = Unit.create_scaled_unit(mol, "P") dmol = Unit.create_scaled_unit(mol, "d") Gmol = Unit.create_scaled_unit(mol, "G") fmol = Unit.create_scaled_unit(mol, "f") hmol = Unit.create_scaled_unit(mol, "h") damol = Unit.create_scaled_unit(mol, "da") mmol = Unit.create_scaled_unit(mol, "m") nmol = Unit.create_scaled_unit(mol, "n") pmol = Unit.create_scaled_unit(mol, "p") umol = Unit.create_scaled_unit(mol, "u") Tmol = Unit.create_scaled_unit(mol, "T") ymol = Unit.create_scaled_unit(mol, "y") Emol = Unit.create_scaled_unit(mol, "E") zmol = Unit.create_scaled_unit(mol, "z") Mmol = Unit.create_scaled_unit(mol, "M") kmol = Unit.create_scaled_unit(mol, "k") Ymol = Unit.create_scaled_unit(mol, "Y") acandle = Unit.create_scaled_unit(candle, "a") ccandle = Unit.create_scaled_unit(candle, "c") Zcandle = Unit.create_scaled_unit(candle, "Z") Pcandle = Unit.create_scaled_unit(candle, "P") dcandle = Unit.create_scaled_unit(candle, "d") Gcandle = Unit.create_scaled_unit(candle, "G") fcandle = Unit.create_scaled_unit(candle, "f") hcandle = Unit.create_scaled_unit(candle, "h") dacandle = Unit.create_scaled_unit(candle, "da") mcandle = Unit.create_scaled_unit(candle, "m") ncandle = Unit.create_scaled_unit(candle, "n") pcandle = Unit.create_scaled_unit(candle, "p") ucandle = Unit.create_scaled_unit(candle, "u") Tcandle = Unit.create_scaled_unit(candle, "T") ycandle = Unit.create_scaled_unit(candle, "y") Ecandle = Unit.create_scaled_unit(candle, "E") zcandle = Unit.create_scaled_unit(candle, "z") Mcandle = Unit.create_scaled_unit(candle, "M") kcandle = Unit.create_scaled_unit(candle, "k") Ycandle = Unit.create_scaled_unit(candle, "Y") agram = Unit.create_scaled_unit(gram, "a") cgram = Unit.create_scaled_unit(gram, "c") Zgram = Unit.create_scaled_unit(gram, "Z") Pgram = Unit.create_scaled_unit(gram, "P") dgram = Unit.create_scaled_unit(gram, "d") Ggram = Unit.create_scaled_unit(gram, "G") fgram = Unit.create_scaled_unit(gram, "f") hgram = Unit.create_scaled_unit(gram, "h") dagram = Unit.create_scaled_unit(gram, "da") mgram = Unit.create_scaled_unit(gram, "m") ngram = Unit.create_scaled_unit(gram, "n") pgram = Unit.create_scaled_unit(gram, "p") ugram = Unit.create_scaled_unit(gram, "u") Tgram = Unit.create_scaled_unit(gram, "T") ygram = Unit.create_scaled_unit(gram, "y") Egram = Unit.create_scaled_unit(gram, "E") zgram = Unit.create_scaled_unit(gram, "z") Mgram = Unit.create_scaled_unit(gram, "M") kgram = Unit.create_scaled_unit(gram, "k") Ygram = Unit.create_scaled_unit(gram, "Y") agramme = Unit.create_scaled_unit(gramme, "a") cgramme = Unit.create_scaled_unit(gramme, "c") Zgramme = Unit.create_scaled_unit(gramme, "Z") Pgramme = Unit.create_scaled_unit(gramme, "P") dgramme = Unit.create_scaled_unit(gramme, "d") Ggramme = Unit.create_scaled_unit(gramme, "G") fgramme = Unit.create_scaled_unit(gramme, "f") hgramme = Unit.create_scaled_unit(gramme, "h") dagramme = Unit.create_scaled_unit(gramme, "da") mgramme = Unit.create_scaled_unit(gramme, "m") ngramme = Unit.create_scaled_unit(gramme, "n") pgramme = Unit.create_scaled_unit(gramme, "p") ugramme = Unit.create_scaled_unit(gramme, "u") Tgramme = Unit.create_scaled_unit(gramme, "T") ygramme = Unit.create_scaled_unit(gramme, "y") Egramme = Unit.create_scaled_unit(gramme, "E") zgramme = Unit.create_scaled_unit(gramme, "z") Mgramme = Unit.create_scaled_unit(gramme, "M") kgramme = Unit.create_scaled_unit(gramme, "k") Ygramme = Unit.create_scaled_unit(gramme, "Y") amolar = Unit.create_scaled_unit(molar, "a") cmolar = Unit.create_scaled_unit(molar, "c") Zmolar = Unit.create_scaled_unit(molar, "Z") Pmolar = Unit.create_scaled_unit(molar, "P") dmolar = Unit.create_scaled_unit(molar, "d") Gmolar = Unit.create_scaled_unit(molar, "G") fmolar = Unit.create_scaled_unit(molar, "f") hmolar = Unit.create_scaled_unit(molar, "h") damolar = Unit.create_scaled_unit(molar, "da") mmolar = Unit.create_scaled_unit(molar, "m") nmolar = Unit.create_scaled_unit(molar, "n") pmolar = Unit.create_scaled_unit(molar, "p") umolar = Unit.create_scaled_unit(molar, "u") Tmolar = Unit.create_scaled_unit(molar, "T") ymolar = Unit.create_scaled_unit(molar, "y") Emolar = Unit.create_scaled_unit(molar, "E") zmolar = Unit.create_scaled_unit(molar, "z") Mmolar = Unit.create_scaled_unit(molar, "M") kmolar = Unit.create_scaled_unit(molar, "k") Ymolar = Unit.create_scaled_unit(molar, "Y") aradian = Unit.create_scaled_unit(radian, "a") cradian = Unit.create_scaled_unit(radian, "c") Zradian = Unit.create_scaled_unit(radian, "Z") Pradian = Unit.create_scaled_unit(radian, "P") dradian = Unit.create_scaled_unit(radian, "d") Gradian = Unit.create_scaled_unit(radian, "G") fradian = Unit.create_scaled_unit(radian, "f") hradian = Unit.create_scaled_unit(radian, "h") daradian = Unit.create_scaled_unit(radian, "da") mradian = Unit.create_scaled_unit(radian, "m") nradian = Unit.create_scaled_unit(radian, "n") pradian = Unit.create_scaled_unit(radian, "p") uradian = Unit.create_scaled_unit(radian, "u") Tradian = Unit.create_scaled_unit(radian, "T") yradian = Unit.create_scaled_unit(radian, "y") Eradian = Unit.create_scaled_unit(radian, "E") zradian = Unit.create_scaled_unit(radian, "z") Mradian = Unit.create_scaled_unit(radian, "M") kradian = Unit.create_scaled_unit(radian, "k") Yradian = Unit.create_scaled_unit(radian, "Y") asteradian = Unit.create_scaled_unit(steradian, "a") csteradian = Unit.create_scaled_unit(steradian, "c") Zsteradian = Unit.create_scaled_unit(steradian, "Z") Psteradian = Unit.create_scaled_unit(steradian, "P") dsteradian = Unit.create_scaled_unit(steradian, "d") Gsteradian = Unit.create_scaled_unit(steradian, "G") fsteradian = Unit.create_scaled_unit(steradian, "f") hsteradian = Unit.create_scaled_unit(steradian, "h") dasteradian = Unit.create_scaled_unit(steradian, "da") msteradian = Unit.create_scaled_unit(steradian, "m") nsteradian = Unit.create_scaled_unit(steradian, "n") psteradian = Unit.create_scaled_unit(steradian, "p") usteradian = Unit.create_scaled_unit(steradian, "u") Tsteradian = Unit.create_scaled_unit(steradian, "T") ysteradian = Unit.create_scaled_unit(steradian, "y") Esteradian = Unit.create_scaled_unit(steradian, "E") zsteradian = Unit.create_scaled_unit(steradian, "z") Msteradian = Unit.create_scaled_unit(steradian, "M") ksteradian = Unit.create_scaled_unit(steradian, "k") Ysteradian = Unit.create_scaled_unit(steradian, "Y") ahertz = Unit.create_scaled_unit(hertz, "a") chertz = Unit.create_scaled_unit(hertz, "c") Zhertz = Unit.create_scaled_unit(hertz, "Z") Phertz = Unit.create_scaled_unit(hertz, "P") dhertz = Unit.create_scaled_unit(hertz, "d") Ghertz = Unit.create_scaled_unit(hertz, "G") fhertz = Unit.create_scaled_unit(hertz, "f") hhertz = Unit.create_scaled_unit(hertz, "h") dahertz = Unit.create_scaled_unit(hertz, "da") mhertz = Unit.create_scaled_unit(hertz, "m") nhertz = Unit.create_scaled_unit(hertz, "n") phertz = Unit.create_scaled_unit(hertz, "p") uhertz = Unit.create_scaled_unit(hertz, "u") Thertz = Unit.create_scaled_unit(hertz, "T") yhertz = Unit.create_scaled_unit(hertz, "y") Ehertz = Unit.create_scaled_unit(hertz, "E") zhertz = Unit.create_scaled_unit(hertz, "z") Mhertz = Unit.create_scaled_unit(hertz, "M") khertz = Unit.create_scaled_unit(hertz, "k") Yhertz = Unit.create_scaled_unit(hertz, "Y") anewton = Unit.create_scaled_unit(newton, "a") cnewton = Unit.create_scaled_unit(newton, "c") Znewton = Unit.create_scaled_unit(newton, "Z") Pnewton = Unit.create_scaled_unit(newton, "P") dnewton = Unit.create_scaled_unit(newton, "d") Gnewton = Unit.create_scaled_unit(newton, "G") fnewton = Unit.create_scaled_unit(newton, "f") hnewton = Unit.create_scaled_unit(newton, "h") danewton = Unit.create_scaled_unit(newton, "da") mnewton = Unit.create_scaled_unit(newton, "m") nnewton = Unit.create_scaled_unit(newton, "n") pnewton = Unit.create_scaled_unit(newton, "p") unewton = Unit.create_scaled_unit(newton, "u") Tnewton = Unit.create_scaled_unit(newton, "T") ynewton = Unit.create_scaled_unit(newton, "y") Enewton = Unit.create_scaled_unit(newton, "E") znewton = Unit.create_scaled_unit(newton, "z") Mnewton = Unit.create_scaled_unit(newton, "M") knewton = Unit.create_scaled_unit(newton, "k") Ynewton = Unit.create_scaled_unit(newton, "Y") apascal = Unit.create_scaled_unit(pascal, "a") cpascal = Unit.create_scaled_unit(pascal, "c") Zpascal = Unit.create_scaled_unit(pascal, "Z") Ppascal = Unit.create_scaled_unit(pascal, "P") dpascal = Unit.create_scaled_unit(pascal, "d") Gpascal = Unit.create_scaled_unit(pascal, "G") fpascal = Unit.create_scaled_unit(pascal, "f") hpascal = Unit.create_scaled_unit(pascal, "h") dapascal = Unit.create_scaled_unit(pascal, "da") mpascal = Unit.create_scaled_unit(pascal, "m") npascal = Unit.create_scaled_unit(pascal, "n") ppascal = Unit.create_scaled_unit(pascal, "p") upascal = Unit.create_scaled_unit(pascal, "u") Tpascal = Unit.create_scaled_unit(pascal, "T") ypascal = Unit.create_scaled_unit(pascal, "y") Epascal = Unit.create_scaled_unit(pascal, "E") zpascal = Unit.create_scaled_unit(pascal, "z") Mpascal = Unit.create_scaled_unit(pascal, "M") kpascal = Unit.create_scaled_unit(pascal, "k") Ypascal = Unit.create_scaled_unit(pascal, "Y") ajoule = Unit.create_scaled_unit(joule, "a") cjoule = Unit.create_scaled_unit(joule, "c") Zjoule = Unit.create_scaled_unit(joule, "Z") Pjoule = Unit.create_scaled_unit(joule, "P") djoule = Unit.create_scaled_unit(joule, "d") Gjoule = Unit.create_scaled_unit(joule, "G") fjoule = Unit.create_scaled_unit(joule, "f") hjoule = Unit.create_scaled_unit(joule, "h") dajoule = Unit.create_scaled_unit(joule, "da") mjoule = Unit.create_scaled_unit(joule, "m") njoule = Unit.create_scaled_unit(joule, "n") pjoule = Unit.create_scaled_unit(joule, "p") ujoule = Unit.create_scaled_unit(joule, "u") Tjoule = Unit.create_scaled_unit(joule, "T") yjoule = Unit.create_scaled_unit(joule, "y") Ejoule = Unit.create_scaled_unit(joule, "E") zjoule = Unit.create_scaled_unit(joule, "z") Mjoule = Unit.create_scaled_unit(joule, "M") kjoule = Unit.create_scaled_unit(joule, "k") Yjoule = Unit.create_scaled_unit(joule, "Y") awatt = Unit.create_scaled_unit(watt, "a") cwatt = Unit.create_scaled_unit(watt, "c") Zwatt = Unit.create_scaled_unit(watt, "Z") Pwatt = Unit.create_scaled_unit(watt, "P") dwatt = Unit.create_scaled_unit(watt, "d") Gwatt = Unit.create_scaled_unit(watt, "G") fwatt = Unit.create_scaled_unit(watt, "f") hwatt = Unit.create_scaled_unit(watt, "h") dawatt = Unit.create_scaled_unit(watt, "da") mwatt = Unit.create_scaled_unit(watt, "m") nwatt = Unit.create_scaled_unit(watt, "n") pwatt = Unit.create_scaled_unit(watt, "p") uwatt = Unit.create_scaled_unit(watt, "u") Twatt = Unit.create_scaled_unit(watt, "T") ywatt = Unit.create_scaled_unit(watt, "y") Ewatt = Unit.create_scaled_unit(watt, "E") zwatt = Unit.create_scaled_unit(watt, "z") Mwatt = Unit.create_scaled_unit(watt, "M") kwatt = Unit.create_scaled_unit(watt, "k") Ywatt = Unit.create_scaled_unit(watt, "Y") acoulomb = Unit.create_scaled_unit(coulomb, "a") ccoulomb = Unit.create_scaled_unit(coulomb, "c") Zcoulomb = Unit.create_scaled_unit(coulomb, "Z") Pcoulomb = Unit.create_scaled_unit(coulomb, "P") dcoulomb = Unit.create_scaled_unit(coulomb, "d") Gcoulomb = Unit.create_scaled_unit(coulomb, "G") fcoulomb = Unit.create_scaled_unit(coulomb, "f") hcoulomb = Unit.create_scaled_unit(coulomb, "h") dacoulomb = Unit.create_scaled_unit(coulomb, "da") mcoulomb = Unit.create_scaled_unit(coulomb, "m") ncoulomb = Unit.create_scaled_unit(coulomb, "n") pcoulomb = Unit.create_scaled_unit(coulomb, "p") ucoulomb = Unit.create_scaled_unit(coulomb, "u") Tcoulomb = Unit.create_scaled_unit(coulomb, "T") ycoulomb = Unit.create_scaled_unit(coulomb, "y") Ecoulomb = Unit.create_scaled_unit(coulomb, "E") zcoulomb = Unit.create_scaled_unit(coulomb, "z") Mcoulomb = Unit.create_scaled_unit(coulomb, "M") kcoulomb = Unit.create_scaled_unit(coulomb, "k") Ycoulomb = Unit.create_scaled_unit(coulomb, "Y") avolt = Unit.create_scaled_unit(volt, "a") cvolt = Unit.create_scaled_unit(volt, "c") Zvolt = Unit.create_scaled_unit(volt, "Z") Pvolt = Unit.create_scaled_unit(volt, "P") dvolt = Unit.create_scaled_unit(volt, "d") Gvolt = Unit.create_scaled_unit(volt, "G") fvolt = Unit.create_scaled_unit(volt, "f") hvolt = Unit.create_scaled_unit(volt, "h") davolt = Unit.create_scaled_unit(volt, "da") mvolt = Unit.create_scaled_unit(volt, "m") nvolt = Unit.create_scaled_unit(volt, "n") pvolt = Unit.create_scaled_unit(volt, "p") uvolt = Unit.create_scaled_unit(volt, "u") Tvolt = Unit.create_scaled_unit(volt, "T") yvolt = Unit.create_scaled_unit(volt, "y") Evolt = Unit.create_scaled_unit(volt, "E") zvolt = Unit.create_scaled_unit(volt, "z") Mvolt = Unit.create_scaled_unit(volt, "M") kvolt = Unit.create_scaled_unit(volt, "k") Yvolt = Unit.create_scaled_unit(volt, "Y") afarad = Unit.create_scaled_unit(farad, "a") cfarad = Unit.create_scaled_unit(farad, "c") Zfarad = Unit.create_scaled_unit(farad, "Z") Pfarad = Unit.create_scaled_unit(farad, "P") dfarad = Unit.create_scaled_unit(farad, "d") Gfarad = Unit.create_scaled_unit(farad, "G") ffarad = Unit.create_scaled_unit(farad, "f") hfarad = Unit.create_scaled_unit(farad, "h") dafarad = Unit.create_scaled_unit(farad, "da") mfarad = Unit.create_scaled_unit(farad, "m") nfarad = Unit.create_scaled_unit(farad, "n") pfarad = Unit.create_scaled_unit(farad, "p") ufarad = Unit.create_scaled_unit(farad, "u") Tfarad = Unit.create_scaled_unit(farad, "T") yfarad = Unit.create_scaled_unit(farad, "y") Efarad = Unit.create_scaled_unit(farad, "E") zfarad = Unit.create_scaled_unit(farad, "z") Mfarad = Unit.create_scaled_unit(farad, "M") kfarad = Unit.create_scaled_unit(farad, "k") Yfarad = Unit.create_scaled_unit(farad, "Y") aohm = Unit.create_scaled_unit(ohm, "a") cohm = Unit.create_scaled_unit(ohm, "c") Zohm = Unit.create_scaled_unit(ohm, "Z") Pohm = Unit.create_scaled_unit(ohm, "P") dohm = Unit.create_scaled_unit(ohm, "d") Gohm = Unit.create_scaled_unit(ohm, "G") fohm = Unit.create_scaled_unit(ohm, "f") hohm = Unit.create_scaled_unit(ohm, "h") daohm = Unit.create_scaled_unit(ohm, "da") mohm = Unit.create_scaled_unit(ohm, "m") nohm = Unit.create_scaled_unit(ohm, "n") pohm = Unit.create_scaled_unit(ohm, "p") uohm = Unit.create_scaled_unit(ohm, "u") Tohm = Unit.create_scaled_unit(ohm, "T") yohm = Unit.create_scaled_unit(ohm, "y") Eohm = Unit.create_scaled_unit(ohm, "E") zohm = Unit.create_scaled_unit(ohm, "z") Mohm = Unit.create_scaled_unit(ohm, "M") kohm = Unit.create_scaled_unit(ohm, "k") Yohm = Unit.create_scaled_unit(ohm, "Y") asiemens = Unit.create_scaled_unit(siemens, "a") csiemens = Unit.create_scaled_unit(siemens, "c") Zsiemens = Unit.create_scaled_unit(siemens, "Z") Psiemens = Unit.create_scaled_unit(siemens, "P") dsiemens = Unit.create_scaled_unit(siemens, "d") Gsiemens = Unit.create_scaled_unit(siemens, "G") fsiemens = Unit.create_scaled_unit(siemens, "f") hsiemens = Unit.create_scaled_unit(siemens, "h") dasiemens = Unit.create_scaled_unit(siemens, "da") msiemens = Unit.create_scaled_unit(siemens, "m") nsiemens = Unit.create_scaled_unit(siemens, "n") psiemens = Unit.create_scaled_unit(siemens, "p") usiemens = Unit.create_scaled_unit(siemens, "u") Tsiemens = Unit.create_scaled_unit(siemens, "T") ysiemens = Unit.create_scaled_unit(siemens, "y") Esiemens = Unit.create_scaled_unit(siemens, "E") zsiemens = Unit.create_scaled_unit(siemens, "z") Msiemens = Unit.create_scaled_unit(siemens, "M") ksiemens = Unit.create_scaled_unit(siemens, "k") Ysiemens = Unit.create_scaled_unit(siemens, "Y") aweber = Unit.create_scaled_unit(weber, "a") cweber = Unit.create_scaled_unit(weber, "c") Zweber = Unit.create_scaled_unit(weber, "Z") Pweber = Unit.create_scaled_unit(weber, "P") dweber = Unit.create_scaled_unit(weber, "d") Gweber = Unit.create_scaled_unit(weber, "G") fweber = Unit.create_scaled_unit(weber, "f") hweber = Unit.create_scaled_unit(weber, "h") daweber = Unit.create_scaled_unit(weber, "da") mweber = Unit.create_scaled_unit(weber, "m") nweber = Unit.create_scaled_unit(weber, "n") pweber = Unit.create_scaled_unit(weber, "p") uweber = Unit.create_scaled_unit(weber, "u") Tweber = Unit.create_scaled_unit(weber, "T") yweber = Unit.create_scaled_unit(weber, "y") Eweber = Unit.create_scaled_unit(weber, "E") zweber = Unit.create_scaled_unit(weber, "z") Mweber = Unit.create_scaled_unit(weber, "M") kweber = Unit.create_scaled_unit(weber, "k") Yweber = Unit.create_scaled_unit(weber, "Y") atesla = Unit.create_scaled_unit(tesla, "a") ctesla = Unit.create_scaled_unit(tesla, "c") Ztesla = Unit.create_scaled_unit(tesla, "Z") Ptesla = Unit.create_scaled_unit(tesla, "P") dtesla = Unit.create_scaled_unit(tesla, "d") Gtesla = Unit.create_scaled_unit(tesla, "G") ftesla = Unit.create_scaled_unit(tesla, "f") htesla = Unit.create_scaled_unit(tesla, "h") datesla = Unit.create_scaled_unit(tesla, "da") mtesla = Unit.create_scaled_unit(tesla, "m") ntesla = Unit.create_scaled_unit(tesla, "n") ptesla = Unit.create_scaled_unit(tesla, "p") utesla = Unit.create_scaled_unit(tesla, "u") Ttesla = Unit.create_scaled_unit(tesla, "T") ytesla = Unit.create_scaled_unit(tesla, "y") Etesla = Unit.create_scaled_unit(tesla, "E") ztesla = Unit.create_scaled_unit(tesla, "z") Mtesla = Unit.create_scaled_unit(tesla, "M") ktesla = Unit.create_scaled_unit(tesla, "k") Ytesla = Unit.create_scaled_unit(tesla, "Y") ahenry = Unit.create_scaled_unit(henry, "a") chenry = Unit.create_scaled_unit(henry, "c") Zhenry = Unit.create_scaled_unit(henry, "Z") Phenry = Unit.create_scaled_unit(henry, "P") dhenry = Unit.create_scaled_unit(henry, "d") Ghenry = Unit.create_scaled_unit(henry, "G") fhenry = Unit.create_scaled_unit(henry, "f") hhenry = Unit.create_scaled_unit(henry, "h") dahenry = Unit.create_scaled_unit(henry, "da") mhenry = Unit.create_scaled_unit(henry, "m") nhenry = Unit.create_scaled_unit(henry, "n") phenry = Unit.create_scaled_unit(henry, "p") uhenry = Unit.create_scaled_unit(henry, "u") Thenry = Unit.create_scaled_unit(henry, "T") yhenry = Unit.create_scaled_unit(henry, "y") Ehenry = Unit.create_scaled_unit(henry, "E") zhenry = Unit.create_scaled_unit(henry, "z") Mhenry = Unit.create_scaled_unit(henry, "M") khenry = Unit.create_scaled_unit(henry, "k") Yhenry = Unit.create_scaled_unit(henry, "Y") alumen = Unit.create_scaled_unit(lumen, "a") clumen = Unit.create_scaled_unit(lumen, "c") Zlumen = Unit.create_scaled_unit(lumen, "Z") Plumen = Unit.create_scaled_unit(lumen, "P") dlumen = Unit.create_scaled_unit(lumen, "d") Glumen = Unit.create_scaled_unit(lumen, "G") flumen = Unit.create_scaled_unit(lumen, "f") hlumen = Unit.create_scaled_unit(lumen, "h") dalumen = Unit.create_scaled_unit(lumen, "da") mlumen = Unit.create_scaled_unit(lumen, "m") nlumen = Unit.create_scaled_unit(lumen, "n") plumen = Unit.create_scaled_unit(lumen, "p") ulumen = Unit.create_scaled_unit(lumen, "u") Tlumen = Unit.create_scaled_unit(lumen, "T") ylumen = Unit.create_scaled_unit(lumen, "y") Elumen = Unit.create_scaled_unit(lumen, "E") zlumen = Unit.create_scaled_unit(lumen, "z") Mlumen = Unit.create_scaled_unit(lumen, "M") klumen = Unit.create_scaled_unit(lumen, "k") Ylumen = Unit.create_scaled_unit(lumen, "Y") alux = Unit.create_scaled_unit(lux, "a") clux = Unit.create_scaled_unit(lux, "c") Zlux = Unit.create_scaled_unit(lux, "Z") Plux = Unit.create_scaled_unit(lux, "P") dlux = Unit.create_scaled_unit(lux, "d") Glux = Unit.create_scaled_unit(lux, "G") flux = Unit.create_scaled_unit(lux, "f") hlux = Unit.create_scaled_unit(lux, "h") dalux = Unit.create_scaled_unit(lux, "da") mlux = Unit.create_scaled_unit(lux, "m") nlux = Unit.create_scaled_unit(lux, "n") plux = Unit.create_scaled_unit(lux, "p") ulux = Unit.create_scaled_unit(lux, "u") Tlux = Unit.create_scaled_unit(lux, "T") ylux = Unit.create_scaled_unit(lux, "y") Elux = Unit.create_scaled_unit(lux, "E") zlux = Unit.create_scaled_unit(lux, "z") Mlux = Unit.create_scaled_unit(lux, "M") klux = Unit.create_scaled_unit(lux, "k") Ylux = Unit.create_scaled_unit(lux, "Y") abecquerel = Unit.create_scaled_unit(becquerel, "a") cbecquerel = Unit.create_scaled_unit(becquerel, "c") Zbecquerel = Unit.create_scaled_unit(becquerel, "Z") Pbecquerel = Unit.create_scaled_unit(becquerel, "P") dbecquerel = Unit.create_scaled_unit(becquerel, "d") Gbecquerel = Unit.create_scaled_unit(becquerel, "G") fbecquerel = Unit.create_scaled_unit(becquerel, "f") hbecquerel = Unit.create_scaled_unit(becquerel, "h") dabecquerel = Unit.create_scaled_unit(becquerel, "da") mbecquerel = Unit.create_scaled_unit(becquerel, "m") nbecquerel = Unit.create_scaled_unit(becquerel, "n") pbecquerel = Unit.create_scaled_unit(becquerel, "p") ubecquerel = Unit.create_scaled_unit(becquerel, "u") Tbecquerel = Unit.create_scaled_unit(becquerel, "T") ybecquerel = Unit.create_scaled_unit(becquerel, "y") Ebecquerel = Unit.create_scaled_unit(becquerel, "E") zbecquerel = Unit.create_scaled_unit(becquerel, "z") Mbecquerel = Unit.create_scaled_unit(becquerel, "M") kbecquerel = Unit.create_scaled_unit(becquerel, "k") Ybecquerel = Unit.create_scaled_unit(becquerel, "Y") agray = Unit.create_scaled_unit(gray, "a") cgray = Unit.create_scaled_unit(gray, "c") Zgray = Unit.create_scaled_unit(gray, "Z") Pgray = Unit.create_scaled_unit(gray, "P") dgray = Unit.create_scaled_unit(gray, "d") Ggray = Unit.create_scaled_unit(gray, "G") fgray = Unit.create_scaled_unit(gray, "f") hgray = Unit.create_scaled_unit(gray, "h") dagray = Unit.create_scaled_unit(gray, "da") mgray = Unit.create_scaled_unit(gray, "m") ngray = Unit.create_scaled_unit(gray, "n") pgray = Unit.create_scaled_unit(gray, "p") ugray = Unit.create_scaled_unit(gray, "u") Tgray = Unit.create_scaled_unit(gray, "T") ygray = Unit.create_scaled_unit(gray, "y") Egray = Unit.create_scaled_unit(gray, "E") zgray = Unit.create_scaled_unit(gray, "z") Mgray = Unit.create_scaled_unit(gray, "M") kgray = Unit.create_scaled_unit(gray, "k") Ygray = Unit.create_scaled_unit(gray, "Y") asievert = Unit.create_scaled_unit(sievert, "a") csievert = Unit.create_scaled_unit(sievert, "c") Zsievert = Unit.create_scaled_unit(sievert, "Z") Psievert = Unit.create_scaled_unit(sievert, "P") dsievert = Unit.create_scaled_unit(sievert, "d") Gsievert = Unit.create_scaled_unit(sievert, "G") fsievert = Unit.create_scaled_unit(sievert, "f") hsievert = Unit.create_scaled_unit(sievert, "h") dasievert = Unit.create_scaled_unit(sievert, "da") msievert = Unit.create_scaled_unit(sievert, "m") nsievert = Unit.create_scaled_unit(sievert, "n") psievert = Unit.create_scaled_unit(sievert, "p") usievert = Unit.create_scaled_unit(sievert, "u") Tsievert = Unit.create_scaled_unit(sievert, "T") ysievert = Unit.create_scaled_unit(sievert, "y") Esievert = Unit.create_scaled_unit(sievert, "E") zsievert = Unit.create_scaled_unit(sievert, "z") Msievert = Unit.create_scaled_unit(sievert, "M") ksievert = Unit.create_scaled_unit(sievert, "k") Ysievert = Unit.create_scaled_unit(sievert, "Y") akatal = Unit.create_scaled_unit(katal, "a") ckatal = Unit.create_scaled_unit(katal, "c") Zkatal = Unit.create_scaled_unit(katal, "Z") Pkatal = Unit.create_scaled_unit(katal, "P") dkatal = Unit.create_scaled_unit(katal, "d") Gkatal = Unit.create_scaled_unit(katal, "G") fkatal = Unit.create_scaled_unit(katal, "f") hkatal = Unit.create_scaled_unit(katal, "h") dakatal = Unit.create_scaled_unit(katal, "da") mkatal = Unit.create_scaled_unit(katal, "m") nkatal = Unit.create_scaled_unit(katal, "n") pkatal = Unit.create_scaled_unit(katal, "p") ukatal = Unit.create_scaled_unit(katal, "u") Tkatal = Unit.create_scaled_unit(katal, "T") ykatal = Unit.create_scaled_unit(katal, "y") Ekatal = Unit.create_scaled_unit(katal, "E") zkatal = Unit.create_scaled_unit(katal, "z") Mkatal = Unit.create_scaled_unit(katal, "M") kkatal = Unit.create_scaled_unit(katal, "k") Ykatal = Unit.create_scaled_unit(katal, "Y") ######### SCALED BASE UNITS TO POWERS ########### metre2 = Unit.create((metre**2).dim, name="metre2", dispname=f"{str(metre)}^2", scale=metre.scale*2) metre3 = Unit.create((metre**3).dim, name="metre3", dispname=f"{str(metre)}^3", scale=metre.scale*3) meter2 = Unit.create((meter**2).dim, name="meter2", dispname=f"{str(meter)}^2", scale=meter.scale*2) meter3 = Unit.create((meter**3).dim, name="meter3", dispname=f"{str(meter)}^3", scale=meter.scale*3) kilogram2 = Unit.create((kilogram**2).dim, name="kilogram2", dispname=f"{str(kilogram)}^2", scale=kilogram.scale*2) kilogram3 = Unit.create((kilogram**3).dim, name="kilogram3", dispname=f"{str(kilogram)}^3", scale=kilogram.scale*3) second2 = Unit.create((second**2).dim, name="second2", dispname=f"{str(second)}^2", scale=second.scale*2) second3 = Unit.create((second**3).dim, name="second3", dispname=f"{str(second)}^3", scale=second.scale*3) amp2 = Unit.create((amp**2).dim, name="amp2", dispname=f"{str(amp)}^2", scale=amp.scale*2) amp3 = Unit.create((amp**3).dim, name="amp3", dispname=f"{str(amp)}^3", scale=amp.scale*3) ampere2 = Unit.create((ampere**2).dim, name="ampere2", dispname=f"{str(ampere)}^2", scale=ampere.scale*2) ampere3 = Unit.create((ampere**3).dim, name="ampere3", dispname=f"{str(ampere)}^3", scale=ampere.scale*3) kelvin2 = Unit.create((kelvin**2).dim, name="kelvin2", dispname=f"{str(kelvin)}^2", scale=kelvin.scale*2) kelvin3 = Unit.create((kelvin**3).dim, name="kelvin3", dispname=f"{str(kelvin)}^3", scale=kelvin.scale*3) mole2 = Unit.create((mole**2).dim, name="mole2", dispname=f"{str(mole)}^2", scale=mole.scale*2) mole3 = Unit.create((mole**3).dim, name="mole3", dispname=f"{str(mole)}^3", scale=mole.scale*3) mol2 = Unit.create((mol**2).dim, name="mol2", dispname=f"{str(mol)}^2", scale=mol.scale*2) mol3 = Unit.create((mol**3).dim, name="mol3", dispname=f"{str(mol)}^3", scale=mol.scale*3) candle2 = Unit.create((candle**2).dim, name="candle2", dispname=f"{str(candle)}^2", scale=candle.scale*2) candle3 = Unit.create((candle**3).dim, name="candle3", dispname=f"{str(candle)}^3", scale=candle.scale*3) kilogramme2 = Unit.create((kilogramme**2).dim, name="kilogramme2", dispname=f"{str(kilogramme)}^2", scale=kilogramme.scale*2) kilogramme3 = Unit.create((kilogramme**3).dim, name="kilogramme3", dispname=f"{str(kilogramme)}^3", scale=kilogramme.scale*3) gram2 = Unit.create((gram**2).dim, name="gram2", dispname=f"{str(gram)}^2", scale=gram.scale*2) gram3 = Unit.create((gram**3).dim, name="gram3", dispname=f"{str(gram)}^3", scale=gram.scale*3) gramme2 = Unit.create((gramme**2).dim, name="gramme2", dispname=f"{str(gramme)}^2", scale=gramme.scale*2) gramme3 = Unit.create((gramme**3).dim, name="gramme3", dispname=f"{str(gramme)}^3", scale=gramme.scale*3) molar2 = Unit.create((molar**2).dim, name="molar2", dispname=f"{str(molar)}^2", scale=molar.scale*2) molar3 = Unit.create((molar**3).dim, name="molar3", dispname=f"{str(molar)}^3", scale=molar.scale*3) radian2 = Unit.create((radian**2).dim, name="radian2", dispname=f"{str(radian)}^2", scale=radian.scale*2) radian3 = Unit.create((radian**3).dim, name="radian3", dispname=f"{str(radian)}^3", scale=radian.scale*3) steradian2 = Unit.create((steradian**2).dim, name="steradian2", dispname=f"{str(steradian)}^2", scale=steradian.scale*2) steradian3 = Unit.create((steradian**3).dim, name="steradian3", dispname=f"{str(steradian)}^3", scale=steradian.scale*3) hertz2 = Unit.create((hertz**2).dim, name="hertz2", dispname=f"{str(hertz)}^2", scale=hertz.scale*2) hertz3 = Unit.create((hertz**3).dim, name="hertz3", dispname=f"{str(hertz)}^3", scale=hertz.scale*3) newton2 = Unit.create((newton**2).dim, name="newton2", dispname=f"{str(newton)}^2", scale=newton.scale*2) newton3 = Unit.create((newton**3).dim, name="newton3", dispname=f"{str(newton)}^3", scale=newton.scale*3) pascal2 = Unit.create((pascal**2).dim, name="pascal2", dispname=f"{str(pascal)}^2", scale=pascal.scale*2) pascal3 = Unit.create((pascal**3).dim, name="pascal3", dispname=f"{str(pascal)}^3", scale=pascal.scale*3) joule2 = Unit.create((joule**2).dim, name="joule2", dispname=f"{str(joule)}^2", scale=joule.scale*2) joule3 = Unit.create((joule**3).dim, name="joule3", dispname=f"{str(joule)}^3", scale=joule.scale*3) watt2 = Unit.create((watt**2).dim, name="watt2", dispname=f"{str(watt)}^2", scale=watt.scale*2) watt3 = Unit.create((watt**3).dim, name="watt3", dispname=f"{str(watt)}^3", scale=watt.scale*3) coulomb2 = Unit.create((coulomb**2).dim, name="coulomb2", dispname=f"{str(coulomb)}^2", scale=coulomb.scale*2) coulomb3 = Unit.create((coulomb**3).dim, name="coulomb3", dispname=f"{str(coulomb)}^3", scale=coulomb.scale*3) volt2 = Unit.create((volt**2).dim, name="volt2", dispname=f"{str(volt)}^2", scale=volt.scale*2) volt3 = Unit.create((volt**3).dim, name="volt3", dispname=f"{str(volt)}^3", scale=volt.scale*3) farad2 = Unit.create((farad**2).dim, name="farad2", dispname=f"{str(farad)}^2", scale=farad.scale*2) farad3 = Unit.create((farad**3).dim, name="farad3", dispname=f"{str(farad)}^3", scale=farad.scale*3) ohm2 = Unit.create((ohm**2).dim, name="ohm2", dispname=f"{str(ohm)}^2", scale=ohm.scale*2) ohm3 = Unit.create((ohm**3).dim, name="ohm3", dispname=f"{str(ohm)}^3", scale=ohm.scale*3) siemens2 = Unit.create((siemens**2).dim, name="siemens2", dispname=f"{str(siemens)}^2", scale=siemens.scale*2) siemens3 = Unit.create((siemens**3).dim, name="siemens3", dispname=f"{str(siemens)}^3", scale=siemens.scale*3) weber2 = Unit.create((weber**2).dim, name="weber2", dispname=f"{str(weber)}^2", scale=weber.scale*2) weber3 = Unit.create((weber**3).dim, name="weber3", dispname=f"{str(weber)}^3", scale=weber.scale*3) tesla2 = Unit.create((tesla**2).dim, name="tesla2", dispname=f"{str(tesla)}^2", scale=tesla.scale*2) tesla3 = Unit.create((tesla**3).dim, name="tesla3", dispname=f"{str(tesla)}^3", scale=tesla.scale*3) henry2 = Unit.create((henry**2).dim, name="henry2", dispname=f"{str(henry)}^2", scale=henry.scale*2) henry3 = Unit.create((henry**3).dim, name="henry3", dispname=f"{str(henry)}^3", scale=henry.scale*3) lumen2 = Unit.create((lumen**2).dim, name="lumen2", dispname=f"{str(lumen)}^2", scale=lumen.scale*2) lumen3 = Unit.create((lumen**3).dim, name="lumen3", dispname=f"{str(lumen)}^3", scale=lumen.scale*3) lux2 = Unit.create((lux**2).dim, name="lux2", dispname=f"{str(lux)}^2", scale=lux.scale*2) lux3 = Unit.create((lux**3).dim, name="lux3", dispname=f"{str(lux)}^3", scale=lux.scale*3) becquerel2 = Unit.create((becquerel**2).dim, name="becquerel2", dispname=f"{str(becquerel)}^2", scale=becquerel.scale*2) becquerel3 = Unit.create((becquerel**3).dim, name="becquerel3", dispname=f"{str(becquerel)}^3", scale=becquerel.scale*3) gray2 = Unit.create((gray**2).dim, name="gray2", dispname=f"{str(gray)}^2", scale=gray.scale*2) gray3 = Unit.create((gray**3).dim, name="gray3", dispname=f"{str(gray)}^3", scale=gray.scale*3) sievert2 = Unit.create((sievert**2).dim, name="sievert2", dispname=f"{str(sievert)}^2", scale=sievert.scale*2) sievert3 = Unit.create((sievert**3).dim, name="sievert3", dispname=f"{str(sievert)}^3", scale=sievert.scale*3) katal2 = Unit.create((katal**2).dim, name="katal2", dispname=f"{str(katal)}^2", scale=katal.scale*2) katal3 = Unit.create((katal**3).dim, name="katal3", dispname=f"{str(katal)}^3", scale=katal.scale*3) ametre2 = Unit.create((ametre**2).dim, name="ametre2", dispname=f"{str(ametre)}^2", scale=ametre.scale*2) ametre3 = Unit.create((ametre**3).dim, name="ametre3", dispname=f"{str(ametre)}^3", scale=ametre.scale*3) cmetre2 = Unit.create((cmetre**2).dim, name="cmetre2", dispname=f"{str(cmetre)}^2", scale=cmetre.scale*2) cmetre3 = Unit.create((cmetre**3).dim, name="cmetre3", dispname=f"{str(cmetre)}^3", scale=cmetre.scale*3) Zmetre2 = Unit.create((Zmetre**2).dim, name="Zmetre2", dispname=f"{str(Zmetre)}^2", scale=Zmetre.scale*2) Zmetre3 = Unit.create((Zmetre**3).dim, name="Zmetre3", dispname=f"{str(Zmetre)}^3", scale=Zmetre.scale*3) Pmetre2 = Unit.create((Pmetre**2).dim, name="Pmetre2", dispname=f"{str(Pmetre)}^2", scale=Pmetre.scale*2) Pmetre3 = Unit.create((Pmetre**3).dim, name="Pmetre3", dispname=f"{str(Pmetre)}^3", scale=Pmetre.scale*3) dmetre2 = Unit.create((dmetre**2).dim, name="dmetre2", dispname=f"{str(dmetre)}^2", scale=dmetre.scale*2) dmetre3 = Unit.create((dmetre**3).dim, name="dmetre3", dispname=f"{str(dmetre)}^3", scale=dmetre.scale*3) Gmetre2 = Unit.create((Gmetre**2).dim, name="Gmetre2", dispname=f"{str(Gmetre)}^2", scale=Gmetre.scale*2) Gmetre3 = Unit.create((Gmetre**3).dim, name="Gmetre3", dispname=f"{str(Gmetre)}^3", scale=Gmetre.scale*3) fmetre2 = Unit.create((fmetre**2).dim, name="fmetre2", dispname=f"{str(fmetre)}^2", scale=fmetre.scale*2) fmetre3 = Unit.create((fmetre**3).dim, name="fmetre3", dispname=f"{str(fmetre)}^3", scale=fmetre.scale*3) hmetre2 = Unit.create((hmetre**2).dim, name="hmetre2", dispname=f"{str(hmetre)}^2", scale=hmetre.scale*2) hmetre3 = Unit.create((hmetre**3).dim, name="hmetre3", dispname=f"{str(hmetre)}^3", scale=hmetre.scale*3) dametre2 = Unit.create((dametre**2).dim, name="dametre2", dispname=f"{str(dametre)}^2", scale=dametre.scale*2) dametre3 = Unit.create((dametre**3).dim, name="dametre3", dispname=f"{str(dametre)}^3", scale=dametre.scale*3) mmetre2 = Unit.create((mmetre**2).dim, name="mmetre2", dispname=f"{str(mmetre)}^2", scale=mmetre.scale*2) mmetre3 = Unit.create((mmetre**3).dim, name="mmetre3", dispname=f"{str(mmetre)}^3", scale=mmetre.scale*3) nmetre2 = Unit.create((nmetre**2).dim, name="nmetre2", dispname=f"{str(nmetre)}^2", scale=nmetre.scale*2) nmetre3 = Unit.create((nmetre**3).dim, name="nmetre3", dispname=f"{str(nmetre)}^3", scale=nmetre.scale*3) pmetre2 = Unit.create((pmetre**2).dim, name="pmetre2", dispname=f"{str(pmetre)}^2", scale=pmetre.scale*2) pmetre3 = Unit.create((pmetre**3).dim, name="pmetre3", dispname=f"{str(pmetre)}^3", scale=pmetre.scale*3) umetre2 = Unit.create((umetre**2).dim, name="umetre2", dispname=f"{str(umetre)}^2", scale=umetre.scale*2) umetre3 = Unit.create((umetre**3).dim, name="umetre3", dispname=f"{str(umetre)}^3", scale=umetre.scale*3) Tmetre2 = Unit.create((Tmetre**2).dim, name="Tmetre2", dispname=f"{str(Tmetre)}^2", scale=Tmetre.scale*2) Tmetre3 = Unit.create((Tmetre**3).dim, name="Tmetre3", dispname=f"{str(Tmetre)}^3", scale=Tmetre.scale*3) ymetre2 = Unit.create((ymetre**2).dim, name="ymetre2", dispname=f"{str(ymetre)}^2", scale=ymetre.scale*2) ymetre3 = Unit.create((ymetre**3).dim, name="ymetre3", dispname=f"{str(ymetre)}^3", scale=ymetre.scale*3) Emetre2 = Unit.create((Emetre**2).dim, name="Emetre2", dispname=f"{str(Emetre)}^2", scale=Emetre.scale*2) Emetre3 = Unit.create((Emetre**3).dim, name="Emetre3", dispname=f"{str(Emetre)}^3", scale=Emetre.scale*3) zmetre2 = Unit.create((zmetre**2).dim, name="zmetre2", dispname=f"{str(zmetre)}^2", scale=zmetre.scale*2) zmetre3 = Unit.create((zmetre**3).dim, name="zmetre3", dispname=f"{str(zmetre)}^3", scale=zmetre.scale*3) Mmetre2 = Unit.create((Mmetre**2).dim, name="Mmetre2", dispname=f"{str(Mmetre)}^2", scale=Mmetre.scale*2) Mmetre3 = Unit.create((Mmetre**3).dim, name="Mmetre3", dispname=f"{str(Mmetre)}^3", scale=Mmetre.scale*3) kmetre2 = Unit.create((kmetre**2).dim, name="kmetre2", dispname=f"{str(kmetre)}^2", scale=kmetre.scale*2) kmetre3 = Unit.create((kmetre**3).dim, name="kmetre3", dispname=f"{str(kmetre)}^3", scale=kmetre.scale*3) Ymetre2 = Unit.create((Ymetre**2).dim, name="Ymetre2", dispname=f"{str(Ymetre)}^2", scale=Ymetre.scale*2) Ymetre3 = Unit.create((Ymetre**3).dim, name="Ymetre3", dispname=f"{str(Ymetre)}^3", scale=Ymetre.scale*3) ameter2 = Unit.create((ameter**2).dim, name="ameter2", dispname=f"{str(ameter)}^2", scale=ameter.scale*2) ameter3 = Unit.create((ameter**3).dim, name="ameter3", dispname=f"{str(ameter)}^3", scale=ameter.scale*3) cmeter2 = Unit.create((cmeter**2).dim, name="cmeter2", dispname=f"{str(cmeter)}^2", scale=cmeter.scale*2) cmeter3 = Unit.create((cmeter**3).dim, name="cmeter3", dispname=f"{str(cmeter)}^3", scale=cmeter.scale*3) Zmeter2 = Unit.create((Zmeter**2).dim, name="Zmeter2", dispname=f"{str(Zmeter)}^2", scale=Zmeter.scale*2) Zmeter3 = Unit.create((Zmeter**3).dim, name="Zmeter3", dispname=f"{str(Zmeter)}^3", scale=Zmeter.scale*3) Pmeter2 = Unit.create((Pmeter**2).dim, name="Pmeter2", dispname=f"{str(Pmeter)}^2", scale=Pmeter.scale*2) Pmeter3 = Unit.create((Pmeter**3).dim, name="Pmeter3", dispname=f"{str(Pmeter)}^3", scale=Pmeter.scale*3) dmeter2 = Unit.create((dmeter**2).dim, name="dmeter2", dispname=f"{str(dmeter)}^2", scale=dmeter.scale*2) dmeter3 = Unit.create((dmeter**3).dim, name="dmeter3", dispname=f"{str(dmeter)}^3", scale=dmeter.scale*3) Gmeter2 = Unit.create((Gmeter**2).dim, name="Gmeter2", dispname=f"{str(Gmeter)}^2", scale=Gmeter.scale*2) Gmeter3 = Unit.create((Gmeter**3).dim, name="Gmeter3", dispname=f"{str(Gmeter)}^3", scale=Gmeter.scale*3) fmeter2 = Unit.create((fmeter**2).dim, name="fmeter2", dispname=f"{str(fmeter)}^2", scale=fmeter.scale*2) fmeter3 = Unit.create((fmeter**3).dim, name="fmeter3", dispname=f"{str(fmeter)}^3", scale=fmeter.scale*3) hmeter2 = Unit.create((hmeter**2).dim, name="hmeter2", dispname=f"{str(hmeter)}^2", scale=hmeter.scale*2) hmeter3 = Unit.create((hmeter**3).dim, name="hmeter3", dispname=f"{str(hmeter)}^3", scale=hmeter.scale*3) dameter2 = Unit.create((dameter**2).dim, name="dameter2", dispname=f"{str(dameter)}^2", scale=dameter.scale*2) dameter3 = Unit.create((dameter**3).dim, name="dameter3", dispname=f"{str(dameter)}^3", scale=dameter.scale*3) mmeter2 = Unit.create((mmeter**2).dim, name="mmeter2", dispname=f"{str(mmeter)}^2", scale=mmeter.scale*2) mmeter3 = Unit.create((mmeter**3).dim, name="mmeter3", dispname=f"{str(mmeter)}^3", scale=mmeter.scale*3) nmeter2 = Unit.create((nmeter**2).dim, name="nmeter2", dispname=f"{str(nmeter)}^2", scale=nmeter.scale*2) nmeter3 = Unit.create((nmeter**3).dim, name="nmeter3", dispname=f"{str(nmeter)}^3", scale=nmeter.scale*3) pmeter2 = Unit.create((pmeter**2).dim, name="pmeter2", dispname=f"{str(pmeter)}^2", scale=pmeter.scale*2) pmeter3 = Unit.create((pmeter**3).dim, name="pmeter3", dispname=f"{str(pmeter)}^3", scale=pmeter.scale*3) umeter2 = Unit.create((umeter**2).dim, name="umeter2", dispname=f"{str(umeter)}^2", scale=umeter.scale*2) umeter3 = Unit.create((umeter**3).dim, name="umeter3", dispname=f"{str(umeter)}^3", scale=umeter.scale*3) Tmeter2 = Unit.create((Tmeter**2).dim, name="Tmeter2", dispname=f"{str(Tmeter)}^2", scale=Tmeter.scale*2) Tmeter3 = Unit.create((Tmeter**3).dim, name="Tmeter3", dispname=f"{str(Tmeter)}^3", scale=Tmeter.scale*3) ymeter2 = Unit.create((ymeter**2).dim, name="ymeter2", dispname=f"{str(ymeter)}^2", scale=ymeter.scale*2) ymeter3 = Unit.create((ymeter**3).dim, name="ymeter3", dispname=f"{str(ymeter)}^3", scale=ymeter.scale*3) Emeter2 = Unit.create((Emeter**2).dim, name="Emeter2", dispname=f"{str(Emeter)}^2", scale=Emeter.scale*2) Emeter3 = Unit.create((Emeter**3).dim, name="Emeter3", dispname=f"{str(Emeter)}^3", scale=Emeter.scale*3) zmeter2 = Unit.create((zmeter**2).dim, name="zmeter2", dispname=f"{str(zmeter)}^2", scale=zmeter.scale*2) zmeter3 = Unit.create((zmeter**3).dim, name="zmeter3", dispname=f"{str(zmeter)}^3", scale=zmeter.scale*3) Mmeter2 = Unit.create((Mmeter**2).dim, name="Mmeter2", dispname=f"{str(Mmeter)}^2", scale=Mmeter.scale*2) Mmeter3 = Unit.create((Mmeter**3).dim, name="Mmeter3", dispname=f"{str(Mmeter)}^3", scale=Mmeter.scale*3) kmeter2 = Unit.create((kmeter**2).dim, name="kmeter2", dispname=f"{str(kmeter)}^2", scale=kmeter.scale*2) kmeter3 = Unit.create((kmeter**3).dim, name="kmeter3", dispname=f"{str(kmeter)}^3", scale=kmeter.scale*3) Ymeter2 = Unit.create((Ymeter**2).dim, name="Ymeter2", dispname=f"{str(Ymeter)}^2", scale=Ymeter.scale*2) Ymeter3 = Unit.create((Ymeter**3).dim, name="Ymeter3", dispname=f"{str(Ymeter)}^3", scale=Ymeter.scale*3) asecond2 = Unit.create((asecond**2).dim, name="asecond2", dispname=f"{str(asecond)}^2", scale=asecond.scale*2) asecond3 = Unit.create((asecond**3).dim, name="asecond3", dispname=f"{str(asecond)}^3", scale=asecond.scale*3) csecond2 = Unit.create((csecond**2).dim, name="csecond2", dispname=f"{str(csecond)}^2", scale=csecond.scale*2) csecond3 = Unit.create((csecond**3).dim, name="csecond3", dispname=f"{str(csecond)}^3", scale=csecond.scale*3) Zsecond2 = Unit.create((Zsecond**2).dim, name="Zsecond2", dispname=f"{str(Zsecond)}^2", scale=Zsecond.scale*2) Zsecond3 = Unit.create((Zsecond**3).dim, name="Zsecond3", dispname=f"{str(Zsecond)}^3", scale=Zsecond.scale*3) Psecond2 = Unit.create((Psecond**2).dim, name="Psecond2", dispname=f"{str(Psecond)}^2", scale=Psecond.scale*2) Psecond3 = Unit.create((Psecond**3).dim, name="Psecond3", dispname=f"{str(Psecond)}^3", scale=Psecond.scale*3) dsecond2 = Unit.create((dsecond**2).dim, name="dsecond2", dispname=f"{str(dsecond)}^2", scale=dsecond.scale*2) dsecond3 = Unit.create((dsecond**3).dim, name="dsecond3", dispname=f"{str(dsecond)}^3", scale=dsecond.scale*3) Gsecond2 = Unit.create((Gsecond**2).dim, name="Gsecond2", dispname=f"{str(Gsecond)}^2", scale=Gsecond.scale*2) Gsecond3 = Unit.create((Gsecond**3).dim, name="Gsecond3", dispname=f"{str(Gsecond)}^3", scale=Gsecond.scale*3) fsecond2 = Unit.create((fsecond**2).dim, name="fsecond2", dispname=f"{str(fsecond)}^2", scale=fsecond.scale*2) fsecond3 = Unit.create((fsecond**3).dim, name="fsecond3", dispname=f"{str(fsecond)}^3", scale=fsecond.scale*3) hsecond2 = Unit.create((hsecond**2).dim, name="hsecond2", dispname=f"{str(hsecond)}^2", scale=hsecond.scale*2) hsecond3 = Unit.create((hsecond**3).dim, name="hsecond3", dispname=f"{str(hsecond)}^3", scale=hsecond.scale*3) dasecond2 = Unit.create((dasecond**2).dim, name="dasecond2", dispname=f"{str(dasecond)}^2", scale=dasecond.scale*2) dasecond3 = Unit.create((dasecond**3).dim, name="dasecond3", dispname=f"{str(dasecond)}^3", scale=dasecond.scale*3) msecond2 = Unit.create((msecond**2).dim, name="msecond2", dispname=f"{str(msecond)}^2", scale=msecond.scale*2) msecond3 = Unit.create((msecond**3).dim, name="msecond3", dispname=f"{str(msecond)}^3", scale=msecond.scale*3) nsecond2 = Unit.create((nsecond**2).dim, name="nsecond2", dispname=f"{str(nsecond)}^2", scale=nsecond.scale*2) nsecond3 = Unit.create((nsecond**3).dim, name="nsecond3", dispname=f"{str(nsecond)}^3", scale=nsecond.scale*3) psecond2 = Unit.create((psecond**2).dim, name="psecond2", dispname=f"{str(psecond)}^2", scale=psecond.scale*2) psecond3 = Unit.create((psecond**3).dim, name="psecond3", dispname=f"{str(psecond)}^3", scale=psecond.scale*3) usecond2 = Unit.create((usecond**2).dim, name="usecond2", dispname=f"{str(usecond)}^2", scale=usecond.scale*2) usecond3 = Unit.create((usecond**3).dim, name="usecond3", dispname=f"{str(usecond)}^3", scale=usecond.scale*3) Tsecond2 = Unit.create((Tsecond**2).dim, name="Tsecond2", dispname=f"{str(Tsecond)}^2", scale=Tsecond.scale*2) Tsecond3 = Unit.create((Tsecond**3).dim, name="Tsecond3", dispname=f"{str(Tsecond)}^3", scale=Tsecond.scale*3) ysecond2 = Unit.create((ysecond**2).dim, name="ysecond2", dispname=f"{str(ysecond)}^2", scale=ysecond.scale*2) ysecond3 = Unit.create((ysecond**3).dim, name="ysecond3", dispname=f"{str(ysecond)}^3", scale=ysecond.scale*3) Esecond2 = Unit.create((Esecond**2).dim, name="Esecond2", dispname=f"{str(Esecond)}^2", scale=Esecond.scale*2) Esecond3 = Unit.create((Esecond**3).dim, name="Esecond3", dispname=f"{str(Esecond)}^3", scale=Esecond.scale*3) zsecond2 = Unit.create((zsecond**2).dim, name="zsecond2", dispname=f"{str(zsecond)}^2", scale=zsecond.scale*2) zsecond3 = Unit.create((zsecond**3).dim, name="zsecond3", dispname=f"{str(zsecond)}^3", scale=zsecond.scale*3) Msecond2 = Unit.create((Msecond**2).dim, name="Msecond2", dispname=f"{str(Msecond)}^2", scale=Msecond.scale*2) Msecond3 = Unit.create((Msecond**3).dim, name="Msecond3", dispname=f"{str(Msecond)}^3", scale=Msecond.scale*3) ksecond2 = Unit.create((ksecond**2).dim, name="ksecond2", dispname=f"{str(ksecond)}^2", scale=ksecond.scale*2) ksecond3 = Unit.create((ksecond**3).dim, name="ksecond3", dispname=f"{str(ksecond)}^3", scale=ksecond.scale*3) Ysecond2 = Unit.create((Ysecond**2).dim, name="Ysecond2", dispname=f"{str(Ysecond)}^2", scale=Ysecond.scale*2) Ysecond3 = Unit.create((Ysecond**3).dim, name="Ysecond3", dispname=f"{str(Ysecond)}^3", scale=Ysecond.scale*3) aamp2 = Unit.create((aamp**2).dim, name="aamp2", dispname=f"{str(aamp)}^2", scale=aamp.scale*2) aamp3 = Unit.create((aamp**3).dim, name="aamp3", dispname=f"{str(aamp)}^3", scale=aamp.scale*3) camp2 = Unit.create((camp**2).dim, name="camp2", dispname=f"{str(camp)}^2", scale=camp.scale*2) camp3 = Unit.create((camp**3).dim, name="camp3", dispname=f"{str(camp)}^3", scale=camp.scale*3) Zamp2 = Unit.create((Zamp**2).dim, name="Zamp2", dispname=f"{str(Zamp)}^2", scale=Zamp.scale*2) Zamp3 = Unit.create((Zamp**3).dim, name="Zamp3", dispname=f"{str(Zamp)}^3", scale=Zamp.scale*3) Pamp2 = Unit.create((Pamp**2).dim, name="Pamp2", dispname=f"{str(Pamp)}^2", scale=Pamp.scale*2) Pamp3 = Unit.create((Pamp**3).dim, name="Pamp3", dispname=f"{str(Pamp)}^3", scale=Pamp.scale*3) damp2 = Unit.create((damp**2).dim, name="damp2", dispname=f"{str(damp)}^2", scale=damp.scale*2) damp3 = Unit.create((damp**3).dim, name="damp3", dispname=f"{str(damp)}^3", scale=damp.scale*3) Gamp2 = Unit.create((Gamp**2).dim, name="Gamp2", dispname=f"{str(Gamp)}^2", scale=Gamp.scale*2) Gamp3 = Unit.create((Gamp**3).dim, name="Gamp3", dispname=f"{str(Gamp)}^3", scale=Gamp.scale*3) famp2 = Unit.create((famp**2).dim, name="famp2", dispname=f"{str(famp)}^2", scale=famp.scale*2) famp3 = Unit.create((famp**3).dim, name="famp3", dispname=f"{str(famp)}^3", scale=famp.scale*3) hamp2 = Unit.create((hamp**2).dim, name="hamp2", dispname=f"{str(hamp)}^2", scale=hamp.scale*2) hamp3 = Unit.create((hamp**3).dim, name="hamp3", dispname=f"{str(hamp)}^3", scale=hamp.scale*3) daamp2 = Unit.create((daamp**2).dim, name="daamp2", dispname=f"{str(daamp)}^2", scale=daamp.scale*2) daamp3 = Unit.create((daamp**3).dim, name="daamp3", dispname=f"{str(daamp)}^3", scale=daamp.scale*3) mamp2 = Unit.create((mamp**2).dim, name="mamp2", dispname=f"{str(mamp)}^2", scale=mamp.scale*2) mamp3 = Unit.create((mamp**3).dim, name="mamp3", dispname=f"{str(mamp)}^3", scale=mamp.scale*3) namp2 = Unit.create((namp**2).dim, name="namp2", dispname=f"{str(namp)}^2", scale=namp.scale*2) namp3 = Unit.create((namp**3).dim, name="namp3", dispname=f"{str(namp)}^3", scale=namp.scale*3) pamp2 = Unit.create((pamp**2).dim, name="pamp2", dispname=f"{str(pamp)}^2", scale=pamp.scale*2) pamp3 = Unit.create((pamp**3).dim, name="pamp3", dispname=f"{str(pamp)}^3", scale=pamp.scale*3) uamp2 = Unit.create((uamp**2).dim, name="uamp2", dispname=f"{str(uamp)}^2", scale=uamp.scale*2) uamp3 = Unit.create((uamp**3).dim, name="uamp3", dispname=f"{str(uamp)}^3", scale=uamp.scale*3) Tamp2 = Unit.create((Tamp**2).dim, name="Tamp2", dispname=f"{str(Tamp)}^2", scale=Tamp.scale*2) Tamp3 = Unit.create((Tamp**3).dim, name="Tamp3", dispname=f"{str(Tamp)}^3", scale=Tamp.scale*3) yamp2 = Unit.create((yamp**2).dim, name="yamp2", dispname=f"{str(yamp)}^2", scale=yamp.scale*2) yamp3 = Unit.create((yamp**3).dim, name="yamp3", dispname=f"{str(yamp)}^3", scale=yamp.scale*3) Eamp2 = Unit.create((Eamp**2).dim, name="Eamp2", dispname=f"{str(Eamp)}^2", scale=Eamp.scale*2) Eamp3 = Unit.create((Eamp**3).dim, name="Eamp3", dispname=f"{str(Eamp)}^3", scale=Eamp.scale*3) zamp2 = Unit.create((zamp**2).dim, name="zamp2", dispname=f"{str(zamp)}^2", scale=zamp.scale*2) zamp3 = Unit.create((zamp**3).dim, name="zamp3", dispname=f"{str(zamp)}^3", scale=zamp.scale*3) Mamp2 = Unit.create((Mamp**2).dim, name="Mamp2", dispname=f"{str(Mamp)}^2", scale=Mamp.scale*2) Mamp3 = Unit.create((Mamp**3).dim, name="Mamp3", dispname=f"{str(Mamp)}^3", scale=Mamp.scale*3) kamp2 = Unit.create((kamp**2).dim, name="kamp2", dispname=f"{str(kamp)}^2", scale=kamp.scale*2) kamp3 = Unit.create((kamp**3).dim, name="kamp3", dispname=f"{str(kamp)}^3", scale=kamp.scale*3) Yamp2 = Unit.create((Yamp**2).dim, name="Yamp2", dispname=f"{str(Yamp)}^2", scale=Yamp.scale*2) Yamp3 = Unit.create((Yamp**3).dim, name="Yamp3", dispname=f"{str(Yamp)}^3", scale=Yamp.scale*3) aampere2 = Unit.create((aampere**2).dim, name="aampere2", dispname=f"{str(aampere)}^2", scale=aampere.scale*2) aampere3 = Unit.create((aampere**3).dim, name="aampere3", dispname=f"{str(aampere)}^3", scale=aampere.scale*3) campere2 = Unit.create((campere**2).dim, name="campere2", dispname=f"{str(campere)}^2", scale=campere.scale*2) campere3 = Unit.create((campere**3).dim, name="campere3", dispname=f"{str(campere)}^3", scale=campere.scale*3) Zampere2 = Unit.create((Zampere**2).dim, name="Zampere2", dispname=f"{str(Zampere)}^2", scale=Zampere.scale*2) Zampere3 = Unit.create((Zampere**3).dim, name="Zampere3", dispname=f"{str(Zampere)}^3", scale=Zampere.scale*3) Pampere2 = Unit.create((Pampere**2).dim, name="Pampere2", dispname=f"{str(Pampere)}^2", scale=Pampere.scale*2) Pampere3 = Unit.create((Pampere**3).dim, name="Pampere3", dispname=f"{str(Pampere)}^3", scale=Pampere.scale*3) dampere2 = Unit.create((dampere**2).dim, name="dampere2", dispname=f"{str(dampere)}^2", scale=dampere.scale*2) dampere3 = Unit.create((dampere**3).dim, name="dampere3", dispname=f"{str(dampere)}^3", scale=dampere.scale*3) Gampere2 = Unit.create((Gampere**2).dim, name="Gampere2", dispname=f"{str(Gampere)}^2", scale=Gampere.scale*2) Gampere3 = Unit.create((Gampere**3).dim, name="Gampere3", dispname=f"{str(Gampere)}^3", scale=Gampere.scale*3) fampere2 = Unit.create((fampere**2).dim, name="fampere2", dispname=f"{str(fampere)}^2", scale=fampere.scale*2) fampere3 = Unit.create((fampere**3).dim, name="fampere3", dispname=f"{str(fampere)}^3", scale=fampere.scale*3) hampere2 = Unit.create((hampere**2).dim, name="hampere2", dispname=f"{str(hampere)}^2", scale=hampere.scale*2) hampere3 = Unit.create((hampere**3).dim, name="hampere3", dispname=f"{str(hampere)}^3", scale=hampere.scale*3) daampere2 = Unit.create((daampere**2).dim, name="daampere2", dispname=f"{str(daampere)}^2", scale=daampere.scale*2) daampere3 = Unit.create((daampere**3).dim, name="daampere3", dispname=f"{str(daampere)}^3", scale=daampere.scale*3) mampere2 = Unit.create((mampere**2).dim, name="mampere2", dispname=f"{str(mampere)}^2", scale=mampere.scale*2) mampere3 = Unit.create((mampere**3).dim, name="mampere3", dispname=f"{str(mampere)}^3", scale=mampere.scale*3) nampere2 = Unit.create((nampere**2).dim, name="nampere2", dispname=f"{str(nampere)}^2", scale=nampere.scale*2) nampere3 = Unit.create((nampere**3).dim, name="nampere3", dispname=f"{str(nampere)}^3", scale=nampere.scale*3) pampere2 = Unit.create((pampere**2).dim, name="pampere2", dispname=f"{str(pampere)}^2", scale=pampere.scale*2) pampere3 = Unit.create((pampere**3).dim, name="pampere3", dispname=f"{str(pampere)}^3", scale=pampere.scale*3) uampere2 = Unit.create((uampere**2).dim, name="uampere2", dispname=f"{str(uampere)}^2", scale=uampere.scale*2) uampere3 = Unit.create((uampere**3).dim, name="uampere3", dispname=f"{str(uampere)}^3", scale=uampere.scale*3) Tampere2 = Unit.create((Tampere**2).dim, name="Tampere2", dispname=f"{str(Tampere)}^2", scale=Tampere.scale*2) Tampere3 = Unit.create((Tampere**3).dim, name="Tampere3", dispname=f"{str(Tampere)}^3", scale=Tampere.scale*3) yampere2 = Unit.create((yampere**2).dim, name="yampere2", dispname=f"{str(yampere)}^2", scale=yampere.scale*2) yampere3 = Unit.create((yampere**3).dim, name="yampere3", dispname=f"{str(yampere)}^3", scale=yampere.scale*3) Eampere2 = Unit.create((Eampere**2).dim, name="Eampere2", dispname=f"{str(Eampere)}^2", scale=Eampere.scale*2) Eampere3 = Unit.create((Eampere**3).dim, name="Eampere3", dispname=f"{str(Eampere)}^3", scale=Eampere.scale*3) zampere2 = Unit.create((zampere**2).dim, name="zampere2", dispname=f"{str(zampere)}^2", scale=zampere.scale*2) zampere3 = Unit.create((zampere**3).dim, name="zampere3", dispname=f"{str(zampere)}^3", scale=zampere.scale*3) Mampere2 = Unit.create((Mampere**2).dim, name="Mampere2", dispname=f"{str(Mampere)}^2", scale=Mampere.scale*2) Mampere3 = Unit.create((Mampere**3).dim, name="Mampere3", dispname=f"{str(Mampere)}^3", scale=Mampere.scale*3) kampere2 = Unit.create((kampere**2).dim, name="kampere2", dispname=f"{str(kampere)}^2", scale=kampere.scale*2) kampere3 = Unit.create((kampere**3).dim, name="kampere3", dispname=f"{str(kampere)}^3", scale=kampere.scale*3) Yampere2 = Unit.create((Yampere**2).dim, name="Yampere2", dispname=f"{str(Yampere)}^2", scale=Yampere.scale*2) Yampere3 = Unit.create((Yampere**3).dim, name="Yampere3", dispname=f"{str(Yampere)}^3", scale=Yampere.scale*3) amole2 = Unit.create((amole**2).dim, name="amole2", dispname=f"{str(amole)}^2", scale=amole.scale*2) amole3 = Unit.create((amole**3).dim, name="amole3", dispname=f"{str(amole)}^3", scale=amole.scale*3) cmole2 = Unit.create((cmole**2).dim, name="cmole2", dispname=f"{str(cmole)}^2", scale=cmole.scale*2) cmole3 = Unit.create((cmole**3).dim, name="cmole3", dispname=f"{str(cmole)}^3", scale=cmole.scale*3) Zmole2 = Unit.create((Zmole**2).dim, name="Zmole2", dispname=f"{str(Zmole)}^2", scale=Zmole.scale*2) Zmole3 = Unit.create((Zmole**3).dim, name="Zmole3", dispname=f"{str(Zmole)}^3", scale=Zmole.scale*3) Pmole2 = Unit.create((Pmole**2).dim, name="Pmole2", dispname=f"{str(Pmole)}^2", scale=Pmole.scale*2) Pmole3 = Unit.create((Pmole**3).dim, name="Pmole3", dispname=f"{str(Pmole)}^3", scale=Pmole.scale*3) dmole2 = Unit.create((dmole**2).dim, name="dmole2", dispname=f"{str(dmole)}^2", scale=dmole.scale*2) dmole3 = Unit.create((dmole**3).dim, name="dmole3", dispname=f"{str(dmole)}^3", scale=dmole.scale*3) Gmole2 = Unit.create((Gmole**2).dim, name="Gmole2", dispname=f"{str(Gmole)}^2", scale=Gmole.scale*2) Gmole3 = Unit.create((Gmole**3).dim, name="Gmole3", dispname=f"{str(Gmole)}^3", scale=Gmole.scale*3) fmole2 = Unit.create((fmole**2).dim, name="fmole2", dispname=f"{str(fmole)}^2", scale=fmole.scale*2) fmole3 = Unit.create((fmole**3).dim, name="fmole3", dispname=f"{str(fmole)}^3", scale=fmole.scale*3) hmole2 = Unit.create((hmole**2).dim, name="hmole2", dispname=f"{str(hmole)}^2", scale=hmole.scale*2) hmole3 = Unit.create((hmole**3).dim, name="hmole3", dispname=f"{str(hmole)}^3", scale=hmole.scale*3) damole2 = Unit.create((damole**2).dim, name="damole2", dispname=f"{str(damole)}^2", scale=damole.scale*2) damole3 = Unit.create((damole**3).dim, name="damole3", dispname=f"{str(damole)}^3", scale=damole.scale*3) mmole2 = Unit.create((mmole**2).dim, name="mmole2", dispname=f"{str(mmole)}^2", scale=mmole.scale*2) mmole3 = Unit.create((mmole**3).dim, name="mmole3", dispname=f"{str(mmole)}^3", scale=mmole.scale*3) nmole2 = Unit.create((nmole**2).dim, name="nmole2", dispname=f"{str(nmole)}^2", scale=nmole.scale*2) nmole3 = Unit.create((nmole**3).dim, name="nmole3", dispname=f"{str(nmole)}^3", scale=nmole.scale*3) pmole2 = Unit.create((pmole**2).dim, name="pmole2", dispname=f"{str(pmole)}^2", scale=pmole.scale*2) pmole3 = Unit.create((pmole**3).dim, name="pmole3", dispname=f"{str(pmole)}^3", scale=pmole.scale*3) umole2 = Unit.create((umole**2).dim, name="umole2", dispname=f"{str(umole)}^2", scale=umole.scale*2) umole3 = Unit.create((umole**3).dim, name="umole3", dispname=f"{str(umole)}^3", scale=umole.scale*3) Tmole2 = Unit.create((Tmole**2).dim, name="Tmole2", dispname=f"{str(Tmole)}^2", scale=Tmole.scale*2) Tmole3 = Unit.create((Tmole**3).dim, name="Tmole3", dispname=f"{str(Tmole)}^3", scale=Tmole.scale*3) ymole2 = Unit.create((ymole**2).dim, name="ymole2", dispname=f"{str(ymole)}^2", scale=ymole.scale*2) ymole3 = Unit.create((ymole**3).dim, name="ymole3", dispname=f"{str(ymole)}^3", scale=ymole.scale*3) Emole2 = Unit.create((Emole**2).dim, name="Emole2", dispname=f"{str(Emole)}^2", scale=Emole.scale*2) Emole3 = Unit.create((Emole**3).dim, name="Emole3", dispname=f"{str(Emole)}^3", scale=Emole.scale*3) zmole2 = Unit.create((zmole**2).dim, name="zmole2", dispname=f"{str(zmole)}^2", scale=zmole.scale*2) zmole3 = Unit.create((zmole**3).dim, name="zmole3", dispname=f"{str(zmole)}^3", scale=zmole.scale*3) Mmole2 = Unit.create((Mmole**2).dim, name="Mmole2", dispname=f"{str(Mmole)}^2", scale=Mmole.scale*2) Mmole3 = Unit.create((Mmole**3).dim, name="Mmole3", dispname=f"{str(Mmole)}^3", scale=Mmole.scale*3) kmole2 = Unit.create((kmole**2).dim, name="kmole2", dispname=f"{str(kmole)}^2", scale=kmole.scale*2) kmole3 = Unit.create((kmole**3).dim, name="kmole3", dispname=f"{str(kmole)}^3", scale=kmole.scale*3) Ymole2 = Unit.create((Ymole**2).dim, name="Ymole2", dispname=f"{str(Ymole)}^2", scale=Ymole.scale*2) Ymole3 = Unit.create((Ymole**3).dim, name="Ymole3", dispname=f"{str(Ymole)}^3", scale=Ymole.scale*3) amol2 = Unit.create((amol**2).dim, name="amol2", dispname=f"{str(amol)}^2", scale=amol.scale*2) amol3 = Unit.create((amol**3).dim, name="amol3", dispname=f"{str(amol)}^3", scale=amol.scale*3) cmol2 = Unit.create((cmol**2).dim, name="cmol2", dispname=f"{str(cmol)}^2", scale=cmol.scale*2) cmol3 = Unit.create((cmol**3).dim, name="cmol3", dispname=f"{str(cmol)}^3", scale=cmol.scale*3) Zmol2 = Unit.create((Zmol**2).dim, name="Zmol2", dispname=f"{str(Zmol)}^2", scale=Zmol.scale*2) Zmol3 = Unit.create((Zmol**3).dim, name="Zmol3", dispname=f"{str(Zmol)}^3", scale=Zmol.scale*3) Pmol2 = Unit.create((Pmol**2).dim, name="Pmol2", dispname=f"{str(Pmol)}^2", scale=Pmol.scale*2) Pmol3 = Unit.create((Pmol**3).dim, name="Pmol3", dispname=f"{str(Pmol)}^3", scale=Pmol.scale*3) dmol2 = Unit.create((dmol**2).dim, name="dmol2", dispname=f"{str(dmol)}^2", scale=dmol.scale*2) dmol3 = Unit.create((dmol**3).dim, name="dmol3", dispname=f"{str(dmol)}^3", scale=dmol.scale*3) Gmol2 = Unit.create((Gmol**2).dim, name="Gmol2", dispname=f"{str(Gmol)}^2", scale=Gmol.scale*2) Gmol3 = Unit.create((Gmol**3).dim, name="Gmol3", dispname=f"{str(Gmol)}^3", scale=Gmol.scale*3) fmol2 = Unit.create((fmol**2).dim, name="fmol2", dispname=f"{str(fmol)}^2", scale=fmol.scale*2) fmol3 = Unit.create((fmol**3).dim, name="fmol3", dispname=f"{str(fmol)}^3", scale=fmol.scale*3) hmol2 = Unit.create((hmol**2).dim, name="hmol2", dispname=f"{str(hmol)}^2", scale=hmol.scale*2) hmol3 = Unit.create((hmol**3).dim, name="hmol3", dispname=f"{str(hmol)}^3", scale=hmol.scale*3) damol2 = Unit.create((damol**2).dim, name="damol2", dispname=f"{str(damol)}^2", scale=damol.scale*2) damol3 = Unit.create((damol**3).dim, name="damol3", dispname=f"{str(damol)}^3", scale=damol.scale*3) mmol2 = Unit.create((mmol**2).dim, name="mmol2", dispname=f"{str(mmol)}^2", scale=mmol.scale*2) mmol3 = Unit.create((mmol**3).dim, name="mmol3", dispname=f"{str(mmol)}^3", scale=mmol.scale*3) nmol2 = Unit.create((nmol**2).dim, name="nmol2", dispname=f"{str(nmol)}^2", scale=nmol.scale*2) nmol3 = Unit.create((nmol**3).dim, name="nmol3", dispname=f"{str(nmol)}^3", scale=nmol.scale*3) pmol2 = Unit.create((pmol**2).dim, name="pmol2", dispname=f"{str(pmol)}^2", scale=pmol.scale*2) pmol3 = Unit.create((pmol**3).dim, name="pmol3", dispname=f"{str(pmol)}^3", scale=pmol.scale*3) umol2 = Unit.create((umol**2).dim, name="umol2", dispname=f"{str(umol)}^2", scale=umol.scale*2) umol3 = Unit.create((umol**3).dim, name="umol3", dispname=f"{str(umol)}^3", scale=umol.scale*3) Tmol2 = Unit.create((Tmol**2).dim, name="Tmol2", dispname=f"{str(Tmol)}^2", scale=Tmol.scale*2) Tmol3 = Unit.create((Tmol**3).dim, name="Tmol3", dispname=f"{str(Tmol)}^3", scale=Tmol.scale*3) ymol2 = Unit.create((ymol**2).dim, name="ymol2", dispname=f"{str(ymol)}^2", scale=ymol.scale*2) ymol3 = Unit.create((ymol**3).dim, name="ymol3", dispname=f"{str(ymol)}^3", scale=ymol.scale*3) Emol2 = Unit.create((Emol**2).dim, name="Emol2", dispname=f"{str(Emol)}^2", scale=Emol.scale*2) Emol3 = Unit.create((Emol**3).dim, name="Emol3", dispname=f"{str(Emol)}^3", scale=Emol.scale*3) zmol2 = Unit.create((zmol**2).dim, name="zmol2", dispname=f"{str(zmol)}^2", scale=zmol.scale*2) zmol3 = Unit.create((zmol**3).dim, name="zmol3", dispname=f"{str(zmol)}^3", scale=zmol.scale*3) Mmol2 = Unit.create((Mmol**2).dim, name="Mmol2", dispname=f"{str(Mmol)}^2", scale=Mmol.scale*2) Mmol3 = Unit.create((Mmol**3).dim, name="Mmol3", dispname=f"{str(Mmol)}^3", scale=Mmol.scale*3) kmol2 = Unit.create((kmol**2).dim, name="kmol2", dispname=f"{str(kmol)}^2", scale=kmol.scale*2) kmol3 = Unit.create((kmol**3).dim, name="kmol3", dispname=f"{str(kmol)}^3", scale=kmol.scale*3) Ymol2 = Unit.create((Ymol**2).dim, name="Ymol2", dispname=f"{str(Ymol)}^2", scale=Ymol.scale*2) Ymol3 = Unit.create((Ymol**3).dim, name="Ymol3", dispname=f"{str(Ymol)}^3", scale=Ymol.scale*3) acandle2 = Unit.create((acandle**2).dim, name="acandle2", dispname=f"{str(acandle)}^2", scale=acandle.scale*2) acandle3 = Unit.create((acandle**3).dim, name="acandle3", dispname=f"{str(acandle)}^3", scale=acandle.scale*3) ccandle2 = Unit.create((ccandle**2).dim, name="ccandle2", dispname=f"{str(ccandle)}^2", scale=ccandle.scale*2) ccandle3 = Unit.create((ccandle**3).dim, name="ccandle3", dispname=f"{str(ccandle)}^3", scale=ccandle.scale*3) Zcandle2 = Unit.create((Zcandle**2).dim, name="Zcandle2", dispname=f"{str(Zcandle)}^2", scale=Zcandle.scale*2) Zcandle3 = Unit.create((Zcandle**3).dim, name="Zcandle3", dispname=f"{str(Zcandle)}^3", scale=Zcandle.scale*3) Pcandle2 = Unit.create((Pcandle**2).dim, name="Pcandle2", dispname=f"{str(Pcandle)}^2", scale=Pcandle.scale*2) Pcandle3 = Unit.create((Pcandle**3).dim, name="Pcandle3", dispname=f"{str(Pcandle)}^3", scale=Pcandle.scale*3) dcandle2 = Unit.create((dcandle**2).dim, name="dcandle2", dispname=f"{str(dcandle)}^2", scale=dcandle.scale*2) dcandle3 = Unit.create((dcandle**3).dim, name="dcandle3", dispname=f"{str(dcandle)}^3", scale=dcandle.scale*3) Gcandle2 = Unit.create((Gcandle**2).dim, name="Gcandle2", dispname=f"{str(Gcandle)}^2", scale=Gcandle.scale*2) Gcandle3 = Unit.create((Gcandle**3).dim, name="Gcandle3", dispname=f"{str(Gcandle)}^3", scale=Gcandle.scale*3) fcandle2 = Unit.create((fcandle**2).dim, name="fcandle2", dispname=f"{str(fcandle)}^2", scale=fcandle.scale*2) fcandle3 = Unit.create((fcandle**3).dim, name="fcandle3", dispname=f"{str(fcandle)}^3", scale=fcandle.scale*3) hcandle2 = Unit.create((hcandle**2).dim, name="hcandle2", dispname=f"{str(hcandle)}^2", scale=hcandle.scale*2) hcandle3 = Unit.create((hcandle**3).dim, name="hcandle3", dispname=f"{str(hcandle)}^3", scale=hcandle.scale*3) dacandle2 = Unit.create((dacandle**2).dim, name="dacandle2", dispname=f"{str(dacandle)}^2", scale=dacandle.scale*2) dacandle3 = Unit.create((dacandle**3).dim, name="dacandle3", dispname=f"{str(dacandle)}^3", scale=dacandle.scale*3) mcandle2 = Unit.create((mcandle**2).dim, name="mcandle2", dispname=f"{str(mcandle)}^2", scale=mcandle.scale*2) mcandle3 = Unit.create((mcandle**3).dim, name="mcandle3", dispname=f"{str(mcandle)}^3", scale=mcandle.scale*3) ncandle2 = Unit.create((ncandle**2).dim, name="ncandle2", dispname=f"{str(ncandle)}^2", scale=ncandle.scale*2) ncandle3 = Unit.create((ncandle**3).dim, name="ncandle3", dispname=f"{str(ncandle)}^3", scale=ncandle.scale*3) pcandle2 = Unit.create((pcandle**2).dim, name="pcandle2", dispname=f"{str(pcandle)}^2", scale=pcandle.scale*2) pcandle3 = Unit.create((pcandle**3).dim, name="pcandle3", dispname=f"{str(pcandle)}^3", scale=pcandle.scale*3) ucandle2 = Unit.create((ucandle**2).dim, name="ucandle2", dispname=f"{str(ucandle)}^2", scale=ucandle.scale*2) ucandle3 = Unit.create((ucandle**3).dim, name="ucandle3", dispname=f"{str(ucandle)}^3", scale=ucandle.scale*3) Tcandle2 = Unit.create((Tcandle**2).dim, name="Tcandle2", dispname=f"{str(Tcandle)}^2", scale=Tcandle.scale*2) Tcandle3 = Unit.create((Tcandle**3).dim, name="Tcandle3", dispname=f"{str(Tcandle)}^3", scale=Tcandle.scale*3) ycandle2 = Unit.create((ycandle**2).dim, name="ycandle2", dispname=f"{str(ycandle)}^2", scale=ycandle.scale*2) ycandle3 = Unit.create((ycandle**3).dim, name="ycandle3", dispname=f"{str(ycandle)}^3", scale=ycandle.scale*3) Ecandle2 = Unit.create((Ecandle**2).dim, name="Ecandle2", dispname=f"{str(Ecandle)}^2", scale=Ecandle.scale*2) Ecandle3 = Unit.create((Ecandle**3).dim, name="Ecandle3", dispname=f"{str(Ecandle)}^3", scale=Ecandle.scale*3) zcandle2 = Unit.create((zcandle**2).dim, name="zcandle2", dispname=f"{str(zcandle)}^2", scale=zcandle.scale*2) zcandle3 = Unit.create((zcandle**3).dim, name="zcandle3", dispname=f"{str(zcandle)}^3", scale=zcandle.scale*3) Mcandle2 = Unit.create((Mcandle**2).dim, name="Mcandle2", dispname=f"{str(Mcandle)}^2", scale=Mcandle.scale*2) Mcandle3 = Unit.create((Mcandle**3).dim, name="Mcandle3", dispname=f"{str(Mcandle)}^3", scale=Mcandle.scale*3) kcandle2 = Unit.create((kcandle**2).dim, name="kcandle2", dispname=f"{str(kcandle)}^2", scale=kcandle.scale*2) kcandle3 = Unit.create((kcandle**3).dim, name="kcandle3", dispname=f"{str(kcandle)}^3", scale=kcandle.scale*3) Ycandle2 = Unit.create((Ycandle**2).dim, name="Ycandle2", dispname=f"{str(Ycandle)}^2", scale=Ycandle.scale*2) Ycandle3 = Unit.create((Ycandle**3).dim, name="Ycandle3", dispname=f"{str(Ycandle)}^3", scale=Ycandle.scale*3) agram2 = Unit.create((agram**2).dim, name="agram2", dispname=f"{str(agram)}^2", scale=agram.scale*2) agram3 = Unit.create((agram**3).dim, name="agram3", dispname=f"{str(agram)}^3", scale=agram.scale*3) cgram2 = Unit.create((cgram**2).dim, name="cgram2", dispname=f"{str(cgram)}^2", scale=cgram.scale*2) cgram3 = Unit.create((cgram**3).dim, name="cgram3", dispname=f"{str(cgram)}^3", scale=cgram.scale*3) Zgram2 = Unit.create((Zgram**2).dim, name="Zgram2", dispname=f"{str(Zgram)}^2", scale=Zgram.scale*2) Zgram3 = Unit.create((Zgram**3).dim, name="Zgram3", dispname=f"{str(Zgram)}^3", scale=Zgram.scale*3) Pgram2 = Unit.create((Pgram**2).dim, name="Pgram2", dispname=f"{str(Pgram)}^2", scale=Pgram.scale*2) Pgram3 = Unit.create((Pgram**3).dim, name="Pgram3", dispname=f"{str(Pgram)}^3", scale=Pgram.scale*3) dgram2 = Unit.create((dgram**2).dim, name="dgram2", dispname=f"{str(dgram)}^2", scale=dgram.scale*2) dgram3 = Unit.create((dgram**3).dim, name="dgram3", dispname=f"{str(dgram)}^3", scale=dgram.scale*3) Ggram2 = Unit.create((Ggram**2).dim, name="Ggram2", dispname=f"{str(Ggram)}^2", scale=Ggram.scale*2) Ggram3 = Unit.create((Ggram**3).dim, name="Ggram3", dispname=f"{str(Ggram)}^3", scale=Ggram.scale*3) fgram2 = Unit.create((fgram**2).dim, name="fgram2", dispname=f"{str(fgram)}^2", scale=fgram.scale*2) fgram3 = Unit.create((fgram**3).dim, name="fgram3", dispname=f"{str(fgram)}^3", scale=fgram.scale*3) hgram2 = Unit.create((hgram**2).dim, name="hgram2", dispname=f"{str(hgram)}^2", scale=hgram.scale*2) hgram3 = Unit.create((hgram**3).dim, name="hgram3", dispname=f"{str(hgram)}^3", scale=hgram.scale*3) dagram2 = Unit.create((dagram**2).dim, name="dagram2", dispname=f"{str(dagram)}^2", scale=dagram.scale*2) dagram3 = Unit.create((dagram**3).dim, name="dagram3", dispname=f"{str(dagram)}^3", scale=dagram.scale*3) mgram2 = Unit.create((mgram**2).dim, name="mgram2", dispname=f"{str(mgram)}^2", scale=mgram.scale*2) mgram3 = Unit.create((mgram**3).dim, name="mgram3", dispname=f"{str(mgram)}^3", scale=mgram.scale*3) ngram2 = Unit.create((ngram**2).dim, name="ngram2", dispname=f"{str(ngram)}^2", scale=ngram.scale*2) ngram3 = Unit.create((ngram**3).dim, name="ngram3", dispname=f"{str(ngram)}^3", scale=ngram.scale*3) pgram2 = Unit.create((pgram**2).dim, name="pgram2", dispname=f"{str(pgram)}^2", scale=pgram.scale*2) pgram3 = Unit.create((pgram**3).dim, name="pgram3", dispname=f"{str(pgram)}^3", scale=pgram.scale*3) ugram2 = Unit.create((ugram**2).dim, name="ugram2", dispname=f"{str(ugram)}^2", scale=ugram.scale*2) ugram3 = Unit.create((ugram**3).dim, name="ugram3", dispname=f"{str(ugram)}^3", scale=ugram.scale*3) Tgram2 = Unit.create((Tgram**2).dim, name="Tgram2", dispname=f"{str(Tgram)}^2", scale=Tgram.scale*2) Tgram3 = Unit.create((Tgram**3).dim, name="Tgram3", dispname=f"{str(Tgram)}^3", scale=Tgram.scale*3) ygram2 = Unit.create((ygram**2).dim, name="ygram2", dispname=f"{str(ygram)}^2", scale=ygram.scale*2) ygram3 = Unit.create((ygram**3).dim, name="ygram3", dispname=f"{str(ygram)}^3", scale=ygram.scale*3) Egram2 = Unit.create((Egram**2).dim, name="Egram2", dispname=f"{str(Egram)}^2", scale=Egram.scale*2) Egram3 = Unit.create((Egram**3).dim, name="Egram3", dispname=f"{str(Egram)}^3", scale=Egram.scale*3) zgram2 = Unit.create((zgram**2).dim, name="zgram2", dispname=f"{str(zgram)}^2", scale=zgram.scale*2) zgram3 = Unit.create((zgram**3).dim, name="zgram3", dispname=f"{str(zgram)}^3", scale=zgram.scale*3) Mgram2 = Unit.create((Mgram**2).dim, name="Mgram2", dispname=f"{str(Mgram)}^2", scale=Mgram.scale*2) Mgram3 = Unit.create((Mgram**3).dim, name="Mgram3", dispname=f"{str(Mgram)}^3", scale=Mgram.scale*3) kgram2 = Unit.create((kgram**2).dim, name="kgram2", dispname=f"{str(kgram)}^2", scale=kgram.scale*2) kgram3 = Unit.create((kgram**3).dim, name="kgram3", dispname=f"{str(kgram)}^3", scale=kgram.scale*3) Ygram2 = Unit.create((Ygram**2).dim, name="Ygram2", dispname=f"{str(Ygram)}^2", scale=Ygram.scale*2) Ygram3 = Unit.create((Ygram**3).dim, name="Ygram3", dispname=f"{str(Ygram)}^3", scale=Ygram.scale*3) agramme2 = Unit.create((agramme**2).dim, name="agramme2", dispname=f"{str(agramme)}^2", scale=agramme.scale*2) agramme3 = Unit.create((agramme**3).dim, name="agramme3", dispname=f"{str(agramme)}^3", scale=agramme.scale*3) cgramme2 = Unit.create((cgramme**2).dim, name="cgramme2", dispname=f"{str(cgramme)}^2", scale=cgramme.scale*2) cgramme3 = Unit.create((cgramme**3).dim, name="cgramme3", dispname=f"{str(cgramme)}^3", scale=cgramme.scale*3) Zgramme2 = Unit.create((Zgramme**2).dim, name="Zgramme2", dispname=f"{str(Zgramme)}^2", scale=Zgramme.scale*2) Zgramme3 = Unit.create((Zgramme**3).dim, name="Zgramme3", dispname=f"{str(Zgramme)}^3", scale=Zgramme.scale*3) Pgramme2 = Unit.create((Pgramme**2).dim, name="Pgramme2", dispname=f"{str(Pgramme)}^2", scale=Pgramme.scale*2) Pgramme3 = Unit.create((Pgramme**3).dim, name="Pgramme3", dispname=f"{str(Pgramme)}^3", scale=Pgramme.scale*3) dgramme2 = Unit.create((dgramme**2).dim, name="dgramme2", dispname=f"{str(dgramme)}^2", scale=dgramme.scale*2) dgramme3 = Unit.create((dgramme**3).dim, name="dgramme3", dispname=f"{str(dgramme)}^3", scale=dgramme.scale*3) Ggramme2 = Unit.create((Ggramme**2).dim, name="Ggramme2", dispname=f"{str(Ggramme)}^2", scale=Ggramme.scale*2) Ggramme3 = Unit.create((Ggramme**3).dim, name="Ggramme3", dispname=f"{str(Ggramme)}^3", scale=Ggramme.scale*3) fgramme2 = Unit.create((fgramme**2).dim, name="fgramme2", dispname=f"{str(fgramme)}^2", scale=fgramme.scale*2) fgramme3 = Unit.create((fgramme**3).dim, name="fgramme3", dispname=f"{str(fgramme)}^3", scale=fgramme.scale*3) hgramme2 = Unit.create((hgramme**2).dim, name="hgramme2", dispname=f"{str(hgramme)}^2", scale=hgramme.scale*2) hgramme3 = Unit.create((hgramme**3).dim, name="hgramme3", dispname=f"{str(hgramme)}^3", scale=hgramme.scale*3) dagramme2 = Unit.create((dagramme**2).dim, name="dagramme2", dispname=f"{str(dagramme)}^2", scale=dagramme.scale*2) dagramme3 = Unit.create((dagramme**3).dim, name="dagramme3", dispname=f"{str(dagramme)}^3", scale=dagramme.scale*3) mgramme2 = Unit.create((mgramme**2).dim, name="mgramme2", dispname=f"{str(mgramme)}^2", scale=mgramme.scale*2) mgramme3 = Unit.create((mgramme**3).dim, name="mgramme3", dispname=f"{str(mgramme)}^3", scale=mgramme.scale*3) ngramme2 = Unit.create((ngramme**2).dim, name="ngramme2", dispname=f"{str(ngramme)}^2", scale=ngramme.scale*2) ngramme3 = Unit.create((ngramme**3).dim, name="ngramme3", dispname=f"{str(ngramme)}^3", scale=ngramme.scale*3) pgramme2 = Unit.create((pgramme**2).dim, name="pgramme2", dispname=f"{str(pgramme)}^2", scale=pgramme.scale*2) pgramme3 = Unit.create((pgramme**3).dim, name="pgramme3", dispname=f"{str(pgramme)}^3", scale=pgramme.scale*3) ugramme2 = Unit.create((ugramme**2).dim, name="ugramme2", dispname=f"{str(ugramme)}^2", scale=ugramme.scale*2) ugramme3 = Unit.create((ugramme**3).dim, name="ugramme3", dispname=f"{str(ugramme)}^3", scale=ugramme.scale*3) Tgramme2 = Unit.create((Tgramme**2).dim, name="Tgramme2", dispname=f"{str(Tgramme)}^2", scale=Tgramme.scale*2) Tgramme3 = Unit.create((Tgramme**3).dim, name="Tgramme3", dispname=f"{str(Tgramme)}^3", scale=Tgramme.scale*3) ygramme2 = Unit.create((ygramme**2).dim, name="ygramme2", dispname=f"{str(ygramme)}^2", scale=ygramme.scale*2) ygramme3 = Unit.create((ygramme**3).dim, name="ygramme3", dispname=f"{str(ygramme)}^3", scale=ygramme.scale*3) Egramme2 = Unit.create((Egramme**2).dim, name="Egramme2", dispname=f"{str(Egramme)}^2", scale=Egramme.scale*2) Egramme3 = Unit.create((Egramme**3).dim, name="Egramme3", dispname=f"{str(Egramme)}^3", scale=Egramme.scale*3) zgramme2 = Unit.create((zgramme**2).dim, name="zgramme2", dispname=f"{str(zgramme)}^2", scale=zgramme.scale*2) zgramme3 = Unit.create((zgramme**3).dim, name="zgramme3", dispname=f"{str(zgramme)}^3", scale=zgramme.scale*3) Mgramme2 = Unit.create((Mgramme**2).dim, name="Mgramme2", dispname=f"{str(Mgramme)}^2", scale=Mgramme.scale*2) Mgramme3 = Unit.create((Mgramme**3).dim, name="Mgramme3", dispname=f"{str(Mgramme)}^3", scale=Mgramme.scale*3) kgramme2 = Unit.create((kgramme**2).dim, name="kgramme2", dispname=f"{str(kgramme)}^2", scale=kgramme.scale*2) kgramme3 = Unit.create((kgramme**3).dim, name="kgramme3", dispname=f"{str(kgramme)}^3", scale=kgramme.scale*3) Ygramme2 = Unit.create((Ygramme**2).dim, name="Ygramme2", dispname=f"{str(Ygramme)}^2", scale=Ygramme.scale*2) Ygramme3 = Unit.create((Ygramme**3).dim, name="Ygramme3", dispname=f"{str(Ygramme)}^3", scale=Ygramme.scale*3) amolar2 = Unit.create((amolar**2).dim, name="amolar2", dispname=f"{str(amolar)}^2", scale=amolar.scale*2) amolar3 = Unit.create((amolar**3).dim, name="amolar3", dispname=f"{str(amolar)}^3", scale=amolar.scale*3) cmolar2 = Unit.create((cmolar**2).dim, name="cmolar2", dispname=f"{str(cmolar)}^2", scale=cmolar.scale*2) cmolar3 = Unit.create((cmolar**3).dim, name="cmolar3", dispname=f"{str(cmolar)}^3", scale=cmolar.scale*3) Zmolar2 = Unit.create((Zmolar**2).dim, name="Zmolar2", dispname=f"{str(Zmolar)}^2", scale=Zmolar.scale*2) Zmolar3 = Unit.create((Zmolar**3).dim, name="Zmolar3", dispname=f"{str(Zmolar)}^3", scale=Zmolar.scale*3) Pmolar2 = Unit.create((Pmolar**2).dim, name="Pmolar2", dispname=f"{str(Pmolar)}^2", scale=Pmolar.scale*2) Pmolar3 = Unit.create((Pmolar**3).dim, name="Pmolar3", dispname=f"{str(Pmolar)}^3", scale=Pmolar.scale*3) dmolar2 = Unit.create((dmolar**2).dim, name="dmolar2", dispname=f"{str(dmolar)}^2", scale=dmolar.scale*2) dmolar3 = Unit.create((dmolar**3).dim, name="dmolar3", dispname=f"{str(dmolar)}^3", scale=dmolar.scale*3) Gmolar2 = Unit.create((Gmolar**2).dim, name="Gmolar2", dispname=f"{str(Gmolar)}^2", scale=Gmolar.scale*2) Gmolar3 = Unit.create((Gmolar**3).dim, name="Gmolar3", dispname=f"{str(Gmolar)}^3", scale=Gmolar.scale*3) fmolar2 = Unit.create((fmolar**2).dim, name="fmolar2", dispname=f"{str(fmolar)}^2", scale=fmolar.scale*2) fmolar3 = Unit.create((fmolar**3).dim, name="fmolar3", dispname=f"{str(fmolar)}^3", scale=fmolar.scale*3) hmolar2 = Unit.create((hmolar**2).dim, name="hmolar2", dispname=f"{str(hmolar)}^2", scale=hmolar.scale*2) hmolar3 = Unit.create((hmolar**3).dim, name="hmolar3", dispname=f"{str(hmolar)}^3", scale=hmolar.scale*3) damolar2 = Unit.create((damolar**2).dim, name="damolar2", dispname=f"{str(damolar)}^2", scale=damolar.scale*2) damolar3 = Unit.create((damolar**3).dim, name="damolar3", dispname=f"{str(damolar)}^3", scale=damolar.scale*3) mmolar2 = Unit.create((mmolar**2).dim, name="mmolar2", dispname=f"{str(mmolar)}^2", scale=mmolar.scale*2) mmolar3 = Unit.create((mmolar**3).dim, name="mmolar3", dispname=f"{str(mmolar)}^3", scale=mmolar.scale*3) nmolar2 = Unit.create((nmolar**2).dim, name="nmolar2", dispname=f"{str(nmolar)}^2", scale=nmolar.scale*2) nmolar3 = Unit.create((nmolar**3).dim, name="nmolar3", dispname=f"{str(nmolar)}^3", scale=nmolar.scale*3) pmolar2 = Unit.create((pmolar**2).dim, name="pmolar2", dispname=f"{str(pmolar)}^2", scale=pmolar.scale*2) pmolar3 = Unit.create((pmolar**3).dim, name="pmolar3", dispname=f"{str(pmolar)}^3", scale=pmolar.scale*3) umolar2 = Unit.create((umolar**2).dim, name="umolar2", dispname=f"{str(umolar)}^2", scale=umolar.scale*2) umolar3 = Unit.create((umolar**3).dim, name="umolar3", dispname=f"{str(umolar)}^3", scale=umolar.scale*3) Tmolar2 = Unit.create((Tmolar**2).dim, name="Tmolar2", dispname=f"{str(Tmolar)}^2", scale=Tmolar.scale*2) Tmolar3 = Unit.create((Tmolar**3).dim, name="Tmolar3", dispname=f"{str(Tmolar)}^3", scale=Tmolar.scale*3) ymolar2 = Unit.create((ymolar**2).dim, name="ymolar2", dispname=f"{str(ymolar)}^2", scale=ymolar.scale*2) ymolar3 = Unit.create((ymolar**3).dim, name="ymolar3", dispname=f"{str(ymolar)}^3", scale=ymolar.scale*3) Emolar2 = Unit.create((Emolar**2).dim, name="Emolar2", dispname=f"{str(Emolar)}^2", scale=Emolar.scale*2) Emolar3 = Unit.create((Emolar**3).dim, name="Emolar3", dispname=f"{str(Emolar)}^3", scale=Emolar.scale*3) zmolar2 = Unit.create((zmolar**2).dim, name="zmolar2", dispname=f"{str(zmolar)}^2", scale=zmolar.scale*2) zmolar3 = Unit.create((zmolar**3).dim, name="zmolar3", dispname=f"{str(zmolar)}^3", scale=zmolar.scale*3) Mmolar2 = Unit.create((Mmolar**2).dim, name="Mmolar2", dispname=f"{str(Mmolar)}^2", scale=Mmolar.scale*2) Mmolar3 = Unit.create((Mmolar**3).dim, name="Mmolar3", dispname=f"{str(Mmolar)}^3", scale=Mmolar.scale*3) kmolar2 = Unit.create((kmolar**2).dim, name="kmolar2", dispname=f"{str(kmolar)}^2", scale=kmolar.scale*2) kmolar3 = Unit.create((kmolar**3).dim, name="kmolar3", dispname=f"{str(kmolar)}^3", scale=kmolar.scale*3) Ymolar2 = Unit.create((Ymolar**2).dim, name="Ymolar2", dispname=f"{str(Ymolar)}^2", scale=Ymolar.scale*2) Ymolar3 = Unit.create((Ymolar**3).dim, name="Ymolar3", dispname=f"{str(Ymolar)}^3", scale=Ymolar.scale*3) aradian2 = Unit.create((aradian**2).dim, name="aradian2", dispname=f"{str(aradian)}^2", scale=aradian.scale*2) aradian3 = Unit.create((aradian**3).dim, name="aradian3", dispname=f"{str(aradian)}^3", scale=aradian.scale*3) cradian2 = Unit.create((cradian**2).dim, name="cradian2", dispname=f"{str(cradian)}^2", scale=cradian.scale*2) cradian3 = Unit.create((cradian**3).dim, name="cradian3", dispname=f"{str(cradian)}^3", scale=cradian.scale*3) Zradian2 = Unit.create((Zradian**2).dim, name="Zradian2", dispname=f"{str(Zradian)}^2", scale=Zradian.scale*2) Zradian3 = Unit.create((Zradian**3).dim, name="Zradian3", dispname=f"{str(Zradian)}^3", scale=Zradian.scale*3) Pradian2 = Unit.create((Pradian**2).dim, name="Pradian2", dispname=f"{str(Pradian)}^2", scale=Pradian.scale*2) Pradian3 = Unit.create((Pradian**3).dim, name="Pradian3", dispname=f"{str(Pradian)}^3", scale=Pradian.scale*3) dradian2 = Unit.create((dradian**2).dim, name="dradian2", dispname=f"{str(dradian)}^2", scale=dradian.scale*2) dradian3 = Unit.create((dradian**3).dim, name="dradian3", dispname=f"{str(dradian)}^3", scale=dradian.scale*3) Gradian2 = Unit.create((Gradian**2).dim, name="Gradian2", dispname=f"{str(Gradian)}^2", scale=Gradian.scale*2) Gradian3 = Unit.create((Gradian**3).dim, name="Gradian3", dispname=f"{str(Gradian)}^3", scale=Gradian.scale*3) fradian2 = Unit.create((fradian**2).dim, name="fradian2", dispname=f"{str(fradian)}^2", scale=fradian.scale*2) fradian3 = Unit.create((fradian**3).dim, name="fradian3", dispname=f"{str(fradian)}^3", scale=fradian.scale*3) hradian2 = Unit.create((hradian**2).dim, name="hradian2", dispname=f"{str(hradian)}^2", scale=hradian.scale*2) hradian3 = Unit.create((hradian**3).dim, name="hradian3", dispname=f"{str(hradian)}^3", scale=hradian.scale*3) daradian2 = Unit.create((daradian**2).dim, name="daradian2", dispname=f"{str(daradian)}^2", scale=daradian.scale*2) daradian3 = Unit.create((daradian**3).dim, name="daradian3", dispname=f"{str(daradian)}^3", scale=daradian.scale*3) mradian2 = Unit.create((mradian**2).dim, name="mradian2", dispname=f"{str(mradian)}^2", scale=mradian.scale*2) mradian3 = Unit.create((mradian**3).dim, name="mradian3", dispname=f"{str(mradian)}^3", scale=mradian.scale*3) nradian2 = Unit.create((nradian**2).dim, name="nradian2", dispname=f"{str(nradian)}^2", scale=nradian.scale*2) nradian3 = Unit.create((nradian**3).dim, name="nradian3", dispname=f"{str(nradian)}^3", scale=nradian.scale*3) pradian2 = Unit.create((pradian**2).dim, name="pradian2", dispname=f"{str(pradian)}^2", scale=pradian.scale*2) pradian3 = Unit.create((pradian**3).dim, name="pradian3", dispname=f"{str(pradian)}^3", scale=pradian.scale*3) uradian2 = Unit.create((uradian**2).dim, name="uradian2", dispname=f"{str(uradian)}^2", scale=uradian.scale*2) uradian3 = Unit.create((uradian**3).dim, name="uradian3", dispname=f"{str(uradian)}^3", scale=uradian.scale*3) Tradian2 = Unit.create((Tradian**2).dim, name="Tradian2", dispname=f"{str(Tradian)}^2", scale=Tradian.scale*2) Tradian3 = Unit.create((Tradian**3).dim, name="Tradian3", dispname=f"{str(Tradian)}^3", scale=Tradian.scale*3) yradian2 = Unit.create((yradian**2).dim, name="yradian2", dispname=f"{str(yradian)}^2", scale=yradian.scale*2) yradian3 = Unit.create((yradian**3).dim, name="yradian3", dispname=f"{str(yradian)}^3", scale=yradian.scale*3) Eradian2 = Unit.create((Eradian**2).dim, name="Eradian2", dispname=f"{str(Eradian)}^2", scale=Eradian.scale*2) Eradian3 = Unit.create((Eradian**3).dim, name="Eradian3", dispname=f"{str(Eradian)}^3", scale=Eradian.scale*3) zradian2 = Unit.create((zradian**2).dim, name="zradian2", dispname=f"{str(zradian)}^2", scale=zradian.scale*2) zradian3 = Unit.create((zradian**3).dim, name="zradian3", dispname=f"{str(zradian)}^3", scale=zradian.scale*3) Mradian2 = Unit.create((Mradian**2).dim, name="Mradian2", dispname=f"{str(Mradian)}^2", scale=Mradian.scale*2) Mradian3 = Unit.create((Mradian**3).dim, name="Mradian3", dispname=f"{str(Mradian)}^3", scale=Mradian.scale*3) kradian2 = Unit.create((kradian**2).dim, name="kradian2", dispname=f"{str(kradian)}^2", scale=kradian.scale*2) kradian3 = Unit.create((kradian**3).dim, name="kradian3", dispname=f"{str(kradian)}^3", scale=kradian.scale*3) Yradian2 = Unit.create((Yradian**2).dim, name="Yradian2", dispname=f"{str(Yradian)}^2", scale=Yradian.scale*2) Yradian3 = Unit.create((Yradian**3).dim, name="Yradian3", dispname=f"{str(Yradian)}^3", scale=Yradian.scale*3) asteradian2 = Unit.create((asteradian**2).dim, name="asteradian2", dispname=f"{str(asteradian)}^2", scale=asteradian.scale*2) asteradian3 = Unit.create((asteradian**3).dim, name="asteradian3", dispname=f"{str(asteradian)}^3", scale=asteradian.scale*3) csteradian2 = Unit.create((csteradian**2).dim, name="csteradian2", dispname=f"{str(csteradian)}^2", scale=csteradian.scale*2) csteradian3 = Unit.create((csteradian**3).dim, name="csteradian3", dispname=f"{str(csteradian)}^3", scale=csteradian.scale*3) Zsteradian2 = Unit.create((Zsteradian**2).dim, name="Zsteradian2", dispname=f"{str(Zsteradian)}^2", scale=Zsteradian.scale*2) Zsteradian3 = Unit.create((Zsteradian**3).dim, name="Zsteradian3", dispname=f"{str(Zsteradian)}^3", scale=Zsteradian.scale*3) Psteradian2 = Unit.create((Psteradian**2).dim, name="Psteradian2", dispname=f"{str(Psteradian)}^2", scale=Psteradian.scale*2) Psteradian3 = Unit.create((Psteradian**3).dim, name="Psteradian3", dispname=f"{str(Psteradian)}^3", scale=Psteradian.scale*3) dsteradian2 = Unit.create((dsteradian**2).dim, name="dsteradian2", dispname=f"{str(dsteradian)}^2", scale=dsteradian.scale*2) dsteradian3 = Unit.create((dsteradian**3).dim, name="dsteradian3", dispname=f"{str(dsteradian)}^3", scale=dsteradian.scale*3) Gsteradian2 = Unit.create((Gsteradian**2).dim, name="Gsteradian2", dispname=f"{str(Gsteradian)}^2", scale=Gsteradian.scale*2) Gsteradian3 = Unit.create((Gsteradian**3).dim, name="Gsteradian3", dispname=f"{str(Gsteradian)}^3", scale=Gsteradian.scale*3) fsteradian2 = Unit.create((fsteradian**2).dim, name="fsteradian2", dispname=f"{str(fsteradian)}^2", scale=fsteradian.scale*2) fsteradian3 = Unit.create((fsteradian**3).dim, name="fsteradian3", dispname=f"{str(fsteradian)}^3", scale=fsteradian.scale*3) hsteradian2 = Unit.create((hsteradian**2).dim, name="hsteradian2", dispname=f"{str(hsteradian)}^2", scale=hsteradian.scale*2) hsteradian3 = Unit.create((hsteradian**3).dim, name="hsteradian3", dispname=f"{str(hsteradian)}^3", scale=hsteradian.scale*3) dasteradian2 = Unit.create((dasteradian**2).dim, name="dasteradian2", dispname=f"{str(dasteradian)}^2", scale=dasteradian.scale*2) dasteradian3 = Unit.create((dasteradian**3).dim, name="dasteradian3", dispname=f"{str(dasteradian)}^3", scale=dasteradian.scale*3) msteradian2 = Unit.create((msteradian**2).dim, name="msteradian2", dispname=f"{str(msteradian)}^2", scale=msteradian.scale*2) msteradian3 = Unit.create((msteradian**3).dim, name="msteradian3", dispname=f"{str(msteradian)}^3", scale=msteradian.scale*3) nsteradian2 = Unit.create((nsteradian**2).dim, name="nsteradian2", dispname=f"{str(nsteradian)}^2", scale=nsteradian.scale*2) nsteradian3 = Unit.create((nsteradian**3).dim, name="nsteradian3", dispname=f"{str(nsteradian)}^3", scale=nsteradian.scale*3) psteradian2 = Unit.create((psteradian**2).dim, name="psteradian2", dispname=f"{str(psteradian)}^2", scale=psteradian.scale*2) psteradian3 = Unit.create((psteradian**3).dim, name="psteradian3", dispname=f"{str(psteradian)}^3", scale=psteradian.scale*3) usteradian2 = Unit.create((usteradian**2).dim, name="usteradian2", dispname=f"{str(usteradian)}^2", scale=usteradian.scale*2) usteradian3 = Unit.create((usteradian**3).dim, name="usteradian3", dispname=f"{str(usteradian)}^3", scale=usteradian.scale*3) Tsteradian2 = Unit.create((Tsteradian**2).dim, name="Tsteradian2", dispname=f"{str(Tsteradian)}^2", scale=Tsteradian.scale*2) Tsteradian3 = Unit.create((Tsteradian**3).dim, name="Tsteradian3", dispname=f"{str(Tsteradian)}^3", scale=Tsteradian.scale*3) ysteradian2 = Unit.create((ysteradian**2).dim, name="ysteradian2", dispname=f"{str(ysteradian)}^2", scale=ysteradian.scale*2) ysteradian3 = Unit.create((ysteradian**3).dim, name="ysteradian3", dispname=f"{str(ysteradian)}^3", scale=ysteradian.scale*3) Esteradian2 = Unit.create((Esteradian**2).dim, name="Esteradian2", dispname=f"{str(Esteradian)}^2", scale=Esteradian.scale*2) Esteradian3 = Unit.create((Esteradian**3).dim, name="Esteradian3", dispname=f"{str(Esteradian)}^3", scale=Esteradian.scale*3) zsteradian2 = Unit.create((zsteradian**2).dim, name="zsteradian2", dispname=f"{str(zsteradian)}^2", scale=zsteradian.scale*2) zsteradian3 = Unit.create((zsteradian**3).dim, name="zsteradian3", dispname=f"{str(zsteradian)}^3", scale=zsteradian.scale*3) Msteradian2 = Unit.create((Msteradian**2).dim, name="Msteradian2", dispname=f"{str(Msteradian)}^2", scale=Msteradian.scale*2) Msteradian3 = Unit.create((Msteradian**3).dim, name="Msteradian3", dispname=f"{str(Msteradian)}^3", scale=Msteradian.scale*3) ksteradian2 = Unit.create((ksteradian**2).dim, name="ksteradian2", dispname=f"{str(ksteradian)}^2", scale=ksteradian.scale*2) ksteradian3 = Unit.create((ksteradian**3).dim, name="ksteradian3", dispname=f"{str(ksteradian)}^3", scale=ksteradian.scale*3) Ysteradian2 = Unit.create((Ysteradian**2).dim, name="Ysteradian2", dispname=f"{str(Ysteradian)}^2", scale=Ysteradian.scale*2) Ysteradian3 = Unit.create((Ysteradian**3).dim, name="Ysteradian3", dispname=f"{str(Ysteradian)}^3", scale=Ysteradian.scale*3) ahertz2 = Unit.create((ahertz**2).dim, name="ahertz2", dispname=f"{str(ahertz)}^2", scale=ahertz.scale*2) ahertz3 = Unit.create((ahertz**3).dim, name="ahertz3", dispname=f"{str(ahertz)}^3", scale=ahertz.scale*3) chertz2 = Unit.create((chertz**2).dim, name="chertz2", dispname=f"{str(chertz)}^2", scale=chertz.scale*2) chertz3 = Unit.create((chertz**3).dim, name="chertz3", dispname=f"{str(chertz)}^3", scale=chertz.scale*3) Zhertz2 = Unit.create((Zhertz**2).dim, name="Zhertz2", dispname=f"{str(Zhertz)}^2", scale=Zhertz.scale*2) Zhertz3 = Unit.create((Zhertz**3).dim, name="Zhertz3", dispname=f"{str(Zhertz)}^3", scale=Zhertz.scale*3) Phertz2 = Unit.create((Phertz**2).dim, name="Phertz2", dispname=f"{str(Phertz)}^2", scale=Phertz.scale*2) Phertz3 = Unit.create((Phertz**3).dim, name="Phertz3", dispname=f"{str(Phertz)}^3", scale=Phertz.scale*3) dhertz2 = Unit.create((dhertz**2).dim, name="dhertz2", dispname=f"{str(dhertz)}^2", scale=dhertz.scale*2) dhertz3 = Unit.create((dhertz**3).dim, name="dhertz3", dispname=f"{str(dhertz)}^3", scale=dhertz.scale*3) Ghertz2 = Unit.create((Ghertz**2).dim, name="Ghertz2", dispname=f"{str(Ghertz)}^2", scale=Ghertz.scale*2) Ghertz3 = Unit.create((Ghertz**3).dim, name="Ghertz3", dispname=f"{str(Ghertz)}^3", scale=Ghertz.scale*3) fhertz2 = Unit.create((fhertz**2).dim, name="fhertz2", dispname=f"{str(fhertz)}^2", scale=fhertz.scale*2) fhertz3 = Unit.create((fhertz**3).dim, name="fhertz3", dispname=f"{str(fhertz)}^3", scale=fhertz.scale*3) hhertz2 = Unit.create((hhertz**2).dim, name="hhertz2", dispname=f"{str(hhertz)}^2", scale=hhertz.scale*2) hhertz3 = Unit.create((hhertz**3).dim, name="hhertz3", dispname=f"{str(hhertz)}^3", scale=hhertz.scale*3) dahertz2 = Unit.create((dahertz**2).dim, name="dahertz2", dispname=f"{str(dahertz)}^2", scale=dahertz.scale*2) dahertz3 = Unit.create((dahertz**3).dim, name="dahertz3", dispname=f"{str(dahertz)}^3", scale=dahertz.scale*3) mhertz2 = Unit.create((mhertz**2).dim, name="mhertz2", dispname=f"{str(mhertz)}^2", scale=mhertz.scale*2) mhertz3 = Unit.create((mhertz**3).dim, name="mhertz3", dispname=f"{str(mhertz)}^3", scale=mhertz.scale*3) nhertz2 = Unit.create((nhertz**2).dim, name="nhertz2", dispname=f"{str(nhertz)}^2", scale=nhertz.scale*2) nhertz3 = Unit.create((nhertz**3).dim, name="nhertz3", dispname=f"{str(nhertz)}^3", scale=nhertz.scale*3) phertz2 = Unit.create((phertz**2).dim, name="phertz2", dispname=f"{str(phertz)}^2", scale=phertz.scale*2) phertz3 = Unit.create((phertz**3).dim, name="phertz3", dispname=f"{str(phertz)}^3", scale=phertz.scale*3) uhertz2 = Unit.create((uhertz**2).dim, name="uhertz2", dispname=f"{str(uhertz)}^2", scale=uhertz.scale*2) uhertz3 = Unit.create((uhertz**3).dim, name="uhertz3", dispname=f"{str(uhertz)}^3", scale=uhertz.scale*3) Thertz2 = Unit.create((Thertz**2).dim, name="Thertz2", dispname=f"{str(Thertz)}^2", scale=Thertz.scale*2) Thertz3 = Unit.create((Thertz**3).dim, name="Thertz3", dispname=f"{str(Thertz)}^3", scale=Thertz.scale*3) yhertz2 = Unit.create((yhertz**2).dim, name="yhertz2", dispname=f"{str(yhertz)}^2", scale=yhertz.scale*2) yhertz3 = Unit.create((yhertz**3).dim, name="yhertz3", dispname=f"{str(yhertz)}^3", scale=yhertz.scale*3) Ehertz2 = Unit.create((Ehertz**2).dim, name="Ehertz2", dispname=f"{str(Ehertz)}^2", scale=Ehertz.scale*2) Ehertz3 = Unit.create((Ehertz**3).dim, name="Ehertz3", dispname=f"{str(Ehertz)}^3", scale=Ehertz.scale*3) zhertz2 = Unit.create((zhertz**2).dim, name="zhertz2", dispname=f"{str(zhertz)}^2", scale=zhertz.scale*2) zhertz3 = Unit.create((zhertz**3).dim, name="zhertz3", dispname=f"{str(zhertz)}^3", scale=zhertz.scale*3) Mhertz2 = Unit.create((Mhertz**2).dim, name="Mhertz2", dispname=f"{str(Mhertz)}^2", scale=Mhertz.scale*2) Mhertz3 = Unit.create((Mhertz**3).dim, name="Mhertz3", dispname=f"{str(Mhertz)}^3", scale=Mhertz.scale*3) khertz2 = Unit.create((khertz**2).dim, name="khertz2", dispname=f"{str(khertz)}^2", scale=khertz.scale*2) khertz3 = Unit.create((khertz**3).dim, name="khertz3", dispname=f"{str(khertz)}^3", scale=khertz.scale*3) Yhertz2 = Unit.create((Yhertz**2).dim, name="Yhertz2", dispname=f"{str(Yhertz)}^2", scale=Yhertz.scale*2) Yhertz3 = Unit.create((Yhertz**3).dim, name="Yhertz3", dispname=f"{str(Yhertz)}^3", scale=Yhertz.scale*3) anewton2 = Unit.create((anewton**2).dim, name="anewton2", dispname=f"{str(anewton)}^2", scale=anewton.scale*2) anewton3 = Unit.create((anewton**3).dim, name="anewton3", dispname=f"{str(anewton)}^3", scale=anewton.scale*3) cnewton2 = Unit.create((cnewton**2).dim, name="cnewton2", dispname=f"{str(cnewton)}^2", scale=cnewton.scale*2) cnewton3 = Unit.create((cnewton**3).dim, name="cnewton3", dispname=f"{str(cnewton)}^3", scale=cnewton.scale*3) Znewton2 = Unit.create((Znewton**2).dim, name="Znewton2", dispname=f"{str(Znewton)}^2", scale=Znewton.scale*2) Znewton3 = Unit.create((Znewton**3).dim, name="Znewton3", dispname=f"{str(Znewton)}^3", scale=Znewton.scale*3) Pnewton2 = Unit.create((Pnewton**2).dim, name="Pnewton2", dispname=f"{str(Pnewton)}^2", scale=Pnewton.scale*2) Pnewton3 = Unit.create((Pnewton**3).dim, name="Pnewton3", dispname=f"{str(Pnewton)}^3", scale=Pnewton.scale*3) dnewton2 = Unit.create((dnewton**2).dim, name="dnewton2", dispname=f"{str(dnewton)}^2", scale=dnewton.scale*2) dnewton3 = Unit.create((dnewton**3).dim, name="dnewton3", dispname=f"{str(dnewton)}^3", scale=dnewton.scale*3) Gnewton2 = Unit.create((Gnewton**2).dim, name="Gnewton2", dispname=f"{str(Gnewton)}^2", scale=Gnewton.scale*2) Gnewton3 = Unit.create((Gnewton**3).dim, name="Gnewton3", dispname=f"{str(Gnewton)}^3", scale=Gnewton.scale*3) fnewton2 = Unit.create((fnewton**2).dim, name="fnewton2", dispname=f"{str(fnewton)}^2", scale=fnewton.scale*2) fnewton3 = Unit.create((fnewton**3).dim, name="fnewton3", dispname=f"{str(fnewton)}^3", scale=fnewton.scale*3) hnewton2 = Unit.create((hnewton**2).dim, name="hnewton2", dispname=f"{str(hnewton)}^2", scale=hnewton.scale*2) hnewton3 = Unit.create((hnewton**3).dim, name="hnewton3", dispname=f"{str(hnewton)}^3", scale=hnewton.scale*3) danewton2 = Unit.create((danewton**2).dim, name="danewton2", dispname=f"{str(danewton)}^2", scale=danewton.scale*2) danewton3 = Unit.create((danewton**3).dim, name="danewton3", dispname=f"{str(danewton)}^3", scale=danewton.scale*3) mnewton2 = Unit.create((mnewton**2).dim, name="mnewton2", dispname=f"{str(mnewton)}^2", scale=mnewton.scale*2) mnewton3 = Unit.create((mnewton**3).dim, name="mnewton3", dispname=f"{str(mnewton)}^3", scale=mnewton.scale*3) nnewton2 = Unit.create((nnewton**2).dim, name="nnewton2", dispname=f"{str(nnewton)}^2", scale=nnewton.scale*2) nnewton3 = Unit.create((nnewton**3).dim, name="nnewton3", dispname=f"{str(nnewton)}^3", scale=nnewton.scale*3) pnewton2 = Unit.create((pnewton**2).dim, name="pnewton2", dispname=f"{str(pnewton)}^2", scale=pnewton.scale*2) pnewton3 = Unit.create((pnewton**3).dim, name="pnewton3", dispname=f"{str(pnewton)}^3", scale=pnewton.scale*3) unewton2 = Unit.create((unewton**2).dim, name="unewton2", dispname=f"{str(unewton)}^2", scale=unewton.scale*2) unewton3 = Unit.create((unewton**3).dim, name="unewton3", dispname=f"{str(unewton)}^3", scale=unewton.scale*3) Tnewton2 = Unit.create((Tnewton**2).dim, name="Tnewton2", dispname=f"{str(Tnewton)}^2", scale=Tnewton.scale*2) Tnewton3 = Unit.create((Tnewton**3).dim, name="Tnewton3", dispname=f"{str(Tnewton)}^3", scale=Tnewton.scale*3) ynewton2 = Unit.create((ynewton**2).dim, name="ynewton2", dispname=f"{str(ynewton)}^2", scale=ynewton.scale*2) ynewton3 = Unit.create((ynewton**3).dim, name="ynewton3", dispname=f"{str(ynewton)}^3", scale=ynewton.scale*3) Enewton2 = Unit.create((Enewton**2).dim, name="Enewton2", dispname=f"{str(Enewton)}^2", scale=Enewton.scale*2) Enewton3 = Unit.create((Enewton**3).dim, name="Enewton3", dispname=f"{str(Enewton)}^3", scale=Enewton.scale*3) znewton2 = Unit.create((znewton**2).dim, name="znewton2", dispname=f"{str(znewton)}^2", scale=znewton.scale*2) znewton3 = Unit.create((znewton**3).dim, name="znewton3", dispname=f"{str(znewton)}^3", scale=znewton.scale*3) Mnewton2 = Unit.create((Mnewton**2).dim, name="Mnewton2", dispname=f"{str(Mnewton)}^2", scale=Mnewton.scale*2) Mnewton3 = Unit.create((Mnewton**3).dim, name="Mnewton3", dispname=f"{str(Mnewton)}^3", scale=Mnewton.scale*3) knewton2 = Unit.create((knewton**2).dim, name="knewton2", dispname=f"{str(knewton)}^2", scale=knewton.scale*2) knewton3 = Unit.create((knewton**3).dim, name="knewton3", dispname=f"{str(knewton)}^3", scale=knewton.scale*3) Ynewton2 = Unit.create((Ynewton**2).dim, name="Ynewton2", dispname=f"{str(Ynewton)}^2", scale=Ynewton.scale*2) Ynewton3 = Unit.create((Ynewton**3).dim, name="Ynewton3", dispname=f"{str(Ynewton)}^3", scale=Ynewton.scale*3) apascal2 = Unit.create((apascal**2).dim, name="apascal2", dispname=f"{str(apascal)}^2", scale=apascal.scale*2) apascal3 = Unit.create((apascal**3).dim, name="apascal3", dispname=f"{str(apascal)}^3", scale=apascal.scale*3) cpascal2 = Unit.create((cpascal**2).dim, name="cpascal2", dispname=f"{str(cpascal)}^2", scale=cpascal.scale*2) cpascal3 = Unit.create((cpascal**3).dim, name="cpascal3", dispname=f"{str(cpascal)}^3", scale=cpascal.scale*3) Zpascal2 = Unit.create((Zpascal**2).dim, name="Zpascal2", dispname=f"{str(Zpascal)}^2", scale=Zpascal.scale*2) Zpascal3 = Unit.create((Zpascal**3).dim, name="Zpascal3", dispname=f"{str(Zpascal)}^3", scale=Zpascal.scale*3) Ppascal2 = Unit.create((Ppascal**2).dim, name="Ppascal2", dispname=f"{str(Ppascal)}^2", scale=Ppascal.scale*2) Ppascal3 = Unit.create((Ppascal**3).dim, name="Ppascal3", dispname=f"{str(Ppascal)}^3", scale=Ppascal.scale*3) dpascal2 = Unit.create((dpascal**2).dim, name="dpascal2", dispname=f"{str(dpascal)}^2", scale=dpascal.scale*2) dpascal3 = Unit.create((dpascal**3).dim, name="dpascal3", dispname=f"{str(dpascal)}^3", scale=dpascal.scale*3) Gpascal2 = Unit.create((Gpascal**2).dim, name="Gpascal2", dispname=f"{str(Gpascal)}^2", scale=Gpascal.scale*2) Gpascal3 = Unit.create((Gpascal**3).dim, name="Gpascal3", dispname=f"{str(Gpascal)}^3", scale=Gpascal.scale*3) fpascal2 = Unit.create((fpascal**2).dim, name="fpascal2", dispname=f"{str(fpascal)}^2", scale=fpascal.scale*2) fpascal3 = Unit.create((fpascal**3).dim, name="fpascal3", dispname=f"{str(fpascal)}^3", scale=fpascal.scale*3) hpascal2 = Unit.create((hpascal**2).dim, name="hpascal2", dispname=f"{str(hpascal)}^2", scale=hpascal.scale*2) hpascal3 = Unit.create((hpascal**3).dim, name="hpascal3", dispname=f"{str(hpascal)}^3", scale=hpascal.scale*3) dapascal2 = Unit.create((dapascal**2).dim, name="dapascal2", dispname=f"{str(dapascal)}^2", scale=dapascal.scale*2) dapascal3 = Unit.create((dapascal**3).dim, name="dapascal3", dispname=f"{str(dapascal)}^3", scale=dapascal.scale*3) mpascal2 = Unit.create((mpascal**2).dim, name="mpascal2", dispname=f"{str(mpascal)}^2", scale=mpascal.scale*2) mpascal3 = Unit.create((mpascal**3).dim, name="mpascal3", dispname=f"{str(mpascal)}^3", scale=mpascal.scale*3) npascal2 = Unit.create((npascal**2).dim, name="npascal2", dispname=f"{str(npascal)}^2", scale=npascal.scale*2) npascal3 = Unit.create((npascal**3).dim, name="npascal3", dispname=f"{str(npascal)}^3", scale=npascal.scale*3) ppascal2 = Unit.create((ppascal**2).dim, name="ppascal2", dispname=f"{str(ppascal)}^2", scale=ppascal.scale*2) ppascal3 = Unit.create((ppascal**3).dim, name="ppascal3", dispname=f"{str(ppascal)}^3", scale=ppascal.scale*3) upascal2 = Unit.create((upascal**2).dim, name="upascal2", dispname=f"{str(upascal)}^2", scale=upascal.scale*2) upascal3 = Unit.create((upascal**3).dim, name="upascal3", dispname=f"{str(upascal)}^3", scale=upascal.scale*3) Tpascal2 = Unit.create((Tpascal**2).dim, name="Tpascal2", dispname=f"{str(Tpascal)}^2", scale=Tpascal.scale*2) Tpascal3 = Unit.create((Tpascal**3).dim, name="Tpascal3", dispname=f"{str(Tpascal)}^3", scale=Tpascal.scale*3) ypascal2 = Unit.create((ypascal**2).dim, name="ypascal2", dispname=f"{str(ypascal)}^2", scale=ypascal.scale*2) ypascal3 = Unit.create((ypascal**3).dim, name="ypascal3", dispname=f"{str(ypascal)}^3", scale=ypascal.scale*3) Epascal2 = Unit.create((Epascal**2).dim, name="Epascal2", dispname=f"{str(Epascal)}^2", scale=Epascal.scale*2) Epascal3 = Unit.create((Epascal**3).dim, name="Epascal3", dispname=f"{str(Epascal)}^3", scale=Epascal.scale*3) zpascal2 = Unit.create((zpascal**2).dim, name="zpascal2", dispname=f"{str(zpascal)}^2", scale=zpascal.scale*2) zpascal3 = Unit.create((zpascal**3).dim, name="zpascal3", dispname=f"{str(zpascal)}^3", scale=zpascal.scale*3) Mpascal2 = Unit.create((Mpascal**2).dim, name="Mpascal2", dispname=f"{str(Mpascal)}^2", scale=Mpascal.scale*2) Mpascal3 = Unit.create((Mpascal**3).dim, name="Mpascal3", dispname=f"{str(Mpascal)}^3", scale=Mpascal.scale*3) kpascal2 = Unit.create((kpascal**2).dim, name="kpascal2", dispname=f"{str(kpascal)}^2", scale=kpascal.scale*2) kpascal3 = Unit.create((kpascal**3).dim, name="kpascal3", dispname=f"{str(kpascal)}^3", scale=kpascal.scale*3) Ypascal2 = Unit.create((Ypascal**2).dim, name="Ypascal2", dispname=f"{str(Ypascal)}^2", scale=Ypascal.scale*2) Ypascal3 = Unit.create((Ypascal**3).dim, name="Ypascal3", dispname=f"{str(Ypascal)}^3", scale=Ypascal.scale*3) ajoule2 = Unit.create((ajoule**2).dim, name="ajoule2", dispname=f"{str(ajoule)}^2", scale=ajoule.scale*2) ajoule3 = Unit.create((ajoule**3).dim, name="ajoule3", dispname=f"{str(ajoule)}^3", scale=ajoule.scale*3) cjoule2 = Unit.create((cjoule**2).dim, name="cjoule2", dispname=f"{str(cjoule)}^2", scale=cjoule.scale*2) cjoule3 = Unit.create((cjoule**3).dim, name="cjoule3", dispname=f"{str(cjoule)}^3", scale=cjoule.scale*3) Zjoule2 = Unit.create((Zjoule**2).dim, name="Zjoule2", dispname=f"{str(Zjoule)}^2", scale=Zjoule.scale*2) Zjoule3 = Unit.create((Zjoule**3).dim, name="Zjoule3", dispname=f"{str(Zjoule)}^3", scale=Zjoule.scale*3) Pjoule2 = Unit.create((Pjoule**2).dim, name="Pjoule2", dispname=f"{str(Pjoule)}^2", scale=Pjoule.scale*2) Pjoule3 = Unit.create((Pjoule**3).dim, name="Pjoule3", dispname=f"{str(Pjoule)}^3", scale=Pjoule.scale*3) djoule2 = Unit.create((djoule**2).dim, name="djoule2", dispname=f"{str(djoule)}^2", scale=djoule.scale*2) djoule3 = Unit.create((djoule**3).dim, name="djoule3", dispname=f"{str(djoule)}^3", scale=djoule.scale*3) Gjoule2 = Unit.create((Gjoule**2).dim, name="Gjoule2", dispname=f"{str(Gjoule)}^2", scale=Gjoule.scale*2) Gjoule3 = Unit.create((Gjoule**3).dim, name="Gjoule3", dispname=f"{str(Gjoule)}^3", scale=Gjoule.scale*3) fjoule2 = Unit.create((fjoule**2).dim, name="fjoule2", dispname=f"{str(fjoule)}^2", scale=fjoule.scale*2) fjoule3 = Unit.create((fjoule**3).dim, name="fjoule3", dispname=f"{str(fjoule)}^3", scale=fjoule.scale*3) hjoule2 = Unit.create((hjoule**2).dim, name="hjoule2", dispname=f"{str(hjoule)}^2", scale=hjoule.scale*2) hjoule3 = Unit.create((hjoule**3).dim, name="hjoule3", dispname=f"{str(hjoule)}^3", scale=hjoule.scale*3) dajoule2 = Unit.create((dajoule**2).dim, name="dajoule2", dispname=f"{str(dajoule)}^2", scale=dajoule.scale*2) dajoule3 = Unit.create((dajoule**3).dim, name="dajoule3", dispname=f"{str(dajoule)}^3", scale=dajoule.scale*3) mjoule2 = Unit.create((mjoule**2).dim, name="mjoule2", dispname=f"{str(mjoule)}^2", scale=mjoule.scale*2) mjoule3 = Unit.create((mjoule**3).dim, name="mjoule3", dispname=f"{str(mjoule)}^3", scale=mjoule.scale*3) njoule2 = Unit.create((njoule**2).dim, name="njoule2", dispname=f"{str(njoule)}^2", scale=njoule.scale*2) njoule3 = Unit.create((njoule**3).dim, name="njoule3", dispname=f"{str(njoule)}^3", scale=njoule.scale*3) pjoule2 = Unit.create((pjoule**2).dim, name="pjoule2", dispname=f"{str(pjoule)}^2", scale=pjoule.scale*2) pjoule3 = Unit.create((pjoule**3).dim, name="pjoule3", dispname=f"{str(pjoule)}^3", scale=pjoule.scale*3) ujoule2 = Unit.create((ujoule**2).dim, name="ujoule2", dispname=f"{str(ujoule)}^2", scale=ujoule.scale*2) ujoule3 = Unit.create((ujoule**3).dim, name="ujoule3", dispname=f"{str(ujoule)}^3", scale=ujoule.scale*3) Tjoule2 = Unit.create((Tjoule**2).dim, name="Tjoule2", dispname=f"{str(Tjoule)}^2", scale=Tjoule.scale*2) Tjoule3 = Unit.create((Tjoule**3).dim, name="Tjoule3", dispname=f"{str(Tjoule)}^3", scale=Tjoule.scale*3) yjoule2 = Unit.create((yjoule**2).dim, name="yjoule2", dispname=f"{str(yjoule)}^2", scale=yjoule.scale*2) yjoule3 = Unit.create((yjoule**3).dim, name="yjoule3", dispname=f"{str(yjoule)}^3", scale=yjoule.scale*3) Ejoule2 = Unit.create((Ejoule**2).dim, name="Ejoule2", dispname=f"{str(Ejoule)}^2", scale=Ejoule.scale*2) Ejoule3 = Unit.create((Ejoule**3).dim, name="Ejoule3", dispname=f"{str(Ejoule)}^3", scale=Ejoule.scale*3) zjoule2 = Unit.create((zjoule**2).dim, name="zjoule2", dispname=f"{str(zjoule)}^2", scale=zjoule.scale*2) zjoule3 = Unit.create((zjoule**3).dim, name="zjoule3", dispname=f"{str(zjoule)}^3", scale=zjoule.scale*3) Mjoule2 = Unit.create((Mjoule**2).dim, name="Mjoule2", dispname=f"{str(Mjoule)}^2", scale=Mjoule.scale*2) Mjoule3 = Unit.create((Mjoule**3).dim, name="Mjoule3", dispname=f"{str(Mjoule)}^3", scale=Mjoule.scale*3) kjoule2 = Unit.create((kjoule**2).dim, name="kjoule2", dispname=f"{str(kjoule)}^2", scale=kjoule.scale*2) kjoule3 = Unit.create((kjoule**3).dim, name="kjoule3", dispname=f"{str(kjoule)}^3", scale=kjoule.scale*3) Yjoule2 = Unit.create((Yjoule**2).dim, name="Yjoule2", dispname=f"{str(Yjoule)}^2", scale=Yjoule.scale*2) Yjoule3 = Unit.create((Yjoule**3).dim, name="Yjoule3", dispname=f"{str(Yjoule)}^3", scale=Yjoule.scale*3) awatt2 = Unit.create((awatt**2).dim, name="awatt2", dispname=f"{str(awatt)}^2", scale=awatt.scale*2) awatt3 = Unit.create((awatt**3).dim, name="awatt3", dispname=f"{str(awatt)}^3", scale=awatt.scale*3) cwatt2 = Unit.create((cwatt**2).dim, name="cwatt2", dispname=f"{str(cwatt)}^2", scale=cwatt.scale*2) cwatt3 = Unit.create((cwatt**3).dim, name="cwatt3", dispname=f"{str(cwatt)}^3", scale=cwatt.scale*3) Zwatt2 = Unit.create((Zwatt**2).dim, name="Zwatt2", dispname=f"{str(Zwatt)}^2", scale=Zwatt.scale*2) Zwatt3 = Unit.create((Zwatt**3).dim, name="Zwatt3", dispname=f"{str(Zwatt)}^3", scale=Zwatt.scale*3) Pwatt2 = Unit.create((Pwatt**2).dim, name="Pwatt2", dispname=f"{str(Pwatt)}^2", scale=Pwatt.scale*2) Pwatt3 = Unit.create((Pwatt**3).dim, name="Pwatt3", dispname=f"{str(Pwatt)}^3", scale=Pwatt.scale*3) dwatt2 = Unit.create((dwatt**2).dim, name="dwatt2", dispname=f"{str(dwatt)}^2", scale=dwatt.scale*2) dwatt3 = Unit.create((dwatt**3).dim, name="dwatt3", dispname=f"{str(dwatt)}^3", scale=dwatt.scale*3) Gwatt2 = Unit.create((Gwatt**2).dim, name="Gwatt2", dispname=f"{str(Gwatt)}^2", scale=Gwatt.scale*2) Gwatt3 = Unit.create((Gwatt**3).dim, name="Gwatt3", dispname=f"{str(Gwatt)}^3", scale=Gwatt.scale*3) fwatt2 = Unit.create((fwatt**2).dim, name="fwatt2", dispname=f"{str(fwatt)}^2", scale=fwatt.scale*2) fwatt3 = Unit.create((fwatt**3).dim, name="fwatt3", dispname=f"{str(fwatt)}^3", scale=fwatt.scale*3) hwatt2 = Unit.create((hwatt**2).dim, name="hwatt2", dispname=f"{str(hwatt)}^2", scale=hwatt.scale*2) hwatt3 = Unit.create((hwatt**3).dim, name="hwatt3", dispname=f"{str(hwatt)}^3", scale=hwatt.scale*3) dawatt2 = Unit.create((dawatt**2).dim, name="dawatt2", dispname=f"{str(dawatt)}^2", scale=dawatt.scale*2) dawatt3 = Unit.create((dawatt**3).dim, name="dawatt3", dispname=f"{str(dawatt)}^3", scale=dawatt.scale*3) mwatt2 = Unit.create((mwatt**2).dim, name="mwatt2", dispname=f"{str(mwatt)}^2", scale=mwatt.scale*2) mwatt3 = Unit.create((mwatt**3).dim, name="mwatt3", dispname=f"{str(mwatt)}^3", scale=mwatt.scale*3) nwatt2 = Unit.create((nwatt**2).dim, name="nwatt2", dispname=f"{str(nwatt)}^2", scale=nwatt.scale*2) nwatt3 = Unit.create((nwatt**3).dim, name="nwatt3", dispname=f"{str(nwatt)}^3", scale=nwatt.scale*3) pwatt2 = Unit.create((pwatt**2).dim, name="pwatt2", dispname=f"{str(pwatt)}^2", scale=pwatt.scale*2) pwatt3 = Unit.create((pwatt**3).dim, name="pwatt3", dispname=f"{str(pwatt)}^3", scale=pwatt.scale*3) uwatt2 = Unit.create((uwatt**2).dim, name="uwatt2", dispname=f"{str(uwatt)}^2", scale=uwatt.scale*2) uwatt3 = Unit.create((uwatt**3).dim, name="uwatt3", dispname=f"{str(uwatt)}^3", scale=uwatt.scale*3) Twatt2 = Unit.create((Twatt**2).dim, name="Twatt2", dispname=f"{str(Twatt)}^2", scale=Twatt.scale*2) Twatt3 = Unit.create((Twatt**3).dim, name="Twatt3", dispname=f"{str(Twatt)}^3", scale=Twatt.scale*3) ywatt2 = Unit.create((ywatt**2).dim, name="ywatt2", dispname=f"{str(ywatt)}^2", scale=ywatt.scale*2) ywatt3 = Unit.create((ywatt**3).dim, name="ywatt3", dispname=f"{str(ywatt)}^3", scale=ywatt.scale*3) Ewatt2 = Unit.create((Ewatt**2).dim, name="Ewatt2", dispname=f"{str(Ewatt)}^2", scale=Ewatt.scale*2) Ewatt3 = Unit.create((Ewatt**3).dim, name="Ewatt3", dispname=f"{str(Ewatt)}^3", scale=Ewatt.scale*3) zwatt2 = Unit.create((zwatt**2).dim, name="zwatt2", dispname=f"{str(zwatt)}^2", scale=zwatt.scale*2) zwatt3 = Unit.create((zwatt**3).dim, name="zwatt3", dispname=f"{str(zwatt)}^3", scale=zwatt.scale*3) Mwatt2 = Unit.create((Mwatt**2).dim, name="Mwatt2", dispname=f"{str(Mwatt)}^2", scale=Mwatt.scale*2) Mwatt3 = Unit.create((Mwatt**3).dim, name="Mwatt3", dispname=f"{str(Mwatt)}^3", scale=Mwatt.scale*3) kwatt2 = Unit.create((kwatt**2).dim, name="kwatt2", dispname=f"{str(kwatt)}^2", scale=kwatt.scale*2) kwatt3 = Unit.create((kwatt**3).dim, name="kwatt3", dispname=f"{str(kwatt)}^3", scale=kwatt.scale*3) Ywatt2 = Unit.create((Ywatt**2).dim, name="Ywatt2", dispname=f"{str(Ywatt)}^2", scale=Ywatt.scale*2) Ywatt3 = Unit.create((Ywatt**3).dim, name="Ywatt3", dispname=f"{str(Ywatt)}^3", scale=Ywatt.scale*3) acoulomb2 = Unit.create((acoulomb**2).dim, name="acoulomb2", dispname=f"{str(acoulomb)}^2", scale=acoulomb.scale*2) acoulomb3 = Unit.create((acoulomb**3).dim, name="acoulomb3", dispname=f"{str(acoulomb)}^3", scale=acoulomb.scale*3) ccoulomb2 = Unit.create((ccoulomb**2).dim, name="ccoulomb2", dispname=f"{str(ccoulomb)}^2", scale=ccoulomb.scale*2) ccoulomb3 = Unit.create((ccoulomb**3).dim, name="ccoulomb3", dispname=f"{str(ccoulomb)}^3", scale=ccoulomb.scale*3) Zcoulomb2 = Unit.create((Zcoulomb**2).dim, name="Zcoulomb2", dispname=f"{str(Zcoulomb)}^2", scale=Zcoulomb.scale*2) Zcoulomb3 = Unit.create((Zcoulomb**3).dim, name="Zcoulomb3", dispname=f"{str(Zcoulomb)}^3", scale=Zcoulomb.scale*3) Pcoulomb2 = Unit.create((Pcoulomb**2).dim, name="Pcoulomb2", dispname=f"{str(Pcoulomb)}^2", scale=Pcoulomb.scale*2) Pcoulomb3 = Unit.create((Pcoulomb**3).dim, name="Pcoulomb3", dispname=f"{str(Pcoulomb)}^3", scale=Pcoulomb.scale*3) dcoulomb2 = Unit.create((dcoulomb**2).dim, name="dcoulomb2", dispname=f"{str(dcoulomb)}^2", scale=dcoulomb.scale*2) dcoulomb3 = Unit.create((dcoulomb**3).dim, name="dcoulomb3", dispname=f"{str(dcoulomb)}^3", scale=dcoulomb.scale*3) Gcoulomb2 = Unit.create((Gcoulomb**2).dim, name="Gcoulomb2", dispname=f"{str(Gcoulomb)}^2", scale=Gcoulomb.scale*2) Gcoulomb3 = Unit.create((Gcoulomb**3).dim, name="Gcoulomb3", dispname=f"{str(Gcoulomb)}^3", scale=Gcoulomb.scale*3) fcoulomb2 = Unit.create((fcoulomb**2).dim, name="fcoulomb2", dispname=f"{str(fcoulomb)}^2", scale=fcoulomb.scale*2) fcoulomb3 = Unit.create((fcoulomb**3).dim, name="fcoulomb3", dispname=f"{str(fcoulomb)}^3", scale=fcoulomb.scale*3) hcoulomb2 = Unit.create((hcoulomb**2).dim, name="hcoulomb2", dispname=f"{str(hcoulomb)}^2", scale=hcoulomb.scale*2) hcoulomb3 = Unit.create((hcoulomb**3).dim, name="hcoulomb3", dispname=f"{str(hcoulomb)}^3", scale=hcoulomb.scale*3) dacoulomb2 = Unit.create((dacoulomb**2).dim, name="dacoulomb2", dispname=f"{str(dacoulomb)}^2", scale=dacoulomb.scale*2) dacoulomb3 = Unit.create((dacoulomb**3).dim, name="dacoulomb3", dispname=f"{str(dacoulomb)}^3", scale=dacoulomb.scale*3) mcoulomb2 = Unit.create((mcoulomb**2).dim, name="mcoulomb2", dispname=f"{str(mcoulomb)}^2", scale=mcoulomb.scale*2) mcoulomb3 = Unit.create((mcoulomb**3).dim, name="mcoulomb3", dispname=f"{str(mcoulomb)}^3", scale=mcoulomb.scale*3) ncoulomb2 = Unit.create((ncoulomb**2).dim, name="ncoulomb2", dispname=f"{str(ncoulomb)}^2", scale=ncoulomb.scale*2) ncoulomb3 = Unit.create((ncoulomb**3).dim, name="ncoulomb3", dispname=f"{str(ncoulomb)}^3", scale=ncoulomb.scale*3) pcoulomb2 = Unit.create((pcoulomb**2).dim, name="pcoulomb2", dispname=f"{str(pcoulomb)}^2", scale=pcoulomb.scale*2) pcoulomb3 = Unit.create((pcoulomb**3).dim, name="pcoulomb3", dispname=f"{str(pcoulomb)}^3", scale=pcoulomb.scale*3) ucoulomb2 = Unit.create((ucoulomb**2).dim, name="ucoulomb2", dispname=f"{str(ucoulomb)}^2", scale=ucoulomb.scale*2) ucoulomb3 = Unit.create((ucoulomb**3).dim, name="ucoulomb3", dispname=f"{str(ucoulomb)}^3", scale=ucoulomb.scale*3) Tcoulomb2 = Unit.create((Tcoulomb**2).dim, name="Tcoulomb2", dispname=f"{str(Tcoulomb)}^2", scale=Tcoulomb.scale*2) Tcoulomb3 = Unit.create((Tcoulomb**3).dim, name="Tcoulomb3", dispname=f"{str(Tcoulomb)}^3", scale=Tcoulomb.scale*3) ycoulomb2 = Unit.create((ycoulomb**2).dim, name="ycoulomb2", dispname=f"{str(ycoulomb)}^2", scale=ycoulomb.scale*2) ycoulomb3 = Unit.create((ycoulomb**3).dim, name="ycoulomb3", dispname=f"{str(ycoulomb)}^3", scale=ycoulomb.scale*3) Ecoulomb2 = Unit.create((Ecoulomb**2).dim, name="Ecoulomb2", dispname=f"{str(Ecoulomb)}^2", scale=Ecoulomb.scale*2) Ecoulomb3 = Unit.create((Ecoulomb**3).dim, name="Ecoulomb3", dispname=f"{str(Ecoulomb)}^3", scale=Ecoulomb.scale*3) zcoulomb2 = Unit.create((zcoulomb**2).dim, name="zcoulomb2", dispname=f"{str(zcoulomb)}^2", scale=zcoulomb.scale*2) zcoulomb3 = Unit.create((zcoulomb**3).dim, name="zcoulomb3", dispname=f"{str(zcoulomb)}^3", scale=zcoulomb.scale*3) Mcoulomb2 = Unit.create((Mcoulomb**2).dim, name="Mcoulomb2", dispname=f"{str(Mcoulomb)}^2", scale=Mcoulomb.scale*2) Mcoulomb3 = Unit.create((Mcoulomb**3).dim, name="Mcoulomb3", dispname=f"{str(Mcoulomb)}^3", scale=Mcoulomb.scale*3) kcoulomb2 = Unit.create((kcoulomb**2).dim, name="kcoulomb2", dispname=f"{str(kcoulomb)}^2", scale=kcoulomb.scale*2) kcoulomb3 = Unit.create((kcoulomb**3).dim, name="kcoulomb3", dispname=f"{str(kcoulomb)}^3", scale=kcoulomb.scale*3) Ycoulomb2 = Unit.create((Ycoulomb**2).dim, name="Ycoulomb2", dispname=f"{str(Ycoulomb)}^2", scale=Ycoulomb.scale*2) Ycoulomb3 = Unit.create((Ycoulomb**3).dim, name="Ycoulomb3", dispname=f"{str(Ycoulomb)}^3", scale=Ycoulomb.scale*3) avolt2 = Unit.create((avolt**2).dim, name="avolt2", dispname=f"{str(avolt)}^2", scale=avolt.scale*2) avolt3 = Unit.create((avolt**3).dim, name="avolt3", dispname=f"{str(avolt)}^3", scale=avolt.scale*3) cvolt2 = Unit.create((cvolt**2).dim, name="cvolt2", dispname=f"{str(cvolt)}^2", scale=cvolt.scale*2) cvolt3 = Unit.create((cvolt**3).dim, name="cvolt3", dispname=f"{str(cvolt)}^3", scale=cvolt.scale*3) Zvolt2 = Unit.create((Zvolt**2).dim, name="Zvolt2", dispname=f"{str(Zvolt)}^2", scale=Zvolt.scale*2) Zvolt3 = Unit.create((Zvolt**3).dim, name="Zvolt3", dispname=f"{str(Zvolt)}^3", scale=Zvolt.scale*3) Pvolt2 = Unit.create((Pvolt**2).dim, name="Pvolt2", dispname=f"{str(Pvolt)}^2", scale=Pvolt.scale*2) Pvolt3 = Unit.create((Pvolt**3).dim, name="Pvolt3", dispname=f"{str(Pvolt)}^3", scale=Pvolt.scale*3) dvolt2 = Unit.create((dvolt**2).dim, name="dvolt2", dispname=f"{str(dvolt)}^2", scale=dvolt.scale*2) dvolt3 = Unit.create((dvolt**3).dim, name="dvolt3", dispname=f"{str(dvolt)}^3", scale=dvolt.scale*3) Gvolt2 = Unit.create((Gvolt**2).dim, name="Gvolt2", dispname=f"{str(Gvolt)}^2", scale=Gvolt.scale*2) Gvolt3 = Unit.create((Gvolt**3).dim, name="Gvolt3", dispname=f"{str(Gvolt)}^3", scale=Gvolt.scale*3) fvolt2 = Unit.create((fvolt**2).dim, name="fvolt2", dispname=f"{str(fvolt)}^2", scale=fvolt.scale*2) fvolt3 = Unit.create((fvolt**3).dim, name="fvolt3", dispname=f"{str(fvolt)}^3", scale=fvolt.scale*3) hvolt2 = Unit.create((hvolt**2).dim, name="hvolt2", dispname=f"{str(hvolt)}^2", scale=hvolt.scale*2) hvolt3 = Unit.create((hvolt**3).dim, name="hvolt3", dispname=f"{str(hvolt)}^3", scale=hvolt.scale*3) davolt2 = Unit.create((davolt**2).dim, name="davolt2", dispname=f"{str(davolt)}^2", scale=davolt.scale*2) davolt3 = Unit.create((davolt**3).dim, name="davolt3", dispname=f"{str(davolt)}^3", scale=davolt.scale*3) mvolt2 = Unit.create((mvolt**2).dim, name="mvolt2", dispname=f"{str(mvolt)}^2", scale=mvolt.scale*2) mvolt3 = Unit.create((mvolt**3).dim, name="mvolt3", dispname=f"{str(mvolt)}^3", scale=mvolt.scale*3) nvolt2 = Unit.create((nvolt**2).dim, name="nvolt2", dispname=f"{str(nvolt)}^2", scale=nvolt.scale*2) nvolt3 = Unit.create((nvolt**3).dim, name="nvolt3", dispname=f"{str(nvolt)}^3", scale=nvolt.scale*3) pvolt2 = Unit.create((pvolt**2).dim, name="pvolt2", dispname=f"{str(pvolt)}^2", scale=pvolt.scale*2) pvolt3 = Unit.create((pvolt**3).dim, name="pvolt3", dispname=f"{str(pvolt)}^3", scale=pvolt.scale*3) uvolt2 = Unit.create((uvolt**2).dim, name="uvolt2", dispname=f"{str(uvolt)}^2", scale=uvolt.scale*2) uvolt3 = Unit.create((uvolt**3).dim, name="uvolt3", dispname=f"{str(uvolt)}^3", scale=uvolt.scale*3) Tvolt2 = Unit.create((Tvolt**2).dim, name="Tvolt2", dispname=f"{str(Tvolt)}^2", scale=Tvolt.scale*2) Tvolt3 = Unit.create((Tvolt**3).dim, name="Tvolt3", dispname=f"{str(Tvolt)}^3", scale=Tvolt.scale*3) yvolt2 = Unit.create((yvolt**2).dim, name="yvolt2", dispname=f"{str(yvolt)}^2", scale=yvolt.scale*2) yvolt3 = Unit.create((yvolt**3).dim, name="yvolt3", dispname=f"{str(yvolt)}^3", scale=yvolt.scale*3) Evolt2 = Unit.create((Evolt**2).dim, name="Evolt2", dispname=f"{str(Evolt)}^2", scale=Evolt.scale*2) Evolt3 = Unit.create((Evolt**3).dim, name="Evolt3", dispname=f"{str(Evolt)}^3", scale=Evolt.scale*3) zvolt2 = Unit.create((zvolt**2).dim, name="zvolt2", dispname=f"{str(zvolt)}^2", scale=zvolt.scale*2) zvolt3 = Unit.create((zvolt**3).dim, name="zvolt3", dispname=f"{str(zvolt)}^3", scale=zvolt.scale*3) Mvolt2 = Unit.create((Mvolt**2).dim, name="Mvolt2", dispname=f"{str(Mvolt)}^2", scale=Mvolt.scale*2) Mvolt3 = Unit.create((Mvolt**3).dim, name="Mvolt3", dispname=f"{str(Mvolt)}^3", scale=Mvolt.scale*3) kvolt2 = Unit.create((kvolt**2).dim, name="kvolt2", dispname=f"{str(kvolt)}^2", scale=kvolt.scale*2) kvolt3 = Unit.create((kvolt**3).dim, name="kvolt3", dispname=f"{str(kvolt)}^3", scale=kvolt.scale*3) Yvolt2 = Unit.create((Yvolt**2).dim, name="Yvolt2", dispname=f"{str(Yvolt)}^2", scale=Yvolt.scale*2) Yvolt3 = Unit.create((Yvolt**3).dim, name="Yvolt3", dispname=f"{str(Yvolt)}^3", scale=Yvolt.scale*3) afarad2 = Unit.create((afarad**2).dim, name="afarad2", dispname=f"{str(afarad)}^2", scale=afarad.scale*2) afarad3 = Unit.create((afarad**3).dim, name="afarad3", dispname=f"{str(afarad)}^3", scale=afarad.scale*3) cfarad2 = Unit.create((cfarad**2).dim, name="cfarad2", dispname=f"{str(cfarad)}^2", scale=cfarad.scale*2) cfarad3 = Unit.create((cfarad**3).dim, name="cfarad3", dispname=f"{str(cfarad)}^3", scale=cfarad.scale*3) Zfarad2 = Unit.create((Zfarad**2).dim, name="Zfarad2", dispname=f"{str(Zfarad)}^2", scale=Zfarad.scale*2) Zfarad3 = Unit.create((Zfarad**3).dim, name="Zfarad3", dispname=f"{str(Zfarad)}^3", scale=Zfarad.scale*3) Pfarad2 = Unit.create((Pfarad**2).dim, name="Pfarad2", dispname=f"{str(Pfarad)}^2", scale=Pfarad.scale*2) Pfarad3 = Unit.create((Pfarad**3).dim, name="Pfarad3", dispname=f"{str(Pfarad)}^3", scale=Pfarad.scale*3) dfarad2 = Unit.create((dfarad**2).dim, name="dfarad2", dispname=f"{str(dfarad)}^2", scale=dfarad.scale*2) dfarad3 = Unit.create((dfarad**3).dim, name="dfarad3", dispname=f"{str(dfarad)}^3", scale=dfarad.scale*3) Gfarad2 = Unit.create((Gfarad**2).dim, name="Gfarad2", dispname=f"{str(Gfarad)}^2", scale=Gfarad.scale*2) Gfarad3 = Unit.create((Gfarad**3).dim, name="Gfarad3", dispname=f"{str(Gfarad)}^3", scale=Gfarad.scale*3) ffarad2 = Unit.create((ffarad**2).dim, name="ffarad2", dispname=f"{str(ffarad)}^2", scale=ffarad.scale*2) ffarad3 = Unit.create((ffarad**3).dim, name="ffarad3", dispname=f"{str(ffarad)}^3", scale=ffarad.scale*3) hfarad2 = Unit.create((hfarad**2).dim, name="hfarad2", dispname=f"{str(hfarad)}^2", scale=hfarad.scale*2) hfarad3 = Unit.create((hfarad**3).dim, name="hfarad3", dispname=f"{str(hfarad)}^3", scale=hfarad.scale*3) dafarad2 = Unit.create((dafarad**2).dim, name="dafarad2", dispname=f"{str(dafarad)}^2", scale=dafarad.scale*2) dafarad3 = Unit.create((dafarad**3).dim, name="dafarad3", dispname=f"{str(dafarad)}^3", scale=dafarad.scale*3) mfarad2 = Unit.create((mfarad**2).dim, name="mfarad2", dispname=f"{str(mfarad)}^2", scale=mfarad.scale*2) mfarad3 = Unit.create((mfarad**3).dim, name="mfarad3", dispname=f"{str(mfarad)}^3", scale=mfarad.scale*3) nfarad2 = Unit.create((nfarad**2).dim, name="nfarad2", dispname=f"{str(nfarad)}^2", scale=nfarad.scale*2) nfarad3 = Unit.create((nfarad**3).dim, name="nfarad3", dispname=f"{str(nfarad)}^3", scale=nfarad.scale*3) pfarad2 = Unit.create((pfarad**2).dim, name="pfarad2", dispname=f"{str(pfarad)}^2", scale=pfarad.scale*2) pfarad3 = Unit.create((pfarad**3).dim, name="pfarad3", dispname=f"{str(pfarad)}^3", scale=pfarad.scale*3) ufarad2 = Unit.create((ufarad**2).dim, name="ufarad2", dispname=f"{str(ufarad)}^2", scale=ufarad.scale*2) ufarad3 = Unit.create((ufarad**3).dim, name="ufarad3", dispname=f"{str(ufarad)}^3", scale=ufarad.scale*3) Tfarad2 = Unit.create((Tfarad**2).dim, name="Tfarad2", dispname=f"{str(Tfarad)}^2", scale=Tfarad.scale*2) Tfarad3 = Unit.create((Tfarad**3).dim, name="Tfarad3", dispname=f"{str(Tfarad)}^3", scale=Tfarad.scale*3) yfarad2 = Unit.create((yfarad**2).dim, name="yfarad2", dispname=f"{str(yfarad)}^2", scale=yfarad.scale*2) yfarad3 = Unit.create((yfarad**3).dim, name="yfarad3", dispname=f"{str(yfarad)}^3", scale=yfarad.scale*3) Efarad2 = Unit.create((Efarad**2).dim, name="Efarad2", dispname=f"{str(Efarad)}^2", scale=Efarad.scale*2) Efarad3 = Unit.create((Efarad**3).dim, name="Efarad3", dispname=f"{str(Efarad)}^3", scale=Efarad.scale*3) zfarad2 = Unit.create((zfarad**2).dim, name="zfarad2", dispname=f"{str(zfarad)}^2", scale=zfarad.scale*2) zfarad3 = Unit.create((zfarad**3).dim, name="zfarad3", dispname=f"{str(zfarad)}^3", scale=zfarad.scale*3) Mfarad2 = Unit.create((Mfarad**2).dim, name="Mfarad2", dispname=f"{str(Mfarad)}^2", scale=Mfarad.scale*2) Mfarad3 = Unit.create((Mfarad**3).dim, name="Mfarad3", dispname=f"{str(Mfarad)}^3", scale=Mfarad.scale*3) kfarad2 = Unit.create((kfarad**2).dim, name="kfarad2", dispname=f"{str(kfarad)}^2", scale=kfarad.scale*2) kfarad3 = Unit.create((kfarad**3).dim, name="kfarad3", dispname=f"{str(kfarad)}^3", scale=kfarad.scale*3) Yfarad2 = Unit.create((Yfarad**2).dim, name="Yfarad2", dispname=f"{str(Yfarad)}^2", scale=Yfarad.scale*2) Yfarad3 = Unit.create((Yfarad**3).dim, name="Yfarad3", dispname=f"{str(Yfarad)}^3", scale=Yfarad.scale*3) aohm2 = Unit.create((aohm**2).dim, name="aohm2", dispname=f"{str(aohm)}^2", scale=aohm.scale*2) aohm3 = Unit.create((aohm**3).dim, name="aohm3", dispname=f"{str(aohm)}^3", scale=aohm.scale*3) cohm2 = Unit.create((cohm**2).dim, name="cohm2", dispname=f"{str(cohm)}^2", scale=cohm.scale*2) cohm3 = Unit.create((cohm**3).dim, name="cohm3", dispname=f"{str(cohm)}^3", scale=cohm.scale*3) Zohm2 = Unit.create((Zohm**2).dim, name="Zohm2", dispname=f"{str(Zohm)}^2", scale=Zohm.scale*2) Zohm3 = Unit.create((Zohm**3).dim, name="Zohm3", dispname=f"{str(Zohm)}^3", scale=Zohm.scale*3) Pohm2 = Unit.create((Pohm**2).dim, name="Pohm2", dispname=f"{str(Pohm)}^2", scale=Pohm.scale*2) Pohm3 = Unit.create((Pohm**3).dim, name="Pohm3", dispname=f"{str(Pohm)}^3", scale=Pohm.scale*3) dohm2 = Unit.create((dohm**2).dim, name="dohm2", dispname=f"{str(dohm)}^2", scale=dohm.scale*2) dohm3 = Unit.create((dohm**3).dim, name="dohm3", dispname=f"{str(dohm)}^3", scale=dohm.scale*3) Gohm2 = Unit.create((Gohm**2).dim, name="Gohm2", dispname=f"{str(Gohm)}^2", scale=Gohm.scale*2) Gohm3 = Unit.create((Gohm**3).dim, name="Gohm3", dispname=f"{str(Gohm)}^3", scale=Gohm.scale*3) fohm2 = Unit.create((fohm**2).dim, name="fohm2", dispname=f"{str(fohm)}^2", scale=fohm.scale*2) fohm3 = Unit.create((fohm**3).dim, name="fohm3", dispname=f"{str(fohm)}^3", scale=fohm.scale*3) hohm2 = Unit.create((hohm**2).dim, name="hohm2", dispname=f"{str(hohm)}^2", scale=hohm.scale*2) hohm3 = Unit.create((hohm**3).dim, name="hohm3", dispname=f"{str(hohm)}^3", scale=hohm.scale*3) daohm2 = Unit.create((daohm**2).dim, name="daohm2", dispname=f"{str(daohm)}^2", scale=daohm.scale*2) daohm3 = Unit.create((daohm**3).dim, name="daohm3", dispname=f"{str(daohm)}^3", scale=daohm.scale*3) mohm2 = Unit.create((mohm**2).dim, name="mohm2", dispname=f"{str(mohm)}^2", scale=mohm.scale*2) mohm3 = Unit.create((mohm**3).dim, name="mohm3", dispname=f"{str(mohm)}^3", scale=mohm.scale*3) nohm2 = Unit.create((nohm**2).dim, name="nohm2", dispname=f"{str(nohm)}^2", scale=nohm.scale*2) nohm3 = Unit.create((nohm**3).dim, name="nohm3", dispname=f"{str(nohm)}^3", scale=nohm.scale*3) pohm2 = Unit.create((pohm**2).dim, name="pohm2", dispname=f"{str(pohm)}^2", scale=pohm.scale*2) pohm3 = Unit.create((pohm**3).dim, name="pohm3", dispname=f"{str(pohm)}^3", scale=pohm.scale*3) uohm2 = Unit.create((uohm**2).dim, name="uohm2", dispname=f"{str(uohm)}^2", scale=uohm.scale*2) uohm3 = Unit.create((uohm**3).dim, name="uohm3", dispname=f"{str(uohm)}^3", scale=uohm.scale*3) Tohm2 = Unit.create((Tohm**2).dim, name="Tohm2", dispname=f"{str(Tohm)}^2", scale=Tohm.scale*2) Tohm3 = Unit.create((Tohm**3).dim, name="Tohm3", dispname=f"{str(Tohm)}^3", scale=Tohm.scale*3) yohm2 = Unit.create((yohm**2).dim, name="yohm2", dispname=f"{str(yohm)}^2", scale=yohm.scale*2) yohm3 = Unit.create((yohm**3).dim, name="yohm3", dispname=f"{str(yohm)}^3", scale=yohm.scale*3) Eohm2 = Unit.create((Eohm**2).dim, name="Eohm2", dispname=f"{str(Eohm)}^2", scale=Eohm.scale*2) Eohm3 = Unit.create((Eohm**3).dim, name="Eohm3", dispname=f"{str(Eohm)}^3", scale=Eohm.scale*3) zohm2 = Unit.create((zohm**2).dim, name="zohm2", dispname=f"{str(zohm)}^2", scale=zohm.scale*2) zohm3 = Unit.create((zohm**3).dim, name="zohm3", dispname=f"{str(zohm)}^3", scale=zohm.scale*3) Mohm2 = Unit.create((Mohm**2).dim, name="Mohm2", dispname=f"{str(Mohm)}^2", scale=Mohm.scale*2) Mohm3 = Unit.create((Mohm**3).dim, name="Mohm3", dispname=f"{str(Mohm)}^3", scale=Mohm.scale*3) kohm2 = Unit.create((kohm**2).dim, name="kohm2", dispname=f"{str(kohm)}^2", scale=kohm.scale*2) kohm3 = Unit.create((kohm**3).dim, name="kohm3", dispname=f"{str(kohm)}^3", scale=kohm.scale*3) Yohm2 = Unit.create((Yohm**2).dim, name="Yohm2", dispname=f"{str(Yohm)}^2", scale=Yohm.scale*2) Yohm3 = Unit.create((Yohm**3).dim, name="Yohm3", dispname=f"{str(Yohm)}^3", scale=Yohm.scale*3) asiemens2 = Unit.create((asiemens**2).dim, name="asiemens2", dispname=f"{str(asiemens)}^2", scale=asiemens.scale*2) asiemens3 = Unit.create((asiemens**3).dim, name="asiemens3", dispname=f"{str(asiemens)}^3", scale=asiemens.scale*3) csiemens2 = Unit.create((csiemens**2).dim, name="csiemens2", dispname=f"{str(csiemens)}^2", scale=csiemens.scale*2) csiemens3 = Unit.create((csiemens**3).dim, name="csiemens3", dispname=f"{str(csiemens)}^3", scale=csiemens.scale*3) Zsiemens2 = Unit.create((Zsiemens**2).dim, name="Zsiemens2", dispname=f"{str(Zsiemens)}^2", scale=Zsiemens.scale*2) Zsiemens3 = Unit.create((Zsiemens**3).dim, name="Zsiemens3", dispname=f"{str(Zsiemens)}^3", scale=Zsiemens.scale*3) Psiemens2 = Unit.create((Psiemens**2).dim, name="Psiemens2", dispname=f"{str(Psiemens)}^2", scale=Psiemens.scale*2) Psiemens3 = Unit.create((Psiemens**3).dim, name="Psiemens3", dispname=f"{str(Psiemens)}^3", scale=Psiemens.scale*3) dsiemens2 = Unit.create((dsiemens**2).dim, name="dsiemens2", dispname=f"{str(dsiemens)}^2", scale=dsiemens.scale*2) dsiemens3 = Unit.create((dsiemens**3).dim, name="dsiemens3", dispname=f"{str(dsiemens)}^3", scale=dsiemens.scale*3) Gsiemens2 = Unit.create((Gsiemens**2).dim, name="Gsiemens2", dispname=f"{str(Gsiemens)}^2", scale=Gsiemens.scale*2) Gsiemens3 = Unit.create((Gsiemens**3).dim, name="Gsiemens3", dispname=f"{str(Gsiemens)}^3", scale=Gsiemens.scale*3) fsiemens2 = Unit.create((fsiemens**2).dim, name="fsiemens2", dispname=f"{str(fsiemens)}^2", scale=fsiemens.scale*2) fsiemens3 = Unit.create((fsiemens**3).dim, name="fsiemens3", dispname=f"{str(fsiemens)}^3", scale=fsiemens.scale*3) hsiemens2 = Unit.create((hsiemens**2).dim, name="hsiemens2", dispname=f"{str(hsiemens)}^2", scale=hsiemens.scale*2) hsiemens3 = Unit.create((hsiemens**3).dim, name="hsiemens3", dispname=f"{str(hsiemens)}^3", scale=hsiemens.scale*3) dasiemens2 = Unit.create((dasiemens**2).dim, name="dasiemens2", dispname=f"{str(dasiemens)}^2", scale=dasiemens.scale*2) dasiemens3 = Unit.create((dasiemens**3).dim, name="dasiemens3", dispname=f"{str(dasiemens)}^3", scale=dasiemens.scale*3) msiemens2 = Unit.create((msiemens**2).dim, name="msiemens2", dispname=f"{str(msiemens)}^2", scale=msiemens.scale*2) msiemens3 = Unit.create((msiemens**3).dim, name="msiemens3", dispname=f"{str(msiemens)}^3", scale=msiemens.scale*3) nsiemens2 = Unit.create((nsiemens**2).dim, name="nsiemens2", dispname=f"{str(nsiemens)}^2", scale=nsiemens.scale*2) nsiemens3 = Unit.create((nsiemens**3).dim, name="nsiemens3", dispname=f"{str(nsiemens)}^3", scale=nsiemens.scale*3) psiemens2 = Unit.create((psiemens**2).dim, name="psiemens2", dispname=f"{str(psiemens)}^2", scale=psiemens.scale*2) psiemens3 = Unit.create((psiemens**3).dim, name="psiemens3", dispname=f"{str(psiemens)}^3", scale=psiemens.scale*3) usiemens2 = Unit.create((usiemens**2).dim, name="usiemens2", dispname=f"{str(usiemens)}^2", scale=usiemens.scale*2) usiemens3 = Unit.create((usiemens**3).dim, name="usiemens3", dispname=f"{str(usiemens)}^3", scale=usiemens.scale*3) Tsiemens2 = Unit.create((Tsiemens**2).dim, name="Tsiemens2", dispname=f"{str(Tsiemens)}^2", scale=Tsiemens.scale*2) Tsiemens3 = Unit.create((Tsiemens**3).dim, name="Tsiemens3", dispname=f"{str(Tsiemens)}^3", scale=Tsiemens.scale*3) ysiemens2 = Unit.create((ysiemens**2).dim, name="ysiemens2", dispname=f"{str(ysiemens)}^2", scale=ysiemens.scale*2) ysiemens3 = Unit.create((ysiemens**3).dim, name="ysiemens3", dispname=f"{str(ysiemens)}^3", scale=ysiemens.scale*3) Esiemens2 = Unit.create((Esiemens**2).dim, name="Esiemens2", dispname=f"{str(Esiemens)}^2", scale=Esiemens.scale*2) Esiemens3 = Unit.create((Esiemens**3).dim, name="Esiemens3", dispname=f"{str(Esiemens)}^3", scale=Esiemens.scale*3) zsiemens2 = Unit.create((zsiemens**2).dim, name="zsiemens2", dispname=f"{str(zsiemens)}^2", scale=zsiemens.scale*2) zsiemens3 = Unit.create((zsiemens**3).dim, name="zsiemens3", dispname=f"{str(zsiemens)}^3", scale=zsiemens.scale*3) Msiemens2 = Unit.create((Msiemens**2).dim, name="Msiemens2", dispname=f"{str(Msiemens)}^2", scale=Msiemens.scale*2) Msiemens3 = Unit.create((Msiemens**3).dim, name="Msiemens3", dispname=f"{str(Msiemens)}^3", scale=Msiemens.scale*3) ksiemens2 = Unit.create((ksiemens**2).dim, name="ksiemens2", dispname=f"{str(ksiemens)}^2", scale=ksiemens.scale*2) ksiemens3 = Unit.create((ksiemens**3).dim, name="ksiemens3", dispname=f"{str(ksiemens)}^3", scale=ksiemens.scale*3) Ysiemens2 = Unit.create((Ysiemens**2).dim, name="Ysiemens2", dispname=f"{str(Ysiemens)}^2", scale=Ysiemens.scale*2) Ysiemens3 = Unit.create((Ysiemens**3).dim, name="Ysiemens3", dispname=f"{str(Ysiemens)}^3", scale=Ysiemens.scale*3) aweber2 = Unit.create((aweber**2).dim, name="aweber2", dispname=f"{str(aweber)}^2", scale=aweber.scale*2) aweber3 = Unit.create((aweber**3).dim, name="aweber3", dispname=f"{str(aweber)}^3", scale=aweber.scale*3) cweber2 = Unit.create((cweber**2).dim, name="cweber2", dispname=f"{str(cweber)}^2", scale=cweber.scale*2) cweber3 = Unit.create((cweber**3).dim, name="cweber3", dispname=f"{str(cweber)}^3", scale=cweber.scale*3) Zweber2 = Unit.create((Zweber**2).dim, name="Zweber2", dispname=f"{str(Zweber)}^2", scale=Zweber.scale*2) Zweber3 = Unit.create((Zweber**3).dim, name="Zweber3", dispname=f"{str(Zweber)}^3", scale=Zweber.scale*3) Pweber2 = Unit.create((Pweber**2).dim, name="Pweber2", dispname=f"{str(Pweber)}^2", scale=Pweber.scale*2) Pweber3 = Unit.create((Pweber**3).dim, name="Pweber3", dispname=f"{str(Pweber)}^3", scale=Pweber.scale*3) dweber2 = Unit.create((dweber**2).dim, name="dweber2", dispname=f"{str(dweber)}^2", scale=dweber.scale*2) dweber3 = Unit.create((dweber**3).dim, name="dweber3", dispname=f"{str(dweber)}^3", scale=dweber.scale*3) Gweber2 = Unit.create((Gweber**2).dim, name="Gweber2", dispname=f"{str(Gweber)}^2", scale=Gweber.scale*2) Gweber3 = Unit.create((Gweber**3).dim, name="Gweber3", dispname=f"{str(Gweber)}^3", scale=Gweber.scale*3) fweber2 = Unit.create((fweber**2).dim, name="fweber2", dispname=f"{str(fweber)}^2", scale=fweber.scale*2) fweber3 = Unit.create((fweber**3).dim, name="fweber3", dispname=f"{str(fweber)}^3", scale=fweber.scale*3) hweber2 = Unit.create((hweber**2).dim, name="hweber2", dispname=f"{str(hweber)}^2", scale=hweber.scale*2) hweber3 = Unit.create((hweber**3).dim, name="hweber3", dispname=f"{str(hweber)}^3", scale=hweber.scale*3) daweber2 = Unit.create((daweber**2).dim, name="daweber2", dispname=f"{str(daweber)}^2", scale=daweber.scale*2) daweber3 = Unit.create((daweber**3).dim, name="daweber3", dispname=f"{str(daweber)}^3", scale=daweber.scale*3) mweber2 = Unit.create((mweber**2).dim, name="mweber2", dispname=f"{str(mweber)}^2", scale=mweber.scale*2) mweber3 = Unit.create((mweber**3).dim, name="mweber3", dispname=f"{str(mweber)}^3", scale=mweber.scale*3) nweber2 = Unit.create((nweber**2).dim, name="nweber2", dispname=f"{str(nweber)}^2", scale=nweber.scale*2) nweber3 = Unit.create((nweber**3).dim, name="nweber3", dispname=f"{str(nweber)}^3", scale=nweber.scale*3) pweber2 = Unit.create((pweber**2).dim, name="pweber2", dispname=f"{str(pweber)}^2", scale=pweber.scale*2) pweber3 = Unit.create((pweber**3).dim, name="pweber3", dispname=f"{str(pweber)}^3", scale=pweber.scale*3) uweber2 = Unit.create((uweber**2).dim, name="uweber2", dispname=f"{str(uweber)}^2", scale=uweber.scale*2) uweber3 = Unit.create((uweber**3).dim, name="uweber3", dispname=f"{str(uweber)}^3", scale=uweber.scale*3) Tweber2 = Unit.create((Tweber**2).dim, name="Tweber2", dispname=f"{str(Tweber)}^2", scale=Tweber.scale*2) Tweber3 = Unit.create((Tweber**3).dim, name="Tweber3", dispname=f"{str(Tweber)}^3", scale=Tweber.scale*3) yweber2 = Unit.create((yweber**2).dim, name="yweber2", dispname=f"{str(yweber)}^2", scale=yweber.scale*2) yweber3 = Unit.create((yweber**3).dim, name="yweber3", dispname=f"{str(yweber)}^3", scale=yweber.scale*3) Eweber2 = Unit.create((Eweber**2).dim, name="Eweber2", dispname=f"{str(Eweber)}^2", scale=Eweber.scale*2) Eweber3 = Unit.create((Eweber**3).dim, name="Eweber3", dispname=f"{str(Eweber)}^3", scale=Eweber.scale*3) zweber2 = Unit.create((zweber**2).dim, name="zweber2", dispname=f"{str(zweber)}^2", scale=zweber.scale*2) zweber3 = Unit.create((zweber**3).dim, name="zweber3", dispname=f"{str(zweber)}^3", scale=zweber.scale*3) Mweber2 = Unit.create((Mweber**2).dim, name="Mweber2", dispname=f"{str(Mweber)}^2", scale=Mweber.scale*2) Mweber3 = Unit.create((Mweber**3).dim, name="Mweber3", dispname=f"{str(Mweber)}^3", scale=Mweber.scale*3) kweber2 = Unit.create((kweber**2).dim, name="kweber2", dispname=f"{str(kweber)}^2", scale=kweber.scale*2) kweber3 = Unit.create((kweber**3).dim, name="kweber3", dispname=f"{str(kweber)}^3", scale=kweber.scale*3) Yweber2 = Unit.create((Yweber**2).dim, name="Yweber2", dispname=f"{str(Yweber)}^2", scale=Yweber.scale*2) Yweber3 = Unit.create((Yweber**3).dim, name="Yweber3", dispname=f"{str(Yweber)}^3", scale=Yweber.scale*3) atesla2 = Unit.create((atesla**2).dim, name="atesla2", dispname=f"{str(atesla)}^2", scale=atesla.scale*2) atesla3 = Unit.create((atesla**3).dim, name="atesla3", dispname=f"{str(atesla)}^3", scale=atesla.scale*3) ctesla2 = Unit.create((ctesla**2).dim, name="ctesla2", dispname=f"{str(ctesla)}^2", scale=ctesla.scale*2) ctesla3 = Unit.create((ctesla**3).dim, name="ctesla3", dispname=f"{str(ctesla)}^3", scale=ctesla.scale*3) Ztesla2 = Unit.create((Ztesla**2).dim, name="Ztesla2", dispname=f"{str(Ztesla)}^2", scale=Ztesla.scale*2) Ztesla3 = Unit.create((Ztesla**3).dim, name="Ztesla3", dispname=f"{str(Ztesla)}^3", scale=Ztesla.scale*3) Ptesla2 = Unit.create((Ptesla**2).dim, name="Ptesla2", dispname=f"{str(Ptesla)}^2", scale=Ptesla.scale*2) Ptesla3 = Unit.create((Ptesla**3).dim, name="Ptesla3", dispname=f"{str(Ptesla)}^3", scale=Ptesla.scale*3) dtesla2 = Unit.create((dtesla**2).dim, name="dtesla2", dispname=f"{str(dtesla)}^2", scale=dtesla.scale*2) dtesla3 = Unit.create((dtesla**3).dim, name="dtesla3", dispname=f"{str(dtesla)}^3", scale=dtesla.scale*3) Gtesla2 = Unit.create((Gtesla**2).dim, name="Gtesla2", dispname=f"{str(Gtesla)}^2", scale=Gtesla.scale*2) Gtesla3 = Unit.create((Gtesla**3).dim, name="Gtesla3", dispname=f"{str(Gtesla)}^3", scale=Gtesla.scale*3) ftesla2 = Unit.create((ftesla**2).dim, name="ftesla2", dispname=f"{str(ftesla)}^2", scale=ftesla.scale*2) ftesla3 = Unit.create((ftesla**3).dim, name="ftesla3", dispname=f"{str(ftesla)}^3", scale=ftesla.scale*3) htesla2 = Unit.create((htesla**2).dim, name="htesla2", dispname=f"{str(htesla)}^2", scale=htesla.scale*2) htesla3 = Unit.create((htesla**3).dim, name="htesla3", dispname=f"{str(htesla)}^3", scale=htesla.scale*3) datesla2 = Unit.create((datesla**2).dim, name="datesla2", dispname=f"{str(datesla)}^2", scale=datesla.scale*2) datesla3 = Unit.create((datesla**3).dim, name="datesla3", dispname=f"{str(datesla)}^3", scale=datesla.scale*3) mtesla2 = Unit.create((mtesla**2).dim, name="mtesla2", dispname=f"{str(mtesla)}^2", scale=mtesla.scale*2) mtesla3 = Unit.create((mtesla**3).dim, name="mtesla3", dispname=f"{str(mtesla)}^3", scale=mtesla.scale*3) ntesla2 = Unit.create((ntesla**2).dim, name="ntesla2", dispname=f"{str(ntesla)}^2", scale=ntesla.scale*2) ntesla3 = Unit.create((ntesla**3).dim, name="ntesla3", dispname=f"{str(ntesla)}^3", scale=ntesla.scale*3) ptesla2 = Unit.create((ptesla**2).dim, name="ptesla2", dispname=f"{str(ptesla)}^2", scale=ptesla.scale*2) ptesla3 = Unit.create((ptesla**3).dim, name="ptesla3", dispname=f"{str(ptesla)}^3", scale=ptesla.scale*3) utesla2 = Unit.create((utesla**2).dim, name="utesla2", dispname=f"{str(utesla)}^2", scale=utesla.scale*2) utesla3 = Unit.create((utesla**3).dim, name="utesla3", dispname=f"{str(utesla)}^3", scale=utesla.scale*3) Ttesla2 = Unit.create((Ttesla**2).dim, name="Ttesla2", dispname=f"{str(Ttesla)}^2", scale=Ttesla.scale*2) Ttesla3 = Unit.create((Ttesla**3).dim, name="Ttesla3", dispname=f"{str(Ttesla)}^3", scale=Ttesla.scale*3) ytesla2 = Unit.create((ytesla**2).dim, name="ytesla2", dispname=f"{str(ytesla)}^2", scale=ytesla.scale*2) ytesla3 = Unit.create((ytesla**3).dim, name="ytesla3", dispname=f"{str(ytesla)}^3", scale=ytesla.scale*3) Etesla2 = Unit.create((Etesla**2).dim, name="Etesla2", dispname=f"{str(Etesla)}^2", scale=Etesla.scale*2) Etesla3 = Unit.create((Etesla**3).dim, name="Etesla3", dispname=f"{str(Etesla)}^3", scale=Etesla.scale*3) ztesla2 = Unit.create((ztesla**2).dim, name="ztesla2", dispname=f"{str(ztesla)}^2", scale=ztesla.scale*2) ztesla3 = Unit.create((ztesla**3).dim, name="ztesla3", dispname=f"{str(ztesla)}^3", scale=ztesla.scale*3) Mtesla2 = Unit.create((Mtesla**2).dim, name="Mtesla2", dispname=f"{str(Mtesla)}^2", scale=Mtesla.scale*2) Mtesla3 = Unit.create((Mtesla**3).dim, name="Mtesla3", dispname=f"{str(Mtesla)}^3", scale=Mtesla.scale*3) ktesla2 = Unit.create((ktesla**2).dim, name="ktesla2", dispname=f"{str(ktesla)}^2", scale=ktesla.scale*2) ktesla3 = Unit.create((ktesla**3).dim, name="ktesla3", dispname=f"{str(ktesla)}^3", scale=ktesla.scale*3) Ytesla2 = Unit.create((Ytesla**2).dim, name="Ytesla2", dispname=f"{str(Ytesla)}^2", scale=Ytesla.scale*2) Ytesla3 = Unit.create((Ytesla**3).dim, name="Ytesla3", dispname=f"{str(Ytesla)}^3", scale=Ytesla.scale*3) ahenry2 = Unit.create((ahenry**2).dim, name="ahenry2", dispname=f"{str(ahenry)}^2", scale=ahenry.scale*2) ahenry3 = Unit.create((ahenry**3).dim, name="ahenry3", dispname=f"{str(ahenry)}^3", scale=ahenry.scale*3) chenry2 = Unit.create((chenry**2).dim, name="chenry2", dispname=f"{str(chenry)}^2", scale=chenry.scale*2) chenry3 = Unit.create((chenry**3).dim, name="chenry3", dispname=f"{str(chenry)}^3", scale=chenry.scale*3) Zhenry2 = Unit.create((Zhenry**2).dim, name="Zhenry2", dispname=f"{str(Zhenry)}^2", scale=Zhenry.scale*2) Zhenry3 = Unit.create((Zhenry**3).dim, name="Zhenry3", dispname=f"{str(Zhenry)}^3", scale=Zhenry.scale*3) Phenry2 = Unit.create((Phenry**2).dim, name="Phenry2", dispname=f"{str(Phenry)}^2", scale=Phenry.scale*2) Phenry3 = Unit.create((Phenry**3).dim, name="Phenry3", dispname=f"{str(Phenry)}^3", scale=Phenry.scale*3) dhenry2 = Unit.create((dhenry**2).dim, name="dhenry2", dispname=f"{str(dhenry)}^2", scale=dhenry.scale*2) dhenry3 = Unit.create((dhenry**3).dim, name="dhenry3", dispname=f"{str(dhenry)}^3", scale=dhenry.scale*3) Ghenry2 = Unit.create((Ghenry**2).dim, name="Ghenry2", dispname=f"{str(Ghenry)}^2", scale=Ghenry.scale*2) Ghenry3 = Unit.create((Ghenry**3).dim, name="Ghenry3", dispname=f"{str(Ghenry)}^3", scale=Ghenry.scale*3) fhenry2 = Unit.create((fhenry**2).dim, name="fhenry2", dispname=f"{str(fhenry)}^2", scale=fhenry.scale*2) fhenry3 = Unit.create((fhenry**3).dim, name="fhenry3", dispname=f"{str(fhenry)}^3", scale=fhenry.scale*3) hhenry2 = Unit.create((hhenry**2).dim, name="hhenry2", dispname=f"{str(hhenry)}^2", scale=hhenry.scale*2) hhenry3 = Unit.create((hhenry**3).dim, name="hhenry3", dispname=f"{str(hhenry)}^3", scale=hhenry.scale*3) dahenry2 = Unit.create((dahenry**2).dim, name="dahenry2", dispname=f"{str(dahenry)}^2", scale=dahenry.scale*2) dahenry3 = Unit.create((dahenry**3).dim, name="dahenry3", dispname=f"{str(dahenry)}^3", scale=dahenry.scale*3) mhenry2 = Unit.create((mhenry**2).dim, name="mhenry2", dispname=f"{str(mhenry)}^2", scale=mhenry.scale*2) mhenry3 = Unit.create((mhenry**3).dim, name="mhenry3", dispname=f"{str(mhenry)}^3", scale=mhenry.scale*3) nhenry2 = Unit.create((nhenry**2).dim, name="nhenry2", dispname=f"{str(nhenry)}^2", scale=nhenry.scale*2) nhenry3 = Unit.create((nhenry**3).dim, name="nhenry3", dispname=f"{str(nhenry)}^3", scale=nhenry.scale*3) phenry2 = Unit.create((phenry**2).dim, name="phenry2", dispname=f"{str(phenry)}^2", scale=phenry.scale*2) phenry3 = Unit.create((phenry**3).dim, name="phenry3", dispname=f"{str(phenry)}^3", scale=phenry.scale*3) uhenry2 = Unit.create((uhenry**2).dim, name="uhenry2", dispname=f"{str(uhenry)}^2", scale=uhenry.scale*2) uhenry3 = Unit.create((uhenry**3).dim, name="uhenry3", dispname=f"{str(uhenry)}^3", scale=uhenry.scale*3) Thenry2 = Unit.create((Thenry**2).dim, name="Thenry2", dispname=f"{str(Thenry)}^2", scale=Thenry.scale*2) Thenry3 = Unit.create((Thenry**3).dim, name="Thenry3", dispname=f"{str(Thenry)}^3", scale=Thenry.scale*3) yhenry2 = Unit.create((yhenry**2).dim, name="yhenry2", dispname=f"{str(yhenry)}^2", scale=yhenry.scale*2) yhenry3 = Unit.create((yhenry**3).dim, name="yhenry3", dispname=f"{str(yhenry)}^3", scale=yhenry.scale*3) Ehenry2 = Unit.create((Ehenry**2).dim, name="Ehenry2", dispname=f"{str(Ehenry)}^2", scale=Ehenry.scale*2) Ehenry3 = Unit.create((Ehenry**3).dim, name="Ehenry3", dispname=f"{str(Ehenry)}^3", scale=Ehenry.scale*3) zhenry2 = Unit.create((zhenry**2).dim, name="zhenry2", dispname=f"{str(zhenry)}^2", scale=zhenry.scale*2) zhenry3 = Unit.create((zhenry**3).dim, name="zhenry3", dispname=f"{str(zhenry)}^3", scale=zhenry.scale*3) Mhenry2 = Unit.create((Mhenry**2).dim, name="Mhenry2", dispname=f"{str(Mhenry)}^2", scale=Mhenry.scale*2) Mhenry3 = Unit.create((Mhenry**3).dim, name="Mhenry3", dispname=f"{str(Mhenry)}^3", scale=Mhenry.scale*3) khenry2 = Unit.create((khenry**2).dim, name="khenry2", dispname=f"{str(khenry)}^2", scale=khenry.scale*2) khenry3 = Unit.create((khenry**3).dim, name="khenry3", dispname=f"{str(khenry)}^3", scale=khenry.scale*3) Yhenry2 = Unit.create((Yhenry**2).dim, name="Yhenry2", dispname=f"{str(Yhenry)}^2", scale=Yhenry.scale*2) Yhenry3 = Unit.create((Yhenry**3).dim, name="Yhenry3", dispname=f"{str(Yhenry)}^3", scale=Yhenry.scale*3) alumen2 = Unit.create((alumen**2).dim, name="alumen2", dispname=f"{str(alumen)}^2", scale=alumen.scale*2) alumen3 = Unit.create((alumen**3).dim, name="alumen3", dispname=f"{str(alumen)}^3", scale=alumen.scale*3) clumen2 = Unit.create((clumen**2).dim, name="clumen2", dispname=f"{str(clumen)}^2", scale=clumen.scale*2) clumen3 = Unit.create((clumen**3).dim, name="clumen3", dispname=f"{str(clumen)}^3", scale=clumen.scale*3) Zlumen2 = Unit.create((Zlumen**2).dim, name="Zlumen2", dispname=f"{str(Zlumen)}^2", scale=Zlumen.scale*2) Zlumen3 = Unit.create((Zlumen**3).dim, name="Zlumen3", dispname=f"{str(Zlumen)}^3", scale=Zlumen.scale*3) Plumen2 = Unit.create((Plumen**2).dim, name="Plumen2", dispname=f"{str(Plumen)}^2", scale=Plumen.scale*2) Plumen3 = Unit.create((Plumen**3).dim, name="Plumen3", dispname=f"{str(Plumen)}^3", scale=Plumen.scale*3) dlumen2 = Unit.create((dlumen**2).dim, name="dlumen2", dispname=f"{str(dlumen)}^2", scale=dlumen.scale*2) dlumen3 = Unit.create((dlumen**3).dim, name="dlumen3", dispname=f"{str(dlumen)}^3", scale=dlumen.scale*3) Glumen2 = Unit.create((Glumen**2).dim, name="Glumen2", dispname=f"{str(Glumen)}^2", scale=Glumen.scale*2) Glumen3 = Unit.create((Glumen**3).dim, name="Glumen3", dispname=f"{str(Glumen)}^3", scale=Glumen.scale*3) flumen2 = Unit.create((flumen**2).dim, name="flumen2", dispname=f"{str(flumen)}^2", scale=flumen.scale*2) flumen3 = Unit.create((flumen**3).dim, name="flumen3", dispname=f"{str(flumen)}^3", scale=flumen.scale*3) hlumen2 = Unit.create((hlumen**2).dim, name="hlumen2", dispname=f"{str(hlumen)}^2", scale=hlumen.scale*2) hlumen3 = Unit.create((hlumen**3).dim, name="hlumen3", dispname=f"{str(hlumen)}^3", scale=hlumen.scale*3) dalumen2 = Unit.create((dalumen**2).dim, name="dalumen2", dispname=f"{str(dalumen)}^2", scale=dalumen.scale*2) dalumen3 = Unit.create((dalumen**3).dim, name="dalumen3", dispname=f"{str(dalumen)}^3", scale=dalumen.scale*3) mlumen2 = Unit.create((mlumen**2).dim, name="mlumen2", dispname=f"{str(mlumen)}^2", scale=mlumen.scale*2) mlumen3 = Unit.create((mlumen**3).dim, name="mlumen3", dispname=f"{str(mlumen)}^3", scale=mlumen.scale*3) nlumen2 = Unit.create((nlumen**2).dim, name="nlumen2", dispname=f"{str(nlumen)}^2", scale=nlumen.scale*2) nlumen3 = Unit.create((nlumen**3).dim, name="nlumen3", dispname=f"{str(nlumen)}^3", scale=nlumen.scale*3) plumen2 = Unit.create((plumen**2).dim, name="plumen2", dispname=f"{str(plumen)}^2", scale=plumen.scale*2) plumen3 = Unit.create((plumen**3).dim, name="plumen3", dispname=f"{str(plumen)}^3", scale=plumen.scale*3) ulumen2 = Unit.create((ulumen**2).dim, name="ulumen2", dispname=f"{str(ulumen)}^2", scale=ulumen.scale*2) ulumen3 = Unit.create((ulumen**3).dim, name="ulumen3", dispname=f"{str(ulumen)}^3", scale=ulumen.scale*3) Tlumen2 = Unit.create((Tlumen**2).dim, name="Tlumen2", dispname=f"{str(Tlumen)}^2", scale=Tlumen.scale*2) Tlumen3 = Unit.create((Tlumen**3).dim, name="Tlumen3", dispname=f"{str(Tlumen)}^3", scale=Tlumen.scale*3) ylumen2 = Unit.create((ylumen**2).dim, name="ylumen2", dispname=f"{str(ylumen)}^2", scale=ylumen.scale*2) ylumen3 = Unit.create((ylumen**3).dim, name="ylumen3", dispname=f"{str(ylumen)}^3", scale=ylumen.scale*3) Elumen2 = Unit.create((Elumen**2).dim, name="Elumen2", dispname=f"{str(Elumen)}^2", scale=Elumen.scale*2) Elumen3 = Unit.create((Elumen**3).dim, name="Elumen3", dispname=f"{str(Elumen)}^3", scale=Elumen.scale*3) zlumen2 = Unit.create((zlumen**2).dim, name="zlumen2", dispname=f"{str(zlumen)}^2", scale=zlumen.scale*2) zlumen3 = Unit.create((zlumen**3).dim, name="zlumen3", dispname=f"{str(zlumen)}^3", scale=zlumen.scale*3) Mlumen2 = Unit.create((Mlumen**2).dim, name="Mlumen2", dispname=f"{str(Mlumen)}^2", scale=Mlumen.scale*2) Mlumen3 = Unit.create((Mlumen**3).dim, name="Mlumen3", dispname=f"{str(Mlumen)}^3", scale=Mlumen.scale*3) klumen2 = Unit.create((klumen**2).dim, name="klumen2", dispname=f"{str(klumen)}^2", scale=klumen.scale*2) klumen3 = Unit.create((klumen**3).dim, name="klumen3", dispname=f"{str(klumen)}^3", scale=klumen.scale*3) Ylumen2 = Unit.create((Ylumen**2).dim, name="Ylumen2", dispname=f"{str(Ylumen)}^2", scale=Ylumen.scale*2) Ylumen3 = Unit.create((Ylumen**3).dim, name="Ylumen3", dispname=f"{str(Ylumen)}^3", scale=Ylumen.scale*3) alux2 = Unit.create((alux**2).dim, name="alux2", dispname=f"{str(alux)}^2", scale=alux.scale*2) alux3 = Unit.create((alux**3).dim, name="alux3", dispname=f"{str(alux)}^3", scale=alux.scale*3) clux2 = Unit.create((clux**2).dim, name="clux2", dispname=f"{str(clux)}^2", scale=clux.scale*2) clux3 = Unit.create((clux**3).dim, name="clux3", dispname=f"{str(clux)}^3", scale=clux.scale*3) Zlux2 = Unit.create((Zlux**2).dim, name="Zlux2", dispname=f"{str(Zlux)}^2", scale=Zlux.scale*2) Zlux3 = Unit.create((Zlux**3).dim, name="Zlux3", dispname=f"{str(Zlux)}^3", scale=Zlux.scale*3) Plux2 = Unit.create((Plux**2).dim, name="Plux2", dispname=f"{str(Plux)}^2", scale=Plux.scale*2) Plux3 = Unit.create((Plux**3).dim, name="Plux3", dispname=f"{str(Plux)}^3", scale=Plux.scale*3) dlux2 = Unit.create((dlux**2).dim, name="dlux2", dispname=f"{str(dlux)}^2", scale=dlux.scale*2) dlux3 = Unit.create((dlux**3).dim, name="dlux3", dispname=f"{str(dlux)}^3", scale=dlux.scale*3) Glux2 = Unit.create((Glux**2).dim, name="Glux2", dispname=f"{str(Glux)}^2", scale=Glux.scale*2) Glux3 = Unit.create((Glux**3).dim, name="Glux3", dispname=f"{str(Glux)}^3", scale=Glux.scale*3) flux2 = Unit.create((flux**2).dim, name="flux2", dispname=f"{str(flux)}^2", scale=flux.scale*2) flux3 = Unit.create((flux**3).dim, name="flux3", dispname=f"{str(flux)}^3", scale=flux.scale*3) hlux2 = Unit.create((hlux**2).dim, name="hlux2", dispname=f"{str(hlux)}^2", scale=hlux.scale*2) hlux3 = Unit.create((hlux**3).dim, name="hlux3", dispname=f"{str(hlux)}^3", scale=hlux.scale*3) dalux2 = Unit.create((dalux**2).dim, name="dalux2", dispname=f"{str(dalux)}^2", scale=dalux.scale*2) dalux3 = Unit.create((dalux**3).dim, name="dalux3", dispname=f"{str(dalux)}^3", scale=dalux.scale*3) mlux2 = Unit.create((mlux**2).dim, name="mlux2", dispname=f"{str(mlux)}^2", scale=mlux.scale*2) mlux3 = Unit.create((mlux**3).dim, name="mlux3", dispname=f"{str(mlux)}^3", scale=mlux.scale*3) nlux2 = Unit.create((nlux**2).dim, name="nlux2", dispname=f"{str(nlux)}^2", scale=nlux.scale*2) nlux3 = Unit.create((nlux**3).dim, name="nlux3", dispname=f"{str(nlux)}^3", scale=nlux.scale*3) plux2 = Unit.create((plux**2).dim, name="plux2", dispname=f"{str(plux)}^2", scale=plux.scale*2) plux3 = Unit.create((plux**3).dim, name="plux3", dispname=f"{str(plux)}^3", scale=plux.scale*3) ulux2 = Unit.create((ulux**2).dim, name="ulux2", dispname=f"{str(ulux)}^2", scale=ulux.scale*2) ulux3 = Unit.create((ulux**3).dim, name="ulux3", dispname=f"{str(ulux)}^3", scale=ulux.scale*3) Tlux2 = Unit.create((Tlux**2).dim, name="Tlux2", dispname=f"{str(Tlux)}^2", scale=Tlux.scale*2) Tlux3 = Unit.create((Tlux**3).dim, name="Tlux3", dispname=f"{str(Tlux)}^3", scale=Tlux.scale*3) ylux2 = Unit.create((ylux**2).dim, name="ylux2", dispname=f"{str(ylux)}^2", scale=ylux.scale*2) ylux3 = Unit.create((ylux**3).dim, name="ylux3", dispname=f"{str(ylux)}^3", scale=ylux.scale*3) Elux2 = Unit.create((Elux**2).dim, name="Elux2", dispname=f"{str(Elux)}^2", scale=Elux.scale*2) Elux3 = Unit.create((Elux**3).dim, name="Elux3", dispname=f"{str(Elux)}^3", scale=Elux.scale*3) zlux2 = Unit.create((zlux**2).dim, name="zlux2", dispname=f"{str(zlux)}^2", scale=zlux.scale*2) zlux3 = Unit.create((zlux**3).dim, name="zlux3", dispname=f"{str(zlux)}^3", scale=zlux.scale*3) Mlux2 = Unit.create((Mlux**2).dim, name="Mlux2", dispname=f"{str(Mlux)}^2", scale=Mlux.scale*2) Mlux3 = Unit.create((Mlux**3).dim, name="Mlux3", dispname=f"{str(Mlux)}^3", scale=Mlux.scale*3) klux2 = Unit.create((klux**2).dim, name="klux2", dispname=f"{str(klux)}^2", scale=klux.scale*2) klux3 = Unit.create((klux**3).dim, name="klux3", dispname=f"{str(klux)}^3", scale=klux.scale*3) Ylux2 = Unit.create((Ylux**2).dim, name="Ylux2", dispname=f"{str(Ylux)}^2", scale=Ylux.scale*2) Ylux3 = Unit.create((Ylux**3).dim, name="Ylux3", dispname=f"{str(Ylux)}^3", scale=Ylux.scale*3) abecquerel2 = Unit.create((abecquerel**2).dim, name="abecquerel2", dispname=f"{str(abecquerel)}^2", scale=abecquerel.scale*2) abecquerel3 = Unit.create((abecquerel**3).dim, name="abecquerel3", dispname=f"{str(abecquerel)}^3", scale=abecquerel.scale*3) cbecquerel2 = Unit.create((cbecquerel**2).dim, name="cbecquerel2", dispname=f"{str(cbecquerel)}^2", scale=cbecquerel.scale*2) cbecquerel3 = Unit.create((cbecquerel**3).dim, name="cbecquerel3", dispname=f"{str(cbecquerel)}^3", scale=cbecquerel.scale*3) Zbecquerel2 = Unit.create((Zbecquerel**2).dim, name="Zbecquerel2", dispname=f"{str(Zbecquerel)}^2", scale=Zbecquerel.scale*2) Zbecquerel3 = Unit.create((Zbecquerel**3).dim, name="Zbecquerel3", dispname=f"{str(Zbecquerel)}^3", scale=Zbecquerel.scale*3) Pbecquerel2 = Unit.create((Pbecquerel**2).dim, name="Pbecquerel2", dispname=f"{str(Pbecquerel)}^2", scale=Pbecquerel.scale*2) Pbecquerel3 = Unit.create((Pbecquerel**3).dim, name="Pbecquerel3", dispname=f"{str(Pbecquerel)}^3", scale=Pbecquerel.scale*3) dbecquerel2 = Unit.create((dbecquerel**2).dim, name="dbecquerel2", dispname=f"{str(dbecquerel)}^2", scale=dbecquerel.scale*2) dbecquerel3 = Unit.create((dbecquerel**3).dim, name="dbecquerel3", dispname=f"{str(dbecquerel)}^3", scale=dbecquerel.scale*3) Gbecquerel2 = Unit.create((Gbecquerel**2).dim, name="Gbecquerel2", dispname=f"{str(Gbecquerel)}^2", scale=Gbecquerel.scale*2) Gbecquerel3 = Unit.create((Gbecquerel**3).dim, name="Gbecquerel3", dispname=f"{str(Gbecquerel)}^3", scale=Gbecquerel.scale*3) fbecquerel2 = Unit.create((fbecquerel**2).dim, name="fbecquerel2", dispname=f"{str(fbecquerel)}^2", scale=fbecquerel.scale*2) fbecquerel3 = Unit.create((fbecquerel**3).dim, name="fbecquerel3", dispname=f"{str(fbecquerel)}^3", scale=fbecquerel.scale*3) hbecquerel2 = Unit.create((hbecquerel**2).dim, name="hbecquerel2", dispname=f"{str(hbecquerel)}^2", scale=hbecquerel.scale*2) hbecquerel3 = Unit.create((hbecquerel**3).dim, name="hbecquerel3", dispname=f"{str(hbecquerel)}^3", scale=hbecquerel.scale*3) dabecquerel2 = Unit.create((dabecquerel**2).dim, name="dabecquerel2", dispname=f"{str(dabecquerel)}^2", scale=dabecquerel.scale*2) dabecquerel3 = Unit.create((dabecquerel**3).dim, name="dabecquerel3", dispname=f"{str(dabecquerel)}^3", scale=dabecquerel.scale*3) mbecquerel2 = Unit.create((mbecquerel**2).dim, name="mbecquerel2", dispname=f"{str(mbecquerel)}^2", scale=mbecquerel.scale*2) mbecquerel3 = Unit.create((mbecquerel**3).dim, name="mbecquerel3", dispname=f"{str(mbecquerel)}^3", scale=mbecquerel.scale*3) nbecquerel2 = Unit.create((nbecquerel**2).dim, name="nbecquerel2", dispname=f"{str(nbecquerel)}^2", scale=nbecquerel.scale*2) nbecquerel3 = Unit.create((nbecquerel**3).dim, name="nbecquerel3", dispname=f"{str(nbecquerel)}^3", scale=nbecquerel.scale*3) pbecquerel2 = Unit.create((pbecquerel**2).dim, name="pbecquerel2", dispname=f"{str(pbecquerel)}^2", scale=pbecquerel.scale*2) pbecquerel3 = Unit.create((pbecquerel**3).dim, name="pbecquerel3", dispname=f"{str(pbecquerel)}^3", scale=pbecquerel.scale*3) ubecquerel2 = Unit.create((ubecquerel**2).dim, name="ubecquerel2", dispname=f"{str(ubecquerel)}^2", scale=ubecquerel.scale*2) ubecquerel3 = Unit.create((ubecquerel**3).dim, name="ubecquerel3", dispname=f"{str(ubecquerel)}^3", scale=ubecquerel.scale*3) Tbecquerel2 = Unit.create((Tbecquerel**2).dim, name="Tbecquerel2", dispname=f"{str(Tbecquerel)}^2", scale=Tbecquerel.scale*2) Tbecquerel3 = Unit.create((Tbecquerel**3).dim, name="Tbecquerel3", dispname=f"{str(Tbecquerel)}^3", scale=Tbecquerel.scale*3) ybecquerel2 = Unit.create((ybecquerel**2).dim, name="ybecquerel2", dispname=f"{str(ybecquerel)}^2", scale=ybecquerel.scale*2) ybecquerel3 = Unit.create((ybecquerel**3).dim, name="ybecquerel3", dispname=f"{str(ybecquerel)}^3", scale=ybecquerel.scale*3) Ebecquerel2 = Unit.create((Ebecquerel**2).dim, name="Ebecquerel2", dispname=f"{str(Ebecquerel)}^2", scale=Ebecquerel.scale*2) Ebecquerel3 = Unit.create((Ebecquerel**3).dim, name="Ebecquerel3", dispname=f"{str(Ebecquerel)}^3", scale=Ebecquerel.scale*3) zbecquerel2 = Unit.create((zbecquerel**2).dim, name="zbecquerel2", dispname=f"{str(zbecquerel)}^2", scale=zbecquerel.scale*2) zbecquerel3 = Unit.create((zbecquerel**3).dim, name="zbecquerel3", dispname=f"{str(zbecquerel)}^3", scale=zbecquerel.scale*3) Mbecquerel2 = Unit.create((Mbecquerel**2).dim, name="Mbecquerel2", dispname=f"{str(Mbecquerel)}^2", scale=Mbecquerel.scale*2) Mbecquerel3 = Unit.create((Mbecquerel**3).dim, name="Mbecquerel3", dispname=f"{str(Mbecquerel)}^3", scale=Mbecquerel.scale*3) kbecquerel2 = Unit.create((kbecquerel**2).dim, name="kbecquerel2", dispname=f"{str(kbecquerel)}^2", scale=kbecquerel.scale*2) kbecquerel3 = Unit.create((kbecquerel**3).dim, name="kbecquerel3", dispname=f"{str(kbecquerel)}^3", scale=kbecquerel.scale*3) Ybecquerel2 = Unit.create((Ybecquerel**2).dim, name="Ybecquerel2", dispname=f"{str(Ybecquerel)}^2", scale=Ybecquerel.scale*2) Ybecquerel3 = Unit.create((Ybecquerel**3).dim, name="Ybecquerel3", dispname=f"{str(Ybecquerel)}^3", scale=Ybecquerel.scale*3) agray2 = Unit.create((agray**2).dim, name="agray2", dispname=f"{str(agray)}^2", scale=agray.scale*2) agray3 = Unit.create((agray**3).dim, name="agray3", dispname=f"{str(agray)}^3", scale=agray.scale*3) cgray2 = Unit.create((cgray**2).dim, name="cgray2", dispname=f"{str(cgray)}^2", scale=cgray.scale*2) cgray3 = Unit.create((cgray**3).dim, name="cgray3", dispname=f"{str(cgray)}^3", scale=cgray.scale*3) Zgray2 = Unit.create((Zgray**2).dim, name="Zgray2", dispname=f"{str(Zgray)}^2", scale=Zgray.scale*2) Zgray3 = Unit.create((Zgray**3).dim, name="Zgray3", dispname=f"{str(Zgray)}^3", scale=Zgray.scale*3) Pgray2 = Unit.create((Pgray**2).dim, name="Pgray2", dispname=f"{str(Pgray)}^2", scale=Pgray.scale*2) Pgray3 = Unit.create((Pgray**3).dim, name="Pgray3", dispname=f"{str(Pgray)}^3", scale=Pgray.scale*3) dgray2 = Unit.create((dgray**2).dim, name="dgray2", dispname=f"{str(dgray)}^2", scale=dgray.scale*2) dgray3 = Unit.create((dgray**3).dim, name="dgray3", dispname=f"{str(dgray)}^3", scale=dgray.scale*3) Ggray2 = Unit.create((Ggray**2).dim, name="Ggray2", dispname=f"{str(Ggray)}^2", scale=Ggray.scale*2) Ggray3 = Unit.create((Ggray**3).dim, name="Ggray3", dispname=f"{str(Ggray)}^3", scale=Ggray.scale*3) fgray2 = Unit.create((fgray**2).dim, name="fgray2", dispname=f"{str(fgray)}^2", scale=fgray.scale*2) fgray3 = Unit.create((fgray**3).dim, name="fgray3", dispname=f"{str(fgray)}^3", scale=fgray.scale*3) hgray2 = Unit.create((hgray**2).dim, name="hgray2", dispname=f"{str(hgray)}^2", scale=hgray.scale*2) hgray3 = Unit.create((hgray**3).dim, name="hgray3", dispname=f"{str(hgray)}^3", scale=hgray.scale*3) dagray2 = Unit.create((dagray**2).dim, name="dagray2", dispname=f"{str(dagray)}^2", scale=dagray.scale*2) dagray3 = Unit.create((dagray**3).dim, name="dagray3", dispname=f"{str(dagray)}^3", scale=dagray.scale*3) mgray2 = Unit.create((mgray**2).dim, name="mgray2", dispname=f"{str(mgray)}^2", scale=mgray.scale*2) mgray3 = Unit.create((mgray**3).dim, name="mgray3", dispname=f"{str(mgray)}^3", scale=mgray.scale*3) ngray2 = Unit.create((ngray**2).dim, name="ngray2", dispname=f"{str(ngray)}^2", scale=ngray.scale*2) ngray3 = Unit.create((ngray**3).dim, name="ngray3", dispname=f"{str(ngray)}^3", scale=ngray.scale*3) pgray2 = Unit.create((pgray**2).dim, name="pgray2", dispname=f"{str(pgray)}^2", scale=pgray.scale*2) pgray3 = Unit.create((pgray**3).dim, name="pgray3", dispname=f"{str(pgray)}^3", scale=pgray.scale*3) ugray2 = Unit.create((ugray**2).dim, name="ugray2", dispname=f"{str(ugray)}^2", scale=ugray.scale*2) ugray3 = Unit.create((ugray**3).dim, name="ugray3", dispname=f"{str(ugray)}^3", scale=ugray.scale*3) Tgray2 = Unit.create((Tgray**2).dim, name="Tgray2", dispname=f"{str(Tgray)}^2", scale=Tgray.scale*2) Tgray3 = Unit.create((Tgray**3).dim, name="Tgray3", dispname=f"{str(Tgray)}^3", scale=Tgray.scale*3) ygray2 = Unit.create((ygray**2).dim, name="ygray2", dispname=f"{str(ygray)}^2", scale=ygray.scale*2) ygray3 = Unit.create((ygray**3).dim, name="ygray3", dispname=f"{str(ygray)}^3", scale=ygray.scale*3) Egray2 = Unit.create((Egray**2).dim, name="Egray2", dispname=f"{str(Egray)}^2", scale=Egray.scale*2) Egray3 = Unit.create((Egray**3).dim, name="Egray3", dispname=f"{str(Egray)}^3", scale=Egray.scale*3) zgray2 = Unit.create((zgray**2).dim, name="zgray2", dispname=f"{str(zgray)}^2", scale=zgray.scale*2) zgray3 = Unit.create((zgray**3).dim, name="zgray3", dispname=f"{str(zgray)}^3", scale=zgray.scale*3) Mgray2 = Unit.create((Mgray**2).dim, name="Mgray2", dispname=f"{str(Mgray)}^2", scale=Mgray.scale*2) Mgray3 = Unit.create((Mgray**3).dim, name="Mgray3", dispname=f"{str(Mgray)}^3", scale=Mgray.scale*3) kgray2 = Unit.create((kgray**2).dim, name="kgray2", dispname=f"{str(kgray)}^2", scale=kgray.scale*2) kgray3 = Unit.create((kgray**3).dim, name="kgray3", dispname=f"{str(kgray)}^3", scale=kgray.scale*3) Ygray2 = Unit.create((Ygray**2).dim, name="Ygray2", dispname=f"{str(Ygray)}^2", scale=Ygray.scale*2) Ygray3 = Unit.create((Ygray**3).dim, name="Ygray3", dispname=f"{str(Ygray)}^3", scale=Ygray.scale*3) asievert2 = Unit.create((asievert**2).dim, name="asievert2", dispname=f"{str(asievert)}^2", scale=asievert.scale*2) asievert3 = Unit.create((asievert**3).dim, name="asievert3", dispname=f"{str(asievert)}^3", scale=asievert.scale*3) csievert2 = Unit.create((csievert**2).dim, name="csievert2", dispname=f"{str(csievert)}^2", scale=csievert.scale*2) csievert3 = Unit.create((csievert**3).dim, name="csievert3", dispname=f"{str(csievert)}^3", scale=csievert.scale*3) Zsievert2 = Unit.create((Zsievert**2).dim, name="Zsievert2", dispname=f"{str(Zsievert)}^2", scale=Zsievert.scale*2) Zsievert3 = Unit.create((Zsievert**3).dim, name="Zsievert3", dispname=f"{str(Zsievert)}^3", scale=Zsievert.scale*3) Psievert2 = Unit.create((Psievert**2).dim, name="Psievert2", dispname=f"{str(Psievert)}^2", scale=Psievert.scale*2) Psievert3 = Unit.create((Psievert**3).dim, name="Psievert3", dispname=f"{str(Psievert)}^3", scale=Psievert.scale*3) dsievert2 = Unit.create((dsievert**2).dim, name="dsievert2", dispname=f"{str(dsievert)}^2", scale=dsievert.scale*2) dsievert3 = Unit.create((dsievert**3).dim, name="dsievert3", dispname=f"{str(dsievert)}^3", scale=dsievert.scale*3) Gsievert2 = Unit.create((Gsievert**2).dim, name="Gsievert2", dispname=f"{str(Gsievert)}^2", scale=Gsievert.scale*2) Gsievert3 = Unit.create((Gsievert**3).dim, name="Gsievert3", dispname=f"{str(Gsievert)}^3", scale=Gsievert.scale*3) fsievert2 = Unit.create((fsievert**2).dim, name="fsievert2", dispname=f"{str(fsievert)}^2", scale=fsievert.scale*2) fsievert3 = Unit.create((fsievert**3).dim, name="fsievert3", dispname=f"{str(fsievert)}^3", scale=fsievert.scale*3) hsievert2 = Unit.create((hsievert**2).dim, name="hsievert2", dispname=f"{str(hsievert)}^2", scale=hsievert.scale*2) hsievert3 = Unit.create((hsievert**3).dim, name="hsievert3", dispname=f"{str(hsievert)}^3", scale=hsievert.scale*3) dasievert2 = Unit.create((dasievert**2).dim, name="dasievert2", dispname=f"{str(dasievert)}^2", scale=dasievert.scale*2) dasievert3 = Unit.create((dasievert**3).dim, name="dasievert3", dispname=f"{str(dasievert)}^3", scale=dasievert.scale*3) msievert2 = Unit.create((msievert**2).dim, name="msievert2", dispname=f"{str(msievert)}^2", scale=msievert.scale*2) msievert3 = Unit.create((msievert**3).dim, name="msievert3", dispname=f"{str(msievert)}^3", scale=msievert.scale*3) nsievert2 = Unit.create((nsievert**2).dim, name="nsievert2", dispname=f"{str(nsievert)}^2", scale=nsievert.scale*2) nsievert3 = Unit.create((nsievert**3).dim, name="nsievert3", dispname=f"{str(nsievert)}^3", scale=nsievert.scale*3) psievert2 = Unit.create((psievert**2).dim, name="psievert2", dispname=f"{str(psievert)}^2", scale=psievert.scale*2) psievert3 = Unit.create((psievert**3).dim, name="psievert3", dispname=f"{str(psievert)}^3", scale=psievert.scale*3) usievert2 = Unit.create((usievert**2).dim, name="usievert2", dispname=f"{str(usievert)}^2", scale=usievert.scale*2) usievert3 = Unit.create((usievert**3).dim, name="usievert3", dispname=f"{str(usievert)}^3", scale=usievert.scale*3) Tsievert2 = Unit.create((Tsievert**2).dim, name="Tsievert2", dispname=f"{str(Tsievert)}^2", scale=Tsievert.scale*2) Tsievert3 = Unit.create((Tsievert**3).dim, name="Tsievert3", dispname=f"{str(Tsievert)}^3", scale=Tsievert.scale*3) ysievert2 = Unit.create((ysievert**2).dim, name="ysievert2", dispname=f"{str(ysievert)}^2", scale=ysievert.scale*2) ysievert3 = Unit.create((ysievert**3).dim, name="ysievert3", dispname=f"{str(ysievert)}^3", scale=ysievert.scale*3) Esievert2 = Unit.create((Esievert**2).dim, name="Esievert2", dispname=f"{str(Esievert)}^2", scale=Esievert.scale*2) Esievert3 = Unit.create((Esievert**3).dim, name="Esievert3", dispname=f"{str(Esievert)}^3", scale=Esievert.scale*3) zsievert2 = Unit.create((zsievert**2).dim, name="zsievert2", dispname=f"{str(zsievert)}^2", scale=zsievert.scale*2) zsievert3 = Unit.create((zsievert**3).dim, name="zsievert3", dispname=f"{str(zsievert)}^3", scale=zsievert.scale*3) Msievert2 = Unit.create((Msievert**2).dim, name="Msievert2", dispname=f"{str(Msievert)}^2", scale=Msievert.scale*2) Msievert3 = Unit.create((Msievert**3).dim, name="Msievert3", dispname=f"{str(Msievert)}^3", scale=Msievert.scale*3) ksievert2 = Unit.create((ksievert**2).dim, name="ksievert2", dispname=f"{str(ksievert)}^2", scale=ksievert.scale*2) ksievert3 = Unit.create((ksievert**3).dim, name="ksievert3", dispname=f"{str(ksievert)}^3", scale=ksievert.scale*3) Ysievert2 = Unit.create((Ysievert**2).dim, name="Ysievert2", dispname=f"{str(Ysievert)}^2", scale=Ysievert.scale*2) Ysievert3 = Unit.create((Ysievert**3).dim, name="Ysievert3", dispname=f"{str(Ysievert)}^3", scale=Ysievert.scale*3) akatal2 = Unit.create((akatal**2).dim, name="akatal2", dispname=f"{str(akatal)}^2", scale=akatal.scale*2) akatal3 = Unit.create((akatal**3).dim, name="akatal3", dispname=f"{str(akatal)}^3", scale=akatal.scale*3) ckatal2 = Unit.create((ckatal**2).dim, name="ckatal2", dispname=f"{str(ckatal)}^2", scale=ckatal.scale*2) ckatal3 = Unit.create((ckatal**3).dim, name="ckatal3", dispname=f"{str(ckatal)}^3", scale=ckatal.scale*3) Zkatal2 = Unit.create((Zkatal**2).dim, name="Zkatal2", dispname=f"{str(Zkatal)}^2", scale=Zkatal.scale*2) Zkatal3 = Unit.create((Zkatal**3).dim, name="Zkatal3", dispname=f"{str(Zkatal)}^3", scale=Zkatal.scale*3) Pkatal2 = Unit.create((Pkatal**2).dim, name="Pkatal2", dispname=f"{str(Pkatal)}^2", scale=Pkatal.scale*2) Pkatal3 = Unit.create((Pkatal**3).dim, name="Pkatal3", dispname=f"{str(Pkatal)}^3", scale=Pkatal.scale*3) dkatal2 = Unit.create((dkatal**2).dim, name="dkatal2", dispname=f"{str(dkatal)}^2", scale=dkatal.scale*2) dkatal3 = Unit.create((dkatal**3).dim, name="dkatal3", dispname=f"{str(dkatal)}^3", scale=dkatal.scale*3) Gkatal2 = Unit.create((Gkatal**2).dim, name="Gkatal2", dispname=f"{str(Gkatal)}^2", scale=Gkatal.scale*2) Gkatal3 = Unit.create((Gkatal**3).dim, name="Gkatal3", dispname=f"{str(Gkatal)}^3", scale=Gkatal.scale*3) fkatal2 = Unit.create((fkatal**2).dim, name="fkatal2", dispname=f"{str(fkatal)}^2", scale=fkatal.scale*2) fkatal3 = Unit.create((fkatal**3).dim, name="fkatal3", dispname=f"{str(fkatal)}^3", scale=fkatal.scale*3) hkatal2 = Unit.create((hkatal**2).dim, name="hkatal2", dispname=f"{str(hkatal)}^2", scale=hkatal.scale*2) hkatal3 = Unit.create((hkatal**3).dim, name="hkatal3", dispname=f"{str(hkatal)}^3", scale=hkatal.scale*3) dakatal2 = Unit.create((dakatal**2).dim, name="dakatal2", dispname=f"{str(dakatal)}^2", scale=dakatal.scale*2) dakatal3 = Unit.create((dakatal**3).dim, name="dakatal3", dispname=f"{str(dakatal)}^3", scale=dakatal.scale*3) mkatal2 = Unit.create((mkatal**2).dim, name="mkatal2", dispname=f"{str(mkatal)}^2", scale=mkatal.scale*2) mkatal3 = Unit.create((mkatal**3).dim, name="mkatal3", dispname=f"{str(mkatal)}^3", scale=mkatal.scale*3) nkatal2 = Unit.create((nkatal**2).dim, name="nkatal2", dispname=f"{str(nkatal)}^2", scale=nkatal.scale*2) nkatal3 = Unit.create((nkatal**3).dim, name="nkatal3", dispname=f"{str(nkatal)}^3", scale=nkatal.scale*3) pkatal2 = Unit.create((pkatal**2).dim, name="pkatal2", dispname=f"{str(pkatal)}^2", scale=pkatal.scale*2) pkatal3 = Unit.create((pkatal**3).dim, name="pkatal3", dispname=f"{str(pkatal)}^3", scale=pkatal.scale*3) ukatal2 = Unit.create((ukatal**2).dim, name="ukatal2", dispname=f"{str(ukatal)}^2", scale=ukatal.scale*2) ukatal3 = Unit.create((ukatal**3).dim, name="ukatal3", dispname=f"{str(ukatal)}^3", scale=ukatal.scale*3) Tkatal2 = Unit.create((Tkatal**2).dim, name="Tkatal2", dispname=f"{str(Tkatal)}^2", scale=Tkatal.scale*2) Tkatal3 = Unit.create((Tkatal**3).dim, name="Tkatal3", dispname=f"{str(Tkatal)}^3", scale=Tkatal.scale*3) ykatal2 = Unit.create((ykatal**2).dim, name="ykatal2", dispname=f"{str(ykatal)}^2", scale=ykatal.scale*2) ykatal3 = Unit.create((ykatal**3).dim, name="ykatal3", dispname=f"{str(ykatal)}^3", scale=ykatal.scale*3) Ekatal2 = Unit.create((Ekatal**2).dim, name="Ekatal2", dispname=f"{str(Ekatal)}^2", scale=Ekatal.scale*2) Ekatal3 = Unit.create((Ekatal**3).dim, name="Ekatal3", dispname=f"{str(Ekatal)}^3", scale=Ekatal.scale*3) zkatal2 = Unit.create((zkatal**2).dim, name="zkatal2", dispname=f"{str(zkatal)}^2", scale=zkatal.scale*2) zkatal3 = Unit.create((zkatal**3).dim, name="zkatal3", dispname=f"{str(zkatal)}^3", scale=zkatal.scale*3) Mkatal2 = Unit.create((Mkatal**2).dim, name="Mkatal2", dispname=f"{str(Mkatal)}^2", scale=Mkatal.scale*2) Mkatal3 = Unit.create((Mkatal**3).dim, name="Mkatal3", dispname=f"{str(Mkatal)}^3", scale=Mkatal.scale*3) kkatal2 = Unit.create((kkatal**2).dim, name="kkatal2", dispname=f"{str(kkatal)}^2", scale=kkatal.scale*2) kkatal3 = Unit.create((kkatal**3).dim, name="kkatal3", dispname=f"{str(kkatal)}^3", scale=kkatal.scale*3) Ykatal2 = Unit.create((Ykatal**2).dim, name="Ykatal2", dispname=f"{str(Ykatal)}^2", scale=Ykatal.scale*2) Ykatal3 = Unit.create((Ykatal**3).dim, name="Ykatal3", dispname=f"{str(Ykatal)}^3", scale=Ykatal.scale*3) aliter = Unit.create_scaled_unit(liter, "a") liter = Unit.create_scaled_unit(liter, "") cliter = Unit.create_scaled_unit(liter, "c") Zliter = Unit.create_scaled_unit(liter, "Z") Pliter = Unit.create_scaled_unit(liter, "P") dliter = Unit.create_scaled_unit(liter, "d") Gliter = Unit.create_scaled_unit(liter, "G") fliter = Unit.create_scaled_unit(liter, "f") hliter = Unit.create_scaled_unit(liter, "h") daliter = Unit.create_scaled_unit(liter, "da") mliter = Unit.create_scaled_unit(liter, "m") nliter = Unit.create_scaled_unit(liter, "n") pliter = Unit.create_scaled_unit(liter, "p") uliter = Unit.create_scaled_unit(liter, "u") Tliter = Unit.create_scaled_unit(liter, "T") yliter = Unit.create_scaled_unit(liter, "y") Eliter = Unit.create_scaled_unit(liter, "E") zliter = Unit.create_scaled_unit(liter, "z") Mliter = Unit.create_scaled_unit(liter, "M") kliter = Unit.create_scaled_unit(liter, "k") Yliter = Unit.create_scaled_unit(liter, "Y") alitre = Unit.create_scaled_unit(litre, "a") litre = Unit.create_scaled_unit(litre, "") clitre = Unit.create_scaled_unit(litre, "c") Zlitre = Unit.create_scaled_unit(litre, "Z") Plitre = Unit.create_scaled_unit(litre, "P") dlitre = Unit.create_scaled_unit(litre, "d") Glitre = Unit.create_scaled_unit(litre, "G") flitre = Unit.create_scaled_unit(litre, "f") hlitre = Unit.create_scaled_unit(litre, "h") dalitre = Unit.create_scaled_unit(litre, "da") mlitre = Unit.create_scaled_unit(litre, "m") nlitre = Unit.create_scaled_unit(litre, "n") plitre = Unit.create_scaled_unit(litre, "p") ulitre = Unit.create_scaled_unit(litre, "u") Tlitre = Unit.create_scaled_unit(litre, "T") ylitre = Unit.create_scaled_unit(litre, "y") Elitre = Unit.create_scaled_unit(litre, "E") zlitre = Unit.create_scaled_unit(litre, "z") Mlitre = Unit.create_scaled_unit(litre, "M") klitre = Unit.create_scaled_unit(litre, "k") Ylitre = Unit.create_scaled_unit(litre, "Y") base_units = [ katal, sievert, gray, becquerel, lux, lumen, henry, tesla, weber, siemens, ohm, farad, volt, coulomb, watt, joule, pascal, newton, hertz, steradian, radian, molar, gramme, gram, kilogramme, candle, mol, mole, kelvin, ampere, amp, second, kilogram, meter, metre, ] scaled_units = [ Ykatal, kkatal, Mkatal, zkatal, Ekatal, ykatal, Tkatal, ukatal, pkatal, nkatal, mkatal, fkatal, Gkatal, Pkatal, Zkatal, akatal, Ysievert, ksievert, Msievert, zsievert, Esievert, ysievert, Tsievert, usievert, psievert, nsievert, msievert, fsievert, Gsievert, Psievert, Zsievert, asievert, Ygray, kgray, Mgray, zgray, Egray, ygray, Tgray, ugray, pgray, ngray, mgray, fgray, Ggray, Pgray, Zgray, agray, Ybecquerel, kbecquerel, Mbecquerel, zbecquerel, Ebecquerel, ybecquerel, Tbecquerel, ubecquerel, pbecquerel, nbecquerel, mbecquerel, fbecquerel, Gbecquerel, Pbecquerel, Zbecquerel, abecquerel, Ylux, klux, Mlux, zlux, Elux, ylux, Tlux, ulux, plux, nlux, mlux, flux, Glux, Plux, Zlux, alux, Ylumen, klumen, Mlumen, zlumen, Elumen, ylumen, Tlumen, ulumen, plumen, nlumen, mlumen, flumen, Glumen, Plumen, Zlumen, alumen, Yhenry, khenry, Mhenry, zhenry, Ehenry, yhenry, Thenry, uhenry, phenry, nhenry, mhenry, fhenry, Ghenry, Phenry, Zhenry, ahenry, Ytesla, ktesla, Mtesla, ztesla, Etesla, ytesla, Ttesla, utesla, ptesla, ntesla, mtesla, ftesla, Gtesla, Ptesla, Ztesla, atesla, Yweber, kweber, Mweber, zweber, Eweber, yweber, Tweber, uweber, pweber, nweber, mweber, fweber, Gweber, Pweber, Zweber, aweber, Ysiemens, ksiemens, Msiemens, zsiemens, Esiemens, ysiemens, Tsiemens, usiemens, psiemens, nsiemens, msiemens, fsiemens, Gsiemens, Psiemens, Zsiemens, asiemens, Yohm, kohm, Mohm, zohm, Eohm, yohm, Tohm, uohm, pohm, nohm, mohm, fohm, Gohm, Pohm, Zohm, aohm, Yfarad, kfarad, Mfarad, zfarad, Efarad, yfarad, Tfarad, ufarad, pfarad, nfarad, mfarad, ffarad, Gfarad, Pfarad, Zfarad, afarad, Yvolt, kvolt, Mvolt, zvolt, Evolt, yvolt, Tvolt, uvolt, pvolt, nvolt, mvolt, fvolt, Gvolt, Pvolt, Zvolt, avolt, Ycoulomb, kcoulomb, Mcoulomb, zcoulomb, Ecoulomb, ycoulomb, Tcoulomb, ucoulomb, pcoulomb, ncoulomb, mcoulomb, fcoulomb, Gcoulomb, Pcoulomb, Zcoulomb, acoulomb, Ywatt, kwatt, Mwatt, zwatt, Ewatt, ywatt, Twatt, uwatt, pwatt, nwatt, mwatt, fwatt, Gwatt, Pwatt, Zwatt, awatt, Yjoule, kjoule, Mjoule, zjoule, Ejoule, yjoule, Tjoule, ujoule, pjoule, njoule, mjoule, fjoule, Gjoule, Pjoule, Zjoule, ajoule, Ypascal, kpascal, Mpascal, zpascal, Epascal, ypascal, Tpascal, upascal, ppascal, npascal, mpascal, fpascal, Gpascal, Ppascal, Zpascal, apascal, Ynewton, knewton, Mnewton, znewton, Enewton, ynewton, Tnewton, unewton, pnewton, nnewton, mnewton, fnewton, Gnewton, Pnewton, Znewton, anewton, Yhertz, khertz, Mhertz, zhertz, Ehertz, yhertz, Thertz, uhertz, phertz, nhertz, mhertz, fhertz, Ghertz, Phertz, Zhertz, ahertz, Ysteradian, ksteradian, Msteradian, zsteradian, Esteradian, ysteradian, Tsteradian, usteradian, psteradian, nsteradian, msteradian, fsteradian, Gsteradian, Psteradian, Zsteradian, asteradian, Yradian, kradian, Mradian, zradian, Eradian, yradian, Tradian, uradian, pradian, nradian, mradian, fradian, Gradian, Pradian, Zradian, aradian, Ymolar, kmolar, Mmolar, zmolar, Emolar, ymolar, Tmolar, umolar, pmolar, nmolar, mmolar, fmolar, Gmolar, Pmolar, Zmolar, amolar, Ygramme, kgramme, Mgramme, zgramme, Egramme, ygramme, Tgramme, ugramme, pgramme, ngramme, mgramme, fgramme, Ggramme, Pgramme, Zgramme, agramme, Ygram, kgram, Mgram, zgram, Egram, ygram, Tgram, ugram, pgram, ngram, mgram, fgram, Ggram, Pgram, Zgram, agram, Ycandle, kcandle, Mcandle, zcandle, Ecandle, ycandle, Tcandle, ucandle, pcandle, ncandle, mcandle, fcandle, Gcandle, Pcandle, Zcandle, acandle, Ymol, kmol, Mmol, zmol, Emol, ymol, Tmol, umol, pmol, nmol, mmol, fmol, Gmol, Pmol, Zmol, amol, Ymole, kmole, Mmole, zmole, Emole, ymole, Tmole, umole, pmole, nmole, mmole, fmole, Gmole, Pmole, Zmole, amole, Yampere, kampere, Mampere, zampere, Eampere, yampere, Tampere, uampere, pampere, nampere, mampere, fampere, Gampere, Pampere, Zampere, aampere, Yamp, kamp, Mamp, zamp, Eamp, yamp, Tamp, uamp, pamp, namp, mamp, famp, Gamp, Pamp, Zamp, aamp, Ysecond, ksecond, Msecond, zsecond, Esecond, ysecond, Tsecond, usecond, psecond, nsecond, msecond, fsecond, Gsecond, Psecond, Zsecond, asecond, Ymeter, kmeter, Mmeter, zmeter, Emeter, ymeter, Tmeter, umeter, pmeter, nmeter, mmeter, fmeter, Gmeter, Pmeter, Zmeter, ameter, Ymetre, kmetre, Mmetre, zmetre, Emetre, ymetre, Tmetre, umetre, pmetre, nmetre, mmetre, fmetre, Gmetre, Pmetre, Zmetre, ametre, ] powered_units = [ Ykatal3, Ykatal2, kkatal3, kkatal2, Mkatal3, Mkatal2, zkatal3, zkatal2, Ekatal3, Ekatal2, ykatal3, ykatal2, Tkatal3, Tkatal2, ukatal3, ukatal2, pkatal3, pkatal2, nkatal3, nkatal2, mkatal3, mkatal2, fkatal3, fkatal2, Gkatal3, Gkatal2, Pkatal3, Pkatal2, Zkatal3, Zkatal2, akatal3, akatal2, Ysievert3, Ysievert2, ksievert3, ksievert2, Msievert3, Msievert2, zsievert3, zsievert2, Esievert3, Esievert2, ysievert3, ysievert2, Tsievert3, Tsievert2, usievert3, usievert2, psievert3, psievert2, nsievert3, nsievert2, msievert3, msievert2, fsievert3, fsievert2, Gsievert3, Gsievert2, Psievert3, Psievert2, Zsievert3, Zsievert2, asievert3, asievert2, Ygray3, Ygray2, kgray3, kgray2, Mgray3, Mgray2, zgray3, zgray2, Egray3, Egray2, ygray3, ygray2, Tgray3, Tgray2, ugray3, ugray2, pgray3, pgray2, ngray3, ngray2, mgray3, mgray2, fgray3, fgray2, Ggray3, Ggray2, Pgray3, Pgray2, Zgray3, Zgray2, agray3, agray2, Ybecquerel3, Ybecquerel2, kbecquerel3, kbecquerel2, Mbecquerel3, Mbecquerel2, zbecquerel3, zbecquerel2, Ebecquerel3, Ebecquerel2, ybecquerel3, ybecquerel2, Tbecquerel3, Tbecquerel2, ubecquerel3, ubecquerel2, pbecquerel3, pbecquerel2, nbecquerel3, nbecquerel2, mbecquerel3, mbecquerel2, fbecquerel3, fbecquerel2, Gbecquerel3, Gbecquerel2, Pbecquerel3, Pbecquerel2, Zbecquerel3, Zbecquerel2, abecquerel3, abecquerel2, Ylux3, Ylux2, klux3, klux2, Mlux3, Mlux2, zlux3, zlux2, Elux3, Elux2, ylux3, ylux2, Tlux3, Tlux2, ulux3, ulux2, plux3, plux2, nlux3, nlux2, mlux3, mlux2, flux3, flux2, Glux3, Glux2, Plux3, Plux2, Zlux3, Zlux2, alux3, alux2, Ylumen3, Ylumen2, klumen3, klumen2, Mlumen3, Mlumen2, zlumen3, zlumen2, Elumen3, Elumen2, ylumen3, ylumen2, Tlumen3, Tlumen2, ulumen3, ulumen2, plumen3, plumen2, nlumen3, nlumen2, mlumen3, mlumen2, flumen3, flumen2, Glumen3, Glumen2, Plumen3, Plumen2, Zlumen3, Zlumen2, alumen3, alumen2, Yhenry3, Yhenry2, khenry3, khenry2, Mhenry3, Mhenry2, zhenry3, zhenry2, Ehenry3, Ehenry2, yhenry3, yhenry2, Thenry3, Thenry2, uhenry3, uhenry2, phenry3, phenry2, nhenry3, nhenry2, mhenry3, mhenry2, fhenry3, fhenry2, Ghenry3, Ghenry2, Phenry3, Phenry2, Zhenry3, Zhenry2, ahenry3, ahenry2, Ytesla3, Ytesla2, ktesla3, ktesla2, Mtesla3, Mtesla2, ztesla3, ztesla2, Etesla3, Etesla2, ytesla3, ytesla2, Ttesla3, Ttesla2, utesla3, utesla2, ptesla3, ptesla2, ntesla3, ntesla2, mtesla3, mtesla2, ftesla3, ftesla2, Gtesla3, Gtesla2, Ptesla3, Ptesla2, Ztesla3, Ztesla2, atesla3, atesla2, Yweber3, Yweber2, kweber3, kweber2, Mweber3, Mweber2, zweber3, zweber2, Eweber3, Eweber2, yweber3, yweber2, Tweber3, Tweber2, uweber3, uweber2, pweber3, pweber2, nweber3, nweber2, mweber3, mweber2, fweber3, fweber2, Gweber3, Gweber2, Pweber3, Pweber2, Zweber3, Zweber2, aweber3, aweber2, Ysiemens3, Ysiemens2, ksiemens3, ksiemens2, Msiemens3, Msiemens2, zsiemens3, zsiemens2, Esiemens3, Esiemens2, ysiemens3, ysiemens2, Tsiemens3, Tsiemens2, usiemens3, usiemens2, psiemens3, psiemens2, nsiemens3, nsiemens2, msiemens3, msiemens2, fsiemens3, fsiemens2, Gsiemens3, Gsiemens2, Psiemens3, Psiemens2, Zsiemens3, Zsiemens2, asiemens3, asiemens2, Yohm3, Yohm2, kohm3, kohm2, Mohm3, Mohm2, zohm3, zohm2, Eohm3, Eohm2, yohm3, yohm2, Tohm3, Tohm2, uohm3, uohm2, pohm3, pohm2, nohm3, nohm2, mohm3, mohm2, fohm3, fohm2, Gohm3, Gohm2, Pohm3, Pohm2, Zohm3, Zohm2, aohm3, aohm2, Yfarad3, Yfarad2, kfarad3, kfarad2, Mfarad3, Mfarad2, zfarad3, zfarad2, Efarad3, Efarad2, yfarad3, yfarad2, Tfarad3, Tfarad2, ufarad3, ufarad2, pfarad3, pfarad2, nfarad3, nfarad2, mfarad3, mfarad2, ffarad3, ffarad2, Gfarad3, Gfarad2, Pfarad3, Pfarad2, Zfarad3, Zfarad2, afarad3, afarad2, Yvolt3, Yvolt2, kvolt3, kvolt2, Mvolt3, Mvolt2, zvolt3, zvolt2, Evolt3, Evolt2, yvolt3, yvolt2, Tvolt3, Tvolt2, uvolt3, uvolt2, pvolt3, pvolt2, nvolt3, nvolt2, mvolt3, mvolt2, fvolt3, fvolt2, Gvolt3, Gvolt2, Pvolt3, Pvolt2, Zvolt3, Zvolt2, avolt3, avolt2, Ycoulomb3, Ycoulomb2, kcoulomb3, kcoulomb2, Mcoulomb3, Mcoulomb2, zcoulomb3, zcoulomb2, Ecoulomb3, Ecoulomb2, ycoulomb3, ycoulomb2, Tcoulomb3, Tcoulomb2, ucoulomb3, ucoulomb2, pcoulomb3, pcoulomb2, ncoulomb3, ncoulomb2, mcoulomb3, mcoulomb2, fcoulomb3, fcoulomb2, Gcoulomb3, Gcoulomb2, Pcoulomb3, Pcoulomb2, Zcoulomb3, Zcoulomb2, acoulomb3, acoulomb2, Ywatt3, Ywatt2, kwatt3, kwatt2, Mwatt3, Mwatt2, zwatt3, zwatt2, Ewatt3, Ewatt2, ywatt3, ywatt2, Twatt3, Twatt2, uwatt3, uwatt2, pwatt3, pwatt2, nwatt3, nwatt2, mwatt3, mwatt2, fwatt3, fwatt2, Gwatt3, Gwatt2, Pwatt3, Pwatt2, Zwatt3, Zwatt2, awatt3, awatt2, Yjoule3, Yjoule2, kjoule3, kjoule2, Mjoule3, Mjoule2, zjoule3, zjoule2, Ejoule3, Ejoule2, yjoule3, yjoule2, Tjoule3, Tjoule2, ujoule3, ujoule2, pjoule3, pjoule2, njoule3, njoule2, mjoule3, mjoule2, fjoule3, fjoule2, Gjoule3, Gjoule2, Pjoule3, Pjoule2, Zjoule3, Zjoule2, ajoule3, ajoule2, Ypascal3, Ypascal2, kpascal3, kpascal2, Mpascal3, Mpascal2, zpascal3, zpascal2, Epascal3, Epascal2, ypascal3, ypascal2, Tpascal3, Tpascal2, upascal3, upascal2, ppascal3, ppascal2, npascal3, npascal2, mpascal3, mpascal2, fpascal3, fpascal2, Gpascal3, Gpascal2, Ppascal3, Ppascal2, Zpascal3, Zpascal2, apascal3, apascal2, Ynewton3, Ynewton2, knewton3, knewton2, Mnewton3, Mnewton2, znewton3, znewton2, Enewton3, Enewton2, ynewton3, ynewton2, Tnewton3, Tnewton2, unewton3, unewton2, pnewton3, pnewton2, nnewton3, nnewton2, mnewton3, mnewton2, fnewton3, fnewton2, Gnewton3, Gnewton2, Pnewton3, Pnewton2, Znewton3, Znewton2, anewton3, anewton2, Yhertz3, Yhertz2, khertz3, khertz2, Mhertz3, Mhertz2, zhertz3, zhertz2, Ehertz3, Ehertz2, yhertz3, yhertz2, Thertz3, Thertz2, uhertz3, uhertz2, phertz3, phertz2, nhertz3, nhertz2, mhertz3, mhertz2, fhertz3, fhertz2, Ghertz3, Ghertz2, Phertz3, Phertz2, Zhertz3, Zhertz2, ahertz3, ahertz2, Ysteradian3, Ysteradian2, ksteradian3, ksteradian2, Msteradian3, Msteradian2, zsteradian3, zsteradian2, Esteradian3, Esteradian2, ysteradian3, ysteradian2, Tsteradian3, Tsteradian2, usteradian3, usteradian2, psteradian3, psteradian2, nsteradian3, nsteradian2, msteradian3, msteradian2, fsteradian3, fsteradian2, Gsteradian3, Gsteradian2, Psteradian3, Psteradian2, Zsteradian3, Zsteradian2, asteradian3, asteradian2, Yradian3, Yradian2, kradian3, kradian2, Mradian3, Mradian2, zradian3, zradian2, Eradian3, Eradian2, yradian3, yradian2, Tradian3, Tradian2, uradian3, uradian2, pradian3, pradian2, nradian3, nradian2, mradian3, mradian2, fradian3, fradian2, Gradian3, Gradian2, Pradian3, Pradian2, Zradian3, Zradian2, aradian3, aradian2, Ymolar3, Ymolar2, kmolar3, kmolar2, Mmolar3, Mmolar2, zmolar3, zmolar2, Emolar3, Emolar2, ymolar3, ymolar2, Tmolar3, Tmolar2, umolar3, umolar2, pmolar3, pmolar2, nmolar3, nmolar2, mmolar3, mmolar2, fmolar3, fmolar2, Gmolar3, Gmolar2, Pmolar3, Pmolar2, Zmolar3, Zmolar2, amolar3, amolar2, Ygramme3, Ygramme2, kgramme3, kgramme2, Mgramme3, Mgramme2, zgramme3, zgramme2, Egramme3, Egramme2, ygramme3, ygramme2, Tgramme3, Tgramme2, ugramme3, ugramme2, pgramme3, pgramme2, ngramme3, ngramme2, mgramme3, mgramme2, fgramme3, fgramme2, Ggramme3, Ggramme2, Pgramme3, Pgramme2, Zgramme3, Zgramme2, agramme3, agramme2, Ygram3, Ygram2, kgram3, kgram2, Mgram3, Mgram2, zgram3, zgram2, Egram3, Egram2, ygram3, ygram2, Tgram3, Tgram2, ugram3, ugram2, pgram3, pgram2, ngram3, ngram2, mgram3, mgram2, fgram3, fgram2, Ggram3, Ggram2, Pgram3, Pgram2, Zgram3, Zgram2, agram3, agram2, Ycandle3, Ycandle2, kcandle3, kcandle2, Mcandle3, Mcandle2, zcandle3, zcandle2, Ecandle3, Ecandle2, ycandle3, ycandle2, Tcandle3, Tcandle2, ucandle3, ucandle2, pcandle3, pcandle2, ncandle3, ncandle2, mcandle3, mcandle2, fcandle3, fcandle2, Gcandle3, Gcandle2, Pcandle3, Pcandle2, Zcandle3, Zcandle2, acandle3, acandle2, Ymol3, Ymol2, kmol3, kmol2, Mmol3, Mmol2, zmol3, zmol2, Emol3, Emol2, ymol3, ymol2, Tmol3, Tmol2, umol3, umol2, pmol3, pmol2, nmol3, nmol2, mmol3, mmol2, fmol3, fmol2, Gmol3, Gmol2, Pmol3, Pmol2, Zmol3, Zmol2, amol3, amol2, Ymole3, Ymole2, kmole3, kmole2, Mmole3, Mmole2, zmole3, zmole2, Emole3, Emole2, ymole3, ymole2, Tmole3, Tmole2, umole3, umole2, pmole3, pmole2, nmole3, nmole2, mmole3, mmole2, fmole3, fmole2, Gmole3, Gmole2, Pmole3, Pmole2, Zmole3, Zmole2, amole3, amole2, Yampere3, Yampere2, kampere3, kampere2, Mampere3, Mampere2, zampere3, zampere2, Eampere3, Eampere2, yampere3, yampere2, Tampere3, Tampere2, uampere3, uampere2, pampere3, pampere2, nampere3, nampere2, mampere3, mampere2, fampere3, fampere2, Gampere3, Gampere2, Pampere3, Pampere2, Zampere3, Zampere2, aampere3, aampere2, Yamp3, Yamp2, kamp3, kamp2, Mamp3, Mamp2, zamp3, zamp2, Eamp3, Eamp2, yamp3, yamp2, Tamp3, Tamp2, uamp3, uamp2, pamp3, pamp2, namp3, namp2, mamp3, mamp2, famp3, famp2, Gamp3, Gamp2, Pamp3, Pamp2, Zamp3, Zamp2, aamp3, aamp2, Ysecond3, Ysecond2, ksecond3, ksecond2, Msecond3, Msecond2, zsecond3, zsecond2, Esecond3, Esecond2, ysecond3, ysecond2, Tsecond3, Tsecond2, usecond3, usecond2, psecond3, psecond2, nsecond3, nsecond2, msecond3, msecond2, fsecond3, fsecond2, Gsecond3, Gsecond2, Psecond3, Psecond2, Zsecond3, Zsecond2, asecond3, asecond2, Ymeter3, Ymeter2, kmeter3, kmeter2, Mmeter3, Mmeter2, zmeter3, zmeter2, Emeter3, Emeter2, ymeter3, ymeter2, Tmeter3, Tmeter2, umeter3, umeter2, pmeter3, pmeter2, nmeter3, nmeter2, mmeter3, mmeter2, fmeter3, fmeter2, Gmeter3, Gmeter2, Pmeter3, Pmeter2, Zmeter3, Zmeter2, ameter3, ameter2, Ymetre3, Ymetre2, kmetre3, kmetre2, Mmetre3, Mmetre2, zmetre3, zmetre2, Emetre3, Emetre2, ymetre3, ymetre2, Tmetre3, Tmetre2, umetre3, umetre2, pmetre3, pmetre2, nmetre3, nmetre2, mmetre3, mmetre2, fmetre3, fmetre2, Gmetre3, Gmetre2, Pmetre3, Pmetre2, Zmetre3, Zmetre2, ametre3, ametre2, katal3, katal2, sievert3, sievert2, gray3, gray2, becquerel3, becquerel2, lux3, lux2, lumen3, lumen2, henry3, henry2, tesla3, tesla2, weber3, weber2, siemens3, siemens2, ohm3, ohm2, farad3, farad2, volt3, volt2, coulomb3, coulomb2, watt3, watt2, joule3, joule2, pascal3, pascal2, newton3, newton2, hertz3, hertz2, steradian3, steradian2, radian3, radian2, molar3, molar2, gramme3, gramme2, gram3, gram2, kilogramme3, kilogramme2, candle3, candle2, mol3, mol2, mole3, mole2, kelvin3, kelvin2, ampere3, ampere2, amp3, amp2, second3, second2, kilogram3, kilogram2, meter3, meter2, metre3, metre2, ] # Current list from http://physics.nist.gov/cuu/Units/units.html, far from complete additional_units = [ pascal * second, newton * metre, watt / metre ** 2, joule / kelvin, joule / (kilogram * kelvin), joule / kilogram, watt / (metre * kelvin), joule / metre ** 3, volt / metre ** 3, coulomb / metre ** 3, coulomb / metre ** 2, farad / metre, henry / metre, joule / mole, joule / (mole * kelvin), coulomb / kilogram, gray / second, katal / metre ** 3, # We don't want liter/litre to be used as a standard unit for display, so we # put it here instead of in the standard units aliter, liter, cliter, Zliter, Pliter, dliter, Gliter, fliter, hliter, daliter, mliter, nliter, pliter, uliter, Tliter, yliter, Eliter, zliter, Mliter, kliter, Yliter, alitre, litre, clitre, Zlitre, Plitre, dlitre, Glitre, flitre, hlitre, dalitre, mlitre, nlitre, plitre, ulitre, Tlitre, ylitre, Elitre, zlitre, Mlitre, klitre, Ylitre, ] all_units = [ Ylitre, klitre, Mlitre, zlitre, Elitre, ylitre, Tlitre, ulitre, plitre, nlitre, mlitre, dalitre, hlitre, flitre, Glitre, dlitre, Plitre, Zlitre, clitre, litre, alitre, litre, Yliter, kliter, Mliter, zliter, Eliter, yliter, Tliter, uliter, pliter, nliter, mliter, daliter, hliter, fliter, Gliter, dliter, Pliter, Zliter, cliter, liter, aliter, liter, Ykatal3, Ykatal2, kkatal3, kkatal2, Mkatal3, Mkatal2, zkatal3, zkatal2, Ekatal3, Ekatal2, ykatal3, ykatal2, Tkatal3, Tkatal2, ukatal3, ukatal2, pkatal3, pkatal2, nkatal3, nkatal2, mkatal3, mkatal2, dakatal3, dakatal2, hkatal3, hkatal2, fkatal3, fkatal2, Gkatal3, Gkatal2, dkatal3, dkatal2, Pkatal3, Pkatal2, Zkatal3, Zkatal2, ckatal3, ckatal2, akatal3, akatal2, Ysievert3, Ysievert2, ksievert3, ksievert2, Msievert3, Msievert2, zsievert3, zsievert2, Esievert3, Esievert2, ysievert3, ysievert2, Tsievert3, Tsievert2, usievert3, usievert2, psievert3, psievert2, nsievert3, nsievert2, msievert3, msievert2, dasievert3, dasievert2, hsievert3, hsievert2, fsievert3, fsievert2, Gsievert3, Gsievert2, dsievert3, dsievert2, Psievert3, Psievert2, Zsievert3, Zsievert2, csievert3, csievert2, asievert3, asievert2, Ygray3, Ygray2, kgray3, kgray2, Mgray3, Mgray2, zgray3, zgray2, Egray3, Egray2, ygray3, ygray2, Tgray3, Tgray2, ugray3, ugray2, pgray3, pgray2, ngray3, ngray2, mgray3, mgray2, dagray3, dagray2, hgray3, hgray2, fgray3, fgray2, Ggray3, Ggray2, dgray3, dgray2, Pgray3, Pgray2, Zgray3, Zgray2, cgray3, cgray2, agray3, agray2, Ybecquerel3, Ybecquerel2, kbecquerel3, kbecquerel2, Mbecquerel3, Mbecquerel2, zbecquerel3, zbecquerel2, Ebecquerel3, Ebecquerel2, ybecquerel3, ybecquerel2, Tbecquerel3, Tbecquerel2, ubecquerel3, ubecquerel2, pbecquerel3, pbecquerel2, nbecquerel3, nbecquerel2, mbecquerel3, mbecquerel2, dabecquerel3, dabecquerel2, hbecquerel3, hbecquerel2, fbecquerel3, fbecquerel2, Gbecquerel3, Gbecquerel2, dbecquerel3, dbecquerel2, Pbecquerel3, Pbecquerel2, Zbecquerel3, Zbecquerel2, cbecquerel3, cbecquerel2, abecquerel3, abecquerel2, Ylux3, Ylux2, klux3, klux2, Mlux3, Mlux2, zlux3, zlux2, Elux3, Elux2, ylux3, ylux2, Tlux3, Tlux2, ulux3, ulux2, plux3, plux2, nlux3, nlux2, mlux3, mlux2, dalux3, dalux2, hlux3, hlux2, flux3, flux2, Glux3, Glux2, dlux3, dlux2, Plux3, Plux2, Zlux3, Zlux2, clux3, clux2, alux3, alux2, Ylumen3, Ylumen2, klumen3, klumen2, Mlumen3, Mlumen2, zlumen3, zlumen2, Elumen3, Elumen2, ylumen3, ylumen2, Tlumen3, Tlumen2, ulumen3, ulumen2, plumen3, plumen2, nlumen3, nlumen2, mlumen3, mlumen2, dalumen3, dalumen2, hlumen3, hlumen2, flumen3, flumen2, Glumen3, Glumen2, dlumen3, dlumen2, Plumen3, Plumen2, Zlumen3, Zlumen2, clumen3, clumen2, alumen3, alumen2, Yhenry3, Yhenry2, khenry3, khenry2, Mhenry3, Mhenry2, zhenry3, zhenry2, Ehenry3, Ehenry2, yhenry3, yhenry2, Thenry3, Thenry2, uhenry3, uhenry2, phenry3, phenry2, nhenry3, nhenry2, mhenry3, mhenry2, dahenry3, dahenry2, hhenry3, hhenry2, fhenry3, fhenry2, Ghenry3, Ghenry2, dhenry3, dhenry2, Phenry3, Phenry2, Zhenry3, Zhenry2, chenry3, chenry2, ahenry3, ahenry2, Ytesla3, Ytesla2, ktesla3, ktesla2, Mtesla3, Mtesla2, ztesla3, ztesla2, Etesla3, Etesla2, ytesla3, ytesla2, Ttesla3, Ttesla2, utesla3, utesla2, ptesla3, ptesla2, ntesla3, ntesla2, mtesla3, mtesla2, datesla3, datesla2, htesla3, htesla2, ftesla3, ftesla2, Gtesla3, Gtesla2, dtesla3, dtesla2, Ptesla3, Ptesla2, Ztesla3, Ztesla2, ctesla3, ctesla2, atesla3, atesla2, Yweber3, Yweber2, kweber3, kweber2, Mweber3, Mweber2, zweber3, zweber2, Eweber3, Eweber2, yweber3, yweber2, Tweber3, Tweber2, uweber3, uweber2, pweber3, pweber2, nweber3, nweber2, mweber3, mweber2, daweber3, daweber2, hweber3, hweber2, fweber3, fweber2, Gweber3, Gweber2, dweber3, dweber2, Pweber3, Pweber2, Zweber3, Zweber2, cweber3, cweber2, aweber3, aweber2, Ysiemens3, Ysiemens2, ksiemens3, ksiemens2, Msiemens3, Msiemens2, zsiemens3, zsiemens2, Esiemens3, Esiemens2, ysiemens3, ysiemens2, Tsiemens3, Tsiemens2, usiemens3, usiemens2, psiemens3, psiemens2, nsiemens3, nsiemens2, msiemens3, msiemens2, dasiemens3, dasiemens2, hsiemens3, hsiemens2, fsiemens3, fsiemens2, Gsiemens3, Gsiemens2, dsiemens3, dsiemens2, Psiemens3, Psiemens2, Zsiemens3, Zsiemens2, csiemens3, csiemens2, asiemens3, asiemens2, Yohm3, Yohm2, kohm3, kohm2, Mohm3, Mohm2, zohm3, zohm2, Eohm3, Eohm2, yohm3, yohm2, Tohm3, Tohm2, uohm3, uohm2, pohm3, pohm2, nohm3, nohm2, mohm3, mohm2, daohm3, daohm2, hohm3, hohm2, fohm3, fohm2, Gohm3, Gohm2, dohm3, dohm2, Pohm3, Pohm2, Zohm3, Zohm2, cohm3, cohm2, aohm3, aohm2, Yfarad3, Yfarad2, kfarad3, kfarad2, Mfarad3, Mfarad2, zfarad3, zfarad2, Efarad3, Efarad2, yfarad3, yfarad2, Tfarad3, Tfarad2, ufarad3, ufarad2, pfarad3, pfarad2, nfarad3, nfarad2, mfarad3, mfarad2, dafarad3, dafarad2, hfarad3, hfarad2, ffarad3, ffarad2, Gfarad3, Gfarad2, dfarad3, dfarad2, Pfarad3, Pfarad2, Zfarad3, Zfarad2, cfarad3, cfarad2, afarad3, afarad2, Yvolt3, Yvolt2, kvolt3, kvolt2, Mvolt3, Mvolt2, zvolt3, zvolt2, Evolt3, Evolt2, yvolt3, yvolt2, Tvolt3, Tvolt2, uvolt3, uvolt2, pvolt3, pvolt2, nvolt3, nvolt2, mvolt3, mvolt2, davolt3, davolt2, hvolt3, hvolt2, fvolt3, fvolt2, Gvolt3, Gvolt2, dvolt3, dvolt2, Pvolt3, Pvolt2, Zvolt3, Zvolt2, cvolt3, cvolt2, avolt3, avolt2, Ycoulomb3, Ycoulomb2, kcoulomb3, kcoulomb2, Mcoulomb3, Mcoulomb2, zcoulomb3, zcoulomb2, Ecoulomb3, Ecoulomb2, ycoulomb3, ycoulomb2, Tcoulomb3, Tcoulomb2, ucoulomb3, ucoulomb2, pcoulomb3, pcoulomb2, ncoulomb3, ncoulomb2, mcoulomb3, mcoulomb2, dacoulomb3, dacoulomb2, hcoulomb3, hcoulomb2, fcoulomb3, fcoulomb2, Gcoulomb3, Gcoulomb2, dcoulomb3, dcoulomb2, Pcoulomb3, Pcoulomb2, Zcoulomb3, Zcoulomb2, ccoulomb3, ccoulomb2, acoulomb3, acoulomb2, Ywatt3, Ywatt2, kwatt3, kwatt2, Mwatt3, Mwatt2, zwatt3, zwatt2, Ewatt3, Ewatt2, ywatt3, ywatt2, Twatt3, Twatt2, uwatt3, uwatt2, pwatt3, pwatt2, nwatt3, nwatt2, mwatt3, mwatt2, dawatt3, dawatt2, hwatt3, hwatt2, fwatt3, fwatt2, Gwatt3, Gwatt2, dwatt3, dwatt2, Pwatt3, Pwatt2, Zwatt3, Zwatt2, cwatt3, cwatt2, awatt3, awatt2, Yjoule3, Yjoule2, kjoule3, kjoule2, Mjoule3, Mjoule2, zjoule3, zjoule2, Ejoule3, Ejoule2, yjoule3, yjoule2, Tjoule3, Tjoule2, ujoule3, ujoule2, pjoule3, pjoule2, njoule3, njoule2, mjoule3, mjoule2, dajoule3, dajoule2, hjoule3, hjoule2, fjoule3, fjoule2, Gjoule3, Gjoule2, djoule3, djoule2, Pjoule3, Pjoule2, Zjoule3, Zjoule2, cjoule3, cjoule2, ajoule3, ajoule2, Ypascal3, Ypascal2, kpascal3, kpascal2, Mpascal3, Mpascal2, zpascal3, zpascal2, Epascal3, Epascal2, ypascal3, ypascal2, Tpascal3, Tpascal2, upascal3, upascal2, ppascal3, ppascal2, npascal3, npascal2, mpascal3, mpascal2, dapascal3, dapascal2, hpascal3, hpascal2, fpascal3, fpascal2, Gpascal3, Gpascal2, dpascal3, dpascal2, Ppascal3, Ppascal2, Zpascal3, Zpascal2, cpascal3, cpascal2, apascal3, apascal2, Ynewton3, Ynewton2, knewton3, knewton2, Mnewton3, Mnewton2, znewton3, znewton2, Enewton3, Enewton2, ynewton3, ynewton2, Tnewton3, Tnewton2, unewton3, unewton2, pnewton3, pnewton2, nnewton3, nnewton2, mnewton3, mnewton2, danewton3, danewton2, hnewton3, hnewton2, fnewton3, fnewton2, Gnewton3, Gnewton2, dnewton3, dnewton2, Pnewton3, Pnewton2, Znewton3, Znewton2, cnewton3, cnewton2, anewton3, anewton2, Yhertz3, Yhertz2, khertz3, khertz2, Mhertz3, Mhertz2, zhertz3, zhertz2, Ehertz3, Ehertz2, yhertz3, yhertz2, Thertz3, Thertz2, uhertz3, uhertz2, phertz3, phertz2, nhertz3, nhertz2, mhertz3, mhertz2, dahertz3, dahertz2, hhertz3, hhertz2, fhertz3, fhertz2, Ghertz3, Ghertz2, dhertz3, dhertz2, Phertz3, Phertz2, Zhertz3, Zhertz2, chertz3, chertz2, ahertz3, ahertz2, Ysteradian3, Ysteradian2, ksteradian3, ksteradian2, Msteradian3, Msteradian2, zsteradian3, zsteradian2, Esteradian3, Esteradian2, ysteradian3, ysteradian2, Tsteradian3, Tsteradian2, usteradian3, usteradian2, psteradian3, psteradian2, nsteradian3, nsteradian2, msteradian3, msteradian2, dasteradian3, dasteradian2, hsteradian3, hsteradian2, fsteradian3, fsteradian2, Gsteradian3, Gsteradian2, dsteradian3, dsteradian2, Psteradian3, Psteradian2, Zsteradian3, Zsteradian2, csteradian3, csteradian2, asteradian3, asteradian2, Yradian3, Yradian2, kradian3, kradian2, Mradian3, Mradian2, zradian3, zradian2, Eradian3, Eradian2, yradian3, yradian2, Tradian3, Tradian2, uradian3, uradian2, pradian3, pradian2, nradian3, nradian2, mradian3, mradian2, daradian3, daradian2, hradian3, hradian2, fradian3, fradian2, Gradian3, Gradian2, dradian3, dradian2, Pradian3, Pradian2, Zradian3, Zradian2, cradian3, cradian2, aradian3, aradian2, Ymolar3, Ymolar2, kmolar3, kmolar2, Mmolar3, Mmolar2, zmolar3, zmolar2, Emolar3, Emolar2, ymolar3, ymolar2, Tmolar3, Tmolar2, umolar3, umolar2, pmolar3, pmolar2, nmolar3, nmolar2, mmolar3, mmolar2, damolar3, damolar2, hmolar3, hmolar2, fmolar3, fmolar2, Gmolar3, Gmolar2, dmolar3, dmolar2, Pmolar3, Pmolar2, Zmolar3, Zmolar2, cmolar3, cmolar2, amolar3, amolar2, Ygramme3, Ygramme2, kgramme3, kgramme2, Mgramme3, Mgramme2, zgramme3, zgramme2, Egramme3, Egramme2, ygramme3, ygramme2, Tgramme3, Tgramme2, ugramme3, ugramme2, pgramme3, pgramme2, ngramme3, ngramme2, mgramme3, mgramme2, dagramme3, dagramme2, hgramme3, hgramme2, fgramme3, fgramme2, Ggramme3, Ggramme2, dgramme3, dgramme2, Pgramme3, Pgramme2, Zgramme3, Zgramme2, cgramme3, cgramme2, agramme3, agramme2, Ygram3, Ygram2, kgram3, kgram2, Mgram3, Mgram2, zgram3, zgram2, Egram3, Egram2, ygram3, ygram2, Tgram3, Tgram2, ugram3, ugram2, pgram3, pgram2, ngram3, ngram2, mgram3, mgram2, dagram3, dagram2, hgram3, hgram2, fgram3, fgram2, Ggram3, Ggram2, dgram3, dgram2, Pgram3, Pgram2, Zgram3, Zgram2, cgram3, cgram2, agram3, agram2, Ycandle3, Ycandle2, kcandle3, kcandle2, Mcandle3, Mcandle2, zcandle3, zcandle2, Ecandle3, Ecandle2, ycandle3, ycandle2, Tcandle3, Tcandle2, ucandle3, ucandle2, pcandle3, pcandle2, ncandle3, ncandle2, mcandle3, mcandle2, dacandle3, dacandle2, hcandle3, hcandle2, fcandle3, fcandle2, Gcandle3, Gcandle2, dcandle3, dcandle2, Pcandle3, Pcandle2, Zcandle3, Zcandle2, ccandle3, ccandle2, acandle3, acandle2, Ymol3, Ymol2, kmol3, kmol2, Mmol3, Mmol2, zmol3, zmol2, Emol3, Emol2, ymol3, ymol2, Tmol3, Tmol2, umol3, umol2, pmol3, pmol2, nmol3, nmol2, mmol3, mmol2, damol3, damol2, hmol3, hmol2, fmol3, fmol2, Gmol3, Gmol2, dmol3, dmol2, Pmol3, Pmol2, Zmol3, Zmol2, cmol3, cmol2, amol3, amol2, Ymole3, Ymole2, kmole3, kmole2, Mmole3, Mmole2, zmole3, zmole2, Emole3, Emole2, ymole3, ymole2, Tmole3, Tmole2, umole3, umole2, pmole3, pmole2, nmole3, nmole2, mmole3, mmole2, damole3, damole2, hmole3, hmole2, fmole3, fmole2, Gmole3, Gmole2, dmole3, dmole2, Pmole3, Pmole2, Zmole3, Zmole2, cmole3, cmole2, amole3, amole2, Yampere3, Yampere2, kampere3, kampere2, Mampere3, Mampere2, zampere3, zampere2, Eampere3, Eampere2, yampere3, yampere2, Tampere3, Tampere2, uampere3, uampere2, pampere3, pampere2, nampere3, nampere2, mampere3, mampere2, daampere3, daampere2, hampere3, hampere2, fampere3, fampere2, Gampere3, Gampere2, dampere3, dampere2, Pampere3, Pampere2, Zampere3, Zampere2, campere3, campere2, aampere3, aampere2, Yamp3, Yamp2, kamp3, kamp2, Mamp3, Mamp2, zamp3, zamp2, Eamp3, Eamp2, yamp3, yamp2, Tamp3, Tamp2, uamp3, uamp2, pamp3, pamp2, namp3, namp2, mamp3, mamp2, daamp3, daamp2, hamp3, hamp2, famp3, famp2, Gamp3, Gamp2, damp3, damp2, Pamp3, Pamp2, Zamp3, Zamp2, camp3, camp2, aamp3, aamp2, Ysecond3, Ysecond2, ksecond3, ksecond2, Msecond3, Msecond2, zsecond3, zsecond2, Esecond3, Esecond2, ysecond3, ysecond2, Tsecond3, Tsecond2, usecond3, usecond2, psecond3, psecond2, nsecond3, nsecond2, msecond3, msecond2, dasecond3, dasecond2, hsecond3, hsecond2, fsecond3, fsecond2, Gsecond3, Gsecond2, dsecond3, dsecond2, Psecond3, Psecond2, Zsecond3, Zsecond2, csecond3, csecond2, asecond3, asecond2, Ymeter3, Ymeter2, kmeter3, kmeter2, Mmeter3, Mmeter2, zmeter3, zmeter2, Emeter3, Emeter2, ymeter3, ymeter2, Tmeter3, Tmeter2, umeter3, umeter2, pmeter3, pmeter2, nmeter3, nmeter2, mmeter3, mmeter2, dameter3, dameter2, hmeter3, hmeter2, fmeter3, fmeter2, Gmeter3, Gmeter2, dmeter3, dmeter2, Pmeter3, Pmeter2, Zmeter3, Zmeter2, cmeter3, cmeter2, ameter3, ameter2, Ymetre3, Ymetre2, kmetre3, kmetre2, Mmetre3, Mmetre2, zmetre3, zmetre2, Emetre3, Emetre2, ymetre3, ymetre2, Tmetre3, Tmetre2, umetre3, umetre2, pmetre3, pmetre2, nmetre3, nmetre2, mmetre3, mmetre2, dametre3, dametre2, hmetre3, hmetre2, fmetre3, fmetre2, Gmetre3, Gmetre2, dmetre3, dmetre2, Pmetre3, Pmetre2, Zmetre3, Zmetre2, cmetre3, cmetre2, ametre3, ametre2, katal3, katal2, sievert3, sievert2, gray3, gray2, becquerel3, becquerel2, lux3, lux2, lumen3, lumen2, henry3, henry2, tesla3, tesla2, weber3, weber2, siemens3, siemens2, ohm3, ohm2, farad3, farad2, volt3, volt2, coulomb3, coulomb2, watt3, watt2, joule3, joule2, pascal3, pascal2, newton3, newton2, hertz3, hertz2, steradian3, steradian2, radian3, radian2, molar3, molar2, gramme3, gramme2, gram3, gram2, kilogramme3, kilogramme2, candle3, candle2, mol3, mol2, mole3, mole2, kelvin3, kelvin2, ampere3, ampere2, amp3, amp2, second3, second2, kilogram3, kilogram2, meter3, meter2, metre3, metre2, Ykatal, kkatal, Mkatal, zkatal, Ekatal, ykatal, Tkatal, ukatal, pkatal, nkatal, mkatal, dakatal, hkatal, fkatal, Gkatal, dkatal, Pkatal, Zkatal, ckatal, akatal, Ysievert, ksievert, Msievert, zsievert, Esievert, ysievert, Tsievert, usievert, psievert, nsievert, msievert, dasievert, hsievert, fsievert, Gsievert, dsievert, Psievert, Zsievert, csievert, asievert, Ygray, kgray, Mgray, zgray, Egray, ygray, Tgray, ugray, pgray, ngray, mgray, dagray, hgray, fgray, Ggray, dgray, Pgray, Zgray, cgray, agray, Ybecquerel, kbecquerel, Mbecquerel, zbecquerel, Ebecquerel, ybecquerel, Tbecquerel, ubecquerel, pbecquerel, nbecquerel, mbecquerel, dabecquerel, hbecquerel, fbecquerel, Gbecquerel, dbecquerel, Pbecquerel, Zbecquerel, cbecquerel, abecquerel, Ylux, klux, Mlux, zlux, Elux, ylux, Tlux, ulux, plux, nlux, mlux, dalux, hlux, flux, Glux, dlux, Plux, Zlux, clux, alux, Ylumen, klumen, Mlumen, zlumen, Elumen, ylumen, Tlumen, ulumen, plumen, nlumen, mlumen, dalumen, hlumen, flumen, Glumen, dlumen, Plumen, Zlumen, clumen, alumen, Yhenry, khenry, Mhenry, zhenry, Ehenry, yhenry, Thenry, uhenry, phenry, nhenry, mhenry, dahenry, hhenry, fhenry, Ghenry, dhenry, Phenry, Zhenry, chenry, ahenry, Ytesla, ktesla, Mtesla, ztesla, Etesla, ytesla, Ttesla, utesla, ptesla, ntesla, mtesla, datesla, htesla, ftesla, Gtesla, dtesla, Ptesla, Ztesla, ctesla, atesla, Yweber, kweber, Mweber, zweber, Eweber, yweber, Tweber, uweber, pweber, nweber, mweber, daweber, hweber, fweber, Gweber, dweber, Pweber, Zweber, cweber, aweber, Ysiemens, ksiemens, Msiemens, zsiemens, Esiemens, ysiemens, Tsiemens, usiemens, psiemens, nsiemens, msiemens, dasiemens, hsiemens, fsiemens, Gsiemens, dsiemens, Psiemens, Zsiemens, csiemens, asiemens, Yohm, kohm, Mohm, zohm, Eohm, yohm, Tohm, uohm, pohm, nohm, mohm, daohm, hohm, fohm, Gohm, dohm, Pohm, Zohm, cohm, aohm, Yfarad, kfarad, Mfarad, zfarad, Efarad, yfarad, Tfarad, ufarad, pfarad, nfarad, mfarad, dafarad, hfarad, ffarad, Gfarad, dfarad, Pfarad, Zfarad, cfarad, afarad, Yvolt, kvolt, Mvolt, zvolt, Evolt, yvolt, Tvolt, uvolt, pvolt, nvolt, mvolt, davolt, hvolt, fvolt, Gvolt, dvolt, Pvolt, Zvolt, cvolt, avolt, Ycoulomb, kcoulomb, Mcoulomb, zcoulomb, Ecoulomb, ycoulomb, Tcoulomb, ucoulomb, pcoulomb, ncoulomb, mcoulomb, dacoulomb, hcoulomb, fcoulomb, Gcoulomb, dcoulomb, Pcoulomb, Zcoulomb, ccoulomb, acoulomb, Ywatt, kwatt, Mwatt, zwatt, Ewatt, ywatt, Twatt, uwatt, pwatt, nwatt, mwatt, dawatt, hwatt, fwatt, Gwatt, dwatt, Pwatt, Zwatt, cwatt, awatt, Yjoule, kjoule, Mjoule, zjoule, Ejoule, yjoule, Tjoule, ujoule, pjoule, njoule, mjoule, dajoule, hjoule, fjoule, Gjoule, djoule, Pjoule, Zjoule, cjoule, ajoule, Ypascal, kpascal, Mpascal, zpascal, Epascal, ypascal, Tpascal, upascal, ppascal, npascal, mpascal, dapascal, hpascal, fpascal, Gpascal, dpascal, Ppascal, Zpascal, cpascal, apascal, Ynewton, knewton, Mnewton, znewton, Enewton, ynewton, Tnewton, unewton, pnewton, nnewton, mnewton, danewton, hnewton, fnewton, Gnewton, dnewton, Pnewton, Znewton, cnewton, anewton, Yhertz, khertz, Mhertz, zhertz, Ehertz, yhertz, Thertz, uhertz, phertz, nhertz, mhertz, dahertz, hhertz, fhertz, Ghertz, dhertz, Phertz, Zhertz, chertz, ahertz, Ysteradian, ksteradian, Msteradian, zsteradian, Esteradian, ysteradian, Tsteradian, usteradian, psteradian, nsteradian, msteradian, dasteradian, hsteradian, fsteradian, Gsteradian, dsteradian, Psteradian, Zsteradian, csteradian, asteradian, Yradian, kradian, Mradian, zradian, Eradian, yradian, Tradian, uradian, pradian, nradian, mradian, daradian, hradian, fradian, Gradian, dradian, Pradian, Zradian, cradian, aradian, Ymolar, kmolar, Mmolar, zmolar, Emolar, ymolar, Tmolar, umolar, pmolar, nmolar, mmolar, damolar, hmolar, fmolar, Gmolar, dmolar, Pmolar, Zmolar, cmolar, amolar, Ygramme, kgramme, Mgramme, zgramme, Egramme, ygramme, Tgramme, ugramme, pgramme, ngramme, mgramme, dagramme, hgramme, fgramme, Ggramme, dgramme, Pgramme, Zgramme, cgramme, agramme, Ygram, kgram, Mgram, zgram, Egram, ygram, Tgram, ugram, pgram, ngram, mgram, dagram, hgram, fgram, Ggram, dgram, Pgram, Zgram, cgram, agram, Ycandle, kcandle, Mcandle, zcandle, Ecandle, ycandle, Tcandle, ucandle, pcandle, ncandle, mcandle, dacandle, hcandle, fcandle, Gcandle, dcandle, Pcandle, Zcandle, ccandle, acandle, Ymol, kmol, Mmol, zmol, Emol, ymol, Tmol, umol, pmol, nmol, mmol, damol, hmol, fmol, Gmol, dmol, Pmol, Zmol, cmol, amol, Ymole, kmole, Mmole, zmole, Emole, ymole, Tmole, umole, pmole, nmole, mmole, damole, hmole, fmole, Gmole, dmole, Pmole, Zmole, cmole, amole, Yampere, kampere, Mampere, zampere, Eampere, yampere, Tampere, uampere, pampere, nampere, mampere, daampere, hampere, fampere, Gampere, dampere, Pampere, Zampere, campere, aampere, Yamp, kamp, Mamp, zamp, Eamp, yamp, Tamp, uamp, pamp, namp, mamp, daamp, hamp, famp, Gamp, damp, Pamp, Zamp, camp, aamp, Ysecond, ksecond, Msecond, zsecond, Esecond, ysecond, Tsecond, usecond, psecond, nsecond, msecond, dasecond, hsecond, fsecond, Gsecond, dsecond, Psecond, Zsecond, csecond, asecond, Ymeter, kmeter, Mmeter, zmeter, Emeter, ymeter, Tmeter, umeter, pmeter, nmeter, mmeter, dameter, hmeter, fmeter, Gmeter, dmeter, Pmeter, Zmeter, cmeter, ameter, Ymetre, kmetre, Mmetre, zmetre, Emetre, ymetre, Tmetre, umetre, pmetre, nmetre, mmetre, dametre, hmetre, fmetre, Gmetre, dmetre, Pmetre, Zmetre, cmetre, ametre, katal, sievert, gray, becquerel, lux, lumen, henry, tesla, weber, siemens, ohm, farad, volt, coulomb, watt, joule, pascal, newton, hertz, steradian, radian, molar, gramme, gram, kilogramme, candle, mol, mole, kelvin, ampere, amp, second, kilogram, meter, metre, ] class _Celsius: """ A dummy object to raise errors when ``celsius`` is used. The use of `celsius` can lead to ambiguities when mixed with temperatures in `kelvin`, so its use is no longer supported. See github issue #817 for details. """ error_text = ( "The unit 'celsius' is no longer supported to avoid" "ambiguities when mixed with absolute temperatures defined" "in Kelvin. Directly use 'kelvin' when you are only" "interested in temperature differences, and add the" "'zero_celsius' constant from the brian2.units.constants" "module if you want to convert a temperature from Celsius to" "Kelvin." ) def __mul__(self, other): raise TypeError(_Celsius.error_text) def __rmul__(self, other): raise TypeError(_Celsius.error_text) def __div__(self, other): raise TypeError(_Celsius.error_text) def __rdiv__(self, other): raise TypeError(_Celsius.error_text) def __pow__(self, other): raise TypeError(_Celsius.error_text) def __eq__(self, other): raise TypeError(_Celsius.error_text) def __neq__(self, other): raise TypeError(_Celsius.error_text) celsius = _Celsius() Unit.automatically_register_units = True for unit in itertools.chain(powered_units, scaled_units, base_units): standard_unit_register.add(unit) for unit in additional_units: additional_unit_register.add(unit) # fmt: on brian2-2.5.4/brian2/units/constants.py000066400000000000000000000064361445201106100176070ustar00rootroot00000000000000r""" A module providing some physical units as `Quantity` objects. Note that these units are not imported by wildcard imports (e.g. `from brian2 import *`), they have to be imported explicitly. You can use ``import ... as ...`` to import them with shorter names, e.g.:: from brian2.units.constants import faraday_constant as F The available constants are: ==================== ================== ======================= ================================================================== Constant Symbol(s) Brian name Value ==================== ================== ======================= ================================================================== Avogadro constant :math:`N_A, L` ``avogadro_constant`` :math:`6.022140857\times 10^{23}\,\mathrm{mol}^{-1}` Boltzmann constant :math:`k` ``boltzmann_constant`` :math:`1.38064852\times 10^{-23}\,\mathrm{J}\,\mathrm{K}^{-1}` Electric constant :math:`\epsilon_0` ``electric_constant`` :math:`8.854187817\times 10^{-12}\,\mathrm{F}\,\mathrm{m}^{-1}` Electron mass :math:`m_e` ``electron_mass`` :math:`9.10938356\times 10^{-31}\,\mathrm{kg}` Elementary charge :math:`e` ``elementary_charge`` :math:`1.6021766208\times 10^{-19}\,\mathrm{C}` Faraday constant :math:`F` ``faraday_constant`` :math:`96485.33289\,\mathrm{C}\,\mathrm{mol}^{-1}` Gas constant :math:`R` ``gas_constant`` :math:`8.3144598\,\mathrm{J}\,\mathrm{mol}^{-1}\,\mathrm{K}^{-1}` Magnetic constant :math:`\mu_0` ``magnetic_constant`` :math:`12.566370614\times 10^{-7}\,\mathrm{N}\,\mathrm{A}^{-2}` Molar mass constant :math:`M_u` ``molar_mass_constant`` :math:`1\times 10^{-3}\,\mathrm{kg}\,\mathrm{mol}^{-1}` 0°C ``zero_celsius`` :math:`273.15\,\mathrm{K}` ==================== ================== ======================= ================================================================== """ import numpy as np from .allunits import ( amp, coulomb, farad, gram, joule, kelvin, kilogram, meter, mole, newton, ) from .fundamentalunits import Unit Unit.automatically_register_units = False #: Avogadro constant (http://physics.nist.gov/cgi-bin/cuu/Value?na) avogadro_constant = 6.022140857e23 / mole #: Boltzmann constant (physics.nist.gov/cgi-bin/cuu/Value?k) boltzmann_constant = 1.38064852e-23 * joule / kelvin #: electric constant (http://physics.nist.gov/cgi-bin/cuu/Value?ep0) electric_constant = 8.854187817e-12 * farad / meter #: Electron rest mass (physics.nist.gov/cgi-bin/cuu/Value?me) electron_mass = 9.10938356e-31 * kilogram #: Elementary charge (physics.nist.gov/cgi-bin/cuu/Value?e) elementary_charge = 1.6021766208e-19 * coulomb #: Faraday constant (http://physics.nist.gov/cgi-bin/cuu/Value?f) faraday_constant = 96485.33289 * coulomb / mole #: gas constant (http://physics.nist.gov/cgi-bin/cuu/Value?r) gas_constant = 8.3144598 * joule / mole / kelvin #: Magnetic constant (http://physics.nist.gov/cgi-bin/cuu/Value?mu0) magnetic_constant = 4 * np.pi * 1e-7 * newton / amp**2 #: Molar mass constant (http://physics.nist.gov/cgi-bin/cuu/Value?mu) molar_mass_constant = 1 * gram / mole #: zero degree Celsius zero_celsius = 273.15 * kelvin Unit.automatically_register_units = True brian2-2.5.4/brian2/units/fundamentalunits.py000066400000000000000000002665011445201106100211550ustar00rootroot00000000000000""" Defines physical units and quantities ===================== ======== ====== Quantity Unit Symbol --------------------- -------- ------ Length metre m Mass kilogram kg Time second s Electric current ampere A Temperature kelvin K Quantity of substance mole mol Luminosity candle cd ===================== ======== ====== """ import collections import itertools import numbers import operator import sys from typing import Callable from warnings import warn import numpy as np from numpy import VisibleDeprecationWarning from sympy import latex __all__ = [ "DimensionMismatchError", "get_or_create_dimension", "get_dimensions", "is_dimensionless", "have_same_dimensions", "in_unit", "in_best_unit", "Quantity", "Unit", "register_new_unit", "check_units", "is_scalar_type", "get_unit", ] unit_checking = True def _flatten(iterable): """ Flatten a given list `iterable`. """ for e in iterable: if isinstance(e, list): yield from _flatten(e) else: yield e def _short_str(arr): """ Return a short string representation of an array, suitable for use in error messages. """ arr = np.asanyarray(arr) old_printoptions = np.get_printoptions() np.set_printoptions(edgeitems=2, threshold=5) arr_string = str(arr) np.set_printoptions(**old_printoptions) return arr_string # =============================================================================== # Numpy ufuncs # =============================================================================== # Note: A list of numpy ufuncs can be found here: # http://docs.scipy.org/doc/numpy/reference/ufuncs.html#available-ufuncs #: ufuncs that work on all dimensions and preserve the dimensions, e.g. abs UFUNCS_PRESERVE_DIMENSIONS = [ "absolute", "rint", "negative", "conj", "conjugate", "floor", "ceil", "trunc", ] #: ufuncs that work on all dimensions but change the dimensions, e.g. square UFUNCS_CHANGE_DIMENSIONS = [ "multiply", "divide", "true_divide", "floor_divide", "sqrt", "square", "reciprocal", "dot", "matmul", ] #: ufuncs that work with matching dimensions, e.g. add UFUNCS_MATCHING_DIMENSIONS = [ "add", "subtract", "maximum", "minimum", "remainder", "mod", "fmod", ] #: ufuncs that compare values, i.e. work only with matching dimensions but do #: not result in a value with dimensions, e.g. equals UFUNCS_COMPARISONS = [ "less", "less_equal", "greater", "greater_equal", "equal", "not_equal", ] #: Logical operations that work on all quantities and return boolean arrays UFUNCS_LOGICAL = [ "logical_and", "logical_or", "logical_xor", "logical_not", "isreal", "iscomplex", "isfinite", "isinf", "isnan", ] #: ufuncs that only work on dimensionless quantities UFUNCS_DIMENSIONLESS = [ "sin", "sinh", "arcsin", "arcsinh", "cos", "cosh", "arccos", "arccosh", "tan", "tanh", "arctan", "arctanh", "log", "log2", "log10", "log1p", "exp", "exp2", "expm1", ] #: ufuncs that only work on two dimensionless quantities UFUNCS_DIMENSIONLESS_TWOARGS = ["logaddexp", "logaddexp2", "arctan2", "hypot"] #: ufuncs that only work on integers and therefore never on quantities UFUNCS_INTEGERS = [ "bitwise_and", "bitwise_or", "bitwise_xor", "invert", "left_shift", "right_shift", ] # ============================================================================== # Utility functions # ============================================================================== def fail_for_dimension_mismatch( obj1, obj2=None, error_message=None, **error_quantities ): """ Compare the dimensions of two objects. Parameters ---------- obj1, obj2 : {array-like, `Quantity`} The object to compare. If `obj2` is ``None``, assume it to be dimensionless error_message : str, optional An error message that is used in the DimensionMismatchError error_quantities : dict mapping str to `Quantity`, optional Quantities in this dictionary will be converted using the `_short_str` helper method and inserted into the ``error_message`` (which should have placeholders with the corresponding names). The reason for doing this in a somewhat complicated way instead of directly including all the details in ``error_messsage`` is that converting large quantity arrays to strings can be rather costly and we don't want to do it if no error occured. Returns ------- dim1, dim2 : `Dimension`, `Dimension` The physical dimensions of the two arguments (so that later code does not need to get the dimensions again). Raises ------ DimensionMismatchError If the dimensions of `obj1` and `obj2` do not match (or, if `obj2` is ``None``, in case `obj1` is not dimensionsless). Notes ----- Implements special checking for ``0``, treating it as having "any dimensions". """ if not unit_checking: return None, None dim1 = get_dimensions(obj1) if obj2 is None: dim2 = DIMENSIONLESS else: dim2 = get_dimensions(obj2) if dim1 is not dim2 and not (dim1 is None or dim2 is None): # Special treatment for "0": # if it is not a Quantity, it has "any dimension". # This allows expressions like 3*mV + 0 to pass (useful in cases where # zero is treated as the neutral element, e.g. in the Python sum # builtin) or comparisons like 3 * mV == 0 to return False instead of # failing # with a DimensionMismatchError. Note that 3*mV == 0*second # is not allowed, though. if (dim1 is DIMENSIONLESS and np.all(obj1 == 0)) or ( dim2 is DIMENSIONLESS and np.all(obj2 == 0) ): return dim1, dim2 # We do another check here, this should allow Brian1 units to pass as # having the same dimensions as a Brian2 unit if dim1 == dim2: return dim1, dim2 if error_message is None: error_message = "Dimension mismatch" else: error_quantities = { name: _short_str(q) for name, q in error_quantities.items() } error_message = error_message.format(**error_quantities) # If we are comparing an object to a specific unit, we don't want to # restate this unit (it is probably mentioned in the text already) if obj2 is None or isinstance(obj2, (Dimension, Unit)): raise DimensionMismatchError(error_message, dim1) else: raise DimensionMismatchError(error_message, dim1, dim2) else: return dim1, dim2 def wrap_function_dimensionless(func): """ Returns a new function that wraps the given function `func` so that it raises a DimensionMismatchError if the function is called on a quantity with dimensions (excluding dimensionless quantities). Quantities are transformed to unitless numpy arrays before calling `func`. These checks/transformations apply only to the very first argument, all other arguments are ignored/untouched. """ def f(x, *args, **kwds): # pylint: disable=C0111 fail_for_dimension_mismatch( x, error_message=( "%s expects a dimensionless argument but got {value}" % func.__name__ ), value=x, ) return func(np.array(x, copy=False), *args, **kwds) f._arg_units = [1] f._return_unit = 1 f.__name__ = func.__name__ f.__doc__ = func.__doc__ f._do_not_run_doctests = True return f def wrap_function_keep_dimensions(func): """ Returns a new function that wraps the given function `func` so that it keeps the dimensions of its input. Quantities are transformed to unitless numpy arrays before calling `func`, the output is a quantity with the original dimensions re-attached. These transformations apply only to the very first argument, all other arguments are ignored/untouched, allowing to work functions like ``sum`` to work as expected with additional ``axis`` etc. arguments. """ def f(x, *args, **kwds): # pylint: disable=C0111 return Quantity(func(np.array(x, copy=False), *args, **kwds), dim=x.dim) f._arg_units = [None] f._return_unit = lambda u: u f.__name__ = func.__name__ f.__doc__ = func.__doc__ f._do_not_run_doctests = True return f def wrap_function_change_dimensions(func, change_dim_func): """ Returns a new function that wraps the given function `func` so that it changes the dimensions of its input. Quantities are transformed to unitless numpy arrays before calling `func`, the output is a quantity with the original dimensions passed through the function `change_dim_func`. A typical use would be a ``sqrt`` function that uses ``lambda d: d ** 0.5`` as ``change_dim_func``. These transformations apply only to the very first argument, all other arguments are ignored/untouched. """ def f(x, *args, **kwds): # pylint: disable=C0111 ar = np.array(x, copy=False) return Quantity(func(ar, *args, **kwds), dim=change_dim_func(ar, x.dim)) f._arg_units = [None] f._return_unit = change_dim_func f.__name__ = func.__name__ f.__doc__ = func.__doc__ f._do_not_run_doctests = True return f def wrap_function_remove_dimensions(func): """ Returns a new function that wraps the given function `func` so that it removes any dimensions from its input. Useful for functions that are returning integers (indices) or booleans, irrespective of the datatype contained in the array. These transformations apply only to the very first argument, all other arguments are ignored/untouched. """ def f(x, *args, **kwds): # pylint: disable=C0111 return func(np.array(x, copy=False), *args, **kwds) f._arg_units = [None] f._return_unit = 1 f.__name__ = func.__name__ f.__doc__ = func.__doc__ f._do_not_run_doctests = True return f # SI dimensions (see table at the top of the file) and various descriptions, # each description maps to an index i, and the power of each dimension # is stored in the variable dims[i] _di = { "Length": 0, "length": 0, "metre": 0, "metres": 0, "meter": 0, "meters": 0, "m": 0, "Mass": 1, "mass": 1, "kilogram": 1, "kilograms": 1, "kg": 1, "Time": 2, "time": 2, "second": 2, "seconds": 2, "s": 2, "Electric Current": 3, "electric current": 3, "Current": 3, "current": 3, "ampere": 3, "amperes": 3, "A": 3, "Temperature": 4, "temperature": 4, "kelvin": 4, "kelvins": 4, "K": 4, "Quantity of Substance": 5, "Quantity of substance": 5, "quantity of substance": 5, "Substance": 5, "substance": 5, "mole": 5, "moles": 5, "mol": 5, "Luminosity": 6, "luminosity": 6, "candle": 6, "candles": 6, "cd": 6, } _ilabel = ["m", "kg", "s", "A", "K", "mol", "cd"] # The same labels with the names used for constructing them in Python code _iclass_label = ["metre", "kilogram", "second", "amp", "kelvin", "mole", "candle"] # SI unit _prefixes as integer exponents of 10, see table at end of file. _siprefixes = { "y": -24, "z": -21, "a": -18, "f": -15, "p": -12, "n": -9, "u": -6, "m": -3, "c": -2, "d": -1, "": 0, "da": 1, "h": 2, "k": 3, "M": 6, "G": 9, "T": 12, "P": 15, "E": 18, "Z": 21, "Y": 24, } class Dimension: """ Stores the indices of the 7 basic SI unit dimension (length, mass, etc.). Provides a subset of arithmetic operations appropriate to dimensions: multiplication, division and powers, and equality testing. Parameters ---------- dims : sequence of `float` The dimension indices of the 7 basic SI unit dimensions. Notes ----- Users shouldn't use this class directly, it is used internally in Quantity and Unit. Even internally, never use ``Dimension(...)`` to create a new instance, use `get_or_create_dimension` instead. This function makes sure that only one Dimension instance exists for every combination of indices, allowing for a very fast dimensionality check with ``is``. """ __slots__ = ["_dims"] __array_priority__ = 1000 #### INITIALISATION #### def __init__(self, dims): self._dims = dims #### METHODS #### def get_dimension(self, d): """ Return a specific dimension. Parameters ---------- d : `str` A string identifying the SI basic unit dimension. Can be either a description like "length" or a basic unit like "m" or "metre". Returns ------- dim : `float` The dimensionality of the dimension `d`. """ return self._dims[_di[d]] @property def is_dimensionless(self): """ Whether this Dimension is dimensionless. Notes ----- Normally, instead one should check dimension for being identical to `DIMENSIONLESS`. """ return all([x == 0 for x in self._dims]) @property def dim(self): """ Returns the `Dimension` object itself. This can be useful, because it allows to check for the dimension of an object by checking its ``dim`` attribute -- this will return a `Dimension` object for `Quantity`, `Unit` and `Dimension`. """ return self #### REPRESENTATION #### def _str_representation(self, python_code=False): """ String representation in basic SI units, or ``"1"`` for dimensionless. Use ``python_code=False`` for display purposes and ``True`` for valid Python code. """ if python_code: power_operator = " ** " else: power_operator = "^" parts = [] for i in range(len(self._dims)): if self._dims[i]: if python_code: s = _iclass_label[i] else: s = _ilabel[i] if self._dims[i] != 1: s += power_operator + str(self._dims[i]) parts.append(s) if python_code: s = " * ".join(parts) if not len(s): return f"{self.__class__.__name__}()" else: s = " ".join(parts) if not len(s): return "1" return s.strip() def _latex(self, *args): parts = [] for i in range(len(self._dims)): if self._dims[i]: s = _ilabel[i] if self._dims[i] != 1: s += "^{%s}" % str(self._dims[i]) parts.append(s) s = "\\,".join(parts) if not len(s): return "1" return s.strip() def _repr_latex(self): return f"${latex(self)}$" def __repr__(self): return self._str_representation(python_code=True) def __str__(self): return self._str_representation(python_code=False) #### ARITHMETIC #### # Note that none of the dimension arithmetic objects do sanity checking # on their inputs, although most will throw an exception if you pass the # wrong sort of input def __mul__(self, value): return get_or_create_dimension([x + y for x, y in zip(self._dims, value._dims)]) def __div__(self, value): return get_or_create_dimension([x - y for x, y in zip(self._dims, value._dims)]) def __truediv__(self, value): return self.__div__(value) def __pow__(self, value): value = np.array(value, copy=False) if value.size > 1: raise TypeError("Too many exponents") return get_or_create_dimension([x * value for x in self._dims]) def __imul__(self, value): raise TypeError("Dimension object is immutable") def __idiv__(self, value): raise TypeError("Dimension object is immutable") def __itruediv__(self, value): raise TypeError("Dimension object is immutable") def __ipow__(self, value): raise TypeError("Dimension object is immutable") #### COMPARISON #### def __eq__(self, value): try: return np.allclose(self._dims, value._dims) except AttributeError: # Only compare equal to another Dimensions object return False def __ne__(self, value): return not self.__eq__(value) def __hash__(self): return hash(self._dims) #### MAKE DIMENSION PICKABLE #### def __getstate__(self): return self._dims def __setstate__(self, state): self._dims = state def __reduce__(self): # Make sure that unpickling Dimension objects does not bypass the singleton system return (get_or_create_dimension, (self._dims,)) ### Dimension objects are singletons and deepcopy is therefore not necessary def __deepcopy__(self, memodict): return self #: The singleton object for dimensionless Dimensions. DIMENSIONLESS = Dimension((0, 0, 0, 0, 0, 0, 0)) _dimensions = {(0, 0, 0, 0, 0, 0, 0): DIMENSIONLESS} def get_or_create_dimension(*args, **kwds): """ Create a new Dimension object or get a reference to an existing one. This function takes care of only creating new objects if they were not created before and otherwise returning a reference to an existing object. This allows to compare dimensions very efficiently using ``is``. Parameters ---------- args : sequence of `float` A sequence with the indices of the 7 elements of an SI dimension. kwds : keyword arguments a sequence of ``keyword=value`` pairs where the keywords are the names of the SI dimensions, or the standard unit. Examples -------- The following are all definitions of the dimensions of force >>> from brian2 import * >>> get_or_create_dimension(length=1, mass=1, time=-2) metre * kilogram * second ** -2 >>> get_or_create_dimension(m=1, kg=1, s=-2) metre * kilogram * second ** -2 >>> get_or_create_dimension([1, 1, -2, 0, 0, 0, 0]) metre * kilogram * second ** -2 Notes ----- The 7 units are (in order): Length, Mass, Time, Electric Current, Temperature, Quantity of Substance, Luminosity and can be referred to either by these names or their SI unit names, e.g. length, metre, and m all refer to the same thing here. """ if len(args): # initialisation by list dims = args[0] try: if len(dims) != 7: raise TypeError() except TypeError: raise TypeError("Need a sequence of exactly 7 items") else: # initialisation by keywords dims = [0, 0, 0, 0, 0, 0, 0] for k in kwds: # _di stores the index of the dimension with name 'k' dims[_di[k]] = kwds[k] dims = tuple(dims) # check whether this Dimension object has already been created if dims in _dimensions: return _dimensions[dims] else: new_dim = Dimension(dims) _dimensions[dims] = new_dim return new_dim class DimensionMismatchError(Exception): """ Exception class for attempted operations with inconsistent dimensions. For example, ``3*mvolt + 2*amp`` raises this exception. The purpose of this class is to help catch errors based on incorrect units. The exception will print a representation of the dimensions of the two inconsistent objects that were operated on. Parameters ---------- description : ``str`` A description of the type of operation being performed, e.g. Addition, Multiplication, etc. dims : `Dimension` The physical dimensions of the objects involved in the operation, any number of them is possible """ def __init__(self, description, *dims): # Call the base class constructor to make Exception pickable, see: # http://bugs.python.org/issue1692335 Exception.__init__(self, description, *dims) self.dims = dims self.desc = description def __repr__(self): dims_repr = [repr(dim) for dim in self.dims] return f"{self.__class__.__name__}({self.desc!r}, {', '.join(dims_repr)})" def __str__(self): s = self.desc if len(self.dims) == 0: pass elif len(self.dims) == 1: s += f" (unit is {get_unit_for_display(self.dims[0])}" elif len(self.dims) == 2: d1, d2 = self.dims s += ( f" (units are {get_unit_for_display(d1)} and {get_unit_for_display(d2)}" ) else: s += ( " (units are" f" {' '.join([f'({get_unit_for_display(d)})' for d in self.dims])}" ) if len(self.dims): s += ")." return s def is_scalar_type(obj): """ Tells you if the object is a 1d number type. Parameters ---------- obj : `object` The object to check. Returns ------- scalar : `bool` ``True`` if `obj` is a scalar that can be interpreted as a dimensionless `Quantity`. """ try: return obj.ndim == 0 and is_dimensionless(obj) except AttributeError: return np.isscalar(obj) and not isinstance(obj, str) def get_dimensions(obj): """ Return the dimensions of any object that has them. Slightly more general than `Quantity.dimensions` because it will return `DIMENSIONLESS` if the object is of number type but not a `Quantity` (e.g. a `float` or `int`). Parameters ---------- obj : `object` The object to check. Returns ------- dim : `Dimension` The physical dimensions of the `obj`. """ try: return obj.dim except AttributeError: # The following is not very pretty, but it will avoid the costly # isinstance check for the common types if type(obj) in [ int, float, np.int32, np.int64, np.float32, np.float64, np.ndarray, ] or isinstance(obj, (numbers.Number, np.number, np.ndarray)): return DIMENSIONLESS try: return Quantity(obj).dim except TypeError: raise TypeError(f"Object of type {type(obj)} does not have dimensions") def is_dimensionless(obj): """ Test if a value is dimensionless or not. Parameters ---------- obj : `object` The object to check. Returns ------- dimensionless : `bool` ``True`` if `obj` is dimensionless. """ return get_dimensions(obj) is DIMENSIONLESS def have_same_dimensions(obj1, obj2): """Test if two values have the same dimensions. Parameters ---------- obj1, obj2 : {`Quantity`, array-like, number} The values of which to compare the dimensions. Returns ------- same : `bool` ``True`` if `obj1` and `obj2` have the same dimensions. """ if not unit_checking: return True # ignore units when unit checking is disabled # If dimensions are consistently created using get_or_create_dimensions, # the fast "is" comparison should always return the correct result. # To be safe, we also do an equals comparison in case it fails. This # should only add a small amount of unnecessary computation for cases in # which this function returns False which very likely leads to a # DimensionMismatchError anyway. dim1 = get_dimensions(obj1) dim2 = get_dimensions(obj2) return (dim1 is dim2) or (dim1 == dim2) or dim1 is None or dim2 is None def in_unit(x, u, precision=None): """ Display a value in a certain unit with a given precision. Parameters ---------- x : {`Quantity`, array-like, number} The value to display u : {`Quantity`, `Unit`} The unit to display the value `x` in. precision : `int`, optional The number of digits of precision (in the given unit, see Examples). If no value is given, numpy's `get_printoptions` value is used. Returns ------- s : `str` A string representation of `x` in units of `u`. Examples -------- >>> from brian2 import * >>> in_unit(3 * volt, mvolt) '3000. mV' >>> in_unit(123123 * msecond, second, 2) '123.12 s' >>> in_unit(10 * uA/cm**2, nA/um**2) '1.00000000e-04 nA/(um^2)' >>> in_unit(10 * mV, ohm * amp) '0.01 ohm A' >>> in_unit(10 * nS, ohm) # doctest: +NORMALIZE_WHITESPACE ... # doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... DimensionMismatchError: Non-matching unit for method "in_unit", dimensions were (m^-2 kg^-1 s^3 A^2) (m^2 kg s^-3 A^-2) See Also -------- Quantity.in_unit """ if is_dimensionless(x): fail_for_dimension_mismatch(x, u, 'Non-matching unit for function "in_unit"') return str(np.array(x / u, copy=False)) else: return x.in_unit(u, precision=precision) def in_best_unit(x, precision=None): """ Represent the value in the "best" unit. Parameters ---------- x : {`Quantity`, array-like, number} The value to display precision : `int`, optional The number of digits of precision (in the best unit, see Examples). If no value is given, numpy's `get_printoptions` value is used. Returns ------- representation : `str` A string representation of this `Quantity`. Examples -------- >>> from brian2.units import * >>> in_best_unit(0.00123456 * volt) '1.23456 mV' >>> in_best_unit(0.00123456 * volt, 2) '1.23 mV' >>> in_best_unit(0.123456) '0.123456' >>> in_best_unit(0.123456, 2) '0.12' See Also -------- Quantity.in_best_unit """ if is_dimensionless(x): if precision is None: precision = np.get_printoptions()["precision"] return str(np.round(x, precision)) u = x.get_best_unit() return x.in_unit(u, precision=precision) def quantity_with_dimensions(floatval, dims): """ Create a new `Quantity` with the given dimensions. Calls `get_or_create_dimensions` with the dimension tuple of the `dims` argument to make sure that unpickling (which calls this function) does not accidentally create new Dimension objects which should instead refer to existing ones. Parameters ---------- floatval : `float` The floating point value of the quantity. dims : `Dimension` The physical dimensions of the quantity. Returns ------- q : `Quantity` A quantity with the given dimensions. Examples -------- >>> from brian2 import * >>> quantity_with_dimensions(0.001, volt.dim) 1. * mvolt See Also -------- get_or_create_dimensions """ return Quantity(floatval, get_or_create_dimension(dims._dims)) class Quantity(np.ndarray): """ A number with an associated physical dimension. In most cases, it is not necessary to create a Quantity object by hand, instead use multiplication and division of numbers with the constant unit names ``second``, ``kilogram``, etc. Notes ----- The `Quantity` class defines arithmetic operations which check for consistency of dimensions and raise the DimensionMismatchError exception if they are inconsistent. It also defines default and other representations for a number for printing purposes. See the documentation on the Unit class for more details about the available unit names like mvolt, etc. *Casting rules* The rules that define the casting operations for Quantity object are: 1. Quantity op Quantity = Quantity Performs dimension checking if appropriate 2. (Scalar or Array) op Quantity = Quantity Assumes that the scalar or array is dimensionless There is one exception to the above rule, the number ``0`` is interpreted as having "any dimension". Examples -------- >>> from brian2 import * >>> I = 3 * amp # I is a Quantity object >>> R = 2 * ohm # same for R >>> I * R 6. * volt >>> (I * R).in_unit(mvolt) '6000. mV' >>> (I * R) / mvolt 6000.0 >>> X = I + R # doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... DimensionMismatchError: Addition, dimensions were (A) (m^2 kg s^-3 A^-2) >>> Is = np.array([1, 2, 3]) * amp >>> Is * R array([ 2., 4., 6.]) * volt >>> np.asarray(Is * R) # gets rid of units array([ 2., 4., 6.]) See also -------- Unit Attributes ---------- dimensions is_dimensionless dim : Dimensions The physical dimensions of this quantity. Methods ------- with_dimensions has_same_dimensions in_unit in_best_unit """ __slots__ = ["dim"] __array_priority__ = 1000 # ========================================================================== # Construction and handling of numpy ufuncs # ========================================================================== def __new__(cls, arr, dim=None, dtype=None, copy=False, force_quantity=False): # Do not create dimensionless quantities, use pure numpy arrays instead if dim is DIMENSIONLESS and not force_quantity: arr = np.array(arr, dtype=dtype, copy=copy) if arr.shape == (): # For scalar values, return a simple Python object instead of # a numpy scalar return arr.item() return arr # All np.ndarray subclasses need something like this, see # http://www.scipy.org/Subclasses subarr = np.array(arr, dtype=dtype, copy=copy).view(cls) # We only want numerical datatypes if not np.issubclass_(np.dtype(subarr.dtype).type, (np.number, np.bool_)): raise TypeError("Quantities can only be created from numerical data.") # If a dimension is given, force this dimension if dim is not None: subarr.dim = dim return subarr # Use the given dimension or the dimension of the given array (if any) try: subarr.dim = arr.dim except AttributeError: if not isinstance(arr, (np.ndarray, np.number, numbers.Number)): # check whether it is an iterable containing Quantity objects try: is_quantity = [isinstance(x, Quantity) for x in _flatten(arr)] except TypeError: # Not iterable is_quantity = [False] if len(is_quantity) == 0: # Empty list subarr.dim = DIMENSIONLESS elif all(is_quantity): dims = [x.dim for x in _flatten(arr)] one_dim = dims[0] for d in dims: if d != one_dim: raise DimensionMismatchError( "Mixing quantities " "with different " "dimensions is not " "allowed", d, one_dim, ) subarr.dim = dims[0] elif any(is_quantity): raise TypeError( "Mixing quantities and non-quantities is not allowed." ) return subarr def __array_finalize__(self, orig): self.dim = getattr(orig, "dim", DIMENSIONLESS) def __array_prepare__(self, array, context=None): if context is None: return array uf, args, _ = context if uf.__name__ in ( UFUNCS_PRESERVE_DIMENSIONS + UFUNCS_CHANGE_DIMENSIONS + UFUNCS_LOGICAL ): # always allowed pass elif uf.__name__ in UFUNCS_INTEGERS: # Numpy should already raise a TypeError by itself raise TypeError(f"{uf.__name__} cannot be used on quantities.") elif uf.__name__ in UFUNCS_MATCHING_DIMENSIONS + UFUNCS_COMPARISONS: # Ok if dimension of arguments match fail_for_dimension_mismatch( args[0], args[1], error_message=( "Cannot calculate {val1} %s {val2}, the units do not match" ) % uf.__name__, val1=args[0], val2=args[1], ) elif uf.__name__ in UFUNCS_DIMENSIONLESS: # Ok if argument is dimensionless fail_for_dimension_mismatch( args[0], error_message="%s expects a dimensionless argument but got {value}" % uf.__name__, value=args[0], ) elif uf.__name__ in UFUNCS_DIMENSIONLESS_TWOARGS: # Ok if both arguments are dimensionless fail_for_dimension_mismatch( args[0], error_message=( "Both arguments for " '"%s" should be ' "dimensionless but " "first argument was " "{value}" ) % uf.__name__, value=args[0], ) fail_for_dimension_mismatch( args[1], error_message=( "Both arguments for " '"%s" should be ' "dimensionless but " "second argument was " "{value}" ) % uf.__name__, value=args[1], ) elif uf.__name__ == "power": fail_for_dimension_mismatch( args[1], error_message=( "The exponent for a " "power operation has to " "be dimensionless but " "was {value}" ), value=args[1], ) if np.array(args[1], copy=False).size != 1: raise TypeError( "Only length-1 arrays can be used as an exponent for quantities." ) elif uf.__name__ in ("sign", "ones_like"): return np.array(array, copy=False) else: warn(f"Unknown ufunc '{uf.__name__}' in __array_prepare__") return array def __array_wrap__(self, array, context=None): dim = DIMENSIONLESS if context is not None: uf, args, _ = context if uf.__name__ in (UFUNCS_PRESERVE_DIMENSIONS + UFUNCS_MATCHING_DIMENSIONS): dim = self.dim elif uf.__name__ in (UFUNCS_DIMENSIONLESS + UFUNCS_DIMENSIONLESS_TWOARGS): # We should have been arrived here only for dimensionless # quantities dim = DIMENSIONLESS elif uf.__name__ in ( UFUNCS_COMPARISONS + UFUNCS_LOGICAL + ["sign", "ones_like"] ): # Do not touch the return value (boolean or integer array) return array elif uf.__name__ == "sqrt": dim = self.dim**0.5 elif uf.__name__ == "power": dim = get_dimensions(args[0]) ** np.array(args[1], copy=False) elif uf.__name__ == "square": dim = self.dim**2 elif uf.__name__ in ("divide", "true_divide", "floor_divide"): dim = get_dimensions(args[0]) / get_dimensions(args[1]) elif uf.__name__ == "reciprocal": dim = get_dimensions(args[0]) ** -1 elif uf.__name__ in ("multiply", "dot", "matmul"): dim = get_dimensions(args[0]) * get_dimensions(args[1]) else: warn(f"Unknown ufunc '{uf.__name__}' in __array_wrap__") # TODO: Remove units in this case? # This seems to be better than using type(self) instead of quantity # This may convert units to Quantities, e.g. np.square(volt) leads to # a 1 * volt ** 2 quantitiy instead of volt ** 2. But this should # rarely be an issue. The alternative leads to more confusing # behaviour: np.float64(3) * mV would result in a dimensionless float64 result = array.view(Quantity) result.dim = dim return result def __deepcopy__(self, memo): return Quantity(self, copy=True) # ============================================================================== # Quantity-specific functions (not existing in ndarray) # ============================================================================== @staticmethod def with_dimensions(value, *args, **keywords): """ Create a `Quantity` object with dim. Parameters ---------- value : {array_like, number} The value of the dimension args : {`Dimension`, sequence of float} Either a single argument (a `Dimension`) or a sequence of 7 values. kwds Keywords defining the dim, see `Dimension` for details. Returns ------- q : `Quantity` A `Quantity` object with the given dim Examples -------- All of these define an equivalent `Quantity` object: >>> from brian2 import * >>> Quantity.with_dimensions(2, get_or_create_dimension(length=1)) 2. * metre >>> Quantity.with_dimensions(2, length=1) 2. * metre >>> 2 * metre 2. * metre """ if len(args) and isinstance(args[0], Dimension): dimensions = args[0] else: dimensions = get_or_create_dimension(*args, **keywords) return Quantity(value, dim=dimensions) ### ATTRIBUTES ### is_dimensionless = property( lambda self: self.dim.is_dimensionless, doc="Whether this is a dimensionless quantity.", ) @property def dimensions(self): """ The physical dimensions of this quantity. """ return self.dim @dimensions.setter def dimensions(self, dim): self.dim = dim #### METHODS #### def has_same_dimensions(self, other): """ Return whether this object has the same dimensions as another. Parameters ---------- other : {`Quantity`, array-like, number} The object to compare the dimensions against. Returns ------- same : `bool` ``True`` if `other` has the same dimensions. """ if not unit_checking: return True # ignore units if unit checking is disabled other_dim = get_dimensions(other) return (self.dim is other_dim) or (self.dim == other_dim) def in_unit(self, u, precision=None, python_code=False): """ Represent the quantity in a given unit. If `python_code` is ``True``, this will return valid python code, i.e. a string like ``5.0 * um ** 2`` instead of ``5.0 um^2`` Parameters ---------- u : {`Quantity`, `Unit`} The unit in which to show the quantity. precision : `int`, optional The number of digits of precision (in the given unit, see Examples). If no value is given, numpy's `get_printoptions` value is used. python_code : `bool`, optional Whether to return valid python code (``True``) or a human readable string (``False``, the default). Returns ------- s : `str` String representation of the object in unit `u`. Examples -------- >>> from brian2.units import * >>> from brian2.units.stdunits import * >>> x = 25.123456 * mV >>> x.in_unit(volt) '0.02512346 V' >>> x.in_unit(volt, 3) '0.025 V' >>> x.in_unit(mV, 3) '25.123 mV' See Also -------- in_unit """ fail_for_dimension_mismatch(self, u, 'Non-matching unit for method "in_unit"') value = np.array(self / u, copy=False) # numpy uses the printoptions setting only in arrays, not in array # scalars, so we use this hackish way of turning the scalar first into # an array, then removing the square brackets from the output if value.shape == (): s = np.array_str(np.array([value]), precision=precision) s = s.replace("[", "").replace("]", "").strip() else: if python_code: s = np.array_repr(value, precision=precision) else: s = np.array_str(value, precision=precision) if not u.is_dimensionless: if isinstance(u, Unit): if python_code: s += f" * {repr(u)}" else: s += f" {str(u)}" else: if python_code: s += f" * {repr(u.dim)}" else: s += f" {str(u.dim)}" elif python_code: # Make a quantity without unit recognisable return f"{self.__class__.__name__}({s.strip()})" return s.strip() def get_best_unit(self, *regs): """ Return the best unit for this `Quantity`. Parameters ---------- regs : any number of `UnitRegistry` objects The registries that are searched for units. If none are provided, it will check the standard, user and additional unit registers in turn. Returns ------- u : `Quantity` or `Unit` The best-fitting unit for the quantity `x`. """ if self.is_dimensionless: return Unit(1) if len(regs): for r in regs: try: return r[self] except KeyError: pass return Quantity(1, self.dim) else: return self.get_best_unit( standard_unit_register, user_unit_register, additional_unit_register ) def _get_best_unit(self, *regs): warn( "Quantity._get_best_unit has been renamed to Quantity.get_best_unit.", VisibleDeprecationWarning, ) return self.get_best_unit(*regs) def in_best_unit(self, precision=None, python_code=False, *regs): """ Represent the quantity in the "best" unit. Parameters ---------- python_code : `bool`, optional If set to ``False`` (the default), will return a string like ``5.0 um^2`` which is not a valid Python expression. If set to ``True``, it will return ``5.0 * um ** 2`` instead. precision : `int`, optional The number of digits of precision (in the best unit, see Examples). If no value is given, numpy's `get_printoptions` value is used. regs : `UnitRegistry` objects The registries where to search for units. If none are given, the standard, user-defined and additional registries are searched in that order. Returns ------- representation : `str` A string representation of this `Quantity`. Examples -------- >>> from brian2.units import * >>> x = 0.00123456 * volt >>> x.in_best_unit() '1.23456 mV' >>> x.in_best_unit(3) '1.235 mV' See Also -------- in_best_unit """ u = self.get_best_unit(*regs) return self.in_unit(u, precision=precision, python_code=python_code) # ============================================================================== # Overwritten ndarray methods # ============================================================================== #### Setting/getting items #### def __getitem__(self, key): """Overwritten to assure that single elements (i.e., indexed with a single integer or a tuple of integers) retain their unit. """ return Quantity(np.ndarray.__getitem__(self, key), self.dim) def item(self, *args): """Overwritten to assure that the returned element retains its unit.""" return Quantity(np.ndarray.item(self, *args), self.dim) def __setitem__(self, key, value): fail_for_dimension_mismatch(self, value, "Inconsistent units in assignment") return super().__setitem__(key, value) #### ARITHMETIC #### def _binary_operation( self, other, operation, dim_operation=lambda a, b: a, fail_for_mismatch=False, operator_str=None, inplace=False, ): """ General implementation for binary operations. Parameters ---------- other : {`Quantity`, `ndarray`, scalar} The object with which the operation should be performed. operation : function of two variables The function with which the two objects are combined. For example, `operator.mul` for a multiplication. dim_operation : function of two variables, optional The function with which the dimension of the resulting object is calculated (as a function of the dimensions of the two involved objects). For example, `operator.mul` for a multiplication. If not specified, the dimensions of `self` are used for the resulting object. fail_for_mismatch : bool, optional Whether to fail for a dimension mismatch between `self` and `other` (defaults to ``False``) operator_str : str, optional The string to use for the operator in an error message. inplace: bool, optional Whether to do the operation in-place (defaults to ``False``). Notes ----- For in-place operations on scalar values, a copy of the original object is returned, i.e. it rather works like a fundamental Python type and not like a numpy array scalar, preventing weird effects when a reference to the same value was stored in another variable. See github issue #469. """ other_dim = None if fail_for_mismatch: if inplace: message = ( "Cannot calculate ... %s {value}, units do not match" % operator_str ) _, other_dim = fail_for_dimension_mismatch( self, other, message, value=other ) else: message = ( "Cannot calculate {value1} %s {value2}, units do not match" % operator_str ) _, other_dim = fail_for_dimension_mismatch( self, other, message, value1=self, value2=other ) if other_dim is None: other_dim = get_dimensions(other) if inplace: if self.shape == (): self_value = Quantity(self, copy=True) else: self_value = self operation(self_value, other) self_value.dim = dim_operation(self.dim, other_dim) return self_value else: newdims = dim_operation(self.dim, other_dim) self_arr = np.array(self, copy=False) other_arr = np.array(other, copy=False) result = operation(self_arr, other_arr) return Quantity(result, newdims) def __mul__(self, other): return self._binary_operation(other, operator.mul, operator.mul) def __rmul__(self, other): return self.__mul__(other) def __imul__(self, other): return self._binary_operation( other, np.ndarray.__imul__, operator.mul, inplace=True ) def __div__(self, other): return self._binary_operation(other, operator.truediv, operator.truediv) def __truediv__(self, other): return self.__div__(other) def __rdiv__(self, other): # division with swapped arguments rdiv = lambda a, b: operator.truediv(b, a) return self._binary_operation(other, rdiv, rdiv) def __rtruediv__(self, other): return self.__rdiv__(other) def __idiv__(self, other): return self._binary_operation( other, np.ndarray.__itruediv__, operator.truediv, inplace=True ) def __itruediv__(self, other): return self._binary_operation( other, np.ndarray.__itruediv__, operator.truediv, inplace=True ) def __mod__(self, other): return self._binary_operation( other, operator.mod, fail_for_mismatch=True, operator_str=r"%" ) def __add__(self, other): return self._binary_operation( other, operator.add, fail_for_mismatch=True, operator_str="+" ) def __radd__(self, other): return self.__add__(other) def __iadd__(self, other): return self._binary_operation( other, np.ndarray.__iadd__, fail_for_mismatch=True, operator_str="+=", inplace=True, ) def __sub__(self, other): return self._binary_operation( other, operator.sub, fail_for_mismatch=True, operator_str="-" ) def __rsub__(self, other): # We allow operations with 0 even for dimension mismatches, e.g. # 0 - 3*mV is allowed. In this case, the 0 is not represented by a # Quantity object so we cannot simply call Quantity.__sub__ if (not isinstance(other, Quantity) or other.dim is DIMENSIONLESS) and np.all( other == 0 ): return self.__neg__() else: return Quantity(other, copy=False, force_quantity=True).__sub__(self) def __isub__(self, other): return self._binary_operation( other, np.ndarray.__isub__, fail_for_mismatch=True, operator_str="-=", inplace=True, ) def __pow__(self, other): if isinstance(other, np.ndarray) or is_scalar_type(other): fail_for_dimension_mismatch( other, error_message=( "Cannot calculate " "{base} ** {exponent}, " "the exponent has to be " "dimensionless" ), base=self, exponent=other, ) other = np.array(other, copy=False) return Quantity(np.array(self, copy=False) ** other, self.dim**other) else: return NotImplemented def __rpow__(self, other): if self.is_dimensionless: if isinstance(other, np.ndarray) or isinstance(other, np.ndarray): new_array = np.array(other, copy=False) ** np.array(self, copy=False) return Quantity(new_array, DIMENSIONLESS) else: return NotImplemented else: base = _short_str(other) exponent = _short_str(self) raise DimensionMismatchError( f"Cannot calculate {base} ** {exponent}, " "the exponent has to be dimensionless.", self.dim, ) def __ipow__(self, other): if isinstance(other, np.ndarray) or is_scalar_type(other): fail_for_dimension_mismatch( other, error_message=( "Cannot calculate " "... **= {exponent}, " "the exponent has to be " "dimensionless" ), exponent=other, ) other = np.array(other, copy=False) super().__ipow__(other) self.dim = self.dim**other return self else: return NotImplemented def __neg__(self): return Quantity(-np.array(self, copy=False), self.dim) def __pos__(self): return self def __abs__(self): return Quantity(abs(np.array(self, copy=False)), self.dim) def tolist(self): """ Convert the array into a list. Returns ------- l : list of `Quantity` A (possibly nested) list equivalent to the original array. """ def replace_with_quantity(seq, dim): """ Replace all the elements in the list with an equivalent `Quantity` with the given `dim`. """ # No recursion needed for single values if not isinstance(seq, list): return Quantity(seq, dim) def top_replace(s): """ Recursivley descend into the list. """ for i in s: if not isinstance(i, list): yield Quantity(i, dim) else: yield type(i)(top_replace(i)) return type(seq)(top_replace(seq)) return replace_with_quantity(np.array(self, copy=False).tolist(), self.dim) #### COMPARISONS #### def _comparison(self, other, operator_str, operation): is_scalar = is_scalar_type(other) if not is_scalar and not isinstance(other, np.ndarray): return NotImplemented if not is_scalar or not np.isinf(other): message = ( "Cannot perform comparison {value1} %s {value2}, units do not match" % operator_str ) fail_for_dimension_mismatch(self, other, message, value1=self, value2=other) return operation(np.array(self, copy=False), np.array(other, copy=False)) def __lt__(self, other): return self._comparison(other, "<", operator.lt) def __le__(self, other): return self._comparison(other, "<=", operator.le) def __gt__(self, other): return self._comparison(other, ">", operator.gt) def __ge__(self, other): return self._comparison(other, ">=", operator.ge) def __eq__(self, other): return self._comparison(other, "==", operator.eq) def __ne__(self, other): return self._comparison(other, "!=", operator.ne) #### MAKE QUANTITY PICKABLE #### def __reduce__(self): return quantity_with_dimensions, (np.array(self, copy=False), self.dim) #### REPRESENTATION #### def __repr__(self): return self.in_best_unit(python_code=True) def _latex(self, expr): """ Translates a scalar, 1-d or 2-d array into a LaTeX representation. Will be called by ``sympy``'s `~sympy.latex` function and used as a "rich representation" in e.g. jupyter notebooks. The values in the array will be formatted with `numpy.array2string` and will therefore observe ``numpy``'s "print options" such as ``precision``. Including all numbers in the LaTeX output will rarely be useful for large arrays; this function will therefore apply a ``threshold`` value divided by 100 (the default ``threshold`` value is 1000, this function hence applies 10). Note that the ``max_line_width`` print option is ignored. """ best_unit = self.get_best_unit() if isinstance(best_unit, Unit): best_unit_latex = latex(best_unit) else: # A quantity best_unit_latex = latex(best_unit.dimensions) unitless = np.array(self / best_unit, copy=False) threshold = np.get_printoptions()["threshold"] // 100 if unitless.ndim == 0: sympy_quantity = float(unitless) elif unitless.ndim == 1: array_str = np.array2string( unitless, separator=" & ", threshold=threshold, max_line_width=sys.maxsize, ) # Replace [ and ] sympy_quantity = ( r"\left[\begin{matrix}" + array_str[1:-1].replace("...", r"\dots") + r"\end{matrix}\right]" ) elif unitless.ndim == 2: array_str = np.array2string( unitless, separator=" & ", threshold=threshold, max_line_width=sys.maxsize, ) array_str = array_str[1:-1].replace("...", r"\dots") array_str = ( array_str.replace("[", "").replace("] &", r"\\").replace("]", "\n") ) lines = array_str.split("\n") n_cols = lines[0].count("&") + 1 new_lines = [] for line in lines: if line.strip() == r"\dots &": new_lines.append(" & ".join([r"\vdots"] * n_cols) + r"\\") else: new_lines.append(line) sympy_quantity = ( r"\left[\begin{matrix}" + "\n" + "\n".join(new_lines) + r"\end{matrix}\right]" ) else: raise NotImplementedError( f"Cannot create a LaTeX representation for a {unitless.ndim}-d matrix." ) return f"{sympy_quantity}\\,{best_unit_latex}" def _repr_latex_(self): return f"${latex(self)}$" def __str__(self): return self.in_best_unit() def __format__(self, format_spec): # Avoid that formatted strings like f"{q}" use floating point formatting for the # quantity, i.e. discard the unit if format_spec == "": return str(self) else: return super().__format__(format_spec) #### Mathematic methods #### cumsum = wrap_function_keep_dimensions(np.ndarray.cumsum) diagonal = wrap_function_keep_dimensions(np.ndarray.diagonal) max = wrap_function_keep_dimensions(np.ndarray.max) mean = wrap_function_keep_dimensions(np.ndarray.mean) min = wrap_function_keep_dimensions(np.ndarray.min) ptp = wrap_function_keep_dimensions(np.ndarray.ptp) # To work around an issue in matplotlib 1.3.1 (see # https://github.com/matplotlib/matplotlib/pull/2591), we make `ravel` # return a unitless array and emit a warning explaining the issue. use_matplotlib_units_fix = False try: import matplotlib if matplotlib.__version__ == "1.3.1": use_matplotlib_units_fix = True except ImportError: pass if use_matplotlib_units_fix: def ravel(self, *args, **kwds): # Note that we don't use Brian's logging system here as we don't want # the unit system to depend on other parts of Brian warn( "As a workaround for a bug in matplotlib 1.3.1, calling " '"ravel()" on a quantity will return unit-less values. If you ' "get this warning during plotting, consider removing the units " "before plotting, e.g. by dividing by the unit. If you are " 'explicitly calling "ravel()", consider using "flatten()" ' "instead." ) return np.array(self, copy=False).ravel(*args, **kwds) ravel._arg_units = [None] ravel._return_unit = 1 ravel.__name__ = np.ndarray.ravel.__name__ ravel.__doc__ = np.ndarray.ravel.__doc__ else: ravel = wrap_function_keep_dimensions(np.ndarray.ravel) round = wrap_function_keep_dimensions(np.ndarray.round) std = wrap_function_keep_dimensions(np.ndarray.std) sum = wrap_function_keep_dimensions(np.ndarray.sum) trace = wrap_function_keep_dimensions(np.ndarray.trace) var = wrap_function_change_dimensions(np.ndarray.var, lambda ar, d: d**2) all = wrap_function_remove_dimensions(np.ndarray.all) any = wrap_function_remove_dimensions(np.ndarray.any) nonzero = wrap_function_remove_dimensions(np.ndarray.nonzero) argmax = wrap_function_remove_dimensions(np.ndarray.argmax) argmin = wrap_function_remove_dimensions(np.ndarray.argmin) argsort = wrap_function_remove_dimensions(np.ndarray.argsort) def fill(self, values): # pylint: disable=C0111 fail_for_dimension_mismatch(self, values, "fill") super().fill(values) fill.__doc__ = np.ndarray.fill.__doc__ fill._do_not_run_doctests = True def put(self, indices, values, *args, **kwds): # pylint: disable=C0111 fail_for_dimension_mismatch(self, values, "fill") super().put(indices, values, *args, **kwds) put.__doc__ = np.ndarray.put.__doc__ put._do_not_run_doctests = True def clip(self, a_min, a_max, *args, **kwds): # pylint: disable=C0111 fail_for_dimension_mismatch(self, a_min, "clip") fail_for_dimension_mismatch(self, a_max, "clip") return Quantity( np.clip( np.array(self, copy=False), np.array(a_min, copy=False), np.array(a_max, copy=False), *args, **kwds, ), self.dim, ) clip.__doc__ = np.ndarray.clip.__doc__ clip._do_not_run_doctests = True def dot(self, other, **kwds): # pylint: disable=C0111 return Quantity( np.array(self).dot(np.array(other), **kwds), self.dim * get_dimensions(other), ) dot.__doc__ = np.ndarray.dot.__doc__ dot._do_not_run_doctests = True def searchsorted(self, v, **kwds): # pylint: disable=C0111 fail_for_dimension_mismatch(self, v, "searchsorted") return super().searchsorted(np.array(v, copy=False), **kwds) searchsorted.__doc__ = np.ndarray.searchsorted.__doc__ searchsorted._do_not_run_doctests = True def prod(self, *args, **kwds): # pylint: disable=C0111 prod_result = super().prod(*args, **kwds) # Calculating the correct dimensions is not completly trivial (e.g. # like doing self.dim**self.size) because prod can be called on # multidimensional arrays along a certain axis. # Our solution: Use a "dummy matrix" containing a 1 (without units) at # each entry and sum it, using the same keyword arguments as provided. # The result gives the exponent for the dimensions. # This relies on sum and prod having the same arguments, which is true # now and probably remains like this in the future dim_exponent = np.ones_like(self).sum(*args, **kwds) # The result is possibly multidimensional but all entries should be # identical if dim_exponent.size > 1: dim_exponent = dim_exponent[0] return Quantity(np.array(prod_result, copy=False), self.dim**dim_exponent) prod.__doc__ = np.ndarray.prod.__doc__ prod._do_not_run_doctests = True def cumprod(self, *args, **kwds): # pylint: disable=C0111 if not self.is_dimensionless: raise TypeError( "cumprod over array elements on quantities " "with dimensions is not possible." ) return Quantity(np.array(self, copy=False).cumprod(*args, **kwds)) cumprod.__doc__ = np.ndarray.cumprod.__doc__ cumprod._do_not_run_doctests = True class Unit(Quantity): r""" A physical unit. Normally, you do not need to worry about the implementation of units. They are derived from the `Quantity` object with some additional information (name and string representation). Basically, a unit is just a number with given dimensions, e.g. mvolt = 0.001 with the dimensions of voltage. The units module defines a large number of standard units, and you can also define your own (see below). The unit class also keeps track of various things that were used to define it so as to generate a nice string representation of it. See below. When creating scaled units, you can use the following prefixes: ====== ====== ============== Factor Name Prefix ====== ====== ============== 10^24 yotta Y 10^21 zetta Z 10^18 exa E 10^15 peta P 10^12 tera T 10^9 giga G 10^6 mega M 10^3 kilo k 10^2 hecto h 10^1 deka da 1 10^-1 deci d 10^-2 centi c 10^-3 milli m 10^-6 micro u (\mu in SI) 10^-9 nano n 10^-12 pico p 10^-15 femto f 10^-18 atto a 10^-21 zepto z 10^-24 yocto y ====== ====== ============== **Defining your own** It can be useful to define your own units for printing purposes. So for example, to define the newton metre, you write >>> from brian2 import * >>> from brian2.units.allunits import newton >>> Nm = newton * metre You can then do >>> (1*Nm).in_unit(Nm) '1. N m' New "compound units", i.e. units that are composed of other units will be automatically registered and from then on used for display. For example, imagine you define total conductance for a membrane, and the total area of that membrane: >>> conductance = 10.*nS >>> area = 20000*um**2 If you now ask for the conductance density, you will get an "ugly" display in basic SI dimensions, as Brian does not know of a corresponding unit: >>> conductance/area 0.5 * metre ** -4 * kilogram ** -1 * second ** 3 * amp ** 2 By using an appropriate unit once, it will be registered and from then on used for display when appropriate: >>> usiemens/cm**2 usiemens / (cmetre ** 2) >>> conductance/area # same as before, but now Brian knows about uS/cm^2 50. * usiemens / (cmetre ** 2) Note that user-defined units cannot override the standard units (`volt`, `second`, etc.) that are predefined by Brian. For example, the unit ``Nm`` has the dimensions "length²·mass/time²", and therefore the same dimensions as the standard unit `joule`. The latter will be used for display purposes: >>> 3*joule 3. * joule >>> 3*Nm 3. * joule """ __slots__ = ["dim", "scale", "_dispname", "_name", "_latexname", "iscompound"] __array_priority__ = 100 automatically_register_units = True #### CONSTRUCTION #### def __new__( cls, arr, dim=None, scale=0, name=None, dispname=None, latexname=None, iscompound=False, dtype=None, copy=False, ): if dim is None: dim = DIMENSIONLESS obj = super().__new__( cls, arr, dim=dim, dtype=dtype, copy=copy, force_quantity=True ) return obj def __array_finalize__(self, orig): self.dim = getattr(orig, "dim", DIMENSIONLESS) self.scale = getattr(orig, "scale", 0) self._name = getattr(orig, "_name", "") self._dispname = getattr(orig, "_dispname", "") self._latexname = getattr(orig, "_latexname", "") self.iscompound = getattr(orig, "_iscompound", False) return self def __init__( self, value, dim=None, scale=0, name=None, dispname=None, latexname="", iscompound=False, ): if value != 10.0**scale: raise AssertionError( f"Unit value has to be 10**scale (scale={scale}, value={value})" ) if dim is None: dim = DIMENSIONLESS self.dim = dim #: The Dimensions of this unit #: The scale for this unit (as the integer exponent of 10), i.e. #: a scale of 3 means 10^3, e.g. for a "k" prefix. self.scale = scale if name is None: if dim is DIMENSIONLESS: name = "Unit(1)" else: name = repr(dim) if dispname is None: if dim is DIMENSIONLESS: dispname = "1" else: dispname = str(dim) #: The full name of this unit. self._name = name #: The display name of this unit. self._dispname = dispname #: A LaTeX expression for the name of this unit. self._latexname = latexname #: Whether this unit is a combination of other units. self.iscompound = iscompound if Unit.automatically_register_units: register_new_unit(self) @staticmethod def create(dim, name, dispname, latexname=None, scale=0): """ Create a new named unit. Parameters ---------- dim : `Dimension` The dimensions of the unit. name : `str` The full name of the unit, e.g. ``'volt'`` dispname : `str` The display name, e.g. ``'V'`` latexname : str, optional The name as a LaTeX expression (math mode is assumed, do not add $ signs or similar), e.g. ``'\\omega'``. If no `latexname` is specified, `dispname` will be used. scale : int, optional The scale of this unit as an exponent of 10, e.g. -3 for a unit that is 1/1000 of the base scale. Defaults to 0 (i.e. a base unit). Returns ------- u : `Unit` The new unit. """ name = str(name) dispname = str(dispname) if latexname is None: latexname = f"\\mathrm{{{dispname}}}" u = Unit( 10.0**scale, dim=dim, scale=scale, name=name, dispname=dispname, latexname=latexname, ) return u @staticmethod def create_scaled_unit(baseunit, scalefactor): """ Create a scaled unit from a base unit. Parameters ---------- baseunit : `Unit` The unit of which to create a scaled version, e.g. ``volt``, ``amp``. scalefactor : `str` The scaling factor, e.g. ``"m"`` for mvolt, mamp Returns ------- u : `Unit` The new unit. """ name = scalefactor + baseunit.name dispname = scalefactor + baseunit.dispname scale = _siprefixes[scalefactor] + baseunit.scale if scalefactor == "u": scalefactor = r"\mu" latexname = f"\\mathrm{{{scalefactor}}}{baseunit.latexname}" u = Unit( 10.0**scale, dim=baseunit.dim, name=name, dispname=dispname, latexname=latexname, scale=scale, ) return u #### METHODS #### def set_name(self, name): """Sets the name for the unit. .. deprecated:: 2.1 Create a new unit with `Unit.create` instead. """ raise NotImplementedError( "Setting the name for a unit after" "its creation is no longer supported, use" "'Unit.create' to create a new unit." ) def set_display_name(self, name): """Sets the display name for the unit. .. deprecated:: 2.1 Create a new unit with `Unit.create` instead. """ raise NotImplementedError( "Setting the display name for a unit after" "its creation is no longer supported, use" "'Unit.create' to create a new unit." ) def set_latex_name(self, name): """Sets the LaTeX name for the unit. .. deprecated:: 2.1 Create a new unit with `Unit.create` instead. """ raise NotImplementedError( "Setting the LaTeX name for a unit after" "its creation is no longer supported, use" "'Unit.create' to create a new unit." ) name = property( fget=lambda self: self._name, fset=set_name, doc="The name of the unit" ) dispname = property( fget=lambda self: self._dispname, fset=set_display_name, doc="The display name of the unit", ) latexname = property( fget=lambda self: self._latexname, fset=set_latex_name, doc="The LaTeX name of the unit", ) #### REPRESENTATION #### def __repr__(self): return self.name def __str__(self): return self.dispname def _latex(self, *args): return self.latexname def _repr_latex_(self): return f"${latex(self)}$" #### ARITHMETIC #### def __mul__(self, other): if isinstance(other, Unit): name = f"{self.name} * {other.name}" dispname = f"{self.dispname} {other.dispname}" latexname = f"{self.latexname}\\,{other.latexname}" scale = self.scale + other.scale u = Unit( 10.0**scale, dim=self.dim * other.dim, name=name, dispname=dispname, latexname=latexname, iscompound=True, scale=scale, ) return u else: return super().__mul__(other) def __rmul__(self, other): return self.__mul__(other) def __div__(self, other): if isinstance(other, Unit): if self.iscompound: dispname = f"({self.dispname})" name = f"({self.name})" else: dispname = self.dispname name = self.name dispname += "/" name += " / " if other.iscompound: dispname += f"({other.dispname})" name += f"({other.name})" else: dispname += other.dispname name += other.name latexname = rf"\frac{{{self.latexname}}}{{{other.latexname}}}" scale = self.scale - other.scale u = Unit( 10.0**scale, dim=self.dim / other.dim, name=name, dispname=dispname, latexname=latexname, scale=scale, iscompound=True, ) return u else: return super().__div__(other) def __rdiv__(self, other): if isinstance(other, Unit): return other.__div__(self) else: try: if is_dimensionless(other) and other == 1: return self**-1 except (ValueError, TypeError, DimensionMismatchError): pass return super().__rdiv__(other) def __pow__(self, other): if is_scalar_type(other): if self.iscompound: dispname = f"({self.dispname})" name = f"({self.name})" latexname = r"\left(%s\right)" % self.latexname else: dispname = self.dispname name = self.name latexname = self.latexname dispname += f"^{str(other)}" name += f" ** {repr(other)}" latexname += "^{%s}" % latex(other) scale = self.scale * other u = Unit( 10.0**scale, dim=self.dim**other, name=name, dispname=dispname, latexname=latexname, scale=scale, iscompound=True, ) # To avoid issues with units like (second ** -1) ** -1 return u else: return super().__pow__(other) def __iadd__(self, other): raise TypeError("Units cannot be modified in-place") def __isub__(self, other): raise TypeError("Units cannot be modified in-place") def __imul__(self, other): raise TypeError("Units cannot be modified in-place") def __idiv__(self, other): raise TypeError("Units cannot be modified in-place") def __itruediv__(self, other): raise TypeError("Units cannot be modified in-place") def __ifloordiv__(self, other): raise TypeError("Units cannot be modified in-place") def __imod__(self, other): raise TypeError("Units cannot be modified in-place") def __ipow__(self, other, modulo=None): raise TypeError("Units cannot be modified in-place") def __eq__(self, other): if isinstance(other, Unit): return other.dim is self.dim and other.scale == self.scale else: return Quantity.__eq__(self, other) def __neq__(self, other): return not self.__eq__(other) def __hash__(self): return hash((self.dim, self.scale)) class UnitRegistry: """ Stores known units for printing in best units. All a user needs to do is to use the `register_new_unit` function. Default registries: The units module defines three registries, the standard units, user units, and additional units. Finding best units is done by first checking standard, then user, then additional. New user units are added by using the `register_new_unit` function. Standard units includes all the basic non-compound unit names built in to the module, including volt, amp, etc. Additional units defines some compound units like newton metre (Nm) etc. Methods ------- add __getitem__ """ def __init__(self): self.units = collections.OrderedDict() self.units_for_dimensions = collections.defaultdict(dict) def add(self, u): """Add a unit to the registry""" self.units[repr(u)] = u self.units_for_dimensions[u.dim][float(u)] = u def __getitem__(self, x): """Returns the best unit for quantity x The algorithm is to consider the value: m=abs(x/u) for all matching units u. We select the unit where this ratio is the closest to 10 (if it is an array with several values, we select the unit where the deviations from that are the smallest. More precisely, the unit that minimizes the sum of (log10(m)-1)**2 over all entries). """ matching = self.units_for_dimensions.get(x.dim, {}) if len(matching) == 0: raise KeyError("Unit not found in registry.") matching_values = np.array(list(matching.keys()), copy=False) print_opts = np.get_printoptions() edgeitems, threshold = print_opts["edgeitems"], print_opts["threshold"] if x.size > threshold: # Only care about optimizing the units for the values that will # actually be shown later # The code looks a bit complex, but should return the same numbers # that are shown by numpy's string conversion slices = [] for shape in x.shape: if shape > 2 * edgeitems: slices.append((slice(0, edgeitems), slice(-edgeitems, None))) else: slices.append((slice(None),)) x_flat = np.hstack( [x[use_slices].flatten() for use_slices in itertools.product(*slices)] ) else: x_flat = np.array(x, copy=False).flatten() floatreps = np.tile(np.abs(x_flat), (len(matching), 1)).T / matching_values # ignore zeros, they are well represented in any unit floatreps[floatreps == 0] = np.nan if np.all(np.isnan(floatreps)): return matching[1.0] # all zeros, use the base unit deviations = np.nansum((np.log10(floatreps) - 1) ** 2, axis=0) return list(matching.values())[deviations.argmin()] def register_new_unit(u): """Register a new unit for automatic displaying of quantities Parameters ---------- u : `Unit` The unit that should be registered. Examples -------- >>> from brian2 import * >>> 2.0*farad/metre**2 2. * metre ** -4 * kilogram ** -1 * second ** 4 * amp ** 2 >>> register_new_unit(pfarad / mmetre**2) >>> 2.0*farad/metre**2 2000000. * pfarad / (mmetre ** 2) """ user_unit_register.add(u) #: `UnitRegistry` containing all the standard units (metre, kilogram, um2...) standard_unit_register = UnitRegistry() #: `UnitRegistry` containing additional units (newton*metre, farad / metre, ...) additional_unit_register = UnitRegistry() #: `UnitRegistry` containing all units defined by the user user_unit_register = UnitRegistry() def get_unit(d): """ Find an unscaled unit (e.g. `volt` but not `mvolt`) for a `Dimension`. Parameters ---------- d : `Dimension` The dimension to find a unit for. Returns ------- u : `Unit` A registered unscaled `Unit` for the dimensions ``d``, or a new `Unit` if no unit was found. """ for unit_register in [ standard_unit_register, user_unit_register, additional_unit_register, ]: if 1.0 in unit_register.units_for_dimensions[d]: return unit_register.units_for_dimensions[d][1.0] return Unit(1.0, dim=d) def get_unit_for_display(d): """ Return a string representation of an appropriate unscaled unit or ``'1'`` for a dimensionless quantity. Parameters ---------- d : `Dimension` or int The dimension to find a unit for. Returns ------- s : str A string representation of the respective unit or the string ``'1'``. """ if (isinstance(d, int) and d == 1) or d is DIMENSIONLESS: return "1" else: return str(get_unit(d)) #### DECORATORS def check_units(**au): """Decorator to check units of arguments passed to a function Examples -------- >>> from brian2.units import * >>> @check_units(I=amp, R=ohm, wibble=metre, result=volt) ... def getvoltage(I, R, **k): ... return I*R You don't have to check the units of every variable in the function, and you can define what the units should be for variables that aren't explicitly named in the definition of the function. For example, the code above checks that the variable wibble should be a length, so writing >>> getvoltage(1*amp, 1*ohm, wibble=1) # doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... DimensionMismatchError: Function "getvoltage" variable "wibble" has wrong dimensions, dimensions were (1) (m) fails, but >>> getvoltage(1*amp, 1*ohm, wibble=1*metre) 1. * volt passes. String arguments or ``None`` are not checked >>> getvoltage(1*amp, 1*ohm, wibble='hello') 1. * volt By using the special name ``result``, you can check the return value of the function. You can also use ``1`` or ``bool`` as a special value to check for a unitless number or a boolean value, respectively: >>> @check_units(value=1, absolute=bool, result=bool) ... def is_high(value, absolute=False): ... if absolute: ... return abs(value) >= 5 ... else: ... return value >= 5 This will then again raise an error if the argument if not of the expected type: >>> is_high(7) True >>> is_high(-7, True) True >>> is_high(3, 4) # doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... TypeError: Function "is_high" expected a boolean value for argument "absolute" but got 4. If the return unit depends on the unit of an argument, you can also pass a function that takes the units of all the arguments as its inputs (in the order specified in the function header): >>> @check_units(result=lambda d: d**2) ... def square(value): ... return value**2 If several arguments take arbitrary units but they have to be consistent among each other, you can state the name of another argument as a string to state that it uses the same unit as that argument. >>> @check_units(summand_1=None, summand_2='summand_1') ... def multiply_sum(multiplicand, summand_1, summand_2): ... "Calculates multiplicand*(summand_1 + summand_2)" ... return multiplicand*(summand_1 + summand_2) >>> multiply_sum(3, 4*mV, 5*mV) 27. * mvolt >>> multiply_sum(3*nA, 4*mV, 5*mV) 27. * pwatt >>> multiply_sum(3*nA, 4*mV, 5*nA) # doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... brian2.units.fundamentalunits.DimensionMismatchError: Function 'multiply_sum' expected the same arguments for arguments 'summand_1', 'summand_2', but argument 'summand_1' has unit V, while argument 'summand_2' has unit A. Raises ------ DimensionMismatchError In case the input arguments or the return value do not have the expected dimensions. TypeError If an input argument or return value was expected to be a boolean but is not. Notes ----- This decorator will destroy the signature of the original function, and replace it with the signature ``(*args, **kwds)``. Other decorators will do the same thing, and this decorator critically needs to know the signature of the function it is acting on, so it is important that it is the first decorator to act on a function. It cannot be used in combination with another decorator that also needs to know the signature of the function. Note that the ``bool`` type is "strict", i.e. it expects a proper boolean value and does not accept 0 or 1. This is not the case the other way round, declaring an argument or return value as "1" *does* allow for a ``True`` or ``False`` value. """ def do_check_units(f): def new_f(*args, **kwds): newkeyset = kwds.copy() arg_names = f.__code__.co_varnames[0 : f.__code__.co_argcount] for n, v in zip(arg_names, args[0 : f.__code__.co_argcount]): if ( not isinstance(v, (Quantity, str, bool)) and v is not None and n in au ): try: # allow e.g. to pass a Python list of values v = Quantity(v) except TypeError: if have_same_dimensions(au[n], 1): raise TypeError( f"Argument {n} is not a unitless value/array." ) else: raise TypeError( f"Argument '{n}' is not a quantity, " "expected a quantity with dimensions " f"{au[n]}" ) newkeyset[n] = v for k in newkeyset: # string variables are allowed to pass, the presumption is they # name another variable. None is also allowed, useful for # default parameters if ( k in au and not isinstance(newkeyset[k], str) and not newkeyset[k] is None and not au[k] is None ): if au[k] == bool: if not isinstance(newkeyset[k], bool): value = newkeyset[k] error_message = ( f"Function '{f.__name__}' " "expected a boolean value " f"for argument '{k}' but got " f"'{value}'" ) raise TypeError(error_message) elif isinstance(au[k], str): if not au[k] in newkeyset: error_message = ( f"Function '{f.__name__}' " "expected its argument to have the " f"same units as argument '{k}', but " "there is no argument of that name" ) raise TypeError(error_message) if not have_same_dimensions(newkeyset[k], newkeyset[au[k]]): d1 = get_dimensions(newkeyset[k]) d2 = get_dimensions(newkeyset[au[k]]) error_message = ( f"Function '{f.__name__}' expected " f"the argument '{k}' to have the same " f"units as argument '{au[k]}', but " f"argument '{k}' has " f"unit {get_unit_for_display(d1)}, " f"while argument '{au[k]}' " f"has unit {get_unit_for_display(d2)}." ) raise DimensionMismatchError(error_message) elif not have_same_dimensions(newkeyset[k], au[k]): unit = repr(au[k]) value = newkeyset[k] error_message = ( f"Function '{f.__name__}' " "expected a quantitity with unit " f"{unit} for argument '{k}' but got " f"'{value}'" ) raise DimensionMismatchError( error_message, get_dimensions(newkeyset[k]) ) result = f(*args, **kwds) if "result" in au: if isinstance(au["result"], Callable) and au["result"] != bool: expected_result = au["result"](*[get_dimensions(a) for a in args]) else: expected_result = au["result"] if au["result"] == bool: if not isinstance(result, bool): error_message = ( "The return value of function " f"'{f.__name__}' was expected to be " "a boolean value, but was of type " f"{type(result)}" ) raise TypeError(error_message) elif not have_same_dimensions(result, expected_result): unit = get_unit_for_display(expected_result) error_message = ( "The return value of function " f"'{f.__name__}' was expected to have " f"unit {unit} but was " f"'{result}'" ) raise DimensionMismatchError(error_message, get_dimensions(result)) return result new_f._orig_func = f new_f.__doc__ = f.__doc__ new_f.__name__ = f.__name__ # store the information in the function, necessary when using the # function in expressions or equations if hasattr(f, "_orig_arg_names"): arg_names = f._orig_arg_names else: arg_names = f.__code__.co_varnames[: f.__code__.co_argcount] new_f._arg_names = arg_names new_f._arg_units = [au.get(name, None) for name in arg_names] return_unit = au.get("result", None) if return_unit is None: new_f._return_unit = None else: new_f._return_unit = return_unit if return_unit == bool: new_f._returns_bool = True else: new_f._returns_bool = False new_f._orig_arg_names = arg_names # copy any annotation attributes if hasattr(f, "_annotation_attributes"): for attrname in f._annotation_attributes: setattr(new_f, attrname, getattr(f, attrname)) new_f._annotation_attributes = getattr(f, "_annotation_attributes", []) + [ "_arg_units", "_arg_names", "_return_unit", "_orig_func", "_returns_bool", ] return new_f return do_check_units brian2-2.5.4/brian2/units/stdunits.py000066400000000000000000000036161445201106100174450ustar00rootroot00000000000000######### PHYSICAL UNIT NAMES ##################### # ------------------------------------ Dan Goodman - # These are optional shorthand unit names which in # most circumstances shouldn't clash with local names """Optional short unit names This module defines the following short unit names: mV, mA, uA (micro_amp), nA, pA, mF, uF, nF, nS, mS, uS, ms, Hz, kHz, MHz, cm, cm2, cm3, mm, mm2, mm3, um, um2, um3 """ # isort:skip_file from .allunits import ( mvolt, mamp, uamp, namp, pamp, pfarad, ufarad, nfarad, nsiemens, usiemens, msiemens, msecond, usecond, hertz, khertz, Mhertz, cmetre, cmetre2, cmetre3, mmetre, mmetre2, mmetre3, umetre, umetre2, umetre3, mmolar, umolar, nmolar, ) from .allunits import all_units __all__ = [ "mV", "mA", "uA", "nA", "pA", "pF", "uF", "nF", "nS", "uS", "mS", "ms", "us", "Hz", "kHz", "MHz", "cm", "cm2", "cm3", "mm", "mm2", "mm3", "um", "um2", "um3", "mM", "uM", "nM", ] mV = mvolt mA = mamp uA = uamp nA = namp pA = pamp pF = pfarad uF = ufarad nF = nfarad nS = nsiemens uS = usiemens mS = msiemens ms = msecond us = usecond Hz = hertz kHz = khertz MHz = Mhertz cm = cmetre cm2 = cmetre2 cm3 = cmetre3 mm = mmetre mm2 = mmetre2 mm3 = mmetre3 um = umetre um2 = umetre2 um3 = umetre3 mM = mmolar uM = umolar nM = nmolar stdunits = { "mV": mV, "mA": mA, "uA": uA, "nA": nA, "pA": pA, "pF": pF, "uF": uF, "nF": nF, "nS": nS, "uS": uS, "ms": ms, "us": us, "Hz": Hz, "kHz": kHz, "MHz": MHz, "cm": cm, "cm2": cm2, "cm3": cm3, "mm": mm, "mm2": mm2, "mm3": mm3, "um": um, "um2": um2, "um3": um3, "mM": mM, "uM": uM, "nM": nM, } all_units.extend(stdunits.values()) brian2-2.5.4/brian2/units/unitsafefunctions.py000066400000000000000000000165271445201106100213440ustar00rootroot00000000000000""" Unit-aware replacements for numpy functions. """ from functools import wraps import numpy as np from .fundamentalunits import ( DIMENSIONLESS, Quantity, check_units, fail_for_dimension_mismatch, is_dimensionless, wrap_function_dimensionless, wrap_function_remove_dimensions, ) __all__ = [ "log", "log10", "exp", "expm1", "log1p", "exprel", "sin", "cos", "tan", "arcsin", "arccos", "arctan", "sinh", "cosh", "tanh", "arcsinh", "arccosh", "arctanh", "diagonal", "ravel", "trace", "dot", "where", "ones_like", "zeros_like", "arange", "linspace", ] def where(condition, *args, **kwds): # pylint: disable=C0111 if len(args) == 0: # nothing to do return np.where(condition, *args, **kwds) elif len(args) == 2: # check that x and y have the same dimensions fail_for_dimension_mismatch( args[0], args[1], "x and y need to have the same dimensions" ) if is_dimensionless(args[0]): return np.where(condition, *args, **kwds) else: # as both arguments have the same unit, just use the first one's dimensionless_args = [np.asarray(arg) for arg in args] return Quantity.with_dimensions( np.where(condition, *dimensionless_args), args[0].dimensions ) else: # illegal number of arguments, let numpy take care of this return np.where(condition, *args, **kwds) where.__doc__ = np.where.__doc__ where._do_not_run_doctests = True # Functions that work on dimensionless quantities only sin = wrap_function_dimensionless(np.sin) sinh = wrap_function_dimensionless(np.sinh) arcsin = wrap_function_dimensionless(np.arcsin) arcsinh = wrap_function_dimensionless(np.arcsinh) cos = wrap_function_dimensionless(np.cos) cosh = wrap_function_dimensionless(np.cosh) arccos = wrap_function_dimensionless(np.arccos) arccosh = wrap_function_dimensionless(np.arccosh) tan = wrap_function_dimensionless(np.tan) tanh = wrap_function_dimensionless(np.tanh) arctan = wrap_function_dimensionless(np.arctan) arctanh = wrap_function_dimensionless(np.arctanh) log = wrap_function_dimensionless(np.log) log10 = wrap_function_dimensionless(np.log10) exp = wrap_function_dimensionless(np.exp) expm1 = wrap_function_dimensionless(np.expm1) log1p = wrap_function_dimensionless(np.log1p) @check_units(x=1, result=1) def exprel(x): x = np.asarray(x) if issubclass(x.dtype.type, np.integer): result = np.empty_like(x, dtype=np.float64) else: result = np.empty_like(x) # Following the implementation of exprel from scipy.special if x.shape == (): if np.abs(x) < 1e-16: return 1.0 elif x > 717: return np.inf else: return np.expm1(x) / x else: small = np.abs(x) < 1e-16 big = x > 717 in_between = np.logical_not(small | big) result[small] = 1.0 result[big] = np.inf result[in_between] = np.expm1(x[in_between]) / x[in_between] return result ones_like = wrap_function_remove_dimensions(np.ones_like) zeros_like = wrap_function_remove_dimensions(np.zeros_like) def wrap_function_to_method(func): """ Wraps a function so that it calls the corresponding method on the Quantities object (if called with a Quantities object as the first argument). All other arguments are left untouched. """ @wraps(func) def f(x, *args, **kwds): # pylint: disable=C0111 if isinstance(x, Quantity): return getattr(x, func.__name__)(*args, **kwds) else: # no need to wrap anything return func(x, *args, **kwds) f.__doc__ = func.__doc__ f.__name__ = func.__name__ f._do_not_run_doctests = True return f @wraps(np.arange) def arange(*args, **kwargs): # arange has a bit of a complicated argument structure unfortunately # we leave the actual checking of the number of arguments to numpy, though # default values start = kwargs.pop("start", 0) step = kwargs.pop("step", 1) stop = kwargs.pop("stop", None) if len(args) == 1: if stop is not None: raise TypeError("Duplicate definition of 'stop'") stop = args[0] elif len(args) == 2: if start != 0: raise TypeError("Duplicate definition of 'start'") if stop is not None: raise TypeError("Duplicate definition of 'stop'") start, stop = args elif len(args) == 3: if start != 0: raise TypeError("Duplicate definition of 'start'") if stop is not None: raise TypeError("Duplicate definition of 'stop'") if step != 1: raise TypeError("Duplicate definition of 'step'") start, stop, step = args elif len(args) > 3: raise TypeError("Need between 1 and 3 non-keyword arguments") if stop is None: raise TypeError("Missing stop argument.") fail_for_dimension_mismatch( start, stop, error_message=( "Start value {start} and stop value {stop} have to have the same units." ), start=start, stop=stop, ) fail_for_dimension_mismatch( stop, step, error_message=( "Stop value {stop} and step value {step} have to have the same units." ), stop=stop, step=step, ) dim = getattr(stop, "dim", DIMENSIONLESS) return Quantity( np.arange( start=np.asarray(start), stop=np.asarray(stop), step=np.asarray(step), **kwargs, ), dim=dim, copy=False, ) arange._do_not_run_doctests = True @wraps(np.linspace) def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None): fail_for_dimension_mismatch( start, stop, error_message=( "Start value {start} and stop value {stop} have to have the same units." ), start=start, stop=stop, ) dim = getattr(start, "dim", DIMENSIONLESS) result = np.linspace( np.asarray(start), np.asarray(stop), num=num, endpoint=endpoint, retstep=retstep, dtype=dtype, ) return Quantity(result, dim=dim, copy=False) linspace._do_not_run_doctests = True # these functions discard subclass info -- maybe a bug in numpy? ravel = wrap_function_to_method(np.ravel) diagonal = wrap_function_to_method(np.diagonal) trace = wrap_function_to_method(np.trace) dot = wrap_function_to_method(np.dot) # This is a very minor detail: setting the __module__ attribute allows the # automatic reference doc generation mechanism to attribute the functions to # this module. Maybe also helpful for IDEs and other code introspection tools. sin.__module__ = __name__ sinh.__module__ = __name__ arcsin.__module__ = __name__ arcsinh.__module__ = __name__ cos.__module__ = __name__ cosh.__module__ = __name__ arccos.__module__ = __name__ arccosh.__module__ = __name__ tan.__module__ = __name__ tanh.__module__ = __name__ arctan.__module__ = __name__ arctanh.__module__ = __name__ log.__module__ = __name__ exp.__module__ = __name__ ravel.__module__ = __name__ diagonal.__module__ = __name__ trace.__module__ = __name__ dot.__module__ = __name__ arange.__module__ = __name__ linspace.__module__ = __name__ brian2-2.5.4/brian2/utils/000077500000000000000000000000001445201106100152065ustar00rootroot00000000000000brian2-2.5.4/brian2/utils/__init__.py000066400000000000000000000001631445201106100173170ustar00rootroot00000000000000""" Utility functions for Brian. """ from .logger import * __all__ = ["get_logger", "BrianLogger", "std_silent"] brian2-2.5.4/brian2/utils/arrays.py000066400000000000000000000024231445201106100170620ustar00rootroot00000000000000""" Helper module containing functions that operate on numpy arrays. """ import numpy as np def calc_repeats(delay): """ Calculates offsets corresponding to an array, where repeated values are subsequently numbered, i.e. if there n identical values, the returned array will have values from 0 to n-1 at their positions. The code is complex because tricks are needed for vectorisation. This function is used in the Python `SpikeQueue` to calculate the offset array for the insertion of spikes with their respective delays into the queue and in the numpy code for synapse creation to calculate how many synapses for each source-target pair exist. Examples -------- >>> import numpy as np >>> print(calc_repeats(np.array([7, 5, 7, 3, 7, 5]))) [0 0 1 0 2 1] """ # We use merge sort because it preserves the input order of equal # elements in the sorted output sort_indices = np.argsort(delay, kind="mergesort") xs = delay[sort_indices] J = xs[1:] != xs[:-1] A = np.hstack((0, np.cumsum(J))) B = np.hstack((0, np.cumsum(np.logical_not(J)))) BJ = np.hstack((0, B[:-1][J])) ei = B - BJ[A] ofs = np.zeros_like(delay, dtype=np.int32) ofs[sort_indices] = np.array(ei, dtype=ofs.dtype) return ofs brian2-2.5.4/brian2/utils/caching.py000066400000000000000000000124031445201106100171540ustar00rootroot00000000000000""" Module to support caching of function results to memory (used to cache results of parsing, generation of state update code, etc.). Provides the `cached` decorator. """ import collections import functools from collections.abc import Mapping class CacheKey: """ Mixin class for objects that will be used as keys for caching (e.g. `Variable` objects) and have to define a certain "identity" with respect to caching. This "identity" is different from standard Python hashing and equality checking: a `Variable` for example would be considered "identical" for caching purposes regardless which object (e.g. `NeuronGroup`) it belongs to (because this does not matter for parsing, creating abstract code, etc.) but this of course matters for the values it refers to and therefore for comparison of equality to other variables. Classes that mix in the `CacheKey` class should re-define the ``_cache_irrelevant_attributes`` attribute to note all the attributes that should be ignored. The property ``_state_tuple`` will refer to a tuple of all attributes that were not excluded in such a way; this tuple will be used as the key for caching purposes. """ #: Set of attributes that should not be considered for caching of state #: update code, etc. _cache_irrelevant_attributes = set() @property def _state_tuple(self): """A tuple with this object's attribute values, defining its identity for caching purposes. See `CacheKey` for details.""" return tuple( value for key, value in sorted(self.__dict__.items()) if key not in self._cache_irrelevant_attributes ) class _CacheStatistics: """ Helper class to store cache statistics """ def __init__(self): self.hits = 0 self.misses = 0 def __repr__(self): return f"" def cached(func): """ Decorator to cache a function so that it will not be re-evaluated when called with the same arguments. Uses the `_hashable` function to make arguments usable as a dictionary key even though they mutable (lists, dictionaries, etc.). Notes ----- This is *not* a general-purpose caching decorator in any way comparable to ``functools.lru_cache`` or joblib's caching functions. It is very simplistic (no maximum cache size, no normalization of calls, e.g. ``foo(3)`` and ``foo(x=3)`` are not considered equivalent function calls) and makes very specific assumptions for our use case. Most importantly, `Variable` objects are considered to be identical when they refer to the same object, even though the actually stored values might have changed. Parameters ---------- func : function The function to decorate. Returns ------- decorated : function The decorated function. """ # For simplicity, we store the cache in the function itself func._cache = {} func._cache_statistics = _CacheStatistics() @functools.wraps(func) def cached_func(*args, **kwds): try: cache_key = tuple( [_hashable(arg) for arg in args] + [(key, _hashable(value)) for key, value in sorted(kwds.items())] ) except TypeError: # If we cannot handle a type here, that most likely means that the # user provided an argument of a type we don't handle. This will # lead to an error message later that is most likely more meaningful # to the user than an error message by the caching system # complaining about an unsupported type. return func(*args, **kwds) if cache_key in func._cache: func._cache_statistics.hits += 1 else: func._cache_statistics.misses += 1 func._cache[cache_key] = func(*args, **kwds) return func._cache[cache_key] return cached_func _of_type_cache = collections.defaultdict(set) def _of_type(obj_type, check_type): if (obj_type, check_type) not in _of_type_cache: _of_type_cache[(obj_type, check_type)] = issubclass(obj_type, check_type) return _of_type_cache[(obj_type, check_type)] def _hashable(obj): """Helper function to make a few data structures hashable (e.g. a dictionary gets converted to a frozenset). The function is specifically tailored to our use case and not meant to be generally useful.""" if hasattr(obj, "_state_tuple"): return _hashable(obj._state_tuple) obj_type = type(obj) if _of_type(obj_type, Mapping): return frozenset( (_hashable(key), _hashable(value)) for key, value in obj.items() ) elif _of_type(obj_type, set): return frozenset(_hashable(el) for el in obj) elif _of_type(obj_type, tuple) or _of_type(obj_type, list): return tuple(_hashable(el) for el in obj) if hasattr(obj, "dim") and getattr(obj, "shape", None) == (): # Scalar Quantity object return float(obj), obj.dim else: try: # Make sure that the object is hashable hash(obj) return obj except TypeError: raise TypeError(f"Do not know how to handle object of type {type(obj)}") brian2-2.5.4/brian2/utils/environment.py000066400000000000000000000005351445201106100201270ustar00rootroot00000000000000""" Utility functions to get information about the environment Brian is running in. """ import builtins def running_from_ipython(): """ Check whether we are currently running under ipython. Returns ------- ipython : bool Whether running under ipython or not. """ return getattr(builtins, "__IPYTHON__", False) brian2-2.5.4/brian2/utils/filelock.py000066400000000000000000000314071445201106100173550ustar00rootroot00000000000000# This is free and unencumbered software released into the public domain. # # Anyone is free to copy, modify, publish, use, compile, sell, or # distribute this software, either in source code form or as a compiled # binary, for any purpose, commercial or non-commercial, and by any # means. # # In jurisdictions that recognize copyright laws, the author or authors # of this software dedicate any and all copyright interest in the # software to the public domain. We make this dedication for the benefit # of the public at large and to the detriment of our heirs and # successors. We intend this dedication to be an overt act of # relinquishment in perpetuity of all present and future rights to this # software under copyright law. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR # OTHER DEALINGS IN THE SOFTWARE. # # For more information, please refer to """ A platform independent file lock that supports the with-statement. """ # Modules # ------------------------------------------------ import logging import os import threading import time try: import warnings except ImportError: warnings = None try: import msvcrt except ImportError: msvcrt = None try: import fcntl except ImportError: fcntl = None # Backward compatibility # ------------------------------------------------ try: TimeoutError except NameError: TimeoutError = OSError # Data # ------------------------------------------------ __all__ = [ "Timeout", "BaseFileLock", "WindowsFileLock", "UnixFileLock", "SoftFileLock", "FileLock", ] __version__ = "3.0.12" _logger = None def logger(): """Returns the logger instance used in this module.""" global _logger _logger = _logger or logging.getLogger(__name__) return _logger # Exceptions # ------------------------------------------------ class Timeout(TimeoutError): """ Raised when the lock could not be acquired in *timeout* seconds. """ def __init__(self, lock_file): """ """ #: The path of the file lock. self.lock_file = lock_file return None def __str__(self): temp = f"The file lock '{self.lock_file}' could not be acquired." return temp # Classes # ------------------------------------------------ # This is a helper class which is returned by :meth:`BaseFileLock.acquire` # and wraps the lock to make sure __enter__ is not called twice when entering # the with statement. # If we would simply return *self*, the lock would be acquired again # in the *__enter__* method of the BaseFileLock, but not released again # automatically. # # :seealso: issue #37 (memory leak) class _Acquire_ReturnProxy: def __init__(self, lock): self.lock = lock return None def __enter__(self): return self.lock def __exit__(self, exc_type, exc_value, traceback): self.lock.release() return None class BaseFileLock: """ Implements the base class of a file lock. """ def __init__(self, lock_file, timeout=-1): """ """ # The path to the lock file. self._lock_file = lock_file # The file descriptor for the *_lock_file* as it is returned by the # os.open() function. # This file lock is only NOT None, if the object currently holds the # lock. self._lock_file_fd = None # The default timeout value. self.timeout = timeout # We use this lock primarily for the lock counter. self._thread_lock = threading.Lock() # The lock counter is used for implementing the nested locking # mechanism. Whenever the lock is acquired, the counter is increased and # the lock is only released, when this value is 0 again. self._lock_counter = 0 return None @property def lock_file(self): """ The path to the lock file. """ return self._lock_file @property def timeout(self): """ You can set a default timeout for the filelock. It will be used as fallback value in the acquire method, if no timeout value (*None*) is given. If you want to disable the timeout, set it to a negative value. A timeout of 0 means, that there is exactly one attempt to acquire the file lock. .. versionadded:: 2.0.0 """ return self._timeout @timeout.setter def timeout(self, value): """ """ self._timeout = float(value) return None # Platform dependent locking # -------------------------------------------- def _acquire(self): """ Platform dependent. If the file lock could be acquired, self._lock_file_fd holds the file descriptor of the lock file. """ raise NotImplementedError() def _release(self): """ Releases the lock and sets self._lock_file_fd to None. """ raise NotImplementedError() # Platform independent methods # -------------------------------------------- @property def is_locked(self): """ True, if the object holds the file lock. .. versionchanged:: 2.0.0 This was previously a method and is now a property. """ return self._lock_file_fd is not None def acquire(self, timeout=None, poll_intervall=0.05): """ Acquires the file lock or fails with a :exc:`Timeout` error. .. code-block:: python # You can use this method in the context manager (recommended) with lock.acquire(): pass # Or use an equivalent try-finally construct: lock.acquire() try: pass finally: lock.release() :arg float timeout: The maximum time waited for the file lock. If ``timeout < 0``, there is no timeout and this method will block until the lock could be acquired. If ``timeout`` is None, the default :attr:`~timeout` is used. :arg float poll_intervall: We check once in *poll_intervall* seconds if we can acquire the file lock. :raises Timeout: if the lock could not be acquired in *timeout* seconds. .. versionchanged:: 2.0.0 This method returns now a *proxy* object instead of *self*, so that it can be used in a with statement without side effects. """ # Use the default timeout, if no timeout is provided. if timeout is None: timeout = self.timeout # Increment the number right at the beginning. # We can still undo it, if something fails. with self._thread_lock: self._lock_counter += 1 lock_id = id(self) lock_filename = self._lock_file start_time = time.time() try: while True: with self._thread_lock: if not self.is_locked: # Level 5 = DIAGNOSTIC logger().log( 5, "Attempting to acquire lock %s on %s", lock_id, lock_filename, ) self._acquire() if self.is_locked: logger().debug("Lock %s acquired on %s", lock_id, lock_filename) break elif timeout >= 0 and time.time() - start_time > timeout: logger().debug( "Timeout on acquiring lock %s on %s", lock_id, lock_filename ) raise Timeout(self._lock_file) else: logger().log( 5, "Lock %s not acquired on %s, waiting %s seconds ...", lock_id, lock_filename, poll_intervall, ) time.sleep(poll_intervall) except BaseException: # Something did go wrong, so decrement the counter. with self._thread_lock: self._lock_counter = max(0, self._lock_counter - 1) raise return _Acquire_ReturnProxy(lock=self) def release(self, force=False): """ Releases the file lock. Please note, that the lock is only completly released, if the lock counter is 0. Also note, that the lock file itself is not automatically deleted. :arg bool force: If true, the lock counter is ignored and the lock is released in every case. """ with self._thread_lock: if self.is_locked: self._lock_counter -= 1 if self._lock_counter == 0 or force: lock_id = id(self) lock_filename = self._lock_file logger().log( 5, "Attempting to release lock %s on %s", lock_id, lock_filename ) self._release() self._lock_counter = 0 logger().debug("Lock %s released on %s", lock_id, lock_filename) return None def __enter__(self): self.acquire() return self def __exit__(self, exc_type, exc_value, traceback): self.release() return None def __del__(self): self.release(force=True) return None # Windows locking mechanism # ~~~~~~~~~~~~~~~~~~~~~~~~~ class WindowsFileLock(BaseFileLock): """ Uses the :func:`msvcrt.locking` function to hard lock the lock file on windows systems. """ def _acquire(self): open_mode = os.O_RDWR | os.O_CREAT | os.O_TRUNC try: fd = os.open(self._lock_file, open_mode) except OSError: pass else: try: msvcrt.locking(fd, msvcrt.LK_NBLCK, 1) except OSError: os.close(fd) else: self._lock_file_fd = fd return None def _release(self): fd = self._lock_file_fd self._lock_file_fd = None msvcrt.locking(fd, msvcrt.LK_UNLCK, 1) os.close(fd) try: os.remove(self._lock_file) # Probably another instance of the application # that acquired the file lock. except OSError: pass return None # Unix locking mechanism # ~~~~~~~~~~~~~~~~~~~~~~ class UnixFileLock(BaseFileLock): """ Uses the :func:`fcntl.flock` to hard lock the lock file on unix systems. """ def _acquire(self): open_mode = os.O_RDWR | os.O_CREAT | os.O_TRUNC fd = os.open(self._lock_file, open_mode) try: fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) except OSError: os.close(fd) else: self._lock_file_fd = fd return None def _release(self): # Do not remove the lockfile: # # https://github.com/benediktschmitt/py-filelock/issues/31 # https://stackoverflow.com/questions/17708885/flock-removing-locked-file-without-race-condition fd = self._lock_file_fd self._lock_file_fd = None fcntl.flock(fd, fcntl.LOCK_UN) os.close(fd) return None # Soft lock # ~~~~~~~~~ class SoftFileLock(BaseFileLock): """ Simply watches the existence of the lock file. """ def _acquire(self): open_mode = os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_TRUNC try: fd = os.open(self._lock_file, open_mode) except OSError: pass else: self._lock_file_fd = fd return None def _release(self): os.close(self._lock_file_fd) self._lock_file_fd = None try: os.remove(self._lock_file) # The file is already deleted and that's what we want. except OSError: pass return None # Platform filelock # ~~~~~~~~~~~~~~~~~ #: Alias for the lock, which should be used for the current platform. On #: Windows, this is an alias for :class:`WindowsFileLock`, on Unix for #: :class:`UnixFileLock` and otherwise for :class:`SoftFileLock`. FileLock = None if msvcrt: FileLock = WindowsFileLock elif fcntl: FileLock = UnixFileLock else: FileLock = SoftFileLock if warnings is not None: warnings.warn("only soft file lock is available", stacklevel=1) brian2-2.5.4/brian2/utils/filetools.py000066400000000000000000000036721445201106100175700ustar00rootroot00000000000000""" File system tools """ import os __all__ = [ "ensure_directory", "ensure_directory_of_file", "in_directory", "copy_directory", ] def ensure_directory_of_file(f): """ Ensures that a directory exists for filename to go in (creates if necessary), and returns the directory path. """ d = os.path.dirname(f) if not os.path.exists(d): os.makedirs(d) return d def ensure_directory(d): """ Ensures that a given directory exists (creates it if necessary) """ if not os.path.exists(d): os.makedirs(d) return d class in_directory: """ Safely temporarily work in a subdirectory Usage:: with in_directory(directory): ... do stuff here Guarantees that the code in the with block will be executed in directory, and that after the block is completed we return to the original directory. """ def __init__(self, new_dir): self.orig_dir = os.getcwd() self.new_dir = new_dir def __enter__(self): os.chdir(self.new_dir) def __exit__(self, *exc_info): os.chdir(self.orig_dir) def copy_directory(source, target): """ Copies directory source to target. """ relnames = [] sourcebase = os.path.normpath(source) + os.path.sep for root, _, filenames in os.walk(source): for filename in filenames: fullname = os.path.normpath(os.path.join(root, filename)) relname = fullname.replace(sourcebase, "") relnames.append(relname) tgtname = os.path.join(target, relname) ensure_directory_of_file(tgtname) with open(fullname) as f: contents = f.read() if os.path.exists(tgtname): with open(tgtname) as f: if f.read() == contents: continue with open(tgtname, "w") as f: f.write(contents) return relnames brian2-2.5.4/brian2/utils/logger.py000066400000000000000000000737531445201106100170560ustar00rootroot00000000000000""" Brian's logging module. Preferences ----------- .. document_brian_prefs:: logging """ import atexit import logging import logging.handlers import os import shutil import sys import tempfile import time from logging.handlers import RotatingFileHandler from warnings import warn import numpy try: import scipy except ImportError: scipy = None import sympy import brian2 from brian2.core.preferences import BrianPreference, prefs from .environment import running_from_ipython __all__ = ["get_logger", "BrianLogger", "std_silent"] # =============================================================================== # Logging preferences # =============================================================================== def log_level_validator(log_level): log_levels = ("CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG", "DIAGNOSTIC") return log_level.upper() in log_levels #: Our new log level for more detailed debug output (mostly useful for debugging #: Brian itself, not for user scripts) DIAGNOSTIC = 5 #: Translation from string representation to number LOG_LEVELS = { "CRITICAL": logging.CRITICAL, "ERROR": logging.ERROR, "WARNING": logging.WARNING, "INFO": logging.INFO, "DEBUG": logging.DEBUG, "DIAGNOSTIC": DIAGNOSTIC, } logging.addLevelName(DIAGNOSTIC, "DIAGNOSTIC") if "logging" not in prefs.pref_register: # Duplicate import of this module can happen when the documentation is built prefs.register_preferences( "logging", "Logging system preferences", delete_log_on_exit=BrianPreference( default=True, docs=""" Whether to delete the log and script file on exit. If set to ``True`` (the default), log files (and the copy of the main script) will be deleted after the brian process has exited, unless an uncaught exception occurred. If set to ``False``, all log files will be kept. """, ), file_log_level=BrianPreference( default="DEBUG", docs=""" What log level to use for the log written to the log file. In case file logging is activated (see `logging.file_log`), which log level should be used for logging. Has to be one of CRITICAL, ERROR, WARNING, INFO, DEBUG or DIAGNOSTIC. """, validator=log_level_validator, ), console_log_level=BrianPreference( default="INFO", docs=""" What log level to use for the log written to the console. Has to be one of CRITICAL, ERROR, WARNING, INFO, DEBUG or DIAGNOSTIC. """, validator=log_level_validator, ), file_log=BrianPreference( default=True, docs=""" Whether to log to a file or not. If set to ``True`` (the default), logging information will be written to a file. The log level can be set via the `logging.file_log_level` preference. """, ), file_log_max_size=BrianPreference( default=10000000, docs=""" The maximum size for the debug log before it will be rotated. If set to any value ``> 0``, the debug log will be rotated once this size is reached. Rotating the log means that the old debug log will be moved into a file in the same directory but with suffix ``".1"`` and the a new log file will be created with the same pathname as the original file. Only one backup is kept; if a file with suffix ``".1"`` already exists when rotating, it will be overwritten. If set to ``0``, no log rotation will be applied. The default setting rotates the log file after 10MB. """, ), save_script=BrianPreference( default=True, docs=""" Whether to save a copy of the script that is run. If set to ``True`` (the default), a copy of the currently run script is saved to a temporary location. It is deleted after a successful run (unless `logging.delete_log_on_exit` is ``False``) but is kept after an uncaught exception occured. This can be helpful for debugging, in particular when several simulations are running in parallel. """, ), std_redirection=BrianPreference( default=True, docs=""" Whether or not to redirect stdout/stderr to null at certain places. This silences a lot of annoying compiler output, but will also hide error messages making it harder to debug problems. You can always temporarily switch it off when debugging. If `logging.std_redirection_to_file` is set to ``True`` as well, then the output is saved to a file and if an error occurs the name of this file will be printed. """, ), std_redirection_to_file=BrianPreference( default=True, docs=""" Whether to redirect stdout/stderr to a file. If both ``logging.std_redirection`` and this preference are set to ``True``, all standard output/error (most importantly output from the compiler) will be stored in files and if an error occurs the name of this file will be printed. If `logging.std_redirection` is ``True`` and this preference is ``False``, then all standard output/error will be completely suppressed, i.e. neither be displayed nor stored in a file. The value of this preference is ignore if `logging.std_redirection` is set to ``False``. """, ), display_brian_error_message=BrianPreference( default=True, docs=""" Whether to display a text for uncaught errors, mentioning the location of the log file, the mailing list and the github issues. Defaults to ``True``.""", ), ) # =============================================================================== # Initial setup # =============================================================================== def _encode(text): """Small helper function to encode unicode strings as UTF-8.""" return text.encode("UTF-8") UNHANDLED_ERROR_MESSAGE = ( "Brian 2 encountered an unexpected error. " "If you think this is a bug in Brian 2, please report this issue either to the " "discourse forum at , " "or to the issue tracker at ." ) def brian_excepthook(exc_type, exc_obj, exc_tb): """ Display a message mentioning the debug log in case of an uncaught exception. """ # Do not catch Ctrl+C if exc_type == KeyboardInterrupt: return logger = logging.getLogger("brian2") BrianLogger.exception_occured = True if not prefs["logging.display_brian_error_message"]: # Put the exception message in the log file, but do not log to the # console if BrianLogger.console_handler is not None: logger.removeHandler(BrianLogger.console_handler) logger.exception("An exception occured", exc_info=(exc_type, exc_obj, exc_tb)) if BrianLogger.console_handler is not None: logger.addHandler(BrianLogger.console_handler) # Run the default except hook return sys.__excepthook__(exc_type, exc_obj, exc_tb) message = UNHANDLED_ERROR_MESSAGE if BrianLogger.tmp_log is not None: message += ( " Please include this file with debug information in your " f"report: {BrianLogger.tmp_log} " ) if BrianLogger.tmp_script is not None: message += ( " Additionally, you can also include a copy " "of the script that was run, available " f"at: {BrianLogger.tmp_script}" ) if hasattr(std_silent, "dest_fname_stdout"): stdout = std_silent.dest_fname_stdout stderr = std_silent.dest_fname_stderr message += ( " You can also include a copy of the " "redirected std stream outputs, available at " f"'{stdout}' and '{stderr}'." ) message += " Thanks!" # very important :) logger.error(message, exc_info=(exc_type, exc_obj, exc_tb)) def clean_up_logging(): """ Shutdown the logging system and delete the debug log file if no error occured. """ logging.shutdown() if not BrianLogger.exception_occured and prefs["logging.delete_log_on_exit"]: if BrianLogger.tmp_log is not None: try: os.remove(BrianLogger.tmp_log) except OSError as exc: warn(f"Could not delete log file: {exc}") # Remove log files that have been rotated (currently only one) rotated_log = f"{BrianLogger.tmp_log}.1" if os.path.exists(rotated_log): try: os.remove(rotated_log) except OSError as exc: warn(f"Could not delete log file: {exc}") if BrianLogger.tmp_script is not None: try: os.remove(BrianLogger.tmp_script) except OSError as exc: warn(f"Could not delete copy of script file: {exc}") std_silent.close() atexit.register(clean_up_logging) class HierarchyFilter: """ A class for suppressing all log messages in a subtree of the name hierarchy. Does exactly the opposite as the `logging.Filter` class, which allows messages in a certain name hierarchy to *pass*. Parameters ---------- name : str The name hiearchy to suppress. See `BrianLogger.suppress_hierarchy` for details. """ def __init__(self, name): self.orig_filter = logging.Filter(name) def filter(self, record): """ Filter out all messages in a subtree of the name hierarchy. """ # do the opposite of what the standard filter class would do return not self.orig_filter.filter(record) class NameFilter: """ A class for suppressing log messages ending with a certain name. Parameters ---------- name : str The name to suppress. See `BrianLogger.suppress_name` for details. """ def __init__(self, name): self.name = name def filter(self, record): """ Filter out all messages ending with a certain name. """ # The last part of the name record_name = record.name.split(".")[-1] return self.name != record_name class BrianLogger: """ Convenience object for logging. Call `get_logger` to get an instance of this class. Parameters ---------- name : str The name used for logging, normally the name of the module. """ #: Class attribute to remember whether any exception occured exception_occured = False #: Class attribute for remembering log messages that should only be #: displayed once _log_messages = set() #: The name of the temporary log file (by default deleted after the run if #: no exception occurred), if any tmp_log = None #: The `logging.FileHandler` responsible for logging to the temporary log #: file file_handler = None #: The `logging.StreamHandler` responsible for logging to the console console_handler = None #: The name of the temporary copy of the main script file (by default #: deleted after the run if no exception occurred), if any tmp_script = None #: The pid of the process that initialized the logger – used to switch off file logging in #: multiprocessing contexts _pid = None def __init__(self, name): self.name = name def _log(self, log_level, msg, name_suffix, once): """ Log an entry. Parameters ---------- log_level : {'debug', 'info', 'warn', 'error'} The level with which to log the message. msg : str The log message. name_suffix : str A suffix that will be added to the logger name. once : bool Whether to suppress identical messages if they are logged again. """ name = self.name if name_suffix: name += f".{name_suffix}" # Switch off file logging when using multiprocessing if BrianLogger.tmp_log is not None and BrianLogger._pid != os.getpid(): BrianLogger.tmp_log = None logging.getLogger("brian2").removeHandler(BrianLogger.file_handler) BrianLogger.file_handler = None if once: # Check whether this exact message has already been displayed log_tuple = (name, log_level, msg) if log_tuple in BrianLogger._log_messages: return else: BrianLogger._log_messages.add(log_tuple) the_logger = logging.getLogger(name) the_logger.log(LOG_LEVELS[log_level], msg) def diagnostic(self, msg, name_suffix=None, once=False): """ Log a diagnostic message. Parameters ---------- msg : str The message to log. name_suffix : str, optional A suffix to add to the name, e.g. a class or function name. once : bool, optional Whether this message should be logged only once and not repeated if sent another time. """ self._log("DIAGNOSTIC", msg, name_suffix, once) def debug(self, msg, name_suffix=None, once=False): """ Log a debug message. Parameters ---------- msg : str The message to log. name_suffix : str, optional A suffix to add to the name, e.g. a class or function name. once : bool, optional Whether this message should be logged only once and not repeated if sent another time. """ self._log("DEBUG", msg, name_suffix, once) def info(self, msg, name_suffix=None, once=False): """ Log an info message. Parameters ---------- msg : str The message to log. name_suffix : str, optional A suffix to add to the name, e.g. a class or function name. once : bool, optional Whether this message should be logged only once and not repeated if sent another time. """ self._log("INFO", msg, name_suffix, once) def warn(self, msg, name_suffix=None, once=False): """ Log a warn message. Parameters ---------- msg : str The message to log. name_suffix : str, optional A suffix to add to the name, e.g. a class or function name. once : bool, optional Whether this message should be logged only once and not repeated if sent another time. """ self._log("WARNING", msg, name_suffix, once) def error(self, msg, name_suffix=None, once=False): """ Log an error message. Parameters ---------- msg : str The message to log. name_suffix : str, optional A suffix to add to the name, e.g. a class or function name. once : bool, optional Whether this message should be logged only once and not repeated if sent another time. """ self._log("ERROR", msg, name_suffix, once) @staticmethod def _suppress(filterobj, filter_log_file): """ Apply a filter object to log messages. Parameters ---------- filterobj : `logging.Filter` A filter object to apply to log messages. filter_log_file : bool Whether the filter also applies to log messages in the log file. """ BrianLogger.console_handler.addFilter(filterobj) if filter_log_file: BrianLogger.file_handler.addFilter(filterobj) @staticmethod def suppress_hierarchy(name, filter_log_file=False): """ Suppress all log messages in a given hiearchy. Parameters ---------- name : str Suppress all log messages in the given `name` hierarchy. For example, specifying ``'brian2'`` suppresses all messages logged by Brian, specifying ``'brian2.codegen'`` suppresses all messages generated by the code generation modules. filter_log_file : bool, optional Whether to suppress the messages also in the log file. Defaults to ``False`` meaning that suppressed messages are not displayed on the console but are still saved to the log file. """ suppress_filter = HierarchyFilter(name) BrianLogger._suppress(suppress_filter, filter_log_file) @staticmethod def suppress_name(name, filter_log_file=False): """ Suppress all log messages with a given name. Parameters ---------- name : str Suppress all log messages ending in the given `name`. For example, specifying ``'resolution_conflict'`` would suppress messages with names such as ``brian2.equations.codestrings.CodeString.resolution_conflict`` or ``brian2.equations.equations.Equations.resolution_conflict``. filter_log_file : bool, optional Whether to suppress the messages also in the log file. Defaults to ``False`` meaning that suppressed messages are not displayed on the console but are still saved to the log file. """ suppress_filter = NameFilter(name) BrianLogger._suppress(suppress_filter, filter_log_file) @staticmethod def log_level_diagnostic(): """ Set the log level to "diagnostic". """ BrianLogger.console_handler.setLevel(DIAGNOSTIC) @staticmethod def log_level_debug(): """ Set the log level to "debug". """ BrianLogger.console_handler.setLevel(logging.DEBUG) @staticmethod def log_level_info(): """ Set the log level to "info". """ BrianLogger.console_handler.setLevel(logging.INFO) @staticmethod def log_level_warn(): """ Set the log level to "warn". """ BrianLogger.console_handler.setLevel(logging.WARN) @staticmethod def log_level_error(): """ Set the log level to "error". """ BrianLogger.console_handler.setLevel(logging.ERROR) @staticmethod def initialize(): """ Initialize Brian's logging system. This function will be called automatically when Brian is imported. """ # get the main logger logger = logging.getLogger("brian2") logger.propagate = False logger.setLevel(LOG_LEVELS["DIAGNOSTIC"]) # Log to a file if prefs["logging.file_log"]: try: # Temporary filename used for logging with tempfile.NamedTemporaryFile( prefix="brian_debug_", suffix=".log", delete=False ) as tmp_f: BrianLogger.tmp_log = tmp_f.name # Remove any previously existing file handler if BrianLogger.file_handler is not None: BrianLogger.file_handler.close() logger.removeHandler(BrianLogger.file_handler) # Rotate log file after prefs['logging.file_log_max_size'] bytes and keep one copy BrianLogger.file_handler = RotatingFileHandler( BrianLogger.tmp_log, mode="a", maxBytes=prefs["logging.file_log_max_size"], backupCount=1, encoding="utf-8", ) BrianLogger.file_handler.setLevel( LOG_LEVELS[prefs["logging.file_log_level"].upper()] ) BrianLogger.file_handler.setFormatter( logging.Formatter( "%(asctime)s %(levelname)-10s %(name)s: %(message)s" ) ) logger.addHandler(BrianLogger.file_handler) BrianLogger._pid = os.getpid() except OSError as ex: warn(f"Could not create log file: {ex}") # Save a copy of the script BrianLogger.tmp_script = None if prefs["logging.save_script"]: if ( len(sys.argv[0]) and not running_from_ipython() and os.path.isfile(sys.argv[0]) ): try: tmp_file = tempfile.NamedTemporaryFile( prefix="brian_script_", suffix=".py", delete=False ) with tmp_file: # Timestamp tmp_file.write(_encode(f"# {time.asctime()}\n")) # Command line arguments tmp_file.write(_encode(f"# Run as: {' '.join(sys.argv)}\n\n")) # The actual script file # TODO: We are copying the script file as it is, this might clash # with the encoding we used for the comments added above with open(os.path.abspath(sys.argv[0]), "rb") as script_file: shutil.copyfileobj(script_file, tmp_file) BrianLogger.tmp_script = tmp_file.name except OSError as ex: warn(f"Could not copy script file to temp directory: {ex}") if BrianLogger.console_handler is not None: logger.removeHandler(BrianLogger.console_handler) # create console handler with a higher log level BrianLogger.console_handler = logging.StreamHandler() BrianLogger.console_handler.setLevel( LOG_LEVELS[prefs["logging.console_log_level"]] ) BrianLogger.console_handler.setFormatter( logging.Formatter("%(levelname)-10s %(message)s [%(name)s]") ) # add the handler to the logger logger.addHandler(BrianLogger.console_handler) # We want to log all warnings logging.captureWarnings(True) # pylint: disable=E1101 # Manually connect to the warnings logger so that the warnings end up in # the log file. Note that connecting to the console handler here means # duplicated warning messages in the ipython notebook, but not doing so # would mean that they are not displayed at all in the standard ipython # interface... warn_logger = logging.getLogger("py.warnings") warn_logger.addHandler(BrianLogger.console_handler) if BrianLogger.file_handler is not None: warn_logger.addHandler(BrianLogger.file_handler) # Put some standard info into the log file logger.log( logging.DEBUG, f"Logging to file: {BrianLogger.tmp_log}, copy of main script saved as:" f" {BrianLogger.tmp_script}", ) logger.log(logging.DEBUG, f"Python interpreter: {sys.executable}") logger.log(logging.DEBUG, f"Platform: {sys.platform}") version_infos = { "brian": brian2.__version__, "numpy": numpy.__version__, "scipy": scipy.__version__ if scipy else "not installed", "sympy": sympy.__version__, "python": sys.version, } for _name, _version in version_infos.items(): logger.log(logging.DEBUG, f"{_name} version is: {str(_version)}") # Handle uncaught exceptions sys.excepthook = brian_excepthook def get_logger(module_name="brian2"): """ Get an object that can be used for logging. Parameters ---------- module_name : str The name used for logging, should normally be the module name as returned by ``__name__``. Returns ------- logger : `BrianLogger` """ return BrianLogger(module_name) class catch_logs: """ A context manager for catching log messages. Use this for testing the messages that are logged. Defaults to catching warning/error messages and this is probably the only real use case for testing. Note that while this context manager is active, *all* log messages are suppressed. Using this context manager returns a list of (log level, name, message) tuples. Parameters ---------- log_level : int or str, optional The log level above which messages are caught. Examples -------- >>> logger = get_logger('brian2.logtest') >>> logger.warn('An uncaught warning') # doctest: +SKIP WARNING brian2.logtest: An uncaught warning >>> with catch_logs() as l: ... logger.warn('a caught warning') ... print('l contains: %s' % l) ... l contains: [('WARNING', 'brian2.logtest', 'a caught warning')] """ _entered = False def __init__(self, log_level=logging.WARN): self.log_list = [] self.handler = LogCapture(self.log_list, log_level) self._entered = False def __enter__(self): if self._entered: raise RuntimeError(f"Cannot enter {self!r} twice") self._entered = True return self.log_list def __exit__(self, *exc_info): if not self._entered: raise RuntimeError(f"Cannot exit {self!r} without entering first") self.handler.uninstall() class LogCapture(logging.Handler): """ A class for capturing log warnings. This class is used by `~brian2.utils.logger.catch_logs` to allow testing in a similar way as with `warnings.catch_warnings`. """ captured_loggers = ["brian2", "py.warnings"] def __init__(self, log_list, log_level=logging.WARN): logging.Handler.__init__(self, level=log_level) self.log_list = log_list # make a copy of the previous handlers self.handlers = {} for logger_name in LogCapture.captured_loggers: self.handlers[logger_name] = list(logging.getLogger(logger_name).handlers) self.install() def emit(self, record): # Append a tuple consisting of (level, name, msg) to the list of # warnings self.log_list.append((record.levelname, record.name, record.msg)) def install(self): """ Install this handler to catch all warnings. Temporarily disconnect all other handlers. """ for logger_name in LogCapture.captured_loggers: the_logger = logging.getLogger(logger_name) for handler in self.handlers[logger_name]: the_logger.removeHandler(handler) the_logger.addHandler(self) def uninstall(self): """ Uninstall this handler and re-connect the previously installed handlers. """ for logger_name in LogCapture.captured_loggers: the_logger = logging.getLogger(logger_name) for handler in self.handlers[logger_name]: the_logger.addHandler(handler) # See http://stackoverflow.com/questions/26126160/redirecting-standard-out-in-err-back-after-os-dup2 # for an explanation of how this function works. Note that 1 and 2 are the file # numbers for stdout and stderr class std_silent: """ Context manager that temporarily silences stdout and stderr but keeps the output saved in a temporary file and writes it if an exception is raised. """ dest_stdout = None dest_stderr = None def __init__(self, alwaysprint=False): self.alwaysprint = alwaysprint or not prefs["logging.std_redirection"] self.redirect_to_file = prefs["logging.std_redirection_to_file"] if ( not self.alwaysprint and self.redirect_to_file and std_silent.dest_stdout is None ): std_silent.dest_fname_stdout = tempfile.NamedTemporaryFile( prefix="brian_stdout_", suffix=".log", delete=False ).name std_silent.dest_fname_stderr = tempfile.NamedTemporaryFile( prefix="brian_stderr_", suffix=".log", delete=False ).name std_silent.dest_stdout = open(std_silent.dest_fname_stdout, "w") std_silent.dest_stderr = open(std_silent.dest_fname_stderr, "w") def __enter__(self): if not self.alwaysprint and self.redirect_to_file: sys.stdout.flush() sys.stderr.flush() self.orig_out_fd = os.dup(1) self.orig_err_fd = os.dup(2) os.dup2(std_silent.dest_stdout.fileno(), 1) os.dup2(std_silent.dest_stderr.fileno(), 2) def __exit__(self, exc_type, exc_value, traceback): if not self.alwaysprint and self.redirect_to_file: std_silent.dest_stdout.flush() std_silent.dest_stderr.flush() if exc_type is not None: with open(std_silent.dest_fname_stdout) as f: out = f.read() with open(std_silent.dest_fname_stderr) as f: err = f.read() os.dup2(self.orig_out_fd, 1) os.dup2(self.orig_err_fd, 2) os.close(self.orig_out_fd) os.close(self.orig_err_fd) if exc_type is not None: sys.stdout.write(out) sys.stderr.write(err) @classmethod def close(cls): if std_silent.dest_stdout is not None: std_silent.dest_stdout.close() if prefs["logging.delete_log_on_exit"]: try: os.remove(std_silent.dest_fname_stdout) except OSError: # TODO: this happens quite frequently - why? # The file objects are closed as far as Python is concerned, # but maybe Windows is still hanging on to them? pass if std_silent.dest_stderr is not None: std_silent.dest_stderr.close() if prefs["logging.delete_log_on_exit"]: try: os.remove(std_silent.dest_fname_stderr) except OSError: pass brian2-2.5.4/brian2/utils/stringtools.py000066400000000000000000000217131445201106100201530ustar00rootroot00000000000000""" A collection of tools for string formatting tasks. """ import re import string __all__ = [ "indent", "deindent", "word_substitute", "replace", "get_identifiers", "strip_empty_lines", "stripped_deindented_lines", "strip_empty_leading_and_trailing_lines", "code_representation", "SpellChecker", ] def indent(text, numtabs=1, spacespertab=4, tab=None): """ Indents a given multiline string. By default, indentation is done using spaces rather than tab characters. To use tab characters, specify the tab character explictly, e.g.:: indent(text, tab='\t') Note that in this case ``spacespertab`` is ignored. Examples -------- >>> multiline = '''def f(x): ... return x*x''' >>> print(multiline) def f(x): return x*x >>> print(indent(multiline)) def f(x): return x*x >>> print(indent(multiline, numtabs=2)) def f(x): return x*x >>> print(indent(multiline, spacespertab=2)) def f(x): return x*x >>> print(indent(multiline, tab='####')) ####def f(x): #### return x*x """ if tab is None: tab = " " * spacespertab indent = tab * numtabs indentedstring = indent + text.replace("\n", f"\n{indent}") return indentedstring def deindent(text, numtabs=None, spacespertab=4, docstring=False): """ Returns a copy of the string with the common indentation removed. Note that all tab characters are replaced with ``spacespertab`` spaces. If the ``docstring`` flag is set, the first line is treated differently and is assumed to be already correctly tabulated. If the ``numtabs`` option is given, the amount of indentation to remove is given explicitly and not the common indentation. Examples -------- Normal strings, e.g. function definitions: >>> multiline = ''' def f(x): ... return x**2''' >>> print(multiline) def f(x): return x**2 >>> print(deindent(multiline)) def f(x): return x**2 >>> print(deindent(multiline, docstring=True)) def f(x): return x**2 >>> print(deindent(multiline, numtabs=1, spacespertab=2)) def f(x): return x**2 Docstrings: >>> docstring = '''First docstring line. ... This line determines the indentation.''' >>> print(docstring) First docstring line. This line determines the indentation. >>> print(deindent(docstring, docstring=True)) First docstring line. This line determines the indentation. """ text = text.replace("\t", " " * spacespertab) lines = text.split("\n") # if it's a docstring, we search for the common tabulation starting from # line 1, otherwise we use all lines if docstring: start = 1 else: start = 0 if docstring and len(lines) < 2: # nothing to do return text # Find the minimum indentation level if numtabs is not None: indentlevel = numtabs * spacespertab else: lineseq = [ len(line) - len(line.lstrip()) for line in lines[start:] if len(line.strip()) ] if len(lineseq) == 0: indentlevel = 0 else: indentlevel = min(lineseq) # remove the common indentation lines[start:] = [line[indentlevel:] for line in lines[start:]] return "\n".join(lines) def word_substitute(expr, substitutions): """ Applies a dict of word substitutions. The dict ``substitutions`` consists of pairs ``(word, rep)`` where each word ``word`` appearing in ``expr`` is replaced by ``rep``. Here a 'word' means anything matching the regexp ``\\bword\\b``. Examples -------- >>> expr = 'a*_b+c5+8+f(A)' >>> print(word_substitute(expr, {'a':'banana', 'f':'func'})) banana*_b+c5+8+func(A) """ for var, replace_var in substitutions.items(): expr = re.sub(f"\\b{var}\\b", str(replace_var), expr) return expr def replace(s, substitutions): """ Applies a dictionary of substitutions. Simpler than `word_substitute`, it does not attempt to only replace words """ for before, after in substitutions.items(): s = s.replace(before, after) return s KEYWORDS = {"and", "or", "not", "True", "False"} def get_identifiers(expr, include_numbers=False): """ Return all the identifiers in a given string ``expr``, that is everything that matches a programming language variable like expression, which is here implemented as the regexp ``\\b[A-Za-z_][A-Za-z0-9_]*\\b``. Parameters ---------- expr : str The string to analyze include_numbers : bool, optional Whether to include number literals in the output. Defaults to ``False``. Returns ------- identifiers : set A set of all the identifiers (and, optionally, numbers) in `expr`. Examples -------- >>> expr = '3-a*_b+c5+8+f(A - .3e-10, tau_2)*17' >>> ids = get_identifiers(expr) >>> print(sorted(list(ids))) ['A', '_b', 'a', 'c5', 'f', 'tau_2'] >>> ids = get_identifiers(expr, include_numbers=True) >>> print(sorted(list(ids))) ['.3e-10', '17', '3', '8', 'A', '_b', 'a', 'c5', 'f', 'tau_2'] """ identifiers = set(re.findall(r"\b[A-Za-z_][A-Za-z0-9_]*\b", expr)) if include_numbers: # only the number, not a + or - numbers = set( re.findall( r"(?<=[^A-Za-z_])[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?|^[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?", expr, ) ) else: numbers = set() return (identifiers - KEYWORDS) | numbers def strip_empty_lines(s): """ Removes all empty lines from the multi-line string `s`. Examples -------- >>> multiline = '''A string with ... ... an empty line.''' >>> print(strip_empty_lines(multiline)) A string with an empty line. """ return "\n".join(line for line in s.split("\n") if line.strip()) def strip_empty_leading_and_trailing_lines(s): """ Removes all empty leading and trailing lines in the multi-line string `s`. """ lines = s.split("\n") while lines and not lines[0].strip(): del lines[0] while lines and not lines[-1].strip(): del lines[-1] return "\n".join(lines) def stripped_deindented_lines(code): """ Returns a list of the lines in a multi-line string, deindented. """ code = deindent(code) code = strip_empty_lines(code) lines = code.split("\n") return lines def code_representation(code): """ Returns a string representation for several different formats of code Formats covered include: - A single string - A list of statements/strings - A dict of strings - A dict of lists of statements/strings """ if not isinstance(code, (str, list, tuple, dict)): code = str(code) if isinstance(code, str): return strip_empty_leading_and_trailing_lines(code) if not isinstance(code, dict): code = {None: code} else: code = code.copy() for k, v in code.items(): if isinstance(v, (list, tuple)): v = "\n".join([str(line) for line in v]) code[k] = v if len(code) == 1 and list(code.keys())[0] is None: return strip_empty_leading_and_trailing_lines(list(code.values())[0]) output = [] for k, v in code.items(): msg = f"Key {k}:\n" msg += indent(str(v)) output.append(msg) return strip_empty_leading_and_trailing_lines("\n".join(output)) # The below is adapted from Peter Norvig's spelling corrector # http://norvig.com/spell.py (MIT licensed) class SpellChecker: """ A simple spell checker that will be used to suggest the correct name if the user made a typo (e.g. for state variable names). Parameters ---------- words : iterable of str The known words alphabet : iterable of str, optional The allowed characters. Defaults to the characters allowed for identifiers, i.e. ascii characters, digits and the underscore. """ def __init__(self, words, alphabet=f"{string.ascii_lowercase + string.digits}_"): self.words = words self.alphabet = alphabet def edits1(self, word): s = [(word[:i], word[i:]) for i in range(len(word) + 1)] deletes = [a + b[1:] for a, b in s if b] transposes = [a + b[1] + b[0] + b[2:] for a, b in s if len(b) > 1] replaces = [a + c + b[1:] for a, b in s for c in self.alphabet if b] inserts = [a + c + b for a, b in s for c in self.alphabet] return set(deletes + transposes + replaces + inserts) def known_edits2(self, word): return { e2 for e1 in self.edits1(word) for e2 in self.edits1(e1) if e2 in self.words } def known(self, words): return {w for w in words if w in self.words} def suggest(self, word): return self.known(self.edits1(word)) or self.known_edits2(word) or set() brian2-2.5.4/brian2/utils/topsort.py000066400000000000000000000004771445201106100173020ustar00rootroot00000000000000from graphlib import TopologicalSorter __all__ = ["topsort"] def topsort(graph): """ Topologically sort a graph The graph should be of the form ``{node: [list of nodes], ...}``. Uses `graphlib.TopologicalSorter`. """ sorter = TopologicalSorter(graph) return list(sorter.static_order()) brian2-2.5.4/dev/000077500000000000000000000000001445201106100134475ustar00rootroot00000000000000brian2-2.5.4/dev/benchmarks/000077500000000000000000000000001445201106100155645ustar00rootroot00000000000000brian2-2.5.4/dev/benchmarks/.gitignore000066400000000000000000000000101445201106100175430ustar00rootroot00000000000000joblib/ brian2-2.5.4/dev/benchmarks/benchmark_spikequeue.py000066400000000000000000000032271445201106100223340ustar00rootroot00000000000000import timeit import itertools import numpy as np GENERAL_SETUP = ['import numpy as np', 'from brian2.tests.test_spikequeue import create_all_to_all, create_one_to_one', 'from brian2.units.stdunits import ms', 'from brian2.synapses.spikequeue import SpikeQueue'] def get_setup_code(N, create_func): return GENERAL_SETUP + [ 'synapses, delays = {}({})'.format(create_func, N), 'queue = SpikeQueue(synapses, delays, 0.1*ms)'] def test_compress(N, create_func): setup_code = get_setup_code(N, create_func) number = 1000/N results = timeit.repeat('queue.compress()', ';'.join(setup_code), repeat=5, number=number) return np.array(results) / number def test_push(N, create_func): setup_code = get_setup_code(N, create_func) + ['queue.compress()'] number = 5000/N results = timeit.repeat('queue.push(np.arange({}));queue.next()'.format(N), ';'.join(setup_code), repeat=5, number=number) return np.array(results) / number def run_benchmark(test_func, N, create_func): result = test_func(N, create_func) print('{} -- {}({}) : {}'.format(test_func.__name__, create_func, N, np.median(result))) if __name__ == '__main__': for test, N, create_func in itertools.product((test_compress, test_push), (10, 100), ('create_all_to_all', 'create_one_to_one')): run_benchmark(test, N, create_func) brian2-2.5.4/dev/benchmarks/compare_to_brian1.py000066400000000000000000000134621445201106100215300ustar00rootroot00000000000000import time from processify import processify def halfway_timer(): global halftime halftime = time.time() @processify def brian1_CUBA(N, duration=1, do_refractory=False, exact_method=False, do_monitor=False, do_synapses=False, **ignored_opts): global halftime import time results = dict() start_time = time.time() from brian import * finished_importing_brian_time = time.time() results['import_brian'] = finished_importing_brian_time-start_time duration *= second Ne = int(0.8*N) Ni = N-Ne taum = 20 * ms taue = 5 * ms taui = 10 * ms Vt = -50 * mV Vr = -60 * mV El = -49 * mV eqs = ''' dv/dt = (ge+gi-(v-El))/taum : volt dge/dt = -ge/taue : volt dgi/dt = -gi/taui : volt ''' kwds = {} if do_refractory: kwds['refractory'] = 5*ms if not exact_method: kwds['method'] = 'Euler' P = NeuronGroup(N, model=eqs, threshold=Vt, reset=Vr, **kwds) P.v = Vr P.ge = 0 * mV P.gi = 0 * mV Pe = P.subgroup(Ne) Pi = P.subgroup(Ni) we = (60 * 0.27 / 10) * mV # excitatory synaptic weight (voltage) wi = (-20 * 4.5 / 10) * mV # inhibitory synaptic weight p = min(80./N, 1) if do_synapses: Ce = Connection(Pe, P, 'ge', weight=we, sparseness=p) Ci = Connection(Pi, P, 'gi', weight=wi, sparseness=p) P.v = Vr + rand(len(P)) * (Vt - Vr) if do_monitor: M = SpikeMonitor(P) netop_halfway_timer = network_operation(clock=EventClock(dt=duration*0.51))(halfway_timer) objects_created_time = time.time() results['object_creation'] = objects_created_time-finished_importing_brian_time run(1 * msecond) initial_run_time = time.time() results['initial_run'] = initial_run_time-objects_created_time run(duration) main_run_time = time.time() results['main_run'] = main_run_time-initial_run_time results['second_half_main_run'] = main_run_time-halftime results['total'] = main_run_time-start_time return results @processify def brian2_CUBA(N, duration=1, do_refractory=False, exact_method=False, do_monitor=False, do_synapses=False, codegen_target='numpy', **ignored_opts): global halftime import time results = dict() start_time = time.time() from brian2 import * prefs.codegen.target = codegen_target finished_importing_brian_time = time.time() results['import_brian'] = finished_importing_brian_time-start_time duration *= second Ne = int(0.8*N) Ni = N-Ne taum = 20 * ms taue = 5 * ms taui = 10 * ms Vt = -50 * mV Vr = -60 * mV El = -49 * mV eqs = ''' dv/dt = (ge+gi-(v-El))/taum : volt (unless refractory) dge/dt = -ge/taue : volt dgi/dt = -gi/taui : volt ''' if not do_refractory: eqs = eqs.replace('(unless refractory)', '') kwds = {} if do_refractory: kwds['refractory'] = 5*ms if not exact_method: kwds['method'] = 'euler' P = NeuronGroup(N, model=eqs, threshold='v>Vt', reset='v=Vr', **kwds) P.v = Vr P.ge = 0 * mV P.gi = 0 * mV we = (60 * 0.27 / 10) * mV # excitatory synaptic weight (voltage) wi = (-20 * 4.5 / 10) * mV # inhibitory synaptic weight p = min(80./N, 1.0) if do_synapses: Ce = Synapses(P, P, on_pre='ge += we') Ci = Synapses(P, P, on_pre='gi += wi') Ce.connect('i=Ne', p=p) P.v = Vr + rand(len(P)) * (Vt - Vr) if do_monitor: M = SpikeMonitor(P) netop_halfway_timer = NetworkOperation(halfway_timer, dt=duration*0.51) objects_created_time = time.time() results['object_creation'] = objects_created_time-finished_importing_brian_time run(1 * msecond) initial_run_time = time.time() results['initial_run'] = initial_run_time-objects_created_time run(duration) main_run_time = time.time() results['main_run'] = main_run_time-initial_run_time results['second_half_main_run'] = main_run_time-halftime results['total'] = main_run_time-start_time return results def brian2_CUBA_cython(*args, **opts): opts['codegen_target'] = 'cython' return brian2_CUBA(*args, **opts) if __name__=='__main__': import functools from pylab import * numfigs = 0 funcs = [ brian1_CUBA, brian2_CUBA, brian2_CUBA_cython, ] options = dict(duration=1, do_monitor=False, do_refractory=False, do_synapses=False, exact_method=False, ) N = [1, 10, 100, 1000, 10000, #100000, ] for name in options.keys()+['']: #for name in ['']: if name: if not isinstance(options[name], bool): continue options[name] = True figure(figsize=(16, 8)) for func in funcs: pfunc = functools.partial(func, **options) all_results = map(pfunc, N) times = collections.defaultdict(list) for res in all_results: for k, v in res.items(): times[k].append(v) for i, k in enumerate(sorted(times.keys())): subplot(2, 3, i+1) if i>numfigs: numfigs = i title(k) v = times[k] loglog(N[1:], v[1:], label=func.__name__) for i in range(numfigs+1): subplot(2, 3, i+1) legend(loc='best') suptitle(', '.join('%s=%s' % (k, options[k]) for k in sorted(options.keys()))) tight_layout() subplots_adjust(top=0.9) if name: savefig('compare_to_brian1.%s.png'%name) options[name] = False else: savefig('compare_to_brian1.png') show() brian2-2.5.4/dev/benchmarks/openmp/000077500000000000000000000000001445201106100170625ustar00rootroot00000000000000brian2-2.5.4/dev/benchmarks/openmp/CUBA_standalone.py000066400000000000000000000036601445201106100223630ustar00rootroot00000000000000#!/usr/bin/env python # coding: latin-1 """ CUBA example with delays. """ import sys, time, os from brian2 import * standalone = int(sys.argv[-2]) n_threads = int(sys.argv[-1]) path = 'data_cuba_%d' %n_threads if standalone == 1: set_device('cpp_standalone') brian_prefs.codegen.cpp_standalone.openmp_threads = n_threads start = time.time() n_cells = 20000 n_exc = int(0.8*n_cells) p_conn = 0.1 taum = 20 * ms taue = 5 * ms taui = 10 * ms Vt = -50 * mV Vr = -60 * mV El = -49 * mV eqs = Equations(''' dv/dt = (ge+gi-(v-El))/taum : volt dge/dt = -ge/taue : volt dgi/dt = -gi/taui : volt ''') P = NeuronGroup(n_cells, model=eqs, threshold='v>Vt', reset='v=Vr', refractory=5 * ms) P.v = Vr + rand(len(P)) * (Vt - Vr) P.ge = 0 * mV P.gi = 0 * mV Pe = P[0:n_exc] Pi = P[n_exc:] we = (60 * 0.27 / 10) # excitatory synaptic weight (voltage) wi = (-20 * 4.5 / 10) # inhibitory synaptic weight Se = Synapses(Pe, P, model = 'w : 1', pre = 'ge += w*mV') Se.connect('i != j', p=p_conn) Se.w = '%g' %(we) Se.delay ='rand()*ms' Si = Synapses(Pi, P, model = 'w : 1', pre = 'gi += w*mV') Si.connect('i != j', p=p_conn) Si.w = '%g' %(wi) Si.delay ='rand()*ms' spike_mon = SpikeMonitor(P) net = Network(P, Se, Si, spike_mon, name='stdp_net') if standalone == 1: device.insert_code('main', 'std::clock_t start = std::clock();') net.run(10 * second, report='text') if standalone == 1: device.insert_code('main', ''' std::ofstream myfile ("speed.txt"); if (myfile.is_open()) { double value = (double) (std::clock() - start)/(%d * CLOCKS_PER_SEC); myfile << value << std::endl; myfile.close(); } ''' %(max(1, n_threads))) try: os.removedirs(path) except Exception: pass if standalone == 1: device.build(project_dir=path, compile_project=True, run_project=True, debug=False) brian2-2.5.4/dev/benchmarks/openmp/STDP_standalone.py000066400000000000000000000044661445201106100224300ustar00rootroot00000000000000#!/usr/bin/env python ''' Spike-timing dependent plasticity Adapted from Song, Miller and Abbott (2000) and Song and Abbott (2001) This example is modified from ``synapses_STDP.py`` and writes a standalone C++ project in the directory ``STDP_standalone``. ''' import sys, time, os from brian2 import * standalone = int(sys.argv[-2]) n_threads = int(sys.argv[-1]) path = 'data_stdp_%d' %n_threads if standalone == 1: set_device('cpp_standalone') brian_prefs.codegen.cpp_standalone.openmp_threads = n_threads start = time.time() N = 1000 taum = 10 * ms taupre = 20 * ms taupost = taupre Ee = 0 * mV vt = -54 * mV vr = -60 * mV El = -74 * mV taue = 5 * ms F = 30 * Hz gmax = .01 dApre = .01 dApost = -dApre * taupre / taupost * 1.05 dApost *= gmax dApre *= gmax eqs_neurons = ''' dv/dt=(ge*(Ee-vr)+El-v)/taum : volt # the synaptic current is linearized dge/dt=-ge/taue : 1 ''' poisson_input = PoissonGroup(N, rates=F) neurons = NeuronGroup(500, eqs_neurons, threshold='v>vt', reset='v=vr') S = Synapses(poisson_input, neurons, '''w:1 dApre/dt=-Apre/taupre : 1 (event-driven) dApost/dt=-Apost/taupost : 1 (event-driven)''', on_pre='''ge+=w Apre+=dApre w=clip(w+Apost,0,gmax)''', on_post='''Apost+=dApost w=clip(w+Apre,0,gmax)''') S.connect() S.w = 'rand()*gmax' state_mon = StateMonitor(S, 'w', record=[0]) spike_mon_1 = SpikeMonitor(poisson_input) spike_mon_2 = SpikeMonitor(neurons) start_time = time.time() net = Network(poisson_input, neurons, S, state_mon, spike_mon_1, spike_mon_2, name='stdp_net') if standalone == 1: device.insert_code('main', 'std::clock_t start = std::clock();') net.run(5 * second, report='text') if standalone == 1: device.insert_code('main', ''' std::ofstream myfile ("speed.txt"); if (myfile.is_open()) { double value = (double) (std::clock() - start)/(%d * CLOCKS_PER_SEC); myfile << value << std::endl; myfile.close(); } ''' %(max(1, n_threads))) try: os.removedirs(path) except Exception: pass if standalone == 1: device.build(project_dir=path, compile_project=True, run_project=True, debug=False) brian2-2.5.4/dev/benchmarks/openmp/example_standalone.py000066400000000000000000000047411445201106100233050ustar00rootroot00000000000000#!/usr/bin/env python # coding: latin-1 """ CUBA example with delays. """ import sys, time, os from brian2 import * standalone = int(sys.argv[-2]) n_threads = int(sys.argv[-1]) path = 'data_example_%d' %n_threads if standalone == 1: set_device('cpp_standalone') brian_prefs.codegen.cpp_standalone.openmp_threads = n_threads start = time.time() n_cells = 1000 numpy.random.seed(42) connectivity = numpy.random.randn(n_cells, n_cells) taum = 20 * ms taus = 5 * ms Vt = -50 * mV Vr = -60 * mV El = -49 * mV fac = (60 * 0.27 / 10) # excitatory synaptic weight (voltage) gmax = 20*fac dApre = .01 taupre = 20 * ms taupost = taupre dApost = -dApre * taupre / taupost * 1.05 dApost *= 0.1*gmax dApre *= 0.1*gmax eqs = Equations(''' dv/dt = (g-(v-El))/taum : volt g : volt ''') P = NeuronGroup(n_cells, model=eqs, threshold='v>Vt', reset='v=Vr', refractory=5 * ms) P.v = Vr + numpy.random.rand(len(P)) * (Vt - Vr) P.g = 0 * mV S = Synapses(P, P, model = '''dApre/dt=-Apre/taupre : 1 (event-driven) dApost/dt=-Apost/taupost : 1 (event-driven) w : 1 dg/dt = -g/taus : volt g_post = g : volt (summed)''', pre = '''g += w*mV Apre += dApre w = w + Apost''', post = '''Apost += dApost w = w + Apre''') S.connect() S.w = fac*connectivity.flatten() spike_mon = SpikeMonitor(P) state_mon = StateMonitor(S, 'w', record=range(10), when=Clock(dt=0.1*second)) v_mon = StateMonitor(P, 'v', record=range(10)) if standalone == 1: device.insert_code('main', 'std::clock_t start = std::clock();') run(5 * second, report='text') if standalone == 1: device.insert_code('main', ''' std::ofstream myfile ("speed.txt"); if (myfile.is_open()) { double value = (double) (std::clock() - start)/(%d * CLOCKS_PER_SEC); myfile << value << std::endl; myfile.close(); } ''' %(max(1, n_threads))) try: os.removedirs(path) except Exception: pass if standalone == 1: device.build(project_dir='data_example_%d' %n_threads, compile_project=True, run_project=True, debug=False) brian2-2.5.4/dev/benchmarks/openmp/example_standalone_bis.py000066400000000000000000000045051445201106100241400ustar00rootroot00000000000000#!/usr/bin/env python # coding: latin-1 """ CUBA example with delays. """ import sys, time, os from brian2 import * standalone = int(sys.argv[-2]) n_threads = int(sys.argv[-1]) path = 'data_example_bis_%d' %n_threads if standalone == 1: set_device('cpp_standalone') brian_prefs.codegen.cpp_standalone.openmp_threads = n_threads start = time.time() n_cells = 1000 numpy.random.seed(42) connectivity = numpy.random.randn(n_cells, n_cells) taum = 20 * ms taus = 5 * ms Vt = -50 * mV Vr = -60 * mV El = -49 * mV fac = (60 * 0.27 / 10) # excitatory synaptic weight (voltage) gmax = 20*fac dApre = .01 taupre = 20 * ms taupost = taupre dApost = -dApre * taupre / taupost * 1.05 dApost *= 0.1*gmax dApre *= 0.1*gmax eqs = Equations(''' dv/dt = (g-(v-El))/taum : volt dg/dt = -g/taus : volt ''') P = NeuronGroup(n_cells, model=eqs, threshold='v>Vt', reset='v=Vr', refractory=5 * ms) P.v = Vr + numpy.random.rand(len(P)) * (Vt - Vr) P.g = 0 * mV S = Synapses(P, P, model = '''dApre/dt=-Apre/taupre : 1 (event-driven) dApost/dt=-Apost/taupost : 1 (event-driven) w : 1''', pre = '''g += w*mV Apre += dApre w = w + Apost''', post = '''Apost += dApost w = w + Apre''') S.connect() S.w = fac*connectivity.flatten() spike_mon = SpikeMonitor(P) state_mon = StateMonitor(S, 'w', record=range(10), when=Clock(dt=0.1*second)) v_mon = StateMonitor(P, 'v', record=range(10)) if standalone == 1: device.insert_code('main', 'std::clock_t start = std::clock();') run(5 * second, report='text') if standalone == 1: device.insert_code('main', ''' std::ofstream myfile ("speed.txt"); if (myfile.is_open()) { double value = (double) (std::clock() - start)/(%d * CLOCKS_PER_SEC); myfile << value << std::endl; myfile.close(); } ''' %(max(1, n_threads))) try: os.removedirs(path) except Exception: pass if standalone == 1: device.build(project_dir=path, compile_project=True, run_project=True, debug=False) brian2-2.5.4/dev/benchmarks/openmp/test_openmp_1.py000066400000000000000000000034601445201106100222140ustar00rootroot00000000000000import sys, os, numpy, time, pylab filename = 'CUBA_standalone.py' datapath = 'data_cuba' threads = [0, 1, 2, 4, 6] results = {} results['duration'] = [] for t in threads: start = time.time() os.system('python %s 1 %d' %(filename, t)) with open('%s_%d/speed.txt' %(datapath, t), 'r') as f: results['duration'] += [float(f.read())] for t in threads: results[t] = {} path = datapath + '_%d/' %t ids = numpy.fromfile(path+'results/_dynamic_array_spikemonitor_i', dtype=numpy.int32) times = numpy.fromfile(path+'results/_dynamic_array_spikemonitor_t', dtype=numpy.float64) vms = numpy.fromfile(path+'results/_array_neurongroup_v', dtype=numpy.float64) w = numpy.fromfile(path+'results/_dynamic_array_synapses_1_w', dtype=numpy.float64) results[t]['spikes'] = (times, ids) results[t]['vms'] = vms results[t]['w'] = w pylab.figure() pylab.subplot(221) pylab.title('Raster plots') pylab.xlabel('Time [s]') pylab.ylabel('# cell') for t in threads: pylab.plot(results[t]['spikes'][0], results[t]['spikes'][1], '.') pylab.legend([str(t) for t in threads]) pylab.xlim(0.5, 0.55) pylab.subplot(222) pylab.title('Mean Rates') pylab.xlabel('Time [s]') pylab.ylabel('Rate [Hz]') for t in threads: x, y = numpy.histogram(results[t]['spikes'][0], 100) pylab.plot(y[1:], x) pylab.legend([str(t) for t in threads]) pylab.subplot(223) pylab.title('Network') pylab.xlabel('# threads') pylab.ylabel('Number of synapses') r = [] for t in threads: r += [len(results[t]['w'])] pylab.bar(threads, r) pylab.ylim(min(r)-1000, max(r)+1000) pylab.subplot(224) pylab.title('Speed') pylab.plot(threads, results['duration']) pylab.xlabel('# threads') pylab.ylabel('Time [s]') pylab.tight_layout() pylab.savefig('cuba_openmp.png') pylab.show() brian2-2.5.4/dev/benchmarks/openmp/test_openmp_2.py000066400000000000000000000037471445201106100222250ustar00rootroot00000000000000import sys, os, numpy, time, pylab filename = 'STDP_standalone.py' datapath = 'data_stdp' threads = [0, 1, 2, 4, 6] results = {} results['duration'] = [] for t in threads: start = time.time() os.system('python %s 1 %d' %(filename, t)) with open('%s_%d/speed.txt' %(datapath, t), 'r') as f: results['duration'] += [float(f.read())] results[t] = {} for t in threads: results[t] = {} path = datapath + '_%d/' %t ids = numpy.fromfile(path+'results/_dynamic_array_spikemonitor_i', dtype=numpy.int32) times = numpy.fromfile(path+'results/_dynamic_array_spikemonitor_t', dtype=numpy.float64) w = numpy.fromfile(path+'results/_dynamic_array_synapses_w', dtype=numpy.float64) times_2 = numpy.fromfile(path+'results/_dynamic_array_statemonitor_t', dtype=numpy.float64) w_over_time = numpy.fromfile(path+'results/_dynamic_array_statemonitor__recorded_w', dtype=numpy.float64) results[t]['spikes'] = (times, ids) results[t]['w'] = w results[t]['trace_w'] = (times_2, w_over_time) pylab.figure() pylab.subplot(221) pylab.title('Raster plots') pylab.xlabel('Time [s]') pylab.ylabel('# cell') for t in threads: pylab.plot(results[t]['spikes'][0], results[t]['spikes'][1], '.') pylab.legend([str(t) for t in threads]) pylab.xlim(0.5, 0.55) pylab.subplot(222) pylab.title('Weight Evolution') pylab.xlabel('Time [s]') pylab.ylabel('Weight [ns]') for t in threads: pylab.plot(results[t]['trace_w'][0], results[t]['trace_w'][1]) pylab.legend([str(t) for t in threads]) pylab.subplot(223) pylab.title('Final Distribution') pylab.xlabel('Weight [ns]') pylab.ylabel('Number of synapses') for t in threads: x, y = numpy.histogram(results[t]['w'], 100) pylab.plot(y[1:], x) pylab.legend([str(t) for t in threads]) pylab.subplot(224) pylab.title('Speed') pylab.plot(threads, results['duration']) pylab.xlabel('# threads') pylab.ylabel('Time [s]') pylab.tight_layout() pylab.savefig('STDP_openmp.png') pylab.show() brian2-2.5.4/dev/benchmarks/openmp/test_openmp_3.py000066400000000000000000000056041445201106100222200ustar00rootroot00000000000000import sys, os, numpy, time, pylab filename = 'example_standalone.py' datapath = 'data_example' threads = [0, 1, 2, 4, 6] results = {} results['duration'] = [] for t in threads: start = time.time() os.system('python %s 1 %d' %(filename, t)) with open('%s_%d/speed.txt' %(datapath, t), 'r') as f: results['duration'] += [float(f.read())] results[t] = {} for t in threads: results[t] = {} path = datapath + '_%d/' %t ids = numpy.fromfile(path+'results/_dynamic_array_spikemonitor_i', dtype=numpy.int32) times = numpy.fromfile(path+'results/_dynamic_array_spikemonitor_t', dtype=numpy.float64) w = numpy.fromfile(path+'results/_dynamic_array_synapses_w', dtype=numpy.float64) times_w = numpy.fromfile(path+'results/_dynamic_array_statemonitor_t', dtype=numpy.float64) w_over_time = numpy.fromfile(path+'results/_dynamic_array_statemonitor__recorded_w', dtype=numpy.float64) v_over_time = numpy.fromfile(path+'results/_dynamic_array_statemonitor_1__recorded_v', dtype=numpy.float64) times_v = numpy.fromfile(path+'results/_dynamic_array_statemonitor_1_t', dtype=numpy.float64) results[t]['spikes'] = (times, ids) results[t]['w'] = w results[t]['trace_w'] = w_over_time.reshape(len(times_w), len(w_over_time)/len(times_w)) results[t]['trace_v'] = v_over_time.reshape(len(times_v), len(v_over_time)/len(times_v)) results['colors'] = ['b', 'g', 'r', 'c', 'k'] pylab.figure() pylab.subplot(321) pylab.title('Raster plots') pylab.xlabel('Time [s]') pylab.ylabel('# cell') for t in threads: pylab.plot(results[t]['spikes'][0], results[t]['spikes'][1], '.') pylab.legend([str(t) for t in threads]) #pylab.xlim(0.5, 0.6) pylab.subplot(322) pylab.title('Final Distribution') pylab.xlabel('Weight [ns]') pylab.ylabel('Number of synapses') for t in threads: x, y = numpy.histogram(results[t]['w'], 100) pylab.plot(y[1:], x) pylab.legend([str(t) for t in threads]) pylab.subplot(323) pylab.title('Weight Evolution') pylab.xlabel('Time [s]') pylab.ylabel('Weight [ns]') for t in threads: pylab.plot(results[t]['trace_w'].T[0]) pylab.legend([str(t) for t in threads]) pylab.subplot(324) pylab.title('Weight Evolution') pylab.xlabel('Time [s]') pylab.ylabel('Weight [ns]') for count, t in enumerate(threads): for i in range(3): pylab.plot(results[t]['trace_w'].T[i], c=results['colors'][count]) #pylab.legend(map(str, threads)) pylab.subplot(325) pylab.title('Voltage Evolution') pylab.xlabel('Time [s]') pylab.ylabel('Voltage [mv]') for count, t in enumerate(threads): for i in range(3): pylab.plot(results[t]['trace_v'].T[i], c=results['colors'][count]) #pylab.legend(map(str, threads)) pylab.subplot(326) pylab.title('Speed') pylab.plot(threads, results['duration']) pylab.xlabel('# threads') pylab.ylabel('Time [s]') pylab.tight_layout() pylab.savefig('net1_openmp.png') pylab.show() brian2-2.5.4/dev/benchmarks/openmp/test_openmp_4.py000066400000000000000000000056261445201106100222250ustar00rootroot00000000000000import sys, os, numpy, time, pylab filename = 'example_standalone_bis.py' datapath = 'data_example_bis' threads = [0, 1, 2, 4, 6] results = {} results['duration'] = [] for t in threads: start = time.time() os.system('python %s 1 %d' %(filename, t)) with open('%s_%d/speed.txt' %(datapath, t), 'r') as f: results['duration'] += [float(f.read())] results[t] = {} for t in threads: results[t] = {} path = datapath + '_%d/' %t ids = numpy.fromfile(path+'results/_dynamic_array_spikemonitor_i', dtype=numpy.int32) times = numpy.fromfile(path+'results/_dynamic_array_spikemonitor_t', dtype=numpy.float64) w = numpy.fromfile(path+'results/_dynamic_array_synapses_w', dtype=numpy.float64) times_w = numpy.fromfile(path+'results/_dynamic_array_statemonitor_t', dtype=numpy.float64) w_over_time = numpy.fromfile(path+'results/_dynamic_array_statemonitor__recorded_w', dtype=numpy.float64) v_over_time = numpy.fromfile(path+'results/_dynamic_array_statemonitor_1__recorded_v', dtype=numpy.float64) times_v = numpy.fromfile(path+'results/_dynamic_array_statemonitor_1_t', dtype=numpy.float64) results[t]['spikes'] = (times, ids) results[t]['w'] = w results[t]['trace_w'] = w_over_time.reshape(len(times_w), len(w_over_time)/len(times_w)) results[t]['trace_v'] = v_over_time.reshape(len(times_v), len(v_over_time)/len(times_v)) results['colors'] = ['b', 'g', 'r', 'c', 'k'] pylab.figure() pylab.subplot(321) pylab.title('Raster plots') pylab.xlabel('Time [s]') pylab.ylabel('# cell') for t in threads: pylab.plot(results[t]['spikes'][0], results[t]['spikes'][1], '.') pylab.legend([str(t) for t in threads]) #pylab.xlim(0.5, 0.6) pylab.subplot(322) pylab.title('Final Distribution') pylab.xlabel('Weight [ns]') pylab.ylabel('Number of synapses') for t in threads: x, y = numpy.histogram(results[t]['w'], 100) pylab.plot(y[1:], x) pylab.legend([str(t) for t in threads]) pylab.subplot(323) pylab.title('Weight Evolution') pylab.xlabel('Time [s]') pylab.ylabel('Weight [ns]') for t in threads: pylab.plot(results[t]['trace_w'].T[0]) pylab.legend([str(t) for t in threads]) pylab.subplot(324) pylab.title('Weight Evolution') pylab.xlabel('Time [s]') pylab.ylabel('Weight [ns]') for count, t in enumerate(threads): for i in range(3): pylab.plot(results[t]['trace_w'].T[i], c=results['colors'][count]) #pylab.legend(map(str, threads)) pylab.subplot(325) pylab.title('Voltage Evolution') pylab.xlabel('Time [s]') pylab.ylabel('Voltage [mv]') for count, t in enumerate(threads): for i in range(3): pylab.plot(results[t]['trace_v'].T[i], c=results['colors'][count]) #pylab.legend(map(str, threads)) pylab.subplot(326) pylab.title('Speed') pylab.plot(threads, results['duration']) pylab.xlabel('# threads') pylab.ylabel('Time [s]') pylab.tight_layout() pylab.savefig('net2_openmp.png') pylab.show() brian2-2.5.4/dev/benchmarks/plot_synapse_creation.py000066400000000000000000000033231445201106100225430ustar00rootroot00000000000000import pickle import matplotlib.pyplot as plt import numpy as np with open('synapse_creation_times_brian1.pickle', 'r') as f: brian1_results = pickle.load(f) with open('synapse_creation_times_brian2.pickle', 'r') as f: brian2_results = pickle.load(f) results = brian1_results results.update(brian2_results) conditions = [('Full', True), ('Full (no-self)', 'i != j'), ('One-to-one', 'i == j'), ('Simple neighbourhood', 'abs(i-j) < 5'), ('Gauss neighbourhood', 'exp(-(i - j)**2/5) > 0.005'), ('Random (50%)', 0.5), ('Random (10%)', 0.1), ('Random (1%)', 0.01), ('Random no-self (50%)', '(i != j) * 0.5'), ('Random no-self (10%)', '(i != j) * 0.1'), ('Random no-self (1%)', '(i != j) * 0.01')] targets = ['PythonLanguage', 'CPPLanguage', 'Brian 1'] # nicer names for the labels lang_translation = {'PythonLanguage': 'Brian 2 (Python)', 'CPPLanguage': 'Brian 2 (C++)', 'Brian 1': 'Brian 1'} # Do some plots for pattern, condition in conditions: plt.figure() for lang_name in targets: data = [(connections, time) for ((lang, connections, p), time) in results.items() if lang == lang_name and p == pattern] data.sort(key=lambda item: item[0]) data = np.array(data).T plt.plot(data[0], data[1], 'o-', label=lang_translation[lang_name]) plt.xscale('log') plt.yscale('log') plt.xlabel('Number of created connections') plt.ylabel('time (s)') plt.legend(loc='best', frameon=False) plt.title('%s: "%s"' % (pattern, condition)) plt.savefig('plots/%s.png' % pattern.replace(' ', '_'))brian2-2.5.4/dev/benchmarks/processify.py000066400000000000000000000031331445201106100203240ustar00rootroot00000000000000# from https://gist.github.com/schlamar/2311116 import os import sys import traceback from functools import wraps from multiprocessing import Process, Queue def processify(func): '''Decorator to run a function as a process. Be sure that every argument and the return value is *pickable*. The created process is joined, so the code does not run in parallel. ''' def process_func(q, *args, **kwargs): try: ret = func(*args, **kwargs) except Exception: ex_type, ex_value, tb = sys.exc_info() error = ex_type, ex_value, ''.join(traceback.format_tb(tb)) ret = None else: error = None q.put((ret, error)) # register original function with different name # in sys.modules so it is pickable process_func.__name__ = func.__name__ + 'processify_func' setattr(sys.modules[__name__], process_func.__name__, process_func) @wraps(func) def wrapper(*args, **kwargs): q = Queue() p = Process(target=process_func, args=[q] + list(args), kwargs=kwargs) p.start() p.join() ret, error = q.get() if error: ex_type, ex_value, tb_str = error message = '%s (in subprocess)\n%s' % (ex_value.message, tb_str) raise ex_type(message) return ret return wrapper @processify def test_function(): return os.getpid() @processify def test_exception(): raise RuntimeError('xyz') def test(): print(os.getpid()) print(test_function()) test_exception() if __name__ == '__main__': test()brian2-2.5.4/dev/benchmarks/synapse_creation.py000066400000000000000000000043661445201106100215150ustar00rootroot00000000000000''' How long does synapse creation take? ''' import time import pickle import numpy as np import joblib from brian2 import * repetitions = 3 memory = joblib.Memory(cachedir='.', verbose=0) @memory.cache def test_connectivity2(N, i, j, n, p, codeobj_class): G = NeuronGroup(N, '') # Do it once without measuring the time to ignore the compilation time for # C code S = Synapses(G, G, '', codeobj_class=codeobj_class) S.connect(i, j, p, n) connections = len(S) del S times = [] for _ in range(repetitions): S = Synapses(G, G, '', codeobj_class=codeobj_class) start = time.time() S.connect(i, j, p, n) times.append(time.time() - start) del S return float(np.median(times)), connections conditions = [('Full', 'True'), ('Full (no-self)', 'i != j'), ('One-to-one', 'i == j'), ('Simple neighbourhood', 'abs(i-j) < 5'), ('Gauss neighbourhood', 'exp(-(i - j)**2/5) > 0.005'), ('Random (50%)', (True, None, 1, 0.5)), ('Random (10%)', (True, None, 1, 0.1)), ('Random (1%)', (True, None, 1, 0.01)), ('Random no-self (50%)', ('(i != j)', None, 1, 0.5)), ('Random no-self (10%)', ('(i != j)', None, 1, 0.1)), ('Random no-self (1%)', ('(i != j)', None, 1, 0.01))] targets = [NumpyCodeObject] results = {} max_connections = 2500000 for target in targets: lang_name = target.class_name for pattern, condition in conditions: N = 1 connections = took = 0 while connections < max_connections and took < 20: print(lang_name, pattern) if isinstance(condition, str): took, connections = test_connectivity2(N, condition, None, 1, 1., codeobj_class=target) else: took, connections = test_connectivity2(N, *condition, codeobj_class=target) print(N, '%.4fs (for %d connections)' % (took, connections)) results[(lang_name, connections, pattern)] = took N *= 2 with open('synapse_creation_times_brian2.pickle', 'w') as f: pickle.dump(results, f) brian2-2.5.4/dev/benchmarks/synapse_creation_brian1.py000066400000000000000000000034451445201106100227460ustar00rootroot00000000000000''' How long does synapse creation take? Brian1 for comparison ''' import time import pickle import numpy as np import joblib from brian import * repetitions = 5 memory = joblib.Memory(cachedir='.', verbose=0) @memory.cache def test_connectivity(N, i, j, n, p): G = NeuronGroup(N, '') # Do it once without measuring the time to ignore the compilation time for # C code -- not really necessary for Brian1 but leave it in for perfect # comparability S = Synapses(G, G, '') S[:, :] = i connections = len(S) del S times = [] for _ in range(repetitions): S = Synapses(G, G, '') start = time.time() S[:, :] = i times.append(time.time() - start) del S return np.median(times), connections conditions = [('Full', True), ('Full (no-self)', 'i != j'), ('One-to-one', 'i == j'), ('Simple neighbourhood', 'abs(i-j) < 5'), ('Gauss neighbourhood', 'exp(-(i - j)**2/5) > 0.005'), ('Random (50%)', 0.5), ('Random (10%)', 0.1), ('Random (1%)', 0.01), ('Random no-self (50%)', '(i != j) * 0.5'), ('Random no-self (10%)', '(i != j) * 0.1'), ('Random no-self (1%)', '(i != j) * 0.01')] results = {} lang_name = 'Brian 1' max_connections = 10000000 for pattern, condition in conditions: N = 1 connections = took = 0 while connections < max_connections and took < 60.: print(lang_name, pattern) took, connections = test_connectivity(N, condition, None, 1, 1.) print(N, '%.4fs (for %d connections)' % (took, connections)) results[(lang_name, connections, pattern)] = took N *= 2 with open('synapse_creation_times_brian1.pickle', 'w') as f: pickle.dump(results, f) brian2-2.5.4/dev/benchmarks/unit_system.py000066400000000000000000000044131445201106100205230ustar00rootroot00000000000000''' Some simple benchmarks to check how much the performance is impaired when using units (units are not used in the main simulation loop but also analysing/plotting should not be noticeably slow for the user) ''' import time import numpy as np from brian2 import * sizes = [10, 100, 1000, 10000, 100000] access = lambda idx, ar1, ar2: ar1[idx] multiply = lambda idx, ar1, ar2: 42 * ar1 addition = lambda idx, ar1, ar2: ar1 + ar2 slicing = lambda idx, ar1, ar2: ar1[idx:] for func_name, func in [('access', access), ('multiply', multiply), ('add', addition), ('slicing', slicing)]: times_no_unit = [] times_unit = [] for size in sizes: no_unit1 = np.random.randn(size) no_unit2 = np.random.randn(size) with_unit1 = no_unit1 * mV with_unit2 = no_unit2 * mV start = time.time() for x in range(size): func(x, no_unit1, no_unit2) times_no_unit.append(time.time() - start) start = time.time() for x in range(size): func(x, with_unit1, with_unit2) times_unit.append(time.time() - start) print('') print(func_name, ':') print('No unit ', times_no_unit) print(' unit ', times_unit) print('relative', np.array(times_unit) / np.array(times_no_unit)) # Dimensionless Quantities print('*' * 60) print('Dimensionless quantities') for func_name, func in [('access', access), ('multiply', multiply), ('add', addition), ('slicing', slicing)]: times_no_unit = [] times_unit = [] for size in sizes: no_unit1 = np.random.randn(size) no_unit2 = np.random.randn(size) with_unit1 = no_unit1 * mV/mV with_unit2 = no_unit2 * mV/mV start = time.time() for x in range(size): func(x, no_unit1, no_unit2) times_no_unit.append(time.time() - start) start = time.time() for x in range(size): func(x, with_unit1, with_unit2) times_unit.append(time.time() - start) print('') print(func_name, ':') print('No unit ', times_no_unit) print(' unit ', times_unit) print('relative', np.array(times_unit) / np.array(times_no_unit))brian2-2.5.4/dev/conda-recipe/000077500000000000000000000000001445201106100160005ustar00rootroot00000000000000brian2-2.5.4/dev/conda-recipe/meta.yaml000066400000000000000000000043641445201106100176210ustar00rootroot00000000000000package: name: brian2 version: "2.4.2.dev0" build: number: 0 script: {{ PYTHON }} -m pip install --no-deps --ignore-installed . --install-option=--with-cython --install-option=--fail-on-error requirements: build: - {{ compiler('cxx') }} host: - python >=3.7 - cython >=0.29 - setuptools >=24 # we build against the oldest supported numpy version - numpy 1.17 run: - python >=3.7 # we don't need the same version as during the build, newer versions are # still ABI-compatible - {{ pin_compatible('numpy') }} - sympy >=1.2 - pyparsing - scipy >=0.13.3 - gsl >1.15 - cython >=0.29 - jinja2 >=2.7 - setuptools >=24 - py-cpuinfo # [win] test: imports: - brian2 - brian2.codegen - brian2.codegen.generators - brian2.codegen.runtime - brian2.codegen.runtime.cython_rt - brian2.codegen.runtime.numpy_rt - brian2.core - brian2.devices - brian2.devices.cpp_standalone - brian2.equations - brian2.groups - brian2.input - brian2.memory - brian2.monitors - brian2.parsing - brian2.spatialneuron - brian2.sphinxext - brian2.stateupdaters - brian2.synapses - brian2.tests - brian2.tests.features - brian2.units - brian2.utils commands: # Run a simple test that uses some of the main simulation elements - 'python -c "from brian2.tests.test_synapses import test_transmission_simple; test_transmission_simple()"' requires: - pytest about: home: http://www.briansimulator.org/ dev_url: https://github.com/brian-team/brian2 doc_url: http://brian2.readthedocs.io/ license: CeCILL-2.1 license_file: LICENSE summary: 'A clock-driven simulator for spiking neural networks' description: | Brian2 is a simulator for spiking neural networks available on almost all platforms. The motivation for this project is that a simulator should not only save the time of processors, but also the time of scientists. It is the successor of Brian1 and shares its approach of being highly flexible and easily extensible. It is based on a code generation framework that allows to execute simulations using other programming languages and/or on different devices. source: path: ../.. brian2-2.5.4/dev/continuous-integration/000077500000000000000000000000001445201106100201765ustar00rootroot00000000000000brian2-2.5.4/dev/continuous-integration/preferences_for_32_bit000066400000000000000000000002531445201106100244320ustar00rootroot00000000000000# Preferences for compiling 32 Bit Code on 64 Bit Linux [codegen.cpp] extra_compile_args_gcc = ['-m32'] extra_link_args = ['-m32'] library_dirs = ['/lib32', '/usr/lib32'] brian2-2.5.4/dev/continuous-integration/run_simple_test.py000066400000000000000000000004361445201106100237670ustar00rootroot00000000000000# Run a simple test that uses the main simulation elements and force code # generation to use Cython from brian2 import prefs from brian2.tests.test_synapses import test_transmission_simple if __name__ == '__main__': prefs.codegen.target = 'cython' test_transmission_simple() brian2-2.5.4/dev/continuous-integration/run_test_suite.py000066400000000000000000000056401445201106100236310ustar00rootroot00000000000000''' Script to run the test suite during automatic testing (easier than putting all the logic into Windows batch/bash statements) ''' # Importing multiprocessing here seems to fix hangs in the test suite on OS X # see https://github.com/scipy/scipy/issues/11835 import multiprocessing import os import sys import numpy as np import brian2 if __name__ == '__main__': split_run = os.environ.get('SPLIT_RUN', None) standalone = os.environ.get('STANDALONE', 'no').lower() in ['yes', 'true'] python_version = os.environ.get('PYTHON_VERSION', os.environ.get('PYTHON')) # If TRAVIS_OS_NAME is not defined, we are testing on appveyor operating_system = os.environ.get('AGENT_OS', 'unknown').lower() cross_compiled = os.environ.get('CROSS_COMPILED', 'FALSE').lower() in ['yes', 'true'] report_coverage = os.environ.get('REPORT_COVERAGE', 'no').lower() in ['yes', 'true'] dtype_32_bit = os.environ.get('FLOAT_DTYPE_32', 'no').lower() in ['yes', 'true'] sphinx_dir = os.environ.get('SPHINX_DIR') src_dir = os.environ.get('SRCDIR') deprecation_error = os.environ.get('DEPRECATION_ERROR', 'false').lower() in ['yes', 'true'] if split_run == '1': targets = ['numpy'] independent = True elif split_run == '2': targets = ['cython'] independent = False else: targets = None independent = True if operating_system == 'windows' or standalone: in_parallel = [] else: in_parallel = ['codegen_independent', 'numpy', 'cpp_standalone'] if operating_system in ['linux', 'windows_nt']: openmp = True else: openmp = False reset_preferences = not cross_compiled if dtype_32_bit: float_dtype = np.float32 else: float_dtype = None if deprecation_error: args = ['-W', 'error::DeprecationWarning'] else: args = [] if standalone: result = brian2.test([], test_codegen_independent=False, test_standalone='cpp_standalone', test_openmp=openmp, test_in_parallel=in_parallel, reset_preferences=reset_preferences, float_dtype=float_dtype, test_GSL=True, sphinx_dir=sphinx_dir, additional_args=args) else: result = brian2.test(targets, test_codegen_independent=independent, test_standalone=None, test_in_parallel=in_parallel, reset_preferences=reset_preferences, float_dtype=float_dtype, test_GSL=True, sphinx_dir=sphinx_dir, additional_args=args) if not result: sys.exit(1) brian2-2.5.4/dev/jenkins/000077500000000000000000000000001445201106100151105ustar00rootroot00000000000000brian2-2.5.4/dev/jenkins/pylint.rc000066400000000000000000000163331445201106100167630ustar00rootroot00000000000000[MASTER] # Specify a configuration file. #rcfile= # Python code to execute, usually for sys.path manipulation such as # pygtk.require(). #init-hook= # Profiled execution. profile=no # Add files or directories to the blacklist. They should be base names, not # paths. ignore=tests,sphinxext # Pickle collected data for later comparisons. persistent=yes # List of plugins (as comma separated values of python modules names) to load, # usually to register additional checkers. load-plugins= [MESSAGES CONTROL] # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option # multiple time. #enable= # Disable the message, report, category or checker with the given id(s). You # can either give multiple identifier separated by comma (,) or put this option # multiple time (only on the command line, not in the configuration file where # it should appear only once). disable=C0302,C0103,R0901,R0902,R0903,R0904,R0911,R0912,R0913,R0914,R0915,W0142,W0212,W0401,W0403,W0614,E0602,E1103,E1101,I0011 [REPORTS] # Set the output format. Available formats are text, parseable, colorized, msvs # (visual studio) and html output-format=parseable # Include message's id in output include-ids=yes # Put messages in a separate file for each module / package specified on the # command line instead of printing them on stdout. Reports (if any) will be # written in a file name "pylint_global.[txt|html]". files-output=no # Tells whether to display a full report or only the messages reports=yes # Python expression which should return a note less than 10 (10 is the highest # note). You have access to the variables errors warning, statement which # respectively contain the number of errors / warnings messages and the total # number of statements analyzed. This is used by the global evaluation report # (RP0004). evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) # Add a comment according to your evaluation note. This is used by the global # evaluation report (RP0004). comment=no [BASIC] # Required attributes for module, separated by a comma required-attributes= # List of builtins function names that should not be used, separated by a comma bad-functions=apply,input # Regular expression which should only match correct module names module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ # Regular expression which should only match correct module level names const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ # Regular expression which should only match correct class names class-rgx=[A-Z_][a-zA-Z0-9]+$ # Regular expression which should only match correct function names function-rgx=[a-z_][a-z0-9_]{2,30}$ # Regular expression which should only match correct method names method-rgx=[a-z_][a-z0-9_]{2,30}$ # Regular expression which should only match correct instance attribute names attr-rgx=[a-z_][a-z0-9_]{2,30}$ # Regular expression which should only match correct argument names argument-rgx=[a-z_][a-z0-9_]{2,30}$ # Regular expression which should only match correct variable names variable-rgx=[a-z_][a-z0-9_]{2,30}$ # Regular expression which should only match correct list comprehension / # generator expression variable names inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ # Good variable names which should always be accepted, separated by a comma good-names=i,j,k,ex,Run,_ # Bad variable names which should always be refused, separated by a comma bad-names=foo,bar,baz,toto,tutu,tata # Regular expression which should only match functions or classes name which do # not require a docstring no-docstring-rgx=__.*__ [TYPECHECK] # Tells whether missing members accessed in mixin class should be ignored. A # mixin class is detected if its name ends with "mixin" (case insensitive). ignore-mixin-members=yes # List of classes names for which member attributes should not be checked # (useful for classes with attributes dynamically set). ignored-classes=SQLObject # When zope mode is activated, add a predefined set of Zope acquired attributes # to generated-members. zope=no # List of members which are set dynamically and missed by pylint inference # system, and so shouldn't trigger E0201 when accessed. Python regular # expressions are accepted. generated-members=REQUEST,acl_users,aq_parent [MISCELLANEOUS] # List of note tags to take in consideration, separated by a comma. notes=FIXME [VARIABLES] # Tells whether we should check for unused import in __init__ files. init-import=no # A regular expression matching the beginning of the name of dummy variables # (i.e. not used). dummy-variables-rgx=_|dummy # List of additional names supposed to be defined in builtins. Remember that # you should avoid to define new builtins when possible. additional-builtins= [SIMILARITIES] # Minimum lines number of a similarity. min-similarity-lines=4 # Ignore comments when computing similarities. ignore-comments=yes # Ignore docstrings when computing similarities. ignore-docstrings=yes [FORMAT] # Maximum number of characters on a single line. max-line-length=80 # Maximum number of lines in a module max-module-lines=1000 # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 # tab). indent-string=' ' [IMPORTS] # Deprecated modules which should not be used, separated by a comma deprecated-modules=regsub,TERMIOS,Bastion,rexec # Create a graph of every (i.e. internal and external) dependencies in the # given file (report RP0402 must not be disabled) import-graph= # Create a graph of external dependencies in the given file (report RP0402 must # not be disabled) ext-import-graph= # Create a graph of internal dependencies in the given file (report RP0402 must # not be disabled) int-import-graph= [CLASSES] # List of interface methods to ignore, separated by a comma. This is used for # instance to not check methods defines in Zope's Interface base class. ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by # List of method names used to declare (i.e. assign) instance attributes. defining-attr-methods=__init__,__new__,setUp # List of valid names for the first argument in a class method. valid-classmethod-first-arg=cls [DESIGN] # Maximum number of arguments for function / method max-args=5 # Argument names that match this expression will be ignored. Default to name # with leading underscore ignored-argument-names=_.* # Maximum number of locals for function / method body max-locals=15 # Maximum number of return / yield for function / method body max-returns=6 # Maximum number of branch for function / method body max-branchs=12 # Maximum number of statements in function / method body max-statements=50 # Maximum number of parents for a class (see R0901). max-parents=7 # Maximum number of attributes for a class (see R0902). max-attributes=7 # Minimum number of public methods for a class (see R0903). min-public-methods=2 # Maximum number of public methods for a class (see R0904). max-public-methods=20 [EXCEPTIONS] # Exceptions that will emit a warning when being caught. Defaults to # "Exception" overgeneral-exceptions=Exception brian2-2.5.4/dev/jenkins/run_pylint.sh000066400000000000000000000001141445201106100176430ustar00rootroot00000000000000#!/bin/bash pylint --rcfile=dev/jenkins/pylint.rc brian2 > pylint.log || : brian2-2.5.4/dev/jenkins/test_brian2.sh000066400000000000000000000030611445201106100176600ustar00rootroot00000000000000source /home/jenkins/.jenkins/virtual_envs/$PythonVersion/$packages/bin/activate # get the newest version of nose and coverage, ignoring installed packages pip install --upgrade -I nose coverage || : # Make sure pyparsing and ipython (used for pretty printing) are installed pip install pyparsing --upgrade pip install ipython # Make sure we have sphinx (for testing the sphinxext) pip install sphinx echo "Using newest available package versions" pip install --upgrade numpy pip install --upgrade scipy pip install --upgrade sympy pip install --upgrade matplotlib # Print the version numbers for the dependencies python -c "import numpy; print('numpy version: ' + numpy.__version__)" python -c "import scipy; print('scipy version: ' + scipy.__version__)" python -c "import sympy; print('sympy version: ' + sympy.__version__)" python -c "import matplotlib; print('matplotlib version: ' + matplotlib.__version__)" # Build Brian2 python setup.py build --build-lib=build/lib # Move to the build directory to make sure it is used for testing # (important for Python 3) cd build/lib # delete remaining compiled code from previous runs echo deleting '~/.python*_compiled' if it exists rm -r ~/.python*_compiled || : # Run unit tests and record coverage but do not fail the build if anything goes wrong here coverage erase --rcfile=../../.coveragerc || : coverage run --rcfile=../../.coveragerc ~/.jenkins/virtual_envs/$PythonVersion/$packages/bin/nosetests --with-xunit --logging-clear-handlers --verbose --with-doctest brian2 || : coverage xml -i --rcfile=../../.coveragerc || : brian2-2.5.4/dev/notes_on_passing_variables_to_templates.txt000066400000000000000000000035311445201106100243720ustar00rootroot00000000000000Template generation keywords: code block multiple code blocks # Handling lumped variables using the standard mechanisms is not # possible, we therefore also directly give the names of the arrays # to the template. The dummy statement in the second line only serves # the purpose of including the variable in the namespace _target_var_array name (lumped updater) ============= StateMonitor =========================== record variable names (statemonitor) group.variables ['_spikespace', 't', 'v', 'dt', 'lastspike', 'not_refractory'] additional_variables { '_t': >, dtype=, scalar=False, constant=False)>, '_clock_t': AttributeVariable(unit=second, obj=Clock(dt=0.10000000000000001 * msecond, name='defaultclock'), attribute='t_', constant=False), '_recorded_v': >, dtype=dtype('float64'), scalar=False, constant=False)>, '_indices': >, dtype=dtype('int32'), scalar=False, constant=True)>, 'v': >, dtype=dtype('float64'), scalar=False, constant=False)>} resolved_namespace [] resolved_namespace2 [] variable_indices defaultdict( at 0x04172570>, {'lastspike': '_idx', 'v': '_idx', 'not_refractory': '_idx'}) template_kwds {'_variable_names': ['v']} ======================================== # For C++ code, we need these names explicitly, since not_refractory # and lastspike might also be used in the threshold condition -- the # names will then refer to single (constant) values and cannot be used # for assigning new values array_not_refractory name (threshold) array_lastspike name (threshold)brian2-2.5.4/dev/tools/000077500000000000000000000000001445201106100146075ustar00rootroot00000000000000brian2-2.5.4/dev/tools/.gitignore000066400000000000000000000000751445201106100166010ustar00rootroot00000000000000/cached_speed_test_results.pkl /cpp_standalone /testing_dir/ brian2-2.5.4/dev/tools/docs/000077500000000000000000000000001445201106100155375ustar00rootroot00000000000000brian2-2.5.4/dev/tools/docs/build_dash_docset.py000077500000000000000000000005171445201106100215560ustar00rootroot00000000000000import brian2 import sphinx import doc2dash import sys import os # - Build documentation with Sphinx os.chdir('../../../docs_sphinx') sphinx.main(['sphinx-build', '-b', 'html', '-d', '_build/doctrees', '.', '_build/html']) # - Run doc2dash on the built documentation os.system('doc2dash _build/html/ -n Brian2 -I index.html -d ..') brian2-2.5.4/dev/tools/docs/build_html_brian2.py000066400000000000000000000011501445201106100214660ustar00rootroot00000000000000import os import shutil import sphinx if not sphinx.version_info >= (1, 8): raise ImportError('Need sphinx version 1.8') from sphinx.cmd.build import main as sphinx_main import sys os.chdir('../../../docs_sphinx') if os.path.exists('../docs'): shutil.rmtree('../docs') # Some code (e.g. the definition of preferences) might need to know that Brian # is used to build the documentation. The READTHEDOCS variable is set # on the readthedocs.io server automatically, so we reuse it here to signal # a documentation build os.environ['READTHEDOCS'] = 'True' sys.exit(sphinx_main(['-b', 'html', '.', '../docs'])) brian2-2.5.4/dev/tools/docs/build_tutorials.py000066400000000000000000000120001445201106100213070ustar00rootroot00000000000000 import os import shutil import glob import codecs from nbformat import NotebookNode from nbformat.v4 import reads from nbconvert.preprocessors import ExecutePreprocessor from nbconvert.exporters.notebook import NotebookExporter from nbconvert.exporters.rst import RSTExporter from brian2.utils.stringtools import deindent, indent src_dir = os.path.abspath('../../../tutorials') target_dir = os.path.abspath('../../../docs_sphinx/resources/tutorials') # Start from scratch to avoid left-over files due to renamed tutorials, changed # cell numbers, etc. if os.path.exists(target_dir): shutil.rmtree(target_dir) os.mkdir(target_dir) tutorials = [] for fname in sorted(glob.glob1(src_dir, '*.ipynb')): basename = fname[:-6] output_ipynb_fname = os.path.join(target_dir, fname) output_rst_fname = os.path.join(target_dir, basename + '.rst') print('Running', fname) with open(os.path.join(src_dir, fname), 'r') as f: notebook = reads(f.read()) # The first line of the tutorial file should give the title title = notebook.cells[0]['source'].split('\n')[0].strip('# ') tutorials.append((basename, title)) # Execute the notebook preprocessor = ExecutePreprocessor(timeout=None) preprocessor.allow_errors = True notebook, _ = preprocessor.preprocess(notebook, {'metadata': {'path': src_dir}}) print('Saving notebook and converting to RST') exporter = NotebookExporter() output, _ = exporter.from_notebook_node(notebook) with codecs.open(output_ipynb_fname, 'w', encoding='utf-8') as f: f.write(output) # Insert a note about ipython notebooks with a download link note = deindent(''' .. only:: html .. |launchbinder| image:: http://mybinder.org/badge.svg .. _launchbinder: https://mybinder.org/v2/gh/brian-team/brian2-binder/master?filepath=tutorials/{tutorial}.ipynb .. note:: This tutorial is a static non-editable version. You can launch an interactive, editable version without installing any local files using the Binder service (although note that at some times this may be slow or fail to open): |launchbinder|_ Alternatively, you can download a copy of the notebook file to use locally: :download:`{tutorial}.ipynb` See the :doc:`tutorial overview page ` for more details. '''.format(tutorial=basename)) notebook.cells.insert(1, NotebookNode(cell_type='raw', metadata={}, source=note)) exporter = RSTExporter() output, resources = exporter.from_notebook_node(notebook, resources={'unique_key': basename+'_image'}) with codecs.open(output_rst_fname, 'w', encoding='utf-8') as f: f.write(output) for image_name, image_data in resources['outputs'].items(): with open(os.path.join(target_dir, image_name), 'wb') as f: f.write(image_data) print('Generating index.rst') text = ''' .. This is a generated file, do not edit directly. (See dev/tools/docs/build_tutorials.py) Tutorials ========= The tutorial consists of a series of `Jupyter Notebooks`_ [#]_. .. only:: html You can quickly view these using the first links below. To use them interactively - allowing you to edit and run the code - there are two options. The easiest option is to click on the "Launch Binder" link, which will open up an interactive version in the browser without having to install Brian locally. This uses the `mybinder.org `_ service. Occasionally, this service will be down or running slowly. The other option is to download the notebook file and run it locally, which requires you to have Brian installed. For more information about how to use Jupyter Notebooks, see the `Jupyter Notebook documentation`_. .. toctree:: :maxdepth: 1 :titlesonly: ''' for tutorial, _ in tutorials: text += ' ' + tutorial + '\n' text += ''' .. only:: html Interactive notebooks and files ------------------------------- ''' for tutorial, _ in tutorials: text += indent(deindent(''' .. |launchbinder{tutid}| image:: http://mybinder.org/badge.svg .. _launchbinder{tutid}: https://mybinder.org/v2/gh/brian-team/brian2-binder/master?filepath=tutorials/{tutorial}.ipynb '''.format(tutorial=tutorial, tutid=tutorial.replace('-', '')))) text += '\n' for tutorial, title in tutorials: text += ' * |launchbinder{tutid}|_ :download:`{title} <{tutorial}.ipynb>`\n'.format(title=title, tutorial=tutorial, tutid=tutorial.replace('-', '')) text += ''' .. _`Jupyter Notebooks`: http://jupyter-notebook-beginner-guide.readthedocs.org/en/latest/what_is_jupyter.html .. _`Jupyter`: http://jupyter.org/ .. _`Jupyter Notebook documentation`: http://jupyter.readthedocs.org/ .. [#] Formerly known as "IPython Notebooks". ''' with open(os.path.join(target_dir, 'index.rst'), 'w') as f: f.write(text) brian2-2.5.4/dev/tools/docs/create_multicompartment_diagrams.py000066400000000000000000000157261445201106100247220ustar00rootroot00000000000000import os from brian2 import * HEADER = ''' ''' # For now, we do not bother showing the actual size of the Soma SPHERE = ''' ''' CYLINDER = ''' ''' FRUSTRUM = ''' ''' PARENT_CYLINDER = ''' ''' CONNECTION = ''' ''' FOOTER = ''' ''' def to_svg(morphology): if isinstance(morphology, Soma): return HEADER.format(minx='-5', miny='-5', width='10', height='10') + SPHERE.format(radius=morphology.diameter[0]/2/um) + FOOTER if isinstance(morphology, Cylinder): summed_length = 0*um elements = [] center = max([7.5*um, max(morphology.end_diameter)/2]) minx = -2.5 if morphology.parent is not None: elements.append(PARENT_CYLINDER.format(starty=(center-morphology.parent.end_diameter[-1]/2)/um, length=morphology.parent.length[-1]/um, center=center/um, diameter=morphology.parent.end_diameter[-1]/um, radius=morphology.parent.end_diameter[-1]/2/um)) minx -= morphology.parent.length[-1]/um for idx, (diameter, length) in enumerate(zip(morphology.diameter, morphology.length)): elements.append(CONNECTION.format(start=(summed_length-3*um)/um, end=summed_length/um, center=center/um)) elements.append(CYLINDER.format(startx=summed_length/um, starty=(center-diameter/2)/um, radius=diameter/2/um, diameter=diameter/um, length=length/um, center=center/um)) summed_length += length + 3*um return HEADER.format(minx=minx, miny=-1, width=summed_length/um+0.5-minx, height=center*2/um+2) + ('\n'.join(elements)) + FOOTER elif isinstance(morphology, Section): summed_length = 0*um elements = [] center = max([7.5*um, max(morphology.end_diameter)/2]) minx = -5 if morphology.parent is not None: elements.append(PARENT_CYLINDER.format(starty=(center-morphology.parent.end_diameter[-1]/2)/um, length=morphology.parent.length[-1]/um, center=center/um, diameter=morphology.parent.end_diameter[-1]/um, radius=morphology.parent.end_diameter[-1]/2/um)) minx -= morphology.parent.length[-1]/um for idx, (start_diameter, end_diameter, length) in enumerate(zip(morphology.start_diameter, morphology.end_diameter, morphology.length)): elements.append(CONNECTION.format(start=(summed_length-3*um)/um, end=summed_length/um, center=center/um)) elements.append(FRUSTRUM.format(startx=summed_length/um, starty1=(center-start_diameter/2)/um, starty2=(center+start_diameter/2)/um, endy1=(center-end_diameter/2)/um, endy2=(center+end_diameter/2)/um, radius1=start_diameter/2/um, radius2=end_diameter/2/um, length=length/um, center=center/um)) summed_length += length + 3*um return HEADER.format(minx=minx, miny=-1, width=summed_length/um+1.5-minx, height=center*2/um+2) + ('\n'.join(elements)) + FOOTER else: raise NotImplementedError() if __name__ == '__main__': dirname = os.path.dirname(__file__) PATH = os.path.join(dirname, '..', '..', '..', 'docs_sphinx', 'user', 'images') root = Cylinder(n=1, diameter=15*um, length=10*um) root.cyl = Cylinder(n=5, diameter=10*um, length=50*um) root.sec = Section(n=5, diameter=[15, 5, 10, 5, 10, 5]*um, length=[10, 20, 5, 5, 10]*um) for filename, morpho in [('soma.svg', Soma(diameter=30*um)), ('cylinder.svg', root.cyl), ('section.svg', root.sec)]: with open(os.path.join(PATH, filename), 'w') as f: print(filename) f.write(to_svg(morpho)) brian2-2.5.4/dev/tools/docs/create_multicompartment_trees.py000066400000000000000000000100301445201106100242340ustar00rootroot00000000000000import os import mayavi.mlab as mayavi from brian2 import * def find_max_coordinates(morpho, current_max): max_x, max_y, max_z = np.max(np.abs(morpho.coordinates), axis=0) new_max = (max([max_x, current_max[0]]), max([max_y, current_max[1]]), max([max_z, current_max[2]])) for child in morpho.children: new_max = find_max_coordinates(child, new_max) return new_max DARKRED = (0.5450980392156862, 0.0, 0.0) DARKBLUE = (0.0, 0.0, 0.5450980392156862) def plot_morphology3D(morpho, color_switch=False, show_compartments=False): if isinstance(morpho, Soma): mayavi.points3d(morpho.x/um, morpho.y/um, morpho.z/um, morpho.diameter/um, color=DARKRED, scale_factor=1.0, resolution=16) else: coords = morpho.coordinates if color_switch: color = DARKBLUE else: color = DARKRED mayavi.plot3d(coords[:, 0]/um, coords[:, 1]/um, coords[:, 2]/um, color=color, tube_radius=1) # dots at the center of the compartments if show_compartments: mayavi.points3d(coords[:, 0]/um, coords[:, 1]/um, coords[:, 2]/um, np.ones(coords.shape[0]), color=color, scale_factor=1, transparent=0.25) for child in morpho.children: plot_morphology3D(child, color_switch=not color_switch) def plot_morphology2D(morpho, axis, color_switch=False): if isinstance(morpho, Soma): ax.plot(morpho.x/um, morpho.y/um, 'o', color='darkred', ms=morpho.diameter/um, mec='none') else: coords = morpho.coordinates if color_switch: color = 'darkblue' else: color = 'darkred' ax.plot(coords[:, 0]/um, coords[:, 1]/um, color='black', lw=2) # dots at the center of the compartments ax.plot(morpho.x/um, morpho.y/um, 'o', color=color, mec='none', alpha=0.75) for child in morpho.children: plot_morphology2D(child, axis=axis, color_switch=not color_switch) if __name__ == '__main__': dirname = os.path.dirname(__file__) PATH = os.path.join(dirname, '..', '..', '..', 'docs_sphinx', 'user', 'images') # Construct binary tree according to Rall's formula diameter = 32*um length = 80*um morpho = Soma(diameter=diameter) endpoints = {morpho} for depth in range(1, 10): diameter /= 2.**(1./3.) length /= 2.**(2./3.) new_endpoints = set() for endpoint in endpoints: new_L = Cylinder(n=max([1, int(length/(5*um))]), diameter=diameter, length=length) new_R = Cylinder(n=max([1, int(length/(5*um))]), diameter=diameter, length=length) new_endpoints.add(new_L) new_endpoints.add(new_R) endpoint.L = new_L endpoint.R = new_R endpoints = new_endpoints print('total number of sections and compartments', morpho.n_sections, len(morpho)) morpho_with_coords = morpho.generate_coordinates() ax = plt.subplot(111) plot_morphology2D(morpho_with_coords, ax) plt.axis('equal') plt.xlabel('x ($\mu$ m)') plt.ylabel('y ($\mu$ m)') plt.savefig(os.path.join(PATH, 'morphology_deterministic_coords.png')) print('be careful, this plotting takes a long time') for title, noise_sec, noise_comp in [('section', 25, 0), ('section_compartment', 25, 15)]: for idx in range(3): fig = mayavi.figure(bgcolor=(0.95, 0.95, 0.95)) print(idx) morpho_with_coords = morpho.generate_coordinates(section_randomness=noise_sec, compartment_randomness=noise_comp) plot_morphology3D(morpho_with_coords) cam = fig.scene.camera cam.zoom(1.1) mayavi.draw() mayavi.savefig(os.path.join(PATH, 'morphology_random_%s_%d.png' % (title, idx+1))) mayavi.close() brian2-2.5.4/dev/tools/docs/quick_rebuild_html_brian2.py000066400000000000000000000012271445201106100232160ustar00rootroot00000000000000import os import shutil import sphinx if not sphinx.version_info >= (1, 8): raise ImportError('Need sphinx version 1.8') from sphinx.cmd.build import main as sphinx_main import sys os.environ['BRIAN2_DOCS_QUICK_REBUILD'] = '1' # Some code (e.g. the definition of preferences) might need to know that Brian # is used to build the documentation. The READTHEDOCS variable is set # on the readthedocs.io server automatically, so we reuse it here to signal # a documentation build os.environ['READTHEDOCS'] = 'True' os.chdir('../../../docs_sphinx') if os.path.exists('../docs'): shutil.rmtree('../docs') sys.exit(sphinx_main(['-b', 'html', '.', '../docs'])) brian2-2.5.4/dev/tools/docs/run_doctests.py000066400000000000000000000002531445201106100206250ustar00rootroot00000000000000import os import sphinx os.chdir('../../../docs_sphinx') sphinx.main(['sphinx-build', '-b', 'doctest', '.', '../docs', '-D', 'exclude_patterns=reference']) brian2-2.5.4/dev/tools/first_commit.sh000066400000000000000000000002531445201106100176420ustar00rootroot00000000000000#!/bin/bash SAVEIFS=$IFS IFS=$(echo -en "\n\b") for a in `git log --pretty="%an" | sort | uniq`; do git log --author="$a" --pretty="%ai %an" | tail -n1; done IFS=$SAVEIFS brian2-2.5.4/dev/tools/release/000077500000000000000000000000001445201106100162275ustar00rootroot00000000000000brian2-2.5.4/dev/tools/release/release.py000066400000000000000000000010441445201106100202200ustar00rootroot00000000000000import os import brian2 # Ask for version number print('Current version is: ' + brian2.__version__) version = input('Enter new Brian2 version number: ').strip() # commit os.system('git commit -a -v --allow-empty -m "***** Release Brian2 %s *****"' % version) # add tag os.system('git tag -a -m "Release Brian2 %s" %s' % (version, version)) # print commands necessary for pushing print('Review the last commit: ') os.system('git show %s' % version) print('') print('To push, using the following command:') print('git push --tags origin master') brian2-2.5.4/dev/tools/repo_stats.py000066400000000000000000000031111445201106100173400ustar00rootroot00000000000000import collections import datetime try: from github import Github except ImportError: raise ImportError('Install PyGithub from https://github.com/PyGithub/PyGithub or via pip') API_TOKEN = None if API_TOKEN is None: raise ValueError('Need to specify an API token') p = Github(API_TOKEN) last_release = datetime.datetime(year=2020, month=9, day=28) authors = [] comments = p.get_repo('brian-team/brian2').get_issues_comments(since=last_release) comment_counter = 0 for comment in comments: name = comment.user.name if name is None: authors.append('`@{login} `_'.format(login=comment.user.login)) else: authors.append( '{name} (`@{login} `_)'.format( login=comment.user.login, name=name)) comment_counter += 1 print('Counted {} comments'.format(comment_counter)) issues = p.get_repo('brian-team/brian2').get_issues(since=last_release) issue_counter = 0 for issue in issues: name = issue.user.name if name is None: authors.append('`@{login} `_'.format(login=issue.user.login)) else: authors.append( '{name} (`@{login} `_)'.format( login=issue.user.login, name=name)) issue_counter += 1 print('Counted {} issues'.format(issue_counter)) counted = collections.Counter(authors) sorted = sorted(counted.items(), key=lambda item: item[1], reverse=True) for name, contributions in sorted: print('{:>4} {}'.format(contributions, name)) brian2-2.5.4/dev/tools/run_examples.py000066400000000000000000000105241445201106100176650ustar00rootroot00000000000000import os import sys import warnings import runpy import pytest from brian2 import device, set_device import numpy as np warnings.simplefilter('ignore') class ExampleRun(pytest.Item): ''' A test case that simply executes a python script ''' @classmethod def from_parent(cls, filename, codegen_target, dtype, parent): super_class = super(ExampleRun, cls) if hasattr(super_class, 'from_parent'): new_node = super_class.from_parent(parent=parent, name=ExampleRun.id(filename)) else: # For pytest < 6 new_node = cls(parent=parent, name=ExampleRun.id(filename)) new_node.filename = filename new_node.codegen_target = codegen_target new_node.dtype = dtype return new_node @staticmethod def id(filename): # Remove the .py and pretend the dirname is a package and the filename # is a class. name = os.path.splitext(os.path.split(filename)[1])[0] pkgname = os.path.split(os.path.split(filename)[0])[1] return pkgname + '.' + name.replace('.', '_') def shortDescription(self): return str(self) def runtest(self): import matplotlib as _mpl _mpl.use('Agg') import numpy as np from brian2 import prefs from brian2.utils.filetools import ensure_directory_of_file prefs.codegen.target = self.codegen_target prefs.core.default_float_dtype = self.dtype # Move to the file's directory for the run, so that it can do relative # imports and load files (e.g. figure style files) curdir = os.getcwd() os.chdir(os.path.dirname(self.filename)) sys.path.append(os.path.dirname(self.filename)) try: runpy.run_path(self.filename, run_name='__main__') if self.codegen_target == 'cython' and self.dtype == np.float64: for fignum in _mpl.pyplot.get_fignums(): fname = os.path.relpath(self.filename, self.example_dir) fname = fname.replace('/', '.').replace('\\\\', '.') fname = fname.replace('.py', '.%d.png' % fignum) fname = os.path.abspath(os.path.join(self.example_dir, '../docs_sphinx/resources/examples_images/', fname)) ensure_directory_of_file(fname) _mpl.pyplot.figure(fignum).savefig(fname) finally: _mpl.pyplot.close('all') os.chdir(curdir) sys.path.remove(os.path.dirname(self.filename)) device.reinit() set_device('runtime') def __str__(self): return 'Example: %s (%s, %s)' % (self.filename, self.codegen_target, self.dtype.__name__) class ExampleCollector(pytest.Collector): @classmethod def from_parent(cls, example_dir, parent): collector = super(ExampleCollector, cls) if hasattr(collector, 'from_parent'): new_collector = collector.from_parent(parent, name='example_collector') else: # For pytest < 6 new_collector = cls(parent=parent, name='example_collector') new_collector.example_dir = example_dir return new_collector def collect(self): for dirname, dirs, files in os.walk(self.example_dir): for filename in files: if filename.endswith('.py'): run = ExampleRun.from_parent(os.path.join(dirname, filename), 'cython', np.float64, parent=self) run.example_dir = self.example_dir yield run class Plugin: def __init__(self, example_dir): self.example_dir = example_dir def pytest_collect_file(self, path, parent): return ExampleCollector.from_parent(self.example_dir, parent=parent) if __name__ == '__main__': example_dir = os.path.abspath(os.path.join(__file__, '..', '..', '..', 'examples')) sys.exit(pytest.main([__file__, '--verbose'], plugins=[Plugin(example_dir)])) brian2-2.5.4/dev/tools/run_feature_tests.py000066400000000000000000000015041445201106100207220ustar00rootroot00000000000000from brian2 import * from brian2.tests.features import * from brian2.tests.features.base import * from brian2.tests.features.monitors import SpikeMonitorTest, StateMonitorTest from brian2.tests.features.input import SpikeGeneratorGroupTest # Full testing print(run_feature_tests().tables_and_exceptions) # Quick testing # print run_feature_tests(configurations=[DefaultConfiguration, # NumpyConfiguration], # feature_tests=[SpikeGeneratorGroupTest]).tables_and_exceptions # Specific testing #from brian2.tests.features.synapses import SynapsesSTDP, SynapsesPost #print run_feature_tests(feature_tests=[SynapsesPost]).tables_and_exceptions #print run_feature_tests(feature_tests=[SynapsesPost], # configurations=[DefaultConfiguration]).exceptions brian2-2.5.4/dev/tools/run_speed_tests.py000066400000000000000000000040271445201106100203720ustar00rootroot00000000000000from brian2 import * from brian2.tests.features import * from brian2.tests.features.base import * from brian2.tests.features.speed import * #from brian2genn.correctness_testing import GeNNConfiguration import os, pickle use_cached_results = True if use_cached_results and os.path.exists('cached_speed_test_results.pkl'): with open('cached_speed_test_results.pkl', 'rb') as f: res = pickle.load(f) else: # Full testing # res = run_speed_tests() # Quick testing res = run_speed_tests(configurations=[NumpyConfiguration, CythonConfiguration, #LocalConfiguration, CPPStandaloneConfiguration, #CPPStandaloneConfigurationOpenMP, # GeNNConfiguration, ], speed_tests=[ LinearNeuronsOnly, HHNeuronsOnly, CUBAFixedConnectivity, COBAHHFixedConnectivity, VerySparseMediumRateSynapsesOnly, SparseMediumRateSynapsesOnly, DenseMediumRateSynapsesOnly, SparseLowRateSynapsesOnly, SparseHighRateSynapsesOnly, STDP, ], #n_slice=slice(None, None, 3), # n_slice=slice(None, -3), run_twice=False, maximum_run_time=1*second, ) with open('cached_speed_test_results.pkl', 'wb') as f: pickle.dump(res, f, -1) res.plot_all_tests() # res.plot_all_tests(profiling_minimum=0.15) res.plot_all_tests(relative=True) show() brian2-2.5.4/dev/tools/run_tests.py000066400000000000000000000003731445201106100172120ustar00rootroot00000000000000''' Run all the non-standalone tests using pytest. Exits with error code 1 if a test failed. ''' import sys import brian2 if __name__ == '__main__': if not brian2.test(): # If the test fails, exit with a non-zero error code sys.exit(1) brian2-2.5.4/dev/tools/run_tests_long_and_standalone.py000066400000000000000000000012221445201106100232550ustar00rootroot00000000000000import sys import brian2 import numpy as np # Run tests for float32 and float64 success = [brian2.test(long_tests=True, test_standalone='cpp_standalone', float_dtype=np.float32), brian2.test(long_tests=True, test_standalone='cpp_standalone', float_dtype=np.float64)] result = ['Tests for {} dtype: {}'.format(dtype, 'passed' if status else 'FAILED') for status, dtype in zip(success, ['float32', 'float64'])] print('\t--\t'.join(result)) if all(success): print('OK: All tests passed successfully') else: print('FAILED: Not all tests passed successfully (see above)') sys.exit(1) brian2-2.5.4/dev/tools/run_tests_standalone.py000066400000000000000000000012361445201106100214210ustar00rootroot00000000000000''' Run all the standalone tests using pytest. Exits with error code 1 if a test failed. ''' import sys import brian2 if __name__ == '__main__': if len(sys.argv) > 1 and sys.argv[1] == 'no-parallel': if not brian2.test([], test_codegen_independent=False, test_standalone='cpp_standalone', test_in_parallel=[]): # If the test fails, exit with a non-zero error code sys.exit(1) else: if not brian2.test([], test_codegen_independent=False, test_standalone='cpp_standalone'): # If the test fails, exit with a non-zero error code sys.exit(1) brian2-2.5.4/dev/tools/run_tests_standalone_with_openmp.py000066400000000000000000000013701445201106100240310ustar00rootroot00000000000000''' Run all the standalone tests using pytest. Exits with error code 1 if a test failed. ''' import sys import brian2 if __name__ == '__main__': if len(sys.argv) > 1 and sys.argv[1] == 'no-parallel': if not brian2.test([], test_codegen_independent=False, test_standalone='cpp_standalone', test_in_parallel=[], test_openmp=True): # If the test fails, exit with a non-zero error code sys.exit(1) else: if not brian2.test([], test_codegen_independent=False, test_standalone='cpp_standalone', test_openmp=True): # If the test fails, exit with a non-zero error code sys.exit(1) brian2-2.5.4/dev/tools/static_codegen/000077500000000000000000000000001445201106100175625ustar00rootroot00000000000000brian2-2.5.4/dev/tools/static_codegen/units.py000066400000000000000000000137721445201106100213100ustar00rootroot00000000000000import os, re curdir, _ = os.path.split(__file__) units_fname = os.path.normpath(os.path.join(curdir, '../../../brian2/units/allunits.py')) _siprefixes = {"y":1e-24, "z":1e-21, "a":1e-18, "f":1e-15, "p":1e-12, "n":1e-9, "u":1e-6, "m":1e-3, "c":1e-2, "d":1e-1, "":1, "da":1e1, "h":1e2, "k":1e3, "M":1e6, "G":1e9, "T":1e12, "P":1e15, "E":1e18, "Z":1e21, "Y":1e24} fundamental_units = ['metre', 'meter', 'kilogram', 'second', 'amp', 'ampere', 'kelvin', 'mole', 'mol', 'candle'] #### DERIVED UNITS, from http://physics.nist.gov/cuu/Units/units.html derived_unit_table = [ ['radian', 'rad', 'get_or_create_dimension()'], ['steradian', 'sr', 'get_or_create_dimension()'], ['hertz', 'Hz', 'get_or_create_dimension(s= -1)'], ['newton', 'N', 'get_or_create_dimension(m=1, kg=1, s=-2)'], ['pascal', 'Pa', 'get_or_create_dimension(m= -1, kg=1, s=-2)'], ['joule', 'J', 'get_or_create_dimension(m=2, kg=1, s=-2)'], ['watt', 'W', 'get_or_create_dimension(m=2, kg=1, s=-3)'], ['coulomb', 'C', 'get_or_create_dimension(s=1, A=1)'], ['volt', 'V', 'get_or_create_dimension(m=2, kg=1, s=-3, A=-1)'], ['farad', 'F', 'get_or_create_dimension(m= -2, kg=-1, s=4, A=2)'], ['ohm', 'ohm', 'get_or_create_dimension(m=2, kg=1, s= -3, A=-2)'], ['siemens', 'S', 'get_or_create_dimension(m= -2, kg=-1, s=3, A=2)'], ['weber', 'Wb', 'get_or_create_dimension(m=2, kg=1, s=-2, A=-1)'], ['tesla', 'T', 'get_or_create_dimension(kg=1, s=-2, A=-1)'], ['henry', 'H', 'get_or_create_dimension(m=2, kg=1, s=-2, A=-2)'], ['lumen', 'lm', 'get_or_create_dimension(cd=1)'], ['lux', 'lx', 'get_or_create_dimension(m=-2, cd=1)'], ['becquerel', 'Bq', 'get_or_create_dimension(s=-1)'], ['gray', 'Gy', 'get_or_create_dimension(m=2, s=-2)'], ['sievert', 'Sv', 'get_or_create_dimension(m=2, s=-2)'], ['katal', 'kat', 'get_or_create_dimension(s=-1, mol=1)'], ] ## Generate derived unit objects and make a table of base units from these and the fundamental ones base_units = fundamental_units+['kilogramme', 'gram', 'gramme', 'molar'] derived = '' for longname, shortname, definition in derived_unit_table: derived += '{longname} = Unit.create({definition}, "{longname}", "{shortname}")\n'.format( longname=longname, shortname=shortname, definition=definition) base_units.append(longname) all_units = base_units + [] definitions = '######### SCALED BASE UNITS ###########\n' # Generate scaled units for all base units scaled_units = [] excluded_scaled_units = set() for _bu in base_units: if _bu in ['kelvin', 'kilogram', 'kilogramme']: continue # do not create "mkelvin", "kkilogram" etc. for _k in _siprefixes.keys(): if len(_k): _u = _k+_bu all_units.append(_u) definitions += '{_u} = Unit.create_scaled_unit({_bu}, "{_k}")\n'.format( _u=_u, _bu=_bu, _k=_k) if _k not in ["da", "d", "c", "h"]: scaled_units.append(_u) else: excluded_scaled_units.add(_u) # Generate 2nd and 3rd powers for all scaled base units definitions += '######### SCALED BASE UNITS TO POWERS ###########\n' powered_units = [] for bu in all_units + []: for i in [2, 3]: u = bu+str(i) definitions += '{u} = Unit.create(({bu}**{i}).dim, name="{u}", ' \ 'dispname=str({bu})+"^{i}", ' \ 'scale={bu}.scale*{i})\n'.format(u=u, bu=bu, i=i) all_units.append(u) if bu not in excluded_scaled_units: powered_units.append(u) additional_units = ''' # Current list from http://physics.nist.gov/cuu/Units/units.html, far from complete additional_units = [ pascal * second, newton * metre, watt / metre ** 2, joule / kelvin, joule / (kilogram * kelvin), joule / kilogram, watt / (metre * kelvin), joule / metre ** 3, volt / metre ** 3, coulomb / metre ** 3, coulomb / metre ** 2, farad / metre, henry / metre, joule / mole, joule / (mole * kelvin), coulomb / kilogram, gray / second, katal / metre ** 3, # We don't want liter/litre to be used as a standard unit for display, so we # put it here instead of in the standard units ''' # Scaled units for liter (we don't want liter**2, etc., so it is not included # in the base units above; also, we don't want it to register in the standard # units, so it is not chosen instead of meter**3 for displaying volume # quantities) for _bu in ['liter', 'litre']: all_units.append(_bu) for _k in _siprefixes.keys(): _u = _k + _bu all_units.append(_u) definitions += '{_u} = Unit.create_scaled_unit({_bu}, "{_k}")\n'.format( _u=_u, _bu=_bu, _k=_k) additional_units += _u + ', ' additional_units += ']' # Add unit names to __all__ all = ''' __all__ = [ {allunits} "celsius" # Dummy object raising an error ] '''.format(allunits='\n'.join(' "'+u+'",' for u in all_units)) def to_definition(name, x): return ''' {name} = [ {items} ] '''.format(name=name, items='\n'.join(' '+i+',' for i in x)) # Note that for presentation purposes, the `UnitRegistry` uses the *last* # defined unit for a given scale. Therefore, we reserve the order of the units # so that the standard units come first. with open('units_template.py') as f: template = f.read() units_str = template.format( derived=derived, all=all, definitions=definitions, base_units=to_definition('base_units', base_units[::-1]), scaled_units=to_definition('scaled_units', scaled_units[::-1]), powered_units=to_definition('powered_units', powered_units[::-1]), all_units=to_definition('all_units', all_units[::-1]), additional_units=additional_units, ) with open(units_fname, 'w') as f: f.write(units_str) brian2-2.5.4/dev/tools/static_codegen/units_template.py000066400000000000000000000064401445201106100231750ustar00rootroot00000000000000""" THIS FILE IS AUTOMATICALLY GENERATED BY A STATIC CODE GENERATION TOOL DO NOT EDIT BY HAND Instead edit the template: dev/tools/static_codegen/units_template.py """ # fmt: off # flake8: noqa import itertools from .fundamentalunits import (Unit, get_or_create_dimension, standard_unit_register, additional_unit_register) {all} Unit.automatically_register_units = False #### FUNDAMENTAL UNITS metre = Unit.create(get_or_create_dimension(m=1), "metre", "m") meter = Unit.create(get_or_create_dimension(m=1), "meter", "m") # Liter has a scale of 10^-3, since 1 l = 1 dm^3 = 10^-3 m^3 liter = Unit.create(dim=(meter**3).dim, name="liter", dispname="l", scale=-3) litre = Unit.create(dim=(meter**3).dim, name="litre", dispname="l", scale=-3) kilogram = Unit.create(get_or_create_dimension(kg=1), "kilogram", "kg") kilogramme = Unit.create(get_or_create_dimension(kg=1), "kilogramme", "kg") gram = Unit.create(dim=kilogram.dim, name="gram", dispname="g", scale=-3) gramme = Unit.create(dim=kilogram.dim, name="gramme", dispname="g", scale=-3) second = Unit.create(get_or_create_dimension(s=1), "second", "s") amp = Unit.create(get_or_create_dimension(A=1), "amp", "A") ampere = Unit.create(get_or_create_dimension(A=1), "ampere", "A") kelvin = Unit.create(get_or_create_dimension(K=1), "kelvin", "K") mole = Unit.create(get_or_create_dimension(mol=1), "mole", "mol") mol = Unit.create(get_or_create_dimension(mol=1), "mol", "mol") # Molar has a scale of 10^3, since 1 M = 1 mol/l = 1000 mol/m^3 molar = Unit.create((mole/liter).dim, name="molar", dispname="M", scale=3) candle = Unit.create(get_or_create_dimension(candle=1), "candle", "cd") fundamental_units = [metre, meter, gram, second, amp, kelvin, mole, candle] {derived} {definitions} {base_units} {scaled_units} {powered_units} {additional_units} {all_units} class _Celsius: """ A dummy object to raise errors when ``celsius`` is used. The use of `celsius` can lead to ambiguities when mixed with temperatures in `kelvin`, so its use is no longer supported. See github issue #817 for details. """ error_text = ( "The unit 'celsius' is no longer supported to avoid" "ambiguities when mixed with absolute temperatures defined" "in Kelvin. Directly use 'kelvin' when you are only" "interested in temperature differences, and add the" "'zero_celsius' constant from the brian2.units.constants" "module if you want to convert a temperature from Celsius to" "Kelvin." ) def __mul__(self, other): raise TypeError(_Celsius.error_text) def __rmul__(self, other): raise TypeError(_Celsius.error_text) def __div__(self, other): raise TypeError(_Celsius.error_text) def __rdiv__(self, other): raise TypeError(_Celsius.error_text) def __pow__(self, other): raise TypeError(_Celsius.error_text) def __eq__(self, other): raise TypeError(_Celsius.error_text) def __neq__(self, other): raise TypeError(_Celsius.error_text) celsius = _Celsius() Unit.automatically_register_units = True for unit in itertools.chain(powered_units, scaled_units, base_units): standard_unit_register.add(unit) for unit in additional_units: additional_unit_register.add(unit) # fmt: on brian2-2.5.4/dev/tools/tests/000077500000000000000000000000001445201106100157515ustar00rootroot00000000000000brian2-2.5.4/dev/tools/tests/run_tests_cython.py000066400000000000000000000003301445201106100217310ustar00rootroot00000000000000''' Run all the Cython tests using pytest. Exits with error code 1 if a test failed. ''' import sys import brian2 if not brian2.test('cython'): # If the test fails, exit with a non-zero error code sys.exit(1) brian2-2.5.4/dev/tools/tests/run_tests_cython_long.py000066400000000000000000000004211445201106100227510ustar00rootroot00000000000000''' Run all the Cython tests (including tests that take a long time) using pytest. Exits with error code 1 if a test failed. ''' import sys import brian2 if not brian2.test('cython', long_tests=True): # If the test fails, exit with a non-zero error code sys.exit(1) brian2-2.5.4/dev/tools/tests/run_tests_numpy.py000066400000000000000000000003661445201106100216060ustar00rootroot00000000000000''' Run all the numpy tests using pytest. Exits with error code 1 if a test failed. ''' import sys import brian2 if not brian2.test('numpy', test_codegen_independent=False): # If the test fails, exit with a non-zero error code sys.exit(1) brian2-2.5.4/dev/tools/tests/run_tests_numpy_long.py000066400000000000000000000004171445201106100226220ustar00rootroot00000000000000''' Run all the numpy tests (including tests that take a long time) using pytest. Exits with error code 1 if a test failed. ''' import sys import brian2 if not brian2.test('numpy', long_tests=True): # If the test fails, exit with a non-zero error code sys.exit(1) brian2-2.5.4/dev/tools/windows_build_inplace.py000066400000000000000000000005631445201106100215310ustar00rootroot00000000000000''' This tool can be used to run setup.py inplace on Windows. ''' import os os.chdir('../../') if os.path.exists('brian2/synapses/cythonspikequeue.pyd'): os.remove('brian2/synapses/cythonspikequeue.pyd') if os.path.exists('brian2/synapses/cythonspikequeue.cpp'): os.remove('brian2/synapses/cythonspikequeue.cpp') os.system('python setup.py build_ext --inplace') brian2-2.5.4/docs_sphinx/000077500000000000000000000000001445201106100152125ustar00rootroot00000000000000brian2-2.5.4/docs_sphinx/.gitignore000066400000000000000000000000751445201106100172040ustar00rootroot00000000000000/_latexbuild /reference /examples /examples_images /tutorialsbrian2-2.5.4/docs_sphinx/_static/000077500000000000000000000000001445201106100166405ustar00rootroot00000000000000brian2-2.5.4/docs_sphinx/_static/.gitignore000066400000000000000000000000001445201106100206160ustar00rootroot00000000000000brian2-2.5.4/docs_sphinx/_static/brian-logo.png000066400000000000000000000465541445201106100214150ustar00rootroot00000000000000PNG  IHDRil EiCCPICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧ5bKGD pHYs  tIME $r IDATxiyS[/3=+f@$BWqJʒNٲd[:vHىs%r-M)v|e)9DmiKe(H`ٷ^sz@5yZ-1\R6Gd}u]c;5z -@p\?:9O[B=O"E s0W+ *h8@XbHD hD L`mA-@<;f"HӀ#2MH,U\.@lpck ps),Ϲ ,Esxg]h=(͢,_gw[Ɵ8404=;X9+gع0W 5Wվa=Z,sMonѾ;oUW+l,a߅SA1-՜sk;ff"oXZ@`]ްHhr\ $<ˊm@5U\Ism# ~+kn^1Ghlޔy??IY]VYxf-y8DuX'.pZN kA׵穁&"3xԆ\JxNU9 IӪu{-h@o}&b?OBzLRF7Q# xa_v'+:cZgSX01L JH wR+\-pZiߣ#/ehϯ_4@rjz0~p}{|O8ȱ0q'8g$MN3aZ qDHcX 8>OHjelN* Ci7;oNXΟ~-^.Y* ^g\@㴻S lHskђeƚ\J {/Y6(XT):vtDК$'%c=4Mdִy@01Ȃ]"b][ %=oܾ?w]n'sɇMC}/]{$0qɃh,̡v;p:R&635]i-NĴo9L;[!UXwLn"%k5+}s סR HLv}vM3Y1 l ~"!WIbpJ Ν3,Sl),\Zauʖ.8=m? -Gv r6i}R8$!v};%R-jxyWIK vkDՙM@yy7vz~'ݱF׼h0vM *L虧%榀:_ZdS٩љl,X7li+H24OYQb'l$LS7A&ڎw8@,+#vhObnW|Ƹu  wҮ(7}sxչ'FN:_pGF5H~wS5Iag؜F5PYYYe Ŭy%hb&WJMMQAr׿{X iS{'y?'Ic8ьy9ضs؋(m,CGw/~x./{֢G-al츐L!iH˲N. v]Im;AAhV@V~-T)GNˁ-U`Jq ǻL- =xfJ5۝n1S =1c]oyjƈ.ꅳ^9 ,Mb,2_ݨۚ &M'vEػM4$ShҀ$\ZxNGRztJRbUCW\a>Hr!{]GFT@B{?]]w{ݒp xY'xp(͟}.sgcGى`Y˲hkL,G-[ Sd[ZBZ@vZdi&İe$z=dM|Wr*[f 31z{_"R1\؍p +/=,<hAV]'PUPj͢ ` w5umRP^B ma1qv$IŽ٤C2,'r곚{$Ludz_%#/3G:[E,V5vBt"Ix .vA2%, $&a0knLے.A@ԁcg#ZQt Ȁ | Z,iXN3/c8o(Iuc `$uLbrwX /p+7:cS+>g_V/g vdz{=ql8ӱcs&ol?FvGiyݞ |iz.4YP[W*pR*!I:Ǐ>5`vA1p)i^L8 NTj)jbPP/s!?} bvgG^J7";f@OnlL3:nkqۑC4$/N,pim-Om@v6Mt@M^D'lЙĢ86|;8.ѹ,UY)0N%#J @y/#WǤCP*_D9,m NX1ƻXne)㦵|5T7H{ mO?^N Km_DZw)l=g4 Z$miak lnGEp{؊V[q+R7R}HDc 0;<v[;2 k)+'5})qkJk$D"Rb&w.u|s_Nžο50Vi,p^{큗ýq9^?Oβ'f $L$Q5b2]ƦY@a'ђ9`:Q9cj V$gHYBn'l s200 <`4֌Ȣ7@ 1َ0\ٔZ<$m5*E HvLX)2W<46 %;҅Nf4vVx#QlF}K^I\WqYGp^S'>=>BS)CB*@2 p-oߘK`kQyNS"IpԨuK %|`sfRv#w_cB,/;za93ӡ\DAVLK }bBMFoEhgr] {1-u;'Cn7o"BEtAyطj VfSL C~x;m ,R~_h+:_ % so `y_}vnĽz{5S96u=6Ƈ45:Z,j7 iUb1E  2@2%eg;f*I=w;TԹ6"e^II~I1>"9b@VV/||I%8]ƁFD׼I|&Tygұ)hp ][O+>"%^'J=PӎC&dAm~ح}(BܴlEXL\7BY/6xh^n)&-Ec qhY@2 Is ~T>@uZ]"i(,Pw(B9Bp`|xM$d>ߞ1hPTN5|<pJKIeMd2 {@G#V*}_5aDYZU{pϥ yLf/h.yT5iFs@uA"M)  2(UJ WIb@na#mMn5d +c k /~c`az -Lmn1y,p~Dզg*5k%hp>^ɁXY'ӽof2{k Y~^Mߏx:4Ho׻\zQ ѝti J >_T~W HPZɒRwucM"1rZ7.&%jzl2|߽}paI]HYy"dk K3aT(VߥkXHAD|%&q{Z1< _egs4KBZ<`Z8jekx6RzDF1PV)@ѧ cO7Ukj4*vׄ1Y Tlu&a) (tVVY,QVmQimiIS t8pj >4>{Ckj2TXҵ%H@('|2K~$CMd@::NGonϤ5L3K}`fۗ.xy,B%B::Y^}M\iNׅ; Fi_tKVm)DE`T4vU'eDt T'Pp?pvNwfXU°80U,fcԋX^ G,ZV1XMZ݇(6mA Z/ggmقgn8T,m^iUBU.oy>wb&Rwuªs.:Xyi^ɣ]ynDKX"J[hG\ 3Dv`/wǞ/*`àBb A Nh) =,9#x3)(-{{dcW d u#v4IU'"p0І 9y:5fA50碂|yA3f&tp9X;2-!+0Р 4ȏ -KU`7Ri`#)*WKDbZԫi^(j?*WP,҂踨UL=$UIphO \@V^?)3о20Mli@t$p2cL1[B7=3nM rF%*v8hMjF5RbCU;!(/_.LcV&UZ6ZW[tcbJTTtwkL 2 07l&_EuF3S`BQ9}}Xjl{)?}y"LfX1̲]j*AN̷vvK4XV$3cmfʼn4٭XGy s -nu/q5%P.0q?_TD1s_Q/ќ?Bc$?eb_ ~ʌރn]H 1f(`;s<5M^v\*A|8Xlj%D<mdN,!* *m.08S l7@ɡE)LjxfB. d. ̗3%< AGu_t/f_hbUf;=C{4K)a+5tLfe̿H+Ҭ:攕8qO:hd7pˣXn̊}e~jNbc $;LLE-n)cQ'Dn 2N gGC,Ck%+Qa%Á ,`ĕ,6j_"+ី=Ǵsp]Ym bANg5ӎ҂x5VS? .{-έxGd?28 N\V.(DQCm{ү~wlLFz), ֍Z֚': L9= gk:Co 7"sUʌm,ȵZBU}W3#GS9OyXZMt 0N%PIR9յCMrf7TF,ΝL`/P]NDP(=X`'M֜,;Mߢً^,}~_kyS5E;0p 9kBV,@nj H? ; 뵦jU~f\dV&y[ٽV\ Te t\N;245@ڜk 5 _|-Gu"a^82}<ո:yvtme ([nuT?>1D x̞w|/r`zT0tuG."pY#}~冂ԳM:Vgm -jSB lf|g0/͚3]](f I3fF2UoAMs7֚j*'q6lСbT=OCk!5ubիtk֣Ԡ.cIIaqdv;'# p~W=6 [;1q]݀N@)RLw?0L68ݒ5BC>W]N  ,Q fD"W&HJ^ UJU FD1]@ɣ%=;h_83T4Xu1'_~~k˝58taZceEʴWj͟4o FÓ#*vRuk5sdz+vttԡ@ {{vdмJ(4}֤ %<0 EhZ HXN\/Ωby/0?.89Ghl6];mK qS_7ہD {p?fiNQߵZ1}\,k Soo'bRWf.U4] `5OVPA:JZzPoX,3C%7k28=gn-Pd3͝CӶ%V1:6QڸLSCLIQ |u[`$o^,WM(܌>J̳ AӯTyw~E?i":0cO`q+aSS@Cu:n=h$G vߛ o7 N~;};Ɓ/8w zR{MRβIo{I0+cϦhJˋ 4@4Ŷ~j_jCW_C&ڡotE g[kp3 T5Iw7?S'FJW7S)ef|Y .R1ѼVufSoC-Mjֵ ZZBʬk쪨vtXN7qm9,#rjg9ggm[fBbtժs+%+`$)мswrwaݴS߻ &Aj?z^bi?夢%p[awj0aBZe] :=,F}zu6*|`tmNnvA\NTt3)cڶ-'YvAA͹Xp9q#uVU3l_UUn@hF.Yϝ7w^ qmȻűj1ZҔptc _]6yh̒eL4P 9;K׸ͬSG5M. n3J6O!RA}kzv&O?ʹN`B.KzweGϛmŨXV"֜(G5ݽ9羧ܻC~sb*=~YD)G&RYf5F>S,ZsZ$V栞TF>y`s 5{Iq7=M J$T4!:-xQh}s[;S n*jpf wHZA&>ve"T]91jPR -2 L+blfT̀ZU`rNisQpDw|mE[:fZr뵎i~Ҟgr`W@T:C\A.[3Ӂ\l>߁!HJ/Ӵڀ -սhlM}6o⺔7t55}uw>WE7fޜIGhAӪ:[wM= ~22|aRly hi-XH{S~LG6ool=#M[&&z(XhAj1_{s`TM,VVs:mb1Ǫ6fj4;޴6ڨ3Y 괡_S~OU`~ nb%Z 1Pv1`gxp54ǚ*媎 He7wpιÁ%bƯַ]a`R)M|7EuTz&C}bi&q LPׄ:$1 #EON.7ઙ:fVɧ:^%@1%:POEYhѝx:p\`pOtzfrw6aEX[iԚWg߈>k:<;Ɣ4oFidU=BYȌ&x3&A 8 ;K__z3HMFtKjVh~_ =:|i!wwhlrǚXJJ| Yv8+PYb%AԢ8 RѼ3J=҃Aږf 攙q"/" ߲@OA2Oimi;3}e4uo,ιZejbe _~]oBT{ьGF*skr>HQjL$@8z0xkNP #v,_"fҦh_M,^0ESj"W[BCQ r(CwV1]&+Bjy>LZIJ`-ϫZ3_9L8V1bK9ejbx;*%2%8 %?'JPu3XAtMwiU_RlM>yl a,Sb!,Ѭٵ:`eT0&31`ύ6CV܂@+@F/v zHf*=0 aۨ>6_KDfW܍|LIfs cC]bn*}8&QoZm$]vzqiF# {=kSB8oe@'gX ̔;m VPhmT7?i+ٮ;Ц>LCU>^{`Ϩ< Y^ CtI*᯦>պ"pw|ˀEj 5Ls%h[uӶ]tBmLb}-Fh=>k]1p)YQ]#j6>gp(J̘"Đi۷HT4`3zH,QY!t~MԵ~dHU2oѾyt!3Ӎ`W0>uw?  |wUc/{~藔$ e NbѸL8Zv-t -mqռKmb]﹏nBԭtf850*[Wi'.p$6MzdҼL XDÁj l٧?Ct>]9sxkNeiekprBe~snx S<伹 ]6󜖨z3:̭FhZ]ɛ_i XkE>뉹Z,:6f]< R/$k4rF`\Z6jĊiKjp * RdPB@-Hʑr}lx4SA `ih| M<tlD5sU KUsK\ky𙿢Nk>V׾V7&^hF,ʑa;w1Z@5DA.0Tvf'8uj꫻7'U)-zv5 YtF_HJ"cQ3E[RF]MK_% ҬiIdVnA(Q'dF[R$pv`q8ǿz#(ѩEuNg+ 8Nz`I ,o-4u.g:>`n_9z\rff+6bm-ͫ 6{U1B̭@OwvrӽY&yL, N&VqiG0ޞsANyIdp5.yEh(zCh(!Y%Q_x/[]SU(#"n-7XojsTI .-j~gu?Lm☀%,eTDznAbզIg-ͮ:b|  Dkvq-Ъ rVDVp?0}&^ucb>5jj_W* aQw (_9xUj>2~#fY"~Z*B gU'uh'}j>[&SV.PC[}  Y%彭WSk5Xh닫/l$Bҕƀ,5[^K{;+tFH10"tMZZCqNAJ࡜i'̚i+n_R$;/,k׷1jǭ]=4o{4|kh麜0yٍ*[QLXufk)Q7)7#l\9>#cAXcQ Ϻ FVLVCFZ _L mAxB.RS,CR8kFsJT2Y1ΐ }9KH6 69|vt3|vn`u' V %A~ [Ut;y!qD(4 4vۢXY*%0<@C~Uo]ʱ]Q4\|e5cRۥUJP&O>xEjGEC3d<{7U"E$dkCSpu|x _cC5{|9.=)p hVJV@O 6F y8l RjCPP-p=pT;R,s!O{oGR0*߅ q!Iz 3$ eX[ϙY6| #p p$<1WH抈T R:4-mt-È (Gja8weL"Vs:u$ CZP ag_RS/=Z5 d$ cXJp}-K>J'+K(TJ9(;<z{3 "LX"j[,}Wg DaPHŒ21#0P)^8=pV.q…Fv6 <Ј@@IE`_} M;J~<29Ԝ$]lG$XuS7UߨXߴ0ZCՀPfF#`fX9Wjs˿_Er38B^ܸKbdl=)ʑh?eq6Ӟba:͊AGIG@ b$*E9XB8{NB"Q #PJԣ8D7j㳍GK29$4c@-,`QKnQ6:drH!Z, dl͸,IDAT)U4,\!Xyg%;mݎdIkE@ezyxvnCFeA$IXD,%I%A'"RF'K6׈ąjϭZ#3nStP+plJ) :&!$b)Y$e> stream xWێI }ﯨǙR캼"X%ohnK!3ڿcWwPu9eD"SK mpn$K%Cȭd!NDGe_RrDZ.E0Y%~8,<-w)M{dc}?5w#П,^Z܅p1 -nrOp/~N|(?}H%zx~7L|{|xPW> PC.Eꗈԭ {1xI9Vl>vGc1.GP%GO%$&UsŠG`xRp_K tş}<#"]j-CR;$N$$FE ]p\.]lop}c=b=cQc2sNwho%)RmTXǞׄ@')+rS1iGcH3'欲;yԬr5*I [dG%}5(ROYT nB&ǞPkkBd[4hnnީ[h!d3U[)x#k73P J -BP4U4-plxgPrlu kxt =F@YZHF5CX5!%/pZ1Df#=ؐ1BMG7+cNg1X"̐45uRϷk.)yL&WD2a>%/o~gA=(O'xSAreU^+H P"j606DBZѴi~Ýl4NPMFbXN1ŷ6Rڰ2knhդTNg247lNWA4jd*jkոlYTjH7i)P.7eM}eǧ>&ʐG 3CCr ;m1LfYN 0*Xts6_7`^BW|H3t@MA]]G |"k MoVy^;!neFWOhZzoFРZ+ sTܞ}[m^\.ڰOwG}#;cM^qPm|6 pėB 6S ϱYQ˰@1hV{Y觵PJ"'rJhAS]B@wxwϮ-[c{ٳB*,(<}SD{vfbrg1l endstream endobj 4 0 obj 1494 endobj 2 0 obj << /ExtGState << /a0 << /CA 1 /ca 1 >> >> /Font << /f-0-0 5 0 R >> >> endobj 6 0 obj << /Type /Page /Parent 1 0 R /MediaBox [ 0 0 513.37146 332.045074 ] /Contents 3 0 R /Group << /Type /Group /S /Transparency /I true /CS /DeviceRGB >> /Resources 2 0 R >> endobj 7 0 obj << /Length 8 0 R /Filter /FlateDecode /Length1 7312 >> stream xX{\UUk}>}8PQQhL2qDR{A6H`dfd8JiYMi3MuAtEX>`5iu?s>kko`C% :Hy`[s [Vt>4њ/n~ 3ZDj@InRn)]0~.e/,#R"D%|'q ]E IӝtΌ03 $ %2,OEo+<.C2 w%1;>NI>;>>nn>Xjd_Md)Z)q5њSռ'{ډXɡHw\t/ϻ<ɘ1{v~nF䨡k}Xa=ƈ ׇlq8?KRchX774<%.%>՟F_cqxHL]1Ўt 0cX[rzjn RSR# p)`7<1WI<$9;ee9$`#Gee㺙OϫqSn_a˷z==~z~9woJJ~~pZs薚gg̚p%խG̑9Nүb`B0zP+8\kqg/b[1Aĺ{:33/XVn?U5}'€hf&`%⚍/Ζ}5{yiS<՞ݫ軋6 m_N;wZ?GI+oȇayh8d{wf^=Am lE2gaY!n!]lTVp@JLLpTe\q 6dEcL1,O7;;.M\m\Yҵp)YJô9RtLjCFo֞<6nS$u5>L티^Ur PUpWwԯ AKeX"3Jn=ުx{Oz/6I}"W} e$1{V;;iIO59N%t^8Oز7cby.\)ח͇Z:H)RWm#G.dߎ9Y7Ԑ{rE>q7 8K-ғ4:>>rޡ0v},^N6@1[7P L^"ĸ f=n+9UpLfof_Ϭ#c #,vfXֲ68Y*-ʥ iT)=,4 m>>6C`KTٯeC6c m LapmB6/]ϮY[.Ǧh mcQN0YVns~9!/o4sr2=#Gff/c> l31.V.f,{3;6ƚ ߒj+fY_bkx$/zrl{?-\Ni@L)hxGX6"{̓{E{Kp~IN5 y!%V٫c&\䙚+/&R4%3w%|0,#)/,iCR#/%MKg!v|wUN|ds[o_^ښۼ=X/?TVKUҶDo5pӍ+;[ a0mmqV|oZXH[ƒ`/]/ |Gyڼ-v5fS|Cۭ#[#0~ǥlMʣ{-϶\򣯲7AiW/66,§-OPݕπ VTD< ӸT)FƀdF]B %xJd1T*uTVJ*BX)ױBXYTJkPn 1N-շ%xaGe}u b!'0N[1Znc[N Eu;x=j3=Sd{ܻ)Δ`SjQ45;8~;gaW=ݗOW*@|jzOS%_K$O.{'u׮{>@[Eey϶U7[?򝯌Uy炅Q=oNL;XǐP~ʯ..xI6\i.ô;9M':  ^/:6am ]?hBָPm7#BZ4s?M¡;<"] pM#\pl4L<<}^tltxt & n7+$9!*xZ-'IdPPM<[ʐ3}1>ƑqeW@  *A5MM֯rLsNs|fIb(תsl t_֖ܶN^dS/<<]RVIݟzu}KOź#v'' ৼӀAR'KupV'?sfS`nKrd_5* l$͑Sv~Wi;VݪY7ln\k`ܦYիt"GPJ?R+sZ=UIOaiW~}/VJ RZ7H230Ec"#xjKlwj+l=X V 8)*{4{? ]|XD$Ɍo z4qe c|v7="gY?@#0vF4g l+FgyW٫4or>jGXxy誓>QґrDOIa#,Z$Ga7leWBKiQUF^f'YFc ަ7$M,xKp~#rTEy_Fnɧ`L>%Xs.j9vA M$;,)Qlzja'/^V y 秠R?E^6qwgQ!NWp?Kݐ%iS04ƈ{uciL(+|ة(^5X^fFhcUr<j5EPK{7Xkdrkw."FK"_$*6jw)niԚSt9*a =^iXSH^e4Vn?"kكWp 'A*IV*\&8~wV5jABtn y͎r4/h1К@G?6Qysͽ' :h͞KVi|МE7ٿ`{mصc R Rnpⵐ_G^NЏI_XY΂Hyr/r਼ʝTn1:Oa)NI8h=S{ˤuF^砜;iL"fvt6? k.4 $jjZ3pN[ɨoF :anw*a,"UXZ}R82wQ`#dxA7'+ٍXD<'Oili=vЋ'q:~$Cg3߼A=/=q{ zUxU*<}V4[[;o |COk'kOo3cU&?cFQG$ <$𠉭kU` x _`ʿ~},9{Y'6 bܳp!Ƨ>%p'=S;LNj^iη >ŷc nX6q \;㦍N)7:n|x! |!P)>൅X ׯ \7גk ^ b\CZ*!&_jX)B`UW•XG w;.;ީrwtnvcR %oĜȗ›S"pb (pX,X(g ,nuQL#Z|Dl#/Κg 3gg 8ff>ͧXƩJz\^)+q!b pO]r}.scq(7QlGa3/3x H7x f#ܘf8lh+ơ>4LI+09C€IvaəA1wcW! A1:Q#14!0GOW`@hDtBW1::!No&P4biR&! ws) A kaUaf_ Sx.y endstream endobj 8 0 obj 5215 endobj 9 0 obj << /Length 10 0 R /Filter /FlateDecode >> stream x]Ko0 >v HytB =4F=ώN#qSܺi=, ـr aTNf̽W wuùu\pݓ]|Parg}:qupFA,}kiڮ{ S|^=LZ2CΨ4mFAB|AՁiJ8Ndၹ.X_d΅sbLeZV5RZspEg=r"Eb8Vr1͹dns\BgSߋ_> endobj 5 0 obj << /Type /Font /Subtype /TrueType /BaseFont /GIMAIM+DejaVuSans /FirstChar 32 /LastChar 121 /FontDescriptor 11 0 R /Encoding /WinAnsiEncoding /Widths [ 317 0 0 0 0 0 0 0 0 0 0 0 0 0 317 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 631 0 774 0 0 0 0 0 862 748 0 0 0 0 634 0 0 0 0 0 0 0 0 0 0 0 500 0 612 0 0 0 615 0 0 633 277 0 579 0 974 633 611 634 0 411 520 392 633 591 0 0 591 ] /ToUnicode 9 0 R >> endobj 1 0 obj << /Type /Pages /Kids [ 6 0 R ] /Count 1 >> endobj 12 0 obj << /Creator (cairo 1.14.6 (http://cairographics.org)) /Producer (cairo 1.14.6 (http://cairographics.org)) >> endobj 13 0 obj << /Type /Catalog /Pages 1 0 R >> endobj xref 0 14 0000000000 65535 f 0000008418 00000 n 0000001609 00000 n 0000000015 00000 n 0000001586 00000 n 0000007979 00000 n 0000001718 00000 n 0000001945 00000 n 0000007254 00000 n 0000007277 00000 n 0000007685 00000 n 0000007708 00000 n 0000008483 00000 n 0000008611 00000 n trailer << /Size 14 /Root 13 0 R /Info 12 0 R >> startxref 8664 %%EOF brian2-2.5.4/docs_sphinx/advanced/custom_events.rst000066400000000000000000000142151445201106100224120ustar00rootroot00000000000000Custom events ============= Overview -------- In most simulations, a `NeuronGroup` defines a threshold on its membrane potential that triggers a spike event. This event can be monitored by a `SpikeMonitor`, it is used in synaptic interactions, and in integrate-and-fire models it also leads to the execution of one or more reset statements. Sometimes, it can be useful to define additional events, e.g. when an ion concentration in the cell crosses a certain threshold. This can be done with the custom events system in Brian, which is illustrated in this diagram. .. image:: custom_events.* You can see in this diagram that the source `NeuronGroup` has four types of events, called ``spike``, ``evt_other``, ``evt_mon`` and ``evt_run``. The event ``spike`` is the default event. It is triggered when you you include ``threshold='...'`` in a `NeuronGroup`, and has two potential effects. Firstly, when the event is triggered it causes the reset code to run, specified by ``reset='...'``. Secondly, if there are `Synapses` connected, it causes the ``on_pre`` on ``on_post`` code to run (depending if the `NeuronGroup` is presynaptic or postsynaptic for those `Synapses`). In the diagram though, we have three additional event types. We've included several event types here to make it clearer, but you could use the same event for different purposes. Let's start with the first one, ``evt_other``. To understand this, we need to look at the `Synapses` object in a bit more detail. A `Synapses` object has multiple *pathways* associated to it. By default, there are just two, called ``pre`` and ``post``. The ``pre`` pathway is activated by presynaptic spikes, and the ``post`` pathway by postsynaptic spikes. Specifically, the ``spike`` event on the presynaptic `NeuronGroup` triggers the ``pre`` pathway, and the ``spike`` event on the postsynaptic `NeuronGroup` triggers the ``post`` pathway. In the example in the diagram, we have created a new pathway called ``other``, and the ``evt_other`` event in the presynaptic `NeuronGroup` triggers this pathway. Note that we can arrange this however we want. We could have ``spike`` trigger the ``other`` pathway if we wanted to, or allow it to trigger both the ``pre`` and ``other`` pathways. We could also allow ``evt_other`` to trigger the ``pre`` pathway. See below for details on the syntax for this. The third type of event in the example is named ``evt_mon`` and this is connected to an `EventMonitor` which works exactly the same way as `SpikeMonitor` (which is just an `EventMonitor` attached by default to the event ``spike``). Finally, the fourth type of event in the example is named ``evt_run``, and this causes some code to be run in the `NeuronGroup` triggered by the event. To add this code, we call `NeuronGroup.run_on_event`. So, when you set ``reset='...'``, this is equivalent to calling `NeuronGroup.run_on_event` with the ``spike`` event. Details ------- Defining an event ~~~~~~~~~~~~~~~~~ This can be done with the ``events`` keyword in the `NeuronGroup` initializer:: group = NeuronGroup(N, '...', threshold='...', reset='...', events={'custom_event': 'x > x_th'}) In this example, we define an event with the name ``custom_event`` that is triggered when the ``x`` variable crosses the threshold ``x_th``. Note that you can define any number of custom events. Each event is defined by its name as the key, and its condition as the value of the dictionary. Recording events ~~~~~~~~~~~~~~~~ Custom events can be recorded with an `EventMonitor`:: event_mon = EventMonitor(group, 'custom_event') Such an `EventMonitor` can be used in the same way as a `SpikeMonitor` -- in fact, creating the `SpikeMonitor` is basically identical to recording the ``spike`` event with an `EventMonitor`. An `EventMonitor` is not limited to record the event time/neuron index, it can also record other variables of the model at the time of the event:: event_mon = EventMonitor(group, 'custom_event', variables['var1', 'var2']) Triggering `NeuronGroup` code ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If the event should trigger a series of statements (i.e. the equivalent of ``reset`` statements), this can be added by calling `~NeuronGroup.run_on_event`:: group.run_on_event('custom_event', 'x=0') Triggering synaptic pathways ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When neurons are connected by `Synapses`, the ``pre`` and ``post`` pathways are triggered by ``spike`` events on the presynaptic and postsynaptic `NeuronGroup` by default. It is possible to change which pathway is triggered by which event by providing an ``on_event`` keyword that either specifies which event to use for all pathways, or a specific event for each pathway (where non-specified pathways use the default ``spike`` event):: synapse_1 = Synapses(group, another_group, '...', on_pre='...', on_event='custom_event') The code above causes all pathways to be triggered by an event named ``custom_event`` instead of the default ``spike``. :: synapse_2 = Synapses(group, another_group, '...', on_pre='...', on_post='...', on_event={'pre': 'custom_event'}) In the code above, only the ``pre`` pathway is triggered by the ``custom_event`` event. We can also create new pathways and have them be triggered by custom events. For example:: synapse_3 = Synapses(group, another_group, '...', on_pre={'pre': '....', 'custom_pathway': '...'}, on_event={'pre': 'spike', 'custom_pathway': 'custom_event'}) In this code, the default ``pre`` pathway is still triggered by the ``spike`` event, but there is a new pathway called ``custom_pathway`` that is triggered by the ``custom_event`` event. Scheduling ~~~~~~~~~~ By default, custom events are checked after the spiking threshold (in the ``after_thresholds`` slots) and statements are executed after the reset (in the ``after_resets`` slots). The slot for the execution of custom event-triggered statements can be changed when it is added with the usual ``when`` and ``order`` keyword arguments (see :ref:`scheduling` for details). To change the time when the condition is checked, use `NeuronGroup.set_event_schedule`. brian2-2.5.4/docs_sphinx/advanced/custom_events.svg000066400000000000000000000426161445201106100224070ustar00rootroot00000000000000 image/svg+xml spike evt_other evt_mon evt_run NeuronGroup G spike NeuronGroup Synapses pre post other EventMonitor G.run_on_event brian2-2.5.4/docs_sphinx/advanced/functions.rst000066400000000000000000000435121445201106100215260ustar00rootroot00000000000000Functions ========= .. contents:: :local: :depth: 2 All equations, expressions and statements in Brian can make use of mathematical functions. However, functions have to be prepared for use with Brian for two reasons: 1) Brian is strict about checking the consistency of units, therefore every function has to specify how it deals with units; 2) functions need to be implemented differently for different code generation targets. Brian provides a number of default functions that are already prepared for use with numpy and C++ and also provides a mechanism for preparing new functions for use (see below). .. _default_functions: Default functions ----------------- The following functions (stored in the `DEFAULT_FUNCTIONS` dictionary) are ready for use: * Random numbers: ``rand`` (random numbers drawn from a uniform distribution between 0 and 1), ``randn`` (random numbers drawn from the standard normal distribution, i.e. with mean 0 and standard deviation 1), and ``poisson`` (discrete random numbers from a Poisson distribution with rate parameter :math:`\lambda`) * Elementary functions: ``sqrt``, ``exp``, ``log``, ``log10``, ``abs``, ``sign`` * Trigonometric functions: ``sin``, ``cos``, ``tan``, ``sinh``, ``cosh``, ``tanh``, ``arcsin``, ``arccos``, ``arctan`` * Functions for improved numerical accuracy: ``expm1`` (calculates ``exp(x) - 1``, more accurate for ``x`` close to 0), ``log1p`` (calculates ``log(1 + x)``, more accurate for ``x`` close to 0), and ``exprel`` (calculates ``(exp(x) - 1)/x``, more accurate for ``x`` close to 0, and returning 1.0 instead of ``NaN`` for ``x == 0`` * General utility functions: ``clip``, ``floor``, ``ceil`` Brian also provides a special purpose function ``int``, which can be used to convert an expression or variable into an integer value. This is especially useful for boolean values (which will be converted into 0 or 1), for example to have a conditional evaluation as part of an equation or statement which sometimes allows to circumvent the lack of an ``if`` statement. For example, the following reset statement resets the variable `v` to either `v_r1` or `v_r2`, depending on the value of `w`: ``'v = v_r1 * int(w <= 0.5) + v_r2 * int(w > 0.5)'`` Finally, the function `~brian2.core.functions.timestep` is a function that takes a time and the length of a time step as an input and returns an integer corresponding to the respective time step. The advantage of using this function over a simple division is that it slightly shifts the time before dividing to avoid floating point issues. This function is used as part of the :doc:`../user/refractoriness` mechanism. .. _user_functions: User-provided functions ----------------------- Python code generation ~~~~~~~~~~~~~~~~~~~~~~ If a function is only used in contexts that use Python code generation, preparing a function for use with Brian only means specifying its units. The simplest way to do this is to use the `check_units` decorator:: @check_units(x1=meter, y1=meter, x2=meter, y2=meter, result=meter) def distance(x1, y1, x2, y2): return sqrt((x1 - x2)**2 + (y1 - y2)**2) Another option is to wrap the function in a `Function` object:: def distance(x1, y1, x2, y2): return sqrt((x1 - x2)**2 + (y1 - y2)**2) # wrap the distance function distance = Function(distance, arg_units=[meter, meter, meter, meter], return_unit=meter) The use of Brian's unit system has the benefit of checking the consistency of units for every operation but at the expense of performance. Consider the following function, for example:: @check_units(I=amp, result=Hz) def piecewise_linear(I): return clip((I-1*nA) * 50*Hz/nA, 0*Hz, 100*Hz) When Brian runs a simulation, the state variables are stored and passed around without units for performance reasons. If the above function is used, however, Brian adds units to its input argument so that the operations inside the function do not fail with dimension mismatches. Accordingly, units are removed from the return value so that the function output can be used with the rest of the code. For better performance, Brian can alter the namespace of the function when it is executed as part of the simulation and remove all the units, then pass values without units to the function. In the above example, this means making the symbol ``nA`` refer to ``1e-9`` and ``Hz`` to ``1``. To use this mechanism, add the decorator `implementation` with the ``discard_units`` keyword:: @implementation('numpy', discard_units=True) @check_units(I=amp, result=Hz) def piecewise_linear(I): return clip((I-1*nA) * 50*Hz/nA, 0*Hz, 100*Hz) Note that the use of the function *outside of simulation runs* is not affected, i.e. using ``piecewise_linear`` still requires a current in Ampere and returns a rate in Hertz. The ``discard_units`` mechanism does not work in all cases, e.g. it does not work if the function refers to units as ``brian2.nA`` instead of ``nA``, if it uses imports inside the function (e.g. ``from brian2 import nA``), etc. The ``discard_units`` can also be switched on for all functions without having to use the `implementation` decorator by setting the `codegen.runtime.numpy.discard_units` preference. Other code generation targets ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To make a function available for other code generation targets (e.g. C++), implementations for these targets have to be added. This can be achieved using the `implementation` decorator. The form of the code (e.g. a simple string or a dictionary of strings) necessary is target-dependent, for C++ both options are allowed, a simple string will be interpreted as filling the ``'support_code'`` block. Note that ``'cpp'`` is used to provide C++ implementations. An implementation for the C++ target could look like this:: @implementation('cpp', ''' double piecewise_linear(double I) { if (I < 1e-9) return 0; if (I > 3e-9) return 100; return (I/1e-9 - 1) * 50; } ''') @check_units(I=amp, result=Hz) def piecewise_linear(I): return clip((I-1*nA) * 50*Hz/nA, 0*Hz, 100*Hz) Alternatively, `FunctionImplementation` objects can be added to the `Function` object. The same sort of approach as for C++ works for Cython using the ``'cython'`` target. The example above would look like this:: @implementation('cython', ''' cdef double piecewise_linear(double I): if I<1e-9: return 0.0 elif I>3e-9: return 100.0 return (I/1e-9-1)*50 ''') @check_units(I=amp, result=Hz) def piecewise_linear(I): return clip((I-1*nA) * 50*Hz/nA, 0*Hz, 100*Hz) Dependencies between functions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The code generation mechanism for user-defined functions only adds the source code for a function when it is necessary. If a user-defined function refers to another function in its source code, it therefore has to explicitly state this dependency so that the code of the dependency is added as well:: @implementation('cpp',''' double rectified_linear(double x) { return clip(x, 0, INFINITY); }''', dependencies={'clip': DEFAULT_FUNCTIONS['clip']} ) @check_units(x=1, result=1) def rectified_linear(x): return np.clip(x, 0, np.inf) .. note:: The dependency mechanism is unnecessary for the ``numpy`` code generation target, since functions are defined as actual Python functions and not as code given in a string. Additional compiler arguments ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If the code for a function needs additional compiler options to work, e.g. to link to an external library, these options can be provided as keyword arguments to the ``@implementation`` decorator. E.g. to link C++ code to the ``foo`` library which is stored in the directory ``/usr/local/foo``, use:: @implementation('cpp', '...', libraries=['foo'], library_dirs=['/usr/local/foo']) These arguments can also be used to refer to external source files, see :ref:`below `. Equivalent arguments can also be set as global :doc:`preferences` in which case they apply to all code and not only to code referring to the respective function. Note that in C++ standalone mode, all files are compiled together, and therefore the additional compiler arguments provided to functions are always combined with the preferences into a common set of settings that is applied to all code. The list of currently supported additional arguments (for further explications, see the respective :doc:`preferences` and the Python documentation of the `distutils.core.Extension` class): ======================== ============== ====== keyword C++ standalone Cython ======================== ============== ====== ``headers`` ✓ ❌ ``sources`` ✓ ✓ ``define_macros`` ✓ ❌ ``libraries`` ✓ ✓ ``include_dirs`` ✓ ✓ ``library_dirs`` ✓ ✓ ``runtime_library_dirs`` ✓ ✓ ======================== ============== ====== Arrays vs. scalar values in user-provided functions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Equations, expressions and abstract code statements are always implicitly referring to all the neurons in a `NeuronGroup`, all the synapses in a `Synapses` object, etc. Therefore, function calls also apply to more than a single value. The way in which this is handled differs between code generation targets that support vectorized expressions (e.g. the ``numpy`` target) and targets that don't (e.g. the ``cpp_standalone`` mode). If the code generation target supports vectorized expressions, it will receive an array of values. For example, in the ``piecewise_linear`` example above, the argument ``I`` will be an array of values and the function returns an array of values. For code generation without support for vectorized expressions, all code will be executed in a loop (over neurons, over synapses, ...), the function will therefore be called several times with a single value each time. In both cases, the function will only receive the "relevant" values, meaning that if for example a function is evaluated as part of a reset statement, it will only receive values for the neurons that just spiked. .. _function_vectorisation: Functions with context-dependent return values ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When using the ``numpy`` target, functions have to return an array of values (e.g. one value for each neuron). In some cases, the number of values to return cannot be deduced from the function's arguments. Most importantly, this is the case for random numbers: a call to `rand()` has to return one value for each neuron if it is part of a neuron's equations, but only one value for each neuron that spiked during the time step if it is part of the reset statement. Such function are said to "auto vectorise", which means that their implementation receives an additional array argument ``_vectorisation_idx``; the length of this array determines the number of values the function should return. This argument is also provided to functions for other code generation targets, but in these cases it is a single value (e.g. the index of the neuron), and is currently ignored. To enable this property on a user-defined function, you'll currently have to manually create a `Function` object:: def exponential_rand(l, _vectorisation_idx): '''Generate a number from an exponential distribution using inverse transform sampling''' uniform = np.random.rand(len(_vectorisation_idx)) return -(1/l)*np.log(1 - uniform) exponential_rand = Function(exponential_rand, arg_units=[1], return_unit=1, stateless=False, auto_vectorise=True) Implementations for other code generation targets can then be added using the `~FunctionImplementationContainer.add_implementation` mechanism:: cpp_code = ''' double exponential_rand(double l, int _vectorisation_idx) { double uniform = rand(_vectorisation_idx); return -(1/l)*log(1 - uniform); } ''' exponential_rand.implementations.add_implementation('cpp', cpp_code, dependencies={'rand': DEFAULT_FUNCTIONS['rand'], 'log': DEFAULT_FUNCTIONS['log']}) Note that by referring to the `rand` function, the new random number generator will automatically generate reproducible random numbers if the `seed` function is use to set its seed. Restoring the random number state with `restore` will have the expected effect as well. Additional namespace ~~~~~~~~~~~~~~~~~~~~ Some functions need additional data to compute a result, e.g. a `TimedArray` needs access to the underlying array. For the ``numpy`` target, a function can simply use a reference to an object defined outside the function, there is no need to explicitly pass values in a namespace. For the other code language targets, values can be passed in the ``namespace`` argument of the `implementation` decorator or the `~brian2.core.functions.FunctionImplementationContainer.add_implementation` method. The namespace values are then accessible in the function code under the given name, prefixed with ``_namespace``. Note that this mechanism should only be used for numpy arrays or general objects (e.g. function references to call Python functions from Cython code). Scalar values should be directly included in the function code, by using a "dynamic implemention" (see `~brian2.core.functions.FunctionImplementationContainer.add_dynamic_implementation`). See `TimedArray` and `BinomialFunction` for examples that use this mechanism. Data types ~~~~~~~~~~ By default, functions are assumed to take any type of argument, and return a floating point value. If you want to put a restriction on the type of an argument, or specify that the return type should be something other than float, either declare it as a `Function` (and see its documentation on specifying types) or use the `declare_types` decorator, e.g.:: @check_units(a=1, b=1, result=1) @declare_types(a='integer', result='highest') def f(a, b): return a*b This is potentially important if you have functions that return integer or boolean values, because Brian's code generation optimisation step will make some potentially incorrect simplifications if it assumes that the return type is floating point. .. _external_sources: External source files ~~~~~~~~~~~~~~~~~~~~~ Code for functions can also be provided via external files in the target language. This can be especially useful for linking to existing code without having to include it a second time in the Python script. For C++-based code generation targets (i.e. the C++ standalone mode), the external code should be in a file that is provided as an argument to the ``sources`` keyword, together with a header file whose name is provided to ``headers`` (see the note for the `codegen.cpp.headers` preference about the necessary format). Since the main simulation code is compiled and executed in a different directory, you should also point the compiler towards the directory of the header file via the ``include_dirs`` keyword. For the same reason, use an absolute path for the source file. For example, the ``piecewise_linear`` function from above can be implemented with external files as follows: .. code-block:: cpp //file: piecewise_linear.h double piecewise_linear(double); .. code-block:: cpp //file: piecewise_linear.cpp double piecewise_linear(double I) { if (I < 1e-9) return 0; if (I > 3e-9) return 100; return (I/1e-9 - 1) * 50; } .. code:: # Python script # Get the absolute directory of this Python script, the C++ files are # expected to be stored alongside of it import os current_dir = os.path.abspath(os.path.dirname(__file__)) @implementation('cpp', '// all code in piecewise_linear.cpp', sources=[os.path.join(current_dir, 'piecewise_linear.cpp')], headers=['"piecewise_linear.h"'], include_dirs=[current_dir]) @check_units(I=amp, result=Hz) def piecewise_linear(I): return clip((I-1*nA) * 50*Hz/nA, 0*Hz, 100*Hz) For Cython, the process is very similar (see the `Cython documentation `_ for general information). The name of the header file does not need to be specified, it is expected to have the same name as the source file (except for the ``.pxd`` extension). The source and header files will be automatically copied to the cache directory where Cython files are compiled, they therefore have to be imported as top-level modules, regardless of whether the executed Python code is itself in a package or module. A Cython equivalent of above's C++ example can be written as: .. code-block:: cython # file: piecewise_linear.pxd cdef double piecewise_linear(double) .. code-block:: cython # file: piecewise_linear.pyx cdef double piecewise_linear(double I): if I<1e-9: return 0.0 elif I>3e-9: return 100.0 return (I/1e-9-1)*50 .. code:: # Python script # Get the absolute directory of this Python script, the Cython files # are expected to be stored alongside of it import os current_dir = os.path.abspath(os.path.dirname(__file__)) @implementation('cython', 'from piecewise_linear cimport piecewise_linear', sources=[os.path.join(current_dir, 'piecewise_linear.pyx')]) @check_units(I=amp, result=Hz) def piecewise_linear(I): return clip((I-1*nA) * 50*Hz/nA, 0*Hz, 100*Hz) brian2-2.5.4/docs_sphinx/advanced/how_brian_works.rst000066400000000000000000000154251445201106100227150ustar00rootroot00000000000000How Brian works =============== In this section we will briefly cover some of the internals of how Brian works. This is included here to understand the general process that Brian goes through in running a simulation, but it will not be sufficient to understand the source code of Brian itself or to extend it to do new things. For a more detailed view of this, see the documentation in the :doc:`../developer/index`. Clock-driven versus event-driven -------------------------------- Brian is a clock-driven simulator. This means that the simulation time is broken into an equally spaced time grid, 0, dt, 2*dt, 3*dt, .... At each time step t, the differential equations specifying the models are first integrated giving the values at time t+dt. Spikes are generated when a condition such as ``v>vt`` is satisfied, and spikes can only occur on the time grid. The advantage of clock driven simulation is that it is very flexible (arbitrary differential equations can be used) and computationally efficient. However, the time grid approximation can lead to an overestimate of the amount of synchrony that is present in a network. This is usually not a problem, and can be managed by reducing the time step dt, but it can be an issue for some models. Note that the inaccuracy introduced by the spike time approximation is of order O(dt), so the total accuracy of the simulation is of order O(dt) per time step. This means that in many cases, there is no need to use a higher order numerical integration method than forward Euler, as it will not improve the order of the error beyond O(dt). See :doc:`state_update` for more details of numerical integration methods. Some simulators use an event-driven method. With this method, spikes can occur at arbitrary times instead of just on the grid. This method can be more accurate than a clock-driven simulation, but it is usually substantially more computationally expensive (especially for larger networks). In addition, they are usually more restrictive in terms of the class of differential equations that can be solved. For a review of some of the simulation strategies that have been used, see `Brette et al. 2007 `_. Code overview ------------- The user-visible part of Brian consists of a number of objects such as `NeuronGroup`, `Synapses`, `Network`, etc. These are all written in pure Python and essentially work to translate the user specified model into the computational engine. The end state of this translation is a collection of short blocks of code operating on a namespace, which are called in a sequence by the `Network`. Examples of these short blocks of code are the "state updaters" which perform numerical integration, or the synaptic propagation step. The namespaces consist of a mapping from names to values, where the possible values can be scalar values, fixed-length or dynamically sized arrays, and functions. Syntax layer ------------ The syntax layer consists of everything that is independent of the way the final simulation is computed (i.e. the language and device it is running on). This includes things like `NeuronGroup`, `Synapses`, `Network`, `Equations`, etc. The user-visible part of this is documented fully in the :doc:`../user/index` and the :doc:`../advanced/index`. In particular, things such as the analysis of equations and assignment of numerical integrators. The end result of this process, which is passed to the computational engine, is a specification of the simulation consisting of the following data: * A collection of variables which are scalar values, fixed-length arrays, dynamically sized arrays, and functions. These are handled by `Variable` objects detailed in :doc:`../developer/variables_indices`. Examples: each state variable of a `NeuronGroup` is assigned an `ArrayVariable`; the list of spike indices stored by a `SpikeMonitor` is assigned a `DynamicArrayVariable`; etc. * A collection of code blocks specified via an "abstract code block" and a template name. The "abstract code block" is a sequence of statements such as ``v = vr`` which are to be executed. In the case that say, ``v`` and ``vr`` are arrays, then the statement is to be executed for each element of the array. These abstract code blocks are either given directly by the user (in the case of neuron threshold and reset, and synaptic pre and post codes), or generated from differential equations combined with a numerical integrator. The template name is one of a small set (around 20 total) which give additional context. For example, the code block ``a = b`` when considered as part of a "state update" means execute that for each neuron index. In the context of a reset statement, it means execute it for each neuron index of a neuron that has spiked. Internally, these templates need to be implemented for each target language/device, but there are relatively few of them. * The order of execution of these code blocks, as defined by the `Network`. Computational engine -------------------- The computational engine covers everything from generating to running code in a particular language or on a particular device. It starts with the abstract definition of the simulation resulting from the syntax layer described above. The computational engine is described by a `Device` object. This is used for allocating memory, generating and running code. There are two types of device, "runtime" and "standalone". In runtime mode, everything is managed by Python, even if individual code blocks are in a different language. Memory is managed using numpy arrays (which can be passed as pointers to use in other languages). In standalone mode, the output of the process (after calling `Device.build`) is a complete source code project that handles everything, including memory management, and is independent of Python. For both types of device, one of the key steps that works in the same way is code generation, the creation of a compilable and runnable block of code from an abstract code block and a collection of variables. This happens in two stages: first of all, the abstract code block is converted into a code snippet, which is a syntactically correct block of code in the target language, but not one that can run on its own (it doesn't handle accessing the variables from memory, etc.). This code snippet typically represents the inner loop code. This step is handled by a `CodeGenerator` object. In some cases it will involve a syntax translation (e.g. the Python syntax ``x**y`` in C++ should be ``pow(x, y)``). The next step is to insert this code snippet into a template to form a compilable code block. This code block is then passed to a runtime `CodeObject`. In the case of standalone mode, this doesn't do anything, but for runtime devices it handles compiling the code and then running the compiled code block in the given namespace.brian2-2.5.4/docs_sphinx/advanced/index.rst000066400000000000000000000004511445201106100206200ustar00rootroot00000000000000Advanced guide ============== This section has additional information on details not covered in the :doc:`../user/index`. .. toctree:: :maxdepth: 2 functions preferences logging namespaces scheduling random custom_events state_update how_brian_works interface brian2-2.5.4/docs_sphinx/advanced/interface.rst000066400000000000000000000023421445201106100214520ustar00rootroot00000000000000Interfacing with external code ============================== Some neural simulations benefit from a direct connections to external libraries, e.g. to support real-time input from a sensor (but note that Brian currently does not offer facilities to assure real-time processing) or to perform complex calculations during a simulation run. If the external library is written in Python (or is a library with Python bindings), then the connection can be made either using the mechanism for :ref:`user_functions`, or using a :ref:`network operation `. In case of C/C++ libraries, only the :ref:`user_functions` mechanism can be used. On the other hand, such simulations can use the same user-provided C++ code to run with the :ref:`cpp_standalone` mode. In addition to that code, one generally needs to include additional header files and use compiler/linker options to interface with the external code. For this, several preferences can be used that will be taken into account for ``cython`` and the ``cpp_standalone`` device. These preferences are mostly equivalent to the respective keyword arguments for Python's `distutils.core.Extension` class, see the documentation of the `~brian2.codegen.cpp_prefs` module for more details. brian2-2.5.4/docs_sphinx/advanced/logging.rst000066400000000000000000000114241445201106100211410ustar00rootroot00000000000000Logging ======= Brian uses a logging system to display warnings and general information messages to the user, as well as writing them to a file with more detailed information, useful for debugging. Each log message has one of the following "log levels": ``ERROR`` Only used when an exception is raised, i.e. an error occurs and the current operation is interrupted. *Example:* You use a variable name in an equation that Brian does not recognize. ``WARNING`` Brian thinks that something is most likely a bug, but it cannot be sure. *Example:* You use a `Synapses` object without any synapses in your simulation. ``INFO`` Brian wants to make the user aware of some automatic choice that it did for the user. *Example:* You did not specify an integration ``method`` for a `NeuronGroup` and therefore Brian chose an appropriate method for you. ``DEBUG`` Additional information that might be useful when a simulation is not working as expected. *Example:* The integration timestep used during the simulation. ``DIAGNOSTIC`` Additional information useful when tracking down bugs in Brian itself. *Example:* The generated code for a `CodeObject`. By default, all messages with level ``DEBUG`` or above are written to the log file and all messages of level ``INFO`` and above are displayed on the console. To change what messages are displayed, see below. .. note:: By default, the log file is deleted after a successful simulation run, i.e. when the simulation exited without an error. To keep the log around, set the `logging.delete_log_on_exit` preference to ``False``. .. _logging_and_multiprocessing: Logging and multiprocessing --------------------------- Brian's logging system is not designed for multiple parallel Brian processes started via Python's `multiprocessing` module (see the :ref:`multiprocessing examples `). Log messages that get printed from different processes to the console are not printed in a well-defined order and do not contain any indication about which processes they are coming from. You might therefore consider using e.g. `BrianLogger.log_level_error()` to only show error messages before starting the processes and avoid cluttering your console with warning and info messages. To avoid issues when multiple processes try to log to the same log file, file logging is automatically switched off for all processes except for the initial process. If you need a file log for sub-processes, you can call `BrianLogger.initialize()` in each sub-process. This way, each process will log to its own file. Showing/hiding log messages --------------------------- If you want to change what messages are displayed on the console, you can call a method of the method of `BrianLogger`:: BrianLogger.log_level_debug() # now also display debug messages It is also possible to suppress messages for certain sub-hierarchies by using `BrianLogger.suppress_hierarchy`:: # Suppress code generation messages on the console BrianLogger.suppress_hierarchy('brian2.codegen') # Suppress preference messages even in the log file BrianLogger.suppress_hierarchy('brian2.core.preferences', filter_log_file=True) Similarly, messages ending in a certain name can be suppressed with `BrianLogger.suppress_name`:: # Suppress resolution conflict warnings BrianLogger.suppress_name('resolution_conflict') These functions should be used with care, as they suppresses messages independent of the level, i.e. even warning and error messages. Preferences ----------- You can also change details of the logging system via Brian's :doc:`preferences` system. With this mechanism, you can switch the logging to a file off completely (by setting `logging.file_log` to ``False``) or have it log less messages (by setting `logging.file_log_level` to a level higher than ``DEBUG``). To debug details of the code generation system, you can also set `logging.file_log_level` to ``DIAGNOSTIC``. Note that this will make the log file grow quickly in size. To prevent it from filling up the disk, it will only be allowed to grow up to a certain size. You can configure the maximum file size with the `logging.file_log_max_size` preference. For a list of all preferences related to logging, see the documentation of the `brian2.utils.logger` module. .. warning:: Most of the logging preferences are only taken into account during the initialization of the logging system which takes place as soon as `brian2` is imported. Therefore, if you use e.g. `prefs.logging.file_log = False` in your script, this will not have the intended effect! To make sure these preferences are taken into account, call `BrianLogger.initialize` after setting the preferences. Alternatively, you can set the preferences in a file (see :doc:`preferences`). brian2-2.5.4/docs_sphinx/advanced/namespaces.rst000066400000000000000000000044751445201106100216420ustar00rootroot00000000000000Namespaces ========== `Equations` can contain references to external parameters or functions. During the initialisation of a `NeuronGroup` or a `Synapses` object, this *namespace* can be provided as an argument. This is a group-specific namespace that will only be used for names in the context of the respective group. Note that units and a set of standard functions are always provided and should not be given explicitly. This namespace does not necessarily need to be exhaustive at the time of the creation of the `NeuronGroup`/`Synapses`, entries can be added (or modified) at a later stage via the `namespace` attribute (e.g. ``G.namespace['tau'] = 10*ms``). At the point of the call to the `Network.run` namespace, any group-specific namespace will be augmented by the "run namespace". This namespace can be either given explicitly as an argument to the `~Network.run` method or it will be taken from the locals and globals surrounding the call. A warning will be emitted if a name is defined in more than one namespace. To summarize: an external identifier will be looked up in the context of an object such as `NeuronGroup` or `Synapses`. It will follow the following resolution hierarchy: 1. Default unit and function names. 2. Names defined in the explicit group-specific namespace. 3. Names in the run namespace which is either explicitly given or the implicit namespace surrounding the run call. Note that if you completely specify your namespaces at the `Group` level, you should probably pass an empty dictionary as the namespace argument to the `~Network.run` call -- this will completely switch off the "implicit namespace" mechanism. The following three examples show the different ways of providing external variable values, all having the same effect in this case:: # Explicit argument to the NeuronGroup G = NeuronGroup(1, 'dv/dt = -v / tau : 1', namespace={'tau': 10*ms}) net = Network(G) net.run(10*ms) # Explicit argument to the run function G = NeuronGroup(1, 'dv/dt = -v / tau : 1') net = Network(G) net.run(10*ms, namespace={'tau': 10*ms}) # Implicit namespace from the context G = NeuronGroup(1, 'dv/dt = -v / tau : 1') net = Network(G) tau = 10*ms net.run(10*ms) External variables are free to change between runs (but not during one run), the value at the time of the `run` call is used in the simulation. brian2-2.5.4/docs_sphinx/advanced/preferences.rst000066400000000000000000000033211445201106100220110ustar00rootroot00000000000000Preferences =========== Brian has a system of global preferences that affect how certain objects behave. These can be set either in scripts by using the `prefs` object or in a file. Each preference looks like ``codegen.cpp.compiler``, i.e. dotted names. Accessing and setting preferences --------------------------------- Preferences can be accessed and set either keyword-based or attribute-based. The following are equivalent:: prefs['codegen.cpp.compiler'] = 'unix' prefs.codegen.cpp.compiler = 'unix' Using the attribute-based form can be particulary useful for interactive work, e.g. in ipython, as it offers autocompletion and documentation. In ipython, ``prefs.codegen.cpp?`` would display a docstring with all the preferences available in the ``codegen.cpp`` category. Preference files ---------------- Preferences are stored in a hierarchy of files, with the following order (each step overrides the values in the previous step but no error is raised if one is missing): * The user default are stored in ``~/.brian/user_preferences`` (which works on Windows as well as Linux). The ``~`` symbol refers to the user directory. * The file ``brian_preferences`` in the current directory. The preference files are of the following form:: a.b.c = 1 # Comment line [a] b.d = 2 [a.b] b.e = 3 This would set preferences ``a.b.c=1``, ``a.b.d=2`` and ``a.b.e=3``. .. raw:: html
File setting all preferences to their default values .. document_brian_prefs:: :nolinks: :as_file: .. raw:: html
List of preferences ------------------- Brian itself defines the following preferences (including their default values): .. document_brian_prefs:: brian2-2.5.4/docs_sphinx/advanced/random.rst000066400000000000000000000104601445201106100207720ustar00rootroot00000000000000Random numbers ============== Brian provides two basic functions to generate random numbers that can be used in model code and equations: ``rand()``, to generate uniformly generated random numbers between 0 and 1, and ``randn()``, to generate random numbers from a standard normal distribution (i.e. normally distributed numbers with a mean of 0 and a standard deviation of 1). All other stochastic elements of a simulation (probabilistic connections, Poisson-distributed input generated by `PoissonGroup` or `PoissonInput`, differential equations using the noise term ``xi``, ...) will internally make use of these two basic functions. For :ref:`runtime`, random numbers are generated by `numpy.random.rand` and `numpy.random.randn` respectively, which uses a `Mersenne-Twister `_ pseudorandom number generator. When the ``numpy`` code generation target is used, these functions are called directly, but for ``cython``, Brian uses a internal buffers for uniformly and normally distributed random numbers and calls the numpy functions whenever all numbers from this buffer have been used. This avoids the overhead of switching between C code and Python code for each random number. For :ref:`cpp_standalone`, the random number generation is based on "randomkit", the same Mersenne-Twister implementation that is used by numpy. The source code of this implementation will be included in every generated standalone project. Seeding and reproducibility --------------------------- Runtime mode ~~~~~~~~~~~~ As explained above, :ref:`runtime` makes use of numpy's random number generator. In principle, using `numpy.random.seed` therefore permits reproducing a stream of random numbers. However, for ``cython``, Brian's buffer complicates the matter a bit: if a simulation sets numpy's seed, uses 10000 random numbers, and then resets the seed, the following 10000 random numbers (assuming the current size of the buffer) will come out of the pre-generated buffer before numpy's random number generation functions are called again and take into account the seed set by the user. Instead, users should use the `seed` function provided by Brian 2 itself, this will take care of setting numpy's random seed *and* empty Brian's internal buffers. This function also has the advantage that it will continue to work when the simulation is switched to standalone code generation (see below). Note that random numbers are not guaranteed to be reproducible across different code generation targets or different versions of Brian, especially if several sources of randomness are used in the same `CodeObject` (e.g. two noise variables in the equations of a `NeuronGroup`). This is because Brian does not guarantee the order of certain operations (e.g. should it first generate all random numbers for the first noise variable for all neurons, followed by the random numbers for the second noise variable for all neurons or rather first the random numbers for all noice variables of the first neuron, then for the second neuron, etc.) Since all random numbers are coming from the same stream of random numbers, the order of getting the numbers out of this stream matter. Standalone mode ~~~~~~~~~~~~~~~ For :ref:`cpp_standalone`, Brian's `seed` function will insert code to set the random number generator seed into the generated code. The code will be generated at the position where the `seed` call was made, allowing detailed control over the seeding. For example the following code would generate identical initial conditions every time it is run, but the noise generated by the ``xi`` variable would differ:: G = NeuronGroup(10, 'dv/dt = -v/(10*ms) + 0.1*xi/sqrt(ms) : 1') seed(4321) G.v = 'rand()' seed() run(100*ms) .. note:: In standalone mode, `seed` will not set numpy's random number generator. If you use random numbers in the Python script itself (e.g. to generate a list of synaptic connections that will be passed to the standalone code as a pre-calculated array), then you have to explicitly call `numpy.random.seed` yourself to make these random numbers reproducible. .. note:: Seeding *should* lead to reproducible random numbers even when using OpenMP with multiple threads (for repeated simulations with the same number of threads), but this has not been rigorously tested. Use at your own risk. brian2-2.5.4/docs_sphinx/advanced/scheduling.rst000066400000000000000000000073661445201106100216520ustar00rootroot00000000000000Custom progress reporting ========================= .. _custom_progress_reporting: Progress reporting ------------------ For custom progress reporting (e.g. graphical output, writing to a file, etc.), the ``report`` keyword accepts a callable (i.e. a function or an object with a ``__call__`` method) that will be called with four parameters: * ``elapsed``: the total (real) time since the start of the run * ``completed``: the fraction of the total simulation that is completed, i.e. a value between 0 and 1 * ``start``: The start of the simulation (in biological time) * ``duration``: the total duration (in biological time) of the simulation The function will be called every ``report_period`` during the simulation, but also at the beginning and end with ``completed`` equal to 0.0 and 1.0, respectively. For the C++ standalone mode, the same standard options are available. It is also possible to implement custom progress reporting by directly passing the code (as a multi-line string) to the ``report`` argument. This code will be filled into a progress report function template, it should therefore only contain a function body. The simplest use of this might look like:: net.run(duration, report='std::cout << (int)(completed*100.) << "% completed" << std::endl;') Examples of custom reporting ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ **Progress printed to a file** :: from brian2.core.network import TextReport report_file = open('report.txt', 'w') file_reporter = TextReport(report_file) net.run(duration, report=file_reporter) report_file.close() **"Graphical" output on the console** This needs a "normal" Linux console, i.e. it might not work in an integrated console in an IDE. Adapted from http://stackoverflow.com/questions/3160699/python-progress-bar :: import sys class ProgressBar(object): def __init__(self, toolbar_width=40): self.toolbar_width = toolbar_width self.ticks = 0 def __call__(self, elapsed, complete, start, duration): if complete == 0.0: # setup toolbar sys.stdout.write("[%s]" % (" " * self.toolbar_width)) sys.stdout.flush() sys.stdout.write("\b" * (self.toolbar_width + 1)) # return to start of line, after '[' else: ticks_needed = int(round(complete * self.toolbar_width)) if self.ticks < ticks_needed: sys.stdout.write("-" * (ticks_needed-self.ticks)) sys.stdout.flush() self.ticks = ticks_needed if complete == 1.0: sys.stdout.write("\n") net.run(duration, report=ProgressBar(), report_period=1*second) **"Standalone Mode" Text based progress bar on console** This needs a "normal" Linux console, i.e. it might not work in an integrated console in an IDE. Adapted from https://stackoverflow.com/questions/14539867/how-to-display-a-progress-indicator-in-pure-c-c-cout-printf :: set_device('cpp_standalone') report_func = ''' int remaining = (int)((1-completed)/completed*elapsed+0.5); if (completed == 0.0) { std::cout << "Starting simulation at t=" << start << " s for duration " << duration << " s"<"; else std::cout << " "; } std::cout << "] " << int(completed * 100.0) << "% completed. | "< v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. html_logo = '_static/brian-logo.png' # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'Brian2doc' # Suppress warnings about the mybinder badges suppress_warnings = ['image.nonlocal_uri'] # -- Options for LaTeX output -------------------------------------------------- latex_engine = 'xelatex' latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'Brian2.tex', 'Brian 2 Documentation', 'Brian authors', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'brian2', 'Brian 2 Documentation', ['Brian authors'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'Brian2', 'Brian 2 Documentation', 'Brian authors', 'Brian2', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' intersphinx_mapping = { 'https://docs.python.org/3': None, 'https://numpy.org/doc/stable': None, 'https://docs.scipy.org/doc/scipy': None, 'https://docs.sympy.org/dev/': None } autodoc_default_options = {'show-inheritance': True} doctest_global_setup = 'from brian2 import *' highlight_language = 'python' # instead of python3 (default for sphinx>=1.4) # Configure linking to github import sphinx sphinx_version = tuple(int(x) for x in sphinx.__version__.split('.')) if sphinx_version >= (4, 0, 0): extlinks = {'issue': ('https://github.com/brian-team/brian2/issues/%s', '# %s')} else: extlinks = {'issue': ('https://github.com/brian-team/brian2/issues/%s', '# ')} brian2-2.5.4/docs_sphinx/conftest.py000066400000000000000000000010301445201106100174030ustar00rootroot00000000000000# Do the equivalent of "from brian2 import *" for all doctests import pytest @pytest.fixture(autouse=True) def add_brian2(doctest_namespace): exec('from brian2 import *', doctest_namespace) import brian2 import numpy as np # Always use numpy for doctests brian2.prefs['codegen.target'] = 'numpy' # Print output changed in numpy 1.14, stick with the old format to # avoid doctest failures try: np.set_printoptions(legacy='1.13') except TypeError: pass # using a numpy version < 1.14 brian2-2.5.4/docs_sphinx/developer/000077500000000000000000000000001445201106100171775ustar00rootroot00000000000000brian2-2.5.4/docs_sphinx/developer/GSL.rst000066400000000000000000000302211445201106100203540ustar00rootroot00000000000000Solving differential equations with the GNU Scientific Library ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Conventionally, Brian generates its own code performing :doc:`../user/numerical_integration` according to the chosen algorithm (see the section on :doc:`codegen`). Another option is to let the differential equation solvers defined in the `GNU Scientific Library (GSL) `_ solve the given equations. In addition to offering a few extra integration methods, the GSL integrator comes with the option of having an adaptable timestep. The latter functionality can have benefits for the speed with which large simulations can be run. This is because it allows the use of larger timesteps for the overhead loops in Python, without losing the accuracy of the numerical integration at points where small timesteps are necessary. In addition, a major benefit of using the ODE solvers from GSL is that an estimation is performed on how wrong the current solution is, so that simulations can be performed with some confidence on accuracy. (Note however that the confidence of accuracy is based on estimation!) StateUpdateMethod ----------------- Translation of equations to abstract code +++++++++++++++++++++++++++++++++++++++++ The first part of Brian's code generation is the translation of equations to what we call 'abstract code'. In the case of Brian's stateupdaters so far, this abstract code describes the calculations that need to be done to update differential variables depending on their equations as is explained in the section on :doc:`../advanced/state_update`. In the case of preparing the equations for GSL integration this is a bit different. Instead of writing down the computations that have to be done to reach the new value of the variable after a time step, the equations have to be described in a way that GSL understands. The differential equations have to be defined in a function and the function is given to GSL. This is best explained with an example. If we have the following equations (taken from the adaptive threshold example): .. code-block:: none dv/dt = -v/(10*ms) : volt dvt/dt = (10*mV - vt)/(15*ms) : volt We would describe the equations to GSL as follows: .. code-block:: C v = y[0] vt = y[1] f[0] = -v/(10e-3) f[1] = (10e-3 - vt) Each differential variable gets an index. Its value at any time is saved in the ``y``-array and the derivatives are saved in the ``f``-array. However, doing this translation in the stateupdater would mean that Brian has to deal with variable descriptions that contain array accessing: something that for example sympy doesn't do. Because we still want to use Brian's existing parsing and checking mechanisms, we needed to find a way to describe the abstract code with only 'normal' variable names. Our solution is to replace the ``y[0]``, ``f[0]``, etc. with a 'normal' variable name that is later replaced just before the final code generation (in the `GSLCodeGenerator`). It has a tag and all the information needed to write the final code. As an example, the GSL abstract code for the above equations would be: .. code-block:: C v = _gsl_y0 vt = _gsl_y1 _gsl_f0 = -v/(10e-3) _gsl_f1 = (10e-3 - vt) In the `GSLCodeGenerator` these tags get replaced by the actual accessing of the arrays. Return value of the StateUpdateMethod +++++++++++++++++++++++++++++++++++++ So far, for each each code generation language (numpy, cython) there was just one set of rules of how to translate abstract code to real code, described in its respective `CodeObject` and `CodeGenerator`. If the target language is set to Cython, the stateupdater will use the `CythonCodeObject`, just like other objects such as the `StateMonitor`. However, to achieve the above decribed translations of the abstract code generated by the `StateUpdateMethod`, we need a special `CythonCodeObject` for the stateupdater alone (which at its turn can contain the special `CodeGenerator`), and this `CodeObject` should be selected based on the chosen `StateUpdateMethod`. In order to achieve `CodeObject` selection based on the chosen stateupdater, the `StateUpdateMethod` returns a class that can be called with an object, and the appropriate `CodeObject` is added as an attribute to the given object. The return value of this callable is the abstract code describing the equations in a language that makes sense to the `GSLCodeGenerator`. GSLCodeObject ------------- Each target language has its own `GSLCodeObject` that is derived from the already existing code object of its language. There are only minimal changes to the already existing code object: * Overwrite ``stateupate`` template: a new version of the ``stateupdate`` template is given (``stateupdate.cpp`` for C++ standalone and ``stateupdate.pyx`` for cython). * Have a GSL specific generator_class: `GSLCythonCodeGenerator` * Add the attribute ``original_generator_class``: the conventional target-language generator is used to do the bulk of the translation to get from abstract code to language-specific code. This defining of GSL-specific code objects also allowed us to catch compilation errors so we can give the user some information on that it might be GSL-related (overwriting the ``compile()`` method in the case of cython). In the case of the C++ `CodeObject` such overriding wasn't really possible so compilation errors in this case might be quite undescriptive. GSLCodeGenerator ---------------- This is where the magic happens. Roughly 1000 lines of code define the translation of abstract code to code that uses the GNU Scientific Library's ODE solvers to achieve state updates. Upon a call to `run`, the code objects necessary for the simulation get made. The code for this is described in the device. Part of making the code objects is generating the code that describes the code objects. This starts with a call to ``translate``, which in the case of GSL brings us to the `GSLCodeGenerator.translate()`. This method is built up as follows: * Some GSL-specific preparatory work: - Check whether the equations contain variable names that are reserved for the GSL code. - Add the 'gsl tags' (see section on StateUpdateMethod) to the variables known to Brian as non-scalars. This is necessary to ensure that all equations containing 'gsl tags' are considered vector equations, and thus added to Brian's vector code. - Add GSL integrator meta variables as official Brian variables, so these are also taken into account upon translation. The meta variables that are possible are described in the user manual (e.g. GSL's step taken in a single overhead step '_step_count'). - Save function names. The original generators delete the function names from the variables dictionary once they are processed. However, we need to know later in the GSL part of the code generation whether a certain encountered variable name refers to a function or not. * Brian's general preparatory work. This piece of code is directly copied from the base CodeGenerator and is thus similar to what is done normally. * A call to ``original_generator.translate()`` to get the abstract code translated into code that is target-language specific. * A lot of statements to translate the target-language specific code to GSL-target-language specific code, described in more detail below. The biggest difference between conventional Brian code and GSL code is that the stateupdate-decribing lines are contained directly in the ``main()`` or in a separate function, respectively. In both cases, the equations describing the system refer to parameters that are in the Brian namespace (e.g. "dv/dt = -v/tau" needs access to "tau"). How can we access Brian's namespace in this separate function that is needed with GSL? To explain the solution we first need some background information on this 'separate function' that is given to the GSL integrators: ``_GSL_func``. This function always gets three arguments: * ``double t``: the current time. This is relevant when the equations are dependent on time. * ``const double _GSL_y[]``': an array containing the current values of the differential variables (const because the cannot be changed by _GSL_func itself). * ``double f[]``: an array containing the derivatives of the differential variables (i.e. the equations describing the differential system). * ``void * params``: a pointer. The pointer can be a pointer to whatever you want, and can thus point to a data structure containing the system parameters (such as tau). To achieve a structure containing all the parameters of the system, a considerable amount of code has to be added/changed to that generated by conventional Brian: * The data structure, _GSL_dataholder, has to be defined with all variables needed in the vector code. For this reason, also the datatype of each variable is required. - This is done in the method `GSLCodeGenerator.write_dataholder` * Instead of referring to the variables by their name only (e.g. ``dv/dt = -v/tau``), the variables have to be accessed as part of the data structure (e.g. ``dv/dt = -v/_GSL_dataholder->tau`` in the case of cpp). Also, as mentioned earlier, we want to translate the 'gsl tags' to what they should be in the final code (e.g. ``_gsl_f0`` to ``f[0]``). - This is done in the method `GSLCodeGenerator.translate_vector_code`. It works based on the to_replace dictionary (generated in the methods `GSLCodeGenerator.diff_var_to_replace` and `GSLCodeGenerator.to_replace_vector_vars`) that simply contains the old variables as keys and new variables as values, and is given to the word_replace function. * The values of the variables in the data structure have to be set to the values of the variables in the Brian namespace. - This is done in the method `GSLCodeGenerator.unpack_namespace`, and for the 'scalar' variables that require calculation first it is done in the method `GSLCodeGenerator.translate_scalar_code`. In addition, a few more 'support' functions are generated for the GSL script: * ``int _set_dimension(size_t * dimension)``: sets the dimension of the system. Required for GSL. * ``double* _assign_memory_y()``: allocates the right amount of memory for the y array (also according to the dimension of the system). * ``int _fill_y_vector(_dataholder* _GSL_dataholder, double* _GSL_y, int _idx)``: pulls out the values for each differential variable out of the 'Brian' array into the y-vector. This happens in the vector loop (e.g. ``y[0] = _GSL_dataholder->_ptr_array_neurongroup_v[_idx];`` for C++). * ``int _empty_y_vector(_dataholder* _GSL_dataholder, double* _GSL_y, int _idx)``: the opposite of _fill_y_vector. Pulls final numerical solutions from the y array and gives it back to Brian's namespace. * ``double* _set_GSL_scale_array()``: sets the array bound for each differential variable, for which the values are based on ``method_options['absolute_error']`` and ``method_options['absolute_error_per_variable']``. All of this is written in support functions so that the vector code in the ``main()`` can stay almost constant for any simulation. Stateupdate templates --------------------- There is many extra things that need to be done for each simulation when using GSL compared to conventional Brian stateupdaters. These are summarized in this section. Things that need to be done for every type of simulation (either before, in or after ``main()``): * Cython-only: define the structs and functions that we will be using in cython language. * Prepare the ``gsl_odeiv2_system``: give function pointer, set dimension, give pointer to ``_GSL_dataholder`` as params. * Allocate the driver (name for the struct that contains the info necessary to perform GSL integration) * Define dt. Things that need to be done every loop iteration for every type of simulation: * Define t and t1 (t + dt). * Transfer the values in the Brian arrays to the y-array that will be given to GSL. * Set ``_GSL_dataholder._idx`` (in case we need to access array variables in ``_GSL_func``). * Initialize the driver (reset counters, set ``dt_start``). * Apply driver (either with adaptable- or fixed time step). * Optionally save certain meta-variables * Transfer values from GSL's y-vector to Brian arrays brian2-2.5.4/docs_sphinx/developer/codegen.rst000066400000000000000000000163001445201106100213350ustar00rootroot00000000000000Code generation =============== The generation of a code snippet is done by a `CodeGenerator` class. The templates are stored in the `CodeObject.templater` attribute, which is typically implemented as a subdirectory of templates. The compilation and running of code is done by a `CodeObject`. See the sections below for each of these. Code path --------- The following gives an outline of the key steps that happen for the code generation associated to a `NeuronGroup` `StateUpdater`. The items in grey are Brian core functions and methods and do not need to be implemented to create a new code generation target or device. The parts in yellow are used when creating a new device. The parts in green relate to generating code snippets from abstract code blocks. The parts in blue relate to creating new templates which these snippets are inserted into. The parts in red relate to creating new runtime behaviour (compiling and running generated code). .. image:: codegen_code_paths.png In brief, what happens can be summarised as follows. `Network.run` will call `BrianObject.before_run` on each of the objects in the network. Objects such as `StateUpdater`, which is a subclass of `CodeRunner` use this spot to generate and compile their code. The process for doing this is to first create the abstract code block, done in the `StateUpdater.update_abstract_code` method. Then, a `CodeObject` is created with this code block. In doing so, Brian will call out to the currently active `Device` to get the `CodeObject` and `CodeGenerator` classes associated to the device, and this hierarchy of calls gives several hooks which can be changed to implement new targets. Code generation --------------- To implement a new language, or variant of an existing language, derive a class from `CodeGenerator`. Good examples to look at are the `NumpyCodeGenerator`, `CPPCodeGenerator` and `CythonCodeGenerator` classes in the ``brian2.codegen.generators`` package. Each `CodeGenerator` has a ``class_name`` attribute which is a string used by the user to refer to this code generator (for example, when defining function implementations). The derived `CodeGenerator` class should implement the methods marked as ``NotImplemented`` in the base `CodeGenerator` class. `CodeGenerator` also has several handy utility methods to make it easier to write these, see the existing examples to get an idea of how these work. Syntax translation ------------------ One aspect of writing a new language is that sometimes you need to translate from Python syntax into the syntax of another language. You are free to do this however you like, but we recommend using a `NodeRenderer` class which allows you to iterate over the abstract syntax tree of an expression. See examples in ``brian2.parsing.rendering``. Templates --------- In addition to snippet generation, you need to create templates for the new language. See the ``templates`` directories in ``brian2.codegen.runtime.*`` for examples of these. They are written in the Jinja2 templating system. The location of these templates is set as the `CodeObject.templater` attribute. Examples such as `CPPCodeObject` show how this is done. Template structure ~~~~~~~~~~~~~~~~~~ Languages typically define a ``common_group`` template that is the base for all other templates. This template sets up the basic code structure that will be reused by all code objects, e.g. by defining a function header and body, and adding standard imports/includes. This template defines several blocks, in particular a ``maincode`` clock containing the actual code that is specific to each code object. The specific templates such as ``reset`` then derive from the ``common_group`` base template and override the ``maincode`` block. The base template can also define additional blocks that are sometimes but not always overwritten. For example, the ``common_group.cpp`` template of the C++ standalone code generator defines an ``extra_headers`` block that can be overwritten by child templates to include additional header files needed for the code in ``maincode``. Template keywords ~~~~~~~~~~~~~~~~~ Templates also specify additional information necessary for the code generation process as Jinja comments (``{# ... #}``). The following keywords are recognized by Brian: ``USES_VARIABLES`` Lists variable names that are used by the template, even if they are not referred to in user code. ``WRITES_TO_READ_ONLY_VARIABLES`` Lists read-only variables that are modified by the template. Normally, read-only variables are not considered to change during code execution, but e.g. synapse creation requires changes to synaptic indices that are considered read-only otherwise. ``ALLOWS_SCALAR_WRITE`` The presence of this keyword means that in this template, writing to scalar variables is permitted. Writing to scalar variables is not permitted by default, because it can be ambiguous in contexts that do not involve all neurons/synapses. For example, should the statement ``scalar_variable += 1`` in a reset statement update the variable once or once for every spiking neuron? ``ITERATE_ALL`` Lists indices that are iterated over completely. For example, during the state update or threshold step, the template iterates over all neurons with the standard index ``_idx``. When executing the reset statements on the other hand, not all neurons are concerned. This is only used for the numpy code generation target, where it allows avoiding expensive unnecessary indexing. Code objects ------------ To allow the final code block to be compiled and run, derive a class from `CodeObject`. This class should implement the placeholder methods defined in the base class. The class should also have attributes ``templater`` (which should be a `Templater` object pointing to the directory where the templates are stored) ``generator_class`` (which should be the `CodeGenerator` class), and ``class_name`` (which should be a string the user can use to refer to this code generation target. Default functions ----------------- You will typically want to implement the default functions such as the trigonometric, exponential and ``rand`` functions. We usually put these implementations either in the same module as the `CodeGenerator` class or the `CodeObject` class depending on whether they are language-specific or runtime target specific. See those modules for examples of implementing these functions. Code guide ---------- * ``brian2.codegen``: everything related to code generation * ``brian2.codegen.generators``: snippet generation, including the `CodeGenerator` classes and default function implementations. * ``brian2.codegen.runtime``: templates, compilation and running of code, including `CodeObject` and default function implementations. * ``brian2.core.functions``, ``brian2.core.variables``: these define the values that variable names can have. * ``brian2.parsing``: tools for parsing expressions, etc. * ``brian2.parsing.rendering``: AST tools for rendering expressions in Python into different languages. * ``brian2.utils``: various tools for string manipulation, file management, etc. Additional information ---------------------- For some additional (older, but still accurate) notes on code generation: .. toctree:: :maxdepth: 2 oldcodegen brian2-2.5.4/docs_sphinx/developer/codegen_code_paths.png000066400000000000000000005476531445201106100235260ustar00rootroot00000000000000PNG  IHDRYd pHYs.#.#x?v OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_FIDATxw̜іK"X4+*M4&&4Mn77[(VP@ Hve{;uw z>Ѕs|g|8[$JJ (JLIi-i2|u$h$h$h>dRJ$2 0 E"AЦp6`Νڸqɤ,b@m0 R)uY=z`m whH$DHMǑ$aWMMli$) 4Y~G0bpD mDڳ0 ƗKp6f$ʯM6q8ҮmT H H Ho GM6ESq$2 kpĠV@|cnIh)m-ep HB;;2ІR\ppp3 CZoi x<.۶h8*H6i*{`0(Im[h^Vk֬Q*R޽T*A"nY$ m8O[4yd},s=;Na<`0_]%I<缾 _kڴi L&Jc=V'p$w|s_cv?΃yy,8H94Ԥ71crrr~Xx\EEE:38B\q:| 2 C]v{}m2MSTJXLeiٲezW$IpX>OhTO?nfr-2 C@@mAi*0 q555)(+Jɶm!0H$ǽc JRH$5 C`Ы7MShԫ7 CHDe)L*ywY$\$ѳ>3f{nرC??P4uT-^XeiС:%IDBPH>}6mڤ- TZZ_6lPvvN8 2DDB?ϕx@=xSAq*..V*҂ l2kN>O-Rvv}znMax=ߨT骩o[͜9Se4M=3f?(kպ۵eAޢ>HwqvܩP(x\t=>_رC|2335qDM8Q7tyM:U=*++5~x566[nzu)h׮]_WM4I7ttXphQ8Ǒi^tl]m|jhhЌ3tqn*_꣏>'|N;M}]tEщ';Cz~mۦm۶)??_H$dne:+++5p@]~m[999 BѣǑmR$Q^^ƍITJ999 ޽.bWŔH$`IҴioq~EQ-]T۶mSյkWaԷo_o{C7|aж J)++K}￯ŋcQ*iZd/_1c(;;[%%%ھ} 86lؠ]vk׮ B|ڵkѨ~RJJJ$}<Ȑ$uQs ƬN 8ǫ_~z'5~x_W߶mB!mڴI,K>;/aȶm% |>}odRiʶmIW GΝ;{ fffzgNY{C*{6acY޽OW^yE6mƍoV^+Rz5}tUVVj֬Y?~_BCƍkiʕ*))ф 3ȶm-[LGuB>effCFZlN>}hڵz衇بE)믗$֬Y4x`?VmmΝ{W*))Qǎn:=䓪QSS>h?^k֬і-[ԳgO͙3G/Խ{w/і-[qFW}՛o uEHD۶m|7sќ9sNK,gҤI?`0澌>H$ԡC= Q86䡇hjhhP(СCXe鬳ΒazG裏4MOtI'^/ [oiƌر./]UU7[o)S4M竸X{… D4`hΝjhhu]R͞=[:4w\k ohql0nM7ۻ}5d~60ay{}o-]CC233uGpe56.jݓ{H$Z$d2-Zm|[}Zx Йg|C*J);;[]tL"qd^MӔ8J${$4w\-[L?OTWWP(d2=β,|>ٶG0 Y%qJd~RdYqض-۶e|>wlߧR)9Ǿ5T*=`Tzz|>֮]YfL`c5g-ZHmx̖-[tR婶V[nU PzzB*--լY|rY%˲$}FZvJJJL&`z,K~_ׯ׬YuVAy@^QQիWVpX;wѨ|>***a% b܄|]]6m$˲uVovͱ_I 1pTMM~\Բ,hѢEZlVXM6MׯRf̘^zIXLׯ׻ᆱnݺ)??_m+JiZ`JJJTQQ˗+K.m[@@LD"e˖iܹׯLTII^}U͚5Kzgk.-\PGuب{LK,Q4լY~z}}.\_~Y۶mSee^{5ܹS֭СCl2=*,,T=m61c,[N>VZK*jʔ)JKKSt{2T(R 31Z~_s 7ܠ /PeJwrss5rH-\P ,{q͞=[/nV~YoY5\F% % A͜9S+V]wݥ IҫzJ?OL&խ[7/KM8Q7xrIW***ҕW^)0kڴi:sžѨF}G?l߮\رChTvnݪ:%IuEwq))kȐ!ݻNizg4|p.0$ ik 6X,AVccQZZ."M>]gq,Xx<ӧI i&mٲEJRjjj c] ,رc TWW'0tjڼytT*h4p8뮻NEEE^Ijhh… u'gU*W2Ԝ9st9㪩Q>}{}}rrrJcq%=zqzB J$^k9$pOV?33rFF /x աC k;TJp{8,KHDh۞mJKKS$m]tղ,Ivt1뗱m[2 ۯ4?{7ǽ}LRo w!g2335w\^ZV"ӴiԣGR) ٶ[-  D$}NR2 Cݺu|oQx\~_K.UEEuwځ@KGQ9H$>}(??_\p*))9`PP[ղ,;>²6mRiiWqE[/'~~ES Tuu|>_f,KzgU__m۶\UUU8qbƍD"N:i˖-zWب͛7kԩ1cLܹS̙ 6h۶m={d2={jƌZp͛ɓ'K/Uaa ٳ5yd}ڵk/^7G,Ka{zW|riɒ%8q***4hР/MrD"7xC~/_r͞=[x\ݺu*c|M͞=[SN5p@}GzTVVbwɓUPP6ۚESG8N,_֭ӦM \B4MS߾}ձcG͜9S:ꨣ|Giz@@-g}&Iԩ=Xqiʕ^JϞ=UPP ۶JhӦMJOOȑ#թS'EQ 6LHīD"ׯ,˒8jjj̙3j~imKn+V()xСd۶;w.]<|ɪU}}u֩Zm{ UVFٳZmMSS5x6݋WCڀp>Ocܤy?챍D"Ѣ0 AD~䮻ks}m# 3M}{zw7i eY^_-!8Bw[/[twsTJMMM}=n_=7?(*׶v\qk;ȭ@wTWW׻Rrssuiy# wRNNm[iiiۂ;qr)l)c~m w—>$=ɞNk1h;H*3.;>=Fzbpppph;X`Gu wh ) p#@+ @+ @+ @+1v8)*h$h$h$h$h>q8#H@qL&8L%0YrGTJx'Iٶ`<+==]x!dHJڎ֕(H{,؇i ʒa M#m@AA233>=̇kG~G3H2a |hH@`Y"5 5%-'Li " 2,`?PJ@3#=f wZ wZ wZ wZ wZ wZ wZ wZ wZ wZ wZ wZ wZ wL` 1IQYU|߇4 G&2MC/~JYRVHl軃3A ~zk:-Tm+i;>a(0g/lps$-Sr{p P8W(ؓzcn)A!L^wlT(8XDӇm!E5!Ჭ28x4 TT;Upppppppppppppp?L9c_oǕH$R~˔_IŢJ"%R !d~?T*FoPDQAG~':َC[$1_8N}=I֭S]]LCzB CkU!>]m39Qս{wo$iɒ%moQ kgU"!g R) 4H:ub@*aba,Ka0tP!IIL>˦&UTTs  [/V|$ݿ%¡:e+J)e;㨼\Xh}ϠeY  ^5= Ðm }>r 8TJ555 @+9&}xI7V?0umۋvl^g)uԪ8nC1[Oh8LT,;I\ ^+0=`)~|>9X,v@a(A 2 +4,K~_x\mS0TmmrrrL&H$tcO7;cY@ qL&[_udAU38tESϟZuQJ&͕m2MSeee*--U߾}eAeYڰazy@4UWW-[q|>uQFڼyϓEEEޤj 'xB&MRAAum&a^/V޽ I-wqa2MSǏ|]rssR4 w߯?2dH'4~OhԩJ&6ln6uUx;HDoz!o 'pHmE0ҥKuw+JkرcR20 ?UTT?T*uLDBUط#kǎ~{wFFN>d}rGYYYzꩧW^^W ˗eYR~a}UccR~W!߯`0tUVVDhTaxam{}MKKܹsH? =ݖaJR{L-˒i1c?8aZDB`PeJTJq>OPH_X<=Fq[8#۶[lqx<75k?xB!{Z~*++k3qPx4Mo_^c%ϧ@ 4b1mٲEUUU-Z|>c}I$2MӋ crυ6hrJm>_7  Ò 6ċEi~ܡPH;wT}}Rwm:JOOW<ۣ*~_c9wg2l16iiiz]'O =f7'u-hذaOܫnWEERJKKH$L&١PHUUU*--8رrrr*4UUU544DdRi*;;۫ êeY:wt% {Zp-Z_:UTTz=sշo_mڴIe)77W7oVSS:uꤜ555yR˲uV/Id2M6Qҥwo{ݹs$iΝ*))QSS222/ R:رbR7,KҎ;ԵkWolp8T*_J&zyfֶc$]d۶ vZeddx1aڶmz#FڲeѨrssթS'/H$sNB![NPHݻwW2ƍ5k,}wܡ@ &讻ܹsu窱q! )kÆ bWff8D"et颴4555y1g0Ԏ;j*s1tW{mN,R0TIIDԵkWYUWM6)k׮bjll<7 ƓYYYҥRw~"֯_@ ݻHiٲeꔕEQa/bZ~ [Ŀnk.YFzuYgy n1*))S.]D1vSYY;v[nVx\o|>6mڤz*;;ۻmVYYlO~I֭[%'۶vgggsK۷oWee":uh4P(ڻvR2Ŕs@w9"x\[nU׮]uM7)//OvDTUU믿^֭$]2 CMMM袋tw+L??4m4UWW{ nMs$iʕ~?_Ԥ;SW_}ɤ&LG}Kbw]?uYgX}u+33S;vg{N't~M2EݺuSv4o<% uAv;mۦ}j]hTTJXLwu-ZX,:'?;Sw}ZllVNNrss5d8]e_WׯRJJJ?I|lVϞ=eo߮"=S_W}b 5jNuEwyuYahݺu,KG֯kJB!mݺUǏٳDԹsg5|o8:tK/H^|E=*//W(ҠAZT~=zꩧT^^.Iѣnv5JXL>OOLah;5tPnFqZp:uo]cƌ8zCPțo^?Թsgjtꩧ;T^^QCCwQCCz4M=ԍ7ިKJnV&uY{/ͱ_I }٤iΝޭqH_{5k={mۦs=WG%5 Cڵk;< 8P1bw.IZf4rHZr,X1cxqrх^^zo߾:եK_W 6L_N8mٲE?޽{kZz.E"IƍoSOU^zj]VUUUukӦMz饗ԫW/KwuL1chܸquE]v*--UYY;8r) B2ez}zp766j۶m:S5|pSw 6{Ѩz{D"z饗T^^SO=;'eeeZf>#uE֬YAyd j˲ԡCgmU[[Rgٳh"+駟uiĉ*..V>}L&[eee+#GʶmY XB'x}oYvءZp :䓕JoNPnݔL&5w\mٲEݻw׍7ިP(_]}U޽8vP($qt}\/|ʁ@@k֬Ckѣ5w\B!]uU2e~+77W7p >L b?ÇS(/I:묳ԫW/׿֤It9+Ԁ4w\:DDsՕW^ѣGkŊZnuUWk׮[l27N\rt颷zK .…ڰaڷooQyyyz7TXXiASMMn}駺袋tE}={tui͚5U__kFzmۦW^yE:4i$W׮]_ݺuӻᆱD";N'||Aq뮻N'p֯_{NC Q߾}K_TTT뮻N'|~['x:u?P}uWSOժU/_~ի~A[nՅ^UTT?VZ+BcǎUΝo>ѣeY~護)+BZx6oެc*??_DBڹsFW^1bzqPmeQ]]:tljbGD&]yҥ^|E\R~}^-ܢZ\R~ڵk`b *..E)I~߫p֯k-^X[n{% vҝ;wjҤIрT__/I:t,YW^yE~{K/r}gZvƏ:KTJs.2=s*,,Դit7;87n~ӟjڴi>N?%IKJ?LjܸqٳuNn۵,Kot)( JEEEի~m]{ٳn6o覛nR$~Ym?~*ϧuiݻwﮪ*A >\o}] 2D ./KtMm[zƎ޽{oք |r|Wee5d͞=[}."MeY;Sah׮]zהNijذaZb^}U 4H&LOzvi+hСu__5jw=;Vu)hС>|c0 }'ZzF\UVV@ܹsh"kFdR`Pz#<x㍊Zt~W^bmEtpwW?V7MShTwu."=㪭UYY~_)@ F]_S0믿;Cݻwȑ#efΜx555ym߾5//OYYYUEE$i|SvviӦ)F7|l٢N;M:tڵkeAcSS 4aUWWK.]˗{=C &$tMT4;ƾNn/P@~++P=zPyyѨ"ccQMML ~ tM7K/YD۶mS m[YYYJOOW]]c>3Ն t]wnmjhhhȗkWDzWnYWDqF7n;8M>]W_}=Xٶr͙3G:tP x@U__-vQG)Jyp5\lZr~󟫸XiiizGv?s='IO}g$-YD[UTXX]viʔ)'^zIC 5\p8F-]T[n'h)S(--MPHV  g/_7*L 7 ߯3gjܹ D4g-ZȋK^{5b1W;vT*… U]]K,QZZF%KT/th۶mڼy [hTeeeҊ+Կ-Yċ{xbvmjjjҥ^ݻ7ƍuYgkgfΜ:vi,˫6lyoӕW^-:?sL-YDO{l?UccWd[n*U|>222TYY)Izu10 M0A󟽶0m+ԭ[7'?֟gٶֿN\pW OP۷os5oV8ɓuw+zDegg{o?im[S"Х^?Fںunfm߾I^_'xBZ}~~O?կ=Q г>}pX Cb[@z[nբETPPUaO>De4M%I/^:tK Ks_Nk/m.==]/~~j^aEnnd۶~D&L$뮻t5רFtFz\_tAeA_|vܩ^zI{V<iJ$7|siԨQh4_L$_~{_%%%nM7| }[t^յc=GyV޽C@ǏΗ{ [nڼy~iժUggg{Ugyr-Zz# /ԟ'mݺUկx/sG?ҼydY|:>c5~xeggX(ԓO>_|Q=za8qƏx<⸃[~^~eTSSS󙛛x@aZhnVU?tp G۶URR⭷']WV$ц rJeddNP߾}[j*}'jllTQQB+V?$x≊D"ڼyԿ/!kYϟO?TaXVNN PSSfϞUVqH{a,hYNO755i^L֭[7-[L|:U[[яܶm]v-bӧZ\pt .>}xB$9sd!Ch^kp8[j֬Y*))Q^4h m޼YTJ{VaaJJJk B*,,T>}Խ{w/Vܴiϟh4~w:v\DBPH֭Ӝ9sTVVLuESaavءuɶmo^={ʕ+UYY)˲ԧOȶm|>͝;W˖-Ӆ^/Z|-Z]v)--M={1U~-ZH ,yĈ  G}-|G%%%jhhPQQ:(?ڵk%I:uRϞ=U\\K,+V(khuKe)  w7V:xbct_mtbt}& E(&nuW<po|#h t '謳R"#vdU0TX,tqo[VwTm{ct݆ޝl4 =\555pp>/ppcٍCѨ{]p3{Un5~8n,L&H$Zl۽S1ý90w@7sn^{<.1| B^y0Zo>Og֒%K rJM:U?ȋ1lޫލ}>76TJxy}|X=nwIw[?yLkƍ;*Hx_fŭo7ٛ{|>XBӧOTzW4p@=5ן;͏`Gۢ.wмb/|_ .eiW_ڵk_~Yׯ׈#O}mE N˂}%S;Rww0qHoڇ4]s_[t1\߻ˎK wӽ|zW5}t~YZ^{mY2clu^kyߟm&c:vz}駽"Ç뗿@{@h=\~9NUTUUyfee)0`p[j…]+։bޚDHkOs&IUTTxwƶoޫ:o lVii)*ܿJӡ r};/qaRt8:\pO`Ο85w,K> u@8mך5k4e5J R"۶-48oiƍzuI'cU,Jia5;1>LT,ӿ/eeesϕa$3@qmz2 Ci7TIIƍ&L]v}m1x;&_5jp 3[rqR)uYZb|>~;2F-D"P,K@@>O]]p 0 aqթSN0`@,e) ɶm?JR^ۜ:hT @ `0FEQB!T*[i* ynkױ,K555J&D"-&n>OhT1>skA555ɲ,Ioq܆a( qUUUI"HqP*=|D"-^˽巾^XlFK4U]]`0C*33q^[[T*p8L6ƍjkk Z#p7 Bjlll4uXuom^{TQQ!q; BBčcJRJ& r{71MS@>qqx}}w1nw PMM cǥn,yR[ĥngbX~o7<裕ߢi>fL*RAAzsSS[5L&uYg{$on#G>O;wc=.]nP" /y/ש_~YƌŋJm[nK y„ Z|~vL/`|*--o% ~7N]t$ܹS<,X]v)33ShTC՘1c$|uu^|EڵKm+''G^zڵk߭k~>#͟?_XLx\zرc0a-Z$۶+R|Zp&ObriiСrG=}qm۶MXLꪫ-4l2Kر.Rڱc:t蠫Z*//ɓsN/KT\\5uT͟?_((;;[]v՘1cuV… UUUtb1s1=zR~OѨ{9m۶Ma(##C~_FR>}$I%%%zT__]7]vRB>S[wq-&ZPHs8RTkg8B!YFoe tE)33S˗/׋/|]~z״yfkN\s2228x -\KuY:cFeYoJDB>O^zd۶***jTFFѨ ?_DBeA?sfeeK/U+t=ڰazj̙m[^x zHMMM[iӦi:cue)J駟Vee駟Ju+Re)HGSzzV^-qԿ;֋y͝;׫5jN7^x7Gu~WlUR> uUW餓NRaa^z%GGujĈ+tqiڵtqkٲezu5 .PNϫZ~B.\~{G_8pɤ P2Wǎ[Um]mmJJJZu|ڲe}Ywy:uǪQ8 ~effo߾zw4g 8K+++Kvء=zv^zIyyyرlb1ch̘1D"z뭷4l0I'-[袋.ȑ#5d/4 CmGQqqs&I:cpohzW%I\s,q.]hܹ^|YXXH$W*RsϩP7pF?PuuuӧLTffx 4H7pF~[>OEEE^a_g}z_]`PݺuS"P~~ojɒ%:5n8a˄B!ڹs:}U>}{﩮N'xɤz!)X,'jȐ!JOOWee~5JW^y3fO>glF.L#FІ 4g >\iT?lV?AGul{ァ!ChѲm[@@]w$iҥ 6mƍˉo;h^b>SYYFٳgcP|>M6M'| ⵵9rn7ϧ|IҸqt裏VVV4{l{*++g}&۶տM6E̙J4qD=Zjǽxrrrx]ӍujjjԧO^Z+VPn$IsOvvnMرzD",=XW]uBFm۶y8 j;v,RVV9m߾ݫ裏4|pUVV>SSSwy@ kVvw'ft+VUW]L% ?wg}&˲tzygkzD{iذa:3׸Ծ}{>@yyyղe˴a :T֭ƍ5}t|:SJDt7Zk֬S,u{&]v2224w\5Q^^"zXއ{mQXXX,|j߾jjjР:(NT\\۷{?rx ڱc NiQuuMQ4eee{}v=8p#0bŊL&կ_?Ō{\=zЦM$}~[ުYfi1cuuש_~J$DU8x 2Dmh=V7Aw3h7Y~,Ka(N .lqW{gw1h׮]uQG;7-R$Ǖl2uҥE<FUTT ʶ=x<,y紦F;vot]~_dR:Ͻ}h-I$޽jkkukֆG6> ݤe(R}}[kkkNھ}6nܨ?_TJ۷oWuuҼ$… kΝӟ$۶UZZ˗^H$H$ԭ[7uQ?я}\SSS$2ejرjllTFF˽w۶ x=mV0ʕ+գGYe˖ɲ,]xᅊb|O4i$}ބʭ^_w'0ɤLTYYVXP(iŋ S~~jkkUZZ:(4M͛7+??ۮ۪bTJ;vԻ+˲+߯&ܹSEEEر/_#Fx8֯_s=W~_pX˗/D"-[L۶mS8V PnԣGwyoX{ pxKOO׺u4`<T:;b`0d2x<-j߾}{}ݻd, C֭[۷??mܹS+V4? 5ҥsǜۯ]dm5e&EJcdwck7rk4/p.]Oװaü ϧxM>]ڵ$M6M/rssէOB!XB+W>{95J P$Ѯ]oK.zw4uTeggHPHgVnnZlܸQ}*]ϟ]vSN*//;Ch4Ν;+??_k֬ч~-\aM8QPݹ/>O;vкuԱcGUWWkԩ0aԳgO%I͙3GSCCI&T^z>=S޽W*''G&g՚9sz쩦&-ZHsQ~$I6mի5|婴TuuusQVV,ҪUSOiҥի󕞞Fkر Pii&Mŋ&gsUee:vr&mC?.];*jڵzezN:iԩ*++S~~K:3y)--M˗/׿o׫gϞQaa&LD"H$}'zԷo_oBDwZ:\zKRAA^z%ou^RM6iڵ3grssU^^jo@ `0^~oV^+Hh޼y^˗'իU\\ ZxvءΝ;kΝ1cjkkեKiÆ 1cڵkx<͛7륗^R 5khܹF2M+,>~>cK9s_4l0Y>LSnݔd2ٳgkݺu:t$i޼yZpw\q( )##C=B***4c -ZH aZv֬Y%K@[nUCCq靛Bٶ;v^ZtA˗/׼yԩS'b1M鹹1bz-04uT[Ϋ J&ܹƌU[NQ$ш#m6UVVꢋ.R$eY={fϞU~[w,KzU]]`0qff~qL<"kĈ6l~jkk+h׮]m[ѣIA0Ԛ5k[o)H(--MvfΜD"}{JOO׎;oR~_:SյkWR),hС@֭[pBJ жm4yd=Gnn8 *Lj̙ZrexSN3fA-_kbtQFyǹl2;rGiiiKdYLTuu^z%[n9rrrrr7t+ c=vM|ڴiN}gcƌQΝ͛7_8*((qkѣ&M*uA]t믫NsrssK/)[n3f***Znj;iڵᰒɤmRyۗhT>ϋ/VcN35-˒eYjjj^m_r,H( zkonK/5o)Cn쮁ƥع=زyۖǍ$˲[:C@B!% /.ujuF\ydcc3۶퍏;V@k7>pϷ*ǽnظD~_SN9EC=iM$ZՁ'ݠ޵$Jq[tE׉'%~y/mٟm5]kow'_ڗ=^pގk_cNH܄{vv7p?3ȶ6<}W,?R7Ѽ/nf?i{{}ϧիWkDB7|7M2;WWgPx?~7p@^@~?cLTmmʾOmSnn^4h^Mu W9ޯcUVV@>;;[-q}ӓ늙TZZϘ377Wyyy}yb$wD"M6p8;~1>T=ν#0 >JKKoaM"'"a?y;Mvuz [|XrM2 cX,O>Yڵ;bbN4F5sL544ˆd2nݺsmQ$H&ٳ+aNpd-e [EEԗ}8-eR3ކtDK|=h)ЪjA5|Y ILΘiĊ8P }Ѣ[_őv1g;Ĝiup8&W$p8΅Q&!#@+ @+ @+ @+apD`ba*h$h$h$hh* g|{p`pppppǷAodHPpMş|Nۯ{kNLm3yd{lf0% B!6s/Z~%-˒ijhhP"JL>,|QkR50MSDBeywr@S+jm)Kg y3Cw1 `>O_yc K>PG)- fԣG[fk\jԥ{ٓ{!Cp1t+)';;;;;;;;;;;;;e,cZjUIֶM MiiH ѤT4q9cV8Rǜ "Vr=2m"A6ڶv}mg2 maHvVYӸ 7;ZMҶUM*#WlJ ÐLÐ8⻒uޢq[[v5(o w0ONq~tf)8B~Km/PI+'o w:<X p&p 2ppppppppppppp@ryC#a VKH78_NxopojjRIIbgGd|~؇d2rUWW3Xg*U]1;~xf`q<V8Ю]H ehWUEѪҚիC---M:uc0@H>m޼Ye4uMX,h4}/nݪ|>>tlURV΀bDBuuuU 8l+Lj}50$%9vb@T*%4 pDg@(==$IaNDBTmR)) 1 3>lxATJdJ;d2`0tN2.AD{8ta0 )4GܵMz뼮u7ﻯ}'MYߚESw_ H`n@'yÑvM|oM݃"$QKw@kb%QZ wZ wZ wZbTqX Gu 5;>ppp=zÑxm<&0 wq88LP@+8ToAr++@<ù 1  -eh$h$6;p;u ߪ;#>{mC;;;;d,}@a,˒i@+Lls9`0p8 h귘eY jhhP4UVV,R*R,cpXmVpXiiiJRJ$J$$0 %h4x<~H5sLUWWk-{o!vr?~m֪W^ٳX~_muⰷ ӝFE+?34M555))==]>OmN@sɤb71+kԩ;R?l'ʲ,A9h4*48LT,S2i B2MSx\`PdRhT  B2 C m[2 ë^@4%64Mi޼yJKKSΝH$swу>(0 aX,P(=ВǕH$Dm566*wQTǿ35^Hi:Ջb,{~*bXA,(J;I@ g~K\/Y6gOy;gq:l6Gyy9d8N cQ`0HEEEDPPj}!BGp?C'3f`vm<èJYYz+%%%\.JKKy9p)))tڕ &av%\nv3vX-Zy\}$''i&^/^x!۷> ''nv̙ٳiӦ̛7<233HOO?!Bs[oEzz:#F 11UU2dC !,, M7n/tҽ{wvԩSo&>>MӘ?>SLaԪUݻӺuk6l۶m~tM̚5_~UU+gȑZjENN+W$&&nݺѩS'NVt]fOtRZhoa[x'`0 ~3fpARSS֭Z!CiԫW͛3a3i~WСC&O̜9sp\rn>Z2i$~gJKKȠf͚r饗bZcܸqZ M8묳ꪫVSB!Bgʕ9sƍ7ވihF56l$nʏ?HNN|7ŋIOOiӦ<#,\8l6cڴi|G1n8|>IIIoߞ7F^Xn'N4122(.\O? !⟱llذ\^uRRR|l6..ׯcٲeL4 ٳg~"##;w./2x` n'))K2aϥ^ʬYXv-7[naݺuL0-Z*ǏcXY&+Wdڴiۗ>}z 0u|TQڵO>lذVZ/7M||<+V￧tڕl:uD||MoMƍ9r$ .'dԩ 4;v0p@ڵk7|i躎b̘1~3O=?8\r ?SLaĈ1h s"B#m@RRtR>+/_ɓ)++ <ˏ?'|•W^ɪUxر#SNeĉL8ƍ3`8ѣiժ^#<_|m۶=F5/(VgϞXp81c&MwO?)SPNu;||g+l޼ÇcZB!~/NEErA}]8`K.o y7x~ƍ|> `nٵegw*BWu]&G.Bf>}:=z`Ѹn ٳ*)( p8Q ,`ܹt EQXv-+W$%%ݎ7l߿[s/X"""1cs%##| V!p8oiݺ5/"> k׮駟6yA"ntrr23f̠QF8N1cO(ظq#`| nO?ѹsg233ٺu+| r`it:`+Bd3K.aҤI<|ԫW ΝKdd$5j7ߤO޽uDz-Ҹ?>s{.NyQQQO? <cJZljeÆ ԨQ"s~xzۗ+vq2rH/^L Xf ۶m㪫"++@ @ZZ˗/'>u뒖jetMSTTiӦr8m'//ٳgӭ[7 yyy\. @XX]taܸq3<(,,d͚5Ѽysj֬㪫gÆ ݛ:uPVVngŊ<AQQn@ @.]x֭SLᡇ}$&&RVV1 ɓ'swC1l0:t耪޽KҡC."FB!Jg`0HLL cƌaܹ̘1AǎѣDDDШQ#v; Ǜ  0bFɢE|nݚn֭[SVVF||DDD*^zҥK/]vYU+N;u֭xY,~Ek׮t֍Lʈ/`٬\(4hgMjjKZY|9\syJuhԨcX4 χ#55ߡCgl2֯_ODDZ'<\5'BϘ@T )++CUU\.PR8Wi>P èAQ"""ߡ( 9sp;pENjӯNˡGjSl6zGD6Dva^ᯩ@Qsv])//~T!Bl)#̃`0&c\|azԬY0R!8⁣ϑb0|ԭ[|){MK WwcB!DUp@ @6mԩSzEEB!`xs \,>!ĩH*B!3y^^?RڒFTx@ڝcm!B*E B!B!B$.B!B!BpB!B!B!jz-_OT?Rw  !Z!'ҿpX-*A*'  !g2I!B!8Nz]S Z~b&@VA.Ei!B!B!'Կ]0 xNo1C7ɷ !B!B!8Nz] 6~=rXxw}س B!B!B!Nz4E!5Iz{I^ElB!B!BqY u%.B!}zEumwacB!8U@!B!B!I]!B!B!8$.B!B!BpB!B!B!KurPFUU,j  ';_ Z(aBQbϝncih@ @xषłc/6 ] IB!?ΔHOUUA4j֬IxxI); ~Hum۶x%11nBq&x9@[9NUU9pf޽{)((pf;Hȩ~grS`0۱Z4]vxHMMjwkƊ+p\ԫW%v;>ﴮ2!O#RBΝ_|ĉq8`̜9R\\l?|btRfΜ;#EU)!6-[?O 8ojFNNseذa̛71il۶9s{vZv|Xt) i۷ogl޼vcZ1c+V8Du/^E$B!,RUU躎jbZ x~qha|?<mX̕ZuJOڵ۷/cŊG Ce|>`ݎigmcv GZ,,~sM04h>ʿ7 B>}xqY!$ݎfR*zqfUY lzUP|p8[())aС3~^ >j>;;C~?-[sΌ3J uk9.F!86c4ͼ!:( {f~󽏥HҵkWv؁X<E[ofK<% P?g6lHXX`мI !ZēRVpo>,X@rr2s,\Ӷm[ n+..&11;Qe2k.,X%--$$ngΝ,\M:uԩ9M`n&ҥKAUU6mJjj*?ԩQQQ,X~ UUiݺ5 6޽{fl6=\jԨQerJD~Ġ0 F< *T( ~sR^^NΝ ̜9EQ8󉈈05kvpо}{222UUU<ӧOgDGGN͚5' bZx<̘1\bbb8󈋋3n/999XEQHHHm۶^;w: 6$33֭[Yt)dddЩS'TU=sbTU+VsN<M4ᬳΪn_&~?n[dٱcK, '!!0l^UVвeK7onM0 ;w@ǎBq8Z躎fcҥZ BSټy3VUUq\iӦ6Knw^,X@YY5jԠnݺ8Nel߾iӦr|4hЀFg+ !Ba"EpjN'Ö#7njb˖-t:ټy3|РAǠA(--EUUV+#G$.. g^}U4 βe>|GU:t:n?csUL6ljٴirGtRv)(( ++%%%;ҠA~? 2cJ/^v˗[Sv.\ȧ~Jdd$gݺu 2Ooje^dիO?Ĝ9sdK?,]/p233)))W_e8l6ƍcĉԪUZj1aƍg>I:fdffRQQ|4 UU~ű_UU믙;w.u!**#Fl2vPWUUUuiӦQTTdf?kYn1zy73g+?B!tXQ7p3fOfPx<|p nE]_|ɓ馛u}( [tҾ}{֭knRTTĉ{[.~?ӧs嗛kB++ڶmKEE[l^zx<233iԨz+믿RZZO?aJ4hM4!!!+ jҫW/.ԩCtt4iii|>|>wѸqczf1w\FO<10 m_=QFp5PN8ix衇HOOsE;0{l.cZz9shݺ5fsٲeUc%f8.~_4iˆ#[.aaa4i҄-[چ&L/**۵kWJKKӧYYY|>ɔvQݺucΝ\zƚ[ӜN{K .BR) rAjժE>}ǦEbݻ:u*'O6'%%%qٳgo_xoQV-j׮MII PsG CJKKi޼8ljj*3f̠UV, ۶mc˖-cV={PXXHRRQQ^^ȑ#)..raظq#71B!]RQQA wԩSp [laAUUCYYaaaӻwo;sL:sO\JJKKͭڴiٳ#11/{QGdd$)))\uƯ7M޽{ٳgF;}y%}O4ŋana,瓢COib7CveggӲeK(// <7##;l$$$pWp8bI @QJJJݻ7=z񐗗_|Cv3insώndeepB\.W^|>sVZѣGsb( aaal9x^l6N󈇜hꑮ`>Bq*-BɯH233ر#n7fPZZJӦMiٲ%%%%n&Nȗ_~ɽk1; *~\-E~?~uGN5jW\q ,}f t:IIIn v;ǴfrJ/_#RSS]6v~'b>ykę7hЀBΝk 0~x g}6+WdDDDAvv6˗/M6|>ڴiĉ)))!22CIQﱬ>ZY7߰~"## 租~Snݣ~ àv~'PV\ɘ1c,,*++{nB!8])aN/+qsb,ˆf? Cvv68Tb?n:vIFF\wu檫`0ȨQطojբ"tB6l{FDEEKDD7|3vUUٿ?~)N(vI۶m<ǎ)))4JI?gɒ%<ƚngժU?N'yyyDGGӫW/"""l6֬YOzz:nK nM2o}:+V@QvEFFyKΝjv;ׯ[ns]M(--?񐜜LAAnnݺѨQ&UUvGaX0[lᢋ..c߾};O^^ankFܹcǢiwrEdd${6WfѪU+N)sm>5kPRRby:۷3zh"""&77MӸ+jrJ;jժar5мys^/vqƱrJsKtt46l <<XfϞjuwhehF͔)S8s((( >>***;BICңGRSS~hWQ &&޽;YYYf5uT-ZDݺu)**ѳgOO^gu? !BR, SlTZZj>Z9fX2ٴX,ر]vKڵ 7 =eINNvfrjٸq#eeeԩS5kV9,`0i#Qߦ% dh͛|b>z,l6gDEEiprp8TTTP^^( "]]qkBp8?-#B-( ^Rs mpϖ-[P4|f,i999ٳEff&.LfcܹD233=E1Wbt:q\U3{\\g撔DFFvݼ כ+t0+))AuwTU%** M{QY4N%~l6|>7n4ʍb6nTaኢ`ٽ{7۷o'))zQXXHii)hn{,Naa!~5krA6mDxx8YYYf[ ]s菦i 8kLK =5z򶨨Dj׮bm6;v`$$$f;m0B!$O5a0i0 Vyi Z5mcv=a<i_xv70ƻ|qEh]GO|BKz>۟~gW.̝ Sn:zx$5ԦBqg7LC5Mc,Y{ v{,w<$܅BψCSTw$yZ0|߹M"eY轏ᓀcIW07!߹ WIY=ѿ+OױG_?*cgVrh:xx=$.uj5! gl'!B!u$eņBϺ(qy>Bq&9Q7<))۩\vIR!'pH*xi!+Iٜߛ-))!BiT)!B!B!' w!B!B!8B!B!BqH]!B!B!8,gCwDuӊHa!1FuÅD!3bCB!BH.B!ĉ&[!B!B!ǁ$܅B!B!B@B!B!B!q !uZ!Θ~SN!$fBB!N$Y.B!B!BpB!B!B!I !B!B!ǁ$܅B!B!B@B!B!B!',)a!d w !u^;Qژi!BjpB(|ۢZ:-,&Bv'qI+l%,B wMu TUv9B)]ץ0NUUzRBq}x<I(oNnr%߲BqZ&!I*III$%%Ia 9Y2 "hܤB!Ŀ K!B!KQP B!upB!BӜۅB!NpB!B!B!I !B!B!ǁ$܅B!B!B@B!B!B!q w!B!B!8B!B!BqH]!B!B!8$.B!B!BpB!B!B!I !B!B!ǁ$܅B!B!B@B!B!B!q w!B!B!8B!B!BqX,ܔ"OB3*Tq50~=aH!g 0YTn:1N)$܅4-9 "E" e6MnU _* J^]R 뷭ê <~鷅8YӦaۀBx `j H" w!8Y5нu $ R(B@6ʲ_CxhU.SH!8ec%ʆbY)Ql CB8p糷fpyusX%fQo ȗn܋r$܅4u9v'rNbI!)0 t emx)!N"7_qX5ή.'!N4_@gy0eBTVdd!8=:o q2۝a`W'aXwB!B!BqH]!B!B!8$.B!B!BpB!B!B!I !B!B!ǁ$܅B!B!B@B!B!B!q w!B!B!8B!B!BqH]!B!B!8$.B!B!BpB!B!B!I !B!B!ǁ$܅4ftBD Bv1(RBpIAGAW$n⤶;EEGEƼ:`0HEE~_iQ QӉ8f3A|I'g젞k/Qx r9ѬHTgvx<LiN$U.!GSR\D (e~›ՊBU梨><n[o[T6mUU>ԍn!.!NBlaZiҤ R q[lao~9PaUIrJ``6ly ě353P h|Y'(Nb E~7984`zb2JKK lShY;%%ٹĊt?8T碻w O{ UUq:`aNH 4 Cv%.U.//~$p:!!&=h~zO1Jѩ(4?@FN@70 Ke 4,V9jE)(A E  r֫jVMp8X,Y)Uzd$&Un1#NG/PPa(8Ő-NN$]-1Uu]r_-I;I=qe{j3*z}Hzk`J+듥O\:ЖHݗ~[APC2WtS|RyzPڝjZU1k~?Kp%ɋgėAbOPQX\EH-{f2W#v'SBѻrKncK"B!\Eb?!sڞ$܅H !B!$Bڝǁ-B!B!B]!B!gè[[qS>m{r^{F$-Q]Hi =봌wB:! !o!#[!NT-E B!B!(B!B! thw?pB!B!B!jpBQERwq3fKyTSHTx'dBRe"d"N w! D9r 4 NN6)?Ln!Bj֝aT˟OOk4Nv'`iwǑ$܅gXD|2W\"Y!BqFip iwRlN*94U!B!B!8d[TMuoRQ9"]ץB!!gN\QR63EP7@P?~uLSC} Xw5UA7 ?V=yUU`tiIo6 EQ|>_',, ߏ3\.n`04@ w'hvq8X,~?^5>2+B0*G~@ #ZűZvuEQ0 Vkı$+v;0zh;7WOZ4 v !?_ 05op8ZA^/@EQf\mZ' GwhvIoCsXiڟ^Q)餼\\El6+]>Yd Ci̓-aU`GP u,1]0(iM=1ߐa`w:*>tgZlV wqTw7nCh`<>`el41bh'Ym6493"P²eXf V֭[vSN='Nvdees &NHǎIHH8椻(Μ9s(,,0 bΝ֭[⬳"//LNˤjeѢEscC-[XldeeOYY͚5}_$<<_|]5J\~fΜisiii8p{wl:tAPO?o(iӦ %%%'\z5g ] 8p97G 226mڠiIov;vm捋'|믿o'5M#??sҥK\. /S:;Mi*,\agb`Yio0' AH =yni,f2㹴mscYa+>6e#nϟ[lٳ[b}7u.8ehX67M߃+UE!d4[4dtpvg$7v/{VUƋd14*M4h}Y@j2x`ڴi+pWӪU+<WIJfCUUt]:ƍy饗x7kU؇'WdRXX@˖-y饗x)((?p+m6*v.BFm& @QV Z- &22?X,3~Ze*yyy 呔W_}E\\6~G} /z\=iڟqvv6/GTTT``:zjߏ(O l~=/66]2l0 OT!qf_Lh5^zz:_|.`0bj+/3++;/TyHG(dX(**ga…UK/DϞ=0b}z4 ]ױlJ;6V75믿f۶mwy8?22}f`0H0n֫ȑ#4hυ+|vt]b}U)#faÆmۖ xؿݻg}+Vó>˅^vQUJ?uhjEu.rԩPZ=}CON0YfqI 0>b,Yr_vmFe͡e|\$59a QQQ\q/&4?<w\̚5^zzѮ];<YFſGSU~5M忱y^>?C7lV b }̢TǪq~F\M U`YPTCfi2wFn}k[7߷QZM>{NL[6yť`T8X5 ժέweݴ#f,Ul*6=\m:UE90i50H}U㝱xfb]UTUAj2?jVMr|?|BV -s~ˆ\rr5ih/vp˛#vQ ~W{6Woɦ} 6 zlOj= krGd4U+=aUUYC}Eئ*ʡDAs짹L\6'k׮{A̙3GKKKYv->\/_pPN3.--eIӦMq\fS4ic=jVhҤ g';;2n7111pwe~GXf f͚nv VXAQQjբQFfb$T~{%;;MӸ>}?zR=Z`̙更hт~dVZśoIIIIeu(gs"55ռaJgggsN233yG_$Eaƍرp7nL\\o… yGҥ |̝;qF͆gO&Mp:~ؾ};(6mڄihт_w8dJJJ̱dΝ;)((nӨQ#v͛iԨ5k$mݺ;w Su5V0Φʕ+Pڵͧv;999lܸ b~nƍ|$&&RfM.\H UVfMqjKf̘og}Grr2Wn~?NݻZjz1 MPuֱ{n233yXp>ԧdgg{n\.M4!66χgŊCI;/U_XLYwE?3 6 # 5b8Amv `k~z:o WCFuuF~4_#71i TEaلaMV]b#]x|~4UeG~jx(uYej% 6+bN k0&vNKXi;e2H"Ԍ귝W.l\f`0&vU'rv.YJX;r_n}~VhY/x<^?a`TEe٦lۻ&Sx񖫙|}e:VK2+6`98Am#Tx]7iݹ<=[z!jf?Hai˖m'9~iq \uTEᖋl&&n%+).vr-ꥣ* @enaR`,FLHlںx|~BDDt{u|\iӦdeeaZ?l6iiiX,;ә>}9裏yO#"##QU|ҫQF 85ki祗^b޽tЁ^zdɡFtܙ)SP^^NDDÇy<#̘1|]w]we&nf [o|?OEE~)C5q<@>}X,{'CΟ ʉpaZ)-+ɏ0|)Ԋa}ѩ}_>7W0(Vv+k7n綷?a٦4gOA r|r'c+ :SylV ?.[Kp.NJS,KeV3_{^F={cX` ;J+W oEЃAl8mQ7o>ItEcU>Phⳛ㷑i,ٸusD!/' xm/~mu^}1<| F0K-x뭷9r$tؑgyʸ `E\zx^\.uv_@dd$|WSrrrAGBq* k.Zha)+[nX,ƏoM۶mPѣG3p@xg4i\s _|16m2oDu7o?Z/g}4+'[***%,,'|UUGuk.>#q+VXeر#7x#[lwa<,ZGy ys5y2dd=z4c̘1\p\pV>5k$22w}g^GLL K|q]wIc'Owޡ}\{W_}[oELL W_}5g}6O?4#F`DFFصk@:B8COԪU|>ݻw{XV{H6mիa0f~mk0`SLgϞ\tEdgg3bĈ*3 Я_?}]fΜɝwIii)/rk͘1c2d9O ݻ76nȤI?>W_}5`￟5j0ydnF֭!##H,Y?LӦMy'e͚5 <~{UPZaBn<,m`A}>O:ż5f׿!{uӸ͏~ /reռ7n:NE#@ݞ]M4Jo{ˉ\~J+00ڬ,X/Oy׵$Fd6^9<"zLIuSٺx~jE3[xk46K>=p:lAkE㥺Q躎dt҅yez-ƌСCUٓÇӰaCn<aٴi˗/' {VAZh|tؑ뮻_\QϟOqq1 4`,[ Fbb"7ndٲeh 1_tt4_~9>>o@ /X|I/_ΤIeFqWIi0(((`ڴibZ)//VZ躎p2zh"##̈́{Ϯ]?> 1e4M32e9U7tp+obXX~=ׯAر͛7cZe˖-̜9{:@ W_}9W_mwkFzz:M6eĈ`X3g 2K._LXX~![la֬Yv{=֭K0䬳.3W$c.aèCP:GᴁnWVex/ }x7nDGGcwݻwgܸq{L>.]0`~?]t!>>~BNNg&==<&Nb!** ό3h׮9&ZV}+\.9F\yر͛i&֬YfFXswРA֭[GS͚53Z̚5 M||}Э[7̐ONqq1x0Uw\ٓӧC[7:u TT֭~-_~9\wu|vN e^!N9Khkcǒʰa CQ:vȵ^ĉi߾=&L/^3QQQM̭-ehFDD`~\r V{믿Eyy9ӦMCUUTr S2p@8sի#FUVՋ?Ki֬/ߦu~ڷoϕW^W_}sEf6nXe;jөS'+Wuj/>?XmXw.EEaMsO/;x\б^aS~fјf;ӾQ=VaZiZ"\NA=Fe2UѡQ= FFR]~]\Үh*~O~C0DU 1j_Ecₕ1. &_83>.xn/E n3l/D¸皮<9-O[;Qnez t\0f>yuk`kEM8y\xVcF4.hϰw@ @Npqϰh*v0g&Rbu3c4b"\:g-"$3X4 Ą#$ՈUU:d^pvڴk\ry0gF,ZI $.*Y6[-Ո٬ߞC=+ÅN_Vmi,*=vbuؗ]. "r` sXj43k, µ^;`֬Y Z#PTT 7@Z/JKKz*>GhKPWQ222HOO7WhiN@ @qq1v0$~hEjn%%%A(@TThFtt4*~!^z)[W_QB{ֆ uH J+I, ^x! 64W~zȑ#iݺ5mڴ1 @h.Bk m_$!AaC:X*UyfD)--%22jԨa& àFG.vPur .4bbbj;<4MI&Ѿ}{5@ckBB\po(p8(//GuAj~KqqysAdee{&FMڵ+׋f1zh :v숦i=p8f209R Q9묳hٲ%% Qooڵٸq#;w$--_!33@ @XXK(t:zIHHj?ǛkhN09^x!ԬY`0ԩSyPU\>|J"##QCs m.Jޟ9PFhҤy~Tn+vܢgϞ\r%*4\*(//pr~t+C޿^x4h|OMkfi|" x<nV9޽￟nvYr%Ǐ7ǧbi7nܘf͚sQ>0p9$Z9TY9NAhGG+y(W q,^<^ʊf|4݌z}* :lj'ӡQ=qՅ+8vf_Q1mVy8$ⰁARlTe^i*rOC0 h/x躁\ت1.*t~hr*xI=C zW*0 O5U%+&"g>Rj%FAQ1rwӼN*NÁݢU?V aN;e/^G͸hZ jU\TP]\^@r˧iM$k=k!iZn\HFLP=Wt ]f ^r в~:QTZ?96 ڝ-Wt*_]*+ VZ0@ׯӀd2o@g=B)A'Jii)jeĉL4;v`XرcSLaƍ\tE4n]q݌7`0Hrr2۷oo壏>v|״k׎n]Y|9[nEu_DŽ  22lΔ)S޽;'O&66nΝ;жm[YnӦMb`_|3f̠{tܙ"&N͛YhEEE| :<4~\"""ضmyRLL s%77{5k}v/^rQvml6a0erss`߾}O+iӆMϞ=֭-[$>><ٺupp-дiS&Ly(..f˖-TTTp7rҬY3RSSQ{2i$ ;;aÆ1c j֬ɷ~Knn.)))ԫWiӦ1k, àQFԩSɓ'3gޣqs=TTT0ydKdd$wfРAYEQ"55E=5}ѠƺhEH˞X6 ;f/IWo0Hbd rahX#lkf$E}CI҃b<͐ٳFR2ys"rTikb((K~3DEz%(5iv~}ԩYYYw%%%3 {n֮]˺uHKK#""-[ҠAv͂ (--ta&fΜƍW̚5W^yTRRR߈d׮] 8+Vp7ҡC~gMf~A.\Hii)LSF \.LD mCO˼$&&%Kp8(--eˌ;k`0ɓͶsN馛8sYjSN5 /fРA:M4aٳ޽{s饗ҢE ͥnݺ?a|wcXp!cɒ%L>GDD;v`dggs7Ӹqc3(ٷ۷={ĉ[.}s5)--SN2a/_NXX%%%޽I&ѿZlIrr22o>}1}tt]N:̝;ׯ… ٷoyh+5k`ܹx͛?2c -[ƚ5kPU֯_OFFvb̘1\q\~n/^LNNaPfMu͖իW3h V^ͅ^Ȅ _>7x#{.3ovЁcB Dǩ l<԰XN唺a9ںpť< gQV vŌ>ֳfn Mx|hWR+.LB s WvXvJJ˙d j.³SV"EX5K6'b|lEL}Elc7Sx\٩N_\4MKfW^!G~M:IN8FS%% ->o#3E=׵Y-G_evUr{sM{?˙Y=pcz#J6Y7y y+y꣱U.m۠6srپ/ztѾQ}#ٵ鵸]sopZuzCÂRgρB]Nc<~ŴȬ;V#EUqL)V5حnX2ݑ1q4W 8D(qsb,ˆf? æM (p_0|pl6Ϯ y3iO> Ms׏#F矛? hO.]8pr^{-O=3gW_eU5==W_}VZk.}Y/! _~9GXX{'>>EQp8tؑ4n7o6cƌ1#550ɡUVyCTU%33w}￟;v y衇xᇙ>}:/rZh?Off&~aÆ1b30󙉠>|4&C+7o~lwQtjP8pv gʋׂ7PuX4~ 6'rHbL8At/KRn}`=uz.~1SW`hAX,a[MW?f jqssQ 598Vϥi4LfuEcA0O^]YwRnelؖGkUhJw8>Y-E܇6͹WŊOW?\t#s4 ֬Ytĉ8p9>x믿`0СC/̕V믿|Lv?Ӭ\LaÆܹ2j~*]Oᇹزe /K,ҡCΝKٵkW1h V\ɠAPUu}v wy^;cիǰaXv-}eUNKK_I&\wuر P~wr=aմ駟fٲe;H4Mcܸq;JF믓ɤI0`EEEU^:3&&¢E۷o2LOO8sx'2e vEQx,?]feew^ӧO=~x*iDDwy'wu_5 77 %%N %֬Y3OHwѷF 鶱(X4&p}INov-_Tws?[CQUD7F32Vsج4JݹTx|L1Qzm8[sb[CKn~<=i&G_P#:r܇Hw:`ti_sh/ï͔Wi~_Ldw??,)*WkgOAɫ_O敯&=I^UUy㎞hfɳxhו+%;4ǏJZIٓ[큭9ԥĻG(鷥 b< %eGx+u=iE=p:lύ0ۋ(4Mݹ\0W=D8[/&@o~D?z33ظ;(<>?H* nFZgٸYC_$6܅|\C5=^:_={7YSf|y(rȥmUӘ2_FaeXV"CCI`, hK^ܟ<;;iܸhb(pTUlnJLL k6ԺvZvEjj* 4 ??K\\n>zzjKrr2k&)) ׋ ??l󉊊"994>Áfڵ撖FӦMU#\r%|<G)));M˦Mغu+.-[R^^NYY$&&{nV^nyFbbـm6۶m#;;Aڵ0u 6sNTU%))jԨ޽{)/|Lr@0d߾}fGBBg޽LJJ QQQfbXz5ԬY )..6WFFFρ(--EUU8Xv-ҤI3:i֭lذgm_TT?>cjR6܏pq`݆=iYV*`<2Rb n?__ Ocx9i1QSo[HL$9!gAac~ډJ|8z.cX̑ W&@eWpX+N7 >o[nOe]Q*|fPbfASqXU VU+D7d$oA|0wPx{moiUQcڢUnWPZ>t뫼~Bs9='E˷ݠPܱVUTVIQi3kR#1,Rf,;w`~)Zf`U찱k[{@}#X6IRS%~҂Ryf򈊊QF$''vQUζmذaJݺu$n7֭#'' 4h@nny7::ˆ رcvdjժEll,>g*φf+A$AkZJB RB 4(N O&]c3qv79vsz8ŋ$@ @,ܹsSOvPZZJFF8l46zTWW3w\ͥK.R7TWWo.u]^/EEE񲮮iӦ%&CzQTT^+V0o޼T=zR% 0`r<222 w o<|uݺuү_ץKpS8={$a6^p8̜9sXneeeݛZǡ,(cʕx<Լ:77X,AQΝIFJZZ2o<,ˢw{t]Rf]q]W^yTʲ5k0TJOuu5 .OnR 7^/WfΜ9ضMnٳ'>/*n~:;;ll0 TUeTVV8tڕ.]P]]Mss3RPP@sss*Okk+h޽{CkgEEEdeeDPF77 SөW^уua64fΜ~޽;p,X&(..<gGH}ݽ^5̮\E]K\ݍt"8CUf-Yk8_OJ Fc硺) QdXE7 rXU׈id嬪k +-@<*J M^仹KGqgD&k Gcf 3sY^`>Y\@$G^O]}~u;鱼Ukrhy9t+ʣGq>,]S4Cfѯ^EX㺉욆嫙UǠ_i }ʊPxG.J|>aJV5RZG$CS +0s Fq˶y B8.^%E QʚFlǥ4?48-gZ|D] ( 2ua%W ;zN,i~e-!=ؽ+f[Z&aj0gңa~5L[ E9RѵCӰlÚ&f-]ɺ&>zѯ[ nly :,IJt-_y1i>/ڝv*冢(-o-i2kնԆcVI$'&3 l2j?^''[{X,iTUU_RRRK2dH* |a&5ppJkLn}r#&HIN>mNMt2VUu~~2{"vt 2y| ?5W`0H w]%ewsn_&7'Fk0Y] g{SWGsQCxsч7>ɰr݉s W\8C%V* oLWo0&i~/v_x?EQ^=-CS\*h/  Ea^Ww(ݺ S-w&%Vdht&L^:5˪qcd\~!6`_ G9dߞAoxuVW!qy>YQ]lC!3crUp%ߚĘSz[|H 2FU67?.Y> ryo]2bߞx㣙\saLS'.瀮s_JFҗw?'.ntkI}G&cUrS <[뫓J0/n2lK}v}4~aNRl֖6eKem-UƚmqdYpSPP ZW7#t]OWMΣ7_7W' -]/$'d?5R2磦9so9w|{붮tݶ5̦}MvIw$jzgCK/Zg2ָrӱnM-@;7woϣ먩Ԑ.imEQ|XؖE|/mlFo133th[\:ʩ H\K.qn{cmuSUTJNVuox&_a^Z\s7K;H4 U[ގ6~Rƻkm▵B"8$۰o wHp?]i膖j[jW?Ƕ7z"^Զ T?lwqvtE16H}b6FP[n6 ]CWUl'qx\!f&M:^z2~5uML^<\u~\rNkh:qS{)l?VzjuӲRE Ks6 wMS7 Eyr&760&-k4f0 F-[Fq3fp10`i[<&[ $om}uݑdo:A9mwQ̎h{Bsk~ЕB,jф<7>Ңl|[ٔj]:=+>&g O x .~ʒqj*t= Vf`5G%V~{4>|/;~q$e%,]Q?kLU'>eպFx|^q_'>WӶO~J(Gz7ӖGsM׭Vrdlp޼lu- Ntzt~Xo.|P%W7Gs8dߞ#1|m"?9hlB-axp/=$urq5yEY{X?ŬC EhBN/z{И5{>LqE}ߟˆcp~=yRs>e7>nj :כֿ ;غooB~{w;21sMɅ/UUU,X ~+.bF##5̏yo8~$'***$;;p8\7X綣ב/Q܍a#f`۬mYkenuqlωoV۶|53Vai%\xpFZ4a{ƅ汝=VnwmaN?۱D;vަַq[ow;kj ~)tqxs[;ɏO'tqFz+by1M3l7!ģ Dn7*18uxsTEQyů\U_&[x̘rmY' j[71۶GМpVf,t "1Ћ[xkF e5,XGrKD&h*J1p+ *y} I&偶rX?z CsKCf[>e3jޚ_n> MQ^V\CK$<^1̠[TFM}+rIY+j)S;oOwBN;`hI<s1C).ʦ(/bYEٔveMM %7N)#wc6+SWҴO˪j%\zugrȁҢl|^xWKq )LnMwΕf-B첩b*:%F͓6nWIdRQ\ӵ/rXiL*ɶ'ibq~~*?x1Dq,KƢmw'c{|@k{q$7}턣Zg;乴sR6vԶ)}{DƞMU@C%Lf8 qPr+6?hÏ1s0xOgqhu1mExtp\ZZ"JlE FXQ*XVk$ݫ8G<7pT׵'wW%EX6^jC17Y G146G۫ ]& a@">bvbU> m74 s?K=( 9驛KW1zDק gpY⦀i'V[6m'^?~ϧy449a UvMi4Mpܶ rM+ V;ɍ!αwq.'Dg~IJҜl河qMuoI{)H=D^u-mOޙ:ݞvhee=F{qyZ}/XokDgi7?y1LˎP_֔Gl(d+<=\.0 MSQPrϝ^I|<'qrsc%(_1H" 0}Rʊ(M4mVմP֭q{Xp X6zL֬mılTCk Tܬ}ru'H&1oOzK=i?TxM̠ O^zb֭k8;oӽ,^QKV?q fnoGii\s IH50D9]39g%Z[BX899s=U5 '[2h}v, **BEY>/;ҫ $뀡!A!g?+;[PZ$/u]=&99Ⱥw;{>Y;U] 9/Gs_2 >V[x4}p_F؋~7U1,^įs㠡 _QDz`[snl$?qN!g>~8T.ZC,fhJ}#:~*)/ߣ!H$ckYDlM#0;U˪#[USO]߀(CFbQO>.8B1,jm#VU`-LX/O۱fg3Y VƋ_l j1{rv߻ֵms1C0y ?h4N(c7Y&Xw\3̘ QϨWMlEJbˬD<sc5,nICSh[jP$FCstq 28a|:?,XÔ+Y[­W&0s}j!#'} yٌrk0q+<$}1suXĊw}{ޞ'.ģk\~h9iYȾ Fxv$>fL[F^V'ϣe3|ֶ̛7k喫<&ySۿF 'slxn]Sih}XD {o>jc>/fqPo)!ex}U8pUMqQ(Oȧ.rU= ([oX6+GM>fGԋWxy->sgҬ~ԛ˔h Gܗt\Fy[Vܑ`䵛fg&ضݾ!vd碢"Ҥ@D`6555q Ð88CQQ~jNZT/ػ'LN?Z~5U/^՗xYEKKnn'%brU~u ^ %.:EQB1tTv!8 j9hN>>8.f(e9x:Z 1+1O6DI ӖEO*pBq*Ӣl@SBD 7n q6VB'|v8Ĺ8dXq Pq%o(p&e*j\U״bzHp&`v!7\v7Hۜ4n3h"@U֓(WtͮOsB!tx=O`neě)8WS^Dqm`qU;=^{]pzB!9VNtA6t݉9lJ? ,NڭMu{z?BYY~~q[_q/B!B!~=p{4=/ x%c|b8~ -qk`}M]IB h;ծ*,^^7Ͱz&v]v/x'dBR3stNd{ ]@e;$MU9pp9Ey,ˣ[yX?2(ͷӖpy-/=|}k4)ik ߕtGӶK*FsHqL⺉t]ߧvqG!j67}=6Ć{ ߟ+C!B!:5b7R8BЇT??IK0~e+͓}ďg 5^xH/W ;m@m5>He5w)ͭWů/×x?uTU34?KBwKߦy˽(h+][߯kJsҼyޣŲ?aA5MxN(.cUYo%qLcGVje$f SU^L([:ENCCp" 8>Gרm!o<1j[pt-qn[ٴFhU!b/ ȏtiw#? w!Psm[o<"qpWAmc]W5utf,Xϣq Z7NSc7>t+`>P5e1xt냼٬n"/+)G,mN_sIp@KT+zJx -q<>K75SJV ow6G&YIQn4>S-K E [ ahJ&scFgy<5u`.ǥӗhb=9tDDz]cU[' Ӵ9>(JU,qӘp> 3hpY5 o28Yx-|2 ?a>^+yih7Ӗss17εZZǣ/~CvVWs}ߘĒuXCSso=s_5B/7_cu,%ťy|l>􀗙 g*~Z!B!bBd⧡(|7q;lh+}e8LE=5~0#[WmCܴ9jxoxZܺƺ5r8,a}g5nupNmͲ904N? 9Gsc44/+6߿r}l?>!i$7xi(c5 {|+B(OUC<jocDϹw'PNskdVKA!Bd"sSAH)N]NVB hMsv53 B[nuIc&VGh'%䦿SPc#sy# 琣} Dw7/gy bT8i[}m'luݍz{c;DMg nQ ufh\ 2YW D:Q5M"[RMM}I3V1Ӳih i* qN&og"XPY.ɑbae-/;ߞT9imrл8q,Q\H xp\t55-,O}A4f&:c]rUAo[-3}>3746*?=)筡{t]cµ eJmؖ fp-'1+U]sԧ lFOA0cFtDzD9Y>Fmx:zT?~;tM%3ztLˡGg8zݱ\{()c̃q_߯}qg^xײO>4F8t/NW7o:1Qn:k`#Ʒ=VFBrBv;L+T3t->XbcR,Nw]7.~AV^ d8w?ò <׳10y|3qQg&^:mu&yCfv"4FoK]c̀wyXv"v .ݻ0:f[Ͱ+\\DžҲ\p5xOZWF.AL~^{k Mv/d^6> )KL]F-'V‚VR'-;/q/Q=l8b1~?VMhhhW_^zq9`r#^ u\i0 3~x0#G#$n3f'F9RY;uI]gL ǣk,Y[/~ V}^v/17oh|kPb/&]}vAU_Gq>}3ʋOfڒ*p]n>XJSiw[2b (him|)[|뛭 q]UU=z4\p---B^\~I'qEQSSC,y XsN܇9Ѹ`0ŀ ƸT>>A}crؾ=x 8y01~+Զ$>Kӎ۟3ҳ.z4>n1e p^O6Q|tte;躊'ψd6)#96f"3z&5-SL~v}\,,UZ u fh$k[ *4R^QMj}ub"wL5Tj냄c& aU449ad?Ƽ;ڕmelinR,lƊ&'3!jܑݻME\νE>7wޙƋc'Si9خ 1o/Dz"Uh ǩZYO9燲R8gW㟢d׳V.;s&ΪkÄ/ҋfRP] 4sOʺ f;#uuka6Ǥ iW-nm=6:QU-8s9p'|m*HϖԴXӴBt6[/ZEii)_|1feQQQ_N߾}YbN?$eJ۪1qy9v(WY#PUMllFcٛ1xY%wKЎʈq<9nUQ0TeESm[x _;jPo On7Ƕ9n|hץ eN%խݹ[mw]lܰ,+$(++Kn `0Huu5t47 *`6 B0 )Nlşߌ恧K1t9hh睴c \ƅ'?07=u_Fq։CY]IIa& an}xFџƖWKCs#ꅢkscZ69iF}S CW&pŭc( EwPL?8 Pao2{:|^tE᪣bR(U Oos_j]3q{%8縡vpov@Oxh ߳isW8}(,e殷RAKk,7\<LsnP~2VPףQ /][f]]{e">Nf.IVV Z~[,4~PONF3uF^m;\z~; ;?<-{?BCFn*NrM< EQ㴴D0LE>]SOB!<~UUYr%>|LL]TWWEQQQjr]H$B @UU/_k׮XÒAuDQQ2:]tI}h8Jii)~4SԏDz,<'Y˲u]ux<.w!D<uuuTWWSPP@AAAܰ& RVVKϧP(u뺤ANNR:#%\Ca<ƭGl~u d|XÜ5(!nY88T7Pѥ}{w#ori Ghi h G6?Z=]uDZڛ*xtUu4%7LLkH_Vp̤WIhIJL"+,]]MzG^FѸI0'#gi1W^b`-_x*f+]0+0LOYǤm<aPdEME9t&nZ{p$.R<-Fo˩( \r%t֍h4رc$LEEw^*o;$VXܳ 1LSXQYKSkl $r.D}TZFz|NE" ]MYQ+`uU=ugSX͹XOۗ#Wrm#]49ģq~vAr48Х0®9\춍*\w^QOMC+i~/yFԻr&Cpv升.l[ >r ,_VKs0BNfny8ٙ>nl(`|D>u2|},Yqٽ,?`b6MW07e(Nb qG`>eTji+cU8t;cN۝4Ojϐ2x*jZK![I6} qu~sH?q(GE ͋1-`;;Kֶ5%=jssQd) N^@cS`(Niy]MlJ+vAu&N;Ú5k?4M=\m466sϱn:~_7PSS8E"^z%|0`N?t\Ų,x l&==p8L$K.$5VY+IJ,jjjk)))IcExwR+ {.}`ʕ̞=x-ZĘ1c|ضmۜCR!:S>K.%-- 4btޝ . rrЫWnjY%ȵJg^w[4U%3y/YxsytESU(7<5¬ \i5ZÌ׃NpImK XqpȐ,Z{7N rXv:7<5nEydXtڦ g2F !7:oMg;.8t'fn|b96 `bb3hh ѯN9׿u*iZj[o{ᡇ3+! s1瓙I,cҥ]9餓{>|8ir2}t: \eƌ,ZロL{v۶K/ԩSyw曉sM7qsEQTTmۨeYhFmm-*>=/2_=>OEBN2f 7x#DQƌʕ+QU4)((/@ IxdSɩԉYs s+=zY{~嵏51owZ!yl `pbE,tXMhb6Y늳7Q1 frc|ػڛ0:x|X@S1m?:CWpj*?&/#@2N<` &߿9`o~;qW?ogp's;gcWeʵ(݈ʅG__Џ~*g T_/;'7s|'$Y&;2x` BKK %%%y睩G}>555{b1222XfF-6o폯6im/JԖLlk-{GV;.ĝw;S[:joJ׮]94˗7l-M[7ejj QχxHKKKL}>iiix^,5K=1xttu0t ϋÊ&h>q#Q5cyP~C^tƌ[Xۡ( iz)..(O.^-jxU(n̲,֭[eY?^/q'XKEEYYY,^I&DXf ˖-ǃa̛7כhtx<wN,?~Rx뭷hnndff/W_FSS3go۶>}:ӧOg̜9u?>L0x*WœUD&=YUK_LaֲUOYA.+z|2c>]ʔE+n~%:0e tuf.]ɤHcAZx"ҿXrow[wk_c^2 Q}Y(w|.}I \pCTPA}k?HGmKg?7v$-MA,Y[ CPY]ϸs8ah60vLԋp,΂U^Uk\[#E~f:W{0 k7i6+W٬EԵohfɚZ s{>p4ΪuumGQ з?U\X׿1jPョkuM7uL"E4<@2e ˗/sУG" ɓ'S__9imm֭[ʕ+m}.PLJKKXn]*n~0MH$BNN]t# u{ N$'.~ L… q;jkkwx<0УG q ʕ+6mz{ҭ[7ǡ6Fl&//.]x֭ݺuc̙,\?xL<GQAuC8&R\\Lvv6iiitޝ'xb|>w%%%azJ%3p@*++>}:ᄅMmfذa躞mYG}4jEg$_^ٓӧ3o<88Ӱ,LuZƹKQQeŋBnn.ALӤ[n'n,YBkk+ڊtM;CܽYEm/>r]=2 Ӳ Fdӣ(Z#Qzѭ(uFAf:y yUXLÐ]i G G(̣ 3=>϶ٯ>a!kZ޷; Vv&L6Nk,Ekbe3[*|"f,[ImsEGeX́}3t>V(ߞrQ$57n9ggt/ȥKN&ѸIEqhږw-![C.<|Eq9eފ5DZHNOK$ZGרm2jѸn]-~A؎CˑC2j-_ҵue#q-uN9ڬ^y;NRT:uS+H`E8f#^8(eYG bPȲr抭OP g^o9,ʿ*]@GJ)D;GC͑E"fΜI$| @Ӵ~"mc&nqq]X,0KId 0 "HjlKUX MR}k,öƮM%lqαi 4Tʘ 7:]%otxR紥,h4СC),,Ht X~fJnIeO>1y#zmqߓd_jjLT4,lf}!''E8`-MVo\:E%H- MߙqD5<hq]|UU0?6Z*8.}l=v8&vj!6=2`o[5]AQU\!6* q+EQTbqq5%ўhDQ&bk^(̩\ū_O_cM396v2q\uyq\bsBUXV]w9y4N9fwL4M,J{E"; r& pj03̙YB-ݶؑ1Ƕ]pxv䳶7$[ Nqϓ1O?im[_7}Uq!;{;N+81tTm;Oel9msF;jo;ǶF6vrʦ} s]U<'۲HvΆG{d+-ʸlgRD;m5G#cviRɏ'2gZnmݯA+m6ѭMFcM4jo5{$nY6M4MEms"i6'sPQQAIIO>KJ>}:Hd\鷵ɜ8dgg3`J|{dTy봬B!5wiӦҲ9}r9}ZZ JTV\IeeN]K߾})((A=sۂ$(L_5 [ +([iwkv#WkJMS S,%[=w/vcqlrֈ!w0±8]smsbo;D6bŊNEmG۶)))*=uNvZX|>Il0Tx!B!ĞWSSڵkJ2FQbɽ4 nB[3%yUkݻ/ѹB]s+ VUѷp- G[ ^;N"u') DMūkpH%l%igg$n6HU㺤k}]D]$ށ%7{'S䝅x83vzL)#ۅB!s)U?mj/-ˢ_~ eI\tZ4mIxQDglw[eI~mͮ;;㧒m[_Sۓޘ*㝌wR>RBH-딎m"Hl|veٕv%Knu{E]&R6lv{nkvRxGٕ׎vd۫gWm)wCTHݞmQ+-WN$b7r [H/B!d^'\K@=NB!B!B!vpB.rZtz-B!:NT;҆Dgjw[pB!B!C}B4NڞK5wٸEt:tebk!)BHuGH?FWe+B!!D׎=!K2BȈ*L!BȜN@mOڟLɖR8B!B!B!ΓBH^XN !B|NImNV !B!B!^]6$R"7؃Eu]wBHB6Q!:Wk'mOtiI)#MDERB!:.eyZ\OIIJ!B!B!bB!B!B HJ!n"XB!DǞҹ!:$qO=i'>[dBc&B!BrB!VtnGN5Ulu%q/#DYu 㝐NWX_%ʞ)cХXe+uZHPܭ{m,}+ e8N;m|1p߭TP4p-)=<`f,˒n5Muli{8ض-AI)U,4wn\14Uvqlu,}Ȟ+k44=.n,5%[K\'#vgNe3>%.#J\nwmwhhݩ@LbXI&Vp',oGfff JP탩7$y(w,4 ZzjvNVVhTQQh 6Gfq>O DtIkkkjIÄbyҼݷRގH}OmL hgc[A9TupS c_( Y(.dA^1v,X,v[]P4o"]=PUT-@]m:;C4 A&6u`K3=.k=vwvz>GoO.?:Lu^,J<'{x ôe q`u%jʂ3 m7 7J`OvUb 3Q#p8n׶kvɊA1|(,ݻ_; wUU Rؓ ,tMֹ!B7B%?ͣ5ba;?EQ4)K뙼N Dl %CB!B$.cPu蚂+QwST#A:ܕMRGB!H]!D됗S!L~n.K!B<*B!.(H]!B܅B!\kB!B܅B!B!B]@B!B!B!. w!B!B!bB!B!B H]!B!B!$.B!B!BpB!B!B!v !B!B!܅B!B!B]@B!B!B!. w!B!B!bB!B!B H]!DJA!:Dt[!B.E l\q\WBfTlBFu,5lGDUUT}']Ŵc8o04 DÐ>KO14Ha!Bty3q]z֮]mR (DL chQ͟pJ( ('kӅZˊP" Mqxo7xtĎ38Y=9vulWaX݄Wp]gw6ou߅e(A`c)UA^j&܅uf͚5] Ȥ3PzgE0v biZ9H<Ă꩸d 88>UT!b݇:D0uOTi3Cvp>c&o=ӑ!0^ڎ뺉V?{4&6Cߑl;@e;}ug_qpv%%]HKk_}BtDpBt:m:;}'YbË+^; UUm4]*(E87.خ+ b踔d%e!vOT4jUDvM&f),`QXV(sw4]_VL +ww/{W)]-DbYtB H](Km( qPUV*FuEzUQ6m=u'6:ڥ۾eeӵjN< bU!ĎM]W6MWhwOVnqĎ(!U?ܶJng2Y|'B!ĎB!]o)u&^~D߸Hwf˓:B!v !BKT|;ShuFsWj\B!^L"B!B$uB! w!B !B!܅B!C_Bm^*w!:E;}BKI]y'r(Di.{ik) !BHB!n#B!l*B!B!BpB!B!B!vI)#:DNh!DjTF!}tBt^G!Į#+܅B!B!B]@V !BlX)!@w#}B!D&w!DX2BtAyZ!}7 Ks:4g!ؕ$B!B!B H]!bdݗB!B퓔2BN)RFQ) !:PmGޅ{i eu7Bz![!w!D,]g|o( xh4JFFFDJKK :(9( dggo5a0e>s.bvKs]e~Hu+k<o(* _e R^ȱg١t ãSh8J~q.e:( M-4U9L!ĞkҖbגswr_|c%//uw(f@]Yv-o&~8}bYQEn0qP]yGy'W_1qDx~MlU5il[z,Z^z 2hР`Kcǔyk//2?<-mˎזGuyMG >`nn6=Pϲe;v,gq%%%}۫b(`xt;+>=U! /:O:X4H]gUU/ߟȜ (хN= &u=:-AWicqBBPn*T]Mߡz<.WPl}κ5vٽ\vvpbضweO$B'DbǢY4o{:H lNz4)e*h]7Gq9>o@at3HtTU%.>0R`0u+WOr!p=yxG8q"s Hd@Ӵ{b14 ۶SǞq @4:,;!UUS늢FSu]T~=E$̮{yyo/QTZI,g3id%J"ED|D;E`x}b{vz,W$US<ģq8DE(B$>Iqt^KT2&>݋f$VGBQ!-=m;cԊd-H(B ݟH5S @4pK[X&nFTݶmTU%3 'LؖE8yFb7T9H ݇B,õ]5q܎ E|hF,m+cߋ&F4"0˲-[*.mqFU!%$.mAl\e̘1L8Fǯ~+([瞋eYʄ ӧpYf/~ :(tR{)MרY]ϘG=si,SoϹ#W Η&˟R F9c<USQۂ?.>P"Zۊwã+wM#Q5v,?ɛ_1{|RNx&s/9kx|k'gΔ8C~eyI ܿ/ <|Ӭgn[UC5*kϤ 1˼xq,_UUt@_)t)/~=W} yA>C;u=y4#O8.9US1 ww_7|x̤,+o Ewe+RL9{G1-{S闞 )Z%;| RB!.!w!D\Fy߿?=z`„ L:Çihhe˖Dի< >qB444P[[GQS4Ν˴iիgΜ9|ͼ |… ;w. <+Wr]wQTTȑ#IOO/Dz,4McOwތ1b 6mk׮TVV2yd e]ҥK/I]]Æ cĉ< 0{olJUU=~SO==CYY g}iӦ#w+B^֭}PUUW^_~ <իWs}pBaY/fܹx<y(((ओNӭr0D4bh4= xb.\{Gzz:]vgaԨQ 0/z 뺌?ZzD&f͚ŬYgϞ|466OkŶmFA8f8矟iaVΊ+oڵ+z{w 8 8H$4C$IC(JmЛ\!_WWGMMMj};;7nՋss뭷r;w.s%;;!C0oyE9T 5<~X3n[OP1;%|޷ZÔꊦk6Ud޴E :]Y6Ϳ 躳 r39C#x- i,awZ[t`<3K~d%,tSʬR{˓dǓGHL>L3ǟa[-MaD#1Jw~~ԭg3Ϣ[RjVCo 8mX4N$8 f$Lu]<^/>&/?2\zeyXhWqL֬XǒO_iZWwLb@λ4"VD*B!I]W꫹1 SrSO1p@.8F wiFMM hh4ʀ7nSNl%eYtIafϞMkk+EEE,[Zb3g1< ?s夓Nb=h4eY?ϽK=x衇(((bMy^x MƭE] _z饩6O?4<z|7g3=eee<ٓ`0]w;#Zۗn,>Sx >CF+©]wEZZ=^z)?>,_5 G.0 ,_w/f…0o"~~ 9h>ԬK'X ۧ7]}*'̦4V-]*q){ 9\38@*^}mdd Eya˟pq2b}®CG rAYi"QBѡGf!܅V2ܨ0 N?t cĈL6 41 qp.+z w~$_x  Yp!FJ 5j_*իK߿?555deeqg;`uuu455aYRֲj*:( ᄅUΖeb MƤIh'###%Z_~BFI:x|''[̱oY`&%??p8L߾}QU &xN94MqTU0 HM ˲p]￟W_}233Sds]X,m۩|\:ɼu pghmmSO=)SPUUE.],ݻӵkW`'W'T]#gx bt3i*իj-cE~qMͤgqܹG0Xt -cP0 aC?s1fE5q/۶ c ^8Vƌ|)[Ģ18(PWK$MJ,5hJ#HK{ë\BKSPkGxh8_z}^244x G9┃NzV"ƒƓFfvi}jI,f7L8{TBzV?~K/Zp7uc;/#)HyXM]Kj}N9TzM9C޽xw9#xw2e 't%%%W_qYgQTTD}}=uuu 2;`0~3Ƕ`Are{,c477sS__O}}=uuuhm-+V@UU+?_=̙3^{thnnNm,ܼQ]onnN1}).+?x. ٿ[[ﰡhƻL/Υfmo=;=69cQ5&gyNsC pi10 p(Bk((g|$>3S~~,W,۟aW y\λ4l ne-mڪjۡ)H,Z73[ڽl,;.kbp9Ȫ5dgi*0+k6dXd5h43Ԯ?b0S)H5*OH-b,a%M -t+BUUχtSJ^Q5x(m[45Xk\۶ϒȆ5#jExqu^U 7t8@ZjV&eb<3'9ffO@Cy(}K/e@"( ms饗rM7_̂ R+t^ub^x!~]RUUE(J [穧ࠃo\pՉwH;HO`vロN;p8:q׏X,466ru1{l% tRJKKyhll䪫bʕŋӥKy رcHOOGTWWk?УG,ˢ&.rnf|Aq gɒ%#}p=sѯ_?lfժUeYs?*x0ӟ())cرs=z,Y]y6lW_}5~F/k'dȐ!{ѻwo֮]Kcc#i.r wub ~a&z+WrAqD=xg;䭷ޢ.]d0⋹裏8묳8shmmE4~aFI<ˁ5+ya}w#_*]0cBa*Pt5pkS/>{ge=ve w\}%DQL|:=X]H8FFV}^כLze=KϦKOqwе{1w]fNC^q.E%CQV,kwWr/`ݪZzF GzWң_9z,_t1S?_5kugrUak8WsŤϧKTlsW Erj*tē)ˁG _)ԭk&zNksѭw)|.ƽ J{VN`42M]ȟ.;\'q\ěƥSwUѨm|lԿ= ,Y/>19}/O%[{G.TUէŶ˸c_`շW׉TU%“?dWSs-ıs~Vo|܂çpl*cmCcfG\s>+iq]IКpdfJzH!>f.ٶ7Lqq1٤h";:t(mmmw}TVVbflLDUU@TqHOO'ӵkW~SOQ]]mW_͚5kmCDR#{{I8G ӧeO}*Q^^~\=oM?~}zj"}ݔԩSW^7o;vСC4iżkڵ 42d&M~ٓٳg?+B4u]l_*#G7СCdddp]w1bƎˮ]xP2y8|0#GLWq]aÆQ]]M.]R]:D"nl7L.ڻw/=Ȳ&-%33v/ٳIOOGAuvI^^<444y={q>O珒?~<_9|0;v@UUNʽK߾}ioo[n 0R}u]4M ~_?;w2flBkk+Ç<,ˢ[n?^})S΃>H]]]MGi o~1c0{l9 7ԩS;v,Xn!㓟$H0(//?i+G<`Ĥoغv'[1e$G[K|6z/gET9Ā} L~4m? t79~pz|_fj:ZZ),gܔ 0.7~e s0|n*W6|o2lZX$N~ݸkyҳҸ)i@QC|85Yd HՋR#;wy-}⅓eY$ E! +8`p'U "˲,EIݱnEQRM<I8:jz;s҅˱=CQLIJ玺MvlWGQJu;c;;֭q666w^t]g߾}|;o~Im۩xuu=WX,v\:t9վ $}͔h4iy x<8hF Hݔu=5"cuˎ ܎uI4TSc!cvQGOW?iCxR}) nDt엎uT{T?h8rMreya 4MJy膿㱣xAxqlU4x b 섍Gr^dۖz(kx؎k;ql+W4?V׽T ]v`Ķ9+O:.f0y&ja%llN\9n,1cj Z* ;l-UW DI81v=ߠq`ffX̷׉^>溏\E(4^7[DAISLd"4_ L~BaښE-(\D3"b[ vm^7/DU܁``(Q~`4x,&S&a&eTOvnc%b!,+q8P:`czM &F*IĢ3#MQ5BxM7Ӭ[6ǿ<&M Ifv.HE q{;&  ܀yW@M df`ģTU0ĢRA`EQUft<׽"Fw!t2]q:6!{b8.qFNc}ZDZNT}/#;n*1ܑ>1^ _cB2=ZJcrSmb( Y8p 玻YS'Hg߹ĦcFjwcT8s\yv}yr;'n׉}L{.}ub2\z1qucuL"|c?$Ε'?od u qq8S7FǡDS"k-'acg?(nGONQT˲x?74 0{ܷ5gHU^Կѵg,V-{>ɼןaMg^|b <{wbBxyBWRup/_3TUU00 {wp-1;.vk|>gY4J ×=T.G! UGuKsi}yضͧ>)_[nLlwVeke뻔/F}yJkp2}wK{ξ]9,1<ׇu)`ώ Oo?Mr ꊽ<ï0eėKmu%^|댻v{+(3dז5,=}? 9zKuGҽ/]數ן}S`(cQDKަb/w|3|9{ |u|]ױ x=۸ңRH# 3Y>>~SlYacs} ?k@}[ybҹܽp4ɷGiyovǭ|=eb{nJK;d,~ gزv 9T%oǿ?ڲ'U/K1AB!3.b0qz\&G=^.(Ϟ={ҿԶbF_ .ۙEc\}EF ?F\ d#n.w| zI|$>Ur ٻc iCuo)(I7:C^Êo.p:wo'?/| LW,baHfᬗ8gߙZhn p=]z0~ʝw7/ѽBirg-,נQ<~h/{.A^)r(wjm-j [X4{]~Hcdfkcg`MQupz&]z##'}0<'552x$,+5~מo<[7QTVN"g3xyTU{_j2D9!DBq~LF xIJ,ioo'Ts^[#wfcٲf 8 Jžsm2|Ko_Hq^D#XD"nplD<,/J1 &sk GpdJX s XL3^r`(*5"팛rWr?1~e?QTx'lqsa(3IX8voE UGnA)eO67WD^a #'Ă7x?0lܵ,?=siVKyAwp2Yd6mɸ2du$bT\!-#QWM׹/O#y>Fǿjش@0̮ͫ1؄s*n@ f˚(?ozw;Ee=ؿsuմoFn3;_{0d92aǣ]aţg ;GMf뺥Yi,rIdɛo!3H]!B!.Q_ayu_d+(f]7p]+GQTz˳q\"J{oW5 *NMq]x@0 D"FuGR7_U5cyF DQɉS`Btt\D<>>EAߗB\$.Bq1o]pG *?i:vGFN'[PzN-kǘpGR ;RF(;~.M w3sp~MedzVU:Eehv~ fUը4d]Q`,yej2{yvPh:/QRJF!I !.ɉQ!.cV!%p> |VUX}陔OkS=?ml 5(j8}-bEaȘk9|:@Mm(fUQDrrO{zIKzV.~WpÏ˽7^aPT JV0 U/·D0.?!暮&aY$I0a  y"Gx,xؖ~730}.%?.(b['I:?a@U1?a~l]7Ѵg"q s g !DB!9^ !q 3Kqyo|~ c2^|{il߰spIN$ꡛA"MlZ1u2:4r 3@{k[.!mkvM"C4jys?|8n:oUtEعi%GSҭ}Ɩ`MVUʣTH\W;1! C(mS{0>ݛQTn 1 aRovo떒Gt.=c&Eul[=Dr HģCuFx, ioi{c%H$ؽRjy.Ode8M1ZK8==ٽu5 5UwGaszM7 IiOPUꊽ(BIƱ-TU[R[]ɶRrJ&-# uQ08{ C!c0 ǶPTH[+ Iރ5~\ Jҥgs.#q! w!B^JI!ćy3QRmpTUĿ|f<%Х) _~Bai76YȚoO+i:d_JI^ W/E[xxf0Pԥ'PU gL}UUذJhom$|^cEhok/SU)-mנ߾~ #C),ƽx ]7x?g:D[s=ߞG.bY @%s^aŔtl\]ٷc#{o ʆ`'%ْ:l_wTX>%zr/3Edd~\"^Zuխl\6_Enl^sߧ=YO;?ѱU ;{[/b+O}REb7: KzV_O_Qx_RW]AY>ٱ%s^#˄fe"nv`#O!$܅B!"鯳QU&Biq=z86GI>rg21տp_> 7s9G{iO2 C,Ɵoذ㯧!z $,r Kt֦z@gk|8[,U&x/ Q3dŬ]vjiذ|Օ{o@ lZ?]@Wm"JE=/~I'=מ?􏸎헔#$bQ4^yl䓍BpB\IE`@4 q%1B*AEr2OA))((\]!3;h{+ͤg&G :&\U/]z?;ޱ@0Nf%3swh*yz[b]9#'L"Sө=8gPjjMu EI@w߻fPܥ= ;.9oXsp.Kxqs pl DZRTz\=tv,ǿprlҳw_>%YkρuN"ls<[]%N;ټj?N^a)`hmi8^jMG4M/]{- y s-L\It+G<ǽhiɺ !D璄2^O4Çe DIIIB2 ÿ0 ,˺dG+_wA旼ԍΠ*`[W5Vمy){ocYEqq*`hy~b( a[rb=߈sݑˀ=IԿcB(JKc-3+t50ٰboǶ4^ 'Pz&}̿q`&뺴57Ϋ{? GeӶmU&+1Ͽm5p$XoG 3&-3kmbF^ukrN^?{c3pUY&C^ 'K8޿= cݒ9 ;`8[ǚ3-GaX&btPT\ס E?vlXs掇R%{9 $QZHȦ"tdNhihiy*] $2 ̄GaCi$Q 3ie:uD*gM3xDq=diǧIbvy.M5A @(c[޿ u48*hS+pPnBYw (B\GMM B! ȉ'}MiӦ=z4D₷iΜ9 WBSI>z_E!H`eeehD{ 42a:#KG*jٱ~7]z^^U1n?HCQȫ༏v{Y()/BӴ ]s`W%wTR֣}'͠e[EbeE?U["Y%<'DVcHDyf˶iG<žǦG޲s^&M+x_ӭ yuaM4ձg:ȝ<Ciӳx_@,PW' ]z'h=o.(f(L^QfO<au;oD[9`( Ee=y{ړٺƺ*-y?K8=⮼=رa)AQ]{ " 3տPܵiTWA V;6p/M}۱0{ڟظj>/.ɶ K)ޏ`8Й4ձoFuUTFnA)%p]PZΡbVYklZ>CƠ:gj;-Mٲw^ʽ;l<dy,wlYE]!oXNmt90.!4VGy.B(. w!erMw4 ]4-Ǎ6M0PU0P%s]T}С# 00My/@͆a:m?`W_}'cUUM-ĶZŌ3kSq&^.9t}pO /^L$aذaxi'8?Ol\T!:# >Pջ}POZ2bbOx4u UU0dOe(nh>*cK#9ǶH u]EaɤgpNfL( \39`$Uvw^w'jrt9%|OC}bq((-K/*lN!cs]\!+7'Dd3{=x,XHfN>yEGfn>YONA1 kj7)ևpz#'ko$ۤ&%<>Cƒ_LAIWr J/BYTن:Va K/&f~CX[ES} ]{gNuEaӳ>q8r2nU\˰ 7Rܥew#;̣i*# w!Ąy^%B\\eӦM:tbLd˖-,[^zaFEff&seȐ!p ֭[ټy3h5\S#ك gϦ~,ZyQZZ]w݅vͼyhoo'##o]㯺f/_뺔DŽ 4551sL,X$ӿ*lFUUXbb1ˍ7ޘJR8p3gj*TwҤI <˲ux<άY`ԨQL4 ͛7|rzƍ=z4̛7aÆqcYiRWWǬYhhh0 ZL<qfΜI~~>`e˖i'OfĈX뺼;̝;40`DlnVglܸŋc63rH/QF8fر% ^d?prI467WRW2 \}xr hibKHJfݬ}w#y\wu.쨤`85w4qE̛^Caۚ]&nCy2q+ݺv7ms=Ksb ٻ KZͣ_7dzSm3op=hn}ʸ1xhʡ}GX*-ˆɃ2 oXOlAT,a+:Xz ݇WsNJs}+XNkceЕ( zg欥9qHCcirO.[WC6J r.d*t-#OWä[Ǡi~" lY ˶a-rf8ҳikjgkK)Ȧp=x0,}k5pCrzl9c;-c⭣Sqvn˶yV/HKc I(V+صy?#'^?]ι̌4ֵ5bs`7Jf%oяFR޷DJط +#MPֽ͕ړ\\ 8濺7Y84ټb;oÊrq3BAuE-KZkc^!37LzVZ0ؼj K_CqB (Q*T>y֓S7p; UѨk?<4+ds/sK_cF_{W*{1 M7&O)aZtNt0N9.f0YTU$v"~|i^7DԿ&~'+!YyExcc;h%N. &ˤmэ& 6h@7M48$e䢶DZmP$`3cuCKC Soʼ)snl\v M7S}qc~hIlx170ccOF멟y؉ _R04q۹j8$=$PpB\V./2w祗^bӦM{̙3,իWS\\gуl\Euvލm 6'xX,W_Joܸg}'2f<㥗^GS7vZz-Nʐ!Chjj[o0 \E4˘1c޽;yk.?~m48 /֭[{={6eeerax ǤIW^! ѣGjkk~p 2m4vJAAi2j((,, 5}ǎKz >˲ӟĄ ͽtL=k+{?\-JCCUUޙzc(-fM#Mf.}G":;6e+K#;'H7ǨkNzGG7ֶP0C'8nԹnhQIsC C' ﰞl]k7OiTb5C)(#0B!P+QR^8RYK=i(8~9~]}KryA侇BkS;#LV^&Ee*mm.zq@Q?}FI+UӨ4#{EnQ@ hU 7ﰞ451 ӇPZC'^纏Llˌq}돢(TW2t߅aV뗜KP~VOİپv7wT2pLM rib-&0\anj(_oNρuYO0lgHPbaa&Gbؤ/KnQ6hƖ;y/o3a ՛L[xLy6a-tCg͔(4X V-GwhO42e ? s%փ H#+Yϰ d`ɬUlXQ+O 0pTo A<`M#ҳup`G%F:mwRQE1½:ضm[Qc'.=fnxI$*Gض{Ǽ;=$5ݠH%/ߏXxߝI(=)dj>mzpm?rd D .Swg?qArG;7sJ[sI$^8;'qO1fǶNytΰݮ"yZK~6v{eBtL*۶)**W^\{?E1rH&Oիihh< ;dTWWCNN識˲R9~ʽދq8o{/Ç'ңGt]g|_=H$bXE0믧W^dM(b,\#FХKlq䧖e1f8taп6nܘp477qƱb Ən6m 3g|'iݺu?1&L{Ջn!զc2i$V\Icc#3vXnv(//'77gy#F`&zb| _4M l޼95}Сl߾@ ĉdxavJnnnj_zmѵw mQb8#L<OVJ3e6܂lm(Bx *peyGKC+ f@oM_۲77WR $%R3e8% Ϲe ׏nT!Ic`W嗈r\2s3~ ,Đ %?1=kpX$i,>OAcB#K&9\ =D<\NcMΈ#>ŜNCn,uhmj#X( ۏRPˈ%\+a* u-Zdgb'l/#c+3Хw n5S'u?”Ldփ~]TUaצT|0u}+w2l (Q̈I3ee.%G& <Ʋ[{#IƸc+nHX;,eͣ3e(8]zJ eT,>w;'7' 5Mt]B"^T:zΐٰt *GYp3~2sұ-xX*ny:2sMzУWn{:b8]zc4*=F)ΐqؾnv¿9b'QyX!Pufy右R%UC{f%R7Vd4C)QDVnQxMt%{HW&c2w)LL~iP5}>$^fNAjp?+BqёB\"I c/x;&6m'O;q222x'Rgu/H0uTnf~CѧO8iRZZC=D׮]S ITOL on,ZZZo~CQQ&M"HmuQU+WRUUŷmL$ qFfΜy\ K6466LKKK|NSSͩ~G}z +Vn^,Pp8,ť~qA `-V&E䶪ipx4Ns] 547 \s]BiAU%e ٸtXMKC+mD'XqHkb {yqjP8@08 ֦vjhih#f$bfAŮL[i"Mpz۲Yd+uU kgA/B0 =3Hk.H#vf`ʝ<ݿX, gRY48W7vf<ϯ%y^2 m-f?+mF{1[i]زz'PXGY^\!#;USٰdnCQUZ"DZ$bm'`cox/la+KMteɬ^n7GhknO: Ѷ(~zvBi!boP0oOwZ0M?iYa:͊0zP~qU e1&a+K\`83ؿ6o0u KUkZÊ9X1wX۲)OIy!3y~Ua6 FKC+*DbDbFmys^\Bģ 漸yӖC' P=+'4I %X0}h'LB!ĕK&MB\f^.6nҥKF 2#G~zNCCVbСՋӧtR/^Lcc#dݺut֍Rn̙36lݻwGQVZիIOO=zD6mWf…ٳSRRrNuJuu5weʕ̝;7U'.iꫯrJ/^LEE'++bv믿ʕ+Y~=mۄa4M_d͚5,YfL0$--=z0c -ZYl9M6dbC 60bYf {ddd0m4VXŋٰa=zo߾4440mڴTРA[oH޽¶mJJJXbclܸ2S ݻwdɒT RO0\.:FyN0 a:c fqӗy6.ۆ453`vfU}n*T{Hw4f>WlghJQ|Ea=n`+ii0 z-X9o=(w'2inhJ,eX:{-]z5]e9RV݇hkoAM^q.9Yr`!M[;غfp=6o]6E~A`Ȥ[RU6a6.LfN}@ճEUGTֳ~6,yG'sQӹ^+ٰt"3;n3B`MNaYrBiAlۡ$ 6r/ʞ-(^DqM!ݩS;//e&돪,xmG*(YLVn&KZMaY>%9,}k5ytYB]Yh \ͦY`#xG%`̕VA]uw%#;EoJBiA-ñҲ҈ya[V!3id8G*Xxk2=^O0lJi*omogMtFw]4Y:k5[VJ @4S5;~1mSSF_.E4Ǝ_<jZ+xz䒢(F͡zb%9e ;]隚,s8uܘm-U5INA&F2qR(Ci9[WG;128q98Uew#a~}b{ζ6n.Fzzśpuncџ)(}RQzߩӝ:Vuq=︑+#u=g8N;~=G+_seul%E{=7{}~>b(?^*KMU=[WC<;gR}ۿ:-96P*^ ͵ \GB\< C ( w!D2iB\2~1}ן)y{e} S9'gj9' MM/ym=,~B[O ݧ\_[Ω͙{v{w'ík40f0lN=1ҙ9rα\ EhQXOBѹ$.)eY444rmCF{ q_#. #nf-ڌaxǵwO{ؖ}?1^pl9eӹuwO_N:!p[j4c;XuEoJB!I]!Ĺ'~=c"BŠ?iHx~ 8o"#܅6r !D瓄Bx:)".  <)e bc c︣sDB!R$ w!{* T5OOW|K-RRb9] 7Jw;X;vߝC_wt9`L>S*")I]!D']wt"!}wd.R< RM!:$܅Bq1IȤB\cFǣr\y|#B!.MpB!8#.s4zWz/DEKXY!B!B!⃓B!gQBs !> r>BN$ w!B!~c]^*.6קO/{q,H]q_ ʧG!x+q(B|H\]!!#܅B!z!zBŞ2B\.9|y2i1+BqSFg!tRRF!B!."B!tw!B2BlsQ=<+qX!.B!BqRBt&I !BB|yFaq]HXH]!:B\~ S=CB:HI!ć@32Ǣ j9dK4H]qY<Bh#>gt/V+ څ/.~e)*rɝ% w!19]q#s] ۱:w!BqI_H_B!.Bq=B!BqH]! #mqkB!BB!Ӓ1evr/}[!B KB!.eJ̅B!L*B!B!Bt.Bq.6yڦ( i*`%+ԜK[Տw$W!B!.VpB!kĢq'ߣ1\}oSa潴5_Ky߮8sNW5`IJl h='/e !B!I !B\.`g$;}!5u)dATo 9nhT9}GDc\}x7J[KYk:uko{_W3?¶T UU1fjԺm$biذ| yAg~2Lq(Gץa[6ÁH{MPU۲EǕt1 3`j~)+nHXɲ:σD,jD#1A5.X"5B Ёjޝ>7n}$8p 0cEQh+nk-b\,[!B IB!Ce lϓ?x#}a9l^o=TUUp]}<\n';Z0y>](/I.uj <}" etvr sAIBb8ٱa_y*e\KŸK?7w>ZHJ#'?=Sÿ'F!K\>^ݲ!%cvE^!B\2]!B|Nޥ4__M߰^z# 8Qm{Nmr=W4 ط/a߶~k[<|_ǀrYhʺy{Hڃnf~O>?(*׿Ǔ3|C{j sp.B!BpB!xGkMH-"S֣q,-_c;Ǒd~ɞE~q.%݊ѷ 񘅢n^MWܷK7-vVܯ_He zQTV@Ff}"OF=&=][ !B OB!Wd>B'&=c>Y=mH( 5UuL,}Pﻆ0둈%E)-/q?8c;(*ӂq]0 uDZ ۲1_B(*+ +X߸|+O%*y6̀37MKNjX  `Y65±]vmK%ikfO*؎eۤeϦ5KmEҽB!w (B!|Gj㺔u/Ёj|~WnоjV/\S?}33rPF_3esV֋9=[_dMt5Ɯi X:kY>o O[44T̘k崵D((òlb[+=\M]KFv:_ZVP(c-ҋ#'?h[;*Y0c 0%݊ݷJCM{3t@ޝ7GsC mQ:=Xh#!Ҳhilcƽ} vk2~2o"Z8%obILОYo,4vx,|smI k`wE&iFm,/jl?Kp7r}ײUd=4e潺U 1l@BiA<§gt[NTz!B!>dqI !B\.;ƨ( [Vmgul]\2v NsC+f(>?vګn}yv=[qխ((:%s#ɷ !B*!B!JS} {ѴkW^חy9[v}%~=\G͡TUb!Z[QՋ*B,g"B!$B!W_p#5~wi!̀QST0:vܲ4] By2̀>MS:1lˢg1ĢSnn,(x,0?#M]EcmE]0$+X PЉHdd{HT͠IcM["n vElYnuP8ȀQY9o3tBM-оjvm܇y ٛn$b TMbava4շwX.Nhơl^V[ZvAddH}~!BqѓB!WPjsp=- T쩢ro5U'O*D#1`s7} Dڢ, SYCA09ٻ8+)lgwe{G@b(jlD-1oT&7$ĘQ lX"Hle{٥ >nr/wg>3w̙3 f>ߙ?'oe[̛߫`Zi,,JmU=͔?恟'0t gf,ݗ>"^},lv0 7S[UϺUXrko24x_o MV{&}l ,dhKgMغ*4nU<0mmeLx}-@8f椹; UVÖ ]7y}yC,x+ xyXIL8ذzޙ~7V/Yˌ>?# El>xm:/mXX}g SP4aBmM=O%ӬX4xBB YUlXڪ:֮Hm+6R_=!@""""t8e|{O%=(T%77?OW\@QcK2PPe?7G4GmiEAq+>uo0~t!Y V/^G>m%sVYWmۜxѼt`}_3$I"a K5y=uq\HQBX2VN۾Nd<~ :q|t3~|3>Zq_Vh2njs<ܻ=E ;?0cbY&{罗2`dO<ϧ^t,f_s} K9p Uz%K%p.u51bp%7?z0RZEmLx=TaPa+ia>$:F$#LNla@20ꌡ"6 s9e2.'e{ GBoM"0R&=kcXIةa ,[yp_jJ;5K 8v0`l2"փGuR`xo,bmLx]$HTl Sed.Xf}fe뷲nF&2/L ٲ yKI"Rzi[6c2^~M ˈDBl^WU[(ԆX}Lz F.a@4+m[=tpvh 7{""""":EDDDۇ 0M?熑i2*<ԴA]y` }+x׬zxߤ,L.wyv]Hey8慿璛ҮKTڠѰ&6b\1n[H(l,d)A7)J7yM3UV |Bav]wDOM.D :yOY8ǝ=':FmG8'L8M9a`./uzc_^mAzUvA^\z@!])TL>N@r]?!H ZԶ#z׵RB"  AC? /}޴͆Ѹ }㢪 pa M:m /?4, ;dma-vftЙ~#z:.g/q\찝4A}M=m͈>[7naѽ'|X` Nà{θIο sگ#vbAcH2A>Aey5yUד'9i(B06B$0:%N%#;+dQ[YG}Mw-! ]G6KeoZSF}m,},2Re0M3;}%Z2W/^O" - 3 heH5Ժap4zJfNph{ƻ!HwIsNybdfgpgb,Lz:+Iai>]|!"U{_]v`l<^~-.l.L.N OE:lϖ TqUA]ذz t;d~&,Le{{Go}BMUmZQWSϪ8i:}3e]p%'WC=ѹw+f[-? 3'?xtod7VnF׏ ƲM\~2/?&NY@QBnd2NXj2eUsԉ`y ;q9|6m!M9fyѓ!u= K G<Ҧ}וOpgEDDDD#J ̤!CefϞM޽ճ"-=w-߯9Ĺ^fYnضmQSUiH:lGmYʫ GCIOn),o(Wb` f.e/nTUPTZ@"P4Ȍ[CMe51L e$\buqZN2^CJ4dQmstLW/hӊHF˶aEOh۩ וcEyr8~RE|LFV4U6Dì_VAm;ӥoGtɝ=|7)}Pa+ԅ’|5C+R :hGn47L/a t(Tmm5.٩:!6ljaHqx"!buq|ZZ}@grƨ,& 7?PĦbsyDa*T%+'P$Dm,dɄCQB8b:[7W>PئUJ<#%7?!I:̝m>=/YQOخ_0L}EDDD`R]DED/'pfHKNi,afCI HFCv*pL]0ReD iJ6M;duYY{7\>L ں+H$0MH$9C$Ilذz)**:pRA@<0 f֭/(( //oDZ,k#H$B!B ȡE :n8֭[@(nov]{s=!CлwozE޽2dcǎ_Dyy988p ={W^ۗÇsUW1mڴ=c˖-uY }6lW]ufo3d s2l0N;46m}qXz5+W$/tz(.""""_p7Ϗ>hF_afoP]z5Ǐg̘1ҥi>:.]{,ZEO_.++cqbuuu.>bɒ%,Y?555L2JOΪU8묳Xpaz[2qDnv o۷+_\zt뮻v p' /_y{[MS]]^xO?}mz뭷x饗;pw+|4<)""""""S WTT0o޼ѣG1vX>(~:'p999l޼7|> x׹y7ˎ&M9CMM Ō;b6˗/W^aҥϽދyi^iPheoDմisק={$??ٳg_˾s 7[_=w@8 /&''7K/1av\}_}?`ʔ),^^z]ǏO??󛕫}?=/_uѡCtIDDDD-&^VVƪUү{_8l/--gNh6spm.~!{/WQQ7ޘ|9s&m+VP[[_uM7SO`qv u]@j0ܧzs94_=~}-]sN_~̟?*zyWԅ;O<}.PhRZ~}[DDDDSý R+eoKs̙ m<;|o?O***>2txΝyw dgg裏r'Ju] UoNgwڕ/?07nHпw 7ҥbVZń eeeq_?쳻mĉټy3CeȑͿ\٬$ͨQ.""}q$s9HZL2<r.M5Og̘1{6[w}s/1o<ү}6lW^>r4<@z22 oܸ1=iqq1^{W޻Kҥuf͚aGZ߬O?O|L4gyf%}DDD8#0 =T|eܤ89""""r𵘒2b I&;vӗr1Ǥˌ̝; /ptR-[eY{RQVVFEE}ٗu_;;;_~[n^Ffww̘1#}cxԩSի7p@;8{=8Æ k6MYY'NRuwUNc9FiDD'krMm>IS< |KV:DDDDD@KYp8~8_j~r!ާu-|ʕkk֬IvR2;*,,[nvگϣ(d=V"L䩧m` ,;NN;ma jkkI$D"4]tƍoo;㏧wk"""4˰i׍._0 [ 0ס5! PIhyoAbM=c_j~K322٧5ͬlԪU}Zf( + u74ϔ)S_^^~d,Y B;9쳹;)//gܹL2ѣG?] """a(D^>B:ޜ>܌0cNH^fWi2""""r{~~>mڴIg/^K}&iH)ӴFW]x.B5_ҫW/.~k׮wqt1q.jwЁN;gyHko _wy:,% aCꈯ@Vr0#)"""""GyWKYmҷot殻D"X,Fmm>}n֭EEEkMKQ__,wN>P%^jXoɓ'p-0h h4cnå^ټ?z*۷_i&t7ȑlI+sI'O6> ϫYܝ+Vs-M6vȺuՐߝ[۶M=4S?L˴v0]1;LSyy9^{m:Tzk֬/nN:$Ux0aBzF-*~E w_0G${o^?S^^ӁTaÆ}evޝOO? 6;f3lٲ`u#qXf!MyGuƌG?iH7o^z/#''cǦ_?r}]:ui""""""""""{>}7~ꫯrwg'MĨQ={v>fZjcn: ߺukF~} ?6{???B Pw9uwԩSz&?piG~T?_}%`Y'Ng۶ms9jj77h6(33f \r 诎H e*--'O_~ex zM߾}c۶m̛7e˖H$Tpi-R~_r5,:뮻#Fټy3 ƍ#Lp7 v^{->(`6m\s ]v`}z6222o~M7ݴ6=o=]eժUu];G!j_I 5w@477~O?*ϟ 7q,p8̓O>UW]O"^c{n(**cƎ~t)tCAhCP<̹瞻yz|\tElrss9Sy׿sGPݻ h׮]ͳ>ˉ'HNNNӔrW0uTn喽ǯk׿ҭ[f\ԩSK#33lJKKwٶnݺa6]t~:u"Fw>b׿RTTDFF3gdٲe;vȄ i}G?6m}\vehtɘɕW^Ivv6o^uDDDDDDDDDZ(#J ̤!CefϞM޽bcYYY4l޼uֱn:\ץ;Ҷm}^c˖-dffҦMNy֭cڵlٲ۶i۶-;wu֟Yx1֭#һwo:wa5i۶m FTTTЪU+ 6r'=fo޼:vۏd5kְf*++1MvQRRBii)7x{?OL9/oӦM{;[n_S&W'fW#;jSDDDDDZ#?///=6mЦM >E.]yz˲ԩ~>|n,VZѪUBP{{޽{Atd2٬,ͅ^_"""""""""Ҳ=:u*3fR>luDw~~)йsguDw=X~=ZE]N]R]d~ih׮~:EDDDDDDDDDvV^Ν9F| ??_""""""""""\tE*##"""""""""D%eDDDDDDDDDDDEDDDDDDDDDDEDDDDDDDDDDEDDDDDDDDDDEDDDDDDDDDDEDDDDDDDDDDEDDDDDDDDDDEDDDDDDDDDDEDDDDDDDDDDEDDDDDDDDDDp_ʨ}mQiq ##֭[aCapO&,][bpT¾ؖ,Uy$CxF$˴,&bg3DDDD@rw@DD>?Xy&N4lLT  )+b>\خc(u4p9OeVϥ ad:}\D8M+(HPc肒$ݙVcv{EDDD>0ؙdG::,uW%bgb4*$""""RDDDD ~7[40(RXB""""rh5-" 8I2D"$I|${ ""@Gv]QnWoL-64MXvm۶)))d2ρw˲燐}{y"9 =%49mfgffs9S)((q.Ӳ,֯_ϳ>뺜}LyChK] """"" N:$뺔1i$y֭[֮]ˈ#0 uy뭷裏?O?ws1[s1 H$–-[?>K,<:wСC)))Imf˖-̜9CұcG>c>c({,={$daG1o<$}adee/ 4df͚Ŗ-[eߟp8L2Res֭[ٳ9D"7:N:$:vyDQ>}:K.Ŷm СC B$fDDDc RK ܻ""""rveamxǶm(--tsӟ@QQO<w}_gy]PWWm6AD>}:w}7^{-uuu<6>C?gرbݮ[8fӦM}L6-y]t7 {Ng{シ m{,wy'$IB'O{?SLalڴ;p8/~ VZeYAwq}\_DDDDDDDDD%9"2Ax;w.'p "LbfܹsO939ټy3l}q> e]F}W^y|~8]618cF[6l:O=2d ZyWbڴi\|\tEX'|~;>CL2w?<#G뮣*{1{=n?ט#>?O6m`&K,n#Hpw2dbO=Ǐw1 C%ODDD1ʈcE=O?4AP]]͆ ҥ ~;YYY6 8'I:,tuwĿiժhu ômۖ'2o<6oL}6;;￟QF. `ٲeL> СC]0SNeʔ)1o==kϞ=>|8i8}?<]t=[&H ݻ7\s SN壏>b͖S__G0zh;80  5kMa&SNN# H$ҡCڶmi̘11cPTTDMM d:835ki6kرc=z4>[neƌjՊ;ZjŰa b>o#=CDDDDDDDDD-:ý1 ysPWWi?_{wxP(yyydeey^aP 믳p˖-# rZMj۩јm5KJJvj8y[;7};nݺX\\זeQUUEEE|{k,PSSuvtʈ8e}_2{NyKnݚG̘1ӧrJx,xǬB*t--o[g)+4H$ɡ[RYY, B,[|\|Aziu]ǧ~#++ 2ڄB!Zj֭[Ӄ6 ǃ ##h4J8׿5pYHg7|9Ru?," 999]UV v*yj*b{,qtv|2> `ԩF ]GL>0m7ւ1b^xGaa!;wf,\P(Gcx`!+C9ˀP 6qF6o[oOSbgq:tq]y6`]H$B}}== , r{0j(?~1D"yG8f̘1<,[,<+W2n8NzhT9L/HѢK4fY{{;?zhTN;[w}rI'sWӫW//^Lmm-:t`;Foڶ]Od.]pM7;3GyH$ʕ+ `ddd-D";luttgFzRwYqѦ[E8Ӄ:aͦmv"o|1~0FxG}}=aXҥQ8Ʋ,q]p8Lff&dY_xqqt`? W98;wk׮eswqr 6!,e)[iRo6e/;P#8YfA2\ c}v;$AM}'Km bA8B[-J_0m}?o\)"""-YpS}."""""W,wӴe6hNˀ" HW]q|luȑ!am p.m:._$VnfLyXE8~v7Mp$D"L""""GCp*q.s/d-+sl\Ai6e[lP?e0f$ѥ{a#!6/cɜA@ρ6-3u@a6ݡ.]HOwØ2DDsvA/)cY&Օ5YtW=+xWٺyյ&aXbUu/uINMg#ajY`%ьw<}.u# h"u)%U6ɄC$棷?ܫ$ai5n5xaU6iߵ-HÅm[H& rٺ::vkKvnɄX! ;dym7*vK,'bɤC Za&K ⶅ8I˶pkWlmiӾ}0uSחM"$7?p$L4}5S[]Oib Kqnz>Օ6ťolcz# 7pp [V*{GAF/kf""r EB|<~+~u]}&8/]er'3m0\q:~ʹ_?Nن ч:p${/ȿ6+қk[VғF!qgKxjh׹kz9ǟ5O&WaPܶ5kIv,~C{{>u1uﳼ8I'=ϯ[A?.9sh_oqes=OR_|iȷ~x H'pcNj-0 $G0;!eX@4#+% q7TWѩgFv<&U߅>KqO;h]ff&hq>wMO$L$77W!""XhF!'针'ڧD6+߽mU1v,a#$k9SrO.u\&~St߅*xɷo_J:sèeʛ3_!x. g-aʍ~@N^COQI!9, OOdC~`| N0 Xqg8D,AAݰ,MS_s%O^M\wgs7ONa,A{p +)n[(.""""rXClذp8'G,"//O!""ӡqxXv݋}͔קi~Э*l 2协膳uQnŔ7g0hT?ni۩z\#HҦC1ޫb`7td} 8g0wB.lI"s=LSټCѸثyxyERUKrsUcm.LLѾ-""""rpر#7oN ֤,w9ByGǎST^/ }kjY|R_0 `2QQVaqIоK)S]YöJڴ/C1xҫ#ٹYAR!kq\QIfvqC6shz B #5Uߑ'y[h2*l# c&u]D"x@(bK%eҮk)㒈]R<ף&>uQu\rZek'b ,J zoCapʢG,\۶t#+N$I:t@II:DDDKq)x׸QSu9zf?'΢KX0slVl(3ұ{;J m3$ח1wBJ;s=f7xС{;]Aƭ| 9l^W5[T6{ӵM+ulJ3غy6N=qxO7oֹ$؍]Jؼ Va&N&!Q=m[c,40 T] –-[={6{> ߰aK.qޥ}?=ֱcGuv{;[n_IDʒnK*i O|Sm3`DnݷiӾuHēضzt֎: AmUQ>|se{>$cIbqz> >zc,´`]!x1a`b\Wv=C!CS~ϙ4~*㟞@Asw1ùGغeH;oo`ICΘSf~궋n~uӟRm-<'(j|.u\V/]]KŖmB6b~|B^xd<<>ؽ=s=F?~NmO~AE8IW0H5 C""""rPTwz֬YömۈM*-aD"rrrСDp9jwH4̶*\̚e1 >C{wh,<kӏQY^M|Lr3I!\ YRsuQVQW[O^A.%|^Kp]ⶅtў] 0-0X6% f-f[ Ey?<‘8XȲy+,б{{v.u\.@2 Ytޞ:/' Ua.% GClRY|Qw-cm[X]lh۩ ٭,ZNI zF~]ظf Sڱ y~@(lim3?Zu-ni_yD"aV/[ǧͧ.;1xT?r3qu_ٶ hF]bZ!ᮀ{ub Kdh(XwK* RXE(dCø$AL8M r6mNA8JY]MMqNjp#}Xa"!LӀ<7v5-p8n/xې H,Lgçn_ݰ p HՆolo${jT+|g'&}d{5JCX>6-3? H2 j{""""r0U-a-l&''GSDDDdo |.r$ɤ %`vJ~@a`4g{ 5xG,ABw\\w"B}y:8{$$w^; m}EDDD""""G 'EDDDDD;EDDD0!m7JiDDDDD-ݏAgP= *@% CvDDDZ/^iDDDDD{w}_ V Ҳׁ{rU`ekDDDDDDDDD1gOa!L  kT/@EDDȏ JPo\g}uh+KRx?liLA zG""A!""""""Wu4"ۥe!aAMP^"""""""""r92'A}C]8`D!oh}_DD DpxeW@ri-rDK&!&?DDDDDDDDDW{lY^]X䁑Vk.9^ F 3==}@Ei{w jpZD"""""""""oT"G P]DDDDDDDDcPQl@DDQ%گEDDD3r2,j"""%EDDDDDnN?%i2"#|7KѰq_w@DDq~Z:ڴ_j9xπMuMkf8IpFo)uIݟPnٵMwf/XǜWMI+p<}t>)""!UMѩC@f`#@pϊ'&q?2 $)ӸaLʹ?a%aμ$ZgWmu6{UeS!qҒWovmgsE'p]5qcSb쳆B.zxcm\f3GQ>|V"ЮKt)\og`Yi=4W@ 33{ Iyx2eAUqu$""rm GBtߙ$Sw5 7w PDH74;ci<ץ[Ntٖ75iӾ|mdlB6-㌋O$O~u<7{ ؞yyiߧ1XI^A6 ೫}={:Cyx#{3-ۄfٻ18Ϗ~՛* 7 VǬOV2kJjn8<+ض.5mM+SRWHN ,U+0m2VB`ZM [ɚeLK6DB/ U]U'`ΜU;A GC|jfXκu[S ;|6g5YM2bFC;ly_3`Z1 nLhh0LXm, 4&K ޿ Z4s( $HXS_jk :u1\z|' }~6Lp$D2ۀP8l t s= Hg yAS{\2a<'pClPO$b B}Lj;51$lHM5 ˰vXL$̎r-瓙M'Ǿ^u S}m qv*MTwOg[2wF&L7. !ںK<̇T(.םלٶW?<'o]Ya&}1^AOrM`΢ {'dJ~xo2 4 ;+X>[*;3gsV{^KGĽ?9v[ۢ>ɽO~@eu 69vzIX&u xzCliũz[模] Ѥ|$ /U*_R D[*|f~y z _; ""c$2gԅlXS摈'iۥ.L{{N@^xg܇TWoXN8¡0b3d!+;<Ldތż Շ%LQM_&%0Vb- AiХwFl:<ܤ$i Yd=$\ s}Baw^io"V'鐑e+}g0b0 |g؉y`OmϽD.ߘǎGZ'ȌRܾ5o<>^yלC ܫ=s-#uਓ2|`id.M"dcIN8{Cay8IhӾ5c.=hVᢂͲyyS'l/צc017A][0 <23mP$wg30-3' bAO]U=<2P_|V]GƘkʻûz VnYcڅFqτWܻ-16%O^{tןBvf?Zoagy籛:!p-0 yQ08;ǶL̦dlV.z*£9Vn|;mƜ6HAw |"a\ ܍^C2]/PN&0\yew-ᴋM"6ْ G FQکhf^/L7%KspW}#f8o9’| /mnxo3s0|c fMס]קCR0VEI4q9vQ"!6WJ"0 2s2:y^΋e?4Ǔ;uIqyʺe bq&<9tӁD߿hA*~أ0x/Q_##'Vhob|1q8adwxO^|s޵gzCk\ӗZDDDD_iY=N4R Phg^Kr7]*5c\~F<>k/Iܣx\~Ȇr,&uռ|2!Ǝ80-IReA~׷\ן &_<7'- HWZY|39* 1'S_.;?@F>a]yɛ ?kF}ֹ`iݟz(x>p*i%~ ǷS%jwG~y1p*w,0 27U`ڭqN Y_-Ɯw"TzعDde_C2ᤪ5d!A@FVL2\It߹ K@>h!'Y~@]M}L(lSPԊQg S1Ʉàc RRozg۳,"/?#{q\$-a;,"8jOVn&ٹddFoK>5s]1)T5e#! w0~u2ټsWqugv|/ #;J.m6q6x6X uIo<>䦳=T{t,%(d¡C|3y?4*HԦ]6\\/Ŵ,f)y9dDY6wiнGL]eأaˤ}&0KӪ0>VE) 2D>/kMn~n y¡tFiOgSܾdҡc1}k >YddF88e0u1u-Cʫi۩P]DDDD~@NFDMU|~nޙ ;SSyk!SFu.oL^S6?[Ͳ5ݟZ&ݑm~M9&-{\p08TӯO;nXt#oMY̘r# rܾ?\LmeC=fg43/?pnfjY1l7΀~.ο_n:#6%QtlϤju9DCXSλӖҧ[o^2SeC/O˰~Se||IƜ=aɼX%U]DDD{^cI$Dvn&Xq4HĒ<X2w=wאnd 0LTpK;I1ĩO"d<ΧX־6 f.4/%7F`zRt&I$Kdƻs5eޙگ#2iR_#K2gBf?YS3ZؐL$޿3uqfLz`m܆>tVddERED,IaiA*7#``4lπ>C{a$݆PWc[y&f֔y`.]K GBz_wǜ9>ϼ6A2TU0wb,_;d5TmNB zE^$L$ Yjc`˲pyzj퉈ȡ0,)Ӥ>M׀u 8YaXķձh3`l2Yj35u N9'92RYNCɖ0WƹWH6:k;[֐^e|h={Db{{ P-˪Y%r9: 9 ӎߙDz5etؑ7pN2BluH:dLrs2O8<׷5) :F<Ქ= >q6""c\+dcobN|0R`mA\ ̝s/.N43B8fkӱBVƲ- R,cJ̨֯Ӈ2pTo,ۢbS%G4(v8D(²ME8" MxGo}…9~#zL8dfg0ob 04q;daLA(d5\Hˠ}kF:A!V0 M"$QHMui]OaI>c.;%$NjІr2qxCq5qأ1 x;dd ^Q'I6S& HV858l4q}R|?[?Xm<5m  6Sc:Ne۬]G$#+ʲy8ᜑC̝7}~#zھvƴMBh(} Xjw"K͈|*? +/3Sl?0,#5E`4ݟu(""""ߑ1hA>;͇k8Gm}a4Hآ(m[irA<:n:}g._ye`@}:/YUƯ4hf`R Ӟo/0 rZe19-He4ZFN~6#F"^`Ŭ\M˘5i.xvt* ɤy̟(g'a篦c+a͒кcͬ]NS[U@2sЪ0hf4V˚%dٰj3`mѥoGBV̝܂b1fOόIm +Wgr:B!UKS~+]|^S/G٬[3pҾ[)[7W_:.y3ߛKqքaŚx\ h]h>b*iBe2o"rZeMmC6ǝ5}S/"""""_pvx3oNY#A!#(ODQ~Nr0`yOm1jhgv(ؼa+nb QKMg옩g $Tb|r2C|aXcN't0YY 'F@8ZX}".@DBE,O:" q7s"{D[[hг3dۓTwZP&/7#U:Gw@DDd_?7zPئSHPd5VӡW[Yt6mu\jyl.\Xªhץ lcvƈɠ;Q=QP܊o|Vnb· 0(lӊ 3&5abt,=^N^a{s}Ɖ$kddF1 TUԐpگ+z 2i]ϠcbZ&N¡s\xLzyߞM(c#OaʷM"d)d3q0]zgб}[i׭W5y`ns=<ףuTgSes7.%#+$bI^N4+a¢S+)(10xi1-L:y K HƓچ1W̒Vћ`lJ:3dܡאD6NGZLˢSvt$eResS%fLن 6)ctَd¡Q=GCL1L"aξT脛tZ6lZ[Fib*˫)R㺩̨""""r8B2!uʰ&dAV^Em8O*:{5۶4*2vt_ɼ>Oe| 1tmW@F$Ĭٶ̆3@$u5O,[SNFQ.dYfjqg]aDlvm9kUԱ\Lg#6$=ڵɥci>sm`r 鼽̍mmk5_G~n}AɄQ1$Xc x{㑌'`!!m`Y&6$+L5>븄!λL|a}LsT4nݮryІvL')T州5:vϰaxú3`ToqC85CQ(,g'%1 '4$(h/niXg0-3q立 e uǞuA{~װҮki8M2j>uװ j{zcƾܷ]w&=!An$wU>Ze2vt_|ԀY2lGXg]>ētԚ=J`=YjraڭVCǥc|NэK6`q],$VnH||CO&=K>4\ ;$$$cII:3vZoKa۝wú6n/'6yoӬ6Lo>MdlDܡG24m%/50+L%yO"""""_n4<~@m,5[K:??~1!=5͋o禱`f.|q&Vp9C-^8'c`RRx#U7_q ߾EsLh)[egQ[g`>[t2Oc'+"8'Wl7ж(/" ҌŁV$1g~"0ą hӱo8DIхwCR {.ѾM.ɤU@@4b}bo.>kZJ4k+71>Y @8dӦu|}JmF![ .3pKMf)? jOr)}_g~&祉-ߓ{5 1N݇q?L r2#tTȕ M vQOex#й]>>=3;߿-dw}Hz ։ o Sw,仗9"""oȗa`> g/x5ME==hpV$UR]-[ƚOaL:u'Me^7=q2 `݆J"aܝ زDCti6TFd}%6S[8N]R%fI+(Vm/ɥMi`re`[_SκD#6=*ꛬSd ocV+H]hر 2BlXUfEճ+bS]VCUmvŹ=NU+ekiЦ0 1LS{bfBczAi=w-߯m""rs}3i]B"|͞1\SG}EkrT90 S"""""CaE ";@,g..EE6\T XbWgTXt\PeӋsC,I2߿]}>iS$ݺl8suȑ%DDD>%[Gɗ=*7ڷEDDDD6e; ""Goȗ݋"w@DDDDDDDDD%a}$r8 R} DD haIT΃ fH]!""x*P8"iCpv; r pd@;DDd4;x[DDDDD8 @h#*0\9D~5, 2פ z<ۨODDDDDDDDDa⢋,[] [ 9@GCOᬁW Z^PFDD>4[DDDD:<V{ ?I`-*-Am@l/II~-""""r ؇m BPx5G# ك!4S),aVz*)#-""""r0/Ow9d]dDDDDD&%׮'c&Ria'uv;VD"""EB""""r8be|UmFܭ2lmQiq4, cS"""_\(&)-tFtmzI郄L:%7Zi*.-1KVfCNy!g&9DDs^}M08w[Gp+?""""rWOO@Qt:}P6)[.Y}f&١<|j`_l(Àw<2B60A:@+&iAJ"""""\pkpyy~mC^8^;*-Y?.Ym>Z ݊3 'aѫPZe/xuLw`tIOUǩ_U[Mi^'$""""ap{3dG hד#%ɉҭ^ iX'g ʥ ' ۫cEDDDDDVd5K̡K_#;ΌՋXu][PW0tVkHz rxrD !3ʊ "{ܭ |BVWP0m,3LmJ!""""""""r8FHA૜0 &""""""""rf #"""""""""2Pz J4dȡ0p29a` ̬ j땒3 ;d{>5}"""""""""#a#!M"}2$I\wdM$HI8\GB A~*Oņr2dde~Z?I"""""""""/)z˶l93iT.[0`D=(ZIxߜA}]orHh,A"J8D"w}0RedEycԩG߈iв.x"""""""""4=^W.d%xS"Y6%9y;ν?{w'Wuqsed]qw#$!!hpwJZB HKB}(w C$ewgyf7 4}@W6dg{s7a`vonwY0s_CҤy abl۷ BiP{x uYmPYc=3&K{SI2)`?&,dN0H+ȩ `CTVwo_":T\l&lu˲cmr>x 7PY"Xvl0q|/ 9q2i˂D"wI2D<7l}c xdD P dg+yѷlbO9C:n@6HE$qMó7^>CJN=~A)|>w;wTPTZ@8).+>9w>d/?3?9֋ smOkP7NDa1X"F/?3OܫNAC㥽?ó7p%<~,܂\>r0ǜ9۱4C9 өEDDDDDDDDV]53wgoDOR}&K wyWǰ}:QԤ.c懟kSWRXR@$q>__GvlIi\桿<䷦Jez{marn.f~4mҦS+V,ZC?䷦q]ߧ}'b<~s_7]R]%wgruwQW9=sY|&\ʷW`as8x=nN\סK4o۔3oBM_ B6„'ީyS2oB ߰ē16'`8£1dkoX[EsҤE)a{|gs9Ne]W3k\5X̖ [iֺ3B6ą?>h?}ǁmbDaD-ϩ*ůI$xe-J៾K7k_}0 ; ܫNm7!oA~a^v8#w<̏r9ʓ]2){oy O3|op>Ay61V.^Óxv]Zs~Dib9.saY;~/>Cz#w<ǀ}һC>tzΏov]0g|ndƟ=ރ!l䎟K~uϵ ˶Xpv ه:L|²,ª/x/8c=ض͎?1oBf~4Ccؾ<7>ٽ\opɣHU]R)zeUm2Y4g)'~hξD8XSߝ{oy:ӱG;8v}#ǯy۔Sõwrciݱ|ǀ%v}Z6۶j/M^=ο4I~HQi! rs]D209l׌s mhpc1^ʇ>}6SI&~D?;/Oj~km3!ce<[v[dd//Dc4-$த*SD&[;JӼM]tdGXre•@6KIe,S6-fr>TE֒18~&mF^AZҳ{apg$WMj6U{ f- /?9ϯ ȉW?YpQoHqY!;*<PC]]C=7?S?d''vvz|'=F5[s[y7 E` `l۴x"?c0&" uDQH5k:K,`ȃ-ȡ|GE^ƣȃx^f%(۩nn$ʳڮM6elBKd=2ylZ0 ,0-ضmnIJJ I${l(m^d[7mIlɒ][>Q ccN5OޝW0)(Ƕ-`8ӥOGjkOˠ1}rP6ocw>@*Sܚ Tuep{~ FQ^l2 ;Pcjo96a_ 7콯hR ˲ejo Y( ټa+e-JZcν$ 15S_>H]S Àae-4k]F_"dņhu+&B\U3~1Z+ýjA K hҢD$r7)dp-5)bg%T#OècNg0yWB6MkOZUC*RēK@sUi۹>k֖ pn>޻o==p%M8hݱ9ߜN 1}nj#w ˍ5ڪ1"""""""""Rԯ{v[:",kN|6s>|}g}l"w ’ KYj;SP\@`j˶Ų,J{!e-ko m(ؕ!M[6 '?ȣIej-"2L1^{Yʏ︂Tyf~Nn^}^DaD$qʮcv ?|^~˶= %vJ{T&zd [;lcV[ݾ~mfrxв#opmf!He6~vRlϲZ(LYRV,^S8lX 6[VZfvlF """""""""u]?7|Opx2Y'O̍}U|z 1ޜN27N,lr[NiץT]Z3'e$Iq\۪&%oץeEL)_2#2eeشf3YWȹ?v'"ͮ6u?| m=ڻ30 =49AUAV}|W CˆTE+H$hֺL:CF/ZFo&ȉ̍lZt70}⧬[$kȉE!zcZC4/s_S/=v][Ela[3'\3IWMZrP>z}*Qql'Mz30ȁd2Ïk}H27!' 8Ty W3O8Gp%Zv 7ν&[d#(,'Uf|4aG9GٞݿaH^Q.b껳yrIy[m>h۹o?-;4c杴lߌn:U.<:lǘG3OeZkod~ ݛL&C215&q).""""""""RIS'F7r/ϧ(kk[Ln~g]yarշ^=7>y|˲RS w,9k0d!d~y>3?oτ^&oA ^| n]Ī~|)}!i8Ey~(lfۦZ g\u9a8E\u˅yۼ;10ˆr r9Ѩv; -HrrGec0" # 9D%]+N`5;ADai>'|^>d<1 OKŷnCn aN}q U%1ogMضE-5+M[I{([erP>3V,^@.3͊IvA3>M[Sߛy+()+b}h׵5-5e1LamοTƜ49-`1Vѽ_'.#Uk_5 6o1' خeۼ[ FLFҒ_w -b쥤+3ѩg;:lK~Q.6{ΦS϶}g>s >e~# CןEkΡz{c껳Yt[1ѲCsP0(*+קu6ZoRv][q/ϣeۦDQ1aWq!|6smAaiukU=y =x$]bCiѡ zK(]DDDDDDDDiA@،][aٜ(TeDӕi 3a؎]kٚv,2>˘r1{!;+9 cIeꊇQHhө%ڵ=QD Foce6ZJAI>w%N},(.+ elPqm͐}ʖ&0 Y!͊8qىTλef?Ux؎MQ<>϶5帮3UxsYcm(2^@y!^PdEeñ_QgBx_k_,ˢǀNҵ0$Aм]unس-uK{kǀ|=_T0ALkj0IBe/؜l .aH^a.۔`<8Q3'[a~U6Lqj?+˖ _c!c||Fvƭ%e(2$1λdLx]|}r r TyE\8 2Fw{ÓNtב_w5Kd5oqwkC^(mVD:Q(S]DDDDDDDDF~鴇s5 u.E:{ԤIEDDDDDDDD($5z]ǀ#_?L*Dǀp7c"ԐF4ֲJ"""""""""uX Njp^XII(]Oqn3uHQ:Q(eGz eLwP˲)ȧk~ik?ilƱ],Vg4*.› 4VU=wvSzB:yA;u4x+. 3_](u^AP(U4'2m;,4NMPQpl;WRQCN/2!K7cɦq:+f0DQmX gE-颀HQ1'} |n*&&ciطcM2;Wqkh_C#"" ''^@pQ_|ewXa_d!"""" [7Mqq; ~D^2bNL܊Oyz SiӌK5=V׈H#9f?xdW mܲluH o߆OgYe;WiP riW܅=ΡO$u4s=0ǵ4,n}x˲b0=Z(WDDD2vWU"iH DDDD1H}""""Ri)d JdY&Fic[DDDDzpl}%/Ƞ44д ΀ lW軈4B6 QH]Uo}pIKq[_ҠXlN3cN+#ZнE:GDDTH}! h\EDDDU[3 ba݊(H8{0]Jd] >][ͯ.粱ֱP#"".!iSu'"""" C ;YMafDP_96 nO~ញkiQ}iR#""""""""RGԻu^%QV4Ld4ɆOO5GULj*F ӫe.m+] ZޒٖUqB""" #"""""RwicWnPi#t%1M^aJuHQ2ܷUQDNQ]-lk~FsT)AipZcZDDDw5-]\".FuSIi4EDDDmrUzAߛ[]]SfhHc>uLhDWu""""'>nܥߚvWT'J34DDDDW]P߮ n"&*iAј:] wcpdοo˲|?|%؍%;C|>#<6Fr#LDDU+ wlk4$̗\ڮMlX&; ZD}°^{,ןaѼu:ve&ѭH8vEDDDD>%e$=O0{ʻ+u܏ x6׍?lu+u:wM"1zu """"""""""uL <7Ig!\, ˲gUqwN0 hծ AՓf:[x?K>ŀIt4Գ@DDL#:wº aYd۶/חq&^ܭ7 _=ll! zAV-[Cs^CYxlj;i uO`;t%:7`Yg,XǍA9TV丳/g%#Ze-2Sg0޿@l\9Sg̏(߱Ҧ?P<ضê ?#,_ ^cx> ÀdN֮7gѧSLDs'Q֢mU_doX9&[ϱehÐ5`,R]DDDx"ΛϾ|\.9ћg{@^A.p0t@m'rL1,?5j߂0~w?L&7?L# C ~ >PܘfƝs#lٰ~{3 HS?'Mʖ﹬'9s,IiӖdҕLz^y\{i۩ƍ%xǙ;lGbض_ݗ?w^{oq\(d2o$.``_"}To';~AmI|/̈́?:LW;ᓉ/& 7gڤי?9 OU+[wqLMwغi=%M>~~zI%x_ѻ~K,S'??ɀW[""Ҩ~u c}vV,^ŝ۞%K<͊ū),)- N&eeǘ8ka8+~XUA<qlWGTuqq*vTк} r_!V/_w~M0UeW;QxA,Í;WZV6k> C2؎M<òlZuh32LlǩZf?v%$rUGD~k+Mls]| N,01XS9."""ҰπpeXH˂YS㗗SR֜t?]zˤy{y[O$mW8ǣwq'p?qǟ2k;x)Tˮgѧb\ړGIcdM}!co}L|IFҭJuQn0+be8ϻz'|Lny{9o8#?}7175瀑^V:F!V._L\s}2(?n_2e^{uU;Zc|sAFB"""-^3ǑÍW%{7^Lݸq%rC[6lfyqaricH& DN#orzJIYQ,+们\"3'"q1y#xOأ /⇜|1lc">xu 8Тms< e21mOƙ,8#8/\2 5>~wǞs8x%Q>=weh۹evy*vTұ{;ƪka>zDu+7co1g|ܸQ}97)$ [ FMV<2>]r< A""""rʴy祇Wv;WPY2wX<.zc;?۶u̚;m~~/m̝!/=r'Wff ;|./UepNG,t*S'ƧS'2K ?>l6}`6ˏ fsH )kц2ZwF~Ȥ* OQc+v7(߾v8gќO8)>=`ˆ^}kHvnB9eLDDD,3G=z#в]30 -UKiٮ9pSΫ)mVL?˶i֪ o?;+׋E<{_7O^A.e-r*n̞2+~s!V7B2'eX r ?e-J),)O3\ó9't &:օ/kلKV3ùrАokdhڲ kF,q6w; f,MVb.<0y5}|jOÆ余<ϗ2ɼn߳uvvjEeE$^ʖٰz3JfLC& dC"k>%(t},ˢEqi3lڕK'IMѶsONU^='F(m֊RLdsYT C7N]ع} X}`bY6lylj1pQLKbqb[Xee봧++؉Ʋ_=IlD:p<2Tu;8CN=Yr~U<'76zrgAHӖ+(WC>:tJҕvR/>p\vLollw]ðq W}>6'}hQZrÝߧs^_s'g,u]V-]==6S6lfrMЬU&:x1)CAq>[7m?>|ArOeͲuLz|fN5s>ϰC}1Y\pzq8K](21Nj, ; čXfdpvӃD2yR(bਣh۩'xYbIٗ#oxD0 qcqF?BhweؖQ=0 8sԭ_{&<ny6Wee[;Dte/OĈ":4mل6sէѺ} "c(Q).+²;$ #8;HԷEDDDkS/.=B Kp  O~Q)A׾2O u]xdZaR;ˤ2dqDa@AQ1D̟1iȷQDN~!ɜ<Ʒi١yeۤitɷ=Lz)^|\;(:| 7EDD83'ល͜_}S!pwOwW5}O{̞2b:},[qʅ?^dU܊u+l-.='" &ʰjZ/\e?ޟœx[θx.Tï'_CٰfM[s[9btَ_oM_?8?<ϗؽ-'\p4L_^ܩ{|㷜rѱL8~~{Iu6^BQB7xtEԟ-6g>EM 9cCybYnf :/͔7Q3fq1?"0ht_?*52_CN~!]̴2d;?UUvܪ1]6 r3hI (2L X^[Î[Xt!%e6`vlݴ: Ty 7'B w+eкSl w}㸸nx"E) ߸I?źUKH*qƨ>DDD+ #<QhȤ3Ԋ9ӈ"Cέ,O1dl~zW򇈈|mW{getCv{yL*Zx]`6^&EsX0cGv MjOjs{W/[7oo^C"' OSަUn?/&̭/a_D6X`8y^: W~G!渔h _kalݸR4o!ǜͻ/<#w2z k/b 9IQq <'RRւb_Mb't/=20fzWUecY-jc O'H*/*L~q)t ׇٌaHN~w;wW"s;rzOQ08h^|0 2TWx/[Lރu??< K(,iʶM(߱bF? ˱o>{ M[w U[6Һc=J0 R]f"""gLݪmAUי'i+{Fav&edXML&S;!#2ә6{Mv_VVk1dvTX׫^N ko^Շeanl]RuZ'SVUEQD:ngiW24nCAc"a3r yS]9УH}F{t3{!aa'ѼM'DPucE!|>2(q]N{:/ࠡg{m]͡'ϻ/<Ďmiݱ;>{I| m;֕=~ EM1gٰvZg(iҜמnWO^gt9_}[6ҪcwyDaAˬXȤSt3G{,r׍a;~&]r9ǝ |7oL(ʮdh׵v'JavTL:Eҵ0 Au}׺8xlIshz < qDAWB{d 9zM8! iTT%mcGEn u Lu}DACqL/`Wv DʆFv:YVƪJfXD.m\NDDDqˎzU.JY$1> Jf, Th6L&0{m ~?jKvow׆p25OT07^BDDD@QӆUZDDDDzp7z/ZKkj:ZQo^~nW0&Ms\u1!cY:FDDDD DDDDYýOCμ˶c/͙43nDs"""" M4>deFz | pl+{9݌HS]DDDaQI:s+m?fÎ4K7򾚒x X-+. N@] """Ҁԯ.KߖPW-^Wg dail' NDDDDzpOll "4Zad"CnQg׮Eq~IĜz˜X2"""""Rի{yIʰHd^h#,W"""_cseF-Z[5ey*^t2*(͍4Jm~{&.ݚCDDkg[TOݲ-}QS]0SDDDDz7i ʘdvz4ˏD@ñ,*2!KV5""" ("""""E *Npsj" nĤ- ˂kۣ]1""HX2\6HǍףdž>ZǺ- $b(2 *=ӄ34SLjH+܎oH"""" [_7|Lbzٛ[SF9OX@aCǦI.ӊn5Q4V %]""""" [7YAolIn]-<W!""f8iD/7nӮ4wSDDDASfظq HrG4 JπK۱ٸv VoMVEz/ HvVHW|Ǧ|G/=&>K,WCp7^%>ŤR~4 ƀiMR4c{۶'ccȤyg[6ly1C$ IWdxtۉʙB s~@ˊd V2O4ToS"gɼ̙t*Cm8mna{Yt=CKӖGl\D6M U^DDDDzp7Ag~?=b]duVHbY[W}I6i4v1 mWˇoLg͊ l\cfF֭HnmXd-Otא,_{o~swM[&vnG=7'Vl??˷~|9 ܸ˔wf0O8ȁēq~CVCNKLys&On~3?ǂsU'{ok f6ب@4 lA'/RKWkDD yI+vV_ :x2Fey0Yp54Sޜ1ٚT(4c!UM؎M2 8aմܒQF8YbAUuظv yٷʂLcrF7#zRBYDDDDzp|6@;(.;'Fax3^$J/""ҠZ7`؄aHLT/EaF7%5yO‰cཀྵ^78;*1&csGdr4kS1ï1Qj<'0t*1 n1z]%e"""" ]66ܲp*fݳedя~OceS1FD {x!,K,^LoMZС{[wkC,hR,,(P^@Dtٖi[âKyXMֆ|>9}6Ҋmvfz^{Q|/$B*|W"""" HpVN FޓpXBu i,'AsIX Әާl^g:s7''/q ʷWyۦxiwto9q_ONd֝|8a*¢KB֯H)(㤋{&0bb9qR啌8z 0>u8:-i1۷ C?m4mv^zm6L&៿{te=3~ %f3E)˲v1@DD݅7ox|AC&B?eY.af"Yr#;(Te6\s SZvhƵdڔqgd2бg[laу((ζUPמV}JӺclmt\NHlv,?n|kEDDDzVýj%e|EYUyV_H#: bqx"wFDkcRMZЬM $&8˚(;QkY ˂n;c7'"Ld#nKFDaDf3-"'/Ycc1-"""f?Ov/hTk.^ pߛƣkL)ho&""WGf]nt HA<1Eylײ:ݲ,ҞS}.2ؖ%+IeмI s>_Ό+(]hR_]~ҧ |F7o]1WɜDm;+yg|nFQ^.{tebҞAzkO"""""""""uI,)]4@d0!@ew=7e9Ƿ1gg^ؖE1yV3[ѳ3d| \yC,]G~m&]Ts +'?' FVo ; _]\pztoDZy}{^jֺ9R5oŷ'Ϭ^y+ΫIx飙\s,^zٲ~͓ر}_ i'A]ԉHTOkgĽjD"y9Zs/II\NXa~1e CEczd3l? y鲳È۞~ߞL'L9=qa=b, />abY _><[vTp1cѳz}Zٸ}7_teqso[ѯs[=c<498>ɿt*,Zߙ֝qŹ ڎʴG2}fnc׏GۘlƸmgع^mZ6/סS˦;y|#H\<O#oOWV!=L#:UjuoYɃ?3øyoxZ6[nuiZ\p/oLk[d27>QϿèA:a}ڦGOܛ2r L> %""ԇ0yIa-"""Ұ42M&|P{B޾Z E2=>j,Ve[вI);*#{uIa> Wo %l*ҤS  SAX]+~G̙ UCf ځs砮^NusPWf麍T=J h.a}57D`U9jAtoӂOathQıRǎt3]ԉH]W{e3o8dVO[3sppL};{=fL6!>sz-KƳ+z}1_EIn-9 w?"6Qdw^&S#:6nm*}z]vv=fLDI^e`:WI!vz{=g>.ocr^.cH\1%k7ɂω~~?"""~ҐXT""""RԿ _*2չa+;/q)ѶD˥Gq?;{GߛJ4sۍiWdWi]&v?nUPϮwwFh݌[3c*vsjR̿^aժ kZuڡv}=jg3糏GQ1p3UyOݻ Qdjo\MWLv}C ""Ҩ4\}si@n (mY mˊiݤ°5cXv} ,\Mh߬q c~e".XB|~w \xκ^Zf`[Эu3RQ]Gd"bM^Ƶ-1QDAN^v V^ώMElӜd%7#"д0VEoVRU*TcMh״mW'[d"lυU;]>@"#ٲd5V K;F7Ne:!2!2].zLa\H(<D8Dx#:14$ۚAŞk{.K%U%u~* u^U(T RҌ0vUbQrnfޑb6}fܶ!J64zgz10ug`jp-&DQ'$A<Rq=@i@w0[`d8@IPM; Cg[*sPPK!F ppt/slides/_rels/slide1.xml.relsĕN0EHC=qZԤ *Jl#{Z#.)Xts}t2gw%WNQ53\*'ϫ+84sҠ#d|xɭEТ]N7:F.55ꐔ*ai+Z@tej;HL<'vt mjq* l>'i8*w8 ݄v#iC@<$QClj%c:x4MzȔ`8S 3qc-=z¸>ED'rɹ5XvYMvm|#eGu]mo.PK!c3 EF^ppt/diagrams/data1.xml}io\ّ?Ż/.7nm. ,*U☋IUWHQ\$~IRd}I%/_,ĉxq˿rrj}tvKzbyzx^At_O'߾^/r}oևo'7g훳]tz\(^-~Z-N^^\d%NG/w߯?D^FQ-P,^3*RrR5sƪ_Ag/x]~ul}ó7os#??ַxYU\.hZ_>xy>:ywrkx.|xv|Z,\\ⵗ/޽=~9ũt,Vȋ?,8=[En?~{t~v,N.úXk\zsFn|q;r%R6^ϫKV0~?WW O %=_}\8p?5$*1OzT56z]-V?k|/8SWyHktH[9F78=RSl&+-ڐSIQ >W6˸4'd-ظ2LNA8E)gO A?M6 /_)LfRr}v38̴'5؅BIJ>%| xӶ~FL.7Mm>6Bffk2t6*b?)hiW]L+l ]JYADPgj#Ӳ/afs+ս}y\3lK|ի~O|mRj/j9rV:>㒨IUfWƘ=c*b6\͂Y8 1"&ۂ~ڵ MQQyRV&ec~sBʏũF<|?>Ęv) s1#nzz\m/3/Sx_SBM kp_#9̥4y\cƆ҈>3/g5z r J^mE?_d6Q!T5(oԒs3/y܀1ls Oe熍ִzSS03eRޑ1$g}&#buiJ![5]i-ΜlΖ{ME!h3Ӟ#Dӽ33՚lmDW>*f7LJ#CL& U:4ju!U#Si:Qr3վ_a\ꮆABYS^Q/%v3Aɶڽ01cd;Lŵ胵73jϽ]*jq~吏ykl& |Mu+RP6*P~o2h҃\%A+G\ھ)]V!k#L%ĻHɶV;gUwr*jf4Ff=c9:;g1Ϲ{ ,N[Z.zhes`L :jrSw{r9 *iլC\LCdq2G.AS=vzC| &ۂ7c!TGt<859JC?q'sw.kf߳gʿWzzf*<*Nr66Ýн]9WTzTvJyV7[Rg*HFL6-wmTkl *ސ"6Ε] L3u]ʈ\tLg*>ST|Lw3V 7)gr+*#{~&5:1Bo#ejsn,gQAE#isİبp w0i9*q$퍓fHxr6^Ù |f3lo[-rL'iKIک nGch.{]kuQ\᝶[|,%xo*g zrTL *E633+2+l-S7z,rh [ C?6f˓w콙yfY9] UFF[.A8KVJJ9vn:rf^2[9Y1/f}Sb&Ϡ΁s媇޸P&ۊsE#҈KҜyp+Ɲ87ϜIs|pfk179N<1gf_ƴ )9diN|"O]s]Q5t(*YB4gR} 93_TёZB.$df9)jw=گhҐMpn)։ Pه93393;0gfOi6ɻyWG(S | L'O-9_kjڻ4QK!`On{vG{Wn16ߩq-4(8k u!kl >wc ;ne& kuw}$=B?SǦޜ(=SvʹknJ7|>vl|z?o Ft)ɹ"Gy6h8bkfܓu>xRW}Ek5,~_zf}B۵;>y~q7T E8}=y޼l[6Njzy=/UDf$;c>NKK1+nSKU|݄T3=kKSwYK-C}ա>ܵHLU\)h<gH+[i^ߚ. n++JN[&znXUHgy'[ \}{tZxu#]=ݸǏY_zq?v?P?@oz䏧:|W|W?|}z\No/p;PDm|+˛FcT$/7>v/ib#϶kdTaؖkhrXA|yRn.;f(%Y.֐BH~:k/$_H1iՇn-lh H)g¬ӔjsbM4\s tzluxYkG .ˁ4(nJq8|cfS FT SŌ]VNb$8+\%( kV7{U ]JWW?J}޺]v[m-Nչ֝ ҏ2])l2Rd M'KN$x|osRo11:ROc\6W } :(6mlG&-szݮ?|o)a>5^:hfJ]q&𰺿] 0$kRzJMR2ИkqeKc"}IerV!ǘ1"_ROI# ?4Q a8!Ol jT3E`Wm=l_"4=@ Q/7ДhB ٚf&GqO([s% "jxd8RhSdOqg549!UX+dP!vp(j"%%̣1j$!WTx'BGe%L5`32acgKlGd:YMI 2Tʩk!AB=  HZF^atgu4Ģ_9&)ĭ&WJPԪOe*HɴlëÓSA2$xqn~ %8`>+L((-8̸k? -ɃBLr# [N;6aj@`.d$K,Z Ǿa TqY ](R"6x`0MO  0ī'Q!@J[)SȖ¨ B=]:T!#/v` 7-C2=u.tHsU*dv)iDk@X kQ;O(DJ(ɂ}a2Zg5pH~2T4{|TE[wӚ6Ze*T <ر`nN^ )U(j0xA* kmE2STCQPEUdnRlL ;F$M<}uәM"a0';Y*^h䂲oe+`WU%oԒ#4VkeAoDZJw]omzV|҃O[J]0nMd<'VD&\SM9x8llC6;Ha/*LN)vB}Q  a؅mlb,vR4|6Y3JxeZL d 5Z(&*[yo–fk*wy(:ltd=,'{LuڹbnT)o4affb鰳;zz0X{ D;Pi`Fcl+RGP&[0z)ۅ2 sր8 M9bL هUl%9H>!evrrJ0i lnrWCJ4=k}.z+?ݻ~GDC 93J4 082,Mƛͻ%乏_&Y+LT3X 1isJ(h;^Wu0Q$,Ha\i ͶvVjjUtRBJ$G%ӷ1,D5EGGݭ4`vN#r&;(+9ѷ[>+Jnm;jl,UʣFA.)]>c7!bKsLŵ胵7blf,5\\J΁$`UqOUx+@dd|Q+0BN2L-ysrcBCrHıhx؁T`$\$U=Ɛu=['K;aǾ$.L8yHӏo@T[vm,Kpf F l#D^\n*ֽ3X&U1fK1N6V-ukfFcj7Q2@GZR=-)naو,[fdAR82Lg+F!ʎN '(W:`Z"UL*)5('{7YbNTѕo>3i2]=ҔGOOňy@lCfC\E5$'2y!'w!>dJ ڊF)BL1yxC!n\s@ƪ{BzluZ`2'w)tJ*Xx;09z7-;NH{3|o6\SxH&z'#v "zɻb62w“K_:lprhBO$*P H=u?laF܀^2Tre! ݷ>KgJ|KkHGTeՈ: 6}@/tn +i02GQRvm~z½RwkEUE( 5'׷wŽkT4Ƈ:px,T(6&?(#%1HytD %!nj@{g 65*d]1XFP LFG'=)_h:$C d~ Y Įfœz?ԫ< Nf:Ń㞀T2'dҨ!]NksB@>s2u&d`J+%dE#mcG| =6ĴxƆw}\%2Z(ӋK r9:$JZЁ<ܽ5 NZ4}`hlͰ##3\Z Qt4ΉZ$m=M$ôҩ @٦¨+xP&>ȗlSݦ'nZ%*MiqdL9E>k/V(9k/%ިdIzrav'&Li $)Wh}Lwd䥹 U:<v8%M[&8& M,bPR,+puVyl|5#}lt8Q4G[Pʅh2~{#q⠽/ZY|ȥl[-(7mVt>QdIeEF}pbpKc&o`pC.5W_w%ޥ;w`In m3uPBjqE~e 4X[Os ޒ[qAz0%.;l[lf `W P KLgV!kT0n(J>P\D/xdM Np6|m֭7VT$9J +7A՚~)(# T}<9vl&oZuw }o8! 7\s}!1R0SzG=o("_ZŬ5=Dxl# ф^3W t!Al71em0Ҝ:u=EԱjyމ[ L}7'yPW7/ tZc Ȱ>]U9p̓td&î@]uۆ<4scӷ6uU)/'{&Xp9(;my<&~6UFlK쩜a)n|T5|t.%NG !xvP^`ۻz 5rز3CDװjCbr7&"u9f5YnxVy0@*6v"i⇫k u+Ɓ=6GAsy7=+'T;8E mYh75WΓ-po Ilɿ s=Qq8{a )&9_Yir0+r c[{wDާ~pɏ[I3 7Fyw%vybF`G$!"NWrH&N:"Q B7οBv\%_mnpL4-w1" &bƒ^#b./"BH>.hm`)Icalzefc۸bуHL)#p\gډX`n@8hσ_>0-ufrÜFeUfsSXq#`у5Q [fEi0,\l F ܈x3a\rEOgq0*#g\`~{pN'\ʜ*А)UM+K,0֙ױ+)ޥ[f3nU(X Qrq^pۄ$g\pz댱m-j#1>ܶخnK7 cVю1"}{M=nWq][h';n[w\wJ0I* g+S4' >8 ;+<իIe ϛÁPGt%N,޶ ` &_S=N8tpZ CpO+<ǤtTS*@SzS|cq&)-F^{1N|pCoMcx)׻vpTO._cCxX((ZH 2w­VNL6 @ b[xR*Z8)7(#-CkIة^k9'`E1JًJ ~\%b9) D{۹ evS=yOO]p sh]-]x0R˂Tu |UaHy)dnI)߭ޒ [ˀjǂ-1ަ_N@@ly p{|90~>^5j03w~?#nV: ί&&k0P]k EBֈ1 vĎ4\t"%p&^%`MS<{}TVh`d`1PoPeNBrMi'G'jbHٍOjVp9*/m-MŜ3>0S͸j_)ONm;FaL/苐b6YW Be\<ۢYr͢TIB?i\ecԊhop;yU'')-R'mI1niP"gq)}$]Yp>P)hcV> h_/NB++6eJNI^)"17ve-~7m3,CU$ffɾõ]| Jz8vqSaߍGN&]AI*ӄ1ʒGZ;8l__ͲLepTO^ern/z܅)B,JwsJ %ЀƉҚ)O鶷\qLڀ ~ 1uk: Kr3doHTcW OC{]mF{vkceNڶ6YaM!)P-Em-W"XHpk5rE7sS䞮Z^ :邂cy5I#jǧy@nȖK -*55@ hZਫ਼\EV M0(ٱb{ȩmGݬ͏NW\m^S3At!r^V`,Y#1'] mjg3k 3 K3Pt6]kq`'xTVX ι]xA1u$w]@Pv/1GplʍwpTOa1B;'%6!VnsִmPX.5/}EX@hpfRF%9i.ǡaw\ysVh\f-\ynV\ h:a%J"E"TG^&lÊtm`qIV(l&LIRV[X#̺ 2#}^B% H>^m~$6T~\I~2gއqbЩ^#%dGFG_%2nwF2(H6jZ0>}pTO>tjLQ=`rؘδH&e Ϲy6YECٶ5^>Qkp ]ꡯ<+ (u@>To1/a}w}*Tײ:) A|dEoN:&XLj\ܳxD8!e{wq^nMD j2ŔY&V31\m=72n?JPv¦!R"$lSAZ.:%z,ix(>f@;<.E,n-Ab9[-;x7 g y [?ggVgN}Έ0xJYq 9=U `-Svhw*#M| a ѿty)T,gо f 2[ ip&dm[C:kMj*yx(u0C5znjԁp-S8o3ngtĺtn .((~mAwcJ vR L1p  Z{[J% a۲3ŵj2]D{pbZo8I1"ibgdLٷujoPa|g~ˬY=n8L$0*f׽SQ,Fɭqώ:`ؚv?xb Pr4ƾSSv4ΆD[۫mg:1y5f"3ZA/>j lk*ZGܘ lӫ+sV[Ȳd@pc;k2|"\:HK!o 1,˱鴁Jl<⬌E|#JRLlk?~P9Ӆ3{%Q.uҟ}kw3Aw쳩[5"p DN mI`4{IxX_75:+}˻+pr2&UĎ 5ϮYO|f6'ϩÉ|)$=R.Y^)spV3O??~6_×~'ӯ7?Ï~o~}?7oOJOO?c[Ͽ_?~_ß/3_؏⏓8f;}_PK! 6ppt/diagrams/data2.xml[nH}_`A{G}  $yY,D'Pd2.߷lk+,;db7Nש:U~|QM;_'cGe=]Ǔ/ zVT˺<-f/gEWfۗpdV/'v\媬ӳe(:x|̚QM(r2b1LR0ŢWVݛyug'ZA LE\)#IQݟQuVΖtUS8/ilg݋r1Yͧ_tyEb'Uu|y3o;:)|*x?}?ϧתŪ*Ig3x4=`Z6N˺#yʹsS5kCƓ50m9evU7RֳES{ی"{VY<դbq*q-2[$H$4$Yӓ?O_z5ʳ|vM'zg+}ՌnYbul^Un18;+]z1 f^~56_n/ǣټ龞!nWeQ7kE=.2/)of[rZ9Q"xDVXbXG]٫9m?ÙB$pVk82N2Tsw<Se2ode+.l"(9#lL<>4<ϟ x>{P#9e9H*ڠsyp5r^@# q9c%`@LjF'8T!QRGXgb%%<Ah PGLHrPos7XeStP/< ד(a D R`@tNԞ@xd rI)HXߝ%qDch{Lr_NV mX" .虂ju|Cx׷?}!Pk R&5 y`GIdsSLD+<ц Pb:B,=&V=^!1k,!^x⽈]qo sg_0o?UUte,ǑKmG a I$ 2jٗɃuDGռ r EBDm O!A"j5 ,dr Xʘa#E"#LIbaY$r/e|u*gQ(PrwL BV"" r{hPbZsN1ʀ&Eng=u3l2!yqa')jµ@)e bAhA~ʁ= fLx"i =hD9<̉N%m@dMd1JoLЗx~#>ZypCf=r$wN$2}T?jDvXɴCގx%(Y锊!'^93~PW =sP4G16^0Q`gP$5;Gퟒ 0ͬ\y8>_.kZLY)ݖMwh ̠G3P Wd{aF` u E J1LQw˻(O/a&1êc`Z8ʎc Kr7CAja7VF htE)O^9]adxLP`Rgk11=#qaЍ NI-0|063u#0I/` g0 cH ʘne@/C05Qq %9XnOR+bn@yaQ퇅JDєJK*A+I1)m|Ԉ K$8oΌC'PgX[-] JO jpkR|A*q` u1!(-<)l1'6T̢Pv}nzMp*(V& A &et@1z@xOIoAb,o09X (dlUUuSr$rO\^a &`(ELHj"EZDTQ3E|s: e) VM;&[[ca·>lH8%GwʫM7i?Yf_A닟QY&:]cQvX_||BпXPK!¾3H ppt/slides/slide1.xmlWn6}/]:؎m7HJ-D$(4/"ùrO}숐-]ÀК5-Lï 0mp("ϗ?Od`MOíR|Eޒ ok&zUlF'@(- {^5Y'TYA:smhGи `KY5z|%Nm {HKNͼҝd!`? !٫lrF^ю`Q-bm}l/3!>E ֡szIh់4l4[$hLh__ϗq&1K5]}[ &Z]ԬwԈ8{"ְv *8G2G-?GMZ 9J2T Sq׬Y#[<+".Ua .6UL $tكTLF6;fDH#Zlrgh}q;9v_6E'$Eո0Y1cr#T$I[r#yZ'&n1 w9 w5 7'{-Fz&J3LEV&uz՘<<ߒb87ܐ!+b@8ɫԑDMa.!ZzVիiʖmYO|o0B8OiHF)Uݜuiɯ@8d?tR=CG\?̴aдB!Lwn.aA(z{y;|# ~t3 ̘dMS* #pJI⨑d*ԀC C!uQM=YC z7y'1|ij5=sFFN^φx[Y۬aR&6v6bL-Jj>pp ߎu٬y Q2F,I'׾#ւc|9t,u-~݉_134s3šmX $mD]Ϡy:!떶JT:pؒgL9? θ֒[N'PK!)vNn!ppt/slideLayouts/slideLayout1.xmln6ZouHi D]{C8}Re'N$n,"<oJj F&pLTˋad~ZiSV%|Oꡠ5޲4Q!K)mlIJ,.XM*x`; Kj%.*O"#cJR ˢ>Vs"g$5x+ Iia| ȼϳ͍ qF3ZD='D^;gܬQ4E;Lֺa?5pzB Ȅ|mկFYәl992U^5=uiv^ueQ1G߸z&Cs4aN%Ba\(r}5@zq:OnPno9<[\eK7L*Ln)5EB*mzs :`[۴NqD>`2W˔gC24>b! 7ZaQѥ^C#Ia9r/@Əuٟ9 VFȦr=r/t\Nj(U (_TT9T<|uUTԡ5HT?\eKSx!C+^{4"؋l-;cnw زÞ޲oc+`ˎؑ&llqVW򀭀-{]/VKbQ%9\:KL])ll}UX|(/t'a:ص|Z4qWQ8i$UYdZ<8]I}HA6r }zW%2eLic?19V/KsވsZ}7r(.YmhhM߉rqj$4&^8$I8NBy^u~3hU?k|\ ٶ/$n'Vu5 kx&UM'k]tSg-Ed 5+ vbsYc$8Ia-K=*3?<)۵Ioj׵(߻{?PK!ђ7,ppt/slideLayouts/_rels/slideLayout5.xml.relsϽ 0]&4uE$ۛт}\MxRbZV ț`5ܮgIÛr\h\xpEQ^B|@0KZkrn鼗0@y#.&1.Cn[rdLtdv_Ku?^n⅑W'O_{SR@IkSݥm./qZ"x=Kaj[fr}\r&O_4dhq~@d\I0Ar&='rU!r8&e{N\"D&"X/ªIJgÜ K#P+&S$q8)Keu6=ɦvDJI&Iw NAkIN8q:qA6|2cLAoqۮ+F7XeX,7XάMV RTf^Ȝ~h^t3ĠjOntv%$ì^z<.'#K[f3%e&0&rJW؝|RsYa'7L >MZS:aߏNOoYk< o_} =-krH߉Ao` N:qxa/ ),3npSV嘿JBfUcYx8fV$>H {*k4qar.wZ'dyB-r #do$FIh$@^C0Ŷ%] LK覜L#ѐ7ď"ue b՛˭~CWU ]=o nՍdcnЍG7iNC[7ulk.|!KK-pb"ւtFuUUUTi]C9qH-o=Hkm{c_~%;Pİ)sjRU1503sr^W5:w"Jy:"'btXr Jڠ(:mt@wA6QJSBGD\Wn r'YC4 x}u]!SA y 5Q 5qN?@ 8kȓ~  5Io/rw\|޸PУkԿ4U֏t~I[7}pzP`pϖ]!@n%C4~Mk7sƇ"l..?*w\oߣPK!i_!,ppt/slideMasters/_rels/slideMaster1.xml.relsMj0}w0%;لB@XjKFRJ}B ah!m?|}uF R ʨN7ϋ-$Kdo4 ||ؿb/}ntIHN@ĘZK͈:҇O۰QVAs^0{,39)4_M]wMuP#;/r2bm HO4v[)[RMLنe=i>{g(]m(')#;+b cdk%(^ҏm$!W8 2Bdښݟ`G#`| ˔-#AcM0Bunz˜3j+M$@YqI[,$i,CJ}ONa#<NJn.ɒ _B\CၶpQ|4>&q~? |oz y+ᑖ35a'[π/<w&zWR/O΅?آ&X5fynUPS+\-j}}쩇UD/J4QN|E͊wǩ?4[wV5)J땹8y;P,q'K)ؤz0 -[eXa 'tǷq^cdhEлU}./hQj6#?'nX;wKA%iVt~|r]c ÕһZI d 4s *h2""EQ7f .럃Q]RrBvPm"利L[7Zo3?Lzm^n]ȝqPK!ϖ͋ !ppt/slideLayouts/slideLayout2.xmlVQn8/;"ɖd٨SDUH`[J䒴k@{dd' 8HQyoynS3&RQLwi ^a|AJČ7dlr]~VL+4FMԩSEEj. |[rYc +% 5{5헧%-Ȍ4a գSЄ$ `.wY#ł5,QuP H@;I5R,ĭ>o%;^3NO6KY@1ۚgF],EusĶGѡUt=V`ǪWk^|Qo,ZfU6P]NtbM˭9F'L2b_yX7$˰IlҸصN}1Z|A#RR>bDiV# IK!rI~ztKe g%3xMΠ0l' 6 lVOnU<ӣ/F><ï(1,Qss%&KԲ+,>8g)WWdhIЧU}Kt]?љ6B4f O27w<|4d[e7ݩo~| jpt7C+IHAn#*#7aU63YN Il Ui!K}Q9=)8m'ĶQ\cS'8x]~ޚ/ &Im^f]M ϾPK!B}}!!ppt/slideLayouts/slideLayout3.xmlX]n8~_` hQe)"H`X$jIڵwQ=NOJObRfoޮXR. VM1 Zg,/vba9)YM i/ɚ-lb@\f`"ӊ3ZT9lW9NhWNf"#-*Z% 5Xk8ʌއ$׍b+h;%ihATCyg27jR)@Ahn9Ыx3mnVZpHlD'?.fVyX M50FWl;ͯfi_YXྦྷtn YRݰeQ36-ghyz :vRw`z"|FNS"z8"?zH5 *aT:3wRȩ\T!%,RhwF; h=JێǩvLKJMyZ'C24>!)7&Ukho'gr^pFj<ooMI2:g~;EݦZhV(DOd8΀nT_=TԹ:OWZ;Y!,IQN)XR'evSJw-D0< 1`tlG`\ 7vx(.~Ap]?rãK`/0xp>7_^vF?z(^[xd7opAJ`SC]nԗtt(ݹ4u検%u5\dگ3u{0Eih,FLǺN]D8qKUYTtR/8^HpKqmQm0P#ԇIa[?-\/uTPMPS]lE{PK!Z!ppt/slideLayouts/slideLayout4.xmlW[n8{ 4ߊ޲l)"*HvHt)ErHڱ[:J$;qn<cR|{ν&W%⢢dhyg)hYۡ~ۉ4HX_ ./.$PD КK#98 57R}[Na]7vjXOg@#Z,jDdR/;q$$f[yGnYǗjijΕZ L(( L 6Yl®vyAUjv2IlpZ ZJuZIP4v_Y[{V;νCWq;wx:{Uh6+u]L֘,Jiևܨ r"\ًjDյ0dCX? ^PS[jl=tđTPRo<~:9]p9?><E2)^&vNIFɣ2);~/JQJ#(у<2?/njl=`kڋO8F,(0v}}n}rUV5ʫGW uWstCʔ*n 5s#nIֵZ-DZ@v4ARK/3CLհmlh߻?PK!$aT!ppt/slideLayouts/slideLayout5.xmlr6;w`5ٳNNgI>rL$*dngg'$v^47Aҏs_p"{,rcAx1zn34MXsdd!Ө)%TxW<+6dЪ͙e߶dF \Pݔ )]8]ze=N X2/5 i5ʪ]h%'ջ&U)l<_MCO ydŅ<TFt׋[nd4 M?҅؏߷C_Ny2ܔ_[#Ka$ds7왛̆{f쭇*j㞺L2j+| ʤ?ڽgu-gmWcF"p{ vc"N+\/KWjjEp?Xr?ꏶ@X ֧LB9tiqY i&K`$4f\l Uj[[/k孵RAqBf,O-kȦbe-7TPC`#r6wQ41Ybjd+/r W:2ʪ0OJT R.tA<-5Q/}[)ϩvϟ}pUfdy&^("GBr #t{/~‰>9m|r_d g/v/YX/zO<@-/ҶU󏸼YMR^oof5oPK!or.!ppt/slideLayouts/slideLayout6.xmlVYn0/;귢Ų,uHP`%:B,IvV{CJL,@PGș{o}<ڶmT gS/:=DX]O/WyHijL9#SoGwtG1Q>;b05SoV `oe5렖;niaa^/ϗ˦"3^[tD5WF(M%DA/$V7 Fwrw-,\+d̎W3c)RZͥDMm^ofm$x~x]֌N=H<FUbZ.^Vw@PêNt:D^%Nyu[t(VM;yӋ9w搯0E=۵\ԔLmij-L Zd5Nl[B"R9y4t $>Oٽ~8)8ֳsim]p*h%O b7巨yƃa0 e̪U/~Xn8 DNȓn._PK!K?!ppt/slideLayouts/slideLayout8.xmlX[n6/=귢g@& ԑD=vf[rf%DIJ=c]ɇG߽_I5T+CUp4ljJPTxpw)["sG P$.QsEj\3BK>)E_,t0L6O6к|RU۹X2ǜX1Y6-I>7JE ~̯u֕q)MgV³1E냦5B'4 Clg2H⭟ * ԧhؔ ,EavJ77U{þxaD; gOSy% X:.ˆӜ)Q0UD`#ggWwg9)ҺitEޖۖr PgH;er鯻‡Xޮ Xr "^b`+6V t`m@J1@J1@öC}Qͅ 1ΛW{+8!Uxbn@?}"),|}SAל2E65QKW4&*fjkuBs8p^Y4 [IȫvL NQus?sˎ/4Ƿ{/GJ AeAgwvw(o-s3E+h#ᥗQ^VU;o}4č xYc/n`i3hM`֍DF~_])O9s&ޑCtPM>LgSxk LxR !\maĦRaf1G 9->E[_VdZ)V^]vҺ4E Ksd=kƮcxY h M(Zۆg^A~Xys۰.R4D(8t&3 }fĵ'3H('",t^yBICf*!ew, 5 ,e4a}.&\)$0X<#]E] PK!pϩ!ppt/slideLayouts/slideLayout9.xmln8ǿ M:*$iM\p4\I]48${l vI~ owEm(5Z&UctLI^thwlT5UkGd`#Ӭ-H}Q1ZˊD-0SN]mYYr%tR%낖pW;;8z$v eYr5e7yS$* )0C l=/V~XV 9U땪QMjk$$9^0>'-< iyC/g׉@()-=v 8()-;m;@Ii*sg%PRZ`x\̤H ڕ%U)D (9bbȭHlJim~;^X!7NUaI z9<@@|Y$ :ƭjUT=r;=A!(O5{Q[(Fu AҋO37 '"˸ оȊ/'U v-T{= Ȃo41`e2*Ĩ=[̟k ]r^LrEdg)nwqQƥS@?Wѳ6vOaς40) (m-=/at뷯Ԫt ?׵h[ښgH!<3$􍫙38ؙ~NqiwP𓣒"KxUWKqTE{brVefbsP!]AŤ]`e}#aU#X=bYЖD3]PK!z "ppt/slideLayouts/slideLayout10.xmlVn6w kE?e٨SDU H`vwJtL9vZIvHIv8x7"E~<ϛ5zWH]cңp^%'ew䜛|,M вQpB'^粌#3FK>O/ 4b}'pfLIFiН|tU&;5]|@ڡkzUH0KR7 ܍&Á{}7(K7f ./YYBrU۪z!RpjUĦ\cs =^u?3{,6I*{evI@;޵PK!ђ7-ppt/slideLayouts/_rels/slideLayout11.xml.relsϽ 0]&4uE$ۛт}\MxRbZV ț`5ܮgIÛr\h\xpEQ"EQL:; >`L:FE4Ռx3`B~Y_`J/{%3*W$5D&PnZ(ÄyRcEHU2vˤH60*S[^K״t(_[*("X%U΢d(ڲp^7M h̿.YzG-:Eяn1#) X@Y|:T dq _O->; 9!;TD4z_4d`ˆf2HO\fE':̛R8N60lsZ11cuqo0R% .wR`pـ\6e o7S"kvtU;cS;,j!"J ;S eڽwCB`iBv_c`ʎVg;'I1ǎr۔}IGeZ^[|8cu+hDpĆ8Vo\gl> ijݳ ]Nж;t5swY<8Tf!"]oxC-&<֡byrtygXK 8yyD#9XWIfs+}yc )c.PK!Kppt/theme/theme1.xmlYO7Y N&!IQgՌ$  ҴR譇6@/IҦPcK% =ӓsiJ1bӬ.@YH#]v0 :sĝK~pH1ݩVy(Ő_S{R(dՈi7%պ6)ę2J'"ptvKC"2sAHAnmӈjy@8"zrNB:8݋ե[t5, Q]xTi+Pln[no %gGho6rKȄ+VxFBU+3»$L 1 %:{8NdMaFuw6oTK iorV OEXZu4ȫg?zz~(^Y+仗㹎~K;P_?|,c~S5tnT28L 5zYas z(}m m& xyv |$5>e9]ҽ0blnBxl;Xp6Ym&4or ߣGY`lur:}.9c#VJWp*26|)c)W$6n gV0%:rF`B\HLjP06lnн*+=dH& )Ցz$0Z9,ѱ#ܠJ+$8lkocd}KW{wf̶$5L Rƫk%=ٙ}+.z ^"e7\7kWUV̤g'1'hb$R]>>L\ jbU0*>"9HTVS#|a:`J.j[m7tFV+X+nJܜD!mVfK@:$L V)<ٹXXs[Y"*r{#u(S_F#͙urin& - u9Ǻ A/w&Vm:/%kdf5NDe3J{"EQL:; >`L:FE4Ռx3`B~Y_`J/{%3*W$5D&PnZ(ÄyRcEHU2vˤH60*S[^K״t(_[*("X%U΢d(ڲp^7M h̿.YzG-:Eяn1#) X@Y|:T dq _O->; 9!;TD4z_4d`ˆf2HO\fE':̛R8N60lsZ11cuqo0R% .wR`pـ\6e o7S"kvtU;cS;,j!"J ;S eڽwCB`iBv_c`ʎVg;'I1ǎr۔}IGeZ^[|8cu+hDpĆ8Vo\gl> ijݳ ]Nж;t5swY<8Tf!"]oxC-&<֡byrtygXK 8yyD#9XWIfs+}yc )c.PK!>appt/diagrams/layout2.xmlXmo6>`AE~̨StKp!~-QTIQ{I؊ckO!OwϑwyH#RQh|5B)o7S3>0N \ߑ [Dl*HHՕ)ְJ )')%N RL9rNsD䋔R(@"K]\߭t[]KS͈loD!QAW`R^{%0A^&MGQK*C zՃ 3۬˴KCꌏPq^/OD{YBY rI0~algb21&c0e 6ω`iʩwMF._{=I+o мVƱg7_8>YYgBC0QYYA(jzwm>{|0}laYcNՎvAE`3WR@'쀰CܵT Q L$EUΧ*4%;)L;)0)Ŗ FM/y)Aԗ%u7yFnR-U p )`KmfSaoYa):դ}ABY8}C}=/%_^YNC19 D0]7*iP9#b:sxfv'$`qAqisWM+Wse= ].HpaYmџ82oEꢧCzS6Wͪ(_kDz67[=T?`RGFr;ؒ ZCD^T] \ɗT MX8vR~v6!˹I89?vVʖ:itI&;ɄfK]>ɜSe(Ƀp^S8Uh7 I%iyX#ZvL't2>\:w{%-mJMw qKzJf3aMUI&j}w nȝl;%&4/ْ|ąc0/3Ѕ2DE̟ibА K%17&Dk$Y}S%_:2ғKF#t_m({»=>ɯ.pRQ_z#$$&8fYp*a1c%m#mUT`~x~4Q[|*(?ο:ϳxm#k)4{Ob~Fb4%Ȣ!h Th!h}EZЪTzfcjL@1c#A| ,L (",Bf/ch\ {OG$+&szA*zb"SNP'9->%T{(|g<< LJ+T ,]K%JjDLSqrqx`ɼFYQr:BU 3K#u:7%6u`Kk"YF/*a\P6ZY2UR''efY%crEAa54J "4PGh4>21Tm6ÈkYEUۢUs(d1vGpSy#{l-4i[7BU֠|TЩSR.2n _XemkI\uvG^REٯ۬[\ jNObsoK8Ȯ8N:7Nvh LbC' fLa/|iOsZG-j-^2{ڎ/vX \. .bkm`-à 螀tO@C'!%V/8^vE;SۉKjJ vT1VϑzGwC$iv$+9RL78ϥ/8: >a򆅖N:? X19Q˯\uqe Pq7b',hf][ n3-֋eǶt _`0RHѭ|-QD ~&Gg!x q< w\G,3GB/9(Fd셣8lF9:8Gnh&c1 =4lq| K3)Lς uR9,yiN߈0 @{0,5{g{a.:KU;tItp>ҭ,m'=R4qc{UovnA!I |qQfZϔ_٠Gj]STkHLK9QEQEQEQEQEQEQEQEQEQEQEQUgԠC Pc8Th?q4 ERփ 'Gݸi?|]k+ō@5cZ4MUէt"n"[ŬnFqގW.5[). h\I@ur$5iѼG}.>9ځTZT Z-FV2"|)pQs+nx4+}nc,KR'q&3Qǥx&࿋4*? ]J|=5{[L`}Yg (_vhփ 'G+E*ݸi??IvoCq/,H22F@Q՚C (Q@Q@Q@Q@Q@Q@'mvH[[ WF,<=OTz5䶷ġY_0B?\:1ᵝpndw.HⶴARܘYm g#j:d8lҌ_]'NzRhxThg">UG=@=3ީsh]Lȷ,䁱?S]My/\ͩ^@љ}׭z5QEQEQE е bGo#$Ь:Kߎluhdd7)[tڥfUf2Tp1kRXۻoeũx5MrcmÝv$9uoK jm]..MT#y*)m XV&פÆlM2_F߅HϤ44;v[[hYY-3!7"J`G_>}޽c|gݿῒ_EPEPEPyuH$)%1 ~We[I Qʸ*F^Q/)+5K['oAP4 A` AO1@یzv;(/YI#8[ÿҿ=}3Z֠hmcnQ$.<¶Uu؛>稟M$w eXw[6v7L[ÿҿ=blNuȾEg »N /X{ WQcqi[\[Ec(|ݨx['C[DM C#`|H㜌W^g7?`&ǜ93;9"]VE$f3[I.z'tQEQEQEQEQEUԿk0C<%AD'zfY,?OM,?7?Ƽ<G_k#7FDaν7c|) m_\G3{[1!H}~ ,8oRxRht>^PtF>լŹ܄msW2ywڀ]?z ݇5?Eq]< _H~sp;i^۬F+ɐ9$q^ߣ\$*[8|b(}>9"I ك 1Yw kspoJ͍k }ϗZ[_[Lou|/$ba}IZ:yɻQx?YXKT04컅c<kv(ҋL.h{p)O9j:oy*ON?!ߨ?V]QXZ-׬Ͷ|#S9X>>n%BC`Ak*)䑽*5k6M6E5/;h߶qZX= VI0WCA佹0"y jSMjs)q{yTW #LO3OxXOK,EOsXFQ϶wW [FwwxZ[$)R@Bh*,"rH#y,TRTAnk2syqoM.3A1?P#۟Z >#x3S6i5dqo15},ph#T|[@$ h1^%ζ)< H;@?}^͕>FB +ˤEG$v&A#Bڌs7,pxׂx- vd&Nsk qnfOik%ěw*K#F]VX| `IkХX2=;exBAc9 8?z-bB9ഌQ}^A$VlNC#(93 bm7UWaKw籯u_50o%e#e#>>.E{0FBcm8'GJhRM<<\# 4>yԚ{7bL'qԎL-~8\9篨|K_֪~Ϻ,i/*!`H $~5Ǝ%˷ܱX'X74uF*ƁX+,'rq1]z8o(4bp R0zQ_d~rQEQEdXAnfp(%Nd r;i7YAtXAq>o@jMbM7M&)o>Ϛ>BExZK\DL9z _y,a>;S<M>g\h'oe%;=ks@O[$W7bڰ,孆q$7n϶kx3˯RQg7YU7E qSnrOyǝmeF6aaԌ:ޓ$u+!~^  շ63qYj݊&)n3뎵f=J?G.mc}?uƙ&ms,r7rztJ_ϒmGN1lu?ZC?'¿U+R5 ('wcjߏ>"_xW^ i#kdw\pÏ~u|mkI,4.nRDFCRhh?^ֶ?Tտ.n{cWOˮ:Hx,<1ٹv %٤YFXt'~6գo.H-f`~d'9sһ:+ޡcOa KV?t Cƫ?V#4 j5G3A=XHz}s>Cx7vN[+dVШRPsEsT*曻7#MrYQYQEQEdk#\$Bf9 ~_Dm n{:v6Ai#v(tŠ(((((( kBZ7O^!#> ^n9LFXeuP?0'װoֿ%4<32[2WO0ʻwal/p x.gB7moMhQ.Uq dD>!rINdc:Z/4(fƘJa72!h^ ^TVnCK^`'VK (.?g .ёP=+ G|_~{C5v2i ĖHvϗ?urֿiG7]rHek{9W^#קhن&F3t#+(((+~6If+Ы"}}9lL@9c\Wda[lcPסm]E卢HXIJ_Znjh+Up219翠 Q'+DE`9*w|ц?}]_fO;bv>m>wmgn~ln[+^uMzKzv%3CI#* ¹'ǩ^'70FOSXs]^~ls~εIsg[Pis8_0ՅoGпZEWԞQEQEQEQEQEQEK@i> 5OjAh˃kw 'w#^ß5 hM=|ZuK8+op$Fr)-_Z|yxQJm+s|3_E.8ڪwm=ARMW%OI5UG+Q*szp}*KL/$Ȍ6!YWrw' B4[5"*:F+Eoy>K^Rk`sw^b}Z|%y"|G } Wy7zmG†+]+r0[]ta֍K /44QEfzEPEPEP1x wLITHf{+.>7?f|\/4h\A^iXOLBRD A?{ zȯ*zeZ.Tϡrs MƾI]SE|->}׶{+ #4;Whƽ}A$#`1<#\=\Uu &}K?[x a6 yvc]y7|uaokkTui)wgFntԘQEnQEQEQEQEQEdk_?z_]xPnu+HlT΅;@l~k%۹즷Ύkpmd<:nK_i+XksrQտ2]0uW=C~]5KzΪH rHW;C>~?Ϫ߫O1^wT_r=N;S_{<;7WxoO~}o% ˈmV3 uϪ߫O1PxNPѴr/h20FD0|p=g362pH袊O(((&x:ƚU$Sg,Hy(}{O"ڊ᫃Z|橇R\<'>?'G(}{O"ڊ7S -y" rw9»(TF PQEjXQEQEQEQEQEPK!mf` ppt/diagrams/layout1.xml][s8~ߪ.'6;5d{w2Nl#H:_?"!"Бttt\į_L߲Z/KmgOǯνvW)EZۇz8 =r? <5B6~3Fe;lZJhA5lO% *}mD&.ž{džHmR9B;j@6fK7A 'y11r`om7MG;amQ\! (R CniIdWw~` zdfA@&|<49rH~zGW3ՋIȑd~'߂Cm|p51I 8ӱZWK͚٢~bo)!]҇X _oIHFBӾ"4Т!d>Q&bAK''Gwrt''Jwrt'' G.;"OJIa9ExFh;SNmnѨ̉+Bٿb̛8۳V=΋6!lB"ú *M/LjE#Exgyi0f밢n⣫y }޵^臸H.H|r>~> M27]yl`D/)wmqfkX5J{˕Q " 1RUc:Es ]\bI9Q2IFWҝJw-yO u(Q7iOb&1ZE)Z}2i"ED>@SGwH ;Y\=ࢸ}Ѱ}MGfW6p䊒et_VnO;ҝπYyL<{BAY>f,| %1hl{ެYPOUcEIئ,lG#~a)m[2oȼhea[w2<78^b{q;WgwzY**5!ˋ mvI&Uͫ؀%}΢7$sMd=R-L%3 QptXҁ.,[1ўɃf)HFyhvYKGsR!|eK&_"’v~N,wxHh=n௢܎xgEHUa)5gőŹ@իXJt'-!LvyI^HL%)[3pLN&KTpS/iyk[wZ(sHkxjSgs*v9~JZ[^n4+3 hA0S0 ]: >q•3bUNXq *v&._9" `N*s$- ,rW{C9.s}#7ẍ9I<_BfL."9Ԑ tV/HYy"{*i2_.{`\\@[Jιx@Nz:QRxJO*iDyKUO)ĭ[ʭSug[̺֩uԙV؃.AR*G2떭\'Yʬ[G\^f>e ڽgέ\@ = oIw4Z)8Z`]^zqj{gҡH` jpBDغN0/o1G?W31|؀(%$>R`0fGEg}n4tE>=5*LŽ7oZv]cY]-3*dOa3 岔9)kX7bYopzQ̓V=:[AHO.r8n|sS0h0a}ăO6BT7CዷuhMO:ŃD1ERB@;~oB.UiO=*bǤ(T1$[(dT*vcG' }F楳sN ~T Ё"Huv}^b>l+9AzC uHO4c`~C`s%yUc+kyŗMAxҐ]}JDr"XP.Px-ߨ-~PK!}Lppt/diagrams/drawing1.xmlYs:ߧJ/N[%5%Z\T|7Kv[T,2E q~_WeZ<{7&oxf|f~vHGeddgrvZ$_|O#+NgwyUߞyJ7:yJ*tV<.Y}"DVIyfN$퓩7I*YdM"MChN~-ƾdkd|rQ8wvi*W b .B4' Ӂ.7;U6WۯŪktXP!)Uѷwc X׉%oӯh0lf|<4!pIirQVҼN.)&l_%j5/tRMh^&:xe_NU7nŸl̾EtA^txk;]4e9^[YeSe6K$=/_.x,gNb\G(bZGobtJM&4HnLE7vwLz\yvEYEI9o#4)UZU>e֘Z[n-Ӷ>!/eZc]3&ix3wW&jEjt]wȆ*Ƒ7L 9C@PǗ $ä.W%dddSfXp9 HcJ#\=lpE8iLoȆV59n]g[.gfեi}byٺDM٧+]?&>'}LtMn3ڪzb?M;z)h6!janоnu??YmJz;9ZV^5(-.WUL/LTy27/:+/^(zQM.Y-׊yRi]gyO:hT}{Y~^Vu RWJ- X몎*gy^EvC`lZCpW5=ٔ;wm'v̖*#D0g bGI䎿ʟceyZ*T%Uz_KKKKKWb/u=MʪH&c`B= S "FEG(*ḖPQ7 b= I77..CwizWsr 9|~@8ۓ?!1518X*# jb;MX 37)L4)))Žw?m?(%a.R8D1r013pM~8+ƁJJJJJJ_tV,אҬ7 O(lf tfRբzd `D;_c4=X "nEL$ chG[28~; +Wрррр`*\ F~Ø"!ֈ{EA+\K&b[(ֺ=h^.&77=`:΅{ls(퇠X+H"GDY02*Ƒ !ɟ'@x獠RW}#h #R=yXe{>$և'1/Ow}8WoצAӪa~RUp^hJ).I+oZeRiy4^r~ r4:D2[Cd8ժ=Jfnfnև#NYF}=$+}ธF:P'"m$ A6d 1VQ΢9k,kv3č2rI      6GݧE~Dxm0X $ۑGA!xH&_-%d}ă HHS@@@`@?5EA2E}@=6K:ܮznq[dE54Χm`^$DsCsY ( vt8Jm)eјD(c18"5Z?,)9@@@@@@@@@ @`@Lu4Cm?4 4 z{_iJ Ec,O\іQ(nQ@S@S@S@S@S@Wnw"22Nk)~̡|Jm@Rqnbd@3ϸ%OI*p oJC==AqFWο8f,bfy)ڨ8CƣxCghr/>A34}dp6as`#`#0c @+g-=f`$)4"kCieJXJq=Nv=-dHdHdHdHP*dHDv֋%_2Mƶh]|.Ҵ{3-/lPK! 0ppt/presProps.xml͊0{`tWcىME(P-'d$ewKWuKFBiwOӘ=h + 2m;{l܀,De{5:Mp{n;׳AۨbJTȆZ5\#TXYt68?zӈ(%ߒtZ<%" '3j[KM1*;{Ӏr]e8,q0 J$_cL0 {::jٛ(TT8M.!:7]D{~vfi{=-FKprZA ۖo Ũub{}MSY a¤LrC PK!ppt/tableStyles.xml I0@Ὁwh}-CQ$ +w*!@he/?JXd45ݤ{c@qqi` yߥ{|d+ai (n7I960ֶ`6e;.è`RyY򭈅y46ҸGXp(Wf؜jwF+K VBA xmA,` '%EΧ4%tM@NaHE˥/sq03+ahCY)K\E}qš32T|)SBY'{ڵy=T-ήC籗vNniC$#\<9V/PK![adocProps/core.xml (AO0&+(BD ^$!ִиK[{D|}lWe9"I"79z[1gZh7(7T`vW9z_Q߂b. X|8 b4a nqI)xm P& gփU@" ]{'{h؟k{X+^.>ZZpacC71=#;9O ߸٩DGetibqJVdD)%fPǔ2v+~PK!02rdocProps/app.xml (TMo0iP -2**ġM&Zǎl8UK<=gn^ E`4z:݈NL*fŷ8/t*0^E7'%8ڍrDKr(`Yc%3C&dw&y.@{z(x)e#ՊW$U1~-QR |$F9`IxbY1D쇱WFknRDx4eb3'{ݥف]=m" 4t%@UnvKt3DVl(s/H9ld #`<րdW1}|d VP0Ax&C 6QRH̭m!'t "$TRhմ:X[>5;v2$lsXu Xk{# #S#޹?cmx衶nUMOV~T#r l%Xjs\U%M!Y"!Ww~s^n] &*R|:AolZU'HIxP޻t UK}xPK-!\xBp[Content_Types].xmlPK-!ht L_rels/.relsPK-!e3 ppt/presentation.xmlPK-!.5  ppt/_rels/presentation.xml.relsPK-!F 6 ppt/slides/_rels/slide1.xml.relsPK-!c3 EF^ ppt/diagrams/data1.xmlPK-! 63Tppt/diagrams/data2.xmlPK-!¾3H B^ppt/slides/slide1.xmlPK-!)vNn!bppt/slideLayouts/slideLayout1.xmlPK-!ђ7,Jgppt/slideLayouts/_rels/slideLayout5.xml.relsPK-!ђ7,Phppt/slideLayouts/_rels/slideLayout6.xml.relsPK-!ђ7,Vippt/slideLayouts/_rels/slideLayout7.xml.relsPK-!ђ7,\jppt/slideLayouts/_rels/slideLayout9.xml.relsPK-!ђ7,bkppt/slideLayouts/_rels/slideLayout8.xml.relsPK-!ђ7,hlppt/slideLayouts/_rels/slideLayout4.xml.relsPK-!ђ7,nmppt/slideLayouts/_rels/slideLayout2.xml.relsPK-!ђ7,tnppt/slideLayouts/_rels/slideLayout1.xml.relsPK-!J2!zoppt/slideMasters/slideMaster1.xmlPK-!i_!,wppt/slideMasters/_rels/slideMaster1.xml.relsPK-!ђ7,bxppt/slideLayouts/_rels/slideLayout3.xml.relsPK-!ђ7-hyppt/slideLayouts/_rels/slideLayout10.xml.relsPK-!u "ozppt/slideLayouts/slideLayout11.xmlPK-!ϖ͋ !~ppt/slideLayouts/slideLayout2.xmlPK-!B}}!![ppt/slideLayouts/slideLayout3.xmlPK-!Z!ppt/slideLayouts/slideLayout4.xmlPK-!$aT!*ppt/slideLayouts/slideLayout5.xmlPK-!or.!hppt/slideLayouts/slideLayout6.xmlPK-! =mk!Փppt/slideLayouts/slideLayout7.xmlPK-!K?!ppt/slideLayouts/slideLayout8.xmlPK-!pϩ!ppt/slideLayouts/slideLayout9.xmlPK-!z "ppt/slideLayouts/slideLayout10.xmlPK-!ђ7- ppt/slideLayouts/_rels/slideLayout11.xml.relsPK-!<+Appt/diagrams/colors2.xmlPK-!Kqppt/theme/theme1.xmlPK-!<+Appt/diagrams/colors1.xmlPK-!>appt/diagrams/layout2.xmlPK-!Yˤ Qppt/diagrams/quickStyle2.xmlPK-!nGYo Tppt/diagrams/quickStyle1.xmlPK-!\%ppt/diagrams/drawing2.xmlPK- !D\$\$docProps/thumbnail.jpegPK-!mf` ppt/diagrams/layout1.xmlPK-!}L&ppt/diagrams/drawing1.xmlPK-! 08ppt/presProps.xmlPK-!ppt/tableStyles.xmlPK-!\;Y~!ppt/viewProps.xmlPK-![a{docProps/core.xmlPK-!02r docProps/app.xmlPK//{brian2-2.5.4/docs_sphinx/developer/devices.rst000066400000000000000000000056371445201106100213660ustar00rootroot00000000000000Devices ======= This document describes how to implement a new `Device` for Brian. This is a somewhat complicated process, and you should first be familiar with devices from the user point of view (:doc:`/user/computation`) as well as the code generation system (:doc:`codegen`). We wrote Brian's devices system to allow for two major use cases, although it can potentially be extended beyond this. The two use cases are: 1. Runtime mode. In this mode, everything is managed by Python, including memory management (using numpy by default) and running the simulation. Actual computational work can be carried out in several different ways, including numpy or Cython. 2. Standalone mode. In this mode, running a Brian script leads to generating an entire source code project tree which can be compiled and run independently of Brian or Python. Runtime mode is handled by `RuntimeDevice` and is already implemented, so here I will mainly discuss standalone devices. A good way to understand these devices is to look at the implementation of `CPPStandaloneDevice` (the only one implemented in the core of Brian). In many cases, the simplest way to implement a new standalone device would be to derive a class from `CPPStandaloneDevice` and overwrite just a few methods. Memory management ----------------- Memory is managed primarily via the `Device.add_array`, `Device.get_value` and `Device.set_value` methods. When a new array is created, the `~Device.add_array` method is called, and when trying to access this memory the other two are called. The `RuntimeDevice` uses numpy to manage the memory and returns the underlying arrays in these methods. The `CPPStandaloneDevice` just stores a dictionary of array names but doesn't allocate any memory. This information is later used to generate code that will allocate the memory, etc. Code objects ------------ As in the case of runtime code generation, computational work is done by a collection of `CodeObject` s. In `CPPStandaloneDevice`, each code object is converted into a pair of ``.cpp`` and ``.h`` files, and this is probably a fairly typical way to do it. Building -------- The method `Device.build` is used to generate the project. This can be implemented any way you like, although looking at `CPPStandaloneDevice.build` is probably a good way to get an idea of how to do it. Device override methods ----------------------- Several functions and methods in Brian are decorated with the `device_override` decorator. This mechanism allows a standalone device to override the behaviour of any of these functions by implementing a method with the name provided to `device_override`. For example, the `CPPStandaloneDevice` uses this to override `Network.run` as `CPPStandaloneDevice.network_run`. Other methods ------------- There are some other methods to implement, including initialising arrays, creating spike queues for synaptic propagation. Take a look at the source code for these. brian2-2.5.4/docs_sphinx/developer/equations_namespaces.rst000066400000000000000000000032221445201106100241370ustar00rootroot00000000000000Equations and namespaces ======================== Equation parsing ---------------- Parsing is done via `pyparsing`_, for now find the grammar at the top of the `brian2.equations.equations` file. .. _pyparsing: https://pythonhosted.org/pyparsing/pyparsing-module.html Variables ---------- Each Brian object that saves state variables (e.g. `NeuronGroup`, `Synapses`, `StateMonitor`) has a ``variables`` attribute, a dictionary mapping variable names to `Variable` objects (in fact a `Variables` object, not a simple dictionary). `Variable` objects contain information *about* the variable (name, dtype, units) as well as access to the variable's value via a ``get_value`` method. Some will also allow setting the values via a corresponding ``set_value`` method. These objects can therefore act as proxies to the variables' "contents". `Variable` objects provide the "abstract namespace" corresponding to a chunk of "abstract code", they are all that is needed to check for syntactic correctness, unit consistency, etc. Namespaces ---------- The `namespace` attribute of a group can contain information about the external (variable or function) names used in the equations. It specifies a group-specific namespace used for resolving names in that group. At run time, this namespace is combined with a "run namespace". This namespace is either explicitly provided to the `Network.run` method, or the implicit namespace consisting of the locals and globals around the point where the run function is called is used. This namespace is then passed down to all the objects via `Network.before_fun` which calls all the individual `BrianObject.before_run` methods with this namespace. brian2-2.5.4/docs_sphinx/developer/functions.rst000066400000000000000000000042711445201106100217450ustar00rootroot00000000000000Adding support for new functions ================================ For a description of Brian's function system from the user point of view, see :doc:`../advanced/functions`. The default functions available in Brian are stored in the `DEFAULT_FUNCTIONS` dictionary. New `Function` objects can be added to this dictionary to make them available to all Brian code, independent of its namespace. To add a new implementation for a code generation target, a `FunctionImplementation` can be added to the `Function.implementations` dictionary. The key for this dictionary has to be either a `CodeGenerator` class object, or a `CodeObject` class object. The `CodeGenerator` of a `CodeObject` (e.g. `CPPCodeGenerator` for `CPPStandaloneCodeObject`) is used as a fallback if no implementation specific to the `CodeObject` class exists. If a function is already provided for the target language (e.g. it is part of a library imported by default), using the same name, all that is needed is to add an empty `FunctionImplementation` object to mark the function as implemented. For example, ``exp`` is a standard function in C++:: DEFAULT_FUNCTIONS['exp'].implementations[CPPCodeGenerator] = FunctionImplementation() Some functions are implemented but have a different name in the target language. In this case, the `FunctionImplementation` object only has to specify the new name:: DEFAULT_FUNCTIONS['arcsin'].implementations[CPPCodeGenerator] = FunctionImplementation('asin') Finally, the function might not exist in the target language at all, in this case the code for the function has to be provided, the exact form of this code is language-specific. In the case of C++, it's a dictionary of code blocks:: clip_code = {'support_code': ''' double _clip(const float value, const float a_min, const float a_max) { if (value < a_min) return a_min; if (value > a_max) return a_max; return value; } '''} DEFAULT_FUNCTIONS['clip'].implementations[CPPCodeGenerator] = FunctionImplementation('_clip', code=clip_code) brian2-2.5.4/docs_sphinx/developer/guidelines/000077500000000000000000000000001445201106100213275ustar00rootroot00000000000000brian2-2.5.4/docs_sphinx/developer/guidelines/defensive_programming.rst000066400000000000000000000036331445201106100264400ustar00rootroot00000000000000Defensive programming ===================== One idea for Brian 2 is to make it so that it's more likely that errors are raised rather than silently causing weird bugs. Some ideas in this line: Synapses.source should be stored internally as a weakref Synapses._source, and Synapses.source should be a computed attribute that dereferences this weakref. Like this, if the source object isn't kept by the user, Synapses won't store a reference to it, and so won't stop it from being deallocated. We should write an automated test that takes a piece of correct code like:: NeuronGroup(N, eqs, reset='V>Vt') and tries replacing all arguments by nonsense arguments, it should always raise an error in this case (forcing us to write code to validate the inputs). For example, you could create a new NonsenseObject class, and do this:: nonsense = NonsenseObject() NeuronGroup(nonsense, eqs, reset='V>Vt') NeuronGroup(N, nonsense, reset='V>Vt') NeuronGroup(N, eqs, nonsense) In general, the idea should be to make it hard for something incorrect to run without raising an error, preferably at the point where the user makes the error and not in some obscure way several lines later. The preferred way to validate inputs is one that handles types in a Pythonic way. For example, instead of doing something like:: if not isinstance(arg, (float, int)): raise TypeError(...) Do something like:: arg = float(arg) (or use try/except to raise a more specific error). In contrast to the ``isinstance`` check it does not make any assumptions about the type except for its ability to be converted to a float. This approach is particular useful for numpy arrays:: arr = np.asarray(arg) (or ``np.asanyarray`` if you want to allow for array subclasses like arrays with units or masked arrays). This approach has also the nice advantage that it allows all "array-like" arguments, e.g. a list of numbers. brian2-2.5.4/docs_sphinx/developer/guidelines/documentation.rst000066400000000000000000000211131445201106100247300ustar00rootroot00000000000000Documentation ============= It is very important to maintain documentation. We use the `Sphinx documentation generator `__ tools. The documentation is all hand written. Sphinx source files are stored in the ``docs_sphinx`` folder. The HTML files can be generated via the script ``dev/tools/docs/build_html_brian2.py`` and end up in the ``docs`` folder. Most of the documentation is stored directly in the Sphinx source text files, but reference documentation for important Brian classes and functions are kept in the documentation strings of those classes themselves. This is automatically pulled from these classes for the reference manual section of the documentation. The idea is to keep the definitive reference documentation near the code that it documents, serving as both a comment for the code itself, and to keep the documentation up to date with the code. The reference documentation includes all classes, functions and other objects that are defined in the modules and only documents them in the module where they were defined. This makes it possible to document a class like `~brian2.units.fundamentalunits.Quantity` only in `brian2.units.fundamentalunits` and not additionally in `brian2.units` and `brian2`. This mechanism relies on the ``__module__`` attribute, in some cases, in particular when wrapping a function with a decorator (e.g. `~brian2.units.fundamentalunits.check_units`), this attribute has to be set manually:: foo.__module__ = __name__ Without this manual setting, the function might not be documented at all or in the wrong module. In addition to the reference, all the examples in the examples folder are automatically included in the documentation. Note that you can directly link to github issues using ``:issue:`issue number```, e.g. writing ``:issue:`33``` links to a github issue about running benchmarks for Brian 2: :issue:`33`. This feature should rarely be used in the main documentation, reserve its use for release notes and important known bugs. Docstrings ---------- Every module, class, method or function has to start with a docstring, unless it is a private or special method (i.e. starting with ``_`` or ``__``) *and* it is obvious what it does. For example, there is normally no need to document ``__str__`` with "Return a string representation.". For the docstring format, we use the our own sphinx extension (in `brian2/sphinxext`) based on `numpydoc `__, allowing to write docstrings that are well readable both in sourcecode as well as in the rendered HTML. We generally follow the `format used by numpy `__ When the docstring uses variable, class or function names, these should be enclosed in single backticks. Class and function/method names will be automatically linked to the corresponding documentation. For classes imported in the main brian2 package, you do not have to add the package name, e.g. writing ```NeuronGroup``` is enough. For other classes, you have to give the full path, e.g. ```brian2.units.fundamentalunits.UnitRegistry```. If it is clear from the context where the class is (e.g. within the documentation of `~brian2.units.fundamentalunits.UnitRegistry`), consider using the ``~`` abbreviation: ```~brian2.units.fundamentalunits.UnitRegistry``` displays only the class name: `~brian2.units.fundamentalunits.UnitRegistry`. Note that you do not have to enclose the exception name in a "Raises" or "Warns" section, or the class/method/function name in a "See Also" section in backticks, they will be automatically linked (putting backticks there will lead to incorrect display or an error message), Inline source fragments should be enclosed in double backticks. Class docstrings follow the same conventions as method docstrings and should document the ``__init__`` method, the ``__init__`` method itself does not need a docstring. Documenting functions and methods ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The docstring for a function/method should start with a one-line description of what the function does, without referring to the function name or the names of variables. Use a "command style" for this summary, e.g. "Return the result." instead of "Returns the result." If the signature of the function cannot be automatically extracted because of an decorator (e.g. `check_units`), place a signature in the very first row of the docstring, before the one-line description. For methods, do not document the ``self`` parameter, nor give information about the method being static or a class method (this information will be automatically added to the documentation). Documenting classes ~~~~~~~~~~~~~~~~~~~ Class docstrings should use the same "Parameters" and "Returns" sections as method and function docstrings for documenting the ``__init__`` constructor. If a class docstring does not have any "Attributes" or "Methods" section, these sections will be automatically generated with all documented (i.e. having a docstring), public (i.e. not starting with `_`) attributes respectively methods of the class. Alternatively, you can provide these sections manually. This is useful for example in the `Quantity` class, which would otherwise include the documentation of many `ndarray` methods, or when you want to include documentation for functions like ``__getitem__`` which would otherwise not be documented. When specifying these sections, you only have to state the names of documented methods/attributes but you can also provide direct documentation. For example:: Attributes ---------- foo bar baz This is a description. This can be used for example for class or instance attributes which do not have "classical" docstrings. However, you can also use a special syntax: When defining class attributes in the class body or instance attributes in ``__init__`` you can use the following variants (here shown for instance attributes):: def __init__(a, b, c): #: The docstring for the instance attribute a. #: Can also span multiple lines self.a = a self.b = b #: The docstring for self.b (only one line). self.c = c 'The docstring for self.c, directly *after* its definition' Long example of a function docstring ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This is a very long docstring, showing all the possible sections. Most of the time no See Also, Notes or References section is needed:: def foo(var1, var2, long_var_name='hi') : """ A one-line summary that does not use variable names or the function name. Several sentences providing an extended description. Refer to variables using back-ticks, e.g. `var1`. Parameters ---------- var1 : array_like Array_like means all those objects -- lists, nested lists, etc. -- that can be converted to an array. We can also refer to variables like `var1`. var2 : int The type above can either refer to an actual Python type (e.g. ``int``), or describe the type of the variable in more detail, e.g. ``(N,) ndarray`` or ``array_like``. Long_variable_name : {'hi', 'ho'}, optional Choices in brackets, default first when optional. Returns ------- describe : type Explanation output : type Explanation tuple : type Explanation items : type even more explaining Raises ------ BadException Because you shouldn't have done that. See Also -------- otherfunc : relationship (optional) newfunc : Relationship (optional), which could be fairly long, in which case the line wraps here. thirdfunc, fourthfunc, fifthfunc Notes ----- Notes about the implementation algorithm (if needed). This can have multiple paragraphs. You may include some math: .. math:: X(e^{j\omega } ) = x(n)e^{ - j\omega n} And even use a greek symbol like :math:`omega` inline. References ---------- Cite the relevant literature, e.g. [1]_. You may also cite these references in the notes section above. .. [1] O. McNoleg, "The integration of GIS, remote sensing, expert systems and adaptive co-kriging for environmental habitat modelling of the Highland Haggis using object-oriented, fuzzy-logic and neural-network techniques," Computers & Geosciences, vol. 22, pp. 585-588, 1996. Examples -------- These are written in doctest format, and should illustrate how to use the function. >>> a=[1,2,3] >>> print([x + 3 for x in a]) [4, 5, 6] >>> print("a\nb") a b """ pass brian2-2.5.4/docs_sphinx/developer/guidelines/index.rst000066400000000000000000000012001445201106100231610ustar00rootroot00000000000000Coding guidelines ================= The basic principles of developing Brian are: 1. For the user, the emphasis is on making the package flexible, readable and easy to use. See the paper "The Brian simulator" in Frontiers in Neuroscience for more details. 2. For the developer, the emphasis is on keeping the package maintainable by a small number of people. To this end, we use stable, well maintained, existing open source packages whenever possible, rather than writing our own code. .. toctree:: :maxdepth: 2 workflow style representation defensive_programming documentation logging testing brian2-2.5.4/docs_sphinx/developer/guidelines/logging.rst000066400000000000000000000107501445201106100235120ustar00rootroot00000000000000.. currentmodule:: brian2 Logging ======= For a description of logging from the users point of view, see :doc:`../../advanced/logging`. Logging in Brian is based on the :mod:`logging` module in Python's standard library. Every brian module that needs logging should start with the following line, using the `get_logger` function to get an instance of `BrianLogger`:: logger = get_logger(__name__) In the code, logging can then be done via:: logger.diagnostic('A diagnostic message') logger.debug('A debug message') logger.info('An info message') logger.warn('A warning message') logger.error('An error message') If a module logs similar messages in different places or if it might be useful to be able to suppress a subset of messages in a module, add an additional specifier to the logging command, specifying the class or function name, or a method name including the class name (do not include the module name, it will be automatically added as a prefix):: logger.debug('A debug message', 'CodeString') logger.debug('A debug message', 'NeuronGroup.update') logger.debug('A debug message', 'reinit') If you want to log a message only once, e.g. in a function that is called repeatedly, set the optional ``once`` keyword to ``True``:: logger.debug('Will only be shown once', once=True) logger.debug('Will only be shown once', once=True) The output of debugging looks like this in the log file:: 2012-10-02 14:41:41,484 DEBUG brian2.equations.equations.CodeString: A debug message and like this on the console (if the log level is set to "debug"):: DEBUG A debug message [brian2.equations.equations.CodeString] .. _log_level_recommendations: Log level recommendations ------------------------- diagnostic Low-level messages that are not of any interest to the normal user but useful for debugging Brian itself. A typical example is the source code generated by the code generation module. debug Messages that are possibly helpful for debugging the user's code. For example, this shows which objects were included in the network, which clocks the network uses and when simulations start and stop. info Messages which are not strictly necessary, but are potentially helpful for the user. In particular, this will show messages about the chosen state updater and other information that might help the user to achieve better performance and/or accuracy in the simulations (e.g. using ``(event-driven)`` in synaptic equations, avoiding incompatible ``dt`` values between `TimedArray` and the `NeuronGroup` using it, ...) warn Messages that alert the user to a potential mistake in the code, e.g. two possible resolutions for an identifier in an equation. In such cases, the warning message should include clear information how to change the code to make the situation unambigous and therefore make the warning message disappear. It can also be used to make the user aware that he/she is using an experimental feature, an unsupported compiler or similar. In this case, normally the ``once=True`` option should be used to raise this warning only once. As a rule of thumb, "common" scripts like the examples provided in the examples folder should normally not lead to any warnings. error This log level is not used currently in Brian, an exception should be raised instead. It might be useful in "meta-code", running scripts and catching any errors that occur. The default log level shown to the user is ``info``. As a general rule, all messages that the user sees in the default configuration (i.e., ``info`` and ``warn`` level) should be avoidable by simple changes in the user code, e.g. the renaming of variables, explicitly specifying a state updater instead of relying on the automatic system, adding ``(clock-driven)``/``(event-driven)`` to synaptic equations, etc. Testing log messages -------------------- It is possible to test whether code emits an expected log message using the `~brian2.utils.logger.catch_logs` context manager. This is normally not necessary for debug and info messages, but should be part of the unit tests for warning messages (`~brian2.utils.logger.catch_logs` by default only catches warning and error messages):: with catch_logs() as logs: # code that is expected to trigger a warning # ... assert len(logs) == 1 # logs contains tuples of (log level, name, message) assert logs[0][0] == 'WARNING' and logs[0][1].endswith('warning_type') brian2-2.5.4/docs_sphinx/developer/guidelines/representation.rst000066400000000000000000000113111445201106100251200ustar00rootroot00000000000000Representing Brian objects ============================= ``__repr__`` and ``__str__`` ---------------------------- Every class should specify or inherit useful ``__repr__`` and ``__str__`` methods. The ``__repr__`` method should give the "official" representation of the object; if possible, this should be a valid Python expression, ideally allowing for ``eval(repr(x)) == x``. The ``__str__`` method on the other hand, gives an "informal" representation of the object. This can be anything that is helpful but does not have to be Python code. For example: .. doctest:: >>> import numpy as np >>> ar = np.array([1, 2, 3]) * mV >>> print(ar) # uses __str__ [ 1. 2. 3.] mV >>> ar # uses __repr__ array([ 1., 2., 3.]) * mvolt If the representation returned by ``__repr__`` is not Python code, it should be enclosed in ``<...>``, e.g. a `Synapses` representation might be ````. If you don't want to make the distinction between ``__repr__`` and ``__str__``, simply define only a ``__repr__`` function, it will be used instead of ``__str__`` automatically (no need to write ``__str__ = __repr__``). Finally, if you include the class name in the representation (which you should in most cases), use ``self.__class__.__name__`` instead of spelling out the name explicitly -- this way it will automatically work correctly for subclasses. It will also prevent you from forgetting to update the class name in the representation if you decide to rename the class. LaTeX representations with sympy -------------------------------- Brian objects dealing with mathematical expressions and equations often internally use sympy. Sympy's `~sympy.printing.latex.latex` function does a nice job of converting expressions into LaTeX code, using fractions, root symbols, etc. as well as converting greek variable names into corresponding symbols and handling sub- and superscripts. For the conversion of variable names to work, they should use an underscore for subscripts and two underscores for superscripts:: >>> from sympy import latex, Symbol >>> tau_1__e = Symbol('tau_1__e') >>> print(latex(tau_1__e)) \tau^{e}_{1} Sympy's printer supports formatting arbitrary objects, all they have to do is to implement a ``_latex`` method (no trailing underscore). For most Brian objects, this is unnecessary as they will never be formatted with sympy's LaTeX printer. For some core objects, in particular the units, is is useful, however, as it can be reused in LaTeX representations for ipython (see below). Note that the ``_latex`` method should not return ``$`` or ``\begin{equation}`` (sympy's method includes a ``mode`` argument that wraps the output automatically). Representations for ipython --------------------------------- "Old" ipython console ~~~~~~~~~~~~~~~~~~~~~ In particular for representations involing arrays or lists, it can be useful to break up the representation into chunks, or indent parts of the representation. This is supported by the ipython console's "pretty printer". To make this work for a class, add a ``_repr_pretty_(self, p, cycle)`` (note the *single* underscores) method. You can find more information in the `ipython documentation `__ . "New" ipython console (qtconsole and notebook) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The new ipython consoles, the qtconsole and the ipython notebook support a much richer set of representations for objects. As Brian deals a lot with mathematical objects, in particular the LaTeX and to a lesser extent the HTML formatting capabilities of the ipython notebook are interesting. To support LaTeX representation, implement a `_repr_latex_` method returning the LaTeX code (*including* ``$``, ``\begin{equation}`` or similar). If the object already has a ``_latex`` method (see `LaTeX representations with sympy`_ above), this can be as simple as:: def _repr_latex_(self): return sympy.latex(self, mode='inline') # wraps the expression in $ .. $ The LaTeX rendering only supports a single mathematical block. For complex objects, e.g. `NeuronGroup` it might be useful to have a richer representation. This can be achieved by returning HTML code from ``_repr_html_`` -- this HTML code is processed by MathJax so it can include literal LaTeX code that will be transformed before it is rendered as HTML. An object containing two equations could therefore be represented with a method like this:: def _repr_html_(self): return '''

Equation 1

{eq_1}

Equation 2

{eq_2}'''.format(eq_1=sympy.latex(self.eq_1, mode='equation'), eq_2=sympy.latex(self.eq_2, mode='equation')) brian2-2.5.4/docs_sphinx/developer/guidelines/style.rst000066400000000000000000000151521445201106100232250ustar00rootroot00000000000000Coding conventions ================== General recommendations ----------------------- Syntax is chosen as much as possible from the user point of view, to reflect the concepts as directly as possible. Ideally, a Brian script should be readable by someone who doesn't know Python or Brian, although this isn't always possible. Function, class and keyword argument names should be explicit rather than abbreviated and consistent across Brian. See Romain's paper `On the design of script languages for neural simulators `__ for a discussion. .. _code_style: Code style ~~~~~~~~~~ We use the `PEP-8 coding conventions `__ for our code, and use the `black formatting tool `__ to enforce a consistent code style. To make sure your code is formatted in the same way, you can either `integrate black with your editor/IDE `__, or install the ``pre-commit`` tool (`pre-commit documentation `__), and install it as a "git hook" with ``pre-commit install``. This will automatically run ``black`` (and in the future, additional linting tools) before each commit. In case that run changes the code formatting, it will reformat the relevant files, and you will have to ``git add`` these changes before doing the commit. For pull requests, this check will also be run automatically on the GitHub CI infrastructure. .. note:: In rare cases like manually aligned tables of related values, you can use ``fmt: skip`` (for single lines), or ``fmt: off`` and ``fmt: on`` (for code blocks), to exclude code from black's formatting. Also note that the code formatting is only enforced for files in the ``brian2`` package itself, code examples in the documentation or examples and tutorials do not have to follow black's style. The code style includes the following conventions in particular: * Use 4 spaces instead of tabs per indentation level * Use spaces after commas and around the following binary operators: assignment (=), augmented assignment (+=, -= etc.), comparisons (==, <, >, !=, <>, <=, >=, in, not in, is, is not), Booleans (and, or, not). * Do *not* use spaces around the equals sign in keyword arguments or when specifying default values. Neither put spaces immediately inside parentheses, brackets or braces, immediately before the open parenthesis that starts the argument list of a function call, or immediately before the open parenthesis that starts an indexing or slicing. * Avoid using a backslash for continuing lines whenever possible, instead use Python's implicit line joining inside parentheses, brackets and braces. Imports ~~~~~~~ Imports should be on different lines (e.g. do not use ``import sys, os``) and should be grouped in the following order, using blank lines between each group: 1. standard library imports 2. third-party library imports (e.g. numpy, scipy, sympy, ...) 3. brian imports This rule is enforced by using the `isort `__ tool, which is integrated with ``pre-commit`` in the same way as ``black``, described above. .. note:: In rare cases, where logical grouping makes more sense that ``isort``'s ordering, or when the order of imports matters, you can skip sorting imports in a file by including the comment ``# isort:skip_file``. Additional rules for imports: * Use absolute imports for everything outside of "your" package, e.g. if you are working in `brian2.equations`, import functions from the stringtools modules via ``from brian2.utils.stringtools import ...``. Use the full path when importing, e.g. do ``from brian2.units.fundamentalunits import seconds`` instead of ``from brian2 import seconds``. * Use "new-style" relative imports for everything in "your" package, e.g. in ``brian2.codegen.functions.py`` import the `Function` class as ``from .specifiers import Function``. * Do not use wildcard imports (``from brian2 import *``), instead import only the identifiers you need, e.g. ``from brian2 import NeuronGroup, Synapses``. For packages like numpy that are used a lot, use ``import numpy as np``. But note that the user should still be able to do something like ``from brian2 import *`` (and this style can also be freely used in examples and tests, for example). Modules always have to use the ``__all__`` mechanism to specify what is being made available with a wildcard import. As an exception from this rule, the main ``brian2/__init__.py`` may use wildcard imports. String formatting ----------------- In general, we use Python `f-strings `__ instead of the ``.format`` method or the `%` operator to format strings. For example, rather use:: raise KeyError(f"Unknown variable '{var}'") # ✔ instead of:: raise KeyError("Unknown variable '{}'".format(var)) # ❌ raise KeyError("Unknown variable %s" % var) # ❌ There are some corner cases where it still makes sense to use either of these, though. The `~str.format` method can be useful when processing several strings instead of single literals:: formatted = [] for s in strings: formatted.append(s.format(**values)) The `%` operator, or string concatenation, can be used when dealing with strings that contain curly braces, which would become difficult to read as an f-string:: latex_code = r'\begin{equation}%s\end{equation}' % equation # OK latex_code = r'\begin{equation}' + equation + r'\end{equation}' # OK Python does not make a difference between single quotation marks and double quotation marks. For consistency, we use black's style that double quotes (i.e. `"..."`) are used everywhere, except when this would lead to escaping of double quotation marks within the string itself (e.g. for ``include = f'#include "{header_file}"'``). When you need to display text to the user (e.g. in exception messages), use single quotation marks to highlight words to avoid this situation (e.g. `"The 'item' value should not be 0."`). Commits only changing the style ------------------------------- We have sometimes made big commits updating the style in our code, which can make using tools like ``git blame`` more difficult, since many lines are affected by such commits. We add the references to such commits to a file ``.git-blame-ignore-revs`` in the main directory, and you can tell ``git blame`` to ignore these commits with:: git config blame.ignoreRevsFile .git-blame-ignore-revs brian2-2.5.4/docs_sphinx/developer/guidelines/testing.rst000066400000000000000000000275201445201106100235440ustar00rootroot00000000000000Testing ======= Brian uses the `pytest package `__ for its testing framework. Running the test suite ---------------------- The pytest tool automatically finds tests in the code. However, to deal with the different code generation targets, and correctly set up tests for standalone mode, it is recommended to use Brian's builtin test function that calls pytest appropriately:: >>> import brian2 >>> brian2.test() # doctest: +SKIP By default, this runs the test suite for all available (runtime) code generation targets. If you only want to test a specific target, provide it as an argument:: >>> brian2.test('numpy') # doctest: +SKIP If you want to test several targets, use a list of targets:: >>> brian2.test(['cython']) # doctest: +SKIP In addition to the tests specific to a code generation target, the test suite will also run a set of independent tests (e.g. parsing of equations, unit system, utility functions, etc.). To exclude these tests, set the ``test_codegen_independent`` argument to ``False``. Not all available tests are run by default, tests that take a long time are excluded. To include these, set ``long_tests`` to ``True``. To run the C++ standalone tests, you have to set the ``test_standalone`` argument to the name of a standalone device. If you provide an empty argument for the runtime code generation targets, you will only run the standalone tests:: >>> brian2.test([], test_standalone='cpp_standalone') # doctest: +SKIP Writing tests ------------- Generally speaking, we aim for a 100% code coverage by the test suite. Less coverage means that some code paths are never executed so there's no way of knowing whether a code change broke something in that path. Unit tests ~~~~~~~~~~ The most basic tests are unit tests, tests that test one kind of functionality or feature. To write a new unit test, add a function called ``test_...`` to one of the ``test_...`` files in the ``brian2.tests`` package. Test files should roughly correspond to packages, test functions should roughly correspond to tests for one function/method/feature. In the test functions, use assertions that will raise an ``AssertionError`` when they are violated, e.g.:: G = NeuronGroup(42, model='dv/dt = -v / (10*ms) : 1') assert len(G) == 42 When comparing arrays, use the `array_equal` function from `numpy.testing.utils` which takes care of comparing types, shapes and content and gives a nicer error message in case the assertion fails. Never make tests depend on external factors like random numbers -- tests should always give the same result when run on the same codebase. You should not only test the expected outcome for the correct use of functions and classes but also that errors are raised when expected. For that you can use pytest's ``raises`` function with which you can define a block of code that should raise an exception of a certain type:: with pytest.raises(DimensionMismatchError): 3*volt + 5*second You can also check whether expected warnings are raised, see the documentation of the :doc:`logging mechanism ` for details For simple functions, doctests (see below) are a great alternative to writing classical unit tests. By default, all tests are executed for all selected runtime code generation targets (see `Running the test suite`_ above). This is not useful for all tests, some basic tests that for example test equation syntax or the use of physical units do not depend on code generation and need therefore not to be repeated. To execute such tests only once, they can be annotated with a ``codegen_independent`` marker, using the `~pytest.mark` decorator:: import pytest from brian2 import NeuronGroup @pytest.mark.codegen_independent def test_simple(): # Test that the length of a NeuronGroup is correct group = NeuronGroup(5, '') assert len(group) == 5 Tests that are not "codegen-independent" are by default only executed for the runtimes device, i.e. not for the ``cpp_standalone`` device, for example. However, many of those tests follow a common pattern that is compatible with standalone devices as well: they set up a network, run it, and check the state of the network afterwards. Such tests can be marked as ``standalone_compatible``, using the `~pytest.mark` decorator in the same way as for ``codegen_independent`` tests.:: import pytest from numpy.testing.utils import assert_equal from brian2 import * @pytest.mark.standalone_compatible def test_simple_run(): # Check that parameter values of a neuron don't change after a run group = NeuronGroup(5, 'v : volt') group.v = 'i*mV' run(1*ms) assert_equal(group.v[:], np.arange(5)*mV) Tests that have more than a single run function but are otherwise compatible with standalone mode (e.g. they don't need access to the number of synapses or results of the simulation before the end of the simulation), can be marked as ``standalone_compatible`` and ``multiple_runs``. They then have to use an explicit ``device.build(...)`` call of the form shown below:: import pytest from numpy.testing.utils import assert_equal from brian2 import * @pytest.mark.standalone_compatible @pytest.mark.multiple_runs def test_multiple_runs(): # Check that multiple runs advance the clock as expected group = NeuronGroup(5, 'v : volt') mon = StateMonitor(group, 'v', record=True) run(1 * ms) run(1 * ms) device.build(direct_call=False, **device.build_options) assert_equal(defaultclock.t, 2 * ms) assert_equal(mon.t[0], 0 * ms) assert_equal(mon.t[-1], 2 * ms - defaultclock.dt) Tests can also be written specifically for a standalone device (they then have to include the `~brian2.devices.device.set_device` call and possibly the `~brian2.devices.device.Device.build` call explicitly). In this case tests have to be annotated with the name of the device (e.g. ``'cpp_standalone'``) and with ``'standalone_only'`` to exclude this test from the runtime tests. Such code would look like this for a single `run` call, i.e. using the automatic "build on run" feature:: import pytest from brian2 import * @pytest.mark.cpp_standalone @pytest.mark.standalone_only def test_cpp_standalone(): set_device('cpp_standalone', directory=None) # set up simulation # run simulation run(...) # check simulation results If the code uses more than one `run` statement, it needs an explicit `~brian2.devices.device.Device.build` call:: import pytest from brian2 import * @pytest.mark.cpp_standalone @pytest.mark.standalone_only def test_cpp_standalone(): set_device('cpp_standalone', build_on_run=False) # set up simulation # run simulation run(...) # do something # run again run(...) device.build(directory=None) # check simulation results Summary ^^^^^^^ +------------------------------------------+------------------------+-------------------------------------------------------------+ | ``@pytest.mark`` marker | Executed for devices | explicit use of `device` | +==========================================+========================+=============================================================+ | ``codegen_independent`` | independent of devices | *none* | +------------------------------------------+------------------------+-------------------------------------------------------------+ | *none* | Runtime targets | *none* | +------------------------------------------+------------------------+-------------------------------------------------------------+ | ``standalone_compatible`` | Runtime and standalone | *none* | +------------------------------------------+------------------------+-------------------------------------------------------------+ | ``standalone_compatible, multiple_runs`` | Runtime and standalone | ``device.build(direct_call=False, **device.build_options)`` | +------------------------------------------+------------------------+-------------------------------------------------------------+ | ``cpp_standalone, standalone_only`` | C++ standalone device | ``set_device('cpp_standalone')`` | | | | ``...`` | | | | ``device.build(directory=None)`` | +------------------------------------------+------------------------+-------------------------------------------------------------+ | ``my_device, standalone_only`` | "My device" | ``set_device('my_device')`` | | | | ``...`` | | | | ``device.build(directory=None)`` | +------------------------------------------+------------------------+-------------------------------------------------------------+ Doctests ~~~~~~~~ Doctests are executable documentation. In the ``Examples`` block of a class or function documentation, simply write code copied from an interactive Python session (to do this from ipython, use ``%doctestmode``), e.g.:: >>> from brian2.utils.stringtools import word_substitute >>> expr = 'a*_b+c5+8+f(A)' >>> print(word_substitute(expr, {'a':'banana', 'f':'func'})) banana*_b+c5+8+func(A) During testing, the actual output will be compared to the expected output and an error will be raised if they don't match. Note that this comparison is strict, e.g. trailing whitespace is not ignored. There are various ways of working around some problems that arise because of this expected exactness (e.g. the stacktrace of a raised exception will never be identical because it contains file names), see the `doctest documentation`_ for details. Doctests can (and should) not only be used in docstrings, but also in the hand-written documentation, making sure that the examples actually work. To turn a code example into a doc test, use the ``.. doctest::`` directive, see :doc:`/user/equations` for examples written as doctests. For all doctests, everything that is available after ``from brian2 import *`` can be used directly. For everything else, add import statements to the doctest code or -- if you do not want the import statements to appear in the document -- add them in a ``.. testsetup::`` block. See the documentation for `Sphinx's doctest extension`_ for more details. Doctests are a great way of testing things as they not only make sure that the code does what it is supposed to do but also that the documentation is up to date! .. _`doctest documentation`: https://docs.python.org/2/library/doctest.html .. _`Sphinx's doctest extension`: http://www.sphinx-doc.org/en/stable/ext/doctest.html Correctness tests ~~~~~~~~~~~~~~~~~ [These do not exist yet for brian2]. Unit tests test a specific function or feature in isolation. In addition, we want to have tests where a complex piece of code (e.g. a complete simulation) is tested. Even if it is sometimes impossible to really check whether the result is correct (e.g. in the case of the spiking activity of a complex network), a useful check is also whether the result is *consistent*. For example, the spiking activity should be the same when using code generation for Python or C++. Or, a network could be pickled before running and then the result of the run could be compared to a second run that starts from the unpickled network. brian2-2.5.4/docs_sphinx/developer/guidelines/workflow.rst000066400000000000000000000070001445201106100237300ustar00rootroot00000000000000Development workflow ==================== Brian development is done in a `git`_ repository on `github`_. Continuous integration testing is provided by `GitHub Actions`_, code coverage is measured with `coveralls.io`_. .. _git: https://git-scm.com/ .. _github: https://github.com/ .. _`GitHub Actions`: https://github.com/features/actions .. _`coveralls.io`: https://coveralls.io/ The repository structure ------------------------ Brian's repository structure is very simple, as we are normally not supporting older versions with bugfixes or other complicated things. The *master* branch of the repository is the basis for releases, a release is nothing more than adding a tag to the branch, creating the tarball, etc. The *master* branch should always be in a deployable state, i.e. one should be able to use it as the base for everyday work without worrying about random breakages due to updates. To ensure this, no commit ever goes into the *master* branch without passing the test suite before (see below). The only exception to this rule is if a commit not touches any code files, e.g. additions to the README file or to the documentation (but even in this case, care should be taken that the documentation is still built correctly). For every feature that a developer works on, a new branch should be opened (normally based on the *master* branch), with a descriptive name (e.g. ``add-numba-support``). For developers that are members of "brian-team", the branch should ideally be created in the main repository. This way, one can easily get an overview over what the "core team" is currently working on. Developers who are not members of the team should fork the repository and work in their own repository (if working on multiple issues/features, also using branches). Implementing a feature/fixing a bug ----------------------------------- Every new feature or bug fix should be done in a dedicated branch and have an issue in the issue database. For bugs, it is important to not only fix the bug but also to introduce a new test case (see :doc:`testing`) that makes sure that the bug will not ever be reintroduced by other changes. It is often a good idea to first define the test cases (that should fail) and then work on the fix so that the tests pass. As soon as the feature/fix is complete *or* as soon as specific feedback on the code is needed, open a "pull request" to merge the changes from your branch into *master*. In this pull request, others can comment on the code and make suggestions for improvements. New commits to the respective branch automatically appear in the pull request which makes it a great tool for iterative code review. Even more useful, GitHub Actions will automatically run the test suite on the result of the merge. As a reviewer, always wait for the result of this test (it can take up to 30 minutes or so until it appears) before doing the merge and never merge when a test fails. As soon as the reviewer (someone from the core team and not the author of the feature/fix) decides that the branch is ready to merge, he/she can merge the pull request and optionally delete the corresponding branch (but it will be hidden by default, anyway). Useful links ------------ * The Brian repository: https://github.com/brian-team/brian2 * GitHub Actions tests for Brian: https://github.com/brian-team/brian2/actions * Code Coverage for Brian: https://coveralls.io/github/brian-team/brian2 * The Pro Git book: https://git-scm.com/book/en/v2 * github's documentation on pull requests: https://help.github.com/articles/using-pull-requests brian2-2.5.4/docs_sphinx/developer/index.rst000066400000000000000000000016061445201106100210430ustar00rootroot00000000000000Developer's guide ================= This section is intended as a guide to how Brian functions internally for people developing Brian itself, or extensions to Brian. It may also be of some interest to others wishing to better understand how Brian works internally. If you use `VS code `_ as your development environment, it will offer to automatically build a Brian development `Docker `_ container when you open the repository, with all the required dependencies installed and configured. Further `documentation `_ for this approach can be found in the ``.devcontainer`` directory. .. toctree:: :maxdepth: 2 guidelines/index units equations_namespaces variables_indices preferences functions codegen standalone openmp devices GSL brian2-2.5.4/docs_sphinx/developer/oldcodegen.rst000066400000000000000000000217331445201106100220420ustar00rootroot00000000000000Older notes on code generation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The following is an outline of how the Brian 2 code generation system works, with indicators as to which packages to look at and which bits of code to read for a clearer understanding. We illustrate the global process with an example, the creation and running of a single `NeuronGroup` object: - Parse the equations, add refractoriness to them: this isn't really part of code generation. - Allocate memory for the state variables. - Create `Thresholder`, `Resetter` and `StateUpdater` objects. - Determine all the variable and function names used in the respective abstract code blocks and templates - Determine the abstract namespace, i.e. determine a `Variable` or `Function` object for each name. - Create a `CodeObject` based on the abstract code, template and abstract namespace. This will generate code in the target language and the namespace in which the code will be executed. - At runtime, each object calls `CodeObject.__call__` to execute the code. Stages of code generation ========================= Equations to abstract code -------------------------- In the case of `Equations`, the set of equations are combined with a numerical integration method to generate an *abstract code block* (see below) which represents the integration code for a single time step. An example of this would be converting the following equations:: eqs = ''' dv/dt = (v0-v)/tau : volt (unless refractory) v0 : volt ''' group = NeuronGroup(N, eqs, threshold='v>10*mV', reset='v=0*mV', refractory=5*ms) into the following abstract code using the `exponential_euler` method (which is selected automatically):: not_refractory = 1*((t - lastspike) > 0.005000) _BA_v = -v0 _v = -_BA_v + (_BA_v + v)*exp(-dt*not_refractory/tau) v = _v The code for this stage can be seen in `NeuronGroup.__init__`, `StateUpdater.__init__`, and `StateUpdater.update_abstract_code` (in ``brian2.groups.neurongroup``), and the `StateUpdateMethod` classes defined in the ``brian2.stateupdaters`` package. For more details, see :doc:`../advanced/state_update`. Abstract code ------------- 'Abstract code' is just a multi-line string representing a block of code which should be executed for each item (e.g. each neuron, each synapse). Each item is independent of the others in abstract code. This allows us to later generate code either for vectorised languages (like numpy in Python) or using loops (e.g. in C++). Abstract code is parsed according to Python syntax, with certain language constructs excluded. For example, there cannot be any conditional or looping statements at the moment, although support for this is in principle possible and may be added later. Essentially, all that is allowed at the moment is a sequence of arithmetical ``a = b*c`` style statements. Abstract code is provided directly by the user for threshold and reset statements in `NeuronGroup` and for pre/post spiking events in `Synapses`. Abstract code to snippet ------------------------ We convert abstract code into a 'snippet', which is a small segment of code which is syntactically correct in the target language, although it may not be runnable on its own (that's handled by insertion into a 'template' later). This is handled by the `CodeGenerator` object in ``brian2.codegen.generators``. In the case of converting into python/numpy code this typically doesn't involve any changes to the code at all because the original code is in Python syntax. For conversion to C++, we have to do some syntactic transformations (e.g. ``a**b`` is converted to ``pow(a, b)``), and add declarations for certain variables (e.g. converting ``x=y*z`` into ``const double x = y*z;``). An example of a snippet in C++ for the equations above:: const double v0 = _ptr_array_neurongroup_v0[_neuron_idx]; const double lastspike = _ptr_array_neurongroup_lastspike[_neuron_idx]; bool not_refractory = _ptr_array_neurongroup_not_refractory[_neuron_idx]; double v = _ptr_array_neurongroup_v[_neuron_idx]; not_refractory = 1 * (t - lastspike > 0.0050000000000000001); const double _BA_v = -(v0); const double _v = -(_BA_v) + (_BA_v + v) * exp(-(dt) * not_refractory / tau); v = _v; _ptr_array_neurongroup_not_refractory[_neuron_idx] = not_refractory; _ptr_array_neurongroup_v[_neuron_idx] = v; The code path that includes snippet generation will be discussed in more detail below, since it involves the concepts of namespaces and variables which we haven't covered yet. Snippet to code block --------------------- The final stage in the generation of a runnable code block is the insertion of a snippet into a template. These use the Jinja2 template specification language. This is handled in ``brian2.codegen.templates``. An example of a template for Python thresholding:: # USES_VARIABLES { not_refractory, lastspike, t } {% for line in code_lines %} {{line}} {% endfor %} _return_values, = _cond.nonzero() # Set the neuron to refractory not_refractory[_return_values] = False lastspike[_return_values] = t and the output code from the example equations above:: # USES_VARIABLES { not_refractory, lastspike, t } v = _array_neurongroup_v _cond = v > 10 * mV _return_values, = _cond.nonzero() # Set the neuron to refractory not_refractory[_return_values] = False lastspike[_return_values] = t Code block to executing code ---------------------------- A code block represents runnable code. Brian operates in two different regimes, either in runtime or standalone mode. In runtime mode, memory allocation and overall simulation control is handled by Python and numpy, and code objects operate on this memory when called directly by Brian. This is the typical way that Brian is used, and it allows for a rapid development cycle. However, we also support a standalone mode in which an entire project workspace is generated for a target language or device by Brian, which can then be compiled and run independently of Brian. Each mode has different templates, and does different things with the outputted code blocks. For runtime mode, in Python/numpy code is executed by simply calling the ``exec`` statement on the code block in a given namespace. In standalone mode, the templates will typically each be saved into different files. Key concepts ============ Namespaces ---------- In general, a namespace is simply a mapping/dict from names to values. In Brian we use the term 'namespace' in two ways: the high level "abstract namespace" maps names to objects based on the `Variables` or `Function` class. In the above example, ``v`` maps to an `ArrayVariable` object, ``tau`` to a `Constant` object, etc. This namespace has all the information that is needed for checking the consistency of units, to determine which variables are boolean or scalar, etc. During the `CodeObject` creation, this abstract namespace is converted into the final namespace in which the code will be executed. In this namespace, ``v`` maps to the numpy array storing the state variable values (without units) and ``tau`` maps to a concrete value (again, without units). See :doc:`equations_namespaces` for more details. Variable ---------- `Variable` objects contain information about the variable they correspond to, including details like the data type, whether it is a single value or an array, etc. See ``brian2.core.variables`` and, e.g. `Group._create_variables`, `NeuronGroup._create_variables`. Templates --------- Templates are stored in Jinja2 format. They come in one of two forms, either they are a single template if code generation only needs to output a single block of code, or they define multiple Jinja macros, each of which is a separate code block. The `CodeObject` should define what type of template it wants, and the names of the macros to define. For examples, see the templates in the directories in ``brian2/codegen/runtime``. See ``brian2.codegen.templates`` for more details. Code guide ========== This section includes a guide to the various relevant packages and subpackages involved in the code generation process. ``codegen`` Stores the majority of all code generation related code. ``codegen.functions`` Code related to including functions - built-in and user-defined - in generated code. ``codegen.generators`` Each `CodeGenerator` is defined in a module here. ``codegen.runtime`` Each runtime `CodeObject` and its templates are defined in a package here. ``core`` ``core.variables`` The `Variable` types are defined here. ``equations`` Everything related to `Equations`. ``groups`` All `Group` related stuff is in here. The `Group.resolve` methods are responsible for determining the abstract namespace. ``parsing`` Various tools using Python's ``ast`` module to parse user-specified code. Includes syntax translation to various languages in ``parsing.rendering``. ``stateupdaters`` Everything related to generating abstract code blocks from integration methods is here. brian2-2.5.4/docs_sphinx/developer/openmp.rst000066400000000000000000000226721445201106100212400ustar00rootroot00000000000000Multi-threading with OpenMP ~~~~~~~~~~~~~~~~~~~~~~~~~~~ The following is an outline of how to make C++ standalone templates compatible with OpenMP, and therefore make them work in a multi-threaded environment. This should be considered as an extension to :doc:`codegen`, that has to be read first. The C++ standalone mode of Brian is compatible with OpenMP, and therefore simulations can be launched by users with one or with multiple threads. Therefore, when adding new templates, the developers need to make sure that those templates are properly handling the situation if launched with OpenMP. Key concepts ============ All the simulations performed with the C++ standalone mode can be launched with multi-threading, and make use of multiple cores on the same machine. Basically, all the Brian operations that can easily be performed in parallel, such as computing the equations for `NeuronGroup`, `Synapses`, and so on can and should be split among several threads. The network construction, so far, is still performed only by one single thread, and all created objects are shared by all the threads. Use of ``#pragma`` flags ======================== In OpenMP, all the parallelism is handled thanks to extra comments, added in the main C++ code, under the form:: #pragma omp ... But to avoid any dependencies in the code that is generated by Brian when OpenMP is not activated, we are using functions that will only add those comments, during code generation, when such a multi-threading mode is turned on. By default, nothing will be inserted. Translations of the ``#pragma`` commands ---------------------------------------- All the translations from ``openmp_pragma()`` calls in the C++ templates are handled in the file ``devices/cpp_standalone/codeobject.py`` In this function, you can see that all calls with various string inputs will generate #pragma statements inserted into the C++ templates during code generation. For example:: {{ openmp_pragma('static') }} will be transformed, during code generation, into:: #pragma omp for schedule(static) You can find the list of all the translations in the core of the ``openmp_pragma()`` function, and if some extra translations are needed, they should be added here. Execution of the OpenMP code ---------------------------- In this section, we are explaining the main ideas behind the OpenMP mode of Brian, and how the simulation is executed in such a parallel context. As can be seen in ``devices/cpp_standalone/templates/main.cpp``, the appropriate number of threads, defined by the user, is fixed at the beginning of the main function in the C++ code with:: {{ openmp_pragma('set_num_threads') }} equivalent to (thanks to the ``openmp_pragam()`` function defined above): nothing if OpenMP is turned off (default), and to:: omp_set_dynamic(0); omp_set_num_threads(nb_threads); otherwise. When OpenMP creates a parallel context, this is the number of threads that will be used. As said, network creation is performed without any calls to OpenMP, on one single thread. Each template that wants to use parallelism has to add ``{{ openmp_pragma{('parallel')}}`` to create a general block that will be executed in parallel or ``{{ openmp_pragma{('parallel-static')}}`` to execute a single loop in parallel. How to make your template use OpenMP parallelism ================================================ To design a parallel template, such as for example ``devices/cpp_standalone/templates/common_group.cpp``, you can see that as soon as you have loops that can safely be split across nodes, you just need to add an openmp command in front of those loops:: {{openmp_pragma('parallel-static')}} for(int _idx=0; _idxpush(spikes, nspikes); } Such a method for the `SynapticPathway` will make sure that when spikes are propagated, all the threads will propagate them to their connections. By default, again, if OpenMP is turned off, the queue vector has size 1. Preparation of the `SynapticPathway` ------------------------------------ Here we are explaining the implementation of the ``prepare()`` method for `SynapticPathway`:: {{ openmp_pragma('parallel') }} { unsigned int length; if ({{ openmp_pragma('get_thread_num') }} == _nb_threads - 1) length = n_synapses - (unsigned int) {{ openmp_pragma('get_thread_num') }}*n_synapses/_nb_threads; else length = (unsigned int) n_synapses/_nb_threads; unsigned int padding = {{ openmp_pragma('get_thread_num') }}*(n_synapses/_nb_threads); queue[{{ openmp_pragma('get_thread_num') }}]->openmp_padding = padding; queue[{{ openmp_pragma('get_thread_num') }}]->prepare(&real_delays[padding], &sources[padding], length, _dt); } Basically, each threads is getting an equal number of synapses (except the last one, that will get the remaining ones, if the number is not a multiple of ``n_threads``), and the queues are receiving a padding integer telling them what part of the synapses belongs to each queue. After that, the parallel context is destroyed, and network creation can continue. Note that this could have been done without a parallel context, in a sequential manner, but this is just speeding up everything. Selection of the spikes ----------------------- Here we are explaining the implementation of the ``peek()`` method for `SynapticPathway`. This is an example of concurrent access to data structures that are not well handled in parallel, such as ``std::vector``. When ``peek()`` is called, we need to return a vector of all the neuron spiking at that particular time. Therefore, we need to ask every queue of the `SynapticPathway` what are the id of the spiking neurons, and concatenate them. Because those ids are stored in vectors with various shapes, we need to loop over nodes to perform this concatenate, in a sequential manner:: {{ openmp_pragma('static-ordered') }} for(int _thread=0; _thread < {{ openmp_pragma('get_num_threads') }}; _thread++) { {{ openmp_pragma('ordered') }} { if (_thread == 0) all_peek.clear(); all_peek.insert(all_peek.end(), queue[_thread]->peek()->begin(), queue[_thread]->peek()->end()); } } The loop, with the keyword 'static-ordered', is therefore performed such that node 0 enters it first, then node 1, and so on. Only one node at a time is executing the block statement. This is needed because vector manipulations can not be performed in a multi-threaded manner. At the end of the loop, ``all_peek`` is now a vector where all sub queues have written the id of spiking cells, and therefore this is the list of all spiking cells within the `SynapticPathway`. Compilation of the code ======================= One extra file needs to be modified, in order for OpenMP implementation to work. This is the makefile ``devices/cpp_standalone/templates/makefile``. As one can simply see, the CFLAGS are dynamically modified during code generation thanks to:: {{ openmp_pragma('compilation') }} If OpenMP is activated, this will add the following dependencies:: -fopenmp such that if OpenMP is turned off, nothing, in the generated code, does depend on it. brian2-2.5.4/docs_sphinx/developer/preferences.rst000066400000000000000000000106221445201106100222330ustar00rootroot00000000000000Preferences system ================== .. currentmodule:: brian2.core.preferences Each preference looks like ``codegen.c.compiler``, i.e. dotted names. Each preference has to be registered and validated. The idea is that registering all preferences ensures that misspellings of a preference value by a user causes an error, e.g. if they wrote ``codgen.c.compiler`` it would raise an error. Validation means that the value is checked for validity, so ``codegen.c.compiler = 'gcc'`` would be allowed, but ``codegen.c.compiler = 'hcc'`` would cause an error. An additional requirement is that the preferences system allows for extension modules to define their own preferences, including extending the existing core brian preferences. For example, an extension might want to define ``extension.*`` but it might also want to define a new language for codegen, e.g. ``codegen.lisp.*``. However, extensions cannot add preferences to an existing category. Accessing and setting preferences --------------------------------- Preferences can be accessed and set either keyword-based or attribute-based. To set/get the value for the preference example mentioned before, the following are equivalent:: prefs['codegen.c.compiler'] = 'gcc' prefs.codegen.c.compiler = 'gcc' if prefs['codegen.c.compiler'] == 'gcc': ... if prefs.codegen.c.compiler == 'gcc': ... Using the attribute-based form can be particulary useful for interactive work, e.g. in ipython, as it offers autocompletion and documentation. In ipython, ``prefs.codegen.c?`` would display a docstring with all the preferences available in the ``codegen.c`` category. Preference files ---------------- Preferences are stored in a hierarchy of files, with the following order (each step overrides the values in the previous step but no error is raised if one is missing): * The global defaults are stored in the installation directory. * The user default are stored in ``~/.brian/preferences`` (which works on Windows as well as Linux). * The file ``brian_preferences`` in the current directory. Registration ------------ Registration of preferences is performed by a call to `BrianGlobalPreferences.register_preferences`, e.g.:: register_preferences( 'codegen.c', 'Code generation preferences for the C language', 'compiler'= BrianPreference( validator=is_compiler, docs='...', default='gcc'), ... ) The first argument ``'codegen.c'`` is the base name, and every preference of the form ``codegen.c.*`` has to be registered by this function (preferences in subcategories such as ``codegen.c.somethingelse.*`` have to be specified separately). In other words, by calling `~BrianGlobalPreferences.register_preferences`, a module takes ownership of all the preferences with one particular base name. The second argument is a descriptive text explaining what this category is about. The preferences themselves are provided as keyword arguments, each set to a `BrianPreference` object. Validation functions -------------------- A validation function takes a value for the preference and returns ``True`` (if the value is a valid value) or ``False``. If no validation function is specified, a default validator is used that compares the value against the default value: Both should belong to the same class (e.g. int or str) and, in the case of a `Quantity` have the same unit. Validation ---------- Setting the value of a preference with a registered base name instantly triggers validation. Trying to set an unregistered preference using keyword or attribute access raises an error. The only exception from this rule is when the preferences are read from configuration files (see below). Since this happens before the user has the chance to import extensions that potentially define new preferences, this uses a special function (`_set_preference`). In this case,for base names that are not yet registered, validation occurs when the base name is registered. If, at the time that `Network.run` is called, there are unregistered preferences set, a `PreferenceError` is raised. File format ----------- The preference files are of the following form:: a.b.c = 1 # Comment line [a] b.d = 2 [a.b] b.e = 3 This would set preferences ``a.b.c=1``, ``a.b.d=2`` and ``a.b.e=3``. Built-in preferences -------------------- Brian itself defines the following preferences: .. document_brian_prefs:: :nolinks: brian2-2.5.4/docs_sphinx/developer/standalone.rst000066400000000000000000000044741445201106100220720ustar00rootroot00000000000000Standalone implementation ========================= .. contents:: :local: :depth: 1 This – currently very incomplete – document describes some of the implementation details of :ref:`cpp_standalone`. .. _array_cache: Array cache ----------- As described in :ref:`standalone variables `, in standalone mode Python code does not usually have access to state variables and synaptic indices, since the code necessary to initialize/create them has not been run yet. Concretely, accessing a state variable (or other variables like synaptic indices), will call `.ArrayVariable.get_value` which delegates to `.CPPStandaloneDevice.get_value`. After a run, this will read the corresponding file from the disk and return the values. The user can therefore use the same code to analyze the results as for runtime mode. Before a run, this file does not exist, but `.CPPStandaloneDevice.get_value` has another mechanism to return values: the "array cache". This cache is a simple dictionary, stored in `.CPPStandaloneDevice.array_cache`, mapping `.ArrayVariable` objects to their respective values. If the requested object is present in this cache, its values can be accessed even before the simulation is run. Values are added to this cache, whenever simulation code sets variables with concrete values. Methods such as `.CPPStandaloneDevice.fill_with_array` or `.CPPStandaloneDevice.init_with_zeros` write the provided values into the array cache so that they can be retrieved later. Conversely, `.CPPStandaloneDevice.code_object` will delete any existing information in ``array_cache`` for variables that are changed by a code object, i.e. invalidate any previously stored values:: >>> set_device('cpp_standalone') >>> G = NeuronGroup(10, 'v : volt') >>> v_var = G.variables['v'] >>> print(device.array_cache[v_var]) # CPPStandaloneDevice.init_with_zeros stored initial zero values [ 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.] >>> G.v = -70*mV >>> print(device.array_cache[v_var]) # CPPStandaloneDevice.fill_with_array updated the values [-0.07 -0.07 -0.07 -0.07 -0.07 -0.07 -0.07 -0.07 -0.07 -0.07] >>> G.v = '-70*mV + i*2*mV' >>> print(device.array_cache[v_var]) # Array cache for v has been invalidated None >>> set_device('runtime') # Reset device to avoid problems in other doctests brian2-2.5.4/docs_sphinx/developer/units.rst000066400000000000000000000213401445201106100210730ustar00rootroot00000000000000Units ===== Casting rules ------------- In Brian 1, a distinction is made between scalars and numpy arrays (including scalar arrays): Scalars could be multiplied with a unit, resulting in a Quantity object whereas the multiplication of an array with a unit resulted in a (unitless) array. Accordingly, scalars were considered as dimensionless quantities for the purpose of unit checking (e.g.. 1 + 1 * mV raised an error) whereas arrays were not (e.g. array(1) + 1 * mV resulted in 1.001 without any errors). Brian 2 no longer makes this distinction and treats both scalars and arrays as dimensionless for unit checking and make all operations involving quantities return a quantity.:: >>> 1 + 1*second # doctest: +ELLIPSIS +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... DimensionMismatchError: Cannot calculate 1. s + 1, units do not match (units are second and 1). >>> np.array([1]) + 1*second # doctest: +ELLIPSIS +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... DimensionMismatchError: Cannot calculate 1. s + [1], units do not match (units are second and 1). >>> 1*second + 1*second 2. * second >>> np.array([1])*second + 1*second array([ 2.]) * second As one exception from this rule, a scalar or array ``0`` is considered as having "any unit", i.e. ``0 + 1 * second`` will result in ``1 * second`` without a dimension mismatch error and ``0 == 0 * mV`` will evaluate to ``True``. This seems reasonable from a mathematical viewpoint and makes some sources of error disappear. For example, the Python builtin ``sum`` (not numpy's version) adds the value of the optional argument ``start``, which defaults to 0, to its main argument. Without this exception, ``sum([1 * mV, 2 * mV])`` would therefore raise an error. The above rules also apply to all comparisons (e.g. ``==`` or ``<``) with one further exception: ``inf`` and ``-inf`` also have "any unit", therefore an expression like ``v <= inf`` will never raise an exception (and always return ``True``). Functions and units ------------------- ndarray methods ~~~~~~~~~~~~~~~ All methods that make sense on quantities should work, i.e. they check for the correct units of their arguments and return quantities with units were appropriate. Most of the methods are overwritten using thin function wrappers: ``wrap_function_keep_dimension``: Strips away the units before giving the array to the method of ``ndarray``, then reattaches the unit to the result (examples: sum, mean, max) ``wrap_function_change_dimension``: Changes the dimensions in a simple way that is independent of function arguments, the shape of the array, etc. (examples: sqrt, var, power) ``wrap_function_dimensionless``: Raises an error if the method is called on a quantity with dimensions (i.e. it works on dimensionless quantities). **List of methods** ``all``, ``any``, ``argmax``, ``argsort``, ``clip``, ``compress``, ``conj``, ``conjugate``, ``copy``, ``cumsum``, ``diagonal``, ``dot``, ``dump``, ``dumps``, ``fill``, ``flatten``, ``getfield``, ``item``, ``itemset``, ``max``, ``mean``, ``min``, ``newbyteorder``, ``nonzero``, ``prod``, ``ptp``, ``put``, ``ravel``, ``repeat``, ``reshape``, ``round``, ``searchsorted``, ``setasflat``, ``setfield``, ``setflags``, ``sort``, ``squeeze``, ``std``, ``sum``, ``take``, ``tolist``, ``trace``, ``transpose``, ``var``, ``view`` **Notes** * Methods directly working on the internal data buffer (``setfield``, ``getfield``, ``newbyteorder``) ignore the dimensions of the quantity. * The type of a quantity cannot be int, therefore ``astype`` does not quite work when trying to convert the array into integers. * ``choose`` is only defined for integer arrays and therefore does not work * ``tostring`` and ``tofile`` only return/save the pure array data without the unit (but you can use ``dump`` or ``dumps`` to pickle a quantity array) * ``resize`` does not work: ``ValueError: cannot resize this array: it does not own its data`` * ``cumprod`` would result in different dimensions for different elements and is therefore forbidden * ``item`` returns a pure Python float by definition * ``itemset`` does not check for units Numpy ufuncs ~~~~~~~~~~~~ All of the standard `numpy ufuncs`_ (functions that operate element-wise on numpy arrays) are supported, meaning that they check for correct units and return appropriate arrays. These functions are often called implicitly, for example when using operators like ``<`` or ``**``. *Math operations:* ``add``, ``subtract``, ``multiply``, ``divide``, ``logaddexp``, ``logaddexp2``, ``true_divide``, ``floor_divide``, ``negative``, ``power``, ``remainder``, ``mod``, ``fmod``, ``absolute``, ``rint``, ``sign``, ``conj``, ``conjugate``, ``exp``, ``exp2``, ``log``, ``log2``, ``log10``, ``expm1``, ``log1p``, ``sqrt``, ``square``, ``reciprocal``, ``ones_like`` *Trigonometric functions:* ``sin``, ``cos``, ``tan``, ``arcsin``, ``arccos``, ``arctan``, ``arctan2``, ``hypot``, ``sinh``, ``cosh``, ``tanh``, ``arcsinh``, ``arccosh``, ``arctanh``, ``deg2rad``, ``rad2deg`` *Bitwise functions:* ``bitwise_and``, ``bitwise_or``, ``bitwise_xor``, ``invert``, ``left_shift``, ``right_shift`` *Comparison functions:* ``greater``, ``greater_equal``, ``less``, ``less_equal``, ``not_equal``, ``equal``, ``logical_and``, ``logical_or``, ``logical_xor``, ``logical_not``, ``maximum``, ``minimum`` *Floating functions:* ``isreal``, ``iscomplex``, ``isfinite``, ``isinf``, ``isnan``, ``floor``, ``ceil``, ``trunc``, ``fmod`` Not taken care of yet: ``signbit``, ``copysign``, ``nextafter``, ``modf``, ``ldexp``, ``frexp`` **Notes** * Everything involving ``log`` or ``exp``, as well as trigonometric functions only works on dimensionless array (for ``arctan2`` and ``hypot`` this is questionable, though) * Unit arrays can only be raised to a scalar power, not to an array of exponents as this would lead to differing dimensions across entries. For simplicity, this is enforced even for dimensionless quantities. * Bitwise functions never works on quantities (numpy will by itself throw a ``TypeError`` because they are floats not integers). * All comparisons only work for matching dimensions (with the exception of always allowing comparisons to 0) and return a pure boolean array. * All logical functions treat quantities as boolean values in the same way as floats are treated as boolean: Any non-zero value is True. .. _numpy ufuncs: http://docs.scipy.org/doc/numpy/reference/ufuncs.html Numpy functions ~~~~~~~~~~~~~~~ Many numpy functions are functional versions of ndarray methods (e.g. ``mean``, ``sum``, ``clip``). They therefore work automatically when called on quantities, as numpy propagates the call to the respective method. There are some functions in numpy that do not propagate their call to the corresponding method (because they use np.asarray instead of np.asanyarray, which might actually be a bug in numpy): ``trace``, ``diagonal``, ``ravel``, ``dot``. For these, wrapped functions in ``unitsafefunctions.py`` are provided. **Wrapped numpy functions in unitsafefunctions.py** These functions are thin wrappers around the numpy functions to correctly check for units and return quantities when appropriate: ``log``, ``exp``, ``sin``, ``cos``, ``tan``, ``arcsin``, ``arccos``, ``arctan``, ``sinh``, ``cosh``, ``tanh``, ``arcsinh``, ``arccosh``, ``arctanh``, ``diagonal``, ``ravel``, ``trace``, ``dot`` **numpy functions that work unchanged** This includes all functional counterparts of the methods mentioned above (with the exceptions mentioned above). Some other functions also work correctly, as they are only using functions/methods that work with quantities: * ``linspace``, ``diff``, ``digitize`` [1]_ * ``trim_zeros``, ``fliplr``, ``flipud``, ``roll``, ``rot90``, ``shuffle`` * ``corrcoeff`` [1]_ .. [1] But does not care about the units of its input. **numpy functions that return a pure numpy array instead of quantities** * ``arange`` * ``cov`` * ``random.permutation`` * ``histogram``, ``histogram2d`` * ``cross``, ``inner``, ``outer`` * ``where`` **numpy functions that do something wrong** * ``insert``, ``delete`` (return a quantity array but without units) * ``correlate`` (returns a quantity with wrong units) * ``histogramdd`` (raises a ``DimensionMismatchError``) **other unsupported functions** Functions in ``numpy``'s subpackages such as ``linalg`` are not supported and will either not work with units, or remove units from their inputs. User-defined functions and units ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ For performance and simplicity reasons, code within the Brian core does not use Quantity objects but unitless numpy arrays instead. See :doc:`functions` for details on how to make use user-defined functions with Brian's unit system. brian2-2.5.4/docs_sphinx/developer/variables_indices.rst000066400000000000000000000254601445201106100234060ustar00rootroot00000000000000Variables and indices ===================== Introduction ------------ To be able to generate the proper code out of abstract code statements, the code generation process has to have access to information about the variables (their type, size, etc.) as well as to the indices that should be used for indexing arrays (e.g. a state variable of a `NeuronGroup` will be indexed differently in the `NeuronGroup` state updater and in synaptic propagation code). Most of this information is stored in the `variables` attribute of a `VariableOwner` (this includes `NeuronGroup`, `Synapses`, `PoissonGroup` and everything else that has state variables). The `variables` attribute can be accessed as a (read-only) dictionary, mapping variable names to `Variable` objects storing the information about the respective variable. However, it is not a simple dictionary but an instance of the `Variables` class. Let's have a look at its content for a simple example:: >>> tau = 10*ms >>> G = NeuronGroup(10, 'dv/dt = -v / tau : volt') >>> for name, var in sorted(G.variables.items()): ... print('%s : %s' % (name, var)) # doctest: +SKIP ... N : dt : i : t : t_in_timesteps : v : The state variable ``v`` we specified for the `NeuronGroup` is represented as an `ArrayVariable`, all the other variables were added automatically. There's another array ``i``, the neuronal indices (simply an array of integers from 0 to 9), that is used for string expressions involving neuronal indices. The constant ``N`` represents the total number of neurons. At the first sight it might be surprising that ``t``, the current time of the clock and ``dt``, its timestep, are `ArrayVariable` objects as well. This is because those values can change during a run (for ``t``) or between runs (for ``dt``), and storing them as arrays with a single value (note the ``scalar=True``) is the easiest way to share this value -- all code accessing it only needs a reference to the array and can access its only element. The information stored in the `Variable` objects is used to do various checks on the level of the abstract code, i.e. before any programming language code is generated. Here are some examples of errors that are caught this way:: >>> G.v = 3*ms # G.variables['v'].unit is volt # doctest: +ELLIPSIS +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... DimensionMismatchError: v should be set with a value with units volt, but got 3. ms (unit is second). >>> G.N = 5 # G.variables['N'] is read-only # doctest: +ELLIPSIS +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... TypeError: Variable N is read-only Creating variables ------------------ Each variable that should be accessible as a state variable and/or should be available for use in abstract code has to be created as a `Variable`. For this, first a `Variables` container with a reference to the group has to be created, individual variables can then be added using the various ``add_...`` methods:: self.variables = Variables(self) self.variables.add_array('an_array', unit=volt, size=100) self.variables.add_constant('N', unit=Unit(1), value=self._N, dtype=np.int32) self.variables.create_clock_variables(self.clock) As an additional argument, array variables can be specified with a specific *index* (see `Indices`_ below). References ---------- For each variable, only one `Variable` object exists even if it is used in different contexts. Let's consider the following example:: >>> G = NeuronGroup(5, 'dv/dt = -v / tau : volt', threshold='v > 1', reset='v = 0', ... name='neurons') >>> subG = G[2:] >>> S = Synapses(G, G, on_pre='v+=1*mV', name='synapses') >>> S.connect() All allow an access to the state variable `v` (note the different shapes, these arise from the different indices used, see below):: >>> G.v >>> subG.v >>> S.v In all of these cases, the `Variables` object stores references to the same `ArrayVariable` object:: >>> id(G.variables['v']) # doctest: +SKIP 108610960 >>> id(subG.variables['v']) # doctest: +SKIP 108610960 >>> id(S.variables['v']) # doctest: +SKIP 108610960 Such a reference can be added using `Variables.add_reference`, note that the name used for the reference is not necessarily the same as in the original group, e.g. in the above example ``S.variables`` also stores references to ``v`` under the names ``v_pre`` and ``v_post``. Indices ------- In subgroups and especially in synapses, the transformation of abstract code into executable code is not straightforward because it can involve variables from different contexts. Here is a simple example:: >>> G = NeuronGroup(5, 'dv/dt = -v / tau : volt', threshold='v > 1', reset='v = 0') >>> S = Synapses(G, G, 'w : volt', on_pre='v+=w') The seemingly trivial operation ``v+=w`` involves the variable ``v`` of the `NeuronGroup` and the variable ``w`` of the `Synapses` object which have to be indexed in the appropriate way. Since this statement is executed in the context of ``S``, the variable indices stored there are relevant:: >>> S.variables.indices['w'] '_idx' >>> S.variables.indices['v'] '_postsynaptic_idx' The index ``_idx`` has a special meaning and always refers to the "natural" index for a group (e.g. all neurons for a `NeuronGroup`, all synapses for a `Synapses` object, etc.). All other indices have to refer to existing arrays:: >>> S.variables['_postsynaptic_idx'] # doctest: +SKIP , scalar=False, constant=True, read_only=True)> In this case, ``_postsynaptic_idx`` refers to a dynamic array that stores the postsynaptic targets for each synapse (since it is an array itself, it also has an index. It is defined for each synapse so its index is ``_idx`` -- in fact there is currently no support for an additional level of indirection in Brian: a variable representing an index has to have ``_idx`` as its own index). Using this index information, the following C++ code (slightly simplified) is generated: .. code-block:: c++ for(int _spiking_synapse_idx=0; _spiking_synapse_idx<_num_spiking_synapses; _spiking_synapse_idx++) { const int _idx = _spiking_synapses[_spiking_synapse_idx]; const int _postsynaptic_idx = _ptr_array_synapses__synaptic_post[_idx]; const double w = _ptr_array_synapses_w[_idx]; double v = _ptr_array_neurongroup_v[_postsynaptic_idx]; v += w; _ptr_array_neurongroup_v[_postsynaptic_idx] = v; } In this case, the "natural" index ``_idx`` iterates over all the synapses that received a spike (this is defined in the template) and ``_postsynaptic_idx`` refers to the postsynaptic targets for these synapses. The variables ``w`` and ``v`` are then pulled out of their respective arrays with these indices so that the statement ``v += w;`` does the right thing. Getting and setting state variables ----------------------------------- When a state variable is accessed (e.g. using ``G.v``), the group does not return a reference to the underlying array itself but instead to a `VariableView` object. This is because a state variable can be accessed in different contexts and indexing it with a number/array (e.g. ``obj.v[0]``) or a string (e.g. ``obj.v['i>3']``) can refer to different values in the underlying array depending on whether the object is the `NeuronGroup`, a `Subgroup` or a `Synapses` object. The ``__setitem__`` and ``__getitem__`` methods in `VariableView` delegate to `VariableView.set_item` and `VariableView.get_item` respectively (which can also be called directly under special circumstances). They analyze the arguments (is the index a number, a slice or a string? Is the target value an array or a string expression?) and delegate the actual retrieval/setting of the values to a specific method: * Getting with a numerical (or slice) index (e.g. ``G.v[0]``): `VariableView.get_with_index_array` * Getting with a string index (e.g. ``G.v['i>3']``): `VariableView.get_with_expression` * Setting with a numerical (or slice) index and a numerical target value (e.g. ``G.v[5:] = -70*mV``): `VariableView.set_with_index_array` * Setting with a numerical (or slice) index and a string expression value (e.g. ``G.v[5:] = (-70+i)*mV``): `VariableView.set_with_expression` * Setting with a string index and a string expression value (e.g. ``G.v['i>5'] = (-70+i)*mV``): `VariableView.set_with_expression_conditional` These methods are annotated with the `device_override` decorator and can therefore be implemented in a different way in certain devices. The standalone device, for example, overrides the all the getting functions and the setting with index arrays. Note that for standalone devices, the "setter" methods do not actually set the values but only note them down for later code generation. Additional variables and indices -------------------------------- The variables stored in the ``variables`` attribute of a `VariableOwner` can be used everywhere (e.g. in the state updater, in the threshold, the reset, etc.). Objects that depend on these variables, e.g. the `Thresholder` of a `NeuronGroup` add additional variables, in particular `AuxiliaryVariables` that are automatically added to the abstract code: a threshold condition ``v > 1`` is converted into the statement ``_cond = v > 1``; to specify the meaning of the variable ``_cond`` for the code generation stage (in particular, C++ code generation needs to know the data type) an `AuxiliaryVariable` object is created. In some rare cases, a specific ``variable_indices`` dictionary is provided that overrides the indices for variables stored in the ``variables`` attribute. This is necessary for synapse creation because the meaning of the variables changes in this context: an expression ``v>0`` does not refer to the ``v`` variable of all the *connected* postsynaptic variables, as it does under other circumstances in the context of a `Synapses` object, but to the ``v`` variable of all *possible* targets. brian2-2.5.4/docs_sphinx/index.rst000066400000000000000000000051511445201106100170550ustar00rootroot00000000000000Brian 2 documentation ===================== Brian is a simulator for spiking neural networks. It is written in the Python programming language and is available on almost all platforms. We believe that a simulator should not only save the time of processors, but also the time of scientists. Brian is therefore designed to be easy to learn and use, highly flexible and easily extensible. To get an idea of what writing a simulation in Brian looks like, take a look at :doc:`a simple example `, or run our `interactive demo `_. .. only:: html You can actually edit and run the examples in the browser without having to install Brian, using the Binder service (note: sometimes this service is down or running slowly): .. image:: http://mybinder.org/badge.svg :target: http://mybinder.org/v2/gh/brian-team/brian2-binder/master?filepath=demo.ipynb Once you have a feel for what is involved in using Brian, we recommend you start by following the :doc:`installation instructions `, and in case you are new to the Python programming language, having a look at :doc:`introduction/scripts`. Then, go through the :doc:`tutorials `, and finally read the :doc:`User Guide `. While reading the documentation, you will see the names of certain functions and classes are highlighted links (e.g. `PoissonGroup`). Clicking on these will take you to the "reference documentation". This section is automatically generated from the code, and includes complete and very detailed information, so for new users we recommend sticking to the :doc:`../user/index`. However, there is one feature that may be useful for all users. If you click on, for example, `PoissonGroup`, and scroll down to the bottom, you'll get a list of all the example code that uses `PoissonGroup`. This is available for each class or method, and can be helpful in understanding how a feature works. Finally, if you're having problems, please do let us know at our :doc:`support page `. Please note that all interactions (e.g. via the mailing list or on github) should adhere to our :doc:`Code of Conduct `. Contents: .. toctree:: :maxdepth: 2 :titlesonly: introduction/index resources/tutorials/index user/index advanced/index .. toctree:: :maxdepth: 1 :titlesonly: examples/index Reference documentation developer/index Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` brian2-2.5.4/docs_sphinx/introduction/000077500000000000000000000000001445201106100177335ustar00rootroot00000000000000brian2-2.5.4/docs_sphinx/introduction/brian1_to_2/000077500000000000000000000000001445201106100220325ustar00rootroot00000000000000brian2-2.5.4/docs_sphinx/introduction/brian1_to_2/brian1hears_bridge.rst000066400000000000000000000041171445201106100263020ustar00rootroot00000000000000.. _brian_hears: Brian Hears =========== .. currentmodule:: brian2.hears .. deprecated:: 2.2.2.2 Use the `brian2hears `_ package instead. This module is designed for users of the Brian 1 library "Brian Hears". It allows you to use Brian Hears with Brian 2 with only a few modifications (although it's not compatible with the "standalone" mode of Brian 2). The way it works is by acting as a "bridge" to the version in Brian 1. To make this work, you must have a copy of Brian 1 installed (preferably the latest version), and import Brian Hears using:: from brian2.hears import * Many scripts will run without any changes, but there are a few caveats to be aware of. Mostly, the problems are due to the fact that the units system in Brian 2 is not 100% compatible with the units system of Brian 1. `FilterbankGroup` now follows the rules for `NeuronGroup` in Brian 2, which means some changes may be necessary to match the syntax of Brian 2, for example, the following would work in Brian 1 Hears:: # Leaky integrate-and-fire model with noise and refractoriness eqs = ''' dv/dt = (I-v)/(1*ms)+0.2*xi*(2/(1*ms))**.5 : 1 I : 1 ''' anf = FilterbankGroup(ihc, 'I', eqs, reset=0, threshold=1, refractory=5*ms) However, in Brian 2 Hears you would need to do:: # Leaky integrate-and-fire model with noise and refractoriness eqs = ''' dv/dt = (I-v)/(1*ms)+0.2*xi*(2/(1*ms))**.5 : 1 (unless refractory) I : 1 ''' anf = FilterbankGroup(ihc, 'I', eqs, reset='v=0', threshold='v>1', refractory=5*ms) Slicing sounds no longer works. Previously you could do, e.g. ``sound[:20*ms]`` but with Brian 2 you would need to do ``sound.slice(0*ms, 20*ms)``. In addition, some functions may not work correctly with Brian 2 units. In most circumstances, Brian 2 units can be used interchangeably with Brian 1 units in the bridge, but in some cases it may be necessary to convert units from one format to another, and to do that you can use the functions `convert_unit_b1_to_b2` and `convert_unit_b2_to_b1`. .. _`Brian Hears`: http://brian.readthedocs.org/en/latest/hears.htmlbrian2-2.5.4/docs_sphinx/introduction/brian1_to_2/container.rst000066400000000000000000000057371445201106100245620ustar00rootroot00000000000000Container image for Brian 1 =========================== Brian 1 depends on Python 2.x and other deprecated libraries, so running it on modern systems has become difficult. For convenience, we provide a Docker image that you can use to run existing Brian 1 code. It is based on a Debian image, and provides Brian 1.4.3, as packaged by the `NeuroDebian team `_. To use these images, you either need to have `docker `_, `podman `_ or `singularity `_ installed – the commands below are shown for the ``docker`` command, but you can simply replace them by ``podman`` if necessary. For singularity, the basic workflow is similar but the commands are slightly different, please see the documentation. To pull the container image with singularity, refer to ``docker://briansimulator/brian1.4.3``. Running a graphical interface within the docker container can be complicated, and the details how to make it work depend on the host operating system. We therefore recommend to instead either 1) only use the container image to generate and save the simulation results to disk, and then to create the plots on the host system, or 2) to use the container image to plot files to disk by adding ``plt.savefig(...)`` to the script. The container already sets the matplotlib backend to ``Agg`` by default (by setting the environment variable ``MPLBACKEND``), necessary to avoid errors when no graphical interface is available. To download the image and to rename it to ``brian1`` (for convenience only, the commands below would also work directly with the full name), use: .. code:: shell docker pull docker.io/briansimulator/brian1.4.3 docker tag briansimulator/brian1.4.3 brian1 The following command runs ``myscript.py`` with the container image providing Brian 1 and its dependencies, mapping the current directory to the working directory in the container (this means, the script has access to all files in the current directory and its subdirectories, and can also write files there): .. code:: shell docker run -v "$(pwd):/workdir" brian1 python myscript.py For Windows users using the Command Prompt (``cmd.exe``) instead of the Powershell, the following command will do the same thing: .. code:: shell docker run -v %cd%:/workdir brian1 python myscript.py To run an interactive ipython prompt, use: .. code:: shell docker run -it -v "$(pwd):/workdir" brian1 ipython Depending on your operating system, files written by the container might be owned by the user "root", which can lead to problems later (e.g. you cannot rename/move/delete/overwrite the file on your home system without administrator rights). On Unix-based systems, you can prevent this issue by running scripts with the same user id as the host user: .. code:: shell docker run -u $(id -u):$(id -g) -v "$(pwd):/workdir" brian1 python myscript.py Please report any issues to the `Brian discussion forum `_. brian2-2.5.4/docs_sphinx/introduction/brian1_to_2/index.rst000066400000000000000000000007051445201106100236750ustar00rootroot00000000000000Detailed Brian 1 to Brian 2 conversion notes ============================================ These documents are only relevant for former users of Brian 1. If you do not have any Brian 1 code to convert, go directly to the main :doc:`../../user/index`. .. toctree:: :maxdepth: 1 :titlesonly: container neurongroup synapses inputs monitors networks_and_clocks preferences multicompartmental library brian1hears_bridge brian2-2.5.4/docs_sphinx/introduction/brian1_to_2/inputs.rst000066400000000000000000000251631445201106100241150ustar00rootroot00000000000000Inputs (Brian 1 --> 2 conversion) ================================= .. sidebar:: Brian 2 documentation For the main documentation about adding external stimulation to a network, see the document :doc:`../../user/input`. .. contents:: :local: :depth: 1 Poisson Input ------------- Brian 2 provides the same two groups that Brian 1 provided: `PoissonGroup` and `PoissonInput`. The mechanism for inhomogoneous Poisson processes has changed: instead of providing a Python function of time, you'll now have to provide a string expression that is evaluated at every time step. For most use cases, this should allow a direct translation: +-------------------------------------------------+------------------------------------------+ | Brian 1 | Brian 2 | +=================================================+==========================================+ + .. code:: | .. code:: | + | | + rates = lambda t:(1+cos(2*pi*t*1*Hz))*10*Hz | rates = '(1 + cos(2*pi*t*1*Hz)*10*Hz)' | + group = PoissonGroup(100, rates=rates) | group = PoissonGroup(100, rates=rates) | + | | +-------------------------------------------------+------------------------------------------+ For more complex rate modulations, the expression can refer to :ref:`user_functions` and/or you can replace the `PoissonGroup` by a general `NeuronGroup` with a threshold condition ``rand()=15*ms``. Brian 2 will return ``0`` for ``t<10*ms``, ``1`` for ``10*ms<=t<20*ms``, and ``2`` for ``t>=20*ms``. +-----------------------------------------------------------+----------------------------------------------------+ | Brian 1 | Brian 2 | +===========================================================+====================================================+ | .. code:: | .. code:: | | | | | # same input for all neurons | # same input for all neurons | | eqs = ''' | I = TimedArray(linspace(0*mV, 20*mV, 100), | | dv/dt = (I - v)/tau : volt | dt=10*ms) | | I : volt | eqs = ''' | | ''' | dv/dt = (I(t) - v)/tau : volt | | group = NeuronGroup(1, model=eqs, | ''' | | reset=0*mV, threshold=15*mV) | group = NeuronGroup(1, model=eqs, | | group.I = TimedArray(linspace(0*mV, 20*mV, 100), | reset='v = 0*mV', | | dt=10*ms) | threshold='v > 15*mV') | | | | +-----------------------------------------------------------+----------------------------------------------------+ | .. code:: | .. code:: | | | | | # neuron-specific input | # neuron-specific input | | eqs = ''' | values = (linspace(0*mV, 20*mV, 100)[:, None] * | | dv/dt = (I - v)/tau : volt | linspace(0, 1, 5)) | | I : volt | I = TimedArray(values, dt=10*ms) | | ''' | eqs = ''' | | group = NeuronGroup(5, model=eqs, | dv/dt = (I(t, i) - v)/tau : volt | | reset=0*mV, threshold=15*mV) | ''' | | values = (linspace(0*mV, 20*mV, 100)[:, None] * | group = NeuronGroup(5, model=eqs, | | linspace(0, 1, 5)) | reset='v = 0*mV', | | group.I = TimedArray(values, dt=10*ms) | threshold='v > 15*mV') | | | | +-----------------------------------------------------------+----------------------------------------------------+ brian2-2.5.4/docs_sphinx/introduction/brian1_to_2/library.rst000066400000000000000000000670121445201106100242360ustar00rootroot00000000000000Library models (Brian 1 --> 2 conversion) ========================================= .. contents:: :local: :depth: 1 Neuron models ------------- The neuron models in Brian 1's ``brian.library.IF`` package are nothing more than shorthands for equations. The following table shows how the models from Brian 1 can be converted to explicit equations (and reset statements in the case of the adaptive exponential integrate-and-fire model) for use in Brian 2. The examples include a "current" ``I`` (depending on the model not necessarily in units of Ampère) and could e.g. be used to plot the f-I curve of the neuron. Perfect integrator ~~~~~~~~~~~~~~~~~~ +------------------------------------------------------------------+------------------------------------------------------------------------------------------+ | Brian 1 | Brian 2 | +==================================================================+==========================================================================================+ + .. code:: | .. code:: | + | | + eqs = (perfect_IF(tau=10*ms) + | tau = 10*ms | + Current('I : volt')) | eqs = '''dvm/dt = I/tau : volt | + group = NeuronGroup(N, eqs, | I : volt''' | + threshold='v > -50*mV', | group = NeuronGroup(N, eqs, | + reset='v = -70*mV') | threshold='v > -50*mV', | + | reset='v = -70*mV') | + | | +------------------------------------------------------------------+------------------------------------------------------------------------------------------+ Leaky integrate-and-fire neuron ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +------------------------------------------------------------------+------------------------------------------------------------------------------------------+ | Brian 1 | Brian 2 | +==================================================================+==========================================================================================+ + .. code:: | .. code:: | + | | + eqs = (leaky_IF(tau=10*ms, El=-70*mV) + | tau = 10*ms; El = -70*mV | + Current('I : volt')) | eqs = '''dvm/dt = ((El - vm) + I)/tau : volt | + group = ... # see above | I : volt''' | + | group = ... # see above | + | | +------------------------------------------------------------------+------------------------------------------------------------------------------------------+ Exponential integrate-and-fire neuron ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +------------------------------------------------------------------+------------------------------------------------------------------------------------------+ | Brian 1 | Brian 2 | +==================================================================+==========================================================================================+ + .. code:: | .. code:: | + | | + eqs = (exp_IF(C=1*nF, gL=30*nS, EL=-70*mV, | C = 1*nF; gL = 30*nS; EL = -70*mV; VT = -50*mV; DeltaT = 2*mV | + VT=-50*mV, DeltaT=2*mV) + | eqs = '''dvm/dt = (gL*(EL-vm)+gL*DeltaT*exp((vm-VT)/DeltaT) + I)/C : volt | + Current('I : amp')) | I : amp''' | + group = ... # see above | group = ... # see above | + | | +------------------------------------------------------------------+------------------------------------------------------------------------------------------+ Quadratic integrate-and-fire neuron ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +------------------------------------------------------------------+------------------------------------------------------------------------------------------+ | Brian 1 | Brian 2 | +==================================================================+==========================================================================================+ + .. code:: | .. code:: | + | | + eqs = (quadratic_IF(C=1*nF, a=5*nS/mV, | C = 1*nF; a=5*nS/mV; EL=-70*mV; VT = -50*mV | + EL=-70*mV, VT=-50*mV) + | eqs = '''dvm/dt = (a*(vm-EL)*(vm-VT) + I)/C : volt | + Current('I : amp')) | I : amp''' | + group = ... # see above | group = ... # see above | + | | +------------------------------------------------------------------+------------------------------------------------------------------------------------------+ Izhikevich neuron ~~~~~~~~~~~~~~~~~ +------------------------------------------------------------------+------------------------------------------------------------------------------------------+ | Brian 1 | Brian 2 | +==================================================================+==========================================================================================+ + .. code:: | .. code:: | + | | + eqs = (Izhikevich(a=0.02/ms, b=0.2/ms) + | a = 0.02/ms; b = 0.2/ms | + Current('I : volt/second')) | eqs = '''dvm/dt = (0.04/ms/mV)*vm**2+(5/ms)*vm+140*mV/ms-w + I : volt | + group = ... # see above | dw/dt = a*(b*vm-w) : volt/second | + | I : volt/second''' | + | group = ... # see above | + | | +------------------------------------------------------------------+------------------------------------------------------------------------------------------+ Adaptive exponential integrate-and-fire neuron ("Brette-Gerstner model") ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +------------------------------------------------------------------+------------------------------------------------------------------------------------------+ | Brian 1 | Brian 2 | +==================================================================+==========================================================================================+ + .. code:: | .. code:: | + | | + # AdEx, aEIF, and Brette_Gerstner all refer to the same model | C = 1*nF; gL = 30*nS; EL = -70*mV; VT = -50*mV; DeltaT = 2*mV; tauw = 150*ms; a = 4*nS | + eqs = (aEIF(C=1*nF, gL=30*nS, EL=-70*mV, | eqs = '''dvm/dt = (gL*(EL-vm)+gL*DeltaT*exp((vm-VT)/DeltaT) -w + I)/C : volt | + VT=-50*mV, DeltaT=2*mV, tauw=150*ms, a=4*nS) + | dw/dt=(a*(vm-EL)-w)/tauw : amp | + Current('I:amp')) | I : amp''' | + group = NeuronGroup(N, eqs, | group = NeuronGroup(N, eqs, | + threshold='v > -20*mV', | threshold='vm > -20*mV', | + reset=AdaptiveReset(Vr=-70*mV, b=0.08*nA))| reset='vm=-70*mV; w += 0.08*nA') | + | | +------------------------------------------------------------------+------------------------------------------------------------------------------------------+ Ionic currents -------------- Brian 1's functions for ionic currents, provided in ``brian.library.ionic_currents`` correspond to the following equations (note that the currents follow the convention to use a shifted membrane potential, i.e. the membrane potential at rest is 0mV): +-------------------------------------------------------------------------+----------------------------------------------------------------------------------+ | Brian 1 | Brian 2 | +=========================================================================+==================================================================================+ + .. code:: | .. code:: | + | | + from brian.library.ionic_currents import * | defaultclock.dt = 0.01*ms | + defaultclock.dt = 0.01*ms | gl = 60*nS; El = 10.6*mV | + eqs_leak = leak_current(gl=60*nS, El=10.6*mV, current_name='I_leak') | eqs_leak = Equations('I_leak = gl*(El - vm) : amp') | + | g_K = 7.2*uS; EK = -12*mV | + eqs_K = K_current_HH(gmax=7.2*uS, EK=-12*mV, current_name='I_K') | eqs_K = Equations('''I_K = g_K*n**4*(EK-vm) : amp | + | dn/dt = alphan*(1-n)-betan*n : 1 | + eqs_Na = Na_current_HH(gmax=24*uS, ENa=115*mV, current_name='I_Na') | alphan = .01*(10*mV-vm)/(exp(1-.1*vm/mV)-1)/mV/ms : Hz | + | betan = .125*exp(-.0125*vm/mV)/ms : Hz''') | + eqs = (MembraneEquation(C=200*pF) + | g_Na = 24*uS; ENa = 115*mV | + eqs_leak + eqs_K + eqs+Na + | eqs_Na = Equations('''I_Na = g_Na*m**3*h*(ENa-vm) : amp | + Current('I_inj : amp')) | dm/dt=alpham*(1-m)-betam*m : 1 | + | dh/dt=alphah*(1-h)-betah*h : 1 | + | alpham=.1*(25*mV-vm)/(exp(2.5-.1*vm/mV)-1)/mV/ms : Hz | + | betam=4*exp(-.0556*vm/mV)/ms : Hz | + | alphah=.07*exp(-.05*vm/mV)/ms : Hz | + | betah=1./(1+exp(3.-.1*vm/mV))/ms : Hz''') | + | C = 200*pF | + | eqs = Equations('''dvm/dt = (I_leak + I_K + I_Na + I_inj)/C : volt | + | I_inj : amp''') + eqs_leak + eqs_K + eqs_Na | + | | +-------------------------------------------------------------------------+----------------------------------------------------------------------------------+ Synapses -------- Brian 1's synaptic models, provided in ``brian.library.synpases`` can be converted to the equivalent Brian 2 equations as follows: Current-based synapses ~~~~~~~~~~~~~~~~~~~~~~ +----------------------------------------------------------------------------------+----------------------------------------------------------------------------------+ | Brian 1 | Brian 2 | +==================================================================================+==================================================================================+ + .. code:: | .. code:: | + | | + syn_eqs = exp_current('s', tau=5*ms, current_name='I_syn') | tau = 5*ms | + eqs = (MembraneEquation(C=1*nF) + Current('Im = gl*(El-vm) : amp') + | syn_eqs = Equations('dI_syn/dt = -I_syn/tau : amp') | + syn_eqs) | eqs = (Equations('dvm/dt = (gl*(El - vm) + I_syn)/C : volt') + | + group = NeuronGroup(N, eqs, threshold='vm>-50*mV', reset='vm=-70*mV') | syn_eqs) | + syn = Synapses(source, group, pre='s += 1*nA') | group = NeuronGroup(N, eqs, threshold='vm>-50*mV', reset='vm=-70*mV') | + # ... connect synapses, etc. | syn = Synapses(source, group, on_pre='I_syn += 1*nA') | + | # ... connect synapses, etc. | + | | +----------------------------------------------------------------------------------+----------------------------------------------------------------------------------+ + .. code:: | .. code:: | + | | + syn_eqs = alpha_current('s', tau=2.5*ms, current_name='I_syn') | tau = 2.5*ms | + eqs = ... # remaining code as above | syn_eqs = Equations('''dI_syn/dt = (s - I_syn)/tau : amp | + | ds/dt = -s/tau : amp''') | + | group = NeuronGroup(N, eqs, threshold='vm>-50*mV', reset='vm=-70*mV') | + | syn = Synapses(source, group, on_pre='s += 1*nA') | + | # ... connect synapses, etc. | + | | +----------------------------------------------------------------------------------+----------------------------------------------------------------------------------+ + .. code:: | .. code:: | + | | + syn_eqs = biexp_current('s', tau1=2.5*ms, tau2=10*ms, current_name='I_syn') | tau1 = 2.5*ms; tau2 = 10*ms; invpeak = (tau2 / tau1) ** (tau1 / (tau2 - tau1))| + eqs = ... # remaining code as above | syn_eqs = Equations('''dI_syn/dt = (invpeak*s - I_syn)/tau1 : amp | + | ds/dt = -s/tau2 : amp''') | + | eqs = ... # remaining code as above | + | | +----------------------------------------------------------------------------------+----------------------------------------------------------------------------------+ Conductance-based synapses ~~~~~~~~~~~~~~~~~~~~~~~~~~ +----------------------------------------------------------------------------------+----------------------------------------------------------------------------------+ | Brian 1 | Brian 2 | +==================================================================================+==================================================================================+ + .. code:: | .. code:: | + | | + syn_eqs = exp_conductance('s', tau=5*ms, E=0*mV, conductance_name='g_syn') | tau = 5*ms; E = 0*mV | + eqs = (MembraneEquation(C=1*nF) + Current('Im = gl*(El-vm) : amp') + | syn_eqs = Equations('dg_syn/dt = -g_syn/tau : siemens') | + syn_eqs) | eqs = (Equations('dvm/dt = (gl*(El - vm) + g_syn*(E - vm))/C : volt') + | + group = NeuronGroup(N, eqs, threshold='vm>-50*mV', reset='vm=-70*mV') | syn_eqs) | + syn = Synapses(source, group, pre='s += 10*nS') | group = NeuronGroup(N, eqs, threshold='vm>-50*mV', reset='vm=-70*mV') | + # ... connect synapses, etc. | syn = Synapses(source, group, on_pre='g_syn += 10*nS') | + | # ... connect synapses, etc. | + | | +----------------------------------------------------------------------------------+----------------------------------------------------------------------------------+ + .. code:: | .. code:: | + | | + syn_eqs = alpha_conductance('s', tau=2.5*ms, E=0*mV, conductance_name='g_syn')| tau = 2.5*ms; E = 0*mV | + eqs = ... # remaining code as above | syn_eqs = Equations('''dg_syn/dt = (s - g_syn)/tau : siemens | + | ds/dt = -s/tau : siemens''') | + | group = NeuronGroup(N, eqs, threshold='vm>-50*mV', reset='vm=-70*mV') | + | syn = Synapses(source, group, on_pre='s += 10*nS') | + | # ... connect synapses, etc. | + | | +----------------------------------------------------------------------------------+----------------------------------------------------------------------------------+ + .. code:: | .. code:: | + | | + syn_eqs = biexp_conductance('s', tau1=2.5*ms, tau2=10*ms, E=0*mV, | tau1 = 2.5*ms; tau2 = 10*ms; E = 0*mV | + conductance_name='g_syn') | invpeak = (tau2 / tau1) ** (tau1 / (tau2 - tau1)) | + eqs = ... # remaining code as above | syn_eqs = Equations('''dg_syn/dt = (invpeak*s - g_syn)/tau1 : siemens | + | ds/dt = -s/tau2 : siemens''') | + | eqs = ... # remaining code as above | + | | +----------------------------------------------------------------------------------+----------------------------------------------------------------------------------+ brian2-2.5.4/docs_sphinx/introduction/brian1_to_2/monitors.rst000066400000000000000000000273161445201106100244470ustar00rootroot00000000000000Monitors (Brian 1 --> 2 conversion) =================================== .. sidebar:: Brian 2 documentation For the main documentation about recording network activity, see the document :doc:`../../user/recording`. .. contents:: :local: :depth: 1 Monitoring spiking activity --------------------------- The main class to record spiking activity is `SpikeMonitor` which is created in the same way as in Brian 1. However, the internal storage and retrieval of spikes is different. In Brian 1, spikes were stored as a list of pairs ``(i, t)``, the index and time of each spike. In Brian 2, spikes are stored as two arrays ``i`` and ``t``, storing the indices and times. You can access these arrays as attributes of the monitor, there's also a convenience attribute ``it`` that returns both at the same time. The following table shows how the spike indices and times can be retrieved in various forms in Brian 1 and Brian 2: +-----------------------------------------------+-------------------------------------------+ | Brian 1 | Brian 2 | +===============================================+===========================================+ + .. code:: | .. code:: | + | | + mon = SpikeMonitor(group) | mon = SpikeMonitor(group) | + #... do the run | #... do the run | + list_of_pairs = mon.spikes | list_of_pairs = zip(*mon.it) | + index_list, time_list = zip(*list_of_pairs) | index_list = list(mon.i) | + index_array = array(index_list) | time_list = list(mon.t) | + time_array = array(time_list) | index_array, time_array = mon.i, mon.t | + # time_array is unitless in Brian 1 | # time_array has units in Brian 2 | +-----------------------------------------------+-------------------------------------------+ You can also access the spike times for individual neurons. In Brian 1, you could directly index the monitor which is no longer allowed in Brian 2. Instead, ask for a dictionary of spike times and index the returned dictionary: +-----------------------------------------------+-----------------------------------------------+ | Brian 1 | Brian 2 | +===============================================+===============================================+ + .. code:: | .. code:: | + | | + # dictionary of spike times for each neuron:| # dictionary of spike times for each neuron:| + spike_dict = mon.spiketimes | spike_dict = mon.spike_trains() | + # all spikes for neuron 3: | # all spikes for neuron 3: | + spikes_3 = spike_dict[3] # (no units) | spikes_3 = spike_dict[3] # with units | + spikes_3 = mon[3] # alternative (no units) | | + | | +-----------------------------------------------+-----------------------------------------------+ In Brian 2, `SpikeMonitor` also provides the functionality of the Brian 1 classes ``SpikeCounter`` and ``PopulationSpikeCounter``. If you are only interested in the counts and not in the individual spike events, use ``record=False`` to save the memory of storing them: +-----------------------------------------------+-----------------------------------------------+ | Brian 1 | Brian 2 | +===============================================+===============================================+ + .. code:: | .. code:: | + | | + counter = SpikeCounter(group) | counter = SpikeMonitor(group, record=False) | + pop_counter = PopulationSpikeCounter(group) | | + #... do the run | #... do the run | + # Number of spikes for neuron 3: | # Number of spikes for neuron 3 | + count_3 = counter[3] | count_3 = counter.count[3] | + # Total number of spikes: | # Total number of spikes: | + total_spikes = pop_counter.nspikes | total_spikes = counter.num_spikes | + | | +-----------------------------------------------+-----------------------------------------------+ Currently Brian 2 provides no functionality to calculate statistics such as correlations or histograms online, there is no equivalent to the following classes that existed in Brian 1: ``AutoCorrelogram``, ``CoincidenceCounter``, ``CoincidenceMatrixCounter``, ``ISIHistogramMonitor``, ``VanRossumMetric``. You will therefore have to be calculate the corresponding statistiacs manually after the simulation based on the information stored in the `SpikeMonitor`. If you use the default :ref:`runtime`, you can also create a new Python class that calculates the statistic online (see this `example from a Brian 2 tutorial `_). Monitoring variables -------------------- Single variables are recorded with a `StateMonitor` in the same way as in Brian 1, but the times and variable values are accessed differently: +---------------------------------------+--------------------------------------+ | Brian 1 | Brian 2 | +=======================================+======================================+ + .. code:: | .. code:: | + | | + mon = StateMonitor(group, 'v', | mon = StateMonitor(group, 'v', | + record=True) | record=True) | + # ... do the run | # ... do the run | + # plot the trace of neuron 3: | # plot the trace of neuron 3: | + plot(mon.times/ms, mon[3]/mV) | plot(mon.t/ms, mon[3].v/mV) | + # plot the traces of all neurons: | # plot the traces of all neurons: | + plot(mon.times/ms, mon.values.T/mV) | plot(mon.t/ms, mon.v.T/mV) | + | | +---------------------------------------+--------------------------------------+ Further differences: * `StateMonitor` now records in the ``'start'`` scheduling slot by default. This leads to a more intuitive correspondence between the recorded times and the values: in Brian 1 (where `StateMonitor` recorded in the ``'end'`` slot) the recorded value at 0ms was not the initial value of the variable but the value after integrating it for a single time step. The disadvantage of this new default is that the very last value at the end of the last time step of a simulation is not recorded anymore. However, this value can be manually added to the monitor by calling `StateMonitor.record_single_timestep`. * To not record every time step, use the ``dt`` argument (as for all other classes) instead of specifying a number of ``timesteps``. * Using ``record=False`` does no longer provide mean and variance of the recorded variable. In contrast to Brian 1, `StateMonitor` can now record multiple variables and therefore replaces Brian 1's ``MultiStateMonitor``: +-----------------------------------------------------------+------------------------------------------------------+ | Brian 1 | Brian 2 | +===========================================================+======================================================+ + .. code:: | .. code:: | + | | + mon = MultiStateMonitor(group, ['v', 'w'], | mon = StateMonitor(group, ['v', 'w'], | + record=True) | record=True) | + # ... do the run | # ... do the run | + # plot the traces of v and w for neuron 3: | # plot the traces of v and w for neuron 3: | + plot(mon['v'].times/ms, mon['v'][3]/mV) | plot(mon.t/ms, mon[3].v/mV) | + plot(mon['w'].times/ms, mon['w'][3]/mV) | plot(mon.t/ms, mon[3].w/mV) | + | | +-----------------------------------------------------------+------------------------------------------------------+ To record variable values at the times of spikes, Brian 2 no longer provides a separate class as Brian 1 did (``StateSpikeMonitor``). Instead, you can use `SpikeMonitor` to record additional variables (in addition to the neuron index and the spike time): +-----------------------------------------------------------+------------------------------------------------------+ | Brian 1 | Brian 2 | +===========================================================+======================================================+ + .. code:: | .. code:: | + | | + # We assume that "group" has a varying threshold | # We assume that "group" has a varying threshold | + mon = StateSpikeMonitor(group, 'v') | mon = SpikeMonitor(group, variables='v') | + # ... do the run | # ... do the run | + # plot the mean v at spike time for each neuron | # plot the mean v at spike time for each neuron | + mean_values = [mean(mon.values('v', idx)) | values = mon.values('v') | + for idx in range(len(group))] | mean_values = [mean(values[idx]) | + | for idx in range(len(group))] | + plot(mean_values/mV, 'o') | plot(mean_values/mV, 'o') | + | | +-----------------------------------------------------------+------------------------------------------------------+ Note that there is no equivalent to ``StateHistogramMonitor``, you will have to calculate the histogram from the recorded values or write your own custom monitor class. brian2-2.5.4/docs_sphinx/introduction/brian1_to_2/multicompartmental.rst000066400000000000000000000055561445201106100265200ustar00rootroot00000000000000Multicompartmental models (Brian 1 --> 2 conversion) ==================================================== .. sidebar:: Brian 2 documentation Support for multicompartmental models is now an integral part of Brian 2 (an early version of it was included as an experimental module in Brian 1). See the document :doc:`../../user/multicompartmental`. Brian 1 offered support for simple multi-compartmental models in the ``compartments`` module. This module allowed you to combine the equations for several compartments into a single `Equations` object. This is only a suitable solution for simple morphologies (e.g. "ball-and-stick" models) but has the advantage over using `SpatialNeuron` that you can have several of such neurons in a `NeuronGroup`. If you already have a definition of a model using Brian 1's ``compartments`` module, then you can simply print out the equations and use them directly in Brian 2. For simple models, writing the equations without that help is rather straightforward anyway: +---------------------------------------------------+---------------------------------------------------+ | Brian 1 | Brian 2 | +===================================================+===================================================+ | .. code:: | .. code:: | | | | | V0 = 10*mV | V0 = 10*mV | | C = 200*pF | C = 200*pF | | Ra = 150*kohm | Ra = 150*kohm | | R = 50*Mohm | R = 50*Mohm | | soma_eqs = (MembraneEquation(C) + | neuron_eqs = ''' | | IonicCurrent('I=(vm-V0)/R : amp')) | dvm_soma/dt = (I_soma + I_soma_dend)/C : volt | | dend_eqs = MembraneEquation(C) | I_soma = (V0 - vm_soma)/R : amp | | neuron_eqs = Compartments({'soma': soma_eqs, | I_soma_dend = (vm_dend - vm_soma)/Ra : amp | | 'dend': dend_eqs}) | dvm_dend/dt = -I_soma_dend/C : volt''' | | | | | neuron = NeuronGroup(N, neuron_eqs) | neuron = NeuronGroup(N, neuron_eqs) | | | | +---------------------------------------------------+---------------------------------------------------+ brian2-2.5.4/docs_sphinx/introduction/brian1_to_2/networks_and_clocks.rst000066400000000000000000000274731445201106100266350ustar00rootroot00000000000000Networks and clocks (Brian 1 --> 2 conversion) ============================================== .. sidebar:: Brian 2 documentation For the main documentation about running simulations, controling the simulation timestep, etc., see the document :doc:`../../user/running`. .. contents:: :local: :depth: 1 Clocks and timesteps -------------------- Brian's system of handling clocks has substantially changed. For details about the new system in place see :ref:`time_steps`. The main differences to Brian 1 are: * There is no more "clock guessing" -- objects either use the `defaultclock` or a ``dt``/``clock`` value that was explicitly specified during their construction. * In Brian 2, the time step is allowed to change after the creation of an object and between runs -- the relevant value is the value in place at the point of the `run` call. * It is rarely necessary to create an explicit `Clock` object, most of the time you should use the `defaultclock` or provide a ``dt`` argument during the construction of the object. * There's only one `Clock` class, the (deprecated) ``FloatClock``, ``RegularClock``, etc. classes that Brian 1 provided no longer exist. * It is no longer possible to (re-)set the time of a clock explicitly, there is no direct equivalent of ``Clock.reinit`` and ``reinit_default_clock``. To start a completely new simulation after you have finished a previous one, either create a new `Network` or use the `start_scope` mechanism. To "rewind" a simulation to a previous point, use the new `store`/`restore` mechanism. For more details, see below and :doc:`../../user/running`. Networks -------- Both Brian 1 and Brian 2 offer two ways to run a simulation: either by explicitly creating a `Network` object, or by using a `MagicNetwork`, i.e. a simple `run` statement. Explicit network ~~~~~~~~~~~~~~~~ The mechanism to create explicit `Network` objects has not changed significantly from Brian 1 to Brian 2. However, creating a new `Network` will now also automatically reset the clock back to 0s, and stricter checks no longer allow the inclusion of the same object in multiple networks. +------------------------------+------------------------------+ + Brian 1 | Brian 2 | +==============================+==============================+ | .. code:: | .. code:: | | | | | group = ... | group = ... | | mon = ... | mon = ... | | net = Network(group, mon) | net = Network(group, mon) | | net.run(1*ms) | net.run(1*ms) | | | | | reinit() | # new network starts at 0s| | group = ... | group = ... | | mon = ... | mon = ... | | net = Network(group, mon) | net = Network(group, mon) | | net.run(1*ms) | net.run(1*ms) | | | | +------------------------------+------------------------------+ "Magic" network ~~~~~~~~~~~~~~~ For most simple, "flat", scripts (see e.g. the :doc:`../../examples/index`), the `run` statement in Brian 2 automatically collects all the Brian objects (`NeuronGroup`, etc.) into a "magic" network in the same way as Brian 1 did. The logic behind this collection has changed, though, with important consequences for more complex simulation scripts: in Brian 1, the magic network includes all Brian objects that have been *created* in the same execution frame as the `run` call. Objects that are created in other functions could be added using ``magic_return`` and ``magic_register``. In Brian 2, the magic network contains all Brian objects that are *visible* in the same execution frame as the `run` call. The advantage of the new system is that it is clearer what will be included in the network and there is no danger of including previously created, but no longer needed, objects in a simulation. E.g. in the following example, a common mistake in Brian 1 was to not include the `clear()`, which meant that each run not only simulated the current objects, but also all objects from previous loop iterations. Also, without the ``reinit_default_clock()``, each run would start at the end time of the previous run. In Brian 2, this loop does not need any explicit clearing up, each `run` will only simulate the object that it "sees" (``group1``, ``group2``, ``syn``, and ``mon``) and start each simulation at 0s: +--------------------------------------------+--------------------------------------------+ | Brian 1 | Brian 2 | +============================================+============================================+ | .. code:: | .. code:: | | | | | for r in range(100): | for r in range(100): | | reinit_default_clock() | | | clear() | | | group1 = NeuronGroup(...) | group1 = NeuronGroup(...) | | group2 = NeuronGroup(...) | group2 = NeuronGroup(...) | | syn = Synapses(group1, group2, ...)| syn = Synapses(group1, group2, ...)| | mon = SpikeMonitor(group2) | mon = SpikeMonitor(group2) | | run(1*second) | run(1*second) | | | | +--------------------------------------------+--------------------------------------------+ There is no replacement for the ``magic_return`` and ``magic_register`` functions. If the returned object is stored in a variable at the level of the `run` call, then it is no longer necessary to use ``magic_return``, as the returned object is "visible" at the level of the `run` call: +-----------------------------------------------+-------------------------------------------------+ | Brian 1 | Brian 2 | +===============================================+=================================================+ | .. code:: | .. code:: | | | | | @magic_return | | | def f(): | def f(): | | return PoissonGroup(100, rates=100*Hz)| return PoissonGroup(100, rates=100*Hz) | | | | | pg = f() # needs magic_return | pg = f() # is "visible" and will be included| | mon = SpikeMonitor(pg) | mon = SpikeMonitor(pg) | | run(100*ms) | run(100*ms) | | | | +-----------------------------------------------+-------------------------------------------------+ The general recommendation is however: if your script is complex (multiple functions/files/classes) and you are not sure whether some objects will be included in the magic network, use an explicit `Network` object. Note that one consequence of the "is visible" approach is that objects stored in containers (lists, dictionaries, ...) will not be automatically included in Brian 2. Use an explicit `Network` object to get around this restriction: +----------------------------------------+----------------------------------------+ | Brian 1 | Brian 2 | +========================================+========================================+ | .. code:: | .. code:: | | | | | groups = {'exc': NeuronGroup(...), | groups = {'exc': NeuronGroup(...), | | 'inh': NeuronGroup(...)} | 'inh': NeuronGroup(...)} | | ... | ... | | | net = Network(groups) | | run(5*ms) | net.run(5*ms) | | | | +----------------------------------------+----------------------------------------+ External constants ~~~~~~~~~~~~~~~~~~ In Brian 2, external constants are taken from the surrounding namespace at the point of the `run` call and not when the object is defined (for other ways to define the namespace, see :ref:`external-variables`). This allows to easily change external constants between runs, in contrast to Brian 1 where the whether this worked or not depended on details of the model (e.g. whether linear integration was used): +----------------------------------------------------------+-----------------------------------------------------------+ | Brian 1 | Brian 2 | +==========================================================+===========================================================+ | .. code:: | .. code:: | | | | | tau = 10*ms | tau = 10*ms | | # to be sure that changes between runs are taken into | | | # account, define "I" as a neuronal parameter | # The value for I will be updated at each run | | group = NeuronGroup(10, '''dv/dt = (-v + I) / tau : 1 | group = NeuronGroup(10, 'dv/dt = (-v + I) / tau : 1') | | I : 1''') | | | group.v = linspace(0, 1, 10) | group.v = linspace(0, 1, 10) | | group.I = 0.0 | I = 0.0 | | mon = StateMonitor(group, 'v', record=True) | mon = StateMonitor(group, 'v', record=True) | | run(5*ms) | run(5*ms) | | group.I = 0.5 | I = 0.5 | | run(5*ms) | run(5*ms) | | group.I = 0.0 | I = 0.0 | | run(5*ms) | run(5*ms) | | | | +----------------------------------------------------------+-----------------------------------------------------------+ brian2-2.5.4/docs_sphinx/introduction/brian1_to_2/neurongroup.rst000066400000000000000000000527051445201106100251600ustar00rootroot00000000000000Neural models (Brian 1 --> 2 conversion) ======================================== .. sidebar:: Brian 2 documentation For the main documentation about defining neural models, see the document :doc:`../../user/models`. .. contents:: :local: :depth: 1 The syntax for specifying neuron models in a `NeuronGroup` changed in several details. In general, a string-based syntax (that was already optional in Brian 1) consistently replaces the use of classes (e.g. ``VariableThreshold``) or guessing (e.g. which variable does ``threshold=50*mV`` check). Threshold and Reset ------------------- String-based thresholds are now the only possible option and replace all the methods of defining threshold/reset in Brian 1: +----------------------------------------------------------------------------+----------------------------------------------------------------------------+ | Brian 1 | Brian 2 | +============================================================================+============================================================================+ + .. code:: | .. code:: | + | | + group = NeuronGroup(N, 'dv/dt = -v / tau : volt', | group = NeuronGroup(N, 'dv/dt = -v / tau : volt', | + threshold=-50*mV, | threshold='v > -50*mV', | + reset=-70*mV) | reset='v = -70*mV') | + | | +----------------------------------------------------------------------------+----------------------------------------------------------------------------+ + .. code:: | .. code:: | + | | + group = NeuronGroup(N, 'dv/dt = -v / tau : volt', | group = NeuronGroup(N, 'dv/dt = -v / tau : volt', | + threshold=Threshold(-50*mV, state='v'), | threshold='v > -50*mV', | + reset=Reset(-70*mV, state='w')) | reset='v = -70*mV') | + | | +----------------------------------------------------------------------------+----------------------------------------------------------------------------+ + .. code:: | .. code:: | + | | + group = NeuronGroup(N, '''dv/dt = -v / tau : volt | group = NeuronGroup(N, '''dv/dt = -v / tau : volt | + dvt/dt = -vt / tau : volt | dvt/dt = -vt / tau : volt | + vr : volt''', | vr : volt''', | + threshold=VariableThreshold(state='v', | threshold='v > vt', | + threshold_state='vt'), | reset='v = vr') | + reset=VariableThreshold(state='v', | | + resetvaluestate='vr')) | | + | | +----------------------------------------------------------------------------+----------------------------------------------------------------------------+ + .. code:: | .. code:: | + | | + group = NeuronGroup(N, 'rate : Hz', | group = NeuronGroup(N, 'rate : Hz', | + threshold=PoissonThreshold(state='rate')) | threshold='rand() -50*mV) & (rand(N) > 0.5) | threshold='v > -50*mV and rand() > 0.5', | + | reset='v = -70*mV') | + group = NeuronGroup(N, 'dv/dt = -v / tau : volt', | | + threshold=SimpleFunThreshold(mythreshold, | | + state='v'), | | + reset=-70*mV) | | + | | +------------------------------------------------------------------+-----------------------------------------------------------------+ For more complicated cases, you can use the general mechanism for :ref:`user_functions` that Brian 2 provides. The only caveat is that you'd have to provide an implementation of the function in the code generation target language which is by default C++ or Cython. However, in the default :ref:`runtime` mode, you can chose different code generation targets for different parts of your simulation. You can thus switch the code generation target for the threshold/reset mechanism to ``numpy`` while leaving the default target for the rest of the simulation in place. The details of this process and the correct definition of the functions (e.g. ``global_reset`` needs a "dummy" return value) are somewhat cumbersome at the moment and we plan to make them more straightforward in the future. Also note that if you use this kind of mechanism extensively, you'll lose all the performance advantage that Brian 2's code generation mechanism provides (in addition to not being able to use :ref:`cpp_standalone` mode at all). +-------------------------------------------------------------------------+-----------------------------------------------------------------+ | Brian 1 | Brian 2 | +=========================================================================+=================================================================+ + .. code:: | .. code:: | + | | + def single_threshold(v): | @check_units(v=volt, result=bool) | + # Only let a single neuron spike | def single_threshold(v): | + crossed_threshold = np.nonzero(v > -50*mV)[0] | pass # ... (identical to Brian 1) | + should_spike = np.zeros(len(P), dtype=np.bool) | | + if len(crossed_threshold): | @check_units(spikes=1, result=1) | + choose = np.random.randint(len(crossed_threshold)) | def global_reset(spikes): | + should_spike[crossed_threshold[choose]] = True | # Reset everything | + return should_spike | if len(spikes): | + | neurons.v_[:] = -0.070 | + def global_reset(P, spikes): | | + # Reset everything | neurons = NeuronGroup(N, 'dv/dt = -v / tau : volt', | + if len(spikes): | threshold='single_threshold(v)', | + P.v_[:] = -70*mV | reset='dummy = global_reset(i)') | + | # Set the code generation target for threshold/reset only: | + neurons = NeuronGroup(N, 'dv/dt = -v / tau : volt', | neuron.thresholder['spike'].codeobj_class = NumpyCodeObject | + threshold=SimpleFunThreshold(single_threshold, | neuron.resetter['spike'].codeobj_class = NumpyCodeObject | + state='v'), | | + reset=global_reset) | | + | | +-------------------------------------------------------------------------+-----------------------------------------------------------------+ For an example how to translate ``EmpiricalThreshold``, see the section on "Refractoriness" below. Refractoriness -------------- For a detailed description of Brian 2's refractoriness mechanism see :doc:`../../user/refractoriness`. In Brian 1, refractoriness was tightly linked with the reset mechanism and some combinations of refractoriness and reset were not allowed. The standard refractory mechanism had two effects during the refractoriness: it prevented the refractory cell from spiking and it clamped a state variable (normally the membrane potential of the cell). In Brian 2, refractoriness is independent of reset and the two effects are specified separately: the ``refractory`` keyword specifies the time (or an expression evaluating to a time) during which the cell does not spike, and the ``(unless refractory)`` flag marks one or more variables to be clamped during the refractory period. To correctly translate the standard refractory mechanism from Brian 1, you'll therefore need to specify both: +---------------------------------------------------------+-----------------------------------------------------------------------------+ | Brian 1 | Brian 2 | +=========================================================+=============================================================================+ + .. code:: | .. code:: | + | | + group = NeuronGroup(N, 'dv/dt = (I - v)/tau : volt', | group = NeuronGroup(N, 'dv/dt = (I - v)/tau : volt (unless refractory)', | + threshold=-50*mV, | threshold='v > -50*mV', | + reset=-70*mV, | reset='v = -70*mV', | + refractory=3*ms) | refractory=3*ms) | + | | +---------------------------------------------------------+-----------------------------------------------------------------------------+ More complex refractoriness mechanisms based on ``SimpleCustomRefractoriness`` and ``CustomRefractoriness`` can be translatated using string expressions or user-defined functions, see the remarks in the preceding section on "Threshold and Reset". Brian 2 no longer has an equivalent to the ``EmpiricalThreshold`` class (which detects at the first threshold crossing but ignores all following threshold crossings for a certain time after that). However, the standard refractoriness mechanism can be used to implement the same behaviour, since it does not reset/clamp any value if not explicitly asked for it (which would be fatal for Hodgkin-Huxley type models): +----------------------------------------------------------------------+----------------------------------------------------------------------+ | Brian 1 | Brian 2 | +======================================================================+======================================================================+ + .. code:: | .. code:: | + | | + group = NeuronGroup(N,''' | group = NeuronGroup(N,''' | + dv/dt = (I_L - I_Na - I_K + I)/Cm : volt | dv/dt = (I_L - I_Na - I_K + I)/Cm : volt | + ...''', | ...''', | + threshold=EmpiricalThreshold(threshold=20*mV, | threshold='v > -20*mV', | + refractory=1*ms, | refractory=1*ms) | + state='v')) | | + | | +----------------------------------------------------------------------+----------------------------------------------------------------------+ Subgroups --------- The class `NeuronGroup` in Brian 2 does no longer provide a ``subgroup`` method, the only way to construct subgroups is therefore the slicing syntax (that works in the same way as in Brian 1): +-------------------------------------+-----------------------------------+ | Brian 1 | Brian 2 | +=====================================+===================================+ + .. code:: | .. code:: | + | | + group = NeuronGroup(4000, ...) | group = NeuronGroup(4000, ...) | + group_exc = group.subgroup(3200) | group_exc = group[:3200] | + group_inh = group.subgroup(800) | group_inh = group[3200:] | + | | +-------------------------------------+-----------------------------------+ Linked Variables ---------------- For a description of Brian 2's mechanism to link variables between groups, see :ref:`linked_variables`. Linked variables need to be explicitly annotated with the ``(linked)`` flag in Brian 2: +----------------------------------------------------------+----------------------------------------------------------+ | Brian 1 | Brian 2 | +==========================================================+==========================================================+ + .. code:: | .. code:: | + | | + group1 = NeuronGroup(N, | group1 = NeuronGroup(N, | + 'dv/dt = -v / tau : volt') | 'dv/dt = -v / tau : volt') | + group2 = NeuronGroup(N, | group2 = NeuronGroup(N, | + '''dv/dt = (-v + w) / tau : volt | '''dv/dt = (-v + w) / tau : volt | + w : volt''') | w : volt (linked)''') | + group2.w = linked_var(group1, 'v') | group2.w = linked_var(group1, 'v') | + | | +----------------------------------------------------------+----------------------------------------------------------+brian2-2.5.4/docs_sphinx/introduction/brian1_to_2/preferences.rst000066400000000000000000000040371445201106100250710ustar00rootroot00000000000000Preferences (Brian 1 --> 2 conversion) ======================================== .. sidebar:: Brian 2 documentation For the main documentation about preferences, see the document :doc:`../../advanced/preferences`. In Brian 1, preferences were set either with the function ``set_global_preferences`` or by creating a module somewhere on the Python path called ``brian_global_config.py``. Setting preferences ------------------- The function ``set_global_preferences`` no longer exists in Brian 2. Instead, importing from ``brian2`` gives you a variable `prefs` that can be used to set preferences. For example, in Brian 1 you would write:: set_global_preferences(weavecompiler='gcc') In Brian 2 you would write:: prefs.codegen.cpp.compiler = 'gcc' Configuration file ------------------ The module ``brian_global_config.py`` is not used by Brian 2, instead we search for configuration files in the current directory, user directory or installation directory. In Brian you would have a configuration file that looks like this:: from brian.globalprefs import * set_global_preferences(weavecompiler='gcc') In Brian 2 you would have a file like this:: codegen.cpp.compiler = 'gcc' Preference name changes ----------------------- * ``defaultclock``: removed because it led to unclear behaviour of scripts. * ``useweave_linear_diffeq``: removed because it was no longer relevant. * ``useweave``: now replaced by `codegen.target` (but note that weave is no longer supported in Brian 2, use Cython instead). * ``weavecompiler``: now replaced by `codegen.cpp.compiler`. * ``gcc_options``: now replaced by `codegen.cpp.extra_compile_args_gcc`. * ``openmp``: now replaced by `devices.cpp_standalone.openmp_threads`. * ``usecodegen*``: removed because it was no longer relevant. * ``usenewpropagate``: removed because it was no longer relevant. * ``usecstdp``: removed because it was no longer relevant. * ``brianhears_usegpu``: removed because Brian Hears doesn't exist in Brian 2. * ``magic_useframes``: removed because it was no longer relevant. brian2-2.5.4/docs_sphinx/introduction/brian1_to_2/synapses.rst000066400000000000000000000721171445201106100244410ustar00rootroot00000000000000Synapses (Brian 1 --> 2 conversion) =================================== .. sidebar:: Brian 2 documentation For the main documentation about defining and creating synapses, see the document :doc:`../../user/synapses`. .. contents:: :local: :depth: 1 Converting Brian 1's ``Connection`` class ----------------------------------------- In Brian 2, the `Synapses` class is the only class to model synaptic connections, you will therefore have to convert all uses of Brian 1's ``Connection`` class. The ``Connection`` class increases a post-synaptic variable by a certain amount (the "synaptic weight") each time a pre-synaptic spike arrives. This has to be explicitly specified when using the `Synapses` class, the equivalent to the basic ``Connection`` usage is: +----------------------------------------------+---------------------------------------------------+ | Brian 1 | Brian 2 | +==============================================+===================================================+ + .. code:: | .. code:: | + | | + conn = Connection(source, target, 'ge') | conn = Synapses(source, target, 'w : siemens', | + | on_pre='ge += w') | + | | +----------------------------------------------+---------------------------------------------------+ Note that he variable ``w``, which stores the synaptic weight, has to have the same units as the post-synaptic variable (in this case: ``ge``) that it increases. Creating synapses and setting weights ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ With the ``Connection`` class, creating a synapse and setting its weight is a single process whereas with the `Synapses` class those two steps are separate. There is no direct equivalent to the convenience functions ``connect_full``, ``connect_random`` and ``connect_one_to_one``, but you can easily implement the same functionality with the general mechanism of `Synapses.connect`: +----------------------------------------------+---------------------------------------------------+ | Brian 1 | Brian 2 | +==============================================+===================================================+ + .. code:: | .. code:: | + | | + conn1 = Connection(source, target, 'ge') | conn1 = Synapses(source, target, 'w: siemens', | + conn1[3, 5] = 3*nS | on_pre='ge += w') | + | conn1.connect(i=3, j=5) | + | conn1.w[3, 5] = 3*nS # (or conn1.w = 3*nS) | + | | +----------------------------------------------+---------------------------------------------------+ + .. code:: | .. code:: | + | | + conn2 = Connection(source, target, 'ge') | conn2 = ... # see above | + conn2.connect_full(source, target, 5*nS) | conn2.connect() | + | conn2.w = 5*nS | + | | +----------------------------------------------+---------------------------------------------------+ + .. code:: | .. code:: | + | | + conn3 = Connection(source, target, 'ge') | conn3 = ... # see above | + conn3.connect_random(source, target, | conn3.connect(p=0.02) | + sparseness=0.02, | conn3.w = 2*nS | + weight=2*ns) | | + | | +----------------------------------------------+---------------------------------------------------+ + .. code:: | .. code:: | + | | + conn4 = Connection(source, target, 'ge') | conn4 = ... # see above | + conn4.connect_one_to_one(source, target, | conn4.connect(j='i') | + weight=4*nS) | conn4.w = 4*nS | + | | +----------------------------------------------+---------------------------------------------------+ + .. code:: | .. code:: | + | | + conn5 = IdentityConnection(source, target,| conn5 = Synapses(source, target, | + weight=3*nS) | 'w : siemens (shared)') | + | conn5.w = 3*nS | + | | +----------------------------------------------+---------------------------------------------------+ Weight matrices ~~~~~~~~~~~~~~~ Brian 2's `Synapses` class does not support setting the weights of a neuron with a weight matrix. However, `Synapses.connect` creates the synapses in a predictable order (first all synapses for the first pre-synaptic cell, then all synapses for the second pre-synaptic cell, etc.), so a reshaped "flat" weight matrix can be used: +----------------------------------------------+---------------------------------------------------+ | Brian 1 | Brian 2 | +==============================================+===================================================+ + .. code:: | .. code:: | + | | + # len(source) == 20, len(target) == 30 | # len(source) == 20, len(target) == 30 | + conn6 = Connection(source, target, 'ge') | conn6 = Synapses(source, target, 'w: siemens', | + W = rand(20, 30)*nS | on_pre='ge += w') | + conn6.connect(source, target, weight=W) | W = rand(20, 30)*nS | + | conn6.connect() | + | conn6.w = W.flatten() | + | | +----------------------------------------------+---------------------------------------------------+ However note that if your weight matrix can be described mathematically (e.g. random as in the example above), then you should not create a weight matrix in the first place but use Brian 2's mechanism to set variables based on mathematical expressions (in the above case: ``conn5.w = 'rand()'``). Especially for big connection matrices this will have better performance, since it will be executed in generated code. You should only resort to explicit weight matrices when there is no alternative (e.g. to load weights from previous simulations). In Brian 1, you can restrict the functions ``connect``, ``connect_random``, etc. to subgroups. Again, there is no direct equivalent to this in Brian 2, but the general string syntax allows you to make connections conditional on logical statements that refer to pre-/post-synaptic indices and can therefore also used to restrict the connection to a subgroup of cells. When you set the synaptic weights, you *can* however use subgroups to restrict the subset of weights you want to set. +--------------------------------------------------------+---------------------------------------------------+ | Brian 1 | Brian 2 | +========================================================+===================================================+ + .. code:: | .. code:: | + | | + conn7 = Connection(source, target, 'ge') | conn7 = Synapses(source, target, 'w: siemens', | + conn7.connect_full(source[:5], target[5:10], 5*nS) | on_pre='ge += w') | + | conn7.connect('i < 5 and j >=5 and j <10') | + | # Alternative (more efficient): | + | # conn7.connect(j='k in range(5, 10) if i < 5')| + | conn7.w[source[:5], target[5:10]] = 5*nS | + | | +--------------------------------------------------------+---------------------------------------------------+ Connections defined by functions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Brian 1 allowed you to pass in a function as the value for the weight argument in a ``connect`` call (and also for the sparseness argument in ``connect_random``). You should be able to replace such use cases by the the general, string-expression based method: +------------------------------------------------------------------+---------------------------------------------------+ | Brian 1 | Brian 2 | +==================================================================+===================================================+ + .. code:: | .. code:: | + | | + conn8 = Connection(source, target, 'ge') | conn8 = Synapses(source, target, 'w: siemens', | + conn8.connect_full(source, target, | on_pre='ge += w') | + weight=lambda i,j:(1+cos(i-j))*2*nS) | conn8.connect() | + | conn8.w = '(1 + cos(i - j))*2*nS' | + | | +------------------------------------------------------------------+---------------------------------------------------+ + .. code:: | .. code:: | + | | + conn9 = Connection(source, target, 'ge') | conn9 = ... # see above | + conn9.connect_random(source, target, | conn9.connect(p=0.02) | + sparseness=0.02, | conn9.w = 'rand()*nS' | + weight=lambda:rand()*nS) | | + | | +------------------------------------------------------------------+---------------------------------------------------+ + .. code:: | .. code:: | + | | + conn10 = Connection(source, target, 'ge') | conn10 = ... # see above | + conn10.connect_random(source, target, | conn10.connect(p='exp(-abs(i - j)*.1)') | + sparseness=lambda i,j:exp(-abs(i-j)*.1),| conn10.w = 2*nS | + weight=2*ns) | | + | | +------------------------------------------------------------------+---------------------------------------------------+ Delays ~~~~~~ The specification of delays changed in several aspects from Brian 1 to Brian 2: In Brian 1, delays where homogeneous by default, and heterogeneous delays had to be marked by ``delay=True``, together with the specification of the maximum delay. In Brian 2, heterogeneous delays are the default and you do not have to state the maximum delay. Brian 1's syntax of specifying a pair of values to get randomly distributed delays in that range is no longer supported, instead use Brian 2's standard string syntax: +----------------------------------------------------------+-----------------------------------------------------+ | Brian 1 | Brian 2 | +==========================================================+=====================================================+ + .. code:: | .. code:: | + | | + conn11 = Connection(source, target, 'ge', delay=True, | conn11 = Synapses(source, target, 'w : siemens', | + max_delay=5*ms) | on_pre='ge += w') | + conn11.connect_full(source, target, weight=3*nS, | conn11.connect() | + delay=(0*ms, 5*ms)) | conn11.w = 3*nS | + | conn11.delay = 'rand()*5*ms' | + | | +----------------------------------------------------------+-----------------------------------------------------+ Modulation ~~~~~~~~~~ In Brian 2, there's no need for the ``modulation`` keyword that Brian 1 offered, you can describe the modulation as part of the ``on_pre`` action: +----------------------------------------------------------+-----------------------------------------------------+ | Brian 1 | Brian 2 | +==========================================================+=====================================================+ + .. code:: | .. code:: | + | | + conn12 = Connection(source, target, 'ge', | conn12 = Synapses(source, target, 'w : siemens', | + modulation='u') | on_pre='ge += w * u_pre') | + | | +----------------------------------------------------------+-----------------------------------------------------+ Structure ~~~~~~~~~ There's no equivalen for Brian 1's ``structure`` keyword in Brian 2, synapses are always stored in a sparse data structure. There is currently no support for changing synapses at run time (i.e. the "dynamic" structure of Brian 1). Converting Brian 1's ``Synapses`` class --------------------------------------- Brian 2's `Synapses` class works for the most part like the class of the same name in Brian 1. There are however some differences in details, listed below: Synaptic models ~~~~~~~~~~~~~~~ The basic syntax to define a synaptic model is unchanged, but the keywords ``pre`` and ``post`` have been renamed to ``on_pre`` and ``on_post``, respectively. +----------------------------------------------------------------------------+----------------------------------------------------------------------------+ | Brian 1 | Brian 2 | +============================================================================+============================================================================+ | .. code:: | .. code:: | | | | | stdp_syn = Synapses(inputs, neurons, model=''' | stdp_syn = Synapses(inputs, neurons, model=''' | | w:1 | w:1 | | dApre/dt = -Apre/taupre : 1 (event-driven) | dApre/dt = -Apre/taupre : 1 (event-driven) | | dApost/dt = -Apost/taupost : 1 (event-driven)''', | dApost/dt = -Apost/taupost : 1 (event-driven)''', | | pre='''ge + =w | on_pre='''ge + =w | | Apre += delta_Apre | Apre += delta_Apre | | w = clip(w + Apost, 0, gmax)''', | w = clip(w + Apost, 0, gmax)''', | | post='''Apost += delta_Apost | on_post='''Apost += delta_Apost | | w = clip(w + Apre, 0, gmax)''') | w = clip(w + Apre, 0, gmax)''') | | | | +----------------------------------------------------------------------------+----------------------------------------------------------------------------+ Lumped variables (summed variables) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The syntax to define lumped variables (we use the term "summed variables" in Brian 2) has been changed: instead of assigning the synaptic variable to the neuronal variable you'll have to include the summed variable in the synaptic equations with the flag ``(summed)``: +------------------------------------------------------------+------------------------------------------------------------+ | Brian 1 | Brian 2 | +============================================================+============================================================+ | .. code:: | .. code:: | | | | | # a non-linear synapse (e.g. NMDA) | # a non-linear synapse (e.g. NMDA) | | neurons = NeuronGroup(1, model=''' | neurons = NeuronGroup(1, model=''' | | dv/dt = (gtot - v)/(10*ms) : 1 | dv/dt = (gtot - v)/(10*ms) : 1 | | gtot : 1''') | gtot : 1''') | | syn = Synapses(inputs, neurons, | syn = Synapses(inputs, neurons, | | model=''' | model=''' | | dg/dt = -a*g+b*x*(1-g) : 1 | dg/dt = -a*g+b*x*(1-g) : 1 | | dx/dt = -c*x : 1 | dx/dt = -c*x : 1 | | w : 1 # synaptic weight''', | w : 1 # synaptic weight | | pre='x += w') | gtot_post = g : 1 (summed)''', | | neurons.gtot=S.g | on_pre='x += w') | | | | +------------------------------------------------------------+------------------------------------------------------------+ Creating synapses ~~~~~~~~~~~~~~~~~ In Brian 1, synapses were created by assigning ``True`` or an integer (the number of synapses) to an indexed `Synapses` object. In Brian 2, all synapse creation goes through the `Synapses.connect` function. For examples how to create more complex connection patterns, see the section on translating ``Connections`` objects above. +-------------------------------+-------------------------------+ | Brian 1 | Brian 2 | +===============================+===============================+ | .. code:: | .. code:: | | | | | syn = Synapses(...) | syn = Synapses(...) | | # single synapse | # single synapse | | syn[3, 5] = True | syn.connect(i=3, j=5) | | | | +-------------------------------+-------------------------------+ | .. code:: | .. code:: | | | | | # all-to-all connections | # all-to-all connections | | syn[:, :] = True | syn.connect() | | | | +-------------------------------+-------------------------------+ | .. code:: | .. code:: | | | | | # all to neuron number 1 | # all to neuron number 1 | | syn[:, 1] = True | syn.connect(j='1') | | | | +-------------------------------+-------------------------------+ | .. code:: | .. code:: | | | | | # multiple synapses | # multiple synapses | | syn[4, 7] = 3 | syn.connect(i=4, j=7, n=3) | | | | +-------------------------------+-------------------------------+ | .. code:: | .. code:: | | | | | # connection probability 2%| # connection probability 2%| | syn[:, :] = 0.02 | syn.connect(p=0.02) | | | | +-------------------------------+-------------------------------+ Multiple pathways ~~~~~~~~~~~~~~~~~ As Brian 1, Brian 2 supports multiple pre- or post-synaptic pathways, with separate pre-/post-codes and delays. In Brian 1, you have to specify the pathways as tuples and can then later access them individually by using their index. In Brian 2, you specify the pathways as a dictionary, i.e. by giving them individual names which you can then later use to access them (the default pathways are called ``pre`` and ``post``): +----------------------------------------------------------+----------------------------------------------------------+ | Brian 1 | Brian 2 | +==========================================================+==========================================================+ | .. code:: | .. code:: | | | | | S = Synapses(..., | S = Synapses(..., | | pre=('ge + =w', | pre={'pre_transmission': | | '''w = clip(w + Apost, 0, inf) | 'ge += w', | | Apre += delta_Apre'''), | 'pre_plasticity': | | post='''Apost += delta_Apost | '''w = clip(w + Apost, 0, inf) | | w = clip(w + Apre, 0, inf)''')| Apre += delta_Apre'''}, | | | post='''Apost += delta_Apost | | S[:, :] = True | w = clip(w + Apre, 0, inf)''')| | S.delay[1][:, :] = 3*ms # delayed trace | | | | S.connect() | | | S.pre_plasticity.delay[:, :] = 3*ms # delayed trace| | | | +----------------------------------------------------------+----------------------------------------------------------+ Monitoring synaptic variables ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Both in Brian 1 and Brian 2, you can record the values of synaptic variables with a `StateMonitor`. You no longer have to call an explicit indexing function, but you can directly provide an appropriately indexed `Synapses` object. You can now also use the same technique to index the `StateMonitor` object to get the recorded values, see the respective section in the :doc:`../../user/synapses` documentation for details. +-------------------------------------------------+----------------------------------------------+ | Brian 1 | Brian 2 | +=================================================+==============================================+ | .. code:: | .. code:: | | | | | syn = Synapses(...) | syn = Synapses(...) | | # record all synapse targetting neuron 3 | # record all synapse targetting neuron 3 | | indices = syn.synapse_index((slice(None), 3))| mon = StateMonitor(S, 'w', record=S[:, 3])| | mon = StateMonitor(S, 'w', record=indices) | | | | | +-------------------------------------------------+----------------------------------------------+brian2-2.5.4/docs_sphinx/introduction/changes.rst000066400000000000000000000274641445201106100221120ustar00rootroot00000000000000Changes for Brian 1 users ========================= .. contents:: :local: :depth: 1 .. note:: If you need to run existing Brian 1 simulations, have a look at :doc:`brian1_to_2/container`. In most cases, Brian 2 works in a very similar way to Brian 1 but there are some important differences to be aware of. The major distinction is that in Brian 2 you need to be more explicit about the definition of your simulation in order to avoid inadvertent errors. In some cases, you will now get a warning in other even an error -- often the error/warning message describes a way to resolve the issue. Specific examples how to convert code from Brian 1 can be found in the document :doc:`brian1_to_2/index`. Physical units -------------- The unit system now extends to arrays, e.g. ``np.arange(5) * mV`` will retain the units of volts and not discard them as Brian 1 did. Brian 2 is therefore also more strict in checking the units. For example, if the state variable ``v`` uses the unit of volt, the statement ``G.v = np.rand(len(G)) / 1000.`` will now raise an error. For consistency, units are returned everywhere, e.g. in monitors. If ``mon`` records a state variable v, ``mon.t`` will return a time in seconds and ``mon.v`` the stored values of ``v`` in units of volts. If you need a pure numpy array without units for further processing, there are several options: if it is a state variable or a recorded variable in a monitor, appending an underscore will refer to the variable values without units, e.g. ``mon.t_`` returns pure floating point values. Alternatively, you can remove units by diving by the unit (e.g. ``mon.t / second``) or by explicitly converting it (``np.asarray(mon.t)``). Here's an overview showing a few expressions and their respective values in Brian 1 and Brian 2: ================================ ================================ ================================= Expression Brian 1 Brian 2 ================================ ================================ ================================= 1 * mV 1.0 * mvolt 1.0 * mvolt np.array(1) * mV 0.001 1.0 * mvolt np.array([1]) * mV array([ 0.001]) array([1.]) * mvolt np.mean(np.arange(5) * mV) 0.002 2.0 * mvolt np.arange(2) * mV array([ 0. , 0.001]) array([ 0., 1.]) * mvolt (np.arange(2) * mV) >= 1 * mV array([False, True], dtype=bool) array([False, True], dtype=bool) (np.arange(2) * mV)[0] >= 1 * mV False False (np.arange(2) * mV)[1] >= 1 * mV DimensionMismatchError True ================================ ================================ ================================= Unported packages ----------------- The following packages have not (yet) been ported to Brian 2. If your simulation critically depends on them, you should consider staying with Brian 1 for now. * ``brian.tools`` * ``brian.library.modelfitting`` * ``brian.library.electrophysiology`` Replacement packages -------------------- The following packages that were included in Brian 1 have now been split into separate packages. * ``brian.hears`` has been updated to `brian2hears `_. Note that there is a legacy package ``brian2.hears`` included in ``brian2``, but this is now deprecated and will be removed in a future release. For now, see :ref:`brian_hears` for details. Removed classes/functions and their replacements ------------------------------------------------ In Brian 2, we have tried to keep the number of classes/functions to a minimum, but make each of them flexible enough to encompass a large number of use cases. A lot of the classes and functions that existed in Brian 1 have therefore been removed. The following table lists (most of) the classes that existed in Brian 1 but do no longer exist in Brian 2. You can consult it when you get a ``NameError`` while converting an existing script from Brian 1. The third column links to a document with further explanation and the second column gives either: 1. the equivalent class in Brian 2 (e.g. `StateMonitor` can record multiple variables now and therefore replaces ``MultiStateMonitor``); 2. the name of a Brian 2 class in square brackets (e.g. [`Synapses`] for ``STDP``), this means that the class can be used as a replacement but needs some additional code (e.g. explicitly specified STDP equations). The "More details" document should help you in making the necessary changes; 3. "string expression", if the functionality of a previously existing class can be expressed using the general string expression framework (e.g. `threshold=VariableThreshold('Vt', 'V')` can be replaced by `threshold='V > Vt'`); 4. a link to the relevant github issue if no equivalent class/function does exist so far in Brian 2; 5. a remark such as "obsolete" if the particular class/function is no longer needed. =============================== ================================= ================================================================ Brian 1 Brian 2 More details =============================== ================================= ================================================================ ``AdEx`` [`Equations`] :doc:`brian1_to_2/library` ``aEIF`` [`Equations`] :doc:`brian1_to_2/library` ``AERSpikeMonitor`` :issue:`298` :doc:`brian1_to_2/monitors` ``alpha_conductance`` [`Equations`] :doc:`brian1_to_2/library` ``alpha_current`` [`Equations`] :doc:`brian1_to_2/library` ``alpha_synapse`` [`Equations`] :doc:`brian1_to_2/library` ``AutoCorrelogram`` [`SpikeMonitor`] :doc:`brian1_to_2/monitors` ``biexpr_conductance`` [`Equations`] :doc:`brian1_to_2/library` ``biexpr_current`` [`Equations`] :doc:`brian1_to_2/library` ``biexpr_synapse`` [`Equations`] :doc:`brian1_to_2/library` ``Brette_Gerstner`` [`Equations`] :doc:`brian1_to_2/library` ``CoincidenceCounter`` [`SpikeMonitor`] :doc:`brian1_to_2/monitors` ``CoincidenceMatrixCounter`` [`SpikeMonitor`] :doc:`brian1_to_2/monitors` ``Compartments`` :issue:`443` :doc:`brian1_to_2/multicompartmental` ``Connection`` `Synapses` :doc:`brian1_to_2/synapses` ``Current`` :issue:`443` :doc:`brian1_to_2/multicompartmental` ``CustomRefractoriness`` [string expression] :doc:`brian1_to_2/neurongroup` ``DefaultClock`` `Clock` :doc:`brian1_to_2/networks_and_clocks` ``EmpiricalThreshold`` string expression :doc:`brian1_to_2/neurongroup` ``EventClock`` `Clock` :doc:`brian1_to_2/networks_and_clocks` ``exp_conductance`` [`Equations`] :doc:`brian1_to_2/library` ``exp_current`` [`Equations`] :doc:`brian1_to_2/library` ``exp_IF`` [`Equations`] :doc:`brian1_to_2/library` ``exp_synapse`` [`Equations`] :doc:`brian1_to_2/library` ``FileSpikeMonitor`` :issue:`298` :doc:`brian1_to_2/monitors` ``FloatClock`` `Clock` :doc:`brian1_to_2/networks_and_clocks` ``FunReset`` [string expression] :doc:`brian1_to_2/neurongroup` ``FunThreshold`` [string expression] :doc:`brian1_to_2/neurongroup` ``hist_plot`` no equivalent -- ``HomogeneousPoissonThreshold`` string expression :doc:`brian1_to_2/neurongroup` ``IdentityConnection`` `Synapses` :doc:`brian1_to_2/synapses` ``IonicCurrent`` :issue:`443` :doc:`brian1_to_2/multicompartmental` ``ISIHistogramMonitor`` [`SpikeMonitor`] :doc:`brian1_to_2/monitors` ``Izhikevich`` [`Equations`] :doc:`brian1_to_2/library` ``K_current_HH`` [`Equations`] :doc:`brian1_to_2/library` ``leak_current`` [`Equations`] :doc:`brian1_to_2/library` ``leaky_IF`` [`Equations`] :doc:`brian1_to_2/library` ``MembraneEquation`` :issue:`443` :doc:`brian1_to_2/multicompartmental` ``MultiStateMonitor`` `StateMonitor` :doc:`brian1_to_2/monitors` ``Na_current_HH`` [`Equations`] :doc:`brian1_to_2/library` ``NaiveClock`` `Clock` :doc:`brian1_to_2/networks_and_clocks` ``NoReset`` obsolete :doc:`brian1_to_2/neurongroup` ``NoThreshold`` obsolete :doc:`brian1_to_2/neurongroup` ``OfflinePoissonGroup`` [`SpikeGeneratorGroup`] :doc:`brian1_to_2/inputs` ``OrnsteinUhlenbeck`` [`Equations`] :doc:`brian1_to_2/library` ``perfect_IF`` [`Equations`] :doc:`brian1_to_2/library` ``PoissonThreshold`` string expression :doc:`brian1_to_2/neurongroup` ``PopulationSpikeCounter`` `SpikeMonitor` :doc:`brian1_to_2/monitors` ``PulsePacket`` [`SpikeGeneratorGroup`] :doc:`brian1_to_2/inputs` ``quadratic_IF`` [`Equations`] :doc:`brian1_to_2/library` ``raster_plot`` ``plot_raster`` (``brian2tools``) `brian2tools documentation `_ ``RecentStateMonitor`` no direct equivalent :doc:`brian1_to_2/monitors` ``Refractoriness`` string expression :doc:`brian1_to_2/neurongroup` ``RegularClock`` `Clock` :doc:`brian1_to_2/networks_and_clocks` ``Reset`` string expression :doc:`brian1_to_2/neurongroup` ``SimpleCustomRefractoriness`` [string expression] :doc:`brian1_to_2/neurongroup` ``SimpleFunThreshold`` [string expression] :doc:`brian1_to_2/neurongroup` ``SpikeCounter`` `SpikeMonitor` :doc:`brian1_to_2/monitors` ``StateHistogramMonitor`` [`StateMonitor`] :doc:`brian1_to_2/monitors` ``StateSpikeMonitor`` `SpikeMonitor` :doc:`brian1_to_2/monitors` ``STDP`` [`Synapses`] :doc:`brian1_to_2/synapses` ``STP`` [`Synapses`] :doc:`brian1_to_2/synapses` ``StringReset`` string expression :doc:`brian1_to_2/neurongroup` ``StringThreshold`` string expression :doc:`brian1_to_2/neurongroup` ``Threshold`` string expression :doc:`brian1_to_2/neurongroup` ``VanRossumMetric`` [`SpikeMonitor`] :doc:`brian1_to_2/monitors` ``VariableReset`` string expression :doc:`brian1_to_2/neurongroup` ``VariableThreshold`` string expression :doc:`brian1_to_2/neurongroup` =============================== ================================= ================================================================ List of detailed instructions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. toctree:: :maxdepth: 2 brian1_to_2/index brian2-2.5.4/docs_sphinx/introduction/code_of_conduct.rst000066400000000000000000000065741445201106100236160ustar00rootroot00000000000000Contributor Covenant Code of Conduct ==================================== Our Pledge ---------- In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. Our Standards ------------- Examples of behavior that contributes to creating a positive environment include: - Using welcoming and inclusive language - Being respectful of differing viewpoints and experiences - Gracefully accepting constructive criticism - Focusing on what is best for the community - Showing empathy towards other community members Examples of unacceptable behavior by participants include: - The use of sexualized language or imagery and unwelcome sexual attention or advances - Trolling, insulting/derogatory comments, and personal or political attacks - Public or private harassment - Publishing others’ private information, such as a physical or electronic address, without explicit permission - Other conduct which could reasonably be considered inappropriate in a professional setting Our Responsibilities -------------------- Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. Scope ----- This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. Enforcement ----------- Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at team@briansimulator.org. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project’s leadership. Attribution ----------- This Code of Conduct is adapted from the `Contributor Covenant `__, version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq brian2-2.5.4/docs_sphinx/introduction/compatibility.rst000066400000000000000000000155231445201106100233440ustar00rootroot00000000000000Compatibility and reproducibility ================================= .. _supported_python: Supported Python and numpy versions ----------------------------------- We follow the approach outlined in numpy's `deprecation policy `_. This means that Brian supports: * All minor versions of Python released 42 months prior to Brian, and at minimum the two latest minor versions. * All minor versions of numpy released in the 24 months prior to Brian, and at minimum the last three minor versions. Note that we do not have control about the versions that are supported by the `conda-forge `_ infrastructure. Therefore, ``brian2`` conda packages might not be provided for all of the supported versions. In this case, affected users can chose to either update the Python/numpy version in their conda environment to a version with a conda package or to install ``brian2`` via pip. General policy -------------- We try to keep backwards-incompatible changes to a minimum. In general, ``brian2`` scripts should continue to work with newer versions and should give the same results. As an exception to the above rule, we will always correct clearly identified bugs that lead to incorrect simulation results (i.e., not just an matter of interpretation). Since we do not want to require new users to take any action to get correct results, we will change the default behaviour in such cases. If possible, we will give the user an option to restore the old, incorrect behaviour to reproduce the previous results with newer Brian versions. This would typically be a preference in the `legacy` category, see `legacy.refractory_timing` for an example. .. note:: The order of terms when evaluating equations is not fixed and can change with the version of ``sympy``, the symbolic mathematics library used in Brian. Similarly, Brian performs a number of optimizations by default and asks the compiler to perform further ones which might introduce subtle changes depending on the compiler and its version. Finally, code generation can lead to either Python or C++ code (with a single or multiple threads) executing the actual simulation which again may affect the numerical results. Therefore, we cannot guarantee exact, "bitwise" reproducibility of results. Syntax deprecations ------------------- We sometimes realize that the names of arguments or other syntax elements are confusing and therefore decide to change them. In such cases, we start to use the new syntax everywhere in the documentation and examples, but leave the former syntax available for compatiblity with previously written code. For example, earlier versions of Brian used ``method='linear'`` to describe the exact solution of differential equations via sympy (that most importantly applies to "linear" equations, i.e. linear differential equations with constant coefficients). However, some users interpreted ``method='linear'`` as a "linear approximation" like the forward Euler method. In newer versions of Brian the recommended syntax is therefore to use ``method='exact'``, but the old syntax remains valid. If the changed syntax is very prominent, its continued use in Brian scripts (published by others) could be confusing to new users. In these cases, we might decide to give a warning when the deprecated syntax is used (e.g. for the ``pre`` and ``post`` arguments in `Synapses` which have been replaced by ``on_pre`` and ``on_post``). Such warnings will contain all the information necessary to rewrite the code so that the warning is no longer raised (in line with our general :ref:`policy for warnings `). Random numbers -------------- Streams of random numbers in Brian simulations (including the generation of synapses, etc.) are reproducible when a seed is set via Brian's `seed` function. Note that there is a difference with regard to random numbers between :doc:`runtime and standalone mode <../user/computation>`: in runtime mode, numpy's random number generator is always used – even from generated Cython code. Therefore, the call to `seed` will set numpy's random number generator seed which then applies to all random numbers. Regardless of whether initial values of a variable are set via an explicit call to `numpy.random.randn`, or via a Brian expression such as ``'randn()'``, both are affected by this seed. In contrast, random numbers in standalone simulations will be generated by an independent random number generator (but based on the same algorithm as numpy's) and the call to `seed` will only affect these numbers, not numbers resulting from explicit calls to `numpy.random`. To make standalone scripts mixing both sources of randomness reproducible, either set numpy's random generator seed manually in addition to calling `seed`, or reformulate the model to use code generation everywhere (e.g. replace ``group.v = -70*mV + 10*mV*np.random.randn(len(group))`` by ``group.v = '-70*mv + 10*mV*randn()'``). Changing the code generation target can imply a change in the order in which random numbers are drawn from the reproducible random number stream. In general, we therefore only guarantee the use of the same numbers if the code generation target and the number of threads (for C++ standalone simulations) is the same. .. note:: If there are several sources of randomness (e.g. multiple `PoissonGroup` objects) in a simulation, then the order in which these elements are executed matters. The order of execution is deterministic, but if it is not unambiguously determined by the ``when`` and ``order`` attributes (see :ref:`scheduling` for details), then it will depend on the names of objects. When not explicitly given via the ``name`` argument during the object's creation, names are automatically generated by Brian as e.g. ``poissongroup``, ``poissongroup_1``, etc. When you repeatedly run simulations within the same process, these names might change and therefore the order in which the elements are simulated. Random numbers will then be differently distributed to the objects. To avoid this and get reproducible random number streams you can either fix the order of elements by specifying the ``order`` or ``name`` argument, or make sure that each simulation gets run in a fresh Python process. Python errors ------------- While we try to guarantee the reproducibility of simulations (within the limits stated above), we do so only for code that does not raise any error. We constantly try to improve the error handling in Brian, and these improvements can lead to errors raised at a different time (e.g. when creating an object as opposed to when running the simulation), different types of errors being raised (e.g. `DimensionMismatchError` instead of `TypeError`), or simply a different error message text. Therefore, Brian scripts should never use ``try``/``except`` blocks to implement program logic. brian2-2.5.4/docs_sphinx/introduction/index.rst000066400000000000000000000002531445201106100215740ustar00rootroot00000000000000Introduction ============ .. toctree:: :maxdepth: 1 install scripts release_notes changes known_issues support compatibility code_of_conduct brian2-2.5.4/docs_sphinx/introduction/install.rst000066400000000000000000000240521445201106100221360ustar00rootroot00000000000000Installation ============ .. contents:: :local: :depth: 1 There are various ways to install Brian, and we recommend that you chose the installation method that they are most familiar with and use for other Python packages. If you do not yet have Python installed on your system (in particular on Windows machines), you can install Python and all of Brian's dependencies via the `Anaconda distribution `_. You can then install Brian with the ``conda`` package manager as detailed below. .. note:: You need to have access to Python >=3.7 (see Brian's :ref:`support policy `). In particular, Brian no longer supports Python 2 (the last version to support Python 2 was :ref:`brian2.3`). All provided Python packages also require a 64 bit system, but every desktop or laptop machine built in the last 10 years (and even most older machines) is 64 bit compatible. If you are relying on Python packages for several, independent projects, we recommend that you make use of separate environments for each project. In this way, you can safely update and install packages for one of your projects without affecting the others. Both, ``conda`` and ``pip`` support installation in environments -- for more explanations see the respective instructions below. Standard install ---------------- .. tabs:: .. group-tab:: conda package We recommend installing Brian into a separate environment, see `conda's documentation `_ for more details. Brian 2 is not part of the main Anaconda distribution, but built using the community-maintained `conda-forge `_ project. You will therefore have to to install it from the `conda-forge channel `_. To do so, use:: conda install -c conda-forge brian2 You can also permanently add the channel to your list of channels:: conda config --add channels conda-forge This has only to be done once. After that, you can install and update the brian2 packages as any other Anaconda package:: conda install brian2 .. group-tab:: PyPI package (``pip``) We recommend installing Brian into a separate "virtual environment", see the `Python Packaging User Guide `_ for more information. Brian is included in the PyPI package index: https://pypi.python.org/pypi/Brian2 You can therefore install it with the ``pip`` utility:: python -m pip install brian2 In rare cases where your current environment does not have access to the ``pip`` utility, you first have to install ``pip`` via:: python -m ensurepip .. group-tab:: Ubuntu/Debian package If you are using a recent `Debian `_-based Linux distribution (Debian itself, or one if its derivatives like `Ubuntu `_ or `Linux Mint `_), you can install Brian using its built-in package manager:: sudo apt install python3-brian Brian releases get packaged by the `Debian Med `_ team, but note that it might take a while until the most recent version shows up in the repository. .. group-tab:: Fedora package If you are using `Fedora Linux `_, you can install Brian using its built-in package manager:: sudo dnf install python-brian2 Brian releases get packaged by the `NeuroFedora `_ team, but note that it might take a while until the most recent version shows up in the repository. .. group-tab:: Spack package `Spack `_ is a flexible package manager supporting multiple versions, configurations, platforms, and compilers. After setting up Spack you can install Brian with the following command:: spack install py-brian2 .. _updating_install: Updating an existing installation --------------------------------- How to update Brian to a new version depends on the installation method you used previously. Typically, you can run the same command that you used for installation (sometimes with an additional option to enforce an upgrade, if available): .. tabs:: .. group-tab:: conda package Depending on whether you added the ``conda-forge`` channel to the list of channels or not (see above), you either have to include it in the update command again or can leave it away. I.e. use:: conda update -c conda-forge brian2 if you did not add the channel, or:: conda update brian2 if you did. .. group-tab:: PyPI package (``pip``) Use the install command together with the ``--upgrade`` or ``-U`` option:: python -m pip install -U brian2 .. group-tab:: Ubuntu/Debian package Update the package repository and ask for an install. Note that the package will also be updated automatically with commands like ``sudo apt full-upgrade``:: sudo apt update sudo apt install python3-brian .. group-tab:: Fedora package Update the package repository (not necessary in general, since it will be updated regularly without asking for it), and ask for an update. Note that the package will also be updated automatically with commands like ``sudo dnf upgrade``:: sudo dnf check-update python-brian2 sudo dnf upgrade python-brian2 .. _installation_cpp: Requirements for C++ code generation ------------------------------------ C++ code generation is highly recommended since it can drastically increase the speed of simulations (see :doc:`../user/computation` for details). To use it, you need a C++ compiler and Cython_ (automatically installed as a dependency of Brian). .. tabs:: .. tab:: Linux and OS X On Linux and Mac OS X, the conda package will automatically install a C++ compiler. But even if you install Brian in a different way, you will most likely already have a working C++ compiler installed on your system (try calling ``g++ --version`` in a terminal). If not, use your distribution's package manager to install a ``g++`` package. .. tab:: Windows On Windows, :ref:`runtime` (i.e. Cython) requires the Visual Studio compiler, but you do not need a full Visual Studio installation, installing the much smaller "Build Tools" package is sufficient: * Install the `Microsoft Build Tools for Visual Studio `_. * In Build tools, install C++ build tools and ensure the latest versions of MSVCv... build tools and Windows 10 SDK are checked. * Make sure that your ``setuptools`` package has at least version 34.4.0 (use ``conda update setuptools`` when using Anaconda, or ``python -m pip install --upgrade setuptools`` when using pip). For :ref:`cpp_standalone`, you can either use the compiler installed above or any other version of Visual Studio. Try running the test suite (see :ref:`testing_brian` below) after the installation to make sure everything is working as expected. .. _development_install: Development install ------------------- When you encounter a problem in Brian, we will sometimes ask you to install Brian's latest development version, which includes changes that were included after its last release. We regularly upload the latest development version of Brian to PyPI's test server. You can install it via:: python -m pip install --upgrade --pre -i https://test.pypi.org/simple/ Brian2 Note that this requires that you already have all of Brian's dependencies installed. If you have ``git`` installed, you can also install directly from github:: python -m pip install git+https://github.com/brian-team/brian2.git Finally, in particular if you want to either contribute to Brian's development or regularly test its latest development version, you can directly clone the git repository at github (https://github.com/brian-team/brian2) and then run ``pip install -e .``, to install Brian in "development mode". With this installation, updating the git repository is in general enough to keep up with changes in the code, i.e. it is not necessary to install it again. .. _testing_brian: Installing other useful packages -------------------------------- There are various packages that are useful but not necessary for working with Brian. These include: matplotlib_ (for plotting), pytest_ (for running the test suite), ipython_ and jupyter_-notebook (for an interactive console). .. tabs:: .. group-tab:: conda package :: conda install matplotlib pytest ipython notebook .. group-tab:: PyPI package (``pip``) :: python -m pip install matplotlib pytest ipython notebook You should also have a look at the brian2tools_ package, which contains several useful functions to visualize Brian 2 simulations and recordings. .. tabs:: .. group-tab:: conda package As of now, ``brian2tools`` is not yet included in the ``conda-forge`` channel, you therefore have to install it from our own ``brian-team`` channel:: conda install -c brian-team brian2tools .. group-tab:: PyPI package (``pip``) :: python -m pip install brian2tools Testing Brian ------------- If you have the pytest_ testing utility installed, you can run Brian's test suite:: import brian2 brian2.test() It should end with "OK", showing a number of skipped tests but no errors or failures. For more control about the tests that are run see the :doc:`developer documentation on testing <../developer/guidelines/testing>`. .. _matplotlib: http://matplotlib.org/ .. _ipython: http://ipython.org/ .. _jupyter: http://jupyter.org/ .. _brian2tools: https://brian2tools.readthedocs.io .. _azure: https://azure.microsoft.com/en-us/services/devops/pipelines/ .. _pytest: https://docs.pytest.org/en/stable/ .. _Cython: http://cython.org/ brian2-2.5.4/docs_sphinx/introduction/known_issues.rst000066400000000000000000000133041445201106100232150ustar00rootroot00000000000000Known issues ============ In addition to the issues noted below, you can refer to our `bug tracker on GitHub `__. .. contents:: List of known issues :local: Cannot find msvcr90d.dll ------------------------ If you see this message coming up, find the file ``PythonDir\Lib\site-packages\numpy\distutils\mingw32ccompiler.py`` and modify the line ``msvcr_dbg_success = build_msvcr_library(debug=True)`` to read ``msvcr_dbg_success = False`` (you can comment out the existing line and add the new line immediately after). "AttributeError: MSVCCompiler instance has no attribute 'compiler_cxx'" ----------------------------------------------------------------------- This is caused by a bug in some versions of numpy on Windows. The easiest solution is to update to the latest version of numpy. If that isn't possible, a hacky solution is to modify the numpy code directly to fix the problem. The following change may work. Modify line 388 of ``numpy/distutils/ccompiler.py`` from ``elif not self.compiler_cxx:`` to ``elif not hasattr(self, 'compiler_cxx') or not self.compiler_cxx:``. If the line number is different, it should be nearby. Search for ``elif not self.compiler_cxx`` in that file. "Missing compiler_cxx fix for MSVCCompiler" ------------------------------------------- If you keep seeing this message, do not worry. It's not possible for us to hide it, but doesn't indicate any problems. Problems with numerical integration ----------------------------------- In some cases, the automatic choice of numerical integration method will not be appropriate, because of a choice of parameters that couldn't be determined in advance. In this case, typically you will get nan (not a number) values in the results, or large oscillations. In this case, Brian will generate a warning to let you know, but will not raise an error. Jupyter notebooks and C++ standalone mode progress reporting ------------------------------------------------------------ When you run simulations in C++ standalone mode and enable progress reporting (e.g. by using ``report='text'`` as a keyword argument), the progress will not be displayed in the jupyter notebook. If you started the notebook from a terminal, you will find the output there. Unfortunately, this is a tricky problem to solve at the moment, due to the details of how the jupyter notebook handles output. Parallel Brian simulations with C++ standalone ---------------------------------------------- Simulations using the C++ standalone device will create code and store results in a dedicated directory (``output``, by default). If you run multiple simulations in parallel, you have to take care that these simulations do not use the same directory – otherwise, everything from compilation errors to incorrect results can happen. Either chose a different directory name for each simulation and provide it as the ``directory`` argument to the `.set_device` or `~.Device.build` call, or use ``directory=None`` which will use a randomly chosen unique temporary directory (in ``/tmp`` on Unix-based systems) for each simulation. If you need to know the directory name, you can access it after the simulation run via ``device.project_dir``. .. _parallel_cython: Parallel Brian simulations with Cython on machines with NFS (e.g. a computing cluster) -------------------------------------------------------------------------------------- Generated Cython code is stored in a cache directory on disk so that it can be reused when it is needed again, without recompiling it. Multiple simulations running in parallel could interfere during the compilation process by trying to generate the same file at the same time. To avoid this, Brian uses a file locking mechanism that ensures that only a process at a time can access these files. Unfortunately, this file locking mechanism is very slow on machines using the Network File System (`NFS `_), which is often the case on computing clusters. On such machines, it is recommend to use an independent cache directory per process, and to disable the file locking mechanism. This can be done with the following code that has to be run at the beginning of each process:: from brian2 import * import os cache_dir = os.path.expanduser(f'~/.cython/brian-pid-{os.getpid()}') prefs.codegen.runtime.cython.cache_dir = cache_dir prefs.codegen.runtime.cython.multiprocess_safe = False Slow C++ standalone simulations ------------------------------- Some versions of the GNU standard library (in particular those used by recent Ubuntu versions) have a bug that can dramatically slow down simulations in C++ standalone mode on modern hardware (see :issue:`803`). As a workaround, Brian will set an environment variable ``LD_BIND_NOW`` during the execution of standalone simulations which changes the way the library is linked so that it does not suffer from this problem. If this environment variable leads to unwanted behaviour on your machine, change the `prefs.devices.cpp_standalone.run_environment_variables` preference. Cython fails with compilation error on OS X: ``error: use of undeclared identifier 'isinf'`` -------------------------------------------------------------------------------------------- Try setting the environment variable ``MACOSX_DEPLOYMENT_TARGET=10.9``. CMD windows open when running Brian on Windows with the Spyder 3 IDE -------------------------------------------------------------------- This is due to the interaction with the integrated ipython terminal. Either change the run configuration to "Execute in an external system terminal" or patch the internal Python function used to spawn processes as described in github issue :issue:`1140`.brian2-2.5.4/docs_sphinx/introduction/release_notes.rst000066400000000000000000002720541445201106100233270ustar00rootroot00000000000000Release notes ============= Brian 2.5.4 ----------- Yet another minor release that fixes an issue with the documentation build. As a bonus, we now provide wheels built with the `musl `_ standard library, which allows installing Brian on distributions such as `Alpine Linux `_. Selected bug fixes ~~~~~~~~~~~~~~~~~~ - Re-introduce the tutorials and example plots that were omitted from the documentation by accident. Thanks to Felix Kern for making us aware of the issue. Infrastructure improvements ~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Provide ``musllinux`` (see `PEP 656 `_) wheels for distributions such as Alpine Linux (:issue:`1478`). Contributions ~~~~~~~~~~~~~ Github code, documentation, and issue contributions (ordered by the number of contributions): * Marcel Stimberg (`@mstimberg `_) * Felix Benjamin Kern (`@kernfel `_) Brian 2.5.3 ----------- This new minor release only fixes two infrastructure issues that came up with the previous release. Selected bug fixes ~~~~~~~~~~~~~~~~~~ - Re-introduce the reference documentation that was no longer created on https://brian2.readthedocs.org with the latest release (:issue:`1474`). Thanks to Michalis Pagkalos for making us aware of the issue. Infrastructure improvements ~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Brian's packaging infrastructure now switches to modern tools such as ``pyproject.toml`` for metadata declaration, ``build`` for source package creation, and ``setuptools_scm`` for versioning (:issue:`1475`). Contributions ~~~~~~~~~~~~~ Github code, documentation, and issue contributions (ordered by the number of contributions): * Marcel Stimberg (`@mstimberg `_) Brian 2.5.2 ----------- This new minor release fixes incompatibility issues with the latest numpy release, and a number of other issues. To make it easier to contribute to Brian, we have now adopted a consistent code style and updated our infrastructure so that the style gets enforced for all new code contributions (see :ref:`code_style` for details). Following `NEP 29 `_, this release supports Python 3.9 & numpy 1.21 and newer. New features ~~~~~~~~~~~~ - We now provide Python wheels for the ``linux-aarch64`` architecture (:issue:`1463`), making it easier to install Brian on ARM-based systems running Linux (including recent Apple hardware). Selected improvements and bug fixes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Fix compability with numpy 1.25 and avoid deprecation warnings (:issue:`1473`) - Add missing ``volume`` attribute to `~.SpatialNeuron` (:issue:`1430`). Thanks to Sebastian Schmitt for contributing this fix. - Fix an issue with pickling `~.Quantity` objects (:issue:`1438`). Thanks to Shailesh Appukuttan for making us aware of this issue. - No longer use the deprecated ``distutils`` package (:issue:`1442`). - Fix an issue with log files on Windows (:issue:`1454`). Thanks to discourse user ``@NiKnight`` for making us aware of the issue. - Fix an issue that prevents building the documentation on recent Python versions (:issue:`1450`). Thanks to Étienne Mollier for contributing this fix. - Fix an issue with the upcoming Cython version for GSL integration (:issue:`1471`). - Fix a broken error message (:issue:`1467`). Thanks to ``@pjsph`` for contributing the fix. - Fix an issue with user-provided header files (:issue:`1436`). Thanks to ``@wxie2013`` for reporting the issue. - Fix an issue when using `SpatialNeuron` with `TimedArray` on Cython (:issue:`1428`). Thanks to Sebastian Schmitt for reporting the issue. Infrastructure and documentation improvements ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - We now enforce a consistent code style for all new code contributions, and check/enforce the code style with tools such as ``black``, ``isort``, ``flake8``, and ``pyupgrade`` (:issue:`1435`, :issue:`1444`, :issue:`1446`). See :ref:`code_style` for details. Thanks to Oleksii Leonov for contributing this feature. - A number of new examples have been added: :doc:`../examples/frompapers.Tetzlaff_2015`, :doc:`../examples/frompapers.Nicola_Clopath_2017` (contributed by Sebastian Schmitt) and :doc:`../examples/coupled_oscillators`. - The development container has been updated, and the repository now contains a file with all the places where dependency versions are listed (:issue:`1451`, :issue:`1468`). Backwards incompatible changes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Using `SpatialNeuron` with the ``numpy`` code generation target now requires the ``scipy`` package to be installed (:issue:`1460`). Contributions ~~~~~~~~~~~~~ Github code, documentation, and issue contributions (ordered by the number of contributions): * Marcel Stimberg (`@mstimberg `_) * Dan Goodman (`@thesamovar `_) * Ben Evans (`@bdevans `_) * Oleksii Leonov (`@oleksii-leonov `_) * Sebastian Schmitt (`@schmitts `_) * Denis Alevi (`@denisalevi `_) * Shailesh Appukuttan (`@appukuttan-shailesh `_) * `@TheSquake `_ * `@tim-ufer `_ * Akalanka (`@boneyag `_) * `@pjsph `_ * `@Bitloader22 `_ * `@MunozatABI `_ * Étienne Mollier (`@emollier `_) * `@KarimHabashy `_ * `@hunjunlee `_ * Arash Golmohammadi (`@arashgmn `_) * Steathy Spikes (`@steathy-spikes `_) * Adam Willats (`@awillats `_) Other contributions outside of github (ordered alphabetically, apologies to anyone we forgot...): * `@NiKnight `_ Brian 2.5.1 ----------- This new minor release contains a large number of bug fixes and improvements, in particular for the C++ standalone mode, as well as many new contributed examples. For users of Visual Studio Code, getting involved with Brian development is now easier than ever, thanks to a new "development container" that automatically provides an environment with all the necessary dependencies. New features ~~~~~~~~~~~~ * Ben Evans added a Docker container for development with Visual Studio Code (:issue:`1387`). * Synaptic indices of synapses created with manually provided indices can now be accessed in standalone mode even before the situation has been run. This makes certain complex situations (e.g. synapses modulating other synapses) easier to write and also makes more detailed error checking possible (:issue:`1403`). * Additional "code slots", as well as more detailed profiling information about compilation times are avaiable for C++ standalone mode (:issue:`1390`, :issue:`1391`). Thanks to Denis Alevi for contributing this feature. * LaTeX output for quantity arrays (which is automatically used for the "rich representation" in jupyter notebooks), is now limited to reasonable size and no longer tries to display all values for large arrays. It now also observes most of numpy's print options (:issue:`1426`) Selected improvements and bug fixes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * Internally, Brian objects now have more consistent names (used in the generated code), and variables declarations are generated in deterministic order. This should make repeated runs of models faster, since less code has to be recompiled (:issue:`1384`, :issue:`1417`). * Running several simulations in parallel with Python's ``multiprocessing`` meant that all processes accessed the same log file which led to redundant information and could lead to crashes when several processes tried to rotate the same file. Brian now switches off logging in subprocesses, but users can enable also enable individual logs for each process, see :ref:`logging_and_multiprocessing`. The default log level for the file log has also been raised to ``DEBUG`` (:issue:`1419`). * Some common plotting idioms (e.g. ``plt.plot(spike_mon.t/ms, spike_mon.i, '.')``) were broken with the most recent matplotlib version and are now working again (:issue:`1412`) * Very long runs (with more then 2e9 simulation time steps) failed to run in C++ standalone mode (:issue:`1394`). Thanks to Kai Chen for making us aware of the issue. Infrastructure and documentation improvements ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * Sebastian Schmitt has contributed several new :doc:`../examples/index`, reproducing results from several papers (e.g. :doc:`../examples/frompapers.Maass_Natschlaeger_Markram_2002` and :doc:`../examples/frompapers.Naud_et_al_2008_adex_firing_patterns`) * Akif Erdem Sağtekin and Sebastian Schmitt contributed the example :doc:`../examples/frompapers.Izhikevich_2003`. * A number of fixes to the documentation have been contributed by Sebastian Schmitt. Contributions ~~~~~~~~~~~~~ Github code, documentation, and issue contributions (ordered by the number of contributions): * Marcel Stimberg (`@mstimberg `_) * Ben Evans (`@bdevans `_) * Sebastian Schmitt (`@schmitts `_) * Denis Alevi (`@denisalevi `_) * Akif Erdem Sağtekin (`@aesagtekin `_) * `@MunozatABI `_ * Dan Goodman (`@thesamovar `_) * `@ivapl `_ * `@dokato `_ * Davide Schiavone (`@davideschiavone `_) * Kai Chen (`@NeoNeuron `_) * Yahya Ashrafi (`@yahya-ashrafi `_) * Ariel Martínez Silberstein (`@ariel-m-s `_) * Adam Willats (`@awillats `_) Brian 2.5.0.3 ------------- Another patch-level release that fixes incorrectly built Python wheels (the binary package used to install packages with ``pip``). The wheels where mistakenly built against the most recent version of ``numpy`` (1.22), which made them incompatible with earlier versions of ``numpy``. This release also fixes a few minor mistakes in the string representation of monitors, contributed by Felix Benjamin Kern. Brian 2.5.0.2 ------------- A new patch-level release that fixes a missing ``#include`` in the synapse generation code for C++ standalone code. This does not matter for most compilers (in particular, it does not matter for the gcc, clang, and Visual Studio compilers that we use for testing on Linux, OS X, and Windows), but it can matter for projects like Brian2GeNN that build on top of Brian2 and use Nvidia's ``nvcc`` compiler. The release also fixes a minor string-formatting error (:issue:`1377`), which led to quantities that were displayed without their units. Brian 2.5.0.1 ------------- A new build to provide binary `wheels `_ for Python 3.10. .. _brian2.5: Brian 2.5 --------- This new major release contains a large number of bug fixes and improvements, as well as important new features for synapse generation: the :ref:`generator_syntax` can now create synapses "in both directions", and also supports random samples of fixed size. In addition, several contributors have helped to improve the documentation, in particular by adding several new :doc:`../examples/index`. We have also updated our test infrastructure and removed workarounds and warnings related to older, now unsupported, versions of Python. Our policy for supported Python and numpy versions now follows the `NEP 29 policy `_ adopted by most packages in the scientific Python ecosystem. This and other policies related to compatibility have been documented in :doc:`compatibility`. As always, we recommend all users of Brian 2 to upgrade. New features ~~~~~~~~~~~~ * :ref:`generator_syntax` has become more powerful: it is now possible to express pre-synaptic indices as a function of post-synaptic indices – previously, only the other direction was supported (:issue:`1294`). * Synapse generation can now make use of fixed-size random sampling (:issue:`1280`). Together with the more powerful generator syntax, this finally makes it possible to have networks where each cell receives a fixed number of random inputs: ``syn.connect(i='k for k in sample(N_pre, size=number_of_inputs)')``. Selected improvements and bug fixes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * Fair default build flags on several architectures (:issue:`1277`). Thanks to Étienne Mollier for contributing this feature. * Better C++ compiler detection on UNIX systems, e.g. with Anaconda installations (:issue:`1304`). Thanks to Jan Marker for this contribution. * Fixed LaTeX output for newer sympy versions (:issue:`1299`). Thanks to Sebastian Schmitt for reporting this issue. The problem and its fix is described in detail in this `blog post `_. * Fixed string representation for units (:issue:`1291`). Recreating a unit from its string representation gave wrong results in some corner cases. * Fix an error during the determination of appropriate C++ compiler flags on Windows with Python 3.9 (:issue:`1286`), and fix the detection of a C99-compatible compiler on Windows (:issue:`1257`). Thanks to Kyle Johnsen for reporting the errors and providing both fixes. * More robust usage of external constants in C++ standalone code, avoiding clashes when the user defines constants with common names like ``x`` (:issue:`1279`). Thanks to user ``@wxie2013`` for making us aware of this issue. * Raise an error if summed variables refer to event-based variables (:issue:`1274`) and a general rework of the dependency checks (:issue:`1328`). Thanks to Rohith Varma Buddaraju for fixing this issue. * Fix an error for deactivated spike-emitting objects (e.g. `NeuronGroup`, `PoissonGroup`). They continued to emit spikes despite ``active=False`` if they had spiked in the last time step of a previous run (:issue:`1319`). Thanks to forum user Shencong for making us aware of the issue. * Avoid warnings about deprecated numpy aliases (:issue:`1273`). * Avoid a warning about an "ignored attribute shape" in some interactive Python consoles (:issue:`1372`). * Check units for summed variables (:issue:`1361`). Thanks to Jan-Hendrik Schleimer for reporting this issue. * Do not raise an error if synapses use restore instead of Synapses.connect (:issue:`1359`). Thanks to forum user SIbanez for reporting this issue. * Fix indexing for sections in SpatialNeuron (:issue:`1358`). Thanks to Sebastian Schmitt for reporting this issue * Better error messages for missing threshold definition (:issue:`1363`). * Raise a useful error for ``namespace`` entries that start with an underscore instead of failing during compilation if the name clashes with built-in functions (:issue:`1362`). Thanks to Denis Alevi for reporting this issue. * Consistently use include/library directory preferences (:issue:`1353`). The preferences can now be used to override the list of include/library directories, replacing the inconsistent behavior where they were either prepended (C++ standalone mode) or appended (Cython runtime mode) to the default list. Thanks to Denis Alevi for opening the discussion on this issue. * Remove a warning about the difference between Python 2 and Python 3 semantics related to division (:issue:`1351`). * Do not generate spurious ``-.o`` files when checking compiler compatibility (:issue:`1348`). For more details, see this `blog post `_. * Make `~.BrianGlobalPreferences.reset_to_defaults` work again, which was inadvertently broken in the Python 2 → 3 transition (:issue:`1342`). Thanks to Denis Alevi for reporting and fixing this issue. * The commands to run and compile the code in C++ standalone mode can now be changed via a preference (:issue:`1338`). This can be useful to run/compile on clusters where jobs have to submitted with special commands. Thanks to Denis Alevi for contributing this feature. Backward-incompatible changes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * The ``default_preferences`` file that was part of the Brian installation has been removed, since it could lead to problems when working with development versions of Brian, and was overwritten with each update (:issue:`1354`). Users can still use a system-wide or per-directory preference file (see :doc:`../advanced/preferences`). * The preferences `codegen.cpp.include_dirs`, `codegen.cpp.library_dirs`, and `codegen.cpp.runtime_library_dirs` now all replace the respective default values. Previously they where prepended (C++ standalone mode) or appended (Cython runtime mode). Users relying on a combination of the default values and their manually set values need to include the default value (e.g. ``os.path.join(sys.prefix, 'include')``) manually. Infrastructure and documentation improvements ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * Tagging a release will now automatically upload the release to PyPI via a GitHub Action. Versions are automatically determined with `versioneer `_ (:issue:`1267`) and include more detailed information when using a development version of Brian. See :ref:`which_version` for more details. * The test suite has been moved to GitHub Actions for all operating systems (:issue:`1298`). Thanks to Rohith Varma Buddaraju for working on this. * New :doc:`../examples/frompapers.Jansen_Rit_1995_single_column` (:issue:`1347`), contributed by Ruben Tikidji-Hamburyan. * New :doc:`../examples/synapses.spike_based_homeostasis` (:issue:`1331`), contributed by Sebastian Schmitt. * New :doc:`../examples/advanced.COBAHH_approximated` (:issue:`1309`), contributed by Sebastian Schmitt. * Several new examples covering several Brian usage pattern, e.g. a :doc:`minimal C++ standalone script <../examples/standalone.simple_case>`, or demonstrations of running multiple simulations in parallel with :doc:`Cython <../examples/multiprocessing.01_using_cython>` or :doc:`C++ standalone <../examples/multiprocessing.02_using_standalone>`, contributed by A. Ziaeemehr. * Corrected units in :doc:`../examples/frompapers.Kremer_et_al_2011_barrel_cortex` (:issue:`1355`). Thanks to Adam Willats for contributing this fix. * Most of Brian's code base should now use a consistent string formatting style (:issue:`1364`), documented in the :doc:`../developer/guidelines/style`. * Test reports will now show the project directory path for C++ standalone projects (:issue:`1336`). Thanks to Denis Alevi for contributing this feature. * Fix the documentation for C++ compiler references (:issue:`1323`, :issue:`1321`). Thanks to Denis Alevi for fixing these issues. * Examples are now listed in a deterministic order in the documentation (:issue:`1312`), and their title is now correctly formatted in the restructured text source (:issue:`1311`). Thanks to Felix C. Stegermann for contributing these fixes. * Document how to plot model functions (e.g. time constants) in complex neuron models (:issue:`1308`). Contributed by Sebastian Schmitt. Contributions ~~~~~~~~~~~~~ Github code, documentation, and issue contributions (ordered by the number of contributions): * Marcel Stimberg (`@mstimberg `_) * Dan Goodman (`@thesamovar `_) * Rohith Varma Buddaraju (`@rohithvarma3000 `_) * Denis Alevi (`@denisalevi `_) * Dingkun.Liu (`@DingkunLiu `_) * Ruben Tikidji-Hamburyan (`@rat-h `_) * Sebastian Schmitt (`@schmitts `_) * `@ramapati166 `_ * Jan Marker (`@jangmarker `_) * Kyle Johnsen (`@kjohnsen `_) * Abolfazl Ziaeemehr (`@Ziaeemehr `_) * Felix Benjamin Kern (`@kernfel `_) * Yann Zerlaut (`@yzerlaut `_) * Adam (`@Adam-Antios `_) * `@ShanqMa `_ * Ljubica Cimeša (`@LjubicaCimesa `_) * `@adididi `_ * VigneswaranC (`@Vigneswaran-Chandrasekaran `_) * Nunna Lakshmi Saranya (`@18sarru `_) * Friedemann Zenke (`@fzenke `_) * `@Alexis-Melot `_ * Adam Willats (`@awillats `_) * Felix C. Stegerman (`@obfusk `_) * Eugen Skrebenkov (`@shcecter `_) * Maurizio DE PITTA (`@mdepitta `_) * Simo (`@sivanni `_) * Peter Quitta (`@peschn `_) * Étienne Mollier (`@emollier `_) * chaddy (`@chaddy1004 `_) * `@DePasquale99 `_ * `@albertalbesa `_ * Christian Behrens (`@chbehrens `_) Other contributions outside of github (ordered alphabetically, apologies to anyone we forgot...): * forum user `Shencong `_ * forum user `SIbanez `_ Brian 2.4.1 ----------- This is a bugfix release with a number of small fixes and updates to the continuous integration testing. Selected improvements and bug fixes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * The `check_units` decorator can now express that some arguments need to have the same units. This mechanism is now used to check the units of the `clip` function (:issue:`1234`). Thanks to Felix Kern for notifying us of this issue. * Using `SpatialNeuron` with Cython no longer raises an unnecessary warning when the ``scipy`` library is not installed (:issue:`1230`). * Raise an error for references to ``N_incoming`` or ``N_outgoing`` in calls to `Synapses.connect`. This use is ill-defined and led to compilation errors in previous versions (:issue:`1227`). Thanks to Denis Alevi for making us aware of this issue. Infrastructure and documentation improvements ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * Brian no longer officially supports installation on 32bit operating systems. Installation via ``pip`` will probably still work, but we are no longer testing this configuration (:issue:`1232`). * Automatic continuous integration tests for Windows now use the `Microsoft Azure Pipeline `_ infrastructure instead of `Appveyor `_. This should speed up tests by running different configurations in parallel (:issue:`1233`). * Fix an issue in the test suite that did not handle ``NotImplementedError`` correctly anymore after the changes introduced with :issue:`1196`. Contributions ~~~~~~~~~~~~~ Github code, documentation, and issue contributions (ordered by the number of contributions): * Marcel Stimberg (`@mstimberg `_) * Dan Goodman (`@thesamovar `_) * Denis Alevi (`@denisalevi `_) * SK (`@akatav `_) Other contributions outside of github (ordered alphabetically, apologies to anyone we forgot...): * Felix B. Kern Brian 2.4 --------- This new release contains a large number of small improvements and bug fixes. We recommend all users of Brian 2 to upgrade. The biggest code change of this new version is that Brian is now Python-3 only (thanks to Ben Evans for working on this). Selected improvements and bug fixes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * Removing objects from networks no longer fails (:issue:`1151`). Thanks to Wilhelm Braun for reporting the issue. * Point currents marked as ``constant over dt`` are now correctly handled (:issue:`1160`). Thanks to Andrew Brughera for reporting the issue. * Elapsed and estimated remaining time are now formatted as hours/minutes/etc. in standalone mode as well (:issue:`1162`). Thanks to Rahul Kumar Gupta, Syed Osama Hussain, Bhuwan Chandra, and Vigneswaran Chandrasekaran for working on this issue as part of the GSoC 2020 application process. * To prevent log files filling up the disk (:issue:`1188`), their file size is now limited to 10MB (configurable via the `logging.file_log_max_size` preference). Thanks to Rike-Benjamin Schuppner for contributing this feature. * Add more complete support for operations on `.VariableView` attributes. Previously, operations like ``group.v**2`` failed and required the workaround ``group.v[:]**2`` (:issue:`1195`) * Fix a number of compatibility issues with newer versions of numpy and sympy, and document our policy on :doc:`compatibility`. * File locking (used to avoid problems when running multiple simulations in parallel) is now based on Benedikt Schmitt's `py-filelock `_ package, which should hopefully make it more robust. * String expressions in `Synapses.connect` are now checked for syntactic correctness before handing them over to the code generation process, improving error messages. Thanks to Denis Alevi for making us aware of this issue. (:issue:`1224`) * Avoid duplicate messages in "chained" exceptions. Also introduces a new preference `logging.display_brian_error_message` to switch off the "Brian 2 encountered an unexpected error" message (:issue:`1196`). * Brian's unit system now correctly deals with matrix multiplication, including the ``@`` operator (:issue:`1216`). Thanks to `@kjohnsen `_ for reporting this issue. * Avoid turning all integer numbers in equations into floating point values (:issue:`1202`). Thanks to Marco K. for making us aware of this issue. * New attributes `.Synapses.N_outgoing_pre` and `.Synapses.N_incoming_post` to access the number of synapses per pre-/post-synaptic cell (see :ref:`accessing_synaptic_variables` for details; :issue:`1225`) Contributions ~~~~~~~~~~~~~ Github code, documentation, and issue contributions (ordered by the number of contributions): * Marcel Stimberg (`@mstimberg `_) * Ben Evans (`@bdevans `_) * Dan Goodman (`@thesamovar `_) * Denis Alevi (`@denisalevi `_) * Rike-Benjamin Schuppner (`@Debilski `_) * Syed Osama Hussain (`@Syed-Osama-Hussain `_) * VigneswaranC (`@Vigneswaran-Chandrasekaran `_) * Tushar (`@smalltimer `_) * Felix Hoffmann (`@felix11h `_) * Rahul Kumar Gupta (`@rahuliitg `_) * Dominik Spicher (`@dspicher `_) * `@nfzd `_ * `@Snow-Crash `_ * `@cnjackhu `_ * `@neurologic `_ * `@kjohnsen `_ * Ashwin Viswanathan Kannan (`@ashwin4ever `_) * Bhuwan Chandra (`@zeph1yr `_) * Wilhelm Braun (`@wilhelmbraun `_) * `@cortical-iv `_ * Eugen Skrebenkov (`@shcecter `_) * `@Aman-A `_ * Felix Benjamin Kern (`@kernfel `_) * Francesco Battaglia (`@fpbattaglia `_) * Shivam Chitnis (`@shivChitinous `_) * Marco K. (`@spokli `_) * `@jcmharry `_ * Friedemann Zenke (`@fzenke `_) * `@Adam-Antios `_ Other contributions outside of github (ordered alphabetically, apologies to anyone we forgot...): * Andrew Brughera * William Xavier .. _brian2.3: Brian 2.3 --------- This release contains the usual mix of bug fixes and new features (see below), but also makes some important changes to the Brian 2 code base to pave the way for the full Python 2 -> 3 transition (the source code is now directly compatible with Python 2 and Python 3, without the need for any translation at install time). Please note that this release will be the last release that supports Python 2, given that Python 2 reaches end-of-life in January 2020. Brian now also uses `pytest `_ as its testing framework, since the previously used ``nose`` package is not maintained anymore. Since `brian2hears `_ has been released as an independent package, using `brian2.hears` as a "bridge" to Brian 1's ``brian.hears`` package is now deprecated. Finally, the Brian project has adopted the "Contributor Covenant" :doc:`code_of_conduct`, pledging "to make participation in our community a harassment-free experience for everyone". New features ~~~~~~~~~~~~ * The `restore` function can now also restore the state of the random number generator, allowing for exact reproducibility of stochastic simulations (:issue:`1134`) * The functions `expm1`, `log1p`, and `exprel` can now be used (:issue:`1133`) * The system for calling random number generating functions has been generalized (see :ref:`function_vectorisation`), and a new `poisson` function for Poisson-distrubted random numbers has been added (:issue:`1111`) * New versions of Visual Studio are now supported for standalone mode on Windows (:issue:`1135`) Selected improvements and bug fixes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * `~brian2.groups.group.Group.run_regularly` operations are now included in the network, even if they are created after the parent object was added to the network (:issue:`1009`). Contributed by `Vigneswaran Chandrasekaran `_. * No longer incorrectly classify some equations as having "multiplicative noise" (:issue:`968`). Contributed by `Vigneswaran Chandrasekaran `_. * Brian is now compatible with Python 3.8 (:issue:`1130`), and doctests are compatible with numpy 1.17 (:issue:`1120`) * Progress reports for repeated runs have been fixed (:issue:`1116`), thanks to Ronaldo Nunes for reporting the issue. * `SpikeGeneratorGroup` now correctly works with `restore` (:issue:`1084`), thanks to Tom Achache for reporting the issue. * An indexing problem in `PopulationRateMonitor` has been fixed (:issue:`1119`). * Handling of equations referring to ``-inf`` has been fixed (:issue:`1061`). * Long simulations recording more than ~2 billion data points no longer crash with a segmentation fault (:issue:`1136`), thanks to Rike-Benjamin Schuppner for reporting the issue. Backward-incompatible changes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * The fix for `~brian2.groups.group.Group.run_regularly` operations (:issue:`1009`, see above) entails a change in how objects are stored within `Network` objects. Previously, `Network.objects` stored a complete list of all objects, including objects such as `~brian2.groups.neurongroup.StateUpdater` that – often invisible to the user – are a part of major objects such as `NeuronGroup`. Now, `Network.objects` only stores the objects directly provided by the user (`NeuronGroup`, `Synapses`, `StateMonitor`, ...), the dependent objects (`~brian2.groups.neurongroup.StateUpdater`, `~brian2.groups.neurongroup.Thresholder`, ...) are taken into account at the time of the run. This might break code in some corner cases, e.g. when removing a `~brian2.groups.neurongroup.StateUpdater` from `Network.objects` via `Network.remove`. * The `brian2.hears` interface to Brian 1's ``brian.hears`` package has been deprecated. Infrastructure and documentation improvements ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * The same code base is used on Python 2 and Python 3 (:issue:`1073`). * The test framework uses ``pytest`` (:issue:`1127`). * We have adapoted a Code of Conduct (:issue:`1113`), thanks to Tapasweni Pathak for the suggestion. Contributions ~~~~~~~~~~~~~ Github code, documentation, and issue contributions (ordered by the number of contributions): * Marcel Stimberg (`@mstimberg `_) * Dan Goodman (`@thesamovar `_) * Vigneswaran Chandrasekaran (`@Vigneswaran-Chandrasekaran `_) * Moritz Orth (`@morth `_) * Tristan Stöber (`@tristanstoeber `_) * `@ulyssek `_ * Wilhelm Braun (`@wilhelmbraun `_) * `@flomlo `_ * Rike-Benjamin Schuppner (`@Debilski `_) * `@sdeiss `_ * Ben Evans (`@bdevans `_) * Tapasweni Pathak (`@tapaswenipathak `_) * `@jonathanoesterle `_ * Richard C Gerkin (`@rgerkin `_) * Christian Behrens (`@chbehrens `_) * Romain Brette (`@romainbrette `_) * XiaoquinNUDT (`@XiaoquinNUDT `_) * Dylan Muir (`@DylanMuir `_) * Aleksandra Teska (`@alTeska `_) * Felix Z. Hoffmann (`@felix11h `__) * `@baixiaotian63648995 `_ * Carlos de la Torre (`@c-torre `_) * Sam Mathias (`@sammosummo `_) * `@Marghepano `_ * Simon Brodeur (`@sbrodeur `_) * Alex Dimitrov (`@adimitr `_) Other contributions outside of github (ordered alphabetically, apologies to anyone we forgot...): * Ronaldo Nunes * Tom Achache Brian 2.2.2.1 ------------- This is a bug-fix release that fixes several bugs and adds a few minor new features. We recommend all users of Brian 2 to upgrade. As always, please report bugs or suggestions to the github bug tracker (https://github.com/brian-team/brian2/issues) or to the brian-development mailing list (brian-development@googlegroups.com). [Note that the original upload of this release was version 2.2.2, but due to a mistake in the released archive, it has been uploaded again as version 2.2.2.1] Selected improvements and bug fixes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * Fix an issue with the synapses generator syntax (:issue:`1037`). * Fix an incorrect error when using a `SpikeGeneratorGroup` with a long period (:issue:`1041`). Thanks to Kévin Cuallado-Keltsch for reporting this issue. * Improve the performance of `SpikeGeneratorGroup` by avoiding a conversion from time to integer time step (:issue:`1043`). This time step is now also available to user code as ``t_in_timesteps``. * Function definitions for weave/Cython/C++ standalone can now declare additional header files and libraries. They also support a new ``sources`` argument to use a function definition from an external file. See the :doc:`../advanced/functions` documentation for details. * For convenience, single-neuron subgroups can now be created with a single index instead of with a slice (e.g. ``neurongroup[3]`` instead of ``neurongroup[3:4]``). * Fix an issue when ``-inf`` is used in an equation (:issue:`1061`). Contributions ~~~~~~~~~~~~~ Github code, documentation, and issue contributions (ordered by the number of contributions): * Marcel Stimberg (`@mstimberg `_) * Dan Goodman (`@thesamovar `_) * Felix Z. Hoffmann (`@Felix11H `_) * `@wjx0914 `_ * Kévin Cuallado-Keltsch (`@kevincuallado `_) * Romain Cazé (`@rcaze `_) * Daphne (`@daphn3cor `_) * Erik (`@parenthetical-e `_) * `@RahulMaram `_ * Eghbal Hosseini (`@eghbalhosseini `_) * Martino Sorbaro (`@martinosorb `_) * Mihir Vaidya (`@MihirVaidya94 `_) * `@hellolingling `_ * Volodimir Slobodyanyuk (`@vslobody `_) * Peter Duggins (`@psipeter `_) Brian 2.2.1 ----------- This is a bug-fix release that fixes a few minor bugs and incompatibilites with recent versions of the dependencies. We recommend all users of Brian 2 to upgrade. As always, please report bugs or suggestions to the github bug tracker (https://github.com/brian-team/brian2/issues) or to the brian-development mailing list (brian-development@googlegroups.com). Selected improvements and bug fixes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * Work around problems with the latest version of ``py-cpuinfo`` on Windows (:issue:`990`, :issue:`1020`) and no longer require it for Linux and OS X. * Avoid warnings with newer versions of Cython (:issue:`1030`) and correctly build the Cython spike queue for Python 3.7 (:issue:`1026`), thanks to Fleur Zeldenrust and Ankur Sinha for reporting these issues. * Fix error messages for ``SyntaxError`` exceptions in jupyter notebooks (:issue:`#964`). Dependency and packaging changes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * Conda packages in `conda-forge `_ are now avaible for Python 3.7 (but no longer for Python 3.5). * Linux and OS X no longer depend on the ``py-cpuinfo`` package. * Source packages on `pypi `_ now require a recent Cython version for installation. Contributions ~~~~~~~~~~~~~ Github code, documentation, and issue contributions (ordered by the number of contributions): * Marcel Stimberg (`@mstimberg `_) * Dan Goodman (`@thesamovar `_) * Christopher (`@Chris-Currin `_) * Peter Duggins (`@psipeter `_) * Paola Suárez (`@psrmx `_) * Ankur Sinha (`@sanjayankur31 `_) * `@JingjinW `_ * Denis Alevi (`@denisalevi `_) * `@lemonade117 `_ * `@wjx0914 `_ * Sven Leach (`@SvennoNito `_) * svadams (`@svadams `_) * `@ghaessig `_ * Varshith Sreeramdass (`@varshiths `_) Brian 2.2 --------- This releases fixes a number of important bugs and comes with a number of performance improvements. It also makes sure that simulation no longer give platform-dependent results for certain corner cases that involve the division of integers. These changes can break backwards-compatiblity in certain cases, see below. We recommend all users of Brian 2 to upgrade. As always, please report bugs or suggestions to the github bug tracker (https://github.com/brian-team/brian2/issues) or to the brian-development mailing list (brian-development@googlegroups.com). Selected improvements and bug fixes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * Divisions involving integers now use floating point division, independent of Python version and code generation target. The ``//`` operator can now used in equations and expressions to denote flooring division (:issue:`984`). * Simulations can now use single precision instead of double precision floats in simulations (:issue:`981`, :issue:`1004`). This is mostly intended for use with GPU code generation targets. * The `~brian2.core.functions.timestep`, introduced in version 2.1.3, was further optimized for performance, making the refractoriness calculation faster (:issue:`996`). * The ``lastupdate`` variable is only automatically added to synaptic models when event-driven equations are used, reducing the memory and performance footprint of simple synaptic models (:issue:`1003`). Thanks to Denis Alevi for bringing this up. * A ``from brian2 import *`` imported names unrelated to Brian, and overwrote some Python builtins such as ``dir`` (:issue:`969`). Now, fewer names are imported (but note that this still includes numpy and plotting tools: :doc:`../user/import`). * The ``exponential_euler`` state updater is no longer failing for systems of equations with differential equations that have trivial, constant right-hand-sides (:issue:`1010`). Thanks to Peter Duggins for making us aware of this issue. Backward-incompatible changes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * Code that divided integers (e.g. ``N/10``) with a C-based code generation target, or with the ``numpy`` target on Python 2, will now use floating point division instead of flooring division (i.e., Python 3 semantics). A warning will notify the user of this change, use either the flooring division operator (``N//10``), or the ``int`` function (``int(N/10)``) to make the expression unambiguous. * Code that directly referred to the ``lastupdate`` variable in synaptic statements, without using any event-driven variables, now has to manually add ``lastupdate : second`` to the equations and update the variable at the end of ``on_pre`` and/or ``on_post`` with ``lastupdate = t``. * Code that relied on ``from brian2 import *`` also importing unrelated names such as ``sympy``, now has to import such names explicitly. Documentation improvements ~~~~~~~~~~~~~~~~~~~~~~~~~~ * Various small fixes and additions (e.g. installation instructions, available functions, fixes in examples) * A new example, :doc:`Izhikevich 2007 <../examples/frompapers.Izhikevich_2007>`, provided by `Guillaume Dumas `_. Contributions ~~~~~~~~~~~~~ Github code, documentation, and issue contributions (ordered by the number of contributions): * Marcel Stimberg (`@mstimberg `_) * Dan Goodman (`@thesamovar `_) * Denis Alevi (`@denisalevi `_) * Thomas Nowotny (`@tnowotny `_) * `@neworderofjamie `_ * Paul Brodersen (`@paulbrodersen `_) * `@matrec4 `_ * svadams (`@svadams `_) * XiaoquinNUDT (`@XiaoquinNUDT `_) * Peter Duggins (`@psipeter `_) * `@nh17937 `_ * Patrick Nave (`@pnave95 `_) * `@AI-pha `_ * Guillaume Dumas (`@deep-introspection `_) * `@godelicbach `_ * `@galharth `_ Brian 2.1.3.1 ------------- This is a bug-fix release that fixes two bugs in the recent 2.1.3 release: * Fix an inefficiency in the newly introduced `~brian2.core.functions.timestep` function when using the ``numpy`` target (:issue:`965`) * Fix inefficiencies in the unit system that could lead to slow operations and high memory use (:issue:`967`). Thanks to Kaustab Pal for making us aware of the issue. Brian 2.1.3 ----------- This is a bug-fix release that fixes a number of important bugs (see below), but does not introduce any new features. We recommend all users of Brian 2 to upgrade. As always, please report bugs or suggestions to the github bug tracker (https://github.com/brian-team/brian2/issues) or to the brian-development mailing list (brian-development@googlegroups.com). Selected improvements and bug fixes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - The Cython cache on disk now uses significantly less space by deleting unnecessary source files (set the `codegen.runtime.cython.delete_source_files` preference to ``False`` if you want to keep these files for debugging). In addition, a warning will be given when the Cython or weave cache exceeds a configurable size (`codegen.max_cache_dir_size`). The `~brian2.__init__.clear_cache` function is provided to delete files from the cache (:issue:`914`). - The C++ standalone mode now respects the ``profile`` option and therefore no longer collects profiling information by default. This can speed up simulations in certain cases (:issue:`935`). - The exact number of time steps that a neuron stays in the state of refractoriness after a spike could vary by up to one time step when the requested refractory time was a multiple of the simulation time step. With this fix, the number of time steps is ensured to be as expected by making use of a new `~brian2.core.functions.timestep` function that avoids floating point rounding issues (:issue:`949`, first reported by `@zhouyanasd `_ in issue :issue:`943`). - When `restore` was called twice for a network, spikes that were not yet delivered to their target were not restored correctly (:issue:`938`, reported by `@zhouyanasd `_). - `SpikeGeneratorGroup` now uses a more efficient method for sorting spike indices and times, leading to a much faster preparation time for groups that store many spikes (:issue:`948`). - Fix a memory leak in `TimedArray` (:issue:`923`, reported by Wilhelm Braun). - Fix an issue with summed variables targetting subgroups (:issue:`925`, reported by `@AI-pha `_). - Fix the use of `~brian2.groups.group.Group.run_regularly` on subgroups (:issue:`922`, reported by `@AI-pha `_). - Improve performance for `SpatialNeuron` by removing redundant computations (:issue:`910`, thanks to `Moritz Augustin `_ for making us aware of the issue). - Fix linked variables that link to scalar variables (:issue:`916`) - Fix warnings for numpy 1.14 and avoid compilation issues when switching between versions of numpy (:issue:`913`) - Fix problems when using logical operators in code generated for the numpy target which could lead to issues such as wrongly connected synapses (:issue:`901`, :issue:`900`). Backward-incompatible changes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - No longer allow ``delay`` as a variable name in a synaptic model to avoid ambiguity with respect to the synaptic delay. Also no longer allow access to the ``delay`` variable in synaptic code since there is no way to distinguish between pre- and post-synaptic delay (:issue:`927`, reported by Denis Alevi). - Due to the changed handling of refractoriness (see bug fixes above), simulations that make use of refractoriness will possibly no longer give exactly the same results. The preference `legacy.refractory_timing` can be set to ``True`` to reinstate the previous behaviour. Infrastructure and documentation improvements ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - From this version on, conda packages will be available on `conda-forge `_. For a limited time, we will copy over packages to the ``brian-team`` channel as well. - Conda packages are no longer tied to a specific numpy version (PR :issue:`954`) - New example (:doc:`Brunel & Wang, 2001 <../examples/frompapers.Brunel_Wang_2001>`) contributed by `Teo Stocco `_ and `Alex Seeholzer `_. Contributions ~~~~~~~~~~~~~ Github code, documentation, and issue contributions (ordered by the number of contributions): * Marcel Stimberg (`@mstimberg `_) * Dan Goodman (`@thesamovar `_) * Teo Stocco (`@zifeo `_) * Dylan Muir (`@DylanMuir `_) * scarecrow (`@zhouyanasd `_) * `@fuadfukhasyi `_ * Aditya Addepalli (`@Dyex719 `_) * Kapil kumar (`@kapilkd13 `_) * svadams (`@svadams `_) * Vafa Andalibi (`@Vafa-Andalibi `_) * Sven Leach (`@SvennoNito `_) * `@matrec4 `_ * `@jarishna `_ * `@AI-pha `_ * `@xdzhangxuejun `_ * Denis Alevi (`@denisalevi `_) * Paul Pfeiffer (`@pfeffer90 `_) * Romain Brette (`@romainbrette `_) * `@hustyanghui `_ * Adrien F. Vincent (`@afvincent `_) * `@ckemere `_ * `@evearmstrong `_ * Paweł Kopeć (`@pawelkopec `_) * Moritz Augustin (`@moritzaugustin `_) * Bart (`@louwers `_) * `@amarsdd `_ * `@ttxtea `_ * Maria Cervera (`@MariaCervera `_) * ouyangxinrong (`@longzhixin `_) Other contributions outside of github (ordered alphabetically, apologies to anyone we forgot...): * Wilhelm Braun Brian 2.1.2 ----------- This is another bug fix release that fixes a major bug in `Equations`' substitution mechanism (:issue:`896`). Thanks to Teo Stocco for reporting this issue. Brian 2.1.1 ----------- This is a bug fix release that re-activates parts of the caching mechanism for code generation that had been erroneously deactivated in the previous release. Brian 2.1 --------- This release introduces two main new features: a new "GSL integration" mode for differential equation that offers to integrate equations with variable-timestep methods provided by the GNU Scientific Library, and caching for the run preparation phase that can significantly speed up simulations. It also comes with a newly written tutorial, as well as additional documentation and examples. As always, please report bugs or suggestions to the github bug tracker (https://github.com/brian-team/brian2/issues) or to the brian-development mailing list (brian-development@googlegroups.com). New features ~~~~~~~~~~~~ * New numerical integration methods with variable time-step integration, based on the GNU Scientific Library (see :ref:`numerical_integration`). Contributed by `Charlee Fletterman `_, supported by 2017's `Google Summer of Code `_ program. * New caching mechanism for the code generation stage (application of numerical integration algorithms, analysis of equations and statements, etc.), reducing the preparation time before the actual run, in particular for simulations with multiple `run` statements. Selected improvements and bug fixes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * Fix a rare problem in Cython code generation caused by missing type information (:issue:`893`) * Fix warnings about improperly closed files on Python 3.6 (:issue:`892`; reported and fixed by `Teo Stocco `_) * Fix an error when using numpy integer types for synaptic indexing (:issue:`888`) * Fix an error in numpy codegen target, triggered when assigning to a variable with an unfulfilled condition (:issue:`887`) * Fix an error when repeatedly referring to subexpressions in multiline statements (:issue:`880`) * Shorten long arrays in warning messages (:issue:`874`) * Enable the use of ``if`` in the shorthand generator syntax for `Synapses.connect` (:issue:`873`) * Fix the meaning of ``i`` and ``j`` in synapses connecting to/from other synapses (:issue:`854`) Backward-incompatible changes and deprecations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * In C++ standalone mode, information about the number of synapses and spikes will now only be displayed when built with ``debug=True`` (:issue:`882`). * The ``linear`` state updater has been renamed to ``exact`` to avoid confusion (:issue:`877`). Users are encouraged to use ``exact``, but the name ``linear`` is still available and does not raise any warning or error for now. * The ``independent`` state updater has been marked as deprecated and might be removed in future versions. Infrastructure and documentation improvements ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * A new, more advanced, :doc:`tutorial <../resources/tutorials/3-intro-to-brian-simulations>` "about managing the slightly more complicated tasks that crop up in research problems, rather than the toy examples we’ve been looking at so far." * Additional documentation on :doc:`../advanced/custom_events` and :doc:`../user/converting_from_integrated_form` (including example code for typical synapse models). * New example code reproducing published findings (:doc:`Platkiewicz and Brette, 2011 <../examples/frompapers.Platkiewicz_Brette_2011>`; :ref:`Stimberg et al., 2018 `) * Fixes to the sphinx documentation creation process, the documentation can be downloaded as a PDF once again (705 pages!) * Conda packages now have support for numpy 1.13 (but support for numpy 1.10 and 1.11 has been removed) Contributions ~~~~~~~~~~~~~ Github code, documentation, and issue contributions (ordered by the number of contributions): * Marcel Stimberg (`@mstimberg `_) * Charlee Fletterman (`@CharleeSF `_) * Dan Goodman (`@thesamovar `_) * Teo Stocco (`@zifeo `_) * `@k47h4 `_ Other contributions outside of github (ordered alphabetically, apologies to anyone we forgot...): * Chaofei Hong * Lucas ("lucascdst") Brian 2.0.2.1 ------------- Fixes a bug in the tutorials' HMTL rendering on readthedocs.org (code blocks were not displayed). Thanks to Flora Bouchacourt for making us aware of this problem. Brian 2.0.2 ----------- New features ~~~~~~~~~~~~ * `molar` and `liter` (as well as `litre`, scaled versions of the former, and a few useful abbreviations such as `mM`) have been added as new units (:issue:`574`). * A new module `brian2.units.constants` provides physical constants such as the Faraday constants or the gas constant (see :ref:`constants` for details). * `SpatialNeuron` now supports non-linear membrane currents (e.g. Goldman–Hodgkin–Katz equations) by linearizing them with respect to v. * Multi-compartmental models can access the capacitive current via `Ic` in their equations (:issue:`677`) * A new function `scheduling_summary` that displays information about the scheduling of all objects (see :ref:`scheduling` for details). * Introduce a new preference to pass arguments to the ``make``/``nmake`` command in C++ standalone mode (`devices.cpp_standalone.extra_make_args_unix` for Linux/OS X and `devices.cpp_standalone.extra_make_args_windows` for Windows). For Linux/OS X, this enables parallel compilation by default. * Anaconda packages for Brian 2 are now available for Python 3.6 (but Python 3.4 support has been removed). Selected improvements and bug fixes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * Work around low performance for certain C++ standalone simulations on Linux, due to a bug in glibc (see :issue:`803`). Thanks to Oleg Strikov (`@xj8z `_) for debugging this issue and providing the workaround that is now in use. * Make exact integration of ``event-driven`` synaptic variables use the ``linear`` numerical integration algorithm (instead of ``independent``), fixing rare occasions where integration failed despite the equations being linear (:issue:`801`). * Better error messages for incorrect unit definitions in equations. * Various fixes for the internal representation of physical units and the unit registration system. * Fix a bug in the assignment of state variables in subtrees of `SpatialNeuron` (:issue:`822`) * Numpy target: fix an indexing error for a `SpikeMonitor` that records from a subgroup (:issue:`824`) * Summed variables targeting the same post-synaptic variable now raise an error (previously, only the one executed last was taken into account, see :issue:`766`). * Fix bugs in synapse generation affecting Cython (:issue:`781`) respectively numpy (:issue:`835`) * C++ standalone simulations with many objects no longer fail on Windows (:issue:`787`) Backwards-incompatible changes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * `celsius` has been removed as a unit, because it was ambiguous in its relation to `kelvin` and gave wrong results when used as an absolute temperature (and not a temperature difference). For temperature differences, you can directly replace `celsius` by `kelvin`. To convert an absolute temperature in degree Celsius to Kelvin, add the `zero_celsius` constant from `brian2.units.constants` (:issue:`817`). * State variables are no longer allowed to have names ending in ``_pre`` or ``_post`` to avoid confusion with references to pre- and post-synaptic variables in `Synapses` (:issue:`818`) Changes to default settings ~~~~~~~~~~~~~~~~~~~~~~~~~~~ * In C++ standalone mode, the ``clean`` argument now defaults to ``False``, meaning that ``make clean`` will not be executed by default before building the simulation. This avoids recompiling all files for unchanged simulations that are executed repeatedly. To return to the previous behaviour, specify ``clean=True`` in the ``device.build`` call (or in ``set_device`` if your script does not have an explicit ``device.build``). Contributions ~~~~~~~~~~~~~ Github code, documentation, and issue contributions (ordered by the number of contributions): * Marcel Stimberg (`@mstimberg `_) * Dan Goodman (`@thesamovar `_) * Thomas McColgan (`@phreeza `_) * Daan Sprenkels (`@dsprenkels `_) * Romain Brette (`@romainbrette `_) * Oleg Strikov (`@xj8z `_) * Charlee Fletterman (`@CharleeSF `_) * Meng Dong (`@whenov `_) * Denis Alevi (`@denisalevi `_) * Mihir Vaidya (`@MihirVaidya94 `_) * Adam (`@ffa `_) * Sourav Singh (`@souravsingh `_) * Nick Hale (`@nik849 `_) * Cody Greer (`@Cody-G `_) * Jean-Sébastien Dessureault (`@jsdessureault `_) * Michele Giugliano (`@mgiugliano `_) * Teo Stocco (`@zifeo `_) * Edward Betts (`@EdwardBetts `_) Other contributions outside of github (ordered alphabetically, apologies to anyone we forgot...): * Christopher Nolan * Regimantas Jurkus * Shailesh Appukuttan Brian 2.0.1 ----------- This is a bug-fix release that fixes a number of important bugs (see below), but does not introduce any new features. We recommend all users of Brian 2 to upgrade. As always, please report bugs or suggestions to the github bug tracker (https://github.com/brian-team/brian2/issues) or to the brian-development mailing list (brian-development@googlegroups.com). Improvements and bug fixes ~~~~~~~~~~~~~~~~~~~~~~~~~~ * Fix `PopulationRateMonitor` for recordings from subgroups (:issue:`772`) * Fix `SpikeMonitor` for recordings from subgroups (:issue:`777`) * Check that string expressions provided as the ``rates`` argument for `PoissonGroup` have correct units. * Fix compilation errors when multiple run statements with different ``report`` arguments are used in C++ standalone mode. * Several documentation updates and fixes Contributions ~~~~~~~~~~~~~ Code and documentation contributions (ordered by the number of commits): * Marcel Stimberg (`@mstimberg `_) * Dan Goodman (`@thesamovar `_) * Alex Seeholzer (`@flinz `_) * Meng Dong (`@whenov `_) Testing, suggestions and bug reports (ordered alphabetically, apologies to anyone we forgot...): * Myung Seok Shim * Pamela Hathway Brian 2.0 (changes since 1.4) ----------------------------- Major new features ~~~~~~~~~~~~~~~~~~ * Much more flexible model definitions. The behaviour of all model elements can now be defined by arbitrary equations specified in standard mathematical notation. * Code generation as standard. Behind the scenes, Brian automatically generates and compiles C++ code to simulate your model, making it much faster. * "Standalone mode". In this mode, Brian generates a complete C++ project tree that implements your model. This can be then be compiled and run entirely independently of Brian. This leads to both highly efficient code, as well as making it much easier to run simulations on non-standard computational hardware, for example on robotics platforms. * Multicompartmental modelling. * Python 2 and 3 support. New features ~~~~~~~~~~~~ * Installation should now be much easier, especially if using the Anaconda Python distribution. See :doc:`/introduction/install`. * Many improvements to `Synapses` which replaces the old ``Connection`` object in Brian 1. This includes: synapses that are triggered by non-spike events; synapses that target other synapses; huge speed improvements thanks to using code generation; new "generator syntax" when creating synapses is much more flexible and efficient. See :doc:`/user/synapses`. * New model definitions allow for much more flexible refractoriness. See :doc:`/user/refractoriness`. * `SpikeMonitor` and `StateMonitor` are now much more flexible, and cover a lot of what used to be covered by things like ``MultiStateMonitor``, etc. See :doc:`/user/recording`. * Multiple event types. In addition to the default ``spike`` event, you can create arbitrary events, and have these trigger code blocks (like reset) or synaptic events. See :doc:`/advanced/custom_events`. * New units system allows arrays to have units. This eliminates the need for a lot of the special casing that was required in Brian 1. See :doc:`/user/units`. * Indexing variable by condition, e.g. you might write ``G.v['x>0']`` to return all values of variable ``v`` in `NeuronGroup` ``G`` where the group's variable ``x>0``. See :ref:`state_variables`. * Correct numerical integration of stochastic differential equations. See :doc:`/user/numerical_integration`. * "Magic" `run` system has been greatly simplified and is now much more transparent. In addition, if there is any ambiguity about what the user wants to run, an erorr will be raised rather than making a guess. This makes it much safer. In addition, there is now a `store`/`restore` mechanism that simplifies restarting simulations and managing separate training/testing runs. See :doc:`/user/running`. * Changing an external variable between runs now works as expected, i.e. something like ``tau=1*ms; run(100*ms); tau=5*ms; run(100*ms)``. In Brian 1 this would have used ``tau=1*ms`` for both runs. More generally, in Brian 2 there is now better control over namespaces. See :doc:`/advanced/namespaces`. * New "shared" variables with a single value shared between all neurons. See :ref:`shared_variables`. * New `Group.run_regularly` method for a codegen-compatible way of doing things that used to be done with `network_operation` (which can still be used). See :ref:`regular_operations`. * New system for handling externally defined functions. They have to specify which units they accept in their arguments, and what they return. In addition, you can easily specify the implementation of user-defined functions in different languages for code generation. See :doc:`/advanced/functions`. * State variables can now be defined as integer or boolean values. See :doc:`/user/equations`. * State variables can now be exported directly to Pandas data frame. See :ref:`storing_state_variables`. * New generalised "flags" system for giving additional information when defining models. See :ref:`flags`. * `TimedArray` now allows for 2D arrays with arbitrary indexing. See :ref:`timed_arrays`. * Better support for using Brian in IPython/Jupyter. See, for example, `start_scope`. * New preferences system. See :doc:`/advanced/preferences`. * Random number generation can now be made reliably reproducible. See :doc:`/advanced/random`. * New profiling option to see which parts of your simulation are taking the longest to run. See :ref:`profiling`. * New logging system allows for more precise control. See :doc:`/advanced/logging`. * New ways of importing Brian for advanced Python users. See :doc:`/user/import`. * Improved control over the order in which objects are updated during a run. See :doc:`/advanced/scheduling`. * Users can now easily define their own numerical integration methods. See :doc:`/advanced/state_update`. * Support for parallel processing using the OpenMP version of standalone mode. Note that all Brian tests pass with this, but it is still considered to be experimental. See :ref:`openmp`. Backwards incompatible changes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ See :doc:`brian1_to_2/index`. Behind the scenes changes ~~~~~~~~~~~~~~~~~~~~~~~~~ * All user models are now passed through the code generation system. This allows us to be much more flexible about introducing new target languages for generated code to make use of non-standard computational hardware. See :doc:`/developer/codegen`. * New standalone/device mode allows generation of a complete project tree that can be compiled and built independently of Brian and Python. This allows for even more flexible use of Brian on non-standard hardware. See :doc:`/developer/devices`. * All objects now have a unique name, used in code generation. This can also be used to access the object through the `Network` object. Contributions ~~~~~~~~~~~~~ Full list of all Brian 2 contributors, ordered by the time of their first contribution: * Dan Goodman (`@thesamovar `_) * Marcel Stimberg (`@mstimberg `_) * Romain Brette (`@romainbrette `_) * Cyrille Rossant (`@rossant `_) * Victor Benichoux (`@victorbenichoux `_) * Pierre Yger (`@yger `_) * Werner Beroux (`@wernight `_) * Konrad Wartke (`@Kwartke `_) * Daniel Bliss (`@dabliss `_) * Jan-Hendrik Schleimer (`@ttxtea `_) * Moritz Augustin (`@moritzaugustin `_) * Romain Cazé (`@rcaze `_) * Dominik Krzemiński (`@dokato `_) * Martino Sorbaro (`@martinosorb `_) * Benjamin Evans (`@bdevans `_) Brian 2.0 (changes since 2.0rc3) -------------------------------- New features ~~~~~~~~~~~~ * A new flag ``constant over dt`` can be applied to subexpressions to have them only evaluated once per timestep (see :doc:`../user/models`). This flag is mandatory for stateful subexpressions, e.g. expressions using ``rand()`` or ``randn()``. (:issue:`720`, :issue:`721`) Improvements and bug fixes ~~~~~~~~~~~~~~~~~~~~~~~~~~ * Fix `EventMonitor.values` and `SpikeMonitor.spike_trains` to always return sorted spike/event times (:issue:`725`). * Respect the ``active`` attribute in C++ standalone mode (:issue:`718`). * More consistent check of compatible time and dt values (:issue:`730`). * Attempting to set a synaptic variable or to start a simulation with synapses without any preceding connect call now raises an error (:issue:`737`). * Improve the performance of coordinate calculation for `Morphology` objects, which previously made plotting very slow for complex morphologies (:issue:`741`). * Fix a bug in `SpatialNeuron` where it did not detect non-linear dependencies on v, introduced via point currents (:issue:`743`). Infrastructure and documentation improvements ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * An interactive demo, tutorials, and examples can now be run in an interactive jupyter notebook on the `mybinder `_ platform, without any need for a local Brian installation (:issue:`736`). Thanks to Ben Evans for the idea and help with the implementation. * A new extensive guide for converting Brian 1 simulations to Brian 2 user coming from Brian 1: :doc:`changes` * A re-organized :doc:`../user/index`, with clearer indications which information is important for new Brian users. Contributions ~~~~~~~~~~~~~ Code and documentation contributions (ordered by the number of commits): * Marcel Stimberg (`@mstimberg `_) * Dan Goodman (`@thesamovar `_) * Benjamin Evans (`@bdevans `_) Testing, suggestions and bug reports (ordered alphabetically, apologies to anyone we forgot...): * Chaofei Hong * Daniel Bliss * Jacopo Bono * Ruben Tikidji-Hamburyan Brian 2.0rc3 ------------ This is another "release candidate" for Brian 2.0 that fixes a range of bugs and introduces better support for random numbers (see below). We are getting close to the final Brian 2.0 release, the remaining work will focus on bug fixes, and better error messages and documentation. As always, please report bugs or suggestions to the github bug tracker (https://github.com/brian-team/brian2/issues) or to the brian-development mailing list (brian-development@googlegroups.com). New features ~~~~~~~~~~~~ * Brian now comes with its own `seed` function, allowing to seed the random number generator and thereby to make simulations reproducible. This function works for all code generation targets and in runtime and standalone mode. See :doc:`../advanced/random` for details. * Brian can now export/import state variables of a group or a full network to/from a `pandas `_ ``DataFrame`` and comes with a mechanism to extend this to other formats. Thanks to Dominik Krzemiński for this contribution (see :issue:`306`). Improvements and bug fixes ~~~~~~~~~~~~~~~~~~~~~~~~~~ * Use a Mersenne-Twister pseudorandom number generator in C++ standalone mode, replacing the previously used low-quality random number generator from the C standard library (see :issue:`222`, :issue:`671` and :issue:`706`). * Fix a memory leak in code running with the weave code generation target, and a smaller memory leak related to units stored repetitively in the `~brian2.units.fundamentalunits.UnitRegistry`. * Fix a difference of one timestep in the number of simulated timesteps between runtime and standalone that could arise for very specific values of dt and t (see :issue:`695`). * Fix standalone compilation failures with the most recent gcc version which defaults to C++14 mode (see :issue:`701`) * Fix incorrect summation in synapses when using the ``(summed)`` flag and writing to *pre*-synaptic variables (see :issue:`704`) * Make synaptic pathways work when connecting groups that define nested subexpressions, instead of failing with a cryptic error message (see :issue:`707`). Contributions ~~~~~~~~~~~~~ Code and documentation contributions (ordered by the number of commits): * Marcel Stimberg (`@mstimberg `_) * Dominik Krzemiński (`@dokato `_) * Dan Goodman (`@thesamovar `_) * Martino Sorbaro (`@martinosorb `_) Testing, suggestions and bug reports (ordered alphabetically, apologies to anyone we forgot...): * Craig Henriquez * Daniel Bliss * David Higgins * Gordon Erlebacher * Max Gillett * Moritz Augustin * Sami Abdul-Wahid Brian 2.0rc1 ------------ This is a bug fix release that we release only about two weeks after the previous release because that release introduced a bug that could lead to wrong integration of stochastic differential equations. Note that standard neuronal noise models were not affected by this bug, it only concerned differential equations implementing a "random walk". The release also fixes a few other issues reported by users, see below for more information. Improvements and bug fixes ~~~~~~~~~~~~~~~~~~~~~~~~~~ * Fix a regression from 2.0b4: stochastic differential equations without any non-stochastic part (e.g. ``dx/dt = xi/sqrt(ms)```) were not integrated correctly (see :issue:`686`). * Repeatedly calling `restore` (or `Network.restore`) no longer raises an error (see :issue:`681`). * Fix an issue that made `PoissonInput` refuse to run after a change of dt (see :issue:`684`). * If the ``rates`` argument of `PoissonGroup` is a string, it will now be evaluated at every time step instead of once at construction time. This makes time-dependent rate expressions work as expected (see :issue:`660`). Contributions ~~~~~~~~~~~~~ Code and documentation contributions (ordered by the number of commits): * Marcel Stimberg (`@mstimberg `_) Testing, suggestions and bug reports (ordered alphabetically, apologies to anyone we forgot...): * Cian O'Donnell * Daniel Bliss * Ibrahim Ozturk * Olivia Gozel Brian 2.0rc ----------- This is a release candidate for the final Brian 2.0 release, meaning that from now on we will focus on bug fixes and documentation, without introducing new major features or changing the syntax for the user. This release candidate itself *does* however change a few important syntax elements, see "Backwards-incompatible changes" below. As always, please report bugs or suggestions to the github bug tracker (https://github.com/brian-team/brian2/issues) or to the brian-development mailing list (brian-development@googlegroups.com). Major new features ~~~~~~~~~~~~~~~~~~ * New "generator syntax" to efficiently generate synapses (e.g. one-to-one connections), see :ref:`creating_synapses` for more details. * For synaptic connections with multiple synapses between a pair of neurons, the number of the synapse can now be stored in a variable, allowing its use in expressions and statements (see :ref:`creating_synapses`). * `Synapses` can now target other `Synapses` objects, useful for some models of synaptic modulation. * The `Morphology` object has been completely re-worked and several issues have been fixed. The new `Section` object allows to model a section as a series of truncated cones (see :ref:`creating_morphology`). * Scripts with a single `run` call, no longer need an explicit ``device.build()`` call to run with the C++ standalone device. A `set_device` in the beginning is enough and will trigger the ``build`` call after the run (see :ref:`cpp_standalone`). * All state variables within a `Network` can now be accessed by `Network.get_states` and `Network.set_states` and the `store`/`restore` mechanism can now store the full state of a simulation to disk. * Stochastic differential equations with multiplicative noise can now be integrated using the Euler-Heun method (``heun``). Thanks to Jan-Hendrik Schleimer for this contribution. * Error messages have been significantly improved: errors for unit mismatches are now much clearer and error messages triggered during the intialization phase point back to the line of code where the relevant object (e.g. a `NeuronGroup`) was created. * `PopulationRateMonitor` now provides a `~brian2.monitors.ratemonitor.PopulationRateMonitor.smooth_rate` method for a filtered version of the stored rates. Improvements and bug fixes ~~~~~~~~~~~~~~~~~~~~~~~~~~ * In addition to the new synapse creation syntax, sparse probabilistic connections are now created much faster. * The time for the initialization phase at the beginning of a `run` has been significantly reduced. * Multicompartmental simulations with a large number of compartments are now simulated more efficiently and are making better use of several processor cores when OpenMP is activated in C++ standalone mode. Thanks to Moritz Augustin for this contribution. * Simulations will use compiler settings that optimize performance by default. * Objects that have user-specified names are better supported for complex simulation scenarios (names no longer have to be unique at all times, but only across a network or across a standalone device). * Various fixes for compatibility with recent versions of numpy and sympy Important backwards-incompatible changes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * The argument names in `Synapses.connect` have changed and the first argument can no longer be an array of indices. To connect based on indices, use ``Synapses.connect(i=source_indices, j=target_indices)``. See :ref:`creating_synapses` and the documentation of `Synapses.connect` for more details. * The actions triggered by pre-synaptic and post-synaptic spikes are now described by the ``on_pre`` and ``on_post`` keyword arguments (instead of ``pre`` and ``post``). * The `Morphology` object no longer allows to change attributes such as length and diameter after its creation. Complex morphologies should instead be created using the `Section` class, allowing for the specification of all details. * `Morphology` objects that are defined with coordinates need to provide the start point (relative to the end point of the parent compartment) as the first coordinate. See :ref:`creating_morphology` for more details. * For simulations using the C++ standalone mode, no longer call `Device.build` (if using a single `run` call), or use `set_device` with ``build_on_run=False`` (see :ref:`cpp_standalone`). Infrastructure improvements ~~~~~~~~~~~~~~~~~~~~~~~~~~~ * Our test suite is now also run on Mac OS-X (on the `Travis CI `_ platform). Contributions ~~~~~~~~~~~~~ Code and documentation contributions (ordered by the number of commits): * Marcel Stimberg (`@mstimberg `_) * Dan Goodman (`@thesamovar `_) * Moritz Augustin (`@moritzaugustin `_) * Jan-Hendrik Schleimer (`@ttxtea `_) * Romain Cazé (`@rcaze `_) * Konrad Wartke (`@Kwartke `_) * Romain Brette (`@romainbrette `_) Testing, suggestions and bug reports (ordered alphabetically, apologies to anyone we forgot...): * Chaofei Hong * Kees de Leeuw * Luke Y Prince * Myung Seok Shim * Owen Mackwood * Github users: @epaxon, @flinz, @mariomulansky, @martinosorb, @neuralyzer, @oleskiw, @prcastro, @sudoankit Brian 2.0b4 ----------- This is the fourth (and probably last) beta release for Brian 2.0. This release adds a few important new features and fixes a number of bugs so we recommend all users of Brian 2 to upgrade. If you are a user new to Brian, we also recommend to directly start with Brian 2 instead of using the stable release of Brian 1. Note that the new recommended way to install Brian 2 is to use the Anaconda distribution and to install the Brian 2 conda package (see :doc:`install`). This is however still a Beta release, please report bugs or suggestions to the github bug tracker (https://github.com/brian-team/brian2/issues) or to the brian-development mailing list (brian-development@googlegroups.com). Major new features ~~~~~~~~~~~~~~~~~~ * In addition to the standard threshold/reset, groups can now define "custom events". These can be recorded with the new `EventMonitor` (a generalization of `SpikeMonitor`) and `Synapses` can connect to these events instead of the standard spike event. See :doc:`../advanced/custom_events` for more details. * `SpikeMonitor` and `EventMonitor` can now also record state variable values at the time of spikes (or custom events), thereby offering the functionality of ``StateSpikeMonitor`` from Brian 1. See :ref:`recording_variables_spike_time` for more details. * The code generation modes that interact with C++ code (weave, Cython, and C++ standalone) can now be more easily configured to work with external libraries (compiler and linker options, header files, etc.). See the documentation of the `~brian2.codegen.cpp_prefs` module for more details. Improvemements and bug fixes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * Cython simulations no longer interfere with each other when run in parallel (thanks to Daniel Bliss for reporting and fixing this). * The C++ standalone now works with scalar delays and the spike queue implementation deals more efficiently with them in general. * Dynamic arrays are now resized more efficiently, leading to faster monitors in runtime mode. * The spikes generated by a `SpikeGeneratorGroup` can now be changed between runs using the `~brian2.input.spikegeneratorgroup.SpikeGeneratorGroup.set_spikes` method. * Multi-step state updaters now work correctly for non-autonomous differential equations * `PoissonInput` now correctly works with multiple clocks (thanks to Daniel Bliss for reporting and fixing this) * The `~brian2.groups.group.Group.get_states` method now works for `StateMonitor`. This method provides a convenient way to access all the data stored in the monitor, e.g. in order to store it on disk. * C++ compilation is now easier to get to work under Windows, see :doc:`install` for details. Important backwards-incompatible changes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * The ``custom_operation`` method has been renamed to `~brian2.groups.group.Group.run_regularly` and can now be called without the need for storing its return value. * `StateMonitor` will now by default record at the beginning of a time step instead of at the end. See :ref:`recording_variables_continuously` for details. * Scalar quantities now behave as python scalars with respect to in-place modifications (augmented assignments). This means that ``x = 3*mV; y = x; y += 1*mV`` will no longer increase the value of the variable ``x`` as well. Infrastructure improvements ~~~~~~~~~~~~~~~~~~~~~~~~~~~ * We now provide conda packages for Brian 2, making it very easy to install when using the Anaconda distribution (see :doc:`install`). Contributions ~~~~~~~~~~~~~ Code and documentation contributions (ordered by the number of commits): * Marcel Stimberg (`@mstimberg `_) * Dan Goodman (`@thesamovar `_) * Daniel Bliss (`@dabliss `_) * Romain Brette (`@romainbrette `_) Testing, suggestions and bug reports (ordered alphabetically, apologies to everyone we forgot...): * Daniel Bliss * Damien Drix * Rainer Engelken * Beatriz Herrera Figueredo * Owen Mackwood * Augustine Tan * Ot de Wiljes Brian 2.0b3 ----------- This is the third beta release for Brian 2.0. This release does not add many new features but it fixes a number of important bugs so we recommend all users of Brian 2 to upgrade. If you are a user new to Brian, we also recommend to directly start with Brian 2 instead of using the stable release of Brian 1. This is however still a Beta release, please report bugs or suggestions to the github bug tracker (https://github.com/brian-team/brian2/issues) or to the brian-development mailing list (brian-development@googlegroups.com). Major new features ~~~~~~~~~~~~~~~~~~ * A new `PoissonInput` class for efficient simulation of Poisson-distributed input events. Improvements ~~~~~~~~~~~~ * The order of execution for ``pre`` and ``post`` statements happending in the same time step was not well defined (it fell back to the default alphabetical ordering, executing ``post`` before ``pre``). It now explicitly specifies the ``order`` attribute so that ``pre`` gets executed before ``post`` (as in Brian 1). See the :doc:`../user/synapses` documentation for details. * The default schedule that is used can now be set via a preference (`core.network.default_schedule`). New automatically generated scheduling slots relative to the explicitly defined ones can be used, e.g. ``before_resets`` or ``after_synapses``. See :ref:`scheduling` for details. * The scipy_ package is no longer a dependency (note that weave_ for compiled C code under Python 2 is now available in a separate package). Note that multicompartmental models will still benefit from the scipy_ package if they are simulated in pure Python (i.e. with the ``numpy`` code generation target) -- otherwise Brian 2 will fall back to a numpy-only solution which is significantly slower. Important bug fixes ~~~~~~~~~~~~~~~~~~~ * Fix `SpikeGeneratorGroup` which did not emit all the spikes under certain conditions for some code generation targets (:issue:`429`) * Fix an incorrect update of pre-synaptic variables in synaptic statements for the ``numpy`` code generation target (:issue:`435`). * Fix the possibility of an incorrect memory access when recording a subgroup with `SpikeMonitor` (:issue:`454`). * Fix the storing of results on disk for C++ standalone on Windows -- variables that had the same name when ignoring case (e.g. ``i`` and ``I``) where overwriting each other (:issue:`455`). Infrastructure improvements ~~~~~~~~~~~~~~~~~~~~~~~~~~~ * Brian 2 now has a chat room on gitter_: https://gitter.im/brian-team/brian2 * The sphinx documentation can now be built from the release archive file * After a big cleanup, all files in the repository have now simple LF line endings (see https://help.github.com/articles/dealing-with-line-endings/ on how to configure your own machine properly if you want to contribute to Brian). .. _scipy: http://scipy.org .. _weave: https://pypi.python.org/pypi/weave .. _gitter: http://gitter.im Contributions ~~~~~~~~~~~~~ Code and documentation contributions (ordered by the number of commits): * Marcel Stimberg (`@mstimberg `_) * Dan Goodman (`@thesamovar `_) * Konrad Wartke (`@kwartke `_) Testing, suggestions and bug reports (ordered alphabetically, apologies to everyone we forgot...): * Daniel Bliss * Owen Mackwood * Ankur Sinha * Richard Tomsett Brian 2.0b2 ----------- This is the second beta release for Brian 2.0, we recommend all users of Brian 2 to upgrade. If you are a user new to Brian, we also recommend to directly start with Brian 2 instead of using the stable release of Brian 1. This is however still a Beta release, please report bugs or suggestions to the github bug tracker (https://github.com/brian-team/brian2/issues) or to the brian-development mailing list (brian-development@googlegroups.com). Major new features ~~~~~~~~~~~~~~~~~~ * Multi-compartmental simulations can now be run using the :ref:`cpp_standalone` mode (this is not yet well-tested, though). * The implementation of `TimedArray` now supports two-dimensional arrays, i.e. different input per neuron (or synapse, etc.), see :ref:`timed_arrays` for details. * Previously, not setting a code generation target (using the `codegen.target` preference) would mean that the ``numpy`` target was used. Now, the default target is ``auto``, which means that a compiled language (``weave`` or ``cython``) will be used if possible. See :doc:`../user/computation` for details. * The implementation of `SpikeGeneratorGroup` has been improved and it now supports a ``period`` argument to repeatedly generate a spike pattern. Improvements ~~~~~~~~~~~~ * The selection of a numerical algorithm (if none has been specified by the user) has been simplified. See :ref:`numerical_integration` for details. * Expressions that are shared among neurons/synapses are now updated only once instead of for every neuron/synapse which can lead to performance improvements. * On Windows, The Microsoft Visual C compiler is now supported in the ``cpp_standalone`` mode, see the respective notes in the :doc:`install` and :doc:`../user/computation` documents. * Simulation runs (using the standard "runtime" device) now collect profiling information. See :ref:`profiling` for details. Infrastructure and documentation improvements ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * :doc:`Tutorials for beginners <../resources/tutorials/index>` in the form of ipython notebooks (currently only covering the basics of neurons and synapses) are now available. * The :doc:`../examples/index` in the documentation now include the images they generated. Several examples have been adapted from Brian 1. * The code is now automatically tested on Windows machines, using the `appveyor `_ service. This complements the Linux testing on `travis `_. * Using a version of a dependency (e.g. sympy) that we don't support will now raise an error when you import ``brian2`` -- see :ref:`dependency_checks` for more details. * Test coverage for the ``cpp_standalone`` mode has been significantly increased. Important bug fixes ~~~~~~~~~~~~~~~~~~~ * The preparation time for complicated equations has been significantly reduced. * The string representation of small physical quantities has been corrected (:issue:`361`) * Linking variables from a group of size 1 now works correctly (:issue:`383`) Contributions ~~~~~~~~~~~~~ Code and documentation contributions (ordered by the number of commits): * Marcel Stimberg (`@mstimberg `_) * Dan Goodman (`@thesamovar `_) * Romain Brette (`@romainbrette `_) * Pierre Yger (`@yger `_) Testing, suggestions and bug reports (ordered alphabetically, apologies to everyone we forgot...): * Conor Cox * Gordon Erlebacher * Konstantin Mergenthaler Brian 2.0beta ------------- This is the first beta release for Brian 2.0 and the first version of Brian 2.0 we recommend for general use. From now on, we will try to keep changes that break existing code to a minimum. If you are a user new to Brian, we'd recommend to start with the Brian 2 beta instead of using the stable release of Brian 1. This is however still a Beta release, please report bugs or suggestions to the github bug tracker (https://github.com/brian-team/brian2/issues) or to the brian-development mailing list (brian-development@googlegroups.com). Major new features ~~~~~~~~~~~~~~~~~~ * New classes `Morphology` and `SpatialNeuron` for the simulation of :doc:`../user/multicompartmental` * A temporary "bridge" for ``brian.hears`` that allows to use its Brian 1 version from Brian 2 (:doc:`brian1_to_2/brian1hears_bridge`) * Cython is now a new code generation target, therefore the performance benefits of compiled code are now also available to users running simulations under Python 3.x (where ``scipy.weave`` is not available) * Networks can now store their current state and return to it at a later time, e.g. for simulating multiple trials starting from a fixed network state (:ref:`continue_repeat`) * C++ standalone mode: multiple processors are now supported via OpenMP (:ref:`openmp`), although this code has not yet been well tested so may be inaccurate. * C++ standalone mode: after a run, state variables and monitored values can be loaded from disk transparently. Most scripts therefore only need two additional lines to use standalone mode instead of Brian's default runtime mode (:ref:`cpp_standalone`). Syntax changes ~~~~~~~~~~~~~~ * The syntax and semantics of everything around simulation time steps, clocks, and multiple runs have been cleaned up, making ``reinit`` obsolete and also making it unnecessary for most users to explicitly generate `Clock` objects -- instead, a ``dt`` keyword can be specified for objects such as `NeuronGroup` (:doc:`../user/running`) * The ``scalar`` flag for parameters/subexpressions has been renamed to ``shared`` * The "unit" for boolean variables has been renamed from ``bool`` to ``boolean`` * C++ standalone: several keywords of `CPPStandaloneDevice.build ` have been renamed * The preferences are now accessible via ``prefs`` instead of ``brian_prefs`` * The ``runner`` method has been renamed to `~brian2.groups.group.Group.custom_operation` Improvements ~~~~~~~~~~~~ * Variables can now be linked across `NeuronGroup`\ s (:ref:`linked_variables`) * More flexible progress reporting system, progress reporting also works in the C++ standalone mode (:ref:`progress_reporting`) * State variables can be declared as ``integer`` (:ref:`equation_strings`) Bug fixes ~~~~~~~~~ 57 github issues have been closed since the alpha release, of which 26 had been labeled as bugs. We recommend all users of Brian 2 to upgrade. Contributions ~~~~~~~~~~~~~ Code and documentation contributions (ordered by the number of commits): * Marcel Stimberg (`@mstimberg `_) * Dan Goodman (`@thesamovar `_) * Romain Brette (`@romainbrette `_) * Pierre Yger (`@yger `_) * Werner Beroux (`@wernight `_) Testing, suggestions and bug reports (ordered alphabetically, apologies to everyone we forgot…): * Guillaume Bellec * Victor Benichoux * Laureline Logiaco * Konstantin Mergenthaler * Maurizio De Pitta * Jan-Hendrick Schleimer * Douglas Sterling * Katharina Wilmes brian2-2.5.4/docs_sphinx/introduction/scripts.rst000066400000000000000000000121061445201106100221540ustar00rootroot00000000000000Running Brian scripts ===================== Brian scripts are standard Python scripts, and can therefore be run in the same way. For interactive, explorative work, you might want to run code in a jupyter notebook or in an ipython shell; for running finished code, you might want to execute scripts through the standard Python interpreter; finally, for working on big projects spanning multiple files, a dedicated integrated development environment for Python could be a good choice. We will briefly describe all these approaches and how they relate to Brian's examples and tutorial that are part of this documentation. Note that none of these approaches are specific to Brian, so you can also search for more information in any of the resources listed on the `Python website `_. .. contents:: :local: :depth: 1 Jupyter notebook ---------------- The Jupyter Notebook is an open-source web application that allows you to create and share documents that contain live code, equations, visualizations and narrative text. (from `jupyter.org `_) Jupyter notebooks are a great tool to run Brian code interactively, and include the results of the simulations, as well as additional explanatory text in a common document. Such documents have the file ending ``.ipynb``, and in Brian we use this format to store the :doc:`../resources/tutorials/index`. These files can be displayed by github (see e.g. `the first Brian tutorial `_), but in this case you can only see them as a static website, not edit or execute any of the code. To make the full use of such notebooks, you have to run them using the jupyter infrastructure. The easiest option is to use the free `mybinder.org `_ web service, which allows you to try out Brian without installing it on your own machine. Links to run the tutorials on this infrastructure are provided as *"launch binder"* buttons on the :doc:`../resources/tutorials/index` page, and also for each of the :doc:`../examples/index` at the top of the respective page (e.g. :doc:`../examples/COBAHH`). To run notebooks on your own machine, you need an installation of the jupyter notebook software on your own machine, as well as Brian itself (see the :doc:`install` instructions for details). To open an existing notebook, you have to download it to your machine. For the Brian tutorials, you find the necessary links on the :doc:`../resources/tutorials/index` page. When you have downloaded/installed everything necessary, you can start the jupyter notebook from the command line (using Terminal on OS X/Linux, Command Prompt on Windows):: jupyter notebook this will open the "Notebook Dashboard" in your default browser, from which you can either open an existing notebook or create a new one. In the notebook, you can then execute individual "code cells" by pressing ``SHIFT+ENTER`` on your keyboard, or by pressing the play button in the toolbar. For more information, see the `jupyter notebook documentation `_. IPython shell ------------- An alternative to using the jupyter notebook is to use the interactive Python shell `IPython `_, which runs in the Terminal/Command Prompt. You can use it to directly type Python code interactively (each line will be executed as soon as you press ``ENTER``), or to run Python code stored in a file. Such files typically have the file ending ``.py``. You can either create it yourself in a text editor of your choice (e.g. by copying&pasting code from one of the :doc:`../examples/index`), or by downloading such files from places such as github (e.g. the `Brian examples `_), or `ModelDB `_. You can then run them from within IPython via:: %run filename.py Python interpreter ------------------ The most basic way to run Python code is to run it through the standard Python interpreter. While you can also use this interpreter interactively, it is much less convenient to use than the IPython shell or the jupyter notebook described above. However, if all you want to do is to run an existing Python script (e.g. one of the Brian :doc:`../examples/index`), then you can do this by calling:: python filename.py in a Terminal/Command Prompt. Integrated development environment (IDE) ---------------------------------------- Python is a widely used programming language, and is therefore support by a wide range of integrated development environments (IDE). Such IDEs provide features that are very convenient for developing complex projects, e.g. they integrate text editor and interactive Python console, graphical debugging tools, etc. Popular environments include `Spyder `_, `PyCharm `_, and `Visual Studio Code `_, for an extensive list see the `Python wiki `_. brian2-2.5.4/docs_sphinx/introduction/support.rst000066400000000000000000000044451445201106100222100ustar00rootroot00000000000000Support ======= If you are stuck with a problem using Brian, please do get in touch at our `community forum `__. You can save time by following this procedure when reporting a problem: 1. Do try to solve the problem on your own first. Read the documentation, including using the search feature, index and reference documentation. 2. Search the mailing list archives to see if someone else already had the same problem. 3. Before writing, try to create a minimal example that reproduces the problem. You’ll get the fastest response if you can send just a handful of lines of code that show what isn’t working. .. _which_version: Which version of Brian am I using? ---------------------------------- When reporting problems, it is important to include the information what exact version of Brian you are using. The different install methods listed in :doc:`install` provide different mechanisms to get this information. For example, if you used ``conda`` for installing Brian, you can use ``conda list brian2``; if you used ``pip``, you can use ``pip show brian2``. A general method that works independent of the installation method is to ask the Brian package itself: .. code-block:: pycon >>> import brian2 >>> print(brian2.__version__) # doctest: +SKIP 2.4.2 This method also has the advantage that you can easily call it from the same environment (e.g. an IDE or a Jupyter Notebook) that you use when you execute Brian scripts. This helps avoiding mistakes where you think you use a specific version but in fact you use a different one. In such cases, it can also be helpful to look at Brian's ``__file__`` attribute: .. code-block:: pycon >>> print(brian2.__file__) # doctest: +SKIP /home/marcel/anaconda3/envs/brian2_test/lib/python3.9/site-packages/brian2/__init__.py In the above example, it shows that the ``brian2`` installation in the conda environment ``brian2_test`` is used. If you installed a :ref:`development version ` of Brian, then the version number will contain additional information: .. code-block:: pycon >>> print(brian2.__version__) # doctest: +SKIP 2.4.2.post408 The above means that the Brian version that is used has 408 additional commits that were added after the 2.4.2 release. brian2-2.5.4/docs_sphinx/resources/000077500000000000000000000000001445201106100172245ustar00rootroot00000000000000brian2-2.5.4/docs_sphinx/user/000077500000000000000000000000001445201106100161705ustar00rootroot00000000000000brian2-2.5.4/docs_sphinx/user/computation.rst000066400000000000000000000404271445201106100212730ustar00rootroot00000000000000Computational methods and efficiency ==================================== .. contents:: :local: :depth: 1 Brian has several different methods for running the computations in a simulation. The default mode is :ref:`runtime`, which runs the simulation loop in Python but compiles and executes the modules doing the actual simulation work (numerical integration, synaptic propagation, etc.) in a defined target language. Brian will select the best available target language automatically. On Windows, to ensure that you get the advantages of compiled code, read the instructions on installing a suitable compiler in :ref:`installation_cpp`. Runtime mode has the advantage that you can combine the computations performed by Brian with arbitrary Python code specified as `NetworkOperation`. The fact that the simulation is run in Python means that there is a (potentially big) overhead for each simulated time step. An alternative is to run Brian in with :ref:`cpp_standalone` -- this is in general faster (for certain types of simulations *much* faster) but cannot be used for all kinds of simulations. To enable this mode, add the following line after your Brian import, but before your simulation code:: set_device('cpp_standalone') For detailed control over the compilation process (both for runtime and standalone code generation), you can change the :ref:`compiler_settings` that are used. .. admonition:: The following topics are not essential for beginners. | .. _runtime: Runtime code generation ----------------------- Code generation means that Brian takes the Python code and strings in your model and generates code in one of several possible different languages which is then executed. The target language for this code generation process is set in the `codegen.target` preference. By default, this preference is set to ``'auto'``, meaning that it will choose the compiled language target if possible and fall back to Python otherwise (also raising a warning). The compiled language target is ``'cython'`` which needs the `Cython`_ package in addition to a working C++ compiler. If you want to chose a code generation target explicitly (e.g. because you want to get rid of the warning that only the Python fallback is available), set the preference to ``'numpy'`` or ``'cython'`` at the beginning of your script:: from brian2 import * prefs.codegen.target = 'numpy' # use the Python fallback See :doc:`../advanced/preferences` for different ways of setting preferences. .. _Cython: http://cython.org/ Caching ~~~~~~~ When you run code with ``cython`` for the first time, it will take some time to compile the code. For short simulations, this can make these targets to appear slow compared to the ``numpy`` target where such compilation is not necessary. However, the compiled code is stored on disk and will be re-used for later runs, making these simulations start faster. If you run many simulations with different code (e.g. Brian's :doc:`test suite <../developer/guidelines/testing>`), this code can take quite a bit of space on the disk. During the import of the ``brian2`` package, we check whether the size of the disk cache exceeds the value set by the `codegen.max_cache_dir_size` preference (by default, 1GB) and display a message if this is the case. You can clear the disk cache manually, or use the `~brian2.__init__.clear_cache` function, e.g. ``clear_cache('cython')``. .. note:: If you run simulations on parallel on a machine using the Network File System, see :ref:`this known issue `. .. _cpp_standalone: Standalone code generation -------------------------- Brian supports generating standalone code for multiple devices. In this mode, running a Brian script generates source code in a project tree for the target device/language. This code can then be compiled and run on the device, and modified if needed. At the moment, the only "device" supported is standalone C++ code. In some cases, the speed gains can be impressive, in particular for smaller networks with complicated spike propagation rules (such as STDP). To use the C++ standalone mode, you only have to make very small changes to your script. The exact change depends on whether your script has only a single `run` (or `Network.run`) call, or several of them: Single run call ~~~~~~~~~~~~~~~ At the beginning of the script, i.e. after the import statements, add:: set_device('cpp_standalone') The `CPPStandaloneDevice.build` function will be automatically called with default arguments right after the `run` call. If you need non-standard arguments then you can specify them as part of the `set_device` call:: set_device('cpp_standalone', directory='my_directory', debug=True) Multiple run calls ~~~~~~~~~~~~~~~~~~ At the beginning of the script, i.e. after the import statements, add:: set_device('cpp_standalone', build_on_run=False) After the last `run` call, call `device.build` explicitly:: device.build(directory='output', compile=True, run=True, debug=False) The `~CPPStandaloneDevice.build` function has several arguments to specify the output directory, whether or not to compile and run the project after creating it and whether or not to compile it with debugging support or not. Multiple builds ~~~~~~~~~~~~~~~ To run multiple full simulations (i.e. multiple ``device.build`` calls, not just multiple `run` calls as discussed above), you have to reinitialize the device again:: device.reinit() device.activate() Note that the device "forgets" about all previously set build options provided to `set_device` (most importantly the ``build_on_run`` option, but also e.g. the directory), you'll have to specify them as part of the `Device.activate` call. Also, `Device.activate` will reset the `defaultclock`, you'll therefore have to set its ``dt`` *after* the ``activate`` call if you want to use a non-default value. Limitations ~~~~~~~~~~~ Not all features of Brian will work with C++ standalone, in particular Python based network operations and some array based syntax such as ``S.w[0, :] = ...`` will not work. If possible, rewrite these using string based syntax and they should work. Also note that since the Python code actually runs as normal, code that does something like this may not behave as you would like:: results = [] for val in vals: # set up a network run() results.append(result) The current C++ standalone code generation only works for a fixed number of `~Network.run` statements, not with loops. If you need to do loops or other features not supported automatically, you can do so by inspecting the generated C++ source code and modifying it, or by inserting code directly into the main loop as described below. .. _standalone_variables: Variables ~~~~~~~~~ In standalone mode, code will only be executed when the simulation is run (after the `run` call by default, or after a call to `.Device.build`, if `set_device` has been called with ``build_on_run`` set to ``False``). This means that it is not possible to access state variables and synaptic connection indices in the Python script doing the set up of the model. For example, the following code would work fine in runtime mode, but raise a ``NotImplementedError`` in standalone mode:: neuron = NeuronGroup(10, 'v : volt') neuron.v = '-70*mV + rand()*10*mV' print(np.mean(neuron.v)) Sometimes, access is needed to make one variable depend on another variable for initialization. In such cases, it is often possible to circumvent the issue by using initialization with string expressions for both variables. For example, to set the initial membrane potential relative to a random leak reversal potential, the following code would work in runtime mode but fail in standalone mode:: neuron = NeuronGroup(10, 'dv/dt = -g_L*(v - E_L)/tau : volt') neuron.E_L = '-70*mV + rand()*10*mV' # E_L between -70mV and -60mV neuron.v = neuron.E_L # initial membrane potential equal to E_L Instead, you can initialize the variable `v` with a string expression, which means that standalone will execute it during the run when the value of `E_L` is available:: neuron = NeuronGroup(10, 'dv/dt = -g_L*(v - E_L)/tau : volt') neuron.E_L = '-70*mV + rand()*10*mV' # E_L between -70mV and -60mV neuron.v = 'E_L' # works both in runtime and standalone mode The same applies to synaptic indices. For example, if we want to set weights differently depending on the target index of a synapse, the following would work in runtime mode but fail in standalone mode, since the synaptic indices have not been determined yet:: neurons = NeuronGroup(10, '') synapses = Synapses(neurons, neurons, 'w : 1') synapses.connect(p=0.25) # Set weights to low values when targetting first five neurons and to high values otherwise synapses.w[:, :5] = 0.1 synapses.w[:, 5:] = 0.9 Again, this initialization can be replaced by string expressions, so that standalone mode can evaluate them in the generated code after synapse creation:: neurons = NeuronGroup(10, '') synapses = Synapses(neurons, neurons, 'w : 1') synapses.connect(p=0.25) # Set weights to low values when targetting first five neurons and to high values otherwise synapses.w['j < 5'] = 0.1 synapses.w['j >= 5'] = 0.9 Note that this limitation only applies if the variables or synapses have been initialized in ways that require the execution of code. If instead they are initialized with concrete values, they can be accessed in Python code even in standalone mode:: neurons = NeuronGroup(10, 'v : volt') neurons.v = -70*mV print(np.mean(neurons.v)) # works in standalone synapses = Synapses(neurons, neurons, 'w : 1') synapses.connect(i=[0, 2, 4, 6, 8], j=[1, 3, 5, 7, 9]) # works as well, since synaptic indices are known synapses.w[:, :5] = 0.1 synapses.w[:, 5:] = 0.9 In any case, state variables, synaptic indices, and monitored variables can be accessed using standard syntax *after* a run (with a few exceptions, e.g. string expressions for indexing). .. _openmp: Multi-threading with OpenMP ~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. warning:: OpenMP code has not yet been well tested and so may be inaccurate. When using the C++ standalone mode, you have the opportunity to turn on multi-threading, if your C++ compiler is compatible with OpenMP. By default, this option is turned off and only one thread is used. However, by changing the preferences of the codegen.cpp_standalone object, you can turn it on. To do so, just add the following line in your python script:: prefs.devices.cpp_standalone.openmp_threads = XX XX should be a positive value representing the number of threads that will be used during the simulation. Note that the speedup will strongly depend on the network, so there is no guarantee that the speedup will be linear as a function of the number of threads. However, this is working fine for networks with not too small timestep (dt > 0.1ms), and results do not depend on the number of threads used in the simulation. Custom code injection ~~~~~~~~~~~~~~~~~~~~~ It is possible to insert custom code directly into the generated code of a standalone simulation using a Device's `~.Device.insert_code` method:: device.insert_code(slot, code) ``slot`` can be one of ``main``, ``before_start``, ``after_start``, ``before_network_run``, ``after_network_run``, ``before_end`` and ``after_end``, which determines where the code is inserted. ``code`` is the code in the Device's language. Here is an example for the C++ Standalone Device:: device.insert_code('main', ''' cout << "Testing direct insertion of code." << endl; ''') For the C++ Standalone Device, all code is inserted into the ``main.cpp`` file, here into the ``main`` slot, referring to the main simulation function. This is a simplified version of this function in ``main.cpp``:: int main(int argc, char **argv) { // before_start brian_start(); // after_start {{main_lines}} // before_end brian_end(); // after_end return 0; } ``{{main_lines}}`` is replaced in the generated code with the actual simulation. Code inserted into the ``main`` slot will be placed within the ``{{main_lines}}``. ``brian_start`` allocates and initializes all arrays needed during the simulation and ``brian_end`` writes the results to disc and deallocates memory. Within the ``{{main_lines}}``, all ``Network`` objects defined in Python are created and run. Code inserted in the ``before/after_network_run`` slot will be inserted around the ``Network.run`` call, which starts the time loop. Note that if your Python script has multiple ``Network`` objects or multiple ``run`` calls, code in the ``before/after_network_run`` slot will be inserted around each ``Network.run`` call in the generated code. The code injection mechanism has been used for benchmarking experiments, see e.g. `here for Brian2CUDA benchmarks `_ or `here for Brian2GeNN benchmarks `_. .. _standalone_custom_build: Customizing the build process ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In standalone mode, a standard "make file" is used to orchestrate the compilation and linking. To provide additional arguments to the ``make`` command (respectively ``nmake`` on Windows), you can use the `devices.cpp_standalone.extra_make_args_unix` or `devices.cpp_standalone.extra_make_args_windows` preference. On Linux, this preference is by default set to ``['-j']`` to enable parallel compilation. Note that you can also use these arguments to overwrite variables in the make file, e.g. to use `clang `_ instead of the default `gcc `_ compiler:: prefs.devices.cpp_standalone.extra_make_args_unix += ['CC=clang++'] .. _compiler_settings: Cleaning up after a run ~~~~~~~~~~~~~~~~~~~~~~~ Standalone simulations store all results of a simulation (final state variable values and values stored in monitors) to disk. These results can take up quite significant amount of space, and you might therefore want to delete these results when you do not need them anymore. You can do this by using the device's `~.Device.delete` method:: device.delete() Be aware that deleting the data will make all access to state variables fail, including the access to values in monitors. You should therefore only delete the data after doing all analysis/plotting that you are interested in. By default, this function will delete both the generated code and the data, i.e. the full project directory. If you want to keep the code (which typically takes up little space compared to the results), exclude it from the deletion:: device.delete(code=False) If you added any additional files to the project directory manually, these will not be deleted by default. To delete the full directory regardless of its content, use the ``force`` option:: device.delete(force=True) .. note:: When you initialize state variables with concrete values (and not with a string expression), they will be stored to disk from your Python script and loaded from disk at the beginning of the standalone run. Since these values are necessary for the compiled binary file to run, they are considered "code" from the point of view of the `~.Device.delete` function. Compiler settings ----------------- If using C++ code generation (either via cython or standalone), the compiler settings can make a big difference for the speed of the simulation. By default, Brian uses a set of compiler settings that switches on various optimizations and compiles for running on the same architecture where the code is compiled. This allows the compiler to make use of as many advanced instructions as possible, but reduces portability of the generated executable (which is not usually an issue). If there are any issues with these compiler settings, for example because you are using an older version of the C++ compiler or because you want to run the generated code on a different architecture, you can change the settings by manually specifying the `codegen.cpp.extra_compile_args` preference (or by using `codegen.cpp.extra_compile_args_gcc` or `codegen.cpp.extra_compile_args_msvc` if you want to specify the settings for either compiler only). brian2-2.5.4/docs_sphinx/user/converting_from_integrated_form.rst000066400000000000000000000102161445201106100253540ustar00rootroot00000000000000.. _integrated_form: Converting from integrated form to ODEs ======================================= Brian requires models to be expressed as systems of first order ordinary differential equations, and the effect of spikes to be expressed as (possibly delayed) one-off changes. However, many neuron models are given in *integrated form*. For example, one form of the Spike Response Model (SRM; Gerstner and Kistler 2002) is defined as .. math:: V(t) = \sum_i w_i \sum_{t_i} \mathrm{PSP}(t-t_i)+V_\mathrm{rest} where :math:`V(t)` is the membrane potential, :math:`V_\mathrm{rest}` is the rest potential, :math:`w_i` is the synaptic weight of synapse :math:`i`, and :math:`t_i` are the timings of the spikes coming from synapse :math:`i`, and PSP is a postsynaptic potential function. An example PSP is the :math:`\alpha`-function :math:`\mathrm{PSP}(t)=(t/\tau)e^{-t/\tau}`. For this function, we could rewrite the equation above in the following ODE form: .. math:: \tau \frac{\mathrm{d}V}{\mathrm{d}t} & = V_\mathrm{rest}-V+g \\ \tau \frac{\mathrm{d}g}{\mathrm{d}t} &= -g \\ g &\leftarrow g+w_i\;\;\;\mbox{upon spike from synapse $i$} This could then be written in Brian as:: eqs = ''' dV/dt = (V_rest-V+g)/tau : 1 dg/dt = -g/tau : 1 ''' G = NeuronGroup(N, eqs, ...) ... S = Synapses(G, G, 'w : 1', on_pre='g += w') To see that these two formulations are the same, you first solve the problem for the case of a single synapse and a single spike at time 0. The initial conditions at :math:`t=0` will be :math:`V(0)=V_\mathrm{rest}`, :math:`g(0)=w`. To solve these equations, let's substitute :math:`s=t/\tau` and take derivatives with respect to :math:`s` instead of :math:`t`, set :math:`u=V-V_\mathrm{rest}`, and assume :math:`w=1`. This gives us the equations :math:`u^\prime=g-u`, :math:`g^\prime=-g` with initial conditions :math:`u(0)=0`, :math:`g(0)=1`. At this point, you can either consult a textbook on solving linear systems of differential equations, or just `plug this into Wolfram Alpha `_ to get the solution :math:`g(s)=e^{-s}`, :math:`u(s)=se^{-s}` which is equal to the PSP given above. Now we use the linearity of these differential equations to see that it also works when :math:`w\neq 0` and for summing over multiple spikes at different times. In general, to convert from integrated form to ODE form, see `Köhn and Wörgötter (1998) `_, `Sánchez-Montañás (2001) `_, and `Jahnke et al. (1999) `_. However, for some simple and widely used types of synapses, use the list below. In this list, we assume synapses are postsynaptic potentials, but you can replace :math:`V(t)` with a current or conductance for postsynaptic currents or conductances. In each case, we give the Brian code with unitless variables, where ``eqs`` is the differential equations for the target `NeuronGroup`, and ``on_pre`` is the argument to `Synapses`. **Exponential synapse** :math:`V(t)=e^{-t/\tau}`:: eqs = ''' dV/dt = -V/tau : 1 ''' on_pre = 'V += w' **Alpha synapse** :math:`V(t)=(t/\tau)e^{-t/\tau}`:: eqs = ''' dV/dt = (x-V)/tau : 1 dx/dt = -x/tau : 1 ''' on_pre = 'x += w' :math:`V(t)` reaches a maximum value of :math:`w/e` at time :math:`t=\tau`. **Biexponential synapse** :math:`V(t)=\frac{\tau_2}{\tau_2-\tau_1}\left(e^{-t/\tau_1}-e^{-t/\tau_2}\right)`:: eqs = ''' dV/dt = ((tau_2 / tau_1) ** (tau_1 / (tau_2 - tau_1))*x-V)/tau_1 : 1 dx/dt = -x/tau_2 : 1 ''' on_pre = 'x += w' :math:`V(t)` reaches a maximum value of :math:`w` at time :math:`t=\frac{\tau_1\tau_2}{\tau_2-\tau_1}\log\left(\frac{\tau_2}{\tau_1}\right)`. **STDP** The weight update equation of the standard STDP is also often stated in an integrated form and can be converted to an ODE form. This is covered in :doc:`Tutorial 2 `. brian2-2.5.4/docs_sphinx/user/equations.rst000066400000000000000000000273431445201106100207430ustar00rootroot00000000000000Equations ========= .. contents:: :local: :depth: 1 .. _equation_strings: Equation strings ---------------- Equations are used both in `NeuronGroup` and `Synapses` to: * define state variables * define continuous-updates on these variables, through differential equations .. note:: Brian models are defined by systems of first order ordinary differential equations, but you might see the integrated form of synapses in some textbooks and papers. See :doc:`converting_from_integrated_form` for details on how to convert between these representations. Equations are defined by multiline strings. An Equation is a set of single lines in a string: (1) ``dx/dt = f : unit`` (differential equation) (2) ``x = f : unit`` (subexpression) (3) ``x : unit`` (parameter) Each equation may be spread out over multiple lines to improve formatting. Comments using ``#`` may also be included. Subunits are not allowed, i.e., one must write ``volt``, not ``mV``. This is to make it clear that the values are internally always saved in the basic units, so no confusion can arise when getting the values out of a `NeuronGroup` and discarding the units. Compound units are of course allowed as well (e.g. ``farad/meter**2``). There are also three special "units" that can be used: ``1`` denotes a dimensionless floating point variable, ``boolean`` and ``integer`` denote dimensionless variables of the respective kind. .. note:: For molar concentration, the base unit that has to be used in the equations is ``mmolar`` (or ``mM``), *not* ``molar``. This is because 1 molar is 10³ mol/m³ in SI units (i.e., it has a "scale" of 10³), whereas 1 millimolar corresponds to 1 mol/m³. Some special variables are defined: `t`, `dt` (time) and `xi` (white noise). Variable names starting with an underscore and a couple of other names that have special meanings under certain circumstances (e.g. names ending in ``_pre`` or ``_post``) are forbidden. For stochastic equations with several ``xi`` values it is necessary to make clear whether they correspond to the same or different noise instantiations. To make this distinction, an arbitrary suffix can be used, e.g. using ``xi_1`` several times refers to the same variable, ``xi_2`` (or ``xi_inh``, ``xi_alpha``, etc.) refers to another. An error will be raised if you use more than one plain ``xi``. Note that noise is always independent across neurons, you can only work around this restriction by defining your noise variable as a shared parameter and update it using a user-defined function (e.g. with `~Group.run_regularly`), or create a group that models the noise and link to its variable (see :ref:`linked_variables`). Arithmetic operations and functions ----------------------------------- Equation strings can make use of standard arithmetic operations for numerical values, using the Python 3 syntax. The supported operations are ``+``, ``-``, ``*``, ``/`` (floating point division), ``//`` (flooring division), ``%`` (remainder), ``**`` (power). For variable assignments, e.g. in reset statements, the corresponding in-place assignments such as ``+=`` can be used as well. For comparisons, the operations ``==`` (equality), ``!=`` (inequality), ``<``, ``<=``, ``>``, and ``>=`` are available. Truth values can be combined using ``and`` and ``or``, or negated using ``not``. Note that Brian does not support any operations specific to integers, e.g. "bitwise AND" or shift operations. .. warning:: Brian versions up to 2.1.3.1 did not support ``//`` as the floor division operator and potentially used different semantics for the ``/`` operator depending on whether Python 2 or 3 was used. To write code that correctly and unambiguously works with both newer and older Brian versions, you can use expressions such as ``1.0*a/b`` to enforce floating point division (if one of the operands is a floating point number, both Python 2 and 3 will use floating point division), or ``floor(a/b)`` to enforce flooring division Note that the ``floor`` function always returns a floating point value, if it is important that the result is an integer value, additionally wrap it with the ``int`` function. Brian also supports standard mathematical functions with the same names as used in the ``numpy`` library (e.g. ``exp``, ``sqrt``, ``abs``, ``clip``, ``sin``, ``cos``, ...) -- for a full list see :ref:`default_functions`. Note that support for such functions is provided by Brian itself and the translation to the various code generation targets is automatically taken care of. You should therefore refer to them directly by name and not as e.g. ``np.sqrt`` or ``numpy.sqrt``, regardless of the way you :doc:`imported Brian or numpy `. This also means that you cannot directly refer to arbitrary functions from ``numpy`` or other libraries. For details on how to extend the support to non-default functions see :ref:`user_functions`. .. _external-variables: External variables ------------------ Equations defining neuronal or synaptic equations can contain references to external parameters or functions. These references are looked up at the time that the simulation is run. If you don't specify where to look them up, it will look in the Python local/global namespace (i.e. the block of code where you call `run`). If you want to override this, you can specify an explicit "namespace". This is a Python dictionary with keys being variable names as they appear in the equations, and values being the desired value of that variable. This namespace can be specified either in the creation of the group or when you can the `run` function using the ``namespace`` keyword argument. The following three examples show the different ways of providing external variable values, all having the same effect in this case:: # Explicit argument to the NeuronGroup G = NeuronGroup(1, 'dv/dt = -v / tau : 1', namespace={'tau': 10*ms}) net = Network(G) net.run(10*ms) # Explicit argument to the run function G = NeuronGroup(1, 'dv/dt = -v / tau : 1') net = Network(G) net.run(10*ms, namespace={'tau': 10*ms}) # Implicit namespace from the context G = NeuronGroup(1, 'dv/dt = -v / tau : 1') net = Network(G) tau = 10*ms net.run(10*ms) See :doc:`../advanced/namespaces` for more details. .. admonition:: The following topics are not essential for beginners. | .. _flags: Flags ----- A *flag* is a keyword in parentheses at the end of the line, which qualifies the equations. There are several keywords: *event-driven* this is only used in Synapses, and means that the differential equation should be updated only at the times of events. This implies that the equation is taken out of the continuous state update, and instead a event-based state update statement is generated and inserted into event codes (pre and post). This can only qualify differential equations of synapses. Currently, only one-dimensional linear equations can be handled (see below). *unless refractory* this means the variable is not updated during the refractory period. This can only qualify differential equations of neuron groups. *constant* this means the parameter will not be changed during a run. This allows optimizations in state updaters. This can only qualify parameters. *constant over dt* this means that the subexpression will be only evaluated once at the beginning of the time step. This can be useful to e.g. approximate a non-linear term as constant over a time step in order to use the ``linear`` numerical integration algorithm. It is also mandatory for subexpressions that refer to stateful functions like ``rand()`` to make sure that they are only evaluated once (otherwise e.g. recording the value with a `StateMonitor` would re-evaluate it and therefore not record the same values that are used in other places). This can only qualify subexpressions. *shared* this means that a parameter or subexpression is not neuron-/synapse-specific but rather a single value for the whole `NeuronGroup` or `Synapses`. A shared subexpression can only refer to other shared variables. *linked* this means that a parameter refers to a parameter in another `NeuronGroup`. See :ref:`linked_variables` for more details. Multiple flags may be specified as follows:: dx/dt = f : unit (flag1,flag2) List of special symbols ----------------------- The following lists all of the special symbols that Brian uses in equations and code blocks, and their meanings. dt Time step width i Index of a neuron (`NeuronGroup`) or the pre-synaptic neuron of a synapse (`Synapses`) j Index of a post-synaptic neuron of a synapse lastspike Last time that the neuron spiked (for refractoriness) lastupdate Time of the last update of synaptic variables in event-driven equations (only defined when event-driven equations are used). N Number of neurons (`NeuronGroup`) or synapses (`Synapses`). Use ``N_pre`` or ``N_post`` for the number of presynaptic or postsynaptic neurons in the context of `Synapses`. not_refractory Boolean variable that is normally true, and false if the neuron is currently in a refractory state t Current time t_in_timesteps Current time measured in time steps xi, xi_* Stochastic differential in equations Event-driven equations ---------------------- Equations defined as event-driven are completely ignored in the state update. They are only defined as variables that can be externally accessed. There are additional constraints: * An event-driven variable cannot be used by any other equation that is not also event-driven. * An event-driven equation cannot depend on a differential equation that is not event-driven (directly, or indirectly through subexpressions). It can depend on a constant parameter. Currently, automatic event-driven updates are only possible for one-dimensional linear equations, but this may be extended in the future. Equation objects ---------------- The model definitions for `NeuronGroup` and `Synapses` can be simple strings or `Equations` objects. Such objects can be combined using the add operator:: eqs = Equations('dx/dt = (y-x)/tau : volt') eqs += Equations('dy/dt = -y/tau: volt') `Equations` allow for the specification of values in the strings, but does this by simple string replacement, e.g. you can do:: eqs = Equations('dx/dt = x/tau : volt', tau=10*ms) but this is exactly equivalent to:: eqs = Equations('dx/dt = x/(10*ms) : volt') The `Equations` object does some basic syntax checking and will raise an error if two equations defining the same variable are combined. It does not however do unit checking, checking for unknown identifiers or incorrect flags -- all this will be done during the instantiation of a `NeuronGroup` or `Synapses` object. Examples of `Equation` objects ------------------------------ **Concatenating equations** .. doctest:: >>> membrane_eqs = Equations('dv/dt = -(v + I)/ tau : volt') >>> eqs1 = membrane_eqs + Equations('''I = sin(2*pi*freq*t) : volt ... freq : Hz''') >>> eqs2 = membrane_eqs + Equations('''I : volt''') >>> print(eqs1) I = sin(2*pi*freq*t) : V dv/dt = -(v + I)/ tau : V freq : Hz >>> print(eqs2) dv/dt = -(v + I)/ tau : V I : V **Substituting variable names** .. doctest:: >>> general_equation = 'dg/dt = -g / tau : siemens' >>> eqs_exc = Equations(general_equation, g='g_e', tau='tau_e') >>> eqs_inh = Equations(general_equation, g='g_i', tau='tau_i') >>> print(eqs_exc) dg_e/dt = -g_e / tau_e : S >>> print(eqs_inh) dg_i/dt = -g_i / tau_i : S **Inserting values** .. doctest:: >>> eqs = Equations('dv/dt = mu/tau + sigma/tau**.5*xi : volt', ... mu=-65*mV, sigma=3*mV, tau=10*ms) >>> print(eqs) dv/dt = (-65. * mvolt)/(10. * msecond) + (3. * mvolt)/(10. * msecond)**.5*xi : V brian2-2.5.4/docs_sphinx/user/images/000077500000000000000000000000001445201106100174355ustar00rootroot00000000000000brian2-2.5.4/docs_sphinx/user/images/cylinder.pdf000066400000000000000000000131721445201106100217450ustar00rootroot00000000000000%PDF-1.5 % 3 0 obj << /Length 4 0 R /Filter /FlateDecode >> stream xn0~ @]%VHHH ZPWm83.RX{~Lr2's]B!т Ѿ~OXOË?lɞќh ?hƯvO 7wl>~3پ'jr %ف2 ޅB7.$ӂc/PAȰː &({…v,x^R(\h8Y%:W&1r)?-$4 ۠* $6W*hKtS Tf&Շ=Er_3nʓ'뫎@I -"qE ƲX$2@" "2(qX jzI14"=nkZj=8}. M;ZJż"h` `A7k 40@-0zCE݁IT)!K(Nm\qҤe0Jr%=cXY>5i{5jԛzG6XB %<`4<96,+@C <Ë7[ݙ%]FʲM"ct`ٻ|,G_ӇZzcUÙ*kfS1omJx(xr f=XV ࡆ ɸ]osw54Ny]wLOѾf<͢9hj$KfSۧn@ 6Dl8uPgPDGC endstream endobj 4 0 obj 755 endobj 2 0 obj << /ExtGState << /a0 << /CA 1 /ca 1 >> /s6 6 0 R /s8 8 0 R >> /Shading << /sh5 5 0 R >> /XObject << /x9 9 0 R >> >> endobj 10 0 obj << /Type /Page /Parent 1 0 R /MediaBox [ 0 0 62.400002 13.6 ] /Contents 3 0 R /Group << /Type /Group /S /Transparency /I true /CS /DeviceRGB >> /Resources 2 0 R >> endobj 9 0 obj << /Type /XObject /Length 103 /Filter /FlateDecode /Subtype /Form /BBox [ 0 -0.4 8 13.6 ] /Group << /Type /Group /S /Transparency /I true /CS /DeviceRGB >> /Resources << /ExtGState << /a0 << /CA 1 /ca 1 >> >> /Pattern << /p7 7 0 R >> >> >> stream xK 0s$/FڸNP YJAPz}#0qq!ܷ+nT좜/UrŒGl6bz"R)dQ endstream endobj 11 0 obj << /FunctionType 2 /Domain [ 0 1 ] /C0 [ 1 1 1 ] /C1 [ 0.133333 0.133333 0.133333 ] /N 1 >> endobj 12 0 obj << /FunctionType 2 /Domain [ 0 1 ] /C0 [ 0.133333 0.133333 0.133333 ] /C1 [ 0.133333 0.133333 0.133333 ] /N 1 >> endobj 13 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Functions [ 11 0 R 12 0 R ] /Bounds [ 0.5 ] /Encode [ 0 1 0 1 ] >> endobj 14 0 obj << /FunctionType 2 /Domain [ 0 1 ] /C0 [ 0 ] /C1 [ 1 ] /N 1 >> endobj 15 0 obj << /FunctionType 2 /Domain [ 0 1 ] /C0 [ 1 ] /C1 [ 1 ] /N 1 >> endobj 16 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Functions [ 14 0 R 15 0 R ] /Bounds [ 0.5 ] /Encode [ 0 1 0 1 ] >> endobj 5 0 obj << /ShadingType 2 /ColorSpace /DeviceRGB /Coords [ -3 0 7 0 ] /Domain [ 0 1 ] /Extend [ true true ] /Function 13 0 R >> endobj 17 0 obj << /ShadingType 2 /ColorSpace /DeviceGray /Coords [ -3 0 7 0 ] /Domain [ 0 1 ] /Extend [ true true ] /Function 16 0 R >> endobj 18 0 obj << /Length 19 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [ -2.5 -1 7.5 15.25 ] /Resources << /ExtGState << /a0 << /ca 1 /CA 1 >> >> /Shading << /sh17 17 0 R >> >> /Group << /Type /Group /S /Transparency /I true /CS /DeviceGray >> >> stream xO4PH/V/04W($R endstream endobj 19 0 obj 24 endobj 20 0 obj << /Type /Mask /S /Luminosity /G 18 0 R >> endobj 6 0 obj << /Type /ExtGState /SMask 20 0 R /ca 1 /CA 1 /AIS false >> endobj 21 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Functions [ 11 0 R 12 0 R ] /Bounds [ 0.5 ] /Encode [ 0 1 0 1 ] >> endobj 22 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Functions [ 14 0 R 15 0 R ] /Bounds [ 0.5 ] /Encode [ 0 1 0 1 ] >> endobj 7 0 obj << /Type /Pattern /PatternType 2 /Matrix [ 0.8 0 0 -0.8 2 12.8 ] /Shading << /ShadingType 2 /ColorSpace /DeviceRGB /Coords [ -3 0 7 0 ] /Domain [ 0 1 ] /Extend [ true true ] /Function 21 0 R >> >> endobj 23 0 obj << /Type /Pattern /PatternType 2 /Matrix [ 0.8 0 0 -0.8 2 12.8 ] /Shading << /ShadingType 2 /ColorSpace /DeviceGray /Coords [ -3 0 7 0 ] /Domain [ 0 1 ] /Extend [ true true ] /Function 22 0 R >> >> endobj 24 0 obj << /Length 25 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [ 0 -0.4 8 13.6 ] /Resources << /ExtGState << /a0 << /ca 1 /CA 1 >> >> /Pattern << /p23 23 0 R >> >> /Group << /Type /Group /S /Transparency /I true /CS /DeviceGray >> >> stream x+O4PH/H,)I-SH.V/02V(N2P0P0331#Cc=3T4@. endstream endobj 25 0 obj 62 endobj 26 0 obj << /Type /Mask /S /Luminosity /G 24 0 R >> endobj 8 0 obj << /Type /ExtGState /SMask 26 0 R /ca 1 /CA 1 /AIS false >> endobj 1 0 obj << /Type /Pages /Kids [ 10 0 R ] /Count 1 >> endobj 27 0 obj << /Creator (cairo 1.14.6 (http://cairographics.org)) /Producer (cairo 1.14.6 (http://cairographics.org)) >> endobj 28 0 obj << /Type /Catalog /Pages 1 0 R >> endobj xref 0 29 0000000000 65535 f 0000004841 00000 n 0000000869 00000 n 0000000015 00000 n 0000000847 00000 n 0000002392 00000 n 0000003296 00000 n 0000003645 00000 n 0000004754 00000 n 0000001252 00000 n 0000001030 00000 n 0000001687 00000 n 0000001807 00000 n 0000001948 00000 n 0000002079 00000 n 0000002170 00000 n 0000002261 00000 n 0000002584 00000 n 0000002778 00000 n 0000003209 00000 n 0000003231 00000 n 0000003383 00000 n 0000003514 00000 n 0000003923 00000 n 0000004203 00000 n 0000004667 00000 n 0000004689 00000 n 0000004907 00000 n 0000005035 00000 n trailer << /Size 29 /Root 28 0 R /Info 27 0 R >> startxref 5088 %%EOF brian2-2.5.4/docs_sphinx/user/images/cylinder.svg000066400000000000000000000064211445201106100217720ustar00rootroot00000000000000 brian2-2.5.4/docs_sphinx/user/images/function_plot.png000066400000000000000000000531421445201106100230330ustar00rootroot00000000000000PNG  IHDR6sBIT|d pHYsaa?i8tEXtSoftwarematplotlib version3.1.1, http://matplotlib.org/f IDATxy|Tי-$d'.)(MAQQjEk[".khDUW*rh- VEPYd $@dz~!䜙|}#3g0m=|BL B$@BBB" B!ZDD!DH!h !-""E$@BBB" B!Zft8x (bt9BaZR^^NNNKQ $//2"bE]$&&dp5Ba^.fsE]VIII B-At!-""E$@BHԍ!/x.#vVkߔr>>Cի*64!D+y< F.tЁg9q#F`̘1|ddd{n:t`tiBVRUV+yyy-:yM4TUUqC~ O?M^^Zp[׮]+H2>*rrr3t:8r!2]dȐ!L< ;<^~eBp\I {.@ ٳ'˗/g}ݼ vq\nBsuN8?kua œ9s8زe ,o>c}Ѷ.S!=ӵ@۷omsEEE ?{lʂ(S4@UU*<8@UU.IFk1޶;vХK!&&-Jgb A}m }h`UBѣ4h=ѥZ fbڵ̙3]vopBKgwoêh3=V_edIB03]\p,Y7|~sӟD#j|5|u+޹~طҍ%ԩSYjCQEazt:ݻ7ͫѣG3sz&MԩStܮ]Op7@.]x8z(\s ߟ 6,ꪫꪫ.C4їGI=|8,e/O^\K4LUU~C[M7Ĉ#6m< w7|3[lim Y\ a(jO>|s9hTO߇r쭏]Fi_8 n;4??5kְxW\/~ ~a,X\ɓ6lW[[0]<|EOhXBD^z!CNBB/r3F[jm%mIZ UJK~|;P?@`͆%"necvXx1fbܹ 6Dy֭[b13v{E6#š/қΎҴ_MJ x[ ^o4h# ի>|83fn۽{wפSRR|o3fL n҅%ZZհa%H#J"vʺuػw/ǎGlذ˗cz!֯__5cǎeҥ,]۷3c NG=￀Mn,!"UCm$@D*?'wo%4ED4[S?tq8j+"ԩSYjCQEazt:ݻ7ͫZ-&MbԩM:n׮]3gӦM#11Ν;pPZMDѧ.,qUo1ǶAZcǎ׏{ rssYx1iiiYn^}ܹ<<~9>}-%"Sxu҅%孂99oҮ8:5裏f/^+xgYr<"W ]XBK+۷j< 1 ( YYY9bc YW$]S'؅%qZKcŋ5kseذa$&&3ϰnݺ>h^oʴ=V@ CHD4Kp g_. b [}"(Jp8ǫWf.c{zIOO$FemehtEW֔4!ªk׮[{r1z X|9;vࡇb^3vX.]ҥKپ};3fɓ BOD4KaY!TV4@V{jҷo_ӹ˹k:t(Z#ӦMcʔ)|͌5i}(jpڋċ?Ď;x;ɯ_7m3džBaf555Oll g[})-dR\^ @zL"H&;Z}j_5BNz^Fo@fa&ȉnIQߐ!Cشiehr-@:'5 $@8Gf4#M@Z !h2%K_ j !h2Xp*@=TCZ ITUmH= H7BD4ɱc) ~(E%@ IGv|ve%@h]s]F$@DfC'3D$kG ^sXս0Fk?t BDKIIIyfKjBqj"W&fԩZy޽[o|N'{f޼y^PeҤIL:Ǯbڴi$&&ҹsg.\RHLt& .؊HtGD먪jشn䋢͛7;vЯ_?{1RRReŤfn6CzMsos3rH&zcvINN111 :9sЭ[7jTUekкIIdTPC'kemq8q裏f/^+xgYrHC믿N^8|0O<Çg˖-tn7n;reBiM)UOuZ ''BUz饗xWطox< c 0 x_>r㈦ &ߟaÆѽ{wp=AAA_"lVGG*sjDrڜ:Î/f֬Y̝;aÆ3<úu=ӯzuyEQ-/r&2"9I=.U K @:˙H D'"* Hw-'gK8@bҞvEѣr6a*GbXp8Z Qc$@YSxC݅uFfaGV\9"ܹ3K;$@Dj|5V u,e~%$$гgf/*(jbғ[k]X5}խ^UD&Պj5 J2.Uw o~Y-2W&"t $@D pdBYD&"U\9ueB!D ^]pAE+DD 8/@d D&"tNF\FH[D "wJLdMD4(\3@.*%D jYXBD6 ѠpS]X"I" * $@TUU@!})j_5վꐿmH3֔R¢X)oǦh *J+D%"Πw_eg㰆*fÒsA\ zU8?tODv#1:9]'"PҺ1 ."DĒg[ mх%˙$@D=J,$@D=e2ʽv-p| *3.X[l؎+]XBD, QOpVr&.+B'8+ a  $@D=m1 )& 2JD]XrX% QO[ua-BDy$@D N wb'H7JD9ӈDžxz7 $@D}ևNfb L (̙3.]hX:%Dd3m_ 2`Ki7b Kf?)/2)))Fnc «ӻ\n9]Hd;+K.] }Hft[h_~%ׯon|rɯٖpy\9@פmrL"R\\/K۴` HNNf8+@3=x~F,,!"dƍ9rcٰlZ͆gϦ,x+..6ȧw_uIfǔ.,!".6o\o-B>}Zg&&&*1jsi-#DхL DWo[||<;vl߾3g0"D4gZ ()dr1tP|I6o͛K())i{qYQKх,!;lwάY< ˣErJ-[*b~|K%BD6 8|07t%%%$''3`-[Ƹq.-bm/@Nt`p5At!"W_5TpN9Wrv2.Dd1݉"odžB Y$@ځmIfD" kڴig}s%S^^h.,R: Hq6! '[{^[N<رcCuLۏkYYƦ\%:(jde],B K,9c[ `ƌt-TͤM5wE!ks&1¬Yxgyqێkf@#w0u2.DY=SﱪtRLÈfVQXVDN- !/dW_{lXHOOgܹ;CKw'CE%ÙA3rD?]D Y|ǡz+"}Zх0?900t$!] fx=_P "ˊB_Z ?QUb:wCmOJbmW|i4ʋ(f.|=zǏÈ&XmUFdu_qB (+**_LY3tU3!ٴ K_WQz!~֭[ǠAZ{Dn;x H«~sA0V l޼u{maDm+݆/#56N .E.,ian}[ny摔D'L`b$HZ 2"lkgDYXB,e"ºva*BH)(pas;kt9- x)\1 Qd |{={6X[, @A03 (Fg6ӧL,!K$-!C e ]Gϵ23V[ ҅%yID 'I?G$@D4X҅%yID gd]H gwn?9W g avº뮻?'|BnnnӆC_ L r]wdV\I~~%ҦUJ^FtT1!DCL wqo:tdo)jX0,{"[,'!~71Lͳ`=z4[oetiwuIC Fq:ӵ@dw[K0,{Մ(簻l7+%% !0] D|V;7qFVY Y@0# q Dq: gml1m '^H/(1!$@"%_P&ÙAԾFvYZVIf#a>.ֺFFQ ?}*#AjUū1@U" FQHV#GqaօF&22(%HDF: m,622lLw"h@Ͼ:Y [߅P^])cO8! [ 99<ʃ H!G$B/ώ;(Fv ~ؿ}mn[.?S0VLCr2f"!>3!Cxs8v]C+ +$f#Nc;aع}>An讐Ē 0 PV]/?ZP #~ ~ Ygq#_a8^.9\0$4}&a.2emX+㺌 /ô:`]p&W ; 0ǀ _,W=./ r6"Ze_DJlJE?R?qtOpӻG1V)-!I$,߻C}UuCmEku?ZTkuN$G]v .JoeB<]'waylhwXػ pZ+%e`O@ÿf/$9L$@LNiDKUD!t ~ =&͇/V6۫dYw!GTU @M^?2mj?~DFƒ K a &t+EEZcY n_57i))F.+=۬[ ,$@L]0&o q >p6h԰0q/5 KL,!LCĤ~7K 0礖Ѻ/.9Lz #Qk_I/ӯ."yHKlf mٛ#|kgbG߯ݰ_r@8+B4 MDC\5=j6 >}>k,W0Tn5oO%]p6(R}$@L`A֖4 > "Pf UA+7oo=ɑD3=e_Ez &PQ5x3ځ걿PWp3֏6{ !Bdjv`|q9&NHNN]RXXw?SOl'jm`#Ho]ڧnKI]~fhL 8^xRM/PI>/W =/)C|ѥN.B_\W &Op]Jl? ?Aw uŠ?׮7Pʊ"3.gs~pf6\%F(D`i9&O 5PgFq_ \ SB|: \4B!_rջCgw^/:.|/7!=@6٤m oHZKV(D)(( 999x3Fʳ>΋n c z|}k5CϱeѮ'APr gϦ,x+..6Fط6TLx* $c8"JϔVSO&Ío/h'c7P!XHLL IIInfT/`B?[qWyl uYppk᥋aGT*D2]TTTi&6m ٴiEEEW:/~CGV?!p~@{ bdd7n*^ 6pyqyZ=y?lpe-wŃ "ߠA@viii$9#X8XyCȊjxG{65epl6w/aofHX0G8'vEx{wmn ;Q P -$@cr-8X_׋50f6btdSp.ީ .}IȡZjwh޴C\ܹOر ^To#HSN:3ڗ+]Ư?5~τkE/njHW 0g`A'i3|]F햢FiErr2eee_X(z 6_뮺vk=޾%% \D#+r;!=.=<:Ykhж9S༟["RZ qWhϟ_£?^/<)>/Qs%< Àط"|./{¸!3T5·ʄr uܰO mSك`ʿ' /yc[((:]ҙOj'P;)S~9 C >: i09Md˱-߿]F^q9;} ;W?Hi+tH9M8d;V?? .]@rLrH[Gg b/Ft'=ÞUP}xȻ:Zk%FZ¢$@Nמg x9.yx\6atJMn (\o Ԝbs uyV,$@NQU_/` %\\ S'sNO(eN GiARAYљ!{ 䜧i=%TD= ETz+yg4f?",#GfʇS~8zL2sMϰaF^? ¤k8PH]-ozo L%I5{㉵OP!3.ߏ=2RV?w|7#nR+8ʊMB${h_{heX9MK? OW{5G2"gJE[=ܿ~V[EEI lUGw@_X][3.5_RC&D Ӵxx#xGWG k2RzEt_T>m|dv++ŷY؉ڿ (Pi$@DSz?ggg3.ʾ}I4C]ZhS]ֲq ހxztAnb.99d'dJjl*b:H$O5VZ.ੂCP~H õ#ӗxi k&qଽ_3-6;M@4 5Ul85/}r7I(b18mNb1-vVvbjZ`Q X( ͡Zuv(nrڿ~vXbC ~bv?}[sY-JvX<3fߗBP=#;o޲.MIE *p09Qs5'VPTYJXO! X,;=Sz3gJ on noP>|j}_jc%]oi˞*V:j75)cjpXŀlĂ( q8ghgTU K;Ҧ9ko.mRu꾧BSжg?A$@"q-'RY{ q#! !0;1!x|_߀Z ,EfU[,X,/D$@@*/eͥR^㣢GGy*jjOϏߺA]EacckktXqڭ986VclHk#1^~bMIR P־䵛7UUS?Nl,vZ[o*>*6S $HrI[i'6daSal%!0e̟?gy=\{9.bj@ [k7_j ~{U/7r3, _vMMc#!FC bZkaY[-, Vo WU|~_ ׯ}NgiYT{|ns(wkϤFk=y|T}Zʢlk8mqVZ[LN9Yj?+mXUJt[o1sLϟψ#ӟĄ غu+;wٸ#n|{_RU^ߩ<@p~z^?ڂ@B Ze'Ն dq߶m(XP[A;+j~k|.8ELuj h-&uj6BZmg!}~16 v l-IDAT;lVՂݢm>o(حJp޶:*`¥Lς 9&MDAA5_1o5[K\ͮ/XXX/yK/V헪VوK81&,aUԶpk`GOGkU֢bh-*_mkS[@6E =lݢ(XSi-cQV9]Q(`=VZNݷP6崿y-pW:6ݯ3_{f&6èZqFzǏϚ5k~K/8UQKNզZ-J9cm/ItWDEQjCFF?(}EݚZU;՚Zz ?[JzE?j3sznQ}a<ʶ/o*@;'33L:kn7n4] ~5w_+DsY, m,4Bׂůz>o >*|mJ=]~v}QSch~}}y5˴}/ת'8 }NCPzDmԎ[}^{>sTbl> ]06-SPP>e hK`%T})iiiX3ZG9U={6eee[qqq[*힩p0x`VXQo+>|x!))M!D {ᦛnbȐ! 6 RTTӍ.M!D nR{1JJJׯ|]t14!u<֒K !DӴTc B!"BB" B!ZDD!DH!h !-b [K?5 !D{Ot r D!"Cyy9~]ԝ8x ;2"//b9 j:N>i祪*`4D#Z V|8qqqtС}8q"񤥥qwxyfFSN <իW]y3w|^UUyrrrp:=-[XqO8q"999(»[|6n4ٿHlxW_}_̄ (**24Ý{wys^x֯_OVVƍ .W*++8p /B7峙9s&K,aѢE|TTTpUWCWνkjrr?bn{7՘LUUU?)((Psrr@  ҥKgm|vɅ^N>޶>}A~;u >Ԭ, nQ՗^zJ4@]dIqS>'Ov]]hQpE]lYjk-ӯ_?rrr.2n77n 3j(bbbsA%~;2h |zSM ƍ?~|Ǐg͚5Ue;w$''|~g 9tP-&&QFϭ)ƍzɡ_~!,F:tzRRR{[7i\\&۰L-ө)ql s/Dq Q$2tt:p hR2e?X"O҄=ir^H$444u\]]M77ק-!!SL-vZ\r{з nB[[[xa'ؼy3Q^^.ĦFw3H$vcݐ:jRŋ}n|ȤczWl]bb"Z- 99{Ecc^_b7t>h`xyy7DNN --Mq9tH\\BCC{;8IYSS_!;::v7n'ԩS]O[,zccc///\|K,/i''' 7z#ZZZdrr [/C =>d2Yh4(++^rss!J+_LٹωIb@x3%vÅD"/NDee%GGG̙3>n}/bI^2bϘzt_B NG:߿ODD4yd5kөShر'qRFAFz5 o%NGt!rvvE u$i޽TQQAV" O?N>Mt9Z`)J!.[n%JETZZJaaaD I߹z"[lbcciرt)*..3gZ6DEEN|N}}=͟?r9Q\\WvJJJh$Jё>!K.T*dN7nfz}psNrqq!DBSL3g vݲeɉb19;;SPP ۍF#mܸI*Ҍ3t{wED}?CqqqdggGr,X@W^h?y5^cfRs 1ƞN 1 1ƘY803 'cfc,@cc1paόӧOC$Ν;ݕ*::ZX\N =cjժF'HNNH[Gu@||E immm0ݍ!(,,… -ޢE`ooorovBLLE ( +WĪU`kk Zݻw>J%^x;vd ̛7 ju <L&JKKmѣO?aҤIJDž 0{l888@RAբؤ]H={ȑ#/"++_zݻw!qq `0JKK1sLr㣏>u3g`ֆA.۷o7ٷ=z4fDEE\#"|W?~C멵ZZZhÆ TTTD?ȑ#СCB;7o&;;;ȠJQF m}gAǏJMM%TJO}BIIInwqq!;;;JNN^[RIo>|hɒ%)f]^^il@ vc HҴiӄ筭dccCB^'tY""Z~=͙3Ǥk׮2rANcc#r255Z[[ITRvvP֭['<7 $رc}WGP($x]dCDDw&[[[2 >999deeE Dd@cVXAsZM۶m3qㄶ d2*,,4i'&&FHx]y)J*))鶎I"mmׯ_/={^(22RxBrz:{J땷𳵵5%Fv.]B~~> Ԙn;F#lggwwwTVV eĤ6bcc1qDT*T* NgccRq=n1bp)ѣP*3g>>>ya4QUUeINN+1c@P~w]\~PxfϞm2n?s׮+Ƕ#Gp}._aѽƃ !uOt60bsHdR&@6Xp!Nm=~ۓ Mn޼...Jh4&#O$ BBBpXlFxGD՘zsa$&&oFRĶm:ݛcB8srr:eHnŋǮ^^P$&&СCoM6z,tpa7e=z%;wƍ455Z8ί]va޼yk׮u;s9s格HJJM4 iiihnnB `ee'vٞD"A[[[cŊBg * jEEE>}:GPtx饗HR\zZOc#"dgg#==OKTwAjj*jkk1~x ȱX%,fq| n߾0X|y>slڴ yyy(++Ctt4zG &`\ޯ;.V Zpbԩ¶pd2DEE Xr%"""K:󨫫í[`41a\x'N@uu5֯_ .rJlٲBBBJ%V^D:;wz͘1cFb111(,,˗̌ ,N ✝QPP6̝;'OFBBT*-uV$$$zYYYH$=&ˈ@||<{~qD"?@xxɶ#Gĉ}6^}U`֬YرcG^֘4iƌW"66AAAXlhr6k֬AXX"##hP(0w\d2NRR6l؀-[sEvv6ܺKff03PMwwwܻwQQQv|OtƆ)OOO,]Zx{{cݺuXt{Ǟ<0Q__\hZ<|;v+W^KK hឲg06L\v (++aغu_c1$:c1pa1fN 1 1ƘY803 'cfc,@cc1pa1fN 1 1ƘY803 'cfX^֓zygYv~eڵ~VZ77+B!B\,ynEEwg= 86g?#|A~/|L&__)B!R&Qطo^{z^>Ol6WlOS۶yᇹBB!B\d R*mD"19//g=}t:OpB!xKIrbժU|+_= >o6***fo !Bq"_~ œr9~GQ|I<.B!Zlcǎ|.⍑"c/Ʋ,gP`jj3y'ԧ> ,B!C>O՗$EL&F466x饗?X~|޽ضUW]uF;mmm󁞯_"V_ۖ߅02~F]3O}|&Ν S(? wQ~[no}[o}[TUUqw~iڵkkތ.mA wad.߅02~Fo~\I27 bX9C? _ꫯWeS;w [n-UYY?>9.nv{9~}M !B˒ 3l۶Ǐi?~iɟ uuu|C駟GŲ,V\g?Y^/۶mgٲe|_ _BwM!BK 3 9?Ω{{|/I!Bw.5 #wad.߅02~͠)[}/i&'B!.Arvd BtuEx䑗͚hg {ظᅥ-[:.z{k_Bq!DWW/}9٬R΍׫S,Z{={FcϞ~oI}Bqn$BK\WW;x~&'f]j+ER^@,P ~ mtZ4-@" 5n{iZM3ᄃX[vq_G$wX,GkTO{m&'O{m (\;W!F!}ϳ}{7٬I*U,* qs(2@&k<^QʃOv~=qۛx(UO6[XfMz{x,BWW/9l/YLVYgP$Z:nalۘ1ZPӚolZlۙ=cGx~g#=tEzGBH,!DuuEؾD"iP4[>$L3M}M;ηHQDx=qF8q"#\~#`d2G2ُifg]$ $=Z/k/&ӧ|8=(4$ 8۷wA!!B\v#̑N'I@fZx01V_xrȟJ_q߼ ERQl{|~h/QT*}k0ϼǍrG#^+Y{1bQ hl{td;.*=Y%#,SSY2LҀ 'hq00 aNsLj GZjzXk$W1F+4^ Ӵqf)ڕBӈI{ί20{({;6L:8ZQ8Y+l&J+;+ sB$B}ŢE>oaY LXMl!D1n`EXbT] |h(ts7줛íhb=ǩ˻,Yn#'Sw]$tPĨ0ndEhhTIЂBŞcl FcccۛVmh:g-bg4mNL16+!8O![>'T&G)?75HQX@0,arQjfMAba~7,kҰqZ&4,^/fբr{Zb+@91NRMa 3Rl6700(P8'L% Q4^FXNA4-,:2"K 3ƦiZ8I-8rQ ?vms=o.ʐUM935L?jV_l?Y}.Li>oLB @b,79XQYj@?2Ts94c,iqT!XL6ԺeCO2ۀ*p?%դG  3ME xBF(`-qٔF( XTuoPx(rEԒ`3/b"k;9CY~IRi8kl48Ry7/b* h׮=(qLR$r,W PvC&!ɤGwBA!*;q"m+rntזÆ*}LA@Q(m8fԷBJy+a0ް&"DZb9gcw T8k.>Ds8Jxp5=%qz_B@5Zg)C4Ꮘjy24/&4w%x&6oA q2JGoooY1a4`cя(Mn;ll[#5ٽ{)b(G!X@;v1>&uL<謧l`V%L~ ^~ xQ |>G)]mIG ѣd287dQI?moJ)HSb  EixQs3Fq0qHSRM׵m@jޱ$Iy~ <ݞR 8BԳq<P(<~D'g0111ME<6Z'B B@"VX'9lվU,2?g)S~ă"XEh#SN f0fOu88tqQtTĒ2tj  K_(d^^ M&,]a4=Z#)/҉$X f,J>ɸ}s  ^J1ꙦByGc/8N-x=ϤB,|]YVkN*.PM])P*v= r{ύ7qIkՇ9@>6L j;U~O&CT ÍdPt4_,BH" 4SQ`Pn IT$m+{~$G 뮻;h3@S E01NPb 6nl]Zݛx;J6kBa'eKRTvq6etnF-] &M{+/e+ɺ}`l (IL0 MkZ4M9"71NjH6kٗ%Rc`Ґ0Xu(@QװdIl[Ng?ҙjs-O٣O'}(`7YJ#/K"sD!@wwb1Rq*bR>L"4w IxƠڟmnW-DHbTWW3͢p-}-K{Qgz?Nqjkk0mg1U$b)<~241N4 S|_A'|ҭZAc)trTTV2Hp7pF^~曯8v8 R]UX tȳl0w?soAb3ԾǟOmgv$@7|XLр8N J( * !8E!3Sf&,Lm4(mR"KkxAV1EЄ <ǻ}k xT^Ckk+ds9I,%i:WTTlٲsSC8L}]SJf**w#xg6p7iI2H &J&El綾^L n9݌RNaF4F8tq,gאqkA6E nFI„-XHQ*#F]3xUegHņI2><}=Ž>IWWu&ow2"_}ۻfMRP*Ob(ܤ Q2**qYq566Ժetزe~۱xmtu;:n=sB?yE@kIPmhn4~VSKQܾ*@15 I5t85v~f8yuvW?b+Wk^{Yۋtut'~:'T YU.6tw\ALB &&2<x{X#D!ana`9fdW%NFj"y`cܸ1gS 1IXN* Xn V&ٻU9-[z|o=>L(^g\Je2~u+I.~*@I6z3 WbdLʋ3R\7<,a7_x},S0_Za Iӌ١"]]ݶ[P=N)00q=Fsgq!(4!b>o˒,!; BG&S$NV@`IijBw4 @b]]v#f264 ڙYV:m 0N3)J iw=h iq#"@ &ƻٍFҮ'hY@ @,25gLOg/"w=\LT=Bcf]blr-=(mf2x(*8@+i;J*KݚMR(0)68)`9XL//oPG?JK˛KX1lT`PNWЍ<'Y,9*x|8銝C^i1M尬z"xGDqٛYLpr2Kh2|;T&&cS+9H#AT&K PxY!K 2TܫȐEc&ji^~::J/_ N={Fٿybs,b^uP4>Er@HER4h HhTQcctޏv{5wܱ$ǀY;4 Elr2rz<=:SE1꠼$@ E<G)dTJя KC!ۖ BZI4 l;@FnPdJZFi&y9-8y \XO+ ]H3cDX VeIt |W,*8Ē?Q IDATHcp0R\YK?+$zcsN)@rƨyqq =:(I\7"245^v_e'<8a6(m5%G f1 !@=qG" SGqj"IsR58a4b23J)l[͚=Ȉ1BH"dt:@F8q gYL%N4ǡC9yrΕ{Fs 1<}}H^E Oj24 Bl[/1A)WP@1A?6XT#G{S~qd֭FdGHݣ0`Z( /PA%)nnw 0ì'Nro^ ,eQ L@~TD|(hl Jd!9llQE<@4*o?R ]q%1"ETdYEc5݅3D+/~kTͨ]H1 I- 6>BY2 ӊbIϨW -8NV4E:lEդ"iE 儛WwG% P0 ccIx_* !VdDqY/?d. '`uhcvzg- Ʀ ; Vxդ/ⶥ!%4q >ϡJS4NjW|SW\q$}}}d@122ioa.O|^ߎcG>[ڄހ(}c<h<ȌhnfUűM޵+0q4].7n! b4.1Iee3GƲNm~+; ì^VOoKkL1}Z5tE#vڇ9[f]n z4OF$׺U38/˻(ϳf =HY߆h4×$Ko!.K<2'N$,'݉ K2[p%yjzx(יhN<E4caPKсtR]wk::B(u رcXe1 $uuKز-[:VcY-8)аЩgetw_=eXr&q?3Sp 9`]~lǧP0pg` C{{;K,AӴYK},TM83Oz *Ac IPB,b?`-E.ڎ[}g[:x`tN YFl~{HQMĚض$< B @(]IJpmE yp&E-:/COm|N[H-Y< (l l$~8zX?/_@nɶm{4K##6Sig-˚7 Jq뭷bYI $2<7Ч fi1AZg( 9qRݗǏ311 ^ZZ޾[>e[3衛↑n=Pd(izؼI~Ym>L}_L9mǾmP 䰉(Ų+!BDq01qoD `:,&?mۈL$a p~TQ[èFtt?t?ۄ7W]n4/pܫ|!HW_FFFHR|B /ZΒьR i%e^>S$0a9'h¤00GN!&͜B\ $B\6NM۵4Y˯B訊ϺYPswl ɒVzIx(=pumMuWV*tq<~r"UL7Q2x<5Ъ% aIk+N6?7.d|~|n*Oj)UY UdYQ>¿s~E;i$-mA` !. 9u?l 8͆&?k9/Y p)j(`c)brZ^!I",twG=8ֆ7 RM`7)>÷c1GH $̟az7}iLisee%˖-Vy7?|Ar<zvR50zr<'P_"(.[})'x#{34<`DYσeY<+?bxReKSO4G!J6:EY!. Bw۷we ,SeڹWxP4r8@L_ mr37<ٸB)B!OO!N,i,TVęȢărF~gGAZ͍F72wvvxb"΍|"'xPh --xb<&|2P@6h.W.ՅB4640R(r++fۗ/_J}kB BwR8U-i'q3GDK=h,EI'l_ެ}|sƍ(LX5B!E]lyMBXt)###ds923.5N>[Q*;?:3L&ޤM63^( PMrU \\@Qx^A[6YJ;di&:iIf;?&&mnL3gΘ~OKK !Ӥ^W?]ѝ<c~TILLrd/w#ލt `:4CTL; dldְna# QUծ"2&L(:+_sk@@<&6lXH|@3)lc.Sʥu6p:Uff&/*N:WDjObۣ 3``jaaX8\S}\.,ˠG[YY-}}HL6cpYxq7%+\eS>d{t3c>;fN_d&NBXO Pt4""ƍ͘I(^l^aO/Ђ BXᧇ=y@2 L68+; rv{0 Ϟ9[o/-A4 txh!'#?>iӦrŬx-B!?_x2(4åcSR^fʵ`~7U1gW89>ӧGs^$4, 4B0ɢ ~by챍E {v b``| :H#f!Ӗ',H4:`'PBۙ󥨎LxnlV+E1ر4GGתily6 jbI5<["֑B '&!Bx;הHLhk '&Ċ^F%?UyJ;v ?§42ڕDuF^^uttvE}}=LWd6֌먯 d#5 ld a$S7I-(N!DI45UWDbF# "2_v׊f`7xdj,3cL^LNN"33̏o,V!k_hM0wQPpwx((r@b?GdNZ.ל?ᄼDrrL^7w -s"66,UeyE$Ɣȸ."X8,Y1`<6݃ q&ɣ|!\*޾|њ63EZZrssM䵱f]FrsIKK-ZH"^jjc+_/ɣ\~Na10W7Ftc$aȨS""xߞLq'x& Ǭ&:miᏏMn@4x˖,-Iw0p\xVVol۶-;NCb,]֭[YZ t/z-[H5Mݎj%$5B"{1M ~ /DoZ?P*hmmVew{Ifl3M8J6lpdɰ$bs8x$$0Mioogڀ>YZ:&?Xuip≣8o`ķo<,_~Z||6d30{{s;T[8bm{Tҵ-D743-%99uD9% "2%gO7>vS +$FD!;iN7; IDATH H1MaħF(KϧnVZ4Yr%}---|nGN{=ƥ}?{6?PC $`!.rqM>a|VG,f( lq3;4<$3Lۿnl v`3/3 :u:YuOw2N}ӽ***bTh1P ޿m:"CIEžN]/_^ה) /+:o@BxnfSJ n2m$߄SDdt)qݝXK;)A*{MU̠ē/`# +x޾c5։OΔ)SDvKm]t{2u 2\q:"Q]m-prFnn1~+47`Պj$[?C-,e5|leHb9RJ@Dd\zX,> TaP|9x&"0 fHMa¥ _6#TT`ݸ\.233v[rn:LI&oq3gX_磺!X>Ī _Jᆵ4=g9཯~Ofr=!淼ӈ*P|.ڐaE"2ꔀȸp4s etB0mLH- 7~/-O.?(/_Eeʦ*K%ܽ={p8hz TUU/&t^|@0H"p୨ $N|p7SQQAKҸyyyƝw~)|7/I/@&q{m3t)nJ8U2 + HH_Ȱȸ`A6@xzA'q$E-`J~}sTu=s(7x#\r ~;o_;JKYSϖ-̈́ 6D4i!cdk8+loM}s 23gPxoA[ov_oW_z 75 K4XơCrrr="3e4cJ[MMM֓4gtw4/|YY_fˢ>H}},˗|ȘKK:y VK pϜ aث)% }&OLCC[ŋ ?ϔ'w3ggO@V^Mkk+_n⩧_++9@ HlL04[, SM+i,06Oi_>*MSSsa͚Lnl8"㈓/a#ܯ]&477SQQAOOݻٳk$CUUxA4FToƌT c+SI0/qHHpr>À=_uu5K<}`Æ RTT^xZ;oJVzM AM?A 'B ظq5""#E kJvxؽ{,2y j`t qpd6opN5u\?kMDyCUU;w$4`~~rr2.W1vt2Y ̤j `/=1}Hj}HgDKc}=nIƴi(((b O]r:q1}tϟG[[ZWWxĭf a!jEbh#MHN7Ѓ՚i:% G>D}zy,96 O0  l@#C`lh1SI8p V07N'1c1d5ʢe-s [, ˣZ裏r8F.ƚHJww7 z=//~G*mEs3y}}=UU~B(..fʗO'a{hi$؃Vr4r&)?HNV]J@{žA8|YӿD&l;K\*D`bPK.[)^ގO0p2 0mtlnF\Nsr]KV;ػ?VL) ~Pooo1sf1sΠhHj'O&77H|P__O=K|K&Nl4*ifr*#`糐8"""r0J@@dUt/ÛO0y4,Gkk+'O>yn6m˹ˣq" X6BT289w(Ɔ I[W07.g:|¾2Oײh"v4MjkwgOyqRY>lGY7uΣ(7ᄏ ߿nt; 81ROBl\xɔ3lcku#\=+#tTe#9m^Rd3Bot 2~~zжE3q72e 999f.\I'ϧ?k׮% w?01hhn7zaFhb6;(d'Rb /> , f p;hR,r g<͝DVV6v_*IOO?<<}k,<"SަN-j:XsAr~-&2 @Jj0DI;spB di,$$$}D$lׯ$FM J@?y|IjkkKر˗w璙/A /KG=v `X,]B]tUXb>y`998N>}<^,\e*/bUq);$l6NOm4NᄦF+yc#@ ŎժE"2 __^`Ν|_'55o BMMx4V5 ٸGul6.۶o(%.oL;C粗.&&l? Z1MR2ʕ+0 y`W]uo|;Nii)+W_q7ʕ+y(,,׿,f͚,X+ dQ X+LJ˨>VFU$4L^^^,/gXí)))J~Pjv/Omk ;Bid> P۷rgLIOw?W<$.."XQ_q򨫫ZJ7ܷ7K2~O.`02,X?vdoܹˇ^˵^{,a0nyw&۷BF$|1Q7˖xϷ aO`۞8 F[sܐFև瓓CMM f+'5c]mv 2g|I$7}/={Yaf_N' U\_7g^0M}ϞuU|_x͖ԩ)|zɏ~tI`1$X¿NZ-Nc[T͂"y lilv,OS/?kٲ^ՑVft:Yx1YYlκuo2% cHŋq:RRy$ NżgYF;qca755M6c__Ç @``@vv" 6L{w-$&&Q\?9.y '|b. y xj*m Χd+s M⋋ġDD&e\^={L4@/.,g+V]䲕b ð]8K?ۋ9 _K@ u_`iflL>)Su,-eWY>?h W@30I- n uaa%;;q\}"2( !rs`4 `KXb2 %줛"WR`"#L4*v YAHO"q!b"?6,ЃSi cFR$*"tP,jb؀8,dc# dHY=dPǮ4{:O=^D:Yd\t  .,`iAR CA +b! m޽il$IIX:]qPzt~~2>ˇ̦=Rg$iG EewEdlL( D_Y^ t*˻LVTAE}(gYT07CxܟpŎp?, ckMf;v@Ed3}nnYJôÆI>5̡,f 2H_vju쮈  e_YaV1M6N'&L7dfʔ)$%&} {8׸g{;ߓ2eJ#yeeta৕ c+'S4X$?I}4UvWD% "2Dcd59G?젘n,l_ \h?3WMFnhڷ>v tt`0{(01w4:C zC/A: mW [,VrsSUvWD % "2!-["''"S ߌDz0 ߮%Lo G]ff&YY|ܹsL7at-[FΝ|dgeD׌l'R,\7\~x9@@9b Vk<)))*+"cրȄy#ٸn~mX!L !1J/R媢 KKk+ͦ;uu|qGwpc]3lU\L˅k/|t֬ɡp2H \͊a؈'++ E1C LX_$L5Hw:KZZZk""2(A=4Ng"}QRpP[WbYTCDd"vSXXLW]}v,G0咧p""rt__?_]y%ڕ+ @ynzy\2"ѣ)X""rW^9`EŎ}#Ҹ#!riRQQѿtR+#v^=J@DDIR׷c$F]W[K @Zj*m]F T~ey#ըF_ ""2)DJܦ@ jDߦ;TVV#%%ePL""2) ,q[TTj~n, y- P__jHewED&U_w|ܷ{Z9܁ew7l@stv`Ȑ<CSccT,Ԅ 8FZDDd,9 sM(  uߎ""2v)Cdgg@OOvX s.zz{ ;+KewED&(% ""r\.FߟkjjKEoo/;kj IDATp݁#-""2(CMJJbʔ)C!*++cƐC /'"..EEoG" g QTD\\.'/_""aX7…y3G'//=;hhh`֭l͟6`%N# ""rl6?9*n ֮\Dz~x**n3|J>DDJ@DD$Ԑ @GM_%+7m555|>rI`EDdP"""Gvr:¼v6^0p:!"rQ"""GpPPP@4qF"v &SNph""2(1mthjnGI[[M3mڴAO HJрbf QkFi)?;Z%""JQڭ(+àOG]]xlϰ􍌄~h =%CG A|o} JRm6w1y2UXwG7ED$4KDD"n&hP \>8&1H)8N?-. -X-9)`b^XXnY|""{J@DD$*"t`::^% HNTvWDDDD"RJ72ͪh7=KewED_%""QY޿TVy@a""DDD'RMӝٺ:k>UW"";% ""UnV\KNX,BA,SXr-JBDDkZ"""QUVVgm1pҐI D9DDDDDDDDF*3^e\GTKDD*R媬o<,_~*`F@DDDDDdhDDD*d@ ]}@DDDp>DDDHT G % ""U"""Q"""Q> ""2URF@DDDDDdhDDDJ}@DDd89Be֬Y3h߭[rBVVW]u1\Ddt G# G[naŃ9rg~#:::Xb7nd͚5ōv""B}@DDd8J@g>zٰa,Y .\wuȘ)XG4M:::C?c̙<쳣ȨSt5אseݺu'|/^̆ F3TQ> ""2M:BvK..l6o̊+83xYp!|Z[[Z""pSO=SO=R.N8.^z%^/NVU"""""% Qt:g>?i8z{{طF}@DDd8J@dԩ|>^Eb T__OVVְviii]~\~ ZDd/~i~Acġ$J*++q8$''LNNY {~ET""#J}@Dd)))QD`}ᇼ |rjkkرK/tTbk4r/ȩJnn.[lW?6=sr  ""2oo;w./G$"2 G# """""2j4"""Q> ""2HTߔHT G % ""UPQ"""Q> ""2URF@DDDDDdhDDDJ}@DDd8R*(QDDDJ}@DDd8J@DD$DDD*X""U"""HT G# ""U"""Q"""Q> ""2% """""2jHT G DpTKDDJ}@DDd8Q*hDDDJ}@DDd8J@DD$DDDDDDDDDF*(R`HT G# """""2j4"""Q> ""2HT G DpȨQ"""Q> ""2% ""U"""Q,*hDDDDDDFF@DD$DDD*(R51M@zzze""e"""dz,^$q8dnVV^=!H gī`|>}Q~ӟRSSCff&-+$##4ikkUViӦo|nQDDH}@DDd8#e]ƢEݺuEh""rZog˪Ul[S3쾁n:鬫ߞ:ms_ft""2zӟoY8`= v:o|[Կ1koM OpO~ԳΊB""2VzCqiQ^^NZZh^DDRwS~;[~9~{g38{ srF<""[+Rɇ8YF,h~fsfƍ}Z9Jk|Q;gO[/^qkzh)""c_A0ͰDDزjUC8-Vq}: 9J1u( Voo/w}7Vp 'ps:4ɖ>aҖw[!"rh2fj?smQ\\O|?Ղslҳ`mذK*""2W]ŭ{'56ұ9wJ]Y &""rF#\+W45kB.^.oB;йs'];vеk#G0C!\w-ZD~M ]aS|&""2&=y'yꩧ{>򑏰rJ>3k8HrA-^<]i`׀r- ˲˿K?ڵk Ó-S}k[}={}"VZźu릪{"""""rY^/iii&rUWdv5SdJKKYdTwEDDSsRVV=Yv-СC<쳴/|a)"".U/}e,gS-9L"z:n&ƴ8r_W'""rYy6Il[73Ĥ g;w.ַX>y?|V\9I?> ""gI@^yn饗p4>9;L >DDDDDdf9#`>9;(Za׀_d^[7c\V^""<RF@DD$ޯHRLDL ""T"""Q"""I: ""2eRF@DDDDDdhDDDJu@DDd"RI*(IDDDJu@DDd" @DD$TDDD&,X""T"""LHRLD# ""T"""Q"""I: ""2 """""2iHRLD$ꀈDKDDJu@DDd"II*hDDDJu@DDd" @DD$TDDD&DDDDDD&I*(R`HRLD# """""2i4"""I: ""2HRLD$ꀈDȤQ"""I: ""2 ''po&_~9o|)蹈Q`{w1۲m6Zjjjx衇hjj_|q2+"2TDDD&|碋.]wE^^*j*~sOFWEDDDD(u lۦh4:>^~eV\>n6222xg'"".^/UH]]` {&""g ꫯ&;;tVX?61M%Kv غudvWDdRLDSNBzz:_b<,]-[PZZ @kk+OjED&ꀈDĶmOmjj*7x#7xcb>)-[ƕW^~`0@JJʸNJ9ۜS^{5NϾ}{.K/_~9B!ҒB""g# <'ԶhcԫTZ[[3g΄7yLZߛoo+"2Vd͚'"2]< @DD$iGPz1FёiVii88Nii{ӳr|ʆ%"rR"""Ip{;ny}]'ۉTUMEED ,X""riw{6oLtd,,$+4ٽow-9>9(S2:nk[8t>y,XbL}V吝UiyEDB%""$J׌FiժuuǴ_WGeee@ 9| @DDShl$POiL IDATnNθiv{rss) p1O`0Hss3w٭|>#`0xZ(""g ""rJr|>m0w\^obx^/sm9(S20ox<̛7/Go7oNGM44gu tȩdf-_Βիɩr^~iLӜ4""rBFhiiz>v/;z -_N,6mzN~\SCIIjp @p%w7xBq%oh yOu:L**,Ĺw)׵w/DL}N9?P @8GFPZZZ""2(V__=y摒򁏙BH-""rR"""+磳#==&jy|ǖ͝KH0} ""2C)532Q%WDp8Ll'N""rR^贻d  /'ٳ.7Xm-9--t0 w;?""39F<.@8'`?p׿]d֏̯ l@c#E""2i ct ܆(s`m~ڞھbhq$""ӗ9F<n_?mmNWT=tF>DDD__wEEn?1r|>@ZZ%%%}yJJKIz룽]iyEDf ""reTUYaK~iy ʪv @eyEDd( c߿af1+/oRI7k wɎ7ȘP+'/?Oض).*RZ^iNSDDdL@ eY|ߦtdeYWDdS"""=wtF5o{ٷMG`1iw]r_Q__mS# 7$ P['衳3H8%%I^/+*YK{:HOOT$9>=~?Ŵ008HmmmT_qŔk*N5{==`$Xm۸Nnm˧x?M\UU5)iwOl^0bm444p=LYa Y.cm?D4%Ķ>ÄpD <\rIY-7oHrrsψtG-((`OG;#R6mGܔmů#Gp u FǶuװL @Ddƪ [ؾ mQ"D~,kPȦKm߼~nƍ\.~q#Sݽiy/ٿO/X@i2f O_OES(MZ?}}AB P4XE H ֒㎋f,"Ӈ|hj0 ,ƶĂH#a  :zߓetߏm7ݱΠd4kNn*)EԳ.H C(=G'14`ƶm,&4ٰA!"rFP,jktv@?l\800('Ϩd6OU'MFww7]]x<5++#ľ.m8aYl*&  Bf==ocZu\Dd "2x(f"pc0G l2!|)=ӭ' ߻)|83FO 7*3W|Lw2|9d8N 8B9 "jIâz>|GPA?V}Buu35A(uukCĊy#{ qfٌ2I! 9D;ŠSv!(,,مdee084DKK[Lwl PA#\D9V*,9@Ô`Mg:!"2DDfBSS?hp,E62^.% h50:";ʏog[簥5Ҁ*~*~x]-[44pQ19jb,_c%Aj2ɰQH픑OYc`Nvpdb1D2c^Ȍ}{'IJ0f/|]dO# 'B"Ln㟧$.5l۶ۜ [ qN'Wgf&Oeݏ ErkkY< Ĺϔ[︎vs XnKdarnr1 a xl, }{甞1tt BD'a6.e`B,;P^"YU/QF/& @'Y /WACQ U /rf->zmc樽elʻ+W,7 nJ}˗j67_DzRfsS^^AIIɌKyl/1NOO;KO#) E<, ll&5uEЁ6oLqۿ\~大S\\7 ig6?x^>3ϜS9kj}pżU!;K>"%x۶h߶CnNX%\s/Y%twwiFQB (0b S}u7ȟtLgӦtwwnuu=ڷmc f&jR2r&HZ0D v E# ={Y`>oVѶmƵ^KMM =MMM</i{]w|+_/[n0 >OƩFp~Łshβ3XFOch tNټ`oǻ׿~v䒚:E8ܕ8`ZW0" w044;lf*++IKKxȅ'Kgخyq+颾6r[(zk6L5ȥjS@{{"-t99K,֮][ouܶwuyyy꫉Z\@,5k׾Əc/sUWqwr7phJ:6MGpba7%Ĺ0/҇:}9,$]Ml|lt @Fm&4܌=*ʦ@ pG^py-O__X_ww6y&i}B>w=Z=Wb).yh5ךފMƑ!Fj*L>ᄒ>^~eV\>n6222xgjkk1M~c477Oȉ5X= Nl8F6q``X)Q̈́'nڧSQ:Cazx5deez]fpg:_N8,nV'6aMMMlذVl۞V>kҶmc7:d21 w`NGvb`cH&Eh4H+"24roߎi,Ydv\֭[۶nJFFsΘ_|1ueNp񴻶}"dpb`!R\vǾ sψ48Y?0@[[St6NJP__@`2+nsԣ}:_\ig…Xzɘo7m eٻw--TUUu=SGڈ Pu%ъub{CZi LE.&A,MQ²b#fJ+"MIhmm椨׏i;^Ucci0E M ÃŲzvwm+̷aϞ.RLbK^GI~ f&©r ֒+tNeoȦemldK_hd{0 eMFƌ3܌y׸kN={X` B1Dy^BиFKD>4"qfRbQ^ 0&.9ft:}H,EmOlIJ޽,!GVin'?iƶc66TfȪJ='J.av5nD駟駟8D͸OBnrErشi7pCb{8f۶mtMm^x!?Oؽ{7Չ6l .8ktʶmZ[[ٿH$2#ݵDc&J?좔v1z%h% JIIm(!2S-[Xxhfq锝u]ǓO>&R?gppo1vŊ|xᇁ/GyR.]:% 2&nH$J4Eal89@ ! qd痓1) ׳tz5EW_͢Es`cm;w:{_٣-b{"np+99TVVGPO|a~"0iiA~˻}V~#uň;=¨)v Fl݉"/x KVqE Yn,#l`.|O"dLk˗ظ]: !LBD-WxpRh~~=c9dMX---@;cRSX^3E|%## .;H${߰,/<娟Trs3pR ޫrvӋI{)s,:8@9{OEdR""Z]]uG0H&k(co֗NhDm|/^GZ vL;ZEIn.#G1c ~m¶,mlۦp[ٱ#i_: !gpɓOBdat8,R?~EN'Qش;˪OH=oX7`cH9^L\v\rIyEdfP""o>l;7 c?il?4T}}}ߗg|w^7+իQ]]75lW}}}xہ< EZT> W&4ef <2KKg-*&5Kww7~XjÇŹtcs[zi tR.0B`P"2#"Ӛ뢯/J,T(K7XaYOIK=X`-U5\n)))IIւzZDIu@P^^?\xf/i]w=|Fvӆ/8d,<3D maY IDATIO֊8|xpa`|\Pn0 \lll7ܲm }0O 7 ZZb .`W{Xe  =0;O_xW~Eް].sNҞHr^T~ݜT&ww$= Kپu+XC xmo\}N6WK0u% ,q\i#DD&c; "Ax.$y#<… 8qK޼1L:jV\ DFxf@]]Ro&u3zxb ERLܘMdөE"2ȴvy/fcdbPNj8x}6rz,noO$&SRR7ogft0h+*5t̾ɒaUUm f4ٷ|9MeQe@ Cv8bsĄL08 #ٳ3x?R!"SB˗l@"DiŢ0n"^6nlMj*Wq~" Il˗җٶ.,kF`ZYz D/bJ#^P[S1ؘyÆV v {?'*&~yESDdƸy G1M3 F8C/eta`I/Ճh |!i}et3ĊTVNi͏su145SftrEmmlYmQS35F ` d;1I0Fk}6pNLƦ}\RRsI쑈Q""3JVV CCz{02ENz,L,yC1dwsIT P[Od_r?*`GyGe'Z$[n`'.W]EނӢb\#?$%xkwSRqtmo}Zm/_Οs_}ubEIロ&Y$ Nl"80qa9 3HKSsZ @DdF9|6nl`[ܣ΢(v|K/,dsy^㏣ ٶͫ'ciW2H::;!X2l8pÑhw!@c#p<`#==tvtmO)k׮M#cN+())0 ._};F.caX80G`1Bbiw="2DDfcC2vRnʈࡑ8plˇX9?}wlڴ4222;RzPX+7%^ABИgx߂ MMM@,(zIb $\>O7R0 4ٴi#݉'R8.eq%9 ֓O?;21000YJ+"gȌ26-ᑴRq0!aBdylIgN֬LMpԞ@!~?]c{v`#G&}EMMMGtle>%V` ))044ĻC^^>>]=Ubk:{,b'\DqE>&!,:9X2ӣ"rP""3N+_==A\lg >L #.2&pWCyfo=_Y7=~bjOWinnDl es34sNzM s@,Al 55eD蠧I:CGg';׋ߏ 8p"<#'S /ŪwuuMii t[z~y!O]]~ş PX 80 ^oiJ+"g M)7''贼eĪC p8HCScEQ|KTTTԴ-|P]}.]tCCN9)7ɔѰ?ёiIsIL sS;{g +|9s{0Vz. ."FmԴ >/1l<ߗMC!N`cCp`KXq8\dg)Q4""3=\%S['vb&4xC89D9``0NZy7- ۲SxcsQVVMJK3Ȃ4Yj{ EEEf_MMxϧ L208@fFEEEĹTdgs4imkîb 45IJQt 0:Z×b 0:)|!0 N\./|c'iKDth˗xeelKؚ #pу'\7B6Na¦IIJ6ё&u1&Xݮbu+z>Ji+mxY''+~?Q?"'ݪPRR51ګ)943gĘ---1)iE^]Mqq1yB[@>=4|p\fFJyZ%y?a8Mh ߔfEUXD"9gD"$*[,6TlXQcM)0|;#&Cԅl\˻I8R5,^~v#& >,WXX8*USUUUs6Џى)..Ƣ+==BCS}٬UֆiMCοws3:FR"&|,Bq\0fvf՚&+K$s?+H=66Ʊ]x*!Ze/h>E(:7hf  ud-q!n>c mg\#[)]<.文bj6oFUm"//ں::::BRvǬnsҪDnAu3m 3 $t#UUD"9; dP^^̵׎%?C 5x<ϝ?qqq`&33 !bIi8<~O=E{U躎k$k=ʕrGyرcI[sq%%(Iy9 AH\Rr4{X{Vh}{W֝cF´n"\$&r._re/y yӦ;t5@i:=\nj8:z2EkJC"D2X ih*X)Q`%LG0*PD|I@b/8pb$3AŬSV-< 2 ̒M\ZVPz>ux @  N- N(G:q yJ>\f]<^Sn*As]{(e%wv(E+Dr#D2(//&77@ Jgg'FMN dfNJ*d  p_NT\炻#G㜫qD](B2c5ʕ)u&(ᄏ^Rwe͚%'e֬YlܸlQ05?f%~_TXD߃?L'4T] {R\p׭kcݺc;UIfjjTg)]FE^^ X,4m2 ?g@QTVJ_;Nqc=Qk) cK t8us?<k)CtF ;袘'|;a.`'bd6vW"7HD" Iw<{F4 tv hG&mR"ЉnG}-Du(|Io&xDlBe7؁@ŴFb'!S8ndcTHX8F#N*B\rw_Dhk re ?O,5{%`J9dfM"8C| Z!;E^UL[SGk@#[FSiZ*9¸q%\ ={@)(Z6WR&Rx4`C` /cIsS#*J$W" YzZ;Y#QPRA1:c hINдf{ȗ`G(`2@`_A!H%Di4sRЩrwÔ)Shmݗ|FF3gdq:~85t,cʼn%~oR`S#~.@A]͟ H=z8V::b?3gں)SpwCug;!ш(QcAA,SL ..0@sPӨ"汊vb@Gp V|L+H;D"$B > IDATPN' a$݌F7sY)@=`!F.Pf_$`SŌxh&Q4:b! VS;8P\\, ), >=ߣ( b:¤Aqܦk &X KvÅ`BqF )L!LZ t+WT9=qre0} ~?D"[(4F䘳DN =+9ib/ NQ0ƮE4>&P:$>FF0;H.ЁဨX,6\.L+HK"H4'O?; b:BD01RQwPJh?*ࡓ c7QLfJ+vh#/E  *V⤙iiuX3:-OwS[ZB|n/|*B*:s!؉'* nXz'R lB*hdvJ{nƛ(ω@IIfҎ ٵ׌Q{'dCa#ɠQ|I+a2(,(gG`E3?wA t(Sf|p3D"9D"'q+WVs]ݭiƢ cIxI#F^ md:::0|q15b>DÊ.ƣU1v3ȰZsַ&oMJaɒiqqmR\'cP  c7 T^xL6ADjnډ%B!Y42F4"TPrYSt2z˜9,[W痢#> Swt |v᠓V44bpS?1*Jn\tM;>&f~ccT00DZEvvY*tH$H$Iˋ2^xa'p]b G$ GNɤr&S>g>9Fb!J6 LsFB;#XŌ ЄN 3ŪKqn7Ə^,}A%ic*^Ʊ8:yl45#RSHjSDoP NP8z_^{u5]x!۷l Ƃv0XFtvJy,+%|y%K4* I5:0fHvK0&t2_3o^˗D4AV_>)\}(^zAӂ?B>* Pm|uld GBo*BFF> 6Gj23?>i=ˋY444Qp\yŴWUNaa!kkF&Q,2Q<dPa$@AF1YL0ZWQT^70aDu@: D2@/.wi#XtttFhf qI'H6Ѕ B +BL0L:a'‘#Ghjj ߟE'%h3"Bd.T0=hB:Ƞ<"]aň@;'qg1A(*.tl%HHD"HHj|H#,Y8^odc[pVw@7 4qx +)./5+Z/lᲽ{)*ͼvdSxq틄<E%N(%p^ a!kh)*a/E4#`Y8Bʹr,:iıbGhdc,t>mTP&M:!=ګiii_4@u<_ 3g)//OG"tb`(G3_2%Lq,XZ i130v9:0H죪V<4!HHD"HAw=@KK]OG;u7G){UD2{DIN.ѭ]M#3).}$z&kA{UUW-{DTX:YT0**z͑,^]TUU%D`QUګRDL}v;h<$ֆq̊6Eu;ІF NF%E6XJ#TR6*?#n3f kEH*.AiFD"I+/y >Q81Ffwv ~mfSp:D"HD"HNe$ b)wV1}`:qEh`W)Wvmm-!hj%+-nM#D8tcFN--tQ‘BTE Qώ'֑N h#"dr 嘴V%@3KÁ'-X |99СCDLct eQ}wm/ރ⌴ Jq<%#+nz #ő# xD2TD"rظP(,䙩l-dӄB*:p3d&rdem =6> :62ԈLq:kұ!tܹlPΝ̈́BqN+Ôxo?3EFhBN/"lZ2Q@% ']atGtbf]!hCUIKCQ32& -o 69tQ#ߠւdcN',f:P$mlXϫk:KH`džr%fvWQұ2(+$H$C H${gD4:tb i&LaqS\TPn<̲bfv=l@M\&IWrYꤸX,=xkAҠS 0t׻!BqņP94;w?P:N0b\&RI&T204,hɡ+ Xd"aT87j_qQ=Ws{f7})E, ba]tѰa5 ,b POFX\S]8u48+,|IP8aGB۸rqLFD]ȑwƙ$A: DrHӺ뮏hhhCq X  ȥbMLWNm&#|D;6&7]|$8HD"H aC=H)mX,+6@ d@ ,DF+G.Ѩ!b홲7==hH! ukdृl .Ep8UPhj)d5I>׍b=F 'DAAA0}T2p(jqEEQdQxjL'M=,(J:Je( BO{z3mrBG]#nyy.Y\"HH$ԽXLG Ff,,6 v8:FM 6tMc){0C.VCE'.QFA8D.t0Z! fj] 0/z(`:DلJ*I&~"iye=Vq': s2hi# ?.qT0vY Eሖ"miÇa}.:AUmxNL:D2H"H$g8GKK0qbplf_L.{tFEa#9D&OQ(F9>V FfFȋ ^7YZW3Y*:6㦓g/C.5`$|İ@#"6plt!=6GPl VtM ·D" H$9@_՗,W_Fww-M$rFYPЀNtI I5\GXl(hRŁnz]7UMt:D"H$\?]wٳy[Qn!H$D",XYhmm+gddp뭷\ve)}xꩧя~s=}q%~z׿lOF;9NiCn233_itwwgŊq~s=ֲ~PD<9NiC;9Ni?@: '}p\x^ x衇)}mۆf„ )g`glD"H$ɹ$%%%,\2˗/Ge߾}~zAAG9csH$D"HvH$2iii /۸Ylws  p83 zlD"H$|k|\q[YYIiiIyl2>䓤t: ý&ڜNgk u[iH~'!wrH~IkL8?;lذS2faa!ɶV^ݫo}}=wޚoy}ݘ9sٞy!wrH~'!7555̟?lOks18@nnnmTTT0qd 6mZ/9-3GsD"H$Drv B԰hѢ=󖯝r:nv!xGQ%F\x1wx駓}{9 7o^/999vm_D"H$ #w>N逘<޽?Y_lٲ[n[obBo֭N1b?Oy'b̚5~k+ҺD"H$DuEB=sUUQ!D7(hg~i&PUI&d,YKzJKKy[ΨnD"H$ɹt@$D"H$CVBH$D"H$g H w.r<駟.CU^?\sMBx ƌ .HXua0Xn_|1.~z*?}g{M H >t.B>=sիW{mܸ1oEEW_}5NssY'_lTU_`l/2qDN'<3S@~gFϞ IN-2]rNPYYO?]wٳy[Qnqo… |8 EQҥKO4 "hllByf(x{|Ro[ԯ wxRP"`I n7/@4b 8?RjkkY~ \c?6n;~vyדmC~CAWWW2ޱ ƦC7x]wݕls8|g՝ٝ$x<7|r m .tcvʾ}R9r|P__AO ڵkSe\..r^/7o淿-c֭ɧPO{ {g08zoݻo~%\ºu6mqﱂZ[[bC"X,|!{>̘1]y~ǎ;Xz5@OrRHDrBD7--m_xᅔ׷vw}7˖-cܹB!GcBA}&8Kܟ]z|__Mondu]ǢEX`կxgtuwN7]t]tQuyy9_=SN{ J?&l6BvO9cާS^x㍔7H& 駟>}1~>d$hs:dS_BpTt̝;7%`l:Tp:#q]?\wuZ !Wc m`0r:D>aiw};VOr)gĉPaÆ1aZ[[m^W}_i{N6I}}}MWũiaaa2 <~q>;g"(@+1'w?Lc4M9Q4Uާ=HKK#;;wD䔓~F;gt:qݸnrssٴiS~7n#lӧiӦB7oFuituu;$'<%9굵.GEQ-Zl_x16w)}{9 7oBFFW^y%SRO"< u555j{غu+W_}um06*\hҥKmH^z /#Fٝ[uرwy*o}+WR[[lOؿǎ@muWJrk؜"]]]y}~AH}Qv G֬Y/~ l-­Jqq1Pzuqw ˗//'? ]]]}v]=ZoB0?Z:c d@9eBQwyW5I`""H0#M$x!d*2HtQ/8>p<߳3ϕ^kZ{VUǏ{O3]fVp8Q+|(mmm0RSS8cΝƅ /_:۷ƩS feeek>>ҥpMNN?ҥA(|v|@ԔTRR5{^MLLĉVSccbcc'?^۷o׎; IJKK5g9 M  Eեϟ?ƍ*ͩSkjٳ9ݺuKɓ'k.*77W :Faa^x6LQQr4<<'OѣG}rss{IRvvcsssTSW^ .áRJOOWmmN߿J(!!An;z]XDH0_cczzzT^^y;vL!S[nնmVaJII =}TEEEaTQQ:66Vзot@{RR#td-..ju]('-׫yy<=JJJ ˗/U\\VdZWIJLLt1 Ð$lt ?.?~VT~-,,:688#G^A333:tPXcoٲeUZϣ3hil6Y, 0Л7otM9N߿_ /??_V-ZXX؊َ۷/r_C;`5D߿ŋJOORgg}>,I]>22"߯ݻwچU\\,߯?uK^ D&~Zn[6MjhhQoo}srrw^h"nАպR`cccVii] `Dq577ZG rJȥXNSϞ=[USS&K}}}_7hǏǏ[? b@QkkN|>׷S/eeea}0 rlHbbtIҫWx>$<,K؛"ޮQuttDӰ i LC`@4!0 i]<.BIENDB`brian2-2.5.4/docs_sphinx/user/images/morphology_random_section_1.png000066400000000000000000000366271445201106100256640ustar00rootroot00000000000000PNG  IHDR-0 IDATxy|\eOmv{tH簉 3 (W%E#*(lz;Q}AA@' zZ-fٓ$+̙g*,9)X !H!+ ,!DҐB$ ,!DҐB$ ,!DҐB$ ,!DҐB$ ,!DҐB$ ,!DҐB$ ,!DҐB$ ,!DҐB$ ,!DҐB$ ,!DҐB$ ,!DҐB$ ,!DҐB$ ,!DҐB$ ,!DҐB$ ,!DҐB$ ,!DҐB$ ,!DҐB$ ,!DҐB$ ,!DҐB$ ,!DҐB$ ,!DҐB$ ,!DҐB$ ,!DҐB$ ,!DҐB$ ,1Xu8Djml]6ۯ6qBK qw00j=?C Lt}3`AaGbHbt}y9hl\p}Ά?R>c,ij([k~2[I ivL nιy<ύv{Eғr(ӊYVĩ*.TnHz2$T{+# 5v)oVכiT1L$8hZ @ ^?@WZ@&LHUW)y?dtX[.8WCI[2*EQzWo3̂&H2,:K֌̧IDK f/I]֞Ȗ1=Fڗ6\N/rXS:3|X+Y%p<U=4#ax([¾l> )`W~5=뿔f8 4dD>H&a[RT̡a=Mb/cvbhԆӂ}?ÝYme02x6ONQƏ!,(xt]Myoqt^ GJJ|t8~uM[?F$zY%#,fJQ< C9LSVMɄ& ꛹Ns;l@$1 ,o׹E̅</xz1T~(6cfm9äTtGK +// B?:M=j fA C|̗8#傊bF |;g|8G$ ٚ#˟f0QeSfkpuh^1Ht(+[~o\#ܮKIĘ'=,1XR )09b's5@4fMv`>LVMYvlO!Y$Raeˆ1=rKY(teNN94QAEfB3"V`.)'"II`[ B: ]׋/fzo}8 *a'T M/WimYU ǭ@@grǰITuA9dBSBHk|}0%!&ڢq|q =a4%6sQKBkiPŋ\ _< n~VjUUC hD4*;g\o#GXD xNKM[g) =j00 #zF"e,G$]y0>|#t=ˈXNh wC];'gX Ͻh,Dj+ CYizoz !1Y%e|?NfldMPȴYu04+&g@iVk1PëS R|?}F:Lze(x`g]:t { Pjs<𕻹C7tNaT6iݠ|-F1_P۷ʆ_sϢ_@["! qy?mGS~l;* @ 4"ӌ#!DJ}|p˿Ƕ_2;zG@ D"n_Sx/=X;Pv1-<{Ϧj)k$/xPzl(/twh&hR2  QPRo [}@*"vчZ UP^͜{uV]a%7)ha\2\a;.TCd!KQ̰os8Z 03FtAd˩<{!h|zE¤g8ӿH8M c4L2$Lb? c#=Pluw}w^TuP 3`&C1Fad@\ uMHu$R5VBiOeVÚE vN"Z٣ bJbvX,ojee=Ǿ=)(Vh5,տ0GW*y~.g{s܆јjkg絿*ҟGVc&ʋl`iPk>_Ӏ:6M+@Tx[q>ٛwb'BWGN| (EurA*TVR 30#kVY:+xIӦ1+%ȺqA,w92!9}]cXㄦF3"e_nr5P PWxwQ S֨v8BKǃ3 J8 föz}9 q"ޒw3m !{X`20N֯?uLqр#î(/kR^EכMs*Q`TB^r`o-7 T.]|qΗ˾dz^ =4y+8Pj nϊJ79\i ^7)ڠoe 9;ڦi&FIjYjk[ qD]NNzv} `K$Rv0L(6͙w'Y?Uכ=XbH6Yp.1a%%]abmгxv36,*`vN@~eC9M~wVR߇Jt}adzw9$ VJ)v"0OKɂ= bv5AǮe}fvnæN?r<ziV:Xd*ޯ a#4ͮ3 (BH-dM'DSS6qi+`@!L w*OsّжpyB̂*P`ΐ&$U~~۱~8 P Շ$+`|NJ k#,K{++Sh6oMyF0 ({C1.I`&+E( yGO8OX ;v 18aRwE~)Tt<'kH(a^>?"@sfC˧R:д oˠ|dyor?pt/=rtDH`my`h2Uy(r㟀dC0~\PBcKUu8:YYʺ"f=o7@Q!|_1I`xRw!O@ ~&?J0}iy[ߟ;*J,NGMH7wVåyyCba| lzA A/ÞnqB~^F%8?KUSȄ_~J`O0\(02(s{a4U]`e Nk|﹎߯C?ʑH4UpGoS;?w:l1ys셏ɂztvp(Bc= $h\ΞEU5 a5Cz:6S'LO"܇O·4UF^]pܞax҈ϛi?3o%E5^r>uB:ş`>is2x0vthbl|40 #X4~ÐvZ;-f)̀; PLXrZ9Ḟַnb*6/.x1*ӄ X2*ł휰ܺօp9^ޗa>t}PgTD"og4Jۑ2M;+k_.4tXQ s^~>pU(w:a!4@`͠9eAsB]f@%upQ>3Q Y fW2(TCՑ?7q,͵XOWt}vg{P 1Ey1ty~4-%wõ2d7X$/AuzZ(  1qH`uw> { od]|[0< ` %([P 50W|{*s0_;O87]aa`|3c* 1x5M-T"(!ɚ w]?>U2>9%P_?rs;.;O9PX{|\⡳xv1?s%4A5L}0߆\W_FR6O!,sׄBZ+pgAAYPfՑsLOU"0SLQh0Lf)` bB:@`dR۸h+D`w]tc[_ƺ8s sNKX.f:`*솅|*/#N罐l/Sxs<9J6|9/rL6ki٫9~!MQY9y&(DWjMUWig\Zh3OA'7ҡ1ժZվP (4A_\L,X1( ߮&H]Ix);֫ A=Lel=vO7߂hLC3YTHn:E0*d ̨Py|m֯'fJ/ ?󞻊"}•yy3 ʠKdi"EYQӬx~SP߾0>5UQsY11IYCY)8S -Pl 5lAzsZ(aE9g 2.p%1ST~`WA+LR6;jyS=Yu__R›R|1oi|HI _SLX:{ιѕS߅SC3 Z)_ 9M55pt_ _Ċ8#xp>l[ǵi*4AuHU3XJ JY4y$kJ(J5 .k0 nV(D$tSUHGEp~uٽeڇLa7Ky`*<|%/-I0`2T٨Ot`ULŒZE~`@o9\t+7 wT[3YTn( r&KQ }ǭhZ?FBX$L+y3l~|\,.ܜ΢2 yZ{ h |YN/b9LT5^ `re;[iN sOUE,P%B~Ywp%3~cy,wCK ̂7ULpX@P7EL=sJucv !yfAyP;rr*\ms&0J8cy|e^`UNG4?‰n*U&ȀD~dX(  (Jj8H`}b/__e%PYȦR]ۙy뛁Xdb B3ܰEP3Yװ*v!pO8zW€ n4g珻3EA1T7ٖ: =7̯D~Wػw1 漏Y=s=GB8+EEPu&D>7W=RiP `i5ZL$= cTdK3Y,_;±Ya%l`眜7_OصEP8P u s$0i)Vb=Tw^ e(|.Ỡ M3pT`piO1KUмPq40m:~sO1:8MS?(4 UUgCT5ֶgH)aNSL2P5Xjx)!9GxT PdŁv E9Jq,%XSe bӫ9n Y-6~!0Ut(8cymP p]UgC!Z(?)?{X.Ȁ%˾b⦪G@  ݞ yf~5è Bi8PEQC 4AdCJ CBK~2 !t63gֳt:Ǿ=T1ALϊbdT@-|se8) dK!\< ^saadB TASDid<]Nj'Sn ~ nwl_"Xf3`;,ӴENtF {{"62)bvAO^^Co=3!&C=L `2̆LyoLASRIDAT Vko~ֹ`xB%{b]E*!#|=a|aa|몒:T<%oe)p6@1=mtN( `;tXF'c ,lU Y`s}n_u/b?z=P 1X KU֯KN|M&M3+g/6'wT5P jɲ+UPTXHQuwa'*j^b[an#w~7wA LC xI9˹b-R|:0ɲ7@i_#6,;5)Daʗr/.}t(RB?َuQQP S UQjj#unb`X;c j!QWMV`,։o"aB> R?Ϻgnikb;CKTuT-cBnٶw]6V$>ţz0|(^}cyT,4g|`eP ,lF\=P;{jZkxkC9`?dNm6۽GY`+^o׆BPb(b/ۀcOBjO|OMWb=Q}I1&n`&[9S^u-$# ぜf U(Q ,ִGB C|3)u(5=I~ UB" 4-M @Mr>.KMM*?;*x]^UH0*?7 wO"3{Q(zO*Y1vLzOW90)N={AѴ;cb\2A43i%Ja TA1dl?ҴŊrD98Htכ(%.sdP , nABoj!;/u{9P!&bp5Wz(ZW?gI6NgRd?ʡˁYseP Ͽ^Q2L:TdB:PdUXG ԴlUNRN<0k("5jeͰ}& дE2XeIZG |/?I֎BY(%5 a*NuEibH$#0~`}_,MvLz(&?/.$#VjQʄdP (z?ijɲ?Ogþ$#ϷNRBOX/?zEŰS' ((a<GIZ4h$ekP(LkL8sȺ(s0OR̚QX- $Ɗ;ںv\uP 5QDGal _RbhA8b'4;FLŌ {w9HՑHnNbX5K-i]J1?K <]@H33]R(wc$Ɩ+uɲk1{8!Y\7CQPߵ,>s_*r/nDNSUV/oi~ UAy% $Ƣp&;od7;;G}eb.P EI`QA!Ԛ=3PȪnZci^|EiY(}&cW4^N|XT?#;{i.ժzvIg2k"Ʈ,{P aa2x]8\ƒv|eP֘AӎqJ>_8a5~)Twwqz7A=F"$ƺ+q̈́/{<㙵^(QhrqRih>했$ ;*(oB5P$(:-YfI`%+򖫪is6rYO'a4fh7C$ iRUw*K(GIX/iY0 %B+g*J!TÃ99Vf Be׾/(f$ d*J!rrM;RQPO9~SUEG!H+Ǡ \/4 =@R$*N BURi*td[": d`,VlnFEvw_1.^m1I`% pOv~(WMR J+e7BP-'rBMq4@bH`% ־A0L3ӺER eԎJz[ 4_'YQVչּl7H *\(k20JG-bL'7H/iYI_GQ2AA:i|AD$ƕ J>hZuflo EP&1ʳ0dH(z#5o IrlAI`OwPK! (KAָ`,V07QVyVe.z!5Y:,9(T%X㜵A&Cips1f.Jkk ]76r o}B1cT*AH`M'Ǻ\%K/|/v(J|KI`MWr]£ݢμMn$&TUWB1{[[P~zY(ݒ]ٱQm.zF bLξ4` ԴU bJX&=,q@οVAuz-%%# J#Q؄m}T6$D99|4mP Ńu,({͑,$Dg JGTn[/z$%ѩ47{kEɴlѕ{uD! R}.z"%zw(x^ 6mEO$DoN(x9U^ *N$Aw&>92ϪlE'X:?3 ,'Cbhr諂 Jm[pw ,W0%;|rJKmpR(1fFX Z$wuUPjTPn*pwHK[^Jt Jpw ,1WP2LB@K Pǂҿ uAiuf,Dr !v̡7a,E;aAXPzߐ^{lH`J((m‚mpwXbPS˺pwщ:6^P͆mV` %QlAt%%L4 e .O֋NA kMsj2Kl"X"yIK !,(E'Xb%P?'gΑE" ,1, 6 T9]$nϊ@vǀ.sAw ,1\,*rkP*EK #ĄZ_c I`"徿VӲ`u{i$7@`Z*ap0vBvg@4mDhNv1E`@*,E(wLK>n}1a2$H=O$F9ךf,_t߳K]jRwj.;/oT>XbDO(]'| ##S*1:vRl6HfXr)5I`Dsr aLGė fxZ*1*K'beV-\^PeCMK4]ɬ'CB1u92ͯw;N]S hhz&?aLXWJǴTe 3h~~1hXbQJ(ݜ=U='WCخ~AzȩgOwoVRC5m(?2$#duXӮSl::}ZX7Ȑ $%F^o׳b؍jj.zSW4ڿ(tڇKG[kNߴ?twAӤ$Hؠi]ӪXiPS=: v;sOFX+XIblPdIkf>ii8uXUuGB !z22/X=%ҽJZ2$#dC<&XN.O46,m\*ȹOkOsJ~W`#4@-vVҒ#p4UmKuv*XouO}uUVZS3Qb&b";L||745W;ՎZKTQbp%F pz_,6lNK1}=,Q3o$( թ=q01eE~P32u$(iVP0Uj?څN.}:P IBKYvPS:MtIj`ǿzU)/$ĨYi\ {-YĞjG:- U=_Y%ϛ]jNit b֨y^!2_$Ę~ p joz:p `\o$n pӚ0 w1ڇ,$ĘN ǓS1`QY $S[߁`jǽ;DlEY4mg$& ,4>u뛖Ą"%HR%HXB!%HXB!%HXB!%H=(txIENDB`brian2-2.5.4/docs_sphinx/user/images/morphology_random_section_2.png000066400000000000000000000221131445201106100256460ustar00rootroot00000000000000PNG  IHDR-0 IDATx{|\uW4i6M/gz&\bAAPp3Ұ?v]}]wOe^WTsP_Ж9iIoi$GH4-$1<ӓG~{0DD*KDBC%"P`Hh(D$4X" , KDBC%"P`Hh(D$4X" , KDBC%"P`Hh(D$4X" , KDBC%"P`Hh(D$4X" , KDBC%"P`Hh(D$4X" , KDBC%"P`Hh(D$4X" , KDBC%"P`Hh(D$4X" , KDBC%"P`"-%j \͕O[_Whs]U &fpKhrop.JdR`wF80C.Id4S`Ruo8eN?x\s'm;E^ {u_V؛rDF5V.E"B McP xu9"5X1a7LQMc- "\#2)rC tCO4cs'CBe>rDF5rl'm/刌j ǟm{ֹl,^#2&2)FZ&kݟU^[iϛ˭{8vf_ʫshU"P8NkdCã,#+H$럈I5ؔ |m !koѺKsѫ]; _:-ĿB/ k$h>^;#\nۮrMLx\7BdҐp(b'l3‰>cJ]N4l`e-rZODވk(b^JKh9x 835\4hWrRH((' kN~w1-!h[pUxATV^V>2 RqR7ze}"ضvk&? :ְsN]GI/Fso~+xìY[}JdtФI4k,I0ɪ{}7ᶷ/쏢}NeR+ۯEF GH]90nf?f1el'ڧ>m,<_EFȱ2]{\wm= yˣt{e /:\2 8^6V%wyt>Kbh;U" s}Gevݱ,-Q3kbcֹ[<wY( +?w-.WϺS~T>g3vaEF-kJͶg_+;2`e Ks3p1@L6l\ΰ780w݇mM\$Re}%2ZhYÐwyį˗kL;fA٧jTYBc^\A*9lz}nnd ۞~7 DrOC![zV`"۪b脝g9 c ׽7CO+|zs`{뮛;zo9.m[)Ko,E/Nx,OS8/ ]7W>0v }_ߎFOxv^sϯtLvzXC7.Aoǧ nʕ*P<DD"S#&8ߐHjg0C|}MEzXCØ耒l:cnJlmJ|J~~*o}soZb!n9l3P<"[gxnSA{)a C;+,nb X=q0DzέdMokov{{c㵰Q>ٹB3>%{29ih\H/2KA(aEFzXCu?t6c&smӗ3a/-k2X է}u_db }/L?Ց~(C;iKK$2q\7 ?bmD̞ʁ¶{m!pCd:ѨKbL?'ka*0[gYkj8W;9,Xddhk4Jn2fٶOo4ykző$cf{ޖ,22XT*u1S ؛J`L﯉F;*2XW*u1UF tc㥖5.g]=<לJEFkTKcv]ykXlE2y1e_5:tP kkl1`G>ȴToh`=~)A"3'hiY&˶REzX!`>wFQhlҘqTT+ p8'qfV$RJ]i^Moq;" +|imh40sUHJR]vߟcY8 ._>9nп5BTj1ۂ}9UeY`FXsS*5͘}m<6g+nLD"=pm3wmdIR`JU }/&NMK^;ޘ+!8ζZSDw,r]ȰP+Ī#jl4fv1k}FXVuXMwmB[f$?)BX wEKdY74(ab9^4' K+O޸ ӠK $O֜򥺺?f@,uHP+޸ ЕbD+ܘJM5jK+ݘJ7oLkJ t*FMEd(mω΀X&*'Xyq.3f?Hs]P`O3uyYG+E"𿞷9刼] <80kEJPWo \iVkUbK@4zj bMEϧ*3=xuZDL 8m}X%XĶRhjhx?.u1׵XoZ.*8HmѸV9H( \dBh_u-"VJ>eLu"k`(![s W<\C]0%mW`O*yHĘzj̊K# P cgَ3O%*tuu_7oCvT [c-+8G%#KU3E&j5{큯+X@'L&W'OpX%CHUvo$I{4=UURcOvkjD"UjVA[j=mgd_-9KFOɦ2Fv}XH<߻jUaɨ5pk5Bj>\{z KB@KKa҇w;r]4vĻȜ8QQ`IP29Wn-$ , /5~ofRf =HUBTj1hr]ՍT1p[4YUXb8͘}eVaЭ9bUUݰ@)55~" , HmmWofӛ}ߪTf/ %ΉV?q /$,NxA 4Icü<Զ ؟NB2+iH(wqvXHlL+!jcÓ|"rdiH(yc֦Ap"ݸGXog ?Mf %-n6# ,OW8|n5ݦ ,[W8)۠ ~~V^P`I>[g ᾆeV)$ݔJ0f *L%wq?jhآ --kWQU5Ų6 (ok͆P.j t+BH%"R[-6mm]Hd(EsXR@pcg9/~+?%0ʒkl{f+J=,)8clw<9[BJd,hy\W$oK 3ڃd2;r]9(۞S`'uxދuu\qeK@>y[G}6huC~erZc0F:牘mۋ̅I%˶-ff8Ƈv~Щs)YBmg9 Ufyދ6P9$'X (sݧYa\hh41$lcQ5öOzgXRl{1gmCY0qBP`h&Ϭlvd:E%q΃R}#XS p ά罏z0Q`Iƌ9}<>(3Ǯ;&34Pms1\/𹟻?qc (k\eWykBeDpU{?H&k{?rZ P-k1+bn8 z͙kjr8 o J"?3w24H0_(Dndj۞I<=^Wt:Miy}]O]3Shw~0̂)|(UֆVI %aoXcch cᝎ ^|?$"(O"NdA;`<S)+~q>hKrQKuӮLO&?*: ,m%Y^<~PV'&7or =o=tl\P`_L^uĦ_{+`&\8gG{ ojZj-OJlBOy]<;9pB}1[ 8 |8c&eu"iػ0gmZ^ J_岠9"Ŗv'=MG[84^d$cvFf(@(D^W.xo: /se #1n_ĺKxհ^04$&ۜNLtoE,A'v}bޖ h hD5Vk_(YUU]%ht&\L8Z{0 P Ӡ²3+up\~QBFL%vq#ج"4ݶQ:9%~oq ca?L DB%xv3wp`;,k7~)VXDo#޾ uݦclbKpvfx k1%^?hYqA;ǟ]s`z Kd]oqf ,P sI|oř9 p/ew+l`yFMC%2rg>rk>\ 0 C)T0q,i k hhck`eTnφv Eb?ew3=G3_BÆ%|$TÄJI%rS4=Ɋ7Sat VI)ڠ VsN i ȲlA~R`GXt5f1'nEk{XcOp/+oy~$7tkQTV~N5cժL }1e}X{L:19NH~VTa)ڂ𱦱G"%=8 C+ob!T)~K۫E#۬|9李`tI'vpǦKϥ7&%X Omocm{H6*?hYȑ_Ä3|oG=re?fE[]wq|)ͅ)_[v[;p^$r^ld/ln=\Vru$7QyC%2S͒}m{нuC[âYlXʚvtgPŰ.\c46^Humo9-'P2暚dm{vuu_ h';,aׇ,xu$R zX"-P`_pAkc ykɔȜX$r &E(x˂d}E5<|`1LFy9+P%G3Ѷ3O29j.a;އwHR`.~8? ̾H?fLXQ07VY<HX K`FԮ5ozֿuKsX"1Ɯan2Nۮ0ô u*AMd2{`$2-ׅKDCCB KDBC%"P`Hh(D$4X" , KDBC%"P`Hh(D$4X" , KDBC%"P`Hh(D$4X" , KDBC%"P`HhNEIENDB`brian2-2.5.4/docs_sphinx/user/images/morphology_random_section_3.png000066400000000000000000000230051445201106100256500ustar00rootroot00000000000000PNG  IHDR-0 IDATxyeowNH 3!@ b*YuMO\Wwյ]\v"!ᐻ+3DRO93L&3NoO5=}]O)멧<--- "ro@Dt,"r \""`5,"r \""`5,"r \""`5,"r \""`5,"r \""`5,"r \""`5,"r \""`5,"r \""`5,"r \""`5,"r \""`5,"r \""`5,"r \Ӛ!D" I@u-~@DHC<---+9N}UCwؖ%^(ѿF" 1AQʎ 1[z)RgXI!a[>iaip߲д5c>qcK|ct|߶&vD.DO8_I9^UExytOqeez{ߟquY}H+J_ib~#)ǫ*8*MXnOWm2+ ͖@ 6Q 1h݊(nZkve@3@!^; |6l`2ope, az } Z D5e^pcOc QcW%vDC?л_E 0o?)b  /D>oYB*J!8p5^o'D``pExtU=y=ƒT |<ꪫ~0 ;N]Dd _->lŜhBJ\)D'g.eY]!ru:]2Ow@^]ĕyt\JO Wc>p-P9 ^Ƨiy8B" irP6_ ? lE 0R{p0<$Rz2`5z5l :Y<$iѫ0XOQʁ1^tR>oYzT?IUz>%rkDոQ"xϲZVMS LQ>|豬-q2lQ.4d!UULWM=_Oln8@qq^SpYv*z9W\ pqǶ߱pXjdEdG6D"okNbaKqӴ~)Kb8܁k5 ;`.bZ8|1d$J[ ֈ+,g:z9[a)U=+eKڐ3zβ614ƅт#k_nß21\q] VWm7J'[qVqEKs>M;[ʽP/DvX(q5 &<_{/WրM Rf@YKL iT tM{XU,U6%ȮHdoag!+pPwWS%@'/ lf*Ji8|ͶKV9N{w(auVӤiu4O/z`NLW~l.+3N.G7+SRu8MRVS/>AʝL$ORZDvj33=% ~kb|ؼ sog).PU5t P?sq}@{H0hn/ec 8d.Qrp5>uӏw׿ [E7 *ĤuRLhV)Az۶XVe8NC( xMᡪL;C);fbG6`G ^L؍8l,4lv(ٸ2_9<>Nl]tKYo;,3:d;yK8"Vd퓅z+J T~1Y\t~ yw++s\9׬kX"VA@g\]_('|Fyo]4-J% e H91/0V@6!J|ithGʱ9Cy{ J hT8?/e#C&@q?Ⳗ%.l0͏FBN4QqC׃5l2>-D '\ q`a+WOsa\rxKc8O/{WqXغ4gr-˕b# '|Ø%B"~?,tgzȍ,ws=sRnDl d!tU-"6z.na@@;~S`zY ;V2qB2MU] Y.l{0X,گ^JWҶˀS-4W RzL(& 1_ZQ*=LbTlWRS "]dΪ'J<qB!;\_f;ɏ1% E'.[|! Mq" e= R6BL15шbR qa\a(,:[cu}-olViU `S&R]i2[e7d |;z־g tJW a_eB}L k+c X//QPbtkyS=N8wUUcmXejf[<Ӝ㘭4`;byKU N"D"״| :<ށ`eL?z I~20XtZ*0%:s孇-I*U`|2&Yqʌ{@%"6+0X4l/o"{4v_/~*kpb@o Y04;byWXX1nP?ÊW,gG-\+֧F[)pyqӫT0y~wY@pwJ [yBM?ܸcM&\ >XWiD.oy l2`Q̸0y$+=0X8WfcWكOCe >$7oj8P1X ն '5X`;*,zf_r!!%y׳0РÃW!+-{E `Q\eB`p<w3P %ES(U YH(T\]@ /_b(n4Ҹb'UŎ =4}gE t@; ,4d%E U(1R!y':Ď T` ;, 3ċM-fůdu^i ,kw)}8_K",'0X4uo,eV-2j;; ra(Phm0͖`_\؋1ckvMК`pq?IVjc(|Ȑw&{,4,Jm@em$+1X"|ǡy͙`Qt#`QD )"'c`QLer爫3Ȱx͙`QJLZBÏT $+%1Xj9\BÌdRE)(zלI1 @޶8J% &E)SՉ_a(eζC R7KL)wBo%{,4<,JekMdR$לI 5g.c3`Q_<,J}Q93`Q^sF&~,J}Rn~ ҂k9r5҂\'u\t3n`QPr! &H pG޻X>>``QYe1_7E;Fl^ҋP};ᳪ2vs5Z1Xwև~MM{=*>5OҲvn0%JNQ Oi+B G="Ag"w(S!B?PQkŲ@݁ |EYd?Ũ`k_[sK7x'0F6<`j?uqil%v~ޔ$b5(߶S>)dM((z/>..@JZmNF\CQ c pnoOE@}nʵ@e=@m$k`M*+èק!.#7hA+EbIR:d F?χ5u(} ;5m5{oŎ͜d %wĎ9a,~d7(ƗO=@'YE1V>|揊2 kE)pۈ!>9!!d ?VVy~i? 3йoJ7BRr50X Rfw,_~ իbFFha۴dB݀4oq50X˖y;#aL;^x7lI)xlUsJ+L^?C.PXe]㴇BfU>:N{UOWSsc0G!. 5E)83'_u=d)m1XD5p^ieO-mu"%h(G}/UmI&e,|} >8LBxOE4>0pdYzwO0h( Ls,P l"J^.D'"J=ǚaU(JIk\Jh(BZIV0XDC٪ t\Jh(@OgsT/Y@+`N83 E)8y Pr Ǽ Lp Puݏٛ!0AY k%t|iw!J.Pr`=|m-& )76+Ji~,#L+a %W@N JR :,ke=q h( ہ&Į  B`?eY;"lc`{S$>P Q8N@+ -. Pl{7K8m ņ10.Jy4oڀ1R:$5,D< OG@ ]>ٶk(" hJK=t, _ IDATf hwPB=/@xޟ4F qBk`: sG J~!f.)b(G3wI UAMmh@+®~ 90p`-|(b 0͡ c=8z=n~8!& Q LZ`OjX֦HD:Nc,ck:`p;#` I !/ Oqeմ=;] 1u_~`Lqػɯ9 ?J} uRn77 ^B=E.Xnx߲˖t)~iS2lR؊LI[(jz\L+C5Va#f3|rwԀ/`˗_t)ΰqn_ӥ&VR:j\R64lk5ga~Uu|R,#}S@TQ@7RtGȾPhi.ǠWiѨliwcb}57G@>L5n~Wmw=(p7jUsybJM(߈ + sP׼e+.Dռ;0𴴴${ D4l=#]d9ܭ\ gaV"qeMuz )J>7'*a堫M & x`"?>덥ȇʙR65[ڸ^Sd7%zh@9`MRa"VBEba\|`5xED`k0XD ED`k0XD ED`k0XD ED`k0XD ED`k0XD ED`k0XD ED`k0XD ED?PiTIENDB`brian2-2.5.4/docs_sphinx/user/images/morphology_random_section_compartment_1.png000066400000000000000000000225771445201106100302740ustar00rootroot00000000000000PNG  IHDR-0 IDATxy|[՝6G^;p, K@R,)Bi)3mG-]hKLi{Zi@_]R*CÚ+%{Yl%$Ym97Wz}e'CιI "r ":V ,"r \ED""``k05XD ,"r \ED""``k05XD ,"r \ED""``k05XD ,"r \ED""``k05XD ,"r \ED""``k05XD ,"r \ED""``k05XD ,"r kPU *eNV+PGS1G@B<kE> iUjߡ mD|颈'N;]+4~Jܪj`\s}NETa x|W'Jux銈 k4>ov'~t9DCjXl]sh1@J _&P0Tj+ӵ2V~.jd%DC+C"|c|>?=mG#C*9,Դq?}C?aM90czԧ_oDQix |?^F;)?nYk\t򕃞9@Ze ,)xR2S~ֻ4_}Q`[RχB󁍉!Etȶ[C%g2|-50[{۶_+?QRW7vYymV Q?sfJӴw Q7s!~+DEyaT mh߬6mGL?8 ̮ö7-ɭoÛ7PjemQJ *FELE:CU@'}to7֚{ 3je}еx> '*zu476.^o-<3Dh+a~:^Sc?OJz5kMMgl{Ӣ//WbCnNqM'+ d6`,]_P~6 (Qؗ^oRuP_ZF| JgB(O+`$>/܊&xCeGn)":))HtN]G*^lZP ځZwa|08GH$66>U_=tc}5L m:Hjr\heV.}cbQ)<״/ !0RÀؼp[{+1zr6ϝojT\bjl氎I*]?\Žb0~1.|Q|se*OaRxC<Q;25sR }Yx״pHtlXG<6ܸX>O+۔oKh`as Gr=x)}?OKR 7 m~auL֛=@~]]x)N8 5QO$*PcIDw@g,ve8|NTx+Lsu08}0Gx$Yo#sjQ M ZTc+ك?>Q!)Wdi7}pכ#hR^@Ŧ z&>a&7j/-ͲV 4K`E"/joƖ\>\,RFnѤ|Jʧrغ~:P'i]B i QJ27B"X~Ֆa] !jb@զyWcc gWN Op=}޿LŖ.`pPw>ujm?+ (\m(10Jj$];45}͑?c4&,<;;Ee@nǿuNxQsJlY)F%PB\ss&|UgJu Q{pj=KNźuP 4/j;ϼ0M55]_I( ad|SC뻁2LyPxf tz`N(4'zi*)_we@ P ^~\tz>+:r/Hcī&{kZ}S yAK{$Ui\8Htg, =ۯF{v1._?7*P %@t@9Pq,\+hㆱsh9]z Q'D݁Q|ˤ|z35wvâԧt}s)b ,a( R`8!ꪀU[0(A[7| ߏ*06e݃b)7(e Fˁ4Y0B{ IJU|Ƌ<,h2)L+*.EXBԴ)hG^]\p>;;L?v[4VnL G>qnjۋlUM6* chYЭ@7ЊW1:@ۯIaztLê YY~E0;D7 ģFpwSOd-mJgY|X+Γ]}wPDek\ZzPmo *UbU_SpJg`i5*LG+\]-hhQ` W/Ɯ6bDxj:)I'`L,p#,+0hG=PmȆbWg;]8Q~0p_}EMd;>h:4)I xe~ V۶-)嘵Bu(Ֆ]EQ '; Gt{`@v`OtDǤ4<״+/%/(bS$P(WWWYVscuV0ե*)k4mw o"Ҳ.X0m '>+L=ZL(3Rzkk6)5;(˽R>))`{x"G cm2y H&)kg*)exvX4Ş )GSG"3e@bOC(ydgK&p&Xnʇ;9ktôZR,e()Of<-k8P*;y➣#c`Rt6 % Ŧ|Ʋ\HBX c^cc ЗVe6S%@Pmsb+YдOb8Er=)_1`vl{a 0eè0:jw !J[*"x39x06ہ @ Piq*ZzӴ 쳬u0R1۴:]p`XN ViΌUiaT],:ĵgc៊#E5 Wg6pÉD;@o&t㡐v )23u5_{U*I g%g!(v{-u}[7`ֶ 74H u}>P `YbN$V}ZolDe9TiZP'gwLfy1>X`U nz+#|-\Ab4V9n;%RwI|7<y?+y0<@0zm?3wN} F2d_S l3THU #ËI}xzCm6`-h .Ҵ1T%;슄7@?VO#,k]"1 0 xl=o]{9uS'PY`L &)5ȟۦ)ܗ89V_x࠴Y*4Y$ Vl2cO_>Udj݅_9Om6ͲkkzsҪ'azR @\7%V?GU,6~Y?.x-э9J ]aYe젨:WUWeaQ_Gi}7ǘt{?{r+PV#wYA^@u(tuCTa?GQ倣]H`QzUuٙ(KH?M|mZ_CNo6 ~g3P*찊QoM+9pdC5YG|ZΜG0) o;ΆA3 z i'-I /U?]v7 =%ܬĕ-@ w&mJm9طj!p[w:;,ʃL}28"Lõ{5_ñC'+nLqV0XmYTͰ('whX Hv Q;soHʼnEy39 mY݇ڠ.-VfVC lhܼ6lb̙e iB08Uדɞl`eA1׏[\~yU2t c-uNE'*0, b|*uѐJwrxtHI+!\϶[[)~t94x\責eJx1g̲̬B tZr3sD+)( LH&3.Ejbt*uӵPqY[VjOCÃNByL~XoY;mr(o8$ߪ0k>SaQJ~ L$Lˡ<``Q!ǿ Mz NByڕLV!``Q1ɹ5"A70(XT|@Rۜ.EŠ4Ϳ:]  _(t.acC\Dv ,*|?jqӵР0(45}[JDw8df}xGwQ:,ke-G/~"|h7␐JF5Z1z#!.Z"t:>âߩi~c=nc:c9,*FC.ŦNEG!!VnQjtzz(x*SCv4: ՖUL4_M&xDzDHXTtf5 ̧RPh 8]:1ؖ5 d8뛁ѷt6:2MsR9ft!$vsJb&5@s៤B,{jdXT\v#.bv*5[7I+w[,.۾%8XLNNCȻQ bBL:StD'K`ǒVǛ|祬F+r`j}!.CB*"l }<tSCV )o8 X)G?Ze5";@차X<(e-0EVN$<&%@0 ,*ƈC-p<>W׻h@`{K(x*CB*  qk*# c4pwnd;[%XkY=OtQˉD p7}>t}$Ý? xѷ,)DZ2Mte TvJ]R`[7c t 2%(!}QyѶX:J홄 `)^%o`ζ, ocH1nÀ1B EkMM/Jl@RJuZ f=@#vM Nb Qۗh;lةR`RmJ3=JQ xKȼГ`l@d|z2` 67cNm֑oۻW1tXT6`Я?HUv˶wJR-@eLe@R% zR(z|| @P4 t=G/#D]N!&uuB..5ը_;}s 0vs(GveىěDzl r(ˆR/l`J6(JJb9O ߤiӀn+[;aaQ{0B8V|Zof8<3I)sJ@%PuZJ2zCJiP*_d.Ո])6'`cRӽXT6v pɺS\V=* XIvĘM/<KwweRT` }ߋ2<_=>#>Y-hjc`Q!eK ×M|<iJQ(ʀR(ɞ̄W/27 @2eXT2QB ram{e-K:v0;y_ك숲ʁ:lBk j:Elm!R ӴihӴ2[Y͛U:V`w=|#~˶_7% ZVʲF QJ3dֻRaJhnQF Q`e Ty)մhjr%)- lہc߇ٌwN|u?LbڡdEsr,pF,6[|<&77DžD'neg _74Z` NӵPqHH&QdXTP|LV`X0kc`QAI$%qR!cJe r?5mӵА``Q흆0 @08rhH0@X֪쭰*t94$XT L/@ g;] @9] ^Cb:==g1k DR@)P'b+`Rh0p*x ,*+L(6(t-4XT^H$N`jMˡ³T<@5' BK. h@5vK4EͶ+*Gik)}Uinp:'ƣWᙳ.D)UX+p|b`4n/D7P+qf/L%F;]'l{R :1u\׮!V)Lݤ|T(c)` Xinq4?]a+lۍIp4? %x`7P]co[)e%:E.f#H3mo1e@;0@Enݏ`8P*Dѐ;&^ p ؉I0"!9,rG|ðax NQgm JNC' yD`/P qhr)0 ܞ=seҐ㐐!y0֞aY@PPAL읦i759]& -m_Fƴx r<N6+zih1MLhpzZMbY=>`u\ ŕ&Tg|47Z !F:U0찈5x\ED""``k05XD ,"r \ED""``k05XD ,"r \ED""``k05XD ,"r \ED""``k05XD ,"r \ED""``k05XD ,"r \ED""``k05XD ,"r \ED""``k05XD ,"r \ED 3xn}IENDB`brian2-2.5.4/docs_sphinx/user/images/morphology_random_section_compartment_2.png000066400000000000000000000170311445201106100302620ustar00rootroot00000000000000PNG  IHDR-0IDATxyxOuڲǯlKe` TqÀd΄% L@= y2;ݭNfMH1 3;Iv"&I8"TMlؖl*|`K$[Ѵ:.#wo7u:D@D$$ ":[ I""i0XD$ `4,"ED`H I""i0XD$ `4,"ED`H I""i0XD$ `4,"ED`H I""i0XD$ `4,"ED`H I""i0XD$ `4,"ED`H I""i0XD$ `4,"ED`H I""i0XD$ `4,"ED`H I""i0XD$ `4,"ED`H I""i0XD$ `4,"ED`H I""i0XD$ `4,"EDH{L`9>Y`h8ޱQ N-10X4𐐆;u] R5+,:5+P!= Q]K[5mrr.NImf3 E^v}פ SQXe#{^@+%O<]?,Ӧh)@"[F (Fm{X A} ''ŏvcF\RWXTQb+t^"_QNu7W [bۻ +n?jAlux kV׭XU eΟ g,?QvSX3 =P8Yg*5Cvg/4iZN4m !fx^K(h{5m8R8P()8t;,hvMɍ{X>(ϹoH[WTF"w_,ȹ sö-k# մEJifSt HXRc WU 48&*۞ S&'W0MJ_!&L8\gۍ M;a[Qqt =GRR\#GrluX ^Ɋ/[ ~q\"{}.zGU Q\SsC_''v$Qc*-1J`ahpk\xr{4?nY9TKKUhF tM+ PUqrcIJ->V0x4l`9m61Q*W8/ .4\LVtb!z3N;$ʉ[OF|$=6]nB ֘|dj7|sVev7"wm57?mZt rm2yjisByP7WS0VI#>q͊$njVȚd1y+Zk~t4EVEA(1틐{_0xa+Ve M+J(z'%^kA_`>g\dM> V-N=d=_q`paXQ UV6X_]}gz;خ 7kj+a%wʘL1W[{r_ ^s< t/`'kxyOo*Jʃ P+ ϥڬA[GRY&Poxd!̻oy@iĺMGUT?i- w>WNRUQ1X ъCocEazd\߃B`įE =8{c >h:ydqWJ4_ hIE){1TkV!^_J#ǸgNS@'4yQ1XfjmXCQ{~M~56F6 q{Uf@كUז|,hZquM[qSX _~5x҂+z `E~[XuRD?qw}c4mS$OBόWj`.39jo&U\TW΢%,L1wp+`pդ`QD;vfD)1 nzq@o(qF;V^Y8 }/%‹jrhziHzTW&vLz͆q_Ne'<Ѷ7[{ڐ,*30)+eWpWX;-^K5h EnO;.;' P7am +Jv⧢q(KNnikK]~иW iRlM[ ,47= _-'`Ѹm`Y{`Ѹi]Y wB(@{E㝦 qVg!1X$]_q!1X$Q!51X$U-bf󟶽YO I@Q]ҝ'` xǶ`6`e~BaHvێӐO,2UMzyɊ"9iZ}@e=fMJ Ice@ǣI"ih=Q1X$|!iB\o:6>u$k4. (be%=%WX$2Uz^WF*ŲO& Ic`_b N& IHQz`!D'q"DO >" ' dtBE0zbaH&5#=9%Ok =@ol}zojjnw6JH&@w .- XUQB0X$~ʠӁn ŲE2)Z2xKUu-7[ ,IX܀ۼ}U5my@em5͗E2)sS@lQUø8\XcH&Rˁӱ>"\hw}_XcH:@WX c-yk2k I8 U9LٲLm_&`GUY.ÆG}EN{^;s=6F~ieg&"$iQ/ Vt-e%~6k I' x^my_DfE"?:3J ("50X$! bG?e@Eք`d(znoHR%~<S IgB,r_>`JPqQ |4l*G[Ĩ>e|7D|l  WX$E)Q] u^y#j6>i>!i0X$^o/Yi N:e}*&Si@a }0=(MKEwN:w u׫\gN$mWzXiO[m6{㫄$B!'bH活}m'4U=8'&i麏ɧP^ 0faYYC30ak‡^]NWX$B!cvN_wd>s3NiX dmCh_i^W4m_gIizgV\#vEJ q}]W }* ,@q8_g`|%RaeYݫ%5ذsV`?'@jo;!!Ijz\uߪαBE*. ;'nJMhOw7w"@Mݾ<: IgFvrE9sJSVf\'P KGF,6`9`KZ4aE#N|}"xN痖L1T ؖrV>'P D'2"d>>)D\a8=YKv[h?loz1vd" [P҉NNL?ci>>'D\a\P8%# h1X$wl "SUtc4m^fEW D^ŵMXF(8 .iчCg"lqzvpTtm'fU|˂E@^;:{8L}"~>Geϓ{X$>Ybm1Zok5[uv_6`]/W''ǫN4DI NE){BhZO4y**cmXE8ʲx%݀;.-]_5mTuqm*MgCEqK1=3Nb]}HCZaK !N󊁢U~Rӆok„=, o_3Cq': @J}h  ?^Ux^}N>k(kxNyٷڰ Ǒe'aȅ wmZ2.C\ad4("qv曭H|p94VERҴBC@.p\UK ~JKm;òJø )&@${ζ'5m߃P"0XD$ a4,"ED`H I""i0XD$ `4,"ED`H I""i0XD$ `4,"ED`H I""i0XD$ `4,"ED`H I""i0XD$ `4,"ED`H I""i0XD$ `4,"ED`H I""i0XD$ `4,"ED`H I""i0XD$ `4,"ED`H I""i0XD$ `4,"ED`H I""iͥ[JXIENDB`brian2-2.5.4/docs_sphinx/user/images/morphology_random_section_compartment_3.png000066400000000000000000000236061445201106100302700ustar00rootroot00000000000000PNG  IHDR-0 IDATxytՙ6G}$EecSX$`!Ld$!a&urf0$C+!T@f K`xk˛[KbKNVzx۷AD^@DtXD ," |ED""``o07XD ," |ED""``o07XD ," |ED""``o07XD ," |ED""``o07XD ," |ED""``o07XD ," |ED""``o07XD ," |ED""``o07XD^X**g]+.gXupg=  W Mu,]E T$w زZ|˲v@V.:A{%^-0^D+%V vmy_=/1ƲЙKQ/[\yн(1be'g;0a"޺d-8nyF)O~Pz]/0bE^`,pSز _µMHf $ugVYXݾPS7l^O!V*+?ʆEIR)9 $0i "V7#[],eTuD(F]k@ . ԉ )n*L #[]b>+4M= y7$@> r`?(z,%w -/6|'TEa70QZ1hc~Mڀv HNHQDY֦m764m?85Sť5ެ|7Tv3gI@wEXCmV[˃ޠL]SUjdg8| p(Ӵ}@ {Ve C4ߨ `1Hf)ʓNdZ|Ncp|^~|`B8ZFtCii*u,؃mV&~$-ƓacFX/Dpz2mzM#4_ khƟҟ١z<(-7MpBY#NA]:0 4 Td,n#JT o>EuV&)J)J8@]=$m o}(F0-kׇB85d 0^| 0nNjoÊ6 o2޹:@?~.r$0iWxx Dq֐ߊUn2MՆ1@Yx<@7 ,.Bk(5/:Ma6ju]7hW>lmKa5Lz4@sD54rs2kFc 虉+Rpx~سv?Xf$9p)Y?/pH8T6Bڅ:ŒΝVp; Cuj6lukh߁6q_p>OBf)J7` 8bY1ȗXCC~_뚶TRLF%I 4ĨT"֐Ri‹xnEÖ& ^ɡП"|֐QՒK+9谬j!&(J1a/mD~ @v0(4]M8j۽(0Ⅺj.ڥi+ƻnu/ LtVW74x+UR 1WUU Dc`]_I,e 8M[uQD_Tu\0 cbnE VQ@pSvB^WD/XqG.Ҁ_u{L뢈+z9|F#>V|O)J p0V qqjYD 7fxK$c1╪4yx(9y5ĵJK󤼹:E+v:_Gjθ``ų՚'DP/׵V\(~  va``Ź@?C@=p¶{ x7OQ"1]]n )1JM娐. )1|`e]V"/1|`~:1 )1AմFwDE`3YX1CU\N ɿ]8eNk!;,?Y&)dQbb`DU<<Ts}%$RY82YÏ?kׇuښ`p>TQ׵ +/%M5k!> ,_ [PW+M[ +*a1]8bYau-D+!TVޮ(SgJ5^BtXBӮgi\MJJ ~PkYLW3$_b`%@HWmyZ.+zd;ξ1\+V0 o\˯a@a0G^@Qչ-3&D":/u'fY\wemƫRRJhv_ x7VWG-k`-LF dۍ^C){]yIUgnmuu6_~;ީj=@I@I u-lq`p c(t#en8 +nLu4-Ҁ4 HRBQJ5my[yBeBRt.Jk'x|upfM[q1"0 - éZawHTFX@*ʭ6x]g5?)_R. j]L|oZVpÆ 0|$[> >:+oVåböw3sH 3eL _QrCӜLEq# ßѴe^ ,o]{q2t`:PJȬȝ$zCʶwӎs@1*iRU* 9*&K6exoCF, UVˏ .k Bii)i"-`` GF 5\0,>Sa]+=$8q)ToVuowJ;U4]4L%9*LxL a2>w[@.D\!z_2,܋KsѰcW;$j2J X_IQ]Fq>pwIh2"[ׯ=Ѳ,+!#SmbiPS&bay:xrs1eiemU p+@d*^3VQg>lp& @7Т(_ySﺍRdoXV7ܦS)MsSd/e@:&=@0WQ|0^?0ǁK^D1S26)3NdL8҆*~/_VLzGV⪗2eV[g}4[V)cRs:xqpCD %\qv> R>;35Ҩr$ {|!򄘡3AUuzSghd+{>L ۑ9c̺C-^@00c`%m;%mE'ׇY},{0,~@2]^cmگN.uOH`o8A)[fҀ.g`z*z>!"xq]Z>u%/U~@Ho\t pv"ڦbقjK#U$GX Oz؊TꏊGhv@ pr HK̃ `o~`&vHukdiaY["oX Q$e(B? ("z0e<f`[?F=@iF@0gJPYֹ7|UDt3P%`L욅wWn/wi4^ 8m{eYy@fT'2w$e'!F C8Z=d30ra:.K2TX jz ^><99cD4 s NXRz㉱h܂0ZonsK+uJuRKH&B%eBQՒ'!5M_~Vkc%?d?&t"8qn%OWV" 8{HʻȘquJp$e@=@(ǢoN hs8(3}9GwS@jdzjY@*TE?n}-X܍1CđB1]pF NhNRn`` |fp3{R\5z+z*?>;e Kbc4m̉]~ǜf;se|Zȶ/9bB/ W,!hZdG[Wzaa*n .IS? ky@BlZJ\(-]gw=lCq/wE@E\"$DcTuQ9C{P@ < Eegewݧ 㟬:LꁩwL4d4SJ-7}? D, ]_s1忪%+txpH)mY83t000sfO#G=nnR֛8<$ǩTE)zuB}4wS@d]@2PmY(^[y~09(`:!+|)㣙@6CQqځc PGӒ+qUUDz"]菚%dEɏDg]#ei: tj ^ YQ i>A5 89+Ğv{_{?2nY{/Vk~{n뗹@۴iڍQCξ!aBBnnBɬR~LQi @pc8S]GKz $ 1A\]N|,I8Ȫji'Jtv`\g(y`8U4 Ednui[3J/D:[B!(<7\]w"L`m[R X'WcN=\u4PQ`f8|-$SL1nPe CTR%XL]uBQ?ۍ.ӴFʺ=LQ-I9hAN&Zr(ʚ-ӄ TD ~WʼpfM{x";3EEx4 ;!+b:4Ea6GjtȜg}xa*NB< צ#iPh3 PsX+o⇇拼;]!(xв"W`PGގþ"MP(vD{=[n rgw6?LrW-/I: ca4|Xt9:GUwo R6Jif*ݠ:W/AIDATU)=kG/awdHvz)_{e0LFc e?G~pD^G8NuVε˶)/Or[K%l BZL^ƕHz&Hr ; 9 QP4Ny%Ł;,Jt:fxR6Tx]}(aQ Uu>8oJYg۵2SZE:SUUU_UM ǹwqqOt݆F찈f_@u?TQʏ9흡( ++o"簈_zͲzRvL 3~sU̲YjᐐSuӆQmlBNԴe\gᐐC=\QeM?9+4upWYH#,D׵@찈>m _V,F++ชhUpFcEt^Lo~{go1X16 7[b+xhڊ˔ύz0FTUFf/Ǣo(r/*XDKӖ&=Mƽow#;}pKcEt4.z9wGVE x]")\l*eJ'k-kՍ| , iWEkRw`\{8$f"}GXD@U3P5w+ub!\^ ^W71.N38D~7П: Y,Kҝ]iVyV[Q݃"$4;\en Puu#!`ف@>Sٍ4=̩ӎ7.Xu#W]crE^-rZqWW5(𺴑sXDcādL0~u]#%oøf4L_J[t'HEhC2 FU]uQ#;,ty|F NnEp ,~A뷔Ѫ-FcCBqunۄurU F8.k Y =z8;$MF8vXDCQf1A&!ځV^kx"H,>U]u! EDD ," |ED""``o07XD ," |ED""``o07XD ," |ED""``o07XD ," |ED""``o07XD ," |ED""``o07XD ," |ED""``o07XD ," B_+IENDB`brian2-2.5.4/docs_sphinx/user/images/section.pdf000066400000000000000000000133541445201106100216020ustar00rootroot00000000000000%PDF-1.5 % 3 0 obj << /Length 4 0 R /Filter /FlateDecode >> stream xV0 +M$M+$$$ *fOI\țϯۋ(q`YORF'0]Yr$kW\OQ]:՟.7<*SyO??OOK7=\bt,A^Z`ѩ ./5i^%!I`A L;i(1[%w[i;BdQгXk{V~|DcHSwbY_RJժ. >k_u<>bĊ +ˏ[횇7X풢4tE!X&& V@x!%T,D#'X9GS}3֦-$}. ri{NClݦ ?nQG8$c~.1"yMXҒ"Ͷ MOܒ? 'KZX ,N6dOG4?`b%(be=y endstream endobj 4 0 obj 868 endobj 2 0 obj << /ExtGState << /a0 << /CA 1 /ca 1 >> /s6 6 0 R /s8 8 0 R >> /Shading << /sh5 5 0 R >> /XObject << /x9 9 0 R >> >> endobj 10 0 obj << /Type /Page /Parent 1 0 R /MediaBox [ 0 0 65.199997 13.6 ] /Contents 3 0 R /Group << /Type /Group /S /Transparency /I true /CS /DeviceRGB >> /Resources 2 0 R >> endobj 9 0 obj << /Type /XObject /Length 101 /Filter /FlateDecode /Subtype /Form /BBox [ 1 -0.4 10 13.6 ] /Group << /Type /Group /S /Transparency /I true /CS /DeviceRGB >> /Resources << /ExtGState << /a0 << /CA 1 /ca 1 >> >> /Pattern << /p7 7 0 R >> >> >> stream x; @w@v3b (-x}7 4?yBXN.pNNmQ Sah=]A?B45Di endstream endobj 11 0 obj << /FunctionType 2 /Domain [ 0 1 ] /C0 [ 1 1 1 ] /C1 [ 0.133333 0.133333 0.133333 ] /N 1 >> endobj 12 0 obj << /FunctionType 2 /Domain [ 0 1 ] /C0 [ 0.133333 0.133333 0.133333 ] /C1 [ 0.133333 0.133333 0.133333 ] /N 1 >> endobj 13 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Functions [ 11 0 R 12 0 R ] /Bounds [ 0.5 ] /Encode [ 0 1 0 1 ] >> endobj 14 0 obj << /FunctionType 2 /Domain [ 0 1 ] /C0 [ 0 ] /C1 [ 1 ] /N 1 >> endobj 15 0 obj << /FunctionType 2 /Domain [ 0 1 ] /C0 [ 1 ] /C1 [ 1 ] /N 1 >> endobj 16 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Functions [ 14 0 R 15 0 R ] /Bounds [ 0.5 ] /Encode [ 0 1 0 1 ] >> endobj 5 0 obj << /ShadingType 2 /ColorSpace /DeviceRGB /Coords [ -3 0 7 0 ] /Domain [ 0 1 ] /Extend [ true true ] /Function 13 0 R >> endobj 17 0 obj << /ShadingType 2 /ColorSpace /DeviceGray /Coords [ -3 0 7 0 ] /Domain [ 0 1 ] /Extend [ true true ] /Function 16 0 R >> endobj 18 0 obj << /Length 19 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [ -3.75 -1 7.5 15.25 ] /Resources << /ExtGState << /a0 << /ca 1 /CA 1 >> >> /Shading << /sh17 17 0 R >> >> /Group << /Type /Group /S /Transparency /I true /CS /DeviceGray >> >> stream xO4PH/V/04W($R endstream endobj 19 0 obj 24 endobj 20 0 obj << /Type /Mask /S /Luminosity /G 18 0 R >> endobj 6 0 obj << /Type /ExtGState /SMask 20 0 R /ca 1 /CA 1 /AIS false >> endobj 21 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Functions [ 11 0 R 12 0 R ] /Bounds [ 0.5 ] /Encode [ 0 1 0 1 ] >> endobj 22 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Functions [ 14 0 R 15 0 R ] /Bounds [ 0.5 ] /Encode [ 0 1 0 1 ] >> endobj 7 0 obj << /Type /Pattern /PatternType 2 /Matrix [ 0.8 0 0 -0.8 4 12.8 ] /Shading << /ShadingType 2 /ColorSpace /DeviceRGB /Coords [ -3 0 7 0 ] /Domain [ 0 1 ] /Extend [ true true ] /Function 21 0 R >> >> endobj 23 0 obj << /Type /Pattern /PatternType 2 /Matrix [ 0.8 0 0 -0.8 4 12.8 ] /Shading << /ShadingType 2 /ColorSpace /DeviceGray /Coords [ -3 0 7 0 ] /Domain [ 0 1 ] /Extend [ true true ] /Function 22 0 R >> >> endobj 24 0 obj << /Length 25 0 R /Filter /FlateDecode /Type /XObject /Subtype /Form /FormType 1 /BBox [ 1 -0.4 10 13.6 ] /Resources << /ExtGState << /a0 << /ca 1 /CA 1 >> >> /Pattern << /p23 23 0 R >> >> /Group << /Type /Group /S /Transparency /I true /CS /DeviceGray >> >> stream x+O4PH/H,)I-SH.V/02V(N2P0P033sCc=3T4@.e endstream endobj 25 0 obj 62 endobj 26 0 obj << /Type /Mask /S /Luminosity /G 24 0 R >> endobj 8 0 obj << /Type /ExtGState /SMask 26 0 R /ca 1 /CA 1 /AIS false >> endobj 1 0 obj << /Type /Pages /Kids [ 10 0 R ] /Count 1 >> endobj 27 0 obj << /Creator (cairo 1.14.6 (http://cairographics.org)) /Producer (cairo 1.14.6 (http://cairographics.org)) >> endobj 28 0 obj << /Type /Catalog /Pages 1 0 R >> endobj xref 0 29 0000000000 65535 f 0000004955 00000 n 0000000982 00000 n 0000000015 00000 n 0000000960 00000 n 0000002504 00000 n 0000003409 00000 n 0000003758 00000 n 0000004868 00000 n 0000001365 00000 n 0000001143 00000 n 0000001799 00000 n 0000001919 00000 n 0000002060 00000 n 0000002191 00000 n 0000002282 00000 n 0000002373 00000 n 0000002696 00000 n 0000002890 00000 n 0000003322 00000 n 0000003344 00000 n 0000003496 00000 n 0000003627 00000 n 0000004036 00000 n 0000004316 00000 n 0000004781 00000 n 0000004803 00000 n 0000005021 00000 n 0000005149 00000 n trailer << /Size 29 /Root 28 0 R /Info 27 0 R >> startxref 5202 %%EOF brian2-2.5.4/docs_sphinx/user/images/section.svg000066400000000000000000000064511445201106100216300ustar00rootroot00000000000000 brian2-2.5.4/docs_sphinx/user/images/soma.pdf000066400000000000000000000031321445201106100210660ustar00rootroot00000000000000%PDF-1.5 % 3 0 obj << /Length 4 0 R /Filter /FlateDecode >> stream x510 MQ &fԁ.EDyuN 6ٱ9%2Fqƪe쩏Y_Pȡ~g'ujFbqҡzb endstream endobj 4 0 obj 98 endobj 2 0 obj << /ExtGState << /a0 << /CA 1 /ca 1 >> >> /Shading << /sh5 5 0 R >> >> endobj 6 0 obj << /Type /Page /Parent 1 0 R /MediaBox [ 0 0 8 8 ] /Contents 3 0 R /Group << /Type /Group /S /Transparency /I true /CS /DeviceRGB >> /Resources 2 0 R >> endobj 7 0 obj << /FunctionType 2 /Domain [ 0 1 ] /C0 [ 1 1 1 ] /C1 [ 0 0 1 ] /N 1 >> endobj 8 0 obj << /FunctionType 2 /Domain [ 0 1 ] /C0 [ 0 0 1 ] /C1 [ 0 0 0.545098 ] /N 1 >> endobj 9 0 obj << /FunctionType 3 /Domain [ 0 1 ] /Functions [ 7 0 R 8 0 R ] /Bounds [ 0.75 ] /Encode [ 0 1 0 1 ] >> endobj 5 0 obj << /ShadingType 3 /ColorSpace /DeviceRGB /Coords [ -0.5 -0.5 0 0 0 1 ] /Domain [ 0 1 ] /Extend [ true true ] /Function 9 0 R >> endobj 1 0 obj << /Type /Pages /Kids [ 6 0 R ] /Count 1 >> endobj 10 0 obj << /Creator (cairo 1.14.6 (http://cairographics.org)) /Producer (cairo 1.14.6 (http://cairographics.org)) >> endobj 11 0 obj << /Type /Catalog /Pages 1 0 R >> endobj xref 0 12 0000000000 65535 f 0000001054 00000 n 0000000211 00000 n 0000000015 00000 n 0000000190 00000 n 0000000854 00000 n 0000000312 00000 n 0000000522 00000 n 0000000620 00000 n 0000000725 00000 n 0000001119 00000 n 0000001247 00000 n trailer << /Size 12 /Root 11 0 R /Info 10 0 R >> startxref 1300 %%EOF brian2-2.5.4/docs_sphinx/user/images/soma.svg000066400000000000000000000012241445201106100211140ustar00rootroot00000000000000 brian2-2.5.4/docs_sphinx/user/import.rst000066400000000000000000000053411445201106100202370ustar00rootroot00000000000000Importing Brian =============== After installation, Brian is available in the `brian2` package. By doing a wildcard import from this package, i.e.:: from brian2 import * you will not only get access to the ``brian2`` classes and functions, but also to everything in the ``pylab`` package, which includes the plotting functions from matplotlib_ and everything included in numpy/scipy (e.g. functions such as ``arange``, ``linspace``, etc.). Apart from this when you use the wildcard import, the builtin `input` function is overshadowed by the `input` module in the `brian2` package. If you wish to use the builtin `input` function in your program after importing the brian2 package then you can explicitly import the `input` function again as shown below:: from brian2 import * from builtins import input .. admonition:: The following topics are not essential for beginners. | Precise control over importing ------------------------------ If you want to use a wildcard import from Brian, but don't want to import all the additional symbols provided by ``pylab`` or don't want to overshadow the builtin `input` function, you can use:: from brian2.only import * Note that whenever you use something different from the most general ``from brian2 import *`` statement, you should be aware that Brian overwrites some numpy functions with their unit-aware equivalents (see :doc:`../developer/units`). If you combine multiple wildcard imports, the Brian import should therefore be the last import. Similarly, you should not import and call overwritten numpy functions directly, e.g. by using ``import numpy as np`` followed by ``np.sin`` since this will not use the unit-aware versions. To make this easier, Brian provides a ``brian2.numpy_`` package that provides access to everything in numpy but overwrites certain functions. If you prefer to use prefixed names, the recommended way of doing the imports is therefore:: import brian2.numpy_ as np import brian2.only as br2 Note that it is safe to use e.g. ``np.sin`` and ``numpy.sin`` after a ``from brian2 import *``. .. _matplotlib: http://matplotlib.org/ .. _dependency_checks: Dependency checks ----------------- Brian will check the dependency versions during import and raise an error for an outdated dependency. An outdated dependency does not necessarily mean that Brian cannot be run with it, it only means that Brian is untested on that version. If you want to force Brian to run despite the outdated dependency, set the `core.outdated_dependency_error` preference to ``False``. Note that this cannot be done in a script, since you do not have access to the preferences before importing `brian2`. See :doc:`../advanced/preferences` for instructions how to set preferences in a file. brian2-2.5.4/docs_sphinx/user/index.rst000066400000000000000000000004411445201106100200300ustar00rootroot00000000000000User's guide ================= .. toctree:: :maxdepth: 2 import units models numerical_integration equations refractoriness synapses input recording running multicompartmental computation converting_from_integrated_form plotting_functions brian2-2.5.4/docs_sphinx/user/input.rst000066400000000000000000000265101445201106100200650ustar00rootroot00000000000000Input stimuli ============= .. sidebar:: For Brian 1 users See the document :doc:`../introduction/brian1_to_2/inputs` for details how to convert Brian 1 code. .. contents:: :local: :depth: 1 There are various ways of providing "external" input to a network. Poisson inputs -------------- For generating spikes according to a Poisson point process, `PoissonGroup` can be used, e.g.:: P = PoissonGroup(100, np.arange(100)*Hz + 10*Hz) G = NeuronGroup(100, 'dv/dt = -v / (10*ms) : 1') S = Synapses(P, G, on_pre='v+=0.1') S.connect(j='i') See `More on Poisson inputs`_ below for further information. For simulations where the individually generated spikes are just used as a source of input to a neuron, the `PoissonInput` class provides a more efficient alternative: see :ref:`poisson_input_class` below for details. Spike generation ---------------- You can also generate an explicit list of spikes given via arrays using `SpikeGeneratorGroup`. This object behaves just like a `NeuronGroup` in that you can connect it to other groups via a `Synapses` object, but you specify three bits of information: ``N`` the number of neurons in the group; ``indices`` an array of the indices of the neurons that will fire; and ``times`` an array of the same length as ``indices`` with the times that the neurons will fire a spike. The ``indices`` and ``times`` arrays are matching, so for example ``indices=[0,2,1]`` and ``times=[1*ms,2*ms,3*ms]`` means that neuron 0 fires at time 1 ms, neuron 2 fires at 2 ms and neuron 1 fires at 3 ms. Example use:: indices = array([0, 2, 1]) times = array([1, 2, 3])*ms G = SpikeGeneratorGroup(3, indices, times) The spikes that will be generated by `SpikeGeneratorGroup` can be changed between runs with the `~brian2.input.spikegeneratorgroup.SpikeGeneratorGroup.set_spikes` method. This can be useful if the input to a system should depend on its previous output or when running multiple trials with different input:: inp = SpikeGeneratorGroup(N, indices, times) G = NeuronGroup(N, '...') feedforward = Synapses(inp, G, '...', on_pre='...') feedforward.connect(j='i') recurrent = Synapses(G, G, '...', on_pre='...') recurrent.connect('i!=j') spike_mon = SpikeMonitor(G) # ... run(runtime) # Replay the previous output of group G as input into the group inp.set_spikes(spike_mon.i, spike_mon.t + runtime) run(runtime) Explicit equations ------------------ If the input can be explicitly expressed as a function of time (e.g. a sinusoidal input current), then its description can be directly included in the equations of the respective group:: G = NeuronGroup(100, '''dv/dt = (-v + I)/(10*ms) : 1 rates : Hz # each neuron's input has a different rate size : 1 # and a different amplitude I = size*sin(2*pi*rates*t) : 1''') G.rates = '10*Hz + i*Hz' G.size = '(100-i)/100. + 0.1' .. _timed_arrays: Timed arrays ------------ If the time dependence of the input cannot be expressed in the equations in the way shown above, it is possible to create a `TimedArray`. This acts as a function of time where the values at given time points are given explicitly. This can be especially useful to describe non-continuous stimulation. For example, the following code defines a `TimedArray` where stimulus blocks consist of a constant current of random strength for 30ms, followed by no stimulus for 20ms. Note that in this particular example, numerical integration can use exact methods, since it can assume that the `TimedArray` is a constant function of time during a single integration time step. .. note:: The semantics of `TimedArray` changed slightly compared to Brian 1: for ``TimedArray([x1, x2, ...], dt=my_dt)``, the value ``x1`` will be returned for all ``0<=t 5 \wedge n (1 - p) > 5` , where :math:`n` is the number of inputs and :math:`p = dt \cdot rate` the spiking probability for a single input. .. _network_operation: Arbitrary Python code (network operations) ------------------------------------------ If none of the above techniques is general enough to fulfill the requirements of a simulation, Brian allows you to write a `NetworkOperation`, an arbitrary Python function that is executed every time step (possible on a different clock than the rest of the simulation). This function can do arbitrary operations, use conditional statements etc. and it will be executed as it is (i.e. as pure Python code even if cython code generation is active). Note that one cannot use network operations in combination with the C++ standalone mode. Network operations are particularly useful when some condition or calculation depends on operations across neurons, which is currently not possible to express in abstract code. The following code switches input on for a randomly chosen single neuron every 50 ms:: G = NeuronGroup(10, '''dv/dt = (-v + active*I)/(10*ms) : 1 I = sin(2*pi*100*Hz*t) : 1 (shared) #single input active : 1 # will be set in the network operation''') @network_operation(dt=50*ms) def update_active(): index = np.random.randint(10) # index for the active neuron G.active_ = 0 # the underscore switches off unit checking G.active_[index] = 1 Note that the network operation (in the above example: ``update_active``) has to be included in the `Network` object if one is constructed explicitly. Only functions with zero or one arguments can be used as a `NetworkOperation`. If the function has one argument then it will be passed the current time ``t``:: @network_operation(dt=1*ms) def update_input(t): if t>50*ms and t<100*ms: pass # do something Note that this is preferable to accessing ``defaultclock.t`` from within the function -- if the network operation is not running on the `defaultclock` itself, then that value is not guaranteed to be correct. Instance methods can be used as network operations as well, however in this case they have to be constructed explicitly, the `network_operation` decorator cannot be used:: class Simulation(object): def __init__(self, data): self.data = data self.group = NeuronGroup(...) self.network_op = NetworkOperation(self.update_func, dt=10*ms) self.network = Network(self.group, self.network_op) def update_func(self): pass # do something def run(self, runtime): self.network.run(runtime) brian2-2.5.4/docs_sphinx/user/models.rst000066400000000000000000000330141445201106100202060ustar00rootroot00000000000000Models and neuron groups ======================== .. sidebar:: For Brian 1 users See the document :doc:`../introduction/brian1_to_2/neurongroup` for details how to convert Brian 1 code. .. contents:: :local: :depth: 1 Model equations --------------- The core of every simulation is a `NeuronGroup`, a group of neurons that share the same equations defining their properties. The minimum `NeuronGroup` specification contains the number of neurons and the model description in the form of equations:: G = NeuronGroup(10, 'dv/dt = -v/(10*ms) : volt') This defines a group of 10 leaky integrators. The model description can be directly given as a (possibly multi-line) string as above, or as an `Equations` object. For more details on the form of equations, see :doc:`equations`. Brian needs the model to be given in the form of differential equations, but you might see the integrated form of synapses in some textbooks and papers. See :doc:`converting_from_integrated_form` for details on how to convert between these representations. Note that model descriptions can make reference to physical units, but also to scalar variables declared outside of the model description itself:: tau = 10*ms G = NeuronGroup(10, 'dv/dt = -v/tau : volt') If a variable should be taken as a *parameter* of the neurons, i.e. if it should be possible to vary its value across neurons, it has to be declared as part of the model description:: G = NeuronGroup(10, '''dv/dt = -v/tau : volt tau : second''') To make complex model descriptions more readable, named subexpressions can be used:: G = NeuronGroup(10, '''dv/dt = I_leak / Cm : volt I_leak = g_L*(E_L - v) : amp''') For a list of some standard model equations, see :doc:`../introduction/brian1_to_2/neurongroup`. Noise ----- In addition to ordinary differential equations, Brian allows you to introduce random noise by specifying a `stochastic differential equation `__. Brian uses the physicists' notation used in the `Langevin equation `__, representing the "noise" as a term :math:`\xi(t)`, rather than the mathematicians' stochastic differential :math:`\mathrm{d}W_t`. The following is an example of the `Ornstein-Uhlenbeck process `__ that is often used to model a leaky integrate-and-fire neuron with a stochastic current:: G = NeuronGroup(10, 'dv/dt = -v/tau + sigma*sqrt(2/tau)*xi : volt') You can start by thinking of ``xi`` as just a Gaussian random variable with mean 0 and standard deviation 1. However, it scales in an unusual way with time and this gives it units of ``1/sqrt(second)``. You don't necessarily need to understand why this is, but it is possible to get a reasonably simple intuition for it by thinking about numerical integration: :ref:`see below `. .. note:: If you want to use noise in more than one equation of a `NeuronGroup` or `Synapses`, you will have to use suffixed names (see :ref:`equation_strings` for details). Threshold and reset ------------------- To emit spikes, neurons need a *threshold*. Threshold and reset are given as strings in the `NeuronGroup` constructor:: tau = 10*ms G = NeuronGroup(10, 'dv/dt = -v/tau : volt', threshold='v > -50*mV', reset='v = -70*mV') Whenever the threshold condition is fulfilled, the reset statements will be executed. Again, both threshold and reset can refer to physical units, external variables and parameters, in the same way as model descriptions:: v_r = -70*mV # reset potential G = NeuronGroup(10, '''dv/dt = -v/tau : volt v_th : volt # neuron-specific threshold''', threshold='v > v_th', reset='v = v_r') You can also create non-spike events. See :doc:`/advanced/custom_events` for more details. Refractoriness -------------- To make a neuron non-excitable for a certain time period after a spike, the refractory keyword can be used:: G = NeuronGroup(10, 'dv/dt = -v/tau : volt', threshold='v > -50*mV', reset='v = -70*mV', refractory=5*ms) This will not allow any threshold crossing for a neuron for 5ms after a spike. The refractory keyword allows for more flexible refractoriness specifications, see :doc:`refractoriness` for details. .. _state_variables: State variables --------------- Differential equations and parameters in model descriptions are stored as *state variables* of the `NeuronGroup`. In addition to these variables, Brian also defines two variables automatically: ``i`` The index of a neuron. ``N`` The total number of neurons. All state variables can be accessed and set as an attribute of the group. To get the values without physical units (e.g. for analysing data with external tools), use an underscore after the name: .. doctest:: >>> G = NeuronGroup(10, '''dv/dt = -v/tau : volt ... tau : second''', name='neurons') >>> G.v = -70*mV >>> G.v >>> G.v_ # values without units The value of state variables can also be set using string expressions that can refer to units and external variables, other state variables or mathematical functions: .. doctest:: >>> G.tau = '5*ms + (1.0*i/N)*5*ms' >>> G.tau You can also set the value only if a condition holds, for example: .. doctest:: >>> G.v['tau>7.25*ms'] = -60*mV >>> G.v .. _subgroups: Subgroups --------- It is often useful to refer to a subset of neurons, this can be achieved using Python's slicing syntax:: G = NeuronGroup(10, '''dv/dt = -v/tau : volt tau : second''', threshold='v > -50*mV', reset='v = -70*mV') # Create subgroups G1 = G[:5] G2 = G[5:] # This will set the values in the main group, subgroups are just "views" G1.tau = 10*ms G2.tau = 20*ms Here ``G1`` refers to the first 5 neurons in G, and ``G2`` to the second 5 neurons. In general ``G[i:j]`` refers to the neurons with indices from ``i`` to ``j-1``, as in general in Python. For convenience, you can also use a single index, i.e. ``G[i]`` is equivalent to ``G[i:i+1]``. In some situations, it can be easier to provide a list of indices instead of a slice, Brian therefore also allows for this syntax. Note that this is restricted to cases that are strictly equivalent with slicing syntax, e.g. you can write ``G[[3, 4, 5]]`` instead of ``G[3:6]``, but you *cannot* write ``G[[3, 5, 7]]`` or ``G[[5, 4, 3]]``. Subgroups can be used in most places where regular groups are used, e.g. their state variables or spiking activity can be recorded using monitors, they can be connected via `Synapses`, etc. In such situations, indices (e.g. the indices of the neurons to record from in a `StateMonitor`) are relative to the subgroup, not to the main group .. admonition:: The following topics are not essential for beginners. | .. _shared_variables: Shared variables ---------------- Sometimes it can also be useful to introduce shared variables or subexpressions, i.e. variables that have a common value for all neurons. In contrast to external variables (such as ``Cm`` above), such variables can change during a run, e.g. by using :meth:`~brian2.groups.group.Group.run_regularly`. This can be for example used for an external stimulus that changes in the course of a run: .. doctest:: >>> G = NeuronGroup(10, '''shared_input : volt (shared) ... dv/dt = (-v + shared_input)/tau : volt ... tau : second''', name='neurons') Note that there are several restrictions around the use of shared variables: they cannot be written to in contexts where statements apply only to a subset of neurons (e.g. reset statements, see below). If a code block mixes statements writing to shared and vector variables, then the shared statements have to come first. By default, subexpressions are re-evaluated whenever they are used, i.e. using a subexpression is completely equivalent to substituting it. Sometimes it is useful to instead only evaluate a subexpression once and then use this value for the rest of the time step. This can be achieved by using the ``(constant over dt)`` flag. This flag is mandatory for subexpressions that refer to stateful functions like ``rand()`` which notably allows them to be recorded with a `StateMonitor` -- otherwise the monitor would record a different instance of the random number than the one that was used in the equations. For shared variables, setting by string expressions can only refer to shared values: .. doctest:: >>> G.shared_input = '(4.0/N)*mV' >>> G.shared_input .. _storing_state_variables: Storing state variables ----------------------- Sometimes it can be convenient to access multiple state variables at once, e.g. to set initial values from a dictionary of values or to store all the values of a group on disk. This can be done with the :meth:`~brian2.groups.group.VariableOwner.get_states` and :meth:`~brian2.groups.group.VariableOwner.set_states` methods: .. doctest:: >>> group = NeuronGroup(5, '''dv/dt = -v/tau : 1 ... tau : second''', name='neurons') >>> initial_values = {'v': [0, 1, 2, 3, 4], ... 'tau': [10, 20, 10, 20, 10]*ms} >>> group.set_states(initial_values) >>> group.v[:] array([ 0., 1., 2., 3., 4.]) >>> group.tau[:] array([ 10., 20., 10., 20., 10.]) * msecond >>> states = group.get_states() >>> states['v'] array([ 0., 1., 2., 3., 4.]) The data (without physical units) can also be exported/imported to/from `Pandas `_ data frames (needs an installation of ``pandas``):: >>> df = group.get_states(units=False, format='pandas') # doctest: +SKIP >>> df # doctest: +SKIP N dt i t tau v 0 5 0.0001 0 0.0 0.01 0.0 1 5 0.0001 1 0.0 0.02 1.0 2 5 0.0001 2 0.0 0.01 2.0 3 5 0.0001 3 0.0 0.02 3.0 4 5 0.0001 4 0.0 0.01 4.0 >>> df['tau'] # doctest: +SKIP 0 0.01 1 0.02 2 0.01 3 0.02 4 0.01 Name: tau, dtype: float64 >>> df['tau'] *= 2 # doctest: +SKIP >>> group.set_states(df[['tau']], units=False, format='pandas') # doctest: +SKIP >>> group.tau # doctest: +SKIP .. _linked_variables: Linked variables ---------------- A `NeuronGroup` can define parameters that are not stored in this group, but are instead a reference to a state variable in another group. For this, a group defines a parameter as ``linked`` and then uses `linked_var` to specify the linking. This can for example be useful to model shared noise between cells:: inp = NeuronGroup(1, 'dnoise/dt = -noise/tau + tau**-0.5*xi : 1') neurons = NeuronGroup(100, '''noise : 1 (linked) dv/dt = (-v + noise_strength*noise)/tau : volt''') neurons.noise = linked_var(inp, 'noise') If the two groups have the same size, the linking will be done in a 1-to-1 fashion. If the source group has the size one (as in the above example) or if the source parameter is a shared variable, then the linking will be done as 1-to-all. In all other cases, you have to specify the indices to use for the linking explicitly:: # two inputs with different phases inp = NeuronGroup(2, '''phase : 1 dx/dt = 1*mV/ms*sin(2*pi*100*Hz*t-phase) : volt''') inp.phase = [0, pi/2] neurons = NeuronGroup(100, '''inp : volt (linked) dv/dt = (-v + inp) / tau : volt''') # Half of the cells get the first input, other half gets the second neurons.inp = linked_var(inp, 'x', index=repeat([0, 1], 50)) .. _time_scaling_of_noise: Time scaling of noise --------------------- Suppose we just had the differential equation :math:`dx/dt=\xi` To solve this numerically, we could compute :math:`x(t+\mathrm{d}t)=x(t)+\xi_1` where :math:`\xi_1` is a normally distributed random number with mean 0 and standard deviation 1. However, what happens if we change the time step? Suppose we used a value of :math:`\mathrm{d}t/2` instead of :math:`\mathrm{d}t`. Now, we compute :math:`x(t+\mathrm{d}t)=x(t+\mathrm{d}t/2)+\xi_1=x(t)+\xi_2+\xi_1` The mean value of :math:`x(t+\mathrm{d}t)` is 0 in both cases, but the standard deviations are different. The first method :math:`x(t+\mathrm{d}t)=x(t)+\xi_1` gives :math:`x(t+\mathrm{d}t)` a standard deviation of 1, whereas the second method :math:`x(t+\mathrm{d}t)=x(t+\mathrm{d}/2)+\xi_1=x(t)+\xi_2+\xi_1` gives :math:`x(t)` a variance of 1+1=2 and therefore a standard deviation of :math:`\sqrt{2}`. In order to solve this problem, we use the rule :math:`x(t+\mathrm{d}t)=x(t)+\sqrt{\mathrm{d}t}\xi_1`, which makes the mean and standard deviation of the value at time :math:`t` independent of :math:`\mathrm{d}t`. For this to make sense dimensionally, :math:`\xi` must have units of ``1/sqrt(second)``. For further details, refer to a textbook on stochastic differential equations. brian2-2.5.4/docs_sphinx/user/multicompartmental.rst000066400000000000000000000563121445201106100226520ustar00rootroot00000000000000Multicompartment models ======================= .. sidebar:: For Brian 1 users See the document :doc:`../introduction/brian1_to_2/multicompartmental` for details how to convert Brian 1 code. It is possible to create neuron models with a spatially extended morphology, using the `SpatialNeuron` class. A `SpatialNeuron` is a single neuron with many compartments. Essentially, it works as a `NeuronGroup` where elements are compartments instead of neurons. A `SpatialNeuron` is specified by a morphology (see :ref:`creating_morphology`) and a set of equations for transmembrane currents (see :ref:`creating_spatialneuron`). .. _creating_morphology: Creating a neuron morphology ---------------------------- Schematic morphologies ~~~~~~~~~~~~~~~~~~~~~~ Morphologies can be created combining geometrical objects:: soma = Soma(diameter=30*um) cylinder = Cylinder(diameter=1*um, length=100*um, n=10) The first statement creates a single iso-potential compartment (i.e. with no axial resistance within the compartment), with its area calculated as the area of a sphere with the given diameter. The second one specifies a cylinder consisting of 10 compartments with identical diameter and the given total length. For more precise control over the geometry, you can specify the length and diameter of each individual compartment, including the diameter at the start of the section (i.e. for ``n`` compartments: ``n`` length and ``n+1`` diameter values) in a `Section` object:: section = Section(diameter=[6, 5, 4, 3, 2, 1]*um, length=[10, 10, 10, 5, 5]*um, n=5) The individual compartments are modeled as truncated cones, changing the diameter linearly between the given diameters over the length of the compartment. Note that the ``diameter`` argument specifies the values at the nodes *between* the compartments, but accessing the ``diameter`` attribute of a `Morphology` object will return the diameter at the *center* of the compartment (see the note below). The following table summarizes the different options to create schematic morphologies (the black compartment before the start of the section represents the parent compartment with diameter 15 μm, not specified in the code below): +-------------+-----------------------------------------------------------------------------------+ | | **Example** | +=============+===================================================================================+ |**Soma** | :: | | | | | | # Soma always has a single compartment | | | Soma(diameter=30*um) | | | | | | .. image:: images/soma.* | | | | +-------------+-----------------------------------------------------------------------------------+ |**Cylinder** | :: | | | | | | # Each compartment has fixed length and diameter | | | Cylinder(n=5, diameter=10*um, length=50*um) | | | | | | .. image:: images/cylinder.* | | | | +-------------+-----------------------------------------------------------------------------------+ |**Section** | :: | | | | | | # Length and diameter individually defined for each compartment (at start | | | # and end) | | | Section(n=5, diameter=[15, 5, 10, 5, 10, 5]*um, | | | length=[10, 20, 5, 5, 10]*um) | | | | | | .. image:: images/section.* | | | | +-------------+-----------------------------------------------------------------------------------+ .. note:: For a `Section`, the ``diameter`` argument specifies the diameter *between* the compartments (and at the beginning/end of the first/last compartment). the corresponding values can therefore be later retrieved from the `Morphology` via the ``start_diameter`` and ``end_diameter`` attributes. The ``diameter`` attribute of a `Morphology` does correspond to the diameter at the midpoint of the compartment. For a `Cylinder`, ``start_diameter``, ``diameter``, and ``end_diameter`` are of course all identical. The tree structure of a morphology is created by attaching `Morphology` objects together:: morpho = Soma(diameter=30*um) morpho.axon = Cylinder(length=100*um, diameter=1*um, n=10) morpho.dendrite = Cylinder(length=50*um, diameter=2*um, n=5) These statements create a morphology consisting of a cylindrical axon and a dendrite attached to a spherical soma. Note that the names ``axon`` and ``dendrite`` are arbitrary and chosen by the user. For example, the same morphology can be created as follows:: morpho = Soma(diameter=30*um) morpho.output_process = Cylinder(length=100*um, diameter=1*um, n=10) morpho.input_process = Cylinder(length=50*um, diameter=2*um, n=5) The syntax is recursive, for example two sections can be added at the end of the dendrite as follows:: morpho.dendrite.branch1 = Cylinder(length=50*um, diameter=1*um, n=3) morpho.dendrite.branch2 = Cylinder(length=50*um, diameter=1*um, n=3) Equivalently, one can use an indexing syntax:: morpho['dendrite']['branch1'] = Cylinder(length=50*um, diameter=1*um, n=3) morpho['dendrite']['branch2'] = Cylinder(length=50*um, diameter=1*um, n=3) The names given to sections are completely up to the user. However, names that consist of a single digit (``1`` to ``9``) or the letters ``L`` (for left) and ``R`` (for right) allow for a special short syntax: they can be joined together directly, without the needs for dots (or dictionary syntax) and therefore allow to quickly navigate through the morphology tree (e.g. ``morpho.LRLLR`` is equivalent to ``morpho.L.R.L.L.R``). This short syntax can also be used to create trees:: >>> morpho = Soma(diameter=30*um) >>> morpho.L = Cylinder(length=10*um, diameter=1*um, n=3) >>> morpho.L1 = Cylinder(length=5*um, diameter=1*um, n=3) >>> morpho.L2 = Cylinder(length=5*um, diameter=1*um, n=3) >>> morpho.L3 = Cylinder(length=5*um, diameter=1*um, n=3) >>> morpho.R = Cylinder(length=10*um, diameter=1*um, n=3) >>> morpho.RL = Cylinder(length=5*um, diameter=1*um, n=3) >>> morpho.RR = Cylinder(length=5*um, diameter=1*um, n=3) The above instructions create a dendritic tree with two main sections, three sections attached to the first section and two to the second. This can be verified with the `Morphology.topology` method:: >>> morpho.topology() # doctest: +NORMALIZE_WHITESPACE ( ) [root] `---| .L `---| .L.1 `---| .L.2 `---| .L.3 `---| .R `---| .R.L `---| .R.R Note that an expression such as ``morpho.L`` will always refer to the entire subtree. However, accessing the attributes (e.g. ``diameter``) will only return the values for the given section. .. note:: To avoid ambiguities, do not use names for sections that can be interpreted in the abbreviated way detailed above. For example, do not name a child section ``L1`` (which will be interpreted as the first child of the child ``L``) The number of compartments in a section can be accessed with ``morpho.n`` (or ``morpho.L.n``, etc.), the number of total sections and compartments in a subtree can be accessed with ``morpho.total_sections`` and ``morpho.total_compartments`` respectively. Adding coordinates ++++++++++++++++++ For plotting purposes, it can be useful to add coordinates to a `Morphology` that was created using the "schematic" approach described above. This can be done by calling the `~Morphology.generate_coordinates` method on a morphology, which will return an identical morphology but with additional 2D or 3D coordinates. By default, this method creates a morphology according to a deterministic algorithm in 2D:: new_morpho = morpho.generate_coordinates() .. image:: images/morphology_deterministic_coords.* To get more "realistic" morphologies, this function can also be used to create morphologies in 3D where the orientation of each section differs from the orientation of the parent section by a random amount:: new_morpho = morpho.generate_coordinates(section_randomness=25) =============================================== =============================================== =============================================== .. image:: images/morphology_random_section_1.* .. image:: images/morphology_random_section_2.* .. image:: images/morphology_random_section_3.* =============================================== =============================================== =============================================== This algorithm will base the orientation of each section on the orientation of the parent section and then randomly perturb this orientation. More precisely, the algorithm first chooses a random vector orthogonal to the orientation of the parent section. Then, the section will be rotated around this orthogonal vector by a random angle, drawn from an exponential distribution with the :math:`\beta` parameter (in degrees) given by ``section_randomness``. This :math:`\beta` parameter specifies both the mean and the standard deviation of the rotation angle. Note that no maximum rotation angle is enforced, values for ``section_randomness`` should therefore be reasonably small (e.g. using a ``section_randomness`` of ``45`` would already lead to a probability of ~14% that the section will be rotated by more than 90 degrees, therefore making the section go "backwards"). In addition, also the orientation of each compartment within a section can be randomly varied:: new_morpho = morpho.generate_coordinates(section_randomness=25, compartment_randomness=15) =========================================================== =========================================================== =========================================================== .. image:: images/morphology_random_section_compartment_1.* .. image:: images/morphology_random_section_compartment_2.* .. image:: images/morphology_random_section_compartment_3.* =========================================================== =========================================================== =========================================================== The algorithm is the same as the one presented above, but applied individually to each compartment within a section (still based on the orientation on the parent *section*, not on the orientation of the previous *compartment*). Complex morphologies ~~~~~~~~~~~~~~~~~~~~ Morphologies can also be created from information about the compartment coordinates in 3D space. Such morphologies can be loaded from a ``.swc`` file (a standard format for neuronal morphologies; for a large database of morphologies in this format see http://neuromorpho.org):: morpho = Morphology.from_file('corticalcell.swc') To manually create a morphology from a list of points in a similar format to SWC files, see `Morphology.from_points`. Morphologies that are created in such a way will use standard names for the sections that allow for the short syntax shown in the previous sections: if a section has one or two child sections, then they will be called ``L`` and ``R``, otherwise they will be numbered starting at ``1``. Morphologies with coordinates can also be created section by section, following the same syntax as for "schematic" morphologies:: soma = Soma(diameter=30*um, x=50*um, y=20*um) cylinder = Cylinder(n=10, x=[0, 100]*um, diameter=1*um) section = Section(n=5, x=[0, 10, 20, 30, 40, 50]*um, y=[0, 10, 20, 30, 40, 50]*um, z=[0, 10, 10, 10, 10, 10]*um, diameter=[6, 5, 4, 3, 2, 1]*um) Note that the ``x``, ``y``, ``z`` attributes of `Morphology` and `SpatialNeuron` will return the coordinates at the midpoint of each compartment (as for all other attributes that vary over the length of a compartment, e.g. ``diameter`` or ``distance``), but during construction the coordinates refer to the start and end of the section (`Cylinder`), respectively to the coordinates of the nodes between the compartments (`Section`). A few additional remarks: 1. In the majority of simulations, coordinates are not used in the neuronal equations, therefore the coordinates are purely for visualization purposes and do not affect the simulation results in any way. 2. Coordinate specification cannot be combined with length specification -- lengths are automatically calculated from the coordinates. 3. The coordinate specification can also be 1- or 2-dimensional (as in the first two examples above), the unspecified coordinate will use 0 μm. 4. All coordinates are interpreted relative to the parent compartment, i.e. the point (0 μm, 0 μm, 0 μm) refers to the end point of the previous compartment. Most of the time, the first element of the coordinate specification is therefore 0 μm, to continue a section where the previous one ended. However, it can be convenient to use a value different from 0 μm for sections connecting to the `Soma` to make them (visually) connect to a point on the sphere surface instead of the center of the sphere. .. _creating_spatialneuron: Creating a spatially extended neuron ------------------------------------ A `SpatialNeuron` is a spatially extended neuron. It is created by specifying the morphology as a `Morphology` object, the equations for transmembrane currents, and optionally the specific membrane capacitance ``Cm`` and intracellular resistivity ``Ri``:: gL = 1e-4*siemens/cm**2 EL = -70*mV eqs = ''' Im=gL * (EL - v) : amp/meter**2 I : amp (point current) ''' neuron = SpatialNeuron(morphology=morpho, model=eqs, Cm=1*uF/cm**2, Ri=100*ohm*cm) neuron.v = EL + 10*mV Several state variables are created automatically: the `SpatialNeuron` inherits all the geometrical variables of the compartments (``length``, ``diameter``, ``area``, ``volume``), as well as the ``distance`` variable that gives the distance to the soma. For morphologies that use coordinates, the ``x``, ``y`` and ``z`` variables are provided as well. Additionally, a state variable ``Cm`` is created. It is initialized with the value given at construction, but it can be modified on a compartment per compartment basis (which is useful to model myelinated axons). The membrane potential is stored in state variable ``v``. Note that for all variable values that vary across a compartment (e.g. ``distance``, ``x``, ``y``, ``z``, ``v``), the value that is reported is the value at the midpoint of the compartment. The key state variable, which must be specified at construction, is ``Im``. It is the total transmembrane current, expressed in units of current per area. This is a mandatory line in the definition of the model. The rest of the string description may include other state variables (differential equations or subexpressions) or parameters, exactly as in `NeuronGroup`. At every timestep, Brian integrates the state variables, calculates the transmembrane current at every point on the neuronal morphology, and updates ``v`` using the transmembrane current and the diffusion current, which is calculated based on the morphology and the intracellular resistivity. Note that the transmembrane current is a surfacic current, not the total current in the compartment. This choice means that the model equations are independent of the number of compartments chosen for the simulation. The space and time constants can obtained for any point of the neuron with the ``space_constant`` respectively ``time_constant`` attributes:: l = neuron.space_constant[0] tau = neuron.time_constant[0] The calculation is based on the local total conductance (not just the leak conductance), therefore, it can potentially vary during a simulation (e.g. decrease during an action potential). The reported value is only correct for compartments with a cylindrical geometry, though, it does not give reasonable values for compartments with strongly varying diameter. To inject a current `I` at a particular point (e.g. through an electrode or a synapse), this current must be divided by the area of the compartment when inserted in the transmembrane current equation. This is done automatically when the flag ``point current`` is specified, as in the example above. This flag can apply only to subexpressions or parameters with amp units. Internally, the expression of the transmembrane current ``Im`` is simply augmented with ``+I/area``. A current can then be injected in the first compartment of the neuron (generally the soma) as follows:: neuron.I[0] = 1*nA State variables of the `SpatialNeuron` include all the compartments of that neuron (including subtrees). Therefore, the statement ``neuron.v = EL + 10*mV`` sets the membrane potential of the entire neuron at -60 mV. Subtrees can be accessed by attribute (in the same way as in `Morphology` objects):: neuron.axon.gNa = 10*gL Note that the state variables correspond to the entire subtree, not just the main section. That is, if the axon had branches, then the above statement would change ``gNa`` on the main section and all the sections in the subtree. To access the main section only, use the attribute ``main``:: neuron.axon.main.gNa = 10*gL A typical use case is when one wants to change parameter values at the soma only. For example, inserting an electrode current at the soma is done as follows:: neuron.main.I = 1*nA A part of a section can be accessed as follows:: initial_segment = neuron.axon[10*um:50*um] Finally, similar to the way that you can refer to a subset of neurons of a `NeuronGroup`, you can also index the `SpatialNeuron` object itself, e.g. to get a group representing only the first compartment of a cell (typically the soma), you can use:: soma = neuron[0] In the same way as for sections, you can also use slices, either with the indices of compartments, or with the distance from the root:: first_compartments = neuron[:3] first_compartments = neuron[0*um:30*um] However, note that this is restricted to contiguous indices which most of the time means that all compartments indexed in this way have to be part of the same section. Such indices can be acquired directly from the morphology:: axon = neuron[morpho.axon.indices[:]] or, more concisely:: axon = neuron[morpho.axon] Synaptic inputs ~~~~~~~~~~~~~~~ There are two methods to have synapses on `SpatialNeuron`. The first one to insert synaptic equations directly in the neuron equations:: eqs=''' Im = gL * (EL - v) : amp/meter**2 Is = gs * (Es - v) : amp (point current) dgs/dt = -gs/taus : siemens ''' neuron = SpatialNeuron(morphology=morpho, model=eqs, Cm=1*uF/cm**2, Ri=100*ohm*cm) Note that, as for electrode stimulation, the synaptic current must be defined as a point current. Then we use a `Synapses` object to connect a spike source to the neuron:: S = Synapses(stimulation, neuron, on_pre='gs += w') S.connect(i=0, j=50) S.connect(i=1, j=100) This creates two synapses, on compartments 50 and 100. One can specify the compartment number with its spatial position by indexing the morphology:: S.connect(i=0, j=morpho[25*um]) S.connect(i=1, j=morpho.axon[30*um]) In this method for creating synapses, there is a single value for the synaptic conductance in any compartment. This means that it will fail if there are several synapses onto the same compartment and synaptic equations are nonlinear. The second method, which works in such cases, is to have synaptic equations in the `Synapses` object:: eqs=''' Im = gL * (EL - v) : amp/meter**2 Is = gs * (Es - v) : amp (point current) gs : siemens ''' neuron = SpatialNeuron(morphology=morpho, model=eqs, Cm=1 * uF / cm ** 2, Ri=100 * ohm * cm) S = Synapses(stimulation, neuron, model='''dg/dt = -g/taus : siemens gs_post = g : siemens (summed)''', on_pre='g += w') Here each synapse (instead of each compartment) has an associated value ``g``, and all values of ``g`` for each compartment (i.e., all synapses targeting that compartment) are collected into the compartmental variable ``gs``. Detecting spikes ~~~~~~~~~~~~~~~~ To detect and record spikes, we must specify a threshold condition, essentially in the same way as for a `NeuronGroup`:: neuron = SpatialNeuron(morphology=morpho, model=eqs, threshold='v > 0*mV', refractory='v > -10*mV') Here spikes are detected when the membrane potential ``v`` reaches 0 mV. Because there is generally no explicit reset in this type of model (although it is possible to specify one), ``v`` remains above 0 mV for some time. To avoid detecting spikes during this entire time, we specify a refractory period. In this case no spike is detected as long as ``v`` is greater than -10 mV. Another possibility could be:: neuron = SpatialNeuron(morphology=morpho, model=eqs, threshold='m > 0.5', refractory='m > 0.4') where ``m`` is the state variable for sodium channel activation (assuming this has been defined in the model). Here a spike is detected when half of the sodium channels are open. With the syntax above, spikes are detected in all compartments of the neuron. To detect them in a single compartment, use the ``threshold_location`` keyword:: neuron = SpatialNeuron(morphology=morpho, model=eqs, threshold='m > 0.5', threshold_location=30, refractory='m > 0.4') In this case, spikes are only detecting in compartment number 30. Reset then applies locally to that compartment (if a reset statement is defined). Again the location of the threshold can be specified with spatial position:: neuron = SpatialNeuron(morphology=morpho, model=eqs, threshold='m > 0.5', threshold_location=morpho.axon[30*um], refractory='m > 0.4') Subgroups ~~~~~~~~~ In the same way that you can refer to a subset of neurons in a `NeuronGroup`, you can also refer to a subset of compartments in a `SpatialNeuron` brian2-2.5.4/docs_sphinx/user/numerical_integration.rst000066400000000000000000000214161445201106100233100ustar00rootroot00000000000000.. _numerical_integration: Numerical integration ===================== By default, Brian chooses an integration method automatically, trying to solve the equations exactly first (for linear equations) and then resorting to numerical algorithms. It will also take care of integrating stochastic differential equations appropriately. Note that in some cases, the automatic choice of integration method will not be appropriate, because of a choice of parameters that couldn't be determined in advance. In this case, typically you will get nan (not a number) values in the results, or large oscillations. In this case, Brian will generate a warning to let you know, but will not raise an error. Method choice ------------- You will get an ``INFO`` message telling you which integration method Brian decided to use, together with information about how much time it took to apply the integration method to your equations. If other methods have been tried but were not applicable, you will also see the time it took to try out those other methods. In some cases, checking other methods (in particular the ``'exact'`` method which attempts to solve the equations analytically) can take a considerable amount of time -- to avoid wasting this time, you can always chose the integration method manually (see below). You can also suppress the message by raising the log level or by explicitly suppressing ``'method_choice'`` log messages -- for details, see :doc:`../advanced/logging`. If you prefer to chose an integration algorithm yourself, you can do so using the ``method`` keyword for `NeuronGroup`, `Synapses`, or `SpatialNeuron`. The complete list of available methods is the following: * ``'exact'``: exact integration for linear equations (alternative name: ``'linear'``) * ``'exponential_euler'``: exponential Euler integration for conditionally linear equations * ``'euler'``: forward Euler integration (for additive stochastic differential equations using the Euler-Maruyama method) * ``'rk2'``: second order Runge-Kutta method (midpoint method) * ``'rk4'``: classical Runge-Kutta method (RK4) * ``'heun'``: stochastic Heun method for solving Stratonovich stochastic differential equations with non-diagonal multiplicative noise. * ``'milstein'``: derivative-free Milstein method for solving stochastic differential equations with diagonal multiplicative noise .. note:: The ``'independent'`` integration method (exact integration for a system of independent equations, where all the equations can be analytically solved independently) should no longer be used and might be removed in future versions of Brian. .. note:: The following methods are still considered experimental * ``'gsl'``: default integrator when choosing to integrate equations with the GNU Scientific Library ODE solver: the rkf45 method. Uses an adaptable time step by default. * ``'gsl_rkf45'``: Runge-Kutta-Fehlberg method. A good general-purpose integrator according to the GSL documentation. Uses an adaptable time step by default. * ``'gsl_rk2'``: Second order Runge-Kutta method using GSL. Uses an adaptable time step by default. * ``'gsl_rk4'``: Fourth order Runge-Kutta method using GSL. Uses an adaptable time step by default. * ``'gsl_rkck'``: Runge-Kutta Cash-Karp method using GSL. Uses an adaptable time step by default. * ``'gsl_rk8pd'``: Runge-Kutta Prince-Dormand method using GSL. Uses an adaptable time step by default. .. admonition:: The following topics are not essential for beginners. | Technical notes --------------- Each class defines its own list of algorithms it tries to apply, `NeuronGroup` and `Synapses` will use the first suitable method out of the methods ``'exact'``, ``'euler'`` and ``'heun'`` while `SpatialNeuron` objects will use ``'exact'``, ``'exponential_euler'``, ``'rk2'`` or ``'heun'``. You can also define your own numerical integrators, see :doc:`../advanced/state_update` for details. .. _gsl_integration: GSL stateupdaters ----------------- The stateupdaters preceded with the gsl tag use ODE solvers defined in the GNU Scientific Library. The benefit of using these integrators over the ones written by Brian internally, is that they are implemented with an adaptable timestep. Integrating with an adaptable timestep comes with two advantages: * These methods check whether the estimated error of the solutions returned fall within a certain error bound. For the non-gsl integrators there is currently no such check. * Systems no longer need to be simulated with just one time step. That is, a bigger timestep can be chosen and the integrator will reduce the timestep when increased accuracy is required. This is particularly useful for systems where both slow and fast time constants coexist, as is the case with for example (networks of neurons with) Hodgkin-Huxley equations. Note that Brian's timestep still determines the resolution for monitors, spike timing, spike propagation etc. Hence, in a network, the simulation error will therefore still be on the order of ``dt``. The benefit is that short time constants occurring in equations no longer dictate the network time step. In addition to a choice between different integration methods, there are a few more options that can be specified when using GSL. These options can be specified by sending a dictionary as the ``method_options`` key upon initialization of the object using the integrator (`NeuronGroup`, `Synapses` or `SpatialNeuron`). The available method options are: * ``'adaptable_timestep'``: whether or not to let GSL reduce the timestep to achieve the accuracy defined with the ``'absolute_error'`` and ``'absolute_error_per_variable'`` options described below. If this is set to ``False``, the timestep is determined by Brian (i.e. the ``dt`` of the respective clock is used, see :ref:`scheduling`). If the resulted estimated error exceeds the set error bounds, the simulation is aborted. When using cython this is reported with an `IntegrationError`. Defaults to ``True``. * ``'absolute_error'``: each of the methods has a way of estimating the error that is the result of using numerical integration. You can specify the maximum size of this error to be allowed for any of the to-be-integrated variables in base units with this keyword. Note that giving very small values makes the simulation slow and might result in unsuccessful integration. In the case of using the ``'absolute_error_per_variable'`` option, this is the error for variables that were not specified individually. Defaults to 1e-6. * ``'absolute_error_per_variable'``: specify the absolute error per variable in its own units. Variables for which the error is not specified use the error set with the ``'absolute_error'`` option. Defaults to None. * ``'max_steps'``: The maximal number of steps that the integrator will take within a single "Brian timestep" in order to reach the given error criterion. Can be set to 0 to not set any limits. Note that without limits, it can take a very long time until the integrator figures out that it cannot reach the desired error level. This will manifest as a simulation that appears to be stuck. Defaults to 100. * ``'use_last_timestep'``: with the ``'adaptable_timestep'`` option set to True, GSL tries different time steps to find a solution that satisfies the set error bounds. It is likely that for Brian's next time step the GSL time step will be somewhat similar per neuron (e.g. active neurons will have a shorter GSL time step than inactive neurons). With this option set to True, the time step GSL found to satisfy the set error bounds is saved per neuron and given to GSL again in Brian's next time step. This also means that the final time steps are saved in Brian's memory and can thus be recorded with the `StateMonitor`: it can be accessed under ``'_last_timestep'``. Note that some extra memory is required to keep track of the last time steps. Defaults to True. * ``'save_failed_steps'``: if ``'adaptable_timestep'`` is set to True, each time GSL tries a time step and it results in an estimated error that exceeds the set bounds, one is added to the ``'_failed_steps'`` variable. For purposes of investigating what happens within GSL during an integration step, we offer the option of saving this variable. Defaults to False. * ``'save_step_count'``: the same goes for the total number of GSL steps taken in a single Brian time step: this is optionally saved in the ``'_step_count'`` variable. Defaults to False. Note that at the moment recording ``'_last_timestep'``, ``'_failed_steps'``, or ``'_step_count'`` requires a call to `run` (e.g. with 0 ms) to trigger the code generation process, before the call to `StateMonitor`. More information on the GSL ODE solver itself can be found in its `documentation `_. brian2-2.5.4/docs_sphinx/user/plotting_functions.rst000066400000000000000000000027461445201106100226630ustar00rootroot00000000000000How to plot functions ===================== Models of synapses and neurons are typically composed of a series of functions. To affirm their correct implementation a plot is often helpful. Consider the following membrane voltage dependent Hodgkin-Huxley equations:: from brian2 import * VT = -63*mV eq = Equations(""" alpha_m = 0.32*(mV**-1)*4*mV/exprel((13*mV-v+VT)/(4*mV))/ms : Hz beta_m = 0.28*(mV**-1)*5*mV/exprel((v-VT-40*mV)/(5*mV))/ms : Hz alpha_h = 0.128*exp((17*mV-v+VT)/(18*mV))/ms : Hz beta_h = 4./(1+exp((40*mV-v+VT)/(5*mV)))/ms : Hz alpha_n = 0.032*(mV**-1)*5*mV/exprel((15*mV-v+VT)/(5*mV))/ms : Hz beta_n = .5*exp((10*mV-v+VT)/(40*mV))/ms : Hz tau_n = 1/(alpha_n + beta_n) : second tau_m = 1/(alpha_m + beta_m) : second tau_h = 1/(alpha_h + beta_h) : second """) We can do the following to plot them as function of membrane voltage:: group = NeuronGroup(100, eq + Equations("v : volt")) group.v = np.linspace(-100, 100, len(group))*mV plt.plot(group.v/mV, group.tau_m[:]/ms, label="tau_m") plt.plot(group.v/mV, group.tau_n[:]/ms, label="tau_n") plt.plot(group.v/mV, group.tau_h[:]/ms, label="tau_h") plt.xlabel('membrane voltage / mV') plt.ylabel('tau / ms') plt.legend() .. image:: images/function_plot.png Note that we need to use ``[:]`` for the ``tau_...`` equations, because Brian cannot resolve the external constant ``VT`` otherwise. Alternatively we could have supplied the constant in the namespace of the `NeuronGroup`, see :doc:`/advanced/namespaces`. brian2-2.5.4/docs_sphinx/user/recording.rst000066400000000000000000000256101445201106100207020ustar00rootroot00000000000000Recording during a simulation ============================= .. sidebar:: For Brian 1 users See the document :doc:`../introduction/brian1_to_2/monitors` for details how to convert Brian 1 code. .. contents:: :local: :depth: 1 Recording variables during a simulation is done with "monitor" objects. Specifically, spikes are recorded with `SpikeMonitor`, the time evolution of variables with `StateMonitor` and the firing rate of a population of neurons with `PopulationRateMonitor`. Recording spikes ---------------- To record spikes from a group ``G`` simply create a `SpikeMonitor` via ``SpikeMonitor(G)``. After the simulation, you can access the attributes ``i``, ``t``, ``num_spikes`` and ``count`` of the monitor. The ``i`` and ``t`` attributes give the array of neuron indices and times of the spikes. For example, if ``M.i==[0, 2, 1]`` and ``M.t==[1*ms, 2*ms, 3*ms]`` it means that neuron 0 fired a spike at 1 ms, neuron 2 fired a spike at 2 ms, and neuron 1 fired a spike at 3 ms. Alternatively, you can also call the `~brian2.monitors.spikemonitor.SpikeMonitor.spike_trains` method to get a dictionary mapping neuron indices to arrays of spike times, i.e. in the above example, ``spike_trains = M.spike_trains(); spike_trains[1]`` would return ``array([ 3.]) * msecond``. The ``num_spikes`` attribute gives the total number of spikes recorded, and ``count`` is an array of the length of the recorded group giving the total number of spikes recorded from each neuron. Example:: G = NeuronGroup(N, model='...') M = SpikeMonitor(G) run(runtime) plot(M.t/ms, M.i, '.') If you are only interested in summary statistics but not the individual spikes, you can set the ``record`` argument to ``False``. You will then not have access to ``i`` and ``t`` but you can still get the ``count`` and the total number of spikes (``num_spikes``). .. _recording_variables_spike_time: Recording variables at spike time --------------------------------- By default, a `SpikeMonitor` only records the time of the spike and the index of the neuron that spiked. Sometimes it can be useful to addtionaly record other variables, e.g. the membrane potential for models where the threshold is not at a fixed value. This can be done by providing an extra ``variables`` argument, the recorded variable can then be accessed as an attribute of the `SpikeMonitor`, e.g.:: G = NeuronGroup(10, 'v : 1', threshold='rand()<100*Hz*dt') G.run_regularly('v = rand()') M = SpikeMonitor(G, variables=['v']) run(100*ms) plot(M.t/ms, M.v, '.') To conveniently access the values of a recorded variable for a single neuron, the `SpikeMonitor.values` method can be used that returns a dictionary with the values for each neuron.:: G = NeuronGroup(N, '''dv/dt = (1-v)/(10*ms) : 1 v_th : 1''', threshold='v > v_th', # randomly change the threshold after a spike: reset='''v=0 v_th = clip(v_th + rand()*0.2 - 0.1, 0.1, 0.9)''') G.v_th = 0.5 spike_mon = SpikeMonitor(G, variables='v') run(1*second) v_values = spike_mon.values('v') print('Threshold crossing values for neuron 0: {}'.format(v_values[0])) hist(spike_mon.v, np.arange(0, 1, .1)) show() .. note:: Spikes are not the only events that can trigger recordings, see :doc:`../advanced/custom_events`. .. _recording_variables_continuously: Recording variables continuously -------------------------------- To record how a variable evolves over time, use a `StateMonitor`, e.g. to record the variable ``v`` at every time step and plot it for neuron 0:: G = NeuronGroup(...) M = StateMonitor(G, 'v', record=True) run(...) plot(M.t/ms, M.v[0]/mV) In general, you specify the group, variables and indices you want to record from. You specify the variables with a string or list of strings, and the indices either as an array of indices or ``True`` to record all indices (but beware because this may take a lot of memory). After the simulation, you can access these variables as attributes of the monitor. They are 2D arrays with shape ``(num_indices, num_times)``. The special attribute ``t`` is an array of length ``num_times`` with the corresponding times at which the values were recorded. Note that you can also use `StateMonitor` to record from `Synapses` where the indices are the synapse indices rather than neuron indices. In this example, we record two variables v and u, and record from indices 0, 10 and 100. Afterwards, we plot the recorded values of v and u from neuron 0:: G = NeuronGroup(...) M = StateMonitor(G, ('v', 'u'), record=[0, 10, 100]) run(...) plot(M.t/ms, M.v[0]/mV, label='v') plot(M.t/ms, M.u[0]/mV, label='u') There are two subtly different ways to get the values for specific neurons: you can either index the 2D array stored in the attribute with the variable name (as in the example above) or you can index the monitor itself. The former will use an index relative to the recorded neurons (e.g. `M.v[1]` will return the values for the second *recorded* neuron which is the neuron with the index 10 whereas `M.v[10]` would raise an error because only three neurons have been recorded), whereas the latter will use an absolute index corresponding to the recorded group (e.g. `M[1].v` will raise an error because the neuron with the index 1 has not been recorded and `M[10].v` will return the values for the neuron with the index 10). If all neurons have been recorded (e.g. with ``record=True``) then both forms give the same result. Note that for plotting all recorded values at once, you have to transpose the variable values:: plot(M.t/ms, M.v.T/mV) .. note:: In contrast to Brian 1, the values are recorded at the beginning of a time step and not at the end (you can set the ``when`` argument when creating a `StateMonitor`, details about scheduling can be found here: :doc:`../advanced/scheduling`). Recording population rates -------------------------- To record the time-varying firing rate of a population of neurons use `PopulationRateMonitor`. After the simulation the monitor will have two attributes ``t`` and ``rate``, the latter giving the firing rate at each time step corresponding to the time in ``t``. For example:: G = NeuronGroup(...) M = PopulationRateMonitor(G) run(...) plot(M.t/ms, M.rate/Hz) To get a smoother version of the rate, use `PopulationRateMonitor.smooth_rate`. .. admonition:: The following topics are not essential for beginners. | Getting all data ---------------- Note that all monitors are implement as "groups", so you can get all the stored values in a monitor with the `~.VariableOwner.get_states` method, which can be useful to dump all recorded data to disk, for example:: import pickle group = NeuronGroup(...) state_mon = StateMonitor(group, 'v', record=...) run(...) data = state_mon.get_states(['t', 'v']) with open('state_mon.pickle', 'w') as f: pickle.dump(data, f) Recording values for a subset of the run ---------------------------------------- Monitors can be created and deleted between runs, e.g. to ignore the first second of your simulation in your recordings you can do:: # Set up network without monitor run(1*second) state_mon = StateMonitor(....) run(...) # Continue run and record with the StateMonitor Alternatively, you can set the monitor's `~.BrianObject.active` attribute as explained in the :ref:`scheduling` section. Freeing up memory in long recordings ------------------------------------ Creating and deleting monitors can also be useful to free memory during a long recording. The following will do a simulation run, dump the monitor data to disk, delete the monitor and finally continue the run with a new monitor:: import pickle # Set up network state_mon = StateMonitor(...) run(...) # a long run data = state_mon.get_states(...) with open('first_part.data', 'w') as f: pickle.dump(data, f) del state_mon del data state_mon = StateMonitor(...) run(...) # another long run Note that this technique cannot be applied in :ref:`standalone mode `. Recording random subsets of neurons ----------------------------------- In large networks, you might only be interested in the activity of a random subset of neurons. While you can specify a ``record`` argument for a `StateMonitor` that allows you to select a subset of neurons, this is not possible for `SpikeMonitor`/`EventMonitor` and `PopulationRateMonitor`. However, Brian allows you to record with these monitors from a subset of neurons by using a :ref:`subgroup `:: group = NeuronGroup(1000, ...) spike_mon = SpikeMonitor(group[:100]) # only record first 100 neurons It might seem like a restriction that such a subgroup has to be contiguous, but the order of neurons in a group does not have any meaning as such; in a randomly ordered group of neurons, any contiguous group of neurons can be considered a random subset. If some aspects of your model *do* depend on the position of the neuron in a group (e.g. a ring model, where neurons are connected based on their distance in the ring, or a model where initial values or parameters span a range of values in a regular fashion), then this requires an extra step: instead of using the order of neurons in the group directly, or depending on the neuron index ``i``, create a new, shuffled, index variable as part of the model definition and then depend on this index instead:: group = NeuronGroup(10000, '''.... index : integer (constant)''') indices = group.i[:] np.random.shuffle(indices) group.index = indices # Then use 'index' in string expressions or use it as an index array # for initial values/parameters defined as numpy arrays If this solution is not feasible for some reason, there is another approach that works for a `SpikeMonitor`/`EventMonitor`. You can add an additional flag to each neuron, stating whether it should be recorded or not. Then, you define a new :doc:`custom event ` that is identical to the event you are interested in, but additionally requires the flag to be set. E.g. to only record the spikes of neurons with the ``to_record`` attribute set:: group = NeuronGroup(..., '''... to_record : boolean (constant)''', threshold='...', reset='...', events={'recorded_spike': '... and to_record'}) group.to_record = ... mon_events = EventMonitor(group, 'recorded_spike') Note that this solution will evaluate the threshold condition for each neuron twice, and is therefore slightly less efficient. There's one additional caveat: you'll have to manually include ``and not_refractory`` in your ``events`` definition if your neuron uses refractoriness. This is done automatically for the ``threshold`` condition, but not for any user-defined events. brian2-2.5.4/docs_sphinx/user/refractoriness.rst000066400000000000000000000160201445201106100217520ustar00rootroot00000000000000Refractoriness ============== .. contents:: :local: :depth: 1 Brian allows you to model the absolute refractory period of a neuron in a flexible way. The definition of refractoriness consists of two components: the amount of time after a spike that a neuron is considered to be refractory, and what changes in the neuron during the refractoriness. Defining the refractory period ------------------------------ The refractory period is specified by the ``refractory`` keyword in the `NeuronGroup` initializer. In the simplest case, this is simply a fixed time, valid for all neurons:: G = NeuronGroup(N, model='...', threshold='...', reset='...', refractory=2*ms) Alternatively, it can be a string expression that evaluates to a time. This expression will be evaluated after every spike and allows for a varying refractory period. For example, the following will set the refractory period to a random duration between 1ms and 3ms after every spike:: G = NeuronGroup(N, model='...', threshold='...', reset='...', refractory='(1 + 2*rand())*ms') In general, modelling a refractory period that varies across neurons involves declaring a state variable that stores the refractory period per neuron as a model parameter. The refractory expression can then refer to this parameter:: G = NeuronGroup(N, model='''... ref : second''', threshold='...', reset='...', refractory='ref') # Set the refractory period for each cell G.ref = ... This state variable can also be a dynamic variable itself. For example, it can serve as an adaptation mechanism by increasing it after every spike and letting it relax back to a steady-state value between spikes:: refractory_0 = 2*ms tau_refractory = 50*ms G = NeuronGroup(N, model='''... dref/dt = (refractory_0 - ref) / tau_refractory : second''', threshold='...', refractory='ref', reset='''... ref += 1*ms''') G.ref = refractory_0 In some cases, the condition for leaving the refractory period is not easily expressed as a certain time span. For example, in a Hodgkin-Huxley type model the threshold is only used for *counting* spikes and the refractoriness is used to prevent the count of multiple spikes for a single threshold crossing (the threshold condition would evaluate to ``True`` for several time points). When a neuron should leave the refractory period is not easily expressed as a time span but more naturally as a condition that the neuron should remain refractory for as long as it stays above the threshold. This can be achieved by using a string expression for the ``refractory`` keyword that evaluates to a boolean condition:: G = NeuronGroup(N, model='...', threshold='v > -20*mV', refractory='v >= -20*mV') The ``refractory`` keyword should be read as "stay refractory as long as the condition remains true". In fact, specifying a time span for the refractoriness will be automatically transformed into a logical expression using the current time ``t`` and the time of the last spike ``lastspike``. Specifying ``refractory=2*ms`` is basically equivalent to specifying ``refractory='(t - lastspike) <= 2*ms'``. However, this expression can give inconsistent results for the common case that the refractory period is a multiple of the simulation timestep. Due to floating point impreciseness, the actual value of ``t - lastspike`` can be slightly above or below a multiple of the simulation time step; comparing it directly to the refractory period can therefore lead to an end of the refractory one time step sooner or later. To avoid this issue, the actual code used for the above example is equivalent to ``refractory='timestep(t - lastspike, dt) <= timestep(2*ms, dt)'``. The `~brian2.core.functions.timestep` function is provided by Brian and takes care of converting a time into a time step in a safe way. .. versionadded:: 2.1.3 The `timestep` function is now used to avoid floating point issues in the refractoriness calculation. To restore the previous behaviour, set the `legacy.refractory_timing` preference to ``True``. Defining model behaviour during refractoriness ---------------------------------------------- The refractoriness definition as described above only has a single effect by itself: threshold crossings during the refractory period are ignored. In the following model, the variable ``v`` continues to update during the refractory period but it does not elicit a spike if it crosses the threshold:: G = NeuronGroup(N, 'dv/dt = -v / tau : 1', threshold='v > 1', reset='v=0', refractory=2*ms) There is also a second implementation of refractoriness that is supported by Brian, one or several state variables can be clamped during the refractory period. To model this kind of behaviour, variables that should stop being updated during refractoriness can be marked with the ``(unless refractory)`` flag:: G = NeuronGroup(N, '''dv/dt = -(v + w)/ tau_v : 1 (unless refractory) dw/dt = -w / tau_w : 1''', threshold='v > 1', reset='v=0; w+=0.1', refractory=2*ms) In the above model, the ``v`` variable is clamped at 0 for 2ms after a spike but the adaptation variable ``w`` continues to update during this time. In addition, a variable of a neuron that is in its refractory period is *read-only*: incoming synapses or other code will have no effect on the value of ``v`` until it leaves its refractory period. .. admonition:: The following topics are not essential for beginners. | Arbitrary refractoriness ------------------------ In fact, arbitrary behaviours can be defined using Brian's refractoriness mechanism. A `NeuronGroup` with refractoriness automatically defines two variables: ``not_refractory`` A boolean variable stating whether a neuron is allowed to spike. ``lastspike`` The time of the last spike of the neuron. The variable ``not_refractory`` is updated at every time step by checking the refractoriness condition -- for a refractoriness defined by a time period, this means comparing ``lastspike`` to the current time ``t``. The ``not_refractory`` variable is then used to implement the refractoriness behaviour. Specifically, the ``threshold`` condition is replaced by ``threshold and not_refractory`` and differential equations that are marked as ``(unless refractory)`` are multiplied by ``int(not_refractory)`` (so that they have the value 0 when the neuron is refractory). This ``not_refractory`` variable is also available to the user to define more sophisticated refractoriness behaviour. For example, the following code updates the ``w`` variable with a different time constant during refractoriness:: G = NeuronGroup(N, '''dv/dt = -(v + w)/ tau_v : 1 (unless refractory) dw/dt = (-w / tau_active)*int(not_refractory) + (-w / tau_ref)*(1 - int(not_refractory)) : 1''', threshold='v > 1', reset='v=0; w+=0.1', refractory=2*ms) brian2-2.5.4/docs_sphinx/user/running.rst000066400000000000000000000362301445201106100204060ustar00rootroot00000000000000Running a simulation ==================== .. sidebar:: For Brian 1 users See the document :doc:`../introduction/brian1_to_2/networks_and_clocks` for details how to convert Brian 1 code. .. contents:: :local: :depth: 1 To run a simulation, one either constructs a new `Network` object and calls its `Network.run` method, or uses the "magic" system and a plain `run` call, collecting all the objects in the current namespace. Note that Brian has several different ways of running the actual computations, and choosing the right one can make orders of magnitude of difference in terms of simplicity and efficiency. See :doc:`computation` for more details. .. _networks: Networks -------- In most straightforward simulations, you do not have to explicitly create a `Network` object but instead can simply call `run` to run a simulation. This is what is called the "magic" system, because Brian figures out automatically what you want to do. When calling `run`, Brian runs the `collect` function to gather all the objects in the current context. It will include all the objects that are "visible", i.e. that you could refer to with an explicit name:: G = NeuronGroup(10, 'dv/dt = -v / (10*ms) : 1', threshold='v > 1', reset='v = 0') S = Synapses(G, G, model='w:1', on_pre='v+=w') S.connect('i!=j') S.w = 'rand()' mon = SpikeMonitor(G) run(10*ms) # will include G, S, mon Note that it will not automatically include objects that are "hidden" in containers, e.g. if you store several monitors in a list. Use an explicit `Network` object in this case. It might be convenient to use the `collect` function when creating the `Network` object in that case:: G = NeuronGroup(10, 'dv/dt = -v / (10*ms) : 1', threshold='v > 1', reset='v = 0') S = Synapses(G, G, model='w:1', on_pre='v+=w') S.connect('i!=j') S.w = 'rand()' monitors = [SpikeMonitor(G), StateMonitor(G, 'v', record=True)] # a simple run would not include the monitors net = Network(collect()) # automatically include G and S net.add(monitors) # manually add the monitors net.run(10*ms) .. _time_steps: Setting the simulation time step -------------------------------- To set the simulation time step for every simulated object, set the ``dt`` attribute of the `defaultclock` which is used by all objects that do not explicitly specify a ``clock`` or ``dt`` value during construction:: defaultclock.dt = 0.05*ms If some objects should use a different clock (e.g. to record values with a `StateMonitor` not at every time step in a long running simulation), you can provide a ``dt`` argument to the respective object:: s_mon = StateMonitor(group, 'v', record=True, dt=1*ms) To sum up: * Set ``defaultclock.dt`` to the time step that should be used by most (or all) of your objects. * Set ``dt`` explicitly when creating objects that should use a different time step. Behind the scenes, a new `Clock` object will be created for each object that defines its own ``dt`` value. .. _progress_reporting: Progress reporting ------------------ Especially for long simulations it is useful to get some feedback about the progress of the simulation. Brian offers a few built-in options and an extensible system to report the progress of the simulation. In the `Network.run` or `run` call, two arguments determine the output: ``report`` and ``report_period``. When ``report`` is set to ``'text'`` or ``'stdout'``, the progress will be printed to the standard output, when it is set to ``'stderr'``, it will be printed to "standard error". There will be output at the start and the end of the run, and during the run in ``report_period`` intervals. It is also possible to do :ref:`custom progress reporting `. .. _continue_repeat: Continuing/repeating simulations -------------------------------- To store the current state of the simulation, call `store` (use the `Network.store` method for a `Network`). You can store more than one snapshot of a system by providing a name for the snapshot; if `store` is called without a specified name, ``'default'`` is used as the name. To restore the state, use `restore`. The following simple example shows how this system can be used to run several trials of an experiment:: # set up the network G = NeuronGroup(...) ... spike_monitor = SpikeMonitor(G) # Snapshot the state store() # Run the trials spike_counts = [] for trial in range(3): restore() # Restore the initial state run(...) # store the results spike_counts.append(spike_monitor.count) The following schematic shows how multiple snapshots can be used to run a network with a separate "train" and "test" phase. After training, the test is run several times based on the trained network. The whole process of training and testing is repeated several times as well:: # set up the network G = NeuronGroup(..., '''... test_input : amp ...''') S = Synapses(..., '''... plastic : boolean (shared) ...''') G.v = ... S.connect(...) S.w = ... # First snapshot at t=0 store('initialized') # Run 3 complete trials for trial in range(3): # Simulate training phase restore('initialized') S.plastic = True run(...) # Snapshot after learning store('after_learning') # Run 5 tests after the training for test_number in range(5): restore('after_learning') S.plastic = False # switch plasticity off G.test_input = test_inputs[test_number] # monitor the activity now spike_mon = SpikeMonitor(G) run(...) # Do something with the result # ... .. admonition:: The following topics are not essential for beginners. | Multiple magic runs ------------------- When you use more than a single `run` statement, the magic system tries to detect which of the following two situations applies: 1. You want to continue a previous simulation 2. You want to start a new simulation For this, it uses the following heuristic: if a simulation consists only of objects that have not been run, it will start a new simulation starting at time 0 (corresponding to the creation of a new `Network` object). If a simulation only consists of objects that have been simulated in the previous `run` call, it will continue that simulation at the previous time. If neither of these two situations apply, i.e., the network consists of a mix of previously run objects and new objects, an error will be raised. If this is not a mistake but intended (e.g. when a new input source and synapses should be added to a network at a later stage), use an explicit `Network` object. In these checks, "non-invalidating" objects (i.e. objects that have `BrianObject.invalidates_magic_network` set to ``False``) are ignored, e.g. creating new monitors is always possible. Note that if you do not want to run an object for the complete duration of your simulation, you can create the object in the beginning of your simulation and then set its `~.BrianObject.active` attribute. For details, see the :ref:`Scheduling ` section below. Changing the simulation time step --------------------------------- You can change the simulation time step after objects have been created or even after a simulation has been run:: defaultclock.dt = 0.1*ms # Set the network # ... run(initial_time) defaultclock.dt = 0.01*ms run(full_time - initial_time) To change the time step between runs for objects that do not use the `defaultclock`, you cannot directly change their ``dt`` attribute (which is read-only) but instead you have to change the ``dt`` of the ``clock`` attribute. If you want to change the ``dt`` value of several objects at the same time (but not for all of them, i.e. when you cannot use ``defaultclock.dt``) then you might consider creating a `Clock` object explicitly and then passing this clock to each object with the ``clock`` keyword argument (instead of ``dt``). This way, you can later change the ``dt`` for several objects at once by assigning a new value to `Clock.dt`. Note that a change of ``dt`` has to be compatible with the internal representation of clocks as an integer value (the number of elapsed time steps). For example, you can simulate an object for 100ms with a time step of 0.1ms (i.e. for 1000 steps) and then switch to a ``dt`` of 0.5ms, the time will then be internally represented as 200 steps. You cannot, however, switch to a dt of 0.3ms, because 100ms are not an integer multiple of 0.3ms. .. _profiling: Profiling --------- To get an idea which parts of a simulation take the most time, Brian offers a basic profiling mechanism. If a simulation is run with the ``profile=True`` keyword argument, it will collect information about the total simulation time for each `CodeObject`. This information can then be retrieved from `Network.profiling_info`, which contains a list of ``(name, time)`` tuples. For convenience, a string summary can be obtained by calling `profiling_summary` (which will automatically refer to the current :ref:`"magic" network `). The following example shows profiling output after running the CUBA example (where the neuronal state updates take up the most time):: >>> from brian2 import profiling_summary >>> profiling_summary(show=5) # show the 5 objects that took the longest # doctest: +SKIP Profiling summary ================= neurongroup_stateupdater 5.54 s 61.32 % synapses_pre 1.39 s 15.39 % synapses_1_pre 1.03 s 11.37 % spikemonitor 0.59 s 6.55 % neurongroup_thresholder 0.33 s 3.66 % If you use an explicit `~.Network` object, you need to pass it to ``profiling_summary``:: >>> net = Network(...) # doctest: +SKIP >>> profiling_summary(net, ...) # doctest: +SKIP .. _scheduling: Scheduling ---------- Every simulated object in Brian has three attributes that can be specified at object creation time: ``dt``, ``when``, and ``order``. The time step of the simulation is determined by ``dt``, if it is specified, or otherwise by ``defaultclock.dt``. Changing this will therefore change the ``dt`` of all objects that don't specify one. Alternatively, a ``clock`` object can be specified directly, this can be useful if a clock should be shared between several objects -- under most circumstances, however, a user should not have to deal with the creation of `Clock` objects and just define ``dt``. During a single time step, objects are updated in an order according first to their ``when`` argument's position in the schedule. This schedule is determined by `Network.schedule` which is a list of strings, determining "execution slots" and their order. It defaults to: ``['start', 'groups', 'thresholds', 'synapses', 'resets', 'end']``. In addition to the names provided in the schedule, names such as ``before_thresholds`` or ``after_synapses`` can be used that are understood as slots in the respective positions. The default for the ``when`` attribute is a sensible value for most objects (resets will happen in the ``reset`` slot, etc.) but sometimes it make sense to change it, e.g. if one would like a `StateMonitor`, which by default records in the ``start`` slot, to record the membrane potential before a reset is applied (otherwise no threshold crossings will be observed in the membrane potential traces). Finally, if during a time step two objects fall in the same execution slot, they will be updated in ascending order according to their ``order`` attribute, an integer number defaulting to 0. If two objects have the same ``when`` and ``order`` attribute then they will be updated in an arbitrary but reproducible order (based on the lexicographical order of their names). Note that objects that don't do any computation by themselves but only act as a container for other objects (e.g. a `NeuronGroup` which contains a `StateUpdater`, a `Resetter` and a `Thresholder`), don't have any value for ``when``, but pass on the given values for ``dt`` and ``order`` to their containing objects. If you want your simulation object to run only for a particular time period of the whole simulation, you can use the `~.BrianObject.active` attribute. For example, this can be useful when you want a monitor to be active only for some time out of a long simulation:: # Set up the network # ... monitor = SpikeMonitor(...) monitor.active = False run(long_time*seconds) # not recording monitor.active = True run(required_time*seconds) # recording To see how the objects in a network are scheduled, you can use the `scheduling_summary` function:: >>> group = NeuronGroup(10, 'dv/dt = -v/(10*ms) : 1', threshold='v > 1', ... reset='v = 0') >>> mon = StateMonitor(group, 'v', record=True, dt=1*ms) >>> scheduling_summary() # doctest: +SKIP object | part of | Clock dt | when | order | active ----------------------------------------+-----------------------------+------------------------+------------+-------+------- statemonitor (StateMonitor) | statemonitor (StateMonitor) | 1. ms (every 10 steps) | start | 0 | yes neurongroup_stateupdater (StateUpdater) | neurongroup (NeuronGroup) | 100. us (every step) | groups | 0 | yes neurongroup_thresholder (Thresholder) | neurongroup (NeuronGroup) | 100. us (every step) | thresholds | 0 | yes neurongroup_resetter (Resetter) | neurongroup (NeuronGroup) | 100. us (every step) | resets | 0 | yes As you can see in the output above, the `StateMonitor` will only record the membrane potential every 10 time steps, but when it does, it will do it at the start of the time step, before the numerical integration, the thresholding, and the reset operation takes place. Every new `Network` starts a simulation at time 0; `Network.t` is a read-only attribute, to go back to a previous moment in time (e.g. to do another trial of a simulation with a new noise instantiation) use the mechanism described below. Store/restore ------------- Note that `Network.run`, `Network.store` and `Network.restore` (or `run`, `store`, `restore`) are the only way of affecting the time of the clocks. In contrast to Brian1, it is no longer necessary (nor possible) to directly set the time of the clocks or call a ``reinit`` function. The state of a network can also be stored on disk with the optional ``filename`` argument of `Network.store`/`store`. This way, you can run the initial part of a simulation once, store it to disk, and then continue from this state later. Note that the `store`/`restore` mechanism does not re-create the network as such, you still need to construct all the `NeuronGroup`, `Synapses`, `StateMonitor`, ... objects, restoring will only restore all the state variable values (membrane potential, conductances, synaptic connections/weights/delays, ...). This restoration does however restore the internal state of the objects as well, e.g. spikes that have not been delivered yet because of synaptic delays will be delivered correctly. brian2-2.5.4/docs_sphinx/user/synapses.rst000066400000000000000000000703561445201106100206020ustar00rootroot00000000000000Synapses ======== .. sidebar:: For Brian 1 users `Synapses` is now the only class for defining synaptic interactions, it replaces *Connection*, *STDP*, etc. See the document :doc:`../introduction/brian1_to_2/synapses` for details how to convert Brian 1 code. .. contents:: :local: :depth: 1 Defining synaptic models ------------------------ The most simple synapse (adding a fixed amount to the target membrane potential on every spike) is described as follows:: w = 1*mV S = Synapses(P, Q, on_pre='v += w') This defines a set of synapses between `NeuronGroup` P and `NeuronGroup` Q. If the target group is not specified, it is identical to the source group by default. The ``on_pre`` keyword defines what happens when a presynaptic spike arrives at a synapse. In this case, the constant ``w`` is added to variable ``v``. Because ``v`` is not defined as a synaptic variable, it is assumed by default that it is a postsynaptic variable, defined in the target `NeuronGroup` Q. Note that this does not create synapses (see `Creating Synapses`_), only the synaptic models. To define more complex models, models can be described as string equations, similar to the models specified in `NeuronGroup`:: S = Synapses(P, Q, model='w : volt', on_pre='v += w') The above specifies a parameter ``w``, i.e. a synapse-specific weight. Note that to avoid confusion, synaptic variables cannot have the same name as a pre- or post-synaptic variables. Synapses can also specify code that should be executed whenever a postsynaptic spike occurs (keyword ``on_post``) and a fixed (pre-synaptic) delay for all synapses (keyword ``delay``). As shown above, variable names that are not referring to a synaptic variable are automatically understood to be post-synaptic variables. To explicitly specify that a variable should be from a pre- or post-synaptic neuron, append the suffix ``_pre`` or ``_post``. An alternative but equivalent formulation of the ``on_pre`` statement above would therefore be ``v_post += w``. .. _synapse_model_syntax: Model syntax ~~~~~~~~~~~~ The model follows exactly the same syntax as for `NeuronGroup`. There can be parameters (e.g. synaptic variable ``w`` above), but there can also be named subexpressions and differential equations, describing the dynamics of synaptic variables. In all cases, synaptic variables are created, one value per synapse. Brian also automatically defines a number of synaptic variables that can be used in equations, ``on_pre`` and ``on_post`` statements, as well as when :ref:`assigning to other synaptic variables `: ``i`` The index of the pre-synaptic source of a synapse. ``j`` The index of the post-synaptic target of a synapse. ``N`` The total number of synapses. ``N_incoming`` The total number of synapses connected to the post-synaptic target of a synapse. ``N_outgoing`` The total number of synapses outgoing from the pre-synaptic source of a synapse. ``lastupdate`` The last time this synapse has applied an ``on_pre`` or ``on_post`` statement. There is normally no need to refer to this variable explicitly, it is used to implement :ref:`event_driven_updates` (see below). It is only defined when event-driven equations are used. .. _event_driven_updates: Event-driven updates ~~~~~~~~~~~~~~~~~~~~ By default, differential equations are integrated in a clock-driven fashion, as for a `NeuronGroup`. This is potentially very time consuming, because all synapses are updated at every timestep and Brian will therefore emit a warning. If you are sure about integrating the equations at every timestep (e.g. because you want to record the values continuously), then you should specify the flag ``(clock-driven)``, which will silence the warning. To ask Brian 2 to simulate differential equations in an event-driven fashion use the flag ``(event-driven)``. A typical example is pre- and postsynaptic traces in STDP:: model='''w:1 dApre/dt=-Apre/taupre : 1 (event-driven) dApost/dt=-Apost/taupost : 1 (event-driven)''' Here, Brian updates the value of ``Apre`` for a given synapse only when this synapse receives a spike, whether it is presynaptic or postsynaptic. More precisely, the variables are updated every time either the ``on_pre`` or ``on_post`` code is called for the synapse, so that the values are always up to date when these codes are executed. Automatic event-driven updates are only possible for a subset of equations, in particular for one-dimensional linear equations. These equations must also be independent of the other ones, that is, a differential equation that is not event-driven cannot depend on an event-driven equation (since the values are not continuously updated). In other cases, the user can write event-driven code explicitly in the update codes (see below). Pre and post codes ~~~~~~~~~~~~~~~~~~ The ``on_pre`` code is executed at each synapse receiving a presynaptic spike. For example:: on_pre='v+=w' adds the value of synaptic variable ``w`` to postsynaptic variable ``v``. Any sort of code can be executed. For example, the following code defines stochastic synapses, with a synaptic weight ``w`` and transmission probability ``p``:: S=Synapses(neuron_input,neurons,model="""w : 1 p : 1""", on_pre="v+=w*(rand()`, e.g. to normalize synaptic weights. For example, after the following assignment the sum of weights of all synapses that a neuron receives is identical to 1, regardless of the number of synapses it receives:: syn.w = '1.0/N_incoming' Note that it is also possible to index synaptic variables with a single index (integer, slice, or array), but in this case synaptic indices have to be provided. The ``N_incoming`` and ``N_outgoing`` variables give access to the total number of incoming/outgoing synapses for a neuron, but this access is given for each *synapse*. This is necessary to apply it to individual synapses as in the statement to normalize synaptic weights mentioned above. To access these values per *neuron* instead, `~.Synapses.N_incoming_post` and `~.Synapses.N_outgoing_pre` can be used. Note that synaptic equations or ``on_pre``/``on_post`` statements should always refer to ``N_incoming`` and ``N_outgoing`` without ``pre``/``post`` suffix. Here's a little example illustrating the use of these variables:: >>> group1 = NeuronGroup(3, '') >>> group2 = NeuronGroup(3, '') >>> syn = Synapses(group1, group2) >>> syn.connect(i=[0, 0, 1, 2], j=[1, 2, 2, 2]) >>> print(syn.N_outgoing_pre) # for each presynaptic neuron [2 1 1] >>> print(syn.N_outgoing[:]) # same numbers, but indexed by synapse [2 2 1 1] >>> print(syn.N_incoming_post) [0 1 3] >>> print(syn.N_incoming[:]) [1 3 3 3] Note that `~.Synapses.N_incoming_post` and `~.Synapses.N_outgoing_pre` can contain zeros for neurons that do not have any incoming respectively outgoing synapses. In contrast, `~.Synapses.N_incoming` and `~.Synapses.N_outgoing` will never contain zeros, because unconnected neurons are not represented in the list of synapses. Delays ------ There is a special synaptic variable that is automatically created: ``delay``. It is the propagation delay from the presynaptic neuron to the synapse, i.e., the presynaptic delay. This is just a convenience syntax for accessing the delay stored in the presynaptic pathway: ``pre.delay``. When there is a postsynaptic code (keyword ``post``), the delay of the postsynaptic pathway can be accessed as ``post.delay``. The delay variable(s) can be set and accessed in the same way as other synaptic variables. The same semantics as for other synaptic variables apply, which means in particular that the delay is only set for the synapses that have been already created with `Synapses.connect`. If you want to set a global delay for all synapses of a `Synapses` object, you can directly specify that delay as part of the `Synapses` initializer:: synapses = Synapses(sources, targets, '...', on_pre='...', delay=1*ms) When you use this syntax, you can still change the delay afterwards by setting ``synapses.delay``, but you can only set it to another scalar value. If you need different delays across synapses, do not use this syntax but instead set the delay variable as any other synaptic variable (see above). Monitoring synaptic variables ----------------------------- A `StateMonitor` object can be used to monitor synaptic variables. For example, the following statement creates a monitor for variable ``w`` for the synapses 0 and 1:: M = StateMonitor(S, 'w', record=[0,1]) Note that these are *synapse* indices, not neuron indices. More convenient is to directly index the `Synapses` object, Brian will automatically calculate the indices for you in this case:: M = StateMonitor(S, 'w', record=S[0, :]) # all synapses originating from neuron 0 M = StateMonitor(S, 'w', record=S['i!=j']) # all synapses excluding autapses M = StateMonitor(S, 'w', record=S['w>0']) # all synapses with non-zero weights (at this time) You can also record a synaptic variable for all synapses by passing ``record=True``. The recorded traces can then be accessed in the usual way, again with the possibility to index the `Synapses` object:: plot(M.t / ms, M[S[0]].w / nS) # first synapse plot(M.t / ms, M[S[0, :]].w / nS) # all synapses originating from neuron 0 plot(M.t / ms, M[S['w>0*nS']].w / nS) # all synapses with non-zero weights (at this time) Note (for users of Brian's advanced standalone mode only): the use of the `Synapses` object for indexing and ``record=True`` only work in the default runtime modes. In standalone mode (see :ref:`cpp_standalone`), the synapses have not yet been created at this point, so Brian cannot calculate the indices. .. admonition:: The following topics are not essential for beginners. | Synaptic connection/weight matrices ----------------------------------- Brian does not directly support specifying synapses by using a matrix, you always have to use a "sparse" format, where each connection is defined by its source and target indices. However, you can easily convert between the two formats. Assuming you have a connection matrix :math:`C` of size :math:`N \times M`, where :math:`N` is the number of presynaptic cells, and :math:`M` the number of postsynaptic cells, with each entry being 1 for a connection, and 0 otherwise. You can convert this matrix to arrays of source and target indices, which you can then provide to Brian's `~.Synapses.connect` function:: C = ... # The connection matrix as a numpy array of 0's and 1's sources, targets = C.nonzero() synapses = Synapses(...) synapses.connect(i=sources, j=targets) Similarly, you can transform the flat array of values stored in a synapse into a matrix form. For example, to get a matrix with all the weight values ``w``, with ``NaN`` values where no synapse exists:: synapses = Synapses(source_group, target_group, '''... w : 1 # synaptic weight''', ...) # ... # Run e.g. a simulation with plasticity that changes the weights run(...) # Create a matrix to store the weights and fill it with NaN W = np.full((len(source_group), len(target_group)), np.nan) # Insert the values from the Synapses object W[synapses.i[:], synapses.j[:]] = synapses.w[:] You can also set synapses given a fully connected weight matrix (as a 2D ``numpy`` array ``W``):: synapses.w[:] = W.flatten() This works because the internal ordering of synapses is exactly the same as for a flattened matrix. .. _generator_syntax: Creating synapses with the generator syntax ------------------------------------------- The most general way of specifying a connection is using the generator syntax, e.g. to connect neuron i to all neurons j with 0<=j<=i:: S.connect(j='k for k in range(0, i+1)') There are several parts to this syntax. The general form is:: j='EXPR for VAR in RANGE if COND' or:: i='EXPR for VAR in RANGE if COND' Here ``EXPR`` can be any integer-valued expression. VAR is the name of the iteration variable (any name you like can be specified here). The ``if COND`` part is optional and lets you give an additional condition that has to be true for the synapse to be created. Finally, ``RANGE`` can be either: 1. a Python ``range``, e.g. ``range(N)`` is the integers from 0 to N-1, ``range(A, B)`` is the integers from A to B-1, ``range(low, high, step)`` is the integers from ``low`` to ``high-1`` with steps of size ``step``; 2. a random sample ``sample(N, p=0.1)`` gives a random sample of integers from 0 to N-1 with 10% probability of each integer appearing in the sample. This can have extra arguments like range, e.g. ``sample(low, high, step, p=0.1)`` will give each integer in ``range(low, high, step)`` with probability 10%; 3. a random sample ``sample(N, size=10)`` with a fixed size, in this example 10 values chosen (without replacement) from the integers from 0 to N-1. As for the random sample based on a probability, the ``sample`` expression can take additional arguments to sample from a restricted range. If you try to create an invalid synapse (i.e. connecting neurons that are outside the correct range) then you will get an error, e.g. you might like to try to do this to connect each neuron to its neighbours:: S.connect(j='i+(-1)**k for k in range(2)') However this won't work at for ``i=0`` it gives ``j=-1`` which is invalid. There is an option to just skip any synapses that are outside the valid range:: S.connect(j='i+(-1)**k for k in range(2)', skip_if_invalid=True) You can also use this argument to deal with random samples of incorrect size, i.e. a negative size or a size bigger than the total population size. With ``skip_if_invalid=True``, no error will be raised and a size of 0 or the population size will be used. Summed variables ---------------- In many cases, the postsynaptic neuron has a variable that represents a sum of variables over all its synapses. This is called a "summed variable". An example is nonlinear synapses (e.g. NMDA):: neurons = NeuronGroup(1, model='''dv/dt=(gtot-v)/(10*ms) : 1 gtot : 1''') S = Synapses(neuron_input, neurons, model='''dg/dt=-a*g+b*x*(1-g) : 1 gtot_post = g : 1 (summed) dx/dt=-c*x : 1 w : 1 # synaptic weight''', on_pre='x+=w') Here, each synapse has a conductance ``g`` with nonlinear dynamics. The neuron's total conductance is ``gtot``. The line stating ``gtot_post = g : 1 (summed)`` specifies the link between the two: ``gtot`` in the postsynaptic group is the summer over all variables ``g`` of the corresponding synapses. What happens during the simulation is that at each time step, presynaptic conductances are summed for each neuron and the result is copied to the variable ``gtot``. Another example is gap junctions:: neurons = NeuronGroup(N, model='''dv/dt=(v0-v+Igap)/tau : 1 Igap : 1''') S=Synapses(neurons,model='''w:1 # gap junction conductance Igap_post = w*(v_pre-v_post): 1 (summed)''') Here, ``Igap`` is the total gap junction current received by the postsynaptic neuron. Note that you cannot target the same post-synaptic variable from more than one `Synapses` object. To work around this restriction, use multiple post-synaptic variables that ar then summed up:: neurons = NeuronGroup(1, model='''dv/dt=(gtot-v)/(10*ms) : 1 gtot = gtot1 + gtot2: 1 gtot1 : 1 gtot2 : 1''') S1 = Synapses(neuron_input, neurons, model='''dg/dt=-a1*g+b1*x*(1-g) : 1 gtot1_post = g : 1 (summed) dx/dt=-c1*x : 1 w : 1 # synaptic weight ''', on_pre='x+=w') S2 = Synapses(neuron_input, neurons, model='''dg/dt=-a2*g+b2*x*(1-g) : 1 gtot2_post = g : 1 (summed) dx/dt=-c2*x : 1 w : 1 # synaptic weight ''', on_pre='x+=w') Creating multi-synapses ----------------------- It is also possible to create several synapses for a given pair of neurons:: S.connect(i=numpy.arange(10), j=1, n=3) This is useful for example if one wants to have multiple synapses with different delays. To distinguish multiple variables connecting the same pair of neurons in synaptic expressions and statements, you can create a variable storing the synapse index with the ``multisynaptic_index`` keyword:: syn = Synapses(source_group, target_group, model='w : 1', on_pre='v += w', multisynaptic_index='synapse_number') syn.connect(i=numpy.arange(10), j=1, n=3) syn.delay = '1*ms + synapse_number*2*ms' This index can then be used to set/get synapse-specific values:: S.delay = '(synapse_number + 1)*ms)' # Set delays between 1 and 10ms S.w['synapse_number<5'] = 0.5 S.w['synapse_number>=5'] = 1 It also enables three-dimensional indexing, the following statement has the same effect as the last one above:: S.w[:, :, 5:] = 1 Multiple pathways ----------------- It is possible to have multiple pathways with different update codes from the same presynaptic neuron group. This may be interesting in cases when different operations must be applied at different times for the same presynaptic spike, e.g. for a STDP rule that shifted in time. To do this, specify a dictionary of pathway names and codes:: on_pre={'pre_transmission': 'ge+=w', 'pre_plasticity': '''w=clip(w+Apost,0,inf) Apre+=dApre'''} This creates two pathways with the given names (in fact, specifying ``on_pre=code`` is just a shorter syntax for ``on_pre={'pre': code}``) through which the delay variables can be accessed. The following statement, for example, sets the delay of the synapse between the first neurons of the source and target groups in the ``pre_plasticity`` pathway:: S.pre_plasticity.delay[0,0] = 3*ms As mentioned above, ``pre`` pathways are generally executed before ``post`` pathways. The order of execution of several ``pre`` (or ``post``) pathways with the same delay is however arbitrary, and simply based on the alphabetical ordering of their names (i.e. ``pre_plasticity`` will be executed before ``pre_transmission``). To explicitly specify the order, set the ``order`` attribute of the pathway, e.g.:: S.pre_transmission.order = -2 will make sure that the ``pre_transmission`` code is executed before the ``pre_plasticity`` code in each time step. Multiple pathways can also be useful for abstract models of synaptic currents, e.g. modelling them as rectangular currents:: synapses = Synapses(..., on_pre={'up': 'I_syn_post += 1*nA', 'down': 'I_syn_post -= 1*nA'}, delay={'up': 0*ms, 'down': 5*ms} # 5ms-wide rectangular current ) Numerical integration --------------------- Differential equation flags ~~~~~~~~~~~~~~~~~~~~~~~~~~~ For the integration of differential equations, one can use the same keywords as for `NeuronGroup`. .. note:: Declaring a subexpression as ``(constant over dt)`` means that it will be evaluated each timestep for all synapses, potentially a very costly operation. Explicit event-driven updates ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ As mentioned above, it is possible to write event-driven update code for the synaptic variables. This can also be done manually, by defining the variable ``lastupdate`` and referring to the predefined variable ``t`` (current time). Here's an example for short-term plasticity:: S=Synapses(neuron_input,neuron, model='''x : 1 u : 1 w : 1 lastupdate : second''', on_pre='''u=U+(u-U)*exp(-(t-lastupdate)/tauf) x=1+(x-1)*exp(-(t-lastupdate)/taud) i+=w*u*x x*=(1-u) u+=U*(1-u) lastupdate = t''') By default, the ``pre`` pathway is executed before the ``post`` pathway (both are executed in the ``'synapses'`` scheduling slot, but the ``pre`` pathway has the ``order`` attribute -1, wheras the ``post`` pathway has ``order`` 1. See :ref:`scheduling` for more details). Note that using the automatic ``event-driven`` approach from above is usually preferable, see :doc:`../examples/frompapers.Stimberg_et_al_2018.example_1_COBA` for an ``event-driven`` implementation of short-term plasticity. Technical notes --------------- How connection arguments are interpreted ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If conditions for connecting neurons are combined with both the ``n`` (number of synapses to create) and the ``p`` (probability of a synapse) keywords, they are interpreted in the following way: | For every pair i, j: | if condition(i, j) is fulfilled: | Evaluate p(i, j) | If uniform random number between 0 and 1 < p(i, j): | Create n(i, j) synapses for (i, j) With the generator syntax ``j='EXPR for VAR in RANGE if COND'`` (where the ``RANGE`` can be a full range or a random sample as described above), the interpretation is: | For every i: | for every VAR in RANGE: | j = EXPR | if COND: | Create n(i, j) synapses for (i, j) Note that the arguments in ``RANGE`` can only depend on ``i`` and the values of presynaptic variables. Similarly, the expression for ``j``, ``EXPR`` can depend on ``i``, presynaptic variables, and on the iteration variable ``VAR``. The condition ``COND`` can depend on anything (presynaptic and postsynaptic variables). The generator syntax expressing ``i`` as a function of ``j`` is interpreted in the same way: | For every j: | for every VAR in RANGE: | i = EXPR | if COND: | Create n(i, j) synapses for (i, j) Here, ``RANGE`` can only depend on ``j`` and postsynaptic variables, and ``EXPR`` can only depend on ``j``, postsynaptic variables, and on the iteration variable ``VAR``. With the 1-to-1 mapping syntax ``j='EXPR'`` the interpretation is: | For every i: | j = EXPR | Create n(i, j) synapses for (i, j) And finally, ``i='EXPR'`` is interpreted as: | For every j: | i = EXPR | Create n(i, j) synapses for (i, j) Efficiency considerations ~~~~~~~~~~~~~~~~~~~~~~~~~ If you are connecting a single pair of neurons, the direct form ``connect(i=5, j=10)`` is the most efficient. However, if you are connecting a number of neurons, it will usually be more efficient to construct an array of ``i`` and ``j`` values and have a single ``connect(i=i, j=j)`` call. For large connections, you should use one of the string based syntaxes where possible as this will generate compiled low-level code that will be typically much faster than equivalent Python code. If you are expecting a majority of pairs of neurons to be connected, then using the condition-based syntax is optimal, e.g. ``connect(condition='i!=j')``. However, if relatively few neurons are being connected then the 1-to-1 mapping or generator syntax will be better. For 1-to-1, ``connect(j='i')`` will always be faster than ``connect(condition='i==j')`` because the latter has to evaluate all ``N**2`` pairs ``(i, j)`` and check if the condition is true, whereas the former only has to do O(N) operations. One tricky problem is how to efficiently generate connectivity with a probability ``p(i, j)`` that depends on both i and j, since this requires ``N*N`` computations even if the expected number of synapses is proportional to N. Some tricks for getting around this are shown in :doc:`../examples/synapses.efficient_gaussian_connectivity`. brian2-2.5.4/docs_sphinx/user/units.rst000066400000000000000000000217461445201106100200760ustar00rootroot00000000000000Physical units ============== .. contents:: :local: :depth: 1 Brian includes a system for physical units. The base units are defined by their standard SI unit names: ``amp``/``ampere``, ``kilogram``/``kilogramme``, ``second``, ``metre``/``meter``, ``mole``/``mol``, ``kelvin``, and ``candela``. In addition to these base units, Brian defines a set of derived units: ``coulomb``, ``farad``, ``gram``/``gramme``, ``hertz``, ``joule``, ``liter``/ ``litre``, ``molar``, ``pascal``, ``ohm``, ``siemens``, ``volt``, ``watt``, together with prefixed versions (e.g. ``msiemens = 0.001*siemens``) using the prefixes ``p, n, u, m, k, M, G, T`` (two exceptions to this rule: ``kilogram`` is not defined with any additional prefixes, and ``metre`` and ``meter`` are additionaly defined with the "centi" prefix, i.e. ``cmetre``/``cmeter``). For convenience, a couple of additional useful standard abbreviations such as ``cm`` (instead of ``cmetre``/``cmeter``), ``nS`` (instead of ``nsiemens``), ``ms`` (instead of ``msecond``), ``Hz`` (instead of ``hertz``), ``mM`` (instead of ``mmolar``) are included. To avoid clashes with common variable names, no one-letter abbreviations are provided (e.g. you can use ``mV`` or ``nS``, but *not* ``V`` or ``S``). Using units ----------- You can generate a physical quantity by multiplying a scalar or vector value with its physical unit:: >>> tau = 20*ms >>> print(tau) 20. ms >>> rates = [10, 20, 30]*Hz >>> print(rates) [ 10. 20. 30.] Hz Brian will check the consistency of operations on units and raise an error for dimensionality mismatches:: >>> tau += 1 # ms? second? # doctest: +ELLIPSIS +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... DimensionMismatchError: Cannot calculate ... += 1, units do not match (units are second and 1). >>> 3*kgram + 3*amp # doctest: +ELLIPSIS +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... DimensionMismatchError: Cannot calculate 3. kg + 3. A, units do not match (units are kilogram and amp). Most Brian functions will also complain about non-specified or incorrect units:: >>> G = NeuronGroup(10, 'dv/dt = -v/tau: volt', dt=0.5) # doctest: +ELLIPSIS +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... DimensionMismatchError: Function "__init__" expected a quantitity with unit second for argument "dt" but got 0.5 (unit is 1). Numpy functions have been overwritten to correctly work with units (see the :doc:`developer documentation <../developer/units>` for more details):: >>> print(mean(rates)) 20. Hz >>> print(rates.repeat(2)) [ 10. 10. 20. 20. 30. 30.] Hz Removing units -------------- There are various options to remove the units from a value (e.g. to use it with analysis functions that do not correctly work with units) * Divide the value by its unit (most of the time the recommended option because it is clear about the scale) * Transform it to a pure numpy array in the base unit by calling `asarray` (no copy) or `array` (copy) * Directly get the unitless value of a state variable by appending an underscore to the name :: >>> tau/ms 20.0 >>> asarray(rates) array([ 10., 20., 30.]) >>> G = NeuronGroup(5, 'dv/dt = -v/tau: volt') >>> print(G.v_[:]) [ 0. 0. 0. 0. 0.] Temperatures ------------ Brian only supports temperatures defined in °K, using the provided ``kelvin`` unit object. Other conventions such as °C, or °F are not compatible with Brian's unit system, because they cannot be expressed as a multiplicative scaling of the SI base unit kelvin (their zero point is different). However, in biological experiments and modeling, temperatures are typically reported in °C. How to use such temperatures depends on whether they are used as *temperature differences* or as *absolute temperatures*: temperature differences Their major use case is the correction of time constants for differences in temperatures based on the `Q10 temperature coefficient `_. In this case, all temperatures can directly use ``kelvin`` even though the temperatures are reported in Celsius, since temperature differences in Celsius and Kelvin are identical. absolute temperatures Equations such as the `Goldman–Hodgkin–Katz voltage equation `_ have a factor that depends on the absolute temperature measured in Kelvin. To get this temperature from a temperature reported in °C, you can use the ``zero_celsius`` constant from the `brian2.units.constants` package (see below):: from brian2.units.constants import zero_celsius celsius_temp = 27 abs_temp = celsius_temp*kelvin + zero_celsius .. note:: Earlier versions of Brian had a ``celsius`` unit which was in fact identical to ``kelvin``. While this gave the correct results for temperature differences, it did not correctly work for absolute temperatures. To avoid confusion and possible misinterpretation, the ``celsius`` unit has therefore been removed. .. _constants: Constants --------- The `brian2.units.constants` package provides a range of physical constants that can be useful for detailed biological models. Brian provides the following constants: ==================== ================== ======================= ================================================================== Constant Symbol(s) Brian name Value ==================== ================== ======================= ================================================================== Avogadro constant :math:`N_A, L` ``avogadro_constant`` :math:`6.022140857\times 10^{23}\,\mathrm{mol}^{-1}` Boltzmann constant :math:`k` ``boltzmann_constant`` :math:`1.38064852\times 10^{-23}\,\mathrm{J}\,\mathrm{K}^{-1}` Electric constant :math:`\epsilon_0` ``electric_constant`` :math:`8.854187817\times 10^{-12}\,\mathrm{F}\,\mathrm{m}^{-1}` Electron mass :math:`m_e` ``electron_mass`` :math:`9.10938356\times 10^{-31}\,\mathrm{kg}` Elementary charge :math:`e` ``elementary_charge`` :math:`1.6021766208\times 10^{-19}\,\mathrm{C}` Faraday constant :math:`F` ``faraday_constant`` :math:`96485.33289\,\mathrm{C}\,\mathrm{mol}^{-1}` Gas constant :math:`R` ``gas_constant`` :math:`8.3144598\,\mathrm{J}\,\mathrm{mol}^{-1}\,\mathrm{K}^{-1}` Magnetic constant :math:`\mu_0` ``magnetic_constant`` :math:`12.566370614\times 10^{-7}\,\mathrm{N}\,\mathrm{A}^{-2}` Molar mass constant :math:`M_u` ``molar_mass_constant`` :math:`1\times 10^{-3}\,\mathrm{kg}\,\mathrm{mol}^{-1}` 0°C ``zero_celsius`` :math:`273.15\,\mathrm{K}` ==================== ================== ======================= ================================================================== Note that these constants are not imported by default, you will have to explicitly import them from `brian2.units.constants`. During the import, you can also give them shorter names using Python's ``from ... import ... as ...`` syntax. For example, to calculate the :math:`\frac{RT}{F}` factor that appears in the `Goldman–Hodgkin–Katz voltage equation `_ you can use:: from brian2 import * from brian2.units.constants import zero_celsius, gas_constant as R, faraday_constant as F celsius_temp = 27 T = celsius_temp*kelvin + zero_celsius factor = R*T/F .. admonition:: The following topics are not essential for beginners. | Importing units --------------- Brian generates standard names for units, combining the unit name (e.g. "siemens") with a prefixes (e.g. "m"), and also generates squared and cubed versions by appending a number. For example, the units "msiemens", "siemens2", "usiemens3" are all predefined. You can import these units from the package ``brian2.units.allunits`` -- accordingly, an ``from brian2.units.allunits import *`` will result in everything from ``Ylumen3`` (cubed yotta lumen) to ``ymol`` (yocto mole) being imported. A better choice is normally to do ``from brian2.units import *`` or import everything ``from brian2 import *`` which only imports the units mentioned in the introductory paragraph (base units, derived units, and some standard abbreviations). In-place operations on quantities --------------------------------- In-place operations on quantity arrays change the underlying array, in the same way as for standard numpy arrays. This means, that any other variables referencing the same object will be affected as well:: >>> q = [1, 2] * mV >>> r = q >>> q += 1*mV >>> q array([ 2., 3.]) * mvolt >>> r array([ 2., 3.]) * mvolt In contrast, scalar quantities will never change the underlying value but instead return a new value (in the same way as standard Python scalars):: >>> x = 1*mV >>> y = x >>> x *= 2 >>> x 2. * mvolt >>> y 1. * mvolt brian2-2.5.4/examples/000077500000000000000000000000001445201106100145075ustar00rootroot00000000000000brian2-2.5.4/examples/.gitignore000066400000000000000000000000621445201106100164750ustar00rootroot00000000000000/STDP_standalone /barrelcortex /brian_preferences brian2-2.5.4/examples/COBAHH.py000077500000000000000000000044371445201106100160200ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: UTF-8 -*- """ This is an implementation of a benchmark described in the following review paper: Simulation of networks of spiking neurons: A review of tools and strategies (2006). Brette, Rudolph, Carnevale, Hines, Beeman, Bower, Diesmann, Goodman, Harris, Zirpe, Natschläger, Pecevski, Ermentrout, Djurfeldt, Lansner, Rochel, Vibert, Alvarez, Muller, Davison, El Boustani and Destexhe. Journal of Computational Neuroscience Benchmark 3: random network of HH neurons with exponential synaptic conductances Clock-driven implementation (no spike time interpolation) R. Brette - Dec 2007 """ from brian2 import * # Parameters area = 20000*umetre**2 Cm = (1*ufarad*cm**-2) * area gl = (5e-5*siemens*cm**-2) * area El = -60*mV EK = -90*mV ENa = 50*mV g_na = (100*msiemens*cm**-2) * area g_kd = (30*msiemens*cm**-2) * area VT = -63*mV # Time constants taue = 5*ms taui = 10*ms # Reversal potentials Ee = 0*mV Ei = -80*mV we = 6*nS # excitatory synaptic weight wi = 67*nS # inhibitory synaptic weight # The model eqs = Equations(''' dv/dt = (gl*(El-v)+ge*(Ee-v)+gi*(Ei-v)- g_na*(m*m*m)*h*(v-ENa)- g_kd*(n*n*n*n)*(v-EK))/Cm : volt dm/dt = alpha_m*(1-m)-beta_m*m : 1 dn/dt = alpha_n*(1-n)-beta_n*n : 1 dh/dt = alpha_h*(1-h)-beta_h*h : 1 dge/dt = -ge*(1./taue) : siemens dgi/dt = -gi*(1./taui) : siemens alpha_m = 0.32*(mV**-1)*4*mV/exprel((13*mV-v+VT)/(4*mV))/ms : Hz beta_m = 0.28*(mV**-1)*5*mV/exprel((v-VT-40*mV)/(5*mV))/ms : Hz alpha_h = 0.128*exp((17*mV-v+VT)/(18*mV))/ms : Hz beta_h = 4./(1+exp((40*mV-v+VT)/(5*mV)))/ms : Hz alpha_n = 0.032*(mV**-1)*5*mV/exprel((15*mV-v+VT)/(5*mV))/ms : Hz beta_n = .5*exp((10*mV-v+VT)/(40*mV))/ms : Hz ''') P = NeuronGroup(4000, model=eqs, threshold='v>-20*mV', refractory=3*ms, method='exponential_euler') Pe = P[:3200] Pi = P[3200:] Ce = Synapses(Pe, P, on_pre='ge+=we') Ci = Synapses(Pi, P, on_pre='gi+=wi') Ce.connect(p=0.02) Ci.connect(p=0.02) # Initialization P.v = 'El + (randn() * 5 - 5)*mV' P.ge = '(randn() * 1.5 + 4) * 10.*nS' P.gi = '(randn() * 12 + 20) * 10.*nS' # Record a few traces trace = StateMonitor(P, 'v', record=[1, 10, 100]) run(1 * second, report='text') plot(trace.t/ms, trace[1].v/mV) plot(trace.t/ms, trace[10].v/mV) plot(trace.t/ms, trace[100].v/mV) xlabel('t (ms)') ylabel('v (mV)') show() brian2-2.5.4/examples/CUBA.py000066400000000000000000000025701445201106100155770ustar00rootroot00000000000000""" This is a Brian script implementing a benchmark described in the following review paper: Simulation of networks of spiking neurons: A review of tools and strategies (2007). Brette, Rudolph, Carnevale, Hines, Beeman, Bower, Diesmann, Goodman, Harris, Zirpe, Natschlager, Pecevski, Ermentrout, Djurfeldt, Lansner, Rochel, Vibert, Alvarez, Muller, Davison, El Boustani and Destexhe. Journal of Computational Neuroscience 23(3):349-98 Benchmark 2: random network of integrate-and-fire neurons with exponential synaptic currents. Clock-driven implementation with exact subthreshold integration (but spike times are aligned to the grid). """ from brian2 import * taum = 20*ms taue = 5*ms taui = 10*ms Vt = -50*mV Vr = -60*mV El = -49*mV eqs = ''' dv/dt = (ge+gi-(v-El))/taum : volt (unless refractory) dge/dt = -ge/taue : volt dgi/dt = -gi/taui : volt ''' P = NeuronGroup(4000, eqs, threshold='v>Vt', reset='v = Vr', refractory=5*ms, method='exact') P.v = 'Vr + rand() * (Vt - Vr)' P.ge = 0*mV P.gi = 0*mV we = (60*0.27/10)*mV # excitatory synaptic weight (voltage) wi = (-20*4.5/10)*mV # inhibitory synaptic weight Ce = Synapses(P, P, on_pre='ge += we') Ci = Synapses(P, P, on_pre='gi += wi') Ce.connect('i<3200', p=0.02) Ci.connect('i>=3200', p=0.02) s_mon = SpikeMonitor(P) run(1 * second) plot(s_mon.t/ms, s_mon.i, ',k') xlabel('Time (ms)') ylabel('Neuron index') show() brian2-2.5.4/examples/IF_curve_Hodgkin_Huxley.py000066400000000000000000000025441445201106100215710ustar00rootroot00000000000000""" Input-Frequency curve of a HH model. Network: 100 unconnected Hodgin-Huxley neurons with an input current I. The input is set differently for each neuron. This simulation should use exponential Euler integration. """ from brian2 import * num_neurons = 100 duration = 2*second # Parameters area = 20000*umetre**2 Cm = 1*ufarad*cm**-2 * area gl = 5e-5*siemens*cm**-2 * area El = -65*mV EK = -90*mV ENa = 50*mV g_na = 100*msiemens*cm**-2 * area g_kd = 30*msiemens*cm**-2 * area VT = -63*mV # The model eqs = Equations(''' dv/dt = (gl*(El-v) - g_na*(m*m*m)*h*(v-ENa) - g_kd*(n*n*n*n)*(v-EK) + I)/Cm : volt dm/dt = 0.32*(mV**-1)*4*mV/exprel((13.*mV-v+VT)/(4*mV))/ms*(1-m)-0.28*(mV**-1)*5*mV/exprel((v-VT-40.*mV)/(5*mV))/ms*m : 1 dn/dt = 0.032*(mV**-1)*5*mV/exprel((15.*mV-v+VT)/(5*mV))/ms*(1.-n)-.5*exp((10.*mV-v+VT)/(40.*mV))/ms*n : 1 dh/dt = 0.128*exp((17.*mV-v+VT)/(18.*mV))/ms*(1.-h)-4./(1+exp((40.*mV-v+VT)/(5.*mV)))/ms*h : 1 I : amp ''') # Threshold and refractoriness are only used for spike counting group = NeuronGroup(num_neurons, eqs, threshold='v > -40*mV', refractory='v > -40*mV', method='exponential_euler') group.v = El group.I = '0.7*nA * i / num_neurons' monitor = SpikeMonitor(group) run(duration) plot(group.I/nA, monitor.count / duration) xlabel('I (nA)') ylabel('Firing rate (sp/s)') show() brian2-2.5.4/examples/IF_curve_LIF.py000066400000000000000000000012171445201106100172560ustar00rootroot00000000000000#!/usr/bin/env python """ Input-Frequency curve of a IF model. Network: 1000 unconnected integrate-and-fire neurons (leaky IF) with an input parameter v0. The input is set differently for each neuron. """ from brian2 import * n = 1000 duration = 1*second tau = 10*ms eqs = ''' dv/dt = (v0 - v) / tau : volt (unless refractory) v0 : volt ''' group = NeuronGroup(n, eqs, threshold='v > 10*mV', reset='v = 0*mV', refractory=5*ms, method='exact') group.v = 0*mV group.v0 = '20*mV * i / (n-1)' monitor = SpikeMonitor(group) run(duration) plot(group.v0/mV, monitor.count / duration) xlabel('v0 (mV)') ylabel('Firing rate (sp/s)') show() brian2-2.5.4/examples/adaptive_threshold.py000066400000000000000000000015631445201106100207370ustar00rootroot00000000000000""" A model with adaptive threshold (increases with each spike) """ from brian2 import * eqs = ''' dv/dt = -v/(10*ms) : volt dvt/dt = (10*mV-vt)/(15*ms) : volt ''' reset = ''' v = 0*mV vt += 3*mV ''' IF = NeuronGroup(1, model=eqs, reset=reset, threshold='v>vt', method='exact') IF.vt = 10*mV PG = PoissonGroup(1, 500 * Hz) C = Synapses(PG, IF, on_pre='v += 3*mV') C.connect() Mv = StateMonitor(IF, 'v', record=True) Mvt = StateMonitor(IF, 'vt', record=True) # Record the value of v when the threshold is crossed M_crossings = SpikeMonitor(IF, variables='v') run(2*second, report='text') subplot(1, 2, 1) plot(Mv.t / ms, Mv[0].v / mV) plot(Mvt.t / ms, Mvt[0].vt / mV) ylabel('v (mV)') xlabel('t (ms)') # zoom in on the first 100ms xlim(0, 100) subplot(1, 2, 2) hist(M_crossings.v / mV, bins=np.arange(10, 20, 0.5)) xlabel('v at threshold crossing (mV)') show() brian2-2.5.4/examples/advanced/000077500000000000000000000000001445201106100162545ustar00rootroot00000000000000brian2-2.5.4/examples/advanced/COBAHH_approximated.py000077500000000000000000000257441445201106100223460ustar00rootroot00000000000000#!/usr/bin/env python3 """Hodgkin-Huxley neuron simulation with approximations for gating variable steady-states and time constants Follows exercise 4, chapter 2 of Eugene M. Izhikevich: Dynamical Systems in Neuroscience Sebastian Schmitt, 2021 """ import argparse from functools import reduce import operator import matplotlib.pyplot as plt from cycler import cycler import numpy as np from brian2 import run from brian2 import mS, cmeter, ms, mV, uA, uF from brian2 import Equations, NeuronGroup, StateMonitor, TimedArray, defaultclock def construct_gating_variable_inf_equation(gating_variable): """Construct the voltage-dependent steady-state gating variable equation. Approximated by Boltzmann function. gating_variable -- gating variable, typically one of "m", "n" and "h" """ return Equations('xinf = 1/(1+exp((v_half-v)/k)) : 1', xinf=f'{gating_variable}_inf', v_half=f'v_{gating_variable}_half', k=f'k_{gating_variable}') def construct_gating_variable_tau_equation(gating_variable): """Construct the voltage-dependent gating variable time constant equation. Approximated by Gaussian function. gating_variable -- gating variable, typically one of "m", "n" and "h" """ return Equations('tau = c_base + c_amp*exp(-(v_max - v)**2/sigma**2) : second', tau=f'tau_{gating_variable}', c_base=f'c_{gating_variable}_base', c_amp=f'c_{gating_variable}_amp', v_max=f'v_{gating_variable}_max', sigma=f'sigma_{gating_variable}') def construct_gating_variable_ode(gating_variable): """Construct the ordinary differential equation of the gating variable. gating_variable -- gating variable, typically one of "m", "n" and "h" """ return Equations('dx/dt = (xinf - x)/tau : 1', x=gating_variable, xinf=f'{gating_variable}_inf', tau=f'tau_{gating_variable}') def construct_neuron_ode(): """Construct the ordinary differential equation of the membrane.""" # conductances g_K_eq = Equations('g_K = g_K_bar*n**4 : siemens/meter**2') g_Na_eq = Equations('g_Na = g_Na_bar*m**3*h : siemens/meter**2') # currents I_K_eq = Equations('I_K = g_K*(v - e_K) : ampere/meter**2') I_Na_eq = Equations('I_Na = g_Na*(v - e_Na) : ampere/meter**2') I_L_eq = Equations('I_L = g_L*(v - e_L) : ampere/meter**2') # external drive I_ext_eq = Equations('I_ext = I_stim(t) : ampere/meter**2') # membrane membrane_eq = Equations('dv/dt = (I_ext - I_K - I_Na - I_L)/C_mem : volt') return [g_K_eq, g_Na_eq, I_K_eq, I_Na_eq, I_L_eq, I_ext_eq, membrane_eq] def plot_tau(ax, parameters): """Plot gating variable time constants as function of membrane potential. ax -- matplotlib axes to be plotted on parameters -- dictionary of parameters for gating variable time constant equations """ tau_group = NeuronGroup(100, Equations('v : volt') + reduce(operator.add, [construct_gating_variable_tau_equation( gv) for gv in ['m', 'n', 'h']]), method='euler', namespace=parameters) min_v = -100 max_v = 100 tau_group.v = np.linspace(min_v, max_v, len(tau_group))*mV ax.plot(tau_group.v/mV, tau_group.tau_m/ms, label=r'$\tau_m$') ax.plot(tau_group.v/mV, tau_group.tau_n/ms, label=r'$\tau_n$') ax.plot(tau_group.v/mV, tau_group.tau_h/ms, label=r'$\tau_h$') ax.set_xlabel('$v$ (mV)') ax.set_ylabel(r'$\tau$ (ms)') ax.yaxis.set_label_position("right") ax.yaxis.tick_right() ax.legend() def plot_inf(ax, parameters): """Plot gating variable steady-state values as function of membrane potential. ax -- matplotlib axes to be plotted on parameters -- dictionary of parameters for gating variable steady-state equations """ inf_group = NeuronGroup(100, Equations('v : volt') + reduce(operator.add, [construct_gating_variable_inf_equation( gv) for gv in ['m', 'n', 'h']]), method='euler', namespace=parameters) inf_group.v = np.linspace(-100, 100, len(inf_group))*mV ax.plot(inf_group.v/mV, inf_group.m_inf, label=r'$m_\infty$') ax.plot(inf_group.v/mV, inf_group.n_inf, label=r'$n_\infty$') ax.plot(inf_group.v/mV, inf_group.h_inf, label=r'$h_\infty$') ax.set_xlabel('$v$ (mV)') ax.set_ylabel('steady-state activation') ax.yaxis.set_label_position("right") ax.yaxis.tick_right() ax.legend() def plot_membrane_voltage(ax, statemon): """Plot simulation result: membrane potential. ax -- matplotlib axes to be plotted on statemon -- StateMonitor (with v recorded) """ ax.plot(statemon.t/ms, statemon.v[0]/mV, label='membrane voltage') ax.set_xlabel('$t$ (ms)') ax.set_ylabel('$v$ (mV)') ax.axhline(0, linestyle='dashed') ax.legend() def plot_gating_variable_activations(ax, statemon): """Plot simulation result: gating variables. ax -- matplotlib axes to be plotted on statemon -- StateMonitor (with m, n and h recorded) """ ax.plot(statemon.t/ms, statemon.m[0], label='$m$') ax.plot(statemon.t/ms, statemon.n[0], label='$n$') ax.plot(statemon.t/ms, statemon.h[0], label='$h$') ax.set_xlabel('$t$ (ms)') ax.set_ylabel('activation') ax.legend() def plot_conductances(ax, statemon): """Plot simulation result: conductances. ax -- matplotlib axes to be plotted on statemon -- StateMonitor (with g_K and g_Na recorded) """ ax.plot(statemon.t/ms, statemon.g_K[0] / (mS/(cmeter**2)), label=r'$g_\mathregular{K}$') ax.plot(statemon.t/ms, statemon.g_Na[0] / (mS/(cmeter**2)), label=r'$g_\mathregular{Na}$') ax.set_xlabel('$t$ (ms)') ax.set_ylabel('$g$ (mS/cm$^2$)') ax.legend() def plot_currents(ax, statemon): """Plot simulation result: currents. ax -- matplotlib axes to be plotted on statemon -- StateMonitor (with I_K, I_Na and I_L recorded) """ ax.plot(statemon.t/ms, statemon.I_K[0] / (uA/(cmeter**2)), label=r'$I_\mathregular{K}$') ax.plot(statemon.t/ms, statemon.I_Na[0] / (uA/(cmeter**2)), label=r'$I_\mathregular{Na}$') ax.plot(statemon.t/ms, (statemon.I_Na[0] + statemon.I_K[0] + statemon.I_L[0]) / (uA/(cmeter**2)), label=r'$I_\mathregular{Na} + I_\mathregular{K} + I_\mathregular{L}$') ax.set_xlabel('$t$ (ms)') ax.set_ylabel(r'I ($\mu$A/cm$^2$)') ax.legend() def plot_current_stimulus(ax, statemon): """Plot simulation result: external current stimulus. ax -- matplotlib axes to be plotted on statemon -- StateMonitor (with I_ext recorded) """ ax.plot(statemon.t/ms, statemon.I_ext[0] / (uA/(cmeter**2)), label=r'$I_\mathregular{ext}$') ax.set_xlabel('$t$ (ms)') ax.set_ylabel(r'I ($\mu$A/cm$^2$)') ax.legend() def plot_gating_variable_time_constants(ax, statemon): """Plot simulation result: gating variable time constants. ax -- matplotlib axes to be plotted on statemon -- StateMonitor (with tau_m, tau_n and tau_h recorded) """ ax.plot(statemon.t/ms, statemon.tau_m[0]/ms, label=r'$\tau_m$') ax.plot(statemon.t/ms, statemon.tau_n[0]/ms, label=r'$\tau_n$') ax.plot(statemon.t/ms, statemon.tau_h[0]/ms, label=r'$\tau_h$') ax.set_xlabel('$t$ (ms)') ax.set_ylabel(r'$\tau$ (ms)') ax.legend() def run_simulation(parameters): """Run the simulation. parameters -- dictionary with parameters """ equations = [] for gating_variable in ["m", "n", "h"]: equations.append( construct_gating_variable_inf_equation(gating_variable)) equations.append( construct_gating_variable_tau_equation(gating_variable)) equations.append(construct_gating_variable_ode(gating_variable)) equations += construct_neuron_ode() eqs_HH = reduce(operator.add, equations) group = NeuronGroup(1, eqs_HH, method='euler', namespace=parameters) group.v = parameters["v_initial"] group.m = parameters["m_initial"] group.n = parameters["n_initial"] group.h = parameters["h_initial"] statemon = StateMonitor(group, ['v', 'I_ext', 'm', 'n', 'h', 'g_K', 'g_Na', 'I_K', 'I_Na', 'I_L', 'tau_m', 'tau_n', 'tau_h'], record=True) defaultclock.dt = parameters["defaultclock_dt"] run(parameters["duration"]) return statemon def main(parameters): """Run simulation and return matplotlib figure. parameters -- dictionary with parameters """ statemon = run_simulation(parameters) fig = plt.figure(figsize=(20, 15), constrained_layout=True) gs = fig.add_gridspec(6, 2) ax0 = fig.add_subplot(gs[0, 0]) ax1 = fig.add_subplot(gs[1, 0]) ax2 = fig.add_subplot(gs[2, 0]) ax3 = fig.add_subplot(gs[3, 0]) ax4 = fig.add_subplot(gs[4, 0]) ax5 = fig.add_subplot(gs[5, 0]) ax6 = fig.add_subplot(gs[:3, 1]) ax7 = fig.add_subplot(gs[3:, 1]) plot_membrane_voltage(ax0, statemon) plot_gating_variable_activations(ax1, statemon) plot_conductances(ax2, statemon) plot_currents(ax3, statemon) plot_current_stimulus(ax4, statemon) plot_gating_variable_time_constants(ax5, statemon) plot_tau(ax6, parameters) plot_inf(ax7, parameters) return fig parameters = { # Boltzmann function parameters 'v_n_half': 12*mV, 'v_m_half': 25*mV, 'v_h_half': 3*mV, 'k_n': 15*mV, 'k_m': 9*mV, 'k_h': -7*mV, # Gaussian function parameters 'v_n_max': -14*mV, 'v_m_max': 27*mV, 'v_h_max': -2*mV, 'sigma_n': 50*mV, 'sigma_m': 30*mV, 'sigma_h': 20*mV, 'c_n_amp': 4.7*ms, 'c_m_amp': 0.46*ms, 'c_h_amp': 7.4*ms, 'c_n_base': 1.1*ms, 'c_m_base': 0.04*ms, 'c_h_base': 1.2*ms, # conductances 'g_K_bar': 36*mS / (cmeter**2), 'g_Na_bar': 120*mS / (cmeter**2), 'g_L': 0.3*mS / (cmeter**2), # reversal potentials 'e_K': -12*mV, 'e_Na': 120*mV, 'e_L': 10.6*mV, # membrane capacitance 'C_mem': 1*uF / cmeter**2, # initial membrane voltage 'v_initial': 0*mV, # initial gating variable activations 'm_initial': 0.05, 'n_initial': 0.32, 'h_initial': 0.60, # external stimulus at 2 ms with 4 uA/cm^2 and at 10 ms with 15 uA/cm^2 # for 0.5 ms each 'I_stim': TimedArray(values=([0]*4+[4]+[0]*15+[15]+[0])*uA/(cmeter**2), dt=0.5*ms), # simulation time step 'defaultclock_dt': 0.01*ms, # simulation duration 'duration': 20*ms } linestyle_cycler = cycler('linestyle',['-','--',':','-.']) plt.rc('axes', prop_cycle=linestyle_cycler) fig = main(parameters) plt.show() brian2-2.5.4/examples/advanced/Ornstein_Uhlenbeck.py000077500000000000000000000024051445201106100224130ustar00rootroot00000000000000#!/usr/bin/env python3 """ Ornstein-Uhlenbeck process Figure 2: Two realizations of the Ornstein-Uhlenbeck process for parameters τ=1.0 and σ=0.1 (black curve), and for τ=0.1 and σ=0.31622 (red curve). In both cases the noise intensity is σ^2*τ=0.01 . The red curve represents a noise that more closely mimics Gaussian white noise. Both realizations begin here at x(0)=1.0 , after which the mean decays exponentially to zero with time constant τ. Andre Longtin (2010) Stochastic dynamical systems. Scholarpedia, 5(4):1619. Sebastian Schmitt, 2022 """ import matplotlib.pyplot as plt import numpy as np from brian2 import run from brian2 import NeuronGroup, StateMonitor from brian2 import second, ms N = NeuronGroup( 2, """ tau : second sigma : 1 dy/dt = -y/tau + sqrt(2*sigma**2/tau)*xi : 1 """, method="euler" ) N.tau = np.array([1, 0.1]) * second N.sigma = np.array([0.1, 0.31622]) N.y = 1 M = StateMonitor(N, "y", record=True) run(10 * second) plt.plot(M.t / second, M.y[1], color="red", label=r"$\tau$=0.1 s, $\sigma$=0.31622") plt.plot(M.t / second, M.y[0], color="k", label=r"$\tau$=1 s, $\sigma$=0.1") plt.xlim(0, 10) plt.ylim(-1.1, 1.1) plt.xlabel("time (sec)") plt.ylabel("Ornstein-Uhlenbeck process") plt.legend() plt.show() brian2-2.5.4/examples/advanced/compare_GSL_to_conventional.py000066400000000000000000000110411445201106100242370ustar00rootroot00000000000000""" Example using GSL ODE solvers with a variable time step and comparing it to the Brian solver. For highly accurate simulations, i.e. simulations with a very low desired error, the GSL simulation with a variable time step can be faster because it uses a low time step only when it is necessary. In biologically detailed models (e.g. of the Hodgkin-Huxley type), the relevant time constants are very short around an action potential, but much longer when the neuron is near its resting potential. The following example uses a very simple neuron model (leaky integrate-and-fire), but simulates a change in relevant time constants by changing the actual time constant every 10ms, independently for each of 100 neurons. To accurately simulate this model with a fixed time step, the time step has to be very small, wasting many unnecessary steps for all the neurons where the time constant is long. Note that using the GSL ODE solver is much slower, if both methods use a comparable number of steps, i.e. if the desired accuracy is low enough so that a single step per "Brian time step" is enough. """ from brian2 import * import time # Run settings start_dt = .1 * ms method = 'rk2' error = 1.e-6 # requested accuracy def runner(method, dt, options=None): seed(0) I = 5 group = NeuronGroup(100, '''dv/dt = (-v + I)/tau : 1 tau : second''', method=method, method_options=options, dt=dt) group.run_regularly('''v = rand() tau = 0.1*ms + rand()*9.9*ms''', dt=10*ms) rec_vars = ['v', 'tau'] if 'gsl' in method: rec_vars += ['_step_count'] net = Network(group) net.run(0 * ms) mon = StateMonitor(group, rec_vars, record=True, dt=start_dt) net.add(mon) start = time.time() net.run(1 * second) mon.add_attribute('run_time') mon.run_time = time.time() - start return mon lin = runner('linear', start_dt) method_options = {'save_step_count': True, 'absolute_error': error, 'max_steps': 10000} gsl = runner('gsl_%s' % method, start_dt, options=method_options) print("Running with GSL integrator and variable time step:") print('Run time: %.3fs' % gsl.run_time) # check gsl error assert np.max(np.abs( lin.v - gsl.v)) < error, "Maximum error gsl integration too large: %f" % np.max( np.abs(lin.v - gsl.v)) print("average step count: %.1f" % np.mean(gsl._step_count)) print("average absolute error: %g" % np.mean(np.abs(gsl.v - lin.v))) print("\nRunning with exact integration and fixed time step:") dt = start_dt count = 0 dts = [] avg_errors = [] max_errors = [] runtimes = [] while True: print('Using dt: %s' % str(dt)) brian = runner(method, dt) print('\tRun time: %.3fs' % brian.run_time) avg_errors.append(np.mean(np.abs(brian.v - lin.v))) max_errors.append(np.max(np.abs(brian.v - lin.v))) dts.append(dt) runtimes.append(brian.run_time) if np.max(np.abs(brian.v - lin.v)) > error: print('\tError too high (%g), decreasing dt' % np.max( np.abs(brian.v - lin.v))) dt *= .5 count += 1 else: break print("Desired error level achieved:") print("average step count: %.2fs" % (start_dt / dt)) print("average absolute error: %g" % np.mean(np.abs(brian.v - lin.v))) print('Run time: %.3fs' % brian.run_time) if brian.run_time > gsl.run_time: print("This is %.1f times slower than the simulation with GSL's variable " "time step method." % (brian.run_time / gsl.run_time)) else: print("This is %.1f times faster than the simulation with GSL's variable " "time step method." % (gsl.run_time / brian.run_time)) fig, (ax1, ax2) = plt.subplots(1, 2) ax2.axvline(1e-6, color='gray') for label, gsl_error, std_errors, ax in [('average absolute error', np.mean(np.abs(gsl.v - lin.v)), avg_errors, ax1), ('maximum absolute error', np.max(np.abs(gsl.v - lin.v)), max_errors, ax2)]: ax.set(xscale='log', yscale='log') ax.plot([], [], 'o', color='C0', label='fixed time step') # for the legend entry for (error, runtime, dt) in zip(std_errors, runtimes, dts): ax.plot(error, runtime, 'o', color='C0') ax.annotate('%s' % str(dt), xy=(error, runtime), xytext=(2.5, 5), textcoords='offset points', color='C0') ax.plot(gsl_error, gsl.run_time, 'o', color='C1', label='variable time step (GSL)') ax.set(xlabel=label, xlim=(10**-10, 10**1)) ax1.set_ylabel('runtime (s)') ax2.legend(loc='lower left') plt.show() brian2-2.5.4/examples/advanced/custom_events.py000066400000000000000000000047631445201106100215360ustar00rootroot00000000000000""" Example demonstrating the use of custom events. Here we have three neurons, the first is Poisson spiking and connects to neuron G, which in turn connects to neuron H. Neuron G has two variables v and g, and the incoming Poisson spikes cause an instantaneous increase in variable g. g decays rapidly, and in turn causes a slow increase in v. If v crosses a threshold, it causes a standard spike and reset. If g crosses a threshold, it causes a custom event ``gspike``, and if it returns below that threshold it causes a custom event ``end_gspike``. The standard spike event when v crosses a threshold causes an instantaneous increase in variable x in neuron H (which happens through the standard ``pre`` pathway in the synapses), and the gspike event causes an increase in variable y (which happens through the custom pathway ``gpath``). """ from brian2 import * # Input Poisson spikes inp = PoissonGroup(1, rates=250*Hz) # First group G eqs_G = ''' dv/dt = (g-v)/(50*ms) : 1 dg/dt = -g/(10*ms) : 1 allow_gspike : boolean ''' G = NeuronGroup(1, eqs_G, threshold='v>1', reset='v = 0; g = 0; allow_gspike = True;', events={'gspike': 'g>1 and allow_gspike', 'end_gspike': 'g<1 and not allow_gspike'}) G.run_on_event('gspike', 'allow_gspike = False') G.run_on_event('end_gspike', 'allow_gspike = True') # Second group H eqs_H = ''' dx/dt = -x/(10*ms) : 1 dy/dt = -y/(10*ms) : 1 ''' H = NeuronGroup(1, eqs_H) # Synapses from input Poisson group to G Sin = Synapses(inp, G, on_pre='g += 0.5') Sin.connect() # Synapses from G to H S = Synapses(G, H, on_pre={'pre': 'x += 1', 'gpath': 'y += 1'}, on_event={'pre': 'spike', 'gpath': 'gspike'}) S.connect() # Monitors Mstate = StateMonitor(G, ('v', 'g'), record=True) Mgspike = EventMonitor(G, 'gspike', 'g') Mspike = SpikeMonitor(G, 'v') MHstate = StateMonitor(H, ('x', 'y'), record=True) # Initialise and run G.allow_gspike = True run(500*ms) # Plot figure(figsize=(10, 4)) subplot(121) plot(Mstate.t/ms, Mstate.g[0], '-g', label='g') plot(Mstate.t/ms, Mstate.v[0], '-b', lw=2, label='V') plot(Mspike.t/ms, Mspike.v, 'ob', label='_nolegend_') plot(Mgspike.t/ms, Mgspike.g, 'og', label='_nolegend_') xlabel('Time (ms)') title('Presynaptic group G') legend(loc='best') subplot(122) plot(MHstate.t/ms, MHstate.y[0], '-r', label='y') plot(MHstate.t/ms, MHstate.x[0], '-k', lw=2, label='x') xlabel('Time (ms)') title('Postsynaptic group H') legend(loc='best') tight_layout() show()brian2-2.5.4/examples/advanced/exprel_function.py000066400000000000000000000031371445201106100220360ustar00rootroot00000000000000# coding=utf-8 """ Show the improved numerical accuracy when using the `exprel` function in rate equations. Rate equations for channel opening/closing rates often include a term of the form :math:`\frac{x}{\exp(x) - 1}`. This term is problematic for two reasons: * It is not defined for :math:`x = 0` (where it should equal to :math:`1` for continuity); * For values :math:`x \approx 0`, there is a loss of accuracy. For better accuracy, and to avoid issues at :math:`x = 0`, Brian provides the function `exprel`, which is equivalent to :math:`\frac{\exp(x) - 1}{x}`, but with better accuracy and the expected result at :math:`x = 0`. In this example, we demonstrate the advantage of expressing a typical rate equation from the HH model with `exprel`. """ from brian2 import * # Dummy group to evaluate the rate equation at various points eqs = '''v : volt # opening rate from the HH model alpha_simple = 0.32*(mV**-1)*(-50*mV-v)/ (exp((-50*mV-v)/(4*mV))-1.)/ms : Hz alpha_improved = 0.32*(mV**-1)*4*mV/exprel((-50*mV-v)/(4*mV))/ms : Hz''' neuron = NeuronGroup(1000, eqs) # Use voltage values around the problematic point neuron.v = np.linspace(-50 - .5e-6, -50 + .5e-6, len(neuron))*mV fig, ax = plt.subplots() ax.plot((neuron.v + 50*mV)/nvolt, neuron.alpha_simple, '.', label=r'$\alpha_\mathrm{simple}$') ax.plot((neuron.v + 50*mV)/nvolt, neuron.alpha_improved, 'k', label=r'$\alpha_\mathrm{improved}$') ax.legend() ax.set(xlabel='$v$ relative to -50mV (nV)', ylabel=r'$\alpha$ (Hz)') ax.ticklabel_format(useOffset=False) plt.tight_layout() plt.show() brian2-2.5.4/examples/advanced/float_32_64_benchmark.py000066400000000000000000000117611445201106100225700ustar00rootroot00000000000000""" Benchmark showing the performance of float32 versus float64. """ from brian2 import * from brian2.devices.device import reset_device, reinit_devices # CUBA benchmark def run_benchmark(name): if name=='CUBA': taum = 20*ms taue = 5*ms taui = 10*ms Vt = -50*mV Vr = -60*mV El = -49*mV eqs = ''' dv/dt = (ge+gi-(v-El))/taum : volt (unless refractory) dge/dt = -ge/taue : volt dgi/dt = -gi/taui : volt ''' P = NeuronGroup(4000, eqs, threshold='v>Vt', reset='v = Vr', refractory=5*ms, method='exact') P.v = 'Vr + rand() * (Vt - Vr)' P.ge = 0*mV P.gi = 0*mV we = (60*0.27/10)*mV # excitatory synaptic weight (voltage) wi = (-20*4.5/10)*mV # inhibitory synaptic weight Ce = Synapses(P, P, on_pre='ge += we') Ci = Synapses(P, P, on_pre='gi += wi') Ce.connect('i<3200', p=0.02) Ci.connect('i>=3200', p=0.02) elif name=='COBA': # Parameters area = 20000 * umetre ** 2 Cm = (1 * ufarad * cm ** -2) * area gl = (5e-5 * siemens * cm ** -2) * area El = -60 * mV EK = -90 * mV ENa = 50 * mV g_na = (100 * msiemens * cm ** -2) * area g_kd = (30 * msiemens * cm ** -2) * area VT = -63 * mV # Time constants taue = 5 * ms taui = 10 * ms # Reversal potentials Ee = 0 * mV Ei = -80 * mV we = 6 * nS # excitatory synaptic weight wi = 67 * nS # inhibitory synaptic weight # The model eqs = Equations(''' dv/dt = (gl*(El-v)+ge*(Ee-v)+gi*(Ei-v)- g_na*(m*m*m)*h*(v-ENa)- g_kd*(n*n*n*n)*(v-EK))/Cm : volt dm/dt = alpha_m*(1-m)-beta_m*m : 1 dn/dt = alpha_n*(1-n)-beta_n*n : 1 dh/dt = alpha_h*(1-h)-beta_h*h : 1 dge/dt = -ge*(1./taue) : siemens dgi/dt = -gi*(1./taui) : siemens alpha_m = 0.32*(mV**-1)*4*mV/exprel((13*mV-v+VT)/(4*mV))/ms : Hz beta_m = 0.28*(mV**-1)*5*mV/exprel((v-VT-40*mV)/(5*mV))/ms : Hz alpha_h = 0.128*exp((17*mV-v+VT)/(18*mV))/ms : Hz beta_h = 4./(1+exp((40*mV-v+VT)/(5*mV)))/ms : Hz alpha_n = 0.032*(mV**-1)*5*mV/exprel((15*mV-v+VT)/(5*mV))/ms : Hz beta_n = .5*exp((10*mV-v+VT)/(40*mV))/ms : Hz ''') P = NeuronGroup(4000, model=eqs, threshold='v>-20*mV', refractory=3 * ms, method='exponential_euler') Pe = P[:3200] Pi = P[3200:] Ce = Synapses(Pe, P, on_pre='ge+=we') Ci = Synapses(Pi, P, on_pre='gi+=wi') Ce.connect(p=0.02) Ci.connect(p=0.02) # Initialization P.v = 'El + (randn() * 5 - 5)*mV' P.ge = '(randn() * 1.5 + 4) * 10.*nS' P.gi = '(randn() * 12 + 20) * 10.*nS' run(1 * second, profile=True) return sum(t for name, t in magic_network.profiling_info) def generate_results(num_repeats): results = {} for name in ['CUBA', 'COBA']: for target in ['numpy', 'cython']: for dtype in [float32, float64]: prefs.codegen.target = target prefs.core.default_float_dtype = dtype times = [run_benchmark(name) for repeat in range(num_repeats)] results[name, target, dtype.__name__] = amin(times) for name in ['CUBA', 'COBA']: for dtype in [float32, float64]: times = [] for _ in range(num_repeats): reset_device() reinit_devices() set_device('cpp_standalone', directory=None, with_output=False) prefs.core.default_float_dtype = dtype times.append(run_benchmark(name)) results[name, 'cpp_standalone', dtype.__name__] = amin(times) return results results = generate_results(3) bar_width = 0.9 names = ['CUBA', 'COBA'] targets = ['numpy', 'cython', 'cpp_standalone'] precisions = ['float32', 'float64'] figure(figsize=(8, 8)) for j, name in enumerate(names): subplot(2, 2, 1+2*j) title(name) index = arange(len(targets)) for i, precision in enumerate(precisions): bar(index+i*bar_width/len(precisions), [results[name, target, precision] for target in targets], bar_width/len(precisions), label=precision, align='edge') ylabel('Time (s)') if j: xticks(index+0.5*bar_width, targets, rotation=45) else: xticks(index+0.5*bar_width, ('',)*len(targets)) legend(loc='best') subplot(2, 2, 2+2*j) index = arange(len(precisions)) for i, target in enumerate(targets): bar(index+i*bar_width/len(targets), [results[name, target, precision] for precision in precisions], bar_width/len(targets), label=target, align='edge') ylabel('Time (s)') if j: xticks(index+0.5*bar_width, precisions, rotation=45) else: xticks(index+0.5*bar_width, ('',)*len(precisions)) legend(loc='best') tight_layout() show() brian2-2.5.4/examples/advanced/modelfitting_sbi.py000066400000000000000000000150401445201106100221500ustar00rootroot00000000000000""" Model fitting with simulation-based inference --------------------------------------------- In this example, a HH-type model is used to demonstrate simulation-based inference with the sbi toolbox (https://www.mackelab.org/sbi/). It is based on a fake current-clamp recording generated from the same model that we use in the inference process. Two of the parameters (the maximum sodium and potassium conductances) are considered parameters of the model. For more details about this approach, see the references below. To run this example, you need to install the sbi package, e.g. with:: pip install sbi References: * https://www.mackelab.org/sbi * Tejero-Cantero et al., (2020). sbi: A toolkit for simulation-based inference. Journal of Open Source Software, 5(52), 2505, https://doi.org/10.21105/joss.02505 """ import matplotlib.pyplot as plt from brian2 import * import sbi.utils import sbi.analysis import sbi.inference import torch # PyTorch defaultclock.dt = 0.05*ms def simulate(params, I=1*nA, t_on=50*ms, t_total=350*ms): """ Simulates the HH-model with Brian2 for parameter sets in params and the given input current (injection of I between t_on and t_total-t_on). Returns a dictionary {'t': time steps, 'v': voltage, 'I_inj': current, 'spike_count': spike count}. """ assert t_total > 2*t_on t_off = t_total - t_on params = np.atleast_2d(params) # fixed parameters gleak = 10*nS Eleak = -70*mV VT = -60.0*mV C = 200*pF ENa = 53*mV EK = -107*mV # The conductance-based model eqs = ''' dVm/dt = -(gNa*m**3*h*(Vm - ENa) + gK*n**4*(Vm - EK) + gleak*(Vm - Eleak) - I_inj) / C : volt I_inj = int(t >= t_on and t < t_off)*I : amp (shared) dm/dt = alpham*(1-m) - betam*m : 1 dn/dt = alphan*(1-n) - betan*n : 1 dh/dt = alphah*(1-h) - betah*h : 1 alpham = (-0.32/mV) * (Vm - VT - 13.*mV) / (exp((-(Vm - VT - 13.*mV))/(4.*mV)) - 1)/ms : Hz betam = (0.28/mV) * (Vm - VT - 40.*mV) / (exp((Vm - VT - 40.*mV)/(5.*mV)) - 1)/ms : Hz alphah = 0.128 * exp(-(Vm - VT - 17.*mV) / (18.*mV))/ms : Hz betah = 4/(1 + exp((-(Vm - VT - 40.*mV)) / (5.*mV)))/ms : Hz alphan = (-0.032/mV) * (Vm - VT - 15.*mV) / (exp((-(Vm - VT - 15.*mV)) / (5.*mV)) - 1)/ms : Hz betan = 0.5*exp(-(Vm - VT - 10.*mV) / (40.*mV))/ms : Hz # The parameters to fit gNa : siemens (constant) gK : siemens (constant) ''' neurons = NeuronGroup(params.shape[0], eqs, threshold='m>0.5', refractory='m>0.5', method='exponential_euler', name='neurons') Vm_mon = StateMonitor(neurons, 'Vm', record=True, name='Vm_mon') spike_mon = SpikeMonitor(neurons, record=False, name='spike_mon') #record=False → do not record times neurons.gNa_ = params[:, 0]*uS neurons.gK = params[:, 1]*uS neurons.Vm = 'Eleak' neurons.m = '1/(1 + betam/alpham)' # Would be the solution when dm/dt = 0 neurons.h = '1/(1 + betah/alphah)' # Would be the solution when dh/dt = 0 neurons.n = '1/(1 + betan/alphan)' # Would be the solution when dn/dt = 0 run(t_total) # For convenient plotting, reconstruct the current I_inj = ((Vm_mon.t >= t_on) & (Vm_mon.t < t_off))*I return dict(v=Vm_mon.Vm, t=Vm_mon.t, I_inj=I_inj, spike_count=spike_mon.count) def calculate_summary_statistics(x): """Calculate summary statistics for results in x""" I_inj = x["I_inj"] v = x["v"]/mV spike_count = x["spike_count"] # Mean and standard deviation during stimulation v_active = v[:, I_inj > 0*nA] mean_active = np.mean(v_active, axis=1) std_active = np.std(v_active, axis=1) # Height of action potential peaks max_v = np.max(v_active, axis=1) # concatenation of summary statistics sum_stats = np.vstack((spike_count, mean_active, std_active, max_v)) return sum_stats.T def simulation_wrapper(params): """ Returns summary statistics from conductance values in `params`. Summarizes the output of the simulation and converts it to `torch.Tensor`. """ obs = simulate(params) summstats = torch.as_tensor(calculate_summary_statistics(obs)) return summstats.to(torch.float32) if __name__ == '__main__': # Define prior distribution over parameters prior_min = [.5, 1e-4] # (gNa, gK) in µS prior_max = [80.,15.] prior = sbi.utils.torchutils.BoxUniform(low=torch.as_tensor(prior_min), high=torch.as_tensor(prior_max)) # Simulate samples from the prior distribution theta = prior.sample((10_000,)) print('Simulating samples from prior simulation... ', end='') stats = simulation_wrapper(theta.numpy()) print('done.') # Train inference network density_estimator_build_fun = sbi.utils.posterior_nn(model='mdn') inference = sbi.inference.SNPE(prior, density_estimator=density_estimator_build_fun) print('Training inference network... ') inference.append_simulations(theta, stats).train() posterior = inference.build_posterior() # true parameters for real ground truth data true_params = np.array([[32., 1.]]) true_data = simulate(true_params) t = true_data['t'] I_inj = true_data['I_inj'] v = true_data['v'] xo = calculate_summary_statistics(true_data) print("The true summary statistics are: ", xo) # Plot estimated posterior distribution samples = posterior.sample((1000,), x=xo, show_progress_bars=False) labels_params = [r'$\overline{g}_{Na}$', r'$\overline{g}_{K}$'] sbi.analysis.pairplot(samples, limits=[[.5, 80], [1e-4, 15.]], ticks=[[.5, 80], [1e-4, 15.]], figsize=(4, 4), points=true_params, labels=labels_params, points_offdiag={'markersize': 6}, points_colors=['r']) plt.tight_layout() # Draw a single sample from the posterior and convert to numpy for plotting. posterior_sample = posterior.sample((1,), x=xo, show_progress_bars=False).numpy() x = simulate(posterior_sample) # plot observation and sample fig, ax = plt.subplots(figsize=(8, 4)) ax.plot(t/ms, v[0, :]/mV, lw=2, label='observation') ax.plot(t/ms, x['v'][0, :]/mV, '--', lw=2, label='posterior sample') ax.legend() ax.set(xlabel='time (ms)', ylabel='voltage (mV)') plt.show() brian2-2.5.4/examples/advanced/opencv_movie.py000066400000000000000000000115111445201106100213160ustar00rootroot00000000000000""" An example that uses a function from external C library (OpenCV in this case). Works for all C-based code generation targets (i.e. for cython and cpp_standalone device) and for numpy (using the Python bindings). This example needs a working installation of OpenCV 3.x and its Python bindings. It has been tested on 64 bit Linux in a conda environment with packages from the ``conda-forge`` channels (opencv 3.4.4, x264 1!152.20180717, ffmpeg 4.1). """ import os import urllib.request, urllib.error, urllib.parse import cv2 # Import OpenCV2 from brian2 import * defaultclock.dt = 1*ms prefs.codegen.target = 'cython' prefs.logging.std_redirection = False set_device('cpp_standalone', clean=True) filename = os.path.abspath('Megamind.avi') if not os.path.exists(filename): print('Downloading the example video file') response = urllib.request.urlopen('http://docs.opencv.org/2.4/_downloads/Megamind.avi') data = response.read() with open(filename, 'wb') as f: f.write(data) video = cv2.VideoCapture(filename) width, height, frame_count = (int(video.get(cv2.CAP_PROP_FRAME_WIDTH)), int(video.get(cv2.CAP_PROP_FRAME_HEIGHT)), int(video.get(cv2.CAP_PROP_FRAME_COUNT))) fps = 24 time_between_frames = 1*second/fps @implementation('cpp', ''' double* get_frame(bool new_frame) { // The following initializations will only be executed once static cv::VideoCapture source("VIDEO_FILENAME"); static cv::Mat frame; static double* grayscale_frame = (double*)malloc(VIDEO_WIDTH*VIDEO_HEIGHT*sizeof(double)); if (new_frame) { source >> frame; double mean_value = 0; for (int row=0; row(row, col)[0] + frame.at(row, col)[1] + frame.at(row, col)[2])/(3.0*128); mean_value += grayscale_value / (VIDEO_WIDTH * VIDEO_HEIGHT); grayscale_frame[row*VIDEO_WIDTH + col] = grayscale_value; } // subtract the mean for (int i=0; i', ''], define_macros=[('VIDEO_WIDTH', width), ('VIDEO_HEIGHT', height)]) @check_units(x=1, y=1, result=1) def video_input(x, y): # we assume this will only be called in the custom operation (and not for # example in a reset or synaptic statement), so we don't need to do indexing # but we can directly return the full result _, frame = video.read() grayscale = frame.mean(axis=2) grayscale /= 128. # scale everything between 0 and 2 return grayscale.ravel() - grayscale.ravel().mean() N = width * height tau, tau_th = 10*ms, time_between_frames G = NeuronGroup(N, '''dv/dt = (-v + I)/tau : 1 dv_th/dt = -v_th/tau_th : 1 row : integer (constant) column : integer (constant) I : 1 # input current''', threshold='v>v_th', reset='v=0; v_th = 3*v_th + 1.0', method='exact') G.v_th = 1 G.row = 'i//width' G.column = 'i%width' G.run_regularly('I = video_input(column, row)', dt=time_between_frames) mon = SpikeMonitor(G) runtime = frame_count*time_between_frames run(runtime, report='text') # Avoid going through the whole Brian2 indexing machinery too much i, t, row, column = mon.i[:], mon.t[:], G.row[:], G.column[:] import matplotlib.animation as animation # TODO: Use overlapping windows stepsize = 100*ms def next_spikes(): step = next_spikes.step if step*stepsize > runtime: next_spikes.step=0 raise StopIteration() spikes = i[(t>=step*stepsize) & (t<(step+1)*stepsize)] next_spikes.step += 1 yield column[spikes], row[spikes] next_spikes.step = 0 fig, ax = plt.subplots() dots, = ax.plot([], [], 'k.', markersize=2, alpha=.25) ax.set_xlim(0, width) ax.set_ylim(0, height) ax.invert_yaxis() def run(data): x, y = data dots.set_data(x, y) ani = animation.FuncAnimation(fig, run, next_spikes, blit=False, repeat=True, repeat_delay=1000) plt.show() brian2-2.5.4/examples/advanced/stochastic_odes.py000066400000000000000000000056631445201106100220160ustar00rootroot00000000000000""" Demonstrate the correctness of the "derivative-free Milstein method" for multiplicative noise. """ from brian2 import * # We only get exactly the same random numbers for the exact solution and the # simulation if we use the numpy code generation target prefs.codegen.target = 'numpy' # setting a random seed makes all variants use exactly the same Wiener process seed = 12347 X0 = 1 mu = 0.5/second # drift sigma = 0.1/second #diffusion runtime = 1*second def simulate(method, dt): """ simulate geometrical Brownian with the given method """ np.random.seed(seed) G = NeuronGroup(1, 'dX/dt = (mu - 0.5*second*sigma**2)*X + X*sigma*xi*second**.5: 1', dt=dt, method=method) G.X = X0 mon = StateMonitor(G, 'X', record=True) net = Network(G, mon) net.run(runtime) return mon.t_[:], mon.X.flatten() def exact_solution(t, dt): """ Return the exact solution for geometrical Brownian motion at the given time points """ # Remove units for simplicity my_mu = float(mu) my_sigma = float(sigma) dt = float(dt) t = asarray(t) np.random.seed(seed) # We are calculating the values at the *start* of a time step, as when using # a StateMonitor. Therefore the Brownian motion starts with zero brownian = np.hstack([0, cumsum(sqrt(dt) * np.random.randn(len(t)-1))]) return (X0 * exp((my_mu - 0.5*my_sigma**2)*(t+dt) + my_sigma*brownian)) figure(1, figsize=(16, 7)) figure(2, figsize=(16, 7)) methods = ['milstein', 'heun'] dts = [1*ms, 0.5*ms, 0.2*ms, 0.1*ms, 0.05*ms, 0.025*ms, 0.01*ms, 0.005*ms] rows = floor(sqrt(len(dts))) cols = ceil(1.0 * len(dts) / rows) errors = dict([(method, zeros(len(dts))) for method in methods]) for dt_idx, dt in enumerate(dts): print('dt: %s' % dt) trajectories = {} # Test the numerical methods for method in methods: t, trajectories[method] = simulate(method, dt) # Calculate the exact solution exact = exact_solution(t, dt) for method in methods: # plot the trajectories figure(1) subplot(rows, cols, dt_idx+1) plot(t, trajectories[method], label=method, alpha=0.75) # determine the mean absolute error errors[method][dt_idx] = mean(abs(trajectories[method] - exact)) # plot the difference to the real trajectory figure(2) subplot(rows, cols, dt_idx+1) plot(t, trajectories[method] - exact, label=method, alpha=0.75) figure(1) plot(t, exact, color='gray', lw=2, label='exact', alpha=0.75) title('dt = %s' % str(dt)) xticks([]) figure(1) legend(frameon=False, loc='best') tight_layout() figure(2) legend(frameon=False, loc='best') tight_layout() figure(3) for method in methods: plot(array(dts) / ms, errors[method], 'o', label=method) legend(frameon=False, loc='best') xscale('log') yscale('log') xlabel('dt (ms)') ylabel('Mean absolute error') tight_layout() show() brian2-2.5.4/examples/compartmental/000077500000000000000000000000001445201106100173555ustar00rootroot00000000000000brian2-2.5.4/examples/compartmental/bipolar_cell.py000066400000000000000000000023141445201106100223560ustar00rootroot00000000000000""" A pseudo MSO neuron, with two dendrites and one axon (fake geometry). """ from brian2 import * # Morphology morpho = Soma(30*um) morpho.axon = Cylinder(diameter=1*um, length=300*um, n=100) morpho.L = Cylinder(diameter=1*um, length=100*um, n=50) morpho.R = Cylinder(diameter=1*um, length=150*um, n=50) # Passive channels gL = 1e-4*siemens/cm**2 EL = -70*mV eqs=''' Im = gL * (EL - v) : amp/meter**2 I : amp (point current) ''' neuron = SpatialNeuron(morphology=morpho, model=eqs, Cm=1*uF/cm**2, Ri=100*ohm*cm, method='exponential_euler') neuron.v = EL neuron.I = 0*amp # Monitors mon_soma = StateMonitor(neuron, 'v', record=[0]) mon_L = StateMonitor(neuron.L, 'v', record=True) mon_R = StateMonitor(neuron, 'v', record=morpho.R[75*um]) run(1*ms) neuron.I[morpho.L[50*um]] = 0.2*nA # injecting in the left dendrite run(5*ms) neuron.I = 0*amp run(50*ms, report='text') subplot(211) plot(mon_L.t/ms, mon_soma[0].v/mV, 'k') plot(mon_L.t/ms, mon_L[morpho.L[50*um]].v/mV, 'r') plot(mon_L.t/ms, mon_R[morpho.R[75*um]].v/mV, 'b') ylabel('v (mV)') subplot(212) for x in linspace(0*um, 100*um, 10, endpoint=False): plot(mon_L.t/ms, mon_L[morpho.L[x]].v/mV) xlabel('Time (ms)') ylabel('v (mV)') show() brian2-2.5.4/examples/compartmental/bipolar_with_inputs.py000066400000000000000000000030701445201106100240140ustar00rootroot00000000000000""" A pseudo MSO neuron, with two dendrites (fake geometry). There are synaptic inputs. """ from brian2 import * # Morphology morpho = Soma(30*um) morpho.L = Cylinder(diameter=1*um, length=100*um, n=50) morpho.R = Cylinder(diameter=1*um, length=100*um, n=50) # Passive channels gL = 1e-4*siemens/cm**2 EL = -70*mV Es = 0*mV eqs=''' Im = gL*(EL-v) : amp/meter**2 Is = gs*(Es-v) : amp (point current) gs : siemens ''' neuron = SpatialNeuron(morphology=morpho, model=eqs, Cm=1*uF/cm**2, Ri=100*ohm*cm, method='exponential_euler') neuron.v = EL # Regular inputs stimulation = NeuronGroup(2, 'dx/dt = 300*Hz : 1', threshold='x>1', reset='x=0', method='euler') stimulation.x = [0, 0.5] # Asynchronous # Synapses taus = 1*ms w = 20*nS S = Synapses(stimulation, neuron, model='''dg/dt = -g/taus : siemens (clock-driven) gs_post = g : siemens (summed)''', on_pre='g += w', method='exact') S.connect(i=0, j=morpho.L[-1]) S.connect(i=1, j=morpho.R[-1]) # Monitors mon_soma = StateMonitor(neuron, 'v', record=[0]) mon_L = StateMonitor(neuron.L, 'v', record=True) mon_R = StateMonitor(neuron.R, 'v', record=morpho.R[-1]) run(50*ms, report='text') subplot(211) plot(mon_L.t/ms, mon_soma[0].v/mV, 'k') plot(mon_L.t/ms, mon_L[morpho.L[-1]].v/mV, 'r') plot(mon_L.t/ms, mon_R[morpho.R[-1]].v/mV, 'b') ylabel('v (mV)') subplot(212) for x in linspace(0*um, 100*um, 10, endpoint=False): plot(mon_L.t/ms, mon_L[morpho.L[x]].v/mV) xlabel('Time (ms)') ylabel('v (mV)') show() brian2-2.5.4/examples/compartmental/bipolar_with_inputs2.py000066400000000000000000000026601445201106100241020ustar00rootroot00000000000000""" A pseudo MSO neuron, with two dendrites (fake geometry). There are synaptic inputs. Second method. """ from brian2 import * # Morphology morpho = Soma(30*um) morpho.L = Cylinder(diameter=1*um, length=100*um, n=50) morpho.R = Cylinder(diameter=1*um, length=100*um, n=50) # Passive channels gL = 1e-4*siemens/cm**2 EL = -70*mV Es = 0*mV taus = 1*ms eqs=''' Im = gL*(EL-v) : amp/meter**2 Is = gs*(Es-v) : amp (point current) dgs/dt = -gs/taus : siemens ''' neuron = SpatialNeuron(morphology=morpho, model=eqs, Cm=1*uF/cm**2, Ri=100*ohm*cm, method='exponential_euler') neuron.v = EL # Regular inputs stimulation = NeuronGroup(2, 'dx/dt = 300*Hz : 1', threshold='x>1', reset='x=0', method='euler') stimulation.x = [0, 0.5] # Asynchronous # Synapses w = 20*nS S = Synapses(stimulation, neuron, on_pre='gs += w') S.connect(i=0, j=morpho.L[99.9*um]) S.connect(i=1, j=morpho.R[99.9*um]) # Monitors mon_soma = StateMonitor(neuron, 'v', record=[0]) mon_L = StateMonitor(neuron.L, 'v', record=True) mon_R = StateMonitor(neuron, 'v', record=morpho.R[99.9*um]) run(50*ms, report='text') subplot(211) plot(mon_L.t/ms, mon_soma[0].v/mV, 'k') plot(mon_L.t/ms, mon_L[morpho.L[99.9*um]].v/mV, 'r') plot(mon_L.t/ms, mon_R[morpho.R[99.9*um]].v/mV, 'b') ylabel('v (mV)') subplot(212) for i in [0, 5, 10, 15, 20, 25, 30, 35, 40, 45]: plot(mon_L.t/ms, mon_L.v[i, :]/mV) xlabel('Time (ms)') ylabel('v (mV)') show() brian2-2.5.4/examples/compartmental/cylinder.py000066400000000000000000000016251445201106100215440ustar00rootroot00000000000000""" A short cylinder with constant injection at one end. """ from brian2 import * defaultclock.dt = 0.01*ms # Morphology diameter = 1*um length = 300*um Cm = 1*uF/cm**2 Ri = 150*ohm*cm N = 200 morpho = Cylinder(diameter=diameter, length=length, n=N) # Passive channels gL = 1e-4*siemens/cm**2 EL = -70*mV eqs = ''' Im = gL * (EL - v) : amp/meter**2 I : amp (point current) ''' neuron = SpatialNeuron(morphology=morpho, model=eqs, Cm=Cm, Ri=Ri, method='exponential_euler') neuron.v = EL la = neuron.space_constant[0] print("Electrotonic length: %s" % la) neuron.I[0] = 0.02*nA # injecting at the left end run(100*ms, report='text') plot(neuron.distance/um, neuron.v/mV, 'kx') # Theory x = neuron.distance ra = la * 4 * Ri / (pi * diameter**2) theory = EL + ra * neuron.I[0] * cosh((length - x) / la) / sinh(length / la) plot(x/um, theory/mV, 'r') xlabel('x (um)') ylabel('v (mV)') show() brian2-2.5.4/examples/compartmental/hh_with_spikes.py000066400000000000000000000040131445201106100227350ustar00rootroot00000000000000""" Hodgkin-Huxley equations (1952). Spikes are recorded along the axon, and then velocity is calculated. """ from brian2 import * from scipy import stats defaultclock.dt = 0.01*ms morpho = Cylinder(length=10*cm, diameter=2*238*um, n=1000, type='axon') El = 10.613*mV ENa = 115*mV EK = -12*mV gl = 0.3*msiemens/cm**2 gNa0 = 120*msiemens/cm**2 gK = 36*msiemens/cm**2 # Typical equations eqs = ''' # The same equations for the whole neuron, but possibly different parameter values # distributed transmembrane current Im = gl * (El-v) + gNa * m**3 * h * (ENa-v) + gK * n**4 * (EK-v) : amp/meter**2 I : amp (point current) # applied current dm/dt = alpham * (1-m) - betam * m : 1 dn/dt = alphan * (1-n) - betan * n : 1 dh/dt = alphah * (1-h) - betah * h : 1 alpham = (0.1/mV) * 10*mV/exprel((-v+25*mV)/(10*mV))/ms : Hz betam = 4 * exp(-v/(18*mV))/ms : Hz alphah = 0.07 * exp(-v/(20*mV))/ms : Hz betah = 1/(exp((-v+30*mV) / (10*mV)) + 1)/ms : Hz alphan = (0.01/mV) * 10*mV/exprel((-v+10*mV)/(10*mV))/ms : Hz betan = 0.125*exp(-v/(80*mV))/ms : Hz gNa : siemens/meter**2 ''' neuron = SpatialNeuron(morphology=morpho, model=eqs, method="exponential_euler", refractory="m > 0.4", threshold="m > 0.5", Cm=1*uF/cm**2, Ri=35.4*ohm*cm) neuron.v = 0*mV neuron.h = 1 neuron.m = 0 neuron.n = .5 neuron.I = 0*amp neuron.gNa = gNa0 M = StateMonitor(neuron, 'v', record=True) spikes = SpikeMonitor(neuron) run(50*ms, report='text') neuron.I[0] = 1*uA # current injection at one end run(3*ms) neuron.I = 0*amp run(50*ms, report='text') # Calculation of velocity slope, intercept, r_value, p_value, std_err = stats.linregress(spikes.t/second, neuron.distance[spikes.i]/meter) print("Velocity = %.2f m/s" % slope) subplot(211) for i in range(10): plot(M.t/ms, M.v.T[:, i*100]/mV) ylabel('v') subplot(212) plot(spikes.t/ms, spikes.i*neuron.length[0]/cm, '.k') plot(spikes.t/ms, (intercept+slope*(spikes.t/second))/cm, 'r') xlabel('Time (ms)') ylabel('Position (cm)') show() brian2-2.5.4/examples/compartmental/hodgkin_huxley_1952.py000066400000000000000000000030531445201106100234310ustar00rootroot00000000000000""" Hodgkin-Huxley equations (1952). """ from brian2 import * morpho = Cylinder(length=10*cm, diameter=2*238*um, n=1000, type='axon') El = 10.613*mV ENa = 115*mV EK = -12*mV gl = 0.3*msiemens/cm**2 gNa0 = 120*msiemens/cm**2 gK = 36*msiemens/cm**2 # Typical equations eqs = ''' # The same equations for the whole neuron, but possibly different parameter values # distributed transmembrane current Im = gl * (El-v) + gNa * m**3 * h * (ENa-v) + gK * n**4 * (EK-v) : amp/meter**2 I : amp (point current) # applied current dm/dt = alpham * (1-m) - betam * m : 1 dn/dt = alphan * (1-n) - betan * n : 1 dh/dt = alphah * (1-h) - betah * h : 1 alpham = (0.1/mV) * 10*mV/exprel((-v+25*mV)/(10*mV))/ms : Hz betam = 4 * exp(-v/(18*mV))/ms : Hz alphah = 0.07 * exp(-v/(20*mV))/ms : Hz betah = 1/(exp((-v+30*mV) / (10*mV)) + 1)/ms : Hz alphan = (0.01/mV) * 10*mV/exprel((-v+10*mV)/(10*mV))/ms : Hz betan = 0.125*exp(-v/(80*mV))/ms : Hz gNa : siemens/meter**2 ''' neuron = SpatialNeuron(morphology=morpho, model=eqs, Cm=1*uF/cm**2, Ri=35.4*ohm*cm, method="exponential_euler") neuron.v = 0*mV neuron.h = 1 neuron.m = 0 neuron.n = .5 neuron.I = 0 neuron.gNa = gNa0 neuron[5*cm:10*cm].gNa = 0*siemens/cm**2 M = StateMonitor(neuron, 'v', record=True) run(50*ms, report='text') neuron.I[0] = 1*uA # current injection at one end run(3*ms) neuron.I = 0*amp run(100*ms, report='text') for i in range(75, 125, 1): plot(cumsum(neuron.length)/cm, i+(1./60)*M.v[:, i*5]/mV, 'k') yticks([]) ylabel('Time [major] v (mV) [minor]') xlabel('Position (cm)') axis('tight') show() brian2-2.5.4/examples/compartmental/infinite_cable.py000066400000000000000000000024341445201106100226650ustar00rootroot00000000000000""" An (almost) infinite cable with pulse injection in the middle. """ from brian2 import * defaultclock.dt = 0.001*ms # Morphology diameter = 1*um Cm = 1*uF/cm**2 Ri = 100*ohm*cm N = 500 morpho = Cylinder(diameter=diameter, length=3*mm, n=N) # Passive channels gL = 1e-4*siemens/cm**2 EL = -70*mV eqs = ''' Im = gL * (EL-v) : amp/meter**2 I : amp (point current) ''' neuron = SpatialNeuron(morphology=morpho, model=eqs, Cm=Cm, Ri=Ri, method = 'exponential_euler') neuron.v = EL taum = Cm /gL # membrane time constant print("Time constant: %s" % taum) la = neuron.space_constant[0] print("Characteristic length: %s" % la) # Monitors mon = StateMonitor(neuron, 'v', record=range(0, N//2, 20)) neuron.I[len(neuron) // 2] = 1*nA # injecting in the middle run(0.02*ms) neuron.I = 0*amp run(10*ms, report='text') t = mon.t plot(t/ms, mon.v.T/mV, 'k') # Theory (incorrect near cable ends) for i in range(0, len(neuron)//2, 20): x = (len(neuron)/2 - i) * morpho.length[0] theory = (1/(la*Cm*pi*diameter) * sqrt(taum / (4*pi*(t + defaultclock.dt))) * exp(-(t+defaultclock.dt)/taum - taum / (4*(t+defaultclock.dt))*(x/la)**2)) theory = EL + theory * 1*nA * 0.02*ms plot(t/ms, theory/mV, 'r') xlabel('Time (ms)') ylabel('v (mV') show() brian2-2.5.4/examples/compartmental/lfp.py000066400000000000000000000047231445201106100205160ustar00rootroot00000000000000""" Hodgkin-Huxley equations (1952) We calculate the extracellular field potential at various places. """ from brian2 import * defaultclock.dt = 0.01*ms morpho = Cylinder(x=[0, 10]*cm, diameter=2*238*um, n=1000, type='axon') El = 10.613* mV ENa = 115*mV EK = -12*mV gl = 0.3*msiemens/cm**2 gNa0 = 120*msiemens/cm**2 gK = 36*msiemens/cm**2 # Typical equations eqs = ''' # The same equations for the whole neuron, but possibly different parameter values # distributed transmembrane current Im = gl * (El-v) + gNa * m**3 * h * (ENa-v) + gK * n**4 * (EK-v) : amp/meter**2 I : amp (point current) # applied current dm/dt = alpham * (1-m) - betam * m : 1 dn/dt = alphan * (1-n) - betan * n : 1 dh/dt = alphah * (1-h) - betah * h : 1 alpham = (0.1/mV) * 10*mV/exprel((-v+25*mV)/(10*mV))/ms : Hz betam = 4 * exp(-v/(18*mV))/ms : Hz alphah = 0.07 * exp(-v/(20*mV))/ms : Hz betah = 1/(exp((-v+30*mV) / (10*mV)) + 1)/ms : Hz alphan = (0.01/mV) * 10*mV/exprel((-v+10*mV)/(10*mV))/ms : Hz betan = 0.125*exp(-v/(80*mV))/ms : Hz gNa : siemens/meter**2 ''' neuron = SpatialNeuron(morphology=morpho, model=eqs, Cm=1*uF/cm**2, Ri=35.4*ohm*cm, method="exponential_euler") neuron.v = 0*mV neuron.h = 1 neuron.m = 0 neuron.n = .5 neuron.I = 0 neuron.gNa = gNa0 neuron[5*cm:10*cm].gNa = 0*siemens/cm**2 M = StateMonitor(neuron, 'v', record=True) # LFP recorder Ne = 5 # Number of electrodes sigma = 0.3*siemens/meter # Resistivity of extracellular field (0.3-0.4 S/m) lfp = NeuronGroup(Ne, model='''v : volt x : meter y : meter z : meter''') lfp.x = 7*cm # Off center (to be far from stimulating electrode) lfp.y = [1*mm, 2*mm, 4*mm, 8*mm, 16*mm] S = Synapses(neuron, lfp, model='''w : ohm*meter**2 (constant) # Weight in the LFP calculation v_post = w*(Ic_pre-Im_pre) : volt (summed)''') S.summed_updaters['v_post'].when = 'after_groups' # otherwise Ic has not yet been updated for the current time step. S.connect() S.w = 'area_pre/(4*pi*sigma)/((x_pre-x_post)**2+(y_pre-y_post)**2+(z_pre-z_post)**2)**.5' Mlfp = StateMonitor(lfp, 'v', record=True) run(50*ms, report='text') neuron.I[0] = 1*uA # current injection at one end run(3*ms) neuron.I = 0*amp run(100*ms, report='text') subplot(211) for i in range(10): plot(M.t/ms, M.v[i*100]/mV) ylabel('$V_m$ (mV)') subplot(212) for i in range(5): plot(M.t/ms, Mlfp.v[i]/mV) ylabel('LFP (mV)') xlabel('Time (ms)') show() brian2-2.5.4/examples/compartmental/morphotest.py000066400000000000000000000011571445201106100221370ustar00rootroot00000000000000""" Demonstrate the usage of the `Morphology` object. """ from brian2 import * # Morphology morpho = Soma(30*um) morpho.L = Cylinder(diameter=1*um, length=100*um, n=5) morpho.LL = Cylinder(diameter=1*um, length=20*um, n=2) morpho.R = Cylinder(diameter=1*um, length=100*um, n=5) # Passive channels gL = 1e-4*siemens/cm**2 EL = -70*mV eqs = ''' Im = gL * (EL-v) : amp/meter**2 ''' neuron = SpatialNeuron(morphology=morpho, model=eqs, Cm=1*uF/cm**2, Ri=100*ohm*cm, method='exponential_euler') neuron.v = arange(0, 13)*volt print(neuron.v) print(neuron.L.v) print(neuron.LL.v) print(neuron.L.main.v) brian2-2.5.4/examples/compartmental/rall.py000066400000000000000000000037551445201106100206730ustar00rootroot00000000000000""" A cylinder plus two branches, with diameters according to Rall's formula """ from brian2 import * defaultclock.dt = 0.01*ms # Passive channels gL = 1e-4*siemens/cm**2 EL = -70*mV # Morphology diameter = 1*um length = 300*um Cm = 1*uF/cm**2 Ri = 150*ohm*cm N = 500 rm = 1 / (gL * pi * diameter) # membrane resistance per unit length ra = (4 * Ri)/(pi * diameter**2) # axial resistance per unit length la = sqrt(rm / ra) # space length morpho = Cylinder(diameter=diameter, length=length, n=N) d1 = 0.5*um L1 = 200*um rm = 1 / (gL * pi * d1) # membrane resistance per unit length ra = (4 * Ri) / (pi * d1**2) # axial resistance per unit length l1 = sqrt(rm / ra) # space length morpho.L = Cylinder(diameter=d1, length=L1, n=N) d2 = (diameter**1.5 - d1**1.5)**(1. / 1.5) rm = 1/(gL * pi * d2) # membrane resistance per unit length ra = (4 * Ri) / (pi * d2**2) # axial resistance per unit length l2 = sqrt(rm / ra) # space length L2 = (L1 / l1) * l2 morpho.R = Cylinder(diameter=d2, length=L2, n=N) eqs=''' Im = gL * (EL-v) : amp/meter**2 I : amp (point current) ''' neuron = SpatialNeuron(morphology=morpho, model=eqs, Cm=Cm, Ri=Ri, method='exponential_euler') neuron.v = EL neuron.I[0] = 0.02*nA # injecting at the left end run(100*ms, report='text') plot(neuron.main.distance/um, neuron.main.v/mV, 'k') plot(neuron.L.distance/um, neuron.L.v/mV, 'k') plot(neuron.R.distance/um, neuron.R.v/mV, 'k') # Theory x = neuron.main.distance ra = la * 4 * Ri/(pi * diameter**2) l = length/la + L1/l1 theory = EL + ra*neuron.I[0]*cosh(l - x/la)/sinh(l) plot(x/um, theory/mV, 'r') x = neuron.L.distance theory = (EL+ra*neuron.I[0]*cosh(l - neuron.main.distance[-1]/la - (x - neuron.main.distance[-1])/l1)/sinh(l)) plot(x/um, theory/mV, 'r') x = neuron.R.distance theory = (EL+ra*neuron.I[0]*cosh(l - neuron.main.distance[-1]/la - (x - neuron.main.distance[-1])/l2)/sinh(l)) plot(x/um, theory/mV, 'r') xlabel('x (um)') ylabel('v (mV)') show() brian2-2.5.4/examples/compartmental/spike_initiation.py000066400000000000000000000024221445201106100232710ustar00rootroot00000000000000""" Ball and stick with Na and K channels """ from brian2 import * defaultclock.dt = 0.025*ms # Morphology morpho = Soma(30*um) morpho.axon = Cylinder(diameter=1*um, length=300*um, n=100) # Channels gL = 1e-4*siemens/cm**2 EL = -70*mV ENa = 50*mV ka = 6*mV ki = 6*mV va = -30*mV vi = -50*mV EK = -90*mV vk = -20*mV kk = 8*mV eqs = ''' Im = gL*(EL-v)+gNa*m*h*(ENa-v)+gK*n*(EK-v) : amp/meter**2 dm/dt = (minf-m)/(0.3*ms) : 1 # simplified Na channel dh/dt = (hinf-h)/(3*ms) : 1 # inactivation dn/dt = (ninf-n)/(5*ms) : 1 # K+ minf = 1/(1+exp((va-v)/ka)) : 1 hinf = 1/(1+exp((v-vi)/ki)) : 1 ninf = 1/(1+exp((vk-v)/kk)) : 1 I : amp (point current) gNa : siemens/meter**2 gK : siemens/meter**2 ''' neuron = SpatialNeuron(morphology=morpho, model=eqs, Cm=1*uF/cm**2, Ri=100*ohm*cm, method='exponential_euler') neuron.v = -65*mV neuron.I = 0*amp neuron.axon[30*um:60*um].gNa = 700*gL neuron.axon[30*um:60*um].gK = 700*gL # Monitors mon=StateMonitor(neuron, 'v', record=True) run(1*ms) neuron.main.I = 0.15*nA run(50*ms) neuron.I = 0*amp run(95*ms, report='text') plot(mon.t/ms, mon.v[0]/mV, 'r') plot(mon.t/ms, mon.v[20]/mV, 'g') plot(mon.t/ms, mon.v[40]/mV, 'b') plot(mon.t/ms, mon.v[60]/mV, 'k') plot(mon.t/ms, mon.v[80]/mV, 'y') xlabel('Time (ms)') ylabel('v (mV)') show() brian2-2.5.4/examples/coupled_oscillators.py000066400000000000000000000102311445201106100211270ustar00rootroot00000000000000""" Coupled oscillators, following the Kuramoto model. The current state of an oscillator is given by its phase :math:`\Theta`, which follows .. math:: \frac{d\Theta_i}{dt} = \omega_i + \frac{K}{N}\sum_j sin(\Theta_j - \Theta_i) where :math:`\omega_i` is the intrinsic frequency of each oscillator, :math:`K` is the coupling strength, and the sum is over all oscillators (all-to-all coupling). The plots show a dot on the unit circle denoting the phase of each neuron (with the color representing the initial phase at the start of the simulation). The black dot and line show the average phase (dot) and the phase coherence (length of the line). The simulations are run four times with different coupling strengths :math:`K`, each simulation starting from the same initial phase distribution. https://en.wikipedia.org/wiki/Kuramoto_model """ import matplotlib.animation as animation from brian2 import * from brian2.core.functions import timestep ### global parameters N = 100 defaultclock.dt = 1*ms ### simulation code def run_sim(K, random_seed=214040893): seed(random_seed) eqs = ''' dTheta/dt = omega + K/N*coupling : radian omega : radian/second (constant) # intrinsic frequency coupling : 1 ''' oscillators = NeuronGroup(N, eqs, method='euler') oscillators.Theta = 'rand()*2*pi' # random initial phase oscillators.omega = 'clip(0.5 + randn()*0.5, 0, inf)*radian/second' # 𝒩(0.5, 0.5) connections = Synapses(oscillators, oscillators, 'coupling_post = sin(Theta_pre - Theta_post) : 1 (summed)') connections.connect() # all-to-all mon = StateMonitor(oscillators, 'Theta', record=True) run(10*second) return mon.Theta[:] ### Create animated plots frame_delay = 40*ms # Helper functions def to_x_y(phases): return np.cos(phases), np.sin(phases) def calc_coherence_and_phase(x, y): phi = np.arctan2(np.sum(y), np.sum(x)) r = np.sqrt(np.sum(x)**2 + np.sum(y)**2)/N return r, phi # Plot an animation with the phase of each oscillator and the average phase def do_animation(fig, axes, K_values, theta_values): ''' Makes animated subplots in the given ``axes``, where each ``theta_values`` entry is the full recording of ``Theta`` from the monitor. ''' artists = [] for ax, K, Theta in zip(axes, K_values, theta_values): x, y = to_x_y(Theta.T[0]) dots = ax.scatter(x, y, c=Theta.T[0]) r, phi = calc_coherence_and_phase(x, y) arrow = ax.arrow(0, 0, r*np.cos(phi), r*np.sin(phi), color='black') mean_dot, = ax.plot(r*np.cos(phi), r*np.sin(phi), 'o', color='black') if abs(K) > 0: title = f"coupling strength K={K:.1f}" else: title = "uncoupled" ax.text(-1., 1.05, title, color='gray', va='bottom') ax.set_aspect('equal') ax.set_axis_off() ax.set(xlim=(-1.2, 1.2), ylim=(-1.2, 1.2)) artists.append((dots, arrow, mean_dot)) def update(frame_number): updated_artists = [] for (dots, arrow, mean_dot), K, Theta in zip(artists, K_values, theta_values): t = frame_delay*frame_number ts = timestep(t, defaultclock.dt) x, y = to_x_y(Theta.T[ts]) dots.set_offsets(np.vstack([x, y]).T) r, phi = calc_coherence_and_phase(x, y) arrow.set_data(dx=r*np.cos(phi), dy=r*np.sin(phi)) mean_dot.set_data(r*np.cos(phi), r*np.sin(phi)) updated_artists.extend([dots, arrow, mean_dot]) return updated_artists ani = animation.FuncAnimation(fig, update, frames=int(magic_network.t/frame_delay), interval=20, blit=True) return ani if __name__ == '__main__': fig, axs = plt.subplots(2, 2) # Manual adjustments instead of layout='tight', to avoid jumps in saved animation fig.subplots_adjust(left=0.025, bottom=0.025, right=0.975, top=0.975, wspace=0, hspace=0) K_values = [0, 1, 2, 4] theta_values = [] for K in K_values: print(f"Running simulation for K={K:.1f}") theta_values.append( run_sim(K/second)) ani = do_animation(fig, axs.flat, K_values, theta_values) plt.show() brian2-2.5.4/examples/frompapers/000077500000000000000000000000001445201106100166655ustar00rootroot00000000000000brian2-2.5.4/examples/frompapers/.gitignore000066400000000000000000000000161445201106100206520ustar00rootroot00000000000000/barrelcortex brian2-2.5.4/examples/frompapers/Brette_2004.py000077500000000000000000000016771445201106100211470ustar00rootroot00000000000000#!/usr/bin/env python """ Phase locking in leaky integrate-and-fire model ----------------------------------------------- Fig. 2A from: Brette R (2004). Dynamics of one-dimensional spiking neuron models. J Math Biol 48(1): 38-56. This shows the phase-locking structure of a LIF driven by a sinusoidal current. When the current crosses the threshold (a<3), the model almost always phase locks (in a measure-theoretical sense). """ from brian2 import * # defaultclock.dt = 0.01*ms # for a more precise picture N = 2000 tau = 100*ms freq = 1/tau eqs = ''' dv/dt = (-v + a + 2*sin(2*pi*t/tau))/tau : 1 a : 1 ''' neurons = NeuronGroup(N, eqs, threshold='v>1', reset='v=0', method='euler') neurons.a = linspace(2, 4, N) run(5*second, report='text') # discard the first spikes (wait for convergence) S = SpikeMonitor(neurons) run(5*second, report='text') i, t = S.it plot((t % tau)/tau, neurons.a[i], ',') xlabel('Spike phase') ylabel('Parameter a') show() brian2-2.5.4/examples/frompapers/Brette_2012/000077500000000000000000000000001445201106100205565ustar00rootroot00000000000000brian2-2.5.4/examples/frompapers/Brette_2012/Fig1.py000066400000000000000000000036121445201106100217200ustar00rootroot00000000000000""" Brette R (2013). Sharpness of spike initiation in neurons explained by compartmentalization. PLoS Comp Biol, doi: 10.1371/journal.pcbi.1003338. Fig 1C-E. Somatic voltage-clamp in a ball-and-stick model with Na channels at a particular location. """ from brian2 import * from params import * defaultclock.dt = 0.025*ms # Morphology morpho = Soma(50*um) # chosen for a target Rm morpho.axon = Cylinder(diameter=1*um, length=300*um, n=300) location = 40*um # where Na channels are placed duration = 500*ms # Channels eqs=''' Im = gL*(EL - v) + gclamp*(vc - v) + gNa*m*(ENa - v) : amp/meter**2 dm/dt = (minf - m) / taum: 1 # simplified Na channel minf = 1 / (1 + exp((va - v) / ka)) : 1 gclamp : siemens/meter**2 gNa : siemens/meter**2 vc = EL + 50*mV * t/duration : volt (shared) # Voltage clamp with a ramping voltage command ''' neuron = SpatialNeuron(morphology=morpho, model=eqs, Cm=Cm, Ri=Ri) compartment = morpho.axon[location] neuron.v = EL neuron.gclamp[0] = gL*500 neuron.gNa[compartment] = gNa_0/neuron.area[compartment] # Monitors mon = StateMonitor(neuron, ['v', 'vc', 'm'], record=True) run(duration, report='text') subplot(221) plot(mon[0].vc/mV, -((mon[0].vc - mon[0].v)*(neuron.gclamp[0]))*neuron.area[0]/nA, 'k') xlabel('V (mV)') ylabel('I (nA)') xlim(-75, -45) title('I-V curve') subplot(222) plot(mon[0].vc/mV, mon[compartment].m, 'k') xlabel('V (mV)') ylabel('m') title('Activation curve (m(V))') subplot(223) # Number of simulation time steps for each volt increment in the voltage-clamp dt_per_volt = len(mon.t)/(50*mV) for v in [-64*mV, -61*mV, -58*mV, -55*mV]: plot(mon.v[:100, int(dt_per_volt*(v - EL))]/mV, 'k') xlabel('Distance from soma (um)') ylabel('V (mV)') title('Voltage across axon') subplot(224) plot(mon[compartment].v/mV, mon[compartment].v/mV, 'k--') # Diagonal plot(mon[0].v/mV, mon[compartment].v/mV, 'k') xlabel('Vs (mV)') ylabel('Va (mV)') tight_layout() show() brian2-2.5.4/examples/frompapers/Brette_2012/Fig3AB.py000066400000000000000000000030441445201106100221240ustar00rootroot00000000000000""" Brette R (2013). Sharpness of spike initiation in neurons explained by compartmentalization. PLoS Comp Biol, doi: 10.1371/journal.pcbi.1003338. Fig. 3. A, B. Kink with only Nav1.6 channels """ from brian2 import * from params import * codegen.target='numpy' defaultclock.dt = 0.025*ms # Morphology morpho = Soma(50*um) # chosen for a target Rm morpho.axon = Cylinder(diameter=1*um, length=300*um, n=300) location = 40*um # where Na channels are placed # Channels eqs=''' Im = gL*(EL - v) + gNa*m*(ENa - v) : amp/meter**2 dm/dt = (minf - m) / taum : 1 # simplified Na channel minf = 1 / (1 + exp((va - v) / ka)) : 1 gNa : siemens/meter**2 Iin : amp (point current) ''' neuron = SpatialNeuron(morphology=morpho, model=eqs, Cm=Cm, Ri=Ri, method="exponential_euler") compartment = morpho.axon[location] neuron.v = EL neuron.gNa[compartment] = gNa_0/neuron.area[compartment] M = StateMonitor(neuron, ['v', 'm'], record=True) run(20*ms, report='text') neuron.Iin[0] = gL * 20*mV * neuron.area[0] run(80*ms, report='text') subplot(121) plot(M.t/ms, M[0].v/mV, 'r') plot(M.t/ms, M[compartment].v/mV, 'k') plot(M.t/ms, M[compartment].m*(80+60)-80, 'k--') # open channels ylim(-80, 60) xlabel('Time (ms)') ylabel('V (mV)') title('Voltage traces') subplot(122) dm = diff(M[0].v) / defaultclock.dt dm40 = diff(M[compartment].v) / defaultclock.dt plot((M[0].v/mV)[1:], dm/(volt/second), 'r') plot((M[compartment].v/mV)[1:], dm40/(volt/second), 'k') xlim(-80, 40) xlabel('V (mV)') ylabel('dV/dt (V/s)') title('Phase plot') tight_layout() show() brian2-2.5.4/examples/frompapers/Brette_2012/Fig3CF.py000066400000000000000000000047111445201106100221340ustar00rootroot00000000000000""" Brette R (2013). Sharpness of spike initiation in neurons explained by compartmentalization. PLoS Comp Biol, doi: 10.1371/journal.pcbi.1003338. Fig. 3C-F. Kink with Nav1.6 and Nav1.2 """ from brian2 import * from params import * defaultclock.dt = 0.01*ms # Morphology morpho = Soma(50*um) # chosen for a target Rm morpho.axon = Cylinder(diameter=1*um, length=300*um, n=300) location16 = 40*um # where Nav1.6 channels are placed location12 = 15*um # where Nav1.2 channels are placed va2 = va + 15*mV # depolarized Nav1.2 # Channels duration = 100*ms eqs=''' Im = gL * (EL - v) + gNa*m*(ENa - v) + gNa2*m2*(ENa - v) : amp/meter**2 dm/dt = (minf - m) / taum : 1 # simplified Na channel minf = 1 / (1 + exp((va - v) / ka)) : 1 dm2/dt = (minf2 - m2) / taum : 1 # simplified Na channel, Nav1.2 minf2 = 1/(1 + exp((va2 - v) / ka)) : 1 gNa : siemens/meter**2 gNa2 : siemens/meter**2 # Nav1.2 Iin : amp (point current) ''' neuron = SpatialNeuron(morphology=morpho, model=eqs, Cm=Cm, Ri=Ri, method="exponential_euler") compartment16 = morpho.axon[location16] compartment12 = morpho.axon[location12] neuron.v = EL neuron.gNa[compartment16] = gNa_0/neuron.area[compartment16] neuron.gNa2[compartment12] = 20*gNa_0/neuron.area[compartment12] # Monitors M = StateMonitor(neuron, ['v', 'm', 'm2'], record=True) run(20*ms, report='text') neuron.Iin[0] = gL * 20*mV * neuron.area[0] run(80*ms, report='text') subplot(221) plot(M.t/ms, M[0].v/mV, 'r') plot(M.t/ms, M[compartment16].v/mV, 'k') plot(M.t/ms, M[compartment16].m*(80+60)-80, 'k--') # open channels ylim(-80, 60) xlabel('Time (ms)') ylabel('V (mV)') title('Voltage traces') subplot(222) plot(M[0].v/mV, M[compartment16].m, 'k') plot(M[0].v/mV, 1 / (1 + exp((va - M[0].v) / ka)), 'k--') plot(M[0].v/mV, M[compartment12].m2, 'r') plot(M[0].v/mV, 1 / (1 + exp((va2 - M[0].v) / ka)), 'r--') xlim(-70, 0) xlabel('V (mV)') ylabel('m') title('Activation curves') subplot(223) dm = diff(M[0].v) / defaultclock.dt dm40 = diff(M[compartment16].v) / defaultclock.dt plot((M[0].v/mV)[1:], dm/(volt/second), 'r') plot((M[compartment16].v/mV)[1:], dm40/(volt/second), 'k') xlim(-80, 40) xlabel('V (mV)') ylabel('dV/dt (V/s)') title('Phase plot') subplot(224) plot((M[0].v/mV)[1:], dm/(volt/second), 'r') plot((M[compartment16].v/mV)[1:], dm40/(volt/second), 'k') plot((M[0].v/mV)[1:], 10 + 0*dm/(volt/second), 'k--') xlim(-70, -40) ylim(0, 20) xlabel('V (mV)') ylabel('dV/dt (V/s)') title('Phase plot(zoom)') tight_layout() show() brian2-2.5.4/examples/frompapers/Brette_2012/Fig4.py000066400000000000000000000034021445201106100217200ustar00rootroot00000000000000""" Brette R (2013). Sharpness of spike initiation in neurons explained by compartmentalization. PLoS Comp Biol, doi: 10.1371/journal.pcbi.1003338. Fig. 4E-F. Spatial distribution of Na channels. Tapering axon near soma. """ from brian2 import * from params import * defaultclock.dt = 0.025*ms # Morphology morpho = Soma(50*um) # chosen for a target Rm # Tapering (change this for the other figure panels) diameters = hstack([linspace(4, 1, 11), ones(290)])*um morpho.axon = Section(diameter=diameters, length=ones(300)*um, n=300) # Na channels Na_start = (25 + 10)*um Na_end = (40 + 10)*um linear_distribution = True # True is F, False is E duration = 500*ms # Channels eqs=''' Im = gL*(EL - v) + gclamp*(vc - v) + gNa*m*(ENa - v) : amp/meter**2 dm/dt = (minf - m) / taum: 1 # simplified Na channel minf = 1 / (1 + exp((va - v) / ka)) : 1 gclamp : siemens/meter**2 gNa : siemens/meter**2 vc = EL + 50*mV * t / duration : volt (shared) # Voltage clamp with a ramping voltage command ''' neuron = SpatialNeuron(morphology=morpho, model=eqs, Cm=Cm, Ri=Ri, method="exponential_euler") compartments = morpho.axon[Na_start:Na_end] neuron.v = EL neuron.gclamp[0] = gL*500 if linear_distribution: profile = linspace(1, 0, len(compartments)) else: profile = ones(len(compartments)) profile = profile / sum(profile) # normalization neuron.gNa[compartments] = gNa_0 * profile / neuron.area[compartments] # Monitors mon = StateMonitor(neuron, 'v', record=True) run(duration, report='text') dt_per_volt = len(mon.t) / (50*mV) for v in [-64*mV, -61*mV, -58*mV, -55*mV, -52*mV]: plot(mon.v[:100, int(dt_per_volt * (v - EL))]/mV, 'k') xlim(0, 50+10) ylim(-65, -25) ylabel('V (mV)') xlabel('Location (um)') title('Voltage across axon') tight_layout() show() brian2-2.5.4/examples/frompapers/Brette_2012/Fig5A.py000066400000000000000000000033631445201106100220300ustar00rootroot00000000000000''' Brette R (2013). Sharpness of spike initiation in neurons explained by compartmentalization. PLoS Comp Biol, doi: 10.1371/journal.pcbi.1003338. Fig. 5A. Voltage trace for current injection, with an additional reset when a spike is produced. Trick: to reset the entire neuron, we use a set of synapses from the spike initiation compartment where the threshold condition applies to all compartments, and the reset operation (v = EL) is applied there every time a spike is produced. ''' from brian2 import * from params import * defaultclock.dt = 0.025*ms duration = 500*ms # Morphology morpho = Soma(50*um) # chosen for a target Rm morpho.axon = Cylinder(diameter=1*um, length=300*um, n=300) # Input taux = 5*ms sigmax = 12*mV xx0 = 7*mV compartment = 40 # Channels eqs = ''' Im = gL * (EL - v) + gNa * m * (ENa - v) + gLx * (xx0 + xx) : amp/meter**2 dm/dt = (minf - m) / taum : 1 # simplified Na channel minf = 1 / (1 + exp((va - v) / ka)) : 1 gNa : siemens/meter**2 gLx : siemens/meter**2 dxx/dt = -xx / taux + sigmax * (2 / taux)**.5 *xi : volt ''' neuron = SpatialNeuron(morphology=morpho, model=eqs, Cm=Cm, Ri=Ri, threshold='m>0.5', threshold_location=compartment, refractory=5*ms) neuron.v = EL neuron.gLx[0] = gL neuron.gNa[compartment] = gNa_0 / neuron.area[compartment] # Reset the entire neuron when there is a spike reset = Synapses(neuron, neuron, on_pre='v = EL') reset.connect('i == compartment') # Connects the spike initiation compartment to all compartments # Monitors S = SpikeMonitor(neuron) M = StateMonitor(neuron, 'v', record=0) run(duration, report='text') # Add spikes for display v = M[0].v for t in S.t: v[int(t / defaultclock.dt)] = 50*mV plot(M.t/ms, v/mV, 'k') tight_layout() show() brian2-2.5.4/examples/frompapers/Brette_2012/README.txt000066400000000000000000000005061445201106100222550ustar00rootroot00000000000000These are Brian scripts corresponding to the following paper: Brette R (2013). Sharpness of spike initiation in neurons explained by compartmentalization. PLoS Comp Biol, doi: 10.1371/journal.pcbi.1003338. params.py contains model parameters Essential figures from the paper: Fig1.py Fig3AB.py Fig3CD.py Fig4.py Fig5A.py brian2-2.5.4/examples/frompapers/Brette_2012/params.py000066400000000000000000000004711445201106100224150ustar00rootroot00000000000000""" Parameters for spike initiation simulations. """ from brian2.units import * # Passive parameters EL = -75*mV S = 7.85e-9*meter**2 # area (sphere of 50 um diameter) Cm = 0.75*uF/cm**2 gL = 1. / (30000*ohm*cm**2) Ri = 150*ohm*cm # Na channels ENa = 60*mV ka = 6*mV va = -40*mV gNa_0 = gL * 2*S taum = 0.1*ms brian2-2.5.4/examples/frompapers/Brette_Gerstner_2005.py000077500000000000000000000024771445201106100230200ustar00rootroot00000000000000#!/usr/bin/env python """ Adaptive exponential integrate-and-fire model. http://www.scholarpedia.org/article/Adaptive_exponential_integrate-and-fire_model Introduced in Brette R. and Gerstner W. (2005), Adaptive Exponential Integrate-and-Fire Model as an Effective Description of Neuronal Activity, J. Neurophysiol. 94: 3637 - 3642. """ from brian2 import * # Parameters C = 281 * pF gL = 30 * nS taum = C / gL EL = -70.6 * mV VT = -50.4 * mV DeltaT = 2 * mV Vcut = VT + 5 * DeltaT # Pick an electrophysiological behaviour tauw, a, b, Vr = 144*ms, 4*nS, 0.0805*nA, -70.6*mV # Regular spiking (as in the paper) #tauw,a,b,Vr=20*ms,4*nS,0.5*nA,VT+5*mV # Bursting #tauw,a,b,Vr=144*ms,2*C/(144*ms),0*nA,-70.6*mV # Fast spiking eqs = """ dvm/dt = (gL*(EL - vm) + gL*DeltaT*exp((vm - VT)/DeltaT) + I - w)/C : volt dw/dt = (a*(vm - EL) - w)/tauw : amp I : amp """ neuron = NeuronGroup(1, model=eqs, threshold='vm>Vcut', reset="vm=Vr; w+=b", method='euler') neuron.vm = EL trace = StateMonitor(neuron, 'vm', record=0) spikes = SpikeMonitor(neuron) run(20 * ms) neuron.I = 1*nA run(100 * ms) neuron.I = 0*nA run(20 * ms) # We draw nicer spikes vm = trace[0].vm[:] for t in spikes.t: i = int(t / defaultclock.dt) vm[i] = 20*mV plot(trace.t / ms, vm / mV) xlabel('time (ms)') ylabel('membrane potential (mV)') show() brian2-2.5.4/examples/frompapers/Brette_Guigon_2003.py000077500000000000000000000030641445201106100224460ustar00rootroot00000000000000#!/usr/bin/env python """ Reliability of spike timing --------------------------- Adapted from Fig. 10D,E of Brette R and E Guigon (2003). Reliability of Spike Timing Is a General Property of Spiking Model Neurons. Neural Computation 15, 279-308. This shows that reliability of spike timing is a generic property of spiking neurons, even those that are not leaky. This is a non-physiological model which can be leaky or anti-leaky depending on the sign of the input I. All neurons receive the same fluctuating input, scaled by a parameter p that varies across neurons. This shows: 1. reproducibility of spike timing 2. robustness with respect to deterministic changes (parameter) 3. increased reproducibility in the fluctuation-driven regime (input crosses the threshold) """ from brian2 import * N = 500 tau = 33*ms taux = 20*ms sigma = 0.02 eqs_input = ''' dx/dt = -x/taux + (2/taux)**.5*xi : 1 ''' eqs = ''' dv/dt = (v*I + 1)/tau + sigma*(2/tau)**.5*xi : 1 I = 0.5 + 3*p*B : 1 B = 2./(1 + exp(-2*x)) - 1 : 1 (shared) p : 1 x : 1 (linked) ''' input = NeuronGroup(1, eqs_input, method='euler') neurons = NeuronGroup(N, eqs, threshold='v>1', reset='v=0', method='euler') neurons.p = '1.0*i/N' neurons.v = 'rand()' neurons.x = linked_var(input, 'x') M = StateMonitor(neurons, 'B', record=0) S = SpikeMonitor(neurons) run(1000*ms, report='text') subplot(211) # The input plot(M.t/ms, M[0].B) xticks([]) title('shared input') subplot(212) plot(S.t/ms, neurons.p[S.i], ',') plot([0, 1000], [.5, .5], color='C1') xlabel('time (ms)') ylabel('p') title('spiking activity') show() brian2-2.5.4/examples/frompapers/Brunel_2000.py000077500000000000000000000073651445201106100211450ustar00rootroot00000000000000#!/usr/bin/env python3 """ Fig. 8 from: Brunel, N. Dynamics of Sparsely Connected Networks of Excitatory and Inhibitory Spiking Neurons. J Comput Neurosci 8, 183–208 (2000). https://doi.org/10.1023/A:1008925309027 Inspired by http://neuronaldynamics.epfl.ch Sebastian Schmitt, 2022 """ import random from brian2 import * import matplotlib.pyplot as plt def sim(g, nu_ext_over_nu_thr, sim_time, ax_spikes, ax_rates, rate_tick_step): """ g -- relative inhibitory to excitatory synaptic strength nu_ext_over_nu_thr -- ratio of external stimulus rate to threshold rate sim_time -- simulation time ax_spikes -- matplotlib axes to plot spikes on ax_rates -- matplotlib axes to plot rates on rate_tick_step -- step size for rate axis ticks """ # network parameters N_E = 10000 gamma = 0.25 N_I = round(gamma * N_E) N = N_E + N_I epsilon = 0.1 C_E = epsilon * N_E C_ext = C_E # neuron parameters tau = 20 * ms theta = 20 * mV V_r = 10 * mV tau_rp = 2 * ms # synapse parameters J = 0.1 * mV D = 1.5 * ms # external stimulus nu_thr = theta / (J * C_E * tau) defaultclock.dt = 0.1 * ms neurons = NeuronGroup(N, """ dv/dt = -v/tau : volt (unless refractory) """, threshold="v > theta", reset="v = V_r", refractory=tau_rp, method="exact", ) excitatory_neurons = neurons[:N_E] inhibitory_neurons = neurons[N_E:] exc_synapses = Synapses(excitatory_neurons, target=neurons, on_pre="v += J", delay=D) exc_synapses.connect(p=epsilon) inhib_synapses = Synapses(inhibitory_neurons, target=neurons, on_pre="v += -g*J", delay=D) inhib_synapses.connect(p=epsilon) nu_ext = nu_ext_over_nu_thr * nu_thr external_poisson_input = PoissonInput( target=neurons, target_var="v", N=C_ext, rate=nu_ext, weight=J ) rate_monitor = PopulationRateMonitor(neurons) # record from the first 50 excitatory neurons spike_monitor = SpikeMonitor(neurons[:50]) run(sim_time, report='text') ax_spikes.plot(spike_monitor.t / ms, spike_monitor.i, "|") ax_rates.plot(rate_monitor.t / ms, rate_monitor.rate / Hz) ax_spikes.set_yticks([]) ax_spikes.set_xlim(*params["t_range"]) ax_rates.set_xlim(*params["t_range"]) ax_rates.set_ylim(*params["rate_range"]) ax_rates.set_xlabel("t [ms]") ax_rates.set_yticks( np.arange( params["rate_range"][0], params["rate_range"][1] + rate_tick_step, rate_tick_step ) ) plt.subplots_adjust(hspace=0) parameters = { "A": { "g": 3, "nu_ext_over_nu_thr": 2, "t_range": [500, 600], "rate_range": [0, 6000], "rate_tick_step": 1000, }, "B": { "g": 6, "nu_ext_over_nu_thr": 4, "t_range": [1000, 1200], "rate_range": [0, 400], "rate_tick_step": 100, }, "C": { "g": 5, "nu_ext_over_nu_thr": 2, "t_range": [1000, 1200], "rate_range": [0, 200], "rate_tick_step": 50, }, "D": { "g": 4.5, "nu_ext_over_nu_thr": 0.9, "t_range": [1000, 1200], "rate_range": [0, 250], "rate_tick_step": 50, }, } for panel, params in parameters.items(): fig = plt.figure(figsize=(4, 5)) fig.suptitle(panel) gs = fig.add_gridspec(ncols=1, nrows=2, height_ratios=[4, 1]) ax_spikes, ax_rates = gs.subplots(sharex="col") sim( params["g"], params["nu_ext_over_nu_thr"], params["t_range"][1] * ms, ax_spikes, ax_rates, params["rate_tick_step"], ) plt.show() brian2-2.5.4/examples/frompapers/Brunel_Hakim_1999.py000077500000000000000000000022471445201106100223020ustar00rootroot00000000000000#!/usr/bin/env python """ Dynamics of a network of sparsely connected inhibitory current-based integrate-and-fire neurons. Individual neurons fire irregularly at low rate but the network is in an oscillatory global activity regime where neurons are weakly synchronized. Reference: "Fast Global Oscillations in Networks of Integrate-and-Fire Neurons with Low Firing Rates" Nicolas Brunel & Vincent Hakim Neural Computation 11, 1621-1671 (1999) """ from brian2 import * N = 5000 Vr = 10*mV theta = 20*mV tau = 20*ms delta = 2*ms taurefr = 2*ms duration = .1*second C = 1000 sparseness = float(C)/N J = .1*mV muext = 25*mV sigmaext = 1*mV eqs = """ dV/dt = (-V+muext + sigmaext * sqrt(tau) * xi)/tau : volt """ group = NeuronGroup(N, eqs, threshold='V>theta', reset='V=Vr', refractory=taurefr, method='euler') group.V = Vr conn = Synapses(group, group, on_pre='V += -J', delay=delta) conn.connect(p=sparseness) M = SpikeMonitor(group) LFP = PopulationRateMonitor(group) run(duration) subplot(211) plot(M.t/ms, M.i, '.') xlim(0, duration/ms) subplot(212) plot(LFP.t/ms, LFP.smooth_rate(window='flat', width=0.5*ms)/Hz) xlim(0, duration/ms) show() brian2-2.5.4/examples/frompapers/Brunel_Wang_2001.py000066400000000000000000000141301445201106100221030ustar00rootroot00000000000000#!/usr/bin/env python """ Sample-specific persistent activity ----------------------------------- Five subpopulations with three selective and one reset stimuli example. Analog to figure 6b in the paper. BRUNEL, Nicolas et WANG, Xiao-Jing. Effects of neuromodulation in a cortical network model of object working memory dominated by recurrent inhibition. Journal of computational neuroscience, 2001, vol. 11, no 1, p. 63-85. """ from brian2 import * # populations N = 1000 N_E = int(N * 0.8) # pyramidal neurons N_I = int(N * 0.2) # interneurons # voltage V_L = -70. * mV V_thr = -50. * mV V_reset = -55. * mV V_E = 0. * mV V_I = -70. * mV # membrane capacitance C_m_E = 0.5 * nF C_m_I = 0.2 * nF # membrane leak g_m_E = 25. * nS g_m_I = 20. * nS # refractory period tau_rp_E = 2. * ms tau_rp_I = 1. * ms # external stimuli rate = 3 * Hz C_ext = 800 # synapses C_E = N_E C_I = N_I # AMPA (excitatory) g_AMPA_ext_E = 2.08 * nS g_AMPA_rec_E = 0.104 * nS * 800. / N_E g_AMPA_ext_I = 1.62 * nS g_AMPA_rec_I = 0.081 * nS * 800. / N_E tau_AMPA = 2. * ms # NMDA (excitatory) g_NMDA_E = 0.327 * nS * 800. / N_E g_NMDA_I = 0.258 * nS * 800. / N_E tau_NMDA_rise = 2. * ms tau_NMDA_decay = 100. * ms alpha = 0.5 / ms Mg2 = 1. # GABAergic (inhibitory) g_GABA_E = 1.25 * nS * 200. / N_I g_GABA_I = 0.973 * nS * 200. / N_I tau_GABA = 10. * ms # subpopulations f = 0.1 p = 5 N_sub = int(N_E * f) N_non = int(N_E * (1. - f * p)) w_plus = 2.1 w_minus = 1. - f * (w_plus - 1.) / (1. - f) # modeling eqs_E = ''' dv / dt = (- g_m_E * (v - V_L) - I_syn) / C_m_E : volt (unless refractory) I_syn = I_AMPA_ext + I_AMPA_rec + I_NMDA_rec + I_GABA_rec : amp I_AMPA_ext = g_AMPA_ext_E * (v - V_E) * s_AMPA_ext : amp I_AMPA_rec = g_AMPA_rec_E * (v - V_E) * 1 * s_AMPA : amp ds_AMPA_ext / dt = - s_AMPA_ext / tau_AMPA : 1 ds_AMPA / dt = - s_AMPA / tau_AMPA : 1 I_NMDA_rec = g_NMDA_E * (v - V_E) / (1 + Mg2 * exp(-0.062 * v / mV) / 3.57) * s_NMDA_tot : amp s_NMDA_tot : 1 I_GABA_rec = g_GABA_E * (v - V_I) * s_GABA : amp ds_GABA / dt = - s_GABA / tau_GABA : 1 ''' eqs_I = ''' dv / dt = (- g_m_I * (v - V_L) - I_syn) / C_m_I : volt (unless refractory) I_syn = I_AMPA_ext + I_AMPA_rec + I_NMDA_rec + I_GABA_rec : amp I_AMPA_ext = g_AMPA_ext_I * (v - V_E) * s_AMPA_ext : amp I_AMPA_rec = g_AMPA_rec_I * (v - V_E) * 1 * s_AMPA : amp ds_AMPA_ext / dt = - s_AMPA_ext / tau_AMPA : 1 ds_AMPA / dt = - s_AMPA / tau_AMPA : 1 I_NMDA_rec = g_NMDA_I * (v - V_E) / (1 + Mg2 * exp(-0.062 * v / mV) / 3.57) * s_NMDA_tot : amp s_NMDA_tot : 1 I_GABA_rec = g_GABA_I * (v - V_I) * s_GABA : amp ds_GABA / dt = - s_GABA / tau_GABA : 1 ''' P_E = NeuronGroup(N_E, eqs_E, threshold='v > V_thr', reset='v = V_reset', refractory=tau_rp_E, method='euler') P_E.v = V_L P_I = NeuronGroup(N_I, eqs_I, threshold='v > V_thr', reset='v = V_reset', refractory=tau_rp_I, method='euler') P_I.v = V_L eqs_glut = ''' s_NMDA_tot_post = w * s_NMDA : 1 (summed) ds_NMDA / dt = - s_NMDA / tau_NMDA_decay + alpha * x * (1 - s_NMDA) : 1 (clock-driven) dx / dt = - x / tau_NMDA_rise : 1 (clock-driven) w : 1 ''' eqs_pre_glut = ''' s_AMPA += w x += 1 ''' eqs_pre_gaba = ''' s_GABA += 1 ''' eqs_pre_ext = ''' s_AMPA_ext += 1 ''' # E to E C_E_E = Synapses(P_E, P_E, model=eqs_glut, on_pre=eqs_pre_glut, method='euler') C_E_E.connect('i != j') C_E_E.w[:] = 1 for pi in range(N_non, N_non + p * N_sub, N_sub): # internal other subpopulation to current nonselective C_E_E.w[C_E_E.indices[:, pi:pi + N_sub]] = w_minus # internal current subpopulation to current subpopulation C_E_E.w[C_E_E.indices[pi:pi + N_sub, pi:pi + N_sub]] = w_plus # E to I C_E_I = Synapses(P_E, P_I, model=eqs_glut, on_pre=eqs_pre_glut, method='euler') C_E_I.connect() C_E_I.w[:] = 1 # I to I C_I_I = Synapses(P_I, P_I, on_pre=eqs_pre_gaba, method='euler') C_I_I.connect('i != j') # I to E C_I_E = Synapses(P_I, P_E, on_pre=eqs_pre_gaba, method='euler') C_I_E.connect() # external noise C_P_E = PoissonInput(P_E, 's_AMPA_ext', C_ext, rate, '1') C_P_I = PoissonInput(P_I, 's_AMPA_ext', C_ext, rate, '1') # at 1s, select population 1 C_selection = int(f * C_ext) rate_selection = 25 * Hz stimuli1 = TimedArray(np.r_[np.zeros(40), np.ones(2), np.zeros(100)], dt=25 * ms) input1 = PoissonInput(P_E[N_non:N_non + N_sub], 's_AMPA_ext', C_selection, rate_selection, 'stimuli1(t)') # at 2s, select population 2 stimuli2 = TimedArray(np.r_[np.zeros(80), np.ones(2), np.zeros(100)], dt=25 * ms) input2 = PoissonInput(P_E[N_non + N_sub:N_non + 2 * N_sub], 's_AMPA_ext', C_selection, rate_selection, 'stimuli2(t)') # at 4s, reset selection stimuli_reset = TimedArray(np.r_[np.zeros(120), np.ones(2), np.zeros(100)], dt=25 * ms) input_reset_I = PoissonInput(P_E, 's_AMPA_ext', C_ext, rate_selection, 'stimuli_reset(t)') input_reset_E = PoissonInput(P_I, 's_AMPA_ext', C_ext, rate_selection, 'stimuli_reset(t)') # monitors N_activity_plot = 15 sp_E_sels = [SpikeMonitor(P_E[pi:pi + N_activity_plot]) for pi in range(N_non, N_non + p * N_sub, N_sub)] sp_E = SpikeMonitor(P_E[:N_activity_plot]) sp_I = SpikeMonitor(P_I[:N_activity_plot]) r_E_sels = [PopulationRateMonitor(P_E[pi:pi + N_sub]) for pi in range(N_non, N_non + p * N_sub, N_sub)] r_E = PopulationRateMonitor(P_E[:N_non]) r_I = PopulationRateMonitor(P_I) # simulate, can be long >120s net = Network(collect()) net.add(sp_E_sels) net.add(r_E_sels) net.run(4 * second, report='stdout') # plotting title('Population rates') xlabel('ms') ylabel('Hz') plot(r_E.t / ms, r_E.smooth_rate(width=25 * ms) / Hz, label='nonselective') plot(r_I.t / ms, r_I.smooth_rate(width=25 * ms) / Hz, label='inhibitory') for i, r_E_sel in enumerate(r_E_sels[::-1]): plot(r_E_sel.t / ms, r_E_sel.smooth_rate(width=25 * ms) / Hz, label=f"selective {p - i}") legend() figure() title(f"Population activities ({N_activity_plot} neurons/pop)") xlabel('ms') yticks([]) plot(sp_E.t / ms, sp_E.i + (p + 1) * N_activity_plot, '.', markersize=2, label="nonselective") plot(sp_I.t / ms, sp_I.i + p * N_activity_plot, '.', markersize=2, label="inhibitory") for i, sp_E_sel in enumerate(sp_E_sels[::-1]): plot(sp_E_sel.t / ms, sp_E_sel.i + (p - i - 1) * N_activity_plot, '.', markersize=2, label=f"selective {p - i}") legend() show() brian2-2.5.4/examples/frompapers/Clopath_et_al_2010_homeostasis.py000066400000000000000000000166501445201106100250650ustar00rootroot00000000000000#!/usr/bin/env python """ This code contains an adapted version of the voltage-dependent triplet STDP rule from: Clopath et al., Connectivity reflects coding: a model of voltage-based STDP with homeostasis, Nature Neuroscience, 2010 (http://dx.doi.org/10.1038/nn.2479) The plasticity rule is adapted for a leaky integrate & fire model in Brian2. More specifically, the filters ``v_lowpass1`` and ``v_lowpass2`` are incremented by a constant at every post-synaptic spike time, to compensate for the lack of an actual spike in the integrate & fire model. As an illustration of the rule, we simulate the competition between inputs projecting on a downstream neuron. We would like to note that the parameters have been chosen arbitrarily to qualitatively reproduce the behavior of the original work, but need additional fitting. We kindly ask to cite the article when using the model presented below. This code was written by Jacopo Bono, 12/2015 """ from brian2 import * ################################################################################ # PLASTICITY MODEL ################################################################################ #### Plasticity Parameters V_rest = -70.*mV # resting potential V_thresh = -55.*mV # spiking threshold Theta_low = V_rest # depolarization threshold for plasticity x_reset = 1. # spike trace reset value taux = 15.*ms # spike trace time constant A_LTD = 1.5e-4 # depression amplitude A_LTP = 1.5e-2 # potentiation amplitude tau_lowpass1 = 40*ms # timeconstant for low-pass filtered voltage tau_lowpass2 = 30*ms # timeconstant for low-pass filtered voltage tau_homeo = 1000*ms # homeostatic timeconstant v_target = 12*mV**2 # target depolarisation #### Plasticity Equations # equations executed at every timestepC Syn_model = (''' w_ampa:1 # synaptic weight (ampa synapse) ''') # equations executed only when a presynaptic spike occurs Pre_eq = (''' g_ampa_post += w_ampa*ampa_max_cond # increment synaptic conductance A_LTD_u = A_LTD*(v_homeo**2/v_target) # metaplasticity w_minus = A_LTD_u*(v_lowpass1_post/mV - Theta_low/mV)*int(v_lowpass1_post/mV - Theta_low/mV > 0) # synaptic depression w_ampa = clip(w_ampa-w_minus, 0, w_max) # hard bounds ''' ) # equations executed only when a postsynaptic spike occurs Post_eq = (''' v_lowpass1 += 10*mV # mimics the depolarisation effect due to a spike v_lowpass2 += 10*mV # mimics the depolarisation effect due to a spike v_homeo += 0.1*mV # mimics the depolarisation effect due to a spike w_plus = A_LTP*x_trace_pre*(v_lowpass2_post/mV - Theta_low/mV)*int(v_lowpass2_post/mV - Theta_low/mV > 0) # synaptic potentiation w_ampa = clip(w_ampa+w_plus, 0, w_max) # hard bounds ''' ) ################################################################################ # I&F Parameters and equations ################################################################################ #### Neuron parameters gleak = 30.*nS # leak conductance C = 300.*pF # membrane capacitance tau_AMPA = 2.*ms # AMPA synaptic timeconstant E_AMPA = 0.*mV # reversal potential AMPA ampa_max_cond = 5.e-8*siemens # Ampa maximal conductance w_max = 1. # maximal ampa weight #### Neuron Equations # We connect 10 presynaptic neurons to 1 downstream neuron # downstream neuron eqs_neurons = ''' dv/dt = (gleak*(V_rest-v) + I_ext + I_syn)/C: volt # voltage dv_lowpass1/dt = (v-v_lowpass1)/tau_lowpass1 : volt # low-pass filter of the voltage dv_lowpass2/dt = (v-v_lowpass2)/tau_lowpass2 : volt # low-pass filter of the voltage dv_homeo/dt = (v-V_rest-v_homeo)/tau_homeo : volt # low-pass filter of the voltage I_ext : amp # external current I_syn = g_ampa*(E_AMPA-v): amp # synaptic current dg_ampa/dt = -g_ampa/tau_AMPA : siemens # synaptic conductance dx_trace/dt = -x_trace/taux :1 # spike trace ''' # input neurons eqs_inputs = ''' dv/dt = gleak*(V_rest-v)/C: volt # voltage dx_trace/dt = -x_trace/taux :1 # spike trace rates : Hz # input rates selected_index : integer (shared) # active neuron ''' ################################################################################ # Simulation ################################################################################ #### Parameters defaultclock.dt = 500.*us # timestep Nr_neurons = 1 # Number of downstream neurons Nr_inputs = 5 # Number of input neurons input_rate = 35*Hz # Rates init_weight = 0.5 # initial synaptic weight final_t = 20.*second # end of simulation input_time = 100.*ms # duration of an input #### Create neuron objects Nrn_downstream = NeuronGroup(Nr_neurons, eqs_neurons, threshold='v>V_thresh', reset='v=V_rest;x_trace+=x_reset/(taux/ms)', method='euler') Nrns_input = NeuronGroup(Nr_inputs, eqs_inputs, threshold='rand() 0) # synaptic depression w_ampa = clip(w_ampa-w_minus,0,w_max) # hard bounds ''' # equations executed only when a postsynaptic spike occurs Post_eq = ''' v_lowpass1 += 10*mV # mimics the depolarisation by a spike v_lowpass2 += 10*mV # mimics the depolarisation by a spike w_plus = A_LTP*x_trace_pre*(v_lowpass2_post/mV - Theta_low/mV)*int(v_lowpass2_post/mV - Theta_low/mV > 0) # synaptic potentiation w_ampa = clip(w_ampa+w_plus,0,w_max) # hard bounds ''' ################################################################################ # I&F Parameters and equations ################################################################################ #### Neuron parameters gleak = 30.*nS # leak conductance C = 300.*pF # membrane capacitance tau_AMPA = 2.*ms # AMPA synaptic timeconstant E_AMPA = 0.*mV # reversal potential AMPA ampa_max_cond = 5.e-10*siemens # Ampa maximal conductance w_max = 1. # maximal ampa weight #### Neuron Equations eqs_neurons = ''' dv/dt = (gleak*(V_rest-v) + I_ext + I_syn)/C: volt # voltage dv_lowpass1/dt = (v-v_lowpass1)/tau_lowpass1 : volt # low-pass filter of the voltage dv_lowpass2/dt = (v-v_lowpass2)/tau_lowpass2 : volt # low-pass filter of the voltage I_ext : amp # external current I_syn = g_ampa*(E_AMPA-v): amp # synaptic current dg_ampa/dt = -g_ampa/tau_AMPA : siemens # synaptic conductance dx_trace/dt = -x_trace/taux :1 # spike trace ''' ################################################################################ # Simulation ################################################################################ #### Parameters defaultclock.dt = 100.*us # timestep Nr_neurons = 2 # Number of neurons rate_array = [1., 5., 10., 15., 20., 30., 50.]*Hz # Rates init_weight = 0.5 # initial synaptic weight reps = 15 # Number of pairings #### Create neuron objects Nrns = NeuronGroup(Nr_neurons, eqs_neurons, threshold='v>V_thresh', reset='v=V_rest;x_trace+=x_reset/(taux/ms)', method='euler')# #### create Synapses Syn = Synapses(Nrns, Nrns, model=Syn_model, on_pre=Pre_eq, on_post=Post_eq ) Syn.connect('i!=j') #### Monitors and storage weight_result = np.zeros((2, len(rate_array))) # to save the final weights #### Run # loop over rates for jj, rate in enumerate(rate_array): # Calculate interval between pairs pair_interval = 1./rate - 10*ms print('Starting simulations for %s' % rate) # Initial values Nrns.v = V_rest Nrns.v_lowpass1 = V_rest Nrns.v_lowpass2 = V_rest Nrns.I_ext = 0*amp Nrns.x_trace = 0. Syn.w_ampa = init_weight # loop over pairings for ii in range(reps): # 1st SPIKE Nrns.v[0] = V_thresh + 1*mV # 2nd SPIKE run(10*ms) Nrns.v[1] = V_thresh + 1*mV # run run(pair_interval) print('Pair %d out of %d' % (ii+1, reps)) #store weight changes weight_result[0, jj] = 100.*Syn.w_ampa[0]/init_weight weight_result[1, jj] = 100.*Syn.w_ampa[1]/init_weight ################################################################################ # Plots ################################################################################ stitle = 'Pairings' scolor = 'k' figure(figsize=(8, 5)) plot(rate_array, weight_result[0,:], '-', linewidth=2, color=scolor) plot(rate_array, weight_result[1,:], ':', linewidth=2, color=scolor) xlabel('Pairing frequency [Hz]', fontsize=22) ylabel('Normalised Weight [%]', fontsize=22) legend(['Pre-Post', 'Post-Pre'], loc='best') subplots_adjust(bottom=0.2, left=0.15, right=0.95, top=0.85) title(stitle) show() brian2-2.5.4/examples/frompapers/Destexhe_et_al_1998.py000066400000000000000000000215601445201106100226520ustar00rootroot00000000000000""" Reproduces Figure 12 (simplified three-compartment model) from the following paper: Dendritic Low-Threshold Calcium Currents in Thalamic Relay Cells Alain Destexhe, Mike Neubig, Daniel Ulrich, John Huguenard Journal of Neuroscience 15 May 1998, 18 (10) 3574-3588 The original NEURON code is available on ModelDB: https://senselab.med.yale.edu/modeldb/ShowModel.cshtml?model=279 Reference for the original morphology: Rat VB neuron (thalamocortical cell), given by J. Huguenard, stained with biocytin and traced by A. Destexhe, December 1992. The neuron is described in: J.R. Huguenard & D.A. Prince, A novel T-type current underlies prolonged calcium-dependent burst firing in GABAergic neurons of rat thalamic reticular nucleus. J. Neurosci. 12: 3804-3817, 1992. Available at NeuroMorpho.org: http://neuromorpho.org/neuron_info.jsp?neuron_name=tc200 NeuroMorpho.Org ID :NMO_00881 Notes ----- * Completely removed the "Fast mechanism for submembranal Ca++ concentration (cai)" -- it did not affect the results presented here * Time constants for the I_T current are slightly different from the equations given in the paper -- the paper calculation seems to be based on 36 degree Celsius but the temperature that is used is 34 degrees. * To reproduce Figure 12C, the "presence of dendritic shunt conductances" meant setting g_L to 0.15 mS/cm^2 for the whole neuron. * Other small discrepancies with the paper -- values from the NEURON code were used whenever different from the values stated in the paper """ from brian2 import * from brian2.units.constants import (zero_celsius, faraday_constant as F, gas_constant as R) defaultclock.dt = 0.01*ms VT = -52*mV El = -76.5*mV # from code, text says: -69.85*mV E_Na = 50*mV E_K = -100*mV C_d = 7.954 # dendritic correction factor T = 34*kelvin + zero_celsius # 34 degC (current-clamp experiments) tadj_HH = 3.0**((34-36)/10.0) # temperature adjustment for Na & K (original recordings at 36 degC) tadj_m_T = 2.5**((34-24)/10.0) tadj_h_T = 2.5**((34-24)/10.0) shift_I_T = -1*mV gamma = F/(R*T) # R=gas constant, F=Faraday constant Z_Ca = 2 # Valence of Calcium ions Ca_i = 240*nM # intracellular Calcium concentration Ca_o = 2*mM # extracellular Calcium concentration eqs = Equations(''' Im = gl*(El-v) - I_Na - I_K - I_T: amp/meter**2 I_inj : amp (point current) gl : siemens/meter**2 # HH-type currents for spike initiation g_Na : siemens/meter**2 g_K : siemens/meter**2 I_Na = g_Na * m**3 * h * (v-E_Na) : amp/meter**2 I_K = g_K * n**4 * (v-E_K) : amp/meter**2 v2 = v - VT : volt # shifted membrane potential (Traub convention) dm/dt = (0.32*(mV**-1)*(13.*mV-v2)/ (exp((13.*mV-v2)/(4.*mV))-1.)*(1-m)-0.28*(mV**-1)*(v2-40.*mV)/ (exp((v2-40.*mV)/(5.*mV))-1.)*m) / ms * tadj_HH: 1 dn/dt = (0.032*(mV**-1)*(15.*mV-v2)/ (exp((15.*mV-v2)/(5.*mV))-1.)*(1.-n)-.5*exp((10.*mV-v2)/(40.*mV))*n) / ms * tadj_HH: 1 dh/dt = (0.128*exp((17.*mV-v2)/(18.*mV))*(1.-h)-4./(1+exp((40.*mV-v2)/(5.*mV)))*h) / ms * tadj_HH: 1 # Low-threshold Calcium current (I_T) -- nonlinear function of voltage I_T = P_Ca * m_T**2*h_T * G_Ca : amp/meter**2 P_Ca : meter/second # maximum Permeability to Calcium G_Ca = Z_Ca**2*F*v*gamma*(Ca_i - Ca_o*exp(-Z_Ca*gamma*v))/(1 - exp(-Z_Ca*gamma*v)) : coulomb/meter**3 dm_T/dt = -(m_T - m_T_inf)/tau_m_T : 1 dh_T/dt = -(h_T - h_T_inf)/tau_h_T : 1 m_T_inf = 1/(1 + exp(-(v/mV + 56)/6.2)) : 1 h_T_inf = 1/(1 + exp((v/mV + 80)/4)) : 1 tau_m_T = (0.612 + 1.0/(exp(-(v/mV + 131)/16.7) + exp((v/mV + 15.8)/18.2))) * ms / tadj_m_T: second tau_h_T = (int(v<-81*mV) * exp((v/mV + 466)/66.6) + int(v>=-81*mV) * (28 + exp(-(v/mV + 21)/10.5))) * ms / tadj_h_T: second ''') # Simplified three-compartment morphology morpho = Cylinder(x=[0, 38.42]*um, diameter=26*um) morpho.dend = Cylinder(x=[0, 12.49]*um, diameter=10.28*um) morpho.dend.distal = Cylinder(x=[0, 84.67]*um, diameter=8.5*um) neuron = SpatialNeuron(morpho, eqs, Cm=0.88*uF/cm**2, Ri=173*ohm*cm, method='exponential_euler') neuron.v = -74*mV # Only the soma has Na/K channels neuron.main.g_Na = 100*msiemens/cm**2 neuron.main.g_K = 100*msiemens/cm**2 # Apply the correction factor to the dendrites neuron.dend.Cm *= C_d neuron.m_T = 'm_T_inf' neuron.h_T = 'h_T_inf' mon = StateMonitor(neuron, ['v'], record=True) store('initial state') def do_experiment(currents, somatic_density, dendritic_density, dendritic_conductance=0.0379*msiemens/cm**2, HH_currents=True): restore('initial state') voltages = [] neuron.P_Ca = somatic_density neuron.dend.distal.P_Ca = dendritic_density * C_d # dendritic conductance (shunting conductance used for Fig 12C) neuron.gl = dendritic_conductance neuron.dend.gl = dendritic_conductance * C_d if not HH_currents: # Shut off spiking (for Figures 12B and 12C) neuron.g_Na = 0*msiemens/cm**2 neuron.g_K = 0*msiemens/cm**2 run(180*ms) store('before current') for current in currents: restore('before current') neuron.main.I_inj = current print('.', end='') run(320*ms) voltages.append(mon[morpho].v[:]) # somatic voltage return voltages ## Run the various variants of the model to reproduce Figure 12 mpl.rcParams['lines.markersize'] = 3.0 fig, axes = plt.subplots(2, 2) print('Running experiments for Figure A1 ', end='') voltages = do_experiment([50, 75]*pA, somatic_density=1.7e-5*cm/second, dendritic_density=1.7e-5*cm/second) print(' done.') cut_off = 100*ms # Do not display first part of simulation axes[0, 0].plot((mon.t - cut_off) / ms, voltages[0] / mV, color='gray') axes[0, 0].plot((mon.t - cut_off) / ms, voltages[1] / mV, color='black') axes[0, 0].set(xlim=(0, 400), ylim=(-80, 40), xticks=[], title='A1: Uniform T-current density', ylabel='Voltage (mV)') axes[0, 0].spines['right'].set_visible(False) axes[0, 0].spines['top'].set_visible(False) axes[0, 0].spines['bottom'].set_visible(False) print('Running experiments for Figure A2 ', end='') voltages = do_experiment([50, 75]*pA, somatic_density=1.7e-5*cm/second, dendritic_density=9.5e-5*cm/second) print(' done.') cut_off = 100*ms # Do not display first part of simulation axes[1, 0].plot((mon.t - cut_off) / ms, voltages[0] / mV, color='gray') axes[1, 0].plot((mon.t - cut_off) / ms, voltages[1] / mV, color='black') axes[1, 0].set(xlim=(0, 400), ylim=(-80, 40), title='A2: High T-current density in dendrites', xlabel='Time (ms)', ylabel='Voltage (mV)') axes[1, 0].spines['right'].set_visible(False) axes[1, 0].spines['top'].set_visible(False) print('Running experiments for Figure B ', end='') currents = np.linspace(0, 200, 41)*pA voltages_somatic = do_experiment(currents, somatic_density=56.36e-5*cm/second, dendritic_density=0*cm/second, HH_currents=False) voltages_somatic_dendritic = do_experiment(currents, somatic_density=1.7e-5*cm/second, dendritic_density=9.5e-5*cm/second, HH_currents=False) print(' done.') maxima_somatic = Quantity(voltages_somatic).max(axis=1) maxima_somatic_dendritic = Quantity(voltages_somatic_dendritic).max(axis=1) axes[0, 1].yaxis.tick_right() axes[0, 1].plot(currents/pA, maxima_somatic/mV, 'o-', color='black', label='Somatic only') axes[0, 1].plot(currents/pA, maxima_somatic_dendritic/mV, 's-', color='black', label='Somatic & dendritic') axes[0, 1].set(xlabel='Injected current (pA)', ylabel='Peak LTS (mV)', ylim=(-80, 0)) axes[0, 1].legend(loc='best', frameon=False) print('Running experiments for Figure C ', end='') currents = np.linspace(200, 400, 41)*pA voltages_somatic = do_experiment(currents, somatic_density=56.36e-5*cm/second, dendritic_density=0*cm/second, dendritic_conductance=0.15*msiemens/cm**2, HH_currents=False) voltages_somatic_dendritic = do_experiment(currents, somatic_density=1.7e-5*cm/second, dendritic_density=9.5e-5*cm/second, dendritic_conductance=0.15*msiemens/cm**2, HH_currents=False) print(' done.') maxima_somatic = Quantity(voltages_somatic).max(axis=1) maxima_somatic_dendritic = Quantity(voltages_somatic_dendritic).max(axis=1) axes[1, 1].yaxis.tick_right() axes[1, 1].plot(currents/pA, maxima_somatic/mV, 'o-', color='black', label='Somatic only') axes[1, 1].plot(currents/pA, maxima_somatic_dendritic/mV, 's-', color='black', label='Somatic & dendritic') axes[1, 1].set(xlabel='Injected current (pA)', ylabel='Peak LTS (mV)', ylim=(-80, 0)) axes[1, 1].legend(loc='best', frameon=False) plt.tight_layout() plt.show() brian2-2.5.4/examples/frompapers/Diesmann_et_al_1999.py000077500000000000000000000026341445201106100226440ustar00rootroot00000000000000#!/usr/bin/env python """ Synfire chains -------------- M. Diesmann et al. (1999). Stable propagation of synchronous spiking in cortical neural networks. Nature 402, 529-533. """ from brian2 import * duration = 100*ms # Neuron model parameters Vr = -70*mV Vt = -55*mV taum = 10*ms taupsp = 0.325*ms weight = 4.86*mV # Neuron model eqs = Equations(''' dV/dt = (-(V-Vr)+x)*(1./taum) : volt dx/dt = (-x+y)*(1./taupsp) : volt dy/dt = -y*(1./taupsp)+25.27*mV/ms+ (39.24*mV/ms**0.5)*xi : volt ''') # Neuron groups n_groups = 10 group_size = 100 P = NeuronGroup(N=n_groups*group_size, model=eqs, threshold='V>Vt', reset='V=Vr', refractory=1*ms, method='euler') Pinput = SpikeGeneratorGroup(85, np.arange(85), np.random.randn(85)*1*ms + 50*ms) # The network structure S = Synapses(P, P, on_pre='y+=weight') S.connect(j='k for k in range((int(i/group_size)+1)*group_size, (int(i/group_size)+2)*group_size) ' 'if i{STDP_DT_MAX/ms}*ms", refractory=1 * second) H = NeuronGroup( REPETITIONS * 2, "tspike:second", threshold="t>tspike", refractory=1 * second ) H.tspike = [point_index * STDP_DT_STEP] * REPETITIONS * 2 synapses_eqs = """ tau : second (constant, shared) rho_star : 1 (constant, shared) gamma_p : 1 (constant, shared) theta_p : 1 (constant, shared) gamma_d : 1 (constant, shared) theta_d : 1 (constant, shared) drho/dt = (-rho*(1-rho)*(rho_star-rho) + gamma_p*(1-rho)*int((c - theta_p) > 0) - gamma_d*rho*int((c-theta_d) > 0) + sigma*sqrt(tau)*sqrt(int((c-theta_d) > 0) + int((c-theta_p) > 0))*xi ) / tau : 1 (clock-driven) dc/dt = -c/tau_Ca : 1 (clock-driven) tau_Ca : second (constant, shared) sigma : 1 (constant, shared) """ C_pre = 1 C_post = 2 D = 13.7 * ms synapses = Synapses( G, H, model=synapses_eqs, on_pre="c += C_pre", on_post="c += C_post", delay=D, method="heun", ) synapses.connect() synapses.tau_Ca = 20 * ms synapses.theta_d = 1 synapses.theta_p = 1.3 synapses.gamma_d = 200 synapses.gamma_p = 321.808 synapses.sigma = 2.8284 synapses.tau = 150 * second synapses.rho_star = 0.5 # start with equal number of synapses in DOWN and UP state # must match b in analysis below rho_initial = np.array([0] * REPETITIONS + [1] * REPETITIONS) synapses.rho = rho_initial def report_callback(elapsed, completed, start, duration): print( f"time difference {(point_index*STDP_DT_STEP - STDP_DT_MAX)/ms:.0f} ms is {completed:2.0%} done" ) run(60 * second, report=report_callback) return synapses.rho[:], rho_initial if __name__ == "__main__": with multiprocessing.Pool() as p: results = p.map(run_sim, range(POINTS)) # initial fraction of synapses in DOWN state beta = 0.5 # ratio of UP and DOWN state weights (w1/w0) b = 5 change_in_syn_strengths = [] for rhos, rhos_initial in results: # average switching probabilities U = np.mean(rhos[rhos_initial < 0.5] > 0.5) D = np.mean(rhos[rhos_initial > 0.5] < 0.5) change_in_syn_strength = ( (1 - U) * beta + D * (1 - beta) + b * (U * beta + (1 - D) * (1 - beta)) ) / (beta + (1 - beta) * b) change_in_syn_strengths.append(change_in_syn_strength) stdp_dts = [ point_index * STDP_DT_STEP - STDP_DT_MAX for point_index in range(POINTS) ] plt.axvline(0, linestyle="dashed", color="k") plt.axhline(1, linestyle="dashed", color="k") plt.plot(stdp_dts / ms, change_in_syn_strengths, marker="o", linestyle="None") plt.xlim( (STDP_DT_MIN - STDP_DT_STEP / 2) / ms, (STDP_DT_MAX + STDP_DT_STEP / 2) / ms ) plt.ylim(0.3, 1.7) plt.xlabel(r"time difference $\Delta$t (ms)") plt.ylabel("change in synaptic strength (after/before)") plt.show() brian2-2.5.4/examples/frompapers/Hindmarsh_Rose_1984.py000066400000000000000000000032151445201106100226320ustar00rootroot00000000000000# encoding: utf8 """ Burst generation in the Hinsmarsh-Rose model. Reproduces Figure 6 of: Hindmarsh, J. L., and R. M. Rose. “A Model of Neuronal Bursting Using Three Coupled First Order Differential Equations.” Proceedings of the Royal Society of London. Series B, Biological Sciences 221, no. 1222 (1984): 87–102. """ from brian2 import * # In the original model, time is measured in arbitrary time units time_unit = 1*ms defaultclock.dt = time_unit/10 x_1 = -1.6 # leftmost equilibrium point of the model without adaptation a = 1; b = 3; c = 1; d = 5 r = 0.001; s = 4 eqs = ''' dx/dt = (y - a*x**3 + b*x**2 + I - z)/time_unit : 1 dy/dt = (c - d*x**2 - y)/time_unit : 1 dz/dt = r*(s*(x - x_1) - z)/time_unit : 1 I : 1 (constant) ''' # We run the model with three different currents neuron = NeuronGroup(3, eqs, method='rk4') # Set all variables to their equilibrium point neuron.x = x_1 neuron.y = 'c - d*x**2' neuron.z = 'r*(s*(x - x_1))' # Set the constant current input neuron.I = [0.4, 2, 4] # Record the "membrane potential" mon = StateMonitor(neuron, 'x', record=True) run(2100*time_unit) ax_top = plt.subplot2grid((2, 3), (0, 0), colspan=3) ax_bottom_l = plt.subplot2grid((2, 3), (1, 0), colspan=2) ax_bottom_r = plt.subplot2grid((2, 3), (1, 2)) for ax in [ax_top, ax_bottom_l, ax_bottom_r]: ax.spines['top'].set_visible(False) ax.spines['right'].set_visible(False) ax.set(ylim=(-2, 2), yticks=[-2, 0, 2]) ax_top.plot(mon.t/time_unit, mon.x[0]) ax_bottom_l.plot(mon.t/time_unit, mon.x[1]) ax_bottom_l.set_xlim(700, 2100) ax_bottom_r.plot(mon.t/time_unit, mon.x[2]) ax_bottom_r.set_xlim(1400, 2100) ax_bottom_r.set_yticks([]) plt.show() brian2-2.5.4/examples/frompapers/Izhikevich_2003.py000066400000000000000000000050271445201106100220040ustar00rootroot00000000000000#!/usr/bin/env python3 """ Fig. 3 of Simple Model of Spiking Neurons IEEE Transactions on Neural Networks ( Volume: 14, Issue: 6, Nov. 2003) Eugene M. Izhikevich based on net.m by Eugene M. Izhikevich (http://izhikevich.org/publications/spikes.htm) Akif Erdem Sağtekin and Sebastian Schmitt, 2022 """ import matplotlib.pyplot as plt import numpy as np from brian2 import NeuronGroup, Synapses, SpikeMonitor, StateMonitor from brian2 import ms, mV from brian2 import defaultclock, run tfinal = 1000 * ms Ne = 800 Ni = 200 re = np.random.uniform(size=Ne) ri = np.random.uniform(size=Ni) weights = np.hstack( [ 0.5 * np.random.uniform(size=(Ne + Ni, Ne)), -np.random.uniform(size=(Ne + Ni, Ni)), ] ).T defaultclock.dt = 1 * ms eqs = """dv/dt = (0.04*v**2 + 5*v + 140 - u + I + I_noise )/ms : 1 du/dt = (a*(b*v - u))/ms : 1 I : 1 I_noise : 1 a : 1 b : 1 c : 1 d : 1 """ N = NeuronGroup(Ne + Ni, eqs, threshold="v>=30", reset="v=c; u+=d", method="euler") N.v = -65 N_exc = N[:Ne] N_inh = N[Ne:] spikemon = SpikeMonitor(N) statemon = StateMonitor(N, 'v', record=0, when='after_thresholds') N_exc.a = 0.02 N_exc.b = 0.2 N_exc.c = -65 + 15 * re**2 N_exc.d = 8 - 6 * re**2 N_inh.a = 0.02 + 0.08 * ri N_inh.b = 0.25 - 0.05 * ri N_inh.c = -65 N_inh.d = 2 N_exc.u = "b*v" N_inh.u = "b*v" S = Synapses( N, N, "w : 1", on_pre={"up": "I += w", "down": "I -= w"}, delay={"up": 0 * ms, "down": 1 * ms}, ) S.connect() S.w[:] = weights.flatten() N_exc.run_regularly("I_noise = 5*randn()", dt=1 * ms) N_inh.run_regularly("I_noise = 2*randn()", dt=1 * ms) run(tfinal) fig, (ax, ax_voltage) = plt.subplots(2, 1, sharex=True, gridspec_kw={'height_ratios': (3, 1)}) ax.scatter(spikemon.t / ms, spikemon.i[:], marker="_", color="k", s=10) ax.set_xlim(0, tfinal / ms) ax.set_ylim(0, len(N)) ax.set_ylabel("neuron number") ax.set_yticks(np.arange(0, len(N), 100)) ax.spines['right'].set_visible(False) ax.spines['top'].set_visible(False) ax.axhline(Ne, color="k") ax.text(500, 900, 'inhibitory', backgroundcolor='w', color='k', ha='center') ax.text(500, 400, 'excitatory', backgroundcolor='w', color='k', ha='center') ax_voltage.plot(statemon.t / ms, np.clip(statemon.v[0], -np.inf, 30), color='k') ax_voltage.text(25, 0, 'v₁(t)') ax_voltage.set_xticks(np.arange(0, tfinal / ms, 100)) ax_voltage.spines['right'].set_visible(False) ax_voltage.spines['top'].set_visible(False) ax_voltage.set_xlabel("time, ms") plt.show() brian2-2.5.4/examples/frompapers/Izhikevich_2007.py000066400000000000000000000105611445201106100220070ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """ STDP modulated with reward Adapted from Fig. 1c of: Eugene M. Izhikevich Solving the distal reward problem through linkage of STDP and dopamine signaling. Cerebral cortex 17, no. 10 (2007): 2443-2452. Note: The variable "mode" can switch the behavior of the synapse from "Classical STDP" to "Dopamine modulated STDP". Author: Guillaume Dumas (Institut Pasteur) Date: 2018-08-24 """ from brian2 import * # Parameters simulation_duration = 6 * second ## Neurons taum = 10*ms Ee = 0*mV vt = -54*mV vr = -60*mV El = -74*mV taue = 5*ms ## STDP taupre = 20*ms taupost = taupre gmax = .01 dApre = .01 dApost = -dApre * taupre / taupost * 1.05 dApost *= gmax dApre *= gmax ## Dopamine signaling tauc = 1000*ms taud = 200*ms taus = 1*ms epsilon_dopa = 5e-3 # Setting the stage ## Stimuli section input_indices = array([0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0]) input_times = array([ 500, 550, 1000, 1010, 1500, 1510, 3500, 3550, 4000, 4010, 4500, 4510])*ms spike_input = SpikeGeneratorGroup(2, input_indices, input_times) neurons = NeuronGroup(2, '''dv/dt = (ge * (Ee-vr) + El - v) / taum : volt dge/dt = -ge / taue : 1''', threshold='v>vt', reset='v = vr', method='exact') neurons.v = vr neurons_monitor = SpikeMonitor(neurons) synapse = Synapses(spike_input, neurons, model='''s: volt''', on_pre='v += s') synapse.connect(i=[0, 1], j=[0, 1]) synapse.s = 100. * mV ## STDP section synapse_stdp = Synapses(neurons, neurons, model='''mode: 1 dc/dt = -c / tauc : 1 (clock-driven) dd/dt = -d / taud : 1 (clock-driven) ds/dt = mode * c * d / taus : 1 (clock-driven) dApre/dt = -Apre / taupre : 1 (event-driven) dApost/dt = -Apost / taupost : 1 (event-driven)''', on_pre='''ge += s Apre += dApre c = clip(c + mode * Apost, -gmax, gmax) s = clip(s + (1-mode) * Apost, -gmax, gmax) ''', on_post='''Apost += dApost c = clip(c + mode * Apre, -gmax, gmax) s = clip(s + (1-mode) * Apre, -gmax, gmax) ''', method='euler' ) synapse_stdp.connect(i=0, j=1) synapse_stdp.mode = 0 synapse_stdp.s = 1e-10 synapse_stdp.c = 1e-10 synapse_stdp.d = 0 synapse_stdp_monitor = StateMonitor(synapse_stdp, ['s', 'c', 'd'], record=[0]) ## Dopamine signaling section dopamine_indices = array([0, 0, 0]) dopamine_times = array([3520, 4020, 4520])*ms dopamine = SpikeGeneratorGroup(1, dopamine_indices, dopamine_times) dopamine_monitor = SpikeMonitor(dopamine) reward = Synapses(dopamine, synapse_stdp, model='''''', on_pre='''d_post += epsilon_dopa''', method='exact') reward.connect() # Simulation ## Classical STDP synapse_stdp.mode = 0 run(simulation_duration/2) ## Dopamine modulated STDP synapse_stdp.mode = 1 run(simulation_duration/2) # Visualisation dopamine_indices, dopamine_times = dopamine_monitor.it neurons_indices, neurons_times = neurons_monitor.it figure(figsize=(12, 6)) subplot(411) plot([0.05, 2.95], [2.7, 2.7], linewidth=5, color='k') text(1.5, 3, 'Classical STDP', horizontalalignment='center', fontsize=20) plot([3.05, 5.95], [2.7, 2.7], linewidth=5, color='k') text(4.5, 3, 'Dopamine modulated STDP', horizontalalignment='center', fontsize=20) plot(neurons_times, neurons_indices, 'ob') plot(dopamine_times, dopamine_indices + 2, 'or') xlim([0, simulation_duration/second]) ylim([-0.5, 4]) yticks([0, 1, 2], ['Pre-neuron', 'Post-neuron', 'Reward']) xticks([]) subplot(412) plot(synapse_stdp_monitor.t/second, synapse_stdp_monitor.d.T/gmax, 'r-') xlim([0, simulation_duration/second]) ylabel('Extracellular\ndopamine d(t)') xticks([]) subplot(413) plot(synapse_stdp_monitor.t/second, synapse_stdp_monitor.c.T/gmax, 'b-') xlim([0, simulation_duration/second]) ylabel('Eligibility\ntrace c(t)') xticks([]) subplot(414) plot(synapse_stdp_monitor.t/second, synapse_stdp_monitor.s.T/gmax, 'g-') xlim([0, simulation_duration/second]) ylabel('Synaptic\nstrength s(t)') xlabel('Time (s)') tight_layout() show() brian2-2.5.4/examples/frompapers/Jansen_Rit_1995_single_column.py000066400000000000000000000065151445201106100247070ustar00rootroot00000000000000#!/usr/bin/env python # coding: utf-8 """ [Jansen and Rit 1995 model](https://link.springer.com/content/pdf/10.1007/BF00199471.pdf) (Figure 3) in Brian2. Equations are the system of differential equations number (6) in the original paper. The rate parameters $a=100 s^{-1}$ and $b=200 s^{-1}$ were changed to excitatory $\tau_e = 1000ms/a =10ms$ and inhibitory $\tau_i = 1000ms/b =20ms$ time constants as in [Thomas Knosche review](https://link.springer.com/referenceworkentry/10.1007%2F978-1-4614-6675-8_65), [Touboul et al. 2011](https://direct.mit.edu/neco/article-abstract/23/12/3232/7717/Neural-Mass-Activity-Bifurcations-and-Epilepsy?redirectedFrom=fulltext), or [David & Friston 2003](https://www.sciencedirect.com/science/article/pii/S1053811903004579). Units were removerd from parameters $e_0$, $v_0$, $r_0$, $A$, $B$, and $p$ to stop Brian's confusion. Ruben Tikidji-Hamburyan 2021 (rth@r-a-r.org) """ from numpy import * from numpy import random as rnd from matplotlib.pyplot import * from brian2 import * defaultclock.dt = .1*ms #default time step te,ti = 10.*ms, 20.*ms #taus for excitatory and inhibitory populations e0 = 5. #max firing rate v0 = 6. #(max FR)/2 input r0 = 0.56 #gain rate A,B,C = 3.25, 22., 135 #standard parameters as in the set (7) of the original paper P,deltaP = 120, 320.-120 #random input uniformly distributed between 120 and #320 pulses per second # Random noise nstim = TimedArray(rnd.rand(70000),2*ms) # Equations as in the system (6) of the original paper equs = """ dy0/dt = y3 /second : 1 dy3/dt = (A * Sp -2*y3 -y0/te*second)/te : 1 dy1/dt = y4 /second : 1 dy4/dt = (A*(p+ C2 * Se)-2*y4 -y1/te*second)/te : 1 dy2/dt = y5 /second : 1 dy5/dt = (B * C4 * Si -2*y5 -y2/ti*second)/ti : 1 p = P0+nstim(t)*dlP : 1 Sp = e0/(1+exp(r0*(v0 - (y1-y2) ))) : 1 Se = e0/(1+exp(r0*(v0 - C1*y0 ))) : 1 Si = e0/(1+exp(r0*(v0 - C3*y0 ))) : 1 C1 : 1 C2 = 0.8 *C1 : 1 C3 = 0.25*C1 : 1 C4 = 0.25*C1 : 1 P0 : 1 dlP : 1 """ n = NeuronGroup(6,equs,method='euler') #creates 6 JR models for different connectivity parameters #set parameters as for different traces on figure 3 of the original paper n.C1[0] = 68 n.C1[1] = 128 n.C1[2] = C n.C1[3] = 270 n.C1[4] = 675 n.C1[5] = 1350 #set stimulus offset and noise magnitude n.P0 = P n.dlP = deltaP #just record everything sm = StateMonitor(n,['y4','y1','y3','y0','y5','y2'],record=True) #Runs for 5 second run(5*second,report='text') #This code goes over all models with different parameters and plot activity of each population. figure(1,figsize=(22,16)) idx1 = where(sm.t/second>2.)[0] o = 0 for p in [0,1,2,3,4,5]: if o == 0: ax = subplot(6,3,1) else :subplot(6,3,1+o,sharex=ax) if o == 0: title("E") plot(sm.t[idx1]/second, sm[p].y1[idx1],'g-') ylabel(f"C={n[p].C1[0]}") if o == 15: xlabel("Time (seconds)") subplot(6,3,2+o,sharex=ax) if o == 0: title("P") plot(sm.t[idx1]/second, sm[p].y0[idx1],'b-') if o == 15: xlabel("Time (seconds)") subplot(6,3,3+o,sharex=ax) if o == 0: title("I") plot(sm.t[idx1]/second, sm[p].y2[idx1],'r-') if o == 15: xlabel("Time (seconds)") o += 3 show() brian2-2.5.4/examples/frompapers/Kremer_et_al_2011_barrel_cortex.py000077500000000000000000000145541445201106100252220ustar00rootroot00000000000000#!/usr/bin/env python """ Late Emergence of the Whisker Direction Selectivity Map in the Rat Barrel Cortex. Kremer Y, Leger JF, Goodman DF, Brette R, Bourdieu L (2011). J Neurosci 31(29):10689-700. Development of direction maps with pinwheels in the barrel cortex. Whiskers are deflected with random moving bars. N.B.: network construction can be long. """ from brian2 import * import time t1 = time.time() # PARAMETERS # Neuron numbers M4, M23exc, M23inh = 22, 25, 12 # size of each barrel (in neurons) N4, N23exc, N23inh = M4**2, M23exc**2, M23inh**2 # neurons per barrel barrelarraysize = 5 # Choose 3 or 4 if memory error Nbarrels = barrelarraysize**2 # Stimulation stim_change_time = 5*ms Fmax = .5/stim_change_time # maximum firing rate in layer 4 (.5 spike / stimulation) # Neuron parameters taum, taue, taui = 10*ms, 2*ms, 25*ms El = -70*mV Vt, vt_inc, tauvt = -55*mV, 2*mV, 50*ms # adaptive threshold # STDP taup, taud = 5*ms, 25*ms Ap, Ad= .05, -.04 # EPSPs/IPSPs EPSP, IPSP = 1*mV, -1*mV EPSC = EPSP * (taue/taum)**(taum/(taue-taum)) IPSC = IPSP * (taui/taum)**(taum/(taui-taum)) Ap, Ad = Ap*EPSC, Ad*EPSC # Layer 4, models the input stimulus eqs_layer4 = ''' rate = int(is_active)*clip(cos(direction - selectivity), 0, inf)*Fmax: Hz is_active = abs((barrel_x + 0.5 - bar_x) * cos(direction) + (barrel_y + 0.5 - bar_y) * sin(direction)) < 0.5: boolean barrel_x : integer # The x index of the barrel barrel_y : integer # The y index of the barrel selectivity : 1 # Stimulus parameters (same for all neurons) bar_x = cos(direction)*(t - stim_start_time)/(5*ms) + stim_start_x : 1 (shared) bar_y = sin(direction)*(t - stim_start_time)/(5*ms) + stim_start_y : 1 (shared) direction : 1 (shared) # direction of the current stimulus stim_start_time : second (shared) # start time of the current stimulus stim_start_x : 1 (shared) # start position of the stimulus stim_start_y : 1 (shared) # start position of the stimulus ''' layer4 = NeuronGroup(N4*Nbarrels, eqs_layer4, threshold='rand() < rate*dt', method='euler', name='layer4') layer4.barrel_x = '(i // N4) % barrelarraysize + 0.5' layer4.barrel_y = 'i // (barrelarraysize*N4) + 0.5' layer4.selectivity = '(i%N4)/(1.0*N4)*2*pi' # for each barrel, selectivity between 0 and 2*pi stimradius = (11+1)*.5 # Chose a new randomly oriented bar every 60ms runner_code = ''' direction = rand()*2*pi stim_start_x = barrelarraysize / 2.0 - cos(direction)*stimradius stim_start_y = barrelarraysize / 2.0 - sin(direction)*stimradius stim_start_time = t ''' layer4.run_regularly(runner_code, dt=60*ms, when='start') # Layer 2/3 # Model: IF with adaptive threshold eqs_layer23 = ''' dv/dt=(ge+gi+El-v)/taum : volt dge/dt=-ge/taue : volt dgi/dt=-gi/taui : volt dvt/dt=(Vt-vt)/tauvt : volt # adaptation barrel_idx : integer x : 1 # in "barrel width" units y : 1 # in "barrel width" units ''' layer23 = NeuronGroup(Nbarrels*(N23exc+N23inh), eqs_layer23, threshold='v>vt', reset='v = El; vt += vt_inc', refractory=2*ms, method='euler', name='layer23') layer23.v = El layer23.vt = Vt # Subgroups for excitatory and inhibitory neurons in layer 2/3 layer23exc = layer23[:Nbarrels*N23exc] layer23inh = layer23[Nbarrels*N23exc:] # Layer 2/3 excitatory # The units for x and y are the width/height of a single barrel layer23exc.x = '(i % (barrelarraysize*M23exc)) * (1.0/M23exc)' layer23exc.y = '(i // (barrelarraysize*M23exc)) * (1.0/M23exc)' layer23exc.barrel_idx = 'floor(x) + floor(y)*barrelarraysize' # Layer 2/3 inhibitory layer23inh.x = 'i % (barrelarraysize*M23inh) * (1.0/M23inh)' layer23inh.y = 'i // (barrelarraysize*M23inh) * (1.0/M23inh)' layer23inh.barrel_idx = 'floor(x) + floor(y)*barrelarraysize' print("Building synapses, please wait...") # Feedforward connections (plastic) feedforward = Synapses(layer4, layer23exc, model='''w:volt dA_source/dt = -A_source/taup : volt (event-driven) dA_target/dt = -A_target/taud : volt (event-driven)''', on_pre='''ge+=w A_source += Ap w = clip(w+A_target, 0*volt, EPSC)''', on_post=''' A_target += Ad w = clip(w+A_source, 0*volt, EPSC)''', name='feedforward') # Connect neurons in the same barrel with 50% probability feedforward.connect('(barrel_x_pre + barrelarraysize*barrel_y_pre) == barrel_idx_post', p=0.5) feedforward.w = EPSC*.5 print('excitatory lateral') # Excitatory lateral connections recurrent_exc = Synapses(layer23exc, layer23, model='w:volt', on_pre='ge+=w', name='recurrent_exc') recurrent_exc.connect(p='.15*exp(-.5*(((x_pre-x_post)/.4)**2+((y_pre-y_post)/.4)**2))') recurrent_exc.w['jexcitatory recurrent_exc.w['j>=Nbarrels*N23exc'] = EPSC # excitatory->inhibitory # Inhibitory lateral connections print('inhibitory lateral') recurrent_inh = Synapses(layer23inh, layer23exc, on_pre='gi+=IPSC', name='recurrent_inh') recurrent_inh.connect(p='exp(-.5*(((x_pre-x_post)/.2)**2+((y_pre-y_post)/.2)**2))') if get_device().__class__.__name__=='RuntimeDevice': print('Total number of connections') print('feedforward: %d' % len(feedforward)) print('recurrent exc: %d' % len(recurrent_exc)) print('recurrent inh: %d' % len(recurrent_inh)) t2 = time.time() print("Construction time: %.1fs" % (t2 - t1)) run(5*second, report='text') # Calculate the preferred direction of each cell in layer23 by doing a # vector average of the selectivity of the projecting layer4 cells, weighted # by the synaptic weight. _r = bincount(feedforward.j, weights=feedforward.w * cos(feedforward.selectivity_pre)/feedforward.N_incoming, minlength=len(layer23exc)) _i = bincount(feedforward.j, weights=feedforward.w * sin(feedforward.selectivity_pre)/feedforward.N_incoming, minlength=len(layer23exc)) selectivity_exc = (arctan2(_r, _i) % (2*pi))*180./pi scatter(layer23.x[:Nbarrels*N23exc], layer23.y[:Nbarrels*N23exc], c=selectivity_exc[:Nbarrels*N23exc], edgecolors='none', marker='s', cmap='hsv') vlines(np.arange(barrelarraysize), 0, barrelarraysize, 'k') hlines(np.arange(barrelarraysize), 0, barrelarraysize, 'k') clim(0, 360) colorbar() show() brian2-2.5.4/examples/frompapers/Maass_Natschlaeger_Markram_2002.py000077500000000000000000000272671445201106100251210ustar00rootroot00000000000000#!/usr/bin/env python3 """ Fig. 2 from: Real-Time Computing Without Stable States: A New Framework for Neural Computation Based on Perturbations Neural Computation 14, 2531–2560 (2002) by Maass W., Natschläger T. and Markram H. Sebastian Schmitt, 2022 """ from collections import defaultdict import multiprocessing import numpy as np import matplotlib.pyplot as plt from brian2 import ( NeuronGroup, Synapses, SpikeGeneratorGroup, SpikeMonitor, Network, prefs, ) from brian2 import ms, mV, Mohm, nA, second, Hz from brian2 import defaultclock, prefs N_NEURONS = 135 V_THRESH = 15 * mV V_RESET = 13.5 * mV STIMULUS_POISSON_RATE = 20 * Hz TARGET_DISTANCES = [0.4, 0.2, 0.1] N_PAIRS = 200 DT = 0.1 * ms DURATION = 500 * ms TS = np.arange(0, DURATION / ms, DT / ms) def exponential_convolution(t, spikes, tau): """Convolute spikes with exponential kernel t -- numpy array of times to evaluate the convolution spikes -- iterable of spike times tau -- exponential decay constant """ if len(spikes): return sum([np.exp(-((t - st) / tau)) * (t >= st) for st in spikes]) else: return np.zeros(len(TS)) def gaussian_convolution(t, spikes, tau): """Convolute spikes with Gaussian kernel t -- numpy array of times to evaluate the convolution spikes -- iterable of spike times tau -- exponential decay constant """ if len(spikes): return sum([np.exp(-(((t - st) / tau) ** 2)) for st in spikes]) else: return np.zeros(len(TS)) def euclidian_distance(liquid_states_u, liquid_states_v): """Euclidian distance between liquid states liquid_states_u -- liquid states liquid_states_v -- other liquid states To match the numbers in the paper, the square root is omitted """ return np.mean((liquid_states_u - liquid_states_v) ** 2, axis=0) def distance(conv_a, conv_b, dt): """Difference of convolutions in the L2-norm conv_a -- convolutions conv_b -- other convolutions dt -- time step To match the numbers in the paper, the square root is omitted """ return sum((conv_a - conv_b) ** 2) * dt def generate_poisson(duration, rate): """Generate Poisson spike train duration -- duration of spike train rate -- rate of spike train Return only spike trains that do not have multiple spikes per time bin """ while True: N = np.random.poisson(rate * duration) spikes = np.random.uniform(0, duration, N) spikes_orig = np.sort(spikes) shift = 1e-3 * (DT / ms) timebins = ((spikes_orig + shift) / (DT / ms)).astype(np.int32) if not any(np.diff(timebins) == 0): return spikes_orig def collect_stimulus_pairs(): """Collect pairs of input stimuli close in target distance""" DELTA_DISTANCE = 0.01 collected_pairs = defaultdict(list) while True: spikes_u = generate_poisson(DURATION / ms, STIMULUS_POISSON_RATE / Hz / 1e3) spikes_v = generate_poisson(DURATION / ms, STIMULUS_POISSON_RATE / Hz / 1e3) conv_u = gaussian_convolution(TS, spikes_u, tau=5) conv_v = gaussian_convolution(TS, spikes_v, tau=5) normed_distance = distance(conv_u, conv_v, DT / ms) / (DURATION / ms) for target_distance in TARGET_DISTANCES: if ( abs(normed_distance - target_distance) < DELTA_DISTANCE and len(collected_pairs[target_distance]) < N_PAIRS ): collected_pairs[target_distance].append((spikes_u, spikes_v)) # stop if we have enough pairs collected if len(collected_pairs) == len(TARGET_DISTANCES) and all( np.array(list(map(len, collected_pairs.values()))) == N_PAIRS ): break return collected_pairs def get_neurons(): neurons = NeuronGroup( N_NEURONS, """ tau_mem : second (shared, constant) tau_refrac : second (constant) v_reset : volt (shared, constant) v_thresh : volt (shared, constant) I_b : ampere (shared, constant) tau_stimulus : second (constant) I_syn_ee_synapses : ampere I_syn_ei_synapses : ampere I_syn_ie_synapses : ampere I_syn_ii_synapses : ampere dI_stimulus/dt = -I_stimulus/tau_stimulus : ampere R_in : ohm dv/dt = -v/tau_mem + (I_syn_ee_synapses + I_syn_ei_synapses + I_syn_ie_synapses + I_syn_ii_synapses)*R_in/tau_mem + I_b*R_in/tau_mem + I_stimulus*R_in/tau_mem: volt (unless refractory) x_pos : 1 (constant) y_pos : 1 (constant) z_pos : 1 (constant) """, threshold="v>v_thresh", reset="v=v_reset", refractory="tau_refrac", method="exact", name="neurons", ) neurons.tau_mem = 30 * ms neurons.v_thresh = V_THRESH neurons.v_reset = V_RESET neurons.I_b = 13.5 * nA neurons.v[:] = ( np.random.uniform(V_RESET / mV, V_THRESH / mV, size=len(neurons)) * mV ) neurons.R_in = 1 * Mohm # to randomly assign excitatory and inhibitory neurons later indices = np.arange(len(neurons)) np.random.shuffle(indices) # a column of 15x3x3 neurons neurons.x_pos = indices % 3 neurons.y_pos = (indices // 3) % 3 neurons.z_pos = indices // 9 return neurons def get_synapses(name, source, target, C, l, tau_I, A, U, D, F, delay): synapses_eqs = """ A : ampere (constant) U : 1 (constant) tau_I : second (shared, constant) D : second (constant) dx/dt = z/D : 1 (clock-driven) # recovered dy/dt = -y/tau_I : 1 (clock-driven) # active z = 1 - x - y : 1 # inactive I_syn_{}_post = A*y : ampere (summed) """.format(name) if F: synapses_eqs += """ du/dt = -u/F : 1 (clock-driven) F : second (constant) """ synapses_action = """ u += U*(1-u) y += u*x # important: update y first x += -u*x """ else: synapses_action = """ y += U*x # important: update y first x += -U*x """ synapses = Synapses( source, target, model=synapses_eqs, on_pre=synapses_action, method="exact", name=name, delay=delay, ) synapses.connect( p=f"{C} * exp(-((x_pos_pre-x_pos_post)**2 + (y_pos_pre-y_pos_post)**2 + (z_pos_pre-z_pos_post)**2)/{l}**2)" ) N_syn = len(synapses) synapses.tau_I = tau_I synapses.A[:] = np.sign(A / nA) * np.random.gamma(1, abs(A / nA), size=N_syn) * nA synapses.U[:] = np.random.normal(U, 0.5, size=N_syn) # paper samples from uniform, we take the mean synapses.U[:][synapses.U < 0] = U synapses.D[:] = np.random.normal(D / ms, 0.5 * D / ms, size=N_syn) * ms # paper samples from uniform, we take the mean synapses.D[:][synapses.D / ms <= 0] = D # start fully recovered synapses.x = 1 if F: synapses.F[:] = np.random.normal(F / ms, 0.5 * F / ms, size=N_syn) * ms # paper samples from uniform, we take the mean synapses.F[:][synapses.F / ms <= 0] = F return synapses def sim(net, spike_times): """Run network with given stimulus Redraws initial membrane voltages net -- the network to simulate spike_times -- the stimulus to inject """ net.restore() net["neurons"].v = ( np.random.uniform(V_RESET / mV, V_THRESH / mV, size=len(neurons)) * mV ) net["stimulus"].set_spikes([0] * len(spike_times), spike_times * ms) net.run(DURATION) spikes = list(net["spike_monitor_exc"].spike_trains().values()) + list( net["spike_monitor_inh"].spike_trains().values() ) liquid_states = np.array( [exponential_convolution(TS, st / ms, tau=30) for st in spikes] ) return liquid_states if __name__ == '__main__': neurons = get_neurons() N_exc = int(0.8 * len(neurons)) exc_neurons = neurons[:N_exc] exc_neurons.tau_refrac = 3 * ms exc_neurons.tau_stimulus = 3 * ms inh_neurons = neurons[N_exc:] inh_neurons.tau_refrac = 2 * ms inh_neurons.tau_stimulus = 6 * ms l_lambda = 2 ee_synapses = get_synapses( "ee_synapses", exc_neurons, exc_neurons, C=0.3, l=l_lambda, tau_I=3 * ms, A=30 * nA, U=0.5, D=1.1 * second, F=0.05 * second, delay=1.5 * ms, ) ei_synapses = get_synapses( "ei_synapses", exc_neurons, inh_neurons, C=0.2, l=l_lambda, tau_I=3 * ms, A=60 * nA, U=0.05, D=0.125 * second, F=1.2 * second, delay=0.8 * ms, ) ie_synapses = get_synapses( "ie_synapses", inh_neurons, exc_neurons, C=0.4, l=l_lambda, tau_I=6 * ms, A=-19 * nA, U=0.25, D=0.7 * second, F=0.02 * second, delay=0.8 * ms, ) ii_synapses = get_synapses( "ii_synapses", inh_neurons, inh_neurons, C=0.1, l=l_lambda, tau_I=6 * ms, A=-19 * nA, U=0.32, D=0.144 * second, F=0.06 * second, delay=0.8 * ms, ) # place holder for stimulus stimulus = SpikeGeneratorGroup(1, [], [] * ms, name="stimulus") spike_monitor_stimulus = SpikeMonitor(stimulus) static_synapses_exc = Synapses( stimulus, exc_neurons, "A : ampere (shared, constant)", on_pre="I_stimulus += A" ) static_synapses_exc.connect(p=1) static_synapses_exc.A = 18 * nA static_synapses_inh = Synapses( stimulus, inh_neurons, "A : ampere (shared, constant)", on_pre="I_stimulus += A" ) static_synapses_inh.connect(p=1) static_synapses_inh.A = 9 * nA spike_monitor_exc = SpikeMonitor(exc_neurons, name="spike_monitor_exc") spike_monitor_inh = SpikeMonitor(inh_neurons, name="spike_monitor_inh") defaultclock.dt = DT net = Network( [ neurons, ee_synapses, ei_synapses, ie_synapses, ii_synapses, static_synapses_exc, static_synapses_inh, stimulus, spike_monitor_exc, spike_monitor_inh, ] ) net.store() collected_pairs = collect_stimulus_pairs() # add only jittered pairs collected_pairs[0] = [ [generate_poisson(DURATION / ms, STIMULUS_POISSON_RATE / Hz / 1e3)] * 2 for _ in range(N_PAIRS) ] def map_sim(spike_times): """Wrapper to sim for multiprocessing """ return sim(net, spike_times) result = defaultdict(list) # loop over all distances and Poisson stimulus pairs for d, pairs in collected_pairs.items(): with multiprocessing.Pool() as p: states_u = p.map(map_sim, [p[0] for p in pairs]) states_v = p.map(map_sim, [p[1] for p in pairs]) for liquid_states_u, liquid_states_v in zip(states_u, states_v): ed = euclidian_distance(liquid_states_u, liquid_states_v) result[d].append(ed) # plot fig, ax = plt.subplots(figsize=(5, 5)) linestyles = ["dashed", (0, (8, 6, 1, 6)), (0, (5, 10)), "solid"] for d, ls in zip(TARGET_DISTANCES + [0], linestyles): eds = result[d] eds = np.array(eds) ax.plot( TS / 1000, np.mean(eds, axis=0), label=f"d(u,v)={d}", linestyle=ls, color="k" ) ax.set_xlabel("time [sec]") ax.set_ylabel("state distance") ax.set_xlim(0, 0.5) ax.set_ylim(0, 2.5) ax.legend(loc="upper center", fontsize="x-large", frameon=False) plt.show() brian2-2.5.4/examples/frompapers/Morris_Lecar_1981.py000066400000000000000000000046251445201106100223110ustar00rootroot00000000000000# encoding: utf8 """ Morris-Lecar model Reproduces Fig. 9 of: Catherine Morris and Harold Lecar. “Voltage Oscillations in the Barnacle Giant Muscle Fiber.” Biophysical Journal 35, no. 1 (1981): 193–213. """ from brian2 import * set_device('cpp_standalone') defaultclock.dt = 0.01*ms g_L = 2*mS g_Ca = 4*mS g_K = 8*mS V_L = -50*mV V_Ca = 100*mV V_K = -70*mV lambda_n__max = 1.0/(15*ms) V_1 = 10*mV V_2 = 15*mV # Note that Figure caption says -15 which seems to be a typo V_3 = -1*mV V_4 = 14.5*mV C = 20*uF # V,N-reduced system (Eq. 9 in article), note that the variables M and N (and lambda_N, etc.) # have been renamed to m and n to better match the Hodgkin-Huxley convention, and because N has # a reserved meaning in Brian (number of neurons) eqs = ''' dV/dt = (-g_L*(V - V_L) - g_Ca*m_inf*(V - V_Ca) - g_K*n*(V - V_K) + I)/C : volt dn/dt = lambda_n*(n_inf - n) : 1 m_inf = 0.5*(1 + tanh((V - V_1)/V_2)) : 1 n_inf = 0.5*(1 + tanh((V - V_3)/V_4)) : 1 lambda_n = lambda_n__max*cosh((V - V_3)/(2*V_4)) : Hz I : amp ''' neuron = NeuronGroup(17, eqs, method='exponential_euler') neuron.I = (np.arange(17)*25+100)*uA neuron.V = V_L neuron.n = 'n_inf' mon = StateMonitor(neuron, ['V', 'n'], record=True) run_time = 220*ms run(run_time) fig, (ax1, ax2) = plt.subplots(1, 2, gridspec_kw={'right': 0.95, 'bottom': 0.15}, figsize=(6.4, 3.2)) fig.subplots_adjust(wspace=0.4) for line_no, idx in enumerate([0, 4, 12, 15]): color = 'C%d' % line_no ax1.plot(mon.t/ms, mon.V[idx]/mV, color=color) ax1.text(225, mon.V[idx][-1]/mV, '%.0f' % (neuron.I[idx]/uA), color=color) ax1.set(xlim=(0, 220), ylim=(-50, 50), xlabel='time (ms)') ax1.set_ylabel('V (mV)', rotation=0) ax1.spines['right'].set_visible(False) ax1.spines['top'].set_visible(False) # dV/dt nullclines V = linspace(-50, 50, 100)*mV for line_no, (idx, color) in enumerate([(0, 'C0'), (4, 'C1'), (8, 'C4'), (12, 'C2'), (16, 'C5')]): n_null = (g_L*(V - V_L) + g_Ca*0.5*(1 + tanh((V - V_1)/V_2))*(V - V_Ca) - neuron.I[idx])/(-g_K*(V - V_K)) ax2.plot(V/mV, n_null, color=color) ax2.text(V[20+5*line_no]/mV, n_null[20+5*line_no]+0.01, '%.0f' % (neuron.I[idx]/uA), color=color) # dn/dt nullcline n_null = 0.5*(1 + tanh((V - V_3)/V_4)) ax2.plot(V/mV, n_null, color='k') ax2.set(xlim=(-50, 50), ylim=(0, 1), xlabel='V (mV)') ax2.set_ylabel('n', rotation=0) ax2.spines['right'].set_visible(False) ax2.spines['top'].set_visible(False) plt.show() brian2-2.5.4/examples/frompapers/Naud_et_al_2008_adex_firing_patterns.py000077500000000000000000000105051445201106100262260ustar00rootroot00000000000000#!/usr/bin/env python """ Firing patterns in the adaptive exponential integrate-and-fire model ----------------------- Naud R et al. (2008): Firing patterns in the adaptive exponential integrate-and-fire model. Biol Cybern. 2008; 99(4): 335–347. doi:10.1007/s00422-008-0264-7 Parameters adapted by P. Müller to match figures, cf. http://www.kip.uni-heidelberg.de/Veroeffentlichungen/details.php?id=3445. Sebastian Schmitt, Sebastian Billaudelle, 2022 """ from brian2 import * import matplotlib.pyplot as plt def sim(ax_vm, ax_w, ax_vm_w, parameters): """ simulate with parameters and plot to axes """ # taken from Touboul_Brette_2008 eqs = """ dvm/dt = (g_l*(e_l - vm) + g_l*d_t*exp((vm-v_t)/d_t) + i_stim - w)/c_m : volt dw/dt = (a*(vm - e_l) - w)/tau_w : amp """ neuron = NeuronGroup( 1, model=eqs, threshold="vm > 0*mV", reset="vm = v_r; w += b", method="euler", namespace=parameters, ) neuron.vm = parameters["e_l"] neuron.w = 0 states = StateMonitor(neuron, ["vm", "w"], record=True, when="thresholds") defaultclock.dt = 0.1 * ms run(0.6 * second) # clip membrane voltages to threshold (0 mV) vms = np.clip(states[0].vm / mV, a_min=None, a_max=0) ax_vm.plot(states[0].t / ms, vms) ax_w.plot(states[0].t / ms, states[0].w / nA) ax_vm_w.plot(vms, states[0].w / nA) ax_w.sharex(ax_vm) ax_vm.tick_params(labelbottom=False) ax_vm.set_ylabel("V [mV]") ax_w.set_xlabel("t [ms]") ax_w.set_ylabel("w [nA]") ax_vm_w.set_xlabel("V [mV]") ax_vm_w.set_ylabel("w [nA]") ax_vm_w.yaxis.tick_right() ax_vm_w.yaxis.set_label_position("right") patterns = { "tonic spiking": { "c_m": 200 * pF, "g_l": 10 * nS, "e_l": -70.0 * mV, "v_t": -50.0 * mV, "d_t": 2.0 * mV, "a": 2.0 * nS, "tau_w": 30.0 * ms, "b": 0.0 * pA, "v_r": -58.0 * mV, "i_stim": 500 * pA, }, "adaptation": { "c_m": 200 * pF, "g_l": 12 * nS, "e_l": -70.0 * mV, "v_t": -50.0 * mV, "d_t": 2.0 * mV, "a": 2.0 * nS, "tau_w": 300.0 * ms, "b": 60.0 * pA, "v_r": -58.0 * mV, "i_stim": 500 * pA, }, "initial burst": { "c_m": 130 * pF, "g_l": 18 * nS, "e_l": -58.0 * mV, "v_t": -50.0 * mV, "d_t": 2.0 * mV, "a": 4.0 * nS, "tau_w": 150.0 * ms, "b": 120.0 * pA, "v_r": -50.0 * mV, "i_stim": 400 * pA, }, "regular bursting": { "c_m": 200 * pF, "g_l": 10 * nS, "e_l": -58.0 * mV, "v_t": -50.0 * mV, "d_t": 2.0 * mV, "a": 2.0 * nS, "tau_w": 120.0 * ms, "b": 100.0 * pA, "v_r": -46.0 * mV, "i_stim": 210 * pA, }, "delayed accelerating": { "c_m": 200 * pF, "g_l": 12 * nS, "e_l": -70.0 * mV, "v_t": -50.0 * mV, "d_t": 2.0 * mV, "a": -10.0 * nS, "tau_w": 300.0 * ms, "b": 0.0 * pA, "v_r": -58.0 * mV, "i_stim": 300 * pA, }, "delayed regular bursting": { "c_m": 100 * pF, "g_l": 10 * nS, "e_l": -65.0 * mV, "v_t": -50.0 * mV, "d_t": 2.0 * mV, "a": -10.0 * nS, "tau_w": 90.0 * ms, "b": 30.0 * pA, "v_r": -47.0 * mV, "i_stim": 110 * pA, }, "transient spiking": { "c_m": 100 * pF, "g_l": 10 * nS, "e_l": -65.0 * mV, "v_t": -50.0 * mV, "d_t": 2.0 * mV, "a": 10.0 * nS, "tau_w": 90.0 * ms, "b": 100.0 * pA, "v_r": -47.0 * mV, "i_stim": 180 * pA, }, "irregular spiking": { "c_m": 100 * pF, "g_l": 12 * nS, "e_l": -60.0 * mV, "v_t": -50.0 * mV, "d_t": 2.0 * mV, "a": -11.0 * nS, "tau_w": 130.0 * ms, "b": 30.0 * pA, "v_r": -48.0 * mV, "i_stim": 160 * pA, }, } # loop over all patterns and plot for pattern, parameters in patterns.items(): fig = plt.figure(figsize=(10, 5)) fig.suptitle(pattern) gs = fig.add_gridspec(2, 2) ax_vm = fig.add_subplot(gs[0, 0]) ax_w = fig.add_subplot(gs[1, 0]) ax_vm_w = fig.add_subplot(gs[:, 1]) sim(ax_vm, ax_w, ax_vm_w, parameters) plt.show() brian2-2.5.4/examples/frompapers/Nicola_Clopath_2017.py000077500000000000000000000121101445201106100225650ustar00rootroot00000000000000#!/usr/bin/env python3 """ FORCE training of a Leaky IF model to mimic a sinusoid (5 Hz) oscillator Nicola, W., Clopath, C. Supervised learning in spiking neural networks with FORCE training Nat Commun 8, 2208 (2017) https://doi.org/10.1038/s41467-017-01827-3 Based on https://github.com/ModelDBRepository/190565/blob/master/CODE%20FOR%20FIGURE%202/LIFFORCESINE.m Sebastian Schmitt, 2022 """ from brian2 import NeuronGroup, Synapses, StateMonitor, SpikeMonitor from brian2 import run, defaultclock, network_operation from brian2 import ms, second, Hz import matplotlib.pyplot as plt from matplotlib.ticker import MaxNLocator import numpy as np # set seed for reproducible figures np.random.seed(1) # decay time of synaptic kernal td = 20*ms # rise time of synaptic kernal tr = 2*ms # membrane time constant tm = 10*ms # refractory period tref = 2*ms # reset potential vreset = -65 # peak/threshold potential vpeak = -40 # bias BIAS = vpeak # integration time step defaultclock.dt = 0.05*ms # total duration of simulation T = 15*second # start of training imin = 5*second # end of training icrit = 10*second # interval of training step = 2.5*ms # feedback scale factor Q = 10 # neuron-to-neuron connection scale factor G = 0.04 # connection probability p = 0.1 # number of neurons N = 2000 # correlation weight matrix for RLMS alpha = defaultclock.dt/second*0.1 Pinv = np.eye(N)*alpha # Sinusoid oscillator def zx(t): freq = 5*Hz return np.sin(2*np.pi*freq*t) neurons = NeuronGroup(N, """ dv/dt = (-v + BIAS + IPSC + E*z)/tm: 1 (unless refractory) dIPSC/dt = -IPSC/tr + h : 1 dh/dt = -h/td : 1/second dr/dt = -r/tr + hr : 1 dhr/dt = -hr/td : 1/second BPhi : 1 z : 1 (shared) E : 1 """, method="euler", threshold="v>=vpeak", reset="v=vreset; hr += 1/(tr*td)*second", refractory=tref) # fixed feedback weights neurons.E = (2*np.random.uniform(size=N)-1)*Q # initial membrane voltage neurons.v = vreset + np.random.uniform(size=N)*(30-vreset) synapses = Synapses(neurons, neurons, "w : second", on_pre="h += w/(tr*td)") synapses.connect() omega = G*(np.random.normal(size=(N,N))*(np.random.uniform(size=(N,N)) imin and t < icrit: cd = Pinv@neurons.r err = neurons.z - zx(t) neurons.BPhi -= cd*err Pinv -= np.outer(cd,cd)/( 1 + np.dot(neurons.r, cd)) run(T, report="text") fig, axes = plt.subplots(2,2, figsize=(10,10)) axes = axes.flatten() axes[0].set_title("Spike raster") axes[0].scatter(spikemon.t/second,spikemon.i, marker='|', linestyle="None", color="black", s=100) axes[0].set_xlim((imin-2*second)/second, imin/second+2) axes[0].set_ylim(0, len(spikemon.source)) axes[0].set_xlabel("t [s]") axes[0].set_ylabel("Neuron") axes[0].yaxis.set_major_locator(MaxNLocator(integer=True)) axes[1].plot(statemon_z.t/second, zx(statemon_z.t), linestyle='--', color='k') axes[1].plot(statemon_z.t/second,statemon_z.z[0]) axes[1].set_title("Target and readout") axes[1].annotate('RLS ON', xy=(imin/second, -1.05), xytext=(imin/second, -1.35), arrowprops=dict(facecolor='black', shrink=1), ha="center") axes[1].annotate('RLS OFF', xy=(icrit/second, -1.05), xytext=(icrit/second, -1.35), arrowprops=dict(facecolor='black', shrink=1), ha="center") axes[1].set_xlabel("t [s]") axes[1].set_xlim((imin-1*second)/second, T/second) axes[1].set_ylim(-1.4,1.1) axes[2].set_title("Error") axes[2].plot(statemon_z.t/second, statemon_z.z[0] - zx(statemon_z.t)) axes[2].annotate('RLS ON', xy=(imin/second, -0.15), xytext=(imin/second, -0.4), arrowprops=dict(facecolor='black', shrink=1), ha="center") axes[2].annotate('RLS OFF', xy=(icrit/second, -0.15), xytext=(icrit/second, -0.4), arrowprops=dict(facecolor='black', shrink=1), ha="center") axes[2].set_xlabel("t [s]") axes[2].set_xlim((imin-1*second)/second, T/second) axes[2].set_ylim(-1,1) axes[3].set_title("Decoders") for j in range(len(statemon_BPhi.record)): axes[3].plot(statemon_BPhi.t/second,statemon_BPhi.BPhi[j]) axes[3].set_xlim((imin-1*second)/second, T/second) axes[3].set_xlabel("t [s]") axes[3].set_ylim(-0.00020, 0.00015) axes[3].set_yticklabels([]) axes[3].annotate('RLS ON', xy=(imin/second, -0.0001455), xytext=(imin/second, -0.00019), arrowprops=dict(facecolor='black', shrink=1), ha="center") axes[3].annotate('RLS OFF', xy=(icrit/second, -0.0001455), xytext=(icrit/second, -0.00019), arrowprops=dict(facecolor='black', shrink=1), ha="center") fig.tight_layout() brian2-2.5.4/examples/frompapers/Platkiewicz_Brette_2011.py000077500000000000000000000054771445201106100235150ustar00rootroot00000000000000#!/usr/bin/env python """ Slope-threshold relationship with noisy inputs, in the adaptive threshold model ------------------------------------------------------------------------------- Fig. 5E,F from: Platkiewicz J and R Brette (2011). Impact of Fast Sodium Channel Inactivation on Spike Threshold Dynamics and Synaptic Integration. PLoS Comp Biol 7(5): e1001129. doi:10.1371/journal.pcbi.1001129 """ from scipy import optimize from scipy.stats import linregress from brian2 import * N = 200 # 200 neurons to get more statistics, only one is shown duration = 1*second # --Biophysical parameters ENa = 60*mV EL = -70*mV vT = -55*mV Vi = -63*mV tauh = 5*ms tau = 5*ms ka = 5*mV ki = 6*mV a = ka / ki tauI = 5*ms mu = 15*mV sigma = 6*mV / sqrt(tauI / (tauI + tau)) # --Theoretical prediction for the slope-threshold relationship (approximation: a=1+epsilon) thresh = lambda slope, a: Vi - slope * tauh * log(1 + (Vi - vT / a) / (slope * tauh)) # -----Exact calculation of the slope-threshold relationship # (note that optimize.fsolve does not work with units, we therefore let th be a # unitless quantity, i.e. the value in volt). thresh_ex = lambda s: optimize.fsolve(lambda th: (a*s*tauh*exp((Vi-th*volt)/(s*tauh))-th*volt*(1-a)-a*(s*tauh+Vi)+vT)/volt, thresh(s, a))*volt eqs = """ dv/dt=(EL-v+mu+sigma*I)/tau : volt dtheta/dt=(vT+a*clip(v-Vi, 0*mV, inf*mV)-theta)/tauh : volt dI/dt=-I/tauI+(2/tauI)**.5*xi : 1 # Ornstein-Uhlenbeck """ neurons = NeuronGroup(N, eqs, threshold="v>theta", reset='v=EL', refractory=5*ms) neurons.v = EL neurons.theta = vT neurons.I = 0 S = SpikeMonitor(neurons) M = StateMonitor(neurons, 'v', record=True) Mt = StateMonitor(neurons, 'theta', record=0) run(duration, report='text') # Linear regression gives depolarization slope before spikes tx = M.t[(M.t > 0*second) & (M.t < 1.5 * tauh)] slope, threshold = [], [] for (i, t) in zip(S.i, S.t): ind = (M.t < t) & (M.t > t - tauh) mx = M.v[i, ind] s, _, _, _, _ = linregress(tx[:len(mx)]/ms, mx/mV) slope.append(s) threshold.append(mx[-1]) # Figure fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4)) ax1.plot(M.t/ms, M.v[0]/mV, 'k') ax1.plot(Mt.t/ms, Mt.theta[0]/mV, 'r') # Display spikes on the trace spike_timesteps = np.round(S.t[S.i == 0]/defaultclock.dt).astype(int) ax1.vlines(S.t[S.i == 0]/ms, M.v[0, spike_timesteps]/mV, 0, color='r') ax1.plot(S.t[S.i == 0]/ms, M.v[0, spike_timesteps]/mV, 'ro', ms=3) ax1.set(xlabel='Time (ms)', ylabel='Voltage (mV)', xlim=(0, 500), ylim=(-75, -35)) ax2.plot(slope, Quantity(threshold)/mV, 'r.') sx = linspace(0.5*mV/ms, 4*mV/ms, 100) t = Quantity([thresh_ex(s) for s in sx]) ax2.plot(sx/(mV/ms), t/mV, 'k') ax2.set(xlim=(0.5, 4), xlabel='Depolarization slope (mV/ms)', ylabel='Threshold (mV)') fig.tight_layout() plt.show() brian2-2.5.4/examples/frompapers/Rossant_et_al_2011bis.py000077500000000000000000000045611445201106100232060ustar00rootroot00000000000000#!/usr/bin/env python """ Distributed synchrony example ============================= Fig. 14 from: Rossant C, Leijon S, Magnusson AK, Brette R (2011). "Sensitivity of noisy neurons to coincident inputs". Journal of Neuroscience, 31(47). 5000 independent E/I Poisson inputs are injected into a leaky integrate-and-fire neuron. Synchronous events, following an independent Poisson process at 40 Hz, are considered, where 15 E Poisson spikes are randomly shifted to be synchronous at those events. The output firing rate is then significantly higher, showing that the spike timing of less than 1% of the excitatory synapses have an important impact on the postsynaptic firing. """ from brian2 import * # neuron parameters theta = -55*mV El = -65*mV vmean = -65*mV taum = 5*ms taue = 3*ms taui = 10*ms eqs = Equations(""" dv/dt = (ge+gi-(v-El))/taum : volt dge/dt = -ge/taue : volt dgi/dt = -gi/taui : volt """) # input parameters p = 15 ne = 4000 ni = 1000 lambdac = 40*Hz lambdae = lambdai = 1*Hz # synapse parameters we = .5*mV/(taum/taue)**(taum/(taue-taum)) wi = (vmean-El-lambdae*ne*we*taue)/(lambdae*ni*taui) # NeuronGroup definition group = NeuronGroup(N=2, model=eqs, reset='v = El', threshold='v>theta', refractory=5*ms, method='exact') group.v = El group.ge = group.gi = 0 # independent E/I Poisson inputs p1 = PoissonInput(group[0:1], 'ge', N=ne, rate=lambdae, weight=we) p2 = PoissonInput(group[0:1], 'gi', N=ni, rate=lambdai, weight=wi) # independent E/I Poisson inputs + synchronous E events p3 = PoissonInput(group[1:], 'ge', N=ne, rate=lambdae-(p*1.0/ne)*lambdac, weight=we) p4 = PoissonInput(group[1:], 'gi', N=ni, rate=lambdai, weight=wi) p5 = PoissonInput(group[1:], 'ge', N=1, rate=lambdac, weight=p*we) # run the simulation M = SpikeMonitor(group) SM = StateMonitor(group, 'v', record=True) BrianLogger.log_level_info() run(1*second) # plot trace and spikes for i in [0, 1]: spikes = (M.t[M.i == i] - defaultclock.dt)/ms val = SM[i].v subplot(2, 1, i+1) plot(SM.t/ms, val) plot(tile(spikes, (2, 1)), vstack((val[array(spikes, dtype=int)], zeros(len(spikes)))), 'C0') title("%s: %d spikes/second" % (["uncorrelated inputs", "correlated inputs"][i], M.count[i])) tight_layout() show() brian2-2.5.4/examples/frompapers/Rothman_Manis_2003.py000077500000000000000000000105461445201106100224530ustar00rootroot00000000000000#!/usr/bin/env python """ Cochlear neuron model of Rothman & Manis ---------------------------------------- Rothman JS, Manis PB (2003) The roles potassium currents play in regulating the electrical activity of ventral cochlear nucleus neurons. J Neurophysiol 89:3097-113. All model types differ only by the maximal conductances. Adapted from their Neuron implementation by Romain Brette """ from brian2 import * #defaultclock.dt=0.025*ms # for better precision ''' Simulation parameters: choose current amplitude and neuron type (from type1c, type1t, type12, type 21, type2, type2o) ''' neuron_type = 'type1c' Ipulse = 250*pA C = 12*pF Eh = -43*mV EK = -70*mV # -77*mV in mod file El = -65*mV ENa = 50*mV nf = 0.85 # proportion of n vs p kinetics zss = 0.5 # steady state inactivation of glt temp = 22. # temperature in degree celcius q10 = 3. ** ((temp - 22) / 10.) # hcno current (octopus cell) frac = 0.0 qt = 4.5 ** ((temp - 33.) / 10.) # Maximal conductances of different cell types in nS maximal_conductances = dict( type1c=(1000, 150, 0, 0, 0.5, 0, 2), type1t=(1000, 80, 0, 65, 0.5, 0, 2), type12=(1000, 150, 20, 0, 2, 0, 2), type21=(1000, 150, 35, 0, 3.5, 0, 2), type2=(1000, 150, 200, 0, 20, 0, 2), type2o=(1000, 150, 600, 0, 0, 40, 2) # octopus cell ) gnabar, gkhtbar, gkltbar, gkabar, ghbar, gbarno, gl = [x * nS for x in maximal_conductances[neuron_type]] # Classical Na channel eqs_na = """ ina = gnabar*m**3*h*(ENa-v) : amp dm/dt=q10*(minf-m)/mtau : 1 dh/dt=q10*(hinf-h)/htau : 1 minf = 1./(1+exp(-(vu + 38.) / 7.)) : 1 hinf = 1./(1+exp((vu + 65.) / 6.)) : 1 mtau = ((10. / (5*exp((vu+60.) / 18.) + 36.*exp(-(vu+60.) / 25.))) + 0.04)*ms : second htau = ((100. / (7*exp((vu+60.) / 11.) + 10.*exp(-(vu+60.) / 25.))) + 0.6)*ms : second """ # KHT channel (delayed-rectifier K+) eqs_kht = """ ikht = gkhtbar*(nf*n**2 + (1-nf)*p)*(EK-v) : amp dn/dt=q10*(ninf-n)/ntau : 1 dp/dt=q10*(pinf-p)/ptau : 1 ninf = (1 + exp(-(vu + 15) / 5.))**-0.5 : 1 pinf = 1. / (1 + exp(-(vu + 23) / 6.)) : 1 ntau = ((100. / (11*exp((vu+60) / 24.) + 21*exp(-(vu+60) / 23.))) + 0.7)*ms : second ptau = ((100. / (4*exp((vu+60) / 32.) + 5*exp(-(vu+60) / 22.))) + 5)*ms : second """ # Ih channel (subthreshold adaptive, non-inactivating) eqs_ih = """ ih = ghbar*r*(Eh-v) : amp dr/dt=q10*(rinf-r)/rtau : 1 rinf = 1. / (1+exp((vu + 76.) / 7.)) : 1 rtau = ((100000. / (237.*exp((vu+60.) / 12.) + 17.*exp(-(vu+60.) / 14.))) + 25.)*ms : second """ # KLT channel (low threshold K+) eqs_klt = """ iklt = gkltbar*w**4*z*(EK-v) : amp dw/dt=q10*(winf-w)/wtau : 1 dz/dt=q10*(zinf-z)/ztau : 1 winf = (1. / (1 + exp(-(vu + 48.) / 6.)))**0.25 : 1 zinf = zss + ((1.-zss) / (1 + exp((vu + 71.) / 10.))) : 1 wtau = ((100. / (6.*exp((vu+60.) / 6.) + 16.*exp(-(vu+60.) / 45.))) + 1.5)*ms : second ztau = ((1000. / (exp((vu+60.) / 20.) + exp(-(vu+60.) / 8.))) + 50)*ms : second """ # Ka channel (transient K+) eqs_ka = """ ika = gkabar*a**4*b*c*(EK-v): amp da/dt=q10*(ainf-a)/atau : 1 db/dt=q10*(binf-b)/btau : 1 dc/dt=q10*(cinf-c)/ctau : 1 ainf = (1. / (1 + exp(-(vu + 31) / 6.)))**0.25 : 1 binf = 1. / (1 + exp((vu + 66) / 7.))**0.5 : 1 cinf = 1. / (1 + exp((vu + 66) / 7.))**0.5 : 1 atau = ((100. / (7*exp((vu+60) / 14.) + 29*exp(-(vu+60) / 24.))) + 0.1)*ms : second btau = ((1000. / (14*exp((vu+60) / 27.) + 29*exp(-(vu+60) / 24.))) + 1)*ms : second ctau = ((90. / (1 + exp((-66-vu) / 17.))) + 10)*ms : second """ # Leak eqs_leak = """ ileak = gl*(El-v) : amp """ # h current for octopus cells eqs_hcno = """ ihcno = gbarno*(h1*frac + h2*(1-frac))*(Eh-v) : amp dh1/dt=(hinfno-h1)/tau1 : 1 dh2/dt=(hinfno-h2)/tau2 : 1 hinfno = 1./(1+exp((vu+66.)/7.)) : 1 tau1 = bet1/(qt*0.008*(1+alp1))*ms : second tau2 = bet2/(qt*0.0029*(1+alp2))*ms : second alp1 = exp(1e-3*3*(vu+50)*9.648e4/(8.315*(273.16+temp))) : 1 bet1 = exp(1e-3*3*0.3*(vu+50)*9.648e4/(8.315*(273.16+temp))) : 1 alp2 = exp(1e-3*3*(vu+84)*9.648e4/(8.315*(273.16+temp))) : 1 bet2 = exp(1e-3*3*0.6*(vu+84)*9.648e4/(8.315*(273.16+temp))) : 1 """ eqs = """ dv/dt = (ileak + ina + ikht + iklt + ika + ih + ihcno + I)/C : volt vu = v/mV : 1 # unitless v I : amp """ eqs += eqs_leak + eqs_ka + eqs_na + eqs_ih + eqs_klt + eqs_kht + eqs_hcno neuron = NeuronGroup(1, eqs, method='exponential_euler') neuron.v = El run(50*ms, report='text') # Go to rest M = StateMonitor(neuron, 'v', record=0) neuron.I = Ipulse run(100*ms, report='text') plot(M.t / ms, M[0].v / mV) xlabel('t (ms)') ylabel('v (mV)') show() brian2-2.5.4/examples/frompapers/Stimberg_et_al_2018/000077500000000000000000000000001445201106100222575ustar00rootroot00000000000000brian2-2.5.4/examples/frompapers/Stimberg_et_al_2018/README.md000066400000000000000000000013731445201106100235420ustar00rootroot00000000000000These Brian scripts reproduce the figures from the following preprint: Modeling neuron-glia interactions with the Brian 2 simulator Marcel Stimberg, Dan F. M. Goodman, Romain Brette, Maurizio De Pittà bioRxiv 198366; doi: https://doi.org/10.1101/198366 Each file can be run individually to reproduce the respective figure. Note that most files use the [standalone mode](http://brian2.readthedocs.io/en/stable/user/computation.html#standalone-code-generation) for faster simulation. If your setup does not support this mode, you can instead fallback to the runtime mode by removing the `set_device('cpp_standalone)` line. Note that example 6 ("recurrent neuron-glial network") takes a relatively long time (~15min on a reasonably fast desktop machine) to run. brian2-2.5.4/examples/frompapers/Stimberg_et_al_2018/example_1_COBA.py000066400000000000000000000204401445201106100252700ustar00rootroot00000000000000# coding=utf-8 """ Modeling neuron-glia interactions with the Brian 2 simulator Marcel Stimberg, Dan F. M. Goodman, Romain Brette, Maurizio De Pittà bioRxiv 198366; doi: https://doi.org/10.1101/198366 Figure 1: Modeling of neurons and synapses. Randomly connected networks with conductance-based synapses (COBA; see Brunel, 2000). Synapses exhibit short-time plasticity (Tsodyks, 2005; Tsodyks et al., 1998). """ from brian2 import * import sympy import plot_utils as pu seed(11922) # to get identical figures for repeated runs ################################################################################ # Model parameters ################################################################################ ### General parameters duration = 1.0*second # Total simulation time sim_dt = 0.1*ms # Integrator/sampling step N_e = 3200 # Number of excitatory neurons N_i = 800 # Number of inhibitory neurons ### Neuron parameters E_l = -60*mV # Leak reversal potential g_l = 9.99*nS # Leak conductance E_e = 0*mV # Excitatory synaptic reversal potential E_i = -80*mV # Inhibitory synaptic reversal potential C_m = 198*pF # Membrane capacitance tau_e = 5*ms # Excitatory synaptic time constant tau_i = 10*ms # Inhibitory synaptic time constant tau_r = 5*ms # Refractory period I_ex = 150*pA # External current V_th = -50*mV # Firing threshold V_r = E_l # Reset potential ### Synapse parameters w_e = 0.05*nS # Excitatory synaptic conductance w_i = 1.0*nS # Inhibitory synaptic conductance U_0 = 0.6 # Synaptic release probability at rest Omega_d = 2.0/second # Synaptic depression rate Omega_f = 3.33/second # Synaptic facilitation rate ################################################################################ # Model definition ################################################################################ # Set the integration time (in this case not strictly necessary, since we are # using the default value) defaultclock.dt = sim_dt ### Neurons neuron_eqs = ''' dv/dt = (g_l*(E_l-v) + g_e*(E_e-v) + g_i*(E_i-v) + I_ex)/C_m : volt (unless refractory) dg_e/dt = -g_e/tau_e : siemens # post-synaptic exc. conductance dg_i/dt = -g_i/tau_i : siemens # post-synaptic inh. conductance ''' neurons = NeuronGroup(N_e + N_i, model=neuron_eqs, threshold='v>V_th', reset='v=V_r', refractory='tau_r', method='euler') # Random initial membrane potential values and conductances neurons.v = 'E_l + rand()*(V_th-E_l)' neurons.g_e = 'rand()*w_e' neurons.g_i = 'rand()*w_i' exc_neurons = neurons[:N_e] inh_neurons = neurons[N_e:] ### Synapses synapses_eqs = ''' # Usage of releasable neurotransmitter per single action potential: du_S/dt = -Omega_f * u_S : 1 (event-driven) # Fraction of synaptic neurotransmitter resources available: dx_S/dt = Omega_d *(1 - x_S) : 1 (event-driven) ''' synapses_action = ''' u_S += U_0 * (1 - u_S) r_S = u_S * x_S x_S -= r_S ''' exc_syn = Synapses(exc_neurons, neurons, model=synapses_eqs, on_pre=synapses_action+'g_e_post += w_e*r_S') inh_syn = Synapses(inh_neurons, neurons, model=synapses_eqs, on_pre=synapses_action+'g_i_post += w_i*r_S') exc_syn.connect(p=0.05) inh_syn.connect(p=0.2) # Start from "resting" condition: all synapses have fully-replenished # neurotransmitter resources exc_syn.x_S = 1 inh_syn.x_S = 1 # ############################################################################## # # Monitors # ############################################################################## # Note that we could use a single monitor for all neurons instead, but in this # way plotting is a bit easier in the end exc_mon = SpikeMonitor(exc_neurons) inh_mon = SpikeMonitor(inh_neurons) ### We record some additional data from a single excitatory neuron ni = 50 # Record conductances and membrane potential of neuron ni state_mon = StateMonitor(exc_neurons, ['v', 'g_e', 'g_i'], record=ni) # We make sure to monitor synaptic variables after synapse are updated in order # to use simple recurrence relations to reconstruct them. Record all synapses # originating from neuron ni synapse_mon = StateMonitor(exc_syn, ['u_S', 'x_S'], record=exc_syn[ni, :], when='after_synapses') # ############################################################################## # # Simulation run # ############################################################################## run(duration, report='text') ################################################################################ # Analysis and plotting ################################################################################ plt.style.use('figures.mplstyle') ### Spiking activity (w/ rate) fig1, ax = plt.subplots(nrows=2, ncols=1, sharex=False, gridspec_kw={'height_ratios': [3, 1], 'left': 0.18, 'bottom': 0.18, 'top': 0.95, 'hspace': 0.1}, figsize=(3.07, 3.07)) ax[0].plot(exc_mon.t[exc_mon.i <= N_e//4]/ms, exc_mon.i[exc_mon.i <= N_e//4], '|', color='C0') ax[0].plot(inh_mon.t[inh_mon.i <= N_i//4]/ms, inh_mon.i[inh_mon.i <= N_i//4]+N_e//4, '|', color='C1') pu.adjust_spines(ax[0], ['left']) ax[0].set(xlim=(0., duration/ms), ylim=(0, (N_e+N_i)//4), ylabel='neuron index') # Generate frequencies bin_size = 1*ms spk_count, bin_edges = np.histogram(np.r_[exc_mon.t/ms, inh_mon.t/ms], int(duration/ms)) rate = double(spk_count)/(N_e + N_i)/bin_size/Hz ax[1].plot(bin_edges[:-1], rate, '-', color='k') pu.adjust_spines(ax[1], ['left', 'bottom']) ax[1].set(xlim=(0., duration/ms), ylim=(0, 10.), xlabel='time (ms)', ylabel='rate (Hz)') pu.adjust_ylabels(ax, x_offset=-0.18) ### Dynamics of a single neuron fig2, ax = plt.subplots(4, sharex=False, gridspec_kw={'left': 0.27, 'bottom': 0.18, 'top': 0.95, 'hspace': 0.2}, figsize=(3.07, 3.07)) ### Postsynaptic conductances ax[0].plot(state_mon.t/ms, state_mon.g_e[0]/nS, color='C0') ax[0].plot(state_mon.t/ms, -state_mon.g_i[0]/nS, color='C1') ax[0].plot([state_mon.t[0]/ms, state_mon.t[-1]/ms], [0, 0], color='grey', linestyle=':') # Adjust axis pu.adjust_spines(ax[0], ['left']) ax[0].set(xlim=(0., duration/ms), ylim=(-5.0, 0.25), ylabel=f"postsyn.\nconduct.\n(${sympy.latex(nS)}$)") ### Membrane potential ax[1].axhline(V_th/mV, color='C2', linestyle=':') # Threshold # Artificially insert spikes ax[1].plot(state_mon.t/ms, state_mon.v[0]/mV, color='black') ax[1].vlines(exc_mon.t[exc_mon.i == ni]/ms, V_th/mV, 0, color='black') pu.adjust_spines(ax[1], ['left']) ax[1].set(xlim=(0., duration/ms), ylim=(-1+V_r/mV, 0.), ylabel=f"membrane\npotential\n(${sympy.latex(mV)}$)") ### Synaptic variables # Retrieves indexes of spikes in the synaptic monitor using the fact that we # are sampling spikes and synaptic variables by the same dt spk_index = np.in1d(synapse_mon.t, exc_mon.t[exc_mon.i == ni]) ax[2].plot(synapse_mon.t[spk_index]/ms, synapse_mon.x_S[0][spk_index], '.', ms=4, color='C3') ax[2].plot(synapse_mon.t[spk_index]/ms, synapse_mon.u_S[0][spk_index], '.', ms=4, color='C4') # Super-impose reconstructed solutions time = synapse_mon.t # time vector tspk = Quantity(synapse_mon.t, copy=True) # Spike times for ts in exc_mon.t[exc_mon.i == ni]: tspk[time >= ts] = ts ax[2].plot(synapse_mon.t/ms, 1 + (synapse_mon.x_S[0]-1)*exp(-(time-tspk)*Omega_d), '-', color='C3') ax[2].plot(synapse_mon.t/ms, synapse_mon.u_S[0]*exp(-(time-tspk)*Omega_f), '-', color='C4') # Adjust axis pu.adjust_spines(ax[2], ['left']) ax[2].set(xlim=(0., duration/ms), ylim=(-0.05, 1.05), ylabel='synaptic\nvariables\n$u_S,\,x_S$') nspikes = np.sum(spk_index) x_S_spike = synapse_mon.x_S[0][spk_index] u_S_spike = synapse_mon.u_S[0][spk_index] ax[3].vlines(synapse_mon.t[spk_index]/ms, np.zeros(nspikes), x_S_spike*u_S_spike/(1-u_S_spike)) pu.adjust_spines(ax[3], ['left', 'bottom']) ax[3].set(xlim=(0., duration/ms), ylim=(-0.01, 0.62), yticks=np.arange(0, 0.62, 0.2), xlabel='time (ms)', ylabel='$r_S$') pu.adjust_ylabels(ax, x_offset=-0.20) plt.show()brian2-2.5.4/examples/frompapers/Stimberg_et_al_2018/example_2_gchi_astrocyte.py000066400000000000000000000203521445201106100275760ustar00rootroot00000000000000# coding=utf-8 """ Modeling neuron-glia interactions with the Brian 2 simulator Marcel Stimberg, Dan F. M. Goodman, Romain Brette, Maurizio De Pittà bioRxiv 198366; doi: https://doi.org/10.1101/198366 Figure 2: Modeling of synaptically-activated astrocytes Two astrocytes (one stochastic and the other deterministic) activated by synapses (connecting "dummy" groups of neurons) (see De Pitta' et al., 2009) """ from brian2 import * import plot_utils as pu set_device('cpp_standalone', directory=None) # Use fast "C++ standalone mode" seed(790824) # to get identical figures for repeated runs ################################################################################ # Model parameters ################################################################################ ### General parameters duration = 30*second # Total simulation time sim_dt = 1*ms # Integrator/sampling step ### Neuron parameters f_0 = 0.5*Hz # Spike rate of the "source" neurons ### Synapse parameters rho_c = 0.001 # Synaptic vesicle-to-extracellular space volume ratio Y_T = 500*mmolar # Total vesicular neurotransmitter concentration Omega_c = 40/second # Neurotransmitter clearance rate ### Astrocyte parameters # --- Calcium fluxes O_P = 0.9*umolar/second # Maximal Ca^2+ uptake rate by SERCAs K_P = 0.1 * umolar # Ca2+ affinity of SERCAs C_T = 2*umolar # Total cell free Ca^2+ content rho_A = 0.18 # ER-to-cytoplasm volume ratio Omega_C = 6/second # Maximal rate of Ca^2+ release by IP_3Rs Omega_L = 0.1/second # Maximal rate of Ca^2+ leak from the ER # --- IP_3R kinectics d_1 = 0.13*umolar # IP_3 binding affinity d_2 = 1.05*umolar # Ca^2+ inactivation dissociation constant O_2 = 0.2/umolar/second # IP_3R binding rate for Ca^2+ inhibition d_3 = 0.9434*umolar # IP_3 dissociation constant d_5 = 0.08*umolar # Ca^2+ activation dissociation constant # --- Agonist-dependent IP_3 production O_beta = 5*umolar/second # Maximal rate of IP_3 production by PLCbeta O_N = 0.3/umolar/second # Agonist binding rate Omega_N = 0.5/second # Maximal inactivation rate K_KC = 0.5*umolar # Ca^2+ affinity of PKC zeta = 10 # Maximal reduction of receptor affinity by PKC # --- IP_3 production O_delta = 0.2 *umolar/second # Maximal rate of IP_3 production by PLCdelta kappa_delta = 1.5 * umolar # Inhibition constant of PLC_delta by IP_3 K_delta = 0.3*umolar # Ca^2+ affinity of PLCdelta # --- IP_3 degradation Omega_5P = 0.1/second # Maximal rate of IP_3 degradation by IP-5P K_D = 0.5*umolar # Ca^2+ affinity of IP3-3K K_3K = 1*umolar # IP_3 affinity of IP_3-3K O_3K = 4.5*umolar/second # Maximal rate of IP_3 degradation by IP_3-3K # --- IP_3 external production F_ex = 0.09*umolar/second # Maximal exogenous IP3 flow I_Theta = 0.3*umolar # Threshold gradient for IP_3 diffusion omega_I = 0.05*umolar # Scaling factor of diffusion ################################################################################ # Model definition ################################################################################ defaultclock.dt = sim_dt # Set the integration time ### "Neurons" # (We are only interested in the activity of the synapse, so we replace the # neurons by trivial "dummy" groups # # Regular spiking neuron source_neurons = NeuronGroup(1, 'dx/dt = f_0 : 1', threshold='x>1', reset='x=0', method='euler') ## Dummy neuron target_neurons = NeuronGroup(1, '') ### Synapses # Our synapse model is trivial, we are only interested in its neurotransmitter # release synapses_eqs = 'dY_S/dt = -Omega_c * Y_S : mmolar (clock-driven)' synapses_action = 'Y_S += rho_c * Y_T' synapses = Synapses(source_neurons, target_neurons, model=synapses_eqs, on_pre=synapses_action, method='exact') synapses.connect() ### Astrocytes # We are modelling two astrocytes, the first is deterministic while the second # displays stochastic dynamics astro_eqs = ''' # Fraction of activated astrocyte receptors: dGamma_A/dt = O_N * Y_S * (1 - Gamma_A) - Omega_N*(1 + zeta * C/(C + K_KC)) * Gamma_A : 1 # IP_3 dynamics: dI/dt = J_beta + J_delta - J_3K - J_5P + J_ex : mmolar J_beta = O_beta * Gamma_A : mmolar/second J_delta = O_delta/(1 + I/kappa_delta) * C**2/(C**2 + K_delta**2) : mmolar/second J_3K = O_3K * C**4/(C**4 + K_D**4) * I/(I + K_3K) : mmolar/second J_5P = Omega_5P*I : mmolar/second delta_I_bias = I - I_bias : mmolar J_ex = -F_ex/2*(1 + tanh((abs(delta_I_bias) - I_Theta)/omega_I)) * sign(delta_I_bias) : mmolar/second I_bias : mmolar (constant) # Ca^2+-induced Ca^2+ release: dC/dt = J_r + J_l - J_p : mmolar # IP3R de-inactivation probability dh/dt = (h_inf - h_clipped)/tau_h * (1 + noise*xi*tau_h**0.5) : 1 h_clipped = clip(h,0,1) : 1 J_r = (Omega_C * m_inf**3 * h_clipped**3) * (C_T - (1 + rho_A)*C) : mmolar/second J_l = Omega_L * (C_T - (1 + rho_A)*C) : mmolar/second J_p = O_P * C**2/(C**2 + K_P**2) : mmolar/second m_inf = I/(I + d_1) * C/(C + d_5) : 1 h_inf = Q_2/(Q_2 + C) : 1 tau_h = 1/(O_2 * (Q_2 + C)) : second Q_2 = d_2 * (I + d_1)/(I + d_3) : mmolar # Neurotransmitter concentration in the extracellular space Y_S : mmolar # Noise flag noise : 1 (constant) ''' # Milstein integration method for the multiplicative noise astrocytes = NeuronGroup(2, astro_eqs, method='milstein') astrocytes.h = 0.9 # IP3Rs are initially mostly available for CICR # The first astrocyte is deterministic ("zero noise"), the second stochastic astrocytes.noise = [0, 1] # Connection between synapses and astrocytes (both astrocytes receive the # same input from the synapse). Note that in this special case, where each # astrocyte is only influenced by the neurotransmitter from a single synapse, # the '(linked)' variable mechanism could be used instead. The mechanism used # below is more general and can add the contribution of several synapses. ecs_syn_to_astro = Synapses(synapses, astrocytes, 'Y_S_post = Y_S_pre : mmolar (summed)') ecs_syn_to_astro.connect() ################################################################################ # Monitors ################################################################################ astro_mon = StateMonitor(astrocytes, variables=['Gamma_A', 'C', 'h', 'I'], record=True) ################################################################################ # Simulation run ################################################################################ run(duration, report='text') ################################################################################ # Analysis and plotting ################################################################################ from matplotlib.ticker import FormatStrFormatter plt.style.use('figures.mplstyle') # Plot Gamma_A fig, ax = plt.subplots(4, 1, figsize=(6.26894, 6.26894*0.66)) ax[0].plot(astro_mon.t/second, astro_mon.Gamma_A.T) ax[0].set(xlim=(0., duration/second), ylim=[-0.05, 1.02], yticks=[0.0, 0.5, 1.0], ylabel=r'$\Gamma_{A}$') # Adjust axis pu.adjust_spines(ax[0], ['left']) # Plot I ax[1].plot(astro_mon.t/second, astro_mon.I.T/umolar) ax[1].set(xlim=(0., duration/second), ylim=[-0.1, 5.0], yticks=arange(0.0, 5.1, 1., dtype=float), ylabel=r'$I$ ($\mu M$)') ax[1].yaxis.set_major_formatter(FormatStrFormatter('%.1f')) ax[1].legend(['deterministic', 'stochastic'], loc='upper left') pu.adjust_spines(ax[1], ['left']) # Plot C ax[2].plot(astro_mon.t/second, astro_mon.C.T/umolar) ax[2].set(xlim=(0., duration/second), ylim=[-0.1, 1.3], ylabel=r'$C$ ($\mu M$)') pu.adjust_spines(ax[2], ['left']) # Plot h ax[3].plot(astro_mon.t/second, astro_mon.h.T) ax[3].set(xlim=(0., duration/second), ylim=[0.4, 1.02], ylabel='h', xlabel='time ($s$)') pu.adjust_spines(ax[3], ['left', 'bottom']) pu.adjust_ylabels(ax, x_offset=-0.1) plt.show()brian2-2.5.4/examples/frompapers/Stimberg_et_al_2018/example_3_io_synapse.py000066400000000000000000000272151445201106100267460ustar00rootroot00000000000000# coding=utf-8 """ Modeling neuron-glia interactions with the Brian 2 simulator Marcel Stimberg, Dan F. M. Goodman, Romain Brette, Maurizio De Pittà bioRxiv 198366; doi: https://doi.org/10.1101/198366 Figure 3: Modeling of modulation of synaptic release by gliotransmission. Three synapses: the first one without astrocyte, the remaining two respectively with open-loop and close-loop gliotransmission (see De Pitta' et al., 2011, 2016) """ from brian2 import * import plot_utils as pu set_device('cpp_standalone', directory=None) # Use fast "C++ standalone mode" ################################################################################ # Model parameters ################################################################################ ### General parameters transient = 16.5*second duration = transient + 600*ms # Total simulation time sim_dt = 1*ms # Integrator/sampling step ### Synapse parameters rho_c = 0.005 # Synaptic vesicle-to-extracellular space volume ratio Y_T = 500*mmolar # Total vesicular neurotransmitter concentration Omega_c = 40/second # Neurotransmitter clearance rate U_0__star = 0.6 # Resting synaptic release probability Omega_f = 3.33/second # Synaptic facilitation rate Omega_d = 2.0/second # Synaptic depression rate # --- Presynaptic receptors O_G = 1.5/umolar/second # Agonist binding (activating) rate Omega_G = 0.5/(60*second) # Agonist release (deactivating) rate ### Astrocyte parameters # --- Calcium fluxes O_P = 0.9*umolar/second # Maximal Ca^2+ uptake rate by SERCAs K_P = 0.05 * umolar # Ca2+ affinity of SERCAs C_T = 2*umolar # Total cell free Ca^2+ content rho_A = 0.18 # ER-to-cytoplasm volume ratio Omega_C = 6/second # Maximal rate of Ca^2+ release by IP_3Rs Omega_L = 0.1/second # Maximal rate of Ca^2+ leak from the ER # --- IP_3R kinectics d_1 = 0.13*umolar # IP_3 binding affinity d_2 = 1.05*umolar # Ca^2+ inactivation dissociation constant O_2 = 0.2/umolar/second # IP_3R binding rate for Ca^2+ inhibition d_3 = 0.9434*umolar # IP_3 dissociation constant d_5 = 0.08*umolar # Ca^2+ activation dissociation constant # --- IP_3 production O_delta = 0.6*umolar/second # Maximal rate of IP_3 production by PLCdelta kappa_delta = 1.5* umolar # Inhibition constant of PLC_delta by IP_3 K_delta = 0.1*umolar # Ca^2+ affinity of PLCdelta # --- IP_3 degradation Omega_5P = 0.05/second # Maximal rate of IP_3 degradation by IP-5P K_D = 0.7*umolar # Ca^2+ affinity of IP3-3K K_3K = 1.0*umolar # IP_3 affinity of IP_3-3K O_3K = 4.5*umolar/second # Maximal rate of IP_3 degradation by IP_3-3K # --- IP_3 diffusion F_ex = 2.0*umolar/second # Maximal exogenous IP3 flow I_Theta = 0.3*umolar # Threshold gradient for IP_3 diffusion omega_I = 0.05*umolar # Scaling factor of diffusion # --- Gliotransmitter release and time course C_Theta = 0.5*umolar # Ca^2+ threshold for exocytosis Omega_A = 0.6/second # Gliotransmitter recycling rate U_A = 0.6 # Gliotransmitter release probability G_T = 200*mmolar # Total vesicular gliotransmitter concentration rho_e = 6.5e-4 # Astrocytic vesicle-to-extracellular volume ratio Omega_e = 60/second # Gliotransmitter clearance rate alpha = 0.0 # Gliotransmission nature ################################################################################ # Model definition ################################################################################ defaultclock.dt = sim_dt # Set the integration time ### "Neurons" # We are only interested in the activity of the synapse, so we replace the # neurons by trivial "dummy" groups spikes = [0, 50, 100, 150, 200, 300, 310, 320, 330, 340, 350, 360, 370, 380, 390, 400]*ms spikes += transient # allow for some initial transient source_neurons = SpikeGeneratorGroup(1, np.zeros(len(spikes)), spikes) target_neurons = NeuronGroup(1, '') ### Synapses # Note that the synapse does not actually have any effect on the post-synaptic # target # Also note that for easier plotting we do not use the "event-driven" flag here, # even though the value of u_S and x_S only needs to be updated on the arrival # of a spike synapses_eqs = ''' # Neurotransmitter dY_S/dt = -Omega_c * Y_S : mmolar (clock-driven) # Fraction of activated presynaptic receptors dGamma_S/dt = O_G * G_A * (1 - Gamma_S) - Omega_G * Gamma_S : 1 (clock-driven) # Usage of releasable neurotransmitter per single action potential: du_S/dt = -Omega_f * u_S : 1 (clock-driven) # Fraction of synaptic neurotransmitter resources available: dx_S/dt = Omega_d *(1 - x_S) : 1 (clock-driven) # released synaptic neurotransmitter resources: r_S : 1 # gliotransmitter concentration in the extracellular space: G_A : mmolar ''' synapses_action = ''' U_0 = (1 - Gamma_S) * U_0__star + alpha * Gamma_S u_S += U_0 * (1 - u_S) r_S = u_S * x_S x_S -= r_S Y_S += rho_c * Y_T * r_S ''' synapses = Synapses(source_neurons, target_neurons, model=synapses_eqs, on_pre=synapses_action, method='exact') # We create three synapses, only the second and third ones are modulated by astrocytes synapses.connect(True, n=3) ### Astrocytes # The astrocyte emits gliotransmitter when its Ca^2+ concentration crosses # a threshold astro_eqs = ''' # IP_3 dynamics: dI/dt = J_delta - J_3K - J_5P + J_ex : mmolar J_delta = O_delta/(1 + I/kappa_delta) * C**2/(C**2 + K_delta**2) : mmolar/second J_3K = O_3K * C**4/(C**4 + K_D**4) * I/(I + K_3K) : mmolar/second J_5P = Omega_5P*I : mmolar/second # Exogenous stimulation delta_I_bias = I - I_bias : mmolar J_ex = -F_ex/2*(1 + tanh((abs(delta_I_bias) - I_Theta)/omega_I)) * sign(delta_I_bias) : mmolar/second I_bias : mmolar (constant) # Ca^2+-induced Ca^2+ release: dC/dt = (Omega_C * m_inf**3 * h**3 + Omega_L) * (C_T - (1 + rho_A)*C) - O_P * C**2/(C**2 + K_P**2) : mmolar dh/dt = (h_inf - h)/tau_h : 1 # IP3R de-inactivation probability m_inf = I/(I + d_1) * C/(C + d_5) : 1 h_inf = Q_2/(Q_2 + C) : 1 tau_h = 1/(O_2 * (Q_2 + C)) : second Q_2 = d_2 * (I + d_1)/(I + d_3) : mmolar # Fraction of gliotransmitter resources available: dx_A/dt = Omega_A * (1 - x_A) : 1 # gliotransmitter concentration in the extracellular space: dG_A/dt = -Omega_e*G_A : mmolar ''' glio_release = ''' G_A += rho_e * G_T * U_A * x_A x_A -= U_A * x_A ''' # The following formulation makes sure that a "spike" is only triggered at the # first threshold crossing -- the astrocyte is considered "refractory" (i.e., # not allowed to trigger another event) as long as the Ca2+ concentration # remains above threshold # The gliotransmitter release happens when the threshold is crossed, in Brian # terms it can therefore be considered a "reset" astrocyte = NeuronGroup(2, astro_eqs, threshold='C>C_Theta', refractory='C>C_Theta', reset=glio_release, method='rk4') # Different length of stimulation astrocyte.x_A = 1.0 astrocyte.h = 0.9 astrocyte.I = 0.4*umolar astrocyte.I_bias = np.asarray([0.8, 1.25])*umolar # Connection between astrocytes and the second synapse. Note that in this # special case, where the synapse is only influenced by the gliotransmitter from # a single astrocyte, the '(linked)' variable mechanism could be used instead. # The mechanism used below is more general and can add the contribution of # several astrocytes ecs_astro_to_syn = Synapses(astrocyte, synapses, 'G_A_post = G_A_pre : mmolar (summed)') # Connect second and third synapse to a different astrocyte ecs_astro_to_syn.connect(j='i+1') ################################################################################ # Monitors ################################################################################ # Note that we cannot use "record=True" for synapses in C++ standalone mode -- # the StateMonitor needs to know the number of elements to record from during # its initialization, but in C++ standalone mode, no synapses have been created # yet. We therefore explicitly state to record from the three synapses. syn_mon = StateMonitor(synapses, variables=['u_S', 'x_S', 'r_S', 'Y_S'], record=[0, 1, 2]) ast_mon = StateMonitor(astrocyte, variables=['C', 'G_A'], record=True) ################################################################################ # Simulation run ################################################################################ run(duration, report='text') ################################################################################ # Analysis and plotting ################################################################################ from matplotlib import cycler plt.style.use('figures.mplstyle') fig, ax = plt.subplots(nrows=7, ncols=1, figsize=(6.26894, 6.26894 * 1.2), gridspec_kw={'height_ratios': [3, 2, 1, 1, 3, 3, 3], 'top': 0.98, 'bottom': 0.08, 'left': 0.15, 'right': 0.95}) ## Ca^2+ traces of the two astrocytes ax[0].plot((ast_mon.t-transient)/second, ast_mon.C[0]/umolar, '-', color='C2') ax[0].plot((ast_mon.t-transient)/second, ast_mon.C[1]/umolar, '-', color='C3') ## Add threshold for gliotransmitter release ax[0].plot(np.asarray([-transient/second, 0.0]), np.asarray([C_Theta, C_Theta])/umolar, ':', color='gray') ax[0].set(xlim=[-transient/second, 0.0], yticks=[0., 0.4, 0.8, 1.2], ylabel=r'$C$ ($\mu$M)') pu.adjust_spines(ax[0], ['left']) ## Gliotransmitter concentration in the extracellular space ax[1].plot((ast_mon.t-transient)/second, ast_mon.G_A[0]/umolar, '-', color='C2') ax[1].plot((ast_mon.t-transient)/second, ast_mon.G_A[1]/umolar, '-', color='C3') ax[1].set(yticks=[0., 50., 100.], xlim=[-transient/second, 0.0], xlabel='time (s)', ylabel=r'$G_A$ ($\mu$M)') pu.adjust_spines(ax[1], ['left', 'bottom']) ## Turn off one axis to display x-labeling of ax[1] correctly ax[2].axis('off') ## Synaptic stimulation ax[3].vlines((spikes-transient)/ms, 0, 1, clip_on=False) ax[3].set(xlim=(0, (duration-transient)/ms)) ax[3].axis('off') ## Synaptic variables # Use a custom cycle that uses black as the first color prop_cycle = cycler(color='k').concat(matplotlib.rcParams['axes.prop_cycle'][2:]) ax[4].set(xlim=(0, (duration-transient)/ms), ylim=[0., 1.], yticks=np.arange(0, 1.1, .25), ylabel='$u_S$', prop_cycle=prop_cycle) ax[4].plot((syn_mon.t-transient)/ms, syn_mon.u_S.T) pu.adjust_spines(ax[4], ['left']) ax[5].set(xlim=(0, (duration-transient)/ms), ylim=[-0.05, 1.], yticks=np.arange(0, 1.1, .25), ylabel='$x_S$', prop_cycle=prop_cycle) ax[5].plot((syn_mon.t-transient)/ms, syn_mon.x_S.T) pu.adjust_spines(ax[5], ['left']) ax[6].set(xlim=(0, (duration-transient)/ms), ylim=(-5., 1500), xticks=np.arange(0, (duration-transient)/ms, 100), xlabel='time (ms)', yticks=[0, 500, 1000, 1500], ylabel=r'$Y_S$ ($\mu$M)', prop_cycle=prop_cycle) ax[6].plot((syn_mon.t-transient)/ms, syn_mon.Y_S.T/umolar) ax[6].legend(['no gliotransmission', 'weak gliotransmission', 'stronger gliotransmission'], loc='upper right') pu.adjust_spines(ax[6], ['left', 'bottom']) pu.adjust_ylabels(ax, x_offset=-0.11) plt.show()brian2-2.5.4/examples/frompapers/Stimberg_et_al_2018/example_4_rsmean.py000066400000000000000000000264431445201106100260650ustar00rootroot00000000000000# coding=utf-8 """ Modeling neuron-glia interactions with the Brian 2 simulator Marcel Stimberg, Dan F. M. Goodman, Romain Brette, Maurizio De Pittà bioRxiv 198366; doi: https://doi.org/10.1101/198366 Figure 4C: Closed-loop gliotransmission. I/O curves in terms average per-spike release vs. rate of stimulation for three synapses: one without gliotransmission, and the other two with open- and close-loop gliotransmssion. """ from brian2 import * import plot_utils as pu set_device('cpp_standalone', directory=None) # Use fast "C++ standalone mode" seed(1929) # to get identical figures for repeated runs ################################################################################ # Model parameters ################################################################################ ### General parameters N_synapses = 100 N_astro = 2 transient = 15*second duration = transient + 180*second # Total simulation time sim_dt = 1*ms # Integrator/sampling step ### Neuron parameters # ### Synapse parameters ### Synapse parameters rho_c = 0.005 # Synaptic vesicle-to-extracellular space volume ratio Y_T = 500*mmolar # Total vesicular neurotransmitter concentration Omega_c = 40/second # Neurotransmitter clearance rate U_0__star = 0.6 # Resting synaptic release probability Omega_f = 3.33/second # Synaptic facilitation rate Omega_d = 2.0/second # Synaptic depression rate # --- Presynaptic receptors O_G = 1.5/umolar/second # Agonist binding (activating) rate Omega_G = 0.5/(60*second) # Agonist release (deactivating) rate ### Astrocyte parameters # --- Calcium fluxes O_P = 0.9*umolar/second # Maximal Ca^2+ uptake rate by SERCAs K_P = 0.05 * umolar # Ca2+ affinity of SERCAs C_T = 2*umolar # Total cell free Ca^2+ content rho_A = 0.18 # ER-to-cytoplasm volume ratio Omega_C = 6/second # Maximal rate of Ca^2+ release by IP_3Rs Omega_L = 0.1/second # Maximal rate of Ca^2+ leak from the ER # --- IP_3R kinectics d_1 = 0.13*umolar # IP_3 binding affinity d_2 = 1.05*umolar # Ca^2+ inactivation dissociation constant O_2 = 0.2/umolar/second # IP_3R binding rate for Ca^2+ inhibition d_3 = 0.9434*umolar # IP_3 dissociation constant d_5 = 0.08*umolar # Ca^2+ activation dissociation constant # --- IP_3 production # --- Agonist-dependent IP_3 production O_beta = 3.2*umolar/second # Maximal rate of IP_3 production by PLCbeta O_N = 0.3/umolar/second # Agonist binding rate Omega_N = 0.5/second # Maximal inactivation rate K_KC = 0.5*umolar # Ca^2+ affinity of PKC zeta = 10 # Maximal reduction of receptor affinity by PKC # --- Endogenous IP3 production O_delta = 0.6*umolar/second # Maximal rate of IP_3 production by PLCdelta kappa_delta = 1.5* umolar # Inhibition constant of PLC_delta by IP_3 K_delta = 0.1*umolar # Ca^2+ affinity of PLCdelta # --- IP_3 degradation Omega_5P = 0.05/second # Maximal rate of IP_3 degradation by IP-5P K_D = 0.7*umolar # Ca^2+ affinity of IP3-3K K_3K = 1.0*umolar # IP_3 affinity of IP_3-3K O_3K = 4.5*umolar/second # Maximal rate of IP_3 degradation by IP_3-3K # --- IP_3 diffusion F_ex = 2.0*umolar/second # Maximal exogenous IP3 flow I_Theta = 0.3*umolar # Threshold gradient for IP_3 diffusion omega_I = 0.05*umolar # Scaling factor of diffusion # --- Gliotransmitter release and time course C_Theta = 0.5*umolar # Ca^2+ threshold for exocytosis Omega_A = 0.6/second # Gliotransmitter recycling rate U_A = 0.6 # Gliotransmitter release probability G_T = 200*mmolar # Total vesicular gliotransmitter concentration rho_e = 6.5e-4 # Astrocytic vesicle-to-extracellular volume ratio Omega_e = 60/second # Gliotransmitter clearance rate alpha = 0.0 # Gliotransmission nature ################################################################################ # Model definition ################################################################################ defaultclock.dt = sim_dt # Set the integration time f_vals = np.logspace(-1, 2, N_synapses)*Hz source_neurons = PoissonGroup(N_synapses, rates=f_vals) target_neurons = NeuronGroup(N_synapses, '') ### Synapses # Note that the synapse does not actually have any effect on the post-synaptic # target # Also note that for easier plotting we do not use the "event-driven" flag here, # even though the value of u_S and x_S only needs to be updated on the arrival # of a spike synapses_eqs = ''' # Neurotransmitter dY_S/dt = -Omega_c * Y_S : mmolar (clock-driven) # Fraction of activated presynaptic receptors dGamma_S/dt = O_G * G_A * (1 - Gamma_S) - Omega_G * Gamma_S : 1 (clock-driven) # Usage of releasable neurotransmitter per single action potential: du_S/dt = -Omega_f * u_S : 1 (event-driven) # Fraction of synaptic neurotransmitter resources available for release: dx_S/dt = Omega_d *(1 - x_S) : 1 (event-driven) r_S : 1 # released synaptic neurotransmitter resources G_A : mmolar # gliotransmitter concentration in the extracellular space ''' synapses_action = ''' U_0 = (1 - Gamma_S) * U_0__star + alpha * Gamma_S u_S += U_0 * (1 - u_S) r_S = u_S * x_S x_S -= r_S Y_S += rho_c * Y_T * r_S ''' synapses = Synapses(source_neurons, target_neurons, model=synapses_eqs, on_pre=synapses_action, method='exact') # We create three synapses per connection: only the first two are modulated by # the astrocyte however. Note that we could also create three synapses per # connection with a single connect call by using connect(j='i', n=3), but this # would create synapses arranged differently (synapses connection pairs # (0, 0), (0, 0), (0, 0), (1, 1), (1, 1), (1, 1), ..., instead of # connections (0, 0), (1, 1), ..., (0, 0), (1, 1), ..., (0, 0), (1, 1), ...) # making the later connection descriptions more complicated. synapses.connect(j='i') # closed-loop modulation synapses.connect(j='i') # open modulation synapses.connect(j='i') # no modulation synapses.x_S = 1.0 ### Astrocytes # The astrocyte emits gliotransmitter when its Ca^2+ concentration crosses # a threshold astro_eqs = ''' # Fraction of activated astrocyte receptors: dGamma_A/dt = O_N * Y_S * (1 - Gamma_A) - Omega_N*(1 + zeta * C/(C + K_KC)) * Gamma_A : 1 # IP_3 dynamics: dI/dt = J_beta + J_delta - J_3K - J_5P + J_ex : mmolar J_beta = O_beta * Gamma_A : mmolar/second J_delta = O_delta/(1 + I/kappa_delta) * C**2/(C**2 + K_delta**2) : mmolar/second J_3K = O_3K * C**4/(C**4 + K_D**4) * I/(I + K_3K) : mmolar/second J_5P = Omega_5P*I : mmolar/second delta_I_bias = I - I_bias : mmolar J_ex = -F_ex/2*(1 + tanh((abs(delta_I_bias) - I_Theta)/omega_I)) * sign(delta_I_bias) : mmolar/second I_bias : mmolar (constant) # Ca^2+-induced Ca^2+ release: dC/dt = (Omega_C * m_inf**3 * h**3 + Omega_L) * (C_T - (1 + rho_A)*C) - O_P * C**2/(C**2 + K_P**2) : mmolar dh/dt = (h_inf - h)/tau_h : 1 # IP3R de-inactivation probability m_inf = I/(I + d_1) * C/(C + d_5) : 1 h_inf = Q_2/(Q_2 + C) : 1 tau_h = 1/(O_2 * (Q_2 + C)) : second Q_2 = d_2 * (I + d_1)/(I + d_3) : mmolar # Fraction of gliotransmitter resources available for release dx_A/dt = Omega_A * (1 - x_A) : 1 # gliotransmitter concentration in the extracellular space dG_A/dt = -Omega_e*G_A : mmolar # Neurotransmitter concentration in the extracellular space Y_S : mmolar ''' glio_release = ''' G_A += rho_e * G_T * U_A * x_A x_A -= U_A * x_A ''' astrocyte = NeuronGroup(N_astro*N_synapses, astro_eqs, # The following formulation makes sure that a "spike" is # only triggered at the first threshold crossing threshold='C>C_Theta', refractory='C>C_Theta', # The gliotransmitter release happens when the threshold # is crossed, in Brian terms it can therefore be # considered a "reset" reset=glio_release, method='rk4') astrocyte.h = 0.9 astrocyte.x_A = 1.0 # Only the second group of N_synapses astrocytes are activated by external stimulation astrocyte.I_bias = (np.r_[np.zeros(N_synapses), np.ones(N_synapses)])*1.0*umolar ## Connections ecs_syn_to_astro = Synapses(synapses, astrocyte, 'Y_S_post = Y_S_pre : mmolar (summed)') # Connect the first N_synapses synapses--astrocyte pairs ecs_syn_to_astro.connect(j='i if i < N_synapses') ecs_astro_to_syn = Synapses(astrocyte, synapses, 'G_A_post = G_A_pre : mmolar (summed)') # Connect the first N_synapses astrocytes--pairs # (closed-loop configuration) ecs_astro_to_syn.connect(j='i if i < N_synapses') # Connect the second N_synapses astrocyte--synapses pairs # (open-loop configuration) ecs_astro_to_syn.connect(j='i if i >= N_synapses and i < 2*N_synapses') ################################################################################ # Monitors ################################################################################ syn_mon = StateMonitor(synapses, 'r_S', record=np.arange(N_synapses*(N_astro+1))) ################################################################################ # Simulation run ################################################################################ run(duration, report='text') ################################################################################ # Analysis and plotting ################################################################################ plt.style.use('figures.mplstyle') fig, ax = plt.subplots(nrows=4, ncols=1, figsize=(3.07, 3.07*1.33), sharex=False, gridspec_kw={'height_ratios': [1, 3, 3, 3], 'top': 0.98, 'bottom': 0.12, 'left': 0.22, 'right': 0.93}) ## Turn off one axis to display accordingly to the other figure in example_4_synrel.py ax[0].axis('off') ax[1].errorbar(f_vals/Hz, np.mean(syn_mon.r_S[2*N_synapses:], axis=1), np.std(syn_mon.r_S[2*N_synapses:], axis=1), fmt='o', color='black', lw=0.5) ax[1].set(xlim=(0.08, 100), xscale='log', ylim=(0., 0.7), ylabel=r'$\langle r_S \rangle$') pu.adjust_spines(ax[1], ['left']) ax[2].errorbar(f_vals/Hz, np.mean(syn_mon.r_S[N_synapses:2*N_synapses], axis=1), np.std(syn_mon.r_S[N_synapses:2*N_synapses], axis=1), fmt='o', color='C2', lw=0.5) ax[2].set(xlim=(0.08, 100), xscale='log', ylim=(0., 0.2), ylabel=r'$\langle r_S \rangle$') pu.adjust_spines(ax[2], ['left']) ax[3].errorbar(f_vals/Hz, np.mean(syn_mon.r_S[:N_synapses], axis=1), np.std(syn_mon.r_S[:N_synapses], axis=1), fmt='o', color='C3', lw=0.5) ax[3].set(xlim=(0.08, 100), xticks=np.logspace(-1, 2, 4), xscale='log', ylim=(0., 0.7), xlabel='input frequency (Hz)', ylabel=r'$\langle r_S \rangle$') ax[3].xaxis.set_major_formatter(ScalarFormatter()) pu.adjust_spines(ax[3], ['left', 'bottom']) pu.adjust_ylabels(ax, x_offset=-0.2) plt.show()brian2-2.5.4/examples/frompapers/Stimberg_et_al_2018/example_4_synrel.py000066400000000000000000000275331445201106100261150ustar00rootroot00000000000000# coding=utf-8 """ Modeling neuron-glia interactions with the Brian 2 simulator Marcel Stimberg, Dan F. M. Goodman, Romain Brette, Maurizio De Pittà bioRxiv 198366; doi: https://doi.org/10.1101/198366 Figure 4B: Closed-loop gliotransmission. Extracellular neurotransmitter concentration (averaged across 500 synapses) for three step increases of the presynaptic rate, for three synapses: one without gliotransmission, and the other two with open- and close-loop gliotransmssion. """ from brian2 import * import plot_utils as pu set_device('cpp_standalone', directory=None) # Use fast "C++ standalone mode" seed(16283) # to get identical figures for repeated runs ################################################################################ # Model parameters ################################################################################ ### General parameters N_synapses = 500 N_astro = 2 duration = 20*second # Total simulation time sim_dt = 1*ms # Integrator/sampling step ### Neuron parameters # ### Synapse parameters ### Synapse parameters rho_c = 0.005 # Synaptic vesicle-to-extracellular space volume ratio Y_T = 500*mmolar # Total vesicular neurotransmitter concentration Omega_c = 40/second # Neurotransmitter clearance rate U_0__star = 0.6 # Resting synaptic release probability Omega_f = 3.33/second # Synaptic facilitation rate Omega_d = 2.0/second # Synaptic depression rate # --- Presynaptic receptors O_G = 1.5/umolar/second # Agonist binding (activating) rate Omega_G = 0.5/(60*second) # Agonist release (deactivating) rate ### Astrocyte parameters # --- Calcium fluxes O_P = 0.9*umolar/second # Maximal Ca^2+ uptake rate by SERCAs K_P = 0.05 * umolar # Ca2+ affinity of SERCAs C_T = 2*umolar # Total cell free Ca^2+ content rho_A = 0.18 # ER-to-cytoplasm volume ratio Omega_C = 6/second # Maximal rate of Ca^2+ release by IP_3Rs Omega_L = 0.1/second # Maximal rate of Ca^2+ leak from the ER # --- IP_3R kinectics d_1 = 0.13*umolar # IP_3 binding affinity d_2 = 1.05*umolar # Ca^2+ inactivation dissociation constant O_2 = 0.2/umolar/second # IP_3R binding rate for Ca^2+ inhibition d_3 = 0.9434*umolar # IP_3 dissociation constant d_5 = 0.08*umolar # Ca^2+ activation dissociation constant # --- IP_3 production # --- Agonist-dependent IP_3 production O_beta = 3.2*umolar/second # Maximal rate of IP_3 production by PLCbeta O_N = 0.3/umolar/second # Agonist binding rate Omega_N = 0.5/second # Maximal inactivation rate K_KC = 0.5*umolar # Ca^2+ affinity of PKC zeta = 10 # Maximal reduction of receptor affinity by PKC # --- Endogenous IP3 production O_delta = 0.6*umolar/second # Maximal rate of IP_3 production by PLCdelta kappa_delta = 1.5* umolar # Inhibition constant of PLC_delta by IP_3 K_delta = 0.1*umolar # Ca^2+ affinity of PLCdelta # --- IP_3 diffusion F = 2*umolar/second # GJC IP_3 permeability I_Theta = 0.3*umolar # Threshold gradient for IP_3 diffusion omega_I = 0.05*umolar # Scaling factor of diffusion # --- IP_3 degradation Omega_5P = 0.05/second # Maximal rate of IP_3 degradation by IP-5P K_D = 0.7*umolar # Ca^2+ affinity of IP3-3K K_3K = 1.0*umolar # IP_3 affinity of IP_3-3K O_3K = 4.5*umolar/second # Maximal rate of IP_3 degradation by IP_3-3K # --- IP_3 diffusion F_ex = 2.0*umolar/second # Maximal exogenous IP3 flow I_Theta = 0.3*umolar # Threshold gradient for IP_3 diffusion omega_I = 0.05*umolar # Scaling factor of diffusion # --- Gliotransmitter release and time course C_Theta = 0.5*umolar # Ca^2+ threshold for exocytosis Omega_A = 0.6/second # Gliotransmitter recycling rate U_A = 0.6 # Gliotransmitter release probability G_T = 200*mmolar # Total vesicular gliotransmitter concentration rho_e = 6.5e-4 # Astrocytic vesicle-to-extracellular volume ratio Omega_e = 60/second # Gliotransmitter clearance rate alpha = 0.0 # Gliotransmission nature ################################################################################ # Model definition ################################################################################ defaultclock.dt = sim_dt # Set the integration time # ### "Neurons" rate_in = TimedArray([0.011, 0.11, 1.1, 11] * Hz, dt=5*second) source_neurons = PoissonGroup(N_synapses, rates='rate_in(t)') target_neurons = NeuronGroup(N_synapses, '') ### Synapses # Note that the synapse does not actually have any effect on the post-synaptic # target # Also note that for easier plotting we do not use the "event-driven" flag here, # even though the value of u_S and x_S only needs to be updated on the arrival # of a spike synapses_eqs = ''' # Neurotransmitter dY_S/dt = -Omega_c * Y_S : mmolar (clock-driven) # Fraction of activated presynaptic receptors dGamma_S/dt = O_G * G_A * (1 - Gamma_S) - Omega_G * Gamma_S : 1 (clock-driven) # Usage of releasable neurotransmitter per single action potential: du_S/dt = -Omega_f * u_S : 1 (event-driven) # Fraction of synaptic neurotransmitter resources available for release: dx_S/dt = Omega_d *(1 - x_S) : 1 (event-driven) r_S : 1 # released synaptic neurotransmitter resources G_A : mmolar # gliotransmitter concentration in the extracellular space ''' synapses_action = ''' U_0 = (1 - Gamma_S) * U_0__star + alpha * Gamma_S u_S += U_0 * (1 - u_S) r_S = u_S * x_S x_S -= r_S Y_S += rho_c * Y_T * r_S ''' synapses = Synapses(source_neurons, target_neurons, model=synapses_eqs, on_pre=synapses_action, method='exact') # We create three synapses per connection: only the first two are modulated by # the astrocyte however. Note that we could also create three synapses per # connection with a single connect call by using connect(j='i', n=3), but this # would create synapses arranged differently (synapses connection pairs # (0, 0), (0, 0), (0, 0), (1, 1), (1, 1), (1, 1), ..., instead of # connections (0, 0), (1, 1), ..., (0, 0), (1, 1), ..., (0, 0), (1, 1), ...) # making the later connection descriptions more complicated. synapses.connect(j='i') # closed-loop modulation synapses.connect(j='i') # open modulation synapses.connect(j='i') # no modulation synapses.x_S = 1.0 ### Astrocytes # The astrocyte emits gliotransmitter when its Ca^2+ concentration crosses # a threshold astro_eqs = ''' # Fraction of activated astrocyte receptors: dGamma_A/dt = O_N * Y_S * (1 - Gamma_A) - Omega_N*(1 + zeta * C/(C + K_KC)) * Gamma_A : 1 # IP_3 dynamics: dI/dt = J_beta + J_delta - J_3K - J_5P + J_ex : mmolar J_beta = O_beta * Gamma_A : mmolar/second J_delta = O_delta/(1 + I/kappa_delta) * C**2/(C**2 + K_delta**2) : mmolar/second J_3K = O_3K * C**4/(C**4 + K_D**4) * I/(I + K_3K) : mmolar/second J_5P = Omega_5P*I : mmolar/second delta_I_bias = I - I_bias : mmolar J_ex = -F_ex/2*(1 + tanh((abs(delta_I_bias) - I_Theta)/omega_I)) * sign(delta_I_bias) : mmolar/second I_bias : mmolar (constant) # Ca^2+-induced Ca^2+ release: dC/dt = (Omega_C * m_inf**3 * h**3 + Omega_L) * (C_T - (1 + rho_A)*C) - O_P * C**2/(C**2 + K_P**2) : mmolar dh/dt = (h_inf - h)/tau_h : 1 # IP3R de-inactivation probability m_inf = I/(I + d_1) * C/(C + d_5) : 1 h_inf = Q_2/(Q_2 + C) : 1 tau_h = 1/(O_2 * (Q_2 + C)) : second Q_2 = d_2 * (I + d_1)/(I + d_3) : mmolar # Fraction of gliotransmitter resources available for release dx_A/dt = Omega_A * (1 - x_A) : 1 # gliotransmitter concentration in the extracellular space dG_A/dt = -Omega_e*G_A : mmolar # Neurotransmitter concentration in the extracellular space Y_S : mmolar ''' glio_release = ''' G_A += rho_e * G_T * U_A * x_A x_A -= U_A * x_A ''' astrocyte = NeuronGroup(N_astro*N_synapses, astro_eqs, # The following formulation makes sure that a "spike" is # only triggered at the first threshold crossing threshold='C>C_Theta', refractory='C>C_Theta', # The gliotransmitter release happens when the threshold # is crossed, in Brian terms it can therefore be # considered a "reset" reset=glio_release, method='rk4') astrocyte.h = 0.9 astrocyte.x_A = 1.0 # Only the second group of N_synapses astrocytes are activated by external stimulation astrocyte.I_bias = (np.r_[np.zeros(N_synapses), np.ones(N_synapses)])*1.0*umolar ## Connections ecs_syn_to_astro = Synapses(synapses, astrocyte, 'Y_S_post = Y_S_pre : mmolar (summed)') # Connect the first N_synapses synapses--astrocyte pairs ecs_syn_to_astro.connect(j='i if i < N_synapses') ecs_astro_to_syn = Synapses(astrocyte, synapses, 'G_A_post = G_A_pre : mmolar (summed)') # Connect the first N_synapses astrocytes--pairs (closed-loop) ecs_astro_to_syn.connect(j='i if i < N_synapses') # Connect the second N_synapses astrocyte--synapses pairs (open-loop) ecs_astro_to_syn.connect(j='i if i >= N_synapses and i < 2*N_synapses') ################################################################################ # Monitors ################################################################################ syn_mon = StateMonitor(synapses, 'Y_S', record=np.arange(N_synapses*(N_astro+1)), dt=10*ms) ################################################################################ # Simulation run ################################################################################ run(duration, report='text') ################################################################################ # Analysis and plotting ################################################################################ plt.style.use('figures.mplstyle') fig, ax = plt.subplots(nrows=4, ncols=1, figsize=(3.07, 3.07*1.33), sharex=False, gridspec_kw={'height_ratios': [1, 3, 3, 3], 'top': 0.98, 'bottom': 0.12, 'left': 0.24, 'right': 0.95}) ax[0].semilogy(syn_mon.t/second, rate_in(syn_mon.t), '-', color='black') ax[0].set(xlim=(0, duration/second), ylim=(0.01, 12), yticks=[0.01, 0.1, 1, 10], ylabel=r'$\nu_{in}$ (Hz)') ax[0].yaxis.set_major_formatter(ScalarFormatter()) pu.adjust_spines(ax[0], ['left']) ax[1].plot(syn_mon.t/second, np.mean(syn_mon.Y_S[2*N_synapses:]/umolar, axis=0), '-', color='black') ax[1].set(xlim=(0, duration/second), ylim=(-5, 260), yticks=np.arange(0, 260, 50), ylabel=r'$\langle Y_S \rangle$ ($\mu$M)') ax[1].legend(['no gliotransmission'], loc='upper left') pu.adjust_spines(ax[1], ['left']) ax[2].plot(syn_mon.t/second, np.mean(syn_mon.Y_S[N_synapses:2*N_synapses]/umolar, axis=0), '-', color='C2') ax[2].set(xlim=(0, duration/second), ylim=(-3, 150), yticks=np.arange(0, 151, 25), ylabel=r'$\langle Y_S \rangle$ ($\mu$M)') ax[2].legend(['open-loop gliotransmission'], loc='upper left') pu.adjust_spines(ax[2], ['left']) ax[3].plot(syn_mon.t/second, np.mean(syn_mon.Y_S[:N_synapses]/umolar, axis=0), '-', color='C3') ax[3].set(xlim=(0, duration/second), ylim=(-2, 150), xticks=np.arange(0., duration/second+1, 5.0), yticks=np.arange(0, 151, 25), xlabel='time (s)', ylabel=r'$\langle Y_S \rangle$ ($\mu$M)') ax[3].legend(['closed-loop gliotransmission'], loc='upper left') pu.adjust_spines(ax[3], ['left', 'bottom']) pu.adjust_ylabels(ax, x_offset=-0.22) plt.show()brian2-2.5.4/examples/frompapers/Stimberg_et_al_2018/example_5_astro_ring.py000066400000000000000000000150201445201106100267350ustar00rootroot00000000000000# coding=utf-8 """ Modeling neuron-glia interactions with the Brian 2 simulator Marcel Stimberg, Dan F. M. Goodman, Romain Brette, Maurizio De Pittà bioRxiv 198366; doi: https://doi.org/10.1101/198366 Figure 5: Astrocytes connected in a network. Intercellular calcium wave propagation in a ring of 50 astrocytes connected by bidirectional gap junctions (see Goldberg et al., 2010) """ from brian2 import * import plot_utils as pu set_device('cpp_standalone', directory=None) # Use fast "C++ standalone mode" ################################################################################ # Model parameters ################################################################################ ### General parameters duration = 4000*second # Total simulation time sim_dt = 50*ms # Integrator/sampling step ### Astrocyte parameters # --- Calcium fluxes O_P = 0.9*umolar/second # Maximal Ca^2+ uptake rate by SERCAs K_P = 0.05 * umolar # Ca2+ affinity of SERCAs C_T = 2*umolar # Total cell free Ca^2+ content rho_A = 0.18 # ER-to-cytoplasm volume ratio Omega_C = 6/second # Maximal rate of Ca^2+ release by IP_3Rs Omega_L = 0.1/second # Maximal rate of Ca^2+ leak from the ER # --- IP_3R kinectics d_1 = 0.13*umolar # IP_3 binding affinity d_2 = 1.05*umolar # Ca^2+ inactivation dissociation constant O_2 = 0.2/umolar/second # IP_3R binding rate for Ca^2+ inhibition d_3 = 0.9434*umolar # IP_3 dissociation constant d_5 = 0.08*umolar # Ca^2+ activation dissociation constant # --- IP_3 production O_delta = 0.6*umolar/second # Maximal rate of IP_3 production by PLCdelta kappa_delta = 1.5* umolar # Inhibition constant of PLC_delta by IP_3 K_delta = 0.1*umolar # Ca^2+ affinity of PLCdelta # --- IP_3 degradation Omega_5P = 0.05/second # Maximal rate of IP_3 degradation by IP-5P K_D = 0.7*umolar # Ca^2+ affinity of IP3-3K K_3K = 1.0*umolar # IP_3 affinity of IP_3-3K O_3K = 4.5*umolar/second # Maximal rate of IP_3 degradation by IP_3-3K # --- IP_3 diffusion F_ex = 0.09*umolar/second # Maximal exogenous IP3 flow F = 0.09*umolar/second # GJC IP_3 permeability I_Theta = 0.3*umolar # Threshold gradient for IP_3 diffusion omega_I = 0.05*umolar # Scaling factor of diffusion ################################################################################ # Model definition ################################################################################ defaultclock.dt = sim_dt # Set the integration time ### Astrocytes astro_eqs = ''' dI/dt = J_delta - J_3K - J_5P + J_ex + J_coupling : mmolar J_delta = O_delta/(1 + I/kappa_delta) * C**2/(C**2 + K_delta**2) : mmolar/second J_3K = O_3K * C**4/(C**4 + K_D**4) * I/(I + K_3K) : mmolar/second J_5P = Omega_5P*I : mmolar/second # Exogenous stimulation (rectangular wave with period of 50s and duty factor 0.4) stimulus = int((t % (50*second))<20*second) : 1 delta_I_bias = I - I_bias*stimulus : mmolar J_ex = -F_ex/2*(1 + tanh((abs(delta_I_bias) - I_Theta)/omega_I)) * sign(delta_I_bias) : mmolar/second # Diffusion between astrocytes J_coupling : mmolar/second # Ca^2+-induced Ca^2+ release: dC/dt = J_r + J_l - J_p : mmolar dh/dt = (h_inf - h)/tau_h : 1 J_r = (Omega_C * m_inf**3 * h**3) * (C_T - (1 + rho_A)*C) : mmolar/second J_l = Omega_L * (C_T - (1 + rho_A)*C) : mmolar/second J_p = O_P * C**2/(C**2 + K_P**2) : mmolar/second m_inf = I/(I + d_1) * C/(C + d_5) : 1 h_inf = Q_2/(Q_2 + C) : 1 tau_h = 1/(O_2 * (Q_2 + C)) : second Q_2 = d_2 * (I + d_1)/(I + d_3) : mmolar # External IP_3 drive I_bias : mmolar (constant) ''' N_astro = 50 # Total number of astrocytes in the network astrocytes = NeuronGroup(N_astro, astro_eqs, method='rk4') # Asymmetric stimulation on the 50th cell to get some nice chaotic patterns astrocytes.I_bias[N_astro//2] = 1.0*umolar astrocytes.h = 0.9 # Diffusion between astrocytes astro_to_astro_eqs = ''' delta_I = I_post - I_pre : mmolar J_coupling_post = -F/2 * (1 + tanh((abs(delta_I) - I_Theta)/omega_I)) * sign(delta_I) : mmolar/second (summed) ''' astro_to_astro = Synapses(astrocytes, astrocytes, model=astro_to_astro_eqs) # Couple neighboring astrocytes: two connections per astrocyte pair, as # the above formulation will only update the I_coupling term of one of the # astrocytes astro_to_astro.connect('j == (i + 1) % N_pre or ' 'j == (i + N_pre - 1) % N_pre') ################################################################################ # Monitors ################################################################################ astro_mon = StateMonitor(astrocytes, variables=['C'], record=True) ################################################################################ # Simulation run ################################################################################ run(duration, report='text') ################################################################################ # Analysis and plotting ################################################################################ plt.style.use('figures.mplstyle') fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(6.26894, 6.26894 * 0.66), gridspec_kw={'left': 0.1, 'bottom': 0.12}) scaling = 1.2 step = 10 ax.plot(astro_mon.t/second, (astro_mon.C[0:N_astro//2-1].T/astro_mon.C.max() + np.arange(N_astro//2-1)*scaling), color='black') ax.plot(astro_mon.t/second, (astro_mon.C[N_astro//2:].T/astro_mon.C.max() + np.arange(N_astro//2, N_astro)*scaling), color='black') ax.plot(astro_mon.t/second, (astro_mon.C[N_astro//2-1].T/astro_mon.C.max() + np.arange(N_astro//2-1, N_astro//2)*scaling), color='C0') ax.set(xlim=(0., duration/second), ylim=(0, (N_astro+1.5)*scaling), xticks=np.arange(0., duration/second, 500), xlabel='time (s)', yticks=np.arange(0.5*scaling, (N_astro + 1.5)*scaling, step*scaling), yticklabels=[str(yt) for yt in np.arange(0, N_astro + 1, step)], ylabel='$C/C_{max}$ (cell index)') pu.adjust_spines(ax, ['left', 'bottom']) pu.adjust_ylabels([ax], x_offset=-0.08) plt.show()brian2-2.5.4/examples/frompapers/Stimberg_et_al_2018/example_6_COBA_with_astro.py000066400000000000000000000346441445201106100275530ustar00rootroot00000000000000# coding=utf-8 """ Modeling neuron-glia interactions with the Brian 2 simulator Marcel Stimberg, Dan F. M. Goodman, Romain Brette, Maurizio De Pittà bioRxiv 198366; doi: https://doi.org/10.1101/198366 Figure 6: Recurrent neuron-glial network. Randomly connected COBA network (see Brunel, 2000) with excitatory synapses modulated by release-increasing gliotransmission from a randomly connected network of astrocytes. """ from brian2 import * import plot_utils as pu set_device('cpp_standalone', directory=None) # Use fast "C++ standalone mode" seed(28371) # to get identical figures for repeated runs ################################################################################ # Model parameters ################################################################################ ### General parameters N_e = 3200 # Number of excitatory neurons N_i = 800 # Number of inhibitory neurons N_a = 3200 # Number of astrocytes ## Some metrics parameters needed to establish proper connections size = 3.75*mmeter # Length and width of the square lattice distance = 50*umeter # Distance between neurons ### Neuron parameters E_l = -60*mV # Leak reversal potential g_l = 9.99*nS # Leak conductance E_e = 0*mV # Excitatory synaptic reversal potential E_i = -80*mV # Inhibitory synaptic reversal potential C_m = 198*pF # Membrane capacitance tau_e = 5*ms # Excitatory synaptic time constant tau_i = 10*ms # Inhibitory synaptic time constant tau_r = 5*ms # Refractory period I_ex = 100*pA # External current V_th = -50*mV # Firing threshold V_r = E_l # Reset potential ### Synapse parameters rho_c = 0.005 # Synaptic vesicle-to-extracellular space volume ratio Y_T = 500.*mmolar # Total vesicular neurotransmitter concentration Omega_c = 40/second # Neurotransmitter clearance rate U_0__star = 0.6 # Resting synaptic release probability Omega_f = 3.33/second # Synaptic facilitation rate Omega_d = 2.0/second # Synaptic depression rate w_e = 0.05*nS # Excitatory synaptic conductance w_i = 1.0*nS # Inhibitory synaptic conductance # --- Presynaptic receptors O_G = 1.5/umolar/second # Agonist binding (activating) rate Omega_G = 0.5/(60*second) # Agonist release (deactivating) rate ### Astrocyte parameters # --- Calcium fluxes O_P = 0.9*umolar/second # Maximal Ca^2+ uptake rate by SERCAs K_P = 0.05*umolar # Ca2+ affinity of SERCAs C_T = 2*umolar # Total cell free Ca^2+ content rho_A = 0.18 # ER-to-cytoplasm volume ratio Omega_C = 6/second # Maximal rate of Ca^2+ release by IP_3Rs Omega_L = 0.1/second # Maximal rate of Ca^2+ leak from the ER # --- IP_3R kinectics d_1 = 0.13*umolar # IP_3 binding affinity d_2 = 1.05*umolar # Ca^2+ inactivation dissociation constant O_2 = 0.2/umolar/second # IP_3R binding rate for Ca^2+ inhibition d_3 = 0.9434*umolar # IP_3 dissociation constant d_5 = 0.08*umolar # Ca^2+ activation dissociation constant # --- IP_3 production # --- Agonist-dependent IP_3 production O_beta = 0.5*umolar/second # Maximal rate of IP_3 production by PLCbeta O_N = 0.3/umolar/second # Agonist binding rate Omega_N = 0.5/second # Maximal inactivation rate K_KC = 0.5*umolar # Ca^2+ affinity of PKC zeta = 10 # Maximal reduction of receptor affinity by PKC # --- Endogenous IP3 production O_delta = 1.2*umolar/second # Maximal rate of IP_3 production by PLCdelta kappa_delta = 1.5*umolar # Inhibition constant of PLC_delta by IP_3 K_delta = 0.1*umolar # Ca^2+ affinity of PLCdelta # --- IP_3 degradation Omega_5P = 0.05/second # Maximal rate of IP_3 degradation by IP-5P K_D = 0.7*umolar # Ca^2+ affinity of IP3-3K K_3K = 1.0*umolar # IP_3 affinity of IP_3-3K O_3K = 4.5*umolar/second # Maximal rate of IP_3 degradation by IP_3-3K # --- IP_3 diffusion F = 0.09*umolar/second # GJC IP_3 permeability I_Theta = 0.3*umolar # Threshold gradient for IP_3 diffusion omega_I = 0.05*umolar # Scaling factor of diffusion # --- Gliotransmitter release and time course C_Theta = 0.5*umolar # Ca^2+ threshold for exocytosis Omega_A = 0.6/second # Gliotransmitter recycling rate U_A = 0.6 # Gliotransmitter release probability G_T = 200*mmolar # Total vesicular gliotransmitter concentration rho_e = 6.5e-4 # Astrocytic vesicle-to-extracellular volume ratio Omega_e = 60/second # Gliotransmitter clearance rate alpha = 0.0 # Gliotransmission nature ################################################################################ # Define HF stimulus ################################################################################ stimulus = TimedArray([1.0, 1.2, 1.0, 1.0], dt=2*second) ################################################################################ # Simulation time (based on the stimulus) ################################################################################ duration = 8*second # Total simulation time ################################################################################ # Model definition ################################################################################ ### Neurons neuron_eqs = ''' dv/dt = (g_l*(E_l-v) + g_e*(E_e-v) + g_i*(E_i-v) + I_ex*stimulus(t))/C_m : volt (unless refractory) dg_e/dt = -g_e/tau_e : siemens # post-synaptic excitatory conductance dg_i/dt = -g_i/tau_i : siemens # post-synaptic inhibitory conductance # Neuron position in space x : meter (constant) y : meter (constant) ''' neurons = NeuronGroup(N_e + N_i, model=neuron_eqs, threshold='v>V_th', reset='v=V_r', refractory='tau_r', method='euler') exc_neurons = neurons[:N_e] inh_neurons = neurons[N_e:] # Arrange excitatory neurons in a grid N_rows = int(sqrt(N_e)) N_cols = N_e//N_rows grid_dist = (size / N_cols) exc_neurons.x = '(i // N_rows)*grid_dist - N_rows/2.0*grid_dist' exc_neurons.y = '(i % N_rows)*grid_dist - N_cols/2.0*grid_dist' # Random initial membrane potential values and conductances neurons.v = 'E_l + rand()*(V_th-E_l)' neurons.g_e = 'rand()*w_e' neurons.g_i = 'rand()*w_i' ### Synapses synapses_eqs = ''' # Neurotransmitter dY_S/dt = -Omega_c * Y_S : mmolar (clock-driven) # Fraction of activated presynaptic receptors dGamma_S/dt = O_G * G_A * (1 - Gamma_S) - Omega_G * Gamma_S : 1 (clock-driven) # Usage of releasable neurotransmitter per single action potential: du_S/dt = -Omega_f * u_S : 1 (event-driven) # Fraction of synaptic neurotransmitter resources available for release: dx_S/dt = Omega_d *(1 - x_S) : 1 (event-driven) U_0 : 1 # released synaptic neurotransmitter resources: r_S : 1 # gliotransmitter concentration in the extracellular space: G_A : mmolar # which astrocyte covers this synapse ? astrocyte_index : integer (constant) ''' synapses_action = ''' U_0 = (1 - Gamma_S) * U_0__star + alpha * Gamma_S u_S += U_0 * (1 - u_S) r_S = u_S * x_S x_S -= r_S Y_S += rho_c * Y_T * r_S ''' exc_syn = Synapses(exc_neurons, neurons, model=synapses_eqs, on_pre=synapses_action+'g_e_post += w_e*r_S', method='exact') exc_syn.connect(True, p=0.05) exc_syn.x_S = 1.0 inh_syn = Synapses(inh_neurons, neurons, model=synapses_eqs, on_pre=synapses_action+'g_i_post += w_i*r_S', method='exact') inh_syn.connect(True, p=0.2) inh_syn.x_S = 1.0 # Connect excitatory synapses to an astrocyte depending on the position of the # post-synaptic neuron N_rows_a = int(sqrt(N_a)) N_cols_a = N_a/N_rows_a grid_dist = size / N_rows_a exc_syn.astrocyte_index = ('int(x_post/grid_dist) + ' 'N_cols_a*int(y_post/grid_dist)') ### Astrocytes # The astrocyte emits gliotransmitter when its Ca^2+ concentration crosses # a threshold astro_eqs = ''' # Fraction of activated astrocyte receptors: dGamma_A/dt = O_N * Y_S * (1 - clip(Gamma_A,0,1)) - Omega_N*(1 + zeta * C/(C + K_KC)) * clip(Gamma_A,0,1) : 1 # Intracellular IP_3 dI/dt = J_beta + J_delta - J_3K - J_5P + J_coupling : mmolar J_beta = O_beta * Gamma_A : mmolar/second J_delta = O_delta/(1 + I/kappa_delta) * C**2/(C**2 + K_delta**2) : mmolar/second J_3K = O_3K * C**4/(C**4 + K_D**4) * I/(I + K_3K) : mmolar/second J_5P = Omega_5P*I : mmolar/second # Diffusion between astrocytes: J_coupling : mmolar/second # Ca^2+-induced Ca^2+ release: dC/dt = J_r + J_l - J_p : mmolar dh/dt = (h_inf - h)/tau_h : 1 J_r = (Omega_C * m_inf**3 * h**3) * (C_T - (1 + rho_A)*C) : mmolar/second J_l = Omega_L * (C_T - (1 + rho_A)*C) : mmolar/second J_p = O_P * C**2/(C**2 + K_P**2) : mmolar/second m_inf = I/(I + d_1) * C/(C + d_5) : 1 h_inf = Q_2/(Q_2 + C) : 1 tau_h = 1/(O_2 * (Q_2 + C)) : second Q_2 = d_2 * (I + d_1)/(I + d_3) : mmolar # Fraction of gliotransmitter resources available for release: dx_A/dt = Omega_A * (1 - x_A) : 1 # gliotransmitter concentration in the extracellular space: dG_A/dt = -Omega_e*G_A : mmolar # Neurotransmitter concentration in the extracellular space: Y_S : mmolar # The astrocyte position in space x : meter (constant) y : meter (constant) ''' glio_release = ''' G_A += rho_e * G_T * U_A * x_A x_A -= U_A * x_A ''' astrocytes = NeuronGroup(N_a, astro_eqs, # The following formulation makes sure that a "spike" is # only triggered at the first threshold crossing threshold='C>C_Theta', refractory='C>C_Theta', # The gliotransmitter release happens when the threshold # is crossed, in Brian terms it can therefore be # considered a "reset" reset=glio_release, method='rk4', dt=1e-2*second) # Arrange astrocytes in a grid astrocytes.x = '(i // N_rows_a)*grid_dist - N_rows_a/2.0*grid_dist' astrocytes.y = '(i % N_rows_a)*grid_dist - N_cols_a/2.0*grid_dist' # Add random initialization astrocytes.C = 0.01*umolar astrocytes.h = 0.9 astrocytes.I = 0.01*umolar astrocytes.x_A = 1.0 ecs_astro_to_syn = Synapses(astrocytes, exc_syn, 'G_A_post = G_A_pre : mmolar (summed)') ecs_astro_to_syn.connect('i == astrocyte_index_post') ecs_syn_to_astro = Synapses(exc_syn, astrocytes, 'Y_S_post = Y_S_pre/N_incoming : mmolar (summed)') ecs_syn_to_astro.connect('astrocyte_index_pre == j') # Diffusion between astrocytes astro_to_astro_eqs = ''' delta_I = I_post - I_pre : mmolar J_coupling_post = -(1 + tanh((abs(delta_I) - I_Theta)/omega_I))* sign(delta_I)*F/2 : mmolar/second (summed) ''' astro_to_astro = Synapses(astrocytes, astrocytes, model=astro_to_astro_eqs) # Connect to all astrocytes less than 75um away # (about 4 connections per astrocyte) astro_to_astro.connect('i != j and ' 'sqrt((x_pre-x_post)**2 +' ' (y_pre-y_post)**2) < 75*um') ################################################################################ # Monitors ################################################################################ # Note that we could use a single monitor for all neurons instead, but this # way plotting is a bit easier in the end exc_mon = SpikeMonitor(exc_neurons) inh_mon = SpikeMonitor(inh_neurons) ast_mon = SpikeMonitor(astrocytes) ################################################################################ # Simulation run ################################################################################ run(duration, report='text') ################################################################################ # Plot of Spiking activity ################################################################################ plt.style.use('figures.mplstyle') fig, ax = plt.subplots(nrows=3, ncols=1, sharex=True, figsize=(6.26894, 6.26894*0.8), gridspec_kw={'height_ratios': [1, 6, 2], 'left': 0.12, 'top': 0.97}) time_range = np.linspace(0, duration/second, int(duration/second*100))*second ax[0].plot(time_range, I_ex*stimulus(time_range)/pA, 'k') ax[0].set(xlim=(0, duration/second), ylim=(98, 122), yticks=[100, 120], ylabel='$I_{ex}$ (pA)') pu.adjust_spines(ax[0], ['left']) ## We only plot a fraction of the spikes fraction = 4 ax[1].plot(exc_mon.t[exc_mon.i <= N_e//fraction]/second, exc_mon.i[exc_mon.i <= N_e//fraction], '|', color='C0') ax[1].plot(inh_mon.t[inh_mon.i <= N_i//fraction]/second, inh_mon.i[inh_mon.i <= N_i//fraction]+N_e//fraction, '|', color='C1') ax[1].plot(ast_mon.t[ast_mon.i <= N_a//fraction]/second, ast_mon.i[ast_mon.i <= N_a//fraction]+(N_e+N_i)//fraction, '|', color='C2') ax[1].set(xlim=(0, duration/second), ylim=[0, (N_e+N_i+N_a)//fraction], yticks=np.arange(0, (N_e+N_i+N_a)//fraction+1, 250), ylabel='cell index') pu.adjust_spines(ax[1], ['left']) # Generate frequencies bin_size = 1*ms spk_count, bin_edges = np.histogram(np.r_[exc_mon.t/second, inh_mon.t/second], int(duration/bin_size)) rate = 1.0*spk_count/(N_e + N_i)/bin_size/Hz rate[rate<0.001] = 0.001 # Fix 0 lower bound for log scale ax[2].semilogy(bin_edges[:-1], rate, '-', color='k') pu.adjust_spines(ax[2], ['left', 'bottom']) ax[2].set(xlim=(0, duration/second), ylim=(0.1, 150), xticks=np.arange(0, 9), yticks=[0.1, 1, 10, 100], xlabel='time (s)', ylabel='rate (Hz)') ax[2].get_yaxis().set_major_formatter(ScalarFormatter()) pu.adjust_ylabels(ax, x_offset=-0.11) plt.show() brian2-2.5.4/examples/frompapers/Stimberg_et_al_2018/figures.mplstyle000066400000000000000000000003761445201106100255240ustar00rootroot00000000000000axes.linewidth : 1 xtick.labelsize : 8 ytick.labelsize : 8 axes.labelsize : 8 lines.linewidth : 1 lines.markersize : 2 legend.frameon : False legend.fontsize : 8 axes.prop_cycle : cycler(color=['e41a1c', '377eb8', '4daf4a', '984ea3', 'ff7f00', 'ffff33'])brian2-2.5.4/examples/frompapers/Stimberg_et_al_2018/plot_utils.py000066400000000000000000000030501445201106100250250ustar00rootroot00000000000000""" Module with useful functions for making publication-ready plots. """ def adjust_spines(ax, spines, position=5): """ Set custom visibility and position of axes ax : Axes Axes handle spines : List String list of 'left', 'bottom', 'right', 'top' spines to show position : Integer Number of points for position of axis """ for loc, spine in ax.spines.items(): if loc in spines: spine.set_position(('outward', position)) else: spine.set_color('none') # don't draw spine # turn off ticks where there is no spine if 'left' in spines: ax.yaxis.set_ticks_position('left') elif 'right' in spines: ax.yaxis.set_ticks_position('right') else: # no yaxis ticks ax.yaxis.set_ticks([]) ax.tick_params(axis='y', which='both', left='off', right='off') if 'bottom' in spines: ax.xaxis.set_ticks_position('bottom') elif 'top' in spines: ax.xaxis.set_ticks_position('top') else: # no xaxis ticks ax.xaxis.set_ticks([]) ax.tick_params(axis='x', which='both', bottom='off', top='off') def adjust_ylabels(ax,x_offset=0): ''' Scan all ax list and identify the outmost y-axis position. Setting all the labels to that position + x_offset. ''' xc = 0.0 for a in ax: xc = min(xc, (a.yaxis.get_label()).get_position()[0]) for a in ax: a.yaxis.set_label_coords(xc + x_offset, (a.yaxis.get_label()).get_position()[1]) brian2-2.5.4/examples/frompapers/Sturzl_et_al_2000.py000077500000000000000000000042501445201106100223530ustar00rootroot00000000000000#!/usr/bin/env python """ Adapted from Theory of Arachnid Prey Localization W. Sturzl, R. Kempter, and J. L. van Hemmen PRL 2000 Poisson inputs are replaced by integrate-and-fire neurons Romain Brette """ from brian2 import * # Parameters degree = 2 * pi / 360. duration = 500*ms R = 2.5*cm # radius of scorpion vr = 50*meter/second # Rayleigh wave speed phi = 144*degree # angle of prey A = 250*Hz deltaI = .7*ms # inhibitory delay gamma = (22.5 + 45 * arange(8)) * degree # leg angle delay = R / vr * (1 - cos(phi - gamma)) # wave delay # Wave (vector w) time = arange(int(duration / defaultclock.dt) + 1) * defaultclock.dt Dtot = 0. w = 0. for f in arange(150, 451)*Hz: D = exp(-(f/Hz - 300) ** 2 / (2 * (50 ** 2))) rand_angle = 2 * pi * rand() w += 100 * D * cos(2 * pi * f * time + rand_angle) Dtot += D w = .01 * w / Dtot # Rates from the wave rates = TimedArray(w, dt=defaultclock.dt) # Leg mechanical receptors tau_legs = 1 * ms sigma = .01 eqs_legs = """ dv/dt = (1 + rates(t - d) - v)/tau_legs + sigma*(2./tau_legs)**.5*xi:1 d : second """ legs = NeuronGroup(8, model=eqs_legs, threshold='v > 1', reset='v = 0', refractory=1*ms, method='euler') legs.d = delay spikes_legs = SpikeMonitor(legs) # Command neurons tau = 1 * ms taus = 1.001 * ms wex = 7 winh = -2 eqs_neuron = ''' dv/dt = (x - v)/tau : 1 dx/dt = (y - x)/taus : 1 # alpha currents dy/dt = -y/taus : 1 ''' neurons = NeuronGroup(8, model=eqs_neuron, threshold='v>1', reset='v=0', method='exact') synapses_ex = Synapses(legs, neurons, on_pre='y+=wex') synapses_ex.connect(j='i') synapses_inh = Synapses(legs, neurons, on_pre='y+=winh', delay=deltaI) synapses_inh.connect('abs(((j - i) % N_post) - N_post/2) <= 1') spikes = SpikeMonitor(neurons) run(duration, report='text') nspikes = spikes.count phi_est = imag(log(sum(nspikes * exp(gamma * 1j)))) print("True angle (deg): %.2f" % (phi/degree)) print("Estimated angle (deg): %.2f" % (phi_est/degree)) rmax = amax(nspikes)/duration/Hz polar(concatenate((gamma, [gamma[0] + 2 * pi])), concatenate((nspikes, [nspikes[0]])) / duration / Hz, c='k') axvline(phi, ls='-', c='g') axvline(phi_est, ls='-', c='b') show() brian2-2.5.4/examples/frompapers/Tetzlaff_2015.py000077500000000000000000000156711445201106100215020ustar00rootroot00000000000000#!/usr/bin/env python3 """ Reproduces Figure 2F of The Use of Hebbian Cell Assemblies for Nonlinear Computation by Tetzlaff C., Dasgupta S., Kulvicius T. and Wörgötter F. Sci Rep 5, 12866 (2015). https://doi.org/10.1038/srep12866 Sebastian Schmitt, 2022 """ import numpy as np import matplotlib.pyplot as plt from brian2 import NeuronGroup, Synapses, StateMonitor, run, defaultclock, ms, second, TimedArray, seed # random seed that gives curves similar to the ones in the publication seed(9873487) # neuron parameters (sigmoidal activation) beta = 0.03 epsilon = 120 F_max = 100 F_T = 1 tau_u = 1*ms R = 0.012 # plasticity timescales tau_ratio = 60 # hebbian tau_H = 3e4*ms # synaptic scaling tau_SS = tau_ratio * tau_H # synaptic weights W_max = np.sqrt(tau_ratio*(F_max**2/(F_max - F_T))) W_ext = W_max W_input = W_max W_I = 0.3*W_max # stimulus N_units = 100 N_stim_units = 20 stim_A_units_until = N_stim_units stim_B_units_from = N_units-N_stim_units # connection probabilities p_E = 0.1 p_I = 0.2 # paper uses 0.3*ms DT = 0.5*ms defaultclock.dt = DT # duration of a learning trial lt = 5000*DT duration = 100*lt no_input_until = 5*lt balanced_until = duration/2 # gate balanced presentation of stimulus 1 and 2 balanced = TimedArray([lt_counter*lt < balanced_until for lt_counter in range(int(duration/lt))], dt=lt) # function used for stimulus (typo in paper, +1 is not part of the argument of sin) stim_func = TimedArray([100*(np.sin(0.1*(i+1))+1) for i in range(int(duration/DT))], dt=DT) # gate learning phase of either stimulus 1 or 2 learning_phase = TimedArray([i%10 > 3 for i in range(int(duration/(0.1*lt)))], dt=0.1*lt) # if not balanced present stimulus A three times more often than stimulus B stim_A_gate = TimedArray([lt_counter % 2 == 0 if balanced(lt_counter*lt) else lt_counter % 4 in [0,1,2] for lt_counter in range(int(duration/lt))], dt=lt) stim_B_gate = TimedArray([lt_counter % 2 == 1 if balanced(lt_counter*lt) else lt_counter % 4 == 3 for lt_counter in range(int(duration/lt))], dt=lt) # noise is applied also during stimulation neurons = NeuronGroup(N_units, """ F = F_max/(1+exp(beta*(epsilon-u))) : 1 du/dt = (-u + R*(I_E - I_I + W_input*(I_stim_A + I_stim_B)))/tau_u + R*W_ext*20*sqrt((DT/ms)/ms)*xi: 1 I_E : 1 I_I : 1 index : 1 (constant) stim_units_A = index < stim_A_units_until : boolean stim_units_B = index >= (stim_B_units_from) : boolean I_stim_A = learning_phase(t)*int(stim_units_A)*stim_A_gate(t)*stim_func(t) : 1 I_stim_B = learning_phase(t)*int(stim_units_B)*stim_B_gate(t)*stim_func(t) : 1 """, method = "euler") neurons.index = range(len(neurons)) # excitatory connections with Hebbian plasticity and synaptic scaling synapses_E = Synapses(neurons, neurons, """ dw/dt = 1/tau_H*F_pre*F_post + 1/tau_SS*(F_T - F_post)*w**2 : 1 (clock-driven) I_E_post = w*F_pre : 1 (summed) """, method="euler" ) # do not connect between the two populations of stimulated neurons synapses_E.connect(p=p_E, condition="((j > stim_A_units_until and i >= stim_B_units_from) or (j < stim_B_units_from and i < stim_A_units_until))" "or ((i > stim_A_units_until and i < stim_B_units_from) and (j > stim_A_units_until and j < stim_B_units_from))") # fixed weight inhibitory connections synapses_I = Synapses(neurons, neurons, """ w : 1 I_I_post = w*F_pre : 1 (summed) """ ) synapses_I.connect(p=p_I) synapses_I.w = W_I statemon_neurons = StateMonitor(neurons, ["F", "I_stim_A", "I_stim_B"], record=True, dt=100*defaultclock.dt) statemon_synapses_E = StateMonitor(synapses_E, "w", record=True, dt=100*defaultclock.dt) statemon_synapses_for_assembly_analysis = StateMonitor(synapses_E, "w", record=True, dt=lt) run(duration, report="text") # threshold saying that synaptic efficacies larger than theta are # 'strong' and others are 'weak' theta = 0.5*W_max in_assembly_A = [] in_assembly_B = [] # traverse through the graph following 'strong' synapses def go(W, source, units_in_assembly): units_in_assembly.add(source) # check all possible targets for target in range(N_units): w = W[source][target] if w > theta: W[source][target] = 0 go(W, target, units_in_assembly) # for each learning trial for ws in statemon_synapses_for_assembly_analysis.w.T: # construct a full weight matrix W = np.full((N_units, N_units), np.nan) W[synapses_E.i[:], synapses_E.j[:]] = ws for in_assembly, stim_units in zip([in_assembly_A, in_assembly_B], [range(stim_A_units_until), range(stim_B_units_from, N_units)]): units_in_assembly = set() # start with units that are stimulated for stim_unit in stim_units: go(W, stim_unit, units_in_assembly) in_assembly.append(len(units_in_assembly)) # competitive development of the two competing cell assemblies A and B as a function of the input protocol fig, ax = plt.subplots() ax.plot(in_assembly_A, linestyle="None", marker='o', color='orange', label="A") ax.plot(in_assembly_B, linestyle="None", marker='o', color='olivedrab', label="B") ax.set_ylim(19, 51) ax.set_xlim(0, 100) ax.set_ylabel("Neurons in Cell Assembly [%]") ax.set_xlabel("Learning Trial") ax.axvline(balanced_until/lt, linestyle='dashed', color='k') ax.text(15, 52, " A A", color='orange', fontfamily="monospace", fontsize="xx-large") ax.text(15, 52, " B B", color='olivedrab', fontfamily="monospace", fontsize="xx-large") ax.text(65, 52, " 3A 3A", color='orange', fontfamily="monospace", fontsize="xx-large") ax.text(65, 52, " B B", color='olivedrab', fontfamily="monospace", fontsize="xx-large") plt.show() # stimulus, neuronal activity and excitatory weights as function of time fig, axes = plt.subplots(3, sharex=True) axes[0].plot(statemon_neurons.I_stim_A[0], label="A", color='orange') axes[0].plot(statemon_neurons.I_stim_B[-1], label="B", color='olivedrab') axes[0].legend(loc="upper right") axes[0].set_title("Stimulus") axes[1].imshow(statemon_neurons.F, aspect='auto') axes[1].set_title("Neuron Activity") axes[1].axhline(stim_A_units_until, linestyle='dashed', color='white') axes[1].axhline(stim_B_units_from, linestyle='dashed', color='white') axes[2].imshow(statemon_synapses_E.w, aspect='auto') axes[2].set_title("Excitatory Weights") axes[2].set_xticks(range(0, 5000, 250)) axes[2].set_xticklabels(f"{i}" for i in range(0, 100, 5)) axes[2].set_xlabel("Learning Trial") axes[2].set_xlim(0, 5000) fig.tight_layout() plt.show() brian2-2.5.4/examples/frompapers/Touboul_Brette_2008.py000077500000000000000000000025711445201106100226560ustar00rootroot00000000000000#!/usr/bin/env python """ Chaos in the AdEx model ----------------------- Fig. 8B from: Touboul, J. and Brette, R. (2008). Dynamics and bifurcations of the adaptive exponential integrate-and-fire model. Biological Cybernetics 99(4-5):319-34. This shows the bifurcation structure when the reset value is varied (vertical axis shows the values of w at spike times for a given a reset value Vr). """ from brian2 import * defaultclock.dt = 0.01*ms C = 281*pF gL = 30*nS EL = -70.6*mV VT = -50.4*mV DeltaT = 2*mV tauw = 40*ms a = 4*nS b = 0.08*nA I = .8*nA Vcut = VT + 5 * DeltaT # practical threshold condition N = 200 eqs = """ dvm/dt=(gL*(EL-vm)+gL*DeltaT*exp((vm-VT)/DeltaT)+I-w)/C : volt dw/dt=(a*(vm-EL)-w)/tauw : amp Vr:volt """ neuron = NeuronGroup(N, model=eqs, threshold='vm > Vcut', reset="vm = Vr; w += b", method='euler') neuron.vm = EL neuron.w = a * (neuron.vm - EL) neuron.Vr = linspace(-48.3 * mV, -47.7 * mV, N) # bifurcation parameter init_time = 3*second run(init_time, report='text') # we discard the first spikes states = StateMonitor(neuron, "w", record=True, when='start') spikes = SpikeMonitor(neuron) run(1 * second, report='text') # Get the values of Vr and w for each spike Vr = neuron.Vr[spikes.i] w = states.w[spikes.i, int_((spikes.t-init_time)/defaultclock.dt)] figure() plot(Vr / mV, w / nA, '.k') xlabel('Vr (mV)') ylabel('w (nA)') show() brian2-2.5.4/examples/frompapers/Tsodyks_Pawelzik_Markram_1998.py000077500000000000000000000126041445201106100247170ustar00rootroot00000000000000#!/usr/bin/env python3 """ Fig. 1 from: M. Tsodyks, K. Pawelzik, H. Markram Neural Networks with Dynamic Synapses Neural Computation 10, 821–835 (1998) https://doi.org/10.1162/089976698300017502 Sebastian Schmitt, 2022 """ import numpy as np import matplotlib.pyplot as plt from brian2 import ( NeuronGroup, Synapses, SpikeGeneratorGroup, SpikeMonitor, StateMonitor, ) from brian2 import ms, mV, pA, Mohm, Gohm, Hz from brian2 import run def get_neuron(tau_mem, R_in): """ tau_mem -- membrane time constant R_in -- input resistance """ neuron = NeuronGroup(1, """ tau_mem : second I_syn : ampere R_in : ohm dv/dt = -v/tau_mem + (R_in*I_syn)/tau_mem : volt """, method="exact") neuron.tau_mem = tau_mem neuron.R_in = R_in return neuron def get_synapses(stimulus, neuron, tau_inact, A_SE, U_SE, tau_rec, tau_facil=None): """ stimulus -- input stimulus neuron -- target neuron tau_inact -- inactivation time constant A_SE -- absolute synaptic strength U_SE -- utilization of synaptic efficacy tau_rec -- recovery time constant tau_facil -- facilitation time constant (optional) """ synapses_eqs = """ dx/dt = z/tau_rec : 1 (clock-driven) # recovered dy/dt = -y/tau_inact : 1 (clock-driven) # active A_SE : ampere U_SE : 1 tau_inact : second tau_rec : second z = 1 - x - y : 1 # inactive I_syn_post = A_SE*y : ampere (summed) """ if tau_facil: synapses_eqs += """ du/dt = -u/tau_facil : 1 (clock-driven) tau_facil : second """ synapses_action = """ u += U_SE*(1-u) y += u*x # important: update y first x += -u*x """ else: synapses_action = """ y += U_SE*x # important: update y first x += -U_SE*x """ synapses = Synapses(stimulus, neuron, model=synapses_eqs, on_pre=synapses_action, method="exponential_euler") synapses.connect() # start fully recovered synapses.x = 1 synapses.tau_inact = tau_inact synapses.A_SE = A_SE synapses.U_SE = U_SE synapses.tau_rec = tau_rec if tau_facil: synapses.tau_facil = tau_facil return synapses def get_stimulus(start, stop, frequency): """ start -- start time of stimulus stop -- stop time of stimulus frequency -- frequency of stimulus """ times = np.arange(start / ms, stop / ms, 1 / (frequency / Hz) * 1e3) * ms stimulus = SpikeGeneratorGroup(1, [0] * len(times), times) return stimulus parameters = { "A": { "neuron": {"tau_mem": 40 * ms, "R_in": 100*Mohm}, "synapse": { "tau_inact": 3 * ms, "A_SE": 250 * pA, "tau_rec": 800 * ms, "U_SE": 0.6, # 0.5 from publication does not match plot }, "stimulus": {"start": 100 * ms, "stop": 1100 * ms, "frequency": 20 * Hz}, "simulation": {"duration": 1200 * ms}, "plot": { "title": "A) D - 20 Hz", "ylim": [0, 1], "xlim": [0, 1200], "xtickstep": 200, }, }, "B": { "neuron": {"tau_mem": 60 * ms, "R_in": 1*Gohm}, "synapse": { "tau_inact": 1.5 * ms, "A_SE": 1540 * pA, "tau_rec": 130 * ms, "U_SE": 0.03, "tau_facil": 530 * ms, }, "stimulus": {"start": 100 * ms, "stop": 1100 * ms, "frequency": 20 * Hz}, "simulation": {"duration": 1200 * ms}, "plot": { "title": "B) F - 20 Hz", "ylim": [0, 14.9], "xlim": [0, 1200], "xtickstep": 200, }, }, "C": { "neuron": {"tau_mem": 60 * ms, "R_in": 1*Gohm}, "synapse": { "tau_inact": 1.5 * ms, "A_SE": 1540 * pA, "tau_rec": 130 * ms, "U_SE": 0.03, "tau_facil": 530 * ms, }, "stimulus": {"start": 100 * ms, "stop": 375 * ms, "frequency": 70 * Hz}, "simulation": {"duration": 500 * ms}, "plot": { "title": "C) F - 70 Hz", "ylim": [0, 20], "xlim": [0, 500], "xtickstep": 50, }, }, } fig, axes = plt.subplots(3) for ax, (panel, p) in zip(axes, parameters.items()): neuron = get_neuron(**p["neuron"]) stimulus = get_stimulus(**p["stimulus"]) synapses = get_synapses(stimulus, neuron, **p["synapse"]) state_monitor_neuron = StateMonitor(neuron, ["v"], record=True) run(p["simulation"]["duration"]) ax.plot( state_monitor_neuron.t / ms, state_monitor_neuron[0].v / mV, label=p["plot"]["title"], ) ax.set_xlim(*p["plot"]["xlim"]) ax.set_ylim(*p["plot"]["ylim"]) ax.set_ylabel("mV") ax.set_xlabel("Time (ms)") ax.set_xticks( np.arange( p["plot"]["xlim"][0], p["plot"]["xlim"][1] + p["plot"]["xtickstep"], p["plot"]["xtickstep"], ) ) ax.legend() plt.show() brian2-2.5.4/examples/frompapers/Tsodyks_Uziel_Markram_2000.py000077500000000000000000000173271445201106100241770ustar00rootroot00000000000000#!/usr/bin/env python3 """ Fig. 1 from: Synchrony Generation in Recurrent Networks with Frequency-Dependent Synapses The Journal of Neuroscience, 2000, Vol. 20 RC50 Implementation partially based on nest-2.0.0/examples/nest/tsodyks_shortterm_bursts.sli by Moritz Helias, 2006. Sebastian Schmitt, 2022 """ import numpy as np # set seed for reproducible figures np.random.seed(5) # for truncated normal import scipy from scipy import stats import matplotlib.pyplot as plt from brian2 import ( NeuronGroup, Synapses, SpikeGeneratorGroup, SpikeMonitor, StateMonitor, ) from brian2 import ms, mV from brian2 import run, defaultclock def truncated_normal(loc, scale, bounds, size): """Normal distribution truncated within bounds loc -- mean (“centre”) of the distribution scale -- standard deviation (spread or “width”) of the distribution bounds -- list of min and maximum size -- number of samples """ bounds = np.array([bounds] * size) s = scipy.stats.truncnorm.rvs( (bounds[:, 0] - loc) / scale, (bounds[:, 1] - loc) / scale, loc=loc, scale=scale ) return s def get_population(name, N, tau_refrac): """Get population of neurons name -- name of population N -- number of neurons tau_refrac -- refractory period """ neurons = NeuronGroup( N, """ tau_mem : second tau_refrac : second v_reset : volt v_thresh : volt I_syn_ee_synapses : volt I_syn_ei_synapses : volt I_syn_ie_synapses : volt I_syn_ii_synapses : volt I_b : volt dv/dt = -v/tau_mem + (I_syn_ee_synapses + I_syn_ei_synapses + I_syn_ie_synapses + I_syn_ii_synapses)/tau_mem + I_b/tau_mem : volt (unless refractory) """, threshold="v>v_thresh", reset="v=v_reset", refractory=tau_refrac, method="exact", name=name, ) v_thresh = 15 * mV v_reset = 13.5 * mV neurons.tau_mem = 30 * ms neurons.v_thresh = v_thresh neurons.v_reset = v_reset # paper gives range of 0.05 mV but population bursts are not visible with that value # -> increased to 1 mV range neurons.I_b = ( np.random.uniform(v_thresh / mV - 0.5, v_thresh / mV + 0.5, size=N) * mV ) return neurons def get_synapses(name, source, target, tau_I, A, U, tau_rec, tau_facil=None): """Construct connections and retrieve synapses name -- name of synapses source -- source of connections target -- target of connections tau_I -- inactivation time constant A -- absolute synaptic strength U -- utilization of synaptic efficacy tau_rec -- recovery time constant tau_facil -- facilitation time constant (optional) """ synapses_eqs = """ A : volt U : 1 tau_I : second tau_rec : second dx/dt = z/tau_rec : 1 (clock-driven) # recovered dy/dt = -y/tau_I : 1 (clock-driven) # active z = 1 - x - y : 1 # inactive I_syn_{}_post = A*y : volt (summed) """.format( name ) if tau_facil: synapses_eqs += """ du/dt = -u/tau_facil : 1 (clock-driven) tau_facil : second """ synapses_action = """ u += U*(1-u) y += u*x # important: update y first x += -u*x """ else: synapses_action = """ y += U*x # important: update y first x += -U*x """ synapses = Synapses( source, target, model=synapses_eqs, on_pre=synapses_action, method="exact", name=name, ) synapses.connect(p=0.1) N_syn = len(synapses) synapses.tau_I = tau_I A_min = min(0.2 * A, 2 * A) A_max = max(0.2 * A, 2 * A) synapses.A = ( truncated_normal( A / mV, 0.5 * abs(A / mV), [A_min / mV, A_max / mV], size=N_syn ) * mV ) assert not any(synapses.A < A_min) assert not any(synapses.A > A_max) U_mean, U_min, U_max = U synapses.U = truncated_normal(U_mean, 0.5 * U_mean, [U_min, U_max], size=N_syn) assert not any(synapses.U <= U_min) assert not any(synapses.U > U_max) tau_min = 5 synapses.tau_rec = ( truncated_normal( tau_rec / ms, 0.5 * tau_rec / ms, [tau_min, np.inf], size=N_syn ) * ms ) assert not any(synapses.tau_rec / ms <= tau_min) if tau_facil: synapses.tau_facil = ( truncated_normal( tau_facil / ms, 0.5 * tau_facil / ms, [tau_min, np.inf], size=N_syn ) * ms ) assert not any(synapses.tau_facil / ms <= tau_min) # start fully recovered synapses.x = 1 return synapses # configure neuron populations exc_neurons = get_population("exc_neurons", N=400, tau_refrac=3 * ms) inh_neurons = get_population("inh_neurons", N=100, tau_refrac=2 * ms) # configure synapses ee_synapses = get_synapses( "ee_synapses", exc_neurons, exc_neurons, tau_I=3 * ms, A=1.8 * mV, U=[0.5, 0.1, 0.9], tau_rec=800 * ms, ) ei_synapses = get_synapses( "ei_synapses", exc_neurons, inh_neurons, tau_I=3 * ms, A=7.2 * mV, U=[0.04, 0.001, 0.07], tau_rec=100 * ms, tau_facil=1000 * ms, ) ie_synapses = get_synapses( "ie_synapses", inh_neurons, exc_neurons, tau_I=3 * ms, A=-5.4 * mV, U=[0.5, 0.1, 0.9], tau_rec=800 * ms, ) ii_synapses = get_synapses( "ii_synapses", inh_neurons, inh_neurons, tau_I=3 * ms, A=-7.2 * mV, U=[0.04, 0.001, 0.07], tau_rec=100 * ms, tau_facil=1000 * ms, ) # run for burnin time to settle network activity defaultclock.dt = 1 * ms burnin = 900 run(burnin * ms) # record from now on spike_monitor_exc = SpikeMonitor(exc_neurons) spike_monitor_inh = SpikeMonitor(inh_neurons) state_monitor_ee = StateMonitor(ee_synapses, ["x"], record=True) duration = 4200 run(duration * ms, report="text") # plots fig, axes = plt.subplots(3, figsize=(6, 8), sharex=True) # raster plot axes[0].plot(spike_monitor_exc.t / ms, spike_monitor_exc.i, ".k", ms=1) axes[0].plot(spike_monitor_inh.t / ms, spike_monitor_inh.i + len(exc_neurons), ".k", ms=1) axes[0].set_ylabel("Neuron No.") axes[0].set_ylim(0, len(exc_neurons) + len(inh_neurons)) # network activity net_activity = np.histogram( np.concatenate( list(spike_monitor_exc.spike_trains().values()) + list(spike_monitor_inh.spike_trains().values()) ) / ms, bins=np.arange(burnin, duration + burnin, 1))[0] / (len(exc_neurons) + len(inh_neurons)) axes[1].plot(np.arange(0, len(net_activity)) + burnin, net_activity, "k") net_activity_min = 0 net_activity_max = 0.2 axes[1].set_ylim(net_activity_min, net_activity_max) axes[1].set_ylabel("Net activity") # network activity inset axins = axes[1].inset_axes([0.05, 0.35, 0.2, 0.6]) axins.plot(np.arange(0, len(net_activity)) + burnin, net_activity, "k") inset_min = 1220 inset_max = 1260 axins.set_xlim(inset_min + burnin, inset_max + burnin) axins.set_ylim(net_activity_min, net_activity_max) axins.set_xticks([inset_min + burnin, inset_max + burnin]) axins.set_xticklabels([inset_min, inset_max]) axins.set_yticks([]) # recovered synaptic partition axes[2].plot( state_monitor_ee.t / ms, np.mean(state_monitor_ee.x, axis=0), "k", label="x" ) axes[2].set_ylim(0.2, 0.6) axes[2].set_xlabel("Time (msec)") axes[2].set_ylabel("Recov excit") axes[2].set_xlim(burnin, duration + burnin) xtickstep = 1000 axes[2].set_xticks(np.arange(burnin, duration + burnin, xtickstep)) axes[2].set_xticklabels(map(str, range(0, duration, xtickstep))) axes[0].xaxis.set_tick_params(which="both", labelbottom=True) axes[1].xaxis.set_tick_params(which="both", labelbottom=True) plt.show() brian2-2.5.4/examples/frompapers/Vogels_et_al_2011.py000077500000000000000000000070061445201106100223130ustar00rootroot00000000000000#!/usr/bin/env python """ Inhibitory synaptic plasticity in a recurrent network model ----------------------------------------------------------- (F. Zenke, 2011) (from the 2012 Brian twister) Adapted from: Vogels, T. P., H. Sprekeler, F. Zenke, C. Clopath, and W. Gerstner. Inhibitory Plasticity Balances Excitation and Inhibition in Sensory Pathways and Memory Networks. Science (November 10, 2011). """ from brian2 import * # ########################################### # Defining network model parameters # ########################################### NE = 8000 # Number of excitatory cells NI = NE/4 # Number of inhibitory cells tau_ampa = 5.0*ms # Glutamatergic synaptic time constant tau_gaba = 10.0*ms # GABAergic synaptic time constant epsilon = 0.02 # Sparseness of synaptic connections tau_stdp = 20*ms # STDP time constant simtime = 10*second # Simulation time # ########################################### # Neuron model # ########################################### gl = 10.0*nsiemens # Leak conductance el = -60*mV # Resting potential er = -80*mV # Inhibitory reversal potential vt = -50.*mV # Spiking threshold memc = 200.0*pfarad # Membrane capacitance bgcurrent = 200*pA # External current eqs_neurons=''' dv/dt=(-gl*(v-el)-(g_ampa*v+g_gaba*(v-er))+bgcurrent)/memc : volt (unless refractory) dg_ampa/dt = -g_ampa/tau_ampa : siemens dg_gaba/dt = -g_gaba/tau_gaba : siemens ''' # ########################################### # Initialize neuron group # ########################################### neurons = NeuronGroup(NE+NI, model=eqs_neurons, threshold='v > vt', reset='v=el', refractory=5*ms, method='euler') Pe = neurons[:NE] Pi = neurons[NE:] # ########################################### # Connecting the network # ########################################### con_e = Synapses(Pe, neurons, on_pre='g_ampa += 0.3*nS') con_e.connect(p=epsilon) con_ii = Synapses(Pi, Pi, on_pre='g_gaba += 3*nS') con_ii.connect(p=epsilon) # ########################################### # Inhibitory Plasticity # ########################################### eqs_stdp_inhib = ''' w : 1 dApre/dt=-Apre/tau_stdp : 1 (event-driven) dApost/dt=-Apost/tau_stdp : 1 (event-driven) ''' alpha = 3*Hz*tau_stdp*2 # Target rate parameter gmax = 100 # Maximum inhibitory weight con_ie = Synapses(Pi, Pe, model=eqs_stdp_inhib, on_pre='''Apre += 1. w = clip(w+(Apost-alpha)*eta, 0, gmax) g_gaba += w*nS''', on_post='''Apost += 1. w = clip(w+Apre*eta, 0, gmax) ''') con_ie.connect(p=epsilon) con_ie.w = 1e-10 # ########################################### # Setting up monitors # ########################################### sm = SpikeMonitor(Pe) # ########################################### # Run without plasticity # ########################################### eta = 0 # Learning rate run(1*second) # ########################################### # Run with plasticity # ########################################### eta = 1e-2 # Learning rate run(simtime-1*second, report='text') # ########################################### # Make plots # ########################################### i, t = sm.it subplot(211) plot(t/ms, i, 'k.', ms=0.25) title("Before") xlabel("") yticks([]) xlim(0.8*1e3, 1*1e3) subplot(212) plot(t/ms, i, 'k.', ms=0.25) xlabel("time (ms)") yticks([]) title("After") xlim((simtime-0.2*second)/ms, simtime/ms) show() brian2-2.5.4/examples/frompapers/Wang_Buszaki_1996.py000077500000000000000000000022331445201106100223160ustar00rootroot00000000000000#!/usr/bin/env python """ Wang-Buszaki model ------------------ J Neurosci. 1996 Oct 15;16(20):6402-13. Gamma oscillation by synaptic inhibition in a hippocampal interneuronal network model. Wang XJ, Buzsaki G. Note that implicit integration (exponential Euler) cannot be used, and therefore simulation is rather slow. """ from brian2 import * defaultclock.dt = 0.01*ms Cm = 1*uF # /cm**2 Iapp = 2*uA gL = 0.1*msiemens EL = -65*mV ENa = 55*mV EK = -90*mV gNa = 35*msiemens gK = 9*msiemens eqs = ''' dv/dt = (-gNa*m**3*h*(v-ENa)-gK*n**4*(v-EK)-gL*(v-EL)+Iapp)/Cm : volt m = alpha_m/(alpha_m+beta_m) : 1 alpha_m = 0.1/mV*10*mV/exprel(-(v+35*mV)/(10*mV))/ms : Hz beta_m = 4*exp(-(v+60*mV)/(18*mV))/ms : Hz dh/dt = 5*(alpha_h*(1-h)-beta_h*h) : 1 alpha_h = 0.07*exp(-(v+58*mV)/(20*mV))/ms : Hz beta_h = 1./(exp(-0.1/mV*(v+28*mV))+1)/ms : Hz dn/dt = 5*(alpha_n*(1-n)-beta_n*n) : 1 alpha_n = 0.01/mV*10*mV/exprel(-(v+34*mV)/(10*mV))/ms : Hz beta_n = 0.125*exp(-(v+44*mV)/(80*mV))/ms : Hz ''' neuron = NeuronGroup(1, eqs, method='exponential_euler') neuron.v = -70*mV neuron.h = 1 M = StateMonitor(neuron, 'v', record=0) run(100*ms, report='text') plot(M.t/ms, M[0].v/mV) show() brian2-2.5.4/examples/multiprocessing/000077500000000000000000000000001445201106100177365ustar00rootroot00000000000000brian2-2.5.4/examples/multiprocessing/01_using_cython.py000066400000000000000000000021351445201106100233220ustar00rootroot00000000000000""" Parallel processes using Cython This example use multiprocessing to run several simulations in parallel. The code is using the default runtime mode (and Cython compilation, if possible). The ``numb_proc`` variable set the number of processes. ``run_sim`` is just a toy example that creates a single neuron and connects a `StateMonitor` to record the voltage. For more details see the `github issue 1154 `_: """ import os import multiprocessing from brian2 import * def run_sim(tau): pid = os.getpid() print(f'RUNNING {pid}') G = NeuronGroup(1, 'dv/dt = -v/tau : 1', method='exact') G.v = 1 mon = StateMonitor(G, 'v', record=0) run(100*ms) print(f'FINISHED {pid}') return mon.t/ms, mon.v[0] if __name__ == "__main__": num_proc = 4 tau_values = np.arange(10)*ms + 5*ms with multiprocessing.Pool(num_proc) as p: results = p.map(run_sim, tau_values) for tau_value, (t, v) in zip(tau_values, results): plt.plot(t, v, label=str(tau_value)) plt.legend() plt.show() brian2-2.5.4/examples/multiprocessing/02_using_standalone.py000066400000000000000000000057551445201106100241620ustar00rootroot00000000000000""" Parallel processes using standalone mode This example use multiprocessing to run several simulations in parallel. The code is using the C++ standalone mode to compile and execute the code. The generated code is stored in a ``standalone{pid}`` directory, with ``pid`` being the id of each process. Note that the `set_device` call should be in the ``run_sim`` function. By moving the `set_device` line into the parallelised function, it creates one C++ standalone device per process. The ``device.reinit()`` needs to be called` if you are running multiple simulations per process (there are 10 tau values and num_proc = 4). Each simulation uses it's own code folder to generate the code for the simulation, controlled by the directory keyword to the set_device call. By setting ``directory=None``, a temporary folder with random name is created. This way, each simulation uses a different folder for code generation and there is nothing shared between the parallel processes. If you don't set the directory argument, it defaults to ``directory="output"``. In that case each process would use the same files to try to generate and compile your simulation, which would lead to compile/execution errors. Setting ``directory=f"standalone{pid}"`` is even better than using ``directory=None`` in this case. That is, giving each parallel process *it's own directory to work on*. This way you avoid the problem of multiple processes working on the same code directories. But you also don't need to recompile the entire project at each simulation. What happens is that in the generated code in two consecutive simulations in a single process will only differ slightly (in this case only the tau parameter). The compiler will therefore only recompile the file that has changed and not the entire project. The ``numb_proc`` sets the number of processes. ``run_sim`` is just a toy example that creates a single neuron and connects a `StateMonitor` to record the voltage. For more details see the `discussion in the Brian forum `_. """ import os import multiprocessing from time import time as wall_time from os import system from brian2 import * def run_sim(tau): pid = os.getpid() directory = f"standalone{pid}" set_device('cpp_standalone', directory=directory) print(f'RUNNING {pid}') G = NeuronGroup(1, 'dv/dt = -v/tau : 1', method='euler') G.v = 1 mon = StateMonitor(G, 'v', record=0) net = Network() net.add(G, mon) net.run(100 * ms) res = (mon.t/ms, mon.v[0]) device.reinit() print(f'FINISHED {pid}') return res if __name__ == "__main__": start_time = wall_time() num_proc = 4 tau_values = np.arange(10)*ms + 5*ms with multiprocessing.Pool(num_proc) as p: results = p.map(run_sim, tau_values) print(f"Done in {wall_time() - start_time:10.3f}") for tau_value, (t, v) in zip(tau_values, results): plt.plot(t, v, label=str(tau_value)) plt.legend() plt.show() brian2-2.5.4/examples/multiprocessing/03_standalone_joblib.py000066400000000000000000000021671445201106100242710ustar00rootroot00000000000000""" This example use C++ standalone mode for the simulation and the `joblib library `_ to parallelize the code. See the previous example (``02_using_standalone.py``) for more explanations. """ from joblib import Parallel, delayed from time import time as wall_time from brian2 import * import os def run_sim(tau): pid = os.getpid() directory = f"standalone{pid}" set_device('cpp_standalone', directory=directory) print(f'RUNNING {pid}') G = NeuronGroup(1, 'dv/dt = -v/tau : 1', method='euler') G.v = 1 mon = StateMonitor(G, 'v', record=0) net = Network() net.add(G, mon) net.run(100 * ms) res = (mon.t/ms, mon.v[0]) device.reinit() print(f'FINISHED {pid}') return res if __name__ == "__main__": start_time = wall_time() n_jobs = 4 tau_values = np.arange(10)*ms + 5*ms results = Parallel(n_jobs=n_jobs)(map(delayed(run_sim), tau_values)) print(f"Done in {wall_time() - start_time:10.3f}") for tau_value, (t, v) in zip(tau_values, results): plt.plot(t, v, label=str(tau_value)) plt.legend() plt.show() brian2-2.5.4/examples/non_reliability.py000066400000000000000000000011251445201106100202430ustar00rootroot00000000000000#!/usr/bin/env python """ Reliability of spike timing. See e.g. Mainen & Sejnowski (1995) for experimental results in vitro. Here: a constant current is injected in all trials. """ from brian2 import * N = 25 tau = 20*ms sigma = .015 eqs_neurons = ''' dx/dt = (1.1 - x) / tau + sigma * (2 / tau)**.5 * xi : 1 (unless refractory) ''' neurons = NeuronGroup(N, model=eqs_neurons, threshold='x > 1', reset='x = 0', refractory=5*ms, method='euler') spikes = SpikeMonitor(neurons) run(500*ms) plot(spikes.t/ms, spikes.i, '.k') xlabel('Time (ms)') ylabel('Neuron index') show() brian2-2.5.4/examples/phase_locking.py000066400000000000000000000012551445201106100176720ustar00rootroot00000000000000#!/usr/bin/env python """ Phase locking of IF neurons to a periodic input. """ from brian2 import * tau = 20*ms n = 100 b = 1.2 # constant current mean, the modulation varies freq = 10*Hz eqs = ''' dv/dt = (-v + a * sin(2 * pi * freq * t) + b) / tau : 1 a : 1 ''' neurons = NeuronGroup(n, model=eqs, threshold='v > 1', reset='v = 0', method='euler') neurons.v = 'rand()' neurons.a = '0.05 + 0.7*i/n' S = SpikeMonitor(neurons) trace = StateMonitor(neurons, 'v', record=50) run(1000*ms) subplot(211) plot(S.t/ms, S.i, '.k') xlabel('Time (ms)') ylabel('Neuron index') subplot(212) plot(trace.t/ms, trace.v.T) xlabel('Time (ms)') ylabel('v') tight_layout() show() brian2-2.5.4/examples/reliability.py000077500000000000000000000015021445201106100173730ustar00rootroot00000000000000#!/usr/bin/env python """ Reliability of spike timing. See e.g. Mainen & Sejnowski (1995) for experimental results in vitro. """ from brian2 import * # The common noisy input N = 25 tau_input = 5*ms neuron_input = NeuronGroup(1, 'dx/dt = -x / tau_input + (2 /tau_input)**.5 * xi : 1') # The noisy neurons receiving the same input tau = 10*ms sigma = .015 eqs_neurons = ''' dx/dt = (0.9 + .5 * I - x) / tau + sigma * (2 / tau)**.5 * xi : 1 I : 1 (linked) ''' neurons = NeuronGroup(N, model=eqs_neurons, threshold='x > 1', reset='x = 0', refractory=5*ms, method='euler') neurons.x = 'rand()' neurons.I = linked_var(neuron_input, 'x') # input.x is continuously fed into neurons.I spikes = SpikeMonitor(neurons) run(500*ms) plt.plot(spikes.t/ms, spikes.i, '.k') xlabel('Time (ms)') ylabel('Neuron index') show() brian2-2.5.4/examples/standalone/000077500000000000000000000000001445201106100166375ustar00rootroot00000000000000brian2-2.5.4/examples/standalone/.gitignore000066400000000000000000000000271445201106100206260ustar00rootroot00000000000000/STDP_standalone /CUBA brian2-2.5.4/examples/standalone/STDP_standalone.py000066400000000000000000000030701445201106100221730ustar00rootroot00000000000000#!/usr/bin/env python """ Spike-timing dependent plasticity. Adapted from Song, Miller and Abbott (2000) and Song and Abbott (2001). This example is modified from ``synapses_STDP.py`` and writes a standalone C++ project in the directory ``STDP_standalone``. """ from brian2 import * set_device('cpp_standalone', directory='STDP_standalone') N = 1000 taum = 10*ms taupre = 20*ms taupost = taupre Ee = 0*mV vt = -54*mV vr = -60*mV El = -74*mV taue = 5*ms F = 15*Hz gmax = .01 dApre = .01 dApost = -dApre * taupre / taupost * 1.05 dApost *= gmax dApre *= gmax eqs_neurons = ''' dv/dt = (ge * (Ee-v) + El - v) / taum : volt dge/dt = -ge / taue : 1 ''' input = PoissonGroup(N, rates=F) neurons = NeuronGroup(1, eqs_neurons, threshold='v>vt', reset='v = vr', method='euler') S = Synapses(input, neurons, '''w : 1 dApre/dt = -Apre / taupre : 1 (event-driven) dApost/dt = -Apost / taupost : 1 (event-driven)''', on_pre='''ge += w Apre += dApre w = clip(w + Apost, 0, gmax)''', on_post='''Apost += dApost w = clip(w + Apre, 0, gmax)''', ) S.connect() S.w = 'rand() * gmax' mon = StateMonitor(S, 'w', record=[0, 1]) s_mon = SpikeMonitor(input) run(100*second, report='text') subplot(311) plot(S.w / gmax, '.k') ylabel('Weight / gmax') xlabel('Synapse index') subplot(312) hist(S.w / gmax, 20) xlabel('Weight / gmax') subplot(313) plot(mon.t/second, mon.w.T/gmax) xlabel('Time (s)') ylabel('Weight / gmax') tight_layout() show() brian2-2.5.4/examples/standalone/cuba_openmp.py000077500000000000000000000017141445201106100215070ustar00rootroot00000000000000#!/usr/bin/env python """ Run the ``cuba.py`` example with OpenMP threads. """ from brian2 import * set_device('cpp_standalone', directory='CUBA') prefs.devices.cpp_standalone.openmp_threads = 4 taum = 20*ms taue = 5*ms taui = 10*ms Vt = -50*mV Vr = -60*mV El = -49*mV eqs = ''' dv/dt = (ge+gi-(v-El))/taum : volt (unless refractory) dge/dt = -ge/taue : volt (unless refractory) dgi/dt = -gi/taui : volt (unless refractory) ''' P = NeuronGroup(4000, eqs, threshold='v>Vt', reset='v = Vr', refractory=5*ms, method='exact') P.v = 'Vr + rand() * (Vt - Vr)' P.ge = 0*mV P.gi = 0*mV we = (60*0.27/10)*mV # excitatory synaptic weight (voltage) wi = (-20*4.5/10)*mV # inhibitory synaptic weight Ce = Synapses(P, P, on_pre='ge += we') Ci = Synapses(P, P, on_pre='gi += wi') Ce.connect('i<3200', p=0.02) Ci.connect('i>=3200', p=0.02) s_mon = SpikeMonitor(P) run(1 * second) plot(s_mon.t/ms, s_mon.i, ',k') xlabel('Time (ms)') ylabel('Neuron index') show() brian2-2.5.4/examples/standalone/simple_case.py000066400000000000000000000006371445201106100215030ustar00rootroot00000000000000#!/usr/bin/env python """ The most simple case how to use standalone mode. """ from brian2 import * set_device('cpp_standalone') # ← only difference to "normal" simulation tau = 10*ms eqs = ''' dv/dt = (1-v)/tau : 1 ''' G = NeuronGroup(10, eqs, method='exact') G.v = 'rand()' mon = StateMonitor(G, 'v', record=True) run(100*ms) plt.plot(mon.t/ms, mon.v.T) plt.gca().set(xlabel='t (ms)', ylabel='v') plt.show() brian2-2.5.4/examples/standalone/simple_case_build.py000066400000000000000000000007761445201106100226660ustar00rootroot00000000000000#!/usr/bin/env python """ The most simple case how to use standalone mode with several `run` calls. """ from brian2 import * set_device('cpp_standalone', build_on_run=False) tau = 10*ms I = 1 # input current eqs = ''' dv/dt = (I-v)/tau : 1 ''' G = NeuronGroup(10, eqs, method='exact') G.v = 'rand()' mon = StateMonitor(G, 'v', record=True) run(20*ms) I = 0 run(80*ms) # Actually generate/compile/run the code: device.build() plt.plot(mon.t/ms, mon.v.T) plt.gca().set(xlabel='t (ms)', ylabel='v') plt.show() brian2-2.5.4/examples/standalone/standalone_multiplerun.py000066400000000000000000000027661445201106100240140ustar00rootroot00000000000000""" This example shows how to run several, independent simulations in standalone mode. Note that this is not the optimal approach if running the same model with minor differences (as in this example). The example come from Tutorial part 3. For a discussion see this `post on the Brian forum `_. """ import numpy as np import pylab as plt import brian2 as b2 from time import time b2.set_device('cpp_standalone') def simulate(tau): # These two lines are needed to start a new standalone simulation: b2.device.reinit() b2.device.activate() eqs = ''' dv/dt = -v/tau : 1 ''' net = b2.Network() P = b2.PoissonGroup(num_inputs, rates=input_rate) G = b2.NeuronGroup(1, eqs, threshold='v>1', reset='v=0', method='euler') S = b2.Synapses(P, G, on_pre='v += weight') S.connect() M = b2.SpikeMonitor(G) net.add([P, G, S, M]) net.run(1000 * b2.ms) return M if __name__ == "__main__": start_time = time() num_inputs = 100 input_rate = 10 * b2.Hz weight = 0.1 npoints = 15 tau_range = np.linspace(1, 15, npoints) * b2.ms output_rates = np.zeros(npoints) for ii in range(npoints): tau_i = tau_range[ii] M = simulate(tau_i) output_rates[ii] = M.num_spikes / b2.second print(f"Done in {time() - start_time}") plt.plot(tau_range/b2.ms, output_rates) plt.xlabel(r"$\tau$ (ms)") plt.ylabel("Firing rate (sp/s)") plt.show() brian2-2.5.4/examples/synapses/000077500000000000000000000000001445201106100163545ustar00rootroot00000000000000brian2-2.5.4/examples/synapses/STDP.py000066400000000000000000000026261445201106100175060ustar00rootroot00000000000000#!/usr/bin/env python """ Spike-timing dependent plasticity Adapted from Song, Miller and Abbott (2000) and Song and Abbott (2001) """ from brian2 import * N = 1000 taum = 10*ms taupre = 20*ms taupost = taupre Ee = 0*mV vt = -54*mV vr = -60*mV El = -74*mV taue = 5*ms F = 15*Hz gmax = .01 dApre = .01 dApost = -dApre * taupre / taupost * 1.05 dApost *= gmax dApre *= gmax eqs_neurons = ''' dv/dt = (ge * (Ee-v) + El - v) / taum : volt dge/dt = -ge / taue : 1 ''' poisson_input = PoissonGroup(N, rates=F) neurons = NeuronGroup(1, eqs_neurons, threshold='v>vt', reset='v = vr', method='euler') S = Synapses(poisson_input, neurons, '''w : 1 dApre/dt = -Apre / taupre : 1 (event-driven) dApost/dt = -Apost / taupost : 1 (event-driven)''', on_pre='''ge += w Apre += dApre w = clip(w + Apost, 0, gmax)''', on_post='''Apost += dApost w = clip(w + Apre, 0, gmax)''', ) S.connect() S.w = 'rand() * gmax' mon = StateMonitor(S, 'w', record=[0, 1]) s_mon = SpikeMonitor(poisson_input) run(100*second, report='text') subplot(311) plot(S.w / gmax, '.k') ylabel('Weight / gmax') xlabel('Synapse index') subplot(312) hist(S.w / gmax, 20) xlabel('Weight / gmax') subplot(313) plot(mon.t/second, mon.w.T/gmax) xlabel('Time (s)') ylabel('Weight / gmax') tight_layout() show() brian2-2.5.4/examples/synapses/continuous_interaction.py000066400000000000000000000071701445201106100235400ustar00rootroot00000000000000""" Synaptic model with continuous interaction ------------------------------------------ This example implements a conductance base synapse that is continuously linking two neurons, i.e. the synaptic gating variable updates at each time step. Two Reduced Traub-Miles Model (RTM) neurons are connected to each other through a directed synapse from neuron 1 to 2. Here, the complexity stems from the fact that the synaptic conductance is a continuous function of the membrane potential, instead of being triggered by individual spikes. This can be useful in particular when analyzing models mathematically but it is not recommended in most cases because they tend to be less efficient. Also note that this model only works with (pre-synaptic) neuron models that model the action potential in detail, i.e. not with integrate-and-fire type models. There are two broad approaches (``s`` as part of the pre-synaptic neuron or ``s`` as part of the Synapses object), all depends on whether the time constants are the same across all synapses or whether they can vary between synapses. In this example, the time constant is assumed to be the same and ``s`` is therefore part of the pre-synaptic neuron model. References: - Introduction to modeling neural dynamics, Börgers, chapter 20 - `Discussion in Brian forum `_ """ from brian2 import * I_e = 1.5*uA simulation_time = 100*ms # neuron RTM parameters El = -67 * mV EK = -100 * mV ENa = 50 * mV ESyn = 0 * mV gl = 0.1 * msiemens gK = 80 * msiemens gNa = 100 * msiemens C = 1 * ufarad weight = 0.25 gSyn = 1.0 * msiemens tau_d = 2 * ms tau_r = 0.2 * ms # forming RTM model with differential equations eqs = """ alphah = 0.128 * exp(-(vm + 50.0*mV) / (18.0*mV))/ms :Hz alpham = 0.32/mV * (vm + 54*mV) / (1.0 - exp(-(vm + 54.0*mV) / (4.0*mV)))/ms:Hz alphan = 0.032/mV * (vm + 52*mV) / (1.0 - exp(-(vm + 52.0*mV) / (5.0*mV)))/ms:Hz betah = 4.0 / (1.0 + exp(-(vm + 27.0*mV) / (5.0*mV)))/ms:Hz betam = 0.28/mV * (vm + 27.0*mV) / (exp((vm + 27.0*mV) / (5.0*mV)) - 1.0)/ms:Hz betan = 0.5 * exp(-(vm + 57.0*mV) / (40.0*mV))/ms:Hz membrane_Im = I_ext + gNa*m**3*h*(ENa-vm) + gl*(El-vm) + gK*n**4*(EK-vm) + gSyn*s_in*(-vm): amp I_ext : amp s_in : 1 dm/dt = alpham*(1-m)-betam*m : 1 dn/dt = alphan*(1-n)-betan*n : 1 dh/dt = alphah*(1-h)-betah*h : 1 ds/dt = 0.5 * (1 + tanh(0.1*vm/mV)) * (1-s)/tau_r - s/tau_d : 1 dvm/dt = membrane_Im/C : volt """ neuron = NeuronGroup(2, eqs, method="exponential_euler") # initialize variables neuron.vm = [-70.0, -65.0]*mV neuron.m = "alpham / (alpham + betam)" neuron.h = "alphah / (alphah + betah)" neuron.n = "alphan / (alphan + betan)" neuron.I_ext = [I_e, 0.0*uA] S = Synapses(neuron, neuron, 's_in_post = weight*s_pre:1 (summed)') S.connect(i=0, j=1) # tracking variables st_mon = StateMonitor(neuron, ["vm", "s", "s_in"], record=[0, 1]) # running the simulation run(simulation_time) # plot the results fig, ax = plt.subplots(2, figsize=(10, 6), sharex=True, gridspec_kw={'height_ratios': (3, 1)}) ax[0].plot(st_mon.t/ms, st_mon.vm[0]/mV, lw=2, c="r", alpha=0.5, label="neuron 0") ax[0].plot(st_mon.t/ms, st_mon.vm[1]/mV, lw=2, c="b", alpha=0.5, label='neuron 1') ax[1].plot(st_mon.t/ms, st_mon.s[0], lw=2, c="r", alpha=0.5, label='s, neuron 0') ax[1].plot(st_mon.t/ms, st_mon.s_in[1], lw=2, c="b", alpha=0.5, label='s_in, neuron 1') ax[0].set(ylabel='v [mV]', xlim=(0, np.max(st_mon.t / ms)), ylim=(-100, 50)) ax[1].set(xlabel="t [ms]", ylabel="s", ylim=(0, 1)) ax[0].legend() ax[1].legend() plt.show() brian2-2.5.4/examples/synapses/efficient_gaussian_connectivity.py000066400000000000000000000121771445201106100253620ustar00rootroot00000000000000""" An example of turning an expensive `Synapses.connect` operation into three cheap ones using a mathematical trick. Consider the connection probability between neurons i and j given by the Gaussian function :math:`p=e^{-\alpha(i-j)^2}` (for some constant :math:`\alpha`). If we want to connect neurons with this probability, we can very simply do:: S.connect(p='exp(-alpha*(i-j)**2)') However, this has a problem. Although we know that this will create :math:`O(N)` synapses if N is the number of neurons, because we have specified ``p`` as a function of i and j, we have to evaluate ``p(i, j)`` for every pair ``(i, j)``, and therefore it takes :math:`O(N^2)` operations. Our first option is to take a cutoff, and say that if :math:`pw`. This time, we note that we want to create a synapse with probability :math:`p(i-j)` and we can rewrite this as :math:`p(i-j)/p(w)\cdot p(w)`. If :math:`|i-j|>w` then this is a product of two probabilities :math:`p(i-j)/p(w)` and :math:`p(w)`. So in the region :math:`|i-j|>w` a synapse will be created if two random events both occur, with these two probabilities. This might seem a little strange until you notice that one of the two probabilities :math:`p(w)` doesn't depend on i or j. This lets us use the much more efficient ``sample`` algorithm to generate a set of candidate ``j`` values, and then add the additional test ``rand() j'] = 'exp(-(i - j)**2/space_constant) * mV' # Generate a matrix for display w_matrix = np.zeros((len(G), len(G))) w_matrix[S.i[:], S.j[:]] = S.w[:] subplot(1, 2, 1) plot(G.v[:] / mV) xlabel('Neuron index') ylabel('v') subplot(1, 2, 2) imshow(w_matrix) xlabel('i') ylabel('j') title('Synaptic weight') tight_layout() show() brian2-2.5.4/examples/synapses/synapses.py000066400000000000000000000012271445201106100205750ustar00rootroot00000000000000""" A simple example of using `Synapses`. """ from brian2 import * G1 = NeuronGroup(10, 'dv/dt = -v / (10*ms) : 1', threshold='v > 1', reset='v=0.', method='exact') G1.v = 1.2 G2 = NeuronGroup(10, 'dv/dt = -v / (10*ms) : 1', threshold='v > 1', reset='v=0', method='exact') syn = Synapses(G1, G2, 'dw/dt = -w / (50*ms): 1 (event-driven)', on_pre='v += w') syn.connect('i == j', p=0.75) # Set the delays syn.delay = '1*ms + i*ms + 0.25*ms * randn()' # Set the initial values of the synaptic variable syn.w = 1 mon = StateMonitor(G2, 'v', record=True) run(20*ms) plot(mon.t/ms, mon.v.T) xlabel('Time (ms)') ylabel('v') show() brian2-2.5.4/pyproject.toml000066400000000000000000000042271445201106100156120ustar00rootroot00000000000000[project] name = "Brian2" authors = [ {name = 'Marcel Stimberg'}, {name = 'Dan Goodman'}, {name ='Romain Brette'} ] requires-python = '>=3.9' dependencies = [ 'numpy>=1.21', 'cython>=0.29', 'sympy>=1.2', 'pyparsing', 'jinja2>=2.7', 'py-cpuinfo;platform_system=="Windows"', 'setuptools>=61', 'packaging', ] dynamic = ["version", "readme"] description = 'A clock-driven simulator for spiking neural networks' keywords = ['computational neuroscience', 'simulation', 'neural networks', 'spiking neurons', 'biological neural networks', 'research'] classifiers = [ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Science/Research', 'License :: OSI Approved :: CEA CNRS Inria Logiciel Libre License, version 2.1 (CeCILL-2.1)', 'Natural Language :: English', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Topic :: Scientific/Engineering :: Bio-Informatics' ] [project.optional-dependencies] test = ['pytest', 'pytest-xdist>=1.22.3'] docs = ['sphinx>=1.8', 'ipython>=5', 'sphinx-tabs'] [project.urls] Homepage = 'https://briansimulator.org' Documentation ='https://brian2.readthedocs.io/' Source = 'https://github.com/brian-team/brian2' Tracker = 'https://github.com/brian-team/brian2/issues' [tool.setuptools] zip-safe = false packages = ['brian2'] [tool.setuptools.dynamic] readme = {file = 'README.rst', content-type = "text/x-rst"} [tool.setuptools_scm] version_scheme = 'post-release' local_scheme = 'no-local-version' write_to = 'brian2/_version.py' tag_regex = '^(?P\d+(?:\.\d+){0,2}[^\+]*(?:\+.*)?)$' fallback_version = 'unknown' [build-system] requires = [ "setuptools>=61", "numpy>=1.10", "wheel", "Cython", "oldest-supported-numpy", "setuptools_scm[toml]>=6.2" ] build-backend = "setuptools.build_meta" [tool.black] target-version = ['py39'] include = '^/brian2/.*\.pyi?$' preview = true [tool.isort] atomic = true profile = "black" py_version = "39" skip_gitignore = true # NOTE: isort has no "include" option, only "skip". skip_glob = ["dev/*", "docs_sphinx/*", "examples/*", "tutorials/*"] brian2-2.5.4/rtd-requirements.txt000066400000000000000000000005111445201106100167410ustar00rootroot00000000000000# This is NOT a requirements file for Brian2 # We only use this file to make sure that ipython is installed on readthedocs, # necessary to make code blocks in the tutorials (which use ipython syntax # highlighting) get displayed correctly ipython >= 5 sphinx-tabs sphinx==1.8.6 Jinja2<3.1.0 commonmark==0.9.1 recommonmark==0.5.0 brian2-2.5.4/setup.cfg000066400000000000000000000005201445201106100145070ustar00rootroot00000000000000[build_sphinx] all-files=1 source-dir=docs_sphinx build-dir=docs [flake8] # Whitespace before : is PEP8-compatible and used by black for slices (E203) # No need to check line length, enforced by black (E501) # Lambda expressions are ok (E731) # We sometimes use #### heading ### style comments (E266) extend-ignore=E203,E501,E731,E266 brian2-2.5.4/setup.py000066400000000000000000000072071445201106100144110ustar00rootroot00000000000000#! /usr/bin/env python ''' Brian2 setup script ''' # isort:skip_file import io import sys import os import platform from setuptools import setup, Extension from setuptools.command.build_ext import build_ext from distutils.errors import CompileError, DistutilsPlatformError try: from Cython.Build import cythonize cython_available = True except ImportError: cython_available = False def has_option(name): try: sys.argv.remove('--%s' % name) return True except ValueError: pass # allow passing all cmd line options also as environment variables env_val = os.getenv(name.upper().replace('-', '_'), 'false').lower() if env_val == "true": return True return False WITH_CYTHON = has_option('with-cython') FAIL_ON_ERROR = has_option('fail-on-error') pyx_fname = os.path.join('brian2', 'synapses', 'cythonspikequeue.pyx') cpp_fname = os.path.join('brian2', 'synapses', 'cythonspikequeue.cpp') if WITH_CYTHON or not os.path.exists(cpp_fname): fname = pyx_fname if not cython_available: if FAIL_ON_ERROR and WITH_CYTHON: raise RuntimeError('Compilation with Cython requested/necessary but ' 'Cython is not available.') else: sys.stderr.write('Compilation with Cython requested/necessary but ' 'Cython is not available.\n') fname = None if not os.path.exists(pyx_fname): if FAIL_ON_ERROR and WITH_CYTHON: raise RuntimeError(('Compilation with Cython requested/necessary but ' 'Cython source file %s does not exist') % pyx_fname) else: sys.stderr.write(('Compilation with Cython requested/necessary but ' 'Cython source file %s does not exist\n') % pyx_fname) fname = None else: fname = cpp_fname if fname is not None: extensions = [Extension("brian2.synapses.cythonspikequeue", [fname], include_dirs=[])] # numpy include dir will be added later if fname == pyx_fname: extensions = cythonize(extensions) else: extensions = [] class optional_build_ext(build_ext): ''' This class allows the building of C extensions to fail and still continue with the building process. This ensures that installation never fails, even on systems without a C compiler, for example. If brian is installed in an environment where building C extensions *should* work, use the "--fail-on-error" option or set the environment variable FAIL_ON_ERROR to true. ''' def build_extension(self, ext): import numpy numpy_incl = numpy.get_include() if hasattr(ext, 'include_dirs') and not numpy_incl in ext.include_dirs: ext.include_dirs.append(numpy_incl) try: build_ext.build_extension(self, ext) except (CompileError, DistutilsPlatformError) as ex: if FAIL_ON_ERROR: raise ex else: error_msg = ('Building %s failed (see error message(s) ' 'above) -- pure Python version will be used ' 'instead.') % ext.name sys.stderr.write('*' * len(error_msg) + '\n' + error_msg + '\n' + '*' * len(error_msg) + '\n') # Use readme file as long description with io.open(os.path.join(os.path.dirname(__file__), 'README.rst'), encoding='utf-8') as f: long_description = f.read() setup(ext_modules=extensions, cmdclass={'build_ext': optional_build_ext}) brian2-2.5.4/tutorials/000077500000000000000000000000001445201106100147175ustar00rootroot00000000000000brian2-2.5.4/tutorials/1-intro-to-brian-neurons.ipynb000066400000000000000000000652101445201106100224570ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": { "heading_collapsed": false, "level": 1 }, "source": [ "# Introduction to Brian part 1: Neurons" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "All Brian scripts start with the following. If you're trying this notebook out in the Jupyter notebook, you should start by running this cell." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "from brian2 import *" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Later we'll do some plotting in the notebook, so we activate inline plotting in the notebook by doing this:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "%matplotlib inline" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you are not using the Jupyter notebook to run this example (e.g. you are using a standard Python terminal, or you copy&paste these example into an editor and run them as a script), then plots will not automatically be displayed. In this case, call the ``show()`` command explicitly after the plotting commands." ] }, { "cell_type": "markdown", "metadata": { "heading_collapsed": false, "level": 2 }, "source": [ "## Units system\n", "\n", "Brian has a system for using quantities with physical dimensions:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "20*volt" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "All of the basic SI units can be used (volt, amp, etc.) along with all the standard prefixes (m=milli, p=pico, etc.), as well as a few special abbreviations like ``mV`` for millivolt, ``pF`` for picofarad, etc." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "1000*amp" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "1e6*volt" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "1000*namp" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Also note that combinations of units with work as expected:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "10*nA*5*Mohm" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And if you try to do something wrong like adding amps and volts, what happens?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "5*amp+10*volt" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you haven't see an error message in Python before that can look a bit overwhelming, but it's actually quite simple and it's important to know how to read these because you'll probably see them quite often.\n", "\n", "You should start at the bottom and work up. The last line gives the error type ``DimensionMismatchError`` along with a more specific message (in this case, you were trying to add together two quantities with different SI units, which is impossible).\n", "\n", "Working upwards, each of the sections starts with a filename (e.g. ``C:\\Users\\Dan\\...``) with possibly the name of a function, and then a few lines surrounding the line where the error occurred (which is identified with an arrow).\n", "\n", "The last of these sections shows the place in the function where the error actually happened. The section above it shows the function that called that function, and so on until the first section will be the script that you actually run. This sequence of sections is called a traceback, and is helpful in debugging.\n", "\n", "If you see a traceback, what you want to do is start at the bottom and scan up the sections until you find your own file because that's most likely where the problem is. (Of course, your code might be correct and Brian may have a bug in which case, please let us know on the email support list.)" ] }, { "cell_type": "markdown", "metadata": { "heading_collapsed": false, "level": 2 }, "source": [ "## A simple model\n", "\n", "Let's start by defining a simple neuron model. In Brian, all models are defined by systems of differential equations. Here's a simple example of what that looks like:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "tau = 10*ms\n", "eqs = '''\n", "dv/dt = (1-v)/tau : 1\n", "'''" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In Python, the notation ``'''`` is used to begin and end a multi-line string. So the equations are just a string with one line per equation. The equations are formatted with standard mathematical notation, with one addition. At the end of a line you write ``: unit`` where ``unit`` is the SI unit of that variable.\n", "Note that this is not the unit of the two sides of the equation (which would be ``1/second``), but the unit of the *variable* defined by the equation, i.e. in this case $v$.\n", "\n", "Now let's use this definition to create a neuron." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "G = NeuronGroup(1, eqs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In Brian, you only create groups of neurons, using the class ``NeuronGroup``. The first two arguments when you create one of these objects are the number of neurons (in this case, 1) and the defining differential equations.\n", "\n", "Let's see what happens if we didn't put the variable ``tau`` in the equation:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "eqs = '''\n", "dv/dt = 1-v : 1\n", "'''\n", "G = NeuronGroup(1, eqs)\n", "run(100*ms)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "An error is raised, but why? The reason is that the differential equation is now dimensionally inconsistent. The left hand side ``dv/dt`` has units of ``1/second`` but the right hand side ``1-v`` is dimensionless. People often find this behaviour of Brian confusing because this sort of equation is very common in mathematics. However, for quantities with physical dimensions it is incorrect because the results would change depending on the unit you measured it in. For time, if you measured it in seconds the same equation would behave differently to how it would if you measured time in milliseconds. To avoid this, we insist that you always specify dimensionally consistent equations.\n", "\n", "Now let's go back to the good equations and actually run the simulation." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start_scope()\n", "\n", "tau = 10*ms\n", "eqs = '''\n", "dv/dt = (1-v)/tau : 1\n", "'''\n", "\n", "G = NeuronGroup(1, eqs)\n", "run(100*ms)" ] }, { "cell_type": "markdown", "metadata": { "level": 7 }, "source": [ "First off, ignore that ``start_scope()`` at the top of the cell. You'll see that in each cell in this tutorial where we run a simulation. All it does is make sure that any Brian objects created before the function is called aren't included in the next run of the simulation.\n", "\n", "Secondly, you'll see that there is an \"INFO\" message about not specifying the numerical integration method. This is harmless and just to let you know what method we chose, but we'll fix it in the next cell by specifying the method explicitly.\n", "\n", "So, what has happened here? Well, the command ``run(100*ms)`` runs the simulation for 100 ms. We can see that this has worked by printing the value of the variable ``v`` before and after the simulation." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start_scope()\n", "\n", "G = NeuronGroup(1, eqs, method='exact')\n", "print('Before v = %s' % G.v[0])\n", "run(100*ms)\n", "print('After v = %s' % G.v[0])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "By default, all variables start with the value 0. Since the differential equation is ``dv/dt=(1-v)/tau`` we would expect after a while that ``v`` would tend towards the value 1, which is just what we see. Specifically, we'd expect ``v`` to have the value ``1-exp(-t/tau)``. Let's see if that's right." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print('Expected value of v = %s' % (1-exp(-100*ms/tau)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Good news, the simulation gives the value we'd expect!\n", "\n", "Now let's take a look at a graph of how the variable ``v`` evolves over time." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start_scope()\n", "\n", "G = NeuronGroup(1, eqs, method='exact')\n", "M = StateMonitor(G, 'v', record=True)\n", "\n", "run(30*ms)\n", "\n", "plot(M.t/ms, M.v[0])\n", "xlabel('Time (ms)')\n", "ylabel('v');" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "This time we only ran the simulation for 30 ms so that we can see the behaviour better. It looks like it's behaving as expected, but let's just check that analytically by plotting the expected behaviour on top." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start_scope()\n", "\n", "G = NeuronGroup(1, eqs, method='exact')\n", "M = StateMonitor(G, 'v', record=0)\n", "\n", "run(30*ms)\n", "\n", "plot(M.t/ms, M.v[0], 'C0', label='Brian')\n", "plot(M.t/ms, 1-exp(-M.t/tau), 'C1--',label='Analytic')\n", "xlabel('Time (ms)')\n", "ylabel('v')\n", "legend();" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As you can see, the blue (Brian) and dashed orange (analytic solution) lines coincide.\n", "\n", "In this example, we used the object ``StateMonitor`` object. This is used to record the values of a neuron variable while the simulation runs. The first two arguments are the group to record from, and the variable you want to record from. We also specify ``record=0``. This means that we record all values for neuron 0. We have to specify which neurons we want to record because in large simulations with many neurons it usually uses up too much RAM to record the values of all neurons.\n", "\n", "Now try modifying the equations and parameters and see what happens in the cell below." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start_scope()\n", "\n", "tau = 10*ms\n", "eqs = '''\n", "dv/dt = (sin(2*pi*100*Hz*t)-v)/tau : 1\n", "'''\n", "\n", "# Change to Euler method because exact integrator doesn't work here\n", "G = NeuronGroup(1, eqs, method='euler')\n", "M = StateMonitor(G, 'v', record=0)\n", "\n", "G.v = 5 # initial value\n", "\n", "run(60*ms)\n", "\n", "plot(M.t/ms, M.v[0])\n", "xlabel('Time (ms)')\n", "ylabel('v');" ] }, { "cell_type": "markdown", "metadata": { "heading_collapsed": false, "level": 2 }, "source": [ "## Adding spikes\n", "\n", "So far we haven't done anything neuronal, just played around with differential equations. Now let's start adding spiking behaviour." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start_scope()\n", "\n", "tau = 10*ms\n", "eqs = '''\n", "dv/dt = (1-v)/tau : 1\n", "'''\n", "\n", "G = NeuronGroup(1, eqs, threshold='v>0.8', reset='v = 0', method='exact')\n", "\n", "M = StateMonitor(G, 'v', record=0)\n", "run(50*ms)\n", "plot(M.t/ms, M.v[0])\n", "xlabel('Time (ms)')\n", "ylabel('v');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We've added two new keywords to the ``NeuronGroup`` declaration: ``threshold='v>0.8'`` and ``reset='v = 0'``. What this means is that when ``v>0.8`` we fire a spike, and immediately reset ``v = 0`` after the spike. We can put any expression and series of statements as these strings.\n", "\n", "As you can see, at the beginning the behaviour is the same as before until ``v`` crosses the threshold ``v>0.8`` at which point you see it reset to 0. You can't see it in this figure, but internally Brian has registered this event as a spike. Let's have a look at that." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start_scope()\n", "\n", "G = NeuronGroup(1, eqs, threshold='v>0.8', reset='v = 0', method='exact')\n", "\n", "spikemon = SpikeMonitor(G)\n", "\n", "run(50*ms)\n", "\n", "print('Spike times: %s' % spikemon.t[:])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The ``SpikeMonitor`` object takes the group whose spikes you want to record as its argument and stores the spike times in the variable ``t``. Let's plot those spikes on top of the other figure to see that it's getting it right." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start_scope()\n", "\n", "G = NeuronGroup(1, eqs, threshold='v>0.8', reset='v = 0', method='exact')\n", "\n", "statemon = StateMonitor(G, 'v', record=0)\n", "spikemon = SpikeMonitor(G)\n", "\n", "run(50*ms)\n", "\n", "plot(statemon.t/ms, statemon.v[0])\n", "for t in spikemon.t:\n", " axvline(t/ms, ls='--', c='C1', lw=3)\n", "xlabel('Time (ms)')\n", "ylabel('v');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here we've used the ``axvline`` command from ``matplotlib`` to draw an orange, dashed vertical line at the time of each spike recorded by the ``SpikeMonitor``.\n", "\n", "Now try changing the strings for ``threshold`` and ``reset`` in the cell above to see what happens." ] }, { "cell_type": "markdown", "metadata": { "heading_collapsed": false, "level": 2 }, "source": [ "## Refractoriness\n", "\n", "A common feature of neuron models is refractoriness. This means that after the neuron fires a spike it becomes refractory for a certain duration and cannot fire another spike until this period is over. Here's how we do that in Brian." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start_scope()\n", "\n", "tau = 10*ms\n", "eqs = '''\n", "dv/dt = (1-v)/tau : 1 (unless refractory)\n", "'''\n", "\n", "G = NeuronGroup(1, eqs, threshold='v>0.8', reset='v = 0', refractory=5*ms, method='exact')\n", "\n", "statemon = StateMonitor(G, 'v', record=0)\n", "spikemon = SpikeMonitor(G)\n", "\n", "run(50*ms)\n", "\n", "plot(statemon.t/ms, statemon.v[0])\n", "for t in spikemon.t:\n", " axvline(t/ms, ls='--', c='C1', lw=3)\n", "xlabel('Time (ms)')\n", "ylabel('v');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As you can see in this figure, after the first spike, ``v`` stays at 0 for around 5 ms before it resumes its normal behaviour. To do this, we've done two things. Firstly, we've added the keyword ``refractory=5*ms`` to the ``NeuronGroup`` declaration. On its own, this only means that the neuron cannot spike in this period (see below), but doesn't change how ``v`` behaves. In order to make ``v`` stay constant during the refractory period, we have to add ``(unless refractory)`` to the end of the definition of ``v`` in the differential equations. What this means is that the differential equation determines the behaviour of ``v`` unless it's refractory in which case it is switched off.\n", "\n", "Here's what would happen if we didn't include ``(unless refractory)``. Note that we've also decreased the value of ``tau`` and increased the length of the refractory period to make the behaviour clearer." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start_scope()\n", "\n", "tau = 5*ms\n", "eqs = '''\n", "dv/dt = (1-v)/tau : 1\n", "'''\n", "\n", "G = NeuronGroup(1, eqs, threshold='v>0.8', reset='v = 0', refractory=15*ms, method='exact')\n", "\n", "statemon = StateMonitor(G, 'v', record=0)\n", "spikemon = SpikeMonitor(G)\n", "\n", "run(50*ms)\n", "\n", "plot(statemon.t/ms, statemon.v[0])\n", "for t in spikemon.t:\n", " axvline(t/ms, ls='--', c='C1', lw=3)\n", "axhline(0.8, ls=':', c='C2', lw=3)\n", "xlabel('Time (ms)')\n", "ylabel('v')\n", "print(\"Spike times: %s\" % spikemon.t[:])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So what's going on here? The behaviour for the first spike is the same: ``v`` rises to 0.8 and then the neuron fires a spike at time 8 ms before immediately resetting to 0. Since the refractory period is now 15 ms this means that the neuron won't be able to spike again until time 8 + 15 = 23 ms. Immediately after the first spike, the value of ``v`` now instantly starts to rise because we didn't specify ``(unless refractory)`` in the definition of ``dv/dt``. However, once it reaches the value 0.8 (the dashed green line) at time roughly 8 ms it doesn't fire a spike even though the threshold is ``v>0.8``. This is because the neuron is still refractory until time 23 ms, at which point it fires a spike.\n", "\n", "Note that you can do more complicated and interesting things with refractoriness. See the full documentation for more details about how it works." ] }, { "cell_type": "markdown", "metadata": { "heading_collapsed": false, "level": 2 }, "source": [ "## Multiple neurons\n", "\n", "So far we've only been working with a single neuron. Let's do something interesting with multiple neurons." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start_scope()\n", "\n", "N = 100\n", "tau = 10*ms\n", "eqs = '''\n", "dv/dt = (2-v)/tau : 1\n", "'''\n", "\n", "G = NeuronGroup(N, eqs, threshold='v>1', reset='v=0', method='exact')\n", "G.v = 'rand()'\n", "\n", "spikemon = SpikeMonitor(G)\n", "\n", "run(50*ms)\n", "\n", "plot(spikemon.t/ms, spikemon.i, '.k')\n", "xlabel('Time (ms)')\n", "ylabel('Neuron index');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This shows a few changes. Firstly, we've got a new variable ``N`` determining the number of neurons. Secondly, we added the statement ``G.v = 'rand()'`` before the run. What this does is initialise each neuron with a different uniform random value between 0 and 1. We've done this just so each neuron will do something a bit different. The other big change is how we plot the data in the end.\n", "\n", "As well as the variable ``spikemon.t`` with the times of all the spikes, we've also used the variable ``spikemon.i`` which gives the corresponding neuron index for each spike, and plotted a single black dot with time on the x-axis and neuron index on the y-value. This is the standard \"raster plot\" used in neuroscience." ] }, { "cell_type": "markdown", "metadata": { "heading_collapsed": false, "level": 2 }, "source": [ "## Parameters\n", "\n", "To make these multiple neurons do something more interesting, let's introduce per-neuron parameters that don't have a differential equation attached to them." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start_scope()\n", "\n", "N = 100\n", "tau = 10*ms\n", "v0_max = 3.\n", "duration = 1000*ms\n", "\n", "eqs = '''\n", "dv/dt = (v0-v)/tau : 1 (unless refractory)\n", "v0 : 1\n", "'''\n", "\n", "G = NeuronGroup(N, eqs, threshold='v>1', reset='v=0', refractory=5*ms, method='exact')\n", "M = SpikeMonitor(G)\n", "\n", "G.v0 = 'i*v0_max/(N-1)'\n", "\n", "run(duration)\n", "\n", "figure(figsize=(12,4))\n", "subplot(121)\n", "plot(M.t/ms, M.i, '.k')\n", "xlabel('Time (ms)')\n", "ylabel('Neuron index')\n", "subplot(122)\n", "plot(G.v0, M.count/duration)\n", "xlabel('v0')\n", "ylabel('Firing rate (sp/s)');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The line ``v0 : 1`` declares a new per-neuron parameter ``v0`` with units ``1`` (i.e. dimensionless).\n", "\n", "The line ``G.v0 = 'i*v0_max/(N-1)'`` initialises the value of v0 for each neuron varying from 0 up to ``v0_max``. The symbol ``i`` when it appears in strings like this refers to the neuron index.\n", "\n", "So in this example, we're driving the neuron towards the value ``v0`` exponentially, but when ``v`` crosses ``v>1``, it fires a spike and resets. The effect is that the rate at which it fires spikes will be related to the value of ``v0``. For ``v0<1`` it will never fire a spike, and as ``v0`` gets larger it will fire spikes at a higher rate. The right hand plot shows the firing rate as a function of the value of ``v0``. This is the I-f curve of this neuron model.\n", "\n", "Note that in the plot we've used the ``count`` variable of the ``SpikeMonitor``: this is an array of the number of spikes each neuron in the group fired. Dividing this by the duration of the run gives the firing rate." ] }, { "cell_type": "markdown", "metadata": { "heading_collapsed": false, "level": 2 }, "source": [ "## Stochastic neurons\n", "\n", "Often when making models of neurons, we include a random element to model the effect of various forms of neural noise. In Brian, we can do this by using the symbol ``xi`` in differential equations. Strictly speaking, this symbol is a \"stochastic differential\" but you can sort of think of it as just a Gaussian random variable with mean 0 and standard deviation 1. We do have to take into account the way stochastic differentials scale with time, which is why we multiply it by ``tau**-0.5`` in the equations below (see a textbook on stochastic differential equations for more details).\n", "Note that we also changed the ``method`` keyword argument to use ``'euler'`` (which stands for the [Euler-Maruyama method](https://en.wikipedia.org/wiki/Euler%E2%80%93Maruyama_method)); the ``'exact'`` method that we used earlier is not applicable to stochastic differential equations." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start_scope()\n", "\n", "N = 100\n", "tau = 10*ms\n", "v0_max = 3.\n", "duration = 1000*ms\n", "sigma = 0.2\n", "\n", "eqs = '''\n", "dv/dt = (v0-v)/tau+sigma*xi*tau**-0.5 : 1 (unless refractory)\n", "v0 : 1\n", "'''\n", "\n", "G = NeuronGroup(N, eqs, threshold='v>1', reset='v=0', refractory=5*ms, method='euler')\n", "M = SpikeMonitor(G)\n", "\n", "G.v0 = 'i*v0_max/(N-1)'\n", "\n", "run(duration)\n", "\n", "figure(figsize=(12,4))\n", "subplot(121)\n", "plot(M.t/ms, M.i, '.k')\n", "xlabel('Time (ms)')\n", "ylabel('Neuron index')\n", "subplot(122)\n", "plot(G.v0, M.count/duration)\n", "xlabel('v0')\n", "ylabel('Firing rate (sp/s)');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "That's the same figure as in the previous section but with some noise added. Note how the curve has changed shape: instead of a sharp jump from firing at rate 0 to firing at a positive rate, it now increases in a sigmoidal fashion. This is because no matter how small the driving force the randomness may cause it to fire a spike." ] }, { "cell_type": "markdown", "metadata": { "collapsed": true, "heading_collapsed": false, "level": 2 }, "source": [ "## End of tutorial\n", "\n", "That's the end of this part of the tutorial. The cell below has another example. See if you can work out what it is doing and why. Try adding a ``StateMonitor`` to record the values of the variables for one of the neurons to help you understand it.\n", "\n", "You could also try out the things you've learned in this cell.\n", "\n", "Once you're done with that you can move on to the next tutorial on Synapses." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start_scope()\n", "\n", "N = 1000\n", "tau = 10*ms\n", "vr = -70*mV\n", "vt0 = -50*mV\n", "delta_vt0 = 5*mV\n", "tau_t = 100*ms\n", "sigma = 0.5*(vt0-vr)\n", "v_drive = 2*(vt0-vr)\n", "duration = 100*ms\n", "\n", "eqs = '''\n", "dv/dt = (v_drive+vr-v)/tau + sigma*xi*tau**-0.5 : volt\n", "dvt/dt = (vt0-vt)/tau_t : volt\n", "'''\n", "\n", "reset = '''\n", "v = vr\n", "vt += delta_vt0\n", "'''\n", "\n", "G = NeuronGroup(N, eqs, threshold='v>vt', reset=reset, refractory=5*ms, method='euler')\n", "spikemon = SpikeMonitor(G)\n", "\n", "G.v = 'rand()*(vt0-vr)+vr'\n", "G.vt = vt0\n", "\n", "run(duration)\n", "\n", "_ = hist(spikemon.t/ms, 100, histtype='stepfilled', facecolor='k', weights=ones(len(spikemon))/(N*defaultclock.dt))\n", "xlabel('Time (ms)')\n", "ylabel('Instantaneous firing rate (sp/s)');" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.0" } }, "nbformat": 4, "nbformat_minor": 1 } brian2-2.5.4/tutorials/2-intro-to-brian-synapses.ipynb000066400000000000000000000552411445201106100226370ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": { "heading_collapsed": false, "level": 1 }, "source": [ "# Introduction to Brian part 2: Synapses" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you haven't yet read part 1: Neurons, go read that now.\n", "\n", "As before we start by importing the Brian package and setting up matplotlib for IPython:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from brian2 import *\n", "%matplotlib inline" ] }, { "cell_type": "markdown", "metadata": { "heading_collapsed": false, "level": 2 }, "source": [ "## The simplest Synapse\n", "\n", "Once you have some neurons, the next step is to connect them up via synapses. We'll start out with doing the simplest possible type of synapse that causes an instantaneous change in a variable after a spike." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start_scope()\n", "\n", "eqs = '''\n", "dv/dt = (I-v)/tau : 1\n", "I : 1\n", "tau : second\n", "'''\n", "G = NeuronGroup(2, eqs, threshold='v>1', reset='v = 0', method='exact')\n", "G.I = [2, 0]\n", "G.tau = [10, 100]*ms\n", "\n", "# Comment these two lines out to see what happens without Synapses\n", "S = Synapses(G, G, on_pre='v_post += 0.2')\n", "S.connect(i=0, j=1)\n", "\n", "M = StateMonitor(G, 'v', record=True)\n", "\n", "run(100*ms)\n", "\n", "plot(M.t/ms, M.v[0], label='Neuron 0')\n", "plot(M.t/ms, M.v[1], label='Neuron 1')\n", "xlabel('Time (ms)')\n", "ylabel('v')\n", "legend();" ] }, { "cell_type": "markdown", "metadata": { "level": 7 }, "source": [ "There are a few things going on here. First of all, let's recap what is going on with the ``NeuronGroup``. We've created two neurons, each of which has the same differential equation but different values for parameters I and tau. Neuron 0 has ``I=2`` and ``tau=10*ms`` which means that is driven to repeatedly spike at a fairly high rate. Neuron 1 has ``I=0`` and ``tau=100*ms`` which means that on its own - without the synapses - it won't spike at all (the driving current I is 0). You can prove this to yourself by commenting out the two lines that define the synapse.\n", "\n", "Next we define the synapses: ``Synapses(source, target, ...)`` means that we are defining a synaptic model that goes from ``source`` to ``target``. In this case, the source and target are both the same, the group ``G``. The syntax ``on_pre='v_post += 0.2'`` means that when a spike occurs in the presynaptic neuron (hence ``on_pre``) it causes an instantaneous change to happen ``v_post += 0.2``. The ``_post`` means that the value of ``v`` referred to is the post-synaptic value, and it is increased by 0.2. So in total, what this model says is that whenever two neurons in G are connected by a synapse, when the source neuron fires a spike the target neuron will have its value of ``v`` increased by 0.2.\n", "\n", "However, at this point we have only defined the synapse model, we haven't actually created any synapses. The next line ``S.connect(i=0, j=1)`` creates a synapse from neuron 0 to neuron 1." ] }, { "cell_type": "markdown", "metadata": { "heading_collapsed": false, "level": 2 }, "source": [ "## Adding a weight\n", "\n", "In the previous section, we hard coded the weight of the synapse to be the value 0.2, but often we would to allow this to be different for different synapses. We do that by introducing synapse equations." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start_scope()\n", "\n", "eqs = '''\n", "dv/dt = (I-v)/tau : 1\n", "I : 1\n", "tau : second\n", "'''\n", "G = NeuronGroup(3, eqs, threshold='v>1', reset='v = 0', method='exact')\n", "G.I = [2, 0, 0]\n", "G.tau = [10, 100, 100]*ms\n", "\n", "# Comment these two lines out to see what happens without Synapses\n", "S = Synapses(G, G, 'w : 1', on_pre='v_post += w')\n", "S.connect(i=0, j=[1, 2])\n", "S.w = 'j*0.2'\n", "\n", "M = StateMonitor(G, 'v', record=True)\n", "\n", "run(50*ms)\n", "\n", "plot(M.t/ms, M.v[0], label='Neuron 0')\n", "plot(M.t/ms, M.v[1], label='Neuron 1')\n", "plot(M.t/ms, M.v[2], label='Neuron 2')\n", "xlabel('Time (ms)')\n", "ylabel('v')\n", "legend();" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This example behaves very similarly to the previous example, but now there's a synaptic weight variable ``w``. The string ``'w : 1'`` is an equation string, precisely the same as for neurons, that defines a single dimensionless parameter ``w``. We changed the behaviour on a spike to ``on_pre='v_post += w'`` now, so that each synapse can behave differently depending on the value of ``w``. To illustrate this, we've made a third neuron which behaves precisely the same as the second neuron, and connected neuron 0 to both neurons 1 and 2. We've also set the weights via ``S.w = 'j*0.2'``. When ``i`` and ``j`` occur in the context of synapses, ``i`` refers to the source neuron index, and ``j`` to the target neuron index. So this will give a synaptic connection from 0 to 1 with weight ``0.2=0.2*1`` and from 0 to 2 with weight ``0.4=0.2*2``." ] }, { "cell_type": "markdown", "metadata": { "heading_collapsed": false, "level": 2 }, "source": [ "## Introducing a delay\n", "\n", "So far, the synapses have been instantaneous, but we can also make them act with a certain delay." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start_scope()\n", "\n", "eqs = '''\n", "dv/dt = (I-v)/tau : 1\n", "I : 1\n", "tau : second\n", "'''\n", "G = NeuronGroup(3, eqs, threshold='v>1', reset='v = 0', method='exact')\n", "G.I = [2, 0, 0]\n", "G.tau = [10, 100, 100]*ms\n", "\n", "S = Synapses(G, G, 'w : 1', on_pre='v_post += w')\n", "S.connect(i=0, j=[1, 2])\n", "S.w = 'j*0.2'\n", "S.delay = 'j*2*ms'\n", "\n", "M = StateMonitor(G, 'v', record=True)\n", "\n", "run(50*ms)\n", "\n", "plot(M.t/ms, M.v[0], label='Neuron 0')\n", "plot(M.t/ms, M.v[1], label='Neuron 1')\n", "plot(M.t/ms, M.v[2], label='Neuron 2')\n", "xlabel('Time (ms)')\n", "ylabel('v')\n", "legend();" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As you can see, that's as simple as adding a line ``S.delay = 'j*2*ms'`` so that the synapse from 0 to 1 has a delay of 2 ms, and from 0 to 2 has a delay of 4 ms." ] }, { "cell_type": "markdown", "metadata": { "heading_collapsed": false, "level": 2 }, "source": [ "## More complex connectivity\n", "\n", "So far, we specified the synaptic connectivity explicitly, but for larger networks this isn't usually possible. For that, we usually want to specify some condition." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start_scope()\n", "\n", "N = 10\n", "G = NeuronGroup(N, 'v:1')\n", "S = Synapses(G, G)\n", "S.connect(condition='i!=j', p=0.2)" ] }, { "cell_type": "markdown", "metadata": { "level": 7 }, "source": [ "Here we've created a dummy neuron group of N neurons and a dummy synapses model that doens't actually do anything just to demonstrate the connectivity. The line ``S.connect(condition='i!=j', p=0.2)`` will connect all pairs of neurons ``i`` and ``j`` with probability 0.2 as long as the condition ``i!=j`` holds. So, how can we see that connectivity? Here's a little function that will let us visualise it." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def visualise_connectivity(S):\n", " Ns = len(S.source)\n", " Nt = len(S.target)\n", " figure(figsize=(10, 4))\n", " subplot(121)\n", " plot(zeros(Ns), arange(Ns), 'ok', ms=10)\n", " plot(ones(Nt), arange(Nt), 'ok', ms=10)\n", " for i, j in zip(S.i, S.j):\n", " plot([0, 1], [i, j], '-k')\n", " xticks([0, 1], ['Source', 'Target'])\n", " ylabel('Neuron index')\n", " xlim(-0.1, 1.1)\n", " ylim(-1, max(Ns, Nt))\n", " subplot(122)\n", " plot(S.i, S.j, 'ok')\n", " xlim(-1, Ns)\n", " ylim(-1, Nt)\n", " xlabel('Source neuron index')\n", " ylabel('Target neuron index')\n", " \n", "visualise_connectivity(S)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There are two plots here. On the left hand side, you see a vertical line of circles indicating source neurons on the left, and a vertical line indicating target neurons on the right, and a line between two neurons that have a synapse. On the right hand side is another way of visualising the same thing. Here each black dot is a synapse, with x value the source neuron index, and y value the target neuron index.\n", "\n", "Let's see how these figures change as we change the probability of a connection:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start_scope()\n", "\n", "N = 10\n", "G = NeuronGroup(N, 'v:1')\n", "\n", "for p in [0.1, 0.5, 1.0]:\n", " S = Synapses(G, G)\n", " S.connect(condition='i!=j', p=p)\n", " visualise_connectivity(S)\n", " suptitle('p = '+str(p));" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And let's see what another connectivity condition looks like. This one will only connect neighbouring neurons." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start_scope()\n", "\n", "N = 10\n", "G = NeuronGroup(N, 'v:1')\n", "\n", "S = Synapses(G, G)\n", "S.connect(condition='abs(i-j)<4 and i!=j')\n", "visualise_connectivity(S)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Try using that cell to see how other connectivity conditions look like." ] }, { "cell_type": "markdown", "metadata": { "level": 7 }, "source": [ "You can also use the generator syntax to create connections like this more efficiently. In small examples like this, it doesn't matter, but for large numbers of neurons it can be much more efficient to specify directly which neurons should be connected than to specify just a condition. Note that the following example uses `skip_if_invalid` to avoid errors at the boundaries (e.g. do not try to connect the neuron with index 1 to a neuron with index -2)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start_scope()\n", "\n", "N = 10\n", "G = NeuronGroup(N, 'v:1')\n", "\n", "S = Synapses(G, G)\n", "S.connect(j='k for k in range(i-3, i+4) if i!=k', skip_if_invalid=True)\n", "visualise_connectivity(S)" ] }, { "cell_type": "markdown", "metadata": { "level": 7 }, "source": [ "If each source neuron is connected to precisely one target neuron (which would be normally used with two separate groups of the same size, not with identical source and target groups as in this example), there is a special syntax that is extremely efficient. For example, 1-to-1 connectivity looks like this:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start_scope()\n", "\n", "N = 10\n", "G = NeuronGroup(N, 'v:1')\n", "\n", "S = Synapses(G, G)\n", "S.connect(j='i')\n", "visualise_connectivity(S)" ] }, { "cell_type": "markdown", "metadata": { "level": 7 }, "source": [ "You can also do things like specifying the value of weights with a string. Let's see an example where we assign each neuron a spatial location and have a distance-dependent connectivity function. We visualise the weight of a synapse by the size of the marker." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start_scope()\n", "\n", "N = 30\n", "neuron_spacing = 50*umetre\n", "width = N/4.0*neuron_spacing\n", "\n", "# Neuron has one variable x, its position\n", "G = NeuronGroup(N, 'x : metre')\n", "G.x = 'i*neuron_spacing'\n", "\n", "# All synapses are connected (excluding self-connections)\n", "S = Synapses(G, G, 'w : 1')\n", "S.connect(condition='i!=j')\n", "# Weight varies with distance\n", "S.w = 'exp(-(x_pre-x_post)**2/(2*width**2))'\n", "\n", "scatter(S.x_pre/um, S.x_post/um, S.w*20)\n", "xlabel('Source neuron position (um)')\n", "ylabel('Target neuron position (um)');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now try changing that function and seeing how the plot changes.\n", "\n", "## More complex synapse models: STDP\n", "\n", "Brian's synapse framework is very general and can do things like short-term plasticity (STP) or spike-timing dependent plasticity (STDP). Let's see how that works for STDP.\n", "\n", "STDP is normally defined by an equation something like this:\n", "\n", "$$\\Delta w = \\sum_{t_{pre}} \\sum_{t_{post}} W(t_{post}-t_{pre})$$\n", "\n", "That is, the change in synaptic weight w is the sum over all presynaptic spike times $t_{pre}$ and postsynaptic spike times $t_{post}$ of some function $W$ of the difference in these spike times. A commonly used function $W$ is:\n", "\n", "$$W(\\Delta t) = \\begin{cases}\n", "A_{pre} e^{-\\Delta t/\\tau_{pre}} & \\Delta t>0 \\\\\n", "A_{post} e^{\\Delta t/\\tau_{post}} & \\Delta t<0\n", "\\end{cases}$$\n", "\n", "This function looks like this:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "tau_pre = tau_post = 20*ms\n", "A_pre = 0.01\n", "A_post = -A_pre*1.05\n", "delta_t = linspace(-50, 50, 100)*ms\n", "W = where(delta_t>0, A_pre*exp(-delta_t/tau_pre), A_post*exp(delta_t/tau_post))\n", "plot(delta_t/ms, W)\n", "xlabel(r'$\\Delta t$ (ms)')\n", "ylabel('W')\n", "axhline(0, ls='-', c='k');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Simulating it directly using this equation though would be very inefficient, because we would have to sum over all pairs of spikes. That would also be physiologically unrealistic because the neuron cannot remember all its previous spike times. It turns out there is a more efficient and physiologically more plausible way to get the same effect.\n", "\n", "We define two new variables $a_{pre}$ and $a_{post}$ which are \"traces\" of pre- and post-synaptic activity, governed by the differential equations:\n", "$$\n", "\\begin{aligned}\n", "\\tau_{pre}\\frac{\\mathrm{d}}{\\mathrm{d}t} a_{pre} &= -a_{pre}\\\\\n", "\\tau_{post}\\frac{\\mathrm{d}}{\\mathrm{d}t} a_{post} &= -a_{post}\n", "\\end{aligned}\n", "$$\n", "\n", "When a presynaptic spike occurs, the presynaptic trace is updated and the weight is modified according to the rule:\n", "\n", "$$\n", "\\begin{aligned}\n", "a_{pre} &\\rightarrow a_{pre}+A_{pre}\\\\\n", "w &\\rightarrow w+a_{post}\n", "\\end{aligned}\n", "$$\n", "\n", "When a postsynaptic spike occurs:\n", "\n", "$$\n", "\\begin{aligned}\n", "a_{post} &\\rightarrow a_{post}+A_{post}\\\\\n", "w &\\rightarrow w+a_{pre}\n", "\\end{aligned}\n", "$$\n", "\n", "To see that this formulation is equivalent, you just have to check that the equations sum linearly, and consider two cases: what happens if the presynaptic spike occurs before the postsynaptic spike, and vice versa. Try drawing a picture of it.\n", "\n", "Now that we have a formulation that relies only on differential equations and spike events, we can turn that into Brian code." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start_scope()\n", "\n", "taupre = taupost = 20*ms\n", "wmax = 0.01\n", "Apre = 0.01\n", "Apost = -Apre*taupre/taupost*1.05\n", "\n", "G = NeuronGroup(1, 'v:1', threshold='v>1', reset='')\n", "\n", "S = Synapses(G, G,\n", " '''\n", " w : 1\n", " dapre/dt = -apre/taupre : 1 (event-driven)\n", " dapost/dt = -apost/taupost : 1 (event-driven)\n", " ''',\n", " on_pre='''\n", " v_post += w\n", " apre += Apre\n", " w = clip(w+apost, 0, wmax)\n", " ''',\n", " on_post='''\n", " apost += Apost\n", " w = clip(w+apre, 0, wmax)\n", " ''')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There are a few things to see there. Firstly, when defining the synapses we've given a more complicated multi-line string defining three synaptic variables (``w``, ``apre`` and ``apost``). We've also got a new bit of syntax there, ``(event-driven)`` after the definitions of ``apre`` and ``apost``. What this means is that although these two variables evolve continuously over time, Brian should only update them at the time of an event (a spike). This is because we don't need the values of ``apre`` and ``apost`` except at spike times, and it is more efficient to only update them when needed.\n", "\n", "Next we have a ``on_pre=...`` argument. The first line is ``v_post += w``: this is the line that actually applies the synaptic weight to the target neuron. The second line is ``apre += Apre`` which encodes the rule above. In the third line, we're also encoding the rule above but we've added one extra feature: we've clamped the synaptic weights between a minimum of 0 and a maximum of ``wmax`` so that the weights can't get too large or negative. The function ``clip(x, low, high)`` does this.\n", "\n", "Finally, we have a ``on_post=...`` argument. This gives the statements to calculate when a post-synaptic neuron fires. Note that we do not modify ``v`` in this case, only the synaptic variables.\n", "\n", "Now let's see how all the variables behave when a presynaptic spike arrives some time before a postsynaptic spike." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start_scope()\n", "\n", "taupre = taupost = 20*ms\n", "wmax = 0.01\n", "Apre = 0.01\n", "Apost = -Apre*taupre/taupost*1.05\n", "\n", "G = NeuronGroup(2, 'v:1', threshold='t>(1+i)*10*ms', refractory=100*ms)\n", "\n", "S = Synapses(G, G,\n", " '''\n", " w : 1\n", " dapre/dt = -apre/taupre : 1 (clock-driven)\n", " dapost/dt = -apost/taupost : 1 (clock-driven)\n", " ''',\n", " on_pre='''\n", " v_post += w\n", " apre += Apre\n", " w = clip(w+apost, 0, wmax)\n", " ''',\n", " on_post='''\n", " apost += Apost\n", " w = clip(w+apre, 0, wmax)\n", " ''', method='linear')\n", "S.connect(i=0, j=1)\n", "M = StateMonitor(S, ['w', 'apre', 'apost'], record=True)\n", "\n", "run(30*ms)\n", "\n", "figure(figsize=(4, 8))\n", "subplot(211)\n", "plot(M.t/ms, M.apre[0], label='apre')\n", "plot(M.t/ms, M.apost[0], label='apost')\n", "legend()\n", "subplot(212)\n", "plot(M.t/ms, M.w[0], label='w')\n", "legend(loc='best')\n", "xlabel('Time (ms)');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A couple of things to note here. First of all, we've used a trick to make neuron 0 fire a spike at time 10 ms, and neuron 1 at time 20 ms. Can you see how that works?\n", "\n", "Secondly, we've replaced the ``(event-driven)`` by ``(clock-driven)`` so you can see how ``apre`` and ``apost`` evolve over time. Try reverting this change and see what happens.\n", "\n", "Try changing the times of the spikes to see what happens.\n", "\n", "Finally, let's verify that this formulation is equivalent to the original one." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start_scope()\n", "\n", "taupre = taupost = 20*ms\n", "Apre = 0.01\n", "Apost = -Apre*taupre/taupost*1.05\n", "tmax = 50*ms\n", "N = 100\n", "\n", "# Presynaptic neurons G spike at times from 0 to tmax\n", "# Postsynaptic neurons G spike at times from tmax to 0\n", "# So difference in spike times will vary from -tmax to +tmax\n", "G = NeuronGroup(N, 'tspike:second', threshold='t>tspike', refractory=100*ms)\n", "H = NeuronGroup(N, 'tspike:second', threshold='t>tspike', refractory=100*ms)\n", "G.tspike = 'i*tmax/(N-1)'\n", "H.tspike = '(N-1-i)*tmax/(N-1)'\n", "\n", "S = Synapses(G, H,\n", " '''\n", " w : 1\n", " dapre/dt = -apre/taupre : 1 (event-driven)\n", " dapost/dt = -apost/taupost : 1 (event-driven)\n", " ''',\n", " on_pre='''\n", " apre += Apre\n", " w = w+apost\n", " ''',\n", " on_post='''\n", " apost += Apost\n", " w = w+apre\n", " ''')\n", "S.connect(j='i')\n", "\n", "run(tmax+1*ms)\n", "\n", "plot((H.tspike-G.tspike)/ms, S.w)\n", "xlabel(r'$\\Delta t$ (ms)')\n", "ylabel(r'$\\Delta w$')\n", "axhline(0, ls='-', c='k');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Can you see how this works?\n", "\n", "## End of tutorial" ] } ], "metadata": { "anaconda-cloud": {}, "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.10" } }, "nbformat": 4, "nbformat_minor": 1 } brian2-2.5.4/tutorials/3-intro-to-brian-simulations.ipynb000066400000000000000000000566651445201106100233550ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Introduction to Brian part 3: Simulations\n", "\n", "If you haven’t yet read parts 1 and 2 on Neurons and Synapses, go read them first.\n", "\n", "This tutorial is about managing the slightly more complicated tasks that crop up in research problems, rather than the toy examples we've been looking at so far. So we cover things like inputting sensory data, modelling experimental conditions, etc.\n", "\n", "As before we start by importing the Brian package and setting up matplotlib for IPython:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "from brian2 import *\n", "%matplotlib inline" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Multiple runs\n", "\n", "Let's start by looking at a very common task: doing multiple runs of a simulation with some parameter that changes. Let's start off with something very simple, how does the firing rate of a leaky integrate-and-fire neuron driven by Poisson spiking neurons change depending on its membrane time constant? Let's set that up." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# remember, this is here for running separate simulations in the same notebook\n", "start_scope() \n", "# Parameters\n", "num_inputs = 100\n", "input_rate = 10*Hz\n", "weight = 0.1\n", "# Range of time constants\n", "tau_range = linspace(1, 10, 30)*ms\n", "# Use this list to store output rates\n", "output_rates = []\n", "# Iterate over range of time constants\n", "for tau in tau_range:\n", " # Construct the network each time\n", " P = PoissonGroup(num_inputs, rates=input_rate)\n", " eqs = '''\n", " dv/dt = -v/tau : 1\n", " '''\n", " G = NeuronGroup(1, eqs, threshold='v>1', reset='v=0', method='exact')\n", " S = Synapses(P, G, on_pre='v += weight')\n", " S.connect()\n", " M = SpikeMonitor(G)\n", " # Run it and store the output firing rate in the list\n", " run(1*second)\n", " output_rates.append(M.num_spikes/second)\n", "# And plot it\n", "plot(tau_range/ms, output_rates)\n", "xlabel(r'$\\tau$ (ms)')\n", "ylabel('Firing rate (sp/s)');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now if you're running the notebook, you'll see that this was a little slow to run. The reason is that for each loop, you're recreating the objects from scratch. We can improve that by setting up the network just once. We store a copy of the state of the network before the loop, and restore it at the beginning of each iteration." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start_scope() \n", "num_inputs = 100\n", "input_rate = 10*Hz\n", "weight = 0.1\n", "tau_range = linspace(1, 10, 30)*ms\n", "output_rates = []\n", "# Construct the network just once\n", "P = PoissonGroup(num_inputs, rates=input_rate)\n", "eqs = '''\n", "dv/dt = -v/tau : 1\n", "'''\n", "G = NeuronGroup(1, eqs, threshold='v>1', reset='v=0', method='exact')\n", "S = Synapses(P, G, on_pre='v += weight')\n", "S.connect()\n", "M = SpikeMonitor(G)\n", "# Store the current state of the network\n", "store()\n", "for tau in tau_range:\n", " # Restore the original state of the network\n", " restore()\n", " # Run it with the new value of tau\n", " run(1*second)\n", " output_rates.append(M.num_spikes/second)\n", "plot(tau_range/ms, output_rates)\n", "xlabel(r'$\\tau$ (ms)')\n", "ylabel('Firing rate (sp/s)');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "That's a very simple example of using store and restore, but you can use it in much more complicated situations. For example, you might want to run a long training run, and then run multiple test runs afterwards. Simply put a store after the long training run, and a restore before each testing run.\n", "\n", "You can also see that the output curve is very noisy and doesn't increase monotonically like we'd expect. The noise is coming from the fact that we run the Poisson group afresh each time. If we only wanted to see the effect of the time constant, we could make sure that the spikes were the same each time (although note that really, you ought to do multiple runs and take an average). We do this by running just the Poisson group once, recording its spikes, and then creating a new `SpikeGeneratorGroup` that will output those recorded spikes each time." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start_scope() \n", "num_inputs = 100\n", "input_rate = 10*Hz\n", "weight = 0.1\n", "tau_range = linspace(1, 10, 30)*ms\n", "output_rates = []\n", "# Construct the Poisson spikes just once\n", "P = PoissonGroup(num_inputs, rates=input_rate)\n", "MP = SpikeMonitor(P)\n", "# We use a Network object because later on we don't\n", "# want to include these objects\n", "net = Network(P, MP)\n", "net.run(1*second)\n", "# And keep a copy of those spikes\n", "spikes_i = MP.i\n", "spikes_t = MP.t\n", "# Now construct the network that we run each time\n", "# SpikeGeneratorGroup gets the spikes that we created before\n", "SGG = SpikeGeneratorGroup(num_inputs, spikes_i, spikes_t)\n", "eqs = '''\n", "dv/dt = -v/tau : 1\n", "'''\n", "G = NeuronGroup(1, eqs, threshold='v>1', reset='v=0', method='exact')\n", "S = Synapses(SGG, G, on_pre='v += weight')\n", "S.connect()\n", "M = SpikeMonitor(G)\n", "# Store the current state of the network\n", "net = Network(SGG, G, S, M)\n", "net.store()\n", "for tau in tau_range:\n", " # Restore the original state of the network\n", " net.restore()\n", " # Run it with the new value of tau\n", " net.run(1*second)\n", " output_rates.append(M.num_spikes/second)\n", "plot(tau_range/ms, output_rates)\n", "xlabel(r'$\\tau$ (ms)')\n", "ylabel('Firing rate (sp/s)');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can see that now there is much less noise and it increases monotonically because the input spikes are the same each time, meaning we're seeing the effect of the time constant, not the random spikes.\n", "\n", "Note that in the code above, we created `Network` objects. The reason is that in the loop, if we just called `run` it would try to simulate all the objects, including the Poisson neurons ``P``, and we only want to run that once. We use `Network` to specify explicitly which objects we want to include.\n", "\n", "The techniques we've looked at so far are the conceptually most simple way to do multiple runs, but not always the most efficient. Since there's only a single output neuron in the model above, we can simply duplicate that output neuron and make the time constant a parameter of the group." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start_scope() \n", "num_inputs = 100\n", "input_rate = 10*Hz\n", "weight = 0.1\n", "tau_range = linspace(1, 10, 30)*ms\n", "num_tau = len(tau_range)\n", "P = PoissonGroup(num_inputs, rates=input_rate)\n", "# We make tau a parameter of the group\n", "eqs = '''\n", "dv/dt = -v/tau : 1\n", "tau : second\n", "'''\n", "# And we have num_tau output neurons, each with a different tau\n", "G = NeuronGroup(num_tau, eqs, threshold='v>1', reset='v=0', method='exact')\n", "G.tau = tau_range\n", "S = Synapses(P, G, on_pre='v += weight')\n", "S.connect()\n", "M = SpikeMonitor(G)\n", "# Now we can just run once with no loop\n", "run(1*second)\n", "output_rates = M.count/second # firing rate is count/duration\n", "plot(tau_range/ms, output_rates)\n", "xlabel(r'$\\tau$ (ms)')\n", "ylabel('Firing rate (sp/s)');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can see that this is much faster again! It's a little bit more complicated conceptually, and it's not always possible to do this trick, but it can be much more efficient if it's possible.\n", "\n", "Let's finish with this example by having a quick look at how the mean and standard deviation of the interspike intervals depends on the time constant." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "trains = M.spike_trains()\n", "isi_mu = full(num_tau, nan)*second\n", "isi_std = full(num_tau, nan)*second\n", "for idx in range(num_tau):\n", " train = diff(trains[idx])\n", " if len(train)>1:\n", " isi_mu[idx] = mean(train)\n", " isi_std[idx] = std(train)\n", "errorbar(tau_range/ms, isi_mu/ms, yerr=isi_std/ms)\n", "xlabel(r'$\\tau$ (ms)')\n", "ylabel('Interspike interval (ms)');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice that we used the ``spike_trains()`` method of `SpikeMonitor`. This is a dictionary with keys being the indices of the neurons and values being the array of spike times for that neuron.\n", "\n", "## Changing things during a run\n", "\n", "Imagine an experiment where you inject current into a neuron, and change the amplitude randomly every 10 ms. Let's see if we can model that using a Hodgkin-Huxley type neuron." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start_scope()\n", "# Parameters\n", "area = 20000*umetre**2\n", "Cm = 1*ufarad*cm**-2 * area\n", "gl = 5e-5*siemens*cm**-2 * area\n", "El = -65*mV\n", "EK = -90*mV\n", "ENa = 50*mV\n", "g_na = 100*msiemens*cm**-2 * area\n", "g_kd = 30*msiemens*cm**-2 * area\n", "VT = -63*mV\n", "# The model\n", "eqs_HH = '''\n", "dv/dt = (gl*(El-v) - g_na*(m*m*m)*h*(v-ENa) - g_kd*(n*n*n*n)*(v-EK) + I)/Cm : volt\n", "dm/dt = 0.32*(mV**-1)*(13.*mV-v+VT)/\n", " (exp((13.*mV-v+VT)/(4.*mV))-1.)/ms*(1-m)-0.28*(mV**-1)*(v-VT-40.*mV)/\n", " (exp((v-VT-40.*mV)/(5.*mV))-1.)/ms*m : 1\n", "dn/dt = 0.032*(mV**-1)*(15.*mV-v+VT)/\n", " (exp((15.*mV-v+VT)/(5.*mV))-1.)/ms*(1.-n)-.5*exp((10.*mV-v+VT)/(40.*mV))/ms*n : 1\n", "dh/dt = 0.128*exp((17.*mV-v+VT)/(18.*mV))/ms*(1.-h)-4./(1+exp((40.*mV-v+VT)/(5.*mV)))/ms*h : 1\n", "I : amp\n", "'''\n", "group = NeuronGroup(1, eqs_HH,\n", " threshold='v > -40*mV',\n", " refractory='v > -40*mV',\n", " method='exponential_euler')\n", "group.v = El\n", "statemon = StateMonitor(group, 'v', record=True)\n", "spikemon = SpikeMonitor(group, variables='v')\n", "figure(figsize=(9, 4))\n", "for l in range(5):\n", " group.I = rand()*50*nA\n", " run(10*ms)\n", " axvline(l*10, ls='--', c='k')\n", "axhline(El/mV, ls='-', c='lightgray', lw=3)\n", "plot(statemon.t/ms, statemon.v[0]/mV, '-b')\n", "plot(spikemon.t/ms, spikemon.v/mV, 'ob')\n", "xlabel('Time (ms)')\n", "ylabel('v (mV)');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the code above, we used a loop over multiple runs to achieve this. That's fine, but it's not the most efficient way to do it because each time we call ``run`` we have to do a lot of initialisation work that slows everything down. It also won't work as well with the more efficient standalone mode of Brian. Here's another way." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start_scope()\n", "group = NeuronGroup(1, eqs_HH,\n", " threshold='v > -40*mV',\n", " refractory='v > -40*mV',\n", " method='exponential_euler')\n", "group.v = El\n", "statemon = StateMonitor(group, 'v', record=True)\n", "spikemon = SpikeMonitor(group, variables='v')\n", "# we replace the loop with a run_regularly\n", "group.run_regularly('I = rand()*50*nA', dt=10*ms)\n", "run(50*ms)\n", "figure(figsize=(9, 4))\n", "# we keep the loop just to draw the vertical lines\n", "for l in range(5):\n", " axvline(l*10, ls='--', c='k')\n", "axhline(El/mV, ls='-', c='lightgray', lw=3)\n", "plot(statemon.t/ms, statemon.v[0]/mV, '-b')\n", "plot(spikemon.t/ms, spikemon.v/mV, 'ob')\n", "xlabel('Time (ms)')\n", "ylabel('v (mV)');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We've replaced the loop that had multiple ``run`` calls with a ``run_regularly``. This makes the specified block of code run every ``dt=10*ms``. The ``run_regularly`` lets you run code specific to a single `NeuronGroup`, but sometimes you might need more flexibility. For this, you can use `network_operation` which lets you run arbitrary Python code (but won't work with the standalone mode)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start_scope()\n", "group = NeuronGroup(1, eqs_HH,\n", " threshold='v > -40*mV',\n", " refractory='v > -40*mV',\n", " method='exponential_euler')\n", "group.v = El\n", "statemon = StateMonitor(group, 'v', record=True)\n", "spikemon = SpikeMonitor(group, variables='v')\n", "# we replace the loop with a network_operation\n", "@network_operation(dt=10*ms)\n", "def change_I():\n", " group.I = rand()*50*nA\n", "run(50*ms)\n", "figure(figsize=(9, 4))\n", "for l in range(5):\n", " axvline(l*10, ls='--', c='k')\n", "axhline(El/mV, ls='-', c='lightgray', lw=3)\n", "plot(statemon.t/ms, statemon.v[0]/mV, '-b')\n", "plot(spikemon.t/ms, spikemon.v/mV, 'ob')\n", "xlabel('Time (ms)')\n", "ylabel('v (mV)');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now let's extend this example to run on multiple neurons, each with a different capacitance to see how that affects the behaviour of the cell." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start_scope()\n", "N = 3\n", "eqs_HH_2 = '''\n", "dv/dt = (gl*(El-v) - g_na*(m*m*m)*h*(v-ENa) - g_kd*(n*n*n*n)*(v-EK) + I)/C : volt\n", "dm/dt = 0.32*(mV**-1)*(13.*mV-v+VT)/\n", " (exp((13.*mV-v+VT)/(4.*mV))-1.)/ms*(1-m)-0.28*(mV**-1)*(v-VT-40.*mV)/\n", " (exp((v-VT-40.*mV)/(5.*mV))-1.)/ms*m : 1\n", "dn/dt = 0.032*(mV**-1)*(15.*mV-v+VT)/\n", " (exp((15.*mV-v+VT)/(5.*mV))-1.)/ms*(1.-n)-.5*exp((10.*mV-v+VT)/(40.*mV))/ms*n : 1\n", "dh/dt = 0.128*exp((17.*mV-v+VT)/(18.*mV))/ms*(1.-h)-4./(1+exp((40.*mV-v+VT)/(5.*mV)))/ms*h : 1\n", "I : amp\n", "C : farad\n", "'''\n", "group = NeuronGroup(N, eqs_HH_2,\n", " threshold='v > -40*mV',\n", " refractory='v > -40*mV',\n", " method='exponential_euler')\n", "group.v = El\n", "# initialise with some different capacitances\n", "group.C = array([0.8, 1, 1.2])*ufarad*cm**-2*area\n", "statemon = StateMonitor(group, variables=True, record=True)\n", "# we go back to run_regularly\n", "group.run_regularly('I = rand()*50*nA', dt=10*ms)\n", "run(50*ms)\n", "figure(figsize=(9, 4))\n", "for l in range(5):\n", " axvline(l*10, ls='--', c='k')\n", "axhline(El/mV, ls='-', c='lightgray', lw=3)\n", "plot(statemon.t/ms, statemon.v.T/mV, '-')\n", "xlabel('Time (ms)')\n", "ylabel('v (mV)');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So that runs, but something looks wrong! The injected currents look like they're different for all the different neurons! Let's check:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plot(statemon.t/ms, statemon.I.T/nA, '-')\n", "xlabel('Time (ms)')\n", "ylabel('I (nA)');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Sure enough, it's different each time. But why? We wrote ``group.run_regularly('I = rand()*50*nA', dt=10*ms)`` which seems like it should give the same value of I for each neuron. But, like threshold and reset statements, ``run_regularly`` code is interpreted as being run separately for each neuron, and because I is a parameter, it can be different for each neuron. We can fix this by making I into a *shared* variable, meaning it has the same value for each neuron." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start_scope()\n", "N = 3\n", "eqs_HH_3 = '''\n", "dv/dt = (gl*(El-v) - g_na*(m*m*m)*h*(v-ENa) - g_kd*(n*n*n*n)*(v-EK) + I)/C : volt\n", "dm/dt = 0.32*(mV**-1)*(13.*mV-v+VT)/\n", " (exp((13.*mV-v+VT)/(4.*mV))-1.)/ms*(1-m)-0.28*(mV**-1)*(v-VT-40.*mV)/\n", " (exp((v-VT-40.*mV)/(5.*mV))-1.)/ms*m : 1\n", "dn/dt = 0.032*(mV**-1)*(15.*mV-v+VT)/\n", " (exp((15.*mV-v+VT)/(5.*mV))-1.)/ms*(1.-n)-.5*exp((10.*mV-v+VT)/(40.*mV))/ms*n : 1\n", "dh/dt = 0.128*exp((17.*mV-v+VT)/(18.*mV))/ms*(1.-h)-4./(1+exp((40.*mV-v+VT)/(5.*mV)))/ms*h : 1\n", "I : amp (shared) # everything is the same except we've added this shared\n", "C : farad\n", "'''\n", "group = NeuronGroup(N, eqs_HH_3,\n", " threshold='v > -40*mV',\n", " refractory='v > -40*mV',\n", " method='exponential_euler')\n", "group.v = El\n", "group.C = array([0.8, 1, 1.2])*ufarad*cm**-2*area\n", "statemon = StateMonitor(group, 'v', record=True)\n", "group.run_regularly('I = rand()*50*nA', dt=10*ms)\n", "run(50*ms)\n", "figure(figsize=(9, 4))\n", "for l in range(5):\n", " axvline(l*10, ls='--', c='k')\n", "axhline(El/mV, ls='-', c='lightgray', lw=3)\n", "plot(statemon.t/ms, statemon.v.T/mV, '-')\n", "xlabel('Time (ms)')\n", "ylabel('v (mV)');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ahh, that's more like it!\n", "\n", "## Adding input\n", "\n", "Now let's think about a neuron being driven by a sinusoidal input. Let's go back to a leaky integrate-and-fire to simplify the equations a bit." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start_scope()\n", "A = 2.5\n", "f = 10*Hz\n", "tau = 5*ms\n", "eqs = '''\n", "dv/dt = (I-v)/tau : 1\n", "I = A*sin(2*pi*f*t) : 1\n", "'''\n", "G = NeuronGroup(1, eqs, threshold='v>1', reset='v=0', method='euler')\n", "M = StateMonitor(G, variables=True, record=True)\n", "run(200*ms)\n", "plot(M.t/ms, M.v[0], label='v')\n", "plot(M.t/ms, M.I[0], label='I')\n", "xlabel('Time (ms)')\n", "ylabel('v')\n", "legend(loc='best');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So far, so good and the sort of thing we saw in the first tutorial. Now, what if that input current were something we had recorded and saved in a file? In that case, we can use `TimedArray`. Let's start by reproducing the picture above but using `TimedArray`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start_scope()\n", "A = 2.5\n", "f = 10*Hz\n", "tau = 5*ms\n", "# Create a TimedArray and set the equations to use it\n", "t_recorded = arange(int(200*ms/defaultclock.dt))*defaultclock.dt\n", "I_recorded = TimedArray(A*sin(2*pi*f*t_recorded), dt=defaultclock.dt)\n", "eqs = '''\n", "dv/dt = (I-v)/tau : 1\n", "I = I_recorded(t) : 1\n", "'''\n", "G = NeuronGroup(1, eqs, threshold='v>1', reset='v=0', method='exact')\n", "M = StateMonitor(G, variables=True, record=True)\n", "run(200*ms)\n", "plot(M.t/ms, M.v[0], label='v')\n", "plot(M.t/ms, M.I[0], label='I')\n", "xlabel('Time (ms)')\n", "ylabel('v')\n", "legend(loc='best');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that for the example where we put the ``sin`` function directly in the equations, we had to use the ``method='euler'`` argument because the exact integrator wouldn't work here (try it!). However, ``TimedArray`` is considered to be constant over its time step and so the linear integrator can be used. This means you won't get the same behaviour from these two methods for two reasons. Firstly, the numerical integration methods ``exact`` and ``euler`` give slightly different results. Secondly, ``sin`` is not constant over a timestep whereas ``TimedArray`` is.\n", "\n", "Now just to show that ``TimedArray`` works for arbitrary currents, let's make a weird \"recorded\" current and run it on that." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start_scope()\n", "A = 2.5\n", "f = 10*Hz\n", "tau = 5*ms\n", "# Let's create an array that couldn't be\n", "# reproduced with a formula\n", "num_samples = int(200*ms/defaultclock.dt)\n", "I_arr = zeros(num_samples)\n", "for _ in range(100):\n", " a = randint(num_samples)\n", " I_arr[a:a+100] = rand()\n", "I_recorded = TimedArray(A*I_arr, dt=defaultclock.dt)\n", "eqs = '''\n", "dv/dt = (I-v)/tau : 1\n", "I = I_recorded(t) : 1\n", "'''\n", "G = NeuronGroup(1, eqs, threshold='v>1', reset='v=0', method='exact')\n", "M = StateMonitor(G, variables=True, record=True)\n", "run(200*ms)\n", "plot(M.t/ms, M.v[0], label='v')\n", "plot(M.t/ms, M.I[0], label='I')\n", "xlabel('Time (ms)')\n", "ylabel('v')\n", "legend(loc='best');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally, let's finish on an example that actually reads in some data from a file. See if you can work out how this example works." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "start_scope()\n", "from matplotlib.image import imread\n", "img = (1-imread('brian.png'))[::-1, :, 0].T\n", "num_samples, N = img.shape\n", "ta = TimedArray(img, dt=1*ms) # 228\n", "A = 1.5\n", "tau = 2*ms\n", "eqs = '''\n", "dv/dt = (A*ta(t, i)-v)/tau+0.8*xi*tau**-0.5 : 1\n", "'''\n", "G = NeuronGroup(N, eqs, threshold='v>1', reset='v=0', method='euler')\n", "M = SpikeMonitor(G)\n", "run(num_samples*ms)\n", "plot(M.t/ms, M.i, '.k', ms=3)\n", "xlim(0, num_samples)\n", "ylim(0, N)\n", "xlabel('Time (ms)')\n", "ylabel('Neuron index');" ] } ], "metadata": { "hide_input": false, "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.0" } }, "nbformat": 4, "nbformat_minor": 2 } brian2-2.5.4/tutorials/brian.png000066400000000000000000000031341445201106100165210ustar00rootroot00000000000000PNG  IHDRD)4sRGBgAMA a pHYsodIDATx^q0 .z\q19ɑ?`B~x.sUPPPPPPPPPPPðW3j~Ky4qhSP ]gЖ.a~m_CjbPVkEY?l F2CL҅ (f!d[+E2fFZѨw2;w.G#bd% %+/+~i!`+NCd%JwҊhDk` YմGA,4f4N'75EV-/hR+ڒoOÏXLVvg# "+2DY{eeqKB}lU/"AVi$뷛  UZHVT[t[AwK kPш/YMyA%eۊdJ42ZUM8-denFLDmeuN h7NdF +v{(:!eskF#SYxp j7Rh]?|z$O6 gǂc{`YW}mo-ʺlWPA^} Ə_Tcxo4 ȯm܍eqZnY'PiؿF^8Df)@Cy>h3* Y{ m9 djo R[ 08$T J0Ȋo*TKXxű0JSX%8|6?oC5ekΤۚ"+ j]`ZҨXJ@(AsT&jksYw`;`lKT[FxX tm:YQLVL0-k[V^pdNYstKVI3;n&&vtQM~[AwF]AYmWyJOF>sݺvr %)+C$$oH|fF%?VIZ9Hw6;= 4JЗ_zc[=ڣ87 O5 cL&GBA2rDSe 8)kX!dU̐cw4C8fhl +r\&&k(AXhx?D"8TMx.DL5qe$)zi$g9D:ktb$Pdjp~8-=2 Y=4CqY;p}pc Y}u!.kQoIENDB`brian2-2.5.4/versions.md000066400000000000000000000040141445201106100150620ustar00rootroot00000000000000Package versions ================ This file contains a list of other files where versions of packages are specified so that they can easily be found when upgrading a dependency version, keeping them all in sync. In the future it would be advantageous to implement an automated way of keep versions synchronised across files e.g. https://github.com/pre-commit/pre-commit/issues/945#issuecomment-527603460 or preferably parsing `.pre-commit-config.yaml` and using it to `pip install` requirements (see discussion here: https://github.com/brian-team/brian2/pull/1449#issuecomment-1372476018). Until then, the files are listed below for manual checking and updating. * [`README.rst`](https://github.com/brian-team/brian2/blob/master/README.rst) * [`setup.py`](https://github.com/brian-team/brian2/blob/master/setup.py) * [`rtd-requirements.txt`](https://github.com/brian-team/brian2/blob/master/rtd-requirements.txt) * [`pyproject.toml`](https://github.com/brian-team/brian2/blob/master/pyproject.toml) * [`.pre-commit-config.yaml`](https://github.com/brian-team/brian2/blob/master/.pre-commit-config.yaml) * [`docs_sphinx/conf.py`](https://github.com/brian-team/brian2/blob/master/docs_sphinx/conf.py) * [`conda-forge/brian2-feedstock/recipe/meta.yaml`](https://github.com/conda-forge/brian2-feedstock/blob/main/recipe/meta.yaml) * [`.github/workflows/publish_to_pypi.yml`](https://github.com/brian-team/brian2/blob/master/.github/workflows/publish_to_pypi.yml) * [`.github/workflows/test_latest.yml`](https://github.com/brian-team/brian2/blob/master/.github/workflows/test_latest.yml) * [`.github/workflows/testsuite.yml`](https://github.com/brian-team/brian2/blob/master/.github/workflows/testsuite.yml) * [`.devcontainer/dev-requirements.txt`](https://github.com/brian-team/brian2/blob/master/.devcontainer/dev-requirements.txt) * [`.devcontainer/devcontainer.json`](https://github.com/brian-team/brian2/blob/master/.devcontainer/devcontainer.json) * [`.devcontainer/Dockerfile`](https://github.com/brian-team/brian2/blob/master/.devcontainer/Dockerfile)