diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 000000000..521660c1c --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,65 @@ +defaults: &defaults + working_directory: ~/markovmodel/PyEMMA + docker: + - image: continuumio/miniconda3 + +inst_conda_bld: &inst_conda_bld + - run: conda config --add channels conda-forge + - run: conda config --set always_yes true + - run: conda config --set quiet true + - run: conda install conda-build + +version: 2 + +jobs: + build: + <<: *defaults + parallelism: 1 + steps: + - checkout + - run: git fetch --unshallow || true + - run: apt-get install -y cpp gcc + - run: apt-get install -y libx11-6 python-dev git build-essential + - run: apt-get install -y autoconf automake gcc g++ make gfortran + - run: apt-get install -y python-tables + - run: apt-get install -y libhdf5-serial-dev + + - run: conda config --add channels conda-forge + - run: conda config --set always_yes true + - run: conda config --set quiet true + - run: conda install conda-build + - run: pip install pip --upgrade; + - run: conda install numpy; + - run: conda install numba; + - run: conda install dask; + - run: pip install tables + - run: pip install scipy==1.5.4 + - run: pip install coverage + - run: pip install cython + - run: pip install asciiplotlib; + - run: pip install ipfx + - run: pip install streamlit + - run: pip install sklearn + - run: pip install seaborn + - run: pip install frozendict + - run: pip install igor + #- run: pip install plotly + - run: pip install allensdk==0.16.3 + - run: pip install --upgrade colorama + - run: pip install -e . + - run: rm -rf /opt/conda/lib/python3.8/site-packages/sciunit + - run: git clone -b neuronunit https://github.com/russelljjarvis/jit_hub.git + - run: cd jit_hub; pip install -e .; cd ..; + - run: git clone -b neuronunit_reduced_cells https://github.com/russelljjarvis/BluePyOpt.git + - run: cd BluePyOpt; pip install -e . + - run: git clone -b dev https://github.com/russelljjarvis/sciunit.git + + - run: cd sciunit; pip install -e .; cd ..; + - run: pip install git+https://github.com/russelljjarvis/eFEL + - run: pip install coveralls + - run: sh build.sh + - run: sh test.sh; + #- run: cd neuronunit/unit_test; coveralls -m unittest rheobase_model_test.py; cd -; + #- run: cd neuronunit/unit_test; coverage report + #- store_artifacts: + # path: htmlcov diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..9fb85ec49 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,14 @@ +# Set the default behavior, in case people don't have core.autocrlf set. +* text=auto + +# Explicitly declare text files you want to always be normalized and converted +# to native line endings on checkout. +*.c text +*.h text + +# Declare files that will always have CRLF line endings on checkout. +*.sln text eol=crlf + +# Denote all files that are truly binary and should not be modified. +*.png binary +*.jpg binary \ No newline at end of file diff --git a/.gitignore b/.gitignore index 9cd35dbdb..3a8e44967 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,10 @@ +*.vscode *.py[co] +*.pkl +*.p +*do-not-add* +neuronunit/examples/model_zoo/*.nml +*.hoc # Packages *.egg @@ -66,3 +72,6 @@ neuronunit/models/NeuroML2/*.py neuronunit/unit_test/bbp.py neuronunit/unit_test/get_tau.py scratch +GeneratedFiles +docs/tmp/ +tmp/ \ No newline at end of file diff --git a/.travis.yml b/.travis.yml old mode 100644 new mode 100755 index d598606d8..a38f68831 --- a/.travis.yml +++ b/.travis.yml @@ -3,32 +3,43 @@ language: python python: # We don't actually use the Travis Python, but this keeps it organized. - - "2.7" - "3.5" - "3.6" + - "3.7" + - "3.8" install: - sudo apt-get update # We do this conditionally because it saves us some downloading if the # version is the same. - - if [[ "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then - wget https://repo.continuum.io/miniconda/Miniconda2-latest-Linux-x86_64.sh -O miniconda.sh; - else - wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh; - fi + - wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh - bash miniconda.sh -b -p $HOME/miniconda - export PATH="$HOME/miniconda/bin:$PATH" - hash -r - conda config --set always_yes yes --set changeps1 no - - conda update -q conda + - conda update -q --all # Useful for debugging any issues with conda - conda info -a - - pip install --no-cache-dir -r requirements.txt . + - pip install -U pip + - pip install . + - pip install sklearn + - pip install seaborn - pip install coveralls + - pip install pylmeasure # required by morphology tests + - sh build.sh + ###################################################### -script: +script: - export NC_HOME='.' # NeuroConstruct isn't used but tests need this - # variable set to pass. - - sh test.sh + # variable set to pass. +<<<<<<< HEAD + - cd neuronunit/unit_test; python -m unittest scores_unit_test.py; cd -; + - cd neuronunit/unit_test; python -m unittest rheobase_dtc_test.py; cd -; + #- sh test.sh +======= + #- cd neuronunit/unit_test; python -m unittest scores_unit_test.py; cd -; + #- cd neuronunit/unit_test; python -m unittest rheobase_model_test.py; cd -; + - sh test.sh +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f after_success: - coveralls diff --git a/Dockerfile b/Dockerfile index 7734ccaf9..a4b2fa247 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,18 +1,54 @@ -FROM russelljarvis/neuronunit +FROM scidash/neuronunit-optimization USER jovyan -RUN sudo /opt/conda/bin/pip install psutil +RUN pip install psutil ENV QT_QPA_PLATFORM offscreen -RUN sudo rm -rf /opt/conda/lib/python3.5/site-packages/neuronunit-0.1.8.8-py3.5.egg/neuronunit -RUN sudo rm -rf $HOME/neuronunit -COPY . $HOME/neuronunit - RUN pip install dask RUN pip install distributed -RUN sudo pip uninstall -y sciunit -RUN sudo pip install git+https://github.com/scidash/sciunit@dev -#COPY BluePyOpt ~/HOME/BluePyOpt -#RUN pip install -e $HOME/BluePyOpt -WORKDIR $HOME +RUN sudo apt-get update +RUN pip install ioloop +RUN sudo chown -R jovyan /home/jovyan +RUN pip install git+https://github.com/OpenSourceBrain/OpenCortex +RUN git clone https://github.com/vrhaynes/AllenInstituteNeuroML.git +RUN pip install PyLEMS + +# RUN sudo /opt/conda/bin/pip install git+https://github.com/python-quantities/python-quantities +# RUN sudo /opt/conda/bin/pip install git+https://github.com/scidash/sciunit@dev +RUN sudo chown -R jovyan /home/jovyan +WORKDIR /home/jovyan/neuronunit/neuronunit/unit_test +RUN sudo chown -R jovyan /home/jovyan +RUN git clone https://github.com/vrhaynes/AllenInstituteNeuroML.git +RUN pip install git+https://github.com/OpenSourceBrain/OpenCortex +# RUN sudo apt-get -y install ipython ipython-notebook +# RUN sudo -H /opt/conda/bin/pip install jupyter +# ADD neuronunit/unit_test/post_install.sh . +RUN git clone https://github.com/OpenSourceBrain/osb-model-validation.git +WORKDIR osb-model-validation +RUN python setup.py install +RUN pip --no-cache-dir install \ + ipykernel \ + jupyter \ + matplotlib \ + numpy \ + scipy \ + sklearn \ + pandas \ + Pillow +RUN sudo /opt/conda/bin/python3 -m ipykernel.kernelspec + +# Then install the Jupyter Notebook using: +RUN pip install jupyter + +RUN sudo /opt/conda/bin/pip uninstall -y tornado +RUN pip install tornado==4.5.3 +RUN /opt/conda/bin/python3 -m pip install ipykernel +RUN /opt/conda/bin/python3 -m ipykernel install --user +RUN pip install deap +WORKDIR $HOME +# ADD . neuronunit +# WORKDIR neuronunit +# RUN sudo /opt/conda/bin/pip install -e . +#RUN bash post_install.sh +ENTRYPOINT /bin/bash diff --git a/README.md b/README.md index 2159fc003..19f85a870 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,14 @@ +### Circle CI russelljjarvis/optimization build: +[![Build Status](https://circleci.com/gh/russelljjarvis/neuronunit/tree/optimization.svg?style=svg)](https://app.circleci.com/pipelines/github/russelljjarvis/neuronunit/) +### Travis CI scidash/optimization build: +[![Travis](https://travis-ci.org/scidash/neuronunit.svg?branch=optimization)](https://travis-ci.org/scidash/neuronunit?branch=optimization) | Master | Dev | | ------------- | ------------- | | [![Travis](https://travis-ci.org/scidash/neuronunit.svg?branch=master)](https://travis-ci.org/scidash/neuronunit) | [![Travis](https://travis-ci.org/scidash/neuronunit.svg?branch=dev)](https://travis-ci.org/scidash/neuronunit) | | [![RTFD](https://readthedocs.org/projects/neuronunit/badge/?version=master)](http://neuronunit.readthedocs.io/en/latest/?badge=master) | [![RTFD](https://readthedocs.org/projects/neuronunit/badge/?version=dev)](http://neuronunit.readthedocs.io/en/latest/?badge=dev) | | [![Coveralls](https://coveralls.io/repos/github/scidash/neuronunit/badge.svg?branch=master)](https://coveralls.io/github/scidash/neuronunit?branch=master) | [![Coveralls](https://coveralls.io/repos/github/scidash/neuronunit/badge.svg?branch=dev)](https://coveralls.io/github/scidash/neuronunit?branch=dev) | -| [![Requirements](https://requires.io/github/scidash/neuronunit/requirements.svg?branch=master)](https://requires.io/github/scidash/neuronunit/requirements/?branch=master) | [![Requirements](https://requires.io/github/scidash/neuronunit/requirements.svg?branch=dev)](https://requires.io/github/scidash/neuronunit/requirements/?branch=dev) | +| [![Requirements](https://requires.io/github/scidash/neuronunit/requirements.svg?branch=master)](https://requires.io/github/scidash/neuronunit/requirements/?branch=master) | [![Requirements](https://requires.io/github/scidash/neuronunit/requirements.svg?branch=dev)](https://requires.io/github/scidash/neuronunit/requirements/?branch=dev) | +| [![Binder](https://mybinder.org/badge.svg)](https://mybinder.org/v2/gh/scidash/neuronunit/master) | @@ -43,13 +48,13 @@ model = ChannelModel(channel_file_path, channel_index=0, name=channel_model_name # Get the experiment data from ChannelWorm and instantiate the test doi = '10.1083/jcb.200203055' fig = '2B' -sample_data = GraphData.objects.get(graph__experiment__reference__doi=doi, +sample_data = GraphData.objects.get(graph__experiment__reference__doi=doi, graph__figure_ref_address=fig) voltage, current_per_farad = sample_data.asunitedarray() -patch_capacitance = pq.Quantity(1e-13,'F') # Assume recorded patch had this capacitance; +patch_capacitance = pq.Quantity(1e-13,'F') # Assume recorded patch had this capacitance; # an arbitrary scaling factor. current = current_per_farad * patch_capacitance -observation = {'v':voltage, +observation = {'v':voltage, 'i':current} test = IVCurvePeakTest(observation) @@ -62,7 +67,7 @@ score.plot(rd['v'],rd['i_pred'],same_fig=True,color='r',label='Predicted (model) ![png](https://raw.githubusercontent.com/scidash/assets/master/figures/SCU_IVCurve_Model_6_0.png) ``` -score.summarize() +score.summarize() """ OUTPUT: Model EGL-19.channel (ChannelModel) achieved score Fail on test 'IV Curve Test (IVCurvePeakTest)'. === """ @@ -89,27 +94,27 @@ neurolex_id = 'nifext_128' # Cerebellar Granule Cell # Specify reference data for a test of resting potential for a granule cell. reference_data = neuroelectro.NeuroElectroSummary( neuron = {'nlex_id':neurolex_id}, # Neuron type. - ephysprop = {'name':'Resting Membrane Potential'}) # Electrophysiological property name. -# Get and verify summary data for the combination above from neuroelectro.org. + ephysprop = {'name':'Resting Membrane Potential'}) # Electrophysiological property name. +# Get and verify summary data for the combination above from neuroelectro.org. reference_data.get_values() vm_test = tests.RestingPotentialTest( observation = {'mean':reference_data.mean, - 'std':reference_data.std}, + 'sd':reference_data.std}, name = 'Resting Potential') # Specify reference data for a test of action potential width. reference_data = neuroelectro.NeuroElectroSummary( neuron = {'nlex_id':neurolex_id}, # Neuron type. - ephysprop = {'name':'Spike Half-Width'}) # Electrophysiological property name. -# Get and verify summary data for the combination above from neuroelectro.org. + ephysprop = {'name':'Spike Half-Width'}) # Electrophysiological property name. +# Get and verify summary data for the combination above from neuroelectro.org. reference_data.get_values() spikewidth_test = tests.InjectedCurrentAPWidthTest( observation = {'mean':reference_data.mean, - 'std':reference_data.std}, + 'sd':reference_data.std}, name = 'Spike Width', params={'injected_square_current':{'amplitude':5.3*pq.pA, 'delay':50.0*pq.ms, - 'duration':500.0*pq.ms}}) + 'duration':500.0*pq.ms}}) # 5.3 pA of injected current in a 500 ms square pulse. # Create a test suite from these two tests. @@ -121,7 +126,7 @@ for model_name in model_names # Iterate through a list of models downloaded from model = nc_models.OSBModel(*model_info) models.append(model) # Add to the list of models to be tested. -score_matrix = suite.judge(models,stop_on_error=True) +score_matrix = suite.judge(models,stop_on_error=True) score_matrix.view() ``` ### Score Matrix for Test Suite 'Neuron Tests' @@ -152,7 +157,7 @@ NeuronUnit is based on [SciUnit](http://github.com/scidash/sciunit), a disciplin 2. Check that the model has the capabilities required to take the test. 1. Make the model take the test. 2. Generate a score from that test run. -1. Bind the score to the specific model/test combination and any related data from test execution. +1. Bind the score to the specific model/test combination and any related data from test execution. 1. Visualize the score (i.e. print or display the result of the test). Here, we will break down how this is accomplished in NeuronUnit. Although NeuronUnit contains several model and test classes that make it easy to work with standards in neuron modeling and electrophysiology data reporting, here we will use toy model and test classes constructed on-the-fly so the process of model and test construction is fully transparent. @@ -179,7 +184,7 @@ Let's see what the `neuronunit.capabilities.ProducesMembranePotential` capabilit ```python class ProducesMembranePotential(Capability): """Indicates that the model produces a somatic membrane potential.""" - + def get_membrane_potential(self): """Must return a neo.core.AnalogSignal.""" raise NotImplementedError() @@ -205,12 +210,12 @@ Now we can then construct a simple test to use on this model or any other test t ```python class ToyAveragePotentialTest(sciunit.Test): """Tests the average membrane potential of a neuron.""" - + def __init__(self, - observation={'mean':None,'std':None}, + observation={'mean':None,'sd':None}, name="Average potential test"): """Takes the mean and standard deviation of reference membrane potentials.""" - + sciunit.Test.__init__(self,observation,name) # Call the base constructor. self.required_capabilities += (neuronunit.capabilities.ProducesMembranePotential,) # This test will require a model to express the above capabilities @@ -219,18 +224,18 @@ class ToyAveragePotentialTest(sciunit.Test): score_type = sciunit.scores.ZScore # The test will return this kind of score. def validate_observation(self, observation): - """An optional method that makes sure an observation to be used as + """An optional method that makes sure an observation to be used as reference data has the right form""" try: assert type(observation['mean']) is quantities.Quantity # From the 'quantities' package - assert type(observation['std']) is quantities.Quantity + assert type(observation['sd']) is quantities.Quantity except Exception as e: raise sciunit.ObservationError(("Observation must be of the form " - "{'mean':float*mV,'std':float*mV}")) + "{'mean':float*mV,'sd':float*mV}")) def generate_prediction(self, model): """Implementation of sciunit.Test.generate_prediction.""" - vm = model.get_median_vm() # If the model has the capability 'ProducesMembranePotential', + vm = model.get_median_vm() # If the model has the capability 'ProducesMembranePotential', # then it implements this method prediction = {'mean':vm} return prediction @@ -247,8 +252,8 @@ The test constructor takes an observation to parameterize the test, e.g.: ```python from quantities import mV -my_observation = {'mean':-60.0*mV, - 'std':3.5*mV} +my_observation = {'mean':-60.0*mV, + 'sd':3.5*mV} my_average_potential_test = ToyAveragePotentialTest(my_observation, name='my_average_potential_test') ``` diff --git a/asv.conf.json b/asv.conf.json new file mode 100644 index 000000000..de36e0994 --- /dev/null +++ b/asv.conf.json @@ -0,0 +1,160 @@ +{ + // The version of the config file format. Do not change, unless + // you know what you are doing. + "version": 1, + + // The name of the project being benchmarked + "project": "neuronunit", + + // The project's homepage + "project_url": "https://github.com/russelljjarvis/neuronunit", + + // The URL or local path of the source code repository for the + // project being benchmarked + "repo": ".", + + // The Python project's subdirectory in your repo. If missing or + // the empty string, the project is assumed to be located at the root + // of the repository. + // "repo_subdir": "", + + // Customizable commands for building, installing, and + // uninstalling the project. See asv.conf.json documentation. + // + // "install_command": ["in-dir={env_dir} python -mpip install {wheel_file}"], + // "uninstall_command": ["return-code=any python -mpip uninstall -y {project}"], + // "build_command": [ + // "python setup.py build", + // "PIP_NO_BUILD_ISOLATION=false python -mpip wheel --no-deps --no-index -w {build_cache_dir} {build_dir}" + // ], + + // List of branches to benchmark. If not provided, defaults to "master" + // (for git) or "default" (for mercurial). + // "branches": ["master"], // for git + // "branches": ["default"], // for mercurial + + // The DVCS being used. If not set, it will be automatically + // determined from "repo" by looking at the protocol in the URL + // (if remote), or by looking for special directories, such as + // ".git" (if local). + // "dvcs": "git", + + // The tool to use to create environments. May be "conda", + // "virtualenv" or other value depending on the plugins in use. + // If missing or the empty string, the tool will be automatically + // determined by looking for tools on the PATH environment + // variable. + "environment_type": "virtualenv", + + // timeout in seconds for installing any dependencies in environment + // defaults to 10 min + //"install_timeout": 600, + + // the base URL to show a commit for the project. + // "show_commit_url": "http://github.com/owner/project/commit/", + + // The Pythons you'd like to test against. If not provided, defaults + // to the current version of Python used to run `asv`. + // "pythons": ["2.7", "3.6"], + + // The list of conda channel names to be searched for benchmark + // dependency packages in the specified order + // "conda_channels": ["conda-forge", "defaults"], + + // The matrix of dependencies to test. Each key is the name of a + // package (in PyPI) and the values are version numbers. An empty + // list or empty string indicates to just test against the default + // (latest) version. null indicates that the package is to not be + // installed. If the package to be tested is only available from + // PyPi, and the 'environment_type' is conda, then you can preface + // the package name by 'pip+', and the package will be installed via + // pip (with all the conda available packages installed first, + // followed by the pip installed packages). + // + // "matrix": { + // "numpy": ["1.6", "1.7"], + // "six": ["", null], // test with and without six installed + // "pip+emcee": [""], // emcee is only available for install with pip. + // }, + + // Combinations of libraries/python versions can be excluded/included + // from the set to test. Each entry is a dictionary containing additional + // key-value pairs to include/exclude. + // + // An exclude entry excludes entries where all values match. The + // values are regexps that should match the whole string. + // + // An include entry adds an environment. Only the packages listed + // are installed. The 'python' key is required. The exclude rules + // do not apply to includes. + // + // In addition to package names, the following keys are available: + // + // - python + // Python version, as in the *pythons* variable above. + // - environment_type + // Environment type, as above. + // - sys_platform + // Platform, as in sys.platform. Possible values for the common + // cases: 'linux2', 'win32', 'cygwin', 'darwin'. + // + // "exclude": [ + // {"python": "3.2", "sys_platform": "win32"}, // skip py3.2 on windows + // {"environment_type": "conda", "six": null}, // don't run without six on conda + // ], + // + // "include": [ + // // additional env for python2.7 + // {"python": "2.7", "numpy": "1.8"}, + // // additional env if run on windows+conda + // {"platform": "win32", "environment_type": "conda", "python": "2.7", "libpython": ""}, + // ], + + // The directory (relative to the current directory) that benchmarks are + // stored in. If not provided, defaults to "benchmarks" + // "benchmark_dir": "benchmarks", + + // The directory (relative to the current directory) to cache the Python + // environments in. If not provided, defaults to "env" + "env_dir": ".asv/env", + + // The directory (relative to the current directory) that raw benchmark + // results are stored in. If not provided, defaults to "results". + "results_dir": ".asv/results", + + // The directory (relative to the current directory) that the html tree + // should be written to. If not provided, defaults to "html". + "html_dir": ".asv/html", + + // The number of characters to retain in the commit hashes. + // "hash_length": 8, + + // `asv` will cache results of the recent builds in each + // environment, making them faster to install next time. This is + // the number of builds to keep, per environment. + // "build_cache_size": 2, + + // The commits after which the regression search in `asv publish` + // should start looking for regressions. Dictionary whose keys are + // regexps matching to benchmark names, and values corresponding to + // the commit (exclusive) after which to start looking for + // regressions. The default is to start from the first commit + // with results. If the commit is `null`, regression detection is + // skipped for the matching benchmark. + // + // "regressions_first_commits": { + // "some_benchmark": "352cdf", // Consider regressions only after this commit + // "another_benchmark": null, // Skip regression detection altogether + // }, + + // The thresholds for relative change in results, after which `asv + // publish` starts reporting regressions. Dictionary of the same + // form as in ``regressions_first_commits``, with values + // indicating the thresholds. If multiple entries match, the + // maximum is taken. If no entry matches, the default is 5%. + // + // "regressions_thresholds": { + // "some_benchmark": 0.01, // Threshold of 1% + // "another_benchmark": 0.5, // Threshold of 50% + // }, +} diff --git a/benchmarks/__init__.py b/benchmarks/__init__.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/benchmarks/__init__.py @@ -0,0 +1 @@ + diff --git a/benchmarks/benchmarks.py b/benchmarks/benchmarks.py new file mode 100644 index 000000000..497b2ca52 --- /dev/null +++ b/benchmarks/benchmarks.py @@ -0,0 +1,19 @@ +# Write the benchmarking functions here. +# See "Writing benchmarks" in the asv docs for more information. +from neuronunit.unit_test.opt_ephys_properties import testOptimizationEphysCase +from neuronunit.unit_test.scores_unit_test import testOptimizationAllenMultiSpike +from neuronunit.unit_test.rheobase_model_test import testModelRheobase + + +class TimeSuite: + """ + An example benchmark that times the performance of various kinds + of iterating over dictionaries in Python. + """ + def speed_check(): + testModelRheobase.setUp() + testModelRheobase.test_opt_1() + +class MemSuite: + def mem_list(self): + return [0] * 256 diff --git a/build.sh b/build.sh new file mode 100644 index 000000000..aae194a8a --- /dev/null +++ b/build.sh @@ -0,0 +1,36 @@ +apt-get install -y cpp gcc +apt-get install -y libx11-6 python-dev git build-essential +apt-get install -y autoconf automake gcc g++ make gfortran +apt-get install -y python-tables +apt-get install -y libhdf5-serial-dev +conda install numpy; +conda install numba; +conda install dask; +pip install pip --upgrade; +pip install tables +pip install scipy==1.5.4 +pip install -e . +pip install coverage +git clone -b neuronunit https://github.com/russelljjarvis/jit_hub.git +cd jit_hub; pip install -e .; cd ..; +pip install cython +pip install asciiplotlib; +git clone -b neuronunit_reduced_cells https://github.com/russelljjarvis/BluePyOpt.git +cd BluePyOpt; pip install -e . +pip install git+https://github.com/russelljjarvis/eFEL +pip install ipfx +pip install streamlit +pip install sklearn +pip install seaborn +pip install frozendict +pip install plotly +<<<<<<< HEAD +======= +pip install igor +pip install pylmeasure +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f +pip install --upgrade colorama +rm -rf /opt/conda/lib/python3.8/site-packages/sciunit +git clone -b dev https://github.com/russelljjarvis/sciunit.git +cd sciunit; pip install -e .; cd ..; +pip install allensdk==0.16.3 diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 000000000..e18db6220 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,9 @@ +coverage: + range: "90...100" + + status: + project: + default: + target: "90%" + threshold: "5%" + patch: false diff --git a/docs/chapter3.ipynb b/docs/chapter3.ipynb index 093288d79..f5d91a409 100644 --- a/docs/chapter3.ipynb +++ b/docs/chapter3.ipynb @@ -2,10 +2,7 @@ "cells": [ { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "![NeuronUnit Logo](https://raw.githubusercontent.com/scidash/assets/master/logos/neuronunit-logo-text.png)\n", "# Chapter 3\n", @@ -14,10 +11,7 @@ }, { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "In the second chapter we tested a real ion channel model using data from the [OpenWorm](http://openworm.org) project. Here we'll test a reduced neuron model using data from the [NeuroElectro](http://neuroelectro.org) project and from the [Allen Brain Institute Cell Types](http://celltypes.brain-map.org) database. \n", "### We'll test an Izhikevich model against data from a Layer V pyramidal cell in primary visual cortex" @@ -26,21 +20,8 @@ { "cell_type": "code", "execution_count": 1, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/rgerkin/Dropbox/miniconda3/lib/python3.5/site-packages/pyNN/neuron/__init__.py:14: UserWarning: mpi4py not available\n", - " warnings.warn(\"mpi4py not available\")\n" - ] - } - ], + "metadata": {}, + "outputs": [], "source": [ "%matplotlib inline\n", "import os,sys\n", @@ -56,11 +37,7 @@ { "cell_type": "code", "execution_count": 2, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [ { "name": "stdout", @@ -68,26 +45,19 @@ "text": [ "Getting Rheobase cached data value for from AIBS dataset 354190013\n", "Getting Input Resistance data values from neuroelectro.org\n", - "{'nlex': 'nifext_50', 'e__name': 'Input Resistance', 'e': None, 'n__name': None, 'n': None}\n", - "http://www.neuroelectro.org/api/1/nes/?nlex=nifext_50&e__name=Input+Resistance\n", + "http://neuroelectro.org/api/1/nes/?e__name=Input+Resistance&nlex=nifext_50\n", "Getting Membrane Time Constant data values from neuroelectro.org\n", - "{'nlex': 'nifext_50', 'e__name': 'Membrane Time Constant', 'e': None, 'n__name': None, 'n': None}\n", - "http://www.neuroelectro.org/api/1/nes/?nlex=nifext_50&e__name=Membrane+Time+Constant\n", + "http://neuroelectro.org/api/1/nes/?e__name=Membrane+Time+Constant&nlex=nifext_50\n", "Getting Cell Capacitance data values from neuroelectro.org\n", - "{'nlex': 'nifext_50', 'e__name': 'Cell Capacitance', 'e': None, 'n__name': None, 'n': None}\n", - "http://www.neuroelectro.org/api/1/nes/?nlex=nifext_50&e__name=Cell+Capacitance\n", + "http://neuroelectro.org/api/1/nes/?e__name=Cell+Capacitance&nlex=nifext_50\n", "Getting Resting membrane potential data values from neuroelectro.org\n", - "{'nlex': 'nifext_50', 'e__name': 'Resting membrane potential', 'e': None, 'n__name': None, 'n': None}\n", - "http://www.neuroelectro.org/api/1/nes/?nlex=nifext_50&e__name=Resting+membrane+potential\n", + "http://neuroelectro.org/api/1/nes/?e__name=Resting+membrane+potential&nlex=nifext_50\n", "Getting Spike Half-Width data values from neuroelectro.org\n", - "{'nlex': 'nifext_50', 'e__name': 'Spike Half-Width', 'e': None, 'n__name': None, 'n': None}\n", - "http://www.neuroelectro.org/api/1/nes/?nlex=nifext_50&e__name=Spike+Half-Width\n", + "http://neuroelectro.org/api/1/nes/?e__name=Spike+Half-Width&nlex=nifext_50\n", "Getting Spike Amplitude data values from neuroelectro.org\n", - "{'nlex': 'nifext_50', 'e__name': 'Spike Amplitude', 'e': None, 'n__name': None, 'n': None}\n", - "http://www.neuroelectro.org/api/1/nes/?nlex=nifext_50&e__name=Spike+Amplitude\n", + "http://neuroelectro.org/api/1/nes/?e__name=Spike+Amplitude&nlex=nifext_50\n", "Getting Spike Threshold data values from neuroelectro.org\n", - "{'nlex': 'nifext_50', 'e__name': 'Spike Threshold', 'e': None, 'n__name': None, 'n': None}\n", - "http://www.neuroelectro.org/api/1/nes/?nlex=nifext_50&e__name=Spike+Threshold\n" + "http://neuroelectro.org/api/1/nes/?e__name=Spike+Threshold&nlex=nifext_50\n" ] } ], @@ -133,10 +103,7 @@ }, { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "### The Izhikevich model here is defined in an NeuroML2/LEMS file. \n", "### We will run it using the jNeuroML backend for simplicity, although this is *much* slower than the native NEURON backend. " @@ -145,11 +112,7 @@ { "cell_type": "code", "execution_count": 3, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [], "source": [ "# This example is from https://github.com/OpenSourceBrain/IzhikevichModel.\n", @@ -167,10 +130,7 @@ }, { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "### Run a series of tests. The RheobaseTest is run first to obtain the rheobase current to use in all the other tests." ] @@ -178,11 +138,7 @@ { "cell_type": "code", "execution_count": 4, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "metadata": {}, "outputs": [ { "data": { @@ -200,17 +156,28 @@ "name": "stdout", "output_type": "stream", "text": [ - "Injected 300.0 current and got 17 spikes\n", - "Injected -0.0 current and got 0 spikes\n", - "Injected 150.0 current and got 8 spikes\n", - "Injected 75.0 current and got 2 spikes\n", - "Injected 37.5 current and got 0 spikes\n", - "Injected 56.25 current and got 1 spikes\n", - "Injected 46.875 current and got 0 spikes\n", - "Injected 51.5625 current and got 0 spikes\n", - "Injected 53.90625 current and got 0 spikes\n", - "Injected 55.078125 current and got 1 spikes\n", - "Injected 54.4921875 current and got 0 spikes\n", + "pyNeuroML >>> Reloading data specified in LEMS file: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpa_c2e9xh/vanilla.xml (/private/var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpa_c2e9xh/vanilla.xml), base_dir: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmp46doda0y, cwd: /Users/rgerkin/Dropbox/dev/scidash/neuronunit/docs\n", + "Injected 300.0 pA current and got 17 spikes\n", + "pyNeuroML >>> Reloading data specified in LEMS file: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpa_c2e9xh/vanilla.xml (/private/var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpa_c2e9xh/vanilla.xml), base_dir: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmp0bu0m549, cwd: /Users/rgerkin/Dropbox/dev/scidash/neuronunit/docs\n", + "Injected -0.0 pA current and got 0 spikes\n", + "pyNeuroML >>> Reloading data specified in LEMS file: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpa_c2e9xh/vanilla.xml (/private/var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpa_c2e9xh/vanilla.xml), base_dir: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpw_sq9rvf, cwd: /Users/rgerkin/Dropbox/dev/scidash/neuronunit/docs\n", + "Injected 150.0 pA current and got 8 spikes\n", + "pyNeuroML >>> Reloading data specified in LEMS file: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpa_c2e9xh/vanilla.xml (/private/var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpa_c2e9xh/vanilla.xml), base_dir: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpiamt417a, cwd: /Users/rgerkin/Dropbox/dev/scidash/neuronunit/docs\n", + "Injected 75.0 pA current and got 2 spikes\n", + "pyNeuroML >>> Reloading data specified in LEMS file: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpa_c2e9xh/vanilla.xml (/private/var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpa_c2e9xh/vanilla.xml), base_dir: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpo0kcmr_l, cwd: /Users/rgerkin/Dropbox/dev/scidash/neuronunit/docs\n", + "Injected 37.5 pA current and got 0 spikes\n", + "pyNeuroML >>> Reloading data specified in LEMS file: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpa_c2e9xh/vanilla.xml (/private/var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpa_c2e9xh/vanilla.xml), base_dir: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpjwzjqckr, cwd: /Users/rgerkin/Dropbox/dev/scidash/neuronunit/docs\n", + "Injected 56.25 pA current and got 1 spikes\n", + "pyNeuroML >>> Reloading data specified in LEMS file: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpa_c2e9xh/vanilla.xml (/private/var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpa_c2e9xh/vanilla.xml), base_dir: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpui008j85, cwd: /Users/rgerkin/Dropbox/dev/scidash/neuronunit/docs\n", + "Injected 46.875 pA current and got 0 spikes\n", + "pyNeuroML >>> Reloading data specified in LEMS file: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpa_c2e9xh/vanilla.xml (/private/var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpa_c2e9xh/vanilla.xml), base_dir: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpytyp7uk0, cwd: /Users/rgerkin/Dropbox/dev/scidash/neuronunit/docs\n", + "Injected 51.5625 pA current and got 0 spikes\n", + "pyNeuroML >>> Reloading data specified in LEMS file: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpa_c2e9xh/vanilla.xml (/private/var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpa_c2e9xh/vanilla.xml), base_dir: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpa6uwqpuc, cwd: /Users/rgerkin/Dropbox/dev/scidash/neuronunit/docs\n", + "Injected 53.90625 pA current and got 0 spikes\n", + "pyNeuroML >>> Reloading data specified in LEMS file: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpa_c2e9xh/vanilla.xml (/private/var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpa_c2e9xh/vanilla.xml), base_dir: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmp_jcnkhg9, cwd: /Users/rgerkin/Dropbox/dev/scidash/neuronunit/docs\n", + "Injected 55.078125 pA current and got 1 spikes\n", + "pyNeuroML >>> Reloading data specified in LEMS file: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpa_c2e9xh/vanilla.xml (/private/var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpa_c2e9xh/vanilla.xml), base_dir: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmptbco_cpa, cwd: /Users/rgerkin/Dropbox/dev/scidash/neuronunit/docs\n", + "Injected 54.4921875 pA current and got 0 spikes\n", "Highest subthreshold current is 54.49 pA\n", "Lowest suprathreshold current is 55.08 pA\n" ] @@ -239,6 +206,14 @@ "metadata": {}, "output_type": "display_data" }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "pyNeuroML >>> Reloading data specified in LEMS file: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpa_c2e9xh/vanilla.xml (/private/var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpa_c2e9xh/vanilla.xml), base_dir: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpi192r5ow, cwd: /Users/rgerkin/Dropbox/dev/scidash/neuronunit/docs\n", + "pyNeuroML >>> Reloading data specified in LEMS file: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpa_c2e9xh/vanilla.xml (/private/var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpa_c2e9xh/vanilla.xml), base_dir: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpjwi8z4by, cwd: /Users/rgerkin/Dropbox/dev/scidash/neuronunit/docs\n" + ] + }, { "data": { "text/html": [ @@ -311,6 +286,13 @@ "metadata": {}, "output_type": "display_data" }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "pyNeuroML >>> Reloading data specified in LEMS file: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpa_c2e9xh/vanilla.xml (/private/var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpa_c2e9xh/vanilla.xml), base_dir: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmprc9xx5ja, cwd: /Users/rgerkin/Dropbox/dev/scidash/neuronunit/docs\n" + ] + }, { "data": { "text/html": [ @@ -335,6 +317,13 @@ "metadata": {}, "output_type": "display_data" }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "pyNeuroML >>> Reloading data specified in LEMS file: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpa_c2e9xh/vanilla.xml (/private/var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpa_c2e9xh/vanilla.xml), base_dir: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpu_mgo1lc, cwd: /Users/rgerkin/Dropbox/dev/scidash/neuronunit/docs\n" + ] + }, { "data": { "text/html": [ @@ -362,7 +351,7 @@ { "data": { "text/html": [ - "Score is Z = -1.47\n" + "Score is Z = -1.72\n" ], "text/plain": [ "" @@ -402,22 +391,15 @@ }, { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "### Now we can sweep across a parameter (the resting potential) and run the same test suite on a model corresponding to each value of this parameter." ] }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, + "execution_count": 5, + "metadata": {}, "outputs": [], "source": [ "models = []\n", @@ -429,22 +411,335 @@ " })\n", " #model.skip_run = True\n", " models.append(model)\n", - "suite.set_verbose(False) # Turn off most print statements. \n", - "score_matrix = suite.judge(models)" + "suite.set_verbose(False) # Turn off most print statements. " ] }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, - "outputs": [], + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "Executing test RheobaseTest on model V_rest=-80mV... " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "pyNeuroML >>> Reloading data specified in LEMS file: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpt7q8xdbr/V_rest=-80mV.xml (/private/var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpt7q8xdbr/V_rest=-80mV.xml), base_dir: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpp_fgr6s3, cwd: /Users/rgerkin/Dropbox/dev/scidash/neuronunit/docs\n", + "pyNeuroML >>> Reloading data specified in LEMS file: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpt7q8xdbr/V_rest=-80mV.xml (/private/var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpt7q8xdbr/V_rest=-80mV.xml), base_dir: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpp871lxef, cwd: /Users/rgerkin/Dropbox/dev/scidash/neuronunit/docs\n", + "pyNeuroML >>> Reloading data specified in LEMS file: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpt7q8xdbr/V_rest=-80mV.xml (/private/var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpt7q8xdbr/V_rest=-80mV.xml), base_dir: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmphocuyv2n, cwd: /Users/rgerkin/Dropbox/dev/scidash/neuronunit/docs\n", + "pyNeuroML >>> Reloading data specified in LEMS file: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpt7q8xdbr/V_rest=-80mV.xml (/private/var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpt7q8xdbr/V_rest=-80mV.xml), base_dir: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpe77b1jhl, cwd: /Users/rgerkin/Dropbox/dev/scidash/neuronunit/docs\n", + "pyNeuroML >>> Reloading data specified in LEMS file: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpt7q8xdbr/V_rest=-80mV.xml (/private/var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpt7q8xdbr/V_rest=-80mV.xml), base_dir: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpcmg1ik_g, cwd: /Users/rgerkin/Dropbox/dev/scidash/neuronunit/docs\n", + "pyNeuroML >>> Reloading data specified in LEMS file: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpt7q8xdbr/V_rest=-80mV.xml (/private/var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpt7q8xdbr/V_rest=-80mV.xml), base_dir: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpyp1ntz3g, cwd: /Users/rgerkin/Dropbox/dev/scidash/neuronunit/docs\n", + "pyNeuroML >>> Reloading data specified in LEMS file: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpt7q8xdbr/V_rest=-80mV.xml (/private/var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpt7q8xdbr/V_rest=-80mV.xml), base_dir: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmp30p49bl4, cwd: /Users/rgerkin/Dropbox/dev/scidash/neuronunit/docs\n", + "pyNeuroML >>> Reloading data specified in LEMS file: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpt7q8xdbr/V_rest=-80mV.xml (/private/var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpt7q8xdbr/V_rest=-80mV.xml), base_dir: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmppmys7gf3, cwd: /Users/rgerkin/Dropbox/dev/scidash/neuronunit/docs\n", + "pyNeuroML >>> Reloading data specified in LEMS file: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpt7q8xdbr/V_rest=-80mV.xml (/private/var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpt7q8xdbr/V_rest=-80mV.xml), base_dir: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmp99a7fpwx, cwd: /Users/rgerkin/Dropbox/dev/scidash/neuronunit/docs\n", + "pyNeuroML >>> Reloading data specified in LEMS file: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpt7q8xdbr/V_rest=-80mV.xml (/private/var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpt7q8xdbr/V_rest=-80mV.xml), base_dir: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmp72tne2z8, cwd: /Users/rgerkin/Dropbox/dev/scidash/neuronunit/docs\n", + "pyNeuroML >>> Reloading data specified in LEMS file: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpt7q8xdbr/V_rest=-80mV.xml (/private/var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpt7q8xdbr/V_rest=-80mV.xml), base_dir: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpgmwji7g8, cwd: /Users/rgerkin/Dropbox/dev/scidash/neuronunit/docs\n" + ] + }, + { + "data": { + "text/html": [ + "Score is Ratio = 1.89\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "Executing test InputResistanceTest on model V_rest=-80mV... " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "pyNeuroML >>> Reloading data specified in LEMS file: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpt7q8xdbr/V_rest=-80mV.xml (/private/var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpt7q8xdbr/V_rest=-80mV.xml), base_dir: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmp3dz6u66j, cwd: /Users/rgerkin/Dropbox/dev/scidash/neuronunit/docs\n" + ] + }, + { + "data": { + "text/html": [ + "Score is Z = -1.04\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "Executing test TimeConstantTest on model V_rest=-80mV... " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "Score is Z = -1.74\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "Executing test CapacitanceTest on model V_rest=-80mV... " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "Score is Z = -0.54\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "Executing test RestingPotentialTest on model V_rest=-80mV... " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "pyNeuroML >>> Reloading data specified in LEMS file: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpt7q8xdbr/V_rest=-80mV.xml (/private/var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpt7q8xdbr/V_rest=-80mV.xml), base_dir: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpcfwsfqbu, cwd: /Users/rgerkin/Dropbox/dev/scidash/neuronunit/docs\n" + ] + }, + { + "data": { + "text/html": [ + "Score is Z = -1.80\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "Executing test InjectedCurrentAPWidthTest on model V_rest=-80mV... " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "pyNeuroML >>> Reloading data specified in LEMS file: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpt7q8xdbr/V_rest=-80mV.xml (/private/var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpt7q8xdbr/V_rest=-80mV.xml), base_dir: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmp8xwvdnur, cwd: /Users/rgerkin/Dropbox/dev/scidash/neuronunit/docs\n" + ] + }, + { + "data": { + "text/html": [ + "Score is Z = -0.80\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "Executing test InjectedCurrentAPAmplitudeTest on model V_rest=-80mV... " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "Score is Z = -1.22\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "Executing test InjectedCurrentAPThresholdTest on model V_rest=-80mV... " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "Score is Z = 1.59\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "score_matrix = suite.judge(models[0])" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "$(\"head\").append($(\"\").attr({\n", + " rel: \"stylesheet\",\n", + " type: \"text/css\",\n", + " href: \"//ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.0/css/jquery.dataTables.css\"\n", + "}));\n", + "$.getScript(\"//ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.0/jquery.dataTables.min.js\", function () {\n", + "$('#1336104884797179007').dataTable();});\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
MeanRheobaseTestInputResistanceTestTimeConstantTestCapacitanceTestRestingPotentialTestInjectedCurrentAPWidthTestInjectedCurrentAPAmplitudeTestInjectedCurrentAPThresholdTest
V_rest=-80mV0.323Ratio = 1.89Z = -1.04Z = -1.74Z = -0.54Z = -1.80Z = -0.80Z = -1.22Z = 1.59
\n", + "
" + ], + "text/plain": [ + " RheobaseTest InputResistanceTest TimeConstantTest \\\n", + "V_rest=-80mV Ratio = 1.89 Z = -1.04 Z = -1.74 \n", + "\n", + " CapacitanceTest RestingPotentialTest InjectedCurrentAPWidthTest \\\n", + "V_rest=-80mV Z = -0.54 Z = -1.80 Z = -0.80 \n", + "\n", + " InjectedCurrentAPAmplitudeTest InjectedCurrentAPThresholdTest \n", + "V_rest=-80mV Z = -1.22 Z = 1.59 " + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "score_matrix.show_mean = True # Show the mean value across test scores. \n", - " # The mean is computed using the sort_key attribute, which is in the range [0,1] for\n", + " # The mean is computed using the norm_score attribute, which is in the range [0,1] for\n", " # all Score types\n", "score_matrix.sortable = True # Make the ScoreMatrix sortable (and searchable)\n", "score_matrix" @@ -452,23 +747,30 @@ }, { "cell_type": "markdown", - "metadata": { - "deletable": true, - "editable": true - }, + "metadata": {}, "source": [ "### Let's take a look at a sweep from one of these models ($V_{rest} = -55 mV$) obtained at the rheobase current. " ] }, { "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false, - "deletable": true, - "editable": true - }, - "outputs": [], + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "ename": "KeyError", + "evalue": "\"No model or test with name 'None'\"", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mKeyError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mmatplotlib\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mmpl\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0mmpl\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrcParams\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'font.size'\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m18\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0mscore_matrix\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'RheobaseTest'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'V_rest=-60mV'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mplot_vm\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# Plot the rheobase current from the model with V_rest = -60 mV\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m~/Dropbox (ASU)/dev/scidash/sciunit/sciunit/scores/collections.py\u001b[0m in \u001b[0;36m__getitem__\u001b[0;34m(self, item)\u001b[0m\n\u001b[1;32m 55\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__getitem__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mitem\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 56\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mitem\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 57\u001b[0;31m \u001b[0mresult\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_by_name\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mitem\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 58\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 59\u001b[0m \u001b[0mresult\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0msuper\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mScoreArray\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__getitem__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mitem\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/Dropbox (ASU)/dev/scidash/sciunit/sciunit/scores/collections.py\u001b[0m in \u001b[0;36mget_by_name\u001b[0;34m(self, name)\u001b[0m\n\u001b[1;32m 66\u001b[0m \u001b[0mitem\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__getitem__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtest_or_model\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 67\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mitem\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 68\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mKeyError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"No model or test with name '%s'\"\u001b[0m \u001b[0;34m%\u001b[0m \u001b[0mitem\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 69\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mitem\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 70\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mKeyError\u001b[0m: \"No model or test with name 'None'\"" + ] + } + ], "source": [ "import matplotlib as mpl\n", "mpl.rcParams['font.size'] = 18\n", diff --git a/docs/morphology.ipynb b/docs/morphology.ipynb new file mode 100755 index 000000000..5b171f811 --- /dev/null +++ b/docs/morphology.ipynb @@ -0,0 +1,802 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Performing Morphology Tests\n", + "\n", + "The examples below show how to compare values of morphology metrics to experimental values.\n", + "\n", + "It assumes you converted your model to an [.SWC file](http://www.neuronland.org/NLMorphologyConverter/MorphologyFormats/SWC/Spec.html). The tests use the [pyLMeasure library](https://pypi.org/project/pylmeasure/) to compute the metrics.\n", + "\n", + "If you have a NEURON model (.HOC or Python), see the [hoc2swc package](https://pypi.org/project/hoc2swc/) to export model morphology to .SWC." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Imports" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/justas/anaconda2/envs/p27/lib/python2.7/site-packages/h5py/__init__.py:36: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.\n", + " from ._conv import register_converters as _register_converters\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Could not load HHpyNNBackend.\n" + ] + } + ], + "source": [ + "from neuronunit.models.morphology import SwcCellModel\n", + "from neuronunit.tests.morphology import *\n", + "\n", + "from pandas import DataFrame\n", + "from sciunit.suites import TestSuite\n", + "import quantities as pq\n", + "\n", + "pass;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create a SWC model instance\n", + "\n", + "Specify the path to the SWC file" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "model = SwcCellModel('/home/justas/Repositories/OlfactoryBulb/prev-ob-models/BhallaBower1993-HOC/mit.swc')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create a test with expected experimental values\n", + "\n", + "Specify the known mean, standard deviation, and sample size" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "test = SomaSurfaceAreaTest({\"mean\":1650*pq.um**2, \"std\":80*pq.um**2,\"n\":26})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Judge the model against the expected distribution" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Z = -0.52" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "score = test.judge(model)\n", + "score" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Build a suite of tests that can be run against one or more models" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "# Values below are arbitrary, for demonstration purposes\n", + "tests = [\n", + " test,\n", + " NumberofStemsTest({\"mean\":0, \"std\":1,\"n\":1}),\n", + " NumberofBifurcationsTest({\"mean\":0, \"std\":1,\"n\":1}),\n", + " NumberofBranchesTest({\"mean\":0, \"std\":1,\"n\":1}),\n", + " OverallWidthTest({\"mean\":0*pq.um, \"std\":1*pq.um,\"n\":1}),\n", + " OverallHeightTest({\"mean\":0*pq.um, \"std\":1*pq.um,\"n\":1}),\n", + " OverallDepthTest({\"mean\":0*pq.um, \"std\":1*pq.um,\"n\":1}),\n", + " AverageDiameterTest({\"mean\":0*pq.um, \"std\":1*pq.um,\"n\":1}),\n", + " TotalLengthTest({\"mean\":0*pq.um, \"std\":1*pq.um,\"n\":1}),\n", + " TotalSurfaceTest({\"mean\":0*pq.um**2, \"std\":1*pq.um**2,\"n\":1}),\n", + " TotalVolumeTest({\"mean\":0*pq.um**3, \"std\":1*pq.um**3,\"n\":1}),\n", + " MaxEuclideanDistanceTest({\"mean\":0*pq.um, \"std\":1*pq.um,\"n\":1}),\n", + " MaxPathDistanceTest({\"mean\":0*pq.um, \"std\":1*pq.um,\"n\":1}),\n", + " MaxBranchOrderTest({\"mean\":0, \"std\":1,\"n\":1}),\n", + " AverageContractionTest({\"mean\":0, \"std\":1,\"n\":1}),\n", + " PartitionAsymmetryTest({\"mean\":0, \"std\":1,\"n\":1}),\n", + " AverageRallsRatioTest({\"mean\":0, \"std\":1,\"n\":1}),\n", + " AverageBifurcationAngleLocalTest({\"mean\":0, \"std\":1,\"n\":1}),\n", + " AverageBifurcationAngleRemoteTest({\"mean\":0, \"std\":1,\"n\":1}),\n", + " FractalDimensionTest({\"mean\":0, \"std\":1,\"n\":1}),\n", + "]\n", + "\n", + "suite = TestSuite(tests)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "Executing test SomaSurfaceAreaTest on model mit... " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "Score is Z = -0.52\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "Executing test NumberofStemsTest on model mit... " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "Score is Z = 3.00\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "Executing test NumberofBifurcationsTest on model mit... " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "Score is Z = 65.00\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "Executing test NumberofBranchesTest on model mit... " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "Score is Z = 133.00\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "Executing test OverallWidthTest on model mit... " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "Score is Z = 2594.66\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "Executing test OverallHeightTest on model mit... " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "Score is Z = 1140.88\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "Executing test OverallDepthTest on model mit... " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "Score is Z = 2077.41\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "Executing test AverageDiameterTest on model mit... " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "Score is Z = 1.87\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "Executing test TotalLengthTest on model mit... " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "Score is Z = 16750.60\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "Executing test TotalSurfaceTest on model mit... " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "Score is Z = 98817.70\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "Executing test TotalVolumeTest on model mit... " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "Score is Z = 67812.30\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "Executing test MaxEuclideanDistanceTest on model mit... " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "Score is Z = 1848.33\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "Executing test MaxPathDistanceTest on model mit... " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "Score is Z = 2109.43\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "Executing test MaxBranchOrderTest on model mit... " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "Score is Z = 9.00\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "Executing test AverageContractionTest on model mit... " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "Score is Z = 0.98\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "Executing test TotalFragmentationTest on model mit... " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "Score is Z = 340.00\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "Executing test PartitionAsymmetryTest on model mit... " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "Score is Z = 0.70\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "Executing test AverageRallsRatioTest on model mit... " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "Score is Z = 1.30\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "Executing test AverageBifurcationAngleLocalTest on model mit... " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "Score is Z = 66.28\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "Executing test AverageBifurcationAngleRemoteTest on model mit... " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "Score is Z = 77.92\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "Executing test FractalDimensionTest on model mit... " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "Score is Z = 1.01\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sm = suite.judge(model)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## View the test suite results in a table" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
SomaSurfaceAreaTestNumberofStemsTestNumberofBifurcationsTestNumberofBranchesTestOverallWidthTestOverallHeightTestOverallDepthTestAverageDiameterTestTotalLengthTestTotalSurfaceTest...MaxEuclideanDistanceTestMaxPathDistanceTestMaxBranchOrderTestAverageContractionTestTotalFragmentationTestPartitionAsymmetryTestAverageRallsRatioTestAverageBifurcationAngleLocalTestAverageBifurcationAngleRemoteTestFractalDimensionTest
mitZ = -0.52Z = 3.00Z = 65.00Z = 133.00Z = 2594.66Z = 1140.88Z = 2077.41Z = 1.87Z = 16750.60Z = 98817.70...Z = 1848.33Z = 2109.43Z = 9.00Z = 0.98Z = 340.00Z = 0.70Z = 1.30Z = 66.28Z = 77.92Z = 1.01
\n", + "

1 rows × 21 columns

\n", + "
" + ], + "text/plain": [ + " SomaSurfaceAreaTest NumberofStemsTest NumberofBifurcationsTest \\\n", + "mit Z = -0.52 Z = 3.00 Z = 65.00 \n", + "\n", + " NumberofBranchesTest OverallWidthTest OverallHeightTest OverallDepthTest \\\n", + "mit Z = 133.00 Z = 2594.66 Z = 1140.88 Z = 2077.41 \n", + "\n", + " AverageDiameterTest TotalLengthTest TotalSurfaceTest ... \\\n", + "mit Z = 1.87 Z = 16750.60 Z = 98817.70 ... \n", + "\n", + " MaxEuclideanDistanceTest MaxPathDistanceTest MaxBranchOrderTest \\\n", + "mit Z = 1848.33 Z = 2109.43 Z = 9.00 \n", + "\n", + " AverageContractionTest TotalFragmentationTest PartitionAsymmetryTest \\\n", + "mit Z = 0.98 Z = 340.00 Z = 0.70 \n", + "\n", + " AverageRallsRatioTest AverageBifurcationAngleLocalTest \\\n", + "mit Z = 1.30 Z = 66.28 \n", + "\n", + " AverageBifurcationAngleRemoteTest FractalDimensionTest \n", + "mit Z = 77.92 Z = 1.01 \n", + "\n", + "[1 rows x 21 columns]" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "DataFrame(sm)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 2", + "language": "python", + "name": "python2" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2.0 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.14" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/environment.yml b/environment.yml index a1e548ed9..4479afbe6 100644 --- a/environment.yml +++ b/environment.yml @@ -6,7 +6,15 @@ dependencies: - pip: - neo==0.4 - elephant - - scoop - - git+http://github.com/scidash/sciunit@dev#egg=sciunit-1.5.6 - - git+http://github.com/rgerkin/AllenSDK@python3.5#egg=allensdk-0.12.4.1 - - git+http://github.com/rgerkin/pyNeuroML@master#egg=pyneuroml-0.2.3 + - dask + - numba + - streamlit + - sklearn + - seaborn + - frozendict + - plotly + - asciiplotlib + - ipfx + - git+https://github.com/russelljjarvis/jit_jub@neuronunit + - git+https://github.com/russelljjarvis/BluePyOpt@neuronunit_reduced_cells + - git+https://github.com/russelljjarvis/sciunit@dev diff --git a/index.html b/index.html deleted file mode 100644 index 9dd857bf0..000000000 --- a/index.html +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - -
RheobaseTestInputResistanceTestTimeConstantTestCapacitanceTestRestingPotentialTestInjectedCurrentAPWidthTestInjectedCurrentAPAmplitudeTestInjectedCurrentAPThresholdTest
-0.0034836044554955635Ratio = 0.31Z = -1.55Z = 925.95Z = -7003330.65Z = 2.06Z = 7.10Z = -6.30Z = 5.31
\ No newline at end of file diff --git a/neuronunit/__init__.py b/neuronunit/__init__.py index fab569a20..ef9860d04 100644 --- a/neuronunit/__init__.py +++ b/neuronunit/__init__.py @@ -1,10 +1,15 @@ -"""NeuronUnit: Testing for neuron and ion channel models -using the SciUnit framework.""" +"""NeuronUnit. +Testing for neuron and ion channel models +using the SciUnit framework. +""" + +import os import platform try: import sciunit + assert sciunit except ImportError as e: print("NeuronUnit requires SciUnit: http://github.com/scidash/sciunit") raise e @@ -12,3 +17,4 @@ IMPLEMENTATION = platform.python_implementation() JYTHON = IMPLEMENTATION == 'Jython' CPYTHON = IMPLEMENTATION == 'CPython' +DIR = os.path.dirname(os.path.realpath(__file__)) diff --git a/neuronunit/aibs.py b/neuronunit/aibs.py old mode 100644 new mode 100755 index a7bc0d848..1ebecf8df --- a/neuronunit/aibs.py +++ b/neuronunit/aibs.py @@ -1,5 +1,4 @@ -"""NeuronUnit module for interaction with the Allen Brain Insitute -Cell Types database""" +"""NeuronUnit module for interaction with the AIBS Cell Types Database.""" import shelve import requests @@ -10,43 +9,46 @@ def is_aibs_up(): + """Check whether the AIBS Cell Types Database API is working.""" url = ("http://api.brain-map.org/api/v2/data/query.xml?criteria=model" - "::Specimen,rma::criteria,[id$eq320654829],rma::include,ephys_result" - "(well_known_files(well_known_file_type[name$eqNWBDownload]))") + "::Specimen,rma::criteria,[id$eq320654829],rma::include," + "ephys_result(well_known_files(well_known_file_type" + "[name$eqNWBDownload]))") request = requests.get(url) return request.status_code == 200 - + def get_sweep_params(dataset_id, sweep_id): - """ - Gets sweep parameters corresponding to the sweep with id 'sweep_id' from + """Get sweep parameters. + + Get those corresponding to the sweep with id 'sweep_id' from the dataset with id 'dataset_id'. """ - ct = CellTypesApi() experiment_params = ct.get_ephys_sweeps(dataset_id) sp = None for sp in experiment_params: - if sp['id']==sweep_id: + if sp['id'] == sweep_id: sweep_num = sp['sweep_number'] if sweep_num is None: - raise Exception('Sweep with ID %d not found in dataset with ID %d.' % (sweep_id, dataset_id)) + msg = "Sweep with ID %d not found in dataset with ID %d." + raise Exception(msg % (sweep_id, dataset_id)) break return sp def get_sp(experiment_params, sweep_ids): - """ + """Get sweep parameters. + A candidate method for replacing 'get_sweep_params'. This fix is necessary due to changes in the allensdk. - Warning: This method may not properly convey the original meaning + Warning: This method may not properly convey the original meaning of 'get_sweep_params'. """ - sp = None for sp in experiment_params: for sweep_id in sweep_ids: - if sp['id']==sweep_id: + if sp['id'] == sweep_id: sweep_num = sp['sweep_number'] if sweep_num is None: raise Exception('Sweep with ID %d not found.' % sweep_id) @@ -55,47 +57,53 @@ def get_sp(experiment_params, sweep_ids): def get_observation(dataset_id, kind, cached=True, quiet=False): - """ - Gets an observation of kind 'kind' from the dataset with id 'dataset_id', + """Get an observation. + + Get an observation of kind 'kind' from the dataset with id 'dataset_id'. optionally using the cached value retrieved previously. """ - db = shelve.open('aibs-cache') if cached else {} - identifier = '%d_%s' % (dataset_id,kind) + identifier = '%d_%s' % (dataset_id, kind) if identifier in db: - print("Getting %s cached data value for from AIBS dataset %s" \ - % (kind.title(),dataset_id)) + print("Getting %s cached data value for from AIBS dataset %s" + % (kind.title(), dataset_id)) value = db[identifier] else: - print("Getting %s data value for from AIBS dataset %s" \ - % (kind.title(),dataset_id)) + print("Getting %s data value for from AIBS dataset %s" + % (kind.title(), dataset_id)) ct = CellTypesApi() - cmd = ct.get_cell(dataset_id) # Cell metadata - if kind == 'rheobase': - sweep_id = cmd['ephys_features'][0]['rheobase_sweep_id'] - sp = get_sweep_params(dataset_id, sweep_id) + cmd = ct.get_cell(dataset_id) # Cell metadata + if kind == 'rheobase': - value = sp['stimulus_absolute_amplitude'] - value = np.round(value,2) # Round to nearest hundredth of a pA. - value *= pq.pA # Apply units. + if 'ephys_features' in cmd: + value = cmd['ephys_features'][0]['threshold_i_long_square'] # newer API + else: + value = cmd['ef__threshold_i_long_square'] # older API + + value = np.round(value, 2) # Round to nearest hundredth of a pA. + value *= pq.pA # Apply units. + + else: + value = cmd[kind] + db[identifier] = value - + if cached: db.close() return {'value': value} - + def get_value_dict(experiment_params, sweep_ids, kind): - """ + """Get a dictionary of data values from the experiment. + A candidate method for replacing 'get_observation'. This fix is necessary due to changes in the allensdk. - Warning: Together with 'get_sp' this method may not properly + Warning: Together with 'get_sp' this method may not properly convey the meaning of 'get_observation'. """ - if kind == str('rheobase'): - sp = get_sp(experiment_params,sweep_ids) + sp = get_sp(experiment_params, sweep_ids) value = sp['stimulus_absolute_amplitude'] - value = np.round(value,2) # Round to nearest hundredth of a pA. - value *= pq.pA # Apply units. - return {'value': value} + value = np.round(value, 2) # Round to nearest hundredth of a pA. + value *= pq.pA # Apply units. + return {'value': value} diff --git a/neuronunit/allenapi/__init__.py b/neuronunit/allenapi/__init__.py new file mode 100644 index 000000000..537314a72 --- /dev/null +++ b/neuronunit/allenapi/__init__.py @@ -0,0 +1,3 @@ +"""Allen API for NeuronUnit""" + +import warnings diff --git a/neuronunit/allenapi/aibs.py b/neuronunit/allenapi/aibs.py new file mode 100755 index 000000000..b3f34bc87 --- /dev/null +++ b/neuronunit/allenapi/aibs.py @@ -0,0 +1,235 @@ +"""NeuronUnit module for interaction with the Allen Brain Insitute +Cell Types database""" +# import logging +# logger = logging.getLogger(name) +# logging.info("test") +import matplotlib as mpl + +try: + mpl.use("agg") +except: + pass +import matplotlib.pyplot as plt +import shelve +import requests +import numpy as np +import quantities as pq +from allensdk.api.queries.cell_types_api import CellTypesApi +from allensdk.core.cell_types_cache import CellTypesCache +from allensdk.api.queries.glif_api import GlifApi +import os +import pickle +from allensdk.api.queries.biophysical_api import BiophysicalApi + +from allensdk.core.cell_types_cache import CellTypesCache +from allensdk.ephys.extract_cell_features import extract_cell_features +from collections import defaultdict +from allensdk.core.nwb_data_set import NwbDataSet + +from neuronunit import models +from neo.core import AnalogSignal +import quantities as qt +from types import MethodType + +from allensdk.ephys.extract_cell_features import extract_cell_features +from collections import defaultdict +from allensdk.core.cell_types_cache import CellTypesCache + +import neo +from elephant.spike_train_generation import threshold_detection +from quantities import mV, ms +from numba import jit +import sciunit +import math +import pdb +from allensdk.ephys.extract_cell_features import extract_cell_features + + +def is_aibs_up(): + """Check whether the AIBS Cell Types Database API is working.""" + url = ( + "http://api.brain-map.org/api/v2/data/query.xml?criteria=model" + "::Specimen,rma::criteria,[id$eq320654829],rma::include," + "ephys_result(well_known_files(well_known_file_type" + "[name$eqNWBDownload]))" + ) + request = requests.get(url) + return request.status_code == 200 + + +def get_observation(dataset_id, kind, cached=True, quiet=False): + """Get an observation. + + Get an observation of kind 'kind' from the dataset with id 'dataset_id'. + optionally using the cached value retrieved previously. + """ + + db = shelve.open("aibs-cache") if cached else {} + identifier = "%d_%s" % (dataset_id, kind) + if identifier in db: + print( + "Getting %s cached data value for from AIBS dataset %s" + % (kind.title(), dataset_id) + ) + value = db[identifier] + else: + print( + "Getting %s data value for from AIBS dataset %s" + % (kind.title(), dataset_id) + ) + ct = CellTypesApi() + cmd = ct.get_cell(dataset_id) # Cell metadata + + if kind == "rheobase": + if "ephys_features" in cmd: + value = cmd["ephys_features"][0]["threshold_i_long_square"] # newer API + else: + value = cmd["ef__threshold_i_long_square"] # older API + + value = np.round(value, 2) # Round to nearest hundredth of a pA. + value *= pq.pA # Apply units. + + else: + value = cmd[kind] + + db[identifier] = value + + if cached: + db.close() + return {"value": value} + + +def get_value_dict(experiment_params, sweep_ids, kind): + """Get a dictionary of data values from the experiment. + + A candidate method for replacing 'get_observation'. + This fix is necessary due to changes in the allensdk. + Warning: Together with 'get_sp' this method may not properly + convey the meaning of 'get_observation'. + """ + + if kind == str("rheobase"): + sp = get_sp(experiment_params, sweep_ids) + value = sp["stimulus_absolute_amplitude"] + value = np.round(value, 2) # Round to nearest hundredth of a pA. + value *= pq.pA # Apply units. + return {"value": value} + + +"""Auxiliary helper functions for analysis of spiking.""" + + +def find_nearest(array, value): + array = np.asarray(array) + idx = (np.abs(array - value)).argmin() + return (array[idx], idx) + + +def inject_square_current(model, current): + if type(current) is type({}): + current = float(current["amplitude"]) + data_set = model.data_set + numbers = data_set.get_sweep_numbers() + injections = [np.max(data_set.get_sweep(sn)["stimulus"]) for sn in numbers] + sns = [sn for sn in numbers] + (nearest, idx) = find_nearest(injections, current) + index = np.asarray(numbers)[idx] + sweep_data = data_set.get_sweep(index) + temp_vm = sweep_data["response"] + injection = sweep_data["stimulus"] + sampling_rate = sweep_data["sampling_rate"] + vm = AnalogSignal(temp_vm, sampling_rate=sampling_rate * qt.Hz, units=qt.V) + model._vm = vm + return model._vm + + +def get_membrane_potential(model): + return model._vm + + +def get_spike_train(vm, threshold=0.0 * mV): + """ + Inputs: + vm: a neo.core.AnalogSignal corresponding to a membrane potential trace. + threshold: the value (in mV) above which vm has to cross for there + to be a spike. Scalar float. + + Returns: + a neo.core.SpikeTrain containing the times of spikes. + """ + spike_train = threshold_detection(vm, threshold=threshold) + return spike_train + + +def get_spike_count(model): + vm = model.get_membrane_potential() + train = get_spike_train(vm) + return len(train) + + +def appropriate_features(): + for s in sweeps: + if s["ramp"]: + print([(k, v) for k, v in s.items()]) + current = {} + current["amplitude"] = s["stimulus_absolute_amplitude"] + current["duration"] = s["stimulus_duration"] + current["delay"] = s["stimulus_start_time"] + + +def get_features(specimen_id=485909730): + data_set = ctc.get_ephys_data(specimen_id) + sweeps = ctc.get_ephys_sweeps(specimen_id) + + # group the sweeps by stimulus + sweep_numbers = defaultdict(list) + for sweep in sweeps: + sweep_numbers[sweep["stimulus_name"]].append(sweep["sweep_number"]) + + # calculate features + cell_features = extract_cell_features( + data_set, + sweep_numbers["Ramp"], + sweep_numbers["Short Square"], + sweep_numbers["Long Square"], + ) + + +def get_sweep_params(dataset_id, sweep_id): + """Get sweep parameters. + + Get those corresponding to the sweep with id 'sweep_id' from + the dataset with id 'dataset_id'. + """ + + ct = CellTypesApi() + experiment_params = ct.get_ephys_sweeps(dataset_id) + sp = None + for sp in experiment_params: + if sp["id"] == sweep_id: + sweep_num = sp["sweep_number"] + if sweep_num is None: + msg = "Sweep with ID %d not found in dataset with ID %d." + raise Exception(msg % (sweep_id, dataset_id)) + break + return sp + + +def get_sp(experiment_params, sweep_ids): + + """Get sweep parameters. + A candidate method for replacing 'get_sweep_params'. + This fix is necessary due to changes in the allensdk. + Warning: This method may not properly convey the original meaning + of 'get_sweep_params'. + """ + + sp = None + for sp in experiment_params: + for sweep_id in sweep_ids: + if sp["id"] == sweep_id: + sweep_num = sp["sweep_number"] + if sweep_num is None: + raise Exception("Sweep with ID %d not found." % sweep_id) + break + return sp diff --git a/neuronunit/allenapi/allen_data_driven.py b/neuronunit/allenapi/allen_data_driven.py new file mode 100644 index 000000000..30252da8e --- /dev/null +++ b/neuronunit/allenapi/allen_data_driven.py @@ -0,0 +1,523 @@ +from typing import Any, Dict, List, Optional, Tuple, Type, Union, Text + +import pickle +import seaborn as sns +import os +import copy +import numpy as np +from collections.abc import Iterable +import pandas as pd +import quantities as pq + +import bluepyopt as bpop +import bluepyopt.ephys as ephys +from bluepyopt.parameters import Parameter + +from sciunit.scores import RelativeDifferenceScore +from sciunit import TestSuite +from sciunit.scores import ZScore +from sciunit.scores.collections import ScoreArray + +from neuronunit.allenapi import make_allen_tests_from_id +from neuronunit.allenapi.make_allen_tests_from_id import * +from neuronunit.allenapi.make_allen_tests import AllenTest + +from neuronunit.optimization.optimization_management import inject_model_soma +from neuronunit.optimization.model_parameters import BPO_PARAMS +from neuronunit.tests import ( + RheobaseTest, + CapacitanceTest, + RestingPotentialTest, + InputResistanceTest, + TimeConstantTest, + FITest, +) + + +def opt_setup( + specimen_id, + model_type, + target_num, + template_model=None, + cached=False, + fixed_current=False, + score_type=ZScore, + efel_filter_iterable=None, +): + if cached: + with open(str(specimen_id) + "later_allen_NU_tests.p", "rb") as f: + suite = pickle.load(f) + + else: + + sweep_numbers, data_set, sweeps = make_allen_tests_from_id.allen_id_to_sweeps( + specimen_id + ) + ( + vmm, + stimulus, + sn, + spike_times, + ) = make_allen_tests_from_id.get_model_parts_sweep_from_spk_cnt( + target_num, data_set, sweep_numbers, specimen_id + ) + ( + suite, + specimen_id, + ) = make_allen_tests_from_id.make_suite_known_sweep_from_static_models( + vmm, stimulus, specimen_id, efel_filter_iterable=efel_filter_iterable + ) + with open(str(specimen_id) + "later_allen_NU_tests.p", "wb") as f: + pickle.dump(suite, f) + if "vm_soma" in suite.traces.keys(): + target = StaticModel(vm=suite.traces["vm_soma"]) + target.vm_soma = suite.traces["vm_soma"] + + nu_tests = suite.tests + + attrs = {k: np.mean(v) for k, v in MODEL_PARAMS[model_type].items()} + for t in nu_tests: + if t.name == "Spikecount": + spk_count = float(t.observation["mean"]) + break + observation_range = {} + observation_range["value"] = spk_count + template_model.backend = model_type + template_model.allen = True + template_model.NU = True + if fixed_current: + uc = { + "amplitude": fixed_current, + "duration": ALLEN_DURATION, + "delay": ALLEN_DELAY, + } + target_current = None + else: + scs = SpikeCountSearch(observation_range) + assert template_model is not None + + target_current = scs.generate_prediction(template_model) + template_model.seeded_current = target_current["value"] + + return template_model, suite, nu_tests, target_current, spk_count + + +def wrap_setups( + specimen_id, + model_type, + target_num_spikes, + template_model=None, + fixed_current=False, + cached=False, + score_type=ZScore, + efel_filter_iterable=None, +): + """ + if os.path.isfile("325479788later_allen_NU_tests.p"): + template_model, suite, nu_tests, target_current, spk_count = opt_setup( + specimen_id, + model_type, + target_num_spikes, + template_model=template_model, + fixed_current=False, + cached=False, + score_type=score_type, + efel_filter_iterable=efel_filter_iterable + ) + else: + """ + assert template_model is not None + [ template_model, suite, nu_tests, target_current, spk_count ] = opt_setup( + specimen_id, + model_type, + target_num_spikes, + template_model=template_model, + fixed_current=False, + cached=None, + score_type=score_type, + efel_filter_iterable=efel_filter_iterable, + ) + template_model.seeded_current = target_current["value"] + template_model.allen = True + template_model.NU = True + template_model.backend = model_type + template_model.efel_filter_iterable = efel_filter_iterable + + cell_evaluator, template_model = opt_setup_two( + model_type, + suite, + nu_tests, + target_current, + spk_count, + template_model=template_model, + score_type=score_type, + efel_filter_iterable=efel_filter_iterable, + ) + cell_evaluator.cell_model.params = BPO_PARAMS[model_type] + assert cell_evaluator.cell_model is not None + cell_evaluator.suite = None + cell_evaluator.suite = suite + return cell_evaluator, template_model, suite, target_current, spk_count + + +class NUFeatureAllenMultiSpike(object): + def __init__( + self, test, model, cnt, target, spike_obs, print_stuff=False, score_type=None + ): + self.test = test + self.model = model + self.spike_obs = spike_obs + self.cnt = cnt + self.target = target + self.score_type = score_type + self.score_array = None + + def calculate_score(self, responses): + if not "features" in responses.keys(): + return 1000.0 + features = responses["features"] + if features is None: + return 1000.0 + self.test.score_type = self.score_type + feature_name = self.test.name + if feature_name not in features.keys(): + return 1000.0 + + if features[feature_name] is None: + return 1000.0 + if type(features[self.test.name]) is type(Iterable): + features[self.test.name] = np.mean(features[self.test.name]) + + self.test.observation["mean"] = np.mean(self.test.observation["mean"]) + self.test.set_prediction(np.mean(features[self.test.name])) + if "Spikecount" == feature_name: + delta = np.abs( + features[self.test.name] - np.mean(self.test.observation["mean"]) + ) + delta += delta + delta += delta + + if np.nan == delta or delta == np.inf: + delta = 1000.0 + return delta + else: + if features[feature_name] is None: + return 1000.0 + + prediction = {"value": np.mean(features[self.test.name])} + score_gene = self.test.judge(responses["model"], prediction=prediction) + if score_gene is not None: + if score_gene.log_norm_score is not None: + delta = np.abs(float(score_gene.log_norm_score)) + else: + if score_gene.raw is not None: + delta = np.abs(float(score_gene.raw)) + else: + delta = 1000.0 + else: + delta = 1000.0 + # if np.nan == delta or delta == np.inf: + # delta = np.abs(float(score_gene.raw)) + if np.nan == delta or delta == np.inf: + delta = 1000.0 + # print(self.test.name,'delta',delta) + + return delta + + +def opt_setup_two( + model_type, + suite, + nu_tests, + target_current, + spk_count, + template_model=None, + score_type=ZScore, + efel_filter_iterable=None, +): + assert template_model.backend == model_type + template_model.params = BPO_PARAMS[model_type] + template_model.params_by_names(list(BPO_PARAMS[model_type].keys())) + template_model.seeded_current = target_current["value"] + template_model.spk_count = spk_count + sweep_protocols = [] + protocol = ephys.protocols.NeuronUnitAllenStepProtocol( + "multi_spiking", [None], [None] + ) + sweep_protocols.append(protocol) + onestep_protocol = ephys.protocols.SequenceProtocol( + "multi_spiking_wraper", protocols=sweep_protocols + ) + objectives = [] + spike_obs = [] + for tt in nu_tests: + if "Spikecount" == tt.name: + spike_obs.append(tt.observation) + spike_obs = sorted(spike_obs, key=lambda k: k["mean"], reverse=True) + for cnt, tt in enumerate(nu_tests): + feature_name = "%s" % (tt.name) + ft = NUFeatureAllenMultiSpike( + tt, template_model, cnt, target_current, spike_obs, score_type=score_type + ) + objective = ephys.objectives.SingletonObjective(feature_name, ft) + objectives.append(objective) + score_calc = ephys.objectivescalculators.ObjectivesCalculator(objectives) + template_model.params_by_names(BPO_PARAMS[template_model.backend].keys()) + template_model.efel_filter_iterable = efel_filter_iterable + cell_evaluator = ephys.evaluators.CellEvaluator( + cell_model=template_model, + param_names=list(BPO_PARAMS[template_model.backend].keys()), + fitness_protocols={onestep_protocol.name: onestep_protocol}, + fitness_calculator=score_calc, + sim="euler", + ) + assert cell_evaluator.cell_model is not None + return cell_evaluator, template_model + + +def opt_exec( + MU, NGEN, mapping_funct, cell_evaluator, mutpb=0.1, cxpb=1, neuronunit=True +): + optimisation = bpop.optimisations.DEAPOptimisation( + evaluator=cell_evaluator, + offspring_size=MU, + eta=35, # was 35, # was 25 + map_function=map, + selector_name="IBEA", + mutpb=mutpb, + cxpb=cxpb, + neuronunit=neuronunit, + ) + final_pop, hall_of_fame, logs, hist = optimisation.run(max_ngen=NGEN) + return final_pop, hall_of_fame, logs, hist + + +def opt_to_model(hall_of_fame, cell_evaluator, suite, target_current, spk_count): + best_ind = hall_of_fame[0] + cell_evaluator.evaluate_with_lists(best_ind) + model = cell_evaluator.cell_model + tests = cell_evaluator.suite.tests + scores = [] + obs_preds = [] + + for t in tests: + scores.append(t.judge(model, prediction=t.prediction)) + obs_preds.append( + (t.name, t.observation["mean"], t.prediction["mean"], scores[-1]) + ) + df = pd.DataFrame(obs_preds) + + opt = model#.model_to_dtc() + opt.attrs = { + str(k): float(v) for k, v in cell_evaluator.param_dict(best_ind).items() + } + # for k,v in cell_evaluator.cell_model.attrs.items(): + # print(opt.attrs[k],'opt attrs',v,'in cell evaluator') + # assert opt.attrs[k] == v + print(best_ind, "the gene") + target = copy.copy(opt) + if "vm_soma" in suite.traces.keys(): + target.vm_soma = suite.traces["vm_soma"] + opt.seeded_current = target_current["value"] + opt.spk_count = spk_count + target.seeded_current = target_current["value"] + target.spk_count = spk_count + target = inject_model_soma(target, solve_for_current=target_current["value"]) + opt = inject_model_soma(opt, solve_for_current=target_current["value"]) + + return opt, target, scores, obs_preds, df + + +def make_allen_hard_coded_complete(): + """ + Manually specificy 4-5 + different passive/static electrical properties + over 4 Allen specimen id's. + FITest + 623960880 + 623893177 + 471819401 + 482493761 + """ + from neuronunit.optimization.optimization_management import TSD + + ## + # 623960880 + ## + rt = RheobaseTest(observation={"mean": 70 * qt.pA, "std": 70 * qt.pA}) # yes + tc = TimeConstantTest(observation={"mean": 23.8 * qt.ms, "std": 23.8 * qt.ms}) + ir = InputResistanceTest(observation={"mean": 241 * qt.MOhm, "std": 241 * qt.MOhm}) + rp = RestingPotentialTest(observation={"mean": -65.1 * qt.mV, "std": 65.1 * qt.mV}) + + # capacitance = ((tc.observation['mean']))/((ir.observation['mean']))#*qt.pF + + # ct = CapacitanceTest(observation={'mean':capacitance,'std':capacitance}) + fislope = FITest( + observation={"value": 0.18 * (pq.Hz / pq.pA), "mean": 0.18 * (pq.Hz / pq.pA)} + ) + fislope.score_type = RelativeDifferenceScore + + allen_tests = [tc, rp, ir, rt] + for t in allen_tests: + t.score_type = RelativeDifferenceScore + allen_suite_623960880 = TestSuite(allen_tests) + allen_suite_623960880.name = ( + "http://celltypes.brain-map.org/mouse/experiment/electrophysiology/623960880" + ) + + ## + # ID 623893177 + ## + rt = RheobaseTest(observation={"mean": 190 * qt.pA, "std": 190 * qt.pA}) # yes + tc = TimeConstantTest(observation={"mean": 27.8 * qt.ms, "std": 27.8 * qt.ms}) + ir = InputResistanceTest(observation={"mean": 136 * qt.MOhm, "std": 136 * qt.MOhm}) + rp = RestingPotentialTest(observation={"mean": -77.0 * qt.mV, "std": 77.0 * qt.mV}) + + capacitance = ((tc.observation["mean"])) / ((ir.observation["mean"])) # *qt.pF + + ct = CapacitanceTest(observation={"mean": capacitance, "std": capacitance}) + + ## + fislope = FITest( + observation={"value": 0.12 * (pq.Hz / pq.pA), "mean": 0.12 * (pq.Hz / pq.pA)} + ) + fislope.score_type = RelativeDifferenceScore + ## + + allen_tests = [tc, rp, ir, rt] + for t in allen_tests: + t.score_type = RelativeDifferenceScore + allen_suite_623893177 = TestSuite(allen_tests) + allen_suite_623893177.name = ( + "http://celltypes.brain-map.org/mouse/experiment/electrophysiology/623893177" + ) + cells = {} + cells["623960880"] = TSD(allen_suite_623960880) + cells["623893177"] = TSD(allen_suite_623893177) + + ## + # 482493761 + ## + + rt = RheobaseTest(observation={"mean": 70 * qt.pA, "std": 70 * qt.pA}) # yes + tc = TimeConstantTest(observation={"mean": 24.4 * qt.ms, "std": 24.4 * qt.ms}) + ir = InputResistanceTest(observation={"mean": 132 * qt.MOhm, "std": 132 * qt.MOhm}) + rp = RestingPotentialTest(observation={"mean": -71.6 * qt.mV, "std": 71.6 * qt.mV}) + + fislope = FITest( + observation={"value": 0.09 * (pq.Hz / pq.pA), "mean": 0.09 * (pq.Hz / pq.pA)} + ) + fislope.score_type = RelativeDifferenceScore + + allen_tests = [rt, tc, rp, ir] + for t in allen_tests: + t.score_type = RelativeDifferenceScore + # allen_tests[-1].score_type = ZScore + allen_suite482493761 = TestSuite(allen_tests) + allen_suite482493761.name = ( + "http://celltypes.brain-map.org/mouse/experiment/electrophysiology/482493761" + ) + ## + # 471819401 + ## + rt = RheobaseTest(observation={"mean": 190 * qt.pA, "std": 190 * qt.pA}) # yes + tc = TimeConstantTest(observation={"mean": 13.8 * qt.ms, "std": 13.8 * qt.ms}) + ir = InputResistanceTest(observation={"mean": 132 * qt.MOhm, "std": 132 * qt.MOhm}) + rp = RestingPotentialTest(observation={"mean": -77.5 * qt.mV, "std": 77.5 * qt.mV}) + + fislope = FITest( + observation={"value": 0.18 * (pq.Hz / pq.pA), "mean": 0.18 * (pq.Hz / pq.pA)} + ) + fislope.score_type = RelativeDifferenceScore + + # F/I Curve Slope 0.18 + allen_tests = [rt, tc, rp, ir] + for t in allen_tests: + t.score_type = RelativeDifferenceScore + allen_suite471819401 = TestSuite(allen_tests) + allen_suite471819401.name = ( + "http://celltypes.brain-map.org/mouse/experiment/electrophysiology/471819401" + ) + list_of_dicts = [] + cells["471819401"] = TSD(allen_suite471819401) + cells["482493761"] = TSD(allen_suite482493761) + + for k, v in cells.items(): + observations = {} + for k1 in cells["623960880"].keys(): + vsd = TSD(v) + if k1 in vsd.keys(): + vsd[k1].observation["mean"] + + observations[k1] = np.round(vsd[k1].observation["mean"], 2) + observations["name"] = k + list_of_dicts.append(observations) + df = pd.DataFrame(list_of_dicts, index=cells.keys()) + df + + return ( + allen_suite_623960880, + allen_suite_623893177, + allen_suite471819401, + allen_suite482493761, + cells, + df, + ) + + +def make_allen_hard_coded_limited(): + # hard/hand code ephys constraints + from quantities import Hz, pA + from neuronunit.optimization.optimization_management import TSD + import pandas as pd + + rt = RheobaseTest(observation={"mean": 70 * qt.pA, "std": 70 * qt.pA}) + tc = TimeConstantTest(observation={"mean": 24.4 * qt.ms, "std": 24.4 * qt.ms}) + ir = InputResistanceTest(observation={"mean": 132 * qt.MOhm, "std": 132 * qt.MOhm}) + rp = RestingPotentialTest(observation={"mean": -71.6 * qt.mV, "std": 77.5 * qt.mV}) + + allen_tests = [rt, tc, rp, ir] + for t in allen_tests: + t.score_type = RelativeDifferenceScore + allen_suite482493761 = TestSuite(allen_tests) + allen_suite482493761.name = ( + "http://celltypes.brain-map.org/mouse/experiment/electrophysiology/482493761" + ) + rt = RheobaseTest(observation={"mean": 190 * qt.pA, "std": 190 * qt.pA}) + tc = TimeConstantTest(observation={"mean": 13.8 * qt.ms, "std": 13.8 * qt.ms}) + ir = InputResistanceTest(observation={"mean": 132 * qt.MOhm, "std": 132 * qt.MOhm}) + rp = RestingPotentialTest(observation={"mean": -77.5 * qt.mV, "std": 77.5 * qt.mV}) + fi = FITest(observation={"mean": 0.09 * Hz / pA}) #'std':77.5*qt.mV}) + + allen_tests = [rt, tc, rp, ir] # ,fi] + for t in allen_tests: + t.score_type = RelativeDifferenceScore + # allen_tests[-1].score_type = ZScore + allen_suite471819401 = TestSuite(allen_tests) + allen_suite471819401.name = ( + "http://celltypes.brain-map.org/mouse/experiment/electrophysiology/471819401" + ) + list_of_dicts = [] + cells = {} + cells["471819401"] = TSD(allen_suite471819401) + cells["482493761"] = TSD(allen_suite482493761) + + allen_tests = [rt, fi] + for t in allen_tests: + t.score_type = RelativeDifferenceScore + allen_suite3 = TestSuite(allen_tests) + + cells["fi_curve"] = TSD(allen_suite3) + + for k, v in cells.items(): + observations = {} + for k1 in cells["482493761"].keys(): + vsd = TSD(v) + if k1 in vsd.keys(): + vsd[k1].observation["mean"] + + observations[k1] = np.round(vsd[k1].observation["mean"], 2) + observations["name"] = k + list_of_dicts.append(observations) + df = pd.DataFrame(list_of_dicts) + return allen_suite471819401, allen_suite482493761, df, cells diff --git a/neuronunit/allenapi/allen_data_efel_features_opt.py b/neuronunit/allenapi/allen_data_efel_features_opt.py new file mode 100644 index 000000000..af8af56c6 --- /dev/null +++ b/neuronunit/allenapi/allen_data_efel_features_opt.py @@ -0,0 +1,355 @@ +from typing import Any, Dict, List, Optional, Tuple, Type, Union, Text + +import pickle +import seaborn as sns +import os +from scipy.interpolate import interp1d + +import bluepyopt as bpop +import bluepyopt.ephys as ephys +from bluepyopt.parameters import Parameter + +import matplotlib.pyplot as plt +import copy +import numpy as np +from collections.abc import Iterable +import pandas as pd + +from sciunit.scores import RelativeDifferenceScore +from sciunit import TestSuite +from sciunit.scores import ZScore +from sciunit.scores.collections import ScoreArray + +from neuronunit.allenapi import make_allen_tests_from_id +from neuronunit.allenapi.make_allen_tests_from_id import * +from neuronunit.allenapi.make_allen_tests import AllenTest +from neuronunit.optimization.optimization_management import inject_model_soma +from neuronunit.optimization.model_parameters import BPO_PARAMS + +def match_current_amp_to_model_param(spk_count, + model_type, + template_model, + fixed_current): + observation_range = {} + observation_range["value"] = spk_count + if fixed_current: + uc = { + "amplitude": fixed_current, + "duration": ALLEN_DURATION, + "delay": ALLEN_DELAY, + } + target_current = None + else: + scs = SpikeCountSearch(observation_range) + target_current = scs.generate_prediction(template_model) + return target_current + +def opt_setup( + specimen_id, + model_type, + target_num, + template_model=None, + cached=None, + fixed_current=False, + score_type=ZScore, + efel_filter_iterable=None, +): + if cached: + with open(str(specimen_id) + "later_allen_NU_tests.p", "rb") as f: + suite = pickle.load(f) + + else: + + sweep_numbers, data_set, sweeps = make_allen_tests_from_id.allen_id_to_sweeps( + specimen_id + ) + ( + vmm, + stimulus, + sn, + spike_times, + ) = make_allen_tests_from_id.get_model_parts_sweep_from_spk_cnt( + target_num, data_set, sweep_numbers, specimen_id + ) + ( + suite, + specimen_id, + ) = make_allen_tests_from_id.make_suite_known_sweep_from_static_models( + vmm, stimulus, specimen_id, efel_filter_iterable=efel_filter_iterable + ) + with open(str(specimen_id) + "later_allen_NU_tests.p", "wb") as f: + pickle.dump(suite, f) + if "vm_soma" in suite.traces.keys(): + target = StaticModel(vm=suite.traces["vm_soma"]) + target.vm_soma = suite.traces["vm_soma"] + else: + target = StaticModel(vm=suite.traces["vm15"]) + target.vm_soma = suite.traces["vm15"] + + nu_tests = suite.tests + + attrs = {k: np.mean(v) for k, v in MODEL_PARAMS[model_type].items()} + spktest = [ t for t in nu_tests if t.name == "Spikecount"][0] + spk_count = float(spktest.observation["mean"]) + template_model.backend = model_type + template_model.allen = True + template_model.NU = True + target_current = match_current_amp_to_model_param(spk_count, + model_type, + template_model, + fixed_current) + template_model.seeded_current = target_current["value"] + + cell_evaluator, template_model = opt_setup_two( + model_type, + suite, + nu_tests, + target_current, + spk_count, + template_model=template_model, + score_type=score_type, + efel_filter_iterable=efel_filter_iterable + ) + return suite, target_current, spk_count, cell_evaluator, template_model + + +class NUFeatureAllenMultiSpike(object): + def __init__( + self, test, model, cnt, target, spike_obs, print_stuff=False, score_type=None + ): + self.test = test + self.model = model + self.spike_obs = spike_obs + self.cnt = cnt + self.target = target + self.score_type = score_type + self.score_array = None + + def calculate_score(self, responses): + if not "features" in responses.keys(): + return 1000.0 + features = responses["features"] + if features is None: + return 1000.0 + self.test.score_type = self.score_type + feature_name = self.test.name + if feature_name not in features.keys(): + return 1000.0 + + if features[feature_name] is None: + return 1000.0 + if type(features[self.test.name]) is type(Iterable): + features[self.test.name] = np.mean(features[self.test.name]) + + self.test.observation["mean"] = np.mean(self.test.observation["mean"]) + self.test.set_prediction(np.mean(features[self.test.name])) + if "Spikecount" == feature_name: + delta = np.abs( + features[self.test.name] - np.mean(self.test.observation["mean"]) + ) + if np.nan == delta or delta == np.inf: + delta = 1000.0 + return delta + else: + if features[feature_name] is None: + return 1000.0 + + prediction = {"value": np.mean(features[self.test.name])} + score_gene = self.test.judge(responses["model"], prediction=prediction) + if score_gene is not None: + if self.test.score_type is RelativeDifferenceScore: + if score_gene.log_norm_score is not None: + delta = np.abs(float(score_gene.log_norm_score)) + else: + if score_gene.raw is not None: + delta = np.abs(float(score_gene.raw)) + else: + delta = 1000.0 + + else: + if score_gene.raw is not None: + delta = np.abs(float(score_gene.raw)) + else: + delta = 1000.0 + else: + delta = 1000.0 + if np.nan == delta or delta == np.inf: + delta = 1000.0 + return delta + + +def opt_setup_two( + model_type, + suite, + nu_tests, + target_current, + spk_count, + template_model=None, + score_type=ZScore, + efel_filter_iterable=None +): + assert template_model.backend == model_type + template_model.params = BPO_PARAMS[model_type] + template_model.params_by_names(list(BPO_PARAMS[model_type].keys())) + template_model.seeded_current = target_current["value"] + template_model.spk_count = spk_count + sweep_protocols = [] + protocol = ephys.protocols.NeuronUnitAllenStepProtocol( + "multi_spiking", [None], [None] + ) + sweep_protocols.append(protocol) + onestep_protocol = ephys.protocols.SequenceProtocol( + "multi_spiking_wraper", protocols=sweep_protocols + ) + objectives = [] + spike_obs = [] + for tt in nu_tests: + if "Spikecount" == tt.name: + spike_obs.append(tt.observation) + spike_obs = sorted(spike_obs, key=lambda k: k["mean"], reverse=True) + for cnt, tt in enumerate(nu_tests): + feature_name = "%s" % (tt.name) + ft = NUFeatureAllenMultiSpike( + tt, template_model, cnt, target_current, spike_obs, score_type=score_type + ) + objective = ephys.objectives.SingletonObjective(feature_name, ft) + objectives.append(objective) + score_calc = ephys.objectivescalculators.ObjectivesCalculator(objectives) + template_model.params_by_names(BPO_PARAMS[template_model.backend].keys()) + template_model.efel_filter_iterable = efel_filter_iterable + cell_evaluator = ephys.evaluators.CellEvaluator( + cell_model=template_model, + param_names=list(BPO_PARAMS[template_model.backend].keys()), + fitness_protocols={onestep_protocol.name: onestep_protocol}, + fitness_calculator=score_calc, + sim="euler", + ) + assert cell_evaluator.cell_model is not None + return cell_evaluator, template_model + + +""" +def multi_layered(MU, NGEN, mapping_funct, cell_evaluator2): + optimisation = bpop.optimisations.DEAPOptimisation( + evaluator=cell_evaluator2, + offspring_size=MU, + map_function=map, + selector_name="IBEA", + mutpb=0.05, + cxpb=0.6, + current_fixed=from_outer, + ) + final_pop, hall_of_fame, logs, hist = optimisation.run(max_ngen=NGEN) + return final_pop, hall_of_fame, logs, hist +""" + + +def opt_exec(MU, NGEN, mapping_funct, cell_evaluator, mutpb=0.05, cxpb=0.6,neuronunit=True): + + optimisation = bpop.optimisations.DEAPOptimisation( + evaluator=cell_evaluator, + offspring_size=MU, + eta=25, + map_function=map, + selector_name="IBEA", + mutpb=mutpb, + cxpb=cxpb, + neuronunit=neuronunit, + ) + final_pop, hall_of_fame, logs, hist = optimisation.run(max_ngen=NGEN) + return final_pop, hall_of_fame, logs, hist + + +def opt_to_model(hall_of_fame, cell_evaluator, suite, target_current, spk_count): + best_ind = hall_of_fame[0] + model = cell_evaluator.cell_model + tests = suite.tests + scores = [] + obs_preds = [] + + for t in tests: + scores.append(t.judge(model, prediction=t.prediction)) + obs_preds.append( + (t.name, t.observation["mean"], t.prediction["mean"], scores[-1]) + ) + df = pd.DataFrame(obs_preds) + + opt = model#.model_to_dtc() + opt.attrs = { + str(k): float(v) for k, v in cell_evaluator.param_dict(best_ind).items() + } + target = copy.copy(opt) + if "vm_soma" in suite.traces.keys(): + target.vm_soma = suite.traces["vm_soma"] + else: # backwards compatibility + target.vm_soma = suite.traces["vm15"] + opt.seeded_current = target_current["value"] + opt.spk_count = spk_count + params = opt.attrs_to_params() + + target.seeded_current = target_current["value"] + target.spk_count = spk_count + target = inject_model_soma( + target, solve_for_current=target_current["value"] + ) + opt = inject_model_soma(opt, solve_for_current=target_current["value"]) + + return opt, target, scores, obs_preds, df + + +''' Not used +def downsample(array, npts): + interpolated = interp1d( + np.arange(len(array)), array, axis=0, fill_value="extrapolate" + ) + downsampled = interpolated(np.linspace(0, len(array), npts)) + return downsampled +''' + +def make_stim_waves_func(): + import allensdk.core.json_utilities as json_utilities + import pickle + + neuronal_model_id = 566302806 + # download model metadata + try: + ephys_sweeps = json_utilities.read("ephys_sweeps.json") + except: + from allensdk.api.queries.glif_api import GlifApi + + glif_api = GlifApi() + nm = glif_api.get_neuronal_models_by_id([neuronal_model_id])[0] + + from allensdk.core.cell_types_cache import CellTypesCache + + # download information about the cell + ctc = CellTypesCache() + ctc.get_ephys_data(nm["specimen_id"], file_name="stimulus.nwb") + ctc.get_ephys_sweeps(nm["specimen_id"], file_name="ephys_sweeps.json") + ephys_sweeps = json_utilities.read("ephys_sweeps.json") + + ephys_file_name = "stimulus.nwb" + sweep_numbers = [ + s["sweep_number"] for s in ephys_sweeps if s["stimulus_units"] == "Amps" + ] + stimulus = [ + s + for s in ephys_sweeps + if s["stimulus_units"] == "Amps" + if s["num_spikes"] != None + if s["stimulus_name"] != "Ramp" and s["stimulus_name"] != "Short Square" + ] + amplitudes = [s["stimulus_absolute_amplitude"] for s in stimulus] + durations = [s["stimulus_duration"] for s in stimulus] + expeceted_spikes = [s["num_spikes"] for s in stimulus] + delays = [s["stimulus_start_time"] for s in stimulus] + sn = [s["sweep_number"] for s in stimulus] + make_stim_waves = {} + for i, j in enumerate(sn): + make_stim_waves[j] = {} + make_stim_waves[j]["amplitude"] = amplitudes[i] + make_stim_waves[j]["delay"] = delays[i] + make_stim_waves[j]["durations"] = durations[i] + make_stim_waves[j]["expeceted_spikes"] = expeceted_spikes[i] + pickle.dump(make_stim_waves, open("waves.p", "wb")) + return make_stim_waves diff --git a/neuronunit/allenapi/make_allen_tests.py b/neuronunit/allenapi/make_allen_tests.py new file mode 100644 index 000000000..31b1f4bf4 --- /dev/null +++ b/neuronunit/allenapi/make_allen_tests.py @@ -0,0 +1,75 @@ +from neuronunit.tests.base import VmTest +import pickle +import numpy as np +from allensdk.core.cell_types_cache import CellTypesCache +from neuronunit.models.optimization_model_layer import OptimizationModel + +from neuronunit.optimization.optimization_management import ( + multi_spiking_feature_extraction, +) +from sciunit.scores import RelativeDifferenceScore + + +class AllenTest(VmTest): + def __init__( + self, + observation={"mean": None, "std": None}, + name="generic_allen", + prediction={"mean": None, "std": None}, + ): + super(AllenTest, self).__init__(observation, name) + self.name = name + self.score_type = RelativeDifferenceScore + self.observation = observation + self.prediction = prediction + + aliases = "" + + def generate_prediction(self, model=None): + if self.prediction is None: + dtc = OptimizationModel() + dtc.backed = model.backend + dtc.attrs = model.attrs + dtc.rheobase = model.rheobase + dtc.tests = [self] + dtc = multi_spiking_feature_extraction(dtc) + dtc, ephys0 = allen_wave_predictions(dtc, thirty=True) + dtc, ephys1 = allen_wave_predictions(dtc, thirty=False) + if self.name in ephys0.keys(): + feature = ephys0[self.name] + self.prediction = {} + self.prediction["value"] = feature + # self.prediction['std'] = feature + if self.name in ephys1.keys(): + feature = ephys1[self.name] + self.prediction = {} + self.prediction["value"] = feature + # self.prediction['std'] = feature + return self.prediction + # ephys1.update() + # if not len(self.prediction.keys()): + + def compute_params(self): + self.params["t_max"] = ( + self.params["delay"] + self.params["duration"] + self.params["padding"] + ) + + # @property + # def prediction(self): + # return self._prediction + + # @property + # def observation(self): + # return self._observation + + # @observation.setter + def set_observation(self, value): + self.observation = {} + self.observation["mean"] = value + self.observation["std"] = value + + # @prediction.setter + def set_prediction(self, value): + self.prediction = {} + self.prediction["mean"] = value + self.prediction["std"] = value diff --git a/neuronunit/allenapi/make_allen_tests_from_id.py b/neuronunit/allenapi/make_allen_tests_from_id.py new file mode 100644 index 000000000..88f062289 --- /dev/null +++ b/neuronunit/allenapi/make_allen_tests_from_id.py @@ -0,0 +1,304 @@ +#!/usr/bin/env python +# coding: utf-8 +from typing import Any, Dict, List, Optional, Tuple, Type, Union, Text +from collections import defaultdict +import numpy as np +import matplotlib.pyplot as plt +import pickle +from neo.core import AnalogSignal +import quantities as qt +from elephant.spike_train_generation import threshold_detection + +from allensdk.core.cell_types_cache import CellTypesCache +from allensdk.ephys.extract_cell_features import extract_cell_features +from allensdk.core.nwb_data_set import NwbDataSet + + +from sciunit import TestSuite + +from neuronunit.optimization.model_parameters import MODEL_PARAMS +from neuronunit.optimization.optimization_management import efel_evaluation +from neuronunit.models import StaticModel +from neuronunit.tests.make_allen_tests import AllenTest +from neuronunit.tests.target_spike_current import SpikeCountSearch +from elephant.statistics import isi +from sklearn.linear_model import LinearRegression + + +def allen_id_to_sweeps(specimen_id: int = -1) -> Union[defaultdict, Any, List]: + ctc = CellTypesCache(manifest_file="cell_types/manifest.json") + + specimen_id = int(specimen_id) + data_set = ctc.get_ephys_data(specimen_id) + sweeps = ctc.get_ephys_sweeps(specimen_id) + sweep_numbers = defaultdict(list) + for sweep in sweeps: + sweep_numbers[sweep["stimulus_name"]].append(sweep["sweep_number"]) + return sweep_numbers, data_set, sweeps + + +def closest(lst, K): + lst = np.asarray(lst) + idx = (np.abs(lst - K)).argmin() + return idx + + + + +def sweeps_build_fi_tests(data_set, sweep_numbers, specimen_id): + sweep_numbers = sweep_numbers["Square - 2s Suprathreshold"] + rheobase = -1 + above_threshold_sn = [] + currents = {} + relation_map = {} + for sn in sweep_numbers: + + spike_times = data_set.get_spike_times(sn) + + if len(spike_times) > 1: + isi_ = isi(spike_times) + rate = 1.0 / np.mean(isi_) + sweep_data = data_set.get_sweep(sn) + + current_stim = np.max(sweep_data["stimulus"]) + relation_map[rate] = current_stim + model = LinearRegression() + x = np.array(list(relation_map.keys())).reshape(-1, 1) + y = np.array(list(relation_map.values())).reshape(-1, 1) + if len(x): + model.fit(x, y) + m = model.coef_ * (qt.Hz / qt.pA) + if type(m) is type(list()): + m = m[0] + slope = m + plot = False + if plot: + c = model.intercept_ + y = supra_thresh_I * float(m) + c + plt.figure() # new figure + plt.plot(supra_thresh_I, fr) + plt.plot(supra_thresh_I, y) + plt.xlabel("Amplitude of Injecting step current (pA)") + plt.ylabel("Firing rate (Hz)") + plt.grid() + plt.show() + else: + slope = None + return slope + + +def get_rheobase(numbers, sets): + rheobase_numbers = [ + sweep_number + for sweep_number in numbers + if len(sets.get_spike_times(sweep_number)) == 1 + ] + sweeps = [sets.get_sweep(n) for n in rheobase_numbers] + temp = [ + (i, np.max(s["stimulus"])) + for i, s in zip(rheobase_numbers, sweeps) + if "stimulus" in s.keys() + ] # if np.min(s['stimulus'])>0 ] + temp = sorted(temp, key=lambda x: [1], reverse=True) + rheobase = temp[0][1] + index = temp[0][0] + return rheobase, index + + +def get_model_parts(data_set, sweep_numbers, specimen_id): + sweep_numbers = sweep_numbers["Square - 2s Suprathreshold"] + rheobase = -1 + above_threshold_sn = [] + currents = {} + # this_cnt_scheme = 0 + for sn in sweep_numbers: + sweep_data = data_set.get_sweep(sn) + + spike_times = data_set.get_spike_times(sn) + + # stimulus is a numpy array in amps + stimulus = sweep_data["stimulus"] + + if len(spike_times) == 1: + if np.max(stimulus) > rheobase and rheobase == -1: + rheobase = np.max(stimulus) + stim = rheobase + currents["rh"] = stim + sampling_rate = sweep_data["sampling_rate"] + vmrh = AnalogSignal( + [v * 1000 for v in sweep_data["response"]], + sampling_rate=sampling_rate * qt.Hz, + units=qt.mV, + ) + vmrh = vmrh[0 : int(len(vmrh) / 2.1)] + if len(spike_times) >= 1: + reponse = sweep_data["response"] + sampling_rate = sweep_data["sampling_rate"] + vmm = AnalogSignal( + [v * 1000 for v in sweep_data["response"]], + sampling_rate=sampling_rate * qt.Hz, + units=qt.mV, + ) + vmm = vmm[0 : int(len(vmm) / 2.1)] + above_threshold_sn.append((np.max(stimulus), sn, vmm)) + if rheobase == -1: + rheobase = above_threshold_sn[0][0] + vmrh = above_threshold_sn[0][2] + myNumber = 3.0 * rheobase + currents_ = [t[0] for t in above_threshold_sn] + indexvm30 = closest(currents_, myNumber) + stim = above_threshold_sn[indexvm30][0] + currents["30"] = stim + vm30 = above_threshold_sn[indexvm30][2] + myNumber = 1.5 * rheobase + currents_ = [t[0] for t in above_threshold_sn] + indexvm15 = closest(currents_, myNumber) + stim = above_threshold_sn[indexvm15][0] + currents["15"] = stim + vm15 = above_threshold_sn[indexvm15][2] + vm15.sn = None + vm15.sn = above_threshold_sn[0][1] + vm15.specimen_id = None + vm15.specimen_id = specimen_id + + del sweep_numbers + del data_set + return vm15, vm30, rheobase, currents, vmrh + + +def get_model_parts_sweep_from_spk_cnt(spk_cnt, data_set, sweep_numbers, specimen_id): + sweep_numbers = sweep_numbers["Square - 2s Suprathreshold"] + rheobase = -1 + above_threshold_sn = [] + + for sn in sweep_numbers: + spike_times = data_set.get_spike_times(sn) + if len(spike_times) >= spk_cnt: + # stimulus is a numpy array in amps + sweep_data = data_set.get_sweep(sn) + stimulus = sweep_data["stimulus"] + reponse = sweep_data["response"] + + if len(spike_times): + thresh_ = len(np.where(reponse > 0)) + + sampling_rate = sweep_data["sampling_rate"] + vmm = AnalogSignal( + [v * 1000 for v in reponse], + sampling_rate=sampling_rate * qt.Hz, + units=qt.mV, + ) + # Reduce the recording to a smaller time window when interesting things + # happen + vmm = vmm[0 : int(len(vmm) / 2.1)] + # store the sweep number in the neo AnalogSignal trace object. + vmm.sn = None + vmm.sn = sn + return vmm, stimulus, sn, spike_times + return None, None, None, None + + +def get_model_parts_sweep_from_number(sn, data_set, sweep_numbers, specimen_id): + sweep_numbers = sweep_numbers["Square - 2s Suprathreshold"] + rheobase = -1 + above_threshold_sn = [] + currents = {} + sweep_data = data_set.get_sweep(sn) + stimulus = sweep_data["stimulus"] + spike_times = data_set.get_spike_times(sn) + reponse = sweep_data["response"] + + sampling_rate = sweep_data["sampling_rate"] + vmm = AnalogSignal( + [v * 1000 for v in reponse], sampling_rate=sampling_rate * qt.Hz, units=qt.mV + ) + vmm = vmm[0 : int(len(vmm) / 2.1)] + vmm.sn = None + vmm.sn = sn + + return vmm, stimulus, sn, spike_times + + +def make_suite_known_sweep_from_static_models( + vm_soma, stimulus, specimen_id, efel_filter_iterable=None +): + sm = StaticModel(vm=vm_soma) + sm.backend = "static_model" + sm.vm_soma = vm_soma + sm = efel_evaluation( + sm, current=np.max(stimulus), efel_filter_iterable=efel_filter_iterable + ) + allen_tests = [] + if sm.efel is not None: + for k, v in sm.efel.items(): + try: + # if v is not None: + at = AllenTest(name=str(k)) + at.set_observation(v) + at = wrangle_tests(at) + allen_tests.append(at) + except: + pass + suite = TestSuite(allen_tests, name=str(specimen_id)) + suite.traces = None + suite.traces = {} + suite.traces["vm_soma"] = sm.vm_soma + + suite.model = sm + suite.stim = stimulus + return suite, specimen_id + + +def wrangle_tests(t): + if hasattr(t.observation["mean"], "units"): + t.observation["mean"] = ( + np.mean(t.observation["mean"]) * t.observation["mean"].units + ) + t.observation["std"] = ( + np.mean(t.observation["mean"]) * t.observation["mean"].units + ) + else: + t.observation["mean"] = np.mean(t.observation["mean"]) + t.observation["std"] = np.mean(t.observation["mean"]) + return t + + +def make_suite_from_static_models(vm_soma, vm30, rheobase, currents, vmrh, specimen_id): + + if vmrh is not None: + sm = StaticModel(vm=vmrh) + else: + sm = StaticModel(vm=vm30) + sm.rheobase = rheobase + sm.vm_soma = vm_soma + sm = efel_evaluation(sm, current=rheobase) + + sm = rekeyed(sm) + useable = False + sm.vmrh = vmrh + allen_tests = [] + + if sm.efel is not None: + for k, v in sm.efel.items(): + try: + at = AllenTest(name=str(k)) + at.set_observation(v) + at = wrangle_tests(at) + allen_tests.append(at) + except: + pass + + suite = TestSuite(allen_tests, name=str(specimen_id)) + suite.traces = None + suite.traces = {} + suite.traces["rh_current"] = sm.rheobase + suite.traces["vmrh"] = sm.vmrh + ## + # Not here deliberate misleading naming + ## + suite.traces["vm_soma"] = sm.vm_soma + suite.model = None + suite.model = sm + suite.stim = None + suite.stim = currents + return suite, specimen_id diff --git a/neuronunit/allenapi/neuroelectroapi.py b/neuronunit/allenapi/neuroelectroapi.py new file mode 100644 index 000000000..4bd4dd51a --- /dev/null +++ b/neuronunit/allenapi/neuroelectroapi.py @@ -0,0 +1,582 @@ +import os +import sciunit +import neuronunit +import pickle +from neuronunit import tests as _, neuroelectro +from neuronunit.tests import passive, waveform, fi +from neuronunit.tests.fi import RheobaseTestP + +from neuronunit.tests import * +import quantities as qt +from sciunit.scores import RatioScore, ZScore, RelativeDifferenceScore +import neuronunit +import pandas as pd + +import pickle + +anchor = __file__ + +import copy +import sciunit +from sciunit.suites import TestSuite +import pickle + + +import urllib.request, json + +from neuronunit import neuroelectro +from scipy import stats +import numpy as np + +import urllib.request, json + + +def id_to_frame(df_n, df_e, nxid): + ntype = str(df_n[df_n["NeuroLex ID"] == nxid]["Neuron Type"].values[0]) + pyr = df_e[df_e["NeuronType"] == ntype] + return pyr + + +def column_to_sem(df, column): + + temp = [i for i in df[column].values if not np.isnan(i)] + sem = stats.sem(temp, axis=None, ddof=0) + std = np.std(temp) # , axis=None, ddof=0) + mean = np.mean(temp) + # print(column,sem) + df = pd.DataFrame([{"sem": sem, "std": std, "mean": mean}], index=[column]) + return df + + +def cell_to_frame(df_n, df_e, nxid): + pyr = id_to_frame(df_n, df_e, nxid) + for cnt, key in enumerate(pyr.columns): + empty = pd.DataFrame() + if not key in "Species": + if cnt == 0: + df_old = column_to_sem(pyr, key) + else: + df_new = column_to_sem(pyr, key) + df_old = pd.concat([df_new, df_old]) + else: + break + return df_old.T + + +def neuroelectro_summary_observation(neuron_name, ontology): + ephysprop_name = "" + verbose = False + reference_data = neuroelectro.NeuroElectroSummary( + neuron=neuron_name, + ephysprop={"name": ontology["name"]}, + get_values=True, + cached=True, + ) + return reference_data + + +def get_obs(pipe): + with urllib.request.urlopen("https://neuroelectro.org/api/1/e/") as url: + ontologies = json.loads(url.read().decode()) + obs = [] + for p in pipe: + for l in ontologies["objects"]: + obs.append(neuroelectro_summary_observation(p, l)) + return obs + + +def update_amplitude(test, tests, score): + rheobase = score.prediction["value"] + for i in [4, 5, 6]: + tests[i].params["injected_square_current"]["amplitude"] = ( + rheobase * 1.01 + ) # I feel that 1.01 may lead to more than one spike + return + + +def substitute_parallel_for_serial(electro_tests): + for test, obs in electro_tests: + test[0] = RheobaseTestP(obs["Rheobase"]) + + return electro_tests + + +def substitute_criteria(observations_donar, observations_acceptor): + # Inputs an observation donar + # and an observation acceptor + # Many neuroelectro data sources have std 0 values + for index, oa in observations_acceptor.items(): + for k, v in oa.items(): + if k == "std" and v == 0.0: + if k in observations_donar.keys(): + oa[k] = observations_donar[index][k] + return observations_acceptor + + +""" +def substitute_parallel_for_serial(electro_tests): + for test,obs in electro_tests: + if str('Rheobase') in obs.keys(): + + test[0] = RheobaseTestP(obs['Rheobase']) + + return electro_tests +""" + + +def replace_zero_std(electro_tests): + for test, obs in electro_tests: + if str("Rheobase") in obs.keys(): + test[0] = RheobaseTestP(obs["Rheobase"]) + for k, v in obs.items(): + if v["std"] == 0: + + obs = substitute_criteria(electro_tests[1][1], obs) + + return electro_tests + + +def executable_druckman_tests(cell_id, file_name=None): + # Use neuroelectro experimental obsevations to find test + # criterion that will be used to inform scientific unit testing. + # some times observations are not sourced from neuroelectro, + # but they are imputated or borrowed from other TestSuite + # if that happens make test objections using observations external + # to this method, and provided as a method argument. + tests = [] + observations = None + test_classes = None + + dm.AP1AP1AHPDepthTest.ephysprop_name = None + dm.AP1AP1AHPDepthTest.ephysprop_name = "AHP amplitude" + dm.AP2AP1AHPDepthTest.ephysprop_name = None + dm.AP2AP1AHPDepthTest.ephysprop_name = "AHP amplitude" + dm.AP1AP1WidthHalfHeightTest.ephysprop_name = None + dm.AP1AP1WidthHalfHeightTest.ephysprop_name = "spike half-width" + dm.AP1AP1WidthPeakToTroughTest.ephysprop_name = None + dm.AP1AP1WidthPeakToTroughTest.ephysprop_name = "spike width" + # dm.IinitialAccomodationMeanTest.ephysprop_name = None + # dm.IinitialAccomodationMeanTest.ephysprop_name = 'adaptation_percent' + + test_classes = [ + fi.RheobaseTest, + dm.AP1AP1AHPDepthTest, + dm.AP2AP1AHPDepthTest, + dm.AP1AP1WidthHalfHeightTest, + dm.AP1AP1WidthPeakToTroughTest, + ] + observations = {} + for index, t in enumerate(test_classes): + obs = t.neuroelectro_summary_observation(cell_id) + + if obs is not None: + if "mean" in obs.keys(): + tests.append(t(obs)) + observations[t.ephysprop_name] = obs + + suite = sciunit.TestSuite(tests, name="vm_suite") + + if file_name is not None: + file_name = file_name + ".p" + with open(file_name, "wb") as f: + pickle.dump(tests, f) + + return tests, observations + + +def executable_tests(cell_id, file_name=None): + # Use neuroelectro experimental obsevations to find test + # criterion that will be used to inform scientific unit testing. + # some times observations are not sourced from neuroelectro, + # but they are imputated or borrowed from other TestSuite + # if that happens make test objections using observations external + # to this method, and provided as a method argument. + tests = [] + observations = None + test_classes = None + test_classes = [ + fi.RheobaseTest, + passive.InputResistanceTest, + passive.TimeConstantTest, + passive.CapacitanceTest, + passive.RestingPotentialTest, + ] # , + observations = {} + for index, t in enumerate(test_classes): + if "RheobaseTest" in t.name: + t.score_type = sciunit.scores.ZScore + if "RheobaseTestP" in t.name: + t.score_type = sciunit.scores.ZScore + + obs = t.neuroelectro_summary_observation(cell_id) + + if obs is not None: + if "mean" in obs.keys(): + tests.append(t(obs)) + observations[t.ephysprop_name] = obs + + # hooks = {tests[0]:{'f':update_amplitude}} #This is a trick to dynamically insert the method + # update amplitude at the location in sciunit thats its passed to, without any loss of generality. + suite = sciunit.TestSuite(tests, name="vm_suite") + + if file_name is not None: + file_name = file_name + ".p" + with open(file_name, "wb") as f: + pickle.dump(tests, f) + + return tests, observations + + +def get_neuron_criteria(cell_id, file_name=None): # ,observation = None): + # Use neuroelectro experimental obsevations to find test + # criterion that will be used to inform scientific unit testing. + # some times observations are not sourced from neuroelectro, + # but they are imputated or borrowed from other TestSuite + # if that happens make test objections using observations external + # to this method, and provided as a method argument. + tests = {} + observations = None + test_classes = None + test_classes = [ + fi.RheobaseTest, + passive.InputResistanceTest, + passive.TimeConstantTest, + passive.CapacitanceTest, + passive.RestingPotentialTest, + ] + observations = {} + for index, t in enumerate(test_classes): + obs = t.neuroelectro_summary_observation(cell_id) + + if obs is not None: + if "mean" in obs.keys(): + print(test_classes[index]) + print(t.name) + tt = t(obs) + tests[t.name] = tt + observations[t.ephysprop_name] = obs + # hooks = {tests[0]:{'f':update_amplitude}} #This is a trick to dynamically insert the method + # update amplitude at the location in sciunit thats its passed to, without any loss of generality. + # suite = sciunit.TestSuite(tests,name="vm_suite") + + if file_name is not None: + file_name = file_name + ".p" + with open(file_name, "wb") as f: + pickle.dump(tests, f) + + return tests, observations + + +def get_olf_cell(): + cell_constraints = {} + olf_mitral = { + "id": 129, + "name": "Olfactory bulb (main) mitral cell", + "neuron_db_id": 267, + "nlex_id": "nlx_anat_100201", + } + + # NLXANAT:100201 + + olf_mitral = { + "id": 129, + "name": "Olfactory bulb (main) mitral cell", + "neuron_db_id": 267, + "nlex_id": "nlx_anat_100201", + } + # olf_mitral['id'] = 'nlx_anat_100201' + # olf_mitral['nlex_id'] = 'nlx_anat_100201' + tests, observations = get_neuron_criteria(olf_mitral) + # import pdb + # pdb.set_trace() + cell_constraints["olf_mitral"] = tests + with open("olf.p", "wb") as f: + pickle.dump(cell_constraints, f) + + return cell_constraints + + +def get_all_cells(): + ### + # sagratio + ### + purkinje = { + "id": 18, + "name": "Cerebellum Purkinje cell", + "neuron_db_id": 271, + "nlex_id": "sao471801888", + } + # fi_basket = {"id": 65, "name": "Dentate gyrus basket cell", "neuron_db_id": None, "nlex_id": "nlx_cell_100201"} + pvis_cortex = { + "id": 111, + "name": "Neocortex pyramidal cell layer 5-6", + "neuron_db_id": 265, + "nlex_id": "nifext_50", + } + # This olfactory mitral cell does not have datum about rheobase, current injection values. + olf_mitral = { + "id": 129, + "name": "Olfactory bulb (main) mitral cell", + "neuron_db_id": 267, + "nlex_id": "nlx_anat_100201", + } + ca1_pyr = { + "id": 85, + "name": "Hippocampus CA1 pyramidal cell", + "neuron_db_id": 258, + "nlex_id": "sao830368389", + } + cell_list = [olf_mitral, ca1_pyr, purkinje, pvis_cortex] + cell_constraints = {} + for cell in cell_list: + tests, observations = get_neuron_criteria(cell) + cell_constraints[cell["name"]] = tests + with open("multicellular_constraints.p", "wb") as f: + pickle.dump(cell_constraints, f) + return cell_constraints + + +def switch_logic(xtests): + # move this logic into sciunit tests + """ + Hopefuly depreciated by future NU debugging. + """ + aTSD = neuronunit.optimisation.optimization_management.TSD() + if type(xtests) is type(aTSD): + xtests = list(xtests.values()) + if type(xtests) is type(list()): + pass + for t in xtests: + if str("RheobaseTest") == t.name: + t.active = True + t.passive = False + elif str("RheobaseTestP") == t.name: + t.active = True + t.passive = False + elif str("InjectedCurrentAPWidthTest") == t.name: + t.active = True + t.passive = False + elif str("InjectedCurrentAPAmplitudeTest") == t.name: + t.active = True + t.passive = False + elif str("InjectedCurrentAPThresholdTest") == t.name: + t.active = True + t.passive = False + elif str("RestingPotentialTest") == t.name: + t.passive = True + t.active = False + elif str("InputResistanceTest") == t.name: + t.passive = True + t.active = False + elif str("TimeConstantTest") == t.name: + t.passive = True + t.active = False + elif str("CapacitanceTest") == t.name: + t.passive = True + t.active = False + else: + t.passive = False + t.active = False + return xtests + + +def process_all_cells(): + ''' + --Synopsis: download some NeuroElectroSummary + observations and use values to construct appropriate + NeuronUnit tests, then pickle them. + TODO rename pull all cells + see alias below. + + ''' + try: + with open("processed_multicellular_constraints.p", "rb") as f: + filtered_cells = pickle.load(f) + return filtered_cells + except: + try: + cell_constraints = pickle.load( + open("multicellular_suite_constraints.p", "rb") + ) + except: + cell_constraints = get_all_cells() + + filtered_cells = {} + for key, cell in cell_constraints.items(): + filtered_cell_constraints = [] + if type(cell) is type(dict()): + for t in cell.values(): + if t.observation is not None: + if float(t.observation["std"]) == 0.0: + t.observation["std"] = t.observation["mean"] + else: + filtered_cell_constraints.append(t) + # filtered_cells[key] = TestSuite(filtered_cell_constraints) + else: + for t in cell.tests: + if t.observation is not None: + if float(t.observation["std"]) == 0.0: + t.observation["std"] = t.observation["mean"] + else: + filtered_cell_constraints.append(t) + + filtered_cells[key] = TestSuite(filtered_cell_constraints) + """ + for t in filtered_cells[key].tests: + t = switch_logic(t) + assert hasattr(t,'active') + assert hasattr(t,'passive') + for t in filtered_cells[key].tests: + assert hasattr(t,'active') + assert hasattr(t,'passive') + """ + with open("processed_multicellular_constraints.p", "wb") as f: + pickle.dump(filtered_cells, f) + return filtered_cells + +def pull_all_cells(): + filtered_cells = process_all_cells() + + + +def get_common_criteria(): + purkinje = { + "id": 18, + "name": "Cerebellum Purkinje cell", + "neuron_db_id": 271, + "nlex_id": "sao471801888", + } + # fi_basket = {"id": 65, "name": "Dentate gyrus basket cell", "neuron_db_id": None, "nlex_id": "nlx_cell_100201"} + pvis_cortex = { + "id": 111, + "name": "Neocortex pyramidal cell layer 5-6", + "neuron_db_id": 265, + "nlex_id": "nifext_50", + } + # This olfactory mitral cell does not have datum about rheobase, current injection values. + olf_mitral = { + "id": 129, + "name": "Olfactory bulb (main) mitral cell", + "neuron_db_id": 267, + "nlex_id": "nlx_anat_100201", + } + ca1_pyr = { + "id": 85, + "name": "Hippocampus CA1 pyramidal cell", + "neuron_db_id": 258, + "nlex_id": "sao830368389", + } + pipe = [olf_mitral, ca1_pyr, purkinje, pvis_cortex] + electro_tests = [] + obs_frame = {} + test_frame = {} + + try: + + anchor = os.path.dirname(anchor) + mypath = os.path.join(os.sep, anchor, "optimisation/all_tests.p") + + electro_path = str(os.getcwd()) + "all_tests.p" + + assert os.path.isfile(electro_path) == True + with open(electro_path, "rb") as f: + (obs_frame, test_frame) = pickle.load(f) + + except: + for p in pipe: + print(p) + p_observations = get_obs(p) + p_tests = p(p_observations) + obs_frame[p["name"]] = p_observations # , p_tests)) + test_frame[p["name"]] = p_tests # , p_tests)) + electro_path = str(os.getcwd()) + "all_tests.p" + with open(electro_path, "wb") as f: + pickle.dump((obs_frame, test_frame), f) + + return (obs_frame, test_frame) + + +def get_tests(backend=str("RAW")): + import neuronunit + + anchor = neuronunit.__file__ + anchor = os.path.dirname(anchor) + mypath = os.path.join(os.sep, anchor, "unit_test/pipe_tests.p") + + # get neuronunit tests + # and select out Rheobase test and input resistance test + # and less about electrophysiology of the membrane. + # We are more interested in phenomonogical properties. + electro_path = mypath + # str(os.getcwd())+'/pipe_tests.p' + def dont_use_cache(): + assert os.path.isfile(electro_path) == True + with open(electro_path, "rb") as f: + electro_tests = pickle.load(f) + + electro_tests = get_common_criteria() + electro_tests = replace_zero_std(electro_tests) + test, observation = electro_tests[0] + tests = copy.copy(electro_tests[0][0]) + suite = sciunit.TestSuite(tests) + + return tests, test, observation, suite + + +def do_use_cache(): + import neuronunit + + anchor = neuronunit.__file__ + anchor = os.path.dirname(anchor) + mypath = os.path.join(os.sep, anchor, "unit_test/pipe_tests.p") + + # get neuronunit tests + # and select out Rheobase test and input resistance test + # and less about electrophysiology of the membrane. + # We are more interested in phenomonogical properties. + electro_path = mypath + assert os.path.isfile(electro_path) == True + with open(electro_path, "rb") as f: + electro_tests = pickle.load(f) + return electro_tests + + +def remake_tests(): + # Use neuroelectro experimental obsevations to find test + # criterion that will be used to inform scientific unit testing. + # some times observations are not sourced from neuroelectro, + # but they are imputated or borrowed from other TestSuite + # if that happens make test objections using observations external + # to this method, and provided as a method argument. + tests = [] + observations = None + test_classes = None + + test_classes = [ + fi.RheobaseTest, + passive.InputResistanceTest, + passive.TimeConstantTest, + passive.CapacitanceTest, + passive.RestingPotentialTest, + ] + electro_obs = do_use_cache() + test_cell_dict = {} + for eo in electro_obs: + tests = [] + for index, t in enumerate(test_classes): + for ind_obs in eo: + test = t(ind_obs) + tests.append(test) + suite = sciunit.TestSuite(tests) + # test_cell_dict[] + + if obs is not None: + if "mean" in obs.keys(): + tests.append(t(obs)) + observations[t.ephysprop_name] = obs + + electro_tests = replace_zero_std(electro_tests) + test, observation = electro_tests[0] + tests = copy.copy(electro_tests[0][0]) + suite = sciunit.TestSuite(tests) + return tests, test, observation, suite diff --git a/neuronunit/allenapi/utils.py b/neuronunit/allenapi/utils.py new file mode 100644 index 000000000..23895bf42 --- /dev/null +++ b/neuronunit/allenapi/utils.py @@ -0,0 +1,345 @@ +import dask +from sciunit.scores import ZScore +from neuronunit.models.optimization_model_layer import OptimizationModel + +# from sciunit import TestSuite +from sciunit.scores.collections import ScoreArray +from mpl_toolkits.mplot3d import Axes3D +import matplotlib.pyplot as plt +from neuronunit.optimization.optimization_management import ( + model_to_rheo, + switch_logic, + active_values, +) +from neuronunit.tests.base import AMPL, DELAY, DURATION +import quantities as pq + +PASSIVE_DURATION = 500.0 * pq.ms +PASSIVE_DELAY = 200.0 * pq.ms +import sciunit +import numpy as np +from bluepyopt.parameters import Parameter + +from neuronunit.optimization.optimization_management import TSD + +import numpy as np +import matplotlib.pyplot as plt + +def dask_map_function(eval_, invalid_ind): + results = [] + for x in invalid_ind: + y = dask.delayed(eval_)(x) + results.append(y) + fitnesses = dask.compute(*results) + return fitnesses + +def glif_specific_modifications(tests): + + """ + Now appropriate for all tests + """ + tests = TSD(tests) + # tests.pop('RheobaseTest',None) + tests.pop("InjectedCurrentAPAmplitudeTest", None) + tests.pop("InjectedCurrentAPThresholdTest", None) + tests.pop("InjectedCurrentAPWidthTest", None) + # tests.pop('InputResistanceTest',None) + # tests.pop('CapacitanceTest',None) + # tests.pop('TimeConstantTest',None) + + return tests + + +def l5pc_specific_modifications(tests): + tests = TSD(tests) + tests.pop("InputResistanceTest", None) + tests.pop("CapacitanceTest", None) + tests.pop("TimeConstantTest", None) + # tests.pop('RestingPotentialTest',None) + + return tests + + +import streamlit as st + + +def make_evaluator( + nu_tests, + MODEL_PARAMS, + experiment=str("Neocortex pyramidal cell layer 5-6"), + model=str("ADEXP"), +): + objectives = [] + + nu_tests[0].score_type = RelativeDifferenceScore + + if "GLIF" in model: + nu_tests_ = glif_specific_modifications(nu_tests) + nu_tests = list(nu_tests_.values()) + simple_cell.name = "GLIF" + + elif "L5PC" in model: + nu_tests_ = l5pc_specific_modifications(nu_tests) + nu_tests = list(nu_tests_.values()) + simple_cell.name = "L5PC" + + else: + simple_cell.name = model + experiment + simple_cell.backend = model + simple_cell.params = {k: np.mean(v) for k, v in simple_cell.params.items()} + + lop = {} + for k, v in MODEL_PARAMS[model].items(): + p = Parameter(name=k, bounds=v, frozen=False) + lop[k] = p + + simple_cell.params = lop + + for tt in nu_tests: + feature_name = tt.name + ft = NUFeature_standard_suite(tt, simple_cell) + objective = ephys.objectives.SingletonObjective(feature_name, ft) + objectives.append(objective) + + score_calc = ephys.objectivescalculators.ObjectivesCalculator(objectives) + + sweep_protocols = [] + for protocol_name, amplitude in [("step1", 0.05)]: + protocol = ephys.protocols.SweepProtocol(protocol_name, [None], [None]) + sweep_protocols.append(protocol) + twostep_protocol = ephys.protocols.SequenceProtocol( + "twostep", protocols=sweep_protocols + ) + + cell_evaluator = ephys.evaluators.CellEvaluator( + cell_model=simple_cell, + param_names=MODEL_PARAMS[model].keys(), + fitness_protocols={twostep_protocol.name: twostep_protocol}, + fitness_calculator=score_calc, + sim="euler", + ) + simple_cell.params_by_names(MODEL_PARAMS[model].keys()) + return cell_evaluator, simple_cell, score_calc, [tt.name for tt in nu_tests] + + +def trace_explore_widget(optimal_model_params=None): + """ + move this to utils file. + Allow app user to explore model behavior around the optimal, + by panning across parameters and then viewing resulting spike shapes. + """ + + attrs = {k: np.mean(v) for k, v in MODEL_PARAMS["IZHI"].items()} + plt.clf() + cnt = 0 + slider_value = st.slider( + "parameter a", min_value=0.01, max_value=0.1, value=0.05, step=0.001 + ) + if optimal_model_params is None: + dtc = OptimizationModel(backend="IZHI", attrs=attrs) + else: + dtc = OptimizationModel(backend="IZHI", attrs=optimal_model_params) + dtc.attrs["a"] = slider_value + dtc = model_to_mode(dtc) + temp_rh = dtc.rheobase + model = dtc.dtc_to_model() + model.attrs = model._backend.default_attrs + model.attrs.update(dtc.attrs) + + uc = {"amplitude": temp_rh, "duration": DURATION, "delay": DELAY} + model._backend.inject_square_current(uc) + vm = model.get_membrane_potential() + plt.plot(vm.times, vm.magnitude) + + cnt += 1 + st.pyplot() + + +def basic_expVar(trace1, trace2): + # https://github.com/AllenInstitute/GLIF_Teeter_et_al_2018/blob/master/query_biophys/query_biophys_expVar.py + """This is the fundamental calculation that is used in all different types of explained variation. + At a basic level, the explained variance is calculated between two traces. These traces can be PSTH's + or single spike trains that have been convolved with a kernel (in this case always a Gaussian) + Input: + trace 1 & 2: 1D numpy array containing values of the trace. (This function requires numpy array + to ensure that this is not a multidemensional list.) + Returns: + expVar: float value of explained variance + """ + + var_trace1 = np.var(trace1) + var_trace2 = np.var(trace2) + var_trace1_minus_trace2 = np.var(trace1 - trace2) + + if var_trace1_minus_trace2 == 0.0: + return 1.0 + else: + return (var_trace1 + var_trace2 - var_trace1_minus_trace2) / ( + var_trace1 + var_trace2 + ) + + +def hof_to_euclid(hof, MODEL_PARAMS, target): + lengths = {} + tv = 1 + cnt = 0 + constellation0 = hof[0] + constellation1 = hof[1] + subset = list(MODEL_PARAMS.keys()) + tg = target.dtc_to_gene(subset_params=subset) + if len(MODEL_PARAMS) == 1: + + ax = plt.subplot() + for k, v in MODEL_PARAMS.items(): + lengths[k] = np.abs(np.abs(v[1]) - np.abs(v[0])) + + x = [h[cnt] for h in hof] + y = [np.sum(h.fitness.values()) for h in hof] + ax.set_xlim(v[0], v[1]) + ax.set_xlabel(k) + tgene = tg[cnt] + yg = 0 + + ax.scatter(x, y, c="b", marker="o", label="samples") + ax.scatter(tgene, yg, c="r", marker="*", label="target") + ax.legend() + + plt.show() + + if len(MODEL_PARAMS) == 2: + + ax = plt.subplot() + for k, v in MODEL_PARAMS.items(): + lengths[k] = np.abs(np.abs(v[1]) - np.abs(v[0])) + + if cnt == 0: + tgenex = tg[cnt] + x = [h[cnt] for h in hof] + ax.set_xlim(v[0], v[1]) + ax.set_xlabel(k) + if cnt == 1: + tgeney = tg[cnt] + + y = [h[cnt] for h in hof] + ax.set_ylim(v[0], v[1]) + ax.set_ylabel(k) + cnt += 1 + + ax.scatter(x, y, c="r", marker="o", label="samples", s=5) + ax.scatter(tgenex, tgeney, c="b", marker="*", label="target", s=11) + + ax.legend() + + plt.sgow() + # print(len(MODEL_PARAMS)) + if len(MODEL_PARAMS) == 3: + fig = plt.figure() + ax = fig.add_subplot(111, projection="3d") + for k, v in MODEL_PARAMS.items(): + lengths[k] = np.abs(np.abs(v[1]) - np.abs(v[0])) + + if cnt == 0: + tgenex = tg[cnt] + + x = [h[cnt] for h in hof] + ax.set_xlim(v[0], v[1]) + ax.set_xlabel(k) + if cnt == 1: + tgeney = tg[cnt] + + y = [h[cnt] for h in hof] + ax.set_ylim(v[0], v[1]) + ax.set_ylabel(k) + if cnt == 2: + tgenez = tg[cnt] + + z = [h[cnt] for h in hof] + ax.set_zlim(v[0], v[1]) + ax.set_zlabel(k) + + cnt += 1 + ax.scatter(x, y, z, c="r", marker="o") + ax.scatter(tgenex, tgeney, tgenez, c="b", marker="*", label="target", s=11) + + plt.show() + + +def initialise_test(v, rheobase): + v = switch_logic([v]) + v = v[0] + k = v.name + if not hasattr(v, "params"): + v.params = {} + if not "injected_square_current" in v.params.keys(): + v.params["injected_square_current"] = {} + if v.passive == False and v.active == True: + keyed = v.params["injected_square_current"] + v.params = active_values(keyed, rheobase) + v.params["injected_square_current"]["delay"] = DELAY + v.params["injected_square_current"]["duration"] = DURATION + if v.passive == True and v.active == False: + + v.params["injected_square_current"]["amplitude"] = -10 * pq.pA + v.params["injected_square_current"]["delay"] = PASSIVE_DELAY + v.params["injected_square_current"]["duration"] = PASSIVE_DURATION + + if v.name in str("RestingPotentialTest"): + v.params["injected_square_current"]["delay"] = PASSIVE_DELAY + v.params["injected_square_current"]["duration"] = PASSIVE_DURATION + v.params["injected_square_current"]["amplitude"] = 0.0 * pq.pA + + return v + + +from sciunit.scores import ZScore +import bluepyopt as bpop +import bluepyopt.ephys as ephys + + +class NUFeature_standard_suite(object): + def __init__(self, test, model): + self.test = test + self.model = model + + def calculate_score(self, responses): + model = responses["model"].dtc_to_model() + model.attrs = responses["params"] + self.test = initialise_test(self.test, responses["rheobase"]) + if "RheobaseTest" in str(self.test.name): + self.test.score_type = ZScore + prediction = {"value": responses["rheobase"]} + score_gene = self.test.compute_score(self.test.observation, prediction) + # lns = np.abs(np.float(score_gene.log_norm_score)) + # return lns + else: + try: + score_gene = self.test.judge(model) + except: + # print(self.test.observation,self.test.name) + # print(score_gene,'\n\n\n') + + return 100.0 + + if not isinstance(type(score_gene), type(None)): + if not isinstance(score_gene, sciunit.scores.InsufficientDataScore): + if not isinstance(type(score_gene.log_norm_score), type(None)): + try: + + lns = np.abs(np.float(score_gene.log_norm_score)) + except: + # works 1/2 time that log_norm_score does not work + # more informative than nominal bad score 100 + lns = np.abs(np.float(score_gene.raw)) + else: + # works 1/2 time that log_norm_score does not work + # more informative than nominal bad score 100 + + lns = np.abs(np.float(score_gene.raw)) + else: + # print(prediction,self.test.observation) + # print(score_gene,'\n\n\n') + lns = 100 + if lns == np.inf: + lns = np.abs(np.float(score_gene.raw)) + # print(lns,self.test.name) + return lns diff --git a/neuronunit/bbp.py b/neuronunit/bbp.py index f09c62bd9..1e9701678 100644 --- a/neuronunit/bbp.py +++ b/neuronunit/bbp.py @@ -1,64 +1,70 @@ -"""NeuronUnit module for interaction with the Allen Brain Insitute -Cell Types database""" +"""NeuronUnit module for interaction with the Blue Brain Project data.""" import os -import sys -import shelve import zipfile -import requests - -import numpy as np +import json +import requests import matplotlib.pyplot as plt -import quantities as pq from neo.io import IgorIO - -try: # Python 2 - from urllib import urlencode, urlretrieve - from urllib2 import urlopen, URLError, HTTPError -except ImportError: # Python 3 - from urllib.parse import urlencode - from urllib.request import urlopen, urlretrieve, URLError, HTTPError +from io import BytesIO +from urllib.request import urlopen, URLError def is_bbp_up(): - url = "http://microcircuits.epfl.ch/released_data/B95_folder.zip" + """Check whether the BBP microcircuit portal is up.""" + url = "http://microcircuits.epfl.ch/data/released_data/B95.zip" request = requests.get(url) return request.status_code == 200 def list_curated_data(): - """ - List of all curated datasets as of July 1st, 2017 found at + """List all curated datasets as of July 1st, 2017. + + Includes those found at http://microcircuits.epfl.ch/#/article/article_4_eph """ - - return ['B95', 'C9', 'C12'] + url = "http://microcircuits.epfl.ch/data/articles/article_4_eph.json" + cells = [] + try: + response = urlopen(url) + except URLError: + print ("Could not find list of curated data at %s" % URL) + else: + data = json.load(response) + table = data['data_table']['table']['rows'] + for section in table: + for row in section: + if 'term' in row: + cell = row['term'].split(' ')[1] + cells.append(cell) + return cells def get_curated_data(data_id, sweeps=None): - """ - Downloads data (Igor files) from the microcircuit portal. - data_id: An ID number like the ones in 'list_curated_data()' that appears in - http://microcircuits.epfl.ch/#/article/article_4_eph. - """ + """Download curated data (Igor files) from the microcircuit portal. + data_id: An ID number like the ones in 'list_curated_data()' that appears + in http://microcircuits.epfl.ch/#/article/article_4_eph. + """ url = "http://microcircuits.epfl.ch/data/released_data/%s.zip" % data_id data = get_sweeps(url, sweeps=sweeps) return data def get_uncurated_data(data_id, sweeps=None): + """Download uncurated data (Igor files) from the microcircuit portal.""" url = "http://microcircuits.epfl.ch/data/uncurated/%s_folder.zip" % data_id data = get_sweeps(url, sweeps=sweeps) return data def get_sweeps(url, sweeps=None): + """Get sweeps of data from the given URL.""" print("Getting data from %s" % url) - path = find_or_download_data(url) # Base path for this data - assert type(sweeps) in [type(None),list], "Sweeps must be None or a list." - sweep_paths = list_sweeps(path) # Available sweeps + path = find_or_download_data(url) # Base path for this data + assert type(sweeps) in [type(None), list], "Sweeps must be None or a list." + sweep_paths = list_sweeps(path) # Available sweeps if sweeps is None: sweeps = sweep_paths else: @@ -67,42 +73,46 @@ def get_sweeps(url, sweeps=None): if any([sweep_path.endswith(sweep for sweep in sweeps)]): sweeps.append(sweep_path) sweeps = set(sweeps) - data = {sweep:open_data(sweep) for sweep in sweeps} + data = {sweep: open_data(sweep) for sweep in sweeps} return data def find_or_download_data(url): - """Return a path to a local directory containing the unzipped data found + """Find or download data from the given URL. + + Return a path to a local directory containing the unzipped data found at the provided url. The zipped file will be downloaded and unzipped if the directory cannot be found. The path to the directory is returned. """ - - zipped = url.split('/')[-1] # Name of zip file - unzipped = zipped.split('.')[0] # Name when unzipped - if not os.path.isdir(unzipped): # If unzipped version not found - downloaded_file, headers = urlretrieve(url) - with zipfile.ZipFile(downloaded_file,"r") as zip_ref: - zip_ref.extractall(unzipped) + zipped = url.split('/')[-1] # Name of zip file + unzipped = zipped.split('.')[0] # Name when unzipped + z = None + if not os.path.isdir(unzipped): # If unzipped version not found + r = requests.get(url) + z = zipfile.ZipFile(BytesIO(r.content)) + z.extractall(unzipped) return unzipped def list_sweeps(url, extension='.ibw'): - path = find_or_download_data(url) # Base path for this data + """List all sweeps available in the file at the given URL.""" + path = find_or_download_data(url) # Base path for this data sweeps = find_sweeps(path, extension=extension) return sweeps def find_sweeps(path, extension='.ibw', depth=0): - """Starting from 'path', recursively searches subdirectories and returns - full paths to all files ending with 'extension'. - """ + """Find sweeps available at the given path. + Starting from 'path', recursively searches subdirectories and returns + full paths to all files ending with 'extension'. + """ sweeps = [] items = os.listdir(path) for item in items: - new_path = os.path.join(path,item) - if os.path.isdir(new_path): - sweeps += find_sweeps(new_path,extension=extension,depth=depth+1) + new_path = os.path.join(path, item) + if os.path.isdir(new_path): + sweeps += find_sweeps(new_path, extension=extension, depth=depth+1) if os.path.isfile(new_path) and item.endswith(extension): sweeps += [new_path] return sweeps @@ -110,16 +120,13 @@ def find_sweeps(path, extension='.ibw', depth=0): def open_data(path): """Take a 'path' to an .ibw file and returns a neo.core.AnalogSignal.""" - igor_io = IgorIO(filename=path) analog_signal = igor_io.read_analogsignal() return analog_signal def plot_data(signal): - """Plots the data in a neo.core.AnalogSignal.""" - - plt.plot(signal.times,signal) + """Plot the data in a neo.core.AnalogSignal.""" + plt.plot(signal.times, signal) plt.xlabel(signal.sampling_period.dimensionality) plt.ylabel(signal.dimensionality) - #plt.show() diff --git a/neuronunit/capabilities/__init__.py b/neuronunit/capabilities/__init__.py index b457f948b..68bd7c368 100644 --- a/neuronunit/capabilities/__init__.py +++ b/neuronunit/capabilities/__init__.py @@ -1,14 +1,15 @@ -"""NeuronUnit abstract Capabilities""" -# The goal is to enumerate all possible capabilities of a model -# that would be tested using NeuronUnit. -# These capabilities exchange 'neo' objects. +"""NeuronUnit abstract Capabilities. -import inspect +The goal is to enumerate all possible capabilities of a model that would be +tested using NeuronUnit. These capabilities exchange 'neo' objects. +""" import numpy as np - +import quantities as pq import sciunit -from .spike_functions import spikes2amplitudes,spikes2widths,spikes2thresholds +import matplotlib.pyplot as plt +from .spike_functions import spikes2amplitudes, spikes2widths,\ + spikes2thresholds class ProducesMembranePotential(sciunit.Capability): @@ -19,83 +20,106 @@ def get_membrane_potential(self, **kwargs): raise NotImplementedError() def get_mean_vm(self, **kwargs): + """Get the mean membrane potential.""" vm = self.get_membrane_potential(**kwargs) return np.mean(vm.base) def get_median_vm(self, **kwargs): + """Get the median membrane potential.""" vm = self.get_membrane_potential(**kwargs) return np.median(vm.base) def get_std_vm(self, **kwargs): + """Get the standard deviation of the membrane potential.""" vm = self.get_membrane_potential(**kwargs) return np.std(vm.base) def get_iqr_vm(self, **kwargs): + """Get the inter-quartile range of the membrane potential.""" vm = self.get_membrane_potential(**kwargs) - return (np.percentile(vm,75) - np.percentile(vm,25))*vm.units + return (np.percentile(vm, 75) - np.percentile(vm, 25))*vm.units + + def get_initial_vm(self, **kwargs): + """Return a quantity corresponding to the starting membrane potential. - def get_initial_vm(self,**kwargs): - """Returns a quantity corresponding to the starting membrane potential. - This will in some cases be the resting potential.""" + This will in some cases be the resting potential. + """ vm = self.get_membrane_potential(**kwargs) - # A neo.core.AnalogSignal object - return vm[0] + return vm[0] # A neo.core.AnalogSignal object + + def plot_membrane_potential(self, ax=None, ylim=(None, None), **kwargs): + """Plot the membrane potential.""" + vm = self.get_membrane_potential(**kwargs) + if ax is None: + ax = plt.gca() + vm = vm.rescale('mV') + ax.plot(vm.times, vm) + y_min = float(vm.min()-5.0*pq.mV) if ylim[0] is None else ylim[0] + y_max = float(vm.max()+5.0*pq.mV) if ylim[1] is None else ylim[1] + ax.set_xlim(vm.times.min(), vm.times.max()) + ax.set_ylim(y_min, y_max) + ax.set_xlabel('Time (s)') + ax.set_ylabel('Vm (mV)') class ProducesSpikes(sciunit.Capability): - """Indicates that the model produces spikes. + """Indicate that the model produces spikes. + No duration is required for these spikes. """ def get_spike_train(self): - """Gets computed spike times from the model. + """Get computed spike times from the model. Arguments: None. Returns: a neo.core.SpikeTrain object. """ - raise NotImplementedError() def get_spike_count(self): + """Get the number of spikes.""" spike_train = self.get_spike_train() return len(spike_train) -class ProducesActionPotentials(ProducesSpikes, +class ProducesActionPotentials(ProducesSpikes, ProducesMembranePotential): - """Indicates the model produces action potential waveforms. + """Indicate the model produces action potential waveforms. + Waveforms must have a temporal extent. """ def get_APs(self): - """Gets action potential waveform chunks from the model. + """Get action potential waveform chunks from the model. Returns ------- Must return a neo.core.AnalogSignal. Each column of the AnalogSignal should be a spike waveform. """ - raise NotImplementedError() def get_AP_widths(self): + """Get widths of action potentials.""" action_potentials = self.get_APs() widths = spikes2widths(action_potentials) return widths def get_AP_amplitudes(self): + """Get amplitudes of action potentials.""" action_potentials = self.get_APs() amplitudes = spikes2amplitudes(action_potentials) return amplitudes def get_AP_thresholds(self): + """Get thresholds of action potentials.""" action_potentials = self.get_APs() thresholds = spikes2thresholds(action_potentials) return thresholds class ReceivesSquareCurrent(sciunit.Capability): - """Indicates that somatic current can be injected into the model as + """Indicate that somatic current can be injected into the model as a square pulse. """ @@ -115,24 +139,16 @@ def inject_square_current(self, current): class ReceivesCurrent(ReceivesSquareCurrent): - """Indicates that somatic current can be injected into the model as + """Indicate that somatic current can be injected into the model as either an arbitrary waveform or as a square pulse. """ def inject_current(self, current): - """Injects somatic current into the model. + """Inject somatic current into the model. Parameters ---------- current : neo.core.AnalogSignal This is a time series of the current to be injected. """ - raise NotImplementedError() - - -class Runnable(sciunit.Capability): - """Capability for models that can be run.""" - - def run(self, **run_params): - return NotImplementedError("%s not implemented" % inspect.stack()[0][3]) diff --git a/neuronunit/capabilities/channel.py b/neuronunit/capabilities/channel.py index 53053dc73..3e285b386 100644 --- a/neuronunit/capabilities/channel.py +++ b/neuronunit/capabilities/channel.py @@ -4,26 +4,29 @@ import sciunit -class NML2_Channel_Runnable(sciunit.Capability): - """Capability for models that can be run using functions available in pyNeuroML.analsysi.NML2ChannelAnalysis""" - def NML2_channel_run(self,**run_params): - return NotImplementedError("%s not implemented" % inspect.stack()[0][3]) - - -class ProducesIVCurve(sciunit.Capability): - """The capability to produce a current-voltage plot for a set of voltage steps""" - def produce_iv_curve(self, **run_params): - """Produces steady-state and peak IV curve at voltages and conditions given according to 'run_params'""" - return NotImplementedError("%s not implemented" % inspect.stack()[0][3]) - - def produce_iv_curve_ss(self, **run_params): - """Produces steady-state IV curve at voltages and conditions given according to 'run_params'""" - return NotImplementedError("%s not implemented" % inspect.stack()[0][3]) - - def produce_iv_curve_peak(self, **run_params): - """Produces peak current IV curve at voltages and conditions given according to 'run_params'""" - return NotImplementedError("%s not implemented" % inspect.stack()[0][3]) - + +class NML2ChannelAnalysis(sciunit.Capability): + """Capability for models that can be altered using functions available + in pyNeuroML.analsysi.NML2ChannelAnalysis""" + + def ca_make_lems_file(self, **run_params): + """Makes a LEMS file using the provided run parameters using + the ChannelAnalysis module.""" + return NotImplementedError("%s not implemented" % + inspect.stack()[0][3]) + + def ca_run_lems_file(self): + """Run the LEMS file using ChannelAnalysis module.""" + return NotImplementedError("%s not implemented" % + inspect.stack()[0][3]) + + def compute_iv_curve(self, results): + """Compute an IV Curve from the iv data in `results`.""" + return NotImplementedError("%s not implemented" % + inspect.stack()[0][3]) + def plot_iv_curve(self, v, i, *plt_args, **plt_kwargs): - """Plots IV Curve using array-like voltage 'v' and array-like current 'i'""" - return NotImplementedError("%s not implemented" % inspect.stack()[0][3]) \ No newline at end of file + """Plots IV Curve using array-like voltage 'v' + and array-like current 'i'""" + return NotImplementedError("%s not implemented" % + inspect.stack()[0][3]) diff --git a/neuronunit/capabilities/morphology.py b/neuronunit/capabilities/morphology.py new file mode 100755 index 000000000..8a36cc0bc --- /dev/null +++ b/neuronunit/capabilities/morphology.py @@ -0,0 +1,16 @@ +"""NeuronUnit abstract Capabilities for multicompartment cell models""" + +import inspect +import sciunit + +class ProducesSWC(sciunit.Capability): + ''' + The capability to produce a morphology SWC file + ''' + def produce_swc(self): + ''' + Produces morphology description file in SWC file format + + :return: absolute path to the produced SWC file + ''' + return NotImplementedError("%s not implemented" % inspect.stack()[0][3]) \ No newline at end of file diff --git a/neuronunit/capabilities/spike_functions.py b/neuronunit/capabilities/spike_functions.py index 3cbaee504..6e30339a2 100644 --- a/neuronunit/capabilities/spike_functions.py +++ b/neuronunit/capabilities/spike_functions.py @@ -1,26 +1,34 @@ -"""Auxiliary helper functions for analysis of spiking""" +"""Auxiliary helper functions for analysis of spiking.""" import numpy as np import neo from elephant.spike_train_generation import threshold_detection from quantities import mV, ms - +from numba import jit import sciunit +import math + def get_spike_train(vm, threshold=0.0*mV): """ + Inputs: vm: a neo.core.AnalogSignal corresponding to a membrane potential trace. threshold: the value (in mV) above which vm has to cross for there to be a spike. Scalar float. + Returns: a neo.core.SpikeTrain containing the times of spikes. """ - spike_train = threshold_detection(vm,threshold=threshold) + spike_train = threshold_detection(vm, threshold=threshold) return spike_train -# Membrane potential trace (1D numpy array) to matrix of spike snippets (2D numpy array) + def get_spike_waveforms(vm, threshold=0.0*mV, width=10*ms): """ + Membrane potential trace (1D numpy array) to matrix of + spike snippets (2D numpy array) + + Inputs: vm: a neo.core.AnalogSignal corresponding to a membrane potential trace. threshold: the value (in mV) above which vm has to cross for there to be a spike. Scalar float. @@ -28,25 +36,45 @@ def get_spike_waveforms(vm, threshold=0.0*mV, width=10*ms): centered at the spike peak. Returns: - a neo.core.AnalogSignal where each column contains a membrane potential + a neo.core.AnalogSignal where each column contains a membrane potential snippets corresponding to one spike. """ - spike_train = threshold_detection(vm,threshold=threshold) + spike_train = threshold_detection(vm, threshold=threshold) # Fix for 0-length spike train issue in elephant. try: - len(spike_train) + assert len(spike_train) != 0 except TypeError: - spike_train = neo.core.SpikeTrain([],t_start=spike_train.t_start, - t_stop=spike_train.t_stop, - units=spike_train.units) + spike_train = neo.core.SpikeTrain([], t_start=spike_train.t_start, + t_stop=spike_train.t_stop, + units=spike_train.units) + + too_short = True + too_long = True + + # This code checks that you are not asking for a window into an array, + # with out of bounds indicies. + t = spike_train[0] + if t-width/2.0 > 0.0*ms: + too_short = False + + t = spike_train[-1] + if t+width/2.0 < vm.times[-1]: + too_long = False + if not too_short and not too_long: + snippets = [vm.time_slice(t-width/2, t+width/2) for t in spike_train] + elif too_long: + snippets = [vm.time_slice(t-width/2, t) for t in spike_train] + elif too_short: + snippets = [vm.time_slice(t, t+width/2) for t in spike_train] - snippets = [vm.time_slice(t-width/2,t+width/2) for t in spike_train] result = neo.core.AnalogSignal(np.array(snippets).T.squeeze(), units=vm.units, sampling_rate=vm.sampling_rate) + return result + def spikes2amplitudes(spike_waveforms): """ IN: @@ -56,12 +84,13 @@ def spikes2amplitudes(spike_waveforms): 1D numpy array of spike amplitudes, i.e. the maxima in each waveform. """ - if spike_waveforms is not None: - ampls = np.max(np.array(spike_waveforms),axis=0) - else: - ampls = np.array([]) + n_spikes = spike_waveforms.shape[1] + ampls = np.array([spike_waveforms[:, i].max() for i in range(n_spikes)]) + if n_spikes: + # Add units. + ampls = ampls * spike_waveforms[:, 0].units + return ampls - return ampls * spike_waveforms.units def spikes2widths(spike_waveforms): """ @@ -75,30 +104,40 @@ def spikes2widths(spike_waveforms): n_spikes = spike_waveforms.shape[1] widths = [] for i in range(n_spikes): - s = spike_waveforms[:,i].squeeze() - x_high = int(np.argmax(s)) - high = s[x_high] + s = spike_waveforms[:, i].squeeze() + try: + x_high = int(np.argmax(s)) + high = s[x_high] + except: + high = 0 + for k in s: + for i, j in enumerate(k): + if j > high: + high = j + x_high = i + if x_high > 0: - try: # Use threshold to compute half-max. + try: # Use threshold to compute half-max. y = np.array(s) dvdt = np.diff(y) trigger = dvdt.max()/10 x_loc = int(np.where(dvdt >= trigger)[0][0]) thresh = (s[x_loc]+s[x_loc+1])/2 mid = (high+thresh)/2 - except: # Use minimum value to compute half-max. + except: # Use minimum value to compute half-max. sciunit.log(("Could not compute threshold; using pre-spike " "minimum to compute width")) low = np.min(s[:x_high]) mid = (high+low)/2 - n_samples = sum(s>mid) # Number of samples above the half-max. + n_samples = sum(s > mid) # Number of samples above the half-max. widths.append(n_samples) - widths = np.array(widths,dtype='float') + widths = np.array(widths, dtype='float') if n_spikes: # Convert from samples to time. widths = widths*spike_waveforms.sampling_period return widths + def spikes2thresholds(spike_waveforms): """ IN: @@ -116,10 +155,9 @@ def spikes2thresholds(spike_waveforms): n_spikes = spike_waveforms.shape[1] thresholds = [] for i in range(n_spikes): - s = spike_waveforms[:,i].squeeze() + s = spike_waveforms[:, i].squeeze() s = np.array(s) dvdt = np.diff(s) - import math for j in dvdt: if math.isnan(j): return thresholds * spike_waveforms.units diff --git a/neuronunit/cellmodelp.py b/neuronunit/cellmodelp.py new file mode 100644 index 000000000..d775b3b64 --- /dev/null +++ b/neuronunit/cellmodelp.py @@ -0,0 +1,1720 @@ +# Obtains the cell threshold, rheobase, resting v, and bias currents for +# steady state v of a cell defined in a hoc file in the given directory. +# Usage: python getCellProperties /path/To/dir/with/.hoc + +import _pickle as cPickle +import csv +import json +import os +import re +import shutil +import string +import urllib +from abc import abstractmethod, ABCMeta +from decimal import Decimal +import inspect +import multiprocessing +cpus = multiprocessing.cpu_count + +import numpy as np +from matplotlib import pyplot as plt +try: + from playhouse.db_url import connect + from sshtunnel import SSHTunnelForwarder + from collector import Collector + from neuronrunner import NeuronRunner, NumericalInstabilityException + from sklearn.decomposition import PCA + from runtimer import RunTimer + from tables import Cells, Model_Waveforms, Morphometrics, Cell_Morphometrics, db_proxy, Models + from nmldbmodel import NMLDB_Model +except: # Hack to allow import to occur so unit tests can pass + print("Many modules required by `cellmodelp` not found") + NMLDB_Model = object +import dask.bag as db # a pip installable module, usually installs without complication +import dask + +from neuronunit.tests.druckmann2013 import * + +class CellModel(NMLDB_Model): + def __init__(self, *args, **kwargs): + super(CellModel, self).__init__(*args, **kwargs) + + self.init_cell_record() + + if self.cell_record.Steady_State_Delay is None: + self.steady_state_delay = 1000 + else: + self.steady_state_delay = self.cell_record.Steady_State_Delay + + self.pickle_file_cache = {} + + self.all_properties.extend([ + 'NEURON_conversion', + 'equation_count', + 'runtime_per_step', + 'structural_metrics', + 'tolerances', + 'stability_range', + 'resting_voltage', + 'threshold', + 'rheobase', + 'DT_SENSITIVITY', + 'stability_range', + 'threshold', + 'rheobase', + 'bias_current', + 'tolerances_with_stim', + 'CVODE_STEP_FREQUENCIES', + 'STEADY_STATE', + 'RAMP', + 'SHORT_SQUARE', + 'SQUARE', + 'LONG_SQUARE', + 'SHORT_SQUARE_HOLD', + 'SHORT_SQUARE_TRIPPLE', + 'SQUARE_SUBTHRESHOLD', + 'DRUCKMANN_PROPERTIES' + # 'NOISE', + # 'NOISE_RAMP', + # 'morphology_data' + ]) + + self.init_cell_record() + + def init_cell_record(self): + self.server.connect() + + self.cell_record = Cells.get_or_none(Cells.Model_ID == self.get_model_nml_id()) + + if self.cell_record is None: + self.cell_record = Cells( + Model_ID=self.get_model_nml_id(), + Stability_Range_Low=None, + Stability_Range_High=None, + Is_Intrinsically_Spiking=False, + Resting_Voltage=None, + Rheobase_Low=None, + Rheobase_High=None, + Threshold_Current_Low=None, + Threshold_Current_High=None, + Bias_Current=None, + Bias_Voltage=None, + Errors=None + ) + + # Create cell record if it doesn't exist (using the NMLDB ID as the pkey) + self.cell_record.save(force_insert=True) + + # Retrieve the freshly created record + self.cell_record = Cells.get_or_none(Cells.Model_ID == self.get_model_nml_id()) + + def save_stability_range(self): + + if self.is_nosim(): + return + + self.use_optimal_dt_if_available() + + print("Getting stability range...") + self.cell_record.Stability_Range_Low, self.cell_record.Stability_Range_High = self.get_stability_range() + + + assert self.cell_record.Stability_Range_Low < self.cell_record.Stability_Range_High + + self.cell_record.save() + + def save_resting_voltage(self): + + if self.is_nosim(): + return + + self.use_optimal_dt_if_available() + + print("Getting resting voltage...") + self.cell_record.Resting_Voltage = self.getRestingV(self.steady_state_delay, save_resting_state=True)["rest"] + + + # No resting v means cell is intrinsically spiking + self.cell_record.Is_Intrinsically_Spiking = self.cell_record.Resting_Voltage is None + + if not self.cell_record.Is_Intrinsically_Spiking: + assert self.cell_record.Resting_Voltage < 1.0 # Allen Glif models rest at 0 + + self.cell_record.save() + + + def save_threshold(self): + + if self.is_nosim(): + return + + if self.cell_record.Is_Intrinsically_Spiking or self.cell_record.Is_Passive: + return + + self.use_optimal_dt_if_available() + + print("Getting threshold...") + + th = self.getThreshold(0, self.cell_record.Stability_Range_High) + self.cell_record.Threshold_Current_Low = np.min(th) + self.cell_record.Threshold_Current_High = np.max(th) + + + + assert self.cell_record.Threshold_Current_Low < self.cell_record.Threshold_Current_High + + self.cell_record.save() + + + def save_rheobase(self): + if self.is_nosim(): + return + + if self.cell_record.Is_Intrinsically_Spiking or self.cell_record.Is_Passive: + return + + self.use_optimal_dt_if_available() + + print("Getting rheobase...") + rb = self.getRheobase(0, self.cell_record.Threshold_Current_High) + self.cell_record.Rheobase_Low = np.min(rb) + self.cell_record.Rheobase_High = np.max(rb) + + + + assert self.cell_record.Rheobase_Low < self.cell_record.Rheobase_High + assert self.cell_record.Rheobase_High < self.cell_record.Threshold_Current_High + + self.cell_record.save() + + def save_bias_current(self): + if self.is_nosim(): + return + + if self.cell_record.Is_Intrinsically_Spiking or self.cell_record.Is_Passive: + return + + self.use_optimal_dt_if_available() + + print("Getting current for bias voltage...") + roundedRest = round(self.cell_record.Resting_Voltage / 10) * 10 + + if roundedRest == -80: + bias_v = -70 + else: + bias_v = -80 + + bias_i = self.getBiasCurrent(targetV=bias_v) + + self.cell_record.Bias_Voltage = bias_v + self.cell_record.Bias_Current = bias_i + + assert self.cell_record.Bias_Current < self.cell_record.Rheobase_High + + if self.cell_record.Bias_Voltage < self.cell_record.Resting_Voltage: + assert self.cell_record.Bias_Current < 0 + else: + assert self.cell_record.Bias_Current > 0 + + self.cell_record.save() + + def get_number_of_compartments(self, h): + if self.is_abstract_cell(): + return 1 + + return sum(s.nseg for s in h.allsec()) + + def save_to_SWC(self, h): + import xml.etree.ElementTree + + root = xml.etree.ElementTree.parse(self.model_record.File_Name).getroot() + + seg_tags = root.findall(".//{http://www.neuroml.org/schema/neuroml2}segment") + + point_ids = {} + self.current_id = 1 + + def add_point(prox_dist): + if len(prox_dist) > 0: + point_str = str(prox_dist[0].attrib) + + if point_str not in point_ids: + point_ids[point_str] = str(self.current_id) + self.current_id += 1 + + for seg_tag in seg_tags: + proximal = seg_tag.findall('{http://www.neuroml.org/schema/neuroml2}proximal') + distal = seg_tag.findall('{http://www.neuroml.org/schema/neuroml2}distal') + + add_point(proximal) + add_point(distal) + + segment_distal_point_ids = {} + + for seg_tag in seg_tags: + distal = seg_tag.findall('{http://www.neuroml.org/schema/neuroml2}distal') + point_id = point_ids[str(distal[0].attrib)] + segment_distal_point_ids[seg_tag.attrib["id"]] = point_id + + swc_points = [] + + def get_type(seg_tag): + seg_name = seg_tag.attrib["name"].lower() + + if "dend" in seg_name: + return "3" + + if "axon" in seg_name: + return "2" + + if "soma" in seg_name: + return "1" + + return "5" + + for tag in seg_tags: + parent_tag = tag.findall('{http://www.neuroml.org/schema/neuroml2}parent') + proximal = tag.findall('{http://www.neuroml.org/schema/neuroml2}proximal') + distal = tag.findall('{http://www.neuroml.org/schema/neuroml2}distal') + + if parent_tag: + + # parent - with prox - use proximal as parent id + if proximal: + parent_id = point_ids[str(proximal[0].attrib)] + + # If diameter of proximal is not the same as parent's distal - add as separate point + if parent_id not in [pt["id"] for pt in swc_points]: + if parent_tag[0].attrib["segment"] not in segment_distal_point_ids: + raise Exception("Segment refers to non-existent parent segment: " + str(parent_tag[0].attrib["segment"])) + + swc_point = { + "id": parent_id, + "type": get_type(tag), + "parent": segment_distal_point_ids[parent_tag[0].attrib["segment"]], + "x": proximal[0].attrib["x"], + "y": proximal[0].attrib["y"], + "z": proximal[0].attrib["z"], + "radius": str(float(proximal[0].attrib["diameter"]) / 2.0) + } + + swc_points.append(swc_point) + + # parent - no prox - use parent's distal as parent id + else: + parent_id = segment_distal_point_ids[parent_tag[0].attrib["segment"]] + + # no parent - add proximal - will become distal's parent + else: + swc_point = { + "id": point_ids[str(proximal[0].attrib)], + "type": get_type(tag), + "parent": "-1", + "x": proximal[0].attrib["x"], + "y": proximal[0].attrib["y"], + "z": proximal[0].attrib["z"], + "radius": str(float(proximal[0].attrib["diameter"]) / 2.0) + } + + parent_id = swc_point["id"] + + swc_points.append(swc_point) + + + # Always add distal + swc_point = { + "id": point_ids[str(distal[0].attrib)], + "type": get_type(tag), + "parent": str(parent_id), + "x": distal[0].attrib["x"], + "y": distal[0].attrib["y"], + "z": distal[0].attrib["z"], + "radius": str(float(distal[0].attrib["diameter"]) / 2.0) + } + + swc_points.append(swc_point) + + swc_file_path = os.path.join(self.get_conversion_dir("swc"),"cell.swc") + + with open(swc_file_path, "w") as file: + for point in swc_points: + file.write( + point["id"] + " " + + point["type"] + " " + + point["x"] + " " + + point["y"] + " " + + point["z"] + " " + + point["radius"] + " " + + point["parent"] + "\n") + + return os.path.abspath(swc_file_path) + + def save_LMeasure_metrics(self, swc_file): + + db = self.server.connect() + cell_id = self.get_model_nml_id() + + # Do all the work within a transaction + with db.atomic(): + # Clear out existing cell metrics + Cell_Morphometrics.delete().where(Cell_Morphometrics.Cell == cell_id).execute() + + # Get a list of metrics + metrics = Morphometrics.select() + + for metric in metrics: + # Compute the metric with lmeasure + f = metric.Function_ID + swc_file = swc_file.replace(os.path.abspath(os.getcwd()) + "/", "") + os.system('../../lmeasure -f'+str(f)+',0,0,10.0 -slmeasure_out.csv '+swc_file+' -C') + + # Read the result + with open('lmeasure_out.csv') as f: + line = list(csv.reader(f, delimiter="\t"))[0] + + # Make sure the db function id corresponds to the Lmeasure function name + assert line[1].startswith(metric.ID) + + # Save to DB + record = Cell_Morphometrics( + Cell=cell_id, + Metric=metric, + Total = float(line[2]), + Compartments_Considered=int(line[3]), + Compartments_Discarded=int(line[4].replace("(", "").replace(")", "")), + Minimum = float(line[5]), + Average = float(line[6]), + Maximum = float(line[7]), + StDev = float(line[8]) + ) + + record.save() + + # Cleanup lmeasure files + os.system("rm lmeasure_out.csv") + + def save_morphology_data(self): + + # Load the model + h = self.build_model(restore_tolerances=False) + + if self.is_abstract_cell() or self.get_number_of_compartments(h) <= 1: + print("Cell is ABSTRACT or SINGLE COMPARTMENT, skipping morphometrics and 3D visualization...") + return + + # Compute morphometrics + self.save_morphometrics(h) + + # Render 3D GIF + # Rotate the cell to be upright along the x,y,z coord PCA axes + self.rotate_cell_along_PCA_axes(h) + + import sys + sys.path.append('/home/justas/Repositories/BlenderNEURON/ForNEURON') + from blenderneuron import BlenderNEURON + + bl = BlenderNEURON(h) + bl.prepare_for_collection() + + # Skip simulation if no basic properties are present + if self.cell_record.Is_Intrinsically_Spiking is not None: + # Inject continuous above rheobase current + if self.cell_record.Rheobase_High is not None: + self.current.delay = 0 + self.current.dur = 100 + self.current.amp = self.cell_record.Rheobase_High * 1.5 + + self.use_optimal_dt_if_available() + h.steps_per_ms = 10 + h.cvode_active(self.config.cvode_active) + h.dt = self.config.dt + + # No additional stim for intrinsic spikers + print("Simulating current injection...") + h.tstop = 100.0 + h.newPlotV() + h.run() + + self.save_rotating_gif(bl) + + + def save_rotating_gif(self, bl): + bl.enqueue_method("clear") + bl.enqueue_method('set_render_params', file_format="JPEG2000") + bl.send_model() + bl.enqueue_method('link_objects') + bl.enqueue_method('show_full_scene') + bl.enqueue_method('color_by_unique_materials') + bl.enqueue_method('orbit_camera_around_model') + + # Remove previous blend file + os.system("rm " + os.path.join(self.get_conversion_dir("Blender"), "*.blend")) + + bl.run_method('save_scene',os.path.join(self.get_conversion_dir("Blender"),"cell.blend")) + + + print("RENDERING... Check progress in Blender command line window...") + + # Wait till prev tasks and rendering is finished + bl.run_method('render_animation', self.get_conversion_dir("gif")) + + print("Creating GIF from rendered frames...") + self.make_gif_from_frames(self.get_conversion_dir("gif")) + + def save_morphometrics(self, h): + swc = self.save_to_SWC(h) + self.save_LMeasure_metrics(swc) + + def rotate_cell_along_PCA_axes(self, h): + sections = [s for s in h.allsec()] + + # Using the first and last coords of sections + coords = [[h.x3d(0, sec=s), + h.y3d(0, sec=s), + h.z3d(0, sec=s)] for s in sections] \ + + \ + [[h.x3d(h.n3d(sec=s) - 1, sec=s), + h.y3d(h.n3d(sec=s) - 1, sec=s), + h.z3d(h.n3d(sec=s) - 1, sec=s)] for s in sections] + + coords = np.array(coords) + + # Get the PCA components + pca = PCA() + pca.fit(coords) + + # Rotate each section point to be along the PCA axes + for sec in sections: + for i in range(int(h.n3d(sec=sec))): + transformed = pca.transform([[h.x3d(i, sec=sec), h.y3d(i, sec=sec), h.z3d(i, sec=sec)]]) + + x = transformed[0][2] + y = transformed[0][1] + z = transformed[0][0] + diam = h.diam3d(i, sec=sec) + + h.pt3dchange(i, x, y, z, diam, sec=sec) + + def save_tolerances_with_stim(self): + if self.is_nosim(): + return + + self.save_tolerances(current_amp=0 if self.cell_record.Is_Intrinsically_Spiking else self.cell_record.Threshold_Current_High) + + def save_STEADY_STATE(self): + """ + Reach steady state and save model state + :return: None + """ + + self.remove_protocol_waveforms("STEADY_STATE") + + if self.is_nosim(): + return + + self.use_optimal_dt_if_available() + + result = self.getRestingV(save_resting_state=True, run_time=self.steady_state_delay) + self.save_tvi_plot(label="STEADY STATE", tvi_dict=result) + self.save_vi_waveforms(protocol="STEADY_STATE", tvi_dict=result) + + + def save_RAMP(self): + """ + From steady state, run ramp injection + :return: None + """ + + self.remove_protocol_waveforms("RAMP") + + if self.is_nosim(): + return + + if self.cell_record.Is_Intrinsically_Spiking or self.cell_record.Is_Passive: + return + + self.use_optimal_dt_if_available() + + result = self.get_ramp_response(ramp_delay=self.steady_state_delay, + ramp_max_duration=5 * 1000, + ramp_increase_rate_per_second=self.cell_record.Rheobase_High, + stop_after_n_spikes_found=10, + restore_state=True) + + self.save_tvi_plot(label="RAMP", tvi_dict=result) + self.save_vi_waveforms(protocol="RAMP", tvi_dict=result) + + def save_SHORT_SQUARE(self): + """ + # Short square is a brief, threshold current pulse after steady state + :return: None + """ + + self.remove_protocol_waveforms("SHORT_SQUARE") + + if self.is_nosim(): + return + + if self.cell_record.Is_Intrinsically_Spiking or self.cell_record.Is_Passive: + return + + self.use_optimal_dt_if_available() + + self.save_square_current_set(protocol="SHORT_SQUARE", + square_low=self.cell_record.Threshold_Current_Low, + square_high=self.cell_record.Threshold_Current_High, + square_steps=2, + delay=self.steady_state_delay, + duration=3) + + + def save_SQUARE(self): + + self.remove_protocol_waveforms("SQUARE") + + if self.is_nosim(): + return + + if self.cell_record.Is_Intrinsically_Spiking or self.cell_record.Is_Passive: + return + + self.use_optimal_dt_if_available() + + self.save_square_current_set(protocol="SQUARE", + square_low=-self.cell_record.Rheobase_High * 0.5, # Note the "-" + square_high=self.cell_record.Rheobase_High * 1.5, + square_steps=11, + delay=self.steady_state_delay, + duration=1000) + + + def save_LONG_SQUARE(self): + """ + Long square is a 2s current pulse after steady state + :return: None + """ + + self.remove_protocol_waveforms("LONG_SQUARE") + + if self.is_nosim(): + return + + if self.cell_record.Is_Intrinsically_Spiking or self.cell_record.Is_Passive: + return + + self.use_optimal_dt_if_available() + + # Up to Druckmann 2013 150% of RB - "standard stimulus" + self.save_square_current_set(protocol="LONG_SQUARE", + square_low=self.cell_record.Rheobase_High, + square_high=self.cell_record.Rheobase_High * 1.5, + square_steps=3, + delay=self.steady_state_delay, + duration=2000) + + # Up to Druckmann 2013 300% of RB - "strong stimulus" + self.save_square_current_set(protocol="LONG_SQUARE", + square_low=self.cell_record.Rheobase_High * 3.0, + square_high=self.cell_record.Rheobase_High * 3.0, + square_steps=1, + delay=self.steady_state_delay, + duration=2000) + + + def save_SHORT_SQUARE_HOLD(self): + """ + SHORT_SQUARE_HOLD is a short threshold stimulus, while under bias current + :return: + """ + + self.remove_protocol_waveforms("SHORT_SQUARE_HOLD") + + if self.is_nosim(): + return + + if self.cell_record.Is_Intrinsically_Spiking or self.cell_record.Is_Passive: + return + + self.use_optimal_dt_if_available() + + def get_current_ti(): + ramp_t = [ + 0, + self.steady_state_delay, + self.steady_state_delay, + self.steady_state_delay + 3.0, + self.steady_state_delay + 3.0, + self.steady_state_delay + 250 + ] + ramp_i = [ + self.cell_record.Bias_Current, + self.cell_record.Bias_Current, + -self.cell_record.Bias_Current + self.cell_record.Threshold_Current_High, + -self.cell_record.Bias_Current + self.cell_record.Threshold_Current_High, + self.cell_record.Bias_Current, + self.cell_record.Bias_Current + ] + + return ramp_t, ramp_i + + self.save_arb_current(protocol="SHORT_SQUARE_HOLD", + delay=self.steady_state_delay, + duration=250, + get_current_ti=get_current_ti, + restore_state=False) # Holding v, not resting + + + def save_SHORT_SQUARE_TRIPPLE(self): + + self.remove_protocol_waveforms("SHORT_SQUARE_TRIPPLE") + + if self.is_nosim(): + return + + if self.cell_record.Is_Intrinsically_Spiking or self.cell_record.Is_Passive: + return + + self.use_optimal_dt_if_available() + + self.save_square_tuple_set(delay=self.steady_state_delay, + threshold_current=self.cell_record.Threshold_Current_High) + + def save_SQUARE_SUBTHRESHOLD(self): + """ + Subthreshold pulses to measure capacitance + :return: None + """ + + self.remove_protocol_waveforms("SQUARE_SUBTHRESHOLD") + + if self.is_nosim(): + return + + if self.cell_record.Is_Intrinsically_Spiking or self.cell_record.Is_Passive: + return + + self.use_optimal_dt_if_available() + + self.save_square_current_set(protocol="SQUARE_SUBTHRESHOLD", + square_low=-self.cell_record.Threshold_Current_Low, + square_high=self.cell_record.Threshold_Current_Low, + square_steps=2, + delay=self.steady_state_delay, + duration=0.5) + + def save_NOISE(self): + + self.remove_protocol_waveforms("NOISE") + + if self.is_nosim(): + return + + if self.cell_record.Is_Intrinsically_Spiking or self.cell_record.Is_Passive: + return + + self.use_optimal_dt_if_available() + + self.save_noise_response_set(protocol="NOISE", + meta_protocol="SEED1", + delay=self.steady_state_delay, + duration=3000, + post_delay=250, + rheobase=self.cell_record.Rheobase_High, + multiples=[0.75, 1.0, 1.25], + noise_pickle_file="noise1.pickle", + restore_state=True) + + self.save_noise_response_set(protocol="NOISE", + meta_protocol="SEED2", + delay=self.steady_state_delay, + duration=3000, + post_delay=250, + rheobase=self.cell_record.Rheobase_High, + multiples=[0.75, 1.0, 1.25], + noise_pickle_file="noise2.pickle", + restore_state=True) + + def save_NOISE_RAMP(self): + + self.remove_protocol_waveforms("NOISE_RAMP") + + if self.is_nosim(): + return + + if self.cell_record.Is_Intrinsically_Spiking or self.cell_record.Is_Passive: + return + + self.use_optimal_dt_if_available() + + self.save_noise_response_set(protocol="NOISE_RAMP", + delay=self.steady_state_delay, + duration=32000, + post_delay=250, + rheobase=self.cell_record.Rheobase_High, + multiples=[1.0], + noise_pickle_file="noisyRamp.pickle", + restore_state=True) + + + + def save_DT_SENSITIVITY(self): + + self.remove_protocol_waveforms("DT_SENSITIVITY") + self.remove_protocol_waveforms("OPTIMAL_DT_BENCHMARK") + + if self.is_nosim(): + return + + if self.cell_record.Is_Intrinsically_Spiking or self.cell_record.Is_Passive: + return + + smallest_dt_result = self.save_dt_sensitivity_set(rheobase=self.cell_record.Rheobase_High) + + # Compute the optimal dt based on runtime and error costs + optimal_dt = self.save_optimal_time_step() + + # Get the waveform at the optimal dt and its error + if optimal_dt is not None: + print('Starting OPTIMAL_DT_BENCHMARK protocol...') + + optimal_dt_result = self.save_dt_sensitivity_set( + rheobase=self.cell_record.Rheobase_High, + protocol="OPTIMAL_DT_BENCHMARK", + steps_per_ms_set=[1.0 / optimal_dt], + save_max_stable_dt=False + ) + + # Interpolate the values of the optimal dt waveform to compare to the 0-error waveform + from scipy.interpolate import interp1d + optimal_interpolated = interp1d(optimal_dt_result["t"], optimal_dt_result["v"], kind="cubic", fill_value='extrapolate') + optimal_v_sub = optimal_interpolated(smallest_dt_result["t_sub"]) + + optimal_dt_error = self.compute_waveform_error(smallest_dt_result["v_sub"], + smallest_dt_result["range"], + optimal_v_sub) + + self.model_record.Optimal_DT_Error = optimal_dt_error + self.model_record.save() + + + def save_CVODE_STEP_FREQUENCIES(self): + + self.remove_protocol_waveforms("CVODE_STEP_FREQUENCIES") + + if self.is_nosim(): + return + + if self.cell_record.Is_Intrinsically_Spiking or self.cell_record.Is_Passive: + return + + self.save_cvode_step_frequencies(protocol="CVODE_STEP_FREQUENCIES", + delay=self.steady_state_delay, + sub_rheobase=self.cell_record.Rheobase_Low, + rheobase=self.cell_record.Rheobase_High) + + self.save_cvode_runtime_complexity_metrics() + + def save_DRUCKMANN_PROPERTIES(self): + """ + Tests of features described in Druckmann et. al. 2013 + (https://academic.oup.com/cercor/article/23/12/2994/470476) + + These tests use SQUARE and LONG_SQUARE waveforms obtained from the NMLDB Web API + The waveforms should be uploaded to production server (dendrite) before running these + tests. + :return: None + """ + + if self.is_nosim(): + return + + if self.cell_record.Is_Intrinsically_Spiking or self.cell_record.Is_Passive or self.cell_record.Is_GLIF: + return + + import sciunit, neuronunit, quantities + from neuronunit.neuromldb import NeuroMLDBStaticModel + + model = NeuroMLDBStaticModel(self.get_model_nml_id()) + + standard = model.nmldb_model.get_druckmann2013_standard_current() + strong = model.nmldb_model.get_druckmann2013_strong_current() + ir_currents = model.nmldb_model.get_druckmann2013_input_resistance_currents() + + tests = [ + AP12AmplitudeDropTest(standard), + AP1SSAmplitudeChangeTest(standard), + AP1AmplitudeTest(standard), + AP1WidthHalfHeightTest(standard), + AP1WidthPeakToTroughTest(standard), + AP1RateOfChangePeakToTroughTest(standard), + AP1AHPDepthTest(standard), + AP2AmplitudeTest(standard), + AP2WidthHalfHeightTest(standard), + AP2WidthPeakToTroughTest(standard), + AP2RateOfChangePeakToTroughTest(standard), + AP2AHPDepthTest(standard), + AP12AmplitudeChangePercentTest(standard), + AP12HalfWidthChangePercentTest(standard), + AP12RateOfChangePeakToTroughPercentChangeTest(standard), + AP12AHPDepthPercentChangeTest(standard), + InputResistanceTest(injection_currents=ir_currents), + AP1DelayMeanTest(standard), + AP1DelaySDTest(standard), + AP2DelayMeanTest(standard), + AP2DelaySDTest(standard), + Burst1ISIMeanTest(standard), + Burst1ISISDTest(standard), + InitialAccommodationMeanTest(standard), + SSAccommodationMeanTest(standard), + AccommodationRateToSSTest(standard), + AccommodationAtSSMeanTest(standard), + AccommodationRateMeanAtSSTest(standard), + ISICVTest(standard), + ISIMedianTest(standard), + ISIBurstMeanChangeTest(standard), + SpikeRateStrongStimTest(strong), + AP1DelayMeanStrongStimTest(strong), + AP1DelaySDStrongStimTest(strong), + AP2DelayMeanStrongStimTest(strong), + AP2DelaySDStrongStimTest(strong), + Burst1ISIMeanStrongStimTest(strong), + Burst1ISISDStrongStimTest(strong), + ] + + for i, test in enumerate(tests): + mean = test.generate_prediction(model)['mean'] + field = test.__class__.__name__[:-4] + setattr(self.cell_record, field, mean) + print('Test ' + str(i+1).rjust(2,' ') + ' ' + field.rjust(50, ' ') + ": " + str(mean)) + + self.cell_record.save() + + + def save_square_tuple_set(self, delay, threshold_current, + intervals=[7, 11, 15, 19, 23, 27, 31, 35], stim_width=3, tuples=3): + + # Create a short square triple waveform + def get_current_ti(interval): + ramp_ti = [(0, 0)] + + for ti in range(tuples): + ramp_ti.append((delay + interval * ti, 0)) + ramp_ti.append((delay + interval * ti, threshold_current)) + ramp_ti.append((delay + interval * ti + stim_width, threshold_current)) + ramp_ti.append((delay + interval * ti + stim_width, 0)) + + ramp_ti.append((delay + interval * tuples + 100, 0)) + + # Split the tuple list into t and i lists + return zip(*ramp_ti) + + for interval in intervals: + freq = round(1000.0 / interval) + self.save_arb_current(protocol="SHORT_SQUARE_TRIPPLE", + label=str(freq) + " Hz", + delay=delay, + duration=interval * tuples + 100, + get_current_ti=lambda: get_current_ti(interval), + restore_state=True) + + def save_square_current_set(self, protocol, square_low, square_high, square_steps, delay, duration, post_delay=250): + + # Create current amplitude set + amps = np.linspace( + max(square_low, self.cell_record.Stability_Range_Low), + min(square_high, self.cell_record.Stability_Range_High), + num=square_steps).tolist() + + # Run each injection as a separate simulation, resuming from steady state + for amp in amps: + result = self.get_square_response(delay=delay, + duration=duration, + post_delay=post_delay, + amp=amp, + restore_state=True) + + self.save_tvi_plot(label=protocol, case=self.short_string(amp) + " nA", tvi_dict=result) + + self.save_vi_waveforms(protocol=protocol, + label=self.short_string(amp) + " nA", + tvi_dict=result) + + def get_square_response(self, + delay, + duration, + post_delay, + amp, + restore_state=False): + + def square_protocol(time_flag): + print('Starting SQUARE PROTOCOL...' + str(amp)) + self.time_flag = time_flag + h = self.build_model() + print('Cell model built, starting current injection...') + + # Set the sqauare current injector + self.current.dur = duration + self.current.delay = delay + self.current.amp = amp + + with RunTimer() as timer: + if restore_state: + self.restore_state() + t, v = self.runFor(duration + post_delay) + else: + h.stdinit() + t, v = self.runFor(delay + duration + post_delay) + + result = { + "t": t.tolist(), + "v": v.tolist(), + "i": self.ic_i_collector.get_values_list(), + "run_time": timer.get_run_time(), + "steps": int(self.tvec.size()), + "cvode_active": int(self.config.cvode_active), + "dt_or_atol": self.config.abs_tolerance if self.config.cvode_active else self.config.dt + } + + return result + + runner = NeuronRunner(square_protocol) + runner.DONTKILL = True + result = runner.run() + return result + + def get_ramp_response(self, + ramp_delay, + ramp_max_duration, + ramp_increase_rate_per_second, + stop_after_n_spikes_found, + restore_state=False): + + def test_condition(t, v): + num_spikes = self.getSpikeCount(v) + + if num_spikes >= stop_after_n_spikes_found: + print("Got " + str(num_spikes) + " spikes at " + str(t[-1]) + " ms. Stopping ramp current injection.") + return True + + return False + + def get_current_ti(): + ramp_i = [0, 0, ramp_max_duration / 1000.0 * ramp_increase_rate_per_second, 0] + ramp_t = [0, ramp_delay, ramp_delay + ramp_max_duration, ramp_delay + ramp_max_duration] + return ramp_t, ramp_i + + return self.get_arb_current_response(delay=ramp_delay, + duration=ramp_max_duration, + get_current_ti=get_current_ti, + test_condition=test_condition, + restore_state=restore_state) + + def save_cvode_step_frequencies(self, protocol, delay, sub_rheobase, rheobase): + # Ensure this is run using the variable step method + orig_cvode = self.config.cvode_active + self.config.cvode_active = 1 + + # First two evaluate no stim and sub-threshold current CVODE step frequencies + self.save_square_current_set(protocol=protocol, + square_low=0, + square_high=sub_rheobase, + square_steps=2, + delay=delay, + post_delay=0, + duration=1000) + + # The rest are used to quantify CVODE step frequency per spike + self.save_square_current_set(protocol=protocol, + square_low=rheobase, + square_high=rheobase*1.5, + square_steps=11, + delay=delay, + post_delay=0, + duration=1000) + + # Restore the integration method to as it was before + self.config.cvode_active = orig_cvode + + + def save_dt_sensitivity_set(self, + rheobase, + protocol="DT_SENSITIVITY", + steps_per_ms_set=[1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1], + save_max_stable_dt=True): + """ + :param rheobase: Cell rheobase current + :param protocol: The label of the protocol to use when saving the waveform to DB + :param steps_per_ms_set: A sequence (power of 2 works well to ensure values can be compared at same time points + :param save_max_stable_dt: Record the largest dt that does not blow up the simulation + :return: Nothing + """ + + noise_pickle_file = "dtSensitivity.pickle" + + # Cache the files - they're slow to load + if noise_pickle_file not in self.pickle_file_cache: + with open(os.path.join("..", "..", noise_pickle_file), "r") as f: + self.pickle_file_cache[noise_pickle_file] = cPickle.load(f) + + def get_current_ti(): + noise = self.pickle_file_cache[noise_pickle_file] + + # 100 ms of rest with 50 ms of 0.75 RB square + ramp_ti = [ + (0, 0), + (100, 0), + (100, 0.75 * rheobase), + (150, 0.75 * rheobase) + ] + + # 50 ms of pink noise at 0.75 RB + ramp_ti += zip((np.array(noise["t"]) + 150.0).tolist(), (np.array(noise["i"]) * rheobase * 0.75).tolist()) + + # 100 ms of square at 1.5 RB + ramp_ti += [ + (200, 1.5 * rheobase), + (300, 1.5 * rheobase), + ] + + # Another 50 ms of pink noise at 1.5 RB + ramp_ti += zip((np.array(noise["t"]) + 300.0).tolist(), (np.array(noise["i"]) * rheobase * 1.5).tolist()) + ramp_ti += zip((np.array(noise["t"]) + 350.0).tolist(), (np.array(noise["i"]) * rheobase * 1.5).tolist()) + + # 50 ms of square at -0.25 RB + ramp_ti += [ + (400, -0.25 * rheobase), + (450, -0.25 * rheobase), + ] + + # Another 50 ms of pink noise at -0.25 RB + ramp_ti += zip((np.array(noise["t"]) + 350.0).tolist(), (np.array(noise["i"]) * rheobase * -0.25).tolist()) + + # Finally a 100ms recovery + ramp_ti += [ + (500, 0), + (600, 0), + ] + + return zip(*ramp_ti) + + smalest_dt_result = None + max_stable_dt = 0 + + for steps_per_ms in steps_per_ms_set: + try: + result = self.get_arb_current_response(delay=0, + duration=600, + post_delay=0, + get_current_ti=get_current_ti, + restore_state=False, + dt=1.0/steps_per_ms, + sampling_period=1.0/steps_per_ms) + + + # Save the smallest dt waveform + if steps_per_ms == max(steps_per_ms_set): + # Comparing everything to smallest dt waveform - its error is 0 + result["error"] = 0 + smalest_dt_result = result + + # Compute the range of the waveform voltages + smalest_dt_result["range"] = max(result["v"]) - min(result["v"]) + + # Subsample the waveform using largest dt interval (1ms) + # (these values will be compared across waveforms) + try: + smalest_dt_result["v_sub"] = np.array(smalest_dt_result["v"])[::max(steps_per_ms_set)] + smalest_dt_result["t_sub"] = np.array(smalest_dt_result["t"])[::max(steps_per_ms_set)] + except: + pass + + else: + # Error is average of differences from baseline waveform expressed as percentages of the waveform range + result["error"] = self.compute_waveform_error(smalest_dt_result["v_sub"], + smalest_dt_result["range"], + np.array(result["v"])[::steps_per_ms]) + + if save_max_stable_dt: + max_stable_dt = 1.0/steps_per_ms + max_stable_dt_error = result["error"] + + dt_str = str(1.0/steps_per_ms) + " ms" + + self.save_tvi_plot(label=protocol, + case=dt_str, + tvi_dict=result) + + self.save_vi_waveforms(protocol=protocol, + label=dt_str, + tvi_dict=result) + + except Exception: + break + + if save_max_stable_dt: + print("Saving max stable time step " + str(max_stable_dt)) + self.model_record.Max_Stable_DT = max_stable_dt + self.model_record.Max_Stable_DT_Error = max_stable_dt_error + self.model_record.save() + + # Clear the cache for this file + self.pickle_file_cache.pop(noise_pickle_file) + + return smalest_dt_result + + def compute_waveform_error(self, lowest_error_v, lowest_error_range, v): + return np.average(np.abs(v - lowest_error_v) / lowest_error_range * 100.0) + + + def interpolate_time_signal(self, t, signal, new_interval): + from scipy.interpolate import interp1d + signal_function = interp1d(t, signal, kind="cubic", fill_value='extrapolate') + + new_t = np.arange(min(t),max(t),step=new_interval).tolist() + new_signal = signal_function(new_t).tolist() + + return new_t, new_signal + + def save_noise_response_set(self, + protocol, + delay, + duration, + post_delay, + rheobase, + noise_pickle_file, + multiples, + meta_protocol=None, + restore_state=False): + + # Cache the files - they're slow to load + if noise_pickle_file not in self.pickle_file_cache: + with open(os.path.join("..", "..", noise_pickle_file), "r") as f: + print('Reading noise .pickle file...') + signal_orig = cPickle.load(f) + t_int, i_int = self.interpolate_time_signal(signal_orig['t'], signal_orig['i'], self.config.dt) + self.pickle_file_cache[noise_pickle_file] = { 't':t_int, 'i': i_int } + signal_orig = None + print('DONE') + + + def get_current_ti(): + noise = self.pickle_file_cache[noise_pickle_file] + + ramp_t = [0, delay] + (np.array(noise["t"]) + delay).tolist() + [delay + duration, + delay + duration + post_delay] + ramp_i = [0, 0] + (np.array(noise["i"]) * rheobase * multiple).tolist() + [0, 0] + + return ramp_t, ramp_i + + for multiple in multiples: + result = self.get_arb_current_response(delay=delay, + duration=duration, + post_delay=post_delay, + get_current_ti=get_current_ti, + restore_state=restore_state) + + multiple_str = str(multiple) + "xRB" + + self.save_tvi_plot(label=protocol, + case=(meta_protocol if meta_protocol is not None else "") + " " + multiple_str, + tvi_dict=result) + + self.save_vi_waveforms(protocol=protocol, + label=multiple_str, + meta_protocol=meta_protocol, + tvi_dict=result) + + # Clear the cache for this file + self.pickle_file_cache.pop(noise_pickle_file) + + def save_arb_current(self, + protocol, + delay, + duration, + get_current_ti, + meta_protocol=None, + label=None, + restore_state=False): + + result = self.get_arb_current_response(delay=delay, + duration=duration, + get_current_ti=get_current_ti, + restore_state=restore_state) + + self.save_tvi_plot(label=protocol, + case=(meta_protocol if meta_protocol is not None else "") + " " + (label if label is not None else ""), + tvi_dict=result) + + self.save_vi_waveforms(protocol=protocol, + label=label, + tvi_dict=result) + + def get_arb_current_response(self, + delay, + duration, + get_current_ti, + post_delay=0, + test_condition=None, + restore_state=False, + dt=None, + sampling_period=None): + + def arb_current_protocol(flag): + self.time_flag = flag + + if dt is not None: + self.config.cvode_active = 0 + self.config.dt = dt + + if sampling_period is not None: + self.config.collection_period_ms = sampling_period + + h = self.build_model() + + # Set up IClamp for arbitrary current + self.current.dur = 1e9 + self.current.delay = 0 + + # Create ramp waveform + ramp_t, ramp_i = get_current_ti() + + rv = h.Vector(ramp_i) + tv = h.Vector(ramp_t) + + # Play ramp waveform into the IClamp (last param is continuous=True) + rv.play(self.current._ref_amp, tv, 1) + + with RunTimer() as timer: + if restore_state: + self.restore_state(keep_events=True) # Keep events ensures .play() works + t, v = self.runFor(duration + post_delay, test_condition) + else: + h.stdinit() + t, v = self.runFor(delay + duration + post_delay, test_condition) + + result = { + "t": t.tolist(), + "v": v.tolist(), + "i": self.ic_i_collector.get_values_list(), + "run_time": timer.get_run_time(), + "steps": int(self.tvec.size()), + "cvode_active": int(self.config.cvode_active), + "dt_or_atol": self.config.abs_tolerance if self.config.cvode_active else self.config.dt + } + + return result + + runner = NeuronRunner(arb_current_protocol) + runner.DONTKILL = True + result = runner.run() + return result + + def load_model(self): + # Load cell hoc and get soma + os.chdir(self.temp_model_path) + print("Loading NEURON... If this step 'freezes', ensure there are no hung NEURON processes with 'pkill -9 nrn*'") + from neuron import h, gui + print("DONE") + + if self.model_record.Publication.Temperature is None: + print("Using default temperature " + str(self.config.default_temperature)) + h.celsius = self.config.default_temperature + else: + print("Using temperature from publication " + str(self.model_record.Publication.Temperature)) + h.celsius = self.model_record.Publication.Temperature + + # Create the cell + if self.is_abstract_cell(): + self.test_cell = self.get_abstract_cell(h) + elif len(self.get_hoc_files()) > 0: + self.test_cell = self.get_cell_with_morphology(h) + else: + raise Exception("Could not find cell .hoc or abstract cell .mod file in: " + self.temp_model_path) + + # Get the root sections and try to find the soma + self.roots = h.SectionList() + self.roots.allroots() + self.roots = [s for s in self.roots] + self.somas = [sec for sec in self.roots if "soma" in sec.name().lower()] + if len(self.somas) == 1: + self.soma = self.somas[0] + elif len(self.somas) == 0 and len(self.roots) == 1: + self.soma = self.roots[0] + else: + raise Exception("Problem finding the soma section") + + return h + + def build_model(self, restore_tolerances=True): + print("Loading cell: " + self.temp_model_path) + h = self.load_model() + + # set up stim + self.current = h.IClamp(self.soma(0.5)) + self.current.delay = 50.0 + self.current.amp = 0 + self.current.dur = 100.0 + + self.vc = h.SEClamp(self.soma(0.5)) + self.vc.dur1 = 0 + + + # Set up variable collectors + self.t_collector = Collector(self.config.collection_period_ms, h._ref_t) + if self.cell_record.V_Variable is None: + self.v_collector = Collector(self.config.collection_period_ms, self.soma(0.5)._ref_v) + else: + self.v_collector = Collector(self.config.collection_period_ms, getattr(self.abstract_mod,"_ref_" + self.cell_record.V_Variable)) + + self.vc_i_collector = Collector(self.config.collection_period_ms, self.vc._ref_i) + self.ic_i_collector = Collector(self.config.collection_period_ms, self.current._ref_i) + + self.tvec = h.Vector() + self.tvec.record(h._ref_t) + + # h.nrncontrolmenu() + self.nState = h.SaveState() + self.sim_init() + self.set_abs_tolerance(self.config.abs_tolerance) + + if not self.is_abstract_cell() and restore_tolerances: + self.restore_tolerances() + + return h + + def is_abstract_cell(self): + return len(self.get_hoc_files()) == 0 and len(self.get_mod_files()) == 1 + + def get_abstract_cell(self, h): + cell_mod_file = self.get_mod_files()[0] + cell_mod_name = cell_mod_file.replace(".mod", "") + + soma = h.Section() + soma.L = 10 + soma.diam = 10 + soma.cm = 318.31927 # Magic number, see: https://github.com/NeuroML/org.neuroml.export/issues/60 + + mod = getattr(h, cell_mod_name)(0.5, sec=soma) + + self.abstract_soma = soma + self.abstract_mod = mod + + return soma + + def get_cell_with_morphology(self, h): + cell_hoc_file = self.get_hoc_files()[0] + cell_template = cell_hoc_file.replace(".hoc", "") + h.load_file(cell_hoc_file) + cell = getattr(h, cell_template)() + return cell + + def sim_init(self): + from neuron import h + h.stdinit() + h.tstop = 1000 + self.current.amp = 0 + self.vc.dur1 = 0 + + def setCurrent(self, amp, delay, dur): + self.current.delay = delay + self.current.amp = amp + self.current.dur = dur + + def get_stability_range(self, testLow=-10, testHigh=15): + + print("Searching for UPPER boundary...") + current_range, found_once = self.find_border( + lowerLevel=0, + upperLevel=testHigh, + current_delay=self.steady_state_delay, + current_duration=3, + run_for_after_delay=10, + test_condition=lambda t, v: False if np.max(np.abs(v)) < 150 else True, + on_unstable=lambda: True, + max_iterations=7, + fig_file="stabilityHigh.png", + skip_current_delay=False + ) + + high_edge = min(current_range) + + print("Searching for LOWER boundary...") + current_range, found_once = self.find_border( + lowerLevel=testLow, + upperLevel=0, + current_delay=self.steady_state_delay, + current_duration=3, + run_for_after_delay=10, + test_condition=lambda t, v: True if np.max(np.abs(v)) < 150 else False, + on_unstable=lambda: False, + max_iterations=7, + fig_file="stabilityLow.png", + skip_current_delay=True + ) + + low_edge = max(current_range) + + return low_edge, high_edge + + def getThreshold(self, minCurrent, maxI): + + def test_condition(t, v): + num_spikes = self.getSpikeCount(v) + print("Got " + str(num_spikes) + " spikes") + return num_spikes > 0 + + current_range, found_once = self.find_border( + lowerLevel=minCurrent, + upperLevel=maxI, + current_delay=self.steady_state_delay, + current_duration=3, + run_for_after_delay=50, + test_condition=test_condition, + max_iterations=10, + fig_file="threshold.png", + skip_current_delay=True + ) + + if not found_once: + raise Exception("Did not find threshold with currents " + str(current_range)) + + return current_range + + def recompute_range(iteration,upperLevel,lowerLevel): + intervals = np.arange(upperLevel,lowerLevel,cpus) + return intervals + + def list_process(simulate_iteration): + runner = NeuronRunner(simulate_iteration) + try: + found = runner.run() + return found + except NumericalInstabilityException: + if on_unstable is not None: + found = on_unstable() + return found + else: + return None + + def simulate_iteration(time_flag): + self.time_flag = time_flag + h = self.build_model() + self.restore_state(state_file=state_file) + + self.setCurrent(amp=currentAmp, delay=current_delay, dur=current_duration) + print("Trying " + str(currentAmp) + " nA...") + + if not test_early: + t, v = self.runFor(run_for_after_delay) + found = test_condition(t, v) + else: + t, v = self.runFor(run_for_after_delay, test_condition) + found = test_condition(t, v) + + plt.plot(t, v, label=str(round(currentAmp, 4)) + ", Found: " + str(found)) + plt.legend(loc='upper left') + plt.savefig(str(iteration) + " " + fig_file) + + print("FOUND" if found else "NOT FOUND") + + return found + + + + def find_border(self, lowerLevel, upperLevel, + current_delay, current_duration, + run_for_after_delay, test_condition, max_iterations, fig_file, + skip_current_delay=False, on_unstable=None, test_early=False): + + state_file = 'border_state.bin' + + if not skip_current_delay: + def reach_resting_state(time_flag): + self.time_flag = time_flag + self.build_model() + + print("Simulating till current onset...") + self.sim_init() + self.setCurrent(amp=0, delay=current_delay, dur=current_duration) + self.runFor(current_delay) + self.save_state(state_file=state_file) + print("Resting state reached. State saved.") + + runner = NeuronRunner(reach_resting_state) + result = runner.run() + + iterate = True + iteration = 0 + found_once = False + + upperLevel_start = upperLevel + lowerLevel_start = lowerLevel + + first_interval = np.arange(lowerLevel_start,upperLevel_start,cpus) + scattered_iterable = db.from_sequence(first_interval,npartitions=cpus) + gathered_list = scattered_iterable.map(list_process) + while iterate: + scattered_iterable = db.from_sequence(intervals,npartitions=cpus) + gathered_list = scattered_iterable.map(list_process) + for g in gathered_list: + if g == True: + break + intervals = recompute_range(iteration,upperLevel,lowerLevel) + if found: + upperLevel = currentAmp + found_once = True + else: + lowerLevel = currentAmp + + iteration = iteration + 1 + + if iteration >= max_iterations or lowerLevel == upperLevel_start or upperLevel == lowerLevel_start: + iterate = False + current_range = (lowerLevel, upperLevel) + return current_range, found_once + + def getRheobase(self, minCurrent, maxI): + + def test_condition(t, v): + return self.getSpikeCount(v) > 0 + + current_range, found_once = self.find_border( + lowerLevel=minCurrent, + upperLevel=maxI, + current_delay=self.steady_state_delay, + current_duration=1000, + run_for_after_delay=500, + test_condition=test_condition, + test_early=True, + max_iterations=10, + fig_file="rheobase.png", + skip_current_delay=True + ) + + if not found_once: + raise Exception("Did not find rheobase with currents " + str(current_range)) + + return current_range + + def getBiasCurrent(self, targetV): + def bias_protocol(flag): + self.time_flag = flag + self.build_model() + self.sim_init() + + self.vc.amp1 = targetV + self.vc.dur1 = 10000 + + t, v = self.runFor(1000) + + i = self.vc_i_collector.get_values_np() + crossings = self.getSpikeCount(i, threshold=0) + + if crossings > 2: + print( + "No bias current exists for steady state at " + str( + targetV) + " mV membrane potential (only spikes)") + result = None + else: + result = self.vc.i + + self.vc.dur1 = 0 + + plt.clf() + plt.plot(t[np.where(t > 50)], i[np.where(t > 50)], + label="Bias Current for " + str(targetV) + "mV = " + str(result)) + plt.legend(loc='upper left') + plt.savefig("biasCurrent" + str(targetV) + ".png") + + return result + + runner = NeuronRunner(bias_protocol) + return runner.run() + + def getRestingV(self, run_time, save_resting_state=False): + def rest_protocol(flag): + self.time_flag = flag + self.build_model() + self.sim_init() + + with RunTimer() as timer: + t, v = self.runFor(run_time) + + result = { + "t": t.tolist(), + "v": v.tolist(), + "i": self.ic_i_collector.get_values_list(), + "run_time": timer.get_run_time(), + "steps": int(self.tvec.size()), + "cvode_active": int(self.config.cvode_active), + "dt_or_atol": self.config.abs_tolerance if self.config.cvode_active else self.config.dt + } + + crossings = self.getSpikeCount(v) + + if crossings > 1: + print("No rest - cell produces multiple spikes without stimulation.") + result["rest"] = None + else: + result["rest"] = v[-1] + + if save_resting_state: + self.save_state() + + return result + + runner = NeuronRunner(rest_protocol) + runner.DONTKILL = True + result = runner.run() + return result + + def get_structural_metrics(self): + + def run_struct_analysis(): + h = self.build_model(restore_tolerances=False) + + result = {} + + if self.is_abstract_cell(): + result["section_count"] = 1 + result["compartment_count"] = 1 + + else: + result["section_count"] = len([sec for sec in h.allsec()]) + result["compartment_count"] = self.get_number_of_compartments(h) + + return result + + runner = NeuronRunner(run_struct_analysis, kill_slow_sims=False) + metrics = runner.run() + + return metrics + + def save_structural_metrics(self): + metrics = self.get_structural_metrics() + + self.cell_record.Sections = metrics["section_count"] + self.cell_record.Compartments = metrics["compartment_count"] + + self.cell_record.save() + + + def get_id_from_nml_file(self, nml): + return re.search('<.*cell.*?id.*?=.*?"(.*?)"', nml, re.IGNORECASE).groups(1)[0] + + def get_tv(self): + from neuron import h + v_np = self.v_collector.get_values_np() + t_np = self.t_collector.get_values_np() + + if np.isnan(v_np).any(): + raise NumericalInstabilityException( + "Simulation is numericaly unstable with dt of " + str(h.dt) + " ms") + + return (t_np, v_np) + + def use_optimal_dt_if_available(self): + if (self.cell_record.CVODE_Active is None or self.cell_record.CVODE_Active == 0) and self.model_record.Optimal_DT is not None: + print("Using optimal DT " + str(self.model_record.Optimal_DT)) + self.config.cvode_active = 0 + self.config.dt = self.model_record.Optimal_DT diff --git a/neuronunit/docs/Chapter6.ipynb b/neuronunit/docs/Chapter6.ipynb index a4187ffa9..a867985bb 100644 --- a/neuronunit/docs/Chapter6.ipynb +++ b/neuronunit/docs/Chapter6.ipynb @@ -639,8 +639,8 @@ " model.run_number+=1\n", " # Run the model, then:\n", " error = []\n", - " other_mean = np.mean([i for i in score.sort_key.values.tolist()[0] if type(i) is not type(None)])\n", - " for my_score in score.sort_key.values.tolist()[0]:\n", + " other_mean = np.mean([i for i in score.norm_score.values.tolist()[0] if type(i) is not type(None)])\n", + " for my_score in score.norm_score.values.tolist()[0]:\n", " if isinstance(my_score,sciunit.ErrorScore):\n", " error.append(-100.0)\n", " elif isinstance(my_score,type(None)):\n", diff --git a/neuronunit/examples/agreement_df.ipynb b/neuronunit/examples/agreement_df.ipynb new file mode 100644 index 000000000..0940a2c81 --- /dev/null +++ b/neuronunit/examples/agreement_df.ipynb @@ -0,0 +1,9736 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Running on Python version: 3.6.4 |Anaconda, Inc.| (default, Jan 16 2018, 18:10:19) \n", + "[GCC 7.2.0]\n" + ] + } + ], + "source": [ + "import sys \n", + "print('Running on Python version: {}'.format(sys.version))\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "import seaborn as sns\n", + "import os\n", + "%matplotlib inline\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Collecting prettyplotlib\n", + "\u001b[?25l Downloading https://files.pythonhosted.org/packages/f2/89/35079781fe5f8c4e5258b88bb0a80d4a2028c6206ccdea12f4a94528dc5e/prettyplotlib-0.1.7.tar.gz (697kB)\n", + "\u001b[K 100% |████████████████████████████████| 706kB 6.2MB/s ta 0:00:011 32% |██████████▍ | 225kB 4.7MB/s eta 0:00:01\n", + "\u001b[?25hRequirement already satisfied: matplotlib>=1.2.1 in /opt/conda/lib/python3.6/site-packages (from prettyplotlib) (2.2.2)\n", + "Collecting brewer2mpl>=1.3.1 (from prettyplotlib)\n", + " Downloading https://files.pythonhosted.org/packages/84/57/00c45a199719e617db0875181134fcb3aeef701deae346547ac722eaaf5e/brewer2mpl-1.4.1-py2.py3-none-any.whl\n", + "Requirement already satisfied: numpy>=1.7.1 in /opt/conda/lib/python3.6/site-packages (from matplotlib>=1.2.1->prettyplotlib) (1.12.1)\n", + "Requirement already satisfied: cycler>=0.10 in /opt/conda/lib/python3.6/site-packages/cycler-0.10.0-py3.6.egg (from matplotlib>=1.2.1->prettyplotlib) (0.10.0)\n", + "Requirement already satisfied: pyparsing!=2.0.4,!=2.1.2,!=2.1.6,>=2.0.1 in /opt/conda/lib/python3.6/site-packages (from matplotlib>=1.2.1->prettyplotlib) (2.2.0)\n", + "Requirement already satisfied: python-dateutil>=2.1 in /opt/conda/lib/python3.6/site-packages (from matplotlib>=1.2.1->prettyplotlib) (2.7.2)\n", + "Requirement already satisfied: pytz in /opt/conda/lib/python3.6/site-packages (from matplotlib>=1.2.1->prettyplotlib) (2018.4)\n", + "Requirement already satisfied: six>=1.10 in /opt/conda/lib/python3.6/site-packages (from matplotlib>=1.2.1->prettyplotlib) (1.11.0)\n", + "Requirement already satisfied: kiwisolver>=1.0.1 in /opt/conda/lib/python3.6/site-packages (from matplotlib>=1.2.1->prettyplotlib) (1.0.1)\n", + "Requirement already satisfied: setuptools in /opt/conda/lib/python3.6/site-packages (from kiwisolver>=1.0.1->matplotlib>=1.2.1->prettyplotlib) (38.4.0)\n", + "Building wheels for collected packages: prettyplotlib\n", + " Running setup.py bdist_wheel for prettyplotlib ... \u001b[?25ldone\n", + "\u001b[?25h Stored in directory: /home/jovyan/.cache/pip/wheels/76/ad/45/9fcfb9e97ecccc850d8b2fb20b8d60992bc6d7c8d9769ee989\n", + "Successfully built prettyplotlib\n", + "Installing collected packages: brewer2mpl, prettyplotlib\n", + "Successfully installed brewer2mpl-1.4.1 prettyplotlib-0.1.7\n", + "\u001b[33mYou are using pip version 10.0.1, however version 18.0 is available.\n", + "You should consider upgrading via the 'pip install --upgrade pip' command.\u001b[0m\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/opt/conda/lib/python3.6/site-packages/sklearn/cross_validation.py:41: DeprecationWarning: This module was deprecated in version 0.18 in favor of the model_selection module into which all the refactored classes and functions are moved. Also note that the interface of the new CV iterators are different from that of this module. This module will be removed in 0.20.\n", + " \"This module will be removed in 0.20.\", DeprecationWarning)\n", + "/opt/conda/lib/python3.6/site-packages/sklearn/grid_search.py:42: DeprecationWarning: This module was deprecated in version 0.18 in favor of the model_selection module into which all the refactored classes and functions are moved. This module will be removed in 0.20.\n", + " DeprecationWarning)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "dict_items([('a', 0.27328841763197198), ('b', -2.5511245505753264e-09), ('vr', -71.213934167873902)])\n" + ] + } + ], + "source": [ + "import pickle\n", + "try:\n", + " import prettyplotlib as ppl\n", + " from prettyplotlib import plt\n", + " from prettyplotlib import brewer2mpl\n", + "except:\n", + " try:\n", + " !pip install prettyplotlib\n", + " import prettyplotlib as ppl\n", + " from prettyplotlib import plt\n", + " from prettyplotlib import brewer2mpl\n", + " \n", + " green_purple = brewer2mpl.get_map('PRGn', 'diverging', 11).mpl_colormap\n", + "\n", + " except:\n", + " import matplotlib.plot as plt\n", + "import numpy as np\n", + "import string\n", + "\n", + "\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import seaborn as sns\n", + "import pandas as pd\n", + "import numpy as np\n", + "import math as math\n", + "from pylab import rcParams\n", + "from neuronunit.optimization.results_analysis import make_report, min_max\n", + "with open('pre_grid_reports.p','rb') as f:#\n", + " grid_results = pickle.load(f)\n", + "\n", + "with open('pre_ga_reports.p','rb') as f:\n", + " package = pickle.load(f)\n", + "pop = package[0]\n", + "print(pop[0].dtc.attrs.items())\n", + "history = package[4]\n", + "gen_vs_pop = package[6]\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['a', 'b', 'vr']\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + " \n", + "\n", + " \n", + " \n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " a\n", + " \n", + " \n", + " \n", + " \n", + " b\n", + " \n", + " \n", + " \n", + " \n", + " vr\n", + " \n", + " \n", + " \n", + " \n", + " RheobaseTestP\n", + " \n", + " \n", + " \n", + " \n", + " InputResistanceTest\n", + " \n", + " \n", + " \n", + " \n", + " total\n", + " \n", + " \n", + "
\n", + " 0\n", + " \n", + " \n", + " \n", + " \n", + " 0.315\n", + " \n", + " \n", + " \n", + " \n", + " -8.33333e-10\n", + " \n", + " \n", + " \n", + " \n", + " -13.3333\n", + " \n", + " \n", + " \n", + " \n", + " 0.677002\n", + " \n", + " \n", + " \n", + " \n", + " 0.999996\n", + " \n", + " \n", + " \n", + " \n", + " 1.677\n", + " \n", + " \n", + "
\n", + " 1\n", + " \n", + " \n", + " \n", + " \n", + " 0.315\n", + " \n", + " \n", + " \n", + " \n", + " -8.33333e-10\n", + " \n", + " \n", + " \n", + " \n", + " -13.3333\n", + " \n", + " \n", + " \n", + " \n", + " 0.677002\n", + " \n", + " \n", + " \n", + " \n", + " 0.999996\n", + " \n", + " \n", + " \n", + " \n", + " 1.677\n", + " \n", + " \n", + "
\n", + " 2\n", + " \n", + " \n", + " \n", + " \n", + " 0.315\n", + " \n", + " \n", + " \n", + " \n", + " -8.33333e-10\n", + " \n", + " \n", + " \n", + " \n", + " -66.6667\n", + " \n", + " \n", + " \n", + " \n", + " 0.680333\n", + " \n", + " \n", + " \n", + " \n", + " 0.999997\n", + " \n", + " \n", + " \n", + " \n", + " 1.68033\n", + " \n", + " \n", + "
\n", + " 3\n", + " \n", + " \n", + " \n", + " \n", + " 0.315\n", + " \n", + " \n", + " \n", + " \n", + " -3.33333e-09\n", + " \n", + " \n", + " \n", + " \n", + " -13.3333\n", + " \n", + " \n", + " \n", + " \n", + " 0.677002\n", + " \n", + " \n", + " \n", + " \n", + " 0.999996\n", + " \n", + " \n", + " \n", + " \n", + " 1.677\n", + " \n", + " \n", + "
\n", + " 4\n", + " \n", + " \n", + " \n", + " \n", + " 0.315\n", + " \n", + " \n", + " \n", + " \n", + " -3.33333e-09\n", + " \n", + " \n", + " \n", + " \n", + " -66.6667\n", + " \n", + " \n", + " \n", + " \n", + " 0.680333\n", + " \n", + " \n", + " \n", + " \n", + " 0.999997\n", + " \n", + " \n", + " \n", + " \n", + " 1.68033\n", + " \n", + " \n", + "
\n", + " 5\n", + " \n", + " \n", + " \n", + " \n", + " 0.0133333\n", + " \n", + " \n", + " \n", + " \n", + " -8.33333e-10\n", + " \n", + " \n", + " \n", + " \n", + " -13.3333\n", + " \n", + " \n", + " \n", + " \n", + " 0.677002\n", + " \n", + " \n", + " \n", + " \n", + " 0.999996\n", + " \n", + " \n", + " \n", + " \n", + " 1.677\n", + " \n", + " \n", + "
\n", + " 6\n", + " \n", + " \n", + " \n", + " \n", + " 0.0133333\n", + " \n", + " \n", + " \n", + " \n", + " -8.33333e-10\n", + " \n", + " \n", + " \n", + " \n", + " -66.6667\n", + " \n", + " \n", + " \n", + " \n", + " 0.677002\n", + " \n", + " \n", + " \n", + " \n", + " 0.999996\n", + " \n", + " \n", + " \n", + " \n", + " 1.677\n", + " \n", + " \n", + "
\n", + " 7\n", + " \n", + " \n", + " \n", + " \n", + " 0.0133333\n", + " \n", + " \n", + " \n", + " \n", + " -3.33333e-09\n", + " \n", + " \n", + " \n", + " \n", + " -13.3333\n", + " \n", + " \n", + " \n", + " \n", + " 0.677002\n", + " \n", + " \n", + " \n", + " \n", + " 0.999996\n", + " \n", + " \n", + " \n", + " \n", + " 1.677\n", + " \n", + " \n", + "
\n", + " 8\n", + " \n", + " \n", + " \n", + " \n", + " 0.0133333\n", + " \n", + " \n", + " \n", + " \n", + " -3.33333e-09\n", + " \n", + " \n", + " \n", + " \n", + " -66.6667\n", + " \n", + " \n", + " \n", + " \n", + " 0.677002\n", + " \n", + " \n", + " \n", + " \n", + " 0.999996\n", + " \n", + " \n", + " \n", + " \n", + " 1.677\n", + " \n", + " \n", + "
\n", + " 9\n", + " \n", + " \n", + " \n", + " \n", + " 0.315\n", + " \n", + " \n", + " \n", + " \n", + " -8.33333e-10\n", + " \n", + " \n", + " \n", + " \n", + " -13.3333\n", + " \n", + " \n", + " \n", + " \n", + " 0.677002\n", + " \n", + " \n", + " \n", + " \n", + " 0.999996\n", + " \n", + " \n", + " \n", + " \n", + " 1.677\n", + " \n", + " \n", + "
\n", + " 10\n", + " \n", + " \n", + " \n", + " \n", + " 0.315\n", + " \n", + " \n", + " \n", + " \n", + " -8.33333e-10\n", + " \n", + " \n", + " \n", + " \n", + " -66.6667\n", + " \n", + " \n", + " \n", + " \n", + " 0.680333\n", + " \n", + " \n", + " \n", + " \n", + " 0.999997\n", + " \n", + " \n", + " \n", + " \n", + " 1.68033\n", + " \n", + " \n", + "
\n", + " 11\n", + " \n", + " \n", + " \n", + " \n", + " 0.315\n", + " \n", + " \n", + " \n", + " \n", + " -3.33333e-09\n", + " \n", + " \n", + " \n", + " \n", + " -13.3333\n", + " \n", + " \n", + " \n", + " \n", + " 0.677002\n", + " \n", + " \n", + " \n", + " \n", + " 0.999996\n", + " \n", + " \n", + " \n", + " \n", + " 1.677\n", + " \n", + " \n", + "
\n", + " 12\n", + " \n", + " \n", + " \n", + " \n", + " 0.315\n", + " \n", + " \n", + " \n", + " \n", + " -3.33333e-09\n", + " \n", + " \n", + " \n", + " \n", + " -66.6667\n", + " \n", + " \n", + " \n", + " \n", + " 0.680333\n", + " \n", + " \n", + " \n", + " \n", + " 0.999997\n", + " \n", + " \n", + " \n", + " \n", + " 1.68033\n", + " \n", + " \n", + "
\n", + " 13\n", + " \n", + " \n", + " \n", + " \n", + " 0.0133333\n", + " \n", + " \n", + " \n", + " \n", + " -8.33333e-10\n", + " \n", + " \n", + " \n", + " \n", + " -13.3333\n", + " \n", + " \n", + " \n", + " \n", + " 0.677002\n", + " \n", + " \n", + " \n", + " \n", + " 0.999996\n", + " \n", + " \n", + " \n", + " \n", + " 1.677\n", + " \n", + " \n", + "
\n", + " 14\n", + " \n", + " \n", + " \n", + " \n", + " 0.0133333\n", + " \n", + " \n", + " \n", + " \n", + " -8.33333e-10\n", + " \n", + " \n", + " \n", + " \n", + " -66.6667\n", + " \n", + " \n", + " \n", + " \n", + " 0.677002\n", + " \n", + " \n", + " \n", + " \n", + " 0.999996\n", + " \n", + " \n", + " \n", + " \n", + " 1.677\n", + " \n", + " \n", + "
\n", + " 15\n", + " \n", + " \n", + " \n", + " \n", + " 0.0133333\n", + " \n", + " \n", + " \n", + " \n", + " -3.33333e-09\n", + " \n", + " \n", + " \n", + " \n", + " -13.3333\n", + " \n", + " \n", + " \n", + " \n", + " 0.677002\n", + " \n", + " \n", + " \n", + " \n", + " 0.999996\n", + " \n", + " \n", + " \n", + " \n", + " 1.677\n", + " \n", + " \n", + "
\n", + " 16\n", + " \n", + " \n", + " \n", + " \n", + " 0.0133333\n", + " \n", + " \n", + " \n", + " \n", + " -3.33333e-09\n", + " \n", + " \n", + " \n", + " \n", + " -66.6667\n", + " \n", + " \n", + " \n", + " \n", + " 0.677002\n", + " \n", + " \n", + " \n", + " \n", + " 0.999996\n", + " \n", + " \n", + " \n", + " \n", + " 1.677\n", + " \n", + " \n", + "
\n", + " 17\n", + " \n", + " \n", + " \n", + " \n", + " 0.60888\n", + " \n", + " \n", + " \n", + " \n", + " -8.33333e-10\n", + " \n", + " \n", + " \n", + " \n", + " -40\n", + " \n", + " \n", + " \n", + " \n", + " 0.999689\n", + " \n", + " \n", + " \n", + " \n", + " 1\n", + " \n", + " \n", + " \n", + " \n", + " 1.99969\n", + " \n", + " \n", + "
\n", + " 18\n", + " \n", + " \n", + " \n", + " \n", + " 0.315\n", + " \n", + " \n", + " \n", + " \n", + " -8.33333e-10\n", + " \n", + " \n", + " \n", + " \n", + " -61.0755\n", + " \n", + " \n", + " \n", + " \n", + " 0.951301\n", + " \n", + " \n", + " \n", + " \n", + " 0.999986\n", + " \n", + " \n", + " \n", + " \n", + " 1.95129\n", + " \n", + " \n", + "
\n", + " 19\n", + " \n", + " \n", + " \n", + " \n", + " 0.331052\n", + " \n", + " \n", + " \n", + " \n", + " -3.33333e-09\n", + " \n", + " \n", + " \n", + " \n", + " -40\n", + " \n", + " \n", + " \n", + " \n", + " 0.999689\n", + " \n", + " \n", + " \n", + " \n", + " 1\n", + " \n", + " \n", + " \n", + " \n", + " 1.99969\n", + " \n", + " \n", + "
\n", + " 20\n", + " \n", + " \n", + " \n", + " \n", + " 0.315\n", + " \n", + " \n", + " \n", + " \n", + " -3.49229e-09\n", + " \n", + " \n", + " \n", + " \n", + " -60.5876\n", + " \n", + " \n", + " \n", + " \n", + " 0.959607\n", + " \n", + " \n", + " \n", + " \n", + " 0.999984\n", + " \n", + " \n", + " \n", + " \n", + " 1.95959\n", + " \n", + " \n", + "
\n", + " 21\n", + " \n", + " \n", + " \n", + " \n", + " 0.0219288\n", + " \n", + " \n", + " \n", + " \n", + " -8.33333e-10\n", + " \n", + " \n", + " \n", + " \n", + " -40\n", + " \n", + " \n", + " \n", + " \n", + " 0.999677\n", + " \n", + " \n", + " \n", + " \n", + " 1\n", + " \n", + " \n", + " \n", + " \n", + " 1.99968\n", + " \n", + " \n", + "
\n", + " 22\n", + " \n", + " \n", + " \n", + " \n", + " 0.02\n", + " \n", + " \n", + " \n", + " \n", + " -8.33333e-10\n", + " \n", + " \n", + " \n", + " \n", + " -64.7362\n", + " \n", + " \n", + " \n", + " \n", + " 0.82081\n", + " \n", + " \n", + " \n", + " \n", + " 0.999994\n", + " \n", + " \n", + " \n", + " \n", + " 1.8208\n", + " \n", + " \n", + "
\n", + " 23\n", + " \n", + " \n", + " \n", + " \n", + " 0.02\n", + " \n", + " \n", + " \n", + " \n", + " -3.21914e-09\n", + " \n", + " \n", + " \n", + " \n", + " -13.3333\n", + " \n", + " \n", + " \n", + " \n", + " 0.677002\n", + " \n", + " \n", + " \n", + " \n", + " 0.999996\n", + " \n", + " \n", + " \n", + " \n", + " 1.677\n", + " \n", + " \n", + "
\n", + " 24\n", + " \n", + " \n", + " \n", + " \n", + " 0.02\n", + " \n", + " \n", + " \n", + " \n", + " -3.33333e-09\n", + " \n", + " \n", + " \n", + " \n", + " -66.6667\n", + " \n", + " \n", + " \n", + " \n", + " 0.677002\n", + " \n", + " \n", + " \n", + " \n", + " 0.999996\n", + " \n", + " \n", + " \n", + " \n", + " 1.677\n", + " \n", + " \n", + "
\n", + " 25\n", + " \n", + " \n", + " \n", + " \n", + " 0.315\n", + " \n", + " \n", + " \n", + " \n", + " -8.33333e-10\n", + " \n", + " \n", + " \n", + " \n", + " -61.0755\n", + " \n", + " \n", + " \n", + " \n", + " 0.951301\n", + " \n", + " \n", + " \n", + " \n", + " 0.999986\n", + " \n", + " \n", + " \n", + " \n", + " 1.95129\n", + " \n", + " \n", + "
\n", + " 26\n", + " \n", + " \n", + " \n", + " \n", + " 0.315\n", + " \n", + " \n", + " \n", + " \n", + " -3.49229e-09\n", + " \n", + " \n", + " \n", + " \n", + " -60.5876\n", + " \n", + " \n", + " \n", + " \n", + " 0.959607\n", + " \n", + " \n", + " \n", + " \n", + " 0.999984\n", + " \n", + " \n", + " \n", + " \n", + " 1.95959\n", + " \n", + " \n", + "
\n", + " 27\n", + " \n", + " \n", + " \n", + " \n", + " 0.02\n", + " \n", + " \n", + " \n", + " \n", + " -8.33333e-10\n", + " \n", + " \n", + " \n", + " \n", + " -64.7362\n", + " \n", + " \n", + " \n", + " \n", + " 0.82081\n", + " \n", + " \n", + " \n", + " \n", + " 0.999994\n", + " \n", + " \n", + " \n", + " \n", + " 1.8208\n", + " \n", + " \n", + "
\n", + " 28\n", + " \n", + " \n", + " \n", + " \n", + " 0.02\n", + " \n", + " \n", + " \n", + " \n", + " -3.33333e-09\n", + " \n", + " \n", + " \n", + " \n", + " -66.6667\n", + " \n", + " \n", + " \n", + " \n", + " 0.677002\n", + " \n", + " \n", + " \n", + " \n", + " 0.999996\n", + " \n", + " \n", + " \n", + " \n", + " 1.677\n", + " \n", + " \n", + "
\n", + " 29\n", + " \n", + " \n", + " \n", + " \n", + " 0.0133333\n", + " \n", + " \n", + " \n", + " \n", + " -3.33333e-09\n", + " \n", + " \n", + " \n", + " \n", + " -66.6667\n", + " \n", + " \n", + " \n", + " \n", + " 0.677002\n", + " \n", + " \n", + " \n", + " \n", + " 0.999996\n", + " \n", + " \n", + " \n", + " \n", + " 1.677\n", + " \n", + " \n", + "
\n", + " 30\n", + " \n", + " \n", + " \n", + " \n", + " 0.0133333\n", + " \n", + " \n", + " \n", + " \n", + " -8.33333e-10\n", + " \n", + " \n", + " \n", + " \n", + " -66.6667\n", + " \n", + " \n", + " \n", + " \n", + " 0.677002\n", + " \n", + " \n", + " \n", + " \n", + " 0.999996\n", + " \n", + " \n", + " \n", + " \n", + " 1.677\n", + " \n", + " \n", + "
\n", + " 31\n", + " \n", + " \n", + " \n", + " \n", + " 0.315\n", + " \n", + " \n", + " \n", + " \n", + " -8.33333e-10\n", + " \n", + " \n", + " \n", + " \n", + " -56.6419\n", + " \n", + " \n", + " \n", + " \n", + " 0.990277\n", + " \n", + " \n", + " \n", + " \n", + " 0.999935\n", + " \n", + " \n", + " \n", + " \n", + " 1.99021\n", + " \n", + " \n", + "
\n", + " 32\n", + " \n", + " \n", + " \n", + " \n", + " 0.355004\n", + " \n", + " \n", + " \n", + " \n", + " -3.49229e-09\n", + " \n", + " \n", + " \n", + " \n", + " -58.3603\n", + " \n", + " \n", + " \n", + " \n", + " 0.982072\n", + " \n", + " \n", + " \n", + " \n", + " 0.999967\n", + " \n", + " \n", + " \n", + " \n", + " 1.98204\n", + " \n", + " \n", + "
\n", + " 33\n", + " \n", + " \n", + " \n", + " \n", + " 0.02\n", + " \n", + " \n", + " \n", + " \n", + " -8.33333e-10\n", + " \n", + " \n", + " \n", + " \n", + " -64.7362\n", + " \n", + " \n", + " \n", + " \n", + " 0.82081\n", + " \n", + " \n", + " \n", + " \n", + " 0.999994\n", + " \n", + " \n", + " \n", + " \n", + " 1.8208\n", + " \n", + " \n", + "
\n", + " 34\n", + " \n", + " \n", + " \n", + " \n", + " 0.02\n", + " \n", + " \n", + " \n", + " \n", + " -3.4855e-09\n", + " \n", + " \n", + " \n", + " \n", + " -65.857\n", + " \n", + " \n", + " \n", + " \n", + " 0.750734\n", + " \n", + " \n", + " \n", + " \n", + " 0.999996\n", + " \n", + " \n", + " \n", + " \n", + " 1.75073\n", + " \n", + " \n", + "
\n", + " 35\n", + " \n", + " \n", + " \n", + " \n", + " 0.0505248\n", + " \n", + " \n", + " \n", + " \n", + " -3.33333e-09\n", + " \n", + " \n", + " \n", + " \n", + " -66.5004\n", + " \n", + " \n", + " \n", + " \n", + " 0.69343\n", + " \n", + " \n", + " \n", + " \n", + " 0.999996\n", + " \n", + " \n", + " \n", + " \n", + " 1.69343\n", + " \n", + " \n", + "
\n", + " 36\n", + " \n", + " \n", + " \n", + " \n", + " 0.0133333\n", + " \n", + " \n", + " \n", + " \n", + " -2.5e-09\n", + " \n", + " \n", + " \n", + " \n", + " -66.6667\n", + " \n", + " \n", + " \n", + " \n", + " 0.677002\n", + " \n", + " \n", + " \n", + " \n", + " 0.999996\n", + " \n", + " \n", + " \n", + " \n", + " 1.677\n", + " \n", + " \n", + "
\n", + " 37\n", + " \n", + " \n", + " \n", + " \n", + " 0.02\n", + " \n", + " \n", + " \n", + " \n", + " -3.33333e-09\n", + " \n", + " \n", + " \n", + " \n", + " -66.6667\n", + " \n", + " \n", + " \n", + " \n", + " 0.677002\n", + " \n", + " \n", + " \n", + " \n", + " 0.999996\n", + " \n", + " \n", + " \n", + " \n", + " 1.677\n", + " \n", + " \n", + "
\n", + " 38\n", + " \n", + " \n", + " \n", + " \n", + " 0.315\n", + " \n", + " \n", + " \n", + " \n", + " -8.33333e-10\n", + " \n", + " \n", + " \n", + " \n", + " -56.6419\n", + " \n", + " \n", + " \n", + " \n", + " 0.990277\n", + " \n", + " \n", + " \n", + " \n", + " 0.999935\n", + " \n", + " \n", + " \n", + " \n", + " 1.99021\n", + " \n", + " \n", + "
\n", + " 39\n", + " \n", + " \n", + " \n", + " \n", + " 0.355004\n", + " \n", + " \n", + " \n", + " \n", + " -3.49229e-09\n", + " \n", + " \n", + " \n", + " \n", + " -58.3603\n", + " \n", + " \n", + " \n", + " \n", + " 0.982072\n", + " \n", + " \n", + " \n", + " \n", + " 0.999967\n", + " \n", + " \n", + " \n", + " \n", + " 1.98204\n", + " \n", + " \n", + "
\n", + " 40\n", + " \n", + " \n", + " \n", + " \n", + " 0.315\n", + " \n", + " \n", + " \n", + " \n", + " -8.33333e-10\n", + " \n", + " \n", + " \n", + " \n", + " -61.0755\n", + " \n", + " \n", + " \n", + " \n", + " 0.951301\n", + " \n", + " \n", + " \n", + " \n", + " 0.999986\n", + " \n", + " \n", + " \n", + " \n", + " 1.95129\n", + " \n", + " \n", + "
\n", + " 41\n", + " \n", + " \n", + " \n", + " \n", + " 0.02\n", + " \n", + " \n", + " \n", + " \n", + " -8.33333e-10\n", + " \n", + " \n", + " \n", + " \n", + " -64.7362\n", + " \n", + " \n", + " \n", + " \n", + " 0.82081\n", + " \n", + " \n", + " \n", + " \n", + " 0.999994\n", + " \n", + " \n", + " \n", + " \n", + " 1.8208\n", + " \n", + " \n", + "
\n", + " 42\n", + " \n", + " \n", + " \n", + " \n", + " 0.02\n", + " \n", + " \n", + " \n", + " \n", + " -3.4855e-09\n", + " \n", + " \n", + " \n", + " \n", + " -65.857\n", + " \n", + " \n", + " \n", + " \n", + " 0.750734\n", + " \n", + " \n", + " \n", + " \n", + " 0.999996\n", + " \n", + " \n", + " \n", + " \n", + " 1.75073\n", + " \n", + " \n", + "
\n", + " 43\n", + " \n", + " \n", + " \n", + " \n", + " 0.30112\n", + " \n", + " \n", + " \n", + " \n", + " -3.33333e-09\n", + " \n", + " \n", + " \n", + " \n", + " -48.3081\n", + " \n", + " \n", + " \n", + " \n", + " 0.999192\n", + " \n", + " \n", + " \n", + " \n", + " 0.990413\n", + " \n", + " \n", + " \n", + " \n", + " 1.98961\n", + " \n", + " \n", + "
\n", + " 44\n", + " \n", + " \n", + " \n", + " \n", + " 0.075065\n", + " \n", + " \n", + " \n", + " \n", + " -2.5e-09\n", + " \n", + " \n", + " \n", + " \n", + " -67.1658\n", + " \n", + " \n", + " \n", + " \n", + " 0.628013\n", + " \n", + " \n", + " \n", + " \n", + " 0.999997\n", + " \n", + " \n", + " \n", + " \n", + " 1.62801\n", + " \n", + " \n", + "
\n", + " 45\n", + " \n", + " \n", + " \n", + " \n", + " 0.314619\n", + " \n", + " \n", + " \n", + " \n", + " -3.92787e-09\n", + " \n", + " \n", + " \n", + " \n", + " -61.1917\n", + " \n", + " \n", + " \n", + " \n", + " 0.949719\n", + " \n", + " \n", + " \n", + " \n", + " 0.999987\n", + " \n", + " \n", + " \n", + " \n", + " 1.94971\n", + " \n", + " \n", + "
\n", + " 46\n", + " \n", + " \n", + " \n", + " \n", + " 0.24292\n", + " \n", + " \n", + " \n", + " \n", + " -2.5e-09\n", + " \n", + " \n", + " \n", + " \n", + " -58.2441\n", + " \n", + " \n", + " \n", + " \n", + " 0.982723\n", + " \n", + " \n", + " \n", + " \n", + " 0.999965\n", + " \n", + " \n", + " \n", + " \n", + " 1.98269\n", + " \n", + " \n", + "
\n", + " 47\n", + " \n", + " \n", + " \n", + " \n", + " 0.02\n", + " \n", + " \n", + " \n", + " \n", + " -8.33333e-10\n", + " \n", + " \n", + " \n", + " \n", + " -64.7362\n", + " \n", + " \n", + " \n", + " \n", + " 0.82081\n", + " \n", + " \n", + " \n", + " \n", + " 0.999994\n", + " \n", + " \n", + " \n", + " \n", + " 1.8208\n", + " \n", + " \n", + "
\n", + " 48\n", + " \n", + " \n", + " \n", + " \n", + " 0.02\n", + " \n", + " \n", + " \n", + " \n", + " -3.4855e-09\n", + " \n", + " \n", + " \n", + " \n", + " -65.857\n", + " \n", + " \n", + " \n", + " \n", + " 0.750734\n", + " \n", + " \n", + " \n", + " \n", + " 0.999996\n", + " \n", + " \n", + " \n", + " \n", + " 1.75073\n", + " \n", + " \n", + "
\n", + " 49\n", + " \n", + " \n", + " \n", + " \n", + " 0.30112\n", + " \n", + " \n", + " \n", + " \n", + " -3.33333e-09\n", + " \n", + " \n", + " \n", + " \n", + " -48.3081\n", + " \n", + " \n", + " \n", + " \n", + " 0.999192\n", + " \n", + " \n", + " \n", + " \n", + " 0.990413\n", + " \n", + " \n", + " \n", + " \n", + " 1.98961\n", + " \n", + " \n", + "
\n", + " 50\n", + " \n", + " \n", + " \n", + " \n", + " 0.075065\n", + " \n", + " \n", + " \n", + " \n", + " -2.5e-09\n", + " \n", + " \n", + " \n", + " \n", + " -67.1658\n", + " \n", + " \n", + " \n", + " \n", + " 0.628013\n", + " \n", + " \n", + " \n", + " \n", + " 0.999997\n", + " \n", + " \n", + " \n", + " \n", + " 1.62801\n", + " \n", + " \n", + "
\n", + " 51\n", + " \n", + " \n", + " \n", + " \n", + " 0.315\n", + " \n", + " \n", + " \n", + " \n", + " -8.33333e-10\n", + " \n", + " \n", + " \n", + " \n", + " -56.6419\n", + " \n", + " \n", + " \n", + " \n", + " 0.990277\n", + " \n", + " \n", + " \n", + " \n", + " 0.999935\n", + " \n", + " \n", + " \n", + " \n", + " 1.99021\n", + " \n", + " \n", + "
\n", + " 52\n", + " \n", + " \n", + " \n", + " \n", + " 0.314619\n", + " \n", + " \n", + " \n", + " \n", + " -3.92787e-09\n", + " \n", + " \n", + " \n", + " \n", + " -61.1917\n", + " \n", + " \n", + " \n", + " \n", + " 0.949719\n", + " \n", + " \n", + " \n", + " \n", + " 0.999987\n", + " \n", + " \n", + " \n", + " \n", + " 1.94971\n", + " \n", + " \n", + "
\n", + " 53\n", + " \n", + " \n", + " \n", + " \n", + " 0.02\n", + " \n", + " \n", + " \n", + " \n", + " -8.33333e-10\n", + " \n", + " \n", + " \n", + " \n", + " -64.7362\n", + " \n", + " \n", + " \n", + " \n", + " 0.82081\n", + " \n", + " \n", + " \n", + " \n", + " 0.999994\n", + " \n", + " \n", + " \n", + " \n", + " 1.8208\n", + " \n", + " \n", + "
\n", + " 54\n", + " \n", + " \n", + " \n", + " \n", + " 0.02\n", + " \n", + " \n", + " \n", + " \n", + " -3.33333e-09\n", + " \n", + " \n", + " \n", + " \n", + " -66.6667\n", + " \n", + " \n", + " \n", + " \n", + " 0.677002\n", + " \n", + " \n", + " \n", + " \n", + " 0.999996\n", + " \n", + " \n", + " \n", + " \n", + " 1.677\n", + " \n", + " \n", + "
\n", + " 55\n", + " \n", + " \n", + " \n", + " \n", + " 0.05308\n", + " \n", + " \n", + " \n", + " \n", + " -3.34239e-09\n", + " \n", + " \n", + " \n", + " \n", + " -56.9061\n", + " \n", + " \n", + " \n", + " \n", + " 0.989271\n", + " \n", + " \n", + " \n", + " \n", + " 0.999941\n", + " \n", + " \n", + " \n", + " \n", + " 1.98921\n", + " \n", + " \n", + "
\n", + " 56\n", + " \n", + " \n", + " \n", + " \n", + " 0.299621\n", + " \n", + " \n", + " \n", + " \n", + " -2.51859e-09\n", + " \n", + " \n", + " \n", + " \n", + " -66.8128\n", + " \n", + " \n", + " \n", + " \n", + " 0.665168\n", + " \n", + " \n", + " \n", + " \n", + " 0.999997\n", + " \n", + " \n", + " \n", + " \n", + " 1.66516\n", + " \n", + " \n", + "
\n", + " 57\n", + " \n", + " \n", + " \n", + " \n", + " 0.295743\n", + " \n", + " \n", + " \n", + " \n", + " -8.33333e-10\n", + " \n", + " \n", + " \n", + " \n", + " -61.2744\n", + " \n", + " \n", + " \n", + " \n", + " 0.948094\n", + " \n", + " \n", + " \n", + " \n", + " 0.999987\n", + " \n", + " \n", + " \n", + " \n", + " 1.94808\n", + " \n", + " \n", + "
\n", + " 58\n", + " \n", + " \n", + " \n", + " \n", + " 0.314623\n", + " \n", + " \n", + " \n", + " \n", + " -3.72768e-09\n", + " \n", + " \n", + " \n", + " \n", + " -69.5439\n", + " \n", + " \n", + " \n", + " \n", + " 0.312905\n", + " \n", + " \n", + " \n", + " \n", + " 0.999998\n", + " \n", + " \n", + " \n", + " \n", + " 1.3129\n", + " \n", + " \n", + "
\n", + " 59\n", + " \n", + " \n", + " \n", + " \n", + " 0.02\n", + " \n", + " \n", + " \n", + " \n", + " -2.5e-09\n", + " \n", + " \n", + " \n", + " \n", + " -64.4274\n", + " \n", + " \n", + " \n", + " \n", + " 0.842909\n", + " \n", + " \n", + " \n", + " \n", + " 0.999994\n", + " \n", + " \n", + " \n", + " \n", + " 1.8429\n", + " \n", + " \n", + "
\n", + " 60\n", + " \n", + " \n", + " \n", + " \n", + " 0.02\n", + " \n", + " \n", + " \n", + " \n", + " -3.37009e-09\n", + " \n", + " \n", + " \n", + " \n", + " -66.3177\n", + " \n", + " \n", + " \n", + " \n", + " 0.709294\n", + " \n", + " \n", + " \n", + " \n", + " 0.999996\n", + " \n", + " \n", + " \n", + " \n", + " 1.70929\n", + " \n", + " \n", + "
\n", + " 61\n", + " \n", + " \n", + " \n", + " \n", + " 0.30112\n", + " \n", + " \n", + " \n", + " \n", + " -3.33333e-09\n", + " \n", + " \n", + " \n", + " \n", + " -48.3081\n", + " \n", + " \n", + " \n", + " \n", + " 0.999192\n", + " \n", + " \n", + " \n", + " \n", + " 0.990413\n", + " \n", + " \n", + " \n", + " \n", + " 1.98961\n", + " \n", + " \n", + "
\n", + " 62\n", + " \n", + " \n", + " \n", + " \n", + " 0.314623\n", + " \n", + " \n", + " \n", + " \n", + " -3.72768e-09\n", + " \n", + " \n", + " \n", + " \n", + " -69.5439\n", + " \n", + " \n", + " \n", + " \n", + " 0.312905\n", + " \n", + " \n", + " \n", + " \n", + " 0.999998\n", + " \n", + " \n", + " \n", + " \n", + " 1.3129\n", + " \n", + " \n", + "
\n", + " 63\n", + " \n", + " \n", + " \n", + " \n", + " 0.315\n", + " \n", + " \n", + " \n", + " \n", + " -8.33333e-10\n", + " \n", + " \n", + " \n", + " \n", + " -56.6419\n", + " \n", + " \n", + " \n", + " \n", + " 0.990277\n", + " \n", + " \n", + " \n", + " \n", + " 0.999935\n", + " \n", + " \n", + " \n", + " \n", + " 1.99021\n", + " \n", + " \n", + "
\n", + " 64\n", + " \n", + " \n", + " \n", + " \n", + " 0.075065\n", + " \n", + " \n", + " \n", + " \n", + " -2.5e-09\n", + " \n", + " \n", + " \n", + " \n", + " -67.1658\n", + " \n", + " \n", + " \n", + " \n", + " 0.628013\n", + " \n", + " \n", + " \n", + " \n", + " 0.999997\n", + " \n", + " \n", + " \n", + " \n", + " 1.62801\n", + " \n", + " \n", + "
\n", + " 65\n", + " \n", + " \n", + " \n", + " \n", + " 0.02\n", + " \n", + " \n", + " \n", + " \n", + " -8.33333e-10\n", + " \n", + " \n", + " \n", + " \n", + " -64.7362\n", + " \n", + " \n", + " \n", + " \n", + " 0.82081\n", + " \n", + " \n", + " \n", + " \n", + " 0.999994\n", + " \n", + " \n", + " \n", + " \n", + " 1.8208\n", + " \n", + " \n", + "
\n", + " 66\n", + " \n", + " \n", + " \n", + " \n", + " 0.02\n", + " \n", + " \n", + " \n", + " \n", + " -3.37009e-09\n", + " \n", + " \n", + " \n", + " \n", + " -66.3177\n", + " \n", + " \n", + " \n", + " \n", + " 0.709294\n", + " \n", + " \n", + " \n", + " \n", + " 0.999996\n", + " \n", + " \n", + " \n", + " \n", + " 1.70929\n", + " \n", + " \n", + "
\n", + " 67\n", + " \n", + " \n", + " \n", + " \n", + " 0.270673\n", + " \n", + " \n", + " \n", + " \n", + " -3.33333e-09\n", + " \n", + " \n", + " \n", + " \n", + " -44.308\n", + " \n", + " \n", + " \n", + " \n", + " 0.999591\n", + " \n", + " \n", + " \n", + " \n", + " 0.530777\n", + " \n", + " \n", + " \n", + " \n", + " 1.53037\n", + " \n", + " \n", + "
\n", + " 68\n", + " \n", + " \n", + " \n", + " \n", + " 0.273521\n", + " \n", + " \n", + " \n", + " \n", + " -3.82345e-09\n", + " \n", + " \n", + " \n", + " \n", + " -69.9012\n", + " \n", + " \n", + " \n", + " \n", + " 0.254777\n", + " \n", + " \n", + " \n", + " \n", + " 0.999998\n", + " \n", + " \n", + " \n", + " \n", + " 1.25478\n", + " \n", + " \n", + "
\n", + " 69\n", + " \n", + " \n", + " \n", + " \n", + " 0.315\n", + " \n", + " \n", + " \n", + " \n", + " -2.5e-09\n", + " \n", + " \n", + " \n", + " \n", + " -58.905\n", + " \n", + " \n", + " \n", + " \n", + " 0.978096\n", + " \n", + " \n", + " \n", + " \n", + " 0.999973\n", + " \n", + " \n", + " \n", + " \n", + " 1.97807\n", + " \n", + " \n", + "
\n", + " 70\n", + " \n", + " \n", + " \n", + " \n", + " 0.075065\n", + " \n", + " \n", + " \n", + " \n", + " -2.5e-09\n", + " \n", + " \n", + " \n", + " \n", + " -67.1658\n", + " \n", + " \n", + " \n", + " \n", + " 0.628013\n", + " \n", + " \n", + " \n", + " \n", + " 0.999997\n", + " \n", + " \n", + " \n", + " \n", + " 1.62801\n", + " \n", + " \n", + "
\n", + " 71\n", + " \n", + " \n", + " \n", + " \n", + " 0.02\n", + " \n", + " \n", + " \n", + " \n", + " -8.33333e-10\n", + " \n", + " \n", + " \n", + " \n", + " -64.8152\n", + " \n", + " \n", + " \n", + " \n", + " 0.82081\n", + " \n", + " \n", + " \n", + " \n", + " 0.999995\n", + " \n", + " \n", + " \n", + " \n", + " 1.8208\n", + " \n", + " \n", + "
\n", + " 72\n", + " \n", + " \n", + " \n", + " \n", + " 0.0240765\n", + " \n", + " \n", + " \n", + " \n", + " -3.37009e-09\n", + " \n", + " \n", + " \n", + " \n", + " -66.2387\n", + " \n", + " \n", + " \n", + " \n", + " 0.715482\n", + " \n", + " \n", + " \n", + " \n", + " 0.999996\n", + " \n", + " \n", + " \n", + " \n", + " 1.71548\n", + " \n", + " \n", + "
\n", + " 73\n", + " \n", + " \n", + " \n", + " \n", + " 0.270673\n", + " \n", + " \n", + " \n", + " \n", + " -3.33333e-09\n", + " \n", + " \n", + " \n", + " \n", + " -44.308\n", + " \n", + " \n", + " \n", + " \n", + " 0.999591\n", + " \n", + " \n", + " \n", + " \n", + " 0.530777\n", + " \n", + " \n", + " \n", + " \n", + " 1.53037\n", + " \n", + " \n", + "
\n", + " 74\n", + " \n", + " \n", + " \n", + " \n", + " 0.273521\n", + " \n", + " \n", + " \n", + " \n", + " -3.82345e-09\n", + " \n", + " \n", + " \n", + " \n", + " -69.9012\n", + " \n", + " \n", + " \n", + " \n", + " 0.254777\n", + " \n", + " \n", + " \n", + " \n", + " 0.999998\n", + " \n", + " \n", + " \n", + " \n", + " 1.25478\n", + " \n", + " \n", + "
\n", + " 75\n", + " \n", + " \n", + " \n", + " \n", + " 0.30112\n", + " \n", + " \n", + " \n", + " \n", + " -3.33333e-09\n", + " \n", + " \n", + " \n", + " \n", + " -48.3081\n", + " \n", + " \n", + " \n", + " \n", + " 0.999192\n", + " \n", + " \n", + " \n", + " \n", + " 0.990413\n", + " \n", + " \n", + " \n", + " \n", + " 1.98961\n", + " \n", + " \n", + "
\n", + " 76\n", + " \n", + " \n", + " \n", + " \n", + " 0.314623\n", + " \n", + " \n", + " \n", + " \n", + " -3.72768e-09\n", + " \n", + " \n", + " \n", + " \n", + " -69.5439\n", + " \n", + " \n", + " \n", + " \n", + " 0.312905\n", + " \n", + " \n", + " \n", + " \n", + " 0.999998\n", + " \n", + " \n", + " \n", + " \n", + " 1.3129\n", + " \n", + " \n", + "
\n", + " 77\n", + " \n", + " \n", + " \n", + " \n", + " 0.075065\n", + " \n", + " \n", + " \n", + " \n", + " -2.5e-09\n", + " \n", + " \n", + " \n", + " \n", + " -67.1658\n", + " \n", + " \n", + " \n", + " \n", + " 0.628013\n", + " \n", + " \n", + " \n", + " \n", + " 0.999997\n", + " \n", + " \n", + " \n", + " \n", + " 1.62801\n", + " \n", + " \n", + "
\n", + " 78\n", + " \n", + " \n", + " \n", + " \n", + " 0.02\n", + " \n", + " \n", + " \n", + " \n", + " -8.33333e-10\n", + " \n", + " \n", + " \n", + " \n", + " -64.7362\n", + " \n", + " \n", + " \n", + " \n", + " 0.82081\n", + " \n", + " \n", + " \n", + " \n", + " 0.999994\n", + " \n", + " \n", + " \n", + " \n", + " 1.8208\n", + " \n", + " \n", + "
\n", + " 79\n", + " \n", + " \n", + " \n", + " \n", + " 0.270673\n", + " \n", + " \n", + " \n", + " \n", + " -3.73502e-09\n", + " \n", + " \n", + " \n", + " \n", + " -72.677\n", + " \n", + " \n", + " \n", + " \n", + " 0.245429\n", + " \n", + " \n", + " \n", + " \n", + " 0.999999\n", + " \n", + " \n", + " \n", + " \n", + " 1.24543\n", + " \n", + " \n", + "
\n", + " 80\n", + " \n", + " \n", + " \n", + " \n", + " 0.375591\n", + " \n", + " \n", + " \n", + " \n", + " -3.89202e-09\n", + " \n", + " \n", + " \n", + " \n", + " -41.9365\n", + " \n", + " \n", + " \n", + " \n", + " 0.999672\n", + " \n", + " \n", + " \n", + " \n", + " 0.980199\n", + " \n", + " \n", + " \n", + " \n", + " 1.97987\n", + " \n", + " \n", + "
\n", + " 81\n", + " \n", + " \n", + " \n", + " \n", + " 0.418643\n", + " \n", + " \n", + " \n", + " \n", + " -3.70706e-09\n", + " \n", + " \n", + " \n", + " \n", + " -48.3081\n", + " \n", + " \n", + " \n", + " \n", + " 0.999192\n", + " \n", + " \n", + " \n", + " \n", + " 0.990413\n", + " \n", + " \n", + " \n", + " \n", + " 1.98961\n", + " \n", + " \n", + "
\n", + " 82\n", + " \n", + " \n", + " \n", + " \n", + " 0.352582\n", + " \n", + " \n", + " \n", + " \n", + " -3.35395e-09\n", + " \n", + " \n", + " \n", + " \n", + " -69.5439\n", + " \n", + " \n", + " \n", + " \n", + " 0.312905\n", + " \n", + " \n", + " \n", + " \n", + " 0.999998\n", + " \n", + " \n", + " \n", + " \n", + " 1.3129\n", + " \n", + " \n", + "
\n", + " 83\n", + " \n", + " \n", + " \n", + " \n", + " 0.0221373\n", + " \n", + " \n", + " \n", + " \n", + " -2.5e-09\n", + " \n", + " \n", + " \n", + " \n", + " -64.8951\n", + " \n", + " \n", + " \n", + " \n", + " 0.811809\n", + " \n", + " \n", + " \n", + " \n", + " 0.999994\n", + " \n", + " \n", + " \n", + " \n", + " 1.8118\n", + " \n", + " \n", + "
\n", + " 84\n", + " \n", + " \n", + " \n", + " \n", + " 0.130058\n", + " \n", + " \n", + " \n", + " \n", + " -2.5e-09\n", + " \n", + " \n", + " \n", + " \n", + " -67.0069\n", + " \n", + " \n", + " \n", + " \n", + " 0.646014\n", + " \n", + " \n", + " \n", + " \n", + " 0.999997\n", + " \n", + " \n", + " \n", + " \n", + " 1.64601\n", + " \n", + " \n", + "
\n", + " 85\n", + " \n", + " \n", + " \n", + " \n", + " 0.270673\n", + " \n", + " \n", + " \n", + " \n", + " -3.33333e-09\n", + " \n", + " \n", + " \n", + " \n", + " -44.308\n", + " \n", + " \n", + " \n", + " \n", + " 0.999591\n", + " \n", + " \n", + " \n", + " \n", + " 0.530777\n", + " \n", + " \n", + " \n", + " \n", + " 1.53037\n", + " \n", + " \n", + "
\n", + " 86\n", + " \n", + " \n", + " \n", + " \n", + " 0.270673\n", + " \n", + " \n", + " \n", + " \n", + " -3.73502e-09\n", + " \n", + " \n", + " \n", + " \n", + " -72.677\n", + " \n", + " \n", + " \n", + " \n", + " 0.245429\n", + " \n", + " \n", + " \n", + " \n", + " 0.999999\n", + " \n", + " \n", + " \n", + " \n", + " 1.24543\n", + " \n", + " \n", + "
\n", + " 87\n", + " \n", + " \n", + " \n", + " \n", + " 0.418643\n", + " \n", + " \n", + " \n", + " \n", + " -3.70706e-09\n", + " \n", + " \n", + " \n", + " \n", + " -48.3081\n", + " \n", + " \n", + " \n", + " \n", + " 0.999192\n", + " \n", + " \n", + " \n", + " \n", + " 0.990413\n", + " \n", + " \n", + " \n", + " \n", + " 1.98961\n", + " \n", + " \n", + "
\n", + " 88\n", + " \n", + " \n", + " \n", + " \n", + " 0.314623\n", + " \n", + " \n", + " \n", + " \n", + " -3.72768e-09\n", + " \n", + " \n", + " \n", + " \n", + " -69.5439\n", + " \n", + " \n", + " \n", + " \n", + " 0.312905\n", + " \n", + " \n", + " \n", + " \n", + " 0.999998\n", + " \n", + " \n", + " \n", + " \n", + " 1.3129\n", + " \n", + " \n", + "
\n", + " 89\n", + " \n", + " \n", + " \n", + " \n", + " 0.075065\n", + " \n", + " \n", + " \n", + " \n", + " -2.5e-09\n", + " \n", + " \n", + " \n", + " \n", + " -67.1658\n", + " \n", + " \n", + " \n", + " \n", + " 0.628013\n", + " \n", + " \n", + " \n", + " \n", + " 0.999997\n", + " \n", + " \n", + " \n", + " \n", + " 1.62801\n", + " \n", + " \n", + "
\n", + " 90\n", + " \n", + " \n", + " \n", + " \n", + " 0.02\n", + " \n", + " \n", + " \n", + " \n", + " -8.33333e-10\n", + " \n", + " \n", + " \n", + " \n", + " -64.7362\n", + " \n", + " \n", + " \n", + " \n", + " 0.82081\n", + " \n", + " \n", + " \n", + " \n", + " 0.999994\n", + " \n", + " \n", + " \n", + " \n", + " 1.8208\n", + " \n", + " \n", + "
\n", + " 91\n", + " \n", + " \n", + " \n", + " \n", + " 0.270673\n", + " \n", + " \n", + " \n", + " \n", + " -3.33333e-09\n", + " \n", + " \n", + " \n", + " \n", + " -44.308\n", + " \n", + " \n", + " \n", + " \n", + " 0.999591\n", + " \n", + " \n", + " \n", + " \n", + " 0.530777\n", + " \n", + " \n", + " \n", + " \n", + " 1.53037\n", + " \n", + " \n", + "
\n", + " 92\n", + " \n", + " \n", + " \n", + " \n", + " 0.270673\n", + " \n", + " \n", + " \n", + " \n", + " -3.66757e-09\n", + " \n", + " \n", + " \n", + " \n", + " -75.3623\n", + " \n", + " \n", + " \n", + " \n", + " 0.674407\n", + " \n", + " \n", + " \n", + " \n", + " 0.999999\n", + " \n", + " \n", + " \n", + " \n", + " 1.67441\n", + " \n", + " \n", + "
\n", + " 93\n", + " \n", + " \n", + " \n", + " \n", + " 0.424699\n", + " \n", + " \n", + " \n", + " \n", + " -3.72721e-09\n", + " \n", + " \n", + " \n", + " \n", + " -49.6004\n", + " \n", + " \n", + " \n", + " \n", + " 0.998911\n", + " \n", + " \n", + " \n", + " \n", + " 0.996737\n", + " \n", + " \n", + " \n", + " \n", + " 1.99565\n", + " \n", + " \n", + "
\n", + " 94\n", + " \n", + " \n", + " \n", + " \n", + " 0.308567\n", + " \n", + " \n", + " \n", + " \n", + " -3.70753e-09\n", + " \n", + " \n", + " \n", + " \n", + " -68.2523\n", + " \n", + " \n", + " \n", + " \n", + " 0.500986\n", + " \n", + " \n", + " \n", + " \n", + " 0.999997\n", + " \n", + " \n", + " \n", + " \n", + " 1.50098\n", + " \n", + " \n", + "
\n", + " 95\n", + " \n", + " \n", + " \n", + " \n", + " 0.0666094\n", + " \n", + " \n", + " \n", + " \n", + " -2.5e-09\n", + " \n", + " \n", + " \n", + " \n", + " -67.1658\n", + " \n", + " \n", + " \n", + " \n", + " 0.628013\n", + " \n", + " \n", + " \n", + " \n", + " 0.999997\n", + " \n", + " \n", + " \n", + " \n", + " 1.62801\n", + " \n", + " \n", + "
\n", + " 96\n", + " \n", + " \n", + " \n", + " \n", + " 0.02\n", + " \n", + " \n", + " \n", + " \n", + " -8.33333e-10\n", + " \n", + " \n", + " \n", + " \n", + " -64.7362\n", + " \n", + " \n", + " \n", + " \n", + " 0.82081\n", + " \n", + " \n", + " \n", + " \n", + " 0.999994\n", + " \n", + " \n", + " \n", + " \n", + " 1.8208\n", + " \n", + " \n", + "
\n", + " 97\n", + " \n", + " \n", + " \n", + " \n", + " 0.270673\n", + " \n", + " \n", + " \n", + " \n", + " -3.33333e-09\n", + " \n", + " \n", + " \n", + " \n", + " -44.308\n", + " \n", + " \n", + " \n", + " \n", + " 0.999591\n", + " \n", + " \n", + " \n", + " \n", + " 0.530777\n", + " \n", + " \n", + " \n", + " \n", + " 1.53037\n", + " \n", + " \n", + "
\n", + " 98\n", + " \n", + " \n", + " \n", + " \n", + " 0.270673\n", + " \n", + " \n", + " \n", + " \n", + " -3.33333e-09\n", + " \n", + " \n", + " \n", + " \n", + " -44.308\n", + " \n", + " \n", + " \n", + " \n", + " 0.999591\n", + " \n", + " \n", + " \n", + " \n", + " 0.530777\n", + " \n", + " \n", + " \n", + " \n", + " 1.53037\n", + " \n", + " \n", + "
\n", + " 99\n", + " \n", + " \n", + " \n", + " \n", + " 0.270673\n", + " \n", + " \n", + " \n", + " \n", + " -3.73502e-09\n", + " \n", + " \n", + " \n", + " \n", + " -72.677\n", + " \n", + " \n", + " \n", + " \n", + " 0.245429\n", + " \n", + " \n", + " \n", + " \n", + " 0.999999\n", + " \n", + " \n", + " \n", + " \n", + " 1.24543\n", + " \n", + " \n", + "
\n", + " 100\n", + " \n", + " \n", + " \n", + " \n", + " 0.418643\n", + " \n", + " \n", + " \n", + " \n", + " -3.70706e-09\n", + " \n", + " \n", + " \n", + " \n", + " -48.3081\n", + " \n", + " \n", + " \n", + " \n", + " 0.999192\n", + " \n", + " \n", + " \n", + " \n", + " 0.990413\n", + " \n", + " \n", + " \n", + " \n", + " 1.98961\n", + " \n", + " \n", + "
\n", + " 101\n", + " \n", + " \n", + " \n", + " \n", + " 0.075065\n", + " \n", + " \n", + " \n", + " \n", + " -2.5e-09\n", + " \n", + " \n", + " \n", + " \n", + " -67.1658\n", + " \n", + " \n", + " \n", + " \n", + " 0.628013\n", + " \n", + " \n", + " \n", + " \n", + " 0.999997\n", + " \n", + " \n", + " \n", + " \n", + " 1.62801\n", + " \n", + " \n", + "
\n", + " 102\n", + " \n", + " \n", + " \n", + " \n", + " 0.308567\n", + " \n", + " \n", + " \n", + " \n", + " -3.70753e-09\n", + " \n", + " \n", + " \n", + " \n", + " -68.2523\n", + " \n", + " \n", + " \n", + " \n", + " 0.500986\n", + " \n", + " \n", + " \n", + " \n", + " 0.999997\n", + " \n", + " \n", + " \n", + " \n", + " 1.50098\n", + " \n", + " \n", + "
\n", + " 103\n", + " \n", + " \n", + " \n", + " \n", + " 0.270673\n", + " \n", + " \n", + " \n", + " \n", + " -3.15941e-09\n", + " \n", + " \n", + " \n", + " \n", + " -42.8222\n", + " \n", + " \n", + " \n", + " \n", + " 0.999641\n", + " \n", + " \n", + " \n", + " \n", + " 0.627117\n", + " \n", + " \n", + " \n", + " \n", + " 1.62676\n", + " \n", + " \n", + "
\n", + " 104\n", + " \n", + " \n", + " \n", + " \n", + " 0.270673\n", + " \n", + " \n", + " \n", + " \n", + " -3.33333e-09\n", + " \n", + " \n", + " \n", + " \n", + " -51.2433\n", + " \n", + " \n", + " \n", + " \n", + " 0.998321\n", + " \n", + " \n", + " \n", + " \n", + " 0.998998\n", + " \n", + " \n", + " \n", + " \n", + " 1.99732\n", + " \n", + " \n", + "
\n", + " 105\n", + " \n", + " \n", + " \n", + " \n", + " 0.200806\n", + " \n", + " \n", + " \n", + " \n", + " -3.71685e-09\n", + " \n", + " \n", + " \n", + " \n", + " -49.4358\n", + " \n", + " \n", + " \n", + " \n", + " 0.998956\n", + " \n", + " \n", + " \n", + " \n", + " 0.996337\n", + " \n", + " \n", + " \n", + " \n", + " 1.99529\n", + " \n", + " \n", + "
\n", + " 106\n", + " \n", + " \n", + " \n", + " \n", + " 0.273288\n", + " \n", + " \n", + " \n", + " \n", + " -3.73459e-09\n", + " \n", + " \n", + " \n", + " \n", + " -70.1854\n", + " \n", + " \n", + " \n", + " \n", + " 0.206859\n", + " \n", + " \n", + " \n", + " \n", + " 0.999998\n", + " \n", + " \n", + " \n", + " \n", + " 1.20686\n", + " \n", + " \n", + "
\n", + " 107\n", + " \n", + " \n", + " \n", + " \n", + " 0.075065\n", + " \n", + " \n", + " \n", + " \n", + " -2.50012e-09\n", + " \n", + " \n", + " \n", + " \n", + " -56.215\n", + " \n", + " \n", + " \n", + " \n", + " 0.991713\n", + " \n", + " \n", + " \n", + " \n", + " 0.999926\n", + " \n", + " \n", + " \n", + " \n", + " 1.99164\n", + " \n", + " \n", + "
\n", + " 108\n", + " \n", + " \n", + " \n", + " \n", + " 0.31791\n", + " \n", + " \n", + " \n", + " \n", + " -3.99577e-09\n", + " \n", + " \n", + " \n", + " \n", + " -71.6096\n", + " \n", + " \n", + " \n", + " \n", + " 0.0484075\n", + " \n", + " \n", + " \n", + " \n", + " 0.999999\n", + " \n", + " \n", + " \n", + " \n", + " 1.04841\n", + " \n", + " \n", + "
\n", + " 109\n", + " \n", + " \n", + " \n", + " \n", + " 0.270673\n", + " \n", + " \n", + " \n", + " \n", + " -3.33333e-09\n", + " \n", + " \n", + " \n", + " \n", + " -44.308\n", + " \n", + " \n", + " \n", + " \n", + " 0.999591\n", + " \n", + " \n", + " \n", + " \n", + " 0.530777\n", + " \n", + " \n", + " \n", + " \n", + " 1.53037\n", + " \n", + " \n", + "
\n", + " 110\n", + " \n", + " \n", + " \n", + " \n", + " 0.270673\n", + " \n", + " \n", + " \n", + " \n", + " -3.33333e-09\n", + " \n", + " \n", + " \n", + " \n", + " -44.308\n", + " \n", + " \n", + " \n", + " \n", + " 0.999591\n", + " \n", + " \n", + " \n", + " \n", + " 0.530777\n", + " \n", + " \n", + " \n", + " \n", + " 1.53037\n", + " \n", + " \n", + "
\n", + " 111\n", + " \n", + " \n", + " \n", + " \n", + " 0.31791\n", + " \n", + " \n", + " \n", + " \n", + " -3.99577e-09\n", + " \n", + " \n", + " \n", + " \n", + " -71.6096\n", + " \n", + " \n", + " \n", + " \n", + " 0.0484075\n", + " \n", + " \n", + " \n", + " \n", + " 0.999999\n", + " \n", + " \n", + " \n", + " \n", + " 1.04841\n", + " \n", + " \n", + "
\n", + " 112\n", + " \n", + " \n", + " \n", + " \n", + " 0.418643\n", + " \n", + " \n", + " \n", + " \n", + " -3.70706e-09\n", + " \n", + " \n", + " \n", + " \n", + " -48.3081\n", + " \n", + " \n", + " \n", + " \n", + " 0.999192\n", + " \n", + " \n", + " \n", + " \n", + " 0.990413\n", + " \n", + " \n", + " \n", + " \n", + " 1.98961\n", + " \n", + " \n", + "
\n", + " 113\n", + " \n", + " \n", + " \n", + " \n", + " 0.075065\n", + " \n", + " \n", + " \n", + " \n", + " -2.5e-09\n", + " \n", + " \n", + " \n", + " \n", + " -67.1658\n", + " \n", + " \n", + " \n", + " \n", + " 0.628013\n", + " \n", + " \n", + " \n", + " \n", + " 0.999997\n", + " \n", + " \n", + " \n", + " \n", + " 1.62801\n", + " \n", + " \n", + "
\n", + " 114\n", + " \n", + " \n", + " \n", + " \n", + " 0.273288\n", + " \n", + " \n", + " \n", + " \n", + " -3.73459e-09\n", + " \n", + " \n", + " \n", + " \n", + " -70.1854\n", + " \n", + " \n", + " \n", + " \n", + " 0.206859\n", + " \n", + " \n", + " \n", + " \n", + " 0.999998\n", + " \n", + " \n", + " \n", + " \n", + " 1.20686\n", + " \n", + " \n", + "
\n", + " 115\n", + " \n", + " \n", + " \n", + " \n", + " 0.270673\n", + " \n", + " \n", + " \n", + " \n", + " -3.33678e-09\n", + " \n", + " \n", + " \n", + " \n", + " -44.308\n", + " \n", + " \n", + " \n", + " \n", + " 0.999591\n", + " \n", + " \n", + " \n", + " \n", + " 0.530777\n", + " \n", + " \n", + " \n", + " \n", + " 1.53037\n", + " \n", + " \n", + "
\n", + " 116\n", + " \n", + " \n", + " \n", + " \n", + " 0.330268\n", + " \n", + " \n", + " \n", + " \n", + " -3.33979e-09\n", + " \n", + " \n", + " \n", + " \n", + " -40.1401\n", + " \n", + " \n", + " \n", + " \n", + " 0.999689\n", + " \n", + " \n", + " \n", + " \n", + " 1\n", + " \n", + " \n", + " \n", + " \n", + " 1.99969\n", + " \n", + " \n", + "
\n", + " 117\n", + " \n", + " \n", + " \n", + " \n", + " 0.31791\n", + " \n", + " \n", + " \n", + " \n", + " -3.99253e-09\n", + " \n", + " \n", + " \n", + " \n", + " -71.167\n", + " \n", + " \n", + " \n", + " \n", + " 0.0329048\n", + " \n", + " \n", + " \n", + " \n", + " 0.999998\n", + " \n", + " \n", + " \n", + " \n", + " 1.0329\n", + " \n", + " \n", + "
\n", + " 118\n", + " \n", + " \n", + " \n", + " \n", + " 0.418643\n", + " \n", + " \n", + " \n", + " \n", + " -3.71029e-09\n", + " \n", + " \n", + " \n", + " \n", + " -51.4749\n", + " \n", + " \n", + " \n", + " \n", + " 0.998179\n", + " \n", + " \n", + " \n", + " \n", + " 0.999056\n", + " \n", + " \n", + " \n", + " \n", + " 1.99723\n", + " \n", + " \n", + "
\n", + " 119\n", + " \n", + " \n", + " \n", + " \n", + " 0.0378987\n", + " \n", + " \n", + " \n", + " \n", + " -3.69099e-09\n", + " \n", + " \n", + " \n", + " \n", + " -67.16\n", + " \n", + " \n", + " \n", + " \n", + " 0.628013\n", + " \n", + " \n", + " \n", + " \n", + " 0.999997\n", + " \n", + " \n", + " \n", + " \n", + " 1.62801\n", + " \n", + " \n", + "
\n", + " 120\n", + " \n", + " \n", + " \n", + " \n", + " 0.273288\n", + " \n", + " \n", + " \n", + " \n", + " -2.55112e-09\n", + " \n", + " \n", + " \n", + " \n", + " -71.2139\n", + " \n", + " \n", + " \n", + " \n", + " 0.0244008\n", + " \n", + " \n", + " \n", + " \n", + " 0.999998\n", + " \n", + " \n", + " \n", + " \n", + " 1.0244\n", + " \n", + " \n", + "
\n", + " 121\n", + " \n", + " \n", + " \n", + " \n", + " 0.270673\n", + " \n", + " \n", + " \n", + " \n", + " -3.33678e-09\n", + " \n", + " \n", + " \n", + " \n", + " -44.308\n", + " \n", + " \n", + " \n", + " \n", + " 0.999591\n", + " \n", + " \n", + " \n", + " \n", + " 0.530777\n", + " \n", + " \n", + " \n", + " \n", + " 1.53037\n", + " \n", + " \n", + "
\n", + " 122\n", + " \n", + " \n", + " \n", + " \n", + " 0.273288\n", + " \n", + " \n", + " \n", + " \n", + " -2.55112e-09\n", + " \n", + " \n", + " \n", + " \n", + " -71.2139\n", + " \n", + " \n", + " \n", + " \n", + " 0.0244008\n", + " \n", + " \n", + " \n", + " \n", + " 0.999998\n", + " \n", + " \n", + " \n", + " \n", + " 1.0244\n", + " \n", + " \n", + "
\n", + " 123\n", + " \n", + " \n", + " \n", + " \n", + " 0.418643\n", + " \n", + " \n", + " \n", + " \n", + " -3.70706e-09\n", + " \n", + " \n", + " \n", + " \n", + " -48.3081\n", + " \n", + " \n", + " \n", + " \n", + " 0.999192\n", + " \n", + " \n", + " \n", + " \n", + " 0.990413\n", + " \n", + " \n", + " \n", + " \n", + " 1.98961\n", + " \n", + " \n", + "
\n", + " 124\n", + " \n", + " \n", + " \n", + " \n", + " 0.0378987\n", + " \n", + " \n", + " \n", + " \n", + " -3.69099e-09\n", + " \n", + " \n", + " \n", + " \n", + " -67.16\n", + " \n", + " \n", + " \n", + " \n", + " 0.628013\n", + " \n", + " \n", + " \n", + " \n", + " 0.999997\n", + " \n", + " \n", + " \n", + " \n", + " 1.62801\n", + " \n", + " \n", + "
\n", + " 125\n", + " \n", + " \n", + " \n", + " \n", + " 0.273288\n", + " \n", + " \n", + " \n", + " \n", + " -3.73459e-09\n", + " \n", + " \n", + " \n", + " \n", + " -70.1854\n", + " \n", + " \n", + " \n", + " \n", + " 0.206859\n", + " \n", + " \n", + " \n", + " \n", + " 0.999998\n", + " \n", + " \n", + " \n", + " \n", + " 1.20686\n", + " \n", + " \n", + "
\n", + " 126\n", + " \n", + " \n", + " \n", + " \n", + " 0.418643\n", + " \n", + " \n", + " \n", + " \n", + " -3.71029e-09\n", + " \n", + " \n", + " \n", + " \n", + " -51.4749\n", + " \n", + " \n", + " \n", + " \n", + " 0.998179\n", + " \n", + " \n", + " \n", + " \n", + " 0.999056\n", + " \n", + " \n", + " \n", + " \n", + " 1.99723\n", + " \n", + " \n", + "
\n", + " 127\n", + " \n", + " \n", + " \n", + " \n", + " 0.237673\n", + " \n", + " \n", + " \n", + " \n", + " -2.85784e-09\n", + " \n", + " \n", + " \n", + " \n", + " -79.495\n", + " \n", + " \n", + " \n", + " \n", + " 0.965835\n", + " \n", + " \n", + " \n", + " \n", + " 0.999999\n", + " \n", + " \n", + " \n", + " \n", + " 1.96583\n", + " \n", + " \n", + "
\n", + " 128\n", + " \n", + " \n", + " \n", + " \n", + " 0.273288\n", + " \n", + " \n", + " \n", + " \n", + " -3.30767e-09\n", + " \n", + " \n", + " \n", + " \n", + " -44.8052\n", + " \n", + " \n", + " \n", + " \n", + " 0.999558\n", + " \n", + " \n", + " \n", + " \n", + " 0.676402\n", + " \n", + " \n", + " \n", + " \n", + " 1.67596\n", + " \n", + " \n", + "
\n", + " 129\n", + " \n", + " \n", + " \n", + " \n", + " 0.430771\n", + " \n", + " \n", + " \n", + " \n", + " -3.70706e-09\n", + " \n", + " \n", + " \n", + " \n", + " -69.4221\n", + " \n", + " \n", + " \n", + " \n", + " 0.332259\n", + " \n", + " \n", + " \n", + " \n", + " 0.999998\n", + " \n", + " \n", + " \n", + " \n", + " 1.33226\n", + " \n", + " \n", + "
\n", + " 130\n", + " \n", + " \n", + " \n", + " \n", + " 0.0596979\n", + " \n", + " \n", + " \n", + " \n", + " -3.86642e-09\n", + " \n", + " \n", + " \n", + " \n", + " -67.16\n", + " \n", + " \n", + " \n", + " \n", + " 0.628013\n", + " \n", + " \n", + " \n", + " \n", + " 0.999997\n", + " \n", + " \n", + " \n", + " \n", + " 1.62801\n", + " \n", + " \n", + "
\n", + " 131\n", + " \n", + " \n", + " \n", + " \n", + " 0.288868\n", + " \n", + " \n", + " \n", + " \n", + " -3.73459e-09\n", + " \n", + " \n", + " \n", + " \n", + " -72.4108\n", + " \n", + " \n", + " \n", + " \n", + " 0.196516\n", + " \n", + " \n", + " \n", + " \n", + " 0.999999\n", + " \n", + " \n", + " \n", + " \n", + " 1.19651\n", + " \n", + " \n", + "
\n", + " 132\n", + " \n", + " \n", + " \n", + " \n", + " 0.484905\n", + " \n", + " \n", + " \n", + " \n", + " -3.71029e-09\n", + " \n", + " \n", + " \n", + " \n", + " -60.8363\n", + " \n", + " \n", + " \n", + " \n", + " 0.955798\n", + " \n", + " \n", + " \n", + " \n", + " 0.999986\n", + " \n", + " \n", + " \n", + " \n", + " 1.95578\n", + " \n", + " \n", + "
\n", + " 133\n", + " \n", + " \n", + " \n", + " \n", + " 0.270673\n", + " \n", + " \n", + " \n", + " \n", + " -3.33678e-09\n", + " \n", + " \n", + " \n", + " \n", + " -44.308\n", + " \n", + " \n", + " \n", + " \n", + " 0.999591\n", + " \n", + " \n", + " \n", + " \n", + " 0.530777\n", + " \n", + " \n", + " \n", + " \n", + " 1.53037\n", + " \n", + " \n", + "
\n", + " 134\n", + " \n", + " \n", + " \n", + " \n", + " 0.273288\n", + " \n", + " \n", + " \n", + " \n", + " -2.55112e-09\n", + " \n", + " \n", + " \n", + " \n", + " -71.2139\n", + " \n", + " \n", + " \n", + " \n", + " 0.0244008\n", + " \n", + " \n", + " \n", + " \n", + " 0.999998\n", + " \n", + " \n", + " \n", + " \n", + " 1.0244\n", + " \n", + " \n", + "
\n", + " 135\n", + " \n", + " \n", + " \n", + " \n", + " 0.273288\n", + " \n", + " \n", + " \n", + " \n", + " -3.30767e-09\n", + " \n", + " \n", + " \n", + " \n", + " -44.8052\n", + " \n", + " \n", + " \n", + " \n", + " 0.999558\n", + " \n", + " \n", + " \n", + " \n", + " 0.676402\n", + " \n", + " \n", + " \n", + " \n", + " 1.67596\n", + " \n", + " \n", + "
\n", + " 136\n", + " \n", + " \n", + " \n", + " \n", + " 0.418643\n", + " \n", + " \n", + " \n", + " \n", + " -3.70706e-09\n", + " \n", + " \n", + " \n", + " \n", + " -48.3081\n", + " \n", + " \n", + " \n", + " \n", + " 0.999192\n", + " \n", + " \n", + " \n", + " \n", + " 0.990413\n", + " \n", + " \n", + " \n", + " \n", + " 1.98961\n", + " \n", + " \n", + "
\n", + " 137\n", + " \n", + " \n", + " \n", + " \n", + " 0.0596979\n", + " \n", + " \n", + " \n", + " \n", + " -3.86642e-09\n", + " \n", + " \n", + " \n", + " \n", + " -67.16\n", + " \n", + " \n", + " \n", + " \n", + " 0.628013\n", + " \n", + " \n", + " \n", + " \n", + " 0.999997\n", + " \n", + " \n", + " \n", + " \n", + " 1.62801\n", + " \n", + " \n", + "
\n", + " 138\n", + " \n", + " \n", + " \n", + " \n", + " 0.430771\n", + " \n", + " \n", + " \n", + " \n", + " -3.70706e-09\n", + " \n", + " \n", + " \n", + " \n", + " -69.4221\n", + " \n", + " \n", + " \n", + " \n", + " 0.332259\n", + " \n", + " \n", + " \n", + " \n", + " 0.999998\n", + " \n", + " \n", + " \n", + " \n", + " 1.33226\n", + " \n", + " \n", + "
\n", + " 139\n", + " \n", + " \n", + " \n", + " \n", + " 0.273235\n", + " \n", + " \n", + " \n", + " \n", + " -2.60048e-09\n", + " \n", + " \n", + " \n", + " \n", + " -73.9265\n", + " \n", + " \n", + " \n", + " \n", + " 0.463329\n", + " \n", + " \n", + " \n", + " \n", + " 0.999999\n", + " \n", + " \n", + " \n", + " \n", + " 1.46333\n", + " \n", + " \n", + "
\n", + " 140\n", + " \n", + " \n", + " \n", + " \n", + " 0.270726\n", + " \n", + " \n", + " \n", + " \n", + " -3.24919e-09\n", + " \n", + " \n", + " \n", + " \n", + " -40.4684\n", + " \n", + " \n", + " \n", + " \n", + " 0.999687\n", + " \n", + " \n", + " \n", + " \n", + " 1\n", + " \n", + " \n", + " \n", + " \n", + " 1.99969\n", + " \n", + " \n", + "
\n", + " 141\n", + " \n", + " \n", + " \n", + " \n", + " 0.273288\n", + " \n", + " \n", + " \n", + " \n", + " -3.30326e-09\n", + " \n", + " \n", + " \n", + " \n", + " -49.2359\n", + " \n", + " \n", + " \n", + " \n", + " 0.999005\n", + " \n", + " \n", + " \n", + " \n", + " 0.995716\n", + " \n", + " \n", + " \n", + " \n", + " 1.99472\n", + " \n", + " \n", + "
\n", + " 142\n", + " \n", + " \n", + " \n", + " \n", + " 0.418643\n", + " \n", + " \n", + " \n", + " \n", + " -3.84301e-09\n", + " \n", + " \n", + " \n", + " \n", + " -48.3081\n", + " \n", + " \n", + " \n", + " \n", + " 0.999192\n", + " \n", + " \n", + " \n", + " \n", + " 0.990413\n", + " \n", + " \n", + " \n", + " \n", + " 1.98961\n", + " \n", + " \n", + "
\n", + " 143\n", + " \n", + " \n", + " \n", + " \n", + " 0.0297963\n", + " \n", + " \n", + " \n", + " \n", + " -3.78388e-09\n", + " \n", + " \n", + " \n", + " \n", + " -67.16\n", + " \n", + " \n", + " \n", + " \n", + " 0.628013\n", + " \n", + " \n", + " \n", + " \n", + " 0.999997\n", + " \n", + " \n", + " \n", + " \n", + " 1.62801\n", + " \n", + " \n", + "
\n", + " 144\n", + " \n", + " \n", + " \n", + " \n", + " 0.430771\n", + " \n", + " \n", + " \n", + " \n", + " -3.70706e-09\n", + " \n", + " \n", + " \n", + " \n", + " -69.4221\n", + " \n", + " \n", + " \n", + " \n", + " 0.332259\n", + " \n", + " \n", + " \n", + " \n", + " 0.999998\n", + " \n", + " \n", + " \n", + " \n", + " 1.33226\n", + " \n", + " \n", + "
\n", + " 145\n", + " \n", + " \n", + " \n", + " \n", + " 0.270673\n", + " \n", + " \n", + " \n", + " \n", + " -3.33678e-09\n", + " \n", + " \n", + " \n", + " \n", + " -44.308\n", + " \n", + " \n", + " \n", + " \n", + " 0.999591\n", + " \n", + " \n", + " \n", + " \n", + " 0.530777\n", + " \n", + " \n", + " \n", + " \n", + " 1.53037\n", + " \n", + " \n", + "
\n", + " 146\n", + " \n", + " \n", + " \n", + " \n", + " 0.273288\n", + " \n", + " \n", + " \n", + " \n", + " -2.55112e-09\n", + " \n", + " \n", + " \n", + " \n", + " -71.2139\n", + " \n", + " \n", + " \n", + " \n", + " 0.0244008\n", + " \n", + " \n", + " \n", + " \n", + " 0.999998\n", + " \n", + " \n", + " \n", + " \n", + " 1.0244\n", + " \n", + " \n", + "
\n", + " 147\n", + " \n", + " \n", + " \n", + " \n", + " 0.273288\n", + " \n", + " \n", + " \n", + " \n", + " -3.30767e-09\n", + " \n", + " \n", + " \n", + " \n", + " -44.8052\n", + " \n", + " \n", + " \n", + " \n", + " 0.999558\n", + " \n", + " \n", + " \n", + " \n", + " 0.676402\n", + " \n", + " \n", + " \n", + " \n", + " 1.67596\n", + " \n", + " \n", + "
\n", + " 148\n", + " \n", + " \n", + " \n", + " \n", + " 0.0596979\n", + " \n", + " \n", + " \n", + " \n", + " -3.86642e-09\n", + " \n", + " \n", + " \n", + " \n", + " -67.16\n", + " \n", + " \n", + " \n", + " \n", + " 0.628013\n", + " \n", + " \n", + " \n", + " \n", + " 0.999997\n", + " \n", + " \n", + " \n", + " \n", + " 1.62801\n", + " \n", + " \n", + "
\n", + " 149\n", + " \n", + " \n", + " \n", + " \n", + " 0.418643\n", + " \n", + " \n", + " \n", + " \n", + " -3.84301e-09\n", + " \n", + " \n", + " \n", + " \n", + " -48.3081\n", + " \n", + " \n", + " \n", + " \n", + " 0.999192\n", + " \n", + " \n", + " \n", + " \n", + " 0.990413\n", + " \n", + " \n", + " \n", + " \n", + " 1.98961\n", + " \n", + " \n", + "
\n", + " 150\n", + " \n", + " \n", + " \n", + " \n", + " 0.273288\n", + " \n", + " \n", + " \n", + " \n", + " -3.30326e-09\n", + " \n", + " \n", + " \n", + " \n", + " -49.2359\n", + " \n", + " \n", + " \n", + " \n", + " 0.999005\n", + " \n", + " \n", + " \n", + " \n", + " 0.995716\n", + " \n", + " \n", + " \n", + " \n", + " 1.99472\n", + " \n", + " \n", + "
\n", + " 151\n", + " \n", + " \n", + " \n", + " \n", + " 0.273344\n", + " \n", + " \n", + " \n", + " \n", + " -3.33678e-09\n", + " \n", + " \n", + " \n", + " \n", + " -42.6729\n", + " \n", + " \n", + " \n", + " \n", + " 0.999655\n", + " \n", + " \n", + " \n", + " \n", + " 0.613916\n", + " \n", + " \n", + " \n", + " \n", + " 1.61357\n", + " \n", + " \n", + "
\n", + " 152\n", + " \n", + " \n", + " \n", + " \n", + " 0.270618\n", + " \n", + " \n", + " \n", + " \n", + " -2.55112e-09\n", + " \n", + " \n", + " \n", + " \n", + " -68.7713\n", + " \n", + " \n", + " \n", + " \n", + " 0.43004\n", + " \n", + " \n", + " \n", + " \n", + " 0.999998\n", + " \n", + " \n", + " \n", + " \n", + " 1.43004\n", + " \n", + " \n", + "
\n", + " 153\n", + " \n", + " \n", + " \n", + " \n", + " 0.428314\n", + " \n", + " \n", + " \n", + " \n", + " -3.21443e-09\n", + " \n", + " \n", + " \n", + " \n", + " -45.1329\n", + " \n", + " \n", + " \n", + " \n", + " 0.999535\n", + " \n", + " \n", + " \n", + " \n", + " 0.754275\n", + " \n", + " \n", + " \n", + " \n", + " 1.75381\n", + " \n", + " \n", + "
\n", + " 154\n", + " \n", + " \n", + " \n", + " \n", + " 0.0596979\n", + " \n", + " \n", + " \n", + " \n", + " -4.40753e-09\n", + " \n", + " \n", + " \n", + " \n", + " -73.7294\n", + " \n", + " \n", + " \n", + " \n", + " 0.431083\n", + " \n", + " \n", + " \n", + " \n", + " 0.999999\n", + " \n", + " \n", + " \n", + " \n", + " 1.43108\n", + " \n", + " \n", + "
\n", + " 155\n", + " \n", + " \n", + " \n", + " \n", + " 0.233812\n", + " \n", + " \n", + " \n", + " \n", + " -3.85316e-09\n", + " \n", + " \n", + " \n", + " \n", + " -48.3081\n", + " \n", + " \n", + " \n", + " \n", + " 0.999192\n", + " \n", + " \n", + " \n", + " \n", + " 0.990413\n", + " \n", + " \n", + " \n", + " \n", + " 1.98961\n", + " \n", + " \n", + "
\n", + " 156\n", + " \n", + " \n", + " \n", + " \n", + " 0.409696\n", + " \n", + " \n", + " \n", + " \n", + " -3.29311e-09\n", + " \n", + " \n", + " \n", + " \n", + " -45.3788\n", + " \n", + " \n", + " \n", + " \n", + " 0.999532\n", + " \n", + " \n", + " \n", + " \n", + " 0.849873\n", + " \n", + " \n", + " \n", + " \n", + " 1.84941\n", + " \n", + " \n", + "
\n", + " 157\n", + " \n", + " \n", + " \n", + " \n", + " 0.270673\n", + " \n", + " \n", + " \n", + " \n", + " -3.33678e-09\n", + " \n", + " \n", + " \n", + " \n", + " -44.308\n", + " \n", + " \n", + " \n", + " \n", + " 0.999591\n", + " \n", + " \n", + " \n", + " \n", + " 0.530777\n", + " \n", + " \n", + " \n", + " \n", + " 1.53037\n", + " \n", + " \n", + "
\n", + " 158\n", + " \n", + " \n", + " \n", + " \n", + " 0.273288\n", + " \n", + " \n", + " \n", + " \n", + " -2.55112e-09\n", + " \n", + " \n", + " \n", + " \n", + " -71.2139\n", + " \n", + " \n", + " \n", + " \n", + " 0.0244008\n", + " \n", + " \n", + " \n", + " \n", + " 0.999998\n", + " \n", + " \n", + " \n", + " \n", + " 1.0244\n", + " \n", + " \n", + "
\n", + " 159\n", + " \n", + " \n", + " \n", + " \n", + " 0.270618\n", + " \n", + " \n", + " \n", + " \n", + " -2.55112e-09\n", + " \n", + " \n", + " \n", + " \n", + " -68.7713\n", + " \n", + " \n", + " \n", + " \n", + " 0.43004\n", + " \n", + " \n", + " \n", + " \n", + " 0.999998\n", + " \n", + " \n", + " \n", + " \n", + " 1.43004\n", + " \n", + " \n", + "
\n", + " 160\n", + " \n", + " \n", + " \n", + " \n", + " 0.0596979\n", + " \n", + " \n", + " \n", + " \n", + " -3.86642e-09\n", + " \n", + " \n", + " \n", + " \n", + " -67.16\n", + " \n", + " \n", + " \n", + " \n", + " 0.628013\n", + " \n", + " \n", + " \n", + " \n", + " 0.999997\n", + " \n", + " \n", + " \n", + " \n", + " 1.62801\n", + " \n", + " \n", + "
\n", + " 161\n", + " \n", + " \n", + " \n", + " \n", + " 0.409696\n", + " \n", + " \n", + " \n", + " \n", + " -3.29311e-09\n", + " \n", + " \n", + " \n", + " \n", + " -45.3788\n", + " \n", + " \n", + " \n", + " \n", + " 0.999532\n", + " \n", + " \n", + " \n", + " \n", + " 0.849873\n", + " \n", + " \n", + " \n", + " \n", + " 1.84941\n", + " \n", + " \n", + "
\n", + " 162\n", + " \n", + " \n", + " \n", + " \n", + " 0.273288\n", + " \n", + " \n", + " \n", + " \n", + " -3.30767e-09\n", + " \n", + " \n", + " \n", + " \n", + " -44.8052\n", + " \n", + " \n", + " \n", + " \n", + " 0.999558\n", + " \n", + " \n", + " \n", + " \n", + " 0.676402\n", + " \n", + " \n", + " \n", + " \n", + " 1.67596\n", + " \n", + " \n", + "
\n", + " 163\n", + " \n", + " \n", + " \n", + " \n", + " 0.30571\n", + " \n", + " \n", + " \n", + " \n", + " -3.29785e-09\n", + " \n", + " \n", + " \n", + " \n", + " -43.3664\n", + " \n", + " \n", + " \n", + " \n", + " 0.999634\n", + " \n", + " \n", + " \n", + " \n", + " 0.0180554\n", + " \n", + " \n", + " \n", + " \n", + " 1.01769\n", + " \n", + " \n", + "
\n", + " 164\n", + " \n", + " \n", + " \n", + " \n", + " 0.318097\n", + " \n", + " \n", + " \n", + " \n", + " -2.59451e-09\n", + " \n", + " \n", + " \n", + " \n", + " -63.1783\n", + " \n", + " \n", + " \n", + " \n", + " 0.896884\n", + " \n", + " \n", + " \n", + " \n", + " 0.999992\n", + " \n", + " \n", + " \n", + " \n", + " 1.89688\n", + " \n", + " \n", + "
\n", + " 165\n", + " \n", + " \n", + " \n", + " \n", + " 0.270618\n", + " \n", + " \n", + " \n", + " \n", + " -3.76371e-09\n", + " \n", + " \n", + " \n", + " \n", + " -70.906\n", + " \n", + " \n", + " \n", + " \n", + " 0.0802616\n", + " \n", + " \n", + " \n", + " \n", + " 0.999998\n", + " \n", + " \n", + " \n", + " \n", + " 1.08026\n", + " \n", + " \n", + "
\n", + " 166\n", + " \n", + " \n", + " \n", + " \n", + " 0.0596979\n", + " \n", + " \n", + " \n", + " \n", + " -2.54952e-09\n", + " \n", + " \n", + " \n", + " \n", + " -67.483\n", + " \n", + " \n", + " \n", + " \n", + " 0.590345\n", + " \n", + " \n", + " \n", + " \n", + " 0.999997\n", + " \n", + " \n", + " \n", + " \n", + " 1.59034\n", + " \n", + " \n", + "
\n", + " 167\n", + " \n", + " \n", + " \n", + " \n", + " 0.407956\n", + " \n", + " \n", + " \n", + " \n", + " -3.29311e-09\n", + " \n", + " \n", + " \n", + " \n", + " -46.0782\n", + " \n", + " \n", + " \n", + " \n", + " 0.999478\n", + " \n", + " \n", + " \n", + " \n", + " 0.926824\n", + " \n", + " \n", + " \n", + " \n", + " 1.9263\n", + " \n", + " \n", + "
\n", + " 168\n", + " \n", + " \n", + " \n", + " \n", + " 0.275028\n", + " \n", + " \n", + " \n", + " \n", + " -3.30767e-09\n", + " \n", + " \n", + " \n", + " \n", + " -44.7916\n", + " \n", + " \n", + " \n", + " \n", + " 0.999558\n", + " \n", + " \n", + " \n", + " \n", + " 0.667153\n", + " \n", + " \n", + " \n", + " \n", + " 1.66671\n", + " \n", + " \n", + "
\n", + " 169\n", + " \n", + " \n", + " \n", + " \n", + " 0.273288\n", + " \n", + " \n", + " \n", + " \n", + " -2.55112e-09\n", + " \n", + " \n", + " \n", + " \n", + " -71.2139\n", + " \n", + " \n", + " \n", + " \n", + " 0.0244008\n", + " \n", + " \n", + " \n", + " \n", + " 0.999998\n", + " \n", + " \n", + " \n", + " \n", + " 1.0244\n", + " \n", + " \n", + "
\n", + " 170\n", + " \n", + " \n", + " \n", + " \n", + " 0.30571\n", + " \n", + " \n", + " \n", + " \n", + " -3.29785e-09\n", + " \n", + " \n", + " \n", + " \n", + " -43.3664\n", + " \n", + " \n", + " \n", + " \n", + " 0.999634\n", + " \n", + " \n", + " \n", + " \n", + " 0.0180554\n", + " \n", + " \n", + " \n", + " \n", + " 1.01769\n", + " \n", + " \n", + "
\n", + " 171\n", + " \n", + " \n", + " \n", + " \n", + " 0.270673\n", + " \n", + " \n", + " \n", + " \n", + " -3.33678e-09\n", + " \n", + " \n", + " \n", + " \n", + " -44.308\n", + " \n", + " \n", + " \n", + " \n", + " 0.999591\n", + " \n", + " \n", + " \n", + " \n", + " 0.530777\n", + " \n", + " \n", + " \n", + " \n", + " 1.53037\n", + " \n", + " \n", + "
\n", + " 172\n", + " \n", + " \n", + " \n", + " \n", + " 0.270618\n", + " \n", + " \n", + " \n", + " \n", + " -2.55112e-09\n", + " \n", + " \n", + " \n", + " \n", + " -68.7713\n", + " \n", + " \n", + " \n", + " \n", + " 0.43004\n", + " \n", + " \n", + " \n", + " \n", + " 0.999998\n", + " \n", + " \n", + " \n", + " \n", + " 1.43004\n", + " \n", + " \n", + "
\n", + " 173\n", + " \n", + " \n", + " \n", + " \n", + " 0.318097\n", + " \n", + " \n", + " \n", + " \n", + " -2.59451e-09\n", + " \n", + " \n", + " \n", + " \n", + " -63.1783\n", + " \n", + " \n", + " \n", + " \n", + " 0.896884\n", + " \n", + " \n", + " \n", + " \n", + " 0.999992\n", + " \n", + " \n", + " \n", + " \n", + " 1.89688\n", + " \n", + " \n", + "
\n", + " 174\n", + " \n", + " \n", + " \n", + " \n", + " 0.270618\n", + " \n", + " \n", + " \n", + " \n", + " -3.76371e-09\n", + " \n", + " \n", + " \n", + " \n", + " -70.906\n", + " \n", + " \n", + " \n", + " \n", + " 0.0802616\n", + " \n", + " \n", + " \n", + " \n", + " 0.999998\n", + " \n", + " \n", + " \n", + " \n", + " 1.08026\n", + " \n", + " \n", + "
\n", + " 175\n", + " \n", + " \n", + " \n", + " \n", + " 0.379051\n", + " \n", + " \n", + " \n", + " \n", + " -3.26524e-09\n", + " \n", + " \n", + " \n", + " \n", + " -71.2139\n", + " \n", + " \n", + " \n", + " \n", + " 0.0244008\n", + " \n", + " \n", + " \n", + " \n", + " 0.999998\n", + " \n", + " \n", + " \n", + " \n", + " 1.0244\n", + " \n", + " \n", + "
\n", + " 176\n", + " \n", + " \n", + " \n", + " \n", + " 0.21782\n", + " \n", + " \n", + " \n", + " \n", + " -2.58774e-09\n", + " \n", + " \n", + " \n", + " \n", + " -43.3664\n", + " \n", + " \n", + " \n", + " \n", + " 0.999634\n", + " \n", + " \n", + " \n", + " \n", + " 0.018054\n", + " \n", + " \n", + " \n", + " \n", + " 1.01769\n", + " \n", + " \n", + "
\n", + " 177\n", + " \n", + " \n", + " \n", + " \n", + " 0.286228\n", + " \n", + " \n", + " \n", + " \n", + " -3.33678e-09\n", + " \n", + " \n", + " \n", + " \n", + " -42.1678\n", + " \n", + " \n", + " \n", + " \n", + " 0.999659\n", + " \n", + " \n", + " \n", + " \n", + " 0.945302\n", + " \n", + " \n", + " \n", + " \n", + " 1.94496\n", + " \n", + " \n", + "
\n", + " 178\n", + " \n", + " \n", + " \n", + " \n", + " 0.27067\n", + " \n", + " \n", + " \n", + " \n", + " -2.55112e-09\n", + " \n", + " \n", + " \n", + " \n", + " -68.7713\n", + " \n", + " \n", + " \n", + " \n", + " 0.43004\n", + " \n", + " \n", + " \n", + " \n", + " 0.999998\n", + " \n", + " \n", + " \n", + " \n", + " 1.43004\n", + " \n", + " \n", + "
\n", + " 179\n", + " \n", + " \n", + " \n", + " \n", + " 0.269936\n", + " \n", + " \n", + " \n", + " \n", + " -2.54621e-09\n", + " \n", + " \n", + " \n", + " \n", + " -61.3654\n", + " \n", + " \n", + " \n", + " \n", + " 0.946426\n", + " \n", + " \n", + " \n", + " \n", + " 0.999988\n", + " \n", + " \n", + " \n", + " \n", + " 1.94641\n", + " \n", + " \n", + "
\n", + " 180\n", + " \n", + " \n", + " \n", + " \n", + " 0.318779\n", + " \n", + " \n", + " \n", + " \n", + " -3.76371e-09\n", + " \n", + " \n", + " \n", + " \n", + " -56.0873\n", + " \n", + " \n", + " \n", + " \n", + " 0.992041\n", + " \n", + " \n", + " \n", + " \n", + " 0.99992\n", + " \n", + " \n", + " \n", + " \n", + " 1.99196\n", + " \n", + " \n", + "
\n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "attrs_ = [ list(p.dtc.attrs.keys()) for i,p in history.genealogy_history.items() ]\n", + "attrs = attrs_[0]\n", + "print(attrs)\n", + "\n", + "scores_ = [ list(p.dtc.scores.keys()) for i,p in history.genealogy_history.items() ]\n", + "scores = scores_[0]\n", + "from collections import OrderedDict\n", + "\n", + "urlDats = []\n", + "hi = [ (i,p) for i,p in history.genealogy_history.items() ]\n", + "pops_ = [ (i,p) for i,p in enumerate(pop) ]\n", + "\n", + "sc = [ (i,p) for i,p in enumerate(grid_results) ]\n", + "\n", + "import quantities as pq\n", + "\n", + "def history_iter(mapped):\n", + " i,p = mapped\n", + " gene_contents = OrderedDict()\n", + " #gene_contents['gene_number'] = i\n", + " \n", + " attrs = list(p.dtc.attrs.keys()) \n", + " scores = list(p.dtc.scores.keys()) \n", + " for a in attrs:\n", + " gene_contents[a] = p.dtc.attrs[a] \n", + " scores0 = scores[0]\n", + " for s in scores:\n", + " gene_contents[s] = p.dtc.scores[s]\n", + " gene_contents[str('total')] = sum(p.dtc.scores.values())\n", + " for test in p.dtc.score.keys():\n", + " if 'agreement' in p.dtc.score[test]:\n", + " gene_contents['disagreement'] = p.dtc.score[test]['agreement']\n", + " if 'observation' in p.dtc.score[test].keys() and 'prediction' in p.dtc.score[test].keys():\n", + " boolean_means = bool('mean' in p.dtc.score[test]['observation'].keys() and 'mean' in p.dtc.score[test]['prediction'].keys())\n", + " boolean_value = bool('value' in p.dtc.score[test]['observation'].keys() and 'value' in p.dtc.score[test]['prediction'].keys())\n", + "\n", + " if boolean_means:\n", + " gene_contents['disagreement'] = np.abs(p.dtc.score[test]['observation']['mean'] - p.dtc.score[test]['prediction']['mean'])\n", + " if boolean_value:\n", + " gene_contents['disagreement'] = np.abs(p.dtc.score[test]['observation']['value'] - p.dtc.score[test]['prediction']['value'])\n", + " print(gene_contents['disagreement'])\n", + " \n", + " return gene_contents\n", + "\n", + "\n", + " \n", + "def process_dics(contents):\n", + " dfs = []\n", + " for gene_contents in contents:\n", + " # pandas Data frames are best data container for maths/stats, but steep learning curve.\n", + " # Other exclusion criteria. Exclude reading levels above grade 100,\n", + " # as this is most likely a problem with the metric algorithm, and or rubbish data in.\n", + "\n", + " if len(dfs) == 0:\n", + " dfs = pd.DataFrame(pd.Series(gene_contents)).T\n", + " dfs = pd.concat([ dfs, pd.DataFrame(pd.Series(gene_contents)).T ])\n", + " return dfs\n", + "\n", + "genes = list(map(history_iter,hi)) \n", + "dfg = process_dics(genes)\n", + "\n", + "grids = list(map(history_iter,sc)) \n", + "dfs = process_dics(grids)\n", + "\n", + "dfg = dfg.reset_index(drop=True)\n", + "\n", + "# Set colormap equal to seaborns light green color palette\n", + "cm = sns.light_palette(\"green\", as_cmap=True)\n", + "display(dfg.style.background_gradient(cmap=cm,subset=['total']))\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + } + ], + "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.5.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/neuronunit/examples/begginer_friendly_backup.ipynb b/neuronunit/examples/begginer_friendly_backup.ipynb new file mode 100644 index 000000000..c15f7ab80 --- /dev/null +++ b/neuronunit/examples/begginer_friendly_backup.ipynb @@ -0,0 +1,1362 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Assumptions, the environment for running this notebook was arrived at by building a dedicated docker file.\n", + "\n", + "https://cloud.docker.com/repository/registry-1.docker.io/russelljarvis/nuo\n", + "\n", + "You can run use dockerhub to get the appropriate file, and launch this notebook using Kitematic." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Import libraries\n", + "To keep the standard running version of minimal and memory efficient, not all available packages are loaded by default. In the cell below I import a mixture common python modules, and custom developed modules associated with NeuronUnit (NU) development" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "\n", + "import numpy as np\n", + "import os\n", + "import pickle\n", + "import pandas as pd\n", + "from neuronunit.tests.fi import RheobaseTestP\n", + "from neuronunit.optimization.model_parameters import reduced_dict, reduced_cells \n", + "from neuronunit.optimization import optimization_management as om\n", + "from sciunit import scores# score_type \n", + "\n", + "from neuronunit.optimization.data_transport_container import DataTC\n", + "from neuronunit.tests.fi import RheobaseTestP# as discovery\n", + "from neuronunit.optimization.optimization_management import dtc_to_rheo, format_test, nunit_evaluation\n", + "import quantities as pq\n", + "from neuronunit.models.reduced import ReducedModel\n", + "from neuronunit.optimization.model_parameters import model_params, path_params\n", + "LEMS_MODEL_PATH = path_params['model_path']\n", + "list_to_frame = []\n", + "from neuronunit.tests.fi import RheobaseTestP\n", + "\n", + " \n", + "from IPython.display import HTML, display\n", + "import seaborn as sns\n", + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false + }, + "source": [ + "# The Izhiketich model is instanced using some well researched parameter sets.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First lets get the points in parameter space, that Izhikich himself has published about. These points are often used by the open source brain project to establish between model reproducibility. The itial motivating factor for choosing these points as constellations, of all possible parameter space subsets, is that these points where initially tuned and used as best guesses for matching real observed experimental recordings." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "explore_param = {k:(np.min(v),np.max(v)) for k,v in reduced_dict.items()}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false + }, + "source": [ + "## Get the experimental Data pertaining to four different classes or neurons, that can constrain models.\n", + "Next we get some electro physiology data for four different classes of cells that are very common targets of scientific neuronal modelling. We are interested in finding out what are the most minimal, and detail reduced, low complexity model equations, that are able to satisfy " + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false + }, + "source": [ + "Below are some of the data set ID's I used to query neuroelectro.\n", + "To save time for the reader, I prepared some data earlier to save time, and saved the data as a pickle, pythons preferred serialisation format.\n", + "\n", + "The interested reader can find some methods for getting cell specific ephys data from neuroelectro in a code file (neuronunit/optimization/get_neab.py) \n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "purkinje ={\"id\": 18, \"name\": \"Cerebellum Purkinje cell\", \"neuron_db_id\": 271, \"nlex_id\": \"sao471801888\"}\n", + "fi_basket = {\"id\": 65, \"name\": \"Dentate gyrus basket cell\", \"neuron_db_id\": None, \"nlex_id\": \"nlx_cell_100201\"}\n", + "pvis_cortex = {\"id\": 111, \"name\": \"Neocortex pyramidal cell layer 5-6\", \"neuron_db_id\": 265, \"nlex_id\": \"nifext_50\"}\n", + "#This olfactory mitral cell does not have datum about rheobase, current injection values.\n", + "olf_mitral = {\"id\": 129, \"name\": \"Olfactory bulb (main) mitral cell\", \"neuron_db_id\": 267, \"nlex_id\": \"nlx_anat_100201\"}\n", + "ca1_pyr = {\"id\": 85, \"name\": \"Hippocampus CA1 pyramidal cell\", \"neuron_db_id\": 258, \"nlex_id\": \"sao830368389\"}\n", + "pipe = [ fi_basket, ca1_pyr, purkinje, pvis_cortex]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "electro_tests = []\n", + "obs_frame = {}\n", + "test_frame = {}\n", + "\n", + "from neuronunit.optimization import get_neab\n", + "try: \n", + "\n", + " electro_path = str(os.getcwd())+'all_tests.p'\n", + "\n", + " assert os.path.isfile(electro_path) == True\n", + " with open(electro_path,'rb') as f:\n", + " (obs_frame,test_frame) = pickle.load(f)\n", + "\n", + "except:\n", + " for p in pipe:\n", + " p_tests, p_observations = get_neab.get_neuron_criteria(p)\n", + " obs_frame[p[\"name\"]] = p_observations#, p_tests))\n", + " test_frame[p[\"name\"]] = p_tests#, p_tests))\n", + " electro_path = str(os.getcwd())+'all_tests.p'\n", + " with open(electro_path,'wb') as f:\n", + " pickle.dump((obs_frame,test_frame),f)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Cast the tabulatable data to pandas data frame\n", + "There are many among us who prefer potentially tabulatable data to be encoded in pandas data frame." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false, + "scrolled": false + }, + "outputs": [], + "source": [ + "for k,v in test_frame.items():\n", + " if \"olf_mit\" not in k:\n", + " obs = obs_frame[k]\n", + " v[0] = RheobaseTestP(obs['Rheobase'])\n", + "df = pd.DataFrame.from_dict(obs_frame)\n", + "print(test_frame.keys())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the data frame below, you can see many different cell types" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "df\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "df['Hippocampus CA1 pyramidal cell']\n", + "\n", + "# enable R style caching.\n", + "#!pip install git+https://github.com/rossant/ipycache\n", + "#%load_ext ipycache" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Tweak Izhikitich equations\n", + "with educated guesses based on information that is already encoded in the predefined experimental observations.\n", + "\n", + "In otherwords use information that is readily amenable into hardcoding into equations \n", + "\n", + "Select out the 'Neocortex pyramidal cell layer 5-6' below, as a target for optimization" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false, + "scrolled": false + }, + "outputs": [], + "source": [ + "free_params = ['a','b','k','c','C','d','vPeak','vr']\n", + "hc_ = reduced_cells['RS']\n", + "hc_['vr'] = -65.2261863636364\n", + "hc_['vPeak'] = hc_['vr'] + 86.364525297619\n", + "explore_param['C'] = (hc_['C']-20,hc_['C']+20)\n", + "explore_param['vr'] = (hc_['vr']-5,hc_['vr']+5)\n", + "use_test = test_frame[\"Neocortex pyramidal cell layer 5-6\"]\n", + "\n", + "#for t in use_test[::-1]:\n", + "# t.score_type = scores.RatioScore\n", + "test_opt = {}\n", + "\n", + "with open('data_dump.p','wb') as f:\n", + " pickle.dump(test_opt,f)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "\n", + "use_test[0].observation\n", + "print(use_test[0].name)\n", + "\n", + "rtp = RheobaseTestP(use_test[0].observation)\n", + "use_test[0] = rtp\n", + "print(use_test[0].observation)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "reduced_cells.keys()\n", + "test_frame.keys()\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "#!pip install neo --upgrade\n", + "#%%cache mycache.pkl df\n", + "df = pd.DataFrame(index=list(test_frame.keys()),columns=list(reduced_cells.keys()))\n", + "\n", + "for k,v in reduced_cells.items():\n", + " temp = {}\n", + " temp[str(v)] = {}\n", + " dtc = DataTC()\n", + " dtc.tests = use_test\n", + " dtc.attrs = v \n", + " dtc.backend = 'RAW'\n", + " dtc.cell_name = 'vanilla'\n", + "\n", + "\n", + " for key, use_test in test_frame.items():\n", + " dtc.tests = use_test\n", + " dtc = dtc_to_rheo(dtc)\n", + " dtc = format_test(dtc)\n", + "\n", + " if dtc.rheobase is not None:\n", + " if dtc.rheobase!=-1.0:\n", + "\n", + " dtc = nunit_evaluation(dtc)\n", + "\n", + " df[k][key] = float(dtc.get_ss())\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A sparse grid sampling over the parameter space, using the published and well corrobarated parameter points, from Izhikitch publications, and the Open Source brain, shows that without optimization, using off the shelf parameter sets to fit real-life biological cell data, does not work so well." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As can be seen none of these well researched parameter sets correspond to good fits, with experimental data about waveform shape. These bad fit's warrant the use of an optimizer, which can efficiently, sparsely, and intelligently explore the parameter space for better model fits." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "\n", + "\n", + "MU = 10\n", + "NGEN = 200\n", + "\n", + "import pickle\n", + "import numpy as np\n", + "print(free_params)\n", + " \n", + "index, DO = om.run_ga(explore_param,NGEN,use_test,free_params=free_params, NSGA = True, MU = MU)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "#df\n", + "import numpy as np\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "import seaborn as sns\n", + "from IPython.display import HTML\n", + "\n", + "\n", + "import seaborn as sns\n", + "\n", + "cm = sns.light_palette(\"green\", as_cmap=True)\n", + "#df.pivot\n", + "\n", + "#sns.heatmap(df)\n", + "s = df.style.background_gradient(cmap=cm)\n", + "#values = df.values\n", + "\n", + "#values\n", + "#pivoted = df.pivot()\n", + "#s\n", + "type(s)\n", + "type(df['RS'])\n", + "s" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "\n", + "\n", + "from neuronunit.tests import dm \n", + "dmtests = dm.Druckmann2013Test\n", + "d_tests = []\n", + "for d in dir(dm):\n", + " if \"Test\" in d:\n", + " exec('d_tests.append(dm.'+str(d)+')')\n", + " \n", + " \n", + "df.min() \n", + "\n", + " \n", + "#print(d_tests)\n", + "\n", + "\n", + "#from neuronunit.tests.dm import InputResistanceTest as DMInputResistanceTest\n", + "#use_test.append(DMInputResistanceTest(injection_currents=[-11.0*pq.pA,-6*pq.pA,-1*pq.pA,]))\n", + "#print(use_test)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "\n", + "\n", + "MU = 6\n", + "NGEN = 200\n", + "\n", + "import pickle\n", + "import numpy as np\n", + "print(free_params)\n", + " \n", + "index, DO = om.run_ga(explore_param,NGEN,use_test,free_params=free_params, NSGA = False, MU = MU)\n", + "'''\n", + "MU = 6\n", + "NGEN = 200\n", + "\n", + "import pickle\n", + "\n", + "import numpy as np\n", + "try:\n", + " with open('multi_objective.p','rb') as f:\n", + " test_opt = pickle.load(f)\n", + "except:\n", + "\n", + "for index, use_test in enumerate(test_frame.values()):\n", + "\n", + " if index % 2 == 0:\n", + " index, DO = om.run_ga(explore_param,NGEN,use_test,free_params=free_params, NSGA = False, MU = MU)\n", + " else:\n", + " index, DO = om.run_ga(explore_param,NGEN,use_test,free_params=free_params, NSGA = False, MU = MU)\n", + " #print(NSGA)\n", + "\n", + " print('can get as low as 2.70295, 2.70679')\n", + "\n", + " test_opt = {str('multi_objective')+str(index):npcl}\n", + " with open('multi_objective.p','wb') as f:\n", + " pickle.dump(test_opt,f)\n", + "\n", + "\n", + "print(np.sum(list(test_opt['multi_objective']['pf'][2].dtc.scores.values())))\n", + "print(np.sum(list(test_opt['multi_objective']['pf'][1].dtc.scores.values())))\n", + "#print(np.sum(list(test_opt['multi_objective']['hof'][0].dtc.scores.values())))\n", + "print(test_opt['multi_objective']['pf'][2].dtc.scores.items())\n", + "print(test_opt['multi_objective']['pf'][1].dtc.scores.items())\n", + "'''\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "test_opt.keys()\n", + "for value in test_opt.values():\n", + " value['stds']\n", + " value['ranges']\n", + " print(value['ranges']) \n", + " print(value['stds'])\n", + " \n", + " #fig = pl.figure()\n", + " #ax = pl.subplot(111)\n", + " #ax.bar(range(len(value.keys())), values)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "with open('data_dump.p','rb') as f:\n", + " test_opt = pickle.load(f)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "test_opt" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "#errorbar\n", + "#for \n", + "import seaborn as sns\n", + "from matplotlib.pyplot import errorbar\n", + "import matplotlib.pyplot as plt\n", + "\n", + "fig0,ax0 = plt.subplots(dim,dim,figsize=(10,10))\n", + "plt.figure(num=None, figsize=(11, 11), dpi=80, facecolor='w', edgecolor='k')\n", + "\n", + "for v in test_opt.values():\n", + " x = 0\n", + " labels = []\n", + " plt.clf()\n", + " for k_,v_ in v['ranges'].items(): \n", + " #print(k_)\n", + " value = v_\n", + "\n", + " y = np.mean(value)\n", + " err = max(value)-min(value)\n", + " errorbar(x, y, err, marker='s', mfc='red',\n", + " mec='green', ms=2, mew=4,label='in '+str(k_))\n", + " x+=1\n", + " labels.append(k_)\n", + " plt.xticks(np.arange(len(labels)), labels)\n", + " ax0[i] = plt\n", + "\n", + " #plt.title(str(v))\n", + "\n", + "plt.show()\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "\n", + "from mpl_toolkits.mplot3d import Axes3D\n", + "from sklearn.cluster import KMeans\n", + "from sklearn import datasets\n", + "import seaborn as sns; sns.set() # for plot styling\n", + "dfs.replace([np.inf, -np.inf], np.nan)\n", + "dfs = dfs.dropna()\n", + "\n", + "#X = dfs[['standard','sp','ss','info_density','gf','standard','uniqueness','info_density','penalty']]\n", + "X = dfs[['standard','sp','ss']]\n", + "\n", + "X = X.as_matrix()\n", + "#import pdb; pdb.set_trace()\n", + "\n", + "est = KMeans(n_clusters=3)\n", + "\n", + "est.fit(X)\n", + "\n", + "y_kmeans = est.predict(X)\n", + "centers = est.cluster_centers_\n", + "\n", + "fignum = 1\n", + "fig = plt.figure(fignum, figsize=(4, 3))\n", + "ax = Axes3D(fig, rect=[0, 0, .95, 1], elev=48, azim=134)\n", + "ax.scatter(X[:, 0], X[:, 1], X[:, 2], c=y_kmeans, s=50)\n", + "ax.scatter(centers[:, 0], centers[:, 1], centers[:, 2], c='black', s=200, alpha=0.5);\n", + "ax.w_xaxis.set_ticklabels([])\n", + "ax.w_yaxis.set_ticklabels([])\n", + "ax.w_zaxis.set_ticklabels([])\n", + "ax.set_xlabel('standard')\n", + "ax.set_ylabel('subjectivity')\n", + "ax.set_zlabel('sentiment polarity')\n", + "#ax.set_title(titles[fignum - 1])\n", + "#ax.dist = 12\n", + "fignum = fignum + 1\n", + "for x,i in enumerate(zip(y_kmeans,dfs['clue_words'])):\n", + " try:\n", + " print(i[0],i[1],dfs['link'][x],dfs['publication'][x],dfs['clue_links'][x],dfs['sp_norm'][x],dfs['ss_norm'][x],dfs['uniqueness'][x])\n", + " except:\n", + " print(i)\n", + "\n", + "fig.savefig('3dCluster.png')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# the parameter 'd' only seems important\n", + "# C does not have to be too precise within a range." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "I consider the final gene populations for each of the eight tests. I compute the variance in each of the converged populations, I see that variance is low in many of the gene populations.\n", + "\n", + "When all variables are used to optomize only against one set of parameters, you expect their would be high variance in parameters, that don't matter much with respect to that error criteria (you expect redundancy of solutions).\n", + "\n", + "I compute std on errors over all the tests in order to estimate how amenable the problem is to multiobjective optimization." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from neuronunit.models.reduced import ReducedModel\n", + "from neuronunit.optimization.model_parameters import model_params, path_params\n", + "LEMS_MODEL_PATH = path_params['model_path']\n", + "import quantities as pq\n", + "plt.figure(num=None, figsize=(11, 11), dpi=80, facecolor='w', edgecolor='k')\n", + "\n", + "for k,v in test_opt.items(): \n", + " model = ReducedModel(LEMS_MODEL_PATH, name= str('vanilla'), backend=('RAW'))\n", + " model.attrs = v['out']['pf'][1].dtc.attrs\n", + " print(str(k), v['out']['pf'][1].dtc.get_ss())#fitness)\n", + " iparams = {}\n", + " iparams['injected_square_current'] = {}\n", + " iparams['injected_square_current']['amplitude'] =v['out']['pf'][1].rheobase['value']*pq.pA\n", + " #['amplitude'] = dtc.vtest[k]['injected_square_current']['amplitude']\n", + " DELAY = 100.0*pq.ms\n", + " DURATION = 1000.0*pq.ms\n", + " iparams['injected_square_current']['delay'] = DELAY\n", + " iparams['injected_square_current']['duration'] = int(DURATION)\n", + "\n", + " model.inject_square_current(iparams)\n", + "\n", + " plt.plot(model.get_membrane_potential().times,model.get_membrane_potential(),label=str(k))\n", + " plt.legend()\n", + "plt.show()\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "'''\n", + "#print([i.fitness.values for i in test_opt['t'][0]['pop']])#.keys()\n", + "print(np.std([i[0] for i in test_opt['t'][0]['pop'][0:5]]))#.keys()\n", + "print(np.std([i[1] for i in test_opt['t'][0]['pop'][0:5]]))#.keys()\n", + "print(np.std([i[2] for i in test_opt['t'][0]['pop'][0:5]]))#.keys()\n", + "print(np.std([i[3] for i in test_opt['t'][0]['pop'][0:5]]))#.keys()\n", + "print(test_opt['t'][0]['pop'][0][0])\n", + "print(test_opt['t'][0]['pop'][0][1])\n", + "test_opt['t'][0]['pop'][0].dtc.attrs\n", + "'''" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "\n", + "#values = { k:v for v in npcl['pop'][i].dtc.attrs.items() for i in npcl['pop'] }\n", + "#print(values) \n", + "#print(stds.keys())\n", + "#stds\n", + "#dtc.variances[k] for k in dtc.attrs.keys() " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "\n", + "DO.seed_pop = npcl['pf'][0:MU]\n", + "npcl, DO = om.run_ga(explore_param,10,reduced_tests,free_params=free_params,hc = hc, NSGA = False, MU = MU, seed_pop = DO.seed_pop)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "attrs_here = npcl['hardened'][0][0].attrs\n", + "attrs_here.update(hc)\n", + "attrs_here\n", + "scores = npcl['hof'][0].dtc.scores\n", + "print(scores)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "#\n", + "use_test = test_frame[\"Neocortex pyramidal cell layer 5-6\"]\n", + "reduced_tests = [use_test[0], use_test[-1], use_test[len(use_test)-1]]\n", + "bigger_tests = use_test[1:-2]\n", + "bigger_tests.insert(0,use_test[0])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "#bigger_tests = bigger_tests[-1::]\n", + "print(bigger_tests)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "DO.seed_pop = npcl['hof'][0:MU]\n", + "reduced_tests = [use_test[0], use_test[-1], use_test[len(use_test)-1]]\n", + "npcl, DO = om.run_ga(explore_param,10,bigger_tests,free_params=free_params,hc = hc, NSGA = False, MU = MU)#, seed_pop = DO.seed_pop)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "print(npcl['hardened'][0][0].attrs)\n", + "print(npcl['hardened'][0][0].scores)\n", + "print(npcl['pf'][0].fitness.values)\n", + "print(npcl['hof'][0].dtc.scores)\n", + "\n", + "#for t in use_test:\n", + "# print(t.name)\n", + " \n", + " \n", + "pop" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# From the scores printed above, it looks like certain error criteria, are in conflict with each other.\n", + "\n", + "Tests, that are amenable to co-optimization appear to be:\n", + "* Width test\n", + "* Input resistance tets\n", + "* Resting Potential Test,\n", + "* Capicitance test.\n", + "* Time constant\n", + "\n", + "Tests/criteria that seem in compatible with the above include: \n", + "* Rheobase, \n", + "* InjectedCurrentAPThresholdTest\n", + "* InjectedCurrentAPAmplitudeTest\n", + "\n", + "Therefore a reduced set of lists is made to check if the bottom three are at least amenable to optimization togethor." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from sklearn.cluster import KMeans\n", + "est = KMeans(n_clusters=2)\n", + "est.fit(X)\n", + "y_kmeans = est.predict(X)\n", + "\n", + "centers = est.cluster_centers_\n", + "\n", + "fig = plt.figure(fignum,figsize=(4,3))\n", + "ax = Axes3D(fig,rect=[0,0,.95,1],elav=48,azim=134)\n", + "ax.scatter(X[:,0],X[:,1],X[:,2],c=y_kmeans,s=50),\n", + "ax.scatter(centres[:,0],centres[:,1],centres[:,2],c='black',s=200,alpha=0.5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "\n", + "print(reduced_tests)\n", + "print(bigger_tests)\n", + "\n", + "DO.seed_pop = npcl['pf'][0:MU]\n", + "npcl, DO = om.run_ga(explore_param,10,reduced_tests,free_params=free_params,hc = hc, NSGA = True, MU = 12)#, seed_pop = DO.seed_pop)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import pickle\n", + "import copy\n", + "import numpy as np\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "#from neuronunit.optimization.optimization_management import wave_measure\n", + "from neuronunit.models.reduced import ReducedModel\n", + "from neuronunit.optimization.model_parameters import model_params, path_params\n", + "LEMS_MODEL_PATH = path_params['model_path']\n", + "import neuronunit.optimization as opt\n", + "import quantities as pq\n", + "fig = plt.figure()\n", + "\n", + "plt.clf()\n", + "\n", + "from neuronunit.optimization.data_transport_container import DataTC\n", + "model = ReducedModel(LEMS_MODEL_PATH,name = str('vanilla'),backend = ('RAW'))\n", + "for i in npcl['pf'][0:2]:\n", + " iparams = {}\n", + " iparams['injected_square_current'] = {}\n", + " iparams['injected_square_current']['amplitude'] =i.dtc.rheobase\n", + " model = None\n", + " model = ReducedModel(LEMS_MODEL_PATH,name = str('vanilla'),backend = ('RAW'))\n", + " model.set_attrs(i.dtc.attrs)\n", + "\n", + " #['amplitude'] = dtc.vtest[k]['injected_square_current']['amplitude']\n", + " DELAY = 100.0*pq.ms\n", + " DURATION = 1000.0*pq.ms\n", + " iparams['injected_square_current']['delay'] = DELAY\n", + " iparams['injected_square_current']['duration'] = int(DURATION)\n", + " model.inject_square_current(iparams)\n", + " n_spikes = len(model.get_spike_train())\n", + " if n_spikes:\n", + " print(n_spikes)\n", + " #print(i[0].scores['RheobaseTestP']*pq.pA)\n", + " plt.plot(model.get_membrane_potential().times,model.get_membrane_potential())#,label='ground truth')\n", + " plt.legend()\n", + "\n", + "#gca().set_axis_off()\n", + "#subplots_adjust(top = 1, bottom = 0, right = 1, left = 0, \n", + "# hspace = 0, wspace = 0)\n", + "#margins(0,0)\n", + "#gca().xaxis.set_major_locator(NullLocator())\n", + "#gca().yaxis.set_major_locator(NullLocator())\n", + "\n", + "plt.subplots_adjust(left=0.0, right=1.0, top=0.9, bottom=0.1)\n", + "fig.tight_layout()\n", + "plt.show()\n", + "\n", + "fig.savefig(\"single_trace.png\", bbox_inches = 'tight',\n", + " pad_inches = 0)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import pickle\n", + "import copy\n", + "import numpy as np\n", + "import pandas as pd\n", + "import matplotlib.pyplot\n", + "\n", + "\n", + "from neuronunit.models.reduced import ReducedModel\n", + "from neuronunit.optimization.model_parameters import model_params, path_params\n", + "LEMS_MODEL_PATH = path_params['model_path']\n", + "import neuronunit.optimization as opt\n", + "import quantities as pq\n", + "fig = plt.figure()\n", + "\n", + "plt.clf()\n", + "\n", + "from neuronunit.optimization.data_transport_container import DataTC\n", + "for i in npcl['hardened']:\n", + " iparams = {}\n", + " iparams['injected_square_current'] = {}\n", + " iparams['injected_square_current']['amplitude'] = i[0].rheobase\n", + " model = None\n", + " model = ReducedModel(LEMS_MODEL_PATH,name = str('vanilla'),backend = ('RAW'))\n", + " model.set_attrs(i[0].attrs)\n", + "\n", + " #['amplitude'] = dtc.vtest[k]['injected_square_current']['amplitude']\n", + " DELAY = 100.0*pq.ms\n", + " DURATION = 1000.0*pq.ms\n", + " iparams['injected_square_current']['delay'] = DELAY\n", + " iparams['injected_square_current']['duration'] = int(DURATION)\n", + " model.inject_square_current(iparams)\n", + " n_spikes = len(model.get_spike_train())\n", + " if n_spikes:\n", + " print(n_spikes)\n", + " print(i[0].scores['RheobaseTestP']*pq.pA)\n", + " plt.plot(model.get_membrane_potential().times,model.get_membrane_potential())#,label='ground truth')\n", + " plt.legend()\n", + "\n", + "#gca().set_axis_off()\n", + "#subplots_adjust(top = 1, bottom = 0, right = 1, left = 0, \n", + "# hspace = 0, wspace = 0)\n", + "#margins(0,0)\n", + "#gca().xaxis.set_major_locator(NullLocator())\n", + "#gca().yaxis.set_major_locator(NullLocator())\n", + "\n", + "plt.subplots_adjust(left=0.0, right=1.0, top=0.9, bottom=0.1)\n", + "fig.tight_layout()\n", + "plt.show()\n", + "\n", + "fig.savefig(\"single_trace.png\", bbox_inches = 'tight',\n", + " pad_inches = 0)\n", + "\n", + "\n", + "'''\n", + "hc = {}\n", + "\n", + "#free_params = ['c','k']\n", + "for k,v in explore_param.items():\n", + " if k not in free_params:\n", + " hc[k] = v\n", + "constants = npcl['hardened'][0][0].attrs\n", + "hc.update(constants) \n", + "npcl, _ = om.run_ga(explore_param,20,test_frame[\"Neocortex pyramidal cell layer 5-6\"],free_params=free_params,hc = hc, NSGA = True)\n", + "'''" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "\n", + "free_params = ['a','b','k']#vt','c','k','d']#,'vt','k','c','C']#,'C'] # this can only be odd numbers.\n", + "\n", + "##\n", + "# Use information that is available\n", + "##\n", + "hc = reduced_cells['RS']\n", + "\n", + "hc['vr'] = -65.2261863636364\n", + "\n", + "hc['vPeak'] = hc['vr'] + 86.364525297619\n", + "hc['C'] = 89.7960714285714\n", + "hc.pop('a',0)\n", + "hc.pop('b',0)\n", + "hc.pop('k',0)\n", + "hc.pop('c',0)\n", + "hc.pop('d',0)\n", + " \n", + "use_test = test_frame[\"Neocortex pyramidal cell layer 5-6\"]\n", + "DO.seed_pop = npcl['pf']\n", + "ga_out = DO.run(max_ngen = 15)\n", + "'''\n", + "hc = {}\n", + "\n", + "free_params = ['C']\n", + "\n", + "for k,v in explore_param.items():\n", + " if k not in free_params:\n", + " hc[k] = v\n", + "#,'vt','k','c','C']#,'C'] # this can only be odd numbers\n", + "constants = npcl['hardened'][0][0].attrs\n", + "hc.update(constants) \n", + "npcl, _ = om.run_ga(explore_param,20,test_frame[\"Neocortex pyramidal cell layer 5-6\"],free_params=free_params,hc = hc, NSGA = True)\n", + "'''" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "'''\n", + "import pandas\n", + " \n", + "try:\n", + " ne_raw = pandas.read_csv('article_ephys_metadata_curated.csv', delimiter='\\t')\n", + " !ls -ltr *.csv\n", + "except:\n", + " !wget https://neuroelectro.org/static/src/article_ephys_metadata_curated.csv\n", + " ne_raw = pandas.read_csv('article_ephys_metadata_curated.csv', delimiter='\\t')\n", + "\n", + "blah = ne_raw[ne_raw['NeuronName'].str.match('Hippocampus CA1 pyramidal cell')]\n", + "#ne_raw['NeuronName']\n", + "#ne_raw['cell\\ capacitance']\n", + "#blah = ne_raw[ne_raw['NeuronName'].str.match('Hippocampus CA1 pyramidal cell')]\n", + "\n", + "print([i for i in blah.columns])\n", + "#rint(blah['rheobase'])\n", + "#print(blah)\n", + "#for i in ne_raw.columns:#['NeuronName']:\n", + "# print(i)\n", + "\n", + "#ne_raw['NeuronName'][85]\n", + "#blah = ne_raw[ne_raw['TableID'].str.match('85')]\n", + "#ne_raw['n'] = 84\n", + "#here = ne_raw[ne_raw['Index']==85]\n", + "here = ne_raw[ne_raw['TableID']==18]\n", + "\n", + "print(here['rheo_raw'])\n", + "#!wget https://neuroelectro.org/apica/1/n/\n", + "'''" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "ca1 = ne_raw[ne_raw['NeuronName'].str.match('Hippocampus CA1 pyramidal cell')]\n", + "ca1['rheo']" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "\n", + " \n", + "test_frame[\"Dentate gyrus basket cell\"][0].observation['std'] = test_frame[\"Dentate gyrus basket cell\"][0].observation['mean']\n", + "for t in test_frame[\"Dentate gyrus basket cell\"]:\n", + " print(t.name)\n", + "\n", + " print(t.observation)\n", + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "'''\n", + "Inibitory Neuron\n", + "This can't pass the Rheobase test\n", + "''' " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "\n", + "from neuronunit.optimization import optimization_management as om\n", + "import pickle\n", + "\n", + "free_params = ['a','vr','b','vt','vPeak','c','k']\n", + "for k,v in explore_param.items():\n", + " if k not in free_params:\n", + " hc[k] = v\n", + "use_test = test_frame[\"Dentate gyrus basket cell\"]\n", + "bcell, _ = om.run_ga(explore_param,20,use_test,free_params=free_params,hc = hc, NSGA = True, MU = 4)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "\n", + " \n", + "#test_frame[\"Dentate gyrus basket cell\"][0].observation['std'] = test_frame[\"Dentate gyrus basket cell\"][0].observation['mean']\n", + "for t in test_frame[\"Hippocampus CA1 pyramidal cell\"]:\n", + " print(t.name)\n", + "\n", + " print(t.observation)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "use_test = test_frame[\"Hippocampus CA1 pyramidal cell\"]\n", + "bcell, _ = om.run_ga(explore_param,20,use_test,free_params=free_params,hc = hc, NSGA = True, MU = 10)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import pickle\n", + "import copy\n", + "import numpy as np\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "\n", + "from neuronunit.models.reduced import ReducedModel\n", + "from neuronunit.optimization.model_parameters import model_params, path_params\n", + "LEMS_MODEL_PATH = path_params['model_path']\n", + "import neuronunit.optimization as opt\n", + "import quantities as pq\n", + "fig = plt.figure()\n", + "\n", + "plt.clf()\n", + "\n", + "from neuronunit.optimization.data_transport_container import DataTC\n", + "for i in bcell['hardened'][0:6]:\n", + " iparams = {}\n", + " iparams['injected_square_current'] = {}\n", + " iparams['injected_square_current']['amplitude'] =i[0].rheobase\n", + " model = None\n", + " model = ReducedModel(LEMS_MODEL_PATH,name = str('vanilla'),backend = ('RAW'))\n", + " model.set_attrs(i[0].attrs)\n", + "\n", + " #['amplitude'] = dtc.vtest[k]['injected_square_current']['amplitude']\n", + " DELAY = 100.0*pq.ms\n", + " DURATION = 1000.0*pq.ms\n", + " iparams['injected_square_current']['delay'] = DELAY\n", + " iparams['injected_square_current']['duration'] = int(DURATION)\n", + " model.inject_square_current(iparams)\n", + " n_spikes = len(model.get_spike_train())\n", + " if n_spikes:\n", + " print(n_spikes)\n", + " print(i[0].scores['RheobaseTestP']*pq.pA)\n", + " plt.plot(model.get_membrane_potential().times,model.get_membrane_potential())#,label='ground truth')\n", + " plt.legend()\n", + "\n", + "#gca().set_axis_off()\n", + "#subplots_adjust(top = 1, bottom = 0, right = 1, left = 0, \n", + "# hspace = 0, wspace = 0)\n", + "#margins(0,0)\n", + "#gca().xaxis.set_major_locator(NullLocator())\n", + "#gca().yaxis.set_major_locator(NullLocator())\n", + "\n", + "plt.subplots_adjust(left=0.0, right=1.0, top=0.9, bottom=0.1)\n", + "fig.tight_layout()\n", + "plt.show()\n", + "\n", + "fig.savefig(\"single_trace.png\", bbox_inches = 'tight',\n", + " pad_inches = 0)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "use_test = test_frame[\"Hippocampus CA1 pyramidal cell\"]\n", + "bcell, _ = om.run_ga(explore_param,20,use_test,free_params=free_params,hc = hc, NSGA = True, MU = 10)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# This cell is in markdown, but it won't be later.\n", + "Later optimize a whole heap of cells in a loop.\n", + "\n", + "try:\n", + " import pickle\n", + " with open('data_dump.p','rb') as f:\n", + " test_opt = pickle.load(f)\n", + "except:\n", + " MU = 12\n", + " NGEN = 25\n", + " cnt = 1\n", + " for t in use_test: \n", + " if cnt==len(use_test):\n", + " MU = 12\n", + " NGEN = 20\n", + "\n", + " npcl, DO = om.run_ga(explore_param,NGEN,[t],free_params=free_params, NSGA = True, MU = MU)\n", + " else:\n", + "\n", + " npcl, DO = om.run_ga(explore_param,NGEN,[t],free_params=free_params, NSGA = True, MU = MU)\n", + "\n", + " test_opt[str(t)] = {'out':npcl}\n", + "\n", + " ranges = {}\n", + " stds = npcl['pop'][0].dtc.attrs\n", + " for k in npcl['pop'][0].dtc.attrs.keys(): \n", + " stds[k] = []\n", + " ranges[k] = []\n", + "\n", + "\n", + " for i in npcl['pop'][::5]:\n", + " for k,v in i.dtc.attrs.items():\n", + " stds[k].append(v)\n", + " ranges[k].append(v)\n", + "\n", + " for k in npcl['pop'][0].dtc.attrs.keys():\n", + " ranges[k] = (np.min(ranges[k][1::]),np.max(ranges[k][1::]))\n", + "\n", + " stds[k] = np.std(stds[k][1::])\n", + " test_opt[str(t)]['stds'] = stds \n", + " test_opt[str(t)]['ranges'] = ranges \n", + "\n", + " cnt+=1\n", + " \n", + " with open('data_dump.p','wb') as f:\n", + " pickle.dump(test_opt,f)\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + } + ], + "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.5.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/neuronunit/examples/begginer_friendly_intro.ipynb b/neuronunit/examples/begginer_friendly_intro.ipynb new file mode 100644 index 000000000..c2cb1eb73 --- /dev/null +++ b/neuronunit/examples/begginer_friendly_intro.ipynb @@ -0,0 +1,3408 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Assumptions, the environment for running this notebook was arrived at by building a dedicated docker file.\n", + "\n", + "https://cloud.docker.com/repository/registry-1.docker.io/russelljarvis/nuo\n", + "\n", + "You can run use dockerhub to get the appropriate file, and launch this notebook using Kitematic." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Import libraries\n", + "To keep the standard running version of minimal and memory efficient, not all available packages are loaded by default. In the cell below I import a mixture common python modules, and custom developed modules associated with NeuronUnit (NU) development" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "\n", + "import numpy as np\n", + "import os\n", + "import pickle\n", + "import pandas as pd\n", + "from neuronunit.tests.fi import RheobaseTestP\n", + "from neuronunit.optimization.model_parameters import reduced_dict, reduced_cells \n", + "from neuronunit.optimization import optimization_management as om\n", + "from sciunit import scores# score_type \n", + "\n", + "from neuronunit.optimization.data_transport_container import DataTC\n", + "from neuronunit.tests.fi import RheobaseTestP# as discovery\n", + "from neuronunit.optimization.optimization_management import dtc_to_rheo, format_test, nunit_evaluation\n", + "import quantities as pq\n", + "from neuronunit.models.reduced import ReducedModel\n", + "from neuronunit.optimization.model_parameters import model_params, path_params\n", + "LEMS_MODEL_PATH = path_params['model_path']\n", + "list_to_frame = []\n", + "from neuronunit.tests.fi import RheobaseTestP" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false + }, + "source": [ + "# The Izhiketich model is instanced using some well researched parameter sets.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First lets get the points in parameter space, that Izhikich himself has published about. These points are used by other scientists and the [Open Source Brain project](http://opensourcebrain.org/projects/izhikevichmodel?explorer=https%3A%2F%2Fraw.githubusercontent.com%2FOpenSourceBrain%2FIzhikevichModel%2Fsample%2FosbSessions%2Fsample%2FSample_Session.json) to establish between model reproducibility. The itial motivating factor for choosing these points as constellations, of all possible parameter space subsets, is that these points where initially tuned and used as best guesses for matching real observed experimental recordings." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "explore_param = {k:(np.min(v),np.max(v)) for k,v in reduced_dict.items()}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false + }, + "source": [ + "## Get the experimental Data pertaining to four different classes or neurons, that can constrain models.\n", + "Next we get some electro physiology data for four different classes of cells that are very common targets of scientific neuronal modelling. We are interested in finding out what are the most minimal, and detail reduced, low complexity model equations, that are able to satisfy " + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false + }, + "source": [ + "Below are some of the data set ID's I used to query neuroelectro.\n", + "To save time for the reader, I prepared some data earlier to save time, and saved the data as a pickle, pythons preferred serialisation format.\n", + "\n", + "The interested reader can find some methods for getting cell specific ephys data from neuroelectro in a code file (neuronunit/optimization/get_neab.py) \n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "purkinje ={\"id\": 18, \"name\": \"Cerebellum Purkinje cell\", \"neuron_db_id\": 271, \"nlex_id\": \"sao471801888\"}\n", + "fi_basket = {\"id\": 65, \"name\": \"Dentate gyrus basket cell\", \"neuron_db_id\": None, \"nlex_id\": \"nlx_cell_100201\"}\n", + "pvis_cortex = {\"id\": 111, \"name\": \"Neocortex pyramidal cell layer 5-6\", \"neuron_db_id\": 265, \"nlex_id\": \"nifext_50\"}\n", + "#This olfactory mitral cell does not have datum about rheobase, current injection values.\n", + "olf_mitral = {\"id\": 129, \"name\": \"Olfactory bulb (main) mitral cell\", \"neuron_db_id\": 267, \"nlex_id\": \"nlx_anat_100201\"}\n", + "ca1_pyr = {\"id\": 85, \"name\": \"Hippocampus CA1 pyramidal cell\", \"neuron_db_id\": 258, \"nlex_id\": \"sao830368389\"}\n", + "pipe = [ fi_basket, ca1_pyr, purkinje, pvis_cortex]" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "electro_tests = []\n", + "obs_frame = {}\n", + "test_frame = {}\n", + "\n", + "from neuronunit.optimization import get_neab\n", + "try: \n", + "\n", + " electro_path = str(os.getcwd())+'all_tests.p'\n", + "\n", + " assert os.path.isfile(electro_path) == True\n", + " with open(electro_path,'rb') as f:\n", + " (obs_frame,test_frame) = pickle.load(f)\n", + "\n", + "except:\n", + " for p in pipe:\n", + " p_tests, p_observations = get_neab.get_neuron_criteria(p)\n", + " obs_frame[p[\"name\"]] = p_observations#, p_tests))\n", + " test_frame[p[\"name\"]] = p_tests#, p_tests))\n", + " electro_path = str(os.getcwd())+'all_tests.p'\n", + " with open(electro_path,'wb') as f:\n", + " pickle.dump((obs_frame,test_frame),f)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Cast the tabulatable data to pandas data frame\n", + "There are many among us who prefer potentially tabulatable data to be encoded in pandas data frame." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false, + "scrolled": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "dict_keys(['Dentate gyrus basket cell', 'Hippocampus CA1 pyramidal cell', 'Neocortex pyramidal cell layer 5-6', 'Cerebellum Purkinje cell'])\n" + ] + } + ], + "source": [ + "for k,v in test_frame.items():\n", + " if \"olf_mit\" not in k:\n", + " obs = obs_frame[k]\n", + " v[0] = RheobaseTestP(obs['Rheobase'])\n", + "df = pd.DataFrame.from_dict(obs_frame)\n", + "print(test_frame.keys())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the data frame below, you can see many different cell types" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Cerebellum Purkinje cellDentate gyrus basket cellHippocampus CA1 pyramidal cellNeocortex pyramidal cell layer 5-6
Cell Capacitance{'mean': 620.2725 pF, 'n': 4, 'std': 261.30303...NaN{'mean': 89.7960714285714 pF, 'n': 14, 'std': ...{'mean': 150.584166666667 pF, 'n': 12, 'std': ...
Input Resistance{'mean': 142.057692307692 Mohm, 'n': 13, 'std'...{'mean': 222.475 Mohm, 'n': 4, 'std': 168.2911...{'mean': 107.080327644332 Mohm, 'n': 113, 'std...{'mean': 120.672073643411 Mohm, 'n': 86, 'std'...
Membrane Time ConstantNaN{'mean': 13.25 ms, 'n': 2, 'std': 3.25 ms}{'mean': 24.5021946169772 ms, 'n': 46, 'std': ...{'mean': 15.7342424242424 ms, 'n': 33, 'std': ...
Resting membrane potential{'mean': -61.5916666666667 mV, 'n': 6, 'std': ...{'mean': -62.3 mV, 'n': 3, 'std': 4.0472212689...{'mean': -65.2261863636364 mV, 'n': 110, 'std'...{'mean': -68.2481434599156 mV, 'n': 79, 'std':...
Rheobase{'mean': 680.794444444444 pA, 'n': 3, 'std': 4...{'mean': 106.7 pA, 'n': 1, 'std': 0.0 pA}{'mean': 189.24 pA, 'n': 17, 'std': 287.163664...{'mean': 213.849583333333 pA, 'n': 32, 'std': ...
Spike Amplitude{'mean': 71.2308333333333 mV, 'n': 6, 'std': 9...{'mean': 78.6 mV, 'n': 2, 'std': 5.7 mV}{'mean': 86.364525297619 mV, 'n': 64, 'std': 1...{'mean': 80.4351020408164 mV, 'n': 49, 'std': ...
Spike Half-Width{'mean': 0.41412962962963 ms, 'n': 9, 'std': 0...{'mean': 0.9975 ms, 'n': 4, 'std': 0.442175021...{'mean': 1.31895278450363 ms, 'n': 59, 'std': ...{'mean': 1.20769387755102 ms, 'n': 49, 'std': ...
Spike Threshold{'mean': -46.8947619047619 mV, 'n': 7, 'std': ...{'mean': -38.4 mV, 'n': 2, 'std': 3.4 mV}{'mean': -47.5985714285714 mV, 'n': 70, 'std':...{'mean': -42.7357232704403 mV, 'n': 53, 'std':...
\n", + "
" + ], + "text/plain": [ + " Cerebellum Purkinje cell \\\n", + "Cell Capacitance {'mean': 620.2725 pF, 'n': 4, 'std': 261.30303... \n", + "Input Resistance {'mean': 142.057692307692 Mohm, 'n': 13, 'std'... \n", + "Membrane Time Constant NaN \n", + "Resting membrane potential {'mean': -61.5916666666667 mV, 'n': 6, 'std': ... \n", + "Rheobase {'mean': 680.794444444444 pA, 'n': 3, 'std': 4... \n", + "Spike Amplitude {'mean': 71.2308333333333 mV, 'n': 6, 'std': 9... \n", + "Spike Half-Width {'mean': 0.41412962962963 ms, 'n': 9, 'std': 0... \n", + "Spike Threshold {'mean': -46.8947619047619 mV, 'n': 7, 'std': ... \n", + "\n", + " Dentate gyrus basket cell \\\n", + "Cell Capacitance NaN \n", + "Input Resistance {'mean': 222.475 Mohm, 'n': 4, 'std': 168.2911... \n", + "Membrane Time Constant {'mean': 13.25 ms, 'n': 2, 'std': 3.25 ms} \n", + "Resting membrane potential {'mean': -62.3 mV, 'n': 3, 'std': 4.0472212689... \n", + "Rheobase {'mean': 106.7 pA, 'n': 1, 'std': 0.0 pA} \n", + "Spike Amplitude {'mean': 78.6 mV, 'n': 2, 'std': 5.7 mV} \n", + "Spike Half-Width {'mean': 0.9975 ms, 'n': 4, 'std': 0.442175021... \n", + "Spike Threshold {'mean': -38.4 mV, 'n': 2, 'std': 3.4 mV} \n", + "\n", + " Hippocampus CA1 pyramidal cell \\\n", + "Cell Capacitance {'mean': 89.7960714285714 pF, 'n': 14, 'std': ... \n", + "Input Resistance {'mean': 107.080327644332 Mohm, 'n': 113, 'std... \n", + "Membrane Time Constant {'mean': 24.5021946169772 ms, 'n': 46, 'std': ... \n", + "Resting membrane potential {'mean': -65.2261863636364 mV, 'n': 110, 'std'... \n", + "Rheobase {'mean': 189.24 pA, 'n': 17, 'std': 287.163664... \n", + "Spike Amplitude {'mean': 86.364525297619 mV, 'n': 64, 'std': 1... \n", + "Spike Half-Width {'mean': 1.31895278450363 ms, 'n': 59, 'std': ... \n", + "Spike Threshold {'mean': -47.5985714285714 mV, 'n': 70, 'std':... \n", + "\n", + " Neocortex pyramidal cell layer 5-6 \n", + "Cell Capacitance {'mean': 150.584166666667 pF, 'n': 12, 'std': ... \n", + "Input Resistance {'mean': 120.672073643411 Mohm, 'n': 86, 'std'... \n", + "Membrane Time Constant {'mean': 15.7342424242424 ms, 'n': 33, 'std': ... \n", + "Resting membrane potential {'mean': -68.2481434599156 mV, 'n': 79, 'std':... \n", + "Rheobase {'mean': 213.849583333333 pA, 'n': 32, 'std': ... \n", + "Spike Amplitude {'mean': 80.4351020408164 mV, 'n': 49, 'std': ... \n", + "Spike Half-Width {'mean': 1.20769387755102 ms, 'n': 49, 'std': ... \n", + "Spike Threshold {'mean': -42.7357232704403 mV, 'n': 53, 'std':... " + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Cell Capacitance {'mean': 89.7960714285714 pF, 'n': 14, 'std': ...\n", + "Input Resistance {'mean': 107.080327644332 Mohm, 'n': 113, 'std...\n", + "Membrane Time Constant {'mean': 24.5021946169772 ms, 'n': 46, 'std': ...\n", + "Resting membrane potential {'mean': -65.2261863636364 mV, 'n': 110, 'std'...\n", + "Rheobase {'mean': 189.24 pA, 'n': 17, 'std': 287.163664...\n", + "Spike Amplitude {'mean': 86.364525297619 mV, 'n': 64, 'std': 1...\n", + "Spike Half-Width {'mean': 1.31895278450363 ms, 'n': 59, 'std': ...\n", + "Spike Threshold {'mean': -47.5985714285714 mV, 'n': 70, 'std':...\n", + "Name: Hippocampus CA1 pyramidal cell, dtype: object" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df['Hippocampus CA1 pyramidal cell']\n", + "\n", + "# enable R style caching.\n", + "#!pip install git+https://github.com/rossant/ipycache\n", + "#%load_ext ipycache" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Tweak Izhikitich equations\n", + "with educated guesses based on information that is already encoded in the predefined experimental observations.\n", + "\n", + "In otherwords use information that is readily amenable into hardcoding into equations \n", + "\n", + "Select out the 'Neocortex pyramidal cell layer 5-6' below, as a target for optimization" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": false, + "scrolled": false + }, + "outputs": [], + "source": [ + "free_params = ['a','b','k','c','C','d','vPeak','vr']\n", + "hc_ = reduced_cells['RS']\n", + "hc_['vr'] = -65.2261863636364\n", + "hc_['vPeak'] = hc_['vr'] + 86.364525297619\n", + "explore_param['C'] = (hc_['C']-20,hc_['C']+20)\n", + "explore_param['vr'] = (hc_['vr']-5,hc_['vr']+5)\n", + "use_test = test_frame[\"Neocortex pyramidal cell layer 5-6\"]\n", + "\n", + "#for t in use_test[::-1]:\n", + "# t.score_type = scores.RatioScore\n", + "test_opt = {}\n", + "\n", + "with open('data_dump.p','wb') as f:\n", + " pickle.dump(test_opt,f)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "RheobaseTestP\n", + "{'mean': array(213.849583333333) * pA, 'n': 32, 'std': array(170.452454715608) * pA}\n" + ] + } + ], + "source": [ + "\n", + "use_test[0].observation\n", + "print(use_test[0].name)\n", + "\n", + "rtp = RheobaseTestP(use_test[0].observation)\n", + "use_test[0] = rtp\n", + "print(use_test[0].observation)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false + }, + "source": [ + "The code in the next two cells below is used to create a sparse grid sampling over the parameter space, using the published and well corrobarated parameter points, from Izhikitch publications, and the Open Source brain" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "dict_keys(['Dentate gyrus basket cell', 'Hippocampus CA1 pyramidal cell', 'Neocortex pyramidal cell layer 5-6', 'Cerebellum Purkinje cell'])" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "reduced_cells.keys()\n", + "test_frame.keys()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "exceptional circumstances pickle file does not exist, rebuilding sparse grid for Izhikich\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/opt/conda/lib/python3.5/site-packages/sciunit/scores/complete.py:68: RuntimeWarning: divide by zero encountered in true_divide\n", + " value = (p_value - o_mean)/o_std\n", + "/home/jovyan/neuronunit/neuronunit/tests/passive.py:85: RuntimeWarning: overflow encountered in exp\n", + " vm_fit[offset:,0] = a * np.exp(-t[offset:]/b) + c\n", + "/home/jovyan/neuronunit/neuronunit/tests/passive.py:85: RuntimeWarning: overflow encountered in multiply\n", + " vm_fit[offset:,0] = a * np.exp(-t[offset:]/b) + c\n" + ] + } + ], + "source": [ + "df = pd.DataFrame(index=list(test_frame.keys()),columns=list(reduced_cells.keys()))\n", + "\n", + "try:\n", + " assert 1==2\n", + " with open('Izh_seeds.p','rb') as f:\n", + " seeds = pickle.load(f)\n", + " #assert seeds is not None\n", + "\n", + "except:\n", + " # print('exceptional circumstances pickle file does not exist, rebuilding sparse grid for Izhikich')\n", + " # Below we perform a sparse grid sampling over the parameter space, using the published and well corrobarated parameter points, from Izhikitch publications, and the Open Source brain, shows that without optimization, using off the shelf parameter sets to fit real-life biological cell data, does not work so well.\n", + "\n", + " for k,v in reduced_cells.items():\n", + " temp = {}\n", + " temp[str(v)] = {}\n", + " dtc = DataTC()\n", + " dtc.tests = use_test\n", + " dtc.attrs = v\n", + " dtc.backend = 'RAW'\n", + " dtc.cell_name = 'vanilla'\n", + " for key, use_test in test_frame.items():\n", + " dtc.tests = use_test\n", + " dtc = dtc_to_rheo(dtc)\n", + " dtc = format_test(dtc)\n", + " if dtc.rheobase is not None:\n", + " if dtc.rheobase!=-1.0:\n", + " dtc = nunit_evaluation(dtc)\n", + " df[k][str(key)] = dtc.get_ss()\n", + "\n", + " best_params = {}\n", + " for index, row in df.iterrows():\n", + " best_params[index] = row == row.min()\n", + " best_params[index] = best_params[index].to_dict()\n", + "\n", + "\n", + " seeds = {}\n", + " for k,v in best_params.items():\n", + " for nested_key,nested_val in v.items():\n", + " if True == nested_val:\n", + " seed = reduced_cells[nested_key]\n", + " seeds[k] = seed\n", + " with open('Izh_seeds.p','wb') as f:\n", + " pickle.dump(seeds,f)\n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As I said above: 'A sparse grid sampling over the parameter space, using the published and well corrobarated parameter points, from Izhikitch publications, and the Open Source brain', The output from the grid shows that without optimization, using off the shelf parameter sets to fit real-life biological cell data, does not work so well." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
RSIBLTSTCTC_burst
Dentate gyrus basket cell5.335756.447026.037696.275356.38901
Hippocampus CA1 pyramidal cell4.26525.845655.705275.746336.06554
Neocortex pyramidal cell layer 5-64.669675.838846.210746.364356.5461
Cerebellum Purkinje cell6.043425.951735.472375.698255.53656
\n", + "
" + ], + "text/plain": [ + " RS IB LTS TC \\\n", + "Dentate gyrus basket cell 5.33575 6.44702 6.03769 6.27535 \n", + "Hippocampus CA1 pyramidal cell 4.2652 5.84565 5.70527 5.74633 \n", + "Neocortex pyramidal cell layer 5-6 4.66967 5.83884 6.21074 6.36435 \n", + "Cerebellum Purkinje cell 6.04342 5.95173 5.47237 5.69825 \n", + "\n", + " TC_burst \n", + "Dentate gyrus basket cell 6.38901 \n", + "Hippocampus CA1 pyramidal cell 6.06554 \n", + "Neocortex pyramidal cell layer 5-6 6.5461 \n", + "Cerebellum Purkinje cell 5.53656 " + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As can be seen none of these well researched parameter sets correspond to good fits, with experimental data about waveform shape. These bad fit's warrant the use of an optimizer, which can efficiently, sparsely, and intelligently explore the parameter space for better model fits. None of the models score higher than:\n", + "\n", + "```4.2652```, and the best is ```5.47237``` in the specific case of the cerebellum purkinje cell. \n", + "and None of the first guess parameter set were very good for the cerebellum Purkinje cell. It should be easy to find a model parameterization that scores better than ```5.47```. A first guess. \n", + "\n", + "The best NeuronUnit score values found via this sparse grid sampling, are used to seed the first generation of the genetic algorithm, as one of the great motivators of GA optimization, is not to waste any computation time.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We now optimize using it to show what can be achieved with the GA." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "\n", + "\n", + "try:\n", + " with open('multi_objective_izhi.p','rb') as f:\n", + " test_opt = pickle.load(f)\n", + "except:\n", + " MU = 6\n", + " NGEN = 150\n", + "\n", + "\n", + " seed = seeds[key]\n", + " print(seed)\n", + " ga_out, _ = om.run_ga(explore_param,NGEN,use_test,free_params=free_params, NSGA = True, MU = MU,seed_pop = seed, model_type = str('RAW'))\n", + "\n", + " test_opt = {str('multi_objective_izhi')+str('_ga_out'):ga_out}\n", + " with open('multi_objective_izhi.p','wb') as f:\n", + " pickle.dump(test_opt,f)\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "its True that 3.1302660755008325 is significantly less than 5.47\n", + "Cerebellum Purkinje cell\n" + ] + } + ], + "source": [ + "for k,v in test_opt.items():\n", + " best_model = v['pf'][0]\n", + "summed = best_model.dtc.get_ss()\n", + "print('its {2} that {0} is significantly less than {1}'.format(summed,5.47,summed<5.47))\n", + "print(key)" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'vt': -40, 'c': -50, 'vr': -65.2261863636364, 'vPeak': 21.138338933982595, 'C': 100, 'd': 100, 'k': 0.7, 'a': 0.03, 'b': -2}\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/jovyan/neuronunit/neuronunit/tests/passive.py:85: RuntimeWarning: overflow encountered in exp\n", + " vm_fit[offset:,0] = a * np.exp(-t[offset:]/b) + c\n", + "/home/jovyan/neuronunit/neuronunit/tests/passive.py:85: RuntimeWarning: overflow encountered in multiply\n", + " vm_fit[offset:,0] = a * np.exp(-t[offset:]/b) + c\n", + "INFO:__main__:gen\tnevals\tavg \tstd \tmin \tmax \n", + "1 \t7 \t5.22203\t0.821745\t3.72429\t6.03463\n", + "2 \t5 \t5.21212\t0.735291\t3.97883\t6.03463\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 3.72428861926\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:3 \t4 \t5.13677\t0.626946\t4.25394\t5.65035\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 3.72428861926\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:4 \t5 \t5.19984\t0.430726\t4.2626 \t5.49532\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 3.72428861926\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:5 \t5 \t5.44865\t0.146973\t5.19086\t5.64343\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 3.72428861926\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:6 \t5 \t5.16537\t0.625444\t3.86973\t5.67245\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 3.72428861926\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/opt/conda/lib/python3.5/site-packages/deap/tools/crossover.py:324: RuntimeWarning: invalid value encountered in double_scalars\n", + " beta_q = (1.0 / (2.0 - rand * alpha))**(1.0 / (eta + 1))\n", + "/home/jovyan/neuronunit/neuronunit/tests/passive.py:85: RuntimeWarning: overflow encountered in exp\n", + " vm_fit[offset:,0] = a * np.exp(-t[offset:]/b) + c\n", + "/home/jovyan/neuronunit/neuronunit/tests/passive.py:85: RuntimeWarning: overflow encountered in multiply\n", + " vm_fit[offset:,0] = a * np.exp(-t[offset:]/b) + c\n", + "INFO:__main__:7 \t6 \t5.47294\t0.286328\t4.88053\t5.71681\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 3.72428861926\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:8 \t5 \t5.1169 \t0.84742 \t3.22897\t5.61862\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 3.72428861926\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:9 \t5 \t4.74357\t0.795977\t3.72429\t5.57846\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 3.72428861926\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:10 \t4 \t4.66108\t0.967007\t3.24729\t5.67859\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 3.24728879435\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:11 \t5 \t5.43769\t0.347115\t4.67548\t5.69242\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 3.24728879435\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:12 \t2 \t4.99029\t0.403366\t4.6111 \t5.54829\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 3.24728879435\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:13 \t5 \t4.78651\t0.605765\t3.59511\t5.54829\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 3.24728879435\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:14 \t6 \t4.66135\t0.838683\t3.3331 \t5.74215\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 3.24728879435\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:15 \t6 \t4.84525\t0.473134\t4.21751\t5.55403\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 3.24728879435\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:16 \t6 \t4.14798\t0.961541\t3.32742\t5.55374\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 3.24728879435\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:17 \t5 \t4.46228\t0.904249\t3.32742\t5.37721\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 3.24728879435\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:18 \t4 \t3.9865 \t0.959449\t3.09455\t5.34938\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 3.24728879435\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:19 \t6 \t4.36213\t0.923614\t3.30176\t5.30928\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 3.24728879435\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:20 \t6 \t4.89491\t0.710129\t3.32925\t5.37684\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 3.24728879435\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:21 \t3 \t4.96855\t0.440792\t4.00273\t5.27258\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 3.24728879435\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:22 \t5 \t3.99606\t0.777385\t3.24729\t5.14799\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 3.24728879435\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:23 \t6 \t4.10263\t0.943001\t2.99182\t5.37337\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.99182182811\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:24 \t5 \t4.62393\t0.589267\t3.55272\t5.20436\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.99182182811\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:25 \t5 \t4.146 \t0.595203\t3.24729\t5.22062\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.99182182811\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:26 \t5 \t4.30695\t0.785958\t3.30176\t5.22537\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.99182182811\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/jovyan/neuronunit/neuronunit/tests/passive.py:85: RuntimeWarning: overflow encountered in exp\n", + " vm_fit[offset:,0] = a * np.exp(-t[offset:]/b) + c\n", + "/home/jovyan/neuronunit/neuronunit/tests/passive.py:85: RuntimeWarning: overflow encountered in multiply\n", + " vm_fit[offset:,0] = a * np.exp(-t[offset:]/b) + c\n", + "INFO:__main__:27 \t6 \t4.31459\t1.00617 \t2.99182\t5.56744\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.99182182811\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:28 \t6 \t4.40688\t0.562529\t3.32854\t5.14262\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.99182182811\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:29 \t5 \t4.542 \t0.619648\t3.27655\t5.26655\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.99182182811\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:30 \t6 \t4.53346\t0.334873\t4.17312\t5.1105 \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.99182182811\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:31 \t4 \t4.43568\t0.303636\t4.17312\t5.03305\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.99182182811\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:32 \t6 \t4.61656\t0.35095 \t4.13065\t5.16013\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.99182182811\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:33 \t5 \t4.29056\t0.633806\t2.99182\t4.99444\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.99182182811\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:34 \t6 \t4.41131\t0.552691\t3.3616 \t5.08154\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.99182182811\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:35 \t5 \t4.12985\t0.659809\t3.18095\t4.77331\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.99182182811\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:36 \t6 \t4.52503\t0.343159\t3.86635\t4.93116\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.99182182811\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:37 \t6 \t4.49488\t0.434338\t3.56517\t4.93141\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.99182182811\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:38 \t6 \t4.30537\t0.559633\t3.36456\t5.04942\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.99182182811\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:39 \t4 \t4.32108\t0.422403\t3.81848\t4.88364\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.99182182811\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:40 \t6 \t4.2473 \t0.523654\t3.38556\t4.81716\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.99182182811\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:41 \t4 \t4.52398\t0.556695\t3.38556\t5.12355\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.99182182811\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:42 \t6 \t4.7039 \t0.401336\t3.85379\t5.06121\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.99182182811\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/jovyan/neuronunit/neuronunit/tests/passive.py:85: RuntimeWarning: overflow encountered in exp\n", + " vm_fit[offset:,0] = a * np.exp(-t[offset:]/b) + c\n", + "/home/jovyan/neuronunit/neuronunit/tests/passive.py:85: RuntimeWarning: overflow encountered in multiply\n", + " vm_fit[offset:,0] = a * np.exp(-t[offset:]/b) + c\n", + "INFO:__main__:43 \t5 \t4.36727\t0.436229\t3.67472\t4.79182\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.99182182811\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:44 \t5 \t3.91217\t0.521324\t3.39523\t4.67254\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.99182182811\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:45 \t5 \t4.50612\t0.6599 \t3.37202\t5.22707\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.99182182811\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/jovyan/neuronunit/neuronunit/tests/passive.py:85: RuntimeWarning: overflow encountered in exp\n", + " vm_fit[offset:,0] = a * np.exp(-t[offset:]/b) + c\n", + "/home/jovyan/neuronunit/neuronunit/tests/passive.py:85: RuntimeWarning: overflow encountered in multiply\n", + " vm_fit[offset:,0] = a * np.exp(-t[offset:]/b) + c\n", + "INFO:__main__:46 \t6 \t4.46049\t0.704344\t3.40209\t5.25458\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.99182182811\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:47 \t6 \t4.09745\t0.841532\t2.99182\t5.4212 \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.99182182811\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:48 \t5 \t4.78211\t0.744664\t3.2739 \t5.55851\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.99182182811\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:49 \t6 \t4.66692\t0.674102\t3.79381\t5.62961\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.99182182811\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:50 \t5 \t4.6925 \t0.229621\t4.44175\t5.15158\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.99182182811\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:51 \t6 \t4.87492\t0.136585\t4.65136\t5.03999\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.99182182811\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:52 \t6 \t4.62645\t0.279176\t4.13133\t4.96709\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.99182182811\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:53 \t6 \t4.71274\t0.290041\t4.4209 \t5.28303\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.99182182811\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:54 \t4 \t4.03018\t0.749879\t2.99182\t4.73957\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.99182182811\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:55 \t4 \t4.57711\t0.507153\t3.59472\t5.07764\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.99182182811\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/jovyan/neuronunit/neuronunit/tests/passive.py:85: RuntimeWarning: overflow encountered in exp\n", + " vm_fit[offset:,0] = a * np.exp(-t[offset:]/b) + c\n", + "/home/jovyan/neuronunit/neuronunit/tests/passive.py:85: RuntimeWarning: overflow encountered in multiply\n", + " vm_fit[offset:,0] = a * np.exp(-t[offset:]/b) + c\n", + "INFO:__main__:56 \t5 \t4.965 \t0.353734\t4.37404\t5.44207\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.99182182811\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:57 \t4 \t4.94033\t0.290541\t4.49424\t5.33279\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.99182182811\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:58 \t4 \t4.01939\t0.874808\t2.99182\t4.95734\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.99182182811\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:59 \t4 \t4.58366\t0.762433\t3.35157\t5.40481\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.99182182811\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:60 \t6 \t3.8724 \t0.706549\t3.15745\t5.30354\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.99182182811\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:61 \t5 \t4.04575\t0.794679\t3.14408\t5.30354\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.99182182811\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:62 \t6 \t4.09405\t0.896957\t3.14351\t5.7101 \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.99182182811\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:63 \t6 \t4.297 \t0.856857\t2.96964\t5.21997\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.96964367379\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:64 \t6 \t4.30922\t0.66727 \t3.29948\t5.10631\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.96964367379\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:65 \t5 \t3.78477\t0.682947\t3.31091\t5.09401\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.96964367379\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:66 \t6 \t4.46096\t0.522407\t3.89602\t5.32886\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.96964367379\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:67 \t5 \t4.49385\t0.428289\t3.91133\t5.20338\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.96964367379\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:68 \t6 \t4.39372\t0.498931\t3.91133\t5.15897\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.96964367379\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:69 \t6 \t4.58499\t0.393384\t3.98095\t5.2565 \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.96964367379\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:70 \t6 \t3.96903\t0.443902\t3.24159\t4.52028\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.96964367379\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:71 \t5 \t4.24541\t0.390298\t3.96593\t5.07562\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.96964367379\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:72 \t5 \t3.5692 \t0.638318\t2.81818\t4.60251\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.81817662327\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:73 \t6 \t3.91753\t0.512632\t3.24758\t4.94453\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.81817662327\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:74 \t4 \t3.62826\t0.985904\t2.81818\t5.48352\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.81817662327\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:75 \t6 \t3.85956\t0.707637\t2.88205\t4.66586\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.81817662327\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:76 \t6 \t3.6798 \t0.631453\t2.81818\t4.27577\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.81817662327\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:77 \t6 \t4.15053\t0.165328\t3.85328\t4.38312\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.81817662327\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:78 \t6 \t3.4361 \t0.53812 \t2.76358\t4.20687\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.76357717108\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:79 \t5 \t3.69135\t0.331072\t3.35456\t4.30059\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.76357717108\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:80 \t6 \t4.05232\t0.710832\t2.76358\t5.10213\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.76357717108\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:81 \t5 \t4.15533\t0.726237\t2.75113\t4.96021\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.76357717108\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:82 \t6 \t3.58236\t0.452427\t2.8718 \t4.37413\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.76357717108\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:83 \t6 \t3.60259\t0.589934\t2.81867\t4.64001\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.76357717108\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:84 \t6 \t3.63368\t0.613307\t3.01626\t4.76451\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.76357717108\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:85 \t6 \t4.14238\t0.754201\t2.82455\t4.8034 \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.76357717108\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:86 \t4 \t3.92542\t0.729978\t3.02329\t4.98257\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.76357717108\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:87 \t5 \t3.51059\t0.663082\t2.94123\t4.82076\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.76357717108\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:88 \t6 \t3.37568\t0.756833\t2.67377\t4.56136\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.76357717108\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:89 \t6 \t3.51921\t0.73003 \t2.67377\t4.64387\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.67376895409\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:90 \t5 \t3.74916\t0.72867 \t2.67377\t4.53191\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.67376895409\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:91 \t5 \t4.2997 \t0.409688\t3.89017\t5.09722\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.67376895409\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:92 \t6 \t3.58176\t0.9467 \t2.67377\t5.07645\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.67376895409\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:93 \t6 \t3.70092\t0.558363\t2.57566\t4.19994\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.57566379294\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:94 \t6 \t4.73009\t0.381539\t4.02488\t5.07645\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.57566379294\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:95 \t6 \t3.78595\t0.434481\t2.89454\t4.22599\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.57566379294\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:96 \t6 \t4.22617\t0.18045 \t3.91835\t4.51524\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.57566379294\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:97 \t4 \t3.88434\t0.821507\t2.57566\t4.93564\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.57566379294\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:98 \t4 \t4.45176\t0.807055\t3.08223\t5.58645\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.57566379294\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:99 \t5 \t3.13081\t0.467016\t2.57566\t3.92952\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.57566379294\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:100\t3 \t3.23386\t0.278715\t2.8643 \t3.67439\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.57566379294\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:101\t6 \t3.75427\t0.643775\t2.86317\t4.7178 \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.57566379294\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:102\t5 \t3.98314\t0.547056\t3.26943\t4.59543\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.57566379294\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:103\t6 \t3.84598\t0.476044\t3.12788\t4.50232\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.57566379294\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:104\t6 \t3.70835\t0.554704\t2.84278\t4.52018\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.57566379294\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:105\t5 \t3.56966\t0.699784\t2.57566\t4.75566\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.57566379294\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:106\t6 \t3.91155\t0.531345\t3.2797 \t4.63286\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.57566379294\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:107\t6 \t4.21167\t0.359962\t3.82872\t4.69631\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.57566379294\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:108\t5 \t3.49911\t0.624585\t2.51113\t4.28619\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.57566379294\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:109\t5 \t3.93379\t0.504683\t3.04739\t4.50143\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.57566379294\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:110\t6 \t3.80815\t0.859721\t2.5838 \t4.84365\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.57566379294\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:111\t3 \t4.44002\t0.694086\t2.95128\t5.0606 \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.57566379294\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:112\t6 \t3.51671\t0.838114\t2.71379\t4.74616\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.57566379294\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:113\t6 \t3.35559\t0.696034\t2.68253\t4.65919\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.57566379294\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:114\t5 \t4.06026\t0.35726 \t3.51036\t4.48074\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.57566379294\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:115\t6 \t3.7781 \t0.624878\t2.69079\t4.53203\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.57566379294\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:116\t6 \t4.52828\t0.350257\t4.02333\t4.97471\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.57566379294\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:117\t6 \t3.79893\t0.733294\t2.57566\t4.73152\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.57566379294\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:118\t6 \t4.12033\t0.43679 \t3.5553 \t4.78582\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.57566379294\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:119\t6 \t4.30814\t0.606473\t3.27424\t5.11783\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.57566379294\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:120\t6 \t4.65842\t0.417121\t4.20234\t5.26784\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.57566379294\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:121\t4 \t3.82854\t1.2548 \t2.57566\t5.30091\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.57566379294\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:122\t6 \t4.02754\t0.928535\t2.60214\t5.08902\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.57566379294\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:123\t5 \t3.73497\t0.660277\t2.92228\t4.96072\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.57566379294\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:124\t6 \t3.19457\t0.771896\t2.57566\t4.68117\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.57566379294\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:125\t5 \t3.10875\t0.368865\t2.75811\t3.88946\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.57566379294\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:126\t6 \t3.48925\t0.482242\t2.52307\t3.99873\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.52307142918\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:127\t5 \t4.19127\t0.339083\t3.74789\t4.64646\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.52307142918\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:128\t6 \t4.40673\t0.395357\t3.65162\t4.75879\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.52307142918\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:129\t6 \t4.16878\t0.66866 \t3.24772\t5.17818\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.52307142918\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:130\t5 \t4.27789\t0.459872\t3.78188\t4.96411\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.52307142918\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:131\t6 \t4.56138\t0.395438\t3.7653 \t4.96354\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.52307142918\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:132\t5 \t4.43038\t0.394408\t3.81467\t4.85663\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.52307142918\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/jovyan/neuronunit/neuronunit/tests/passive.py:85: RuntimeWarning: overflow encountered in exp\n", + " vm_fit[offset:,0] = a * np.exp(-t[offset:]/b) + c\n", + "/home/jovyan/neuronunit/neuronunit/tests/passive.py:85: RuntimeWarning: overflow encountered in multiply\n", + " vm_fit[offset:,0] = a * np.exp(-t[offset:]/b) + c\n", + "INFO:__main__:133\t6 \t4.14342\t0.644854\t2.82012\t4.6975 \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.52307142918\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:134\t6 \t4.25484\t0.387427\t3.90716\t4.97103\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.52307142918\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/jovyan/neuronunit/neuronunit/tests/passive.py:85: RuntimeWarning: overflow encountered in exp\n", + " vm_fit[offset:,0] = a * np.exp(-t[offset:]/b) + c\n", + "/home/jovyan/neuronunit/neuronunit/tests/passive.py:85: RuntimeWarning: overflow encountered in multiply\n", + " vm_fit[offset:,0] = a * np.exp(-t[offset:]/b) + c\n", + "INFO:__main__:135\t6 \t3.48361\t0.807436\t2.52307\t4.6866 \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.52307142918\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:136\t4 \t3.98643\t0.897139\t2.82012\t5.02655\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.52307142918\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:137\t3 \t3.68158\t0.525409\t2.82012\t4.36394\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.52307142918\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/jovyan/neuronunit/neuronunit/tests/passive.py:85: RuntimeWarning: overflow encountered in exp\n", + " vm_fit[offset:,0] = a * np.exp(-t[offset:]/b) + c\n", + "/home/jovyan/neuronunit/neuronunit/tests/passive.py:85: RuntimeWarning: overflow encountered in multiply\n", + " vm_fit[offset:,0] = a * np.exp(-t[offset:]/b) + c\n", + "INFO:__main__:138\t6 \t4.05625\t0.440638\t3.3411 \t4.51551\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.52307142918\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/jovyan/neuronunit/neuronunit/tests/passive.py:85: RuntimeWarning: overflow encountered in exp\n", + " vm_fit[offset:,0] = a * np.exp(-t[offset:]/b) + c\n", + "/home/jovyan/neuronunit/neuronunit/tests/passive.py:85: RuntimeWarning: overflow encountered in multiply\n", + " vm_fit[offset:,0] = a * np.exp(-t[offset:]/b) + c\n", + "/home/jovyan/neuronunit/neuronunit/tests/passive.py:85: RuntimeWarning: overflow encountered in exp\n", + " vm_fit[offset:,0] = a * np.exp(-t[offset:]/b) + c\n", + "/home/jovyan/neuronunit/neuronunit/tests/passive.py:85: RuntimeWarning: overflow encountered in multiply\n", + " vm_fit[offset:,0] = a * np.exp(-t[offset:]/b) + c\n", + "INFO:__main__:139\t6 \t3.91535\t0.503572\t3.03509\t4.36077\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.52307142918\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:140\t5 \t3.87695\t0.58941 \t2.96266\t4.5847 \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.52307142918\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:141\t6 \t3.69296\t0.565951\t3.03827\t4.46658\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.52307142918\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:142\t6 \t3.29519\t0.437201\t2.71273\t3.98314\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.52307142918\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:143\t6 \t4.07268\t0.334234\t3.35507\t4.36123\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.52307142918\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:144\t5 \t3.96881\t0.702166\t2.68926\t4.74622\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.52307142918\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:145\t6 \t4.0623 \t0.283412\t3.50879\t4.41424\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.52307142918\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:146\t6 \t4.21897\t0.474539\t3.18645\t4.58636\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.52307142918\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:147\t5 \t4.28308\t0.376596\t3.66355\t4.82235\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.52307142918\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:148\t5 \t4.11082\t0.0954588\t4.06037\t4.32177\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.52307142918\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:149\t6 \t3.87063\t0.657622 \t2.80581\t4.47962\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.52307142918\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:150\t6 \t3.95304\t0.490059 \t3.23952\t4.40974\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "true minimum 2.52307142918\n" + ] + } + ], + "source": [ + "key = str('Neocortex pyramidal cell layer 5-6')\n", + "\n", + "\n", + "try:\n", + " with open('multi_objective_izhi'+str(key)+'.p','rb') as f:\n", + " test_opt = pickle.load(f)\n", + "except:\n", + " MU = 6\n", + " NGEN = 150\n", + " seed = seeds[key]\n", + " use_test = test_frame[key]\n", + " print(seed)\n", + " ga_out, _ = om.run_ga(explore_param,NGEN,use_test,free_params=free_params, NSGA = True, MU = MU,seed_pop = seed, model_type = str('RAW'))\n", + "\n", + " test_opt = {str('multi_objective_izhi')+str(key):ga_out}\n", + " with open('multi_objective_izhi'+str(key)+'.p','wb') as f:\n", + " pickle.dump(test_opt,f)\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The optimizer needs to find a parameter set much better than 4.66 in order to be worthwhile, for the case of the Neo Cortical Pyramidal neuron. In only 10 generations the optimizer finds a parameter set 3.24. In 23 generations it finds a model with score 2.99." + ] + }, + { + "cell_type": "code", + "execution_count": 77, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "its True that 2.523071429176899 is significantly less than 5.47\n", + "Neocortex pyramidal cell layer 5-6\n" + ] + } + ], + "source": [ + "for k,v in test_opt.items():\n", + " #list_best = []\n", + " best_model = v['pf'][0]\n", + " second_best_model = v['pf'][1]\n", + " third_best_model = v['pf'][2]\n", + " list_best = [best_model,second_best_model,third_best_model]\n", + "summed = best_model.dtc.get_ss()\n", + "print('its {2} that {0} is significantly less than {1}'.format(summed,5.47,summed<5.47))\n", + "print(key)" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'C': 80.32327976522879,\n", + " 'a': 0.017035973642268745,\n", + " 'b': -1.995763503718458,\n", + " 'c': -56.110284353607987,\n", + " 'd': 39.870524431534278,\n", + " 'k': 0.52366419898375982,\n", + " 'vPeak': 3.4684930072840432,\n", + " 'vr': -67.290036991963049}" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "best_model.dtc.attrs" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'CapacitanceTest': 0.14163892773420128,\n", + " 'InjectedCurrentAPAmplitudeTest': 0.9999999997243523,\n", + " 'InjectedCurrentAPThresholdTest': 0.3846682140841715,\n", + " 'InjectedCurrentAPWidthTest': 0.10023627982283978,\n", + " 'InputResistanceTest': 0.08782389566447724,\n", + " 'RestingPotentialTest': 0.02152619567192071,\n", + " 'RheobaseTestP': 0.7321282418074668,\n", + " 'TimeConstantTest': 0.05504967466746935}" + ] + }, + "execution_count": 45, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "best_model.dtc.scores\n" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'CapacitanceTest': {'value': 0.8583610722657987},\n", + " 'InjectedCurrentAPAmplitudeTest': {'value': 2.7564772686616834e-10},\n", + " 'InjectedCurrentAPThresholdTest': {'value': 0.6153317859158285},\n", + " 'InjectedCurrentAPWidthTest': {'value': 0.8997637201771602},\n", + " 'InputResistanceTest': {'value': 0.9121761043355228},\n", + " 'RestingPotentialTest': {'value': 0.9784738043280793},\n", + " 'RheobaseTestP': {'value': 0.26787175819253317},\n", + " 'TimeConstantTest': {'value': 0.9449503253325306}}" + ] + }, + "execution_count": 50, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "best_model.dtc.score" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'mean': array(213.849583333333) * pA, 'n': 32, 'std': array(170.452454715608) * pA}\n", + "{'mean': array(120.672073643411) * Mohm, 'n': 86, 'std': array(77.6331608333564) * Mohm}\n", + "{'mean': array(15.7342424242424) * ms, 'n': 33, 'std': array(7.31162636832495) * ms}\n", + "{'mean': array(150.584166666667) * pF, 'n': 12, 'std': array(139.683884626343) * pF}\n", + "{'mean': array(-68.2481434599156) * mV, 'n': 79, 'std': array(6.53234788156637) * mV}\n", + "{'mean': array(1.20769387755102) * ms, 'n': 49, 'std': array(0.534345918375033) * ms}\n", + "{'mean': array(80.4351020408164) * mV, 'n': 49, 'std': array(12.7488030357545) * mV}\n", + "{'mean': array(-42.7357232704403) * mV, 'n': 53, 'std': array(8.04073233409085) * mV}\n" + ] + } + ], + "source": [ + "for row in test_frame['Neocortex pyramidal cell layer 5-6']:\n", + " print(row.observation)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "key = str('Neocortex pyramidal cell layer 5-6')\n", + "\n", + "\n", + "\n", + "'''\n", + "dtc = dtc_to_rheo(dtc)\n", + "dtc = format_test(dtc)\n", + "if dtc.rheobase is not None:\n", + " if dtc.rheobase!=-1.0:\n", + " dtc = nunit_evaluation(dtc)\n", + "''' \n", + " \n" + ] + }, + { + "cell_type": "code", + "execution_count": 76, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "#df" + ] + }, + { + "cell_type": "code", + "execution_count": 83, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
observationpredictiondisagreement
RheobaseTestP213.849583333333 pA24.9921875 pA188.857395833333 pA
InputResistanceTest120.672073643411 Mohm129234556.37052076 kg*m**2/(s**3*A**2)8.562482727109753 Mohm
TimeConstantTest15.7342424242424 ms0.016239105584981928 s0.5048631607395286 ms
CapacitanceTest150.584166666667 pF1.2565606321596948e-10 s**4*A**2/(kg*m**2)24.92810345069752 pF
RestingPotentialTest-68.2481434599156 mV-0.06842440161605753 V0.17625815614194096 mV
InjectedCurrentAPWidthTest1.20769387755102 ms0.001275 s0.06730612244898015 ms
InjectedCurrentAPAmplitudeTest80.4351020408164 mV-3.394359574052097e-05 V80.46904563655693 mV
InjectedCurrentAPThresholdTest-42.7357232704403 mV-0.03869543745051891 V4.040285819921387 mV
RheobaseTestP213.849583333333 pA25.154296875 pA188.695286458333 pA
InputResistanceTest120.672073643411 Mohm127521728.72962098 kg*m**2/(s**3*A**2)6.849655086209978 Mohm
TimeConstantTest15.7342424242424 ms0.015195661560195011 s0.5385808640473879 ms
CapacitanceTest150.584166666667 pF1.1916135165022537e-10 s**4*A**2/(kg*m**2)31.422815016441632 pF
RestingPotentialTest-68.2481434599156 mV-0.068399020199018 V0.15087673910240085 mV
InjectedCurrentAPWidthTest1.20769387755102 ms0.001275 s0.06730612244898015 ms
InjectedCurrentAPAmplitudeTest80.4351020408164 mV-0.0003786137860437716 V80.81371582686018 mV
InjectedCurrentAPThresholdTest-42.7357232704403 mV-0.03830641512782343 V4.429308142616875 mV
RheobaseTestP213.849583333333 pA24.9921875 pA188.857395833333 pA
InputResistanceTest120.672073643411 Mohm129375643.30537802 kg*m**2/(s**3*A**2)8.703569661967023 Mohm
TimeConstantTest15.7342424242424 ms0.01617715819692096 s0.4429157726785604 ms
CapacitanceTest150.584166666667 pF1.2504021455365e-10 s**4*A**2/(kg*m**2)25.543952113017 pF
RestingPotentialTest-68.2481434599156 mV-0.06839796225443817 V0.14981879452257374 mV
InjectedCurrentAPWidthTest1.20769387755102 ms0.0012000000000000001 s0.007693877551019801 ms
InjectedCurrentAPAmplitudeTest80.4351020408164 mV-0.001715732173261715 V82.15083421407812 mV
InjectedCurrentAPThresholdTest-42.7357232704403 mV-0.03680516308404327 V5.930560186397038 mV
\n", + "
" + ], + "text/plain": [ + " observation \\\n", + "RheobaseTestP 213.849583333333 pA \n", + "InputResistanceTest 120.672073643411 Mohm \n", + "TimeConstantTest 15.7342424242424 ms \n", + "CapacitanceTest 150.584166666667 pF \n", + "RestingPotentialTest -68.2481434599156 mV \n", + "InjectedCurrentAPWidthTest 1.20769387755102 ms \n", + "InjectedCurrentAPAmplitudeTest 80.4351020408164 mV \n", + "InjectedCurrentAPThresholdTest -42.7357232704403 mV \n", + "RheobaseTestP 213.849583333333 pA \n", + "InputResistanceTest 120.672073643411 Mohm \n", + "TimeConstantTest 15.7342424242424 ms \n", + "CapacitanceTest 150.584166666667 pF \n", + "RestingPotentialTest -68.2481434599156 mV \n", + "InjectedCurrentAPWidthTest 1.20769387755102 ms \n", + "InjectedCurrentAPAmplitudeTest 80.4351020408164 mV \n", + "InjectedCurrentAPThresholdTest -42.7357232704403 mV \n", + "RheobaseTestP 213.849583333333 pA \n", + "InputResistanceTest 120.672073643411 Mohm \n", + "TimeConstantTest 15.7342424242424 ms \n", + "CapacitanceTest 150.584166666667 pF \n", + "RestingPotentialTest -68.2481434599156 mV \n", + "InjectedCurrentAPWidthTest 1.20769387755102 ms \n", + "InjectedCurrentAPAmplitudeTest 80.4351020408164 mV \n", + "InjectedCurrentAPThresholdTest -42.7357232704403 mV \n", + "\n", + " prediction \\\n", + "RheobaseTestP 24.9921875 pA \n", + "InputResistanceTest 129234556.37052076 kg*m**2/(s**3*A**2) \n", + "TimeConstantTest 0.016239105584981928 s \n", + "CapacitanceTest 1.2565606321596948e-10 s**4*A**2/(kg*m**2) \n", + "RestingPotentialTest -0.06842440161605753 V \n", + "InjectedCurrentAPWidthTest 0.001275 s \n", + "InjectedCurrentAPAmplitudeTest -3.394359574052097e-05 V \n", + "InjectedCurrentAPThresholdTest -0.03869543745051891 V \n", + "RheobaseTestP 25.154296875 pA \n", + "InputResistanceTest 127521728.72962098 kg*m**2/(s**3*A**2) \n", + "TimeConstantTest 0.015195661560195011 s \n", + "CapacitanceTest 1.1916135165022537e-10 s**4*A**2/(kg*m**2) \n", + "RestingPotentialTest -0.068399020199018 V \n", + "InjectedCurrentAPWidthTest 0.001275 s \n", + "InjectedCurrentAPAmplitudeTest -0.0003786137860437716 V \n", + "InjectedCurrentAPThresholdTest -0.03830641512782343 V \n", + "RheobaseTestP 24.9921875 pA \n", + "InputResistanceTest 129375643.30537802 kg*m**2/(s**3*A**2) \n", + "TimeConstantTest 0.01617715819692096 s \n", + "CapacitanceTest 1.2504021455365e-10 s**4*A**2/(kg*m**2) \n", + "RestingPotentialTest -0.06839796225443817 V \n", + "InjectedCurrentAPWidthTest 0.0012000000000000001 s \n", + "InjectedCurrentAPAmplitudeTest -0.001715732173261715 V \n", + "InjectedCurrentAPThresholdTest -0.03680516308404327 V \n", + "\n", + " disagreement \n", + "RheobaseTestP 188.857395833333 pA \n", + "InputResistanceTest 8.562482727109753 Mohm \n", + "TimeConstantTest 0.5048631607395286 ms \n", + "CapacitanceTest 24.92810345069752 pF \n", + "RestingPotentialTest 0.17625815614194096 mV \n", + "InjectedCurrentAPWidthTest 0.06730612244898015 ms \n", + "InjectedCurrentAPAmplitudeTest 80.46904563655693 mV \n", + "InjectedCurrentAPThresholdTest 4.040285819921387 mV \n", + "RheobaseTestP 188.695286458333 pA \n", + "InputResistanceTest 6.849655086209978 Mohm \n", + "TimeConstantTest 0.5385808640473879 ms \n", + "CapacitanceTest 31.422815016441632 pF \n", + "RestingPotentialTest 0.15087673910240085 mV \n", + "InjectedCurrentAPWidthTest 0.06730612244898015 ms \n", + "InjectedCurrentAPAmplitudeTest 80.81371582686018 mV \n", + "InjectedCurrentAPThresholdTest 4.429308142616875 mV \n", + "RheobaseTestP 188.857395833333 pA \n", + "InputResistanceTest 8.703569661967023 Mohm \n", + "TimeConstantTest 0.4429157726785604 ms \n", + "CapacitanceTest 25.543952113017 pF \n", + "RestingPotentialTest 0.14981879452257374 mV \n", + "InjectedCurrentAPWidthTest 0.007693877551019801 ms \n", + "InjectedCurrentAPAmplitudeTest 82.15083421407812 mV \n", + "InjectedCurrentAPThresholdTest 5.930560186397038 mV " + ] + }, + "execution_count": 83, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from neuronunit.optimization.optimization_management import bridge_judge\n", + "dfs = []\n", + "for model in list_best:\n", + " df = pd.DataFrame(index=list(dtc.tests),columns=['observation','prediction','disagreement'])#,columns=list(reduced_cells.keys()))\n", + "\n", + " dtc = DataTC()\n", + " dtc.tests = use_test\n", + " dtc.attrs = model.dtc.attrs\n", + " dtc.backend = 'RAW'\n", + " dtc.cell_name = 'vanilla'\n", + " #for key, use_test in test_frame.items():\n", + " dtc.tests = test_frame[key]\n", + " dtc = dtc_to_rheo(dtc)\n", + " dtc = format_test(dtc)\n", + " for k,t in enumerate(dtc.tests):\n", + " t.params = dtc.vtest[k]\n", + " score,pred = bridge_judge((t,dtc))\n", + " #print(score,pred) \n", + " df.iloc[k]['observation'] = t.observation['mean'] \n", + " try:\n", + " agreement = np.abs(t.observation['mean'] - pred['value'])\n", + " df.iloc[k]['prediction'] = pred['value']\n", + " df.iloc[k]['disagreement'] = agreement\n", + "\n", + " except:\n", + " agreement = np.abs(t.observation['mean'] - pred['mean'])\n", + " df.iloc[k]['prediction'] = pred['mean']\n", + " df.iloc[k]['disagreement'] = agreement\n", + " dfs.append(df)\n", + "result = pd.concat(dfs)\n", + "result " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "df\n", + "from neuronunit.models.reduced import ReducedModel\n", + "from neuronunit.optimization.data_transport_container import DataTC\n", + "from neuronunit.optimization.model_parameters import model_params, path_params, transcribe_units\n", + "from neuronunit.optimization.optimization_management import mint_generic_model\n", + "LEMS_MODEL_PATH = path_params['model_path']\n", + "\n", + "\n", + "model = ReducedModel(LEMS_MODEL_PATH,name = str('vanilla'),backend = ('RAW'))\n", + "dtc = DataTC()\n", + "dtc.attrs = model.attrs" + ] + } + ], + "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.5.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/neuronunit/examples/brian_multi_comp_ca2_HH.ipynb b/neuronunit/examples/brian_multi_comp_ca2_HH.ipynb new file mode 100644 index 000000000..f445d6dd4 --- /dev/null +++ b/neuronunit/examples/brian_multi_comp_ca2_HH.ipynb @@ -0,0 +1,971 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "first demonstrate that the brian backend works:\n", + "bash\n", + "```sudo /opt/conda/bin/conda install cython\n", + "\n", + "sudo /opt/conda/bin/pip install git+https://github.co\n", + "m/brian-team/brian2.git\n", + "\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "\n", + "'''\n", + "Hodgkin-Huxley equations (1952).\n", + "Spikes are recorded along the axon, and then velocity is calculated.\n", + "'''\n", + "from brian2 import *\n", + "from scipy import stats\n", + "\n", + "defaultclock.dt = 0.01*ms\n", + "\n", + "morpho = Cylinder(length=10*cm, diameter=2*238*um, n=1000, type='axon')\n", + "\n", + "El = 10.613*mV\n", + "ENa = 115*mV\n", + "EK = -12*mV\n", + "gl = 0.3*msiemens/cm**2\n", + "gNa0 = 120*msiemens/cm**2\n", + "gK = 36*msiemens/cm**2\n", + "\n", + "# Typical equations\n", + "eqs = '''\n", + "# The same equations for the whole neuron, but possibly different parameter values\n", + "# distributed transmembrane current\n", + "Im = gl * (El-v) + gNa * m**3 * h * (ENa-v) + gK * n**4 * (EK-v) : amp/meter**2\n", + "I : amp (point current) # applied current\n", + "dm/dt = alpham * (1-m) - betam * m : 1\n", + "dn/dt = alphan * (1-n) - betan * n : 1\n", + "dh/dt = alphah * (1-h) - betah * h : 1\n", + "alpham = (0.1/mV) * (-v+25*mV) / (exp((-v+25*mV) / (10*mV)) - 1)/ms : Hz\n", + "betam = 4 * exp(-v/(18*mV))/ms : Hz\n", + "alphah = 0.07 * exp(-v/(20*mV))/ms : Hz\n", + "betah = 1/(exp((-v+30*mV) / (10*mV)) + 1)/ms : Hz\n", + "alphan = (0.01/mV) * (-v+10*mV) / (exp((-v+10*mV) / (10*mV)) - 1)/ms : Hz\n", + "betan = 0.125*exp(-v/(80*mV))/ms : Hz\n", + "gNa : siemens/meter**2\n", + "'''\n", + "\n", + "neuron = SpatialNeuron(morphology=morpho, model=eqs, method=\"exponential_euler\", \n", + " refractory=\"m > 0.4\", threshold=\"m > 0.5\",\n", + " Cm=1*uF/cm**2, Ri=35.4*ohm*cm)\n", + "neuron.v = 0*mV\n", + "neuron.h = 1\n", + "neuron.m = 0\n", + "neuron.n = .5\n", + "neuron.I = 0*amp\n", + "neuron.gNa = gNa0\n", + "M = StateMonitor(neuron, 'v', record=True)\n", + "spikes = SpikeMonitor(neuron)\n", + "\n", + "run(50*ms, report='text')\n", + "neuron.I[0] = 1*uA # current injection at one end\n", + "run(3*ms)\n", + "neuron.I = 0*amp\n", + "run(50*ms, report='text')\n", + "\n", + "# Calculation of velocity\n", + "slope, intercept, r_value, p_value, std_err = stats.linregress(spikes.t/second,\n", + " neuron.distance[spikes.i]/meter)\n", + "print(\"Velocity = %.2f m/s\" % slope)\n", + "\n", + "subplot(211)\n", + "for i in range(10):\n", + " plot(M.t/ms, M.v.T[:, i*100]/mV)\n", + "ylabel('v')\n", + "subplot(212)\n", + "plot(spikes.t/ms, spikes.i*neuron.length[0]/cm, '.k')\n", + "plot(spikes.t/ms, (intercept+slope*(spikes.t/second))/cm, 'r')\n", + "xlabel('Time (ms)')\n", + "ylabel('Position (cm)')\n", + "show()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "'''\n", + "http://neuromorpho.org/neuron_info.jsp?neuron_name=tc200\n", + "NeuroMorpho.Org ID :NMO_00881\n", + "Notes\n", + "-----\n", + "* Completely removed the \"Fast mechanism for submembranal Ca++ concentration\n", + " (cai)\" -- it did not affect the results presented here\n", + "* Time constants for the I_T current are slightly different from the equations\n", + " given in the paper -- the paper calculation seems to be based on 36 degree\n", + " Celsius but the temperature that is used is 34 degrees.\n", + "* To reproduce Figure 12C, the \"presence of dendritic shunt conductances\" meant\n", + " setting g_L to 0.15 mS/cm^2 for the whole neuron.\n", + "* Other small discrepancies with the paper -- values from the NEURON code were\n", + " used whenever different from the values stated in the paper\n", + "'''\n", + "\n", + "from neo import AnalogSignal\n", + "import neuronunit.capabilities.spike_functions as sf\n", + "import neuronunit.capabilities as cap\n", + "cap.ReceivesCurrent\n", + "cap.ProducesActionPotentials\n", + "from types import MethodType\n", + "\n", + "def bind_NU_interface(model):\n", + "\n", + " def load_model(self):\n", + " neuron = None\n", + " from brian2 import *\n", + "\n", + " from brian2.units.constants import (zero_celsius, faraday_constant as F,\n", + " gas_constant as R)\n", + "\n", + " defaultclock.dt = 0.01*ms\n", + "\n", + " VT = -52*mV\n", + " El = -76.5*mV # from code, text says: -69.85*mV\n", + "\n", + " E_Na = 50*mV\n", + " E_K = -100*mV\n", + " C_d = 7.954 # dendritic correction factor\n", + "\n", + " T = 34*kelvin + zero_celsius # 34 degC (current-clamp experiments)\n", + " tadj_HH = 3.0**((34-36)/10.0) # temperature adjustment for Na & K (original recordings at 36 degC)\n", + " tadj_m_T = 2.5**((34-24)/10.0)\n", + " tadj_h_T = 2.5**((34-24)/10.0)\n", + "\n", + " shift_I_T = -1*mV\n", + "\n", + " gamma = F/(R*T) # R=gas constant, F=Faraday constant\n", + " Z_Ca = 2 # Valence of Calcium ions\n", + " Ca_i = 240*nM # intracellular Calcium concentration\n", + " Ca_o = 2*mM # extracellular Calcium concentration\n", + "\n", + " eqs = Equations('''\n", + " Im = gl*(El-v) - I_Na - I_K - I_T: amp/meter**2\n", + " I_inj : amp (point current)\n", + " gl : siemens/meter**2\n", + " # HH-type currents for spike initiation\n", + " g_Na : siemens/meter**2\n", + " g_K : siemens/meter**2\n", + " I_Na = g_Na * m**3 * h * (v-E_Na) : amp/meter**2\n", + " I_K = g_K * n**4 * (v-E_K) : amp/meter**2\n", + " v2 = v - VT : volt # shifted membrane potential (Traub convention)\n", + " dm/dt = (0.32*(mV**-1)*(13.*mV-v2)/\n", + " (exp((13.*mV-v2)/(4.*mV))-1.)*(1-m)-0.28*(mV**-1)*(v2-40.*mV)/\n", + " (exp((v2-40.*mV)/(5.*mV))-1.)*m) / ms * tadj_HH: 1\n", + " dn/dt = (0.032*(mV**-1)*(15.*mV-v2)/\n", + " (exp((15.*mV-v2)/(5.*mV))-1.)*(1.-n)-.5*exp((10.*mV-v2)/(40.*mV))*n) / ms * tadj_HH: 1\n", + " 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\n", + " # Low-threshold Calcium current (I_T) -- nonlinear function of voltage\n", + " I_T = P_Ca * m_T**2*h_T * G_Ca : amp/meter**2\n", + " P_Ca : meter/second # maximum Permeability to Calcium\n", + " 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\n", + " dm_T/dt = -(m_T - m_T_inf)/tau_m_T : 1\n", + " dh_T/dt = -(h_T - h_T_inf)/tau_h_T : 1\n", + " m_T_inf = 1/(1 + exp(-(v/mV + 56)/6.2)) : 1\n", + " h_T_inf = 1/(1 + exp((v/mV + 80)/4)) : 1\n", + " 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\n", + " tau_h_T = (int(v<-81*mV) * exp((v/mV + 466)/66.6) +\n", + " int(v>=-81*mV) * (28 + exp(-(v/mV + 21)/10.5))) * ms / tadj_h_T: second\n", + " ''')\n", + " \n", + " self.neuron = SpatialNeuron(morphology=morpho, model=eqs, method=\"exponential_euler\", \n", + " refractory=\"m > 0.4\", threshold=\"m > 0.5\",\n", + " Cm=1*uF/cm**2, Ri=35.4*ohm*cm)\n", + " self.M = StateMonitor(neuron, 'v', record=True)\n", + "\n", + " \n", + "\n", + "\n", + "\n", + " def init_backend(self, attrs = None, cell_name= 'HH_cond_exp', current_src_name = 'hannah', DTC = None, dt=0.01):\n", + " backend = 'HHpyNN'\n", + " self.current_src_name = current_src_name\n", + " self.cell_name = cell_name\n", + " self.adexp = True\n", + "\n", + " self.DCSource = DCSource\n", + " self.setup = setup\n", + " self.model_path = None\n", + " self.related_data = {}\n", + " self.lookup = {}\n", + " self.attrs = {}\n", + " self.neuron = neuron\n", + " self.model._backend = str('ExternalSim')\n", + " self.backend = self\n", + " self.model.attrs = {}\n", + "\n", + " #self.orig_lems_file_path = 'satisfying'\n", + " #self.model._backend.use_memory_cache = False\n", + " #self.model.unpicklable += ['h','ns','_backend']\n", + " self.dt = dt\n", + " if type(DTC) is not type(None):\n", + " if type(DTC.attrs) is not type(None):\n", + "\n", + " self.set_attrs(**DTC.attrs)\n", + " assert len(self.model.attrs.keys()) > 0\n", + "\n", + " if hasattr(DTC,'current_src_name'):\n", + " self._current_src_name = DTC.current_src_name\n", + "\n", + " if hasattr(DTC,'cell_name'):\n", + " self.cell_name = DTC.cell_name\n", + " \n", + " self.load_model()\n", + "\n", + " def get_membrane_potential(self):\n", + " \"\"\"Must return a neo.core.AnalogSignal.\n", + " And must destroy the hoc vectors that comprise it.\n", + " \"\"\"\n", + " #dt = float(copy.copy(self.neuron.dt))\n", + " data = self.hhcell.get_data().segments[0]\n", + " volts = data.filter(name=\"v\")[0]#/10.0\n", + " #data_block = all_cells.get_data()\n", + "\n", + " vm = AnalogSignal(volts,\n", + " units = mV,\n", + " sampling_period = self.dt *ms)\n", + " #results['vm'] = vm\n", + " return vm#data.filter(name=\"v\")[0]\n", + "\n", + " def _local_run(self):\n", + " '''\n", + " pyNN lazy array demands a minimum population size of 3. Why is that.\n", + " '''\n", + " results = {}\n", + " DURATION = 1000.0\n", + " \n", + " #ctx_cells.celltype.recordable\n", + " \n", + " \n", + " if self.celltype == 'HH_cond_exp':\n", + "\n", + " self.hhcell.record('spikes','v')\n", + "\n", + " else:\n", + " self.neuron.record_v(self.hhcell, \"Results/HH_cond_exp_%s.v\" % str(neuron))\n", + "\n", + " #self.neuron.record_gsyn(self.hhcell, \"Results/HH_cond_exp_%s.gsyn\" % str(neuron))\n", + " self.neuron.run(DURATION)\n", + " data = self.hhcell.get_data().segments[0]\n", + " volts = data.filter(name=\"v\")[0]#/10.0\n", + " #data_block = all_cells.get_data()\n", + "\n", + " vm = AnalogSignal(volts,\n", + " units = mV,\n", + " sampling_period = self.dt *ms)\n", + " results['vm'] = vm\n", + " results['t'] = vm.times # self.times\n", + " results['run_number'] = results.get('run_number',0) + 1\n", + " return results\n", + "\n", + "\n", + "\n", + "\n", + " def set_attrs(self,**attrs): \n", + " '''\n", + " example params:\n", + " neuron.v = 0*mV\n", + " neuron.h = 1\n", + " neuron.m = 0\n", + " neuron.n = .5\n", + " neuron.I = 0*amp\n", + " neuron.gNa = gNa0\n", + " '''\n", + " self.init_backend()\n", + " self.model.attrs.update(attrs)\n", + " for k, v in attrs.items():\n", + " exec('self.neuron.'+str(k)+'='+str(v)\n", + "\n", + " assert type(self.model.attrs) is not type(None)\n", + " return self\n", + "\n", + "\n", + " def inject_square_current(self,current):\n", + " attrs = copy.copy(self.model.attrs)\n", + " self.init_backend()\n", + " self.set_attrs(**attrs)\n", + " c = copy.copy(current)\n", + " if 'injected_square_current' in c.keys():\n", + " c = current['injected_square_current']\n", + "\n", + " stop = float(c['delay'])+float(c['duration'])\n", + " duration = float(c['duration'])\n", + " start = float(c['delay'])\n", + " amplitude = float(c['amplitude'])\n", + " #electrode = self.neuron.DCSource(start=start, stop=stop, amplitude=amplitude)\n", + " self.neuron.I[0] = amplitude*uA # current injection at one end\n", + "\n", + "\n", + " #electrode.inject_into(self.hhcell)\n", + " #self.results = self._local_run()\n", + " run(3*ms)\n", + "\n", + " self.vm = self.results['vm']\n", + " \n", + " \n", + "\n", + "\n", + " def get_APs(self,vm):\n", + " # spikes = self.M(self.neuron)\n", + "\n", + " vm = self.get_membrane_potential()\n", + " waveforms = sf.get_spike_waveforms(vm,threshold=-45.0*mV)\n", + " return waveforms\n", + "\n", + " def get_spike_train(self,**run_params):\n", + " vm = self.get_membrane_potential()\n", + "\n", + " spike_train = threshold_detection(vm,threshold=-45.0*mV)\n", + "\n", + " return spike_train\n", + " \n", + " def get_spike_count(self,**run_params):\n", + " vm = self.get_membrane_potential()\n", + " return len(threshold_detection(vm,threshold=-45.0*mV))\n", + " \n", + " model.init_backend = MethodType(init_backend,model)\n", + " model.get_spike_count = MethodType(get_spike_count,model)\n", + " model.get_APs = MethodType(get_APs,model)\n", + " model.get_spike_train = MethodType(get_spike_train,model)\n", + " model.set_attrs = MethodType(set_attrs, model) # Bind to the score.\n", + " model.inject_square_current = MethodType(inject_square_current, model) # Bind to the score.\n", + " model.set_attrs = MethodType(set_attrs, model) # Bind to the score.\n", + " model.get_membrane_potential = MethodType(get_membrane_potential,model)\n", + " model.load_model = MethodType(load_model, model) # Bind to the score.\n", + " model._local_run = MethodType(_local_run,model)\n", + " model.init_backend(model)\n", + " #model.load_model() #= MethodType(load_model, model) # Bind to the score.\n", + "\n", + " return model\n", + "HH_cond_exp = bind_NU_interface(HH_cond_exp) \n", + "#HH_cond_exp\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Starting simulation at t=0. s for a duration of 50. ms\n", + "50. ms (100%) simulated in 3s\n", + "Starting simulation at t=53. ms for a duration of 50. ms\n", + "50. ms (100%) simulated in 2s\n", + "Velocity = 12.33 m/s\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "'''\n", + "Hodgkin-Huxley equations (1952).\n", + "Spikes are recorded along the axon, and then velocity is calculated.\n", + "'''\n", + "from brian2 import *\n", + "from scipy import stats\n", + "\n", + "defaultclock.dt = 0.01*ms\n", + "\n", + "morpho = Cylinder(length=10*cm, diameter=2*238*um, n=1000, type='axon')\n", + "\n", + "El = 10.613*mV\n", + "ENa = 115*mV\n", + "EK = -12*mV\n", + "gl = 0.3*msiemens/cm**2\n", + "gNa0 = 120*msiemens/cm**2\n", + "gK = 36*msiemens/cm**2\n", + "\n", + "# Typical equations\n", + "eqs = '''\n", + "# The same equations for the whole neuron, but possibly different parameter values\n", + "# distributed transmembrane current\n", + "Im = gl * (El-v) + gNa * m**3 * h * (ENa-v) + gK * n**4 * (EK-v) : amp/meter**2\n", + "I : amp (point current) # applied current\n", + "dm/dt = alpham * (1-m) - betam * m : 1\n", + "dn/dt = alphan * (1-n) - betan * n : 1\n", + "dh/dt = alphah * (1-h) - betah * h : 1\n", + "alpham = (0.1/mV) * (-v+25*mV) / (exp((-v+25*mV) / (10*mV)) - 1)/ms : Hz\n", + "betam = 4 * exp(-v/(18*mV))/ms : Hz\n", + "alphah = 0.07 * exp(-v/(20*mV))/ms : Hz\n", + "betah = 1/(exp((-v+30*mV) / (10*mV)) + 1)/ms : Hz\n", + "alphan = (0.01/mV) * (-v+10*mV) / (exp((-v+10*mV) / (10*mV)) - 1)/ms : Hz\n", + "betan = 0.125*exp(-v/(80*mV))/ms : Hz\n", + "gNa : siemens/meter**2\n", + "'''\n", + "\n", + "neuron = SpatialNeuron(morphology=morpho, model=eqs, method=\"exponential_euler\", \n", + " refractory=\"m > 0.4\", threshold=\"m > 0.5\",\n", + " Cm=1*uF/cm**2, Ri=35.4*ohm*cm)\n", + "neuron.v = 0*mV\n", + "neuron.h = 1\n", + "neuron.m = 0\n", + "neuron.n = .5\n", + "neuron.I = 0*amp\n", + "neuron.gNa = gNa0\n", + "M = StateMonitor(neuron, 'v', record=True)\n", + "spikes = SpikeMonitor(neuron)\n", + "\n", + "#run(50*ms, report='text')\n", + "neuron.I[0] = 1*uA # current injection at one end\n", + "run(3*ms)\n", + "\n", + "#neuron.I = 0*amp\n", + "#run(50*ms, report='text')\n", + "\n", + "'''\n", + "# Calculation of velocity\n", + "slope, intercept, r_value, p_value, std_err = stats.linregress(spikes.t/second,\n", + " neuron.distance[spikes.i]/meter)\n", + "print(\"Velocity = %.2f m/s\" % slope)\n", + "\n", + "subplot(211)\n", + "for i in range(10):\n", + " plot(M.t/ms, M.v.T[:, i*100]/mV)\n", + "ylabel('v')\n", + "subplot(212)\n", + "plot(spikes.t/ms, spikes.i*neuron.length[0]/cm, '.k')\n", + "plot(spikes.t/ms, (intercept+slope*(spikes.t/second))/cm, 'r')\n", + "xlabel('Time (ms)')\n", + "ylabel('Position (cm)')\n", + "show()\n", + "'''" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Requirement already satisfied: lazyarray in /opt/conda/lib/python3.5/site-packages (0.3.2)\n", + "Requirement already satisfied: numpy>=1.8 in /opt/conda/lib/python3.5/site-packages (from lazyarray) (1.12.1)\n", + "\u001b[31mcryptography 2.2.1 requires asn1crypto>=0.21.0, which is not installed.\u001b[0m\n", + "\u001b[31mcffi 1.11.5 requires pycparser, which is not installed.\u001b[0m\n", + "\u001b[31mallensdk 0.14.2 has requirement pandas<0.20.0,>=0.16.2, but you'll have pandas 0.23.1 which is incompatible.\u001b[0m\n", + "\u001b[33mYou are using pip version 10.0.1, however version 18.1 is available.\n", + "You should consider upgrading via the 'pip install --upgrade pip' command.\u001b[0m\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/opt/conda/lib/python3.5/site-packages/pyNN/neuron/__init__.py:14: UserWarning: mpi4py not available\n", + " warnings.warn(\"mpi4py not available\")\n" + ] + } + ], + "source": [ + "from pyNN.neuron import *\n", + "from pyNN.neuron import HH_cond_exp\n", + "from pyNN.neuron import EIF_cond_exp_isfa_ista\n", + "from pyNN.neuron import Izhikevich\n", + "\n", + "from pyNN import neuron\n", + "#\n", + "from pyNN.neuron import simulator as sim\n", + "from pyNN.neuron import setup as setup\n", + "\n", + "from pyNN.neuron import DCSource\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import io\n", + "import math\n", + "import pdb\n", + "#from numba import jit\n", + "from contextlib import redirect_stdout\n", + "import numpy as np\n", + "#from .base import *\n", + "import quantities as qt\n", + "from quantities import mV, ms, s\n", + "import matplotlib.pyplot as plt\n", + "from pyNN.neuron import *\n", + "from pyNN.neuron import HH_cond_exp\n", + "from pyNN.neuron import EIF_cond_exp_isfa_ista\n", + "from pyNN.neuron import Izhikevich\n", + "from elephant.spike_train_generation import threshold_detection\n", + "\n", + "from pyNN import neuron\n", + "#\n", + "#from pyNN.neuron import simulator as sim\n", + "from pyNN.neuron import setup as setup\n", + "#from pyNN.neuron import Izhikevich\n", + "#from pyNN.neuron import Population\n", + "from pyNN.neuron import DCSource\n", + "import numpy as np\n", + "import copy\n", + "\n", + "\n", + "from neo import AnalogSignal\n", + "import neuronunit.capabilities.spike_functions as sf\n", + "import neuronunit.capabilities as cap\n", + "cap.ReceivesCurrent\n", + "cap.ProducesActionPotentials\n", + "from types import MethodType\n", + "\n", + "def bind_NU_interface(model):\n", + "\n", + " def load_model(self):\n", + " neuron = None\n", + " from pyNN import neuron\n", + " self.hhcell = neuron.create(EIF_cond_exp_isfa_ista())\n", + " neuron.setup(timestep=self.dt, min_delay=1.0)\n", + "\n", + "\n", + " def init_backend(self, attrs = None, cell_name= 'HH_cond_exp', current_src_name = 'hannah', DTC = None, dt=0.01):\n", + " backend = 'HHpyNN'\n", + " self.current_src_name = current_src_name\n", + " self.cell_name = cell_name\n", + " self.adexp = True\n", + "\n", + " self.DCSource = DCSource\n", + " self.setup = setup\n", + " self.model_path = None\n", + " self.related_data = {}\n", + " self.lookup = {}\n", + " self.attrs = {}\n", + " self.neuron = neuron\n", + " self.model._backend = str('ExternalSim')\n", + " self.backend = self\n", + " self.model.attrs = {}\n", + "\n", + " #self.orig_lems_file_path = 'satisfying'\n", + " #self.model._backend.use_memory_cache = False\n", + " #self.model.unpicklable += ['h','ns','_backend']\n", + " self.dt = dt\n", + " if type(DTC) is not type(None):\n", + " if type(DTC.attrs) is not type(None):\n", + "\n", + " self.set_attrs(**DTC.attrs)\n", + " assert len(self.model.attrs.keys()) > 0\n", + "\n", + " if hasattr(DTC,'current_src_name'):\n", + " self._current_src_name = DTC.current_src_name\n", + "\n", + " if hasattr(DTC,'cell_name'):\n", + " self.cell_name = DTC.cell_name\n", + " \n", + " self.load_model()\n", + "\n", + " def get_membrane_potential(self):\n", + " \"\"\"Must return a neo.core.AnalogSignal.\n", + " And must destroy the hoc vectors that comprise it.\n", + " \"\"\"\n", + " #dt = float(copy.copy(self.neuron.dt))\n", + " data = self.hhcell.get_data().segments[0]\n", + " volts = data.filter(name=\"v\")[0]#/10.0\n", + " #data_block = all_cells.get_data()\n", + "\n", + " vm = AnalogSignal(volts,\n", + " units = mV,\n", + " sampling_period = self.dt *ms)\n", + " #results['vm'] = vm\n", + " return vm#data.filter(name=\"v\")[0]\n", + "\n", + " def _local_run(self):\n", + " '''\n", + " pyNN lazy array demands a minimum population size of 3. Why is that.\n", + " '''\n", + " results = {}\n", + " DURATION = 1000.0\n", + " \n", + " #ctx_cells.celltype.recordable\n", + " \n", + " \n", + " if self.celltype == 'HH_cond_exp':\n", + "\n", + " self.hhcell.record('spikes','v')\n", + "\n", + " else:\n", + " self.neuron.record_v(self.hhcell, \"Results/HH_cond_exp_%s.v\" % str(neuron))\n", + "\n", + " #self.neuron.record_gsyn(self.hhcell, \"Results/HH_cond_exp_%s.gsyn\" % str(neuron))\n", + " self.neuron.run(DURATION)\n", + " data = self.hhcell.get_data().segments[0]\n", + " volts = data.filter(name=\"v\")[0]#/10.0\n", + " #data_block = all_cells.get_data()\n", + "\n", + " vm = AnalogSignal(volts,\n", + " units = mV,\n", + " sampling_period = self.dt *ms)\n", + " results['vm'] = vm\n", + " results['t'] = vm.times # self.times\n", + " results['run_number'] = results.get('run_number',0) + 1\n", + " return results\n", + "\n", + "\n", + "\n", + "\n", + " def set_attrs(self,**attrs):\n", + " self.init_backend()\n", + " self.model.attrs.update(attrs)\n", + " assert type(self.model.attrs) is not type(None)\n", + " self.hhcell[0].set_parameters(**attrs)\n", + " return self\n", + "\n", + "\n", + " def inject_square_current(self,current):\n", + " attrs = copy.copy(self.model.attrs)\n", + " self.init_backend()\n", + " self.set_attrs(**attrs)\n", + " c = copy.copy(current)\n", + " if 'injected_square_current' in c.keys():\n", + " c = current['injected_square_current']\n", + "\n", + " stop = float(c['delay'])+float(c['duration'])\n", + " duration = float(c['duration'])\n", + " start = float(c['delay'])\n", + " amplitude = float(c['amplitude'])\n", + " electrode = self.neuron.DCSource(start=start, stop=stop, amplitude=amplitude)\n", + "\n", + "\n", + " electrode.inject_into(self.hhcell)\n", + " self.results = self._local_run()\n", + " self.vm = self.results['vm']\n", + "\n", + " def get_APs(self,vm):\n", + " vm = self.get_membrane_potential()\n", + " waveforms = sf.get_spike_waveforms(vm,threshold=-45.0*mV)\n", + " return waveforms\n", + "\n", + " def get_spike_train(self,**run_params):\n", + " vm = self.get_membrane_potential()\n", + "\n", + " spike_train = threshold_detection(vm,threshold=-45.0*mV)\n", + "\n", + " return spike_train\n", + " \n", + " def get_spike_count(self,**run_params):\n", + " vm = self.get_membrane_potential()\n", + " return len(threshold_detection(vm,threshold=-45.0*mV))\n", + " \n", + " model.init_backend = MethodType(init_backend,model)\n", + " model.get_spike_count = MethodType(get_spike_count,model)\n", + " model.get_APs = MethodType(get_APs,model)\n", + " model.get_spike_train = MethodType(get_spike_train,model)\n", + " model.set_attrs = MethodType(set_attrs, model) # Bind to the score.\n", + " model.inject_square_current = MethodType(inject_square_current, model) # Bind to the score.\n", + " model.set_attrs = MethodType(set_attrs, model) # Bind to the score.\n", + " model.get_membrane_potential = MethodType(get_membrane_potential,model)\n", + " model.load_model = MethodType(load_model, model) # Bind to the score.\n", + " model._local_run = MethodType(_local_run,model)\n", + " model.init_backend(model)\n", + " #model.load_model() #= MethodType(load_model, model) # Bind to the score.\n", + "\n", + " return model\n", + "HH_cond_exp = bind_NU_interface(HH_cond_exp) \n", + "#HH_cond_exp" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "electro_tests = []\n", + "obs_frame = {}\n", + "test_frame = {}\n", + "import os\n", + "import pickle\n", + "try: \n", + "\n", + " electro_path = str(os.getcwd())+'all_tests.p'\n", + "\n", + " assert os.path.isfile(electro_path) == True\n", + " with open(electro_path,'rb') as f:\n", + " (obs_frame,test_frame) = pickle.load(f)\n", + "\n", + "except:\n", + " for p in pipe:\n", + " p_tests, p_observations = get_neab.get_neuron_criteria(p)\n", + " obs_frame[p[\"name\"]] = p_observations#, p_tests))\n", + " test_frame[p[\"name\"]] = p_tests#, p_tests))\n", + " electro_path = str(os.getcwd())+'all_tests.p'\n", + " with open(electro_path,'wb') as f:\n", + " pickle.dump((obs_frame,test_frame),f)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": true + }, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'test_frame' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0muse_test\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtest_frame\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"Neocortex pyramidal cell layer 5-6\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0muse_test\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mobservation\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;31m#from neuronunit.tests import RheobaseP\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0mneuronunit\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtests\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfi\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mRheobaseTest\u001b[0m\u001b[0;31m# as discovery\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mNameError\u001b[0m: name 'test_frame' is not defined" + ] + } + ], + "source": [ + "use_test = test_frame[\"Neocortex pyramidal cell layer 5-6\"]\n", + "use_test[0].observation\n", + "#from neuronunit.tests import RheobaseP\n", + "from neuronunit.tests.fi import RheobaseTest# as discovery\n", + "\n", + "rtp = RheobaseTest(use_test[0].observation)\n", + "use_test[0] = rtp\n", + "\n", + "\n", + "HH_cond_exp.attrs = HH_cond_exp.simple_parameters(HH_cond_exp)\n", + "#print(HH_cond_exp.attrs)\n", + "HH_cond_exp.scaled_parameters(HH_cond_exp)\n", + "dir(HH_cond_exp)\n", + "HH_cond_exp.default_initial_values\n", + "HH_cond_exp.attrs\n", + "NGEN = 10\n", + "MU = 10\n", + "from neuronunit.optimization import optimization_management as om\n", + "explore_ranges = {'e_rev_Na' : (40,70), 'e_rev_K': (-90.0,-75.0), 'cm' : (0.25,1.5)}\n", + "npcl, DO = om.run_ga(explore_ranges,NGEN,use_test,free_params=explore_ranges.keys(), NSGA = True, MU = MU,model_type=None)\n", + "\n", + "#hc = " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "#dir(HH_cond_exp)\n", + "#HH_cond_exp.get_parameters()\n", + "#hhcell[0].get_parameters()\n", + "#dir(HH_cond_exp)\n", + "HH_cond_exp.attrs = HH_cond_exp.simple_parameters(HH_cond_exp)\n", + "HH_cond_exp.celltype = HH_cond_exp\n", + "iparams = {}\n", + "iparams['injected_square_current'] = {}\n", + "#iparams['injected_square_current']['amplitude'] = 1.98156805*pq.pA\n", + "iparams['injected_square_current']['amplitude'] = 0.68156805*pq.pA\n", + "\n", + "DELAY = 100.0*pq.ms\n", + "DURATION = 1000.0*pq.ms\n", + "iparams['injected_square_current']['delay'] = DELAY\n", + "iparams['injected_square_current']['duration'] = int(DURATION)\n", + "\n", + "HH_cond_exp.inject_square_current(iparams)\n", + "print(HH_cond_exp.get_spike_count())\n", + "\n", + "print(HH_cond_exp.vm)\n", + "\n", + "import matplotlib.pyplot as plt\n", + "plt.plot(HH_cond_exp.vm.times,HH_cond_exp.vm)\n", + "\n", + "\n", + "plt.show()\n", + "iparams['injected_square_current']['amplitude'] = 0.8598156805*pq.pA\n", + "#iparams['injected_square_current']['amplitude'] = 2000.98156805*pq.pA\n", + "\n", + "DELAY = 100.0*pq.ms\n", + "DURATION = 1000.0*pq.ms\n", + "iparams['injected_square_current']['delay'] = DELAY\n", + "iparams['injected_square_current']['duration'] = int(DURATION)\n", + "\n", + "HH_cond_exp.inject_square_current(iparams)\n", + "print(HH_cond_exp.get_spike_count())\n", + "import matplotlib.pyplot as plt\n", + "plt.plot(HH_cond_exp.vm.times,HH_cond_exp.vm)\n", + "\n", + "plt.show()\n", + "pred = use_test[0].generate_prediction(HH_cond_exp)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "#dir(HH_cond_exp)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "print(pred)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "iparams['injected_square_current']['amplitude'] = pred['value']\n", + "#iparams['injected_square_current']['amplitude'] = 2000.98156805*pq.pA\n", + "\n", + "DELAY = 100.0*pq.ms\n", + "DURATION = 1000.0*pq.ms\n", + "iparams['injected_square_current']['delay'] = DELAY\n", + "iparams['injected_square_current']['duration'] = int(DURATION)\n", + "\n", + "HH_cond_exp.inject_square_current(iparams)\n", + "print(HH_cond_exp.get_spike_count())\n", + "import matplotlib.pyplot as plt\n", + "plt.plot(HH_cond_exp.vm.times,HH_cond_exp.vm)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "\n", + "import pyNN\n", + "from pyNN import neuron\n", + "from pyNN.neuron import EIF_cond_exp_isfa_ista\n", + "#neurons = pyNN.Population(N_CX, pyNN.EIF_cond_exp_isfa_ista, RS_parameters)\n", + "\n", + "cell = neuron.create(EIF_cond_exp_isfa_ista())\n", + "#cell[0].set_parameters(**LTS_parameters)\n", + "cell[0].get_parameters()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "\n", + "\n", + "explore_ranges = {'E_Na' : (40,70), 'E_K': (-90.0,-75.0), 'C_m' : (0.25,1.5), 'g_K':(30,40), 'g_Na':(100,140), 'g_L':(0.1,0.5), 'E_L':(-60.0,-45)}\n", + "\n", + "attrs = { 'g_K' : 36.0, 'g_Na' : 120.0, 'g_L' : 0.3, \\\n", + " 'C_m' : 1.0, 'E_L' : -54.387, 'E_K' : -77.0, 'E_Na' : 50.0, 'vr':-65.0 } \n", + "\n", + " \n", + "from neuronunit.optimization import optimization_management as om\n", + "print(test_frame) \n", + "MU = 12\n", + "NGEN = 25\n", + "cnt = 1\n", + "#hc = { 'g_L' : 0.3, 'E_L' : -54.387,\n", + "hc = {'vr':-65.0 } \n", + "\n", + "#npcl, DO = om.run_g\n", + "npcl, DO = om.run_ga(explore_ranges,NGEN,use_test,free_params=explore_ranges.keys(), hc = hc, NSGA = True, MU = MU,model_type='HH')\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + } + ], + "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.5.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/neuronunit/examples/cache-and-hash.ipynb b/neuronunit/examples/cache-and-hash.ipynb new file mode 100644 index 000000000..9dfad349e --- /dev/null +++ b/neuronunit/examples/cache-and-hash.ipynb @@ -0,0 +1,3318 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import neuronunit.models as neuron_models\n", + "import neuronunit.tests as neuron_tests\n", + "import quantities as pq" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "test = neuron_tests.RheobaseTest(observation={\"n\": 1, \n", + " \"std\": 1 * pq.pA, \n", + " \"mean\": 1 * pq.pA})" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{}" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "getattr(test, 'observation2', {})" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'name': 'RheobaseTest',\n", + " 'params': {},\n", + " 'verbose': 1,\n", + " 'observation': {'n': 1, 'std': array(1.) * pA, 'mean': array(1.) * pA},\n", + " 'unpicklable': [],\n", + " 'required_capabilities': (neuronunit.capabilities.ReceivesSquareCurrent,\n", + " neuronunit.capabilities.ProducesSpikes,\n", + " sciunit.capabilities.Runnable,\n", + " neuronunit.capabilities.ProducesMembranePotential),\n", + " 'prediction': {},\n", + " 'high': array(300.) * pA,\n", + " 'small': array(0.) * pA,\n", + " 'rheobase_vm': None}" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "test.__dict__" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# I explicitly specify the backend as jNeuroML but this won't matter until simulations are run. \n", + "# You should write a Gepetto backend in the same form, whose _backend_run() method never needs to be implemented\n", + "# Because you will always find the result in your cache.\n", + "model = neuron_models.ReducedModel('/mnt/d/Dropbox (ASU)/dev/scidash/neuronunit/neuronunit/models/NeuroML2/LEMS_2007One.xml',\n", + " backend='jNeuroML')" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'e53c47b34249fc37dfa57bcef424e47e8ae6c3fcf7ce35ebf997eb81'" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Model hash after initialization\n", + "model.hash" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'e53c47b34249fc37dfa57bcef424e47e8ae6c3fcf7ce35ebf997eb81'" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "results = {'v': [0.1, 0.2, 0.3, 0.4, 0.5],\n", + " 't': [1, 2, 3, 4, 5]}\n", + "model.set_memory_cache(results)\n", + "# Simply updating the cache does not change the hash\n", + "model.hash" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 0 ns, sys: 0 ns, total: 0 ns\n", + "Wall time: 4.73 ms\n" + ] + } + ], + "source": [ + "# This simulation runs instantly because it just looks up from the cache that we just set\n", + "%time model.run()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'e53c47b34249fc37dfa57bcef424e47e8ae6c3fcf7ce35ebf997eb81'" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Changing random model attributes also does not change the hash\n", + "model.something = 1\n", + "model.hash" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 0 ns, sys: 0 ns, total: 0 ns\n", + "Wall time: 2.82 ms\n" + ] + } + ], + "source": [ + "# Therefore the result is looked up quickly from the cache again\n", + "%time model.run()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'name': 'LEMS_2007One',\n", + " 'attrs': {},\n", + " 'run_params': {'v': False,\n", + " 'default_java_max_memory': '400M',\n", + " 'nogui': True,\n", + " 'exit_on_fail': False},\n", + " 'backend': 'jNeuroML'}" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Only the attributes shown in this dictionary matter to the hash\n", + "model.state" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'45907ef12716b1b273e3205905f7ef0085b0042ae3fe408981c491f8'" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Changing one of these does change the hash\n", + "model.attrs['something'] = 2\n", + "model.hash" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "# Now the cache (for this hash) is empty because no results have been written since the model had this state\n", + "assert not model.get_memory_cache()" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 297 ms, sys: 62.5 ms, total: 359 ms\n", + "Wall time: 3.15 s\n" + ] + } + ], + "source": [ + "# Therefore running the model will not use the cache and will actually run it\n", + "%time model.run()" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'t': [0.0,\n", + " 2.5e-06,\n", + " 5e-06,\n", + " 7.5e-06,\n", + " 1e-05,\n", + " 1.25e-05,\n", + " 1.5e-05,\n", + " 1.75e-05,\n", + " 2e-05,\n", + " 2.25e-05,\n", + " 2.5e-05,\n", + " 2.75e-05,\n", + " 3e-05,\n", + " 3.25e-05,\n", + " 3.5e-05,\n", + " 3.75e-05,\n", + " 4e-05,\n", + " 4.25e-05,\n", + " 4.5e-05,\n", + " 4.75e-05,\n", + " 5e-05,\n", + " 5.25e-05,\n", + " 5.5e-05,\n", + " 5.75e-05,\n", + " 6e-05,\n", + " 6.25e-05,\n", + " 6.5e-05,\n", + " 6.75e-05,\n", + " 7e-05,\n", + " 7.25e-05,\n", + " 7.5e-05,\n", + " 7.75e-05,\n", + " 8e-05,\n", + " 8.25e-05,\n", + " 8.5e-05,\n", + " 8.75e-05,\n", + " 9e-05,\n", + " 9.25e-05,\n", + " 9.5e-05,\n", + " 9.75e-05,\n", + " 0.0001,\n", + " 0.0001025,\n", + " 0.000105,\n", + " 0.0001075,\n", + " 0.00011,\n", + " 0.0001125,\n", + " 0.000115,\n", + " 0.0001175,\n", + " 0.00012,\n", + " 0.0001225,\n", + " 0.000125,\n", + " 0.0001275,\n", + " 0.00013,\n", + " 0.0001325,\n", + " 0.000135,\n", + " 0.0001375,\n", + " 0.00014,\n", + " 0.0001425,\n", + " 0.000145,\n", + " 0.0001475,\n", + " 0.00015,\n", + " 0.0001525,\n", + " 0.000155,\n", + " 0.0001575,\n", + " 0.00016,\n", + " 0.0001625,\n", + " 0.000165,\n", + " 0.0001675,\n", + " 0.00017,\n", + " 0.0001725,\n", + " 0.000175,\n", + " 0.0001775,\n", + " 0.00018,\n", + " 0.0001825,\n", + " 0.000185,\n", + " 0.0001875,\n", + " 0.00019,\n", + " 0.0001925,\n", + " 0.000195,\n", + " 0.0001975,\n", + " 0.0002,\n", + " 0.0002025,\n", + " 0.000205,\n", + " 0.0002075,\n", + " 0.00021,\n", + " 0.0002125,\n", + " 0.000215,\n", + " 0.0002175,\n", + " 0.00022,\n", + " 0.0002225,\n", + " 0.000225,\n", + " 0.0002275,\n", + " 0.00023,\n", + " 0.0002325,\n", + " 0.000235,\n", + " 0.0002375,\n", + " 0.00024,\n", + " 0.0002425,\n", + " 0.000245,\n", + " 0.0002475,\n", + " 0.00025,\n", + " 0.0002525,\n", + " 0.000255,\n", + " 0.0002575,\n", + " 0.00026,\n", + " 0.0002625,\n", + " 0.000265,\n", + " 0.0002675,\n", + " 0.00027,\n", + " 0.0002725,\n", + " 0.000275,\n", + " 0.0002775,\n", + " 0.00028,\n", + " 0.0002825,\n", + " 0.000285,\n", + " 0.0002875,\n", + " 0.00029,\n", + " 0.0002925,\n", + " 0.000295,\n", + " 0.0002975,\n", + " 0.0003,\n", + " 0.0003025,\n", + " 0.000305,\n", + " 0.0003075,\n", + " 0.00031,\n", + " 0.0003125,\n", + " 0.000315,\n", + " 0.0003175,\n", + " 0.00032,\n", + " 0.0003225,\n", + " 0.000325,\n", + " 0.0003275,\n", + " 0.00033,\n", + " 0.0003325,\n", + " 0.000335,\n", + " 0.0003375,\n", + " 0.00034,\n", + " 0.0003425,\n", + " 0.000345,\n", + " 0.0003475,\n", + " 0.00035,\n", + " 0.0003525,\n", + " 0.000355,\n", + " 0.0003575,\n", + " 0.00036,\n", + " 0.0003625,\n", + " 0.000365,\n", + " 0.0003675,\n", + " 0.00037,\n", + " 0.0003725,\n", + " 0.000375,\n", + " 0.0003775,\n", + " 0.00038,\n", + " 0.0003825,\n", + " 0.000385,\n", + " 0.0003875,\n", + " 0.00039,\n", + " 0.0003925,\n", + " 0.000395,\n", + " 0.0003975,\n", + " 0.0004,\n", + " 0.0004025,\n", + " 0.000405,\n", + " 0.0004075,\n", + " 0.00041,\n", + " 0.0004125,\n", + " 0.000415,\n", + " 0.0004175,\n", + " 0.00042,\n", + " 0.0004225,\n", + " 0.000425,\n", + " 0.0004275,\n", + " 0.00043,\n", + " 0.0004325,\n", + " 0.000435,\n", + " 0.0004375,\n", + " 0.00044,\n", + " 0.0004425,\n", + " 0.000445,\n", + " 0.0004475,\n", + " 0.00045,\n", + " 0.0004525,\n", + " 0.000455,\n", + " 0.0004575,\n", + " 0.00046,\n", + " 0.0004625,\n", + " 0.000465,\n", + " 0.0004675,\n", + " 0.00047,\n", + " 0.0004725,\n", + " 0.000475,\n", + " 0.0004775,\n", + " 0.00048,\n", + " 0.0004825,\n", + " 0.000485,\n", + " 0.0004875,\n", + " 0.00049,\n", + " 0.0004925,\n", + " 0.000495,\n", + " 0.0004975,\n", + " 0.0005,\n", + " 0.0005025,\n", + " 0.000505,\n", + " 0.0005075,\n", + " 0.00051,\n", + " 0.0005125,\n", + " 0.000515,\n", + " 0.0005175,\n", + " 0.00052,\n", + " 0.0005225,\n", + " 0.000525,\n", + " 0.0005275,\n", + " 0.00053,\n", + " 0.0005325,\n", + " 0.000535,\n", + " 0.0005375,\n", + " 0.00054,\n", + " 0.0005425,\n", + " 0.000545,\n", + " 0.0005475,\n", + " 0.00055,\n", + " 0.0005525,\n", + " 0.000555,\n", + " 0.0005575,\n", + " 0.00056,\n", + " 0.0005625,\n", + " 0.000565,\n", + " 0.0005675,\n", + " 0.00057,\n", + " 0.0005725,\n", + " 0.000575,\n", + " 0.0005775,\n", + " 0.00058,\n", + " 0.0005825,\n", + " 0.000585,\n", + " 0.0005875,\n", + " 0.00059,\n", + " 0.0005925,\n", + " 0.000595,\n", + " 0.0005975,\n", + " 0.0006,\n", + " 0.0006025,\n", + " 0.000605,\n", + " 0.0006075,\n", + " 0.00061,\n", + " 0.0006125,\n", + " 0.000615,\n", + " 0.0006175,\n", + " 0.00062,\n", + " 0.0006225,\n", + " 0.000625,\n", + " 0.0006275,\n", + " 0.00063,\n", + " 0.0006325,\n", + " 0.000635,\n", + " 0.0006375,\n", + " 0.00064,\n", + " 0.0006425,\n", + " 0.000645,\n", + " 0.0006475,\n", + " 0.00065,\n", + " 0.0006525,\n", + " 0.000655,\n", + " 0.0006575,\n", + " 0.00066,\n", + " 0.0006625,\n", + " 0.000665,\n", + " 0.0006675,\n", + " 0.00067,\n", + " 0.0006725,\n", + " 0.000675,\n", + " 0.0006775,\n", + " 0.00068,\n", + " 0.0006825,\n", + " 0.000685,\n", + " 0.0006875,\n", + " 0.00069,\n", + " 0.0006925,\n", + " 0.000695,\n", + " 0.0006975,\n", + " 0.0007,\n", + " 0.0007025,\n", + " 0.000705,\n", + " 0.0007075,\n", + " 0.00071,\n", + " 0.0007125,\n", + " 0.000715,\n", + " 0.0007175,\n", + " 0.00072,\n", + " 0.0007225,\n", + " 0.000725,\n", + " 0.0007275,\n", + " 0.00073,\n", + " 0.0007325,\n", + " 0.000735,\n", + " 0.0007375,\n", + " 0.00074,\n", + " 0.0007425,\n", + " 0.000745,\n", + " 0.0007475,\n", + " 0.00075,\n", + " 0.0007525,\n", + " 0.000755,\n", + " 0.0007575,\n", + " 0.00076,\n", + " 0.0007625,\n", + " 0.000765,\n", + " 0.0007675,\n", + " 0.00077,\n", + " 0.0007725,\n", + " 0.000775,\n", + " 0.0007775,\n", + " 0.00078,\n", + " 0.0007825,\n", + " 0.000785,\n", + " 0.0007875,\n", + " 0.00079,\n", + " 0.0007925,\n", + " 0.000795,\n", + " 0.0007975,\n", + " 0.0008,\n", + " 0.0008025,\n", + " 0.000805,\n", + " 0.0008075,\n", + " 0.00081,\n", + " 0.0008125,\n", + " 0.000815,\n", + " 0.0008175,\n", + " 0.00082,\n", + " 0.0008225,\n", + " 0.000825,\n", + " 0.0008275,\n", + " 0.00083,\n", + " 0.0008325,\n", + " 0.000835,\n", + " 0.0008375,\n", + " 0.00084,\n", + " 0.0008425,\n", + " 0.000845,\n", + " 0.0008475,\n", + " 0.00085,\n", + " 0.0008525,\n", + " 0.000855,\n", + " 0.0008575,\n", + " 0.00086,\n", + " 0.0008625,\n", + " 0.000865,\n", + " 0.0008675,\n", + " 0.00087,\n", + " 0.0008725,\n", + " 0.000875,\n", + " 0.0008775,\n", + " 0.00088,\n", + " 0.0008825,\n", + " 0.000885,\n", + " 0.0008875,\n", + " 0.00089,\n", + " 0.0008925,\n", + " 0.000895,\n", + " 0.0008975,\n", + " 0.0009,\n", + " 0.0009025,\n", + " 0.000905,\n", + " 0.0009075,\n", + " 0.00091,\n", + " 0.0009125,\n", + " 0.000915,\n", + " 0.0009175,\n", + " 0.00092,\n", + " 0.0009225,\n", + " 0.000925,\n", + " 0.0009275,\n", + " 0.00093,\n", + " 0.0009325,\n", + " 0.000935,\n", + " 0.0009375,\n", + " 0.00094,\n", + " 0.0009425,\n", + " 0.000945,\n", + " 0.0009475,\n", + " 0.00095,\n", + " 0.0009525,\n", + " 0.000955,\n", + " 0.0009575,\n", + " 0.00096,\n", + " 0.0009625,\n", + " 0.000965,\n", + " 0.0009675,\n", + " 0.00097,\n", + " 0.0009725,\n", + " 0.000975,\n", + " 0.0009775,\n", + " 0.00098,\n", + " 0.0009825,\n", + " 0.000985,\n", + " 0.0009875,\n", + " 0.00099,\n", + " 0.0009925,\n", + " 0.000995,\n", + " 0.0009975,\n", + " 0.001,\n", + " 0.0010025,\n", + " 0.001005,\n", + " 0.0010075,\n", + " 0.00101,\n", + " 0.0010125,\n", + " 0.001015,\n", + " 0.0010175,\n", + " 0.00102,\n", + " 0.0010225,\n", + " 0.001025,\n", + " 0.0010275,\n", + " 0.00103,\n", + " 0.0010325,\n", + " 0.001035,\n", + " 0.0010375,\n", + " 0.00104,\n", + " 0.0010425,\n", + " 0.001045,\n", + " 0.0010475,\n", + " 0.00105,\n", + " 0.0010525,\n", + " 0.001055,\n", + " 0.0010575,\n", + " 0.00106,\n", + " 0.0010625,\n", + " 0.001065,\n", + " 0.0010675,\n", + " 0.00107,\n", + " 0.0010725,\n", + " 0.001075,\n", + " 0.0010775,\n", + " 0.00108,\n", + " 0.0010825,\n", + " 0.001085,\n", + " 0.0010875,\n", + " 0.00109,\n", + " 0.0010925,\n", + " 0.001095,\n", + " 0.0010975,\n", + " 0.0011,\n", + " 0.0011025,\n", + " 0.001105,\n", + " 0.0011075,\n", + " 0.00111,\n", + " 0.0011125,\n", + " 0.001115,\n", + " 0.0011175,\n", + " 0.00112,\n", + " 0.0011225,\n", + " 0.001125,\n", + " 0.0011275,\n", + " 0.00113,\n", + " 0.0011325,\n", + " 0.001135,\n", + " 0.0011375,\n", + " 0.00114,\n", + " 0.0011425,\n", + " 0.001145,\n", + " 0.0011475,\n", + " 0.00115,\n", + " 0.0011525,\n", + " 0.001155,\n", + " 0.0011575,\n", + " 0.00116,\n", + " 0.0011625,\n", + " 0.001165,\n", + " 0.0011675,\n", + " 0.00117,\n", + " 0.0011725,\n", + " 0.001175,\n", + " 0.0011775,\n", + " 0.00118,\n", + " 0.0011825,\n", + " 0.001185,\n", + " 0.0011875,\n", + " 0.00119,\n", + " 0.0011925,\n", + " 0.001195,\n", + " 0.0011975,\n", + " 0.0012,\n", + " 0.0012025,\n", + " 0.001205,\n", + " 0.0012075,\n", + " 0.00121,\n", + " 0.0012125,\n", + " 0.001215,\n", + " 0.0012175,\n", + " 0.00122,\n", + " 0.0012225,\n", + " 0.001225,\n", + " 0.0012275,\n", + " 0.00123,\n", + " 0.0012325,\n", + " 0.001235,\n", + " 0.0012375,\n", + " 0.00124,\n", + " 0.0012425,\n", + " 0.001245,\n", + " 0.0012475,\n", + " 0.00125,\n", + " 0.0012525,\n", + " 0.001255,\n", + " 0.0012575,\n", + " 0.00126,\n", + " 0.0012625,\n", + " 0.001265,\n", + " 0.0012675,\n", + " 0.00127,\n", + " 0.0012725,\n", + " 0.001275,\n", + " 0.0012775,\n", + " 0.00128,\n", + " 0.0012825,\n", + " 0.001285,\n", + " 0.0012875,\n", + " 0.00129,\n", + " 0.0012925,\n", + " 0.001295,\n", + " 0.0012975,\n", + " 0.0013,\n", + " 0.0013025,\n", + " 0.001305,\n", + " 0.0013075,\n", + " 0.00131,\n", + " 0.0013125,\n", + " 0.001315,\n", + " 0.0013175,\n", + " 0.00132,\n", + " 0.0013225,\n", + " 0.001325,\n", + " 0.0013275,\n", + " 0.00133,\n", + " 0.0013325,\n", + " 0.001335,\n", + " 0.0013375,\n", + " 0.00134,\n", + " 0.0013425,\n", + " 0.001345,\n", + " 0.0013475,\n", + " 0.00135,\n", + " 0.0013525,\n", + " 0.001355,\n", + " 0.0013575,\n", + " 0.00136,\n", + " 0.0013625,\n", + " 0.001365,\n", + " 0.0013675,\n", + " 0.00137,\n", + " 0.0013725,\n", + " 0.001375,\n", + " 0.0013775,\n", + " 0.00138,\n", + " 0.0013825,\n", + " 0.001385,\n", + " 0.0013875,\n", + " 0.00139,\n", + " 0.0013925,\n", + " 0.001395,\n", + " 0.0013975,\n", + " 0.0014,\n", + " 0.0014025,\n", + " 0.001405,\n", + " 0.0014075,\n", + " 0.00141,\n", + " 0.0014125,\n", + " 0.001415,\n", + " 0.0014175,\n", + " 0.00142,\n", + " 0.0014225,\n", + " 0.001425,\n", + " 0.0014275,\n", + " 0.00143,\n", + " 0.0014325,\n", + " 0.001435,\n", + " 0.0014375,\n", + " 0.00144,\n", + " 0.0014425,\n", + " 0.001445,\n", + " 0.0014475,\n", + " 0.00145,\n", + " 0.0014525,\n", + " 0.001455,\n", + " 0.0014575,\n", + " 0.00146,\n", + " 0.0014625,\n", + " 0.001465,\n", + " 0.0014675,\n", + " 0.00147,\n", + " 0.0014725,\n", + " 0.001475,\n", + " 0.0014775,\n", + " 0.00148,\n", + " 0.0014825,\n", + " 0.001485,\n", + " 0.0014875,\n", + " 0.00149,\n", + " 0.0014925,\n", + " 0.001495,\n", + " 0.0014975,\n", + " 0.0015,\n", + " 0.0015025,\n", + " 0.001505,\n", + " 0.0015075,\n", + " 0.00151,\n", + " 0.0015125,\n", + " 0.001515,\n", + " 0.0015175,\n", + " 0.00152,\n", + " 0.0015225,\n", + " 0.001525,\n", + " 0.0015275,\n", + " 0.00153,\n", + " 0.0015325,\n", + " 0.001535,\n", + " 0.0015375,\n", + " 0.00154,\n", + " 0.0015425,\n", + " 0.001545,\n", + " 0.0015475,\n", + " 0.00155,\n", + " 0.0015525,\n", + " 0.001555,\n", + " 0.0015575,\n", + " 0.00156,\n", + " 0.0015625,\n", + " 0.001565,\n", + " 0.0015675,\n", + " 0.00157,\n", + " 0.0015725,\n", + " 0.001575,\n", + " 0.0015775,\n", + " 0.00158,\n", + " 0.0015825,\n", + " 0.001585,\n", + " 0.0015875,\n", + " 0.00159,\n", + " 0.0015925,\n", + " 0.001595,\n", + " 0.0015975,\n", + " 0.0016,\n", + " 0.0016025,\n", + " 0.001605,\n", + " 0.0016075,\n", + " 0.00161,\n", + " 0.0016125,\n", + " 0.001615,\n", + " 0.0016175,\n", + " 0.00162,\n", + " 0.0016225,\n", + " 0.001625,\n", + " 0.0016275,\n", + " 0.00163,\n", + " 0.0016325,\n", + " 0.001635,\n", + " 0.0016375,\n", + " 0.00164,\n", + " 0.0016425,\n", + " 0.001645,\n", + " 0.0016475,\n", + " 0.00165,\n", + " 0.0016525,\n", + " 0.001655,\n", + " 0.0016575,\n", + " 0.00166,\n", + " 0.0016625,\n", + " 0.001665,\n", + " 0.0016675,\n", + " 0.00167,\n", + " 0.0016725,\n", + " 0.001675,\n", + " 0.0016775,\n", + " 0.00168,\n", + " 0.0016825,\n", + " 0.001685,\n", + " 0.0016875,\n", + " 0.00169,\n", + " 0.0016925,\n", + " 0.001695,\n", + " 0.0016975,\n", + " 0.0017,\n", + " 0.0017025,\n", + " 0.001705,\n", + " 0.0017075,\n", + " 0.00171,\n", + " 0.0017125,\n", + " 0.001715,\n", + " 0.0017175,\n", + " 0.00172,\n", + " 0.0017225,\n", + " 0.001725,\n", + " 0.0017275,\n", + " 0.00173,\n", + " 0.0017325,\n", + " 0.001735,\n", + " 0.0017375,\n", + " 0.00174,\n", + " 0.0017425,\n", + " 0.001745,\n", + " 0.0017475,\n", + " 0.00175,\n", + " 0.0017525,\n", + " 0.001755,\n", + " 0.0017575,\n", + " 0.00176,\n", + " 0.0017625,\n", + " 0.001765,\n", + " 0.0017675,\n", + " 0.00177,\n", + " 0.0017725,\n", + " 0.001775,\n", + " 0.0017775,\n", + " 0.00178,\n", + " 0.0017825,\n", + " 0.001785,\n", + " 0.0017875,\n", + " 0.00179,\n", + " 0.0017925,\n", + " 0.001795,\n", + " 0.0017975,\n", + " 0.0018,\n", + " 0.0018025,\n", + " 0.001805,\n", + " 0.0018075,\n", + " 0.00181,\n", + " 0.0018125,\n", + " 0.001815,\n", + " 0.0018175,\n", + " 0.00182,\n", + " 0.0018225,\n", + " 0.001825,\n", + " 0.0018275,\n", + " 0.00183,\n", + " 0.0018325,\n", + " 0.001835,\n", + " 0.0018375,\n", + " 0.00184,\n", + " 0.0018425,\n", + " 0.001845,\n", + " 0.0018475,\n", + " 0.00185,\n", + " 0.0018525,\n", + " 0.001855,\n", + " 0.0018575,\n", + " 0.00186,\n", + " 0.0018625,\n", + " 0.001865,\n", + " 0.0018675,\n", + " 0.00187,\n", + " 0.0018725,\n", + " 0.001875,\n", + " 0.0018775,\n", + " 0.00188,\n", + " 0.0018825,\n", + " 0.001885,\n", + " 0.0018875,\n", + " 0.00189,\n", + " 0.0018925,\n", + " 0.001895,\n", + " 0.0018975,\n", + " 0.0019,\n", + " 0.0019025,\n", + " 0.001905,\n", + " 0.0019075,\n", + " 0.00191,\n", + " 0.0019125,\n", + " 0.001915,\n", + " 0.0019175,\n", + " 0.00192,\n", + " 0.0019225,\n", + " 0.001925,\n", + " 0.0019275,\n", + " 0.00193,\n", + " 0.0019325,\n", + " 0.001935,\n", + " 0.0019375,\n", + " 0.00194,\n", + " 0.0019425,\n", + " 0.001945,\n", + " 0.0019475,\n", + " 0.00195,\n", + " 0.0019525,\n", + " 0.001955,\n", + " 0.0019575,\n", + " 0.00196,\n", + " 0.0019625,\n", + " 0.001965,\n", + " 0.0019675,\n", + " 0.00197,\n", + " 0.0019725,\n", + " 0.001975,\n", + " 0.0019775,\n", + " 0.00198,\n", + " 0.0019825,\n", + " 0.001985,\n", + " 0.0019875,\n", + " 0.00199,\n", + " 0.0019925,\n", + " 0.001995,\n", + " 0.0019975,\n", + " 0.002,\n", + " 0.0020025,\n", + " 0.002005,\n", + " 0.0020075,\n", + " 0.00201,\n", + " 0.0020125,\n", + " 0.002015,\n", + " 0.0020175,\n", + " 0.00202,\n", + " 0.0020225,\n", + " 0.002025,\n", + " 0.0020275,\n", + " 0.00203,\n", + " 0.0020325,\n", + " 0.002035,\n", + " 0.0020375,\n", + " 0.00204,\n", + " 0.0020425,\n", + " 0.002045,\n", + " 0.0020475,\n", + " 0.00205,\n", + " 0.0020525,\n", + " 0.002055,\n", + " 0.0020575,\n", + " 0.00206,\n", + " 0.0020625,\n", + " 0.002065,\n", + " 0.0020675,\n", + " 0.00207,\n", + " 0.0020725,\n", + " 0.002075,\n", + " 0.0020775,\n", + " 0.00208,\n", + " 0.0020825,\n", + " 0.002085,\n", + " 0.0020875,\n", + " 0.00209,\n", + " 0.0020925,\n", + " 0.002095,\n", + " 0.0020975,\n", + " 0.0021,\n", + " 0.0021025,\n", + " 0.002105,\n", + " 0.0021075,\n", + " 0.00211,\n", + " 0.0021125,\n", + " 0.002115,\n", + " 0.0021175,\n", + " 0.00212,\n", + " 0.0021225,\n", + " 0.002125,\n", + " 0.0021275,\n", + " 0.00213,\n", + " 0.0021325,\n", + " 0.002135,\n", + " 0.0021375,\n", + " 0.00214,\n", + " 0.0021425,\n", + " 0.002145,\n", + " 0.0021475,\n", + " 0.00215,\n", + " 0.0021525,\n", + " 0.002155,\n", + " 0.0021575,\n", + " 0.00216,\n", + " 0.0021625,\n", + " 0.002165,\n", + " 0.0021675,\n", + " 0.00217,\n", + " 0.0021725,\n", + " 0.002175,\n", + " 0.0021775,\n", + " 0.00218,\n", + " 0.0021825,\n", + " 0.002185,\n", + " 0.0021875,\n", + " 0.00219,\n", + " 0.0021925,\n", + " 0.002195,\n", + " 0.0021975,\n", + " 0.0022,\n", + " 0.0022025,\n", + " 0.002205,\n", + " 0.0022075,\n", + " 0.00221,\n", + " 0.0022125,\n", + " 0.002215,\n", + " 0.0022175,\n", + " 0.00222,\n", + " 0.0022225,\n", + " 0.002225,\n", + " 0.0022275,\n", + " 0.00223,\n", + " 0.0022325,\n", + " 0.002235,\n", + " 0.0022375,\n", + " 0.00224,\n", + " 0.0022425,\n", + " 0.002245,\n", + " 0.0022475,\n", + " 0.00225,\n", + " 0.0022525,\n", + " 0.002255,\n", + " 0.0022575,\n", + " 0.00226,\n", + " 0.0022625,\n", + " 0.002265,\n", + " 0.0022675,\n", + " 0.00227,\n", + " 0.0022725,\n", + " 0.002275,\n", + " 0.0022775,\n", + " 0.00228,\n", + " 0.0022825,\n", + " 0.002285,\n", + " 0.0022875,\n", + " 0.00229,\n", + " 0.0022925,\n", + " 0.002295,\n", + " 0.0022975,\n", + " 0.0023,\n", + " 0.0023025,\n", + " 0.002305,\n", + " 0.0023075,\n", + " 0.00231,\n", + " 0.0023125,\n", + " 0.002315,\n", + " 0.0023175,\n", + " 0.00232,\n", + " 0.0023225,\n", + " 0.002325,\n", + " 0.0023275,\n", + " 0.00233,\n", + " 0.0023325,\n", + " 0.002335,\n", + " 0.0023375,\n", + " 0.00234,\n", + " 0.0023425,\n", + " 0.002345,\n", + " 0.0023475,\n", + " 0.00235,\n", + " 0.0023525,\n", + " 0.002355,\n", + " 0.0023575,\n", + " 0.00236,\n", + " 0.0023625,\n", + " 0.002365,\n", + " 0.0023675,\n", + " 0.00237,\n", + " 0.0023725,\n", + " 0.002375,\n", + " 0.0023775,\n", + " 0.00238,\n", + " 0.0023825,\n", + " 0.002385,\n", + " 0.0023875,\n", + " 0.00239,\n", + " 0.0023925,\n", + " 0.002395,\n", + " 0.0023975,\n", + " 0.0024,\n", + " 0.0024025,\n", + " 0.002405,\n", + " 0.0024075,\n", + " 0.00241,\n", + " 0.0024125,\n", + " 0.002415,\n", + " 0.0024175,\n", + " 0.00242,\n", + " 0.0024225,\n", + " 0.002425,\n", + " 0.0024275,\n", + " 0.00243,\n", + " 0.0024325,\n", + " 0.002435,\n", + " 0.0024375,\n", + " 0.00244,\n", + " 0.0024425,\n", + " 0.002445,\n", + " 0.0024475,\n", + " 0.00245,\n", + " 0.0024525,\n", + " 0.002455,\n", + " 0.0024575,\n", + " 0.00246,\n", + " 0.0024625,\n", + " 0.002465,\n", + " 0.0024675,\n", + " 0.00247,\n", + " 0.0024725,\n", + " 0.002475,\n", + " 0.0024775,\n", + " 0.00248,\n", + " 0.0024825,\n", + " 0.002485,\n", + " 0.0024875,\n", + " 0.00249,\n", + " 0.0024925,\n", + " 0.002495,\n", + " 0.0024975,\n", + " ...],\n", + " 'RS_pop[0]/v': [-0.06,\n", + " -0.0599975,\n", + " -0.059995,\n", + " -0.059992503,\n", + " -0.059990004,\n", + " -0.059987508,\n", + " -0.05998501,\n", + " -0.05998252,\n", + " -0.059980024,\n", + " -0.05997753,\n", + " -0.05997504,\n", + " -0.059972547,\n", + " -0.05997006,\n", + " -0.059967566,\n", + " -0.059965078,\n", + " -0.059962593,\n", + " -0.059960105,\n", + " -0.05995762,\n", + " -0.059955135,\n", + " -0.05995265,\n", + " -0.059950165,\n", + " -0.059947684,\n", + " -0.0599452,\n", + " -0.05994272,\n", + " -0.05994024,\n", + " -0.05993776,\n", + " -0.059935283,\n", + " -0.059932806,\n", + " -0.05993033,\n", + " -0.059927855,\n", + " -0.059925377,\n", + " -0.059922904,\n", + " -0.05992043,\n", + " -0.05991796,\n", + " -0.059915487,\n", + " -0.059913017,\n", + " -0.059910547,\n", + " -0.059908077,\n", + " -0.05990561,\n", + " -0.059903145,\n", + " -0.05990068,\n", + " -0.059898213,\n", + " -0.059895746,\n", + " -0.059893284,\n", + " -0.05989082,\n", + " -0.05988836,\n", + " -0.059885897,\n", + " -0.059883438,\n", + " -0.05988098,\n", + " -0.05987852,\n", + " -0.059876062,\n", + " -0.059873603,\n", + " -0.05987115,\n", + " -0.059868693,\n", + " -0.05986624,\n", + " -0.059863787,\n", + " -0.059861332,\n", + " -0.05985888,\n", + " -0.05985643,\n", + " -0.05985398,\n", + " -0.05985153,\n", + " -0.059849083,\n", + " -0.059846636,\n", + " -0.05984419,\n", + " -0.05984174,\n", + " -0.059839297,\n", + " -0.059836853,\n", + " -0.05983441,\n", + " -0.059831966,\n", + " -0.059829526,\n", + " -0.059827086,\n", + " -0.059824646,\n", + " -0.059822205,\n", + " -0.059819765,\n", + " -0.05981733,\n", + " -0.059814893,\n", + " -0.059812456,\n", + " -0.05981002,\n", + " -0.059807587,\n", + " -0.059805155,\n", + " -0.059802722,\n", + " -0.05980029,\n", + " -0.05979786,\n", + " -0.059795428,\n", + " -0.059793,\n", + " -0.05979057,\n", + " -0.059788145,\n", + " -0.059785716,\n", + " -0.05978329,\n", + " -0.059780866,\n", + " -0.05977844,\n", + " -0.05977602,\n", + " -0.059773598,\n", + " -0.059771176,\n", + " -0.059768755,\n", + " -0.059766334,\n", + " -0.059763916,\n", + " -0.059761494,\n", + " -0.05975908,\n", + " -0.059756663,\n", + " -0.059754245,\n", + " -0.05975183,\n", + " -0.059749417,\n", + " -0.059747003,\n", + " -0.05974459,\n", + " -0.05974218,\n", + " -0.05973977,\n", + " -0.05973736,\n", + " -0.059734948,\n", + " -0.05973254,\n", + " -0.05973013,\n", + " -0.059727725,\n", + " -0.059725318,\n", + " -0.059722915,\n", + " -0.05972051,\n", + " -0.059718106,\n", + " -0.059715703,\n", + " -0.0597133,\n", + " -0.0597109,\n", + " -0.0597085,\n", + " -0.0597061,\n", + " -0.0597037,\n", + " -0.059701305,\n", + " -0.059698906,\n", + " -0.05969651,\n", + " -0.059694115,\n", + " -0.05969172,\n", + " -0.059689324,\n", + " -0.059686933,\n", + " -0.05968454,\n", + " -0.05968215,\n", + " -0.059679758,\n", + " -0.05967737,\n", + " -0.059674982,\n", + " -0.05967259,\n", + " -0.059670206,\n", + " -0.05966782,\n", + " -0.059665434,\n", + " -0.059663046,\n", + " -0.059660662,\n", + " -0.05965828,\n", + " -0.059655897,\n", + " -0.059653517,\n", + " -0.059651136,\n", + " -0.059648756,\n", + " -0.059646375,\n", + " -0.059643995,\n", + " -0.05964162,\n", + " -0.05963924,\n", + " -0.059636865,\n", + " -0.05963449,\n", + " -0.059632115,\n", + " -0.059629742,\n", + " -0.05962737,\n", + " -0.059624996,\n", + " -0.059622627,\n", + " -0.059620254,\n", + " -0.059617884,\n", + " -0.059615515,\n", + " -0.05961315,\n", + " -0.05961078,\n", + " -0.059608415,\n", + " -0.05960605,\n", + " -0.059603684,\n", + " -0.05960132,\n", + " -0.059598956,\n", + " -0.059596594,\n", + " -0.059594233,\n", + " -0.05959187,\n", + " -0.059589513,\n", + " -0.05958715,\n", + " -0.059584793,\n", + " -0.059582435,\n", + " -0.059580076,\n", + " -0.059577722,\n", + " -0.059575368,\n", + " -0.059573013,\n", + " -0.05957066,\n", + " -0.059568305,\n", + " -0.059565954,\n", + " -0.0595636,\n", + " -0.05956125,\n", + " -0.059558902,\n", + " -0.05955655,\n", + " -0.059554204,\n", + " -0.059551854,\n", + " -0.059549507,\n", + " -0.059547164,\n", + " -0.059544817,\n", + " -0.059542473,\n", + " -0.05954013,\n", + " -0.059537787,\n", + " -0.059535444,\n", + " -0.059533104,\n", + " -0.05953076,\n", + " -0.05952842,\n", + " -0.059526082,\n", + " -0.059523746,\n", + " -0.059521407,\n", + " -0.05951907,\n", + " -0.059516735,\n", + " -0.0595144,\n", + " -0.059512064,\n", + " -0.05950973,\n", + " -0.0595074,\n", + " -0.059505068,\n", + " -0.059502736,\n", + " -0.059500404,\n", + " -0.059498075,\n", + " -0.059495747,\n", + " -0.05949342,\n", + " -0.05949109,\n", + " -0.059488766,\n", + " -0.059486438,\n", + " -0.059484113,\n", + " -0.05948179,\n", + " -0.059479468,\n", + " -0.059477143,\n", + " -0.059474822,\n", + " -0.0594725,\n", + " -0.05947018,\n", + " -0.05946786,\n", + " -0.059465542,\n", + " -0.05946322,\n", + " -0.059460904,\n", + " -0.059458587,\n", + " -0.059456274,\n", + " -0.059453957,\n", + " -0.059451643,\n", + " -0.05944933,\n", + " -0.059447017,\n", + " -0.059444703,\n", + " -0.059442393,\n", + " -0.059440084,\n", + " -0.059437774,\n", + " -0.059435464,\n", + " -0.059433155,\n", + " -0.05943085,\n", + " -0.059428543,\n", + " -0.059426237,\n", + " -0.05942393,\n", + " -0.059421625,\n", + " -0.059419323,\n", + " -0.05941702,\n", + " -0.05941472,\n", + " -0.059412416,\n", + " -0.059410114,\n", + " -0.059407815,\n", + " -0.059405517,\n", + " -0.05940322,\n", + " -0.05940092,\n", + " -0.059398625,\n", + " -0.059396327,\n", + " -0.05939403,\n", + " -0.059391737,\n", + " -0.059389442,\n", + " -0.05938715,\n", + " -0.059384856,\n", + " -0.059382565,\n", + " -0.059380274,\n", + " -0.059377987,\n", + " -0.059375696,\n", + " -0.05937341,\n", + " -0.059371118,\n", + " -0.059368834,\n", + " -0.059366547,\n", + " -0.05936426,\n", + " -0.059361976,\n", + " -0.059359692,\n", + " -0.05935741,\n", + " -0.059355125,\n", + " -0.059352845,\n", + " -0.05935056,\n", + " -0.05934828,\n", + " -0.059346,\n", + " -0.05934372,\n", + " -0.059341446,\n", + " -0.059339166,\n", + " -0.05933689,\n", + " -0.059334613,\n", + " -0.05933234,\n", + " -0.059330065,\n", + " -0.059327792,\n", + " -0.05932552,\n", + " -0.059323248,\n", + " -0.059320975,\n", + " -0.059318703,\n", + " -0.059316434,\n", + " -0.059314165,\n", + " -0.059311897,\n", + " -0.059309628,\n", + " -0.059307363,\n", + " -0.059305094,\n", + " -0.05930283,\n", + " -0.059300564,\n", + " -0.0592983,\n", + " -0.059296038,\n", + " -0.059293773,\n", + " -0.05929151,\n", + " -0.05928925,\n", + " -0.05928699,\n", + " -0.05928473,\n", + " -0.05928247,\n", + " -0.059280213,\n", + " -0.059277955,\n", + " -0.059275698,\n", + " -0.059273444,\n", + " -0.059271187,\n", + " -0.059268933,\n", + " -0.05926668,\n", + " -0.059264425,\n", + " -0.059262175,\n", + " -0.05925992,\n", + " -0.05925767,\n", + " -0.05925542,\n", + " -0.05925317,\n", + " -0.05925092,\n", + " -0.059248675,\n", + " -0.05924643,\n", + " -0.05924418,\n", + " -0.059241936,\n", + " -0.05923969,\n", + " -0.059237443,\n", + " -0.0592352,\n", + " -0.059232958,\n", + " -0.059230715,\n", + " -0.059228472,\n", + " -0.059226234,\n", + " -0.05922399,\n", + " -0.059221752,\n", + " -0.059219513,\n", + " -0.059217278,\n", + " -0.05921504,\n", + " -0.059212804,\n", + " -0.05921057,\n", + " -0.059208333,\n", + " -0.0592061,\n", + " -0.059203863,\n", + " -0.05920163,\n", + " -0.0591994,\n", + " -0.05919717,\n", + " -0.059194937,\n", + " -0.059192706,\n", + " -0.05919048,\n", + " -0.059188247,\n", + " -0.05918602,\n", + " -0.05918379,\n", + " -0.059181567,\n", + " -0.05917934,\n", + " -0.059177116,\n", + " -0.05917489,\n", + " -0.059172668,\n", + " -0.059170444,\n", + " -0.059168223,\n", + " -0.059166,\n", + " -0.05916378,\n", + " -0.05916156,\n", + " -0.059159342,\n", + " -0.059157122,\n", + " -0.059154905,\n", + " -0.059152685,\n", + " -0.05915047,\n", + " -0.059148256,\n", + " -0.05914604,\n", + " -0.059143823,\n", + " -0.05914161,\n", + " -0.059139397,\n", + " -0.059137184,\n", + " -0.059134975,\n", + " -0.059132762,\n", + " -0.059130553,\n", + " -0.059128344,\n", + " -0.059126135,\n", + " -0.059123926,\n", + " -0.059121717,\n", + " -0.05911951,\n", + " -0.059117306,\n", + " -0.0591151,\n", + " -0.059112895,\n", + " -0.059110694,\n", + " -0.05910849,\n", + " -0.059106287,\n", + " -0.059104085,\n", + " -0.059101883,\n", + " -0.05909968,\n", + " -0.059097484,\n", + " -0.059095286,\n", + " -0.059093084,\n", + " -0.059090886,\n", + " -0.059088692,\n", + " -0.059086494,\n", + " -0.0590843,\n", + " -0.059082106,\n", + " -0.05907991,\n", + " -0.059077717,\n", + " -0.059075523,\n", + " -0.059073333,\n", + " -0.059071142,\n", + " -0.059068948,\n", + " -0.05906676,\n", + " -0.05906457,\n", + " -0.05906238,\n", + " -0.059060194,\n", + " -0.059058007,\n", + " -0.05905582,\n", + " -0.059053633,\n", + " -0.05905145,\n", + " -0.059049264,\n", + " -0.05904708,\n", + " -0.059044898,\n", + " -0.059042715,\n", + " -0.059040535,\n", + " -0.059038352,\n", + " -0.059036173,\n", + " -0.059033994,\n", + " -0.059031814,\n", + " -0.059029635,\n", + " -0.05902746,\n", + " -0.05902528,\n", + " -0.059023105,\n", + " -0.05902093,\n", + " -0.059018753,\n", + " -0.05901658,\n", + " -0.059014406,\n", + " -0.059012234,\n", + " -0.059010062,\n", + " -0.05900789,\n", + " -0.05900572,\n", + " -0.05900355,\n", + " -0.05900138,\n", + " -0.05899921,\n", + " -0.058997042,\n", + " -0.058994874,\n", + " -0.05899271,\n", + " -0.05899054,\n", + " -0.058988377,\n", + " -0.058986213,\n", + " -0.05898405,\n", + " -0.058981884,\n", + " -0.058979724,\n", + " -0.05897756,\n", + " -0.0589754,\n", + " -0.058973238,\n", + " -0.058971077,\n", + " -0.05896892,\n", + " -0.05896676,\n", + " -0.058964603,\n", + " -0.058962446,\n", + " -0.05896029,\n", + " -0.05895813,\n", + " -0.05895598,\n", + " -0.058953825,\n", + " -0.05895167,\n", + " -0.058949515,\n", + " -0.058947366,\n", + " -0.058945213,\n", + " -0.05894306,\n", + " -0.05894091,\n", + " -0.05893876,\n", + " -0.05893661,\n", + " -0.05893446,\n", + " -0.058932316,\n", + " -0.058930166,\n", + " -0.05892802,\n", + " -0.058925875,\n", + " -0.05892373,\n", + " -0.058921587,\n", + " -0.05891944,\n", + " -0.0589173,\n", + " -0.058915157,\n", + " -0.058913015,\n", + " -0.058910873,\n", + " -0.05890873,\n", + " -0.058906592,\n", + " -0.058904454,\n", + " -0.058902316,\n", + " -0.058900177,\n", + " -0.05889804,\n", + " -0.058895905,\n", + " -0.058893766,\n", + " -0.05889163,\n", + " -0.058889497,\n", + " -0.058887362,\n", + " -0.05888523,\n", + " -0.058883097,\n", + " -0.058880966,\n", + " -0.058878835,\n", + " -0.058876704,\n", + " -0.058874574,\n", + " -0.058872443,\n", + " -0.058870316,\n", + " -0.05886819,\n", + " -0.05886606,\n", + " -0.058863934,\n", + " -0.058861807,\n", + " -0.058859684,\n", + " -0.058857556,\n", + " -0.058855433,\n", + " -0.05885331,\n", + " -0.058851186,\n", + " -0.058849066,\n", + " -0.058846943,\n", + " -0.058844823,\n", + " -0.058842704,\n", + " -0.058840584,\n", + " -0.058838464,\n", + " -0.058836345,\n", + " -0.05883423,\n", + " -0.058832113,\n", + " -0.058829997,\n", + " -0.05882788,\n", + " -0.058825765,\n", + " -0.058823653,\n", + " -0.058821537,\n", + " -0.058819424,\n", + " -0.058817312,\n", + " -0.0588152,\n", + " -0.058813088,\n", + " -0.05881098,\n", + " -0.05880887,\n", + " -0.05880676,\n", + " -0.05880465,\n", + " -0.058802545,\n", + " -0.058800437,\n", + " -0.05879833,\n", + " -0.058796223,\n", + " -0.05879412,\n", + " -0.058792014,\n", + " -0.05878991,\n", + " -0.058787808,\n", + " -0.058785703,\n", + " -0.058783602,\n", + " -0.0587815,\n", + " -0.0587794,\n", + " -0.0587773,\n", + " -0.0587752,\n", + " -0.0587731,\n", + " -0.058771003,\n", + " -0.058768906,\n", + " -0.05876681,\n", + " -0.058764715,\n", + " -0.058762617,\n", + " -0.058760524,\n", + " -0.058758426,\n", + " -0.058756333,\n", + " -0.058754243,\n", + " -0.05875215,\n", + " -0.058750056,\n", + " -0.058747966,\n", + " -0.058745876,\n", + " -0.058743786,\n", + " -0.058741696,\n", + " -0.058739606,\n", + " -0.05873752,\n", + " -0.058735434,\n", + " -0.058733344,\n", + " -0.058731258,\n", + " -0.058729175,\n", + " -0.05872709,\n", + " -0.058725003,\n", + " -0.05872292,\n", + " -0.05872084,\n", + " -0.058718756,\n", + " -0.058716673,\n", + " -0.058714595,\n", + " -0.058712512,\n", + " -0.058710434,\n", + " -0.058708355,\n", + " -0.058706276,\n", + " -0.058704197,\n", + " -0.05870212,\n", + " -0.058700044,\n", + " -0.05869797,\n", + " -0.058695894,\n", + " -0.05869382,\n", + " -0.058691744,\n", + " -0.05868967,\n", + " -0.058687598,\n", + " -0.058685526,\n", + " -0.058683455,\n", + " -0.058681384,\n", + " -0.058679312,\n", + " -0.05867724,\n", + " -0.058675174,\n", + " -0.058673106,\n", + " -0.05867104,\n", + " -0.05866897,\n", + " -0.058666904,\n", + " -0.058664836,\n", + " -0.058662772,\n", + " -0.05866071,\n", + " -0.058658645,\n", + " -0.05865658,\n", + " -0.058654517,\n", + " -0.058652453,\n", + " -0.058650393,\n", + " -0.058648333,\n", + " -0.05864627,\n", + " -0.058644213,\n", + " -0.058642153,\n", + " -0.058640093,\n", + " -0.058638036,\n", + " -0.05863598,\n", + " -0.05863392,\n", + " -0.058631863,\n", + " -0.05862981,\n", + " -0.058627754,\n", + " -0.0586257,\n", + " -0.058623645,\n", + " -0.058621593,\n", + " -0.05861954,\n", + " -0.05861749,\n", + " -0.05861544,\n", + " -0.05861339,\n", + " -0.058611337,\n", + " -0.05860929,\n", + " -0.05860724,\n", + " -0.05860519,\n", + " -0.058603145,\n", + " -0.058601096,\n", + " -0.05859905,\n", + " -0.058597006,\n", + " -0.05859496,\n", + " -0.058592916,\n", + " -0.05859087,\n", + " -0.05858883,\n", + " -0.058586787,\n", + " -0.058584746,\n", + " -0.058582705,\n", + " -0.058580663,\n", + " -0.05857862,\n", + " -0.058576584,\n", + " -0.058574542,\n", + " -0.058572505,\n", + " -0.058570467,\n", + " -0.05856843,\n", + " -0.058566395,\n", + " -0.058564357,\n", + " -0.058562323,\n", + " -0.058560286,\n", + " -0.05855825,\n", + " -0.05855622,\n", + " -0.058554187,\n", + " -0.058552153,\n", + " -0.058550123,\n", + " -0.058548093,\n", + " -0.058546063,\n", + " -0.058544032,\n", + " -0.058542002,\n", + " -0.05853997,\n", + " -0.058537945,\n", + " -0.05853592,\n", + " -0.058533892,\n", + " -0.058531865,\n", + " -0.05852984,\n", + " -0.058527812,\n", + " -0.05852579,\n", + " -0.058523767,\n", + " -0.058521744,\n", + " -0.05851972,\n", + " -0.0585177,\n", + " -0.058515675,\n", + " -0.058513656,\n", + " -0.058511633,\n", + " -0.058509614,\n", + " -0.058507595,\n", + " -0.058505576,\n", + " -0.05850356,\n", + " -0.05850154,\n", + " -0.058499526,\n", + " -0.05849751,\n", + " -0.05849549,\n", + " -0.05849348,\n", + " -0.058491465,\n", + " -0.05848945,\n", + " -0.058487438,\n", + " -0.058485426,\n", + " -0.058483414,\n", + " -0.058481403,\n", + " -0.05847939,\n", + " -0.05847738,\n", + " -0.05847537,\n", + " -0.05847336,\n", + " -0.05847135,\n", + " -0.058469344,\n", + " -0.05846734,\n", + " -0.05846533,\n", + " -0.058463324,\n", + " -0.05846132,\n", + " -0.058459315,\n", + " -0.05845731,\n", + " -0.058455307,\n", + " -0.058453303,\n", + " -0.058451302,\n", + " -0.0584493,\n", + " -0.058447298,\n", + " -0.058445297,\n", + " -0.058443297,\n", + " -0.058441296,\n", + " -0.0584393,\n", + " -0.0584373,\n", + " -0.058435302,\n", + " -0.058433305,\n", + " -0.05843131,\n", + " -0.058429312,\n", + " -0.058427315,\n", + " -0.058425322,\n", + " -0.058423325,\n", + " -0.058421332,\n", + " -0.05841934,\n", + " -0.058417346,\n", + " -0.058415353,\n", + " -0.058413364,\n", + " -0.05841137,\n", + " -0.05840938,\n", + " -0.058407392,\n", + " -0.058405403,\n", + " -0.058403414,\n", + " -0.05840143,\n", + " -0.05839944,\n", + " -0.058397453,\n", + " -0.058395468,\n", + " -0.058393482,\n", + " -0.058391497,\n", + " -0.05838951,\n", + " -0.058387525,\n", + " -0.058385544,\n", + " -0.05838356,\n", + " -0.05838158,\n", + " -0.058379598,\n", + " -0.058377616,\n", + " -0.058375634,\n", + " -0.058373656,\n", + " -0.058371678,\n", + " -0.058369696,\n", + " -0.058367718,\n", + " -0.05836574,\n", + " -0.058363765,\n", + " -0.058361787,\n", + " -0.058359813,\n", + " -0.05835784,\n", + " -0.05835586,\n", + " -0.05835389,\n", + " -0.058351915,\n", + " -0.05834994,\n", + " -0.05834797,\n", + " -0.058345996,\n", + " -0.058344025,\n", + " -0.058342054,\n", + " -0.058340084,\n", + " -0.058338117,\n", + " -0.058336146,\n", + " -0.05833418,\n", + " -0.05833221,\n", + " -0.05833024,\n", + " -0.058328275,\n", + " -0.058326308,\n", + " -0.058324344,\n", + " -0.058322378,\n", + " -0.058320414,\n", + " -0.05831845,\n", + " -0.058316488,\n", + " -0.058314525,\n", + " -0.05831256,\n", + " -0.058310598,\n", + " -0.05830864,\n", + " -0.05830668,\n", + " -0.05830472,\n", + " -0.05830276,\n", + " -0.0583008,\n", + " -0.05829884,\n", + " -0.058296885,\n", + " -0.058294926,\n", + " -0.05829297,\n", + " -0.058291014,\n", + " -0.05828906,\n", + " -0.058287103,\n", + " -0.05828515,\n", + " -0.058283195,\n", + " -0.058281243,\n", + " -0.05827929,\n", + " -0.05827734,\n", + " -0.058275387,\n", + " -0.058273435,\n", + " -0.058271483,\n", + " -0.058269534,\n", + " -0.058267586,\n", + " -0.058265638,\n", + " -0.05826369,\n", + " -0.05826174,\n", + " -0.058259793,\n", + " -0.058257848,\n", + " -0.0582559,\n", + " -0.058253955,\n", + " -0.05825201,\n", + " -0.058250066,\n", + " -0.05824812,\n", + " -0.05824618,\n", + " -0.058244236,\n", + " -0.058242295,\n", + " -0.058240354,\n", + " -0.058238413,\n", + " -0.058236472,\n", + " -0.05823453,\n", + " -0.05823259,\n", + " -0.058230653,\n", + " -0.058228716,\n", + " -0.05822678,\n", + " -0.058224842,\n", + " -0.058222905,\n", + " -0.058220968,\n", + " -0.05821903,\n", + " -0.058217097,\n", + " -0.058215164,\n", + " -0.05821323,\n", + " -0.058211297,\n", + " -0.058209363,\n", + " -0.05820743,\n", + " -0.0582055,\n", + " -0.058203567,\n", + " -0.058201637,\n", + " -0.058199707,\n", + " -0.058197778,\n", + " -0.058195848,\n", + " -0.058193922,\n", + " -0.058191992,\n", + " -0.058190066,\n", + " -0.05818814,\n", + " -0.058186214,\n", + " -0.05818429,\n", + " -0.058182362,\n", + " -0.058180436,\n", + " -0.058178514,\n", + " -0.058176592,\n", + " -0.058174666,\n", + " -0.058172744,\n", + " -0.05817082,\n", + " -0.058168903,\n", + " -0.05816698,\n", + " -0.058165062,\n", + " -0.05816314,\n", + " -0.05816122,\n", + " -0.058159303,\n", + " -0.058157384,\n", + " -0.05815547,\n", + " -0.05815355,\n", + " -0.058151636,\n", + " -0.058149718,\n", + " -0.058147803,\n", + " -0.05814589,\n", + " -0.058143973,\n", + " -0.058142062,\n", + " -0.058140147,\n", + " -0.058138236,\n", + " -0.05813632,\n", + " -0.05813441,\n", + " -0.0581325,\n", + " -0.05813059,\n", + " -0.05812868,\n", + " -0.05812677,\n", + " -0.058124863,\n", + " -0.058122955,\n", + " -0.058121044,\n", + " -0.058119137,\n", + " -0.058117233,\n", + " -0.058115326,\n", + " -0.05811342,\n", + " -0.058111515,\n", + " -0.05810961,\n", + " -0.058107708,\n", + " -0.058105804,\n", + " -0.0581039,\n", + " -0.058101997,\n", + " -0.058100097,\n", + " -0.058098193,\n", + " -0.058096293,\n", + " -0.058094393,\n", + " -0.058092494,\n", + " -0.058090594,\n", + " -0.058088694,\n", + " -0.058086798,\n", + " -0.058084898,\n", + " -0.058083,\n", + " -0.058081105,\n", + " -0.05807921,\n", + " -0.058077313,\n", + " -0.05807542,\n", + " -0.058073524,\n", + " -0.058071632,\n", + " -0.058069736,\n", + " -0.058067843,\n", + " -0.05806595,\n", + " -0.05806406,\n", + " -0.05806217,\n", + " -0.058060277,\n", + " -0.05805839,\n", + " -0.058056496,\n", + " -0.058054607,\n", + " -0.05805272,\n", + " -0.058050834,\n", + " -0.058048945,\n", + " -0.058047056,\n", + " -0.05804517,\n", + " -0.058043286,\n", + " -0.058041397,\n", + " -0.058039512,\n", + " -0.05803763,\n", + " -0.058035746,\n", + " -0.05803386,\n", + " -0.05803198,\n", + " -0.058030095,\n", + " -0.058028214,\n", + " -0.058026332,\n", + " -0.05802445,\n", + " -0.058022574,\n", + " -0.058020692,\n", + " -0.058018815,\n", + " -0.058016934,\n", + " -0.058015056,\n", + " -0.05801318,\n", + " -0.0580113,\n", + " -0.058009423,\n", + " -0.05800755,\n", + " -0.058005672,\n", + " -0.058003798,\n", + " -0.058001924,\n", + " -0.05800005,\n", + " -0.057998177,\n", + " -0.057996303,\n", + " -0.05799443,\n", + " -0.05799256,\n", + " -0.057990685,\n", + " -0.057988815,\n", + " -0.057986945,\n", + " -0.057985075,\n", + " -0.057983205,\n", + " -0.05798134,\n", + " -0.05797947,\n", + " -0.057977602,\n", + " -0.05797573,\n", + " -0.057973865,\n", + " -0.057972,\n", + " -0.057970133,\n", + " -0.05796827,\n", + " -0.057966404,\n", + " -0.05796454,\n", + " -0.057962675,\n", + " -0.057960812,\n", + " -0.05795895,\n", + " -0.057957087,\n", + " -0.057955224,\n", + " -0.057953365,\n", + " -0.057951503,\n", + " -0.057949644,\n", + " -0.057947785,\n", + " -0.057945926,\n", + " -0.057944067,\n", + " -0.057942208,\n", + " -0.05794035,\n", + " -0.057938494,\n", + " -0.057936635,\n", + " -0.05793478,\n", + " -0.057932924,\n", + " -0.05793107,\n", + " -0.057929214,\n", + " -0.057927363,\n", + " -0.057925507,\n", + " -0.057923656,\n", + " -0.0579218,\n", + " -0.05791995,\n", + " -0.057918098,\n", + " -0.057916246,\n", + " -0.057914395,\n", + " -0.057912547,\n", + " -0.057910696,\n", + " -0.057908848,\n", + " -0.057907,\n", + " -0.057905152,\n", + " -0.057903305,\n", + " -0.057901457,\n", + " -0.05789961,\n", + " -0.057897765,\n", + " -0.057895917,\n", + " -0.057894073,\n", + " -0.05789223,\n", + " -0.057890385,\n", + " -0.05788854,\n", + " -0.057886697,\n", + " -0.057884857,\n", + " -0.057883013,\n", + " -0.057881173,\n", + " -0.057879332,\n", + " -0.057877492,\n", + " -0.057875652,\n", + " -0.05787381,\n", + " -0.05787197,\n", + " -0.057870135,\n", + " -0.057868294,\n", + " -0.057866458,\n", + " -0.05786462,\n", + " ...],\n", + " 'RS_pop[0]/u': [0.0,\n", + " 0.0,\n", + " -3.75e-19,\n", + " -1.1248406e-18,\n", + " -2.2493626e-18,\n", + " -3.748407e-18,\n", + " -5.6218143e-18,\n", + " -7.869426e-18,\n", + " -1.0491082e-17,\n", + " -1.3486626e-17,\n", + " -1.6855896e-17,\n", + " -2.0598736e-17,\n", + " -2.4714987e-17,\n", + " -2.9204488e-17,\n", + " -3.4067086e-17,\n", + " -3.9302617e-17,\n", + " -4.4910928e-17,\n", + " -5.0891854e-17,\n", + " -5.7245246e-17,\n", + " -6.397094e-17,\n", + " -7.106878e-17,\n", + " -7.85386e-17,\n", + " -8.638026e-17,\n", + " -9.459359e-17,\n", + " -1.0317843e-16,\n", + " -1.1213464e-16,\n", + " -1.2146204e-16,\n", + " -1.3116048e-16,\n", + " -1.4122981e-16,\n", + " -1.5166988e-16,\n", + " -1.6248052e-16,\n", + " -1.7366157e-16,\n", + " -1.8521287e-16,\n", + " -1.9713429e-16,\n", + " -2.0942565e-16,\n", + " -2.220868e-16,\n", + " -2.3511758e-16,\n", + " -2.4851783e-16,\n", + " -2.622874e-16,\n", + " -2.7642616e-16,\n", + " -2.909339e-16,\n", + " -3.058105e-16,\n", + " -3.2105582e-16,\n", + " -3.3666966e-16,\n", + " -3.5265187e-16,\n", + " -3.6900233e-16,\n", + " -3.8572086e-16,\n", + " -4.028073e-16,\n", + " -4.2026154e-16,\n", + " -4.3808335e-16,\n", + " -4.562726e-16,\n", + " -4.748292e-16,\n", + " -4.937529e-16,\n", + " -5.130436e-16,\n", + " -5.327012e-16,\n", + " -5.5272537e-16,\n", + " -5.7311615e-16,\n", + " -5.938733e-16,\n", + " -6.149966e-16,\n", + " -6.3648597e-16,\n", + " -6.5834127e-16,\n", + " -6.8056233e-16,\n", + " -7.03149e-16,\n", + " -7.2610105e-16,\n", + " -7.4941844e-16,\n", + " -7.73101e-16,\n", + " -7.971485e-16,\n", + " -8.215608e-16,\n", + " -8.4633785e-16,\n", + " -8.714794e-16,\n", + " -8.969853e-16,\n", + " -9.228554e-16,\n", + " -9.490896e-16,\n", + " -9.756877e-16,\n", + " -1.0026495e-15,\n", + " -1.029975e-15,\n", + " -1.057664e-15,\n", + " -1.0857161e-15,\n", + " -1.1141316e-15,\n", + " -1.1429099e-15,\n", + " -1.172051e-15,\n", + " -1.201555e-15,\n", + " -1.2314214e-15,\n", + " -1.2616502e-15,\n", + " -1.2922412e-15,\n", + " -1.3231943e-15,\n", + " -1.3545093e-15,\n", + " -1.3861861e-15,\n", + " -1.4182246e-15,\n", + " -1.4506246e-15,\n", + " -1.4833858e-15,\n", + " -1.5165082e-15,\n", + " -1.5499916e-15,\n", + " -1.5838359e-15,\n", + " -1.618041e-15,\n", + " -1.6526065e-15,\n", + " -1.6875325e-15,\n", + " -1.7228187e-15,\n", + " -1.7584652e-15,\n", + " -1.7944715e-15,\n", + " -1.8308376e-15,\n", + " -1.8675634e-15,\n", + " -1.9046487e-15,\n", + " -1.9420934e-15,\n", + " -1.9798972e-15,\n", + " -2.0180602e-15,\n", + " -2.056582e-15,\n", + " -2.0954625e-15,\n", + " -2.1347018e-15,\n", + " -2.1742995e-15,\n", + " -2.2142553e-15,\n", + " -2.2545696e-15,\n", + " -2.2952416e-15,\n", + " -2.3362717e-15,\n", + " -2.3776594e-15,\n", + " -2.4194046e-15,\n", + " -2.4615072e-15,\n", + " -2.5039672e-15,\n", + " -2.5467841e-15,\n", + " -2.5899582e-15,\n", + " -2.633489e-15,\n", + " -2.6773765e-15,\n", + " -2.7216204e-15,\n", + " -2.766221e-15,\n", + " -2.8111774e-15,\n", + " -2.85649e-15,\n", + " -2.9021587e-15,\n", + " -2.948183e-15,\n", + " -2.994563e-15,\n", + " -3.0412985e-15,\n", + " -3.088389e-15,\n", + " -3.1358352e-15,\n", + " -3.183636e-15,\n", + " -3.231792e-15,\n", + " -3.2803026e-15,\n", + " -3.3291677e-15,\n", + " -3.3783873e-15,\n", + " -3.4279612e-15,\n", + " -3.4778893e-15,\n", + " -3.5281713e-15,\n", + " -3.5788074e-15,\n", + " -3.629797e-15,\n", + " -3.68114e-15,\n", + " -3.7328365e-15,\n", + " -3.7848863e-15,\n", + " -3.8372892e-15,\n", + " -3.8900454e-15,\n", + " -3.943154e-15,\n", + " -3.9966153e-15,\n", + " -4.0504294e-15,\n", + " -4.104596e-15,\n", + " -4.1591143e-15,\n", + " -4.213985e-15,\n", + " -4.2692074e-15,\n", + " -4.3247817e-15,\n", + " -4.380708e-15,\n", + " -4.4369856e-15,\n", + " -4.4936144e-15,\n", + " -4.5505943e-15,\n", + " -4.6079257e-15,\n", + " -4.665608e-15,\n", + " -4.7236406e-15,\n", + " -4.782024e-15,\n", + " -4.840758e-15,\n", + " -4.899842e-15,\n", + " -4.9592767e-15,\n", + " -5.0190615e-15,\n", + " -5.079196e-15,\n", + " -5.13968e-15,\n", + " -5.2005138e-15,\n", + " -5.261697e-15,\n", + " -5.32323e-15,\n", + " -5.3851115e-15,\n", + " -5.447342e-15,\n", + " -5.5099222e-15,\n", + " -5.5728504e-15,\n", + " -5.6361276e-15,\n", + " -5.699753e-15,\n", + " -5.7637267e-15,\n", + " -5.8280487e-15,\n", + " -5.892719e-15,\n", + " -5.9577367e-15,\n", + " -6.023102e-15,\n", + " -6.0888154e-15,\n", + " -6.1548764e-15,\n", + " -6.221284e-15,\n", + " -6.2880393e-15,\n", + " -6.3551412e-15,\n", + " -6.4225905e-15,\n", + " -6.490386e-15,\n", + " -6.5585287e-15,\n", + " -6.6270172e-15,\n", + " -6.695852e-15,\n", + " -6.7650336e-15,\n", + " -6.834561e-15,\n", + " -6.904434e-15,\n", + " -6.9746527e-15,\n", + " -7.0452173e-15,\n", + " -7.116127e-15,\n", + " -7.187382e-15,\n", + " -7.2589826e-15,\n", + " -7.330928e-15,\n", + " -7.403218e-15,\n", + " -7.4758535e-15,\n", + " -7.548833e-15,\n", + " -7.622156e-15,\n", + " -7.6958246e-15,\n", + " -7.7698375e-15,\n", + " -7.844193e-15,\n", + " -7.918894e-15,\n", + " -7.993938e-15,\n", + " -8.069326e-15,\n", + " -8.145057e-15,\n", + " -8.2211315e-15,\n", + " -8.297549e-15,\n", + " -8.37431e-15,\n", + " -8.4514135e-15,\n", + " -8.5288594e-15,\n", + " -8.606648e-15,\n", + " -8.68478e-15,\n", + " -8.763254e-15,\n", + " -8.842069e-15,\n", + " -8.921227e-15,\n", + " -9.000727e-15,\n", + " -9.0805684e-15,\n", + " -9.160752e-15,\n", + " -9.241277e-15,\n", + " -9.322142e-15,\n", + " -9.40335e-15,\n", + " -9.484898e-15,\n", + " -9.566787e-15,\n", + " -9.649017e-15,\n", + " -9.731588e-15,\n", + " -9.814499e-15,\n", + " -9.89775e-15,\n", + " -9.981342e-15,\n", + " -1.0065274e-14,\n", + " -1.0149545e-14,\n", + " -1.0234157e-14,\n", + " -1.03191085e-14,\n", + " -1.0404399e-14,\n", + " -1.0490029e-14,\n", + " -1.0575998e-14,\n", + " -1.0662307e-14,\n", + " -1.0748954e-14,\n", + " -1.083594e-14,\n", + " -1.0923265e-14,\n", + " -1.10109286e-14,\n", + " -1.109893e-14,\n", + " -1.1187271e-14,\n", + " -1.1275949e-14,\n", + " -1.13649655e-14,\n", + " -1.145432e-14,\n", + " -1.1544011e-14,\n", + " -1.1634041e-14,\n", + " -1.1724407e-14,\n", + " -1.1815112e-14,\n", + " -1.19061535e-14,\n", + " -1.1997531e-14,\n", + " -1.2089246e-14,\n", + " -1.21812986e-14,\n", + " -1.2273687e-14,\n", + " -1.23664125e-14,\n", + " -1.24594735e-14,\n", + " -1.2552872e-14,\n", + " -1.2646605e-14,\n", + " -1.2740675e-14,\n", + " -1.283508e-14,\n", + " -1.29298216e-14,\n", + " -1.3024898e-14,\n", + " -1.312031e-14,\n", + " -1.3216057e-14,\n", + " -1.3312139e-14,\n", + " -1.3408557e-14,\n", + " -1.35053085e-14,\n", + " -1.3602395e-14,\n", + " -1.3699817e-14,\n", + " -1.3797573e-14,\n", + " -1.3895663e-14,\n", + " -1.3994087e-14,\n", + " -1.4092845e-14,\n", + " -1.4191937e-14,\n", + " -1.4291364e-14,\n", + " -1.4391122e-14,\n", + " -1.4491216e-14,\n", + " -1.4591642e-14,\n", + " -1.4692401e-14,\n", + " -1.4793493e-14,\n", + " -1.4894919e-14,\n", + " -1.4996678e-14,\n", + " -1.5098769e-14,\n", + " -1.5201192e-14,\n", + " -1.5303947e-14,\n", + " -1.5407035e-14,\n", + " -1.5510456e-14,\n", + " -1.5614207e-14,\n", + " -1.571829e-14,\n", + " -1.5822708e-14,\n", + " -1.5927453e-14,\n", + " -1.6032533e-14,\n", + " -1.6137943e-14,\n", + " -1.6243683e-14,\n", + " -1.6349755e-14,\n", + " -1.645616e-14,\n", + " -1.6562892e-14,\n", + " -1.6669957e-14,\n", + " -1.6777353e-14,\n", + " -1.6885077e-14,\n", + " -1.6993133e-14,\n", + " -1.7101519e-14,\n", + " -1.7210234e-14,\n", + " -1.731928e-14,\n", + " -1.7428655e-14,\n", + " -1.753836e-14,\n", + " -1.7648392e-14,\n", + " -1.7758756e-14,\n", + " -1.786945e-14,\n", + " -1.798047e-14,\n", + " -1.809182e-14,\n", + " -1.82035e-14,\n", + " -1.8315507e-14,\n", + " -1.8427844e-14,\n", + " -1.8540508e-14,\n", + " -1.8653502e-14,\n", + " -1.8766821e-14,\n", + " -1.8880471e-14,\n", + " -1.8994448e-14,\n", + " -1.9108752e-14,\n", + " -1.9223384e-14,\n", + " -1.9338343e-14,\n", + " -1.945363e-14,\n", + " -1.9569244e-14,\n", + " -1.9685185e-14,\n", + " -1.9801452e-14,\n", + " -1.9918048e-14,\n", + " -2.0034969e-14,\n", + " -2.0152215e-14,\n", + " -2.026979e-14,\n", + " -2.038769e-14,\n", + " -2.0505915e-14,\n", + " -2.0624468e-14,\n", + " -2.0743346e-14,\n", + " -2.086255e-14,\n", + " -2.098208e-14,\n", + " -2.1101934e-14,\n", + " -2.1222114e-14,\n", + " -2.134262e-14,\n", + " -2.146345e-14,\n", + " -2.1584605e-14,\n", + " -2.1706085e-14,\n", + " -2.182789e-14,\n", + " -2.1950019e-14,\n", + " -2.2072472e-14,\n", + " -2.2195252e-14,\n", + " -2.2318353e-14,\n", + " -2.2441779e-14,\n", + " -2.2565529e-14,\n", + " -2.2689602e-14,\n", + " -2.2813999e-14,\n", + " -2.293872e-14,\n", + " -2.3063764e-14,\n", + " -2.3189131e-14,\n", + " -2.3314822e-14,\n", + " -2.3440836e-14,\n", + " -2.356717e-14,\n", + " -2.369383e-14,\n", + " -2.3820811e-14,\n", + " -2.3948115e-14,\n", + " -2.4075741e-14,\n", + " -2.420369e-14,\n", + " -2.433196e-14,\n", + " -2.4460551e-14,\n", + " -2.4589466e-14,\n", + " -2.4718702e-14,\n", + " -2.4848259e-14,\n", + " -2.4978138e-14,\n", + " -2.5108337e-14,\n", + " -2.5238858e-14,\n", + " -2.53697e-14,\n", + " -2.5500864e-14,\n", + " -2.5632347e-14,\n", + " -2.5764152e-14,\n", + " -2.5896276e-14,\n", + " -2.6028721e-14,\n", + " -2.6161487e-14,\n", + " -2.6294573e-14,\n", + " -2.6427977e-14,\n", + " -2.6561703e-14,\n", + " -2.6695748e-14,\n", + " -2.6830112e-14,\n", + " -2.6964796e-14,\n", + " -2.70998e-14,\n", + " -2.7235123e-14,\n", + " -2.7370765e-14,\n", + " -2.7506726e-14,\n", + " -2.7643005e-14,\n", + " -2.7779603e-14,\n", + " -2.7916519e-14,\n", + " -2.8053755e-14,\n", + " -2.8191308e-14,\n", + " -2.832918e-14,\n", + " -2.8467368e-14,\n", + " -2.8605878e-14,\n", + " -2.8744703e-14,\n", + " -2.8883847e-14,\n", + " -2.9023306e-14,\n", + " -2.9163083e-14,\n", + " -2.930318e-14,\n", + " -2.9443594e-14,\n", + " -2.9584323e-14,\n", + " -2.9725368e-14,\n", + " -2.986673e-14,\n", + " -3.0008412e-14,\n", + " -3.015041e-14,\n", + " -3.029272e-14,\n", + " -3.043535e-14,\n", + " -3.0578296e-14,\n", + " -3.0721556e-14,\n", + " -3.0865135e-14,\n", + " -3.1009026e-14,\n", + " -3.1153235e-14,\n", + " -3.129776e-14,\n", + " -3.1442598e-14,\n", + " -3.1587756e-14,\n", + " -3.1733225e-14,\n", + " -3.187901e-14,\n", + " -3.202511e-14,\n", + " -3.2171524e-14,\n", + " -3.2318254e-14,\n", + " -3.2465296e-14,\n", + " -3.2612656e-14,\n", + " -3.2760327e-14,\n", + " -3.2908314e-14,\n", + " -3.3056616e-14,\n", + " -3.320523e-14,\n", + " -3.335416e-14,\n", + " -3.35034e-14,\n", + " -3.3652954e-14,\n", + " -3.380282e-14,\n", + " -3.3953004e-14,\n", + " -3.41035e-14,\n", + " -3.4254307e-14,\n", + " -3.4405428e-14,\n", + " -3.4556864e-14,\n", + " -3.4708608e-14,\n", + " -3.4860668e-14,\n", + " -3.501304e-14,\n", + " -3.5165725e-14,\n", + " -3.531872e-14,\n", + " -3.5472025e-14,\n", + " -3.5625647e-14,\n", + " -3.5779576e-14,\n", + " -3.593382e-14,\n", + " -3.6088374e-14,\n", + " -3.6243242e-14,\n", + " -3.639842e-14,\n", + " -3.6553907e-14,\n", + " -3.6709707e-14,\n", + " -3.6865815e-14,\n", + " -3.7022238e-14,\n", + " -3.717897e-14,\n", + " -3.733601e-14,\n", + " -3.7493365e-14,\n", + " -3.7651028e-14,\n", + " -3.7809e-14,\n", + " -3.7967283e-14,\n", + " -3.8125875e-14,\n", + " -3.8284778e-14,\n", + " -3.844399e-14,\n", + " -3.8603513e-14,\n", + " -3.8763345e-14,\n", + " -3.8923485e-14,\n", + " -3.9083933e-14,\n", + " -3.9244693e-14,\n", + " -3.940576e-14,\n", + " -3.956714e-14,\n", + " -3.9728823e-14,\n", + " -3.9890817e-14,\n", + " -4.005312e-14,\n", + " -4.021573e-14,\n", + " -4.0378647e-14,\n", + " -4.0541873e-14,\n", + " -4.070541e-14,\n", + " -4.086925e-14,\n", + " -4.1033403e-14,\n", + " -4.119786e-14,\n", + " -4.1362625e-14,\n", + " -4.1527698e-14,\n", + " -4.169308e-14,\n", + " -4.1858766e-14,\n", + " -4.202476e-14,\n", + " -4.219106e-14,\n", + " -4.2357668e-14,\n", + " -4.252458e-14,\n", + " -4.2691802e-14,\n", + " -4.285933e-14,\n", + " -4.3027163e-14,\n", + " -4.3195302e-14,\n", + " -4.3363747e-14,\n", + " -4.35325e-14,\n", + " -4.3701554e-14,\n", + " -4.3870916e-14,\n", + " -4.4040587e-14,\n", + " -4.421056e-14,\n", + " -4.4380837e-14,\n", + " -4.4551422e-14,\n", + " -4.472231e-14,\n", + " -4.4893505e-14,\n", + " -4.5065002e-14,\n", + " -4.5236808e-14,\n", + " -4.5408915e-14,\n", + " -4.5581326e-14,\n", + " -4.5754043e-14,\n", + " -4.5927065e-14,\n", + " -4.6100388e-14,\n", + " -4.627402e-14,\n", + " -4.644795e-14,\n", + " -4.6622188e-14,\n", + " -4.6796727e-14,\n", + " -4.6971572e-14,\n", + " -4.7146718e-14,\n", + " -4.732217e-14,\n", + " -4.749792e-14,\n", + " -4.7673975e-14,\n", + " -4.7850335e-14,\n", + " -4.8027e-14,\n", + " -4.820396e-14,\n", + " -4.8381228e-14,\n", + " -4.8558796e-14,\n", + " -4.873667e-14,\n", + " -4.891484e-14,\n", + " -4.9093318e-14,\n", + " -4.9272096e-14,\n", + " -4.9451176e-14,\n", + " -4.9630557e-14,\n", + " -4.981024e-14,\n", + " -4.9990224e-14,\n", + " -5.017051e-14,\n", + " -5.0351094e-14,\n", + " -5.0531983e-14,\n", + " -5.0713174e-14,\n", + " -5.0894662e-14,\n", + " -5.1076453e-14,\n", + " -5.1258544e-14,\n", + " -5.1440934e-14,\n", + " -5.1623626e-14,\n", + " -5.180662e-14,\n", + " -5.198991e-14,\n", + " -5.2173503e-14,\n", + " -5.2357393e-14,\n", + " -5.2541586e-14,\n", + " -5.272608e-14,\n", + " -5.291087e-14,\n", + " -5.309596e-14,\n", + " -5.328135e-14,\n", + " -5.3467037e-14,\n", + " -5.3653025e-14,\n", + " -5.3839315e-14,\n", + " -5.40259e-14,\n", + " -5.4212785e-14,\n", + " -5.439997e-14,\n", + " -5.4587448e-14,\n", + " -5.477523e-14,\n", + " -5.4963307e-14,\n", + " -5.5151684e-14,\n", + " -5.534036e-14,\n", + " -5.5529332e-14,\n", + " -5.5718603e-14,\n", + " -5.590817e-14,\n", + " -5.6098037e-14,\n", + " -5.62882e-14,\n", + " -5.647866e-14,\n", + " -5.6669418e-14,\n", + " -5.686047e-14,\n", + " -5.705182e-14,\n", + " -5.724347e-14,\n", + " -5.7435414e-14,\n", + " -5.7627656e-14,\n", + " -5.78202e-14,\n", + " -5.801303e-14,\n", + " -5.820616e-14,\n", + " -5.839959e-14,\n", + " -5.8593314e-14,\n", + " -5.878733e-14,\n", + " -5.898165e-14,\n", + " -5.9176256e-14,\n", + " -5.937116e-14,\n", + " -5.9566366e-14,\n", + " -5.976186e-14,\n", + " -5.9957654e-14,\n", + " -6.0153746e-14,\n", + " -6.035012e-14,\n", + " -6.05468e-14,\n", + " -6.0743775e-14,\n", + " -6.094104e-14,\n", + " -6.11386e-14,\n", + " -6.133646e-14,\n", + " -6.1534606e-14,\n", + " -6.1733055e-14,\n", + " -6.193179e-14,\n", + " -6.213082e-14,\n", + " -6.2330146e-14,\n", + " -6.252977e-14,\n", + " -6.272968e-14,\n", + " -6.292989e-14,\n", + " -6.313039e-14,\n", + " -6.333118e-14,\n", + " -6.353227e-14,\n", + " -6.373365e-14,\n", + " -6.393532e-14,\n", + " -6.413729e-14,\n", + " -6.4339545e-14,\n", + " -6.45421e-14,\n", + " -6.474494e-14,\n", + " -6.494808e-14,\n", + " -6.5151504e-14,\n", + " -6.5355226e-14,\n", + " -6.555924e-14,\n", + " -6.576354e-14,\n", + " -6.596814e-14,\n", + " -6.6173026e-14,\n", + " -6.6378204e-14,\n", + " -6.658368e-14,\n", + " -6.678944e-14,\n", + " -6.6995495e-14,\n", + " -6.720184e-14,\n", + " -6.7408474e-14,\n", + " -6.76154e-14,\n", + " -6.782262e-14,\n", + " -6.803012e-14,\n", + " -6.823792e-14,\n", + " -6.8446015e-14,\n", + " -6.865439e-14,\n", + " -6.886306e-14,\n", + " -6.907202e-14,\n", + " -6.928127e-14,\n", + " -6.9490807e-14,\n", + " -6.970064e-14,\n", + " -6.991076e-14,\n", + " -7.012117e-14,\n", + " -7.033186e-14,\n", + " -7.054285e-14,\n", + " -7.0754126e-14,\n", + " -7.0965694e-14,\n", + " -7.117755e-14,\n", + " -7.138969e-14,\n", + " -7.160212e-14,\n", + " -7.181484e-14,\n", + " -7.202785e-14,\n", + " -7.224115e-14,\n", + " -7.245474e-14,\n", + " -7.266861e-14,\n", + " -7.2882774e-14,\n", + " -7.309722e-14,\n", + " -7.331196e-14,\n", + " -7.3526986e-14,\n", + " -7.3742295e-14,\n", + " -7.39579e-14,\n", + " -7.417379e-14,\n", + " -7.4389964e-14,\n", + " -7.4606425e-14,\n", + " -7.482318e-14,\n", + " -7.5040214e-14,\n", + " -7.5257536e-14,\n", + " -7.547515e-14,\n", + " -7.5693046e-14,\n", + " -7.591123e-14,\n", + " -7.6129695e-14,\n", + " -7.634845e-14,\n", + " -7.6567496e-14,\n", + " -7.678682e-14,\n", + " -7.700644e-14,\n", + " -7.722634e-14,\n", + " -7.7446526e-14,\n", + " -7.7667e-14,\n", + " -7.7887756e-14,\n", + " -7.81088e-14,\n", + " -7.8330124e-14,\n", + " -7.855174e-14,\n", + " -7.877364e-14,\n", + " -7.8995824e-14,\n", + " -7.921829e-14,\n", + " -7.9441045e-14,\n", + " -7.966408e-14,\n", + " -7.9887404e-14,\n", + " -8.011101e-14,\n", + " -8.03349e-14,\n", + " -8.0559074e-14,\n", + " -8.078354e-14,\n", + " -8.100828e-14,\n", + " -8.1233306e-14,\n", + " -8.1458616e-14,\n", + " -8.168421e-14,\n", + " -8.191009e-14,\n", + " -8.213625e-14,\n", + " -8.23627e-14,\n", + " -8.2589426e-14,\n", + " -8.281644e-14,\n", + " -8.3043734e-14,\n", + " -8.327131e-14,\n", + " -8.3499166e-14,\n", + " -8.372731e-14,\n", + " -8.395574e-14,\n", + " -8.418444e-14,\n", + " -8.441343e-14,\n", + " -8.464271e-14,\n", + " -8.487226e-14,\n", + " -8.51021e-14,\n", + " -8.533221e-14,\n", + " -8.556261e-14,\n", + " -8.57933e-14,\n", + " -8.602426e-14,\n", + " -8.6255506e-14,\n", + " -8.648703e-14,\n", + " -8.671884e-14,\n", + " -8.695093e-14,\n", + " -8.71833e-14,\n", + " -8.741595e-14,\n", + " -8.764888e-14,\n", + " -8.7882094e-14,\n", + " -8.8115584e-14,\n", + " -8.834936e-14,\n", + " -8.858341e-14,\n", + " -8.881775e-14,\n", + " -8.905236e-14,\n", + " -8.9287254e-14,\n", + " -8.952243e-14,\n", + " -8.9757886e-14,\n", + " -8.999362e-14,\n", + " -9.022963e-14,\n", + " -9.0465924e-14,\n", + " -9.07025e-14,\n", + " -9.093935e-14,\n", + " -9.117648e-14,\n", + " -9.141389e-14,\n", + " -9.165158e-14,\n", + " -9.188955e-14,\n", + " -9.2127796e-14,\n", + " -9.236632e-14,\n", + " -9.260512e-14,\n", + " -9.284421e-14,\n", + " -9.308357e-14,\n", + " -9.332321e-14,\n", + " -9.3563124e-14,\n", + " -9.380332e-14,\n", + " -9.40438e-14,\n", + " -9.4284545e-14,\n", + " -9.452558e-14,\n", + " -9.476688e-14,\n", + " -9.500847e-14,\n", + " -9.525033e-14,\n", + " -9.549247e-14,\n", + " -9.573488e-14,\n", + " -9.5977574e-14,\n", + " -9.622055e-14,\n", + " -9.646379e-14,\n", + " -9.670732e-14,\n", + " -9.695112e-14,\n", + " -9.7195194e-14,\n", + " -9.7439546e-14,\n", + " -9.768418e-14,\n", + " -9.7929084e-14,\n", + " -9.817427e-14,\n", + " -9.8419726e-14,\n", + " -9.866546e-14,\n", + " -9.891147e-14,\n", + " -9.9157756e-14,\n", + " -9.940432e-14,\n", + " -9.9651156e-14,\n", + " -9.9898266e-14,\n", + " -1.00145654e-13,\n", + " -1.0039332e-13,\n", + " -1.00641256e-13,\n", + " -1.0088947e-13,\n", + " -1.01137957e-13,\n", + " -1.0138672e-13,\n", + " -1.01635754e-13,\n", + " -1.0188507e-13,\n", + " -1.0213466e-13,\n", + " -1.0238452e-13,\n", + " -1.0263465e-13,\n", + " -1.0288506e-13,\n", + " -1.0313574e-13,\n", + " -1.033867e-13,\n", + " -1.0363793e-13,\n", + " -1.0388943e-13,\n", + " -1.0414121e-13,\n", + " -1.0439327e-13,\n", + " -1.0464559e-13,\n", + " -1.04898186e-13,\n", + " -1.05151056e-13,\n", + " -1.05404204e-13,\n", + " -1.0565762e-13,\n", + " -1.0591131e-13,\n", + " -1.0616527e-13,\n", + " -1.0641951e-13,\n", + " -1.0667402e-13,\n", + " -1.069288e-13,\n", + " -1.07183854e-13,\n", + " -1.0743918e-13,\n", + " -1.0769478e-13,\n", + " -1.0795065e-13,\n", + " -1.0820679e-13,\n", + " -1.08463206e-13,\n", + " -1.0871989e-13,\n", + " -1.0897685e-13,\n", + " -1.09234074e-13,\n", + " -1.0949158e-13,\n", + " -1.0974935e-13,\n", + " -1.1000739e-13,\n", + " -1.10265706e-13,\n", + " -1.1052429e-13,\n", + " -1.1078315e-13,\n", + " -1.1104227e-13,\n", + " -1.1130167e-13,\n", + " -1.11561334e-13,\n", + " -1.1182128e-13,\n", + " -1.1208149e-13,\n", + " -1.12341966e-13,\n", + " -1.1260271e-13,\n", + " -1.1286373e-13,\n", + " -1.13125024e-13,\n", + " -1.1338658e-13,\n", + " -1.1364841e-13,\n", + " -1.1391051e-13,\n", + " -1.1417287e-13,\n", + " -1.1443552e-13,\n", + " -1.1469843e-13,\n", + " -1.149616e-13,\n", + " -1.1522505e-13,\n", + " -1.1548877e-13,\n", + " -1.1575274e-13,\n", + " -1.16017e-13,\n", + " -1.1628152e-13,\n", + " -1.1654631e-13,\n", + " -1.1681137e-13,\n", + " -1.1707669e-13,\n", + " -1.1734229e-13,\n", + " -1.1760815e-13,\n", + " -1.1787428e-13,\n", + " -1.1814069e-13,\n", + " -1.1840736e-13,\n", + " -1.1867429e-13,\n", + " -1.1894149e-13,\n", + " -1.1920896e-13,\n", + " -1.1947671e-13,\n", + " -1.1974471e-13,\n", + " -1.20013e-13,\n", + " -1.2028152e-13,\n", + " -1.2055034e-13,\n", + " -1.2081941e-13,\n", + " -1.2108875e-13,\n", + " -1.2135837e-13,\n", + " -1.2162824e-13,\n", + " -1.2189838e-13,\n", + " -1.221688e-13,\n", + " -1.2243947e-13,\n", + " -1.2271041e-13,\n", + " -1.2298162e-13,\n", + " -1.2325309e-13,\n", + " -1.2352483e-13,\n", + " -1.2379683e-13,\n", + " -1.2406912e-13,\n", + " -1.2434164e-13,\n", + " -1.2461446e-13,\n", + " -1.2488753e-13,\n", + " -1.2516085e-13,\n", + " -1.2543445e-13,\n", + " -1.2570832e-13,\n", + " -1.2598245e-13,\n", + " -1.2625685e-13,\n", + " -1.265315e-13,\n", + " -1.2680643e-13,\n", + " -1.2708163e-13,\n", + " -1.2735708e-13,\n", + " -1.276328e-13,\n", + " -1.2790878e-13,\n", + " -1.2818503e-13,\n", + " -1.2846155e-13,\n", + " -1.2873832e-13,\n", + " -1.2901536e-13,\n", + " -1.2929267e-13,\n", + " -1.2957022e-13,\n", + " -1.2984806e-13,\n", + " -1.3012616e-13,\n", + " -1.3040452e-13,\n", + " -1.3068314e-13,\n", + " -1.3096203e-13,\n", + " -1.3124117e-13,\n", + " -1.3152058e-13,\n", + " -1.3180026e-13,\n", + " -1.320802e-13,\n", + " -1.323604e-13,\n", + " -1.3264086e-13,\n", + " -1.3292159e-13,\n", + " -1.3320259e-13,\n", + " -1.3348383e-13,\n", + " -1.3376534e-13,\n", + " -1.3404712e-13,\n", + " -1.3432917e-13,\n", + " -1.3461147e-13,\n", + " -1.3489402e-13,\n", + " -1.3517685e-13,\n", + " -1.3545993e-13,\n", + " -1.3574328e-13,\n", + " -1.3602689e-13,\n", + " -1.3631076e-13,\n", + " -1.3659489e-13,\n", + " -1.3687929e-13,\n", + " -1.3716395e-13,\n", + " -1.3744886e-13,\n", + " -1.3773404e-13,\n", + " -1.3801948e-13,\n", + " -1.3830517e-13,\n", + " -1.3859114e-13,\n", + " -1.3887735e-13,\n", + " -1.3916383e-13,\n", + " -1.3945057e-13,\n", + " -1.3973757e-13,\n", + " -1.4002483e-13,\n", + " -1.4031236e-13,\n", + " -1.4060014e-13,\n", + " -1.4088818e-13,\n", + " -1.4117649e-13,\n", + " -1.4146505e-13,\n", + " -1.4175386e-13,\n", + " -1.4204294e-13,\n", + " -1.4233229e-13,\n", + " -1.4262188e-13,\n", + " -1.4291174e-13,\n", + " -1.4320186e-13,\n", + " -1.4349223e-13,\n", + " -1.4378287e-13,\n", + " -1.4407376e-13,\n", + " -1.4436491e-13,\n", + " -1.4465633e-13,\n", + " -1.4494799e-13,\n", + " -1.4523993e-13,\n", + " -1.455321e-13,\n", + " -1.4582456e-13,\n", + " -1.4611726e-13,\n", + " -1.4641021e-13,\n", + " -1.4670344e-13,\n", + " -1.4699692e-13,\n", + " -1.4729065e-13,\n", + " -1.4758465e-13,\n", + " -1.4787889e-13,\n", + " -1.481734e-13,\n", + " -1.4846817e-13,\n", + " -1.4876319e-13,\n", + " -1.4905847e-13,\n", + " -1.49354e-13,\n", + " -1.496498e-13,\n", + " -1.4994585e-13,\n", + " -1.5024215e-13,\n", + " -1.5053872e-13,\n", + " -1.5083555e-13,\n", + " -1.5113262e-13,\n", + " -1.5142995e-13,\n", + " -1.5172755e-13,\n", + " -1.5202539e-13,\n", + " -1.523235e-13,\n", + " -1.5262185e-13,\n", + " -1.5292047e-13,\n", + " -1.5321934e-13,\n", + " -1.5351846e-13,\n", + " -1.5381785e-13,\n", + " -1.5411748e-13,\n", + " -1.5441737e-13,\n", + " -1.5471752e-13,\n", + " -1.5501793e-13,\n", + " -1.5531859e-13,\n", + " -1.556195e-13,\n", + " -1.5592067e-13,\n", + " -1.562221e-13,\n", + " -1.5652377e-13,\n", + " -1.5682571e-13,\n", + " -1.5712789e-13,\n", + " -1.5743034e-13,\n", + " -1.5773304e-13,\n", + " -1.5803599e-13,\n", + " -1.5833919e-13,\n", + " -1.5864266e-13,\n", + " -1.5894637e-13,\n", + " -1.5925034e-13,\n", + " -1.5955455e-13,\n", + " -1.5985904e-13,\n", + " -1.6016377e-13,\n", + " -1.6046874e-13,\n", + " -1.6077398e-13,\n", + " -1.6107947e-13,\n", + " -1.6138521e-13,\n", + " -1.616912e-13,\n", + " -1.6199746e-13,\n", + " -1.6230397e-13,\n", + " -1.6261072e-13,\n", + " -1.6291772e-13,\n", + " -1.6322498e-13,\n", + " -1.635325e-13,\n", + " -1.6384027e-13,\n", + " ...]}" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# And we have new results in the memory cache\n", + "model.get_memory_cache()" + ] + } + ], + "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.6.7" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/neuronunit/examples/cluster_script.py b/neuronunit/examples/cluster_script.py new file mode 100644 index 000000000..ca627f9b8 --- /dev/null +++ b/neuronunit/examples/cluster_script.py @@ -0,0 +1,365 @@ + +# coding: utf-8 + +# Assumptions, the environment for running this notebook was arrived at by building a dedicated docker file. +# +# https://cloud.docker.com/repository/registry-1.docker.io/russelljarvis/nuo +# +# You can run use dockerhub to get the appropriate file, and launch this notebook using Kitematic. + +# This is code, change cell type to markdown. +# ![alt text](plan.jpg "Pub plan") + + +# # Import libraries +# To keep the standard running version of minimal and memory efficient, not all available packages are loaded by default. In the cell below I import a mixture common python modules, and custom developed modules associated with NeuronUnit (NU) development +#!pip install dask distributed seaborn +#!bash after_install.sh +import numpy as np +import os +import pickle +import pandas as pd +from neuronunit.tests.fi import RheobaseTestP +from neuronunit.optimization.model_parameters import reduced_dict, reduced_cells +from neuronunit.optimization import optimization_management as om +from sciunit import scores# score_type + +from neuronunit.optimization import get_neab +from neuronunit.optimization.data_transport_container import DataTC +from neuronunit.tests.fi import RheobaseTestP# as discovery +from neuronunit.optimization.optimization_management import dtc_to_rheo, format_test, nunit_evaluation +import quantities as pq +from neuronunit.models.reduced import ReducedModel +from neuronunit.optimization.model_parameters import model_params, path_params +LEMS_MODEL_PATH = path_params['model_path'] +list_to_frame = [] +from neuronunit.tests.fi import RheobaseTestP +import copy +from sklearn.model_selection import ParameterGrid +from neuronunit.models.interfaces import glif +from neuronunit.optimization.data_transport_container import DataTC +# # The Izhiketich model is instanced using some well researched parameter sets. +# First lets get the points in parameter space, that Izhikich himself has published about. These points are often used by the open source brain project to establish between model reproducibility. The itial motivating factor for choosing these points as constellations, of all possible parameter space subsets, is that these points where initially tuned and used as best guesses for matching real observed experimental recordings. + +explore_param = {k:(np.min(v),np.max(v)) for k,v in reduced_dict.items()} + +# ## Get the experimental Data pertaining to four different classes or neurons, that can constrain models. +# Next we get some electro physiology data for four different classes of cells that are very common targets of scientific neuronal modelling. We are interested in finding out what are the most minimal, and detail reduced, low complexity model equations, that are able to satisfy +# Below are some of the data set ID's I used to query neuroelectro. +# To save time for the reader, I prepared some data earlier to save time, and saved the data as a pickle, pythons preferred serialisation format. +# The interested reader can find some methods for getting cell specific ephys data from neuroelectro in a code file (neuronunit/optimization/get_neab.py) + + +purkinje ={"id": 18, "name": "Cerebellum Purkinje cell", "neuron_db_id": 271, "nlex_id": "sao471801888"} +fi_basket = {"id": 65, "name": "Dentate gyrus basket cell", "neuron_db_id": None, "nlex_id": "nlx_cell_100201"} +pvis_cortex = {"id": 111, "name": "Neocortex pyramidal cell layer 5-6", "neuron_db_id": 265, "nlex_id": "nifext_50"} +#does not have rheobase +olf_mitral = {"id": 129, "name": "Olfactory bulb (main) mitral cell", "neuron_db_id": 267, "nlex_id": "nlx_anat_100201"} +ca1_pyr = {"id": 85, "name": "Hippocampus CA1 pyramidal cell", "neuron_db_id": 258, "nlex_id": "sao830368389"} +pipe = [ fi_basket, ca1_pyr, purkinje, pvis_cortex] + +electro_tests = [] +obs_frame = {} +test_frame = {} + +try: + electro_path = str(os.getcwd())+'all_tests.p' + assert os.path.isfile(electro_path) == True + with open(electro_path,'rb') as f: + (obs_frame,test_frame) = pickle.load(f) + +except: + for p in pipe: + p_tests, p_observations = get_neab.get_neuron_criteria(p) + obs_frame[p["name"]] = p_observations#, p_tests)) + test_frame[p["name"]] = p_tests#, p_tests)) + electro_path = str(os.getcwd())+'all_tests.p' + with open(electro_path,'wb') as f: + pickle.dump((obs_frame,test_frame),f) + + +# # Cast the tabulatable data to pandas data frame +# There are many among us who prefer potentially tabulatable data to be encoded in pandas data frame. + +# idea something like: +# test_frame['Olfactory bulb (main) mitral cell'].insert(0,test_frame['Cerebellum Purkinje cell'][0]) + +for k,v in test_frame.items(): + if "Olfactory bulb (main) mitral cell" not in k: + pass + if "Olfactory bulb (main) mitral cell" in k: + import pdb; pdb.set_trace() + v[0] = RheobaseTestP(obs['Rheobase']) +df = pd.DataFrame.from_dict(obs_frame) +print(test_frame.keys()) + + +# In the data frame below, you can see many different cell types +df['Hippocampus CA1 pyramidal cell'] +# # Tweak Izhikitich equations +# with educated guesses based on information that is already encoded in the predefined experimental observations. +# In otherwords use information that is readily amenable into hardcoding into equations +# Select out the 'Neocortex pyramidal cell layer 5-6' below, as a target for optimization + +free_params = ['a','b','k','c','C','d','vPeak','vr','vt'] +hc_ = reduced_cells['RS'] +hc_['vr'] = -65.2261863636364 +hc_['vPeak'] = hc_['vr'] + 86.364525297619 +explore_param['C'] = (hc_['C']-20,hc_['C']+20) +explore_param['vr'] = (hc_['vr']-5,hc_['vr']+5) +use_test = test_frame["Neocortex pyramidal cell layer 5-6"] +test_opt = {} +with open('data_dump.p','wb') as f: + pickle.dump(test_opt,f) +use_test[0].observation +rtp = RheobaseTestP(use_test[0].observation) +use_test[0] = rtp +reduced_cells.keys() +df = pd.DataFrame(index=list(test_frame.keys()),columns=list(reduced_cells.keys())) + +MU = 6 +NGEN = 90 +gc = glif.GC() +glif_dic = gc.glif.to_dict() +explore_ranges = {} +gd = glif_dic +explore_ranges['El'] = (glif_dic['El'],glif_dic['El']+10.0) +explore_ranges['R_input'] = (glif_dic['R_input']-glif_dic['R_input']/2.0,glif_dic['R_input']+glif_dic['R_input']/2.0) +explore_ranges['C'] = (glif_dic['C']-glif_dic['C']/2.0,glif_dic['C']+glif_dic['C']/2.0) +explore_ranges['th_inf'] = (glif_dic['th_inf']-glif_dic['th_inf']/4.0,glif_dic['th_inf']+glif_dic['th_inf']/4.0) +model = ReducedModel(LEMS_MODEL_PATH,name = str('vanilla'),backend = (str('GLIF'))) + +store_glif_results = {} +params = gc.glif.to_dict() +grid = ParameterGrid(explore_ranges) +print() +store_glif_results = {} +hold_constant_glif = {} + +for k,v in gd.items(): + if k not in explore_ranges: + hold_constant_glif[k] = v +try: + with open('glif_seeds.p','rb') as f: + seeds = pickle.load(f) + assert seeds is not None + +except: + + for local_attrs in grid: + store_glif_results[str(local_attrs.values())] = {} + dtc = DataTC() + dtc.tests = use_test + complete_params = {} + dtc.attrs = local_attrs + dtc.backend = 'GLIF' + dtc.cell_name = 'GLIF' + for key, use_test in test_frame.items(): + dtc.tests = use_test + dtc = dtc_to_rheo(dtc) + dtc = format_test(dtc) + if dtc.rheobase is not None: + if dtc.rheobase!=-1.0: + dtc = nunit_evaluation(dtc) + print(dtc.get_ss()) + store_glif_results[str(local_attrs.values())][key] = dtc.get_ss() + df = pd.DataFrame(store_glif_results) + best_params = {} + for index, row in df.iterrows(): + best_params[index] = row == row.min() + best_params[index] = best_params[index].to_dict() + + + seeds = {} + for k,v in best_params.items(): + for nested_key,nested_val in v.items(): + if True == nested_val: + seed = nested_key + seeds[k] = seed + with open('glif_seeds.p','wb') as f: + pickle.dump(seeds,f) + + + + + + +MU = 6 +NGEN = 150 + + +try: + with open('multi_objective_glif.p','rb') as f: + test_opt = pickle.load(f) + +except: + for key, use_test in test_frame.items(): + seed = seeds[key] + print(seed) + ga_out, _ = om.run_ga(explore_ranges,NGEN,use_test,free_params=explore_ranges.keys(), NSGA = True, MU = MU, model_type = str('GLIF'),seed_pop=seed) + test_opt = {str('multi_objective_glif')+str(seed):ga_out} + with open('multi_objective_glif.p','wb') as f: + pickle.dump(test_opt,f) + +import pdb; pdb.set_trace() + + + + +model = ReducedModel(LEMS_MODEL_PATH,name = str('vanilla'),backend = (str('HH'))) +explore_ranges = {'E_Na' : (40,70), 'g_Na':(100.0,140.0), 'C_m':(0.5,1.5)} +attrs_hh = { 'g_K' : 36.0, 'g_Na' : 120.0, 'g_L' : 0.3, \ + 'C_m' : 1.0, 'E_L' : -54.387, 'E_K' : -77.0, 'E_Na' : 50.0, 'vr':-65.0 } + +try: + with open('HH_seeds.p','rb') as f: + seeds = pickle.load(f) + assert seeds is not None + +except: + + grid = ParameterGrid(explore_ranges) + store_hh_results = {} + for local_attrs in grid: + store_hh_results[str(local_attrs.values())] = {} + dtc = DataTC() + dtc.tests = use_test + updatable_attrs = copy.copy(attrs_hh) + updatable_attrs.update(local_attrs) + dtc.attrs = updatable_attrs + print(updatable_attrs) + + dtc.backend = 'HH' + dtc.cell_name = 'Point Hodgkin Huxley' + for key, use_test in test_frame.items(): + dtc.tests = use_test + dtc = dtc_to_rheo(dtc) + dtc = format_test(dtc) + if dtc.rheobase is not None: + if dtc.rheobase!=-1.0: + dtc = nunit_evaluation(dtc) + print(dtc.get_ss()) + store_hh_results[str(local_attrs.values())][key] = dtc.get_ss() + df = pd.DataFrame(store_hh_results) + best_params = {} + for index, row in df.iterrows(): + best_params[index] = row == row.min() + best_params[index] = best_params[index].to_dict() + + + seeds = {} + for k,v in best_params.items(): + for nested_key,nested_val in v.items(): + if True == nested_val: + seed = nested_key + seeds[k] = seed + with open('HH_seeds.p','wb') as f: + pickle.dump(seeds,f) + + + +attrs_hh = { 'g_K' : 36.0, 'g_Na' : 120.0, 'g_L' : 0.3, \ + 'C_m' : 1.0, 'E_L' : -54.387, 'E_K' : -77.0, \ + 'E_Na' : 50.0, 'vr':-65.0 } + + +explore_hh_ranges = {'E_Na' : (30,80), 'E_K': (-90.0,-75.0), 'g_K': (30.0,42.0),\ + 'C_m':(0.5,1.5), 'g_Na':(100.0,140.0),'g_L':(0.1,0.5), \ + 'E_L' : (-64.387,-44.387), 'vr':(-85.0,45.0)} + + + +hold_constant_hh = {} +for k,v in attrs_hh.items(): + if k not in explore_ranges.keys(): + hold_constant_hh[k] = v + + +MU = 6 +NGEN = 150 + + +try: + with open('multi_objective_HH.p','rb') as f: + test_opt = pickle.load(f) + +except: + for key, use_test in test_frame.items(): + seed = seeds[key] + print(seed) + ga_out, _ = om.run_ga(explore_hh_ranges,NGEN,use_test,free_params=explore_ranges.keys(), NSGA = True, MU = MU, model_type = str('HH'),hc = hold_constant_hh,seed_pop=seed) + test_opt = {str('multi_objective_HH')+str(ga_out):ga_out} + with open('multi_objective_HH.p','wb') as f: + pickle.dump(test_opt,f) + + + + +with open('Izh_seeds.p','rb') as f: + seeds = pickle.load(f) + +try: + #assert 1==2 + assert seeds is not None + +except: + print('exceptional circumstances pickle file does not exist, rebuilding sparse grid for Izhikich') + # Below we perform a sparse grid sampling over the parameter space, using the published and well corrobarated parameter points, from Izhikitch publications, and the Open Source brain, shows that without optimization, using off the shelf parameter sets to fit real-life biological cell data, does not work so well. + + for k,v in reduced_cells.items(): + temp = {} + temp[str(v)] = {} + dtc = DataTC() + dtc.tests = use_test + dtc.attrs = v + dtc.backend = 'RAW' + dtc.cell_name = 'vanilla' + for key, use_test in test_frame.items(): + dtc.tests = use_test + dtc = dtc_to_rheo(dtc) + dtc = format_test(dtc) + if dtc.rheobase is not None: + if dtc.rheobase!=-1.0: + dtc = nunit_evaluation(dtc) + df[k][key] = dtc.get_ss() + + best_params = {} + for index, row in df.iterrows(): + best_params[index] = row == row.min() + best_params[index] = best_params[index].to_dict() + + + seeds = {} + for k,v in best_params.items(): + for nested_key,nested_val in v.items(): + if True == nested_val: + seed = reduced_cells[nested_key] + seeds[k] = seed + with open('Izh_seeds.p','wb') as f: + pickle.dump(seeds,f) + + + + +MU = 6 +NGEN = 150 + + +for key, use_test in test_frame.items(): + + # use the best parameters found via the sparse grid search above, to inform the first generation + # of the GA. + + seed = seeds[key] + print(seed) + ga_out, _ = om.run_ga(explore_param,NGEN,use_test,free_params=free_params, NSGA = True, MU = MU,seed_pop = seed, model_type = str('RAW')) + + test_opt = {str('multi_objective_izhi')+str(ga_out):ga_out} + with open('multi_objective_izhi.p','wb') as f: + pickle.dump(test_opt,f) + + + + +#Next HH, model and Adaptive Exp. +#model = ReducedModel(LEMS_MODEL_PATH,name = str('vanilla'),backend = ('HH')) diff --git a/neuronunit/examples/druckman_tests.ipynb b/neuronunit/examples/druckman_tests.ipynb new file mode 100644 index 000000000..38542fd1e --- /dev/null +++ b/neuronunit/examples/druckman_tests.ipynb @@ -0,0 +1,750 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Requirement already up-to-date: numpy in /opt/conda/lib/python3.5/site-packages (1.15.4)\n", + "Requirement already up-to-date: numba in /opt/conda/lib/python3.5/site-packages (0.41.0)\n", + "Requirement not upgraded as not directly required: llvmlite>=0.26.0dev0 in /opt/conda/lib/python3.5/site-packages (from numba) (0.26.0)\n", + "\u001b[31mcryptography 2.2.1 requires asn1crypto>=0.21.0, which is not installed.\u001b[0m\n", + "\u001b[31mcffi 1.11.5 requires pycparser, which is not installed.\u001b[0m\n", + "\u001b[31mallensdk 0.14.2 has requirement pandas<0.20.0,>=0.16.2, but you'll have pandas 0.23.1 which is incompatible.\u001b[0m\n", + "\u001b[33mYou are using pip version 10.0.1, however version 18.1 is available.\n", + "You should consider upgrading via the 'pip install --upgrade pip' command.\u001b[0m\n", + "Requirement already satisfied: lmfit in /opt/conda/lib/python3.5/site-packages (0.9.12)\n", + "Requirement already satisfied: numpy>=1.10 in /opt/conda/lib/python3.5/site-packages (from lmfit) (1.15.4)\n", + "Requirement already satisfied: six>1.10 in /opt/conda/lib/python3.5/site-packages (from lmfit) (1.11.0)\n", + "Requirement already satisfied: asteval>=0.9.12 in /opt/conda/lib/python3.5/site-packages (from lmfit) (0.9.13)\n", + "Requirement already satisfied: uncertainties>=3.0 in /opt/conda/lib/python3.5/site-packages (from lmfit) (3.0.3)\n", + "Requirement already satisfied: scipy>=0.17 in /opt/conda/lib/python3.5/site-packages (from lmfit) (1.1.0)\n", + "\u001b[31mcffi 1.11.5 requires pycparser, which is not installed.\u001b[0m\n", + "\u001b[31mcryptography 2.2.1 requires asn1crypto>=0.21.0, which is not installed.\u001b[0m\n", + "\u001b[31mallensdk 0.14.2 has requirement pandas<0.20.0,>=0.16.2, but you'll have pandas 0.23.1 which is incompatible.\u001b[0m\n", + "\u001b[33mYou are using pip version 10.0.1, however version 18.1 is available.\n", + "You should consider upgrading via the 'pip install --upgrade pip' command.\u001b[0m\n" + ] + } + ], + "source": [ + "!pip install numpy numba --upgrade \n", + "!pip install lmfit \n", + "\n", + "\n", + " assert np.isin\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "electro_tests = []\n", + "obs_frame = {}\n", + "test_frame = {}\n", + "import os\n", + "import pickle\n", + "try: \n", + "\n", + " electro_path = str(os.getcwd())+'all_tests.p'\n", + "\n", + " assert os.path.isfile(electro_path) == True\n", + " with open(electro_path,'rb') as f:\n", + " (obs_frame,test_frame) = pickle.load(f)\n", + "\n", + "except:\n", + " for p in pipe:\n", + " p_tests, p_observations = get_neab.get_neuron_criteria(p)\n", + " obs_frame[p[\"name\"]] = p_observations#, p_tests))\n", + " test_frame[p[\"name\"]] = p_tests#, p_tests))\n", + " electro_path = str(os.getcwd())+'all_tests.p'\n", + " with open(electro_path,'wb') as f:\n", + " pickle.dump((obs_frame,test_frame),f)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "import sciunit, neuronunit, quantities\n", + "from neuronunit.tests.dm import *\n", + "import time\n", + "from neuronunit.tests import dm #this is me importing the Druckman tests\n", + "from neuronunit.tests import RheobaseTestP, fi, RheobaseTest \n", + "\n", + "import matplotlib.pyplot as plt\n", + "\n", + "from neuronunit.models.reduced import ReducedModel\n", + "from neuronunit.optimization.data_transport_container import DataTC\n", + "from neuronunit.optimization.model_parameters import model_params, path_params, transcribe_units\n", + "from neuronunit.optimization.optimization_management import mint_generic_model\n", + "LEMS_MODEL_PATH = path_params['model_path']\n", + "\n", + "\n", + "model = ReducedModel(LEMS_MODEL_PATH,name = str('vanilla'),backend = ('RAW'))\n", + "dtc = DataTC()\n", + "dtc.attrs = model.attrs\n", + "dtc.ampl=0\n", + "dtc.attrs_raw = {'C':89.7960714285714, 'a':0.01, 'b':15, 'c':-60, 'd':10,\\\n", + " 'k':1.6, 'vPeak':(86.364525297619-65.2261863636364),\\\n", + " 'vr':-65.2261863636364, 'vt':-50}\n", + "\n", + "\n", + "dtc.attrs = transcribe_units(dtc.attrs_raw)\n", + "\n", + "start0 = time.time()\n", + "model = ReducedModel(LEMS_MODEL_PATH, name= str('vanilla'), backend=('NEURON', {'DTC':dtc}))\n", + "rt = RheobaseTestP(obs_frame['Dentate gyrus basket cell']['Rheobase'])\n", + "pred0 = rt.generate_prediction(model)\n", + "end0 = time.time()\n", + "print(model.attrs)\n", + "\n", + "model = ReducedModel(LEMS_MODEL_PATH, name= str('vanilla'), backend=('NEURON', {'DTC':dtc}))\n", + "start1 = time.time()\n", + "rt = fi.RheobaseTest(obs_frame['Dentate gyrus basket cell']['Rheobase'])\n", + "pred1 = rt.generate_prediction(model)\n", + "end1 = time.time()\n", + "\n", + "print('parallel Rhsearch time NEURON', end0-start0)\n", + "print('serial Rhsearch time NEURON',end1-start1)\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true, + "scrolled": true + }, + "outputs": [], + "source": [ + "explore_ranges = {'E_Na' : (40,70), 'E_K': (-90.0,-75.0)}\n", + "\n", + "attrs = { 'g_K' : 36.0, 'g_Na' : 120.0, 'g_L' : 0.3, \\\n", + " 'C_m' : 1.0, 'E_L' : -54.387, 'E_K' : -77.0, 'E_Na' : 50.0, 'vr':-65.0 } \n", + "\n", + "model = ReducedModel(LEMS_MODEL_PATH,name = str('vanilla'),backend = ('HH'))\n", + "model.attrs = attrs\n", + "dtc.attrs = attrs\n", + "iparams = {}\n", + "iparams['injected_square_current'] = {}\n", + "iparams['injected_square_current']['amplitude'] = 1.98156805*pq.pA\n", + "iparams['injected_square_current']['amplitude'] = 2.98156805*pq.pA\n", + "\n", + "DELAY = 100.0*pq.ms\n", + "DURATION = 1000.0*pq.ms\n", + "iparams['injected_square_current']['delay'] = DELAY\n", + "iparams['injected_square_current']['duration'] = int(DURATION)\n", + "bf = time.time()\n", + "model.inject_square_current(iparams)\n", + "vm = model.get_membrane_potential()\n", + "af = time.time()\n", + "#volts = [v[0] for v in vm ]\n", + "print(len(vm[0]),len(vm.times))\n", + "\n", + "\n", + "bfp = time.time()\n", + "model = ReducedModel(LEMS_MODEL_PATH, name= str('vanilla'), backend=('HH', {'DTC':dtc}))\n", + "model._backend.cell_name = str('vanilla')\n", + "rt = RheobaseTestP(obs_frame['Dentate gyrus basket cell']['Rheobase'])\n", + "pred0 = rt.generate_prediction(model)\n", + "afp = time.time()\n", + "print(model.attrs)\n", + "plt.plot(vm.times,vm)\n", + "plt.show()\n", + "print('elapsed parallel: ',afp-bfp)\n", + "\n", + "\n", + "bfs = time.time()\n", + "model = ReducedModel(LEMS_MODEL_PATH, name= str('vanilla'), backend=('HH', {'DTC':dtc}))\n", + "model._backend.cell_name = str('vanilla')\n", + "\n", + "rt = RheobaseTest(obs_frame['Dentate gyrus basket cell']['Rheobase'])\n", + "pred0 = rt.generate_prediction(model)\n", + "afs = time.time()\n", + "print(model.attrs)\n", + "plt.plot(vm.times,vm)\n", + "plt.show()\n", + "print('elapsed serial: ',afs-bfs)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "\n", + "import pdb\n", + "with open(electro_path,'rb') as f:\n", + " (obs_frame,test_frame) = pickle.load(f)\n", + "use_test = test_frame[\"Neocortex pyramidal cell layer 5-6\"]\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "use_test[0].observation\n", + "#from neuronunit.tests import RheobaseP\n", + "from neuronunit.tests.fi import RheobaseTestP# as discovery\n", + "\n", + "rtp = RheobaseTestP(use_test[0].observation)\n", + "use_test[0] = rtp" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "explore_ranges = {'E_Na' : (40.0,70.0), 'E_K': (-90.0,-75.0), 'g_K':(30,40), 'g_Na':(100.0,140.0), 'g_L':(0.1,0.5), 'E_L':(-60.0,-45.0)}\n", + "\n", + "attrs = { 'g_K' : 36.0, 'g_Na' : 120.0, 'g_L' : 0.3, \\\n", + " 'C_m' : 1.0, 'E_L' : -54.387, 'E_K' : -77.0, 'E_Na' : 50.0, 'vr':-65.0 } \n", + "\n", + " \n", + "from neuronunit.optimization import optimization_management as om\n", + "print(test_frame) \n", + "MU = 12\n", + "NGEN = 25\n", + "cnt = 1\n", + "#hc = { 'g_L' : 0.3, 'E_L' : -54.387,\n", + "hc = {'vr':-65.0, 'C_m':1.0 } \n", + "\n", + "npcl, DO = om.run_ga(explore_ranges,NGEN,use_test,free_params=explore_ranges.keys(), hc = hc, NSGA = True, MU = MU,model_type='HH')\n", + " \n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "attrs = {'cm': 0.20000000000000001,\n", + " 'e_rev_E': 0.0,\n", + " 'e_rev_I': -80.0,\n", + " 'e_rev_K': -90.0,\n", + " 'e_rev_Na': 50.0,\n", + " 'e_rev_leak': -65.0,\n", + " 'g_leak': 0.01,\n", + " 'gbar_K': 6.0,\n", + " 'gbar_Na': 20.0,\n", + " 'i_offset': 0.0,\n", + " 'tau_syn_E': 0.20000000000000001,\n", + " 'tau_syn_I': 2.0,\n", + " 'v_offset': -63.0}\n", + "\n", + "# def __init__(self, \n", + "# I_ampl=10., g_leak=0.3,\n", + "# g_K=36., g_Na=120., V_leak=-54.402, V_K=-77., V_Na=50.):\n", + "\n", + "dtc.attrs = attrs\n", + "bfp = time.time()\n", + "#model = ReducedModel(LEMS_MODEL_PATH, name= str('vanilla'), backend=('HH', {'DTC':dtc}))\n", + "model = ReducedModel(LEMS_MODEL_PATH,name = str('vanilla'),backend = ('HHpyNN', {'DTC':dtc}))\n", + "\n", + "rt0 = RheobaseTestP(obs_frame['Dentate gyrus basket cell']['Rheobase'])\n", + "pred0 = rt.generate_prediction(model)\n", + "afp = time.time()\n", + "print('elapsed parallel: ',afp-bfp)\n", + "\n", + "\n", + "bfs = time.time()\n", + "#model = ReducedModel(LEMS_MODEL_PATH, name= str('vanilla'), backend=('HH', {'DTC':dtc}))\n", + "model = ReducedModel(LEMS_MODEL_PATH,name = str('vanilla'),backend = ('HHpyNN', {'DTC':dtc}))\n", + "\n", + "rt1 = RheobaseTest(obs_frame['Dentate gyrus basket cell']['Rheobase'])\n", + "pred1 = rt.generate_prediction(model)\n", + "afs = time.time()\n", + "\n", + "\n", + "print(model.attrs)\n", + "plt.plot(vm.times,vm)\n", + "plt.show()\n", + "print('elapsed Serial: ',afs-bfs)\n", + "print(rt0,rt1)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "attrs = { 'gK' : 36.0, 'gNa' : 120.0, 'gL' : 0.3, 'Cm' : 1.0, 'Vl' : -10.402, 'VK' : -12.0, 'VNa' : -115, 'vr':-58.402 } \n", + "\n", + "C_m = 1\n", + "V_Na = -115\n", + "V_K = 12\n", + "V_l = -10.613\n", + "g_Na = 120\n", + "g_K = 36\n", + "g_l = 0.3\n", + "\n", + "model = ReducedModel(LEMS_MODEL_PATH,name = str('vanilla'),backend = ('HH'))\n", + "model.attrs = attrs\n", + "iparams['injected_square_current']['amplitude'] = 15.6805*pq.pA\n", + "\n", + "model.inject_square_current(iparams)\n", + "vm = model.get_membrane_potential()\n", + "af = time.time()\n", + "#volts = [v[0] for v in vm ]\n", + "print(len(vm[0]),len(vm.times))\n", + "plt.plot(vm.times,vm)\n", + "\n", + "len(vm)\n", + "len(vm.times)\n", + "np.shape(vm)\n", + "print(vm[6000])\n", + "print(vm[5000])\n", + "print(vm[0])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "start4 = time.time()\n", + "\n", + "model = ReducedModel(LEMS_MODEL_PATH, name= str('vanilla'), backend=('RAW'))\n", + "rt = fi.RheobaseTestP(obs_frame['Dentate gyrus basket cell']['Rheobase'])\n", + "pred1 = rt.generate_prediction(model)\n", + "end4 = time.time()\n", + "\n", + "\n", + "start3 = time.time()\n", + "\n", + "model = ReducedModel(LEMS_MODEL_PATH, name= str('vanilla'), backend=('RAW'))\n", + "\n", + "rt = fi.RheobaseTest(obs_frame['Dentate gyrus basket cell']['Rheobase'])\n", + "pred1 = rt.generate_prediction(model)\n", + "end3 = time.time()\n", + "\n", + "print(pred1)\n", + "ir_currents = {}\n", + "ir_currents = pred1['value']\n", + "standard = 1.5*ir_currents\n", + "standard*=1.5\n", + "strong = 3*ir_currents\n", + "print(standard,strong,ir_currents)\n", + "\n", + "\n", + "\n", + "print('parallel Rhsearch time RAW', end4-start4)\n", + "print('serial Rhsearch time RAW',end3-start3)\n", + "\n", + "print(pred)\n", + "#for i in npcl['pf'][0:2]:\n", + "iparams = {}\n", + "iparams['injected_square_current'] = {}\n", + "iparams['injected_square_current']['amplitude'] = pred1['value']\n", + "model = None\n", + "model = ReducedModel(LEMS_MODEL_PATH,name = str('vanilla'),backend = ('RAW'))\n", + "#model.set_attrs(i.dtc.attrs)\n", + "\n", + "#['amplitude'] = dtc.vtest[k]['injected_square_current']['amplitude']\n", + "DELAY = 100.0*pq.ms\n", + "DURATION = 1000.0*pq.ms\n", + "iparams['injected_square_current']['delay'] = DELAY\n", + "iparams['injected_square_current']['duration'] = int(DURATION)\n", + "model.inject_square_current(iparams)\n", + "n_spikes = len(model.get_spike_train())\n", + "\n", + "if n_spikes:\n", + " print(n_spikes)\n", + " #print(i[0].scores['RheobaseTestP']*pq.pA)\n", + " plt.plot(model.get_membrane_potential().times,model.get_membrane_potential())#,label='ground truth')\n", + " plt.legend()\n", + "print(obs_frame['Dentate gyrus basket cell']['Rheobase'])\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "#speed_up= (end1-start1)/(end0-start0)\n", + "#print(speed_up, 'speed up for NEURON')\n", + "speed_up= (end3-start3)/(end4-start4)\n", + "print(speed_up, 'speed up (slow down) for rawpy')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "These results show that parallel rheobase is ~3.5-7 times faster for NEURON, but slower for numba jit depending on model.\n", + "\n", + "This makes sense, because numba jit evaluations are over so quickly, it rivals the time, for interprocessor communication, not so with NEURON simulations, where simulation takes a long time.\n", + "\n", + "The reason parallel is faster given interprocessor comm speed < sim evaluation time, is because in the case of binary search.\n", + "\n", + "For each sim evaluation, the search engine only narrows by 50%.\n", + "\n", + "In the parallel case, 8 simultaneous sim evaluations, are able to narrow the search interval space, by 7/8ths.\n", + "\n", + "This fast narrowing of intervals is what makes the parallel case faster than the binary case." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "\n", + "tests = [AP12AmplitudeDropTest(standard), \n", + " AP1SSAmplitudeChangeTest(standard), \n", + " AP1AmplitudeTest(standard), \n", + " AP1WidthHalfHeightTest(standard), \n", + " AP1WidthPeakToTroughTest(standard), \n", + " AP1RateOfChangePeakToTroughTest(standard), \n", + " AP1AHPDepthTest(standard), \n", + " AP2AmplitudeTest(standard), \n", + " AP2WidthHalfHeightTest(standard), \n", + " AP2WidthPeakToTroughTest(standard), \n", + " AP2RateOfChangePeakToTroughTest(standard), \n", + " AP2AHPDepthTest(standard), \n", + " AP12AmplitudeChangePercentTest(standard), \n", + " AP12HalfWidthChangePercentTest(standard), \n", + " AP12RateOfChangePeakToTroughPercentChangeTest(standard), \n", + " AP12AHPDepthPercentChangeTest(standard), \n", + " AP1DelayMeanTest(standard), \n", + " AP1DelaySDTest(standard), \n", + " AP2DelayMeanTest(standard), \n", + " AP2DelaySDTest(standard), \n", + " Burst1ISIMeanTest(standard), \n", + " Burst1ISISDTest(standard), \n", + " InitialAccommodationMeanTest(standard), \n", + " SSAccommodationMeanTest(standard), \n", + " AccommodationRateToSSTest(standard), \n", + " AccommodationAtSSMeanTest(standard), \n", + " AccommodationRateMeanAtSSTest(standard), \n", + " ISICVTest(standard), \n", + " ISIMedianTest(standard), \n", + " ISIBurstMeanChangeTest(standard), \n", + " SpikeRateStrongStimTest(strong), \n", + " AP1DelayMeanStrongStimTest(strong), \n", + " AP1DelaySDStrongStimTest(strong), \n", + " AP2DelayMeanStrongStimTest(strong), \n", + " AP2DelaySDStrongStimTest(strong), \n", + " Burst1ISISDStrongStimTest(strong),\n", + " Burst1ISIMeanStrongStimTest(strong)]\n", + "\n", + "AHP_list = [AP1AHPDepthTest(standard), \n", + " AP2AHPDepthTest(standard), \n", + " AP12AHPDepthPercentChangeTest(standard) ] \n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "print(ir_currents)\n", + "print(standard)\n", + "print(strong)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "start2 = time.time()\n", + "\n", + "model = ReducedModel(LEMS_MODEL_PATH, name= str('vanilla'), backend=('RAW'))\n", + "\n", + " \n", + "for i, test in enumerate(tests):\n", + " mean = test.generate_prediction(model)['mean']\n", + " \n", + " \n", + "\n", + " #print(mean,test)\n", + "stop2 = time.time()\n", + "delta2 = stop2-start2\n", + "print('serial time: ',stop2-start2)\n", + "\n", + "'''\n", + "USING NEURON WOULD TAKE HALF AN HOUR\n", + "start3 = time.time()\n", + "model = ReducedModel(LEMS_MODEL_PATH, name= str('vanilla'), backend=('NEURON', {'DTC':dtc}))\n", + "model.atts = dtc.attrs\n", + "\n", + "\n", + "for i, test in enumerate(tests):\n", + " mean = test.generate_prediction(model)['mean']\n", + " #print(mean, tests)\n", + "stop3 = time.time()\n", + "print(stop3-start3)\n", + "'''" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# can do these tests in parallel:\n", + "import dask.bag as db\n", + "import multiprocessing\n", + "npart = multiprocessing.cpu_count()\n", + "\n", + "\n", + "start5 = time.time()\n", + "bag = db.from_sequence(tests, npartitions = npart)\n", + "means = list(bag.map(takes_tests).compute()) \n", + "end5 = time.time()\n", + "#print(end5-start5)\n", + "\n", + "print(means)\n", + "print('parallel time: ',end5-start5)\n", + "print('speed up:',delta2/(end5-start5))\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "dmtests = dm.Druckmann2013Test\n", + "d_tests = []\n", + "for d in dir(dm):\n", + " if \"Test\" in d:\n", + " exec('d_tests.append(dm.'+str(d)+')')\n", + "#print()\n", + "dt = d_tests[1:-1]\n", + "print(dt)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "import quantities as pq\n", + "current_amplitude = {'mean': 106.7 * pq.pA, 'n': 1, 'std': 0.0 * pq.pA}\n", + "test = dm.AP12AmplitudeChangePercentTest(current_amplitude)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "import os\n", + "import pickle\n", + "import matplotlib.pyplot as plt\n", + "electro_path = str(os.getcwd())+'all_tests.p'\n", + "\n", + "assert os.path.isfile(electro_path) == True\n", + "with open(electro_path,'rb') as f:\n", + " (obs_frame,test_frame) = pickle.load(f)\n", + "\n", + "\n", + "rt = RheobaseTestP(obs_frame['Dentate gyrus basket cell']['Rheobase'])\n", + "\n", + "pred = rt.generate_prediction(model)\n", + "print(pred)\n", + "#for i in npcl['pf'][0:2]:\n", + "iparams = {}\n", + "iparams['injected_square_current'] = {}\n", + "iparams['injected_square_current']['amplitude'] = pred['value']\n", + "model = None\n", + "model = ReducedModel(LEMS_MODEL_PATH,name = str('vanilla'),backend = ('RAW'))\n", + "#model.set_attrs(i.dtc.attrs)\n", + "\n", + "#['amplitude'] = dtc.vtest[k]['injected_square_current']['amplitude']\n", + "DELAY = 100.0*pq.ms\n", + "DURATION = 1000.0*pq.ms\n", + "iparams['injected_square_current']['delay'] = DELAY\n", + "iparams['injected_square_current']['duration'] = int(DURATION)\n", + "model.inject_square_current(iparams)\n", + "n_spikes = len(model.get_spike_train())\n", + "\n", + "if n_spikes:\n", + " print(n_spikes)\n", + " #print(i[0].scores['RheobaseTestP']*pq.pA)\n", + " plt.plot(model.get_membrane_potential().times,model.get_membrane_potential())#,label='ground truth')\n", + " plt.legend()\n", + "print(obs_frame['Dentate gyrus basket cell']['Rheobase'])\n", + " \n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "print(model)\n", + "help(dt[0])\n", + "dt = dt[0]()\n", + "dt[0].generate_prediction(model)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "'''\n", + "import pdb\n", + "for d in dt:\n", + " pdb.set_trace()\n", + " #dmtO = d(pred['value'])#obs_frame['Dentate gyrus basket cell']['Rheobase'])\n", + "print(dmt0)\n", + "'''" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + } + ], + "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.5.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/neuronunit/examples/geppeto_backend.py b/neuronunit/examples/geppeto_backend.py new file mode 100644 index 000000000..e850aaec0 --- /dev/null +++ b/neuronunit/examples/geppeto_backend.py @@ -0,0 +1,10 @@ +import quantities as pq +from neuronunit.tests.passive import InputResistanceTest +from neuronunit.models.reduced import ReducedModel +test = InputResistanceTest(observation={'mean': 200.0*pq.MOhm, + 'std': 50.0*pq.MOhm}) +model_url = ("https://raw.githubusercontent.com/scidash/neuronunit" + "/dev/neuronunit/models/NeuroML2/LEMS_2007One.xml") +model = ReducedModel(model_url, backend='Geppetto') +test.setup_protocol(model) +print(model.lems_file_path) \ No newline at end of file diff --git a/neuronunit/examples/geppetto-prep.ipynb b/neuronunit/examples/geppetto-prep.ipynb new file mode 100644 index 000000000..6d5848fa6 --- /dev/null +++ b/neuronunit/examples/geppetto-prep.ipynb @@ -0,0 +1,143 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Demonstration of using `Test.setup_protocol` to rewrite the NeuroML file before sending it to a remote Geppetto server." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "import quantities as pq\n", + "from neuronunit.tests.passive import InputResistanceTest\n", + "from neuronunit.models.reduced import ReducedModel\n", + "# Don't worry about not being able to load the NEURONBackend" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "test = InputResistanceTest(observation={'mean':200.0*pq.MOhm, \n", + " 'std':50.0*pq.MOhm})\n", + "model_url = (\"https://raw.githubusercontent.com/scidash/neuronunit\"\n", + " \"/dev/neuronunit/models/NeuroML2/LEMS_2007One.xml\")\n", + "\n", + "# A new Backend which is just like the jNeuroMLBackend, in that it writes nml files, \n", + "# but does not actually so simulation\n", + "model = ReducedModel(model_url, backend='Geppetto') " + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# Actually do current injection. In the Geppetto (or jNeuroMLBackend, this will write new nml files)\n", + "# For the InputResistanceTest, it should change the amplitude to -10.0 pA. \n", + "test.setup_protocol(model) " + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "# Get paths to the files (same path before and after writing, but these are the ones edited)\n", + "nml_paths = model.get_nml_paths() \n", + "# In this examples there is only one nml file, which is an include of the LEMS file at `model_url`. \n", + "path = nml_paths[0] " + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[0m\u001b[01;32m/tmp/tmpt6muan44/Izh2007One.net.nml\u001b[0m*\n" + ] + } + ], + "source": [ + "ls /tmp/tmpt6muan44/Izh2007One.net.nml" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + "\n", + "\n", + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# IPython line magic to display the contents of the nml file at this path. \n", + "# It displays the correct, new current amplitude (-10 pA) for me.\n", + "%more $path" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "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.6.7" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/neuronunit/unit_test/m2m_test.ipynb b/neuronunit/examples/m2m_test.ipynb similarity index 99% rename from neuronunit/unit_test/m2m_test.ipynb rename to neuronunit/examples/m2m_test.ipynb index 5b54b0c60..1ebccb43a 100644 --- a/neuronunit/unit_test/m2m_test.ipynb +++ b/neuronunit/examples/m2m_test.ipynb @@ -3,7 +3,9 @@ { "cell_type": "code", "execution_count": 1, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "%matplotlib inline\n", @@ -19,7 +21,9 @@ { "cell_type": "code", "execution_count": 2, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "# Assumes imported neuronunit is from source, e.g. pip install -e\n", @@ -31,7 +35,9 @@ { "cell_type": "code", "execution_count": 3, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "# Let the first two models remain identical but change the third one\n", @@ -42,7 +48,9 @@ { "cell_type": "code", "execution_count": 4, - "metadata": {}, + "metadata": { + "collapsed": false + }, "outputs": [ { "data": { @@ -64,7 +72,9 @@ { "cell_type": "code", "execution_count": 5, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "# A new M2M test which will compare the equality of spike counts across models\n", @@ -81,7 +91,9 @@ { "cell_type": "code", "execution_count": 6, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "test = MyTest()" @@ -90,7 +102,9 @@ { "cell_type": "code", "execution_count": 7, - "metadata": {}, + "metadata": { + "collapsed": true + }, "outputs": [], "source": [ "s = test.judge(models)" @@ -99,7 +113,9 @@ { "cell_type": "code", "execution_count": 8, - "metadata": {}, + "metadata": { + "collapsed": false + }, "outputs": [ { "data": { @@ -184,7 +200,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.3" + "version": "3.5.2" } }, "nbformat": 4, diff --git a/neuronunit/examples/model_zoo/LEMS_Test_ca_boyle.xml b/neuronunit/examples/model_zoo/LEMS_Test_ca_boyle.xml new file mode 100644 index 000000000..72b64a6eb --- /dev/null +++ b/neuronunit/examples/model_zoo/LEMS_Test_ca_boyle.xml @@ -0,0 +1,252 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/neuronunit/examples/model_zoo/models-1.ipynb b/neuronunit/examples/model_zoo/models-1.ipynb new file mode 100644 index 000000000..1188dfafd --- /dev/null +++ b/neuronunit/examples/model_zoo/models-1.ipynb @@ -0,0 +1,465 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "import numpy as np\n", + "import quantities as pq\n", + "import os\n", + "#os.environ['NEURON_HOME'] = '/Applications/NEURON-7.6/nrn/x86_64'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### All of these will use the default 'jNeuroML backend'. You should set `backend` in the line where the model is instantiation line to your backend." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## OpenWorm ion channel model" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/rgerkin/miniconda3/lib/python3.7/site-packages/airspeed/__init__.py:505: FutureWarning: Possible nested set at position 8\n", + " KEYVALSEP = re.compile(r'[ \\t]*:[[ \\t]*(.*)$', re.S)\n" + ] + } + ], + "source": [ + "# A model of one ion channel type from C. elegans\n", + "from neuronunit.models.channel import ChannelModel\n", + "model_url = (\"https://raw.githubusercontent.com/openworm/ChannelWorm2\"\n", + " \"/master/NML2_models/EGL-19.channel.nml\")\n", + "# model_url = (\"/home/rgerkin/EGL-19.channel.nml\")\n", + "model = ChannelModel(model_url, channel_index=0)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/rgerkin/miniconda3/lib/python3.7/importlib/_bootstrap.py:219: ImportWarning: can't resolve package from __spec__ or __package__, falling back on __name__ and __path__\n", + " return f(*args, **kwds)\n" + ] + } + ], + "source": [ + "# A test for the ion channel's IV curve (current vs voltage relationship)\n", + "from neuronunit.tests.channel import IVCurveSSTest\n", + "# Made up observations\n", + "voltage = np.array([-80, -60, -40, -20, 0, 20, 40, 60]) * pq.mV\n", + "current = np.array([10, 0, -30, -50, -20, 20, 50, 100]) * pq.pA\n", + "test = IVCurveSSTest(observation = {'v': voltage, \n", + " 'i': current})" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "pyNeuroML >>> Reloading data specified in LEMS file: /Users/rgerkin/Dropbox/dev/scidash/neuronunit/neuronunit/examples/model_zoo/LEMS_Test_ca_boyle.xml (/Users/rgerkin/Dropbox/dev/scidash/neuronunit/neuronunit/examples/model_zoo/LEMS_Test_ca_boyle.xml), base_dir: ., cwd: /Users/rgerkin/Dropbox/dev/scidash/neuronunit/neuronunit/examples/model_zoo\n", + "pyNeuroML >>> Looking at holding voltage -0.1 V, and currents between times 0.01 s and 0.09 s\n", + "pyNeuroML >>> Looking at holding voltage -0.08 V, and currents between times 0.01 s and 0.09 s\n", + "pyNeuroML >>> Looking at holding voltage -0.06 V, and currents between times 0.01 s and 0.09 s\n", + "pyNeuroML >>> Looking at holding voltage -0.04 V, and currents between times 0.01 s and 0.09 s\n", + "pyNeuroML >>> Looking at holding voltage -0.02 V, and currents between times 0.01 s and 0.09 s\n", + "pyNeuroML >>> Looking at holding voltage 0.0 V, and currents between times 0.01 s and 0.09 s\n", + "pyNeuroML >>> Looking at holding voltage 0.02 V, and currents between times 0.01 s and 0.09 s\n", + "pyNeuroML >>> Looking at holding voltage 0.04 V, and currents between times 0.01 s and 0.09 s\n", + "pyNeuroML >>> Looking at holding voltage 0.06 V, and currents between times 0.01 s and 0.09 s\n", + "pyNeuroML >>> Looking at holding voltage 0.08 V, and currents between times 0.01 s and 0.09 s\n", + "pyNeuroML >>> Looking at holding voltage 0.1 V, and currents between times 0.01 s and 0.09 s\n" + ] + }, + { + "data": { + "text/html": [ + "=== Model EGL-19 achieved score Fail on test 'IV Curve Test'. ===\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "score = test.judge(model)\n", + "score.summarize()\n", + "# This test should be a BooleanScore(False) because this made-up date does not much resemble\n", + "# the model's output\n", + "assert score.score == False\n", + "# The raw measure of disagreement should be ~1.5e5 pA^2\n", + "assert 1e5 * pq.pA**2 < score._raw < 2e5 * pq.pA**2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Izhikevich reduced neuron model" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "from neuronunit.models.reduced import ReducedModel\n", + "# The 2007 Izhikevich model with units\n", + "model_url = (\"https://raw.githubusercontent.com/scidash/neuronunit\"\n", + " \"/dev/neuronunit/models/NeuroML2/LEMS_2007One.xml\")\n", + "model = ReducedModel(model_url)#, backend='NEURON')" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "# An input resistance test\n", + "from neuronunit.tests.passive import InputResistanceTest\n", + "# Made up observations\n", + "test = InputResistanceTest(observation={'mean': 100*pq.MOhm, \n", + " 'std': 50*pq.MOhm})" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "=== Model LEMS_2007One achieved score Z = -0.41 on test 'InputResistanceTest'. ===\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "score = test.judge(model)\n", + "score.summarize()\n", + "# The Z-score should be around -0.4\n", + "assert -0.5 < score.score < -0.3" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "# A serial rheobase test\n", + "from neuronunit.tests.fi import RheobaseTest\n", + "# Made up observations\n", + "test = RheobaseTest(observation={'mean': 100*pq.pA, \n", + " 'std': 25*pq.pA})" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Highest subthreshold current is 51.5625 pA\n", + "Lowest suprathreshold current is 52.1484375 pA\n" + ] + }, + { + "data": { + "text/html": [ + "=== Model LEMS_2007One achieved score Ratio = 0.52 on test 'RheobaseTest'. ===\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "score = test.judge(model)\n", + "score.summarize()\n", + "# The RatioScore should be around 0.5\n", + "assert 0.4 < score.score < 0.6" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "# There should be one or a small number of action potentials in this plot\n", + "score.plot_vm()" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "# A parallel rheobase test (runs on multiple cores at once)\n", + "from neuronunit.tests.fi import RheobaseTestP\n", + "# Made up observations\n", + "test = RheobaseTestP(observation={'mean': 100*pq.pA, \n", + " 'std': 25*pq.pA})" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/rgerkin/miniconda3/lib/python3.7/tempfile.py:796: ResourceWarning: Implicitly cleaning up \n", + " _warnings.warn(warn_message, ResourceWarning)\n", + "/Users/rgerkin/miniconda3/lib/python3.7/tempfile.py:796: ResourceWarning: Implicitly cleaning up \n", + " _warnings.warn(warn_message, ResourceWarning)\n", + "/Users/rgerkin/miniconda3/lib/python3.7/tempfile.py:796: ResourceWarning: Implicitly cleaning up \n", + " _warnings.warn(warn_message, ResourceWarning)\n", + "/Users/rgerkin/miniconda3/lib/python3.7/tempfile.py:796: ResourceWarning: Implicitly cleaning up \n", + " _warnings.warn(warn_message, ResourceWarning)\n", + "/Users/rgerkin/miniconda3/lib/python3.7/tempfile.py:796: ResourceWarning: Implicitly cleaning up \n", + " _warnings.warn(warn_message, ResourceWarning)\n", + "/Users/rgerkin/miniconda3/lib/python3.7/tempfile.py:796: ResourceWarning: Implicitly cleaning up \n", + " _warnings.warn(warn_message, ResourceWarning)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Getting rheobase vm\n" + ] + }, + { + "data": { + "text/html": [ + "=== Model LEMS_2007One achieved score Ratio = 0.54 on test 'RheobaseTestP'. ===\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "score = test.judge(model)\n", + "score.summarize()\n", + "# The RatioScore should be around 0.5\n", + "assert 0.4 < score.score < 0.6" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "# There should be one or a small number of action potentials in this plot\n", + "score.plot_vm()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Allen Institute GLIF Model" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "from neuronunit.models.reduced import ReducedModel\n", + "model_url = (\"https://raw.githubusercontent.com/OpenSourceBrain/AllenInstituteNeuroML\"\n", + " \"/master/CellTypesDatabase/models/NeuroML2/LEMS_472424854.xml\")\n", + "model = ReducedModel(model_url)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "No results returned: buffered error, warning, and notice messages follow:\n", + "\n", + "pyNeuroML >>> *** Problem running command: \n", + "pyNeuroML >>> Command 'java -Xmx400M -Djava.awt.headless=true -jar \"/Users/rgerkin/miniconda3/lib/python3.7/site-packages/pyneuroml/lib/jNeuroML-0.8.4-jar-with-dependencies.jar\" \"/var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpjd65xte7/LEMS_472424854.xml\" -nogui -I '/Users/rgerkin/Dropbox/dev/scidash/neuronunit/neuronunit/examples/model_zoo'' returned non-zero exit status 1.\n", + "pyNeuroML >>> jNeuroML >> jNeuroML v0.8.4\n", + "pyNeuroML >>> jNeuroML >> Loading: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpjd65xte7/LEMS_472424854.xml with jLEMS, NO GUI mode...\n", + "pyNeuroML >>> jNeuroML >> org.neuroml.export.exceptions.ModelFeatureSupportException: Feature not supported in LEMS: MULTICOMPARTMENTAL_CELL_MODEL (Model with multicompartmental cell(s))\n", + "pyNeuroML >>> jNeuroML >> Level of support for MULTICOMPARTMENTAL_CELL_MODEL in LEMS is insufficient: NONE\n", + "pyNeuroML >>> jNeuroML >> \n", + "pyNeuroML >>> jNeuroML >> Info on supported features:\n", + "pyNeuroML >>> jNeuroML >> Format: LEMS\n", + "pyNeuroML >>> jNeuroML >> ✓ SINGLE_COMP_MODEL (Model with only a single component (no network)): HIGH\n", + "pyNeuroML >>> jNeuroML >> ✓ NETWORK_MODEL (Network model): HIGH\n", + "pyNeuroML >>> jNeuroML >> ✓ MULTI_CELL_MODEL (Network model with more than one cell in any population): HIGH\n", + "pyNeuroML >>> jNeuroML >> ✓ MULTI_POPULATION_MODEL (Network model with multiple populations of cells): HIGH\n", + "pyNeuroML >>> jNeuroML >> ✓ NETWORK_WITH_INPUTS_MODEL (Network model with external inputs to cells): HIGH\n", + "pyNeuroML >>> jNeuroML >> ✓ NETWORK_WITH_PROJECTIONS_MODEL (Network model with projections between populations): HIGH\n", + "pyNeuroML >>> jNeuroML >> ✓ NETWORK_WITH_GAP_JUNCTIONS_MODEL (Network model with gap junctions/electrical connections between cells): HIGH\n", + "pyNeuroML >>> jNeuroML >> ✓ NETWORK_WITH_ANALOG_CONNS_MODEL (Network model with analog/continuously communicating connections between cells): HIGH\n", + "pyNeuroML >>> jNeuroML >> ✓ ABSTRACT_CELL_MODEL (Model with abstract (non conductance based) cell(s)): HIGH\n", + "pyNeuroML >>> jNeuroML >> ✓ COND_BASED_CELL_MODEL (Model with conductance based cell(s)): HIGH\n", + "pyNeuroML >>> jNeuroML >> x MULTICOMPARTMENTAL_CELL_MODEL (Model with multicompartmental cell(s)): NONE\n", + "pyNeuroML >>> jNeuroML >> ✓ CHANNEL_POPULATIONS_CELL_MODEL (Model with channel populations): HIGH\n", + "pyNeuroML >>> jNeuroML >> x CHANNEL_DENSITY_ON_SEGMENT (Model with channel density specified per segment (aot segmentGroup)): NONE\n", + "pyNeuroML >>> jNeuroML >> ✓ HH_CHANNEL_MODEL (Model with HH based ion channel(s)): HIGH\n", + "pyNeuroML >>> jNeuroML >> ✓ KS_CHANNEL_MODEL (Model with kinetic scheme based ion channel(s)): MEDIUM\n", + "pyNeuroML >>> jNeuroML >> \n", + "pyNeuroML >>> jNeuroML >> \tat org.neuroml.export.utils.support.SupportLevelInfo.checkConversionSupported(SupportLevelInfo.java:144)\n", + "pyNeuroML >>> jNeuroML >> \tat org.neuroml.export.utils.Utils.loadLemsFile(Utils.java:514)\n", + "pyNeuroML >>> jNeuroML >> \tat org.neuroml.export.utils.Utils.runLemsFile(Utils.java:481)\n", + "pyNeuroML >>> jNeuroML >> \tat org.neuroml.JNeuroML.main(JNeuroML.java:576)\n", + "pyNeuroML >>> jNeuroML >> INFO Apr 17,2019 22:06 (INFO) Loading LEMS file from: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpjd65xte7/LEMS_472424854.xml\n", + "pyNeuroML >>> jNeuroML >> INFO Apr 17,2019 22:06 (INFO) Reading from: /var/folders/_j/vg2m860n23d_9ty1h2z9_2880000gn/T/tmpjd65xte7/LEMS_472424854.xml\n", + "pyNeuroML >>> jNeuroML >> \n", + "\n" + ] + }, + { + "ename": "AttributeError", + "evalue": "'bool' object has no attribute 'keys'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;31m# I don't know what to expect here because jNeuroML isn't working for this model and my NEURON installation is broken.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0;31m# But maybe it works in Geppetto?\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 7\u001b[0;31m \u001b[0mtest\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjudge\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmodel\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m~/Dropbox/dev/scidash/sciunit/sciunit/tests.py\u001b[0m in \u001b[0;36mjudge\u001b[0;34m(self, model, skip_incapable, stop_on_error, deep_error)\u001b[0m\n\u001b[1;32m 303\u001b[0m \u001b[0mscore\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtest\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 304\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mscore\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mErrorScore\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mand\u001b[0m \u001b[0mstop_on_error\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 305\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mscore\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mscore\u001b[0m \u001b[0;31m# An exception.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 306\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mscore\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 307\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/Dropbox/dev/scidash/sciunit/sciunit/tests.py\u001b[0m in \u001b[0;36mjudge\u001b[0;34m(self, model, skip_incapable, stop_on_error, deep_error)\u001b[0m\n\u001b[1;32m 292\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 293\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 294\u001b[0;31m \u001b[0mscore\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_judge\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmodel\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mskip_incapable\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mskip_incapable\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 295\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mCapabilityError\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0me\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 296\u001b[0m \u001b[0mscore\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mNAScore\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0me\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/Dropbox/dev/scidash/sciunit/sciunit/tests.py\u001b[0m in \u001b[0;36m_judge\u001b[0;34m(self, model, skip_incapable)\u001b[0m\n\u001b[1;32m 232\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 233\u001b[0m \u001b[0;31m# 2.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 234\u001b[0;31m \u001b[0mprediction\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mgenerate_prediction\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmodel\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 235\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcheck_prediction\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mprediction\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 236\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlast_model\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmodel\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/Dropbox/dev/scidash/neuronunit/neuronunit/tests/dynamics.py\u001b[0m in \u001b[0;36mgenerate_prediction\u001b[0;34m(self, model)\u001b[0m\n\u001b[1;32m 166\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 167\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mgenerate_prediction\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmodel\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 168\u001b[0;31m \u001b[0mst\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmodel\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_spike_train\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 169\u001b[0m \u001b[0misis\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0misi\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mst\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 170\u001b[0m \u001b[0mvalue\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mfloat\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmean\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0misis\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0;36m1000.0\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0mpq\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mms\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/Dropbox/dev/scidash/neuronunit/neuronunit/models/reduced.py\u001b[0m in \u001b[0;36mget_spike_train\u001b[0;34m(self, **run_params)\u001b[0m\n\u001b[1;32m 45\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 46\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mget_spike_train\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mrun_params\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 47\u001b[0;31m \u001b[0mvm\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_membrane_potential\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m**\u001b[0m\u001b[0mrun_params\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 48\u001b[0m \u001b[0mspike_train\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0msf\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_spike_train\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mvm\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 49\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mspike_train\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m~/Dropbox/dev/scidash/neuronunit/neuronunit/models/reduced.py\u001b[0m in \u001b[0;36mget_membrane_potential\u001b[0;34m(self, **run_params)\u001b[0m\n\u001b[1;32m 31\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mget_membrane_potential\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mrun_params\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 32\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrun\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m**\u001b[0m\u001b[0mrun_params\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 33\u001b[0;31m \u001b[0;32mfor\u001b[0m \u001b[0mrkey\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mresults\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mkeys\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 34\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;34m'v'\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mrkey\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0;34m'vm'\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mrkey\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 35\u001b[0m \u001b[0mv\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0marray\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mresults\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mrkey\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mAttributeError\u001b[0m: 'bool' object has no attribute 'keys'" + ] + } + ], + "source": [ + "from neuronunit.tests.dynamics import ISITest\n", + "# Made up observation for inter-spike interval statistics\n", + "test = ISITest(observation={'mean': 100*pq.ms, \n", + " 'std': 50*pq.ms})\n", + "# I don't know what to expect here because jNeuroML isn't working for this model and my NEURON installation is broken. \n", + "# But maybe it works in Geppetto?\n", + "test.judge(model)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Other models you could try\n", + "#url = (\"https://raw.githubusercontent.com/OpenSourceBrain/PinskyRinzelModel\"\n", + "# \"/master/NeuroML2/twoCompartment/twoCompartmentCell.cell.nml\")\n", + "#url = (\"https://raw.githubusercontent.com/OpenSourceBrain/PyloricNetwork\"\n", + "# \"/master/NeuroML2/LP_1.cell.nml\")\n", + "#url = ('LEMS_2007One.xml')" + ] + } + ], + "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.7.1" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/neuronunit/examples/nmldb.py b/neuronunit/examples/nmldb.py new file mode 100644 index 000000000..501371641 --- /dev/null +++ b/neuronunit/examples/nmldb.py @@ -0,0 +1,16 @@ +import os +from urllib import request, parse + +# Example URL including an extra meaningless query key-value pair +example_url = 'https://neuroml-db.org/model_info?model_id=NMLCL000129&stuff=3' + +# Parse the model_id from URL +parsed = parse.urlparse(example_url) +query = parse.parse_qs(parsed.query) +model_id = query['model_id'][0] + +# Build the URL to the zip file and download it +zip_url = "https://neuroml-db.org/GetModelZip?modelID=%s&version=NeuroML" % model_id +location = '/tmp/%s.zip' % model_id +request.urlretrieve(zip_url, location) +assert os.path.isfile(location) \ No newline at end of file diff --git a/neuronunit/examples/reduced-model-simulation.ipynb b/neuronunit/examples/reduced-model-simulation.ipynb new file mode 100644 index 000000000..81865f651 --- /dev/null +++ b/neuronunit/examples/reduced-model-simulation.ipynb @@ -0,0 +1,413 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Using Python and NeuronUnit with two simple models" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
Fitzhugh and Nagumo, 1969
\n", + "\\begin{matrix} \n", + "\\frac{dV}{dt} & = & \\frac{V^3}{3} - W + I \\\\\n", + "\\frac{dU}{dt} & = & \\phi(V + a - bU) \n", + "\\end{matrix}\n", + "

\n", + "
Izhikevich, 2007
\n", + "\\begin{matrix} \n", + "\\frac{dV}{dt} & = & 0.04V^2 +5V +140 - U + I \\\\\n", + "\\frac{dU}{dt} & = & a(bV - U) \\\\\n", + "\\end{matrix}\n", + "
if $v>30$, set $v=c$ and $u=u+d$" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Install (this) copy of NeuronUnit. May take a few minutes. You can skip if NeuronUnit is already installed in your account." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "!pip install -q --user -e ../../\n", + "import sys\n", + "sys.path.append('../../')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Import all of the basic numerical libraries and set up the plotting environment" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "from matplotlib import cm\n", + "import numpy as np\n", + "from pathlib import Path\n", + "import seaborn as sns\n", + "sns.set(font_scale=1.5)\n", + "sns.set_style('whitegrid')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now get model files from GitHub (you only need to do this once):" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "if not Path('Fitzhugh-Nagumo').exists():\n", + " !git clone -q http://github.com/OpenSourceBrain/Fitzhugh-Nagumo" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "if not Path('IzhikevichModel').exists():\n", + " !git clone -q http://github.com/OpenSourceBrain/IzhikevichModel" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Import a model class from NeuronUnit that can work with NeuroML model files.
\n", + "This will work on JupyterHub, but if you have any problems, try: `!pip install --user git+http://github.com/scidash/neuronunit@dev` in an empty cell." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/opt/conda/lib/python3.7/site-packages/airspeed/__init__.py:505: FutureWarning: Possible nested set at position 8\n", + " KEYVALSEP = re.compile(r'[ \\t]*:[[ \\t]*(.*)$', re.S)\n" + ] + } + ], + "source": [ + "from neuronunit.models import LEMSModel\n", + "# Ignore 'FutureWarning' that may show below; that is an upstream package" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Wrap the relevant model files in NeuronUnit classes. I know these are the correct files because all OpenSourceBrain projects have a similar structure: a file describing the model, a file describing a netork of neurons from that model, and a file describing a simulation experiment (and sometimes others). Here I am wrapping the file that describes a simulation experiment because it is easier to \"just run it\"." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "izh_model = LEMSModel('IzhikevichModel/NeuroML2/LEMS_2007One.xml')" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "fhn_model = LEMSModel('Fitzhugh-Nagumo/NeuroML2/LEMS_FitzHughNagumo.xml')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now run (i.e. simulate, i.e. numerically integrate the differential equations with respect to time) each model" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "izh_model.run()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "fhn_model.run()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Extract the simulated results from each model. I know how to extract these results because I opened the LEMS files (.xml) above and saw that the pointed to specific named cells, populations, and variables. For example, LEMS_FitzHughNagumo.xml includes (uses) FN.net.nml, which is the underlying model being simulated. And that file contains ``, which is a population of 1 Fitzhugh Nagumo cell. `fn1pop` refers to that populaion, and `fn1pop[0]` to its first an only cell. The `/V` on the end, below, means the voltage state variable. The other state variable is referred to as `W` but I am going to reassign it to a Python variable called `u` for consistency across models." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "# For each model, create a lookup for the state variables `v` and `u`,\n", + "# whose values are the corresponding recorded entities in the NeuroML file.\n", + "fhn_lookup = {'t': 't', 'v': 'fn1pop[0]/V', 'u': 'fn1pop[0]/W'}\n", + "izh_lookup = {'t': 't', 'v': 'RS_pop/0/RS/v', 'u': 'RS_pop/0/RS/u'}\n", + "# Note: I have no idea why the first cell is accessed with [0] for one model but /0 for the other!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "I've created a function to make it easier to extract the simulation results" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "def get_results(model, lookup, n_time_steps=np.inf):\n", + " \"\"\"Given a model, a lookup dictionary, and a number of time steps,\n", + " return the state variables (and time)\"\"\"\n", + " return [np.array(model.results[lookup[x]])[:n_time_steps] for x in lookup]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And another function for plotting them" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "def plot_results(t, v, u, name, offset=3):\n", + " \"\"\"Plot V and U vs time and against each other\n", + " Offset U by `offset` with each timestep for visualization purposes\"\"\"\n", + " fig, ax = plt.subplots(2, 2, figsize=(18, 12))\n", + " ax[0, 0].plot(t, v)\n", + " ax[0, 0].set_xlabel('Time (s)')\n", + " ax[0, 0].set_ylabel('V')\n", + " ax[1, 0].plot(t, u, 'red')\n", + " ax[1, 0].set_xlabel('Time (s)')\n", + " ax[1, 0].set_ylabel('U')\n", + " ax[0, 1].scatter(v, u+t*offset, color=cm.jet(np.array(t)/max(t)), alpha=0.1)\n", + " ax[0, 1].set_xlabel('V')\n", + " ax[0, 1].set_ylabel('U')\n", + " ax[1, 1].axis('off') # Unused\n", + " plt.suptitle(name)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now let's extract the FitzHugh-Nagumo results..." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "t, v, u = get_results(fhn_model, fhn_lookup, n_time_steps=10000)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And plot them..." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABD8AAAMfCAYAAADRwnz2AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8li6FKAAAgAElEQVR4nOzdd5xU9b3/8dc5Z8r2papAFBQVRUAsIGKLUYy5xhKv5caCJhj1xhjj9afGghVFYiIPLypYwIYmxGtLchOjXkssoIhEBQuixo5Stk895ffH95zZXXaXusvC8H7msdndOWXOzK66857P9/OxgiAIEBEREREREREpUnZ3X4CIiIiIiIiISFdS+CEiIiIiIiIiRU3hh4iIiIiIiIgUNYUfIiIiIiIiIlLUFH6IiIiIiIiISFFT+CEiIiIiIiIiRU3hh4iIyEb63ve+x5AhQ9b48d577wEwdepUhgwZwh133NGl1/TjH/+YIUOG8MYbb3Tp/WyMRx55hCFDhnDFFVd06nkPPvjgwvO+YMGCdvdxXbewTzab7dT7FxERkc1PrLsvQEREpFgceOCB9O3bt91t1dXVazx26tSpzJgxgwsuuICf//znXXF5W6WpU6cye/bs7r4MERER6WYKP0RERDrJ2WefzX777bfGfc444wyOOeYYevXqtYmuautVWlrK/PnzeemllzjooIO6+3JERESkG2nZi4iIyCbUq1cvBg8eTM+ePbv7UoreqaeeCpjqDxEREdm6KfwQERHZhNrr+XHwwQczY8YMAG699dZWvUKi/Vr2sejo48knn2z3PhcsWMCECRPYd999GTlyJKeccgpz585ts9+nn37KkCFDGDduXLvnefXVVxkyZAhnnnlmu9ufeOIJ/v3f/50999yT/fbbj/POO48PPvhgnXp71NfXc/3113PwwQczbNgwDj/8cG677TZc1+3wmLU57rjj2GmnnVi8eDF///vf1/m4hQsXMnnyZH70ox8xZswYhg0bxiGHHMLFF1/M0qVL1/gYbrzxRr773e8yfPhwDjvsMG655RYymUyHPViin+uyZcvanC/qSzJ06NA13j5nzhyOPfZY9txzTw466CBuuOEG0uk0ADU1NVx33XUccsghDB8+nKOOOoonnniiw8ewfPlyJk2axLhx4xg+fDijRo1i/Pjx/O1vf1vn509ERGRzpGUvIiIi3ewHP/gBc+fO5YMPPmDo0KEMGTKksG233XYr7FNXV9fmWM/z+Otf/4rrujiO02b7c889x3333cfgwYMZO3YsH3/8MQsWLOCss87i/vvvZ9999+2Ux3DLLbdw55134jgO++yzD3379mXx4sWcdNJJ/OhHP1rjsfX19Zx00knU1dUxYsQIcrkc8+fPZ9q0aXz77bdcd911G3RNtm3zy1/+kl/96lfceuutjBs3Dtte+/s+t9xyCwsXLmTXXXdln332AeDDDz/kT3/6E8888wwzZ84s3B6pq6vjlFNOYenSpfTo0YNDDz2UfD7P7NmzmT9//kaFOGtyww03MGfOHEaPHs2AAQN44403eOCBB/jkk0/4zW9+w0knnUQmk2H48OHU19fzxhtvcOmll+I4DkcffXSrc3300UecccYZLF++nAEDBnD44YdTW1vL/Pnzee2113jjjTeYOHFilzwOERGRrqbwQ0REpJtddtllTJ06lQ8++IBx48a12/D0sssua/fY66+/Htd12XvvvTniiCPabL/33nuZPHkyxx13HABBEHDNNdfwhz/8gdtvv5177713o69/4cKF3HXXXZSVlTFr1iz22msvAHzf5+abb2bWrFlrPP7pp59m3LhxTJkyhfLycgDefPNNTj31VB555BHOPfdc+vfvv0HXduSRR7LHHnuwePFinnzyybUGMQA/+9nPGDZsWKu+LEEQ8PDDD3Pddddx9dVX85e//KXVMb/73e9YunQpo0ePZvr06VRUVACmkuKMM87go48+2qDrXxPP83jqqaf485//zMCBAwFYtmwZxx57LC+99BLjx49n+PDhTJ48mZKSEgAeeughrrvuOqZNm9Yq/AiCgIsuuojly5dz8sknc9VVVxGLmT8T3333Xc4880xmz57NqFGjOPLIIzv9sYiIiHQ1LXsRERHpJOPHj293Ocq0adO65P7uu+8+Zs+ezcCBA7njjjtIJBJt9jn66KMLwQeAZVmcf/75ALzxxht4nrfR1/HQQw8RBAH/8R//UQg+wFReXHjhhWy77bZrPL6yspJJkyYVgg+AvffemwMOOADf95k/f/4GX5tlWfzqV78CYNq0aeRyubUec/DBB7dpSGtZFqeeeiojRozgww8/5OOPPy5sa2xs5IknnsCyLCZOnFgIPgD69u3LJZdcssHXvzYXXnhhIfgA2G677fjhD38IwDfffMO1115bCD4ATj75ZKqqqvj000/55ptvCrfPmzeP9957j169enHZZZcVgg+AoUOH8rOf/Qwwv3MiIiJbIlV+iIiIdJKORt3uvvvunX5fzz33HFOmTKFHjx7ceeedHTZQbW/KSZ8+faisrKShoYG6urqNnjyzYMECwCzNWV0ikeCII47gwQcf7PD4ESNG0KNHjza377jjjrz00kssX758o67v4IMPZt999+WNN97gkUceKTRCXZMVK1bw/PPPs3TpUhoaGvB9H4BVq1YB8K9//YuddtoJgEWLFpHNZtl5553Zdddd25zru9/9LhUVFTQ2Nm7U42jPAQcc0Oa2HXbYAYDhw4dTVVXValssFmPAgAHU19fzzTffFIKpqBfJuHHjKC0tbXPO448/nt/+9re88847ZLNZkslkZz8UERGRLqXwQ0REpJOsy6jbzrB48WIuuugiHMfhtttuY8cdd+xw3+22267d28vKymhoaFinSoi1+fbbbwEYMGBAu9vXtmSlo8qQsrIygFbXOGfOHBYuXNhqP9u2ufHGG9d4HxdeeCGnnnoq06dP5/jjjycej3e474MPPsjNN99MNpvtcJ+WQUZUQbGmx9m/f3+WLFmyxmtcX7Ztt/vcRc9bR89rFG60fF6jx/Cd73yn3WN69+5NWVkZqVSKFStWdPizFhER2Vwp/BAREdmCLFu2jHPPPZdUKsWUKVMYNWrUGve3LKvT7juqflhfQRCscfu6NCGNzJ8/nz//+c+tbnMcZ63hx7777svBBx/MP/7xDx588EF++tOftrvfwoULmTRpEvF4nMsvv5xDDjmE7bbbrrB05IILLuCpp55a62Na3fruD2t/vtf2s12f53V9rq8zf6dEREQ2FfX8EBER2UI0NTVxzjnn8O233/KLX/yiVS+PzhBVQ6RSqXa3f/311+3evs022wDw5ZdfrtdxG+K3v/0tH3zwQauPd999d52OvfDCC7Esi3vuuYeGhoZ293n66acBOPPMMznjjDMYNGhQq54Zn332WZtjogqLr776qsP77ug5iJ7zpqamNtvaG3/bVaIKoS+++KLd7atWrSKVShGLxejdu/cmuy4REZHOovBDRERkMxC9CO6oAanneVx44YW8//77HHPMMYWmpZ2pd+/eOI7DypUr2x2r+8orr7R7XDT29W9/+1ubbblcrhAodLehQ4fy/e9/n7q6OmbOnNnuPrW1tQD069evzbYPP/yQDz74oM3tw4YNI5lM8tFHH7W7tOWFF17osN9HFBx98sknbba9/PLLHT+YThaNPH7mmWdIp9Nttj/++OOA6SOifh8iIrIlUvghIiKyGYiqBzoaiXrjjTfy4osvMmrUKG644YYuuYZkMsnIkSMJgoDbb7+91bZHH32Up556qt3jTjnlFCzL4g9/+EOrfhy+7zN16tRWU0W62wUXXIDjOMyePbvd7VET08cff7xVBcyqVau4/PLL2w2nKioqOO644wiCgOuvv75V0LFixQpuvvnmDq8n6hEzc+bMVqHD22+/3WVTgtozZswYdtttN1atWsXkyZNxXbew7f333+euu+4CTEWMiIjIlkg9P0RERDYDBx98MMlkkr/97W+sWLGC7bffHtu2GTduHIMHDy68WK+oqOCqq65q9xwnn3xyq1GzG+L8889nwoQJ3H///cybN49Bgwbx8ccf88knn/CTn/yEWbNmtTlm77335uyzz+bOO+/k1FNPZdSoUfTp04dFixbx9ddfc/LJJzNnzpw1NhndVHbaaSeOPfZYHnvssXa3n3DCCTzwwAO88847jBs3jr333pt8Ps/rr79Ov379+N73vsdzzz3X5riLLrqIBQsW8Prrr3P44YczevRoXNdl3rx57LrrrgwfPpx33nmnzTji008/nUceeYQ333yTI488kj333JNvv/2Wd955hwkTJnDnnXd2yfOwOsuyuOWWWzjjjDOYM2cOL7/8MiNHjqSuro7XXnuNfD7PqaeeypFHHrlJrkdERKSzqfJDRERkM7Dtttty5513Mnr0aN577z0ef/xx/ud//od33323VePL559/nscff7zdj/b6Uayv/fffn7vvvpu99tqLTz/9lJdffpltttmG2bNntzs2N/Jf//VfTJkyhd12242FCxfy0ksvseOOO/LHP/6xMP63o3G8m9r555/fJoSI9OzZk0cffZTjjz+eRCLBCy+8wNKlSznppJOYM2cO5eXl7R5XXV3Nww8/zPjx40kmkzz33HO8//77/PjHP+bee+8tjMhd/Tno3bs3Dz/8MEcccQSpVIoXXniBbDbLpEmT+OUvf9m5D3wtBg8ezGOPPcbpp5+OZVk8/fTTvPXWW+y1115MnTq1w9BNRERkS2AFG9J+XERERGQdnX766bz++uvcfvvtHH744d19OZvc559/zhFHHEFlZSWvvfaapqWIiIh0A1V+iIiIyEb76KOP2jT1dF2X6dOn8/rrr9O7d+81Vo4Ug3feeafNyNhly5ZxySWX4Ps+xxxzjIIPERGRbqKeHyIiIrLR/vCHPzBnzhyGDh3KdtttR2NjI0uWLOGbb74hkUgwefLkop8S8pOf/ITS0lJ22WUXevTowddff827775LJpNh11135YILLujuSxQREdlqadmLiIiIbLR58+bx8MMPs2jRImpqasjn8/Tt25dRo0YxYcIEhgwZ0t2X2OXuuusunn/+eT799FPq6+uJx+MMGjSIcePGccYZZ3TYL0RERES6nsIPERERERERESlq6vkhIiIiIiIiIkVN4YeIiIiIiIiIFDWFHyIiIiIiIiJS1BR+iIiIiIiIiEhRU/ghIiIiIiIiIkVN4YeIiIiIiIiIFDWFHyIiIiIiIiJS1BR+iIiIiIiIiEhRU/ghIiIiIiIiIkVN4YeIiIiIiIiIFDWFHyIiIiIiIiJS1BR+iIiIiIiIiEhRU/ghIiIiIiIiIkVN4YeIiIiIiIiIFDWFHyIiIiIiIiJS1BR+iIiIiIiIiEhRU/ghIiIiIiIiIkVN4YeIiIiIiIiIFDWFHyIiIiIiIiJS1BR+iIiIiIiIiEhRU/ghIiIiIiIiIkVN4YeIiIiIiIiIFDWFHyIiIiIiIiJS1BR+iIiIiIiIiEhRU/ghIiIiIiIiIkVN4YeIiIiIiIiIFDWFHyIiIiIiIiJS1BR+iIiIiIiIiEhRU/ghIiIiIiIiIkVN4YeIiIiIiIiIFDWFHyIiIiIiIiJS1BR+iIiIiIiIiEhRU/ghIiIiIiIiIkVN4YeIiIiIiIiIFDWFHyIiIiIiIiJS1BR+iIiIiIiIiEhRU/ghIiIiIiIiIkVN4YeIiIiIiIiIFDWFHyIiIiIiIiJS1BR+iIiIiIiIiEhRU/ghIiIiIiIiIkVN4YeIiIiIiIiIFDWFHyIiIiIiIiJS1BR+iIiIiIiIiEhRU/ghIiIiIiIiIkVN4YeIiIiIiIiIFDWFHyIiIiIiIiJS1BR+iIiIiIiIiEhRU/ghIiIiIiIiIkVN4YeIiIiIiIiIFDWFHyIiIiIiIiJS1BR+iIiIiIiIiEhRU/ghIiIiIiIiIkVN4YeIiIiIiIiIFDWFHyIiIiIiIiJS1BR+iIiIiIiIiEhRU/ghIiIiIiIiIkVN4YeIiIiIiIiIFDWFHyIiIiIiIiJS1BR+iIiIiIiIiEhRU/ghIiIiIiIiIkVN4YeIiIiIiIiIFDWFHyIiIiIiIiJS1BR+iIiIiIiIiEhRi3X3BWwpfN+nqamJeDyOZVndfTkiIiKbjSAIyOfzlJeXY9t6X6Ur6e8RERGR9q3t7xGFH+uoqamJJUuWdPdliIiIbLZ23XVXKisru/syipr+HhEREVmzjv4eUfixjuLxOGCeyEQi0WnnXbRoEcOGDeu0823N9Fx2Hj2XnUfPZefRc9l5Ovu5zOVyLFmypPDfSuk6G/L3SDH+s6PHtGXQY9oy6DFtGfSY1m5tf48o/FhHUWlpIpEgmUx26rk7+3xbMz2XnUfPZefRc9l59Fx2nq54LrUMo+tt6N8jxfjPjh7TlkGPacugx7Rl0GNaNx39PaKFuSIiIiIiIiJS1BR+iIiIiIiIiEhRU/ghIiIiIiIiIkVN4YeIiIiIiIiIFDWFHyIiIiIiIiJS1LaKaS9z587lySefZOHChSxbtozq6mpGjBjB+eefz5AhQ7r78kRERERERESkC20V4cfvf/97amtrOfPMMxk8eDArVqzgnnvu4YQTTuDBBx9k5MiR3X2JIiIiIiIiItJFtorw4+qrr6Z3796tbjvwwAM57LDDmDlzJtOmTeumKxMRERERERGRrrZVhB+rBx8AVVVVDBw4kGXLlnXDFYmIiIiIiHQvz/MgkyFXX4/f1ESQTpNvaMDPZAgaGvCzWYLGRvymJtzGRtymJrymJoJUCtJp/EwGL5OBfB7yeXzXBd8H1wXLwvY87FgMEgmwbUgmoaQEp7KSkupqnKoqnOpqrJ49sfv1w+nXj3i/fpRWVnb3UyNFaKsIP9qzatUqPvzwQ4466qjuvpQ1SmXyLP2ilkH9qqkqT3T35YiIiIiIyGbE8zyCXA4/lcJNpfBTKfz6eryGBtyGBvIrV+LV1uLW1BDU1OA2NODV1uI1NeE3NMDKlbwbj2Pl81ieVwgy8P3mz0FgAo0gKHzYvo8TbeuAH35efcqGBzSFX8cAa/UDbRu7vBynd2/YfnvYfXfs3XcnOXYsZSNGUFJS0hlPnWxltsrwIwgCJk6ciO/7TJgwYb2OXbRoUadfz4IFC9q9vabRZebT39KY8Yk58L0R1ey/WwWW1eZfDxLq6LmU9afnsvPouew8ei47j55LEZHNj+/7+Nms+Uin8RsbyTc24tfXk6+vx12xAq+ujuyKFfi1tXj19dDQYKo2MhlTqZHPm89RkOF5zR8twovCRy6HH76+iEVBRrTN81peXPPtgG9Z2GBem6wlAFnTiFEXiLd9IvAbGrAbGrD+9S946SV8IB1+xGIx2GknOOAAOOooqv7t3ygtLV2PZ1q2Rltl+PGb3/yGZ599lsmTJzN48OD1OnbYsGEkk8lOu5YFCxawzz77tLvtpgfm4wU2l5y+Ny+++QVPL1yG61Rx/kkjiTmaUry6NT2Xsn70XHYePZedR89l5+ns5zKbzXbJmwMiIls6z3VNVUYuh59OEzQ14aVS5OvqcGtqcOvr8VatIl9TY8KMhgbcVIognS6EIOTz+LkcQT5fCCR83y+EEVYQYPk+vu9jW5YJPCyrOayIQg8wt0FzqOF55jbHgSAgCM9VCDmi/dv7bFlm/y58/nzAaef2wHWxliyBJUvg3nupB9IVpbDfGDjhRNhjWBdelWyptrrwY+rUqcyaNYsrrriC448/vrsvp0ONqRxz3/6KYw/ZmYNGDuDAPfsz59klPPTU+zSl81w6fhTxmAIQEREREZFNxfd9vGyWIJfDS6fx0mmCTAa3rg6vsRG3poZcbS1eXR1ubS1+2D/Dz2Tw0mnsKAiJemOEy0qCMEzwfd+EDy0CCAvww5DCCkML2/cJPI/A9wmCgMDzTAVGdGzLkCM8N6udt1WQYdutqjqA5q9b3hZVoK+l2qOzdBR+tHe725gm9uLz8OLzDABW9e5B7N9PhIt+TdVOO3X5tcrmb6sKP2699VZmzJjBxRdfzPjx47v7ctbonx8uxw9g/2H9AFNO9h/jhlBZlmDGY29z8+w3uOT0fVUBIp1qZV2a9z+tIdPkdvelSBHKuz5vfbic0mSMoTv20hI+ERHpdlFw4EdLRcKQwk+lTHPPujrydXUmzKipIffhh3zcowdES0yiMCOfx8/nsS3LnC8MGWzLMsEFYLkufsvgwfPww8qNIFyWEi0hCcKgIggrN6zwNj86drVwBMsqnCf6vhBO2C1eL9i2CURiMRO8RPu2DD/aC0Gi+4vOGx4ThTFrCkLW9l/7Nb2aWZ9XOi2vwAJYWUsw826sWXdTn0zCKacTv/JaSvv3X4+zSjHZasKP2267jTvuuIMLLriAs846q7svZ60Wf7SS0qTDrjv0aHX7UQfsiOf73P3EIn770AIuPnUfHAUg0gmWfFbDlTNeIZ31sCz4rG4xp/1gd1UYSadwPZ8rZ7zCu5+sAmDIDj35r1P3pn+fim6+MhERKTZ+OG3Ed93m5SaZDLmGBoJUCi9sBppvaMCtrTVLTFIps282SxAuVQlaNvy0LPP9qlXkGhoKy01wXTxoDiiCoLlKw/dxfR/LsrBdF9+ysPJ5AsCOgoowSLDC8CAIp6RYQWCahUbVGZbVXHVh22YfANclCLdZrgvxsHtGFFjYdvM5ooAjOk+41IUWb0ZY0f14XvP+tt28byQIcMBc9+p9Qlrev+/jrKVCpL3KjkhHf4Wu69snUc5DNgv33oN77z2kt+0Ll06k9D/PX8ezSLHYKsKPWbNmMW3aNA499FDGjh3LP//5z8K2RCLB0KFDu/Hq2vfpsgZ22K6q3WDjmIMG4/sBM/+0mJhtc+Epe+PYegdVNlwQBMx47G3KSuJM/OkY/ufpf/LYC0v5+Ks6LjtjFGUlbdpQiayXv8/9F+9+sopzfjSceMzhvr8s5v/d+g+umjCG3Qb16u7LExGRzVzg+/iuS5DPmyUn4YhVr7GRIJUi19Bg+mXU1xM0NeGn03hR01DXNb0zwjDDgkJfC8uy8IMA33XN8pKw54Udbi9UYHieCRlyOYJcDsAsXYHCJJQgqqoIj/Uxy1PMLkEh6CgcE4ULmKoFy3EKtwXhNJXAssw218WKxQrbovMFYfWF5fsQi5nPq4UbdixmghfbxorHCRwHJxaDeBwrHieXTlPevz9WeTlWeTlOWZn5XFqKVV5OsqLCjKpNJrGSSexYDLukxFxPLIbjOIX7tBzHXHM4GSbI5czzl8mYkCmdxquvJ6irw6uvJ/fNN/DNN/Dtt7B8OdTWQn09eJ4JVzr4fWgvFFmXl0MBwIrlcPEvSV/8S+xTTsO++Tbi1dVrP1i2eFtF+PH8888XPkdfRwYMGMBzzz3XHZfVoSAI+NfX9ew/vF+H+xx3yM7kXZ8H/voeJUmH807YUyXkssH+9XU9H35ey7nHj2D4zn3I1fXkoH13Zdoj/+SKGa9y/dn7U1GmUcuy4Z5+/TN22b4HRx2wI5ZlsecufbjqrrlcMeNVrvzJaPYask13X6LIWs2dO5cnn3yShQsXsmzZMqqrqxkxYgTnn38+Q4YMWevxn332GTfddBOvvfYavu+z7777cumll7LzzjtvgqsX2fy0DDT8MFTwMxncdNoEGY2N+E1NuHV1BOEY12iZSZDLmQqLfL6wHCSqQggwL46DMLQwdxY0LyNpWXXRIgQJXNfcls/jRy/iPQ87Cj6iySm+b4KKKIRwHBMuBAGB45ilLpiqCMu2TSPSWMwc6ziF67Sg0GA0cJzWS1zCxwFg2TYkEtiOQ2DbOGFoQTyOk0ziVFRgl5URr6rCqa7GrqjA7tWLWEUFsYoKnMpK7LIy7IoKnJISrJIS7NJSE2LYNgsWLGDIZtZQPJ1Ok/34Y7JvvYX79tvw5pvw3nvw5ZfNE2lW47RTQmKvpYDZf3g29h9nk9//AJj5R+JaElPUtorw48EHH+zuS1gvdY05GlI5dtiuco37nXjYrmRyHn98dgnJhMNZxwxTACIbZN6iZVgWHDCi+V/4h4/egaqKBJPvm8/Eu+Zy/TljqShVBYisv29Xpfj4yzomHLNH4d9R2/Uu5ze/OIiJd77KpFmvcdVZY9hzl77dfKUia/b73/+e2tpazjzzTAYPHsyKFSu45557OOGEE3jwwQcZOXJkh8euXLmSU045hd69ezNlyhQcx2H69OmcdtppPPHEE2y33Xab8JGIdJ2oh0aQz+M3NpJdudI0/EylyDc1EYSBRr6hwTQLDQMNL+q5ES43CYIA2/PM0o+oOiLqbxFVZAQBfhBgR1USYQUHto3ruoUApMXFFSaf+EGA5Xnmfiyr1bhWz7LMUo0gMAFILIaVz5tX11Hw4Pvma9s2QUpJCXa45CWIxZqrNjwPu8W5nLCyw4rHsW3bnDsWM8eXlWEnkzg9exKrqiJWXY1TVYVVWUmyuhqrtNQEGclkYV87kcBe2yv8LVBpaSmle+wBe+wBp5zSaltTbS2NzzwDf/0rvPwiLP2EeAevatf5qZn7CgwdgDfmAJwHH4e++pukGG0V4ceW5tuaFAD9epevdd/TjtyNdNblT//4mNJkjNOO3L2rL0+K0AefrmKHbSvpUdl6jPPoodtx2ZmjmHzf61xz11yuO2d/LYGR9fbBpzUADB/cp9XtPSqTTDp3LFdMf4XrZr7GNWeNYfjOfdo7hchm4eqrr6Z3796tbjvwwAM57LDDmDlzJtOmTevw2JkzZ1JfX8+jjz7KtttuC8DIkSM57LDDmD59Otdee22XXrvIxvI9rznUiJp8plJm4klTE1441cRraMBLpfByObJLl7JswQKzVMV1C01A/TBwAJorNcJeF0EUbGCWoATRmFbfN8tSWox3DQCiqg8oVHVEAUdUPdGq0sK2CcJzEYuZJTBhSEE8XlgmEgUthRAjqtgI+20QjzcvMYnFiDkObixGMh4niMexS0qwEwmcHj1wKiqIV1VBVRWJykpiYZARKy/HLi01wUcyiZNI6I3MdVDeowflJ54IJ55YuG3VW2/BrLvhycfgy6+BsNfHataWhfjzXsHZfRs46ni4+/dmuY8UDQNVn44AACAASURBVIUfm6GVdWkAeleXrHVfy7I465hhZLIuc55ZQmkixr9/b5euvkQpIkEQsOSzWsYMa/9dx9FDt+PS8aO46f75XHP3PK752RgFILJelnxeQyJmM7BfVZtt1RVJJp17AJdPf4VrZ87j2p/tzx479W7nLCLdb/XgA6CqqoqBAweybNmyNR777LPPMnbs2ELwAdCzZ08OPfRQnnnmGYUf0m0CzzNLT8LmoF7UK6OxES+Vwm1owG/ZQyObhVwOL5drniziuiZkiCaVRMtIGhvxmppaLUmxLMs0+wxHvQZhtUU0xST6njCkCKLRr+E+VhSQYMadWo5TCEaA5qUk4bKUQpQQXlc0gaUwPjbcZtm2OR9mmUlUleE4julzkUhgJZPU1NTQc9gwnIoKnLA6I1ZWhlVWhhNVY5SUmMqOWExhxibSa8894dbb4NbbcF2Xt6+ZyM5/+wu8u6jVfu0tjWnX/z4G/ZNw5WT41a87/4KlWyj82AwtrzXhR58epeu0v21bnHfiSLI5j/v+911KkjGOOmDHrrxEKSIrajM0pHIM/k6PDvcZM6wfl5y+L1MefIMb7n2dq84aQzK+rv/1kK3dx1/WsWP/6g5Hc/eoTHLDf47l8jte4dp75jHp3LHsukPPTXyVIhtm1apVfPjhhxx11FEd7pPJZPjss8848sgj22wbMmQIf/nLX1i5cmW74YrIhor6afj5PEE2ayo20mncpibcxka8xkbTQ6NFqBHk84UqjSiIiMKEoOXUEMKGob6P73mmiWZYFYJtFxqHEt6/5fvmVWc42jUalxotXSl8jwkeAigsQbEsyxxrWaZaJAxSLMsyzUTDhqCt+nZE34fX6kfLTGIxE2zE49iJBLGSEuzycpzycqyqqkKPjFhFhVli0qIqIzp+1Ztv0n8z648hrcViMYIfnUDVpMkApKf/N9w8Gb5tG1KvdVnMpMtg+m/h0Rdg2LDOv1jZpBR+bIZW1maIx2yqyte9zMqxLS48ZW+yeY8Zj71NScLhsFE7dOFVSrH4emUjAAP6rnmZ1dgR/bnwx3tzy8MLuOn++Vx+5miNwZV18vXKprVWc/SsLGHSuWO59LaXufquuUw+70AGtVMpIrI5CYKAiRMn4vs+EyZM6HC/uro6giCgup1pAj16mOC5trZ2vcKPRYsWrX2nFhYsWLBe+28JttbHVOip4bpmconr4mcyBJmMCTBSKYJUCtJpU5ERjm7F85rHokYVD7ZdaK4ZnhwwgYPv+6bRZ/hWuZXLmWMxS0fsfL7wPYTNPcPRqoHvm0oPx+HrL78sBBRRf42W9+VH1+H7heqRwvWE+xUmpUQBTHjdhNNRrFjMLE+Ix7FKS6G01HwuKzMTSioqTICRTGKF+xH22rDbKwXIZMxHTc0G/5y2NEX9mEYfAI/8Bb7+mp63/ZaeL7+IA8Qc8K3wd7AFd7Xvg9qVBIcN59vD/o1VF123Ca68Y0X9c9oEFH5shlbUpulTXbreZXIxx+aS0/fl+pmv8d9zFlKSiHHAnupYLGv29QrTY2a7degx8929v0M253LbI2/xu4cWcPFp+7Q7jlkkknc9VtSm16mHUe/qUiadO5Zf3/4yE+98lZvOO5ABfSs2wVWKbJjf/OY3PPvss0yePJnBgwevdf/OLH8fNmwYyWRy7Tti/rDcp8jeqS7mx9Ry+omfy5l+Gum0qdSor8draiLIZsmH4UZUXUHYvLMQGyQSUFKCVVVVWJZSCDfCvhtBLgeJhBlPmsmY5pwlJaZCI5831ROJhAlLwvMTfe+65nc66oER9uwoVGoEAV9+/jn9Bw1qXnoSBhtW1FjU80wlhu/j27YJRhwHy3Gw43GsZBISCZxEAjusyCh8DisznNJSnDDQsBMJCJesdPXPqZhEjykgwCVHQB6XPC4ZfHK4ZPBw8cnhkSFHHp8sPnlcPAI88mTw8AjImaALDx/H9E3BxsbBJo6NQ5wSHBLYlBGnhCQVJKmilEpKWfPAh/V9TG388IcA5K+/HH43uc3mjhqnAuz4f39lx3kvwJ9fN41YN7Fi/t3rLNlsdo1vDij82AytrM/Qax36fbQnEXe44iejuequudw8+w0S8dGMGqoO8tKxZSubcGyLvuu4zOr7YwaRyXnc8+Qikn90uODkvbDXZbC6bJW+WZUiCNYtXAOz3/XnjOWyO17myumvcNMvDmLbXmVdfJUi62/q1KnMmjWLK664guOPP36N+1ZXV2NZFrW1tW22RbdFFSBS3ALfx8/nzUc6jZfL4dXX46VSZF5/nW+++govlTLhRyZDEPbiIGy6WQggYjFTsRGGB0E0sjVa9hEGEkEuh++6hUDDy2axHAerpISgqQkwPS6icbHR9354Lsu2TS+M8H4sxymEG4HjEAAxxzHLSsLvC8tXwkagdjxutjmOGa0aj5u+GKWl2GVlpulnRQWx8nLTM6OkxPTMSCRMn40wDFHvjLXz8cNAIodPFpdsGFykyNFEnhQ56sjRgEs6/Jxlxfafs4rZuGTxcfHJ4+OGAYaHqY2IPsLJOwRhxUT7Y2cBvMJXJgLpePvq1Tc2UI5DFRVsQ0/6UUl/+rIzvdmBBBvfhDQ+8UaYeCP5qTfB9Zfh0MESmNVvy6bgiGHwy8vh0hs2+jpk01L4sRlqSOU26t3OkmSMq88aw5UzXmHy/fO5WiMkZQ2+XtnENr3K1quC49iDB5POujz01PuUJBzOPX6E/iiRdi1bue7TqyLbb1sZBiCvMHHGq9z0iwPpVbVhgbBIV7j11luZMWMGF198MePHj1/r/iUlJWy//fYsWbKkzbYlS5bQq1cv9fsoEkFYMeHncviZDG5jo+mxUV9PPmwcSjaLl8mY3hq5nDnO93E/+4y05zX32oj6YLhuoV9GFEj4TU1YYajgh8ta7LIyE2Jks1glJSb8yOcLx3j5PJZlmUagnldYLmLZNoFlmbGwUZPQZLLQQwPPM8tFwvsPfB/bccxSE8cxo1ZjMZx43FRjlJURq6jAKS9n+SefsO2oUWaiSRRmxGJmqsk6d57cOpkgI4dLnoAMObJAmixp8jSQp54cjeRpJEcTOZpwacIrVGlk8HHDrz0gD1hhqGHuIQhrNNyyDGmS4Rab1gs/gvDDojn8sLAKt0UfbTlEAUfHAYnh0TIAcfGBBlwayPI1K3m71Z5gYdOHfuxKf4YxiBFUsmHT4uIX/hou/DVcdxn8901td+joz9v/vhGeegL+d75ZXiVbBIUfm6GGphyVAzcu0SwvjXPNz/bn8umvMGnWa1x/zlh2G9Srk65Qismqusw6V320dPLhu5LJujz6/FJKkzHOOGqoAhBpI5pe1bfn+v2O7di/mmt+Noar7nyVK2e8yuSfH9AVlyey3m677TbuuOMOLrjgAs4666x1Pu7www/noYceYvny5fTta96QqK2t5fnnn19js1TZvBSaiGazeNksXkMD+cZGvPp63Lo6MxmlRfNQL+y1EU0eib62bLtQoeF7XvP0kaYmE0iEoQZBgF1Sgp/LEeRy2GVlhX4fQSxmXlK6rgkioLlhaPgWdtSkM/A8syQkmSw0LbVKSwtNRy3LgmQS2/cL/TNs2zbNPhMJ7JISU5lRXt5crVFSYsKORMIEMWEY0/JvgZjjULbTTpv2h7SZ8sKKCrOkJBtWZuTI04jboiLDo5Es9eE+GbywisMvLDuJwgwTkFgE+HiF6TcQhDFFULjnsBaHADe8PdpmjnAsGwiIEQUPVqvttDg7rY6PwpL2//5bW+SxbnyiYKS5UiTAZxVf8hpf8hrzwlvjbMtg9mVXDlz/u7lqMlx6DYz/d/i//21bjNKeJe/C7pXwpzdhzz3X/z5lk1P4sZkJgoCGVI7Kso0fJVpdkeT6c8z6+WvunssN/3nAGid6yNappiGzQZM1LMvijKOGkm4RgJw8bkgXXKFsyWoasoD599H62m1gLyb+dAzX3D2Xq+6ay4n7r3v1iEhXmDVrFtOmTePQQw9l7Nix/POf/yxsSyQSDB06FIDTTz+d119/nQ8++KCwfcKECfzpT3/i7LPP5rzzziMWizF9+nRisRjnnnvuJn8s0rHA902vjWwWL5XCa2ggV1uLW1eH19CA29CAF1Z3EE44CcCEDNHyE2iukrBt00/DcXAcBz+dNpNLkklzDtclKCsrnMuKxQjCqg27tNRMW/F9Ey6EjUPtZLIQeFBaau7D97HjcUgkTO+PqF9HEJimnrZtAo5w6QmJBPEw0HDKy7HDj1gYeFiJhFmiEo839/KQAhM5RIFEPgwqMuTJ4NKASyoMMhrxaMIlTZ4MAXm8MAAx8YULuHhhVYX5n4UdxhDNAYJHEEYXVli1YYVdOoLwSItoOYoV7g9RRGJCBK9w9S0fSdtAg3a2d6+2V2BCEa/FLXm+4X2e4n2ewtsF3mcQe3IYwzhk3e4kmYQ5f4G334YTDoW6Va23t/vK2YdjRsKNd8KpZ6/jo5HuovBjM5PJebheQGXZxq9lA+hVVcKkc8Zy6e0vc9Vdc7npvAPZftvOaSQkxaG2IUvPyg1bUmBZFuf8aASZnMfsp94nmYhx3CFrb/onW4+a+gyVZfENngw0fOc+XHbmaG649zUefiHNvnu7lCT1ny7pHs8//3zhc/R1ZMCAATz33HMdHtunTx8eeughpkyZwiWXXEIQBOyzzz7Mnj2b/v3VnHxTC4LALE/JZMiHI1+9FSvI1dXh1taaEbC5HEEmY5pyBoFZBuI4rfprRA08CZeiEI+b5STZLH4Q4FRU4GUykM+bcMT3TRVGMmmqPlzXBBNhn4yoYSe+b6oubBs/rP6IenFY8bh5nz0KVmzbVHiEE0xipaU44YSTWEUFTkUFsaiXRjJpenAkEmbpipaeFPhhOODiAbmwb0YubPqZIk8jedLUV73DUj7EpSlcVpINKzNyYcyQL0QZFl5YmdG80MQK/z8o3OaF3TOsMMDwCxFIy6UpJtZoXkQSHW9hh0tQmqORIKwIsXDCx+Vj4YTH27RcvtJ62Ur0dculLmDKIKLqD7vFtpZVIm11ZlzWXvzS3m2FMCQODfyLl7mXl7kPh96M4oeM5Ltrv7MRI2DJSph0KUz7jXnI7f0Z0/Ifn8vPgcVvwo0z1n5+6Tb6C3Iz09Bk1n5WrseY27XZplcZN5xrApArZ7zKlF8cuM7NB6W4pbMumZxHz8r1f1c+YtsWvzxpJNmcx8w/LaIk4XDk/oM67yJli1bTkKXHBoZrkX1335b/d9q+THlgPpPufY2rJowhEdcf7LLpPfjggxu136BBg5g+fXpnXpKsReD7eNksfjptgo36erLLl+PW1JgpKqlUobGoDziW1TzONQo9bNsU/Ps+5HJYiYRpDhqOerVLSwkyGRNYhD0yfM8r7GeFS0kKLy8TCdMw1PNwwl4BQbiPXVpq+obYtgkpLItYPA7xOE5pKVY8jlNeXgg1nLBJaLQtWuaiBqFGFGiYyow8AVl8PHKk8EjjhU1AXVJhwJElIIdLFo98OLkkICAPmHghV7mcWmrwCcLXw9Eik6hyw4QZ0WtlDw+70GvD7OuFx7oEYazgF84RxSDN8URz3UbzfVmFrhvNrDC2sHCwCbBbxCMm+LCItQhfohAjIAgIa02gbfDRsv4kCk5ahiId/541j5Bd2xsgzdvbBhp2B7dbbUbUdnQVHg4eK3mF+3mF++nJLhzCyQxgLW/YXTkFTv85HLkX1Lcz9nj1hz7nTvhwMTzy0jpdmWx6Cj82M/WpMPzopMqPSP++FUwKJyhcMeNVppx3IH02oM+DFJfacElCz6oNDz8AHMfmolP3IZv3uOPRtyhJOHx3n+074xJlC2cqizbu9wvggBH9OXa/njwxbwU3PTCfy88cTUxjlkUkFASBWaqSSpGvqyO7ahX5b78lt3Kl6cORTpuGn/m8ecFkWTiWefFkhZUWBAFeWFkBmEkpQKykpNBnwwITjoSjY5143ExGiZaYhOezSkoKzUPtUvP3VuB5psrDMeM/rXgcJ+y14SST2K5L6S67EIsqNioriVVUmFAjrNiwwwalWzPT78JMIfHCQMMji0s6DC1aNv5swiMX7pPHa3GsiUaaJ5e0bOgZhIta7Fb3ar63bPO9Xai28Aohhfl/L4wVvDDaiJadNNd2WC3uM2gRZlhhFGICDyu8vnghHLGJERTO3bzYxZw91uI2U+FhEeAQD/dyCAhwws+mxW0MC4dsLkUJ1Vg4OMSxKcEijk2SGHFiJLBIhJ9jQClJ4oCDjQM4YQVKVLVihdfg4BWatnrh0p8saRrDyTMpcjSSpQmfFJBtVXtC+Ky1b93+OWivOqSGj3mCyUCc/TiRfflexycYOBDeWwXnnwaPPdR8SR29B/Pmy/DdneHvi80yGtmsKPzYzESVH1WdWPkRGdivimvP3p8rpr9qJsGcd+AGL3eQ4lDTkAHY6HfmAeIxm1+fMYpr757H1D8sJJmIsf/wfht9Xtmy1TZk2WWHzuk1NHKncvoN2J7pj77NLQ+/yUWn7oOjMcsiW53A9/Gamkh//TW5lSvJffstmWXLzMjYxkYzSSXqu+E4hTGxdvjZCvtp+Hb4zngsZvaJxrsGAYSTUaLlKFFVRzQFhVgMO1wCY2Gah1qWZUa+WpZZumJZ2PE4TjKJU1aGlUwSr6oiXl1tlqKUlGBXVJglKvE4X731FtuMHt1tz2t3MhUP+cJn00cjh0+GHCkCsrikcGkMQ4w0kG/RRNTFKoQZzUNYW/ariBaSUNga7d28tMQKX7T7YQhi6jeiJSzRshIvfGFv+moQBglRTYcdRh9WGI/YYZRh4+Di42CFNSBmUQphnUa0VMXGDkMac1/mbHYhSLGxiZEgwAkHyMaBOA5xIImNQ4IqHEqJU0qMcuKUEyt8X4JNghglWCRIkODNT95hdK/9w2vvXjlypKilnq+p4ytq+YoVfEo9X+HTEP4M238JG7Rz/e1VhzTPssnzCg/zCnMYxuEcxokdX9i02XDEsfCLk9rf3jIM+fwjGNsfnv8Eqqo6Pqdscgo/NjMNhcqPjW942p5dtu/J1WeN4aq75nLVnXO58ecHdHqViWw5omaUnfHOPEAy7nDlT0dz1V1z+c2DbzDxp/ux927bdMq5ZctU05Dp1JD138buSCbrce9fFpOMO5x/0khsBSAiRSsIArx0mtyqVWRXriT7xRfkli2jadEiPu3TB7LZQs+NqA9Hy9DC9jyzfCUWM8EEFBqRhndgpqN4ngk3YjGwbQLHMRNY4nHTSLRFLw4fsEtKIAgKwUWstBS7pIREjx6mYqO6mlhlpdmeTJqJKLHYGpeiFFv/DfNy3sUjEwYa+bCPRhqXDAFZ8jSGFRrRchOz1MQsFnGJJpUEYSVEEMYIfiGw8MNKAdMe1GpVERHVDERBQsvqDnOF0SKSqAaD8LONhReGEVEFRSFECayw+sHHvJSK7tEunDMohBo+QYtwI6qNiEKGIKycsLHD+o0EDjFsYtgkcagIA4uyQoiRpJIYSSxKiFOCTUl4TBJnnUaUtGXO0/3BBxDGMdvQg22AthNUmqjjaz7gK97hU94lx7LwJxo9h6tr/Zx0tFTmLZ5lEc+yG4fyff6j/Z2OPhH2+RIO3x2a6ptvb2/ab8MqOKA/vPQF9NDAic2Fwo/NTEPKrCnszJ4fq9tjp95c+ZPRXDfzNa65ey7XnzOWspKuCVtk81bfBZVGZSVxrjlrDFdMf5Ub7nuda382hmGDN2z2umzZcnmPTM7r9Eq24w/dmXTW5Q/PfEBJ0uHs44ZrbbtIkfBdl1xNDdnly0l/+inpr74iv2IFXtiAFN83/TJqavDDACJathK4rqn0AFOd4TjN/Tt8v3As0Dz9xHFMYBJ+HwBWEJiJKI6D7zjEkknsZJJ4RQV2ZSWxqiqSPXsSq6oyTUSjcbBhj45iFgUOfriMwQ8nl/iFUCNLniYCMrikyVQv5Uu+wg8rNMxkE59oJGvzMpPoZWsUbriFKozmyMKjuSFoy8kozVNK/BZfN7Na7ROs1rfCDiOK5nAlWtVgh1cU1Vw4YbhihUtG7MICjyj0MNUeFjFiYWVHEnBwSOJQTowkMSqxKSNBBTHKC1UYDgkckuHSkvhmE0ZsbsqpZmdGszPNVVL1LOc95rGUV0nxpWl6GkB7S2NWDz+itrKmPS0s5nkW8xL7cQwH8P22F9C/P7xbB0ePgnffWPPFZpvgoH7wwhfQu/f6PEzpIgo/NjOpjAk/yrs4jNhryDZcOn5fJt8/34QgPxtDSUK/DlubxrDSqKKTq38qyhJce/b+XHbHy1w38zUmnTt2g8bpypatMW3+fVbRBZVsp3x/CJmcyxMvfkRpMsb4fxva6fchIl3Py+XIrVpF6osvSH/8ManPPzcjZVMpAs8zAQVgB+E8DMvC8n3TcyOfNy+KHacwjQXbNp8TCaKJKDiO6cMR9uTAssyklcC8DLbicexEAqesDLu0lER1NbFevYj16GEqOUpKsEtLTcBRZNUZkSDsE+GHgYZZ6pHDJY0fLjkxFRoposkmJpjIhd0w3LBawizQiKIFN1FPlvKwfqK5m4apjjBLR6Lbmq/FbRFVmKqNKCyxCl0zok4ZUT2H1Wqf1m/DB9jECq1ETWQRC2MMc11RuBL13AgKwQjYYT+LBHF8LGLZgEoGYpEkThkxKnAoI0kZNqU4lIRVGAnsQiVHvIOqBOkMVfRlP45mP44G4Cs+4u/LHyI78BMcWkyAAdqvBGn7s3mVP/Eaf+Zofs4utPM3xp/nw//7KTx+b9ttLTOXbAa++x146WtVgGwG9Gp3M5PNeVgWGzwWcn2MGdaP//rx3vzu4QVMeeANrviJGghubZrSeeIxm2QXTM7oUZlk0rljufS2l7k6HLM8sJ/WPW5NCuFaaeeHH5Zl8dOj9yCT83jk/z6kNBnjxMN27fT7EZHO5XseuZoaUp99RsP775P+7DMyy5cT5HIm1AjDC8u2zQSUsAeHFy1nsW2CIChUcgTxuAk4bNssWQHsWMwsbQlHwAKm/0ZJienBUVFhem/06kW8b1/iYTWHU1pqRsEWWcBhQg2XABcfNxzdmsejCZ9cWKmRDm/LhiGBWaYSVV9EE0aiCgo7fEkZdc0w9+MXQomoPsO2XMykFDus+mheHhIdA9Gkk2jZQuuqjeY2obQINuywbWdUeWG1aPdprtVuURkShAFIdE7T2DOGFS4XsYmHvTBKcCgjTilOYbmJ6ZFhEcchQXr52+y8wyhVZmzG+jOYESuOYp+B+7CCj3mFP/MFb7EuTVK9Vl8HPMHtVDOA07mQUlYbFvHbWTB4F/jt5c23RWlaq5OGAciLX0J19QY+KukMCj82M5mcR0li040nO2Tv75DKutzxP28x9fdvctEp+2j9/FakMZ2nvAtemEZ6V5cWApCr7nqVKb84SGOWtyJNafOHbkVp1yzjsyyL/zx+BOmMywN/fY+KsgQ/0Jhlkc2Om8mQ/uIL6hcvpv6998gvW0auqQkrCEwD0rBJqBWNmY2WqoAJMVr04ii8NHYcE2xYFlYiYZathOGIFY/jJBI41dXEq6pI9u1LfJttiIff26WlOIktv9+ZeZHvhuFGLmwEapqERmNco34bZtqJB+TC9p1eeA6I+mZE4YYdTicJWtxuPueJggc/7MthzhBVS/iFzhzNrUVbLkNprtpoFnXYCJcjhVUZq3NWC1goXFfUZyMWNh01/TIs4pjpJCa8iFOKFTb8tCktTC8xFRmxsO/GuoVeph+Hgo8tRR924lguAGARL/Mij+LS0GKPtf/c61jGbVzKgfyI/Tm09cb/vAz6fAeuGL/mk+Sa4Ps7wf99BuX6W7i7KPzYzGRyLslNvPzkB/sPojGVMy8eSuOce/yIol+zKkZjOt8l78q3tF3vcq47Z38uu/1lJt5pApBeVZoytDVoTJvKj/LSrvt3mm1b/OrHe9GUyTP90beoKIlz0F4Duuz+RGTduKkUjR99RP2bb1L33nvkVqzAz2Sag4sgMJNSomCDsPw8nJJC1JPDtrEc86aQHQYgdni7HY9DIkG8vJxYz57E+/ShZMAASnr3xqmuJlZWhrOFjpqMmoWa4CIfNg6Nlp6kw1GuWXyyYQCSD3ti5AnC3hSEi02iThhRvUVzh41oeYlTqPYwwUaCgGyLqMImCKs2zHljRD03rBatQKOJJ1FdSBR8mGDDAmKFEbB+2Gkj2hotfYnqO0wnDrsQTFg44RKSJA5JbEqIhctMTKARLTFJtDgmpqUmG8l1IZ+HbBYyecjmIZWBnGc+MmnwfPN1Ng9uDvI++BZYXvgbGP7yxeNQ4kAiCck4VJaaj6oyqKzcNFNhh3EgwziQFXzKX5lNDZ+12m4iQaed24wXeZz5/IMJ/D/KaRFgnHg69OkJ5xzd9k5b/glUvwqO3g2e+3wjH4lsKIUfm5ls3lR+bGonfG8XGlN5HnthKZVlCU77we6b/Bpk02tKdW3lR2TgdlVcfdYYrpzxKlfd+SqTzztQU4a2As09P7r2Zx1zzJjlq++ay+8eXkBZaYx9dtu2S+9TRNry8nlSH3/MirlzaVi4kOyKFfjZLFY4EjYIAtNE1HVNmGGZF85+WPVhxePmpWq4hCWwbdOrIww+7JISnMpKkj17Eh8wgO0OOojkNtsQq6oiVlZmGp9uAZr7a5gRrS5ZAjLkEl9QQxyXxrCBaBYv3McPqy7MkNSoaac5W3MfDPM95AjCSAEymNki8fAcLpCE8P5N5YQDhSUuJpyIAhMrjCCie4IEhHUgdhhqRE0+o84cdqH2IwDPKVR1UDhXSdjO0yw3MSNaE2HzTzOKNWoAaka4mp4ZZtlKRxM9ZHWeZ0KLdA5SWWhIQVMG6tLm86o6qG2CmkZ4f2kfks9AUxOkctDYZPbJuSbUyHnghh+eZwIP3zXtdXzffB/4Ufva1h9Ay9my7bJdiFuQiEN5KfSqgL49Ycd+bwbhIgAAIABJREFUsFM/2H0n2HMwDNrBhCgbqw8DGc8V1FLLU9zHV7wXbmn90threygpapjGlRzNaezBPs0bDv0h3P8snHG4+d50zW1rxRdw/Eh47J8b/0BkvSn82Mxkc16X9F9YG8uyOPOHQ2lM55nz7BIqyuIcd8jOm/w6ZNNqzOSp7sLJQi0NGdiLK34ymmvveY3r7pnH9eeMpSSpfwUVs6Yo/NgEAVsy7jDxp/tx+R2vcON987n+nP0ZuqM6q4tsCpkVK1gxdy6rXnyR9Kef4mYyBJZ5WY1lmXGzQYAdTVMBU+0RNSq1LPO2b1h1ajkOdiJBrKKCRM+elGy/PaWDBlG67bbEe/bEKStj5cKF9Bg5shsfdceiqo2gMAUlHwYaKfI04ZPGC4MNM9XExcfDrfwXDeTC3hjRko6oEahFy0UkkMMEEQE+mTBgSBCQC5eGOES9OqLuF6ayIxZ+H1VyRAtKnPB7M8LVDs8dhSwWiRYRSxDuF90SC/tnOBB+bSoxSvgmlaQPI8OqjFIckuH+caywosN8KNDoiO+b6otMDtJ5aEyb6ouGNNSmobYeVjXBqnpY1Qh1DVCfCvfLQS4ML7JhVYaXh1wAgQd+YAKNXLYXMdt8H3jN9xt9DuzmoMP3mwcGW4Af7b+hD9A1x2YDc40NOVhWB/wLXlzYdvcSC3bYFvYeCgfvBT8YA4MGbdhd96AH/8GvqKWWJ5nBSj5vJ/Bofl0W0ByIPM5sFrOIkzijedf9D4P7n4MJ31vzHS99C/7zhzD9Lxt24bLB9MpjM5PJut02dcWyLH5+wp40ZfLM/NNiKkrjHD56YLdci2waTak8A/pUbLL7G7nrNlx82j5MeWA+N973OhMn7Ec8VlyN5aRZVPmxKaqLovu59uz9+fXtL3HdPfOYfN6B7NhfjcVEukrD0qUs++tfWTVvHvkVK/DDaSt2+NkDHMfBj5a1hNNVbNsmCCew4DhYsRhOLIbTowcl22xD2Y47Ur7zzpRsuy2JqiqzvGUzYmoaTANRE2Jkwx4bKXLUAemw/0aGqMmoVTgumk3S3MIzOicQ9tJwwtvSBIVQobmCIzqnacLpF9p7mloNH4s4FGKKGBDHvLxMhoGKF+4TNoglGuMKzS9pzYSTGDGCsDmoqdRIYJMkRkWhp4aZahILe2jEWgUaX6QX0ANVE7fk++ZFfiZcStKUM2FGXZP5qGkyIUZNo/m+vgn+P3vnHd9Gff7x991pe9tx9l4QsskOBEoKJOxZWlbZtLRl/SiUUTa0lD1bKElZKRDK3hQIhQQChIQwshOySMj2tubd/f74fk+SbSnTjmTr+85LL0d3p9MjW5bvPvc8n09tWGxfH4NIBKIxCMdEx0XMBNPpwrClGGHLzgtLLIvacuzJREprYrkp9DGQQ0jJbiy2s40GmiX2b8sNnG0bP6ZZSdb+4t88CNmwdJ24Pf9+YlXXYjh0PPxqMkw6cNeeqphizuJq1rGa6TyMST3O+FUyQvhICHVLmc/tLOZirqEQaeo/7hB4/F24YHLTJ0o+5P30Lbjnarjijl0rVrFHKPEjywhHTbwZGHtxMHSNK07bn/pglIdemE+e3824wZ0zVo+iZakNRlrUjyEV44d05g+/GMaDL8znnmfnceUZIzGUyW6bpLY+is9j7NUUqeICL7dcOJ4/PTyTG/45m7/94UA670WBT6HIBSoXLWLd9OlUffUV0epqNE3D0jQhekjDUsvtxtA0IXjEYmgyJlZzu7E1TRiSFhbi79iRQL9+FO23H96OHfEUFWXF+Iro3ojKSNcwEUKY1GBSK/02QliEQAoRjpRhNRj9AHH25vhuOMklIWzZSWERkUKBB003sTHjnRma7OAQe3VEDacfQwgfNhoGXimM2IA/yWvDIwUQS57GueSQjCfJE8MtuzaSk00cXw1/kpjhliMw6oJFYyxLiBGRqBQz5KhJTRAqa8SYSWVdQtyoCcmujCCELSlm2BCNQNRMGh8Ric6On6zoukAIGCaA7MKISaHDMmWXBrKDI2l7R6Qw5TKkUIINpg0xU8OlScHETtIcNNCtxP6Scbo/Wu4bm2KZ0O6aEoMft8CTr4sbQImvJ+ecCFf8Gjrv5KlMF3rwf9zFl8zgY15t/BRpyozyILdwGr+lN7Jr/oBJcN9LcPlJiQ1THW5P+xv0HQTHnbFzBSr2GCV+ZBmhiElZUWavcLhdBteePZrrH/uMO5+Zy03nuxnavzyjNSmaH9u2qQtGW9yPIRWHjelBbTDKv95YQL7/G35/8lBlstsGqdsLhrqpaF8a4JbfiJSh6x+bzZ1/OJCyIv+OH6hQKLZL7Zo1/PCvf7Ft5kxi1dWJLg8nhtYwsDQN2+XCsO141KxjVKp5vXjLyyno35/8oUPJ790bb1kZRoY6O2zZxC6iXyOY1BGhGpNaTGqkF0dYiiCx+PVeS9p1Oh4ZWjzw1UTDJbNUwmjSH8MmjI2NgVd2djiGo5asQnZdaIlxEtGh4ZXb6egE4nKKjjAN1+RIi5BXAGn0ifTIECMmnnh0qxPjqsc9NISg4SSlKAS27IiIxMStXnZoOKMmXy4NsKxedmUExbL6IATl9qGwGCsxQxDThOBgmglhwRk3sR3xwZLn8yZYuriPFCDiy5O9NZzHaYn9WBbYmhxPcbY3QRNvJ7BkBweODS7xBgZdA02zxdSZnbRebmvJACanWwQ70ZThOMu0qAiSTKo2k1TLTKioDXDv03Dv0+A34FdHwU2/g+7dd/w0o5nIUA7gaR5kK445acMM28av+xke5VCO4wAmiAWTToSbHofbLtj+k910JvQeCoMH77gwxR6jxI8sIxzJ3NhLMj6vixvPH8s1f/+U2574gtt+O559epRmuixFMxIMx7BsyPNl5qDzhJ/1paY+wn8+XEZBwMNZR+2XkToULUdtMJIRcQ2gW4cCbr5wLNf941Nu+Ods7lAmuwrFbhMNBlk1bRo/Tp9OeNOmuOihaRqmpqEZhhhvsSx0txvdMNANA1wuXF4vno4dKRw8mOKRIyno2xd3QcFerV8MhkRlSoro4IhSTZRKYtRjS3NRsFIMpmgN9gLCF0MYlorRE3EhOkZC1HAEDifDxMTAj3Q7kV0UtuzWEF0bGjZ21Cs9OESXh42TzeJCl0MoohPDiyE9NHQC8qtY5sInOzWcbZWoAbJDwhSdFeGIEDNCUWECWhsUIkZ1vRwxiUIwJEZN4oaf8qtlw8bNJRT/SFwssJI6L2xbiAWWnC0xna9OR0ZSF4Vly/W6eKxpgR1LGi0xhWCRPMKiaYnODs0SSSomQsCwQXSDyMdosknH0sV2hgamAR7Z7eFynscAl554PYYunst27pPkdaGJ53L8QGy5TLO3M/6yPWUkVRfH9kj1JKn232i7oAlPvApPvAQFPrjsPLjlsu0/lRcvF3Al3zObN3khzdM27IT6gLfYzEaO52Sx4JTzYfM6eOympk+QfM3v3FHw0RbIV52qLU3mz7IVDQhHMpP2kor8gIebLxzHnx6eyc1yfr5Hx8JMl6VoJmrrnSSOzHUanXnEAGrqo7w4Yxn5fjcnTeyXsVoUzU9tcO+kCaWjX7cS/nzuGG56/HNufvxzbv3tePzKZFeh2CUqFy5k4e23Uzl/PrYlLjtrmiau+so4WpeMn9VcLmzDwHa7cZWXUzR8OOUHH0zBvvvizsvb4XM1ByYRTOqJESRGJTEqiVCFRZAYQWxMTGLyGq6GFc8ysaTYYYH01UCOlWh4ZFIKMnHEwiQihQUNXcbM6jhZnXp8REVIJYF4fWJExY6vE8uEL4dm+fBQJs1BA7jkKIoQNnyyW8ODiH1Vn2VO6ojToRGMyGQTOVZSHRIjJtX1UBsS4kUwIkZJwrGEb4YtT9xNeSnfthLLbCkUxM0+bbAtDVt2a9ia2J+uCXHFkuJBVAoczj5NS05sOPtHdlGA6Oxwuiu0hDiCLgQL5+TeNiBmiWVakqblshLiim1IUUQKLs75tS5zjp2JMh1waYn1YaLke6XbiyFeg0sXkbQuL3jl/90e8Bjipuvg84htMeT+7ETXSFT+fMJhcYtEoF6OBdXVQV0Q6urFLRiG0M60j+xs50eqbaTQUhOCWx8RtwMGwVN3Q58+6R86iHH0ZDCPczd1VEF8Vw3P15zRonl8xWa2cgG/ESt+fyOsWQbv/DuxcWM7kVgYztgfXl26Ey9GsSeoT84sI5ShtJd0lBb6uPU3cn7+sdncefEEOpQGdvxARdZTF5JmlBnq/ABhsvvbE4dQF4zy5FsLyQ+4mTS2Z8bqUTQv9aEYZUW+jNYwpG85V505kr8+NYe/PPElN5yvTHYVip3BsizWvPgiy+67j9DGjWi6jm2a8fEWTdOwDQNd10HXhY9Hfj55ffrQ4YgjKB0zBl+7di1Wn3DkqMeklghVRKggxjZi1GBKnw4LU1qByhQZmXrijKlYsveCePqJJsUOCycNxcCFHff0EGd5NpbsqnDJLg2v7ORwejvccRtTsX+X7NrwoMvEE4M8XOTJBBQfGm42VH9PB0bJMZTcHAU1ZZyqI0xETDFGUhtOjJ1UyRST2pDYpj4kTrRjclTFlOkhpiVEAjtJ1HCWx70tTOKdF85Ih9OpEYvJERBdCi3S+DNqaURtKRzYossjebwkJvdjA4YtOi8M2bWBLgQTy06swxTCgWUkRBY0qXlIcQXEY1xJbwtNE0KIroundgv/YHxu8OiQ7we/HwrzoNALeV4oyhO3wgD4vWIbrxuWL13F2BHD8HvA6xFxsq69fJYYiUBFBazfBivWwLK1sGwNLPwBVm6AzVVC3NnlbhFIbdhhwqffQN/DoEt7mHoHTPpZ6ofnk8/l3MQ7vMhcPm+yXggfiR/OWlZyL3fzf/xRLPjbNFi1BJZ+lb7GdcvgpnPgpid2+mUpdh0lfmQZoUhmDU9T0bEsTxgIPjKLG/8pBJDCvRSPqmg5QmHx1yPTV8INXePyU/enLhTl7y9+Q1G+l7GDOmW0JkXzEArHMv7+Ahg7qBOX/nIY9z33Nfc99zV/PH0EujLZVSjSEovFWHz33ax56imiNTVouo5lWSKW1vHvkGktumHgKiykeNQoup50EsVDh2J4mvcYQQyOCKEjxDYibCJUPJ/1LMUiJKNhxZYy00KKB46UYSIiXs2450HimreNLUUSxxTUirto6Ii0FMdg1Il9NUCOrghJxTEEFWMnomsjD4N8mYwSkI/3SK+Q1Md5uu1FJ7uSbZoDx6siIk1BIzER2RqKiA6NWmkOWh8SgkYkKoxAw1Hx/6jTceEYfSaJC04CSbyJQnZtOF0VTgpKXJCQwoIt1znjIzHZdaCTeHzc00GOgOhOhwZSrJCChtsj/u8y5KiICMvBjInRE8sCW/5KWDHxGgzEiIthC9HD0ECXXw1Nig86BLzg80KBF4ryoTgABXlidKM4DwoCQuzI90OeBzxuIWrs6q9gpMKkU4bt/Twe6NBB3IanCQiqrITPvoYZc2HW1/DtEgi6aChupHMmTaZRl8m6TTD5XCjLh6fvgyN/nvphR3AyPdmXl3iygQYj3m4Nf68rqeCv/IVruFYsmD4HDi6H6i1Nd+x8HL31JIydDJN/uRMvQrE7ZP6oVBHHNC1ipoUvC04WGtOjUyHXnzuG6x/7jFunfs5tFx2QVR0qil0nGBF/HXzezP8c3S6da349imv/8Sl3TZvL7b8dz749lcdMayeYJeIHwMSR3amsCfPEmwspK/Jx3rGDMl2SQpGVRKNRFlx/PWuefx4rFMLWNIhGQdPQddHHoGlSKMjLo2zcOHqdfTaFgwaJLpBmIEYYkxrCbCXCT0TZTJh6TIJoxMRVVs9WorLDwjEeTXR1WLLLI1nklPmf8W4NR8TQ40alwnlDl90aCdcOYR7qxsADeHATQKcAFwHc5KPhwx332kiIJG0Z207ErIajovMiGhPiRb30y6gLwfwlhSypE/4awbAYB4lFRUeH021hIsYl4saccv9oYmxEsxMmpJpc5nhsoCU6Nhzzz/jICokRE8dA1JajGVbSCAiWFDPkcxoQ97b0kHi8Kbf3GDYuV2I8RrdFR4LTqaHJTgy/T46PGOA2ICDFiTyfFDG8UJAPRQEo9IvODL9PfA145JhJBjowspniYjjyEHFzCAbhjRnw/Hsw43OoEiFMCaIpdpRqVCYGWyvhqHOgUxm8ORX237/pZgMYxB/4Mw/yl7ijT0MDD4EJ1FLHLdzKDVwvFv5nAUzq0HSnyYfi1/8KhkzY+YgaxS6hfp2yiHBUaIjZKioM7F3GFaeP4G9Pz+GuZ77imrNHq4jSVkwwLP4yZMvJqc/r4obzxnLVwzO5ZeoX3HnxgXRtv3dN8RTNSyiSPeIHCJPdLVUhXv14BWVFfo4/eDtDvgpFDmJZFgtuu42VzzyDHQqJhdJ9UZOJLKZt4zYMCoYOpf8ll9Bu3Lg9Ej1E30WQKJWE2ECQdUTYik09FpF4QKyTU2HL0RVdi6Fhyh4NPW4tqsmOjWTLUmRvho0hh1Nc8jGiS0R0WwjzUSGCeHFTgJsCDAoxpPeGSE9xS5GjbR7/OF0aMRnfGowmDEJrw0LcqJMjKEFpGhqOQSwiOzTk4y1bCBU/bvYS9YplLsfYU4oPTnqITSLhBMTyGIkxFRDrpOVM3PTT1qTQkTTGosnOC1vuXzcSYoguWzksXXRcxHct/TBs+fqRvhWWJjs+bOGh4ZPdGGZejM7FYjzE6xLdF3leMUpS7Id8KWb4PULo8HnA4xLbysYpRTPi98MpR4kbCD+Rf0yHB56o5cetfnCTWgBJplG3yE9bYcTxMGo/eH86FBU1XF9MMTdwJ/dxB5VUNtFSkrtCwoS4hdu5geugfXuY8jH85mCxMt3HyAWj4a0fd1C0YnfInqNSBaGI+FXJFsPTVBwwpDMXHj+Yx175jsde+ZaLThyiIkpbKaEsEz8Aigu83HzBOK586BNufPxz7r54AiWFmfWMUOwelmUTiphZkV7loGka5x07iK1VQaa+/j1lhT4mDO+S6bIUiqxh2ZNPsvLxx7GDwaRL6OISuW3b2LaNt7iYnhdcQJ9zzsGzm8kEJiHCbCXMBkKsIsQWotSSOANxpAVHvBC+HeJEWGauaOJMVkgcztiKATI21pazB7pcnvDjcGHglbGvebgpwk0xLvIxCMgODyGCtCVsW3RpxEwpVliiMyMUFcaTNWER6VoXFuMoUUuagkbFto4o4HRd6Ekxq5qWED00WwgGhi26OyKWRjgqtg+KQJz4OIltJ7ownP3oTmeHnRA+NCvxnHZS7KppixERyxk1kT9kJy3FhvhOYojnMrWEb4YmPTI8JIw8fS4xalIgOzPyA5DvFWMmASloLFywiXGjuuA2RGeGOgzOLvLy4I/nwiFD1zBiRDmP/hv+9jisWic3aGysmqoLxBK3Od9C8QC48ndw55+bbnY5VzOVx1nNiviyVGaoYcLcxl/5M9fAiIPgor/Ao9emfxHb1sGt58D1yv+juWlbn+ytnJAcQ/Bm0clCKo4+sDdbKoO89NFy2hX5OeXQ/pkuSbEbOOJHNp2cAnRql8cN543l2n98yk1TPuevvzuAQAZNWRW7RzhqYtvgz4KxqmQMXeOK00ZQVTube5+bR3GBl8F9W86UUaFoLWyZP58lf/kLZm0t2Da6KXM7HY8P2ya/Tx+G3Hkn7ceP36V929hEqSLIeupZToi1xAhiEoS4v4bTwUFcvHBGWgAs2Vou/ukkukCE54YcyJGPF8KHMBYN4KIAN8V4KMFFgezk8MoRldZ/5po8fhKzZHJGRAgZtWHpp+HEu0akMagpBIuoFDWczgjnvNBO8rSIJ50gBATNgFCMePSqkyxiyiQS05RpJ4jH6UjDUFt0TjjihCHFDGxxcd7p3tAN2W1BoruDJEEjHu/q1G3JxBE9kVJiaOB2CbHCK0dNCr2Q54dCnxA4Al6xzu8VnRkel3jMjjozfsqzyPM34w9Q0aL89nRxq6qCa+4SXSENWjNSGag2Ekju+js8+i+Y+QoMHdpw3XlcwKu8zDzmpvViFSMwQe7gHq7mCjjvGvjyA5g3o+nGzmH520/CwSfDQUftxKtU7CzZddaT44RbQeeHw1lH7ce26hDPvLOI0kIfh47unumSFLtI0Hm/ZdnJKUD/7iVc/etR3PqvL7jjqTnccP5YXIbqE21NxMW1LOoscvC4Df58zmiuengWtz/xBXf8YQI9O6kYb0XuEovF+O6qqwht3Igtz3RNy8Kly5wU26Zw2DDGTJ1Kfved+3tvYxNmC3WspIZFhNiARZ0cTTGggcxhouOSHRzOGiM+0pIYUdEQh64atuVGCBzCbFSIHPlS5CjHRSFu8lu1yOEYb0akqOGIF/UhqJbCRl1QjKXURYXAEInJeFF5FqYjz+PsxAhHDCl0OFe8pdARMoU3BQhfDtMWnRCmTDCxbeFfYcrn0OX4SbxzQz6P4YylIK5/a7pIH7GlaOGSddhSOLFM8X/TiXTVEimgGuI5PS6RZuKWAoXPDfk+KPALYSPPKww/fT5Rs9ctH2OI7g7VnaEoKoK/3yZu/34JLrsFttSk2DCVYWoUaqIw7FA4/zR4/IGGq4/nRALkMYtPmgggyfcrqeJ+/sFlXAT/eB9+3g5qKxIbND5kuvYE+G8lBFTSZnORfUelOYwjfmRb2ksqNE3j4lOGU1ET5qH/zKek0MuIfVMY+CiylmA4hq5lr8fMyAEd+MPJQ3nwhfk89MJ8LvvVcDVi1YpwDHWzaawqmfyAh5suGMuVD87kpsdnc9fFB1Feoi7lKXKT1S++yKY5c8QZsJOraYuwVzdQOHw4Y6dNI6/Djv/Oh9lGNUuo5juibMCkHkfOkO4hOEMpyZ4cIrHFhRH35dAwcMutDHQsOYoiYmK1MJQyEhdleCiS6Sq+VmM26nRTxGQKSjgKm2oMFv0oOjZqQiIJxfHViEQTooZjBmqZQjzQddHNockuimSfjJBMIcES3R66JrwnglFxUuYWScVEZKyrpiUiXp3xFNNOTJW4NGGf4JICh9sl1uvSSBQ9IWzYFrhdVnxbTRNdGU63iMsRM2Q8q88jRkzyfELYCHgh4AO/O+GZ4XaJ12Nk56GLopVw+kni9slMOPUKWL9BrkhjhJrMlGfhxdfh20+gW7fE8sOZhAcf7/Pf+DJTfr4ls4FNTOUZztPPhMe/hDP7pS/UjMLlk+Cxmbv0+hTpyc6j0hzFGXvJtjGEdLhdOtecNYpr/v4pdzw1hzsvnkCvzkU7fqAiKwiFY/i8rqwWFA4b04MtVSGefW8x5cV+zjgiTfaZIutwopSz+fOsfUmAmy4Yy9WPzOKmKbO58w8TyPOrEStFbhGLxVh8770QDguDB5CzCjqYJq4uXRgxZcp2hY8YYepZwVbmEmQZMWqRQxLxIRUr/n+nF0HHiieraGjSdBTp0mHjkikrflwU46ETPtrjpRSDfDZXLaCUkS383dl9nDSUeLSrHDupknGulbXCU8Pp5jAtWPZDET3CQhDRbDHC4US3Grq0IbCkUaglOiosS4y0+N2iAyMUEdt6DeGxYVpC4HDMSHVDbGeRiFV1xltcesLcE014YFi2ECaiNnhl54bHkOeIUoBxEk10J9lEE4KG1wt6fZghvcX4SYFfiBo+t7jvcYn0E5euBA1FZjhoAqz7Ej6YAb+8BLZVN9ogaeQqmcoq6L4/PHQr/OG3ieU/42A0NP7LB2lHYCxgGT/wGv/luL6Hw6UPwAOXpt5YBxbMgtemwnHn7fLrUzQle49Kc5BQK+r8cAj43Nx4/liuuP9jbpn6BfdcehClyqCyVRAMx7L6xNThV4f1Z3NFPdM/WErX9vn8bES3HT9IkXESaULZ/XnWq3MR1541mhsfn82d077ihnPHYKgRK0UOsWnmTGqXLUt0fDiRGpYFPh+Db72Vkv6pvb2iVFHB11TyOWF+wiKCjRyVkf4dhhxqcXw6hOAhrobqcbHDJYURDwZF+GiHl2746IibIlzkx+NnHbJhjMWUiSihqOjcqA4KYaM6JP5fHxYdFlFTjJHEkCkncuRE1xJChvDH0Ig6nR22EEc8Mq2kJiQEAxCdIS4DfDrUJ3VpmKYcNTGESOEIFyCFCemDYZtiuePn4dKEYOGkq7j1pM4NTdi+5MvRE0NGtvo9oisj3yMjWj0JDw23s60O8+fXMGLEXv/RKBS7xKETYev38OQzcM51JLo9Uo3AmImvF18Lb74H776SWH0wB2Fi8yEfNhFAnN1ZwExmU0YxB/7qEpjxAnz3adPncj7m7roADjpx916cogHZf+aTQ8THXrJ0DCEdpYU+rj9vLH96eCa3/esL/vr7A1vda8hFQhEz609MQYxYXXTSUDZsreeB6fNpXxpgv15lmS5LsQOCWez50Zih/cu56KQhPPyfb5j6xgIuPH5wpktSKPYaP0ybBsGgdLS0kqIybApHjKDnqac2eUyIbWxlFpXMJsKWeMSsYzaqxUdVNCwMGUSrYWAAHnlPR8OLQSE+2hOgN34646EUA39WiBtO90YoIoSImqC4VQahuh5qI6KjIxqFiDQddbooDOmNYSX5V1hSpbCk8adpC+HE7xInQ0HTIBYT3RD1YfEY3SXMRbUkoURzRAwt0XUBQuDIM2RfjSYMPmPyx+kknNiWMCx166Iup/PC7xGiRUAmm+T7Id8tuje8bhnzKkUNZ9RGoWhrnH2muP36D/DMyyk2SNHO8d7H0K43rPg6EYk7kYOpJ8wsZsW3i6UYgXmV/9KRcvo++AEcWgym84tPoxhcG648HC56dA9enQKU+JFVJKJuW9+PpXeXIq44fQR/efJL7ntuHledMRJdz/yBiyI9wXAsa/0YGuN26Vxz9iiueOAT/vLkl9xz6cF0KFXmT9lMKMs9PxozaWxP1m6s5bVPVtC1fT5Hju+V6ZIUihYnFoux6dNPE+MuyXg8DLj00gadaEKVAAAgAElEQVSjkRFq2MIMtvE/ImzDlqMtAHbcmLSh4CE8O9xSHHHhwoOXDvjoST598NIRF3kZEzucCNhITIyP1ARF10ZlnRhRqayXka0RISREozIy10hEvOrJyShI89CYEAqwoC4mTDh1TXSC6IbwUpEasfDTsKS7iQ5WTCzzeWRCiktsbyH+X+gWNQP4jEQiitM47DXEdh49YfyZ5xVjJwGv+H/Ak1jndQsRxdCVMahCAfD0w3DDZTD+aNi8VS5sHJHrEIOtW6C4F3z1PvFOp6M5nCD1zOWblA0kDo/yb67xXUzZXW8IgSMdS7/CP/MlVCvVntE6jkpzhLDj+dEKrsanYuygTpx91ECeeHMBz7ZfzBmTlT9DNhOUnh+thYKAhxvOG8MfH5zJLVM/566LJ6gI3CwmGJLiRysSc885ZiDrNtfy2Cvf0aksj+H7tM90SQpFi1KzciXRjRvFHavhkb1WVkano48Wq7DYxpds5iXqWZska+giclSmsAijUrccbDFkP4iGQR4BOpPPQPLohZf20sx072FJ/41gVIgc22qhoga21InUlKDs7oiZYnxEk90Ntp2IeI1LPZoQSzSE4BCOCZ8OryEu2EZiiNhVTSSoYEuhRHZhOJ4Zhi46MEyZouJzWeiyIyPfI8ZlNJ14No5LF90ZbpfYh8eVEDMK/ULQyPcLY1Bn/MQlb0rUUCh2jb59YdNiuOpGEXW7QzPUGIw8BJ5+FM48TSz6BcezlQqWsyblcziNJHfzT/467kr4+Rnw4bTUBenQ9dW/wgXXQV7ebr4qRes5Ks0BwtHWOfaSzAk/68OPm2qY/v5SupYrf4ZsJhSJUVLQuvxZurYv4Jpfj+KGx2dz5zNfcb3yZ8hagpHWM/biYOgaV54xgj89PIu/PT2Huy45iG4dCjJdlkLRYlR8/TWEQinXlY0ahdfrJcQW1jGNSj7HJIhjSOr4d2jostMDhMOHSw61+MmjK0UMoYABeGnfxLejJbBtEf1aHxEdHNvqYGutiLSsDYmOjGAITBm3ii6EDUfkMLREdKsZFQKDrSVGWvwemYxiC2EChFiiyxEUy06IIpYtxl/cLrFM14RIEZPTRV6XMAxFiiA+w6IokBg/8XnkCIoUN3wu8HsTnRpu6auhUChajjtvhjNPhtGTGn1cpmnn+PVvYcVKuOk6cf+3nMPt3E8FVfFtLMAmcb4XIsz9PMVlNz8Dn78FdRUNdyp/z90A1x0H93+wpy8rZ2k9R6U5QChiomuixb+10tifoVO7PPbpUZrpshQpCIVjBNq1vo+Aof3LuejEITzy4jc88eZCzj9uUKZLUqTASXtpDb4yyQR8bq4/dwxXPPAJt079gnsuO4iCgCfTZSkULULV8uXxkRfnoqbTIFA2bhzVfMsqHiPEGixsOcqSOGh3xAxdCh4GLly0o4ghFDOCAN1btMPDNGUsbFAIHJuqxa0qCMGwSDuxnQhY2VFhOu6gmjh3cdkiCUWz5WKXFCdIinqNJTovHDSZqGLZQoxwRvRdOnj8iX245Z9ZQ6aneN3g8woho9AnbgU+IXQsy6tm9AghuLgMJWwoFNnC4MEQXA8HHQkzP2e7IzAAN98OGzbAow+J+9dxGX/iNkxS2oYAsJqfeJdPmHznO/CHsfHPKRofRs3/EGa+AROO2cNXlZu0vjOfNkwoEsPrye7o0Z3B7dK5+qxR/N/9H/PXp+Zw3+UHt7oOg1wgGDZb1VX5ZCaP68majTW89skK+nYr5mf7d810SYpGBMMxXIaG29W6xA+A9qUBrjtnNNf8fRZ3/3suN5w3FkN5GCnaIPWrV2NaFrbdsJ/b0HVifatYym3EGlytFMMumjTu0+JH5gYBelHKgZQwEi/FzV6raYnEk8pa2FQDnyzLZ54UOsJhIWCYpui0MKT3hilfliZVDOfwKmYnRkks6ZeBM15iJzxfvS75GDk24ogRPrcYibFMcW7idhGPhvW5pJ+GF4p8UBiAIjmS4nMnxlGMFKMom9eYFPib/VunUCiaiU/ehjvug2tupakA0qgT5LGpsHkTvDRd3L+OS7iFB7e7//f4jH5DT6XPpF/DB0+n3/COM2H8VpUPvRu0zjOfNko4YuJrRTG326Mwz8O1Z4/mygc/4c5nvuLW34zHpS5hZBXC86P1vt/OPWYgP6yr4qEX5tOjYwG9OhdluiRFEqFWEqWcjn17lnLhCUP4+4vf8Nx/lYeRom0SqapqInwAmLpFRccZFOCMfUl1QPQ/SM8PoQrk0Zt2TKaEUbhonjN32xZpJ9tqYUMVrK2AnyqE+FEXESMoGzYG6KRL81FkdCwiejYqR1nQpEmoJsQTQxcdG5r04HAbQghxyZ24dSGGeB3jzySPDg0hXHgN8MpY12K/GEcp9ENRnhQ/3IlxlFZ+LUuhUKTg6svhoDFwwOSkhWlGYF5+A446Gt56Ewop5CJO52H+3WQ7C1e8++5hnuP2ax8h8MmrEKlOveNwFTx0GVz20B69llyk9R6ZtkHCERNvGxE/QCTA/OGUYdz77DyeVOMJWYVt24QisVZlRtkYl6HzpzNHctl9H/PXJ+dw72UHka/GE7KGYKR1GeqmYvLYHixdXcH095fSv1sJowd2zHRJCkWzEg0GU6/QAH8Q8JHovQY5cY6OjpdyyjmOcn6Omz1L37IsqKqHzbWwchOs2io9OsLSWFR6Z9hyNMWyxViJKf0y7KQODxvRwaFrSZKNTEKx5DrNSES/eqRIoSOEEZ8UMPJ9EPAJgaM0H4oDsnvDIzpC3C4lbigUucr48bBpBfQZCjW1aTaSIVpvvweTjoT33oY+9OIEDuMVHM8OHbNR0pUJPKA/zzU3vwTXHNZ0v8615DcfhhOvgO499/wF5RCt+8i0jRGKtO4rpak4ZEQ3lq6p4LVPVtCvWzEHq/GErCAcNbHt1hNDmo6SQh/XnDWKa/4+i3uencf1545REctZQihstvr3l6Zp/PakIaz6qYp7np3LfZcdTOfy/EyXpVA0H+lapm2E22d8Ol3DET403BRzEN04Ez+7JwjaNlTVia6OZZthxQbYXCe6PSxbGoWSiHDVtaSIWQADbFvDlCakhp4kz8iXpGnCf0OXEa5YQvAwZIqL3ytEjMIAlOZBWR6U5InklDxpKqpSUhQKRTrKy2HLKhg4ApavaLSyUXr4f9+HY46FN16HgxjDUlazgB/S+n/8RAVvjduXo0YeCV+9nVhhQAOt5JbjYcr8PX4tuUTrPjJtY4TaWOeHw3nHDuKHdVU8+MJ8enQqpGenwkyXlPM4ZpSt/co8iPGEC44fzD9e+pbn31/CaZP2zXRJCsRYVWszO02F121wzVmjuey+j7n9yS+5+5KDWr2oo1A4uPxpxlRssGrDiF5uR/iw0CmhK+fQgSMwmrjwbZ9IFDZUw7INsHAdrKsUaSyW062hyYhZ2YlhaoljfEsTy5zEFN2S4/aGOJA1tITXB4iSdTnWkucFj1uIG+3yoV2BEDvyZXqKR/p3KBQKxa7i8cCy72DCoTDrM7kwzQjMm+/AqafBc8/C+ZzCddxLDZEUW4oPpPeYzeBb7qf7se+S1mF11Tfw0YtwyMl7+lJyBvVxn0WEI2arjrlNh8vQufrXowj4XNz5zBxC4TSfCoq9RlD+DNrCySnAEeN6MnFkN55/fwnfLNuc6XIUSE+ZNtLJ1r40wFVnjmDtxhr++cp3mS5HoWg2PKVp0tgsiG2OiP+gASYGHenHTXTm6J0WPupDsHg9PPc53Po63P4GPD0LvlopTEtDUQhHhTASiopzBtMW/hymlYicRRMXPF3SdNQwhD+HxxDdHW4X5HugtAD6dIBxfeCYYXDmeDhrApx3EPxiDEwcBEN6QNd2UJwnUleU8KFQKPaUmR/AcUfKO01tlGQ3HTz/PFx+uVh0NRek2FDHQounwtwbeA0ufmT7T37/eXIGULEzqI/8LEIYnraNk4XGlBT6uOK0/flxUy2Pv/Z9psvJeUIRR/xoG+83TdO46MQhdCnP595n51JVG850STlPKBJrM+8vgGH923PKz/vzwZw1fDzvx0yXo1A0C/ldu6ae67Ahti4M8hBcoyP9uJESBu9wn5EILFoHT82E61+Cv74Fr86FHzaKTo9wVCSzhCMQMYV3h9P9gSXEDs0ZV5EmpF45uuIyhOFooQ9K/RH26wwTB8Lp4+Dsg+GCg+GMA2DyMNi/N3QrFyKHx63GVxQKRcvy6otw9mkpVpg0aNy4/wG44w4ooIBzOREnMQsMTLQG2kkUm6nHdYROfVM/qQGEq2Hqtc30Kto+SvzIIoTnR9u4Ep+KYf3bc/LEfvz3i9XM/HpdpsvJaZzOj7Yktvm8Lq46cyQ19VHuf/7rlAkGir2HGHtpO+8vgFMP34cBPUt55MVv+GlLXabLUSj2GH/XrmlbH+oX1yCO2PPpx58pZvuJR+u2wktz4Kr/wI2vwcvz4IctUF0vxI6QBWFTdHdETdHZgS1HVWxhVuoyRDeHoYFHE54cfh+0K4QBXeDnA+DMA+A3h8BJQys4/QA4dBDs0wU6FguDUtXJoVAoMsUTU+D8c5MWpJlWueZaeO45GEZ/RsvP1tS9Gxrz+IHvbr6v6SqdxGzgy3fC1q27XXcuof5EZBHhaNv0/EjmtEn7sk+PEh5+cT4btqqTh0zheH60tZPTXp2LOPeYgXy1aCOvz/wh0+XkNKGw2SY8ZZIxDJ0/nj4CXde4a9pXRGNpjmoUilZCQZ8+Ym5EYpPo2LYWhDEwaMeplDI85eOjUZi/Cm59Fa54HqZ+LLo+aoIipSViSsEjBpYJtuzsMKSRqNPh4ZG+HC4D8r3QvQxG94GTR8HvJsIlPxejK5OGChGkfRH4VTeHQqHIQh5/DM45S95Jdx3OhtNOhy+/hDM4Gr80lG6K+Hx+tPdSIgf/AtuFMDpy0fQs/q4z9rT0nECJH1lEWzU8TcZl6Fx5xkg04O5pczFNdfKQCYJy7KWtnZwCHHVAL8YM7MiTby5gxY+VmS4nZwm20U629qUBLj5lGMvWVvLvdxdluhyFYo8o7tcPAhYWIpwgJm9RwFoL0I0enNXkcZEIfLQIrngOrn4B3l8kzExDMTnSEoNYVI6hy5EW2xYHnYb07/C4RMxsvhe6l8AB/eCsA+H/JsPlh8Hp42FcPyGE5PmV0KFQKFoP/5oCxx+bZqWd+DpmDGzYAJdzdooNDUwSn8uPX3lsOi9Vwfx3Ycm3u11zrqDEjywi3AajblPRoTTA704eypI1Fbz8v+WZLicnCcXHXtreyammaVzyy+EU5nm4//mv1dX5DGBZdpv2MDpgSGcmje3By/9bzqKV2zJdjkKx2/jLSrGLrJTt1mYllGyYhDvpiqRpwszF8Id/w02vwpzVUBsWHSBRU46zyPl2WxdRtdjCqNTtkp4dHijPh2HdhVfHH4+Aq44U4yyje4vxFa9nb30HFAqFomV45SWYcGCjhSk6QQYPhrJoMSdyEI73h4VBrNHm3/o28+2hZ6d/Qh245/Q9rLrto8SPLCFmWsRMu02ejKZiwrAuHDCkM8++t4TVP1VnupycIxQRh7pttdOoMM/D708exqqfqpn+/pJMl5NzRKIySrmNvr8Azj1mIOXFfu5/fl7cQFihaG0EWY/dN027dT1EZyei6Zesg8unw59egq9WQU1YdHgETYjaYqzFtITgYetiFN0jI2h9XuhSJDw7Lj8cbjoRLp8szEq7twNvuo5vhUKhaMV88gnst5+8k2YEZssWOPhg+Dlj6EQxkM4qROeZ84ZhFnVKvSMNWPc9zHp9z4pu4yjxI0sIt/GT0cZomsZFJw0hz+/i/ufnEVPjL3uVcKTtGZ42ZvTAjkwc2Y3/zFjGsrUVmS4np3DEtbYsfgR8bi45ZTjrt9Qx7Z3FmS5Hodgt6lhKYLQ/9UoTts2aSygEj34E5z4JMxYIA9OIHG2JxGRaiwmWE0MrzUr9buhYAocPgqsmw60nwYUTYf+eIoFFoVAocoFvv4F27ba/zezZcNVV8H/8avsb+uClP/6m4TINGqSPP/Lb3SkzZ1DiR5bgXDn0tuGT0cYU5Xu56KShLP+xipdmLMt0OTlFvPPD3XZPTgEuOH4wxfleOf6iMtD3FrnyeTa0fzlHjO/J6zNXsOAH5bKuaH3UsZq8cQFhnpeClTM+43fPwAPvwaYaCEah3hSihxkTFzI1GUOrS0+OQp8QOC4/HO78BVx8GAzvBQWBvfWqFAqFInswDFi+bMdJVHfdBe+/FuA0Dm20RqgbNiIR5oNRxVT0G5JIyG283+qf4PXHmqn6tocSP7KEcA5cKU3FAUM6M2FYF55/fynrNtdmupycIRQx8bgNdL1tO8jl+91cfMow1myo4cUPlcC2t4h/nnnb/ufZOUcPpLwkwEMvzFcCm6LVEeJHAuP9kJ96/abvl/DRN0HqTQhF5VhLDEwbkJ0emi0O6tsXwInD4c5fwe0nwyEDoaRgr74chUKhyEqKiuDbnfAiPf546LtxCD0oI1ndkFZKIL/ec1U6N1XJ03/ak3LbNDkhfmzYsIHbbruNU089leHDh7PPPvvwxRdfZLqsBoSjuXElPhUXHDcIj1vn0Ze+xbbTZUIpmpNQG03iSMXIAR2YMKwL/5mxjJ+2qHjlvUEoB8aqHPxeFxedOIR1m2t59eMVmS5HodglomzDX+qDrk3X1QNarJaytZ+J8RZTJLlYgGYJwcMGOhTBWePg72fC5UfCPp12fIVToVAoco2BA2H69B1vN2oUXMrJgPi8TXVZZUO3jiwddUD6nUSr4Pnbd6vOtk5O/HlavXo1b731FoFAgLFjx2a6nJSEwk7nR9s/WWhMSaGPM48YwPxlm5n1zfpMl5MTiCSO3BA/AM47diAuQ+OxV5TAtjdo64a6jRk5oAPjBnfi+feXsmlbfabLUSh2GpswYOMb1/TYwwZcROmx7E1iTreHE1urQZEPfjECppwFvzsMupTt5eIVCoWilXHKKfDHP25/m7Vr4aJf5/FLDkzjkSq6tu+9dDIpjzicQ6/pt4tcckUDckL8GDVqFLNnz2bq1KmcdNJJmS4nJYkZ+dw4WWjMEeN70adrEVNe+476UDTT5bR5QpFYm/djSKasyM9pkwYwd/EmPv/+p0yX0+bJxTG+848bhKbBP1/9LtOlKBQ7jYaGCyg+oiR+wGwjuzvkNn1//IhY0hG4ywX794CHTocbjoeuSvRQKBSKneauu2DkyO1v88wzUP3qCMqbzCTqOB/WsZISPj/6MLFIk6tcJD68rSD8+/rmLL1NkBPih94K+i9z7UppYwxd43cnDaWiJswLHyzNdDltnlCOdX4AHHNgL3p2KuTx176PR7EqWoZcGntxaF8S4JeH9ueLBRuYu3hjpstRKHYKHQ8AhT/Lg/xEi7WF7PAA2tWswFu5CWzwe+CccfDE2TCyd8bKVigUilbNrFmQn8ZryeHEE+GcyhMRioa4Wehx41MTeOacSdToWmrjU4DX74dQqHmLb+VkvyqQIzieH7l2QppM/+4lHDKiG6/P/IFNFap1vCURYy+5c2IKYBg6Fxw/iM0VQd6c9UOmy2nT5KqYe/zBfelUlseTby7EtNR4lSL7cVEIgL/ED70ShnqQ+L+LevqtfptCP9x+PFx/HOSr5BaFQqHYbbxemDNn+9vYNpx8UAkTGQgkROnkz2l8Pt75xRHpd2JFVPdHI3Lr7KcZ+P7775t9n3PnzmXpcpF0snTJQjauzd0fy5AuMT6eZ/Hgvz/lhHGlu/z4uXPntkBVbY9tFdXk+43tfr/a6veyX2cfz723iHJvJQHv3tF/2+r3Mh3LVtQAsGTR96xt5sSXbP9eHrCvlxc/3caTL81kWO+8TJezXbL9e6loeby0px7RHe053E9sfrDJNjoWA1e9yJ//cTaTBu39GhUKhaItsu++8MADcOml6bf57jtY9ZefYVy7IKXxKWi8c+qRHPbS+5RYKWwDNODtB+DUmyGgVGtQ4scuM2jQILxeb7Ptb+7cuYwYMYIf61YAlYweMZz8gKfZ9t8aWVu9gJf/t5xzTxhN7y5FO/0453up2DH6+x/Sobww7ferLX8v23Wu5pJ7PmLpFj/nHdvyR/Jt+XuZjh8qlwJVjBk1Ak8zJli1hu/l/vvbfLPmE2YtCnLG8QdkbYJXc38vw+Fwi1wcyDY2bNjAlClTWLBgAYsXL6a+vp6nn36aMWPG7PCxV199Na+88kqT5UOHDuWFF15oiXJ3iJcOAAQtKDqylPr710GSP57TvzSy+isO6R8Bcvv4RKFQKJqTSy6B116DGTOSlyYH28J118GbvzyMF/u8n2IPOhjw4plH8OunXqfJGaoBEIXnrofz7mnm6lsnauwlSwjnaJt4Kk7+eX/y/W6eenthpktps4QjsZwbe3Ho0amQn4/qzpuzVqrxqhYiFDHRNXC7cu9PjKZpnHP0QLZUhXhLjVe1OfY0PS4QCDB9+vQGt9tvz1wcoYcuUO/CsiFveB60T6xzPPN0gPqtrP/oowxUqFAoFG2bd95J9v+waDTYAsDvf96L7hSDrZMwPRXnjDYw68QjqPZ5xQe3JlclH+a//QjUq2NeUOJH1hCKxNB1DZehfiT5fjcnHtKPeYs3sXRNRabLaZPkouFpMr86fB/A5pWPlme6lDaJkyakadqON26DDO7bjuH9y3nlfyvi5q+KtsGepscZhsGwYcMa3Pr169cCle4cAXpQFXVjY2MYoB+Q+J31IY6h/QCxGMsy1J2iUCgUbRmPBxLacurhltWrYdvNk8By1I3E1o4XyAunHU29o4k0OfwKw4s3N2/hrRR1pp0lhOXJaK6eLDTmyPE9yfe7VfJLCxGKmDndZdS+JMDEkd1574vVVFQrF+zmJpzj4hrALw/bh8raMP/9YnWmS1E0I60hPW5X8NKBitoCLNtCA8qOK4tH3kLD4+d1M2Zg28rIV6FQKJqbkSPhyiu3v83DNxXRfkkBQDzxJZkvjjuUSu92RhPffFglv5BD4se7777Lu+++y9dffw3AnDlzePfdd/n4448zXJkgFDGzdjY8EwR8bo49qA9fLNjAyvVVmS6nTREzLWKmhc+bm2MvDidP7IdpWrzy8YpMl9LmCIVzL02oMQN7lzGwdxkvf7ScaExFKysE9fX1jB8/ngEDBnDIIYdwxx13UFdXl7F6tlbo/LipMzriYDpvoh+SrLb8SdvG1q1j/ezZe7lChUKhyA3uvBO6dUu1xgRiQIynjvoZkGowBsDglV8dR9rhFrseXrtrj+ts7eSM+HHppZdy6aWXMmXKFAAeeughLr30Um6+OTtagHIxenRHHHNgL/xeF//5cFmmS2lTODGkuX5lvlO7PA7avyvvfLaS6rrIjh+g2GnE2Etuv78Afnlof7ZWhZjx1dpMl6LIAvbdd1+uuuoq7r77bv75z38yefJkpk2bxllnnUU0msKlfy8wbz0sXr2fuGODP98Pw8VdC4gm36JRlj33XEbqVCgUilxg5szGS2IkrKdh48Z8ip8bleKR4pjri2MPoyJdyp4GvHYPWKmlk1whZ862lyxZkukStos6WWhKfsDD5HE9ee2TFWypDNKu2L/jByl2SFh6EHiV2MbJh/Tjf3N/5P0vVnPSxMzN3bc1QpFYzotrAMP6l9O3axGvz/yBw8f0UGONOc7ZZ5/d4P6ECRPo1asX119/PW+//TbHHXfcLu1vV9N1UkUbf/hNId/UDuTEmIbXLQ6wy35RzJYPK1NOni968UUKGr2OTNIW45rVa2odqNfUOmiNr+n88/OZMqUjiR6F5HFDjftP68mZE+dBYcPH2QjR+pVjjuHcF19NjC1qSbuKVbHmkSuoHX9Gi9W/O+zNn5M6+8kSwjnuwZCOI8f35NWPl/PO7FWcecSATJfTJlCdHwl6dCpkcJ92vPXZSo4/uA+GMhxuFkIRE78S19A0jaMP7M39z3/Nt8u3MLRfeaZLUmQZxx57LDfeeCPz58/fZfFj0KBBeL1Ngg1Tki7a+JmlsHBhHtGYD487iAbkH53PxoJKqEmxo23b6GTbdB45cpdqbQlaQ/T1rqJeU+tAvabWQWt9TY8/Dh99FGXFilSG6VEgwLLLJtDvuYZtIk4/x+xTj+TE11+ns5m6w2Ofec/Cxfc1a817QnP/nMLh8HYvDqgj/SwhHFUGganoWJbH6P068u7sVUSiam6+OQiFxYeper8Jjj6wF5srgny5cEOmS2kzKDE3wYRhXSjM8/Cmir1VpMAxEM2UkeqWOqiIdGZLdTm2LS4Q5pUEMIameUAkwqJp0/ZmiQqFQpFzfPhhujXib8bnz/fAWOkDhOjR+AzpnROOTu/9UbcJ/vf0nhfZSlHiR5Yg2sTVldJUHHNgb6rrIsycvy7TpbQJnM4PNfYiGDOwI+Ulft6ctTLTpbQZ1OdZAo/bYNLYHny5YAObtqU9FFHkKK+//jqWZTF0aDq1oWWpD4uv360dhK0lmqtLf1Gc9jErX3ut5QtTKBSKHKZHDzfXXdd4qSNxCCemlw85CGINh2IcZhx/BNXbm7T9zy3NUWarRIkfWUKuR49ujyH92tGlPI/3v1yT6VLaBGE19tIAw9CZPLYn3y7fwoatmUtdaEuEIia+dIZbOcjkcT2xgQ/mqM+wtsDOpMdNnDiRiRMnxu+vW7eO008/nWeffZZZs2bx8ccfc8cdd3DrrbcyfPhwjjzyyL3+OiDRJv3VkglYscT9ouPyoCD1Y6Lr1/OjSn1RKBSKFuW22/x07OjcM2nc41G9ugRzVkmjR2mAAR4PM446NH33R8UK+Ob9Zq64daAuzWUJYRV1mxZN05g4sjvPvLOIDVvr6FiWl+mSWjWhiDP2on79HQ4Z0Y1p7y5ixldrOW3Svpkup9UTVgbODWhfEmBo33JmfLWWXx22D7qujE9bM5deemmD+w899BAAXbp0YcaMGSkfk6727rgAACAASURBVJ+fT0lJCVOmTGHLli3Ytk23bt248MILufDCC3G5MvN57PKIr1+vGkM4FsDlqkXTbPLKfWhDwP40xYMiERY9+SRdx43bq7UqFApFrvHGGzBqFKQLt3120lhO2/ZfSHFq9PpJx3L4Wx8QaNwa4vy5ef46GHpYM1bbOlBnP1lCOBLD51U/jnSok9PmQxmeNqW8xM+Qvu2Y8dVaTj18H5XKsQfYti06P5S41oBDRnbjvufmsWjVNgb2Lst0OYo9YGfS4xqLIEVFRTz88MMtVdJuUyT9UivqurBuS0f26bIMzQZNg86nFbHu06qUj1v99ttYlpUxrxKFQqHIBUaO9HPCCUFeeSXNBpFCfprenk7nboovspH9IXl5fHLgSCbP/koIII0PbVfPgVWLoGduBUqov1pZgjhZUCej6Ug+ObWsVNNtip0lEXWr3m/JTBzZnY3b6lm4clumS2nVRGIWtq3EtcaMH9wJn8fgQzX6osgiOhcmjofnLBkPJK4vFp8QAGn9YQGxpFtowwbWfPTRXq1VoVAocpFnn9XYXnPgR+ftD9VC8HBuDm+cciJhaCp8OLxwTXOV2WpQ4kcWEI1ZmJatxl52wMSR3di4rZ6layoyXUqrJtH5oa7MJ+OcnP5v3o+ZLqVVk0gTUu+vZHxeF+OHdGbWN+uJxlRylSI76FGSOBD8dNkhxGKu+DFyoDiAd5QQPho3XNuxGN//6197r1CFQqHIUXw+H4884txzLgAny9E+vn+gKzZNzU/rO3RizqAB6b0/5r8JW7Y0d8lZjRI/soBwVKVv7AyjB3bCZWjM/u6nTJfSqlFjL6nxeV2MGNCBz7//CVN1F+02ylA3PROGdSEYjvHNstw60FBkLz1LwflN/X7NaKrqi7Fs4gfRXX9dgpXmiuHqDz4gHA7vpUoVCoUid7nwwkL69AGR9BJrsv7bGwbC1sZLdUDj1VNOIpp2zya8/ZfmK7QVoMSPLCAcN6BUJwvbI9/vZki/cj77bj22rU5Od5dwJIbbpWMY6te/MeMHd6KyJsyS1Wr0ZXdRhrrpGdqvHX6vSwm4iqyhb3vwxMUND9+sGiQ6P2ywbSg9wR8ffWmMtWULy1TsrUKhUOwV0vp+AOBn7t09IGggJG0DZ9Zla9++LO3eKXX3hwH8byrEmgoqbRV19pMFOFfilQfDjhk/uBMbttaz6qfqTJfSalH+MukZOaADLkNXJ6d7QPzzTEXdNsHtMhg1oANfLFDdRYrsoEspFPjkHQM+WXQEZlQcMtuAX/dTcFCaB1sWC6dO3TuFKhQKRY4zeHAhBx5Yk3b9ijv2gxSrbeDlk09uOL4oE3HRgFg1zJjSrLVmM0r8yAJUm/jOM2ZgJzQNPvtWnZzuLqFITI1YpSHgczOsfzmfffeT6i7aTdTn2fYZO7gTVbURFq9S3UWKzON2Q/cSRPafBrNXHEx9NB9TfvwFga7nlqU1y9v4+efUVygfLoVCodgb3H57NekDCb18eVdXnBYPJ/XFApaNHMG64nzqXYjP+0RjiODNv7VYzdmGEj+ygFA8fUOdkO6I4gIv/buX8PWSTTveWJES1fmxfcYM7MimbfX8uKk206W0StTYy/YZsW97DF1jzsINmS5FoQBgWNfE/+vD7Vi6vi+AFIBjtDvCC+3SPLi6msXTprV4jQqFQqGAvDy4/vrGS6Px26q7+1BbnRA9knn7mGObGKLGqVwFiz9u1lqzFSV+ZAHKgHLXGN6/PcvWVlBTH8l0Ka2SsBI/tsuw/uUAfL1UCWy7gxrj2z4Bn5t9e5Yyf9nmTJeiUAAwvk/C9BTgfwuPwLJsNEw0G9xuN8XHNn2ccxD9nRp9USgUir3GzTeXUlTk3GtsZepn4f2daWrwYfDpIROp1HWC6Xb80q3NWGX2osSPLMAxPFVRtzvH8H3KsWz4ViUm7BZq7GX7dCzLo1O7POYvVSenu0NYdX7skOH9y/lhXRVVtSopQ5F5xvcEb1L780eLJhGMBrBl6gsmdD+vRLRKI3IGoklfty5ezKYlS/Zy1QqFQpG7/P3v6det+ltfaquc+RbnpoHPxyfjxqfv/lj2EWzc2NylZh1K/MgC4jPyXnWysDP0715CwOdSV+Z3EzX2smOG9S/nu+VbiMYaNw0qdoTqZNsxw/qXYysBV5El9OkI5XmJ+xV1Xfnhp57omkh8sSPQflwh9BKt1E0OnMNhvvnnP/dixQqFQpHbnHZaKX37plsbYMWUkhTdH/D28ScQgtTdH5oFb/+12WrMVpT4kQWok4Vdw2XoDOnbjq/VlfndIhyJqavyO2B4//aEIqaKvN0N1NjLjunbrYQ8v1sJuIqsQNNgaJeGyz5ccASW+FUmivDF6/RLd5MZcocl06e3YIUKhUKhaMwTT6Rft/ymfahtZF1nA7UdOjB/n/4NRWwn+QVg9tPNWmM2osSPLCB+sqDGXnaawX3asWlbPVsq006uKdIQipjqxHQHDO5TBsCClVszXEnrIxSJoWnq82x7GLrGoN5lfP+Den8psoPJAxoeEH608EiCUS8W4KQy9/pNEfhTPz6yfj3L33mnpctUKBQKheTAA0sZPbrxUml+anlZ9XQAU3Z/JBugvn7iycQMCBoI0SP5wz9aAZ8807KFZxglfmQB4ahKe9lVBvQqBWDRSnVlflcJhdXYy47ID3jo1qFAvb92g3DExOs20NJnsSmA/XqV8tOWOiprlO+HIvMc1r+hrrGlpifLN/RBc3w/QpDXNQ//sDQ7sG3mPvpoyxeqUCgUijjTpnmS7jU0P11yY3/qgkL4SGb1gEGsKy5J7/3xzt3NWGH2ocSPLCAcMTF0DbdL/Th2ll6di/B6DBauUldOdxU19rJz7NerlMWrK7CstH8eFCkQnjLq/bUj9u0pBdxVSmBTZJ6+HaGTkx4grwZ+uPAYLFv4fgTlx2D33xSk3ce6jz6ivqKixWtVKBQKhaBfvxImT4amEgdQX8T6N91Q71yMcuZbNN6ZdDQ2abw/NnwLqxe2TMFZgDrbzgLUGMKu4zJ09uleok4cdhHTsonELNX5sRMM6FlKXTDK2k01mS6lVSHShNT7a0f07VqMy9BZrD7DFFnCAb1pkHn77nfHEoz4sAEbGzDpfIoHStLsoKaG7598ssXrVCgUCkWCqVO9kMaRaeHVA6gP6zSeb5n5s4lU61r67o93/9bMVWYPSvzIAkJhdSV+d9i3Zykr11cTDMcyXUqrIR5DqpKFdogardo9QuEYfvX+2iEet0HfrkVKwFVkDccMiqfZAlAV7MLS9fui2TbY4sDa7/dTfHhiGyGJiNhbE5j32GN7r2CFQqFQ0LlzMSedlPqik72hhE2zaJr84vMxa+QYIEX3hw7MexmCbdNXUYkfWYCKHt09BvQsxbJslv9YmelSWg2OUKROTndMp7I8ivI9LF2j2rh3haASP3aafXuWsmxtpYpUVmQFk/pBkbvhsne+OxbLEu9PUx4H9/59AejiOmNy9K0NVC9fzro5c/ZSxQqFQqEA+Mc/8tKssVl2Uz/qG9qBYAFvHHsyTVzHdHmzauGztml8qsSPLCCkPBh2i95dxIDyD+uqMlxJ66E+pDo/dhZN0+jduYgV6v21SyjxY+fp27WYmGnxoxqtUmQB+fkwuFPDZf9deBy14XxsOzFR3nFCEUbfNE3WpsmX997bwpUqFAqFIpny8kJOOy15SQynJ69uXhkVC8GsF5/jjmhd2aULy7t2xnYh2v5cNFQGZjy4l6rfuyjxIwsIR0x8XtX5sauUFvooLvAq8WMXcDo/AurkdKfo3aWINRuq1ZX5XUCJHzuPEnAV2cZJQ4QlnkMwXMb3awcBwvjUQBwf9zzHl3Yfy955h2AbbZdWKBSKbOWhhxzX6qbmpz/8rQv1KTxR3550HCZpjE83LYCV3zdjhdmBEj+ygKDy/NhtencpUicOu4Aae9k1+nQpJmbarN2orszvLMGwqd5fO0nn8ny8HoMf1qvPMEV2cNx+kNcopfrN+b/ENMGyE6MvPS/wQ36anVRV8d3/s3ff4XHU18LHvzOzXasuuWC5ImzcwMTBxjTTW0JoTiFcQiCVG54Q8pKbQpI3uUkuJORewg2XS96EdFKJgSSAKTYYsA0YGWMb4967ZPXtOzPvHzO70kq7tiRvkXbP53mEpdl2VozXM2fO75xf/SqncQohhEhVU1POTTcBadqYHn1qPOEDGoQS61qssV5N515Im9OdufHp0uIbeyvJj2FApr0M3cnjKtlzuItoLE06U/QjyY/BmdKQuDIvfWUGKhSJSyXbAGmqwqSxFZLAFcPG+HqYUpe6bemmq+gIVgCQWDZeVltG5XmZn2fN//5vbgIUQgiR0U9+UpHxtt3/W0PQUEit74MVZ56deezt2ichHM5miAUnyY9hIBKVMvGhmjKuEsMw2X2os9ChjAjJ5IdH9reBGFtbhtetSd+PATJNU5a9DNKUkyrZub8D08x43UWIvLphVurPpurjjV3WwbHRazc99UuZGuxB58aN7F2zJjcBCiGESKuuroKPfSz9bfseHEO8nX5ZjmeuuJYI6epFgHgHrP5zdoMsMEl+DAOhiFR+DNWUk6wr87sOSPJjIMJS+TEoqqowaWwlO2X/GpBo3MAwTNm/BmHKuEoC4ThH2qRHghgeFp0GXqsiGpyACk+8cyOxuGIdHIesOS/jLquACamPTUyAMQyDt370o/wGLoQQggcfrOmzRceq23Nx8EmNYK8shwm0jh3LtoYGoE9eJFEkUmSNTyX5MQxEZNrLkI2uLcOhqew90l3oUEYEWfYyeA2j/OyX/WtAQmFpqDtY40eXA0hfGTFszGiAiZWkHCGu2b2QI131GKZOuNf1wUmftP40sA6tdXoSIJuffZZgMJivsIUQQgCjRlVw7bWJnxKfypat35uAHoZ4KHXyyzOXXU1co2fUbSIBDnBoLRzalafoc0+SHwVmGCbRuIFXKj+GRFMVxtWXyajIAQrayQ+3U/a3gRo/upz27ghdwWihQxn2ZFnV4DWMsrpG7pMEmxhGrpvVd4vG8+9ejqmQUhs99Qvl4Ek3WwD0zk7elt4fQgiRdw8+WG1/lzqt0DxcydE3QTdTl7m8+b5z6dScmInkR0pbEKOoqj8k+VFgMd3a9dxS+TFkDaPL2XdYThwGwurHoKGqyvHvLIBeJ6eyjx1XOCqVRYNV6XdTUeaSBK4YVj42B/oOs33ynX8hFnXYS1+sbf5RfirPz/w8qyX5IYQQeTdhQiUXX5z+tp0/Hkcklpj8Ypd4uD2seN98IEPj07f+lKNI80+SHwUWjVvJD5mOMHQNo/wcbg0kE0kis1BYmlEOVnJZgpycHlfQXvYiy/gGp2GUXyo/xLBy2nh76Usv+9pnsOPIKZgmROmpip7xFW/G5wls3862pUtzGaoQQog0HnigMu327qX1hPaBVcrX4+mLrsnc+DR0ENYtyXaIBSHJjwJLJj/kZGHIxo8qxzChtSte6FCGPZnEMXj11T6cDlVOTgdAlr0MTcOocqn8EMPO9TP6b3ty3UfQzdSpLw2XVOGYnPl5Xr3vvuwHJ4QQ4phmz65m7tz0t+37eTlBg5Qyj5bJU9hTMwrIUP3x6v9kO8SCkORHgcXi1losj/T8GLLElfmWzliBIxn+JPkxeFZfGb+cnA6ANNQdmoZRfjq6o3QGpK+MGD5uel//pS9/f/fDdIX8GCbovY6Op/+rM+PzHHjlFdoOHMhNkEIIITL60Y/STX6Jc+B/q4h3WL0/envugivS9nACYNNLEAhkP8g8k+RHgUnlx4k7qa4MgKOdUvlxPOGojted+SBVpDeu3s+BZqn8OB5JfgzNOLuvjEwVEsPJzHFwcnXqtki0hjf2zMXEWvpiMTj5M24o77mfSc8kAT0a5bV7781DxEIIIXq76KJapk5N/BQnuagl6qf5BYjYPyamdC2fdwFBVem/9EUDCMDq3+Q85lyT5EeBSc+PE+dxO6gqd9MWkOTH8YTCcdnXhmB0jY/DrSEMQ/rKHEsi+SGjbgdnTI0PgMOtI/+KiiguH51tf6MCDkCDv665jZgOhpk4XDbxVfoYdaV1197jExNf6x57jHA4nO/whRCi5H3721Vpt++5fyzxiFXFlzi6jVdW8s60WdC7H6qDnukvr/w89wHnmCQ/CiwmlR9ZMbrGR3t3xkItYZNlL0MzutZHXDdo7ZSD92NJJD88so8Nyqhk8iNY4EiESHXzXPA6SDlaXL33Eo50jQbTJNJr6cvsr7tBTd8sL97Wxju/+12uwxVCCNHHTTfVU1fX/5M5vLGK7k0OQsn5tlamY8l5HySmYI297Tscsnk9HNmT+6BzSJIfBRaVnh9ZMbrGR1u3VH4cjyQ/hmZMjbW0Sk5Ojy0cieN0qDg0+adlMDwuq3pN9i8x3Eyqh5l1fbcq/HPDBzH6HBSPnlOJ7/TMz7Xi/vuzHZ4QQogBuOuu9FO5DvzMjxlX7TIP60P93TPOotntwyRd41MdXn04h5HmnhyhFljPshc5IT0Ro2t8dAR1dN0odCjDWlCSH0MyulaWJQyE7F9DZy2tkuSHGH4+8b7+2/665jbCEZc19aXXdYeZX8l8Iadr61a2vvhi9gMUQghxTHfdVYenbwdroPm3VcTa+jc+fX3O2UCGsbdNf8x6fPkkyY8C61n2IpUfJ2J0TRmmCS0dsiwhE103iMZ06ccwBKOqvSgKHD4qJ6fHIpVFQze6WpIfYni6aU5KL1MAWkKTWb9vJgDRmFUsrQGn3lgLYzI/17LvfjdXYQohhMjA6/Vy4419t+qAk5ZnINJn7O0zCz9IhAzJj649sH1ljiLNPUl+FFii8sMtPT9OiDQMPL5Q1OqJ4vXIvjZYTodGTYWHQ3JyekyhsCQ/hmp0rY/m9pBUr4lhp8YPc8f13/7HtZ8iroPZ5+h45uf7LhLvcWjlSg5v25blCIUQQhzPD37Q+4M8hjXjxWTff1ZhRFKrP46On8yemtFAuqUvwIqf5S7QHJPkR4FF4wYuh4qmZj5YEMeXXJYgV+YzCoZiAPg8Mup2KGRZwvEFw3HKvLJ/DcXoGh+GYXJUqtfEMPS5+f373i3dejUtgRrrymDy6Nhk5hc9UJbhiQyDl771rVyFKYQQIoOxYyu48EKwEh89Itur6HoPIoAe6ZnYtfTsy+z0SB8asOEZiI/MXouS/CiwWNyUqo8sqKuyGvkcaUubnxRAIGx92MnJ6dCMqvFxpE2SH8cSCMUok+TakIyWiS9iGLv2VKjt+1db8/D0hquJAyFMrMNlg7JqH+OuTvMkqgqKwu6XXybU0ZHzmIUQQqT6znfSr0s89Isy9DhWMYjtpbkXE9KsqS+h3gNhAKItsOHpHEebG5L8KLBIzJRlCFng0FT8HpWjHZL8yCRgV3745eR0SGorPLR1hjGMtCsgBdAdjlHmlc+zoaittBK48hkmhiOPBy5ttH+wpiGCAn9Y8xkiUQ3DTF2u9b7vOHsOkh0OcLtR3W6clZVoDgcH3norj9ELIYQAOP/8aqZM6b+9+TdlxDs0Qkaig5NGuK6O98ZNRVGwSv/6lv+t/HnO480FSX4UWCRuSAPKLCn3aRztlJLxTBLJD5+cnA5JbaWXuG7SGYgWOpRhKxiKSWXRENVWWm3YZdmLGK4+dyZodtIj4VD4FNYfmI0CRHvl7eqmVVBzHuAAzePB6fHgLCvDVVaGt7aWWCSS5+iFEEIA3H13dZqtPlqfsXs49fosX3r25cnuIP1sfxVCI++CjSQ/CiwSM/FJ5UdWVHg1WuXEIaNA2FqbJyenQ9NzcjryPujzwTBMgmFZ9jJUPo8Tr1ujVRK4Ypha2Ajjff23/3bN54jp/deFz/++E89Y0PxOXJWVeKqrKauvp2byZGobG/s/kRBCiJy79dZ6/P7+2w/8vAyiqY1PV85dSLfiAtI0PtU7Yd3jOYszVyT5UWCRmCENKLOk3KfJiekxJCo/5OR0aJLJDzk5TSscjWOYklw7ETUVXqn8EMPajTP7b3t5xwdp6a5PmfqiAePOqWD2/1GpaNQoHzOGygkTqDnlFE656ipqJk3KV8hCCCF68Xg8fPSjWq8tOhAn1FRBaHefsbceD+tOmQFkGHu7auQtfZHkR4FFYqYse8mScq9GVzBGJKYXOpRhSRqenpiengxycppOIGRVFkkyd+hqKz1S+SGGtX+dB55+W138bf0N6FhLX3ofUs/6QgVn/SjO1Buv59QPfYh5t9/O7I9/HM3lylvMQgghUn3rW+Pt7+L0TmscfkwBI7X6Y8nZVxGz79Vv4ffeJuhoz2ms2SbJjwKLxgxpeJolFV7rkEuWvqQXCMVwuzQcmvy1H4qqcjeKIvtXJonkml+Sa0NWU+mR6jUxrDXUwpxR/bc/tuZzBCNOUvtBG2gOhTELFGZ90cH8L3yByRdeiLss0xxcIYQQ+TBxYgVnn91/+8GflxELQsTsGXm7btY8OjzWmseUy8sKoARh/Z9yH3AWyVlQgUViJl6p/MiKcp+V/JCTh/RkDOmJcWgqVX637F8ZJJdVSUPdIautsCo/TFMmConh6/Yz+29rj45n5a4F1g8hA+tqopH8iqq/QtW0/g8UQghREF/72uj+GzvL6XxTQzcVe+mLAg43r8+Yh6mCmZj6khgKA/Dm7/IVclZI8qOAdN0gpptSJp4lycoPKRtPKxCWSRwnqrbSIz0/MkhUfsjn2dDVVHpkopAY9j4yE2p75zFUwAGPNt1JRIdQ2rkA+2jlqTxFKIQQ4niuvno0o9JU8h16VIO4CqZGIlXw4rzLiNqbQhqpY28PvAOtR/IRclZI8qOAQpHEGnm5UpoNPZUfcnKaTjAUp0z2tRNSW+mVZS8ZJCo/ZNnL0CX6ykgCVwxnHg9ceYr9g4PkkeQ7Rxayq3Vihq54EOWBfIQnhBBigD772f5jbzv/Xkas1S7is4udt516Bi3uigzPEoC1f8xZjNmW9eRHNCpXrAYqaI8elYan2eFxKjg0lY7uSKFDGZa6pfLjhFWVu2mX/SutnmUvso8NVW1FYpyyJD/E8Pals0BLc+jy66bPJhuf9mZdJFxLB+tzH5wQQogBufPOehz9PsvdtDwDfVfgvjFzATqkre1jze9zEl8uZD35ce655/Ld736XDRs2ZPupi04wItMRsklRFKr8Ljk5zSAQkuTHiar0u+nsjmAY0pOhL1n2cuIq/NYEjM6AfIaJ4W3uBJiW5iLg4k2foDVQmWx8qmAdaCYONkP8ME8RCiGEOJ66Oj/nn5/4KdGvKU7zowpmxK7+sC096xLi9vf9ut8deQ9aD+c42uzIesmB3+/nj3/8I3/605+YOnUqixYt4uqrr6aqqirbLzXihezKD5n2kj2V5W46uqX6KB1Jfpy4Sr8Lw4SuYJRKv7vQ4QwrgVAcl1PD6ZDVlENVZe9T8hl24r7+9a9nvE1RFDweD+PHj+fCCy9k0qRJ+QusiHx+Lnzxpb5bvTy18Xo+deavIASqt+/tS+imBT91+QlSCCHEMX3lK+NZtmwvvWe5hNdWEdzdRtk0rEyHF3ZPmcWRsmoaAm39n8QIwLo/wAV35SvsIcv6WfeyZctYtWoVixcv5sUXX+QHP/gB999/PxdffDE33HAD55xzDoqiHP+JsiwQCPDAAw+wZMkSOjs7aWxs5Atf+AIXX3xx3mNJCEYSV0ol+ZEtlWVuWfaShmmaBMMy7eVEJU5OOwOS/OgrGI7hl0kvJ8TrdsjSvSx54oknBnS/+++/n8997nPceeedOY6o+Nw6B+55Cbr6bP9/b93Fx07/HZo7jjUc0aCnEYhON/fjlwoQIYQYFq64op6xY/dy8GDq9ua/gPcbVu/ThFdnLuBjbz7Tf+mLArz1+9JMfgAsWLCABQsW0N3dzdNPP83ixYt59tlnWbJkCWPGjOG6667juuuuY/z48bl4+bTuuOMONm7cyN13301DQwNPPPEEd9xxB4888ggLFy7MWxy9Sc+P7Kv0u9h3pO+hmIhEdeK6KZUfJyiR8GjvjjB+dHmBoxleuoJR2b9OUGLpnlR+nLilS5ce8/ZQKMS2bdt47LHHeOSRRzj11FO5/PLL8xRdcfB74cqT4S/bU7e3RSawYtd5XDn1JXpfSezxGCG+g5d+ZSFCCCEK4NOfruJ732tO2XbkVw7GfdkgpJl4QyZ4FZbNv5zr33wGN8mCkJ7Rty2braUvNWlG6A4jOa1P9vv9fPSjH+XPf/4zzzzzDLfeeiuxWIyHH36Yyy+/nFtuuYV//OMfuQwBgOXLl7Ny5Uq+//3v8+EPf5gFCxbwwx/+kDlz5nDffffl/PUzSSY/5Gp81lT63bR3RzH7dukpcYnRmRVlrgJHMrJVJpclyJX5vroCMSrKpBrmRFX4paluNowbN+6YX42NjVxxxRX88pe/pLGxkT/84Q8Det5Dhw7x/e9/nxtvvJEzzjiDadOm8cYbbww4rg0bNnDLLbcwZ84czjzzTO666y4OHx4Z66TT+dJZqRMPAVDhf976KhHdJNJvYThABwF+kfvghBBCDMgdd9SjaX02NpfTtV7BRMXKbqjsmzidQxW14ADTgVVGkXxcANb9OY9RD03eFmdPmTKFf/u3f+OVV17hkUce4eyzz+aNN97gq1/9as5f+4UXXqC8vDxliYuiKFx33XXs2LGDbdu25TyGdEL2shevVH5kTZXfTTSmE46mu9pUujqDVvKj3CfJjxNRaTek7OiSk9O+OgMRyn2SyD1RVX63NDzNI6fTyZVXXsl77703oPvv3r2bp59+Gp/Px1lnnTWo19q+fTs333wzpmny4IMP8r3vfY+NGzdy8803EwgEhhJ+wS2YCNMTRXCa/aXAe0fP4t0j0/tNC+jxYF7iE0IIcXyjRpWT7p+01j8A8dTGpytmnYOJtZgx1vcBI2Dkbd47061bt45ly5axdu1awDrwxNX7lQAAIABJREFUyLWtW7fS2NiIqqa+3WnTpgGwZcuWnMeQTqLywyPJj6xJnpzKldMUXVL5kRUVPheKAh0BWZbQV1cwKpUfWVDhd9Euy17yqq6ujmAwOKD7nnnmmaxatYpHH32UG264YVCv89///d+UlZUll9teccUVPPzww+zdu5fHHntsKKEPC7efSa8rfz0eeevLxEyI9an+sO56mBYGVm0jhBAi9770pf7tKFr/oKEHsFo32Z/lL7//YqJYyY943wcc2gBtzX23Dit5Oetubm7mqaeeYvHixezcuRPTNJk+fXpyEkyutbe3p+3mXllZmbx9oLI5wrf5SCcVPo21b6/J2nOWuuZDewF44613aKiTE7GE9busA/u9u7YSbts1oMc0NTXlMKKRy+tS2bpzH01NA79SW+y/S9M06eiOEOhqzfl7LfbfZSTQTltHKC/vs9h/lwO1Z8+eAU+k63sRZaBisRgvv/wyH/7wh/H5fMntJ598MqeffjrPP/88n/3sZ4f03IX2qdPhm8uho8/2pbs+wr72b3JytdVFLzH2tmeZzA+Bj+crTCGEEMdwww1jqa3dy9GjYKU2DIi46VwZpPpyMEzrM3zvhOkc8tUwIdhK/+K+btj4Nzjn83mOfuBylvyIx+MsW7aMxYsX89prrxGPx6moqODGG29k0aJFzJgxI1cvndaxJswMZvrMrFmzcLuzc1J92uk6Z7zRxNy5c7PyfKWuqamJuXNm8oflrzC2YQpzZ44pdEjDxsHQDqCVBfPOGNCUkqYm2S8zqV3ajstbPuDfTyn8LoPhGPof9zN1ynjmzj0lZ69TCr/LHe1bWLXpPWbOOj2nVYHZ/l1GIpGsXhzIlyNHjvDXv/6VBQsW5PR19u7dSzgc5pRT+v/9mDZtGk8++WROXz+XvF740Cnwu639b/tF0x1875J78IUBD5AsljaBbbTyPDVcls9whRBCpKEoCtdf7+HnP+9O2X7kMai+GCIayTbVr89cQMPqp1GB5Md7wjt/Kq3kx6ZNm1i8eDH/+Mc/khUV8+fPZ9GiRVx22WW4XPkvu6+qqkpb3dHRYV2nSFSA5JvToVHmSVMrKoas9zQO0SPR8NQv0zhOWKXfLdM4+ugKWqs+ZVnViUuMU+4IRGVJ5Ak4XjIhFAqxfft2nnnmGYLBIJ/+9KdzGk/iGCTd8UZVVRXhcJhwOIzH4+l3eyaDTTblstLnqir4HdPp2/70D+/dzJ1n3YejvAslpKP0vodpEuy+m517fzXk1y3G6iV5TyODvKeRQd7T4Fx4YZif/zx1W+ApB9H2OK560BNLX2ZcyDWrn0bDSmX3Xt0Y37OaLa8uA9/Az6/z+f8p60dW1157LQBjx47l9ttv5/rrr6ehoSHbLzMojY2NPP/88xiGkVKymuj1MXXq1EKFJrIscfLVHZST0966AtYYUk3Le5ufolNR5mLv4e7j37GEJBp0SkPdE9d7otDoGt9x7i0y+drXvnbMqs7ERLCxY8dy7733MmvWrLzEla0qVBhcJWquq6bmAvdvhTX9rjN5+d07t/F/zvkJ/jS7s9u3jbpRIao5d9CvWYyVYPKeRgZ5TyODvKfBmzsX/v3fV7BpU++tLtpfNqldZIBpgg+2T59DS1k14wJtmEDKx7sWZK73IMy9aECvme9K1KwnPy6//HIWLVrEueeeO+h/yHPl0ksv5fHHH2fZsmVccsklye1PPvkkkydPprGxsYDRiWzyuDQcmpK8Ei0snYGoXJXPEr/XJcm1ProCicoP6bNzovz2xJxu+Qw7Iffee+8xb3e73TQ0NDBz5ky0fvP9si/RUyRdFWp7ezsejydrS2oL5SvnwI1P99mowM/evptPz30EpxLE7U33yHuA5bkPUAghxHHdfnsDd965L2Xb0ccUaj+kEVLBGwK88Oa0eVyz5jkUrMoPL/SMUln7Z3j/TXmNe6Cynvx48MHhN75s4cKFzJ8/n3vuuYf29nYaGhp48sknaWpq4uGHHy50eCKLFEXB73XRJSenKTqDUSrkqnxWlPucdIdimKY5bBK8hZYcpVwmy6pOVKJ6pjskn2En4rrrrit0CCnGjx+Px+Nh69b+jTG2bNmSthfISPOxWfDl5+FgjOTIWxSIUcXiTR/hM3N+neGRq2nnHao4PW+xCiGESO+Tn6zn61/fR+8haKFlEGsB50kkp3u9PPtiPvDOc+lHx+5fBdEoFKDdxfGURA28oig8/PDDfOADH+CBBx7gM5/5DJs3b+ahhx7ioosGVpIjRg6/fXIqenQFo5RL5UdWlHmdxOIGkZhe6FCGjcSyF6n8OHGJvjxSvVZcnE4nCxcu5LnnniMU6lkdvXPnTtauXctllxVH089bZwNO+o514T/f/DZdUQfRtGNvweTu/AQohBDimCoqfJx3Xt+KSBftLwIm6Ia1ZdMpc+hwVQDWJNwU0TbY8mKOIx2akkh+APj9fr797W+zYsUK1q9fzxNPPJGyBEYUj3KfLEvoS5a9ZE/iynxAEmxJXYEYimIlhsSJSS57kcqPYWvJkiUsWbKEt99+G4DVq1ezZMkSli/vWbpx0UUX9bu48sUvfpHu7m5uv/12XnnlFZ577jluv/12xo0bx8c/XhwjX798ds80gN4Cxhie3vpBTNNKeCS+MAzry3yNdtblNVYhhBDp3XHHhF4/mYBO65+AGER1rHUuDgdNk09LjrtNzW3r8O7f8hLrYEkreVF0yrxOWjvDhQ5jWOkKRKUZZZYkfo9dwRi1lWkXsJeczkAEv9eJpsoyoBPldGi4XZr0/BjG7rzzzpSff/rTnwIwbtw4li1blvFxjY2N/OY3v+HHP/4xX/ziF3E4HJxzzjl87Wtfw+/35zTmfKktgyvGwxN7+99275s/4ENT/44vZIDbbpyXYJpW9Yf2fP6CFUIIkdZVV42hvn4nzc09xyLhFRBtBtc4SGQ8Xj3tYi7Z/Fr6aoodL1qf88NsibgkP0TRKfc52XO4q9BhDBuhSJxwVKeqXJYkZEPPsgS5Mp/Q3h2R/SuL/F6nJD+Gsc2bNx/3PpmSIKeddhq//e1vsx3SsPLdc+GpP/Yvg26NTOb5HVdw3bSn8fVOfCQtp1NfR4V2Wj7CFEIIkYGqqlxzjZdf/KL3sYhC+zMmoz4FIcOq8ltzyvvp0rxU6aH+CZDug7D3LZhwZv4CH4CSWfYiSocse0nV3mX1Y6iWk9OskGkc/bV1Rqgu9xQ6jKJR7pOmzWLkmj0WzqhMf9v3Xr+XUPxYVwHvyklMQgghBufzn5/Qr2ij9c+gx8EwQQ9BrKyMTeOmomAlvFOXvsRg41N5i3egJPkhio7f5yIYjqPr/drvlKS2LmsJkJycZoc/MY1DTk6T2rsk+ZFNZV5p2ixGtm+ek2ajCkdip/DCzsuJRDI98jU69dU5jEwIIcRAzJ1bx5QpqdsiqxWihxUMAzBVQOXV0y4knu4JFGDz33Me52BJ8kMUncSyBDl5sLR12pUfFVL5kQ3lPtm/+mrrCsv+lUXlPqc01BUj2rWnwvhEmykNa5G1fcT572/eS7rdu2e2gFR/CCHEcHDzzaP7betcAhgKId0EFF4/9SzCqgOTZCsQK/GhAa07of1QvsIdEEl+iKIjJ6eppPIju7xuB6qqyLIEW6KnjCyryh6/V5a9iJHv/8zDSnr0KZs+FDqV53ZcQSTYs00zTdB1iMdBf51O/eU8RiqEECKd224bj7PPIL+2vwBxrExHCDqrx7K3bgKmAwwHhBz0ymZ3w+Z/5jPk45Lkhyg6/uQ0Djl5AGjriqCqCuUy6jYrFEWxGlJKcg3oSa5VSXIta/w+2b/EyPe50yBD6w++ufoBQrpijbzVdSvxkWiCapoQ/2K+whRCCJHB+PF+Zs1KzWBH3oTYERPTiCdLPVZMm9+vyXXSe8Nr6YskP0TRkYaUqdo6w1T5XTKGNIvKfTKNIyG5rEoqP7LG73MSierE4nqhQxFiyDwe+MyM9LcdDU/h79s+SDBopo68TXqXQOSJnMYnhBDi+G65pcH+zsAq+YjT/qIOJoT0KMThjennEkNJXfqScGC1VdU3TEjyQxSdcmlImaKtKyJX5bNMliX0SC6rqpB9LFv83sRnmCTYxMj2jQXgy3Dbd1b/F6G4luFWgDtzEZIQQohBuOWWsfh8Jr0HmHf8Bcy4nbuOwc6G6bS7qzDse6X0tI60wt7X8xrzsUjyQxQdn8cBQCA8fLKMhdTWFaZGTkyzqszrJBiWE1OQyo9cSDRtlgSbGOmqy+D6Selva49N4C/bFxHJuJvvJxB5KEeRCSGEGIiqKh/ve19qojq8EqLNgAm6AWga68fNTN6eugQmCtuW5CHSgZHkhyg6ZR7rxEFOTi1tnRE5Mc0yr8dBUJJrgJVcU1UlWXElTlyZnfwIRmQfEyPfD862+p4mJaYAOODbb/6EQLj/Z0fPIs1vEo1KElAIIQrps5+d1G9b5yugA2Ezih6B12adgwHpl75sfyHnMQ6UJD9E0XE6VByaIiengG6YtHdHqJLkR1aVeaTyI6G1M0x1uRtVespkTaJ6TT7DRDGYUAdXjLV/SEwBsD8uotTys3c/RzRs326aaLqBqhvW5US9g1j8W/kPWgghRNINN9RTUZG6rfNvQAxMAzA0mhrnE1LdJBbIpHQta9kK4e58hXtMkvwQRUdRFLxuOTkFq9mpYZjUV2dadS2GwieVH0nNbSHqq7yFDqOo+OzqtZDsY6JI3HceKI4Mt228l85wuTX5xUjX/PS/iASbcxmeEEKIY/D5vJxzTuqxXmgpxNvB1AEDApWjOFA5CrAqP5I1ewpgdsPOV/IYcWaS/BBFqcwrJ6dgnZgCcnKaZT6Pk3BUR097oF5amttDklzLMp870bdIEriiOMwcAwtrM93q5d53vkEolGlQYhyTW3MUmRBCiIG49dYJqRtM6H4dUCBkhiEMayafga5hZRhUrEo/DSAG217Mc8TpSfJDFCWf2ynJD6C5PQhAfbUkP7IpsSwhVOInp6Zp0tIulR/Z5kv0/JDPMFFEfnhe5tt+se1uDgfGHOPRTxMJrs56TEIIIQbm6qvrqanpvcWk4ykddJNEs4+mxvkYCqCCodK7gRPseymf4WYkyQ9RlLweB8FIaZ+YglR+5Eriynypn5x2dEeJxQ1JrmWZ1y3JNVF85p0E8yoz3arypdU/IRpJf6t1/HxzTuISQghxfB6Ph/PP9wHx5FfwaQOjU8E0VHTd5N1JpxHCk2x6Gur9BG17IRIoQOSpJPkhilKZx0kwVNonpmAtSSjzOpM9BER2+GQaB9BTWVQnybWs0lQFj0sr+f1LFJ//WphmowI44MUjH2XL0dNSbzNATR5nbyYS+H+5D1IIIURan/zkRFLmuYSg+10TTIgSI6xVc6hyTPqJL3oX7F+T13jTkeSHKEo+qfwApBllriR7MoRKex+TyqLckaa6ohid0wBzE9UfKj3TX2wfW/VrokErH6Lo4DD6HKjqdxELpVxLFEIIkSdXXllPfX3qts6n4piY1tQXEzY0TEe3viW1k1MU9q/KV6gZSfJDFCWvx0FAKj9obg/KkoQcKLMrP0IlfmW+ud1OfkjD06zzeZzS8FQUpQcWYiU90hyB7gqdwauHPoRmgpa2n3QQI/b53AYohBAiLZfLxXnnlads63oa9JA18lY3TNZNmoOJPeSFPktf9q7MX7AZSPJDFKUyj5NQJIZplvY0Dqn8yA2vVH4A1v7ldmmU+2RZVbb5PA4ZdSuK0nkNMLc88+0fXvlLAiHXMZ7ht8Q738l6XEIIIY7v1lsno/bOIByEyB4TAwiZMTbUzyaGo/+yF4CW9/IT5DFI8kMUJZ/HQVw3icUzjc4rfsFwjO5QTK7K50Bi2kup92Q43BpgVLUXRVGOf2cxKNbEqtJOroni9eAxJr9EqeXR9759nGdYlNV4hBBCDMxll9VT22d0eeDZaHKdS3PVeALusmTfj5QkSOgIRLrzFms6kvwQRSnR4LOUy8YPNFsdlU+qKytwJMWnzN6/Sn0ax4GWACfV+QsdRlGyJlaVdnJNFK9zGmBBxskv8G9bv0FbuCF1ownEQYsD8W2UeX+dwwiFEEKkk3bpywtA1B55q8KhylFW7yZ69f5QASMELVvyHHEqSX6IopS4Ml/KZeMHWqzM6kn1cnKabW6XhqpAoIT3L8MwOdQSYKwk13KizOOUhqeiqD18fpqN9uQXULhr1c97tuvWV6/eqIypeoh455FchiiEECKNT3xiMr2LfqOrTOJtgGktfdlePQnTAaZmfUWSfZ7i0LKtMEHbJPkhilJyGkcJX5k/2GJVfoyplWUv2aYoCl5PaS9LONoRJho3pLIoR6xpL6W7f4niN2csXFpt/5BIevTKbvzp6BXsbrsSDav5qdbn8U4ViH8oH6EKIYTo5Yor6qirA2sOuZWdDrwVxzQBXWFP/VRryYtd/tGz9MWAtq2FCDlJkh+iKPnsaRylfOX0QEuAukoPHpej0KEUpbISH0WarCySZS854fU4CEXiGEZpN20Wxe3hxOSXvpkN21UvPQZm+uanigHwBmbrr3IUnRBCiHTcbjfnnpu4+GUdpwSei4AJJir7y0dhYOW1e+5hf9e1O6+x9iXJD1GUEpUfJX1y2tzNWDkxzRlfiVd+HLAri8bWS+VHLvjcTkwTwtHS/QwTxa9xFFw/OvPt241qluz/Ufobk0fTn8fsbst2aEIIIY7hYx+bmLL0pfs5MIIqmLDPV4uBikFP09NI4o6dB/Mea2+S/BBFKdHwtJRPTg8eDXCSnJjmjK/UKz+au3E6VOoqZZRyLpR57b5F0vRUFLn/PQ+ONSz7xrfuJM6M1I2JQW46oEchdE2OohNCCJHO1VePprq614ZmCO83ME1o91YlKz8S+ZHk/M1oRz7D7EeSH6IoJUeRlujJaXcoRkd3VPox5FCpV34cbAkwprYMVZUxt7ngc8vSPVEaRlXApydlvr0b+O66J3s26CSrPnpWy7yK2fxzhBBC5IfX62HevNQK8+DrcXQg4HCh22mGRJFeslgvHspXiGlJ8kMUJY+97KVUS8b3HOoEYPzo8uPcUwyVx6URjuqFDqNg9hzqYoLsXznj9UjTZlE6/nMB9Ps0SU5+gfv2nEILX0PFOnDV6Lma2OPzINNfhBAib266aSJqr2xCcGkY4ia6qWLYn9Ip424BlML2MpPkhyhKLoeKqlCyJ6e7D1rJj4ljKgocSfHyuh2ES3RJQjgS51BrgIljZf/KFa+dwI1ESvMzTJQWrxe+dQY9GY00TVA//NK9KExMk/RIMCByUa5CFEII0ceHPjQWf6/ij/ArYIbApcftjh8WBSv5EQPQPHmOMpUkP0RRUhQFTwmfnO4+1IXP46C+Wvox5IrH7SBUosm1PYe7ME2YOEYqP3LF47LO/EIlWr0mSs9XzoBxGslqj75eC8CLHU+nvzFxWdF4F5q/naMIhRBC9FZR4eW003w9Gzohtt+gItSBplnJj951HjEAZ2GHMUjyQxQtj8tRss0Cdx3sZOKYChRF+jHkiselEY7EMc3SG0WaqCyaJJUfOZOo/CjVBK4oTQ+fc+zbb1k1E5NvWD8kPnrjWH1ADPsr/j1ofjNnMQohhOjx0Y+Ot6e+mIBO6K0o47sPomFiaoAGpgqmZn1RdlJB45XkhyhaXndpJj9M02T3wU4myFX5nPK6HeiGSVw3jn/nIrP7UBcup8boWmmomyuJvkWlWl0kStOHToZ5x/hYOQj8350/AKZZiY5efz1SUv3xyyEYzEmMQggheixaNA6fL46ViTYIrowxc/+7qSsXFSs1EkeFulMLEmeCJD9E0fK6S7MhZWtnmO5QTK7K55jHlRhFWnr72O6DnUwY7UeTSS85k1j2IpUfotQ8tjDDDfYR639sg0M8R9wAzNRRij3aoePKXIUohBDCNmZMGdOnu5M/h9+Ictb+t1LuY39cE0WFMbPzG2AfkvwQRctTopUfOw/YzU4l+ZFTXndpnpyapsnOgx1MGltZ6FCKmtsly15EaWqsg9t6V0Unpr7YR6wG8JHXJrL/6FeO0fwU4BU4+P0cRSmEECLhmmsakt9PaNvB5OA+IDUxbQBdTj/BsXPyG1wfkvwQRcvjcpTkqNute9pQFDh5nJyc5lLPsoTS2sea20J0dEc5ZUJVoUMpapqq4HZpsuxFlKT/ORfKHaSd+gLwWggeP3ILcEn/G+O9v74FLctzGaoQQpS8G25owGMPcbnz7L9TRgyTXr2oAVDYUTuZvX5peCpETpTqKNIte9sZP7ocn8dZ6FCKmqdEr8xv2dsGwCnjJfmRa15XaX6GCeHxwAOnH/s+P2gbR9jzd6C6Z2O6vy6hK6CzOZvhCSGE6GX69BomTnRw+uh3uWbWW+hYVR9m8ksjhpPnZlzJbkIFjVWSH6JoeVxayfVjME2TrXvb5MQ0D3qmcZTWPrZlTzsOTZVlL3ngcWslV1kkRMKnZsLpnsy3h3FxS5MXnEusDRl7T4ehY0G2wxNCCNHLBy9z8aOrf0mZO4QS66n40FHRMTjqrWXpjEvYS6SgcUryQxQtr7v0lr0kliRMnVB9/DuLE5JIfgRL7Mr81r1tTBlXgdMh/3zkmkcqP0SJe/zCDDco1n/+chSWBucBP+gZfZvWdth7RbbDE0IIARDp5s7p/83MMfsxUTENrD/R7OSHk6dmfJCwr4q9FHYSlxy9iqLlsZe9mOYxj4iKSmJJwtTxkvzINU+i4WkJJdh0w2Tb3nbZv/LE49JKrrJIiN4aq+HOSX02qvaX3Qvko6sgWvsN4KL+T2DQq//Hc7D/ntwFK4QQpai7GZ69jbHhVSiYgIqBgolCHAVd0dhZNY7HT/sYAM3EChquJD9E0fK4NAwTovGMtbBFZ9OuNpwOVSa95IG3BHt+7D7YSTiqc4pUFuWFx+2QZS+i5P3nfBijYSU7NPrNtT0KfOo1oO5pYHTPDTr9l8JE/wMO/TZ3wQohRCk5tBb+eRPsXo5DVXG7NDBMDF0lrmkYmoNut4+HzvwMYW8ZAO1pmzPljyQ/RNHq6clQOicPG3a0MH1SjSxJyIPktJcSujK/YXsLALNOri1wJKWhVJs2C9GbpsEfjtOy4/fN8EKzB/wrSB7aZir6DN0CzS9lM0QhhCgt0RA0PQzPfxpaN1ofu6aBq8xBHJVYzImuOIlqLv469YOsbFyIblrHy7Fjr1HMOTlDEkUrMY0jVCInD92hGDv2dzDr5LpCh1IS3M7SW/ayYcdRRtf4GFXtK3QoJcEjo26FAODCCbDoODnXRash5DkZ3IuP/4TdF0HrpuwEJ4QQpULXYddyePY2ePshCBzFyjQroGo4VCe66SCmq8RVFy+OP5tHz/wUoBI1FAhS4EUvkvwQRSxZ+VEiJw8bdxzFNOWqfL6oqmJPFCqN5IdhmGzY3sJsSa7ljUcqP4RI+t25kHGOmQM6gRteBUZdA+p3+t/HxFoKk/hqPQPaD+QkViGEKCrxOOx/HZZ9GV79Ghx9B+uD1E58KAqoDrwOD6guIrqbF8fM50cLvozh8oCpgN0IVS1w+kGSH6JoJRtSlsjJw/rtLTgdKtOkH0PeeNyOkkmu7T7USVcwJsm1PPLKtBchkjwe+P1ZfTaqgJNkH5Bnu+E3W4Fx/xdYlHrffu2/wtAyHTqacxCtEEIUgUgX7FoKy78MK+6BwysgHgRMMA0r6YECigaaE1Qnrmo/f9o1n+8v+Arhsgp78ot1d0OP4k90qy4QR0FfXYgcKrVlL+u3tzBtYjUuZ2E/VEpJKZ2crt+W6PchlR/54nE7iMYNdN1A0+RahRAfmAiLtsHjHVi102n+ubt1PVxSD+Mm/xV2zgHesS5QptUJzdNB3Qnl5TmLWwghRgxDh46dsO8V2PcqBPdDNAhmHAwD0O2eSipoDlBVrAy0Du569JNv4sfPj6PG50Mxwex1+BLSTcq6TCjgx60cTYmi1bPspfhPTls7w2zf18H7po0qdCglxeMunWUvTZuOMK6+jNE10u8jX7zJccqlUV0kxED8/lyoAaviIw0TuOAV+4cxq4CGzE9mAPpROHAydHZmM0whhBg59Bh07IGt/4AV34SV98Dmv0D3PoiHrISIEbfKN8xEpYcKqhsUN2geGDMPLnuEhsvvpOE8f2qJhWl9mXEwthS24alUfoii5S2haRxN7x0G4P3TRx/nniKbPC5HSSTXwpE467e3cNXZkwsdSklJVK+Fo3HKvBnO9IQoMW43PDEPFr6Z+QB6G/CpFfDoOV6ofRuONAIdqXdK+ehuthIgbIaKmuwHLYQQw00sbCU3jr4Lh9dA9x6IdoIetSs8DJIflA4F4io4HNZSF1PFKuswwV0Pp37U+vL4AXBfVUEw8Tp24gOsp9u3rB3m5vWdppDkhyhaiZ4fpXBl/q1Nh6mr9DBpbEWhQykpXreDQKjQfatz752tzcTiBmdKci2vesYpF/9nmBCDcX4DfHj1Af5qNva/0T6y/eVRuHIHLJpSB/rbcHQ6ELFuTPtXqgUOTAHjXagal6PIhRCiQPQYhFuhY7eV8OjYDsEjEO3GLoPrqe5I/AmAaiU5VA1MB6gmKAa4qqHhfJhxM1SOT77MfgLoJ/tR4qFkPyZM6yXMgMHWvx2Gr+T3rfcmyQ9RtLyJq6ZFfuIQixu8vbmZ888Yh6Iox3+AyBqPW6OlI1ToMHLurU1H8Lo1ZkyRZqf55HUlmjYXf/WaEIP11QkdrNsPmxP/xKv0W8z9kQ2wvRomV08Gcw20ziZN59NeOuDQFNDXQu303AQuhBD5EI9AuB26D0D7VujcAaGjVnVHPIr1WWhYa1HQreRIokRDVUE3QFGt3kqmw7qPoUBZNZx0Hky9Dion9XvZ33GYuMt++kTFhwGmaRLZHqFzY5TNm48ybVphjikl+SGKlsupoSgQKvJlCRt3HCUUicuSlwLwlEBee/zlAAAgAElEQVTDU8MwWb3xEKefUo/TIW2i8ilZ+VHkn2FCDNWyhTBxeYZCDqzj7nNWwc6LwV0zA8zXoW1e+jsnnyQKB2eA/iKMujj7QQshRLaZhlXBEWqF4CGrYWn3AYgchXjAWsqCCbpuJzsMMCJW/w7TzlKoDvs2sJqZOq0kiKGD6oKy0XDSBTDxQvCPTRvGDgI8STd4wBuBUGLJiwnEIfhkN+EAPPvsAUl+CJFtqqrgcWlFf9X01Xf243FpzJlaX+hQSo7X7Sj6njKbd7dxtCPMLR84qdChlJxk0+YiT7AJMVQnVcKvZ8K/vJv5PgeBS5fDK5cBtWeCuRJazk69U7q/YkcugfgjcNLnshixEEKcID1ujZuNdECgGYL7oPsgRDoh0gZmxO7Zgb18JW49xrA/6EwdKxuhWMtXFADNHl3rANVpVW2oJjgrrSq4sefDqNPAlbnpfZQo/84e2hMbHF6IhKzhMIaJ0R4n+HgnGPDMM/v40pdm5+o3dEyS/BBFrdgbUsZ1g5XrDjJv5phkc0SRPx6XVtT7F1jJNadDZf7MMYUOpeR4ZNmLEMd108nw3H74XXvm+7wah6+8DfefAdQtAF6HlrOsG481eKD189D5Npz6SBYjFkKIATBNa+lKPGAlNkJH8DevgPfWQrQdogHQ7USHaViJDMOAeAwwrMakRhwru2v2NChVFEAFVbHur2p2hYdhbXeWWctZxi6A2plQdvyLq3Hi/Af7aLKXFaqAoZJcWYMO3Yu74Kh1/3Xr2ggGI/h87uz/3o5DzpZEUfO4HUXdLHDdtha6glHOmyPN2QrB63YQixvEdQOHVnxLQgzDZMU7+5l76ih8Hpk2km+y7GV4CgQCPPDAAyxZsoTOzk4aGxv5whe+wMUXH3uJxE9/+lMeeuihftvr6upYsWJFrsItCb89H15/Grb2zRP2Osr98UE4rRxubgTq5oOyDprf16vMu4/Ec+k/g3fegqmvgtebg+iFECXNNKwkhx6yEhqhZmu5SrAZYu0QDVuVHkYcX/d+6BpjNxA1wIz1JD/Q7YIO025tFLcSIiZ2osNh3YZiLXdBB4dpVXq4K6B8CoyaA1WN4Ku3kyTHFyHGf3GAf2L1wFNQMDFxeMHVrRDRDaL7dDp/0pp8TGenyeuvH+Gii8ZnetqckeSHKGpel6Oor5q+tnY/Po+D900bVehQSlJyWUJUx+8tvuTHxp1Hae2MSHKtQGTZy/B0xx13sHHjRu6++24aGhp44oknuOOOO3jkkUdYuHDhcR//q1/9Cp+vp3TY6ZTEYja8ei5MWgHhxD/5Wv/7fGI7NFbAglFA7WxQN8PB04BA6h37HTY0wZbRMG4l1M3KeuxCiCJnmlZDUT0CehBi3RBqs6avRFoh2mX14IgGsJqPAmpi6YpuPd6Mo6i9p7HoYJig2I1ISfTvADTVTpAo1lIWBet2dKvSw1EGnlFQfQrUToOyk8BdPui3dYgw/8lBXiOIXTeCgoqaGO+iqOhBk46fHIWWnseFQvDcc/sl+SFEtnncWtFWfkRiOivXH2T+zDG4nGmO8kTOJa/Mh+P4vcV3AvPymn24nBpnzpAlL4WQWMomlR/Dx/Lly1m5ciUPPfQQl156KQBnnXUWe/fu5b777htQ8mPWrFlUVMhY8mwbXQn/nA2XrD/GnXQ4by3sPBPGVwPVU0DbCXtmA4et+2QcBtMF+2dD4D9g4tezGrsQogiYptVY1IhC3K7iiHRArM1KdMSD1vZYCMwoVobDXnqi2IkJ07CXpsStxIVh9+cw7fuapvX8hmlVbygA9vhZsBIdYCdB7OdVNdDKwFcNFVOgYjL4TwJPlXXbEBgYvEoHP+MIu4gn3gmKFQ0qCgoaOL0cfeYAkT929XuOF188MKTXPlFFn/zYunUrv//973n33XfZvHkz0WiUpUuX0tDQUOjQRB543A66AtFCh5ETq9YdIBCKccm8CYUOpWQlxykX4clpOBLnlbf3c+7pJyUrEER+OR0qDk0p6uq1keaFF16gvLw8ZYmLoihcd911fOtb32Lbtm00NjYWMMLSdvF4+GEAvrojwx0063Rj9mrYuwDKy4GKepiyA3acBazP3AMk8THf8g1o+QtMfQnKq7L9FoQQw1Wi4sKIWctUYsGexqORdtC77W2hniSIafZMU1HsygyTnv4chg4Y1qQVPWJXaiT6dUTt5SlYI2cBE7uSQ1VINi0lsczFsHp3qB6r2aivDsongb/B6tvhqhhysqO3nQT4M62soJsOjGTSA6yCO9X+WQVmOxSO/NeRfs+hKLBrV4iWlv5JkVwr+iPaDRs28NJLLzFjxgzKysp4/fXXCx2SyCOvy0FzW7DQYeTE82/sYUytj1lT6godSsnyuK1/RIqxumjFugOEInEumz+x0KGUNI+ruPsWjTRbt26lsbERVU1d5jZt2jQAtmzZctzkx1VXXcXRo0epra3lggsu4K677qK2tjAj/4rRv50Kbx2Bv3b3uSFxRA50ANNXwZZzwefD+s+sdbDheuCJ/k/a76/gWthSDaN+AeM/leV3IITIO9NuBmrE7MRFxKrQiAch1mU1HI11WYkNIwp6uNcEFdNOZqj0zHU17T+iYKpWUkIPWUkKJfGzndxQsZexKL2Wp2jWdlPpeT5Vw8RhjZ1FB9NhjaN1lYGjyqrm8I8FT51V1eHwDLhvx0DsJ8LTHOUVumkhlvxYtFM7KUkPE5iJk++XTWD7uC207E5dWmiaOsGgwfLlh5k0KWshDkjRJz+uueYarrvuOgB+/etfS/KjxHjdDkLh4jtxONDSzfrtLdx85XRUNXsfbGJwPO7irfx4/o3djKsvY8bkmkKHUtI87uKeWDXStLe3MynNkVplZWXy9kzGjx/Pl7/8ZaZPn47T6WTNmjX84he/YNWqVSxevDj5HAO1YcOGQd2/qalpUPcfCTK9p6+WwZruBrZTTjLjkViQbtuPybTXgvytajda8mLoPZQzlnE8zEC6OJmHP03XofvYp/43kJ3eW6X0/2kkk/c0MjQ1NSUTG4ppTT5RjBiKEUU1wijxEJoRQo0HUc0wGBFUI4ZCDMXUUXQDRTEwFRXTMNEUw26loaCaOgoKYFgFHYqCRhTT1DBUJ6oRtPIaihMTE82IgQI6LlRiKKaBiWIlNBSs10Oxis8UFdOMYyomqqFiqhqG4sJw1bG13U3UWYvhqkWnHCPmx9TdEFWgNQocsL9OnA4ccMDqCnjPp9DhVNAViKsmugo6JkZiaq4CcQOcCpzSDR8/EOYg73LqqXFWroz1e+5wGB57rIl77mnM675X9MmPvldnRGnxuDVC0eIrGX/xzT2oClx8Zv4bBYkeyWUvRbYsYd+RLjbubOWTH5iBksWrBmLwvG6t6Pavke5YfyeOddu1116b8vOCBQuYM2cOt912G4899hj/+q//Oqg4Zs2ahds9sDGBTU1NzJ07d1DPP9wd7z2tC8KU15KdPNLa5/Dx6e461l3Ue+tcOPJhOHg5EE1T9YF1WdP+a+ljG6ONq6Dqbph2/6DfR2+l+P9pJJL3NIyY9qQTI2Y1FDUi1vKReJD177zF7KkTINptV2pErOUqRtzum4E17UnR7OVuLqs3hhG3+244er4nsVxFsft1KKC4rO+NKKg+UGLWmFnVbS1LiYfsZqMuwG5Qqmh2PlbtaU6q2O8DFaviwwkOH7jLwDsKvHXWl6OMpvWbmPv++Vmt6EinnRjrCfAqQbYRpt1Kc+DFQMdEx6R3OsOw34YHuJQKPlc+Gv9YFwCf+MQo/vSnpYTD9jTdXnbutM7Ts7nvRSKRY14cKPrkhyhtXreDcCSOaZpFcxIXiek89/puzpwxhtpKGbtXSF6P9REaLLJlCf98bScOTeUiSa4VnLfIx3WPNFVVVWmrOzo6OgAGXb1xzjnnUF9fz9q1a7MSn+jh88Ha+TDlDewBjOmtB+Yug6beCZBRF4DvMGxfAGzq/6B0+cj2H8MbP4OGR2Dcx08kdCGEaffDMGJW0kCPWMtE9BBEgz39NWIBa9xrPGIlPgzDSmiYJv6unXC0s2daiuq0+2gADpd1f1UBYj2JjWg3aHZSOdoBDj9gWBNaHH5AsRIcDp/1vREDzYP1BJr1fHZ/Dpy+npGyuOz3FQXFaU9c8VlfTj+4qqweHa4q63EOL2iu/kkO1ZmzxEcXcXYRZjUhNhPmCHHC6BiYmJh2L2gVBcNO05jJZS4GMBYnH6eWS6lC6zVua9680VRUKIRCqQ2VFAX27g3Q0nKsT+jsk+SHKGoelwPdMInrBk5HcUxEeblpH52BKNecf3KhQyl5Hpe1TxXTKNLuYJQXV+/h/DPGUV3uKXQ4JU96fgwvjY2NPP/88xiGkVJZumXLFgCmTp066Oc0TVOqVHNkTCW8ORdmp6uo7nUEvAY44xV4+/xet/ur4PT3YNvXoOOHPdszNUQ1Ab0Ldt0Eu74OjT+D0Vec8HsQomiY9tQSQ7eqNMyYlTyIBe3ERtAa+apHrCRHPAxmpOf+RhwU0x7rqltVFaZufa96rPti2hUZEVTs1zLjVkIiHrAai6JAuMvqiWHqEItYyQY7aWL13ojbk1NMu5FFosrOtJIT2I1JtQqr94dir/tQ7PeoOMDhthIYqttqNuquAGeV9aervFeCQ8t5JUfa/x2YtBNnNxHWE2YbYVrQiWAQw1rSAgpxTExUNAwMTFQUVMBp/TbwojGPMv6FesbSvxrR63UxY0Y1hw61pr6+CV1dsG5dgMsvz8Mbto2o5Mcbb7zBJz7xiQHdd9WqVdTUZH+t+mDX2A5EMa6xK5S+v8uWI1bHs1VvNFHmGfnJD9M0+fPzhxlT7STSvoumpt05ey3ZL48vHLPy4Fu376LedTTj/UbS7/K1jV1EojpT6yPDMu7hGFMuRULdtAf0nLzvUvtdZsOll17K448/zrJly7jkkkuS25988kkmT5486Ekvr732Gi0tLZx++unZDlXYZtXC0llw8SZ6lrCkOfpdG4dpr8A788DTO+/beB80L4I9lwFt6ZMfvZbBWPbAtith2xQ45UEY9cHsvBkhhpvey04M3W4WGgMjbDULjYatpIYRgGiiOiMGZhh0e3qKiZUIUTV7XYRhV2noVoJB9VgJEcXAOuVOTEGJkRznGu/sqYqIdYLmwVQU67U1O7GRbPwTsxqOYo+V1VxYSQ4VVK+VvFCd4LBP7xW7yageA00F097ucFmP8fis53CWg6vSSnQ4/FYjUs1tfamFSXD0FUKnmRg7ibKVMPuI0o5OHIhh9SCxflMmBqY9wlZBxcRAsT86FUxMXMA0PNxADadRhnqMTknnnjuK5ctb0Xt9TioKxGLw9tuduXzL/Yyo5MeUKVO49957B3Rfv9+fkxgGs8Z2IEbsGrthKN3vsk3fzbNNa5k2fRaja3wFiix71mw+QnPHfu668Qze//7cjbiV/XJgdMOEv/6dulFjmTt3Wtr7jKTfZVw3eOjpFzitsY4PXLKg0OH0M5J+l9ny8qYmOna3Zv19Z/t3ebw1tsVi4cKFzJ8/n3vuuYf29nYaGhp48sknaWpq4uGHH07e7+abb+bNN99k8+bNyW3XXnst1157LZMnT8bhcPD222/z6KOPMnHiRG666aZCvJ2ScdFJsDgK12cagWvbAjS+Ce+dYY/BTah/P9Q0w7u3QfS3/R+YsS3PDth0NWwaDQ3fgSmfH1L8QuSNadrVEnbFRKKXhh6GeBT0AMTshIYetJabJJakKIad/DCtZAYqVsbRXhxhGvYYVzfWUhPDqtIwolYSIbFkRVWtqg9VtR4XbbeqJBQVop3gKLNzGN32UpREtYW7p+oCe8Ss6rKqMdCsySimYffkMKw4E9Ufhj1uFqzXUVTr/qrLSnKoHmt5itNvV3CUW4/VPFbiQ3P2LHcZRkLotKKznzA7ibKfOJ3oBDCJ27UdUay0UNxutmp9WdUeDgx0u9LDSt0oeICT8XAFFZyBD+cA0gkXXHASP/rRJut/sZ1ATvy5bl0g8wNzYEQlP+rr67n++usLHYYYQZLTOIqkbPyJl7ZRVe7mvDnjCh2KADRVweXUimZZwqtr99PSEeb2G+Qq9HDhcTuk4ekwoigK/5+9946Xs6zz/t/X3aaemoQUEgjh0AQhEAgldsVVdF3B1V1XZFUUxbI+PmtbC89akV2FZ58fK7qKuiJYEUR3QRBZGxqkl4SEQICQkJ5Tp93l+v1xXfc9k5BGOG3O+b7zmsycu14zZ2bOXJ/5fD/fr371q1x66aVcdtllDA4O0tfXx+WXX87LXvayve67aNEirrnmGjZv3kwURcyZM4c3vvGNvPe976Wzs3Oc7sH05eyF8F0N563dwwb2E/F6YME98NDxcHB3y3rXheP/E7b8Azz2WmDj3k9oMxINm+CpC+Gpf4Ce82DBZ6Bb/o4L40BWahK15Gc0bEZGzYRyxnWKA3fC45tMuUkc2ZITU/xg9rWCiFKmdWuq+CnfOjJsOYqy02elrNsCcztpWFHCOjO8fMttm18XWWEDIKmCU7bjT2fMqilQoMEt2vMpm8GhrdgBEJMo3wgTOgbXa7aZRRkBxA/M8dyccYf4RfC7wC+Z8QUlczzXbjcJxY1WIhKGSegnZAMRT9FgEw2GUFSJiElIcOw1xCiMz8Y4PtKmukZyUrhoNAqXBIVDJw5HU+BllDmK/E65Hvti6dLZdHRAvb7zcqVgwwaoVhsUCsHoPRh7oa3ED0F4tuRtN47qFGgVuXLtdu59ZAtvf+2xUya/ZCpQtKG67U6caH54y2oWzu3k5GNmT/RwBEs+cKfE+9dUolwuc9FFF3HRRRftcZurrrrqGcsuvfTSsRyWsB+89TBjlD9/VwFklz+pA8AhK+B3R8AZs3bZdtYSmPU0PPJ52P7pPZ8s2d3CEHZcCVuvBN0HCz4AB7/TpLMKwr7IhAAbBJpERsTQVqyIqla8qJqSk6Rml9eb+RppeQq2VMW6JArD66B/GOPQsIICWBEjsW4Imu4M1zXbxcNGIEi7m7hWLIhqzayMdKyedXmkvUF0bM9jp91OzuZtYIUNbDhpB+aV6xlxREfNcyahESZibWbtyjHnwSNxhqAwD/y8EUfcDiNs+AUjuDjWseFY18YkKEvZXyISqiT0E7GFkI3EbCFhiJAhEkIrcGhbumJkLFPqk6CIsvUKB4fENNwFsKUuRgbxgLkUWEKeEynvNtNjfyiVcixa1MHWrUPmKWVdH0rByAjce+9WTj993nN7UPaTKS9+VKtVfvOb3wBk9tPf/va39Pb20tvby9KlSydyeMIYU5hCzo8f3LKKrnLAWWcsnOihCC2Ydsrt//z63b3rWb9lmI+fdwqO0z4fAKY6hZxHvRETJxpXfi+C8Jx5x2HganjbU9gOD7vfLgGWPQJfG4J3L9rNBkd8CkY+BCvOBa7f/wFkfy7WwLoPmgvPh0PeDsPH7/9xhPYmFTJSYSAOgajZjjWuQWPElpZUbW5GxXQaSSKTnaFDQBsnRypY6JaWrCq9ViZfw01buqaNSVMBIsJRsa13SGxwqGeEgagOKjZCAa4RVHDM+qhh1inXHkfbUhVtxugXrGCijLtCJzb7wroxHBdUGZPBYVvCaityODS3AaBgbrtG2DBhonnj1nCKkEsDRPPGteF4DA49CIed0nbCRisaTZ2EinV1PJxXbGGQbUQMEVED6sS2Ea15DsS2M4v5jaisU0u6XqNxUbZviylw8Uj1WkUJhx4CjiLPiZQ4FB//Wbg89sSSJTO5446hnZYlCYRhxD33iPgxamzbto0PfvCDOy37zGc+A8DSpUt3++2MMHVIxY92L0tY9cR27l61mbe95nlZKY8wOcgH7e/8iBPNj361ikPndHD68+dO9HCEFtL3sHojopj3J3g0gjA1+PtFUPLgjY/ve9v3bITlg/CtxbtZWSrBKdfB9rWw8lzg9r0fbI9/Kh6Atf+bozVw8wKY9VqY/QaY9SLw5XU/6UnSriI2HyMJjcMhqtnrEStkpK6MYVt2YrM0dGSDP215SZLYFqyxDfa0mRUKu1w1g3eVdWA42mRYpIIHVpDQ2lyUZ10b1m2hAALTehVlYi61FVKcVBTxME9am72RBja4dqrsKHMMrODiFsganwbdGL8BLa1gY9BeizBjHRduwYgfju2O4udBFa1jo2zWe3kjeDiBuS+u11ICs3u047eIJ5ObBE3DujkqaLYRsYOIHWiGSBghpopmXafHXCq2yazxcZhUFYX182QVd9oe1z57bGELYJel/4Mih0MvLkeR52jyzCcgPwqCRyunnTabb3xjLVHUqjrHaA1//OPTvPe94yP+TvlZ1Pz583cKHBOmF/mceeFW27xm/vs3r6KzFHDWssMmeijCLhSmQCbD7fdtYN2mYT761pPF9THJyLcIuCJ+CMLo8deHwH8F8JrVu1m5y9vgtytw+3L48/N2CUJN6T0Mlv0Btj8MK/8euOPZDcaKIiZRYB1sucJccIFF0PsimPlymPVyKB307I4t7B9am6yLNOQzadjwzoZxY4QVI1yEI5R23Adr1thlw0ZASBpWwAghSgND411KO6zjIi0h0daVkT7htK0HcJymuJHNExOTtZFou84eJ6kbASS2gkWaS6HsbR1jhJCk5WAatGvFC0OCY0WPVGTxzbZuoTkeB3A6TM5GWtKS3tYOeFaIcW0XFMdmZng54+rwc6b0JCiadV5gy088I4S4vrkvbezU2BsaTZQ5OaBGwg5iBojoJ2HIih8hmthuC2l/GmU9GpBkWR1mfZwd3VxjpZGWZ5ZFERPj2HKWMj4H4XE0PoeQYybeqDg89sTJJ88kn48YGdm57EVreOih/jE7765MefFDmN4UbOZHrY3LEh5Ys5W7Hjauj4K4PiYdhZzHSDWc6GEcMGGUcNWNKzl0TgfLjh8fy6Gw/xQC80Gk1mhvgU0QJiNnzYHlHpyxoqVhi/3SfFdWATNWwM8Wwqv3FIvUezQsWw7bH4VV74fwpuc4whjiR2DLI7DlSlgJ0AXuYdB1IvQshs7F0Pk8KM58judqc9IWqUlkBYvQiBJRzQgEYc24LMIqxENGuIisoJFUzc9pJkbUMN1AdBoQivk5Np1IOga3w4YZdtKOdVcoKxIoKyK0zu7UzpP5VORQtkQknaam2yZWoGgVSJRVH7R9gipthBqtW86X9uRwWqI00g4mNphU2fXp2FTqyvCNsOLm7LGsyOIGTUHDtaUpjm9uuzY/w82bLA6vaEtPAtve1TMuDcdrGwfGcyUVOBok1NDU0fRbgWMQTdWKHA00oW0f20znUGj7c/N2Ws6iiVFoRVbWYs4Hjo0rTX0cqcCVyiAJCXkcisAsSswn4BA8ZhFQwmlxhIwtfX1ddHR4jIw052Tpy2Tz5gr1ekguN/Zf8shMSpjStHvmR5JovvXzB5nZXeC1L9xd0bEw0eRzLlsHqhM9jAPmxj+u5eltI/yfd54mro9JSFa6V2vP9zBBmOwsnQlPHA/Hr4Dt+9g2BM56HN62Cb69N4d27+Fw+o0wNARPfR62XA5Udr+t3v1iYA8tdAcgvhe23wtb0v09oAPUDCgcDMVF0Hk4FOZDbg4EB0FuFgRdZnLqTYKP/2lL1TgN4LSiRWRdFKmbIm7Y8M66uQ4roEeseFGzORg1I2LoxLg00vKROMH81lRLyGcqFti/d45jXAuKpuMgFSo04DrGsJGkTosYNw3tTEzJSDM/w7Z1TSATIrQyx4em2KFpChat5wKy7iWKlmM7LYGjqRhiS1Ecex8Se522dU3JymRs6Ut6H92cOad1XkR+A7oWWUGjaIQMv2S7opSsgBE03RmO7dwyBR0ae0NbV0YINEioo6kSM0jCIJoRYipoanZdw3ZKibLEDWXlDPOcMO1mHbtVYoUNZRvNNhsFK/svzXbxM6cHpAUtrSUuAS5lXGbicjA+88jRjUMJd9zEjl3xfZ8FCwo8/fTQM9YND8c8/vgARx019iLuJHj3E4SxI/BdlGrfspff3rueNU8N8KE3n0TOnx6qebvRzpkfw9WQH9y8mhOOmMmSo8VKPRnJyl7a2L0mCJOdg7vhyRPg5Pvg4d1tsMtc4TtV+MXd8KsFcMKu3WBa6eiAYy4xl6dugEcvhvhPozPotIoBMFOkHaB3QGUNVH4DW+02iYuZDnmYfIY8OAVzCbrMtV80k95solswTgEnZye7ntk/K9egWZ6Rdv+AppjhRMYlQcys9eugPgNUaNwXqlXosE4NYiNK0OJm0DaAM/0+27HuivTrbTe16FihQLvgOVYcgKb44DSFBI1xIGQtT+2YlS3r0MqWXWiaAaK2DES51gmCycdIopbyDDvlbHVfqMQKLK0ui1YXSCpgaHtOtykkpONXthQly/JwrK6RPiEd8ztwPePGcFzjwnB949DwcuCXrTPD/o69vP0d+/Z3bFwc/YP3wqKl007M2JW0O0rqzKiSULdujREShompYUpWQqBufRpNB4fKRItUtohIUgmLhu224pHYQFLH7pU6PBz7v8lOMT4IlwSNS4Krsf1WTOmLgyaHQx5FFx5z8ZiFx0w8ynjk0uffJKGvr+cZoacAjQasXt0v4ocgPFccR5lWkW04OW2EMVf99woWHdzFS06aP9HDEfZAIee1rbj2k1tXM1Rp8LbXHoua5h94Jivt7l4ThHahVIKVZ8A5d8B1u77cdjN/2AosXgfv2ALfONo2udgb819nLiMj8PQ34MlvAg89Q1jZb3bbSreFNPWQ2F4aZO6TxLoTInvyOHUYuDTvbMvEvbWcIhURUueBtusVdn87mbfH6AobsDHf3Ded5KfCgJMeOz2m2uV8aXmIZ+5HYgUA7ZJ9L66t4yFuzc5Ix0NT3HBSB4gN5VTWYp/YjiWOYxwneM0wguz+pA4MTZL+nI09FTPS7bBiSerSUDwj5yPtyILTfDwcm3fhBk1hwvGtoJEzomTjBxgAACAASURBVFSu1CJkFGypiQ0CdWxJinKNAPJsnBmON+WFj2bb1zR7wzgzRmyg6AiaERIaGFEjRNEgzvI1XBQhCeDiWfFDtZSqpM/XKhBYla4BgCIgoU7q0SBLgHHQOCg8NLEVPlyaHVliexwjYboUY81B+HThMguXg3DpxKMDl/wkEzp2xzHHdDXNTi2utyiCJ54YGZcxiPghTHnygdeWmR83/O4xNu+o8g9vOlHKESYx7Squbdw2wg2/e4yXLJlP3/zuiR6OsAea4kd7CmyC0G78dCn826Pwv7bu3/bfqsEP7oPvzYWz5+zHDqUS9P0vc6lUYOP34InvQngnmvpzGvtO7K2cJkuvxAokWUAENuCC5kqnZR8b2KmtiKCaEz4jQMRNkcGWXzjZMWmeQ7eUcyStYkuLGKCwooAVGJK4KRZozLmSdNvYuFPSUpZUjElnV1mZiR1rGuCZzsKcVMhxrDjjme1ct3ksZdc7Cq1yxhWTikDp0LPyFHsc12s6Z5TtZOIG4FgnRlAGt2xEjKAEbtE4M1TQsm2am9HiDBF2Ig0CTR0bkS072eTBI9QYsq1ia1aAaFhhIyKxoaImbyPCTIwVJmDUsZ4O84pwqNviFEgYBvKYbis1ILACRtWKGQ6Khn0Rui0FLw4KF42Lg58le6SiiGPzO6CAi4+iA4duFDPw6cVldX/MGXTjpWUwbcbhh3c1uy+3fKxJEnjssYFxGYOIH8KUJ5/z2m5yumVHlR/csopTj53DCUfuzVMrTDSFvEcUJ4RRgu9NftU95T+ufwDXUZz36udN9FCEvZC3oc2VNnsPE4R25oOHw4u64MWPwjMN2jzDrVHRcM5meP5m+MnBcOSM/TxRsQiLLjAXYOWvvsOSWSth043AahhNMeQZpNYR69Z4xrp0Rp/ebnF9ZMGb1kGBzZNIEut4SDKhwLg1Elum0uLwSFL3RRrkiVmvsvCL5lWrWyIVUBLrGklFifS+pC6LxOZh4NsSndSJkW6bhnA6zWNmrhTMurTjieM1y0TcgMhX0LnQdCtxrQvDL1sXRhm8DuvKyDcdGakIkpaniJCxT1JRI0ITWcEiBiq2FKViMzbSYNGaLVVJO6M82uGxhRpYx4drjxqj8G2L2LTEJA0VDW0BioNDDePCcFHUMaGiAVAjfXaqLMnDPJuNcBHYcfst/g7Q5Oxe5pwJBVxyQA5FGZceW7rSjUsRhxymBW2ryLEhAb8NHB574pBDOggCqNV2Xp4ksGHD8LiMQcQPYcrTjq1Iv3nDA2gNF7z++RM9FGEfpB2F6o0I3wsmeDT7x/IHn+bPKzbx9tc+j1k9hYkejrAXCrm024uIH4Iwnpw4EzaX4az74LZdV+7h0/MDwFHr4S82wncPhoOeramu5/lwwtuAS8xsYPtDsO0W2Pp7GLwHeJpMEEl1hwNC7ea6dTLe2jmEZ67LhAqaLo7UpZHlVpiyEZW1UN2lnCYtRUlLbdIcDNdt3s5KQdymewO3WfqCXZ/Y8TpW3IjtcseF2DGhpW7OnsezrVdz4NlcE69sWroGHeZ2rmRasnrWjeEXbGlJHtyALffezyHHLbWiSPtORCeKtPwksi6NENP+NQJG0NRtCUrVXmJbPhKTENlSEPPUT2wpinFdGKFE4RGT4BA6jhVQjKAR2eexg2YYbbMzYJiEonVSVEko2O2SLHPDjDmwYoeVxNBofFx8G1qqcCgQEdtjeTi4KAIUPooSDh24dOLQiUPZlqrkoG2dHM+W2bMLxlC1C0rB9u1jKfY2EfFDmPIU2sz5cdfDm7j9/qd566uP4aDe4kQPR9gHWSBlPabcBr+uWiPiP65/gAWzO3jdiw6f6OEI+yAvmR+CMGHk8/DrU+Hrj8N79tUKpoVfxjB7A/zlBvj6PJh7IJWFjgMzn28uR/1vsyyKYOhx6P8TDK2AgQdgZC2Em4FhTGcT+16xW3FE7XJ7FwFjp2+U0xnKLiKIosVFYYWK1PHhYIULGxKKslPaNHQ1zb5wm4fOMi/sOXWLGOI6oH3Qnikh8Wzwqpu3wkPOuCtc22416ASnbEWMIrhdEBStgFGyDoyc2Vb5xrnheOwUNro/uAWzr5CRWJHCZGqYPIwEsnKTGqYrSg0yl4YRP8jKQyLrojD/m5DQCGXlMUVIGv1pBBDjyDDb1mxZiQtUMd1QHJ0wTEIJFwfsbdVSwKVo9sYxr4GcvdYoSvacoOiALOPDtffXZHFofDx8TPBoiTxlXMo4dFjBI8Alj7IlNVNf4NgbnZ3ebsQP48sZGWmMyxhE/BCmPPnAZWCcXlDPlUYY8/WfPsDBs0qc/RKZmLYDqfOjXb6Z//Gtj7B5R5UvvncZnivfWE12PNfB95y2EnAFYarx7oVwTi+8fI1xd+wXEfzcg59vgBdugH+fDc/f33KYPeF50NNnLrtSH4GRDVBbB5Unof40VJ4217UtEA1DOAK6btrG6gSITZmJsiqJTnM/UlrKXVRT0MhKRxzHOCs8t0X0SMM2jdgRhjUozrBihi0D0YFxU/hWmHDz4JRaykZaykeCUlOwcG3WhmsDPpVvziVlJKOOtu6KqEXQaOZpaCtmJFSznxMrvTVDRc0zTFv5y5SNxCgb8gmxLQtxcYjYOeDTQWX7hXY7B0UV8OztGmQujAaaQtY+1rg0UODZZQmKovVrJCg60DRs9kYnaT5HQgA2+8OMt4g5hg8EOBTAOjhM29g8DkUcfOvwcKe5uLEv8vkAx9EoFaF1GhBsImVrtfH5nCPihzDlyec8Nm2vTPQw9osf3bqap7eN8Ll3n47vSWvbdqCQT50fk39y+uTGQX562xpesmQ+zz987NuJCaNDPmgv95ogTEVmdcL9J8HX18F7t+xiqtjHp+nfAcdvgkO3wac64Z37E4z6bMmVIHcEcMS+t41jiBsQ1yCsmA4nSQ2IIKybNrPENO9lawhq6hjxyTq+OI792bVhnb5dFvD4ffczY8mpgC1DkVKRCUFbZ4ZtLJy5M4yokfB4oFBUqGACQeskVGmWmqQlKgkajWNvOSj7HEmzM5Q9j7Iih7Zeh5hUWNM07DoXaKBMC9csV8OUlVRtWYoJH7VihhUuUoHBPvuIUXSgCK1w14lxl6Ch3CKiOJggUz8TNkxWR4BDFwofKOBQyi6KAi4BJmfDSHrTozxlrNDaCB8ASsVZJrFS4Hnj87iK+CFMeYo5ry0s44+tH+Antz7CS5fMZ/GRB030cIT9JB8YkWqyT07jOOHffngPhZzH+X953EQPR3gWFHIutUZ75RYJwlTl3QvgrTPgb9fAz/e24W7m908A7xqE9wxqXgd8ciYs6Z2AiZTrmtINClDoGdtzBd2mq4kwqsS2uWqU5WFoQtvNpAbUWlwZVWKqaCt2GAEkFTISTJvWdZ0um6lZuSKVuBy7hbKJGiZ8MyLGszkaaa8UD9MGNgJyGJljGEXZOkAqaIr2CBWbsUHmClHWJWL2TTCChGP3LZDKcMaxYTqxKAr2/vikXVVU9rIrAYWEzJWRw6GISxFFHkUZhzxOlseRujYcETbGlEYjJI53bnOb4vvj86WviB/ClCef86hO8olDFCf83x/cTWcp4F0SctpWtEsmw89++yirn+znI+cuobsjt+8dhElDu+UWCcJUp1iEG46H+7bCX2+ANbvbaHfmhhhwTVnAdcB1W6GrX3O2gvd3jeWIhcmKthKCCQBNxQzjyqjbUpM6xgkxZB0Zpl2rET0athVraIUIU0Cgs2ICJ3NnpNN60641sc4LbUcRq7TjibLCioNvkzEaGGEhb89psjfMtnXr1HAwokvqxoggEy/ABHqanidGfDCODU0HWUoNHWBdIWaCas7bzNnAOjbKdjxGtDCBoSXr5MhbgSOHw4qBiFMo44EIG5OE7dtDopaPMzYXGa2hs3N8PpuK+CFMefKBS7UeWavV5Hzj+/Gtj7B2wyCfeNtSOooSotVOFNLA00kssD21eYjv3fQwpx03hxcuPniihyM8S9qxXbcgTAdOmAmPzISfboL3bIItsPdP1uqZX3cOAN/R8J3+BC84nFc8HvGmIpxTgq6SfExvJ7TWRFoTKU2osJ1MWstI0pwMGLHLajZ7oo4msqUbcRbqCVjPRUIWGWtpFl+oLKQzaVliilIatpQETFiowiGimbTg2jIUUw6jrHBhXBm2UXHLGFTW16TVCxJYB0dAMwzUxbwUErushHFpOGjyKNuVxYw0AFwcXDRFW5aSwyUP5FGUcCkCOevW8DBtZP2WY+yuFKWkyXJAhMnBmjX9RFFLTrJtFOW6MG/e+HQflHdVYcpTyHkkiSaMEoJxslQ9Gx5/epAf/WoVL1p8MKc/f+5ED0d4lkz2spc40fy/H95Lznd57xtOmLQCoLBnCkF7lO4JwnTlnNnm8u2N8OHtsMfGMLubh0WAZ6aYkR9wUwI3DcM7ajBnW8Qy4JV5OKugmd/h7+YAwmgTJ0nWiSS0bocInTkxHg5caskIVTQVNFWgaoWMCE2kFZFOiBXEKsmcDWnpRiospDiYlsBOlp+RdjxpimWuzd1oyg7pcZohtanHw7XFKg6pJOLY5j/pEXV25Ji0t4kZRyqleDbbw0Hj4+CRkKBtNxRzP1xMIGiaCJM6PdLuLApa8jKULT9R5DHlKAUc2yHFCBqpCJJma4hTY+px992baDR27ogNJgaor+9A2mI9e0T8EKY82Tfz9WjSiR9hZMpdSgWfC86Wcpd2pDDJy15u+O2jrHx8Ox9680n0dOYnejjCAVDIe/QP1yd6GIIg7IO3zzGXH26Cj20zGR8Z+/OJe5e53kbgWmKuNSEOlIfqHAqcAiwN4EQPjs1BR0FKGVvRWhPqhLilTKNhHRYNNDWbgWGECyNeVKwzo24vIZq6FRoi+3MqSmzuDZhJBW2dBzFJ1hg4dSJowDGZm+bbbdJLmqrRvG3KQZpdTrR1TqRPCE0zMLTZkLgpjGgULmm4qM5+Tj0RRgRJbLaGETX8TLIwXVaCRGclJr7dxggZ6fgccna0OUwaSM6Wl/i2lKUI5FtEDdPeVeFZAaUpioioMV25+eZ1aN10fDiOKXkJAjjxxFnjMgYRP4QpTz5rRRoz2Upqr/nlwzz61ACfeNspdJXlw0s7kgsmr/jx2PoBvvvfKzn12Dm8dMn8iR6OcICkpXuCILQHfzPbXP60DT6yDX6/t429ZC8rdy6nHI7gIQ8eAr7TABoJbiWhe0fEXBcOBY4GDvNggQtzHTjYhW7Po5ib3J8xtNZEcUwC1LXJwGgADW0cGA2ghgn1HCGmQkINhxFi6lbAqNmSkrrdLnYhVMYFEaGtkKHs1J6dikRMK1XjPNBWEEgyEYJMlHBQhCp1YaSdS9yso0jDChTpOcj2b54pdVqky9MylJQ0iyM9jsqWq2wUjm0Rm4oXpmQlsW4JZcebZP6J1JVhxJnUC2LKR1wU5QRm4OEDeVzyuOQwZSMFTLioETqcLBzUI83yEDFD2DdPPLGdhx7abhpB7fKU6ejwWLx4fLoQivghTHkm6zfzD6zZyrW3PcIrTz2U058/b6KHIxwgrqPIBe6ky/yoNSK+fPWddBR9PvCmxVLu0sZI5ocgtCenzYDfzYDhYfjCdvhmDba2bvCcPoWn0ZawA9gRwwoXboQ0OROPBB9NkZi8X6cIdLVcujGdNLqAIjElD7rsN/kuECjT/tNV6aQdlL2tVTM3AsxkPW2dqltcFo+WciyvbCdCUcOUkdSskyIEGo6m4egsHyPt+hFlXgjzc+pzSOylVTxICz9cmynh2CVurHDcnZ0XZOUlzX4mTZkgbeWqW3Ix0hKUpg8jdkxmRVrakZCQ5mO4u4xJZbfVTpkd6b5pCKjZR2Xr0hKYtLVqswymOX5txY60vMTJfnfm9+bjE4AVMQIbCKop4lFAt4gZ8GB/yMl0ZMGk4s4QRpsrrrif4eHmz47tkp0kcPTRPcye3Tku4xDxQ5jy5HOTL5NhuNLg0mvuYt7MEu/6K2k72u5MxkyG7/xiBes2DfPZC04XV1Gb0y7tugVB2D3lMlxchotR3LVd8+V+0yZ3BMD2vdhv0hk1e3OMmA0TKygMAAMhsEtkSDpJd4iN6yECRyW41u6gtM1y0M1JfJrt4FinhAJUy2yiOaE31/WeDvJWHHCzbTSOLeRwE3N8x22OJz2Ot8sxdxUUWgUHhbIiiWN7lZifnQSU2xQZPCtsgMm1SMtJPLt9a1+UZvZG67h0dp2mb5jzO/Z+NcfTvD/alqEYZ4lqOU7qJkmzMrD320HZrA0rRNFsyVoE68bwKFpXRp60dSt2P7LsDHc/hIy8Ns4OQRgLHn10G1dfvQqtYxzHzZwfrmsEkLPPPmzcxiLihzDlac38mAxorbn8J/exY6jOv/7DC7NWqUL7ks9NrrKEO1Zs5L/+sJbXv/hwTjzqoIkejvAcyec8GlFCHCe4rnw4FYR2Zkmv4vu95vaft2u+3g83YDvF6JidVILd0TKPfca7gW5dr5855U2VgpbNnGwyb9uTakg0OC07e9m26X5N4cMFlFEMsu12kmWUsqGazfO61rGSDknbbIx0v/TY6X6t4keruNJ6vvR+pOJDmn8Ro3CtNyMt/UjFjdYxGY+Hsu6R1IGhMxdEcwwKP9EEVtyAZklM6tEwXU5MOYpSTTEiFTCatx1KKAqYNq2pkFHEI49qETJU1klFgkCFdqPRCPnQh37H1q0NXBeSxLxRpcJHZyf87d8eOW7jkVmXMOXJyl4ak2Nyeuuf1/GH+zZw3lnHcMSCnokejjAKFCZRWcKOoRr/74f3sHBuJ+eddcxED0cYBdLcomojplwQ8UMQpgqn9CpO6QXw2DIUccnq9dxT7ONOYHB3O+zr5b/TnHg34odqXZtqJXrXVTsJEWDKWdKMDGVzMKzekXlWXLtdui7VbxqOEQk8sLkYTVdI5uZQTcGleZ6dnSDNDIzm/uny1GnhZQJG6uYwI3Ss4JDY4E2y/bUtMcEu09n9SpenDg7TutWIG8VY04sJ8zRhn9iuJS4lHEqY1q4FZQJBCzjkSduzNlu1ipAhTHWiKOLTn/4Dv//9U1b4AKViXFfhOC6uC2ee2cXMmR3jNiYRP4QpTzZxqE98JsO6TUN8/br7Oe7wGZzz0iMmejjCKJEPvEkhrsWJ5tKr76Zai/jChUvwvcnV3Ug4MAq2dK9WjygXpNWlIExFZnV4vJkRvnyI+cyyYSjixprLr6oxdwLrPNi551Na3rEnmi1Q97bF7vZQe9hmf6fpOwV96p2P27ouc3LoZlZGun9rPobbct16jNZtUlFGEWdiCMQ4jsomO74N8VQ4eGjrvsB2JTHdTArWjVHGpQQUUZRwrCvDCBkPbX2SpQd3W/HDhn4qJdlagtBCGIZ8/vPLueqqVSSJzvI9XDctd4k55JAO/uZvDhnXcYn4IUx5JkvmR60e8aXv/plc4PLhtyzBdeSP5FShkPMYqYYTPQx+eMsq7n1kC+9/42IOnTM+wVHC2DPZSvcEQRh75nV4nN8B59uP6nEcs3Yk4o8h3F+H+3B4nITNQF1B4xkqyG6yRJyd8zKazVD1Tjkdrmre3jmrozWUsylEtIoTu+4XkRC0bGucGg6uTdvIzqd2Hlur68OhGVfi2+OmwkWAi4tpuxoARRxyQBeKkqsoKUXZlpSUcSliupeYsE9znICWziXOvt11GxPodmQKJQh7YseOChdd9Ed+/vPHCMMIx3HQOsH3wZS8KIpFnw9/+CRmz26M69jklStMeSbDxEFrzRU/vZ91m4b4zLtOZ0ZXYcLGIow++ZzLlv7qhI7hnlWb+cEtq3jZyQt45anjq6ILY0t+kpXuCYIw/riuS1+nS99OS02Y9fZqlbVhwqMxrE9gXQTrgM0o+tGMOFB3TdlJ2i0lLR9JWvIvFOYb2dQv0mzzurPrw8HBtXs6gOfs7MZIXRwe4EaaDnu7eTHlHzm0cV64RrwoWWGibEtJjPtC2VwMhzKObcNKlomRUwrfdqXxlMJ1HHFgCMIEsnz501x88R3cf/9WkiTBdV20NlKr0RY1vq943esW8pa3PI977713XMcn4ocw5cn5Lo6jqNQm7pv5m5c/ya/vXMebX3mUBFBOQYo5n+oEPr+2DVT5yjV3sWB2Bxeec7x88JtiFK34UamK+CEIwjPpLRToLcCSfWw3Uq8zEscMa80IUNGaitbU8GgAkdbEShFpk30RkvaM0bs4MVw85eG6kMPNuor4tmtKoFTWbWTV5nUs6Z1rXBZ2edAqViiF60qJpiC0O5s2DXLllQ9yww2Ps22bKdJLEuMxcxxQyslKXpYuncsXv7hsQl77In4IUx6lFKW8P2FlCY+tH+Dr193P4iNm8TdnHjUhYxDGlnLRZ3iCnl9xnPAvV91JvRHz8fNOke5BU5CSzfkYnkCBTRCE9qeUy1ECxvMrmDhK6MtJu3VBmKps2zbCTTc9xo9/vJonnxyhXte2la25dl0H329mEJ100kH8+7+/lO7u0oSMVz4lC9OCiZqcjlRDvvTdP9NRDPhHyfmYspQKPrVGTBQneOPcivSqG1eyYu12/vEtS1gwe/zSsoXxo1wIABiuiPghCIIgCMLE8/TTQ9x88+PcdNNa1q0bptFISFraRCnl4HkKx0lLXhxOPXUWX/7yi5gzgbl0In4I04JSYfydH1pr/u2H97Bpe4UvXriM7g755mOqknbgGKmGdJXH7/e8/MGnufa2Nbz69IW85KT543ZeYXwpFcyf6skQqisIgiAIwvSkVgtZtWo7t976BHffvYlNm0ao1xPiOCGONXEMSiUopQiCZvxxLufy2tcexsc+toSOjonNPRTxQ5gWlAvj7/z48a2P8McHnubtrz2WYxfNGNdzC+NLVpYwjuLHuk1DfOWau+lb0M07/+q4cTmnMDEUch6Ooxiujm8iuiAIgiAI05swjFm/fpC77trM8uVP88QT/QwPR0RRQhwrkkTbQFNwXQ34eF4COCSJZs6cMhdccBxnn903KfJ9RPwQpgWlgs/WcezG8ecVG/neTSt50YkHc/ZLDh+38woTQ6vzYzwYqYZ84dvLyfkun3zbUgJ/4v+YCGPHROcWCYIgCIIwfajVQtavH+ahh7Zw991bWL9+iIGBGnFsxJAk0dbpYdppmzDTGNd1UQqSxCGX81i2bB7vec/xHHZY9wTfoyYifgjTgvF0fqzfMsxXrr6Lw+Z28YE3LZbOG9OA8cxkSBLNl6++i43bKnzhwmXM7Ja2ydOBiQzVFQRBEARh6qK1ZmCgxlNPGcFj1aodbNo0wvBwSBRp6/LQRJERPRIb7qGUwvMUnqdJEs8uczjmmB7e8pajOeOM+ZPC7dGKiB/CtKA8TpkflZr5Rt5xHD7x9qXkA3mJTQfGM5Phml8+zJ0rN3HhG46XcqppxETkFgmCIAiCMPVIkoShoTqbNlVYu3aA1au3s2lTlf7+GmEYE0UQRTFJYq7jWBFFEMca0Liug1IJjtNsgu15DosWdfP61y9i2bL5FOwXg5MNmZkJ04JSwSeMEhphPGYlAkmiuez7d7N+ywifveB0ZvcWx+Q8wuSjXLTOjzHOZLj9/g388FerOXPpIbz69IVjei5hcjERuUWCIAiCILQ/jUbE4GCDLVtGePLJIdau7WfbthoDAyH1eojWijCMiGPQ2ggeYWjcHVqbNrWep2xJi4vjmNIW0BSLPn193Zx11iKWLJlLLje55YXJPTpBGCXKLYGUvWMkfvzwV6v504MbeddfHccJR8wak3MIk5PWwNOx4omnB7ns+3dz1KE9XPiG46Wcapox3rlFgiAIgiC0H2EYMzLSYGDAODvWrRtk06Yq27fXqFRCoihB6wStNWEIUaRJEpPjkSQQxwmmNS1EEbguuK6L1gmmgkXjeR4zZuQ56aRZvOAFCzj88B7rApn8iPghTAtKLYGUvZ35UT/+8gef5ppfPszLTl7AX75w0agfX5jc5HwX33PGrCxhuNLgC9++g0LO45/+/hR8b3LVTwpjjzg/BEEQBEFI0VrTaESMjITW1THMxo0VNm0aYWgoolJpEIaaKFIoldBoJDgOaJ0Qhmb/OE5LWcBml+I4DkpplHJxXXAcRRwnOI5LZ2eJvr4uXvCCgzniiB46OkZ/TjXWiPghTAvGMpDyiacHTcvR+V28969PkG/kpymlMZqcRnHCJd+9ky39Fb544QuY0SUBp9OR8cotEgRBEARh8mAcGgnVaoPNm6usXLmFzZsrbN1aob+/TrXaYGQkIY7NRWuFUtoGlZqAUq0TlFLU60kmdkSRaVHrugqtNb6v0NqIHVqbXBDPc+ns9Fm0qIvFi2fT19dDT097fw4V8UOYFqSBlKOdydA/VOezV/6JQs7lk28/lZy0HJ22lAv+qItrWmv+4/oHuPeRLXzwb07kmMN6R/X4QvuQ5hbVw1jeZwRBEARhipEkxslRrUZUKiH9/TV27KixbVuNoaGQkZE6q1dvYu5cDzCujSTRKAWNhrFtGPdGQhgmKOUQRRowJS5RZM7jui5JkhAELloDaJTyiOMEpUyGR29vniOP7OHII3tYsKCTcjk3IY/JWCDihzAt6LIv2oHh0RM/GmHMF79zB/3DDb70Pmk5Ot3pLAUMjoyuuPaL36/lxtsf5w0v7eMVSw8Z1WML7UVnybjXBobrHNQjYcqCIAiC0G4kiREmarWQej1hcLDOwECd/v4aQ0MNBgdD6vXIhpBq6nWN4xgnRxwbB0iSmGvj0nBoNEKSRKO1Y4WQBMdRJAmkXWaTxMFEcpgFWmsrfEChkKOrK2D+/DJ9fT0cfHCZGTMKeFO0xFrED2Fa0G3Fj/7h+qgcT2vN//fje1n5+HY+ft4pHLGgZ1SOK7Qv3R051m0aGrXj3fXwJr75swc49dg5nHfW80btuEJ70mPravuHRPwQBEEQhMmIydHQ1OsRYRhTqYRZJsfgYJ2hoTqVSkyjt+xMPwAAIABJREFUEVOtRiRJYstPTIlJ2lZWKVOeopTJ26jVoqyLSrUa4lpVI4oitAatHVzXlN0HgWk/mySglEIph8Q0bsF1HQoFl66uHPPmlVm4sIPZs0v09BQJgqkpduyKiB/CtCCf88gHLv1DoyN+/PjWR/ifu57i3FcdzbIT5o3KMYX2pruc4/5Hto7KsZ7cOMi/XHUnh87t5B/fsgTHkRyZ6U53x+gKuIIgCIIgPDtM+Yhxb4ShKVGpVk0b2aGhOsPDDSqViEYjoVqN0NoEjSqliKIE1zUdURqNCKWMGyOOzfKRkTpB4KE1VCoRuZyL4zjUajUcx8F1TU6HyRZUuC72GBowrg/ToUXb2x65nEuh4DNrVp65c0vMm9fJjBl5SiV/yjo79oWIH8K0obsjNyrixx/u38BVN67kJSfN502vOHIURiZMBbo78gxXQ8IowfcOvN3XwHCdz165nMB3+dQ7TqUwyfulC+ND5l4bhfewa375MH+4ZzNLljznQwmCIAjClMC4NpIW54YRMGq1iKGhhhU2QituGPeGyekwtoo4jvA8PztGoeAxMhLa7inYVrIJcRzhug5RlBDHMUHgWqHEiBpxHOM4xqVhQkcdK4oogsA4N1I3h+OYgFPPcyiVAvJ5l56eAnPmFDnooBI9PXlKpQDfd6Qhg0U+VQvThu5yjv7h2nM6xpp1/Vx6zd0cfWgPH3jTYnkjETLSb+YHhusHnP8SRiZHZvtgjYvfu0zKG4SM9Pm1Y+i5vYf98k9P8P2bV7H0yPJoDEsQBEEQJj2pYyO9NBox9XrM8HCDWi1kaMiIGvW6ETu0hpGRBmAECK1N+YnvO1bAMOUlw8MNcjnPlquYjI0wjFBKMThYx/PSrioxvu9Sq0V4nummEkUxQeCgtcJxjIBhjusSBBDHxt1RKvnU6+C6pjNLqWScG6WSz8yZeWbOLNDbW6BUCiiVArzn8AXcdEDED2Ha0NOZZ8OW4QPef9tAlc99azld5YBPvH0pgXRcEFro6Wh+M38g4ofWmst/fB8r1m7nI+cu4ahDpbOL0CTwXUp57zk5P+57ZAtXXHsfJx45i784KRjF0QmCIAjC+JNmbMRxQhjGhGGS5WmsXTsIbKBSiRgZaRCGCVEUU6slWdZGHJtjOI5xZhiXRkK9HlMsBgwNheRyJjejXo/w/YBKJbLbGzeFCR81waSVSt1mb5iQUuPmAM9zSBLjBkk7sXR0uLb8BYpF023FcbBCSEAQOORyLuVyjp6eHD09BZ58ssELXvA88nkP33elLPoAEPFDmDZ0l3M89Ni2A9q31oj4/LeWU62H/MsHXpSFDwpCynPNZLj2tjX8+s51vPmVR/GiE+eP5tCEKcJzKd1bt2mIi//zz8ybVeZj553CwyvuH+XRCYIgCMLo0OrUCMOYKDKCRL0eU6uFNkg0olJpWCdHQqMREYamtWsYxjzxxCBDQ5vR2mRuOI6iXk8oFDyq1RDf93AcxfBwRG9vju3baxQKPo1Ga0eVhCgypSiu69BoxJlAEccJoOxtSBIoFHzq9Zh83iUMzbKODo9aLcwyPkw4qRFLurryeJ6iXPbp6MjT3Z2jXM7R3Z0jn3fJ5z2CoDldbzTW0dUlc5DngogfwrShpyPHUKVBFCd47v5bwpJEc9n37+bR9QN86h2nsnBu5xiOUmhX0kyG7YPPvizhjw9s4Lv/vYIXLj6YN7/yqNEemjBF6O7Is+MAxI+B4Tqfu3I5nqu46PxTKRX8MRidIAiCIOyZJEkzNZIsNNQ4MSIqlZhqtUGtFmU5G6moEUVG9HBdZfM1FGCutcYuTwUHk7dRLHoMD4fkcg79/UbocF0TNFooeDQaRiQx+RqKSsU4MLQ2rhCllO2igu3CEuE4mlzOiBvGwRGjtaJQcBkZScjlPLRO8H1l9wPf9/F9h85On1zOo7MzT09PQLEY0NGRs4Gk4uIYT0T8EKYNs3qKaA1bdlSZO7O03/t9579WcPv9T3P+645j6fPmjOEIhXZmRlcBR8HmHZVntd+qJ7bz5avv5sgFPXzwb0+UHBlhjxzUU+DBZ+leq4cxn//WcrYNVPnChcuYM2P/3/sEQRAEYU9orTNBw5SQGMdFoxFTq8U0GhGVSki1GlOpNAhDTaMRZoJGkmjq9QTHUTYXw5SfKGXatHpe2iHFuCsGBup0d+fp769RLgdZKGlnZ45aLSQIHOr1GK011aoRH+r12I42oV7XOI5LHJsylSBwqFSM+OH7DtUq5PMejUZCELh4nmPXuRjBRWelK0mi8X0Hz3NwXZfZswNyOZdSKaCjw7dOjhz5vGdLVBw8TwSOyYCIH8K0Yc4MEx65cdvIfosf//WHtVz3P2t47bLD+KsXLRrL4Qltju85zOwpsmnb/osfG7eN8LlvLaenI8en3nEqOcmREfbCnBkl/ufup/a7o1CSaC675m5WPbmDj513CkcvlBwZQRAEYfe0ihlxrG03koRaLSKONSMjDVt6EjE0FFqhw3Q+CUNtnRAQhkbYUEplXU7S1qyNRmKdF6Z8xPNcBgcbdHfnGBxsEAQOrquoVkO6u3MMD0fkch5xHAHKHjsNLjXnqNdTN4hCa43nuSSJEVCCwGV4OKKjw7NZHUbMcN0Yz3PI5Txc14SOFotG+MjnXcD8je3uzuO6iu7uPPm8aRvb0RFQKHiUyz6FgkcQuPi+a4UQCRud7Ij4IUwb0m88N27fv8npn1ds5D+uu5+lz5vDO1//fPlGXtgnc3qLbNw2sl/bDlUa/PM3/kQca/75XadlmSGCsCfmzDDutc07Khw8a9/dWv7zv1bwh/s38I6/PJZlx88bhxEKgiAIk4U0DNR0J9Et2RkRYRhn5SW1Wsx9921l69ZHqdWMmyIMY7tPYvM3YlJxAchyLoKg6drI502piWnFqqhUjIAxMNDA993sOEniUq/HOI5DGEY2iyPOxpt2S6nXI7TWKAVxrFtKURT5vEu1GpLP+7gumXtDKcjnXaJI2zKYmGLRlMOknVUajZju7oAkUfT0FPA8l3zeoVDwrLhhSlTSFrG5nJuVpYhzo/0R8UOYNvR25vFch037MTlds66ff7nqThYd3MVHzl2CK292wn4wZ0aJO1Zs3Od2YRTzhW/fwabtFT737tOZf1DHOIxOaHdm91oBd9vIPsWPG29fy0//Zw1nnbGQ17/48PEYniAIgjCGxLEJ4mwNAjWui4h6PaZaja2YEWYZGkbw0DQaUYsQYo6VfqkXx7Bu3RBxvJ04hlyumZ1RKnlUq3EmKoyMhPi+Qxhq253Esx1LHGq1CIB63YzTdR1GRiJbxqJtloZDGCaAad0ax0ZM8X1FpWLOXa1GeB7kch7VakQu52ZtZl3XoVDwCUNNqeSjtRFESqXAihp54ljT0eHR2ekza1aJQsEjn09FDdMiNpfzyOVcXNfJylfkS87pgYgfwrTBcRSze4ts2Lp38WPzjgqfvfJPdJQCPn3+aeRz8jIR9o85M4r0D9Wp1EKK+d2HSmqt+bcf3MtDj23jH9+yhOMOnznOoxTalbRcb8OWEZYcveft7ly5ia/99H5OPmY2F4hrbdQZGRnhsssu46abbmJwcJC+vj7e97738fKXv3yf+z755JN86UtfYvny5SRJwsknn8zHPvYx+vr6xmHkgiBMBprlJcYJEUXaOjJCwjCxAaAhYWjKS+r1kGo1ykpL6vWYJDGlJqmYoRQopTOHhOcp4ti0WvX9pjvD81x7fnBdrJNCkySKMDQui1TsABgYCAkCRZIohocb+L4ROUzpiHFnOI6y7gxzLNc1Yoc5t0OjEVIs+tRqCUppCgWXajWmo8MEj6YtYfP5pnMDTDlLR0dAFKVChyaXc+nszOG6inzeODWKRY9i0Sef98nnXXI5D89zeOCBEZYu7bPjk7+DgkFmdcK04rB5naxe17/H9cPVkM988080wpjPvecMejulnZSw/xw2rwuAx9YP7FHU+N5ND/Obe57ira8+hpecJC1thf2npyNHZylg7YaBPW7z6FP9XPLdP7NwXhcffevJUn88Brz//e9nxYoVfPjDH2b+/Plcd911vP/97+drX/saL37xi/e437Zt2/i7v/s7ZsyYwSWXXILrulxxxRWce+65XH/99cyZI4HagtBupI6G1Clh2q3GmUiRChmmxCSmUqlTq8VZdgaY0M8oMnkZaZ6F4ziAtpkZpqOJaauqSRIHpRLAsa4KIyqAa0WLhEZDWzcDDA4a8UEpzeBgnVIpsLcbdHTkUMpkbLiuQmtFrRZSLLpo3TwPmNasSaKzshKtjSgxMmI6rTiOolZLyOd9kiTMSkXyeQ+tIQg8HMc4Q7q7PeLYhIe6rsn+6OnJEwQmh6NYNHkaqUsjCDxyORMammZruO7eRY0gcOVvoPAMRPwQphVHLOjm9/dtYGC4Tld554yFWiPi899azoYtw/zzu07n0DnS0lZ4dvTN7wbgkXX9uxU/bvjdo/zoV6t55amH8saXHzHewxPaHKUUfQu6eWQPAu6GLcP88zeMa+2i80+lIK61Uec3v/kNt99+O5dffjlnnnkmAKeddhrr1q3jS1/60l7FjyuvvJLBwUGuvfZaZs+eDcDixYt5+ctfzhVXXMFnPvOZcbkPgiA8e6IoYceOKuvW9XP77Zt47LGHqFZDGo3IOjE09XpaWqJtRxEjTCSJxvNMAKdSxomstcJxwHEgSYyoYeM0cF0na+2qFFmL1/Q4jpNmcZiWq6CpVhN8X9vSlBiljOsijpvho2mJCJgAUs9zbBipKTdJb5dKAVEU43lQKvnUajH5vJfdL9/3rChihJlcLhVdNOVyDlB0deXxffB908Y1l3OtoOFbx4a5eJ6TdVYx3VUcydUQxhT5ZCRMK45Y0APAyse3c9pxc7PlUZxwyXfvZMXabXzk3JM54YhZEzVEoY3p7shxUE+BlY9v5+xd1t121zq+cf2DnHbcHN77huPFgikcEEcu6OFHq1YxXGlQLgbZ8m0DVT799dvRaD57wenM6CpM4CinLrfccgsdHR07lbgopTj77LP59Kc/zZo1a/ZYwvKrX/2KM844IxM+AHp6enjpS1/KLbfcIuKHIExSoijhsce28/jj/WzcOMzatYNs3LihpaOJ+Xueln8kRhewbgwjeDQajv3ZtEwFsvIQpRySxLR8NeUqJmS00Ugy0SQycRp4ttNXKqSY28Yh4nmOzeEwwofWJmOjVDK3tYauroAwBMfRdHbmCMMY13Wsc6NZPuP7ThZS2tHho5RDLmfaupoAUId8vrXkJG3pqmwIqZNldLiuCBrC5EHED2FacfTCXkoFnz89+HQmfoRRwleuuYs7V27ifX99Ai9cfPAEj1JoZ04+Zja33rmOWiMiH5i32N/ft57/+4N7OL5vJh85V0oRhAPnlOfN5ge3rOKOFRt52cmHAEb4+NTXbmeoEvLFC5dJgO4Y8sgjj9DX12ct6U2OOuooAFavXr1b8aNWq/Hkk0/yqle96hnrjjrqKH7xi1+wbds2ZsyYMTYDFwThgOnvrzIwUM/KUXzfJQh8BgfrKKUxbwfNTihKKVw3FTXMMRwnzbbQtmsIVtwwJSVA1tnE7KNQStvPCxrfVziOg+eZnA7fNwKEWefY24ZyGdvqVWfukTjWBIGTtXo1rVnB9/M2lyPHUUfNpFAw5SbFok8QeBSLXktLWJWJGd5+tFsXhMmIiB/CtML3HE4/bi6/u289b331MXiuw1euvot7Vm/h/Ncdy6tOXzjRQxTanBcsPpj/vv1xbl7+BK9dtog/PjzEzfc8xdGH9vLJty8l8N2JHqLQxhyxoJvZvUV+/vu1vPikBTy+YYAv/uefGRpp8H/eeRp9C7oneohTmv7+fhYuXPiM5V1dXdn63TEwMIDWOtuule7u7mxfET8EYfIxNNSw5SygtVEzgqDp4IiitJTFuDlSMSTtbgLKZnYY4cKIH9o6K5R1bRhxQymT1WGEFNc6JhxSvdUIGi5BYESSNAOjtXwkl3PI5fxMyDDXLrmccWgEgZc5O1zXlL/cc0+Fk0+WzmDC1EfED2Ha8cZXHMFv73mKD132G+phTCNM+Ic3LebMUw+d6KEJU4DjFs1g8RGz+NYND/Hz3z3Gxm0VTj12Dh8+d0nmBBGEA0UpxbmvOpqvXHM3F37pVjbtqNBdDvjihctE+Bgn9laytq9yttEsd3vwwQef1fZ33XXXqJ17siD3qT1o9/u0eXOVLVtqDAw0GBho4DiKp55aT39/w+Z2KFv+gs3WIFsOZOvS26nDA0zHlXS9cVa4mTBi3Bna5mYoCgWfIHAJAmXbtxrRo1leEmciius2svFrDZWKuewJpVTb/552h9yn9mA875N8EhemHfNmlvnsu8/guv9ZQ7no8/oX97FwroSbCqODUoqPnXcy3/3vlWzpr3L6kTne/oalkvEhjBovWbKAehjzh/s2cMbxcznnpUfQWQr2vaPwnOnu7t6tu2NgwHTg2Z2zI12ulNrtvumy1AGyvxx33HHkcrl9b4j5YLlkyZJndfzJjtyn9mAq3KdKJWTNmu1s3jzMtm017rtvJQcdNI9yuZG5PIzY4VjhQdkOJo51aTjkci5B4GWlJKVSQD5vupkUiwG5nGs7n5gSk9a8jH11NRkNpsLvaVfkPrUHo32f6vX6Xr8cmPLixy9/+UtuvPFGHnjgAbZs2cLMmTM55ZRT+MAHPsD8+dJmcrpy7KIZHLtI7MXC2FAuBrz3r08AzJu6CB/CaPMXpy3kL05bONHDmHb09fVx880323DCZs376tWrATjyyCN3u18+n2fBggXZdq2sXr2a3t5eKXkRhElKseizcGEXjtPMzpg3r0wQuJTLAcViQKHgUioFWWvWQsHPcjmMgyPtZjL2QoYgCHtmyosf3/zmN5k5cybve9/7mD9/PuvXr+eKK67gnHPO4dprr2XBggUTPURBEARBENqAM888k5/85Cf8+te/5hWveEW2/Prrr+ewww7bY6cXgFe84hVcffXVbNmyhVmzTEex/v5+brvtNl7zmteM+dgFQThwOjvzHHusCQXt6trB0qUnPSP4WBCEyc+UFz++9rWvPePblJNPPpkzzzyTq6++mo9//OMTNDJBEARBENqJF7/4xZx66ql88pOfpL+/n/nz53P99ddz11138dWvfjXb7q1vfSt33HEHq1atypadf/753HDDDVxwwQW8733vw/M8rrjiCjzP4z3vec9E3B1BEJ4FSqnMzSHChyC0J1Ne/NidjXTBggX09PSwcePGCRiRIAiCIAjtiFKKr371q1x66aVcdtllDA4O0tfXx+WXX87LXvayve47c+ZMrr76ai655BI++tGPorVmyZIlfO9732PevHnjdA8EQRAEYfoy5cWP3bF69Wq2b9/OEUccMdFDEQRBEAShjSiXy1x00UVcdNFFe9zmqquu2u3yhQsXcsUVV4zV0ARBEARB2AvTzrPVaDT45Cc/SXd3N29+85snejiCIAiCIAiCIAiCIIwxbeX8WL58Oeedd95+bfvHP/6R3t7enZbFccxHP/pRVq5cyde//vVnrN8f9tY650CZiv2aJwp5LEcPeSxHD3ksRw95LEcPeSwFQRAEQZhOtJX4sWjRIi6++OL92rZcLu/0c5Ik/NM//RO33HILl112GcuWLTugMRx33HHkcrkD2nd3TMV+zROFPJajhzyWo4c8lqOHPJajx2g/lvV6fUy+HBAEQRAEQRgt2kr8mDVrFuecc86z3i9JEj7xiU/wi1/8gn/913/lla985RiMThAEQRAEQRAEQRCEyUhbiR8HgtaaT33qU/zsZz/j4osv5jWvec1ED0kQBEEQBEEQBEEQhHFkyosfn//857n22mt505vexMKFC7n33nuzdeVymb6+vgkcnSAIgiAIgiAIgiAIY82UFz9uu+02AH70ox/xox/9aKd1S5cu3WM7ul3RWgOmW8xoU6/XR/2Y0xV5LEcPeSxHD3ksRw95LEeP0Xws07+N6d9KYew40M8jU/G1I/epPZD71B7IfWoP5D7tnX19HlFaPqnsF0NDQ6xevXqihyEIgiAIk5YjjzySjo6OiR7GlEY+jwiCIAjC3tnT5xERP/aTJEkYGRnB932UUhM9HEEQBEGYNGitCcOQUqmE4zgTPZwpjXweEQRBEITds6/PIyJ+CIIgCIIgCIIgCIIwpZGvZwRBEARBEARBEARBmNKI+CEIgiAIgiAIgiAIwpRGxA9BEARBEARBEARBEKY0In4IgiD8/+zdeVhUZfsH8C87igsirrgvM4Cg8Cpi4r6n5kKkWZqWZWVmWeaWmr1Z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vPFGhYSE6NChQ9q9e7eeeOIJV1ymSzi7LUtLS3XttdfqxIkTmjZtmi655BLl5+frpZdeUlBQkD788EP5+/u76nKdqr5tmZKSoqVLl6p379767rvvlJmZqezs7Gq3nTFjhj788EM9/PDD6tmzp9atW6f/+7//08yZMzVhwgRnXRoAAIBz2FBv69ats0VERNg+//zzinXl5eW2m266yXbNNdfUuO9zzz1n69Onj+3777+vWHfs2DFbv379bMnJyZXW9enTx/bMM89U2n/OnDm2Xr162f73v/856GrM5Yq2LCgosJWXl1fZ/9Zbb7UNHDjQAVfhHlzRlr80e/Zs2+jRo21z5syxRURE2E6cOOGYC3EDrmjL8vJy229/+1vbPffcU+396S1c0ZZff/21LSIiwvbvf/+70v7//ve/bREREbavv/7aQVdjrotpy3PnzlX896xZs2wRERHVbpeTk2OLiIiwvf7665XWP/LII7YBAwbYzpw5U/8LAAAAMAHDXi7CypUr1bx5c40YMaJincViUVJSkvbs2VPjUIFVq1Zp8ODBateuXcW6li1bavjw4Vq5cmXFui+++EIlJSVKSkqqtH9SUpLOnj3rNUM2XNGWISEhslgsVfbv06ePCgsLdebMGQddjblc0ZZ233zzjd566y39+c9/lp+fn2MvxA24oi03bdqknJwc3XnnndXen97CFW1pvwebN29eaX/7997S6+Ni2rJBg9r9s79q1SpZLBZdd911ldaPHTtWJ06c0Ndff12/4AEAAExC8uMi5Obmymq1Vvlj0j6GOicnp9r9zpw5owMHDigiIqLKZ5GRkSooKFBBQUHFOSwWi7p3715pu65duyogIEC5ubmOuBTTuaItq2Oz2bRx40aFhYUpICDgIq7AfbiqLcvKyjRjxgxNmDBB0dHRDrwC9+GKtty8ebMkqby8XBMmTFBUVJQGDBigRx55xKvqVLiiLfv27avo6GilpKRox44dOnXqlHbs2KGUlBQNGDBAl156qYOvyhz1bcu6nqN169YKCQlx2jkAAABcieTHRSgsLFSLFi2qrLevKywsrHa/EydOyGazVbtvcHBwpX0LCwvVpEmTat9YBgUFnfccnsYVbVmdN998U5mZmbr//vvrE7ZbclVbvvLKKzp58qQeeughR4TtllzRlocPH5YkPfDAA+rXr58WLlyoRx99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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_results(t, v, u, 'Fitzhugh-Nagumo')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can do the same for the Izhikevich model" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "t, v, u = get_results(izh_model, izh_lookup, n_time_steps=100000)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABEwAAAMfCAYAAADMvEJtAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8li6FKAAAgAElEQVR4nOzdeXxU9b3/8feZJQl7WBRUUMBIFFFKQUGkoiyiXrsAVq9VvPaK2iqWqz+13B+/wtWf1K2FliLIrdyfrYq2l02tVkVRXLDaxuUWN1C0Iq2KQAhkme2c3x9nmclkEjKQyWTmvJ4+IpMzZ/nOmSzn+87n+z2GZVmWAAAAAAAA4AnkuwEAAAAAAAAdDYEJAAAAAABAGgITAAAAAACANAQmAAAAAAAAaQhMAAAAAAAA0hCYAAAAAAAApCEwAQAABzRhwgRVVlbqtddeO6jt16xZo8rKSs2ZMyer7ebMmaPKykqtWbOmVcs7AvdcffbZZ4e0n8rKSlVWVrZRqwAAQLYITAAAAAAAANKE8t0AAACA5txwww268sordfjhh+e7Ka12//33KxaLqW/fvvluCgAAOAQEJgAAoMM6/PDDCyoskaSjjz46300AAABtgCE5AAAga7/61a+8OTaa+5gxY0bGbfft26fbbrtN48eP17BhwzRx4kQtXrxY8Xi8yboHM1fJ7373Ow0dOlTjxo3T+++/3+i5/fv3a+nSpfrOd76jESNGaPjw4fr2t7+tFStWKBqNNlp32rRpqqys1Kuvvtrssb7//e+rsrJSTz31lLespTlMotGoHnjgAV188cUaNWqUTjrpJE2cOFHXX399i8dZt26dpk2bpuHDh+vUU0/Vddddp7/97W+tPSUAAOAgUGECAACydsIJJ2jq1KkZn3vzzTf1ySefKBBo+neZmpoaXXTRRdqzZ49OPvlkRaNR/eUvf9E999yjzz//XD/96U8PqV2LFy/WPffco8GDB+u+++7TUUcd5T23Y8cO/eu//qs++eQTHXbYYTrllFNkGIbeeust3XXXXXrhhRe0YsUKlZSUSJK+853v6J133tGjjz6q0047rcmxvvjiC/3pT39Sjx49NGHChAO2bffu3Zo5c6beeecdde7cWV//+tfVvXt3/eMf/9CGDRsUiUQyHmfhwoVasWKFTjrpJJ1xxhl6++239cwzz+iNN97Q448/rl69eh3CGQMAAM0hMAEAAFmbNGmSJk2a1GT5W2+9pSeffFKlpaW6/vrrmzz/3HPPacKECfr973+vrl27SpLefvttXXzxxVqzZo1++MMfasCAAVm3Jx6Pa/78+Vq1apVGjBihZcuWqWfPnt7zlmXpRz/6kT755BPNnDlTs2fP9oKRmpoaXX/99Xr55Ze1bNkyzZ49W5J0/vnn66677tLTTz+t+fPnq1OnTo2O+eijj8o0TZ133nnevlry4x//WO+8847Gjh2rn//8542CjpqamibVMK5Vq1bp4Ycf1sknnyxJqq2t1fe//329/fbbWrlypWbNmpXdyQIAAK3CkBwAANAmtm/frmuuuUbRaFR33nmnvva1rzVZp0uXLlqwYIEXlkjS8OHDdcYZZ8iyLP35z3/O+rh1dXW65pprtGrVKk2YMEH3339/o7BEkp5//nlt3rxZo0eP1k033dQo4Ojevbtuv/12hcNhrVy5UpZlSZJ69eqlb3zjG6qrq9P69eubHPexxx6TZFeiHMjmzZv14osvqry8XIsXL25SFdK9e3edeuqpGbedPXu2F5ZI9jm84oorJOmgb/MMAAAOjMAEAAAcspqaGl199dXatWuXbrjhBp177rkZ1xs2bFjGISSDBg2SJO3cuTOr4+7evVv/8i//oo0bN+qiiy7SkiVLVFZW1mS9l156SZI0ZcqUjPs5/PDDNXDgQFVXV+uTTz7xlrvDjh599NFG67/zzjvaunWrBg4cmDEYSvfKK69Iks4++2x169atVa/N9Y1vfKPJMvd8ffnll1ntCwAAtB5DcgAAwCGJxWL60Y9+pI8++kjf/e53ddVVVzW7br9+/TIu79y5syQ1mXj1QBYtWqR4PK7zzjtPt956a7Pr7dixQ5J06623trieZIcwbiBx5plnqry8XK+++qq++OIL71bB69atk6Rm53FJ9/e//12SNHDgwFatnyrTOXPPVywWy3p/AACgdQhMAADAIZk/f75effVVjR07Vv/xH//R4rqZJoI9FFOmTNH69eu1fv16Pffcc5o4cWLG9UzTlCSNGTNGRxxxRIv7LC8v9x6XlJTovPPO08qVK/WHP/xBV1xxheLxuJ544gkZhqFvfetbWbXXMIys1pfa/pwBAIDWITABAAAHbfny5Vq9erWOO+44LV68WKFQ+15ajBs3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zaLrxhMfF3FNQiIiIl1KdnY2e/bswev1nrTcP3dJa+9rAOLj48nKymp1jpOCggLS09PbNRwnkjQ03Nfmfab5c9/XzzqwIhER6Yoi7my7srKy1U9i/MsqKipOu23guoHS0tJoaGigoaGB+Ph4iouLqa+v54477uDHP/4xEyZMYPv27TzxxBMUFhbyt7/9rV0tssEcM3yiop5Lx3XDqDtEXt6hoO23Kwv32LhoomMZPDqWwaNjGTw6lu03Z84cVq1axZo1a7j88sublq9evZrBgweTnZ3d5raXX345K1eupKSkhIyMDMB6P/PBBx/wta99LeS1h0tcXBym+XNAbewiIhI+YQ1MNmzYwA033HBW637yySekp6cD5zcO+Gy3N00Tp9PJ4sWLm+Y1yc3NJS4ujoceeohPPvmECy644Kxqh+COGZ48WW8egknHMnh0LINHxzJ4dCyDJ9jH8nRjhqPJrFmzyM3N5Wc/+xmVlZX079+f1atXk5eXx/LlzZfPXbBgAZ999hn5+flNy37wgx/w6quv8qMf/YjbbrsNh8PB008/jcPh4JZbbgnHwxEREekywhqYDBkyhIcffvis1k1OTgbOPA74dGN5A2eUb6myspL4+PimUMO/7oUXXnjSehdffDEA27dvb1dgIiIiIl2TYRgsX76cxx9/nKVLl1JdXU12djbLli3j0ksvPe22PXv2ZOXKlTzyyCP89Kc/xTRNJk+ezF//+lf69u3bQY9ARESkawprYJKRkcG1117brm2ys7NbHcubn5+P3W5nyJAhbW6blZVFfHz8SXOV+BUUFJw0t8nw4cN5/fXXT1nPNE0AbLaIm/5FREREwiQ5OZkHHniABx54oM11nn/++VaXDxo0iKeffjpUpYmIiEgbIu6sf86cORQUFLBz586mZY2Njbz++uvMmDGjqROlNTExMcyaNYu3336b+vr6puX79u1j8+bNXHHFFSf9HMMwWLdu3Un78H8/fvz4YD0kEREREREREelkIm7S1+uvv56VK1eyePFi7rnnHlJTU1mxYgXFxcX89re/PWldf5vrmjVrmpbdcccdzJs3j0WLFvH973+f+vp6li5dSr9+/Zg/f37TekOHDuVb3/oWy5cvx+v1MmnSJLZt28ayZcu4+OKLmTJlSsc8YBERERERERHpcBEXmMTFxfHcc8/x6KOP8uCDD+J0Ohk1ahTPPvssY8aMOeP22dnZPPfcc/zmN7/hjjvuwOFwMHPmTO67775TulPuv/9+evfuzapVq3j66afp0aMH3/3ud7nzzjtD9fBEREREREREpBOIuMAErLlPHnvssTOuF9hZEmjcuHGsWLHijNs7HA4WLVrEokWL2l2jiIiIiIiIiESuiJvDREREREREREQk1BSYiIiIiIiIiIi0oMBERERERERERKQFBSYiIiIiIiIiIi1E5KSvkcQ0TQAaGxuDvm+n0xn0fXZVOpbBo2MZPDqWwaNjGTzBPJb+/zf6/18poRPK9yMdQX/D7adj1n46Zu2j49V+Ombt1xHH7HTvRwxT71JC6sSJExQUFIS7DBERkU5r+PDhpKSkhLuMqKb3IyIiIqfX2vsRBSYh5vV6qa2tJSYmBsMwwl2OiIhIp2GaJi6Xi6SkJGw2jRIOJb0fERERad3p3o8oMBERERERERERaUEf54iIiIiIiIiItKDARERERERERESkBQUmIiIiIiIiIiItKDAREREREREREWlBgYmIiIiIiIiISAsKTEREREREREREWlBgIiIiIiIiIiLSggITEREREREREZEWFJiESW1tLb/61a+48MILGTduHNdeey3vv//+WW178OBBbr31ViZPnszEiRO5+eab2b17d6vrrlixgq985SuMGTOGyy+/nD/+8Y94vd5gPpSw64hjmZOT0+rXCy+8EOyHE1bneiw3btzIf/7nf3L11VczevRocnJy2lzX5XLxxBNPMHv2bMaMGcPXvvY1XnzxxWA+jE4h1Mfy0KFDbf5efvjhh8F+OGF1rsfyxRdf5JZbbmH27NmMGzeOK664gl/96leUl5e3ur5eL9vWnmPZVV4vJThKS0u59957yc3NZcKECcyfP59Nmzad9fbbtm3jxhtvZMKECUydOpW77rqL48ePt7ruzp07ue2228jNzWXs2LHMmTOHZcuWBeuhdIiOPF5+L730Ejk5OUyZMuV8yw+LjjhmW7du5ec//zlz585l4sSJzJw5kx/84Ads3Lgx2A8naHQu0n6hPmb79u3j4Ycf5pvf/CaTJ08mNzeX+fPnn/XP6Iw66vfMb8OGDYwYMYKcnByqq6uD8RAwTNM0g7InaZebbrqJHTt28JOf/IT+/fvz8ssv89prr/HMM88wa9asNrcrKyvj6quvpkePHtx+++3Y7Xae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QIcOHdLcuXOT/VbX29jVXpI0evRo5c+fX927d/dEdGPsbLObjRs3TqdOndKYMWPuJnq64lok7ey65rjZzJkzFRkZqbfffvsuk5tjV5t99NFHunz5sgYNGuSh5ElRMDEkpf+Y7/Sftif+U88Ivxiklh1tOX78+CT/btmypbp376733ntPnTt3znAV39u5l7a8l+dI2Mb7MvXy5cunN998M/HfVatWVYsWLdSuXTuNHTs20xRMJM+0ZVRUlPr166eLFy9qzpw5yf4Cd6/PkRHY0ZZO+bxEUjt37lSPHj1Ste+OHTsSexre63syNY93u92KiYlR//791bdvX0lSjRo1FBAQoJEjR2rHjh2qXbt2qrJ7ije317Zt27R8+XItXLhQvr6+qcpoB29us5vNnj1bs2bN0gsvvKBatWrd8TlM4Fok7exus/Xr12vs2LFq3769OnTokObHe4P0brNDhw4pIiJCkyZNSnbiak+gYGJASEhIshW1ixcvSkq+kp2w3eVyJfvYhG0JVbeQkBBFRUXp+vXrt/wye+nSpds+R0ZjR1smx8fHR23atNG3336rgwcPpkv3L7vdbVum9TmS636X0rc4GZEdbZmcrFmzqkWLFvroo4907ty5W4Y+ZUSeaMvo6Gg999xzOnDggD755BOVLl36lufg89IzbZmczPh5iVuVKFFC77zzTqr2zZ49u6Q7vydT+n/4798y3uzChQsKDAxUQEBAkn3r1q2bZL/69etLkvbv3297wcRb2ys2NlbDhg1Tp06dVLhw4cRlOWNjY+V2u3Xp0iX5+/sra9asqcruSd7aZjebP3++3nrrLT355JNeuwIT1yJpZ/c1x6ZNmzRo0CA1a9ZMo0aNupfoxtjRZsOGDVOdOnVUpUqVxM+rmJgYSdLly5fl6+t7z4UUCiYGhIaGau3atYqPj08ypivhQjK58VqSFBgYqCJFiiR7wXnw4EHlzp07sWtSaGio3G63Dh06lGSiq6NHj952PGZGZEdb3k7CGvKZZZLOu23LtD7HqlWrdP78eeXKlStdnsMb2NGWt5PwvsyI39wk517bMiYmRs8//7z27t2rjz/+WA8//HCyz8HnpWfa8nYy2+clbnXfffepffv2aXpMaGhosv8P//LLL/L19VWJEiVu+9iE8fzJzYNw8ODBJD+3YWFhWrly5S37ud1uSWbel97aXlFRUfrjjz80b948zZs375Z9q1WrplatWmnixIlpyu4J3tpmf7dgwQKNGDFC//jHP/Svf/0rTVntxLVI2tl5zbF582b1799f9evX1/jx472qp1da2NFmhw8f1uXLl1WtWrVb9m3cuLEqVqyoL7744p7Og99cDGjWrJkuXbqkDRs2JNm+dOlSPfDAAwoNDb3tY5s2bart27fr9OnTidsuXLigjRs3JumCX79+fWXJkkXLli1L8vglS5bIz89PjRs39tDZmGVHWyYnPj5eK1asUFBQUIb7wL+de2nL1GratKncbreWL1+eZPuSJUuUM2dO1ahR456fwxvY0ZbJiYqK0tq1a1WsWLEkBamM7F7a8vr163r++ef17bffavLkyapevXqy+/F56bm2TE5m/LyEZzRr1kwHDx7UTz/9lLjt+vXrWrlypWrVqpXYSyA5/v7+atCggdasWaOoqKjE7b/99pv27t2r5s2bJ3kel8ulzZs3JzlGwr8rVqzoqVNKV3a0V7Zs2TRr1qxb/tStWzfxvn79+qXfSXqYXe8xSVq0aFFi75xhw4Z5/mQ8iGuRtLPrmmPLli3q37+/ateurffee0/+/v6ePREb2dFmERERt3xeJazMFBERoREjRtzzedDDxIAGDRqoRo0aeu2113ThwgXdf//9Wrp0qfbs2aPJkycn7te9e3ft2rVLv/zyS+K23r17a/ny5erbt6/69esnPz8/TZkyRX5+fnr22WcT98uVK5eeeeYZTZ48WTly5FCNGjW0d+9eTZs2TT169FDBggVtPef0YkdbfvLJJ/rtt99Us2ZN3XfffTpz5ozmzZunPXv2aPjw4cl2x8yI7qUtz507p127dkmSfv/9d0nWMo6SVLhw4cTlR8PCwtS+fXtNmDBBbrdbZcqU0caNG7V8+XINHz4808xtYEdbjh49WvHx8apcubJy586tP/74Q59++qmOHTum//znP3adarq7l7YcMGCAtm7dqn79+ilbtmzau3dv4n25c+dW0aJFJfF56cm2dMrnJTyjY8eO+uyzz9S/f3+99NJLCg4O1qxZs3Tq1Cm99957SfZNuLj6+y/eAwYMUKdOnfTcc8/pqaeeUlRUlCZOnKjChQvrH//4R+J+JUuWVOfOnTV58mTFx8fr4YcfVmRkpD788EPVr19fVatWteeE75Ed7eXn55fslxdLliyRr69v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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_results(t, v, u, 'Izhikevich')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The simulations above are not only for particular values of the model parameters (all the things not called `U`, `V`, or `I` in the equations at the top) but also for particular simulation experiments (particular time-varying values of `I`). You can go into the model directories you checked out from GitHub and edit the files to change these values, which will give different simulation results. Editing these files is a very tedious way to change model parameters, and not the way we would normally do it. Next time we will explore how to change these values programatically." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "BONUS:\n", + "You can explore the phase diagram for each model by asking what its gradient is at different points in state space. In the plots below, we visualize that gradient as a \"wind\" with arrows. Any point in state space (any value of V and U) will follow that wind. The interesting cases are oscillations, when that wind blows in a loop." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAz0AAAFPCAYAAABnHaCWAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8li6FKAAAgAElEQVR4nOydd3wUZf7H37N9N9n03iC00ELvVQSFQ1GaBcVyoqCHYC8/9bw77zzFhoocKgpKs2ABRAQFAQUpSpHeaxLSe7J95vfH7C4JySa7ocO8X699ze7MM2XbPM/n+TZBkiQJBQUFBQUFBQUFBQWFKxTVxb4ABQUFBQUFBQUFBQWF84kiehQUFBQUFBQUFBQUrmgU0aOgoKCgoKCgoKCgcEWjiB4FBQUFBQUFBQUFhSsaRfQoKCgoKCgoKCgoKFzRKKJHQUFBQUFBQUFBQeGKRhE9CpcETqeTtLQ0nn/++XN63CeffJLWrVuf02M2hKlTp5KWlkZ2dvbFvhQFBQUFhSuMu+66i2uvvdavtps2bSItLY1vvvmmznUXmrS0NJ599tmA98vIyCAtLY1p06adh6tSuJJQRI/CecdzM/X12L59e637iaLItGnTWLVq1QW+4ksXj3hq06YNx44dq7H9t99+Iy0tjU8++eSCX5uCgoKCwtnh6S8//vjji30pCgpXHJqLfQEKVw833ngj/fr1q7E+JSUFjUbDjh07UKvV3vWiKPLee+8xevRoBg4ceCEv9ZLH6XTy1ltv8e67717sS1FQUFBQuIzo2rUrO3bsQKO5tIaAO3bsQKVS5uIVzh+X1i9e4YqmdevW3HzzzT636/X6C3g1lzdt27ZlxYoV7Nixg3bt2l3sy1FQUFBQuExQqVSXZH97KV6TwpWFIqkVLgnOjOk5fvw4bdq0AeCrr77yusJ54nOefPJJn+5ytcXwlJaW8uKLL9KjRw/S09MZM2YMO3bsqNZm4cKFpKWl8ccff9TYf8yYMVx33XU11s+fP5/BgweTnp7O4MGDWbBgQZ3HsdlsvP766/Tt25e2bdsyfPhwfvnll4A/r0mTJmEwGHj99df9aj9v3jzuvfde+vTpQ9u2benTpw9PP/00WVlZNdq6XC7ee+89BgwYQHp6OjfddBM//PBDrXFJvj6X48ePk5aWxv/+9z/vOo/r3eLFi5k7d673cxs2bBhr164FYN++fdx333107NiR7t278/LLL+N0Omscf9OmTdx777106tSJ9u3bM3LkyIvqi66goKBwPrjrrrt89nW1xfDk5OTw+OOP07VrVzp06MC4ceM4evRotTaBxO98++23tGnThsmTJ2O3273rd+7cycSJE+nevTtt27Zl8ODBzJgxo9r9+tFHH6Vt27YUFhbWOO6RI0dIS0vj5Zdf9q7zFdOzceNGxo8fT/fu3UlPT2fgwIE899xztR539erVjBo1ivT0dPr06cOUKVNq7UMUrk4US4/CBcNisdS4Sel0OoKDg2u0jYqKYsqUKTzzzDN069aN0aNHA3hN33fccQd9+/attk9RURGvv/46ERER1dZLksRf//pXYmJimDRpEoWFhcyePZsJEyawcuVKgoKCGvR+ZsyYwdtvv03btm154oknqKys5IMPPiAyMtLnPk899RQ6nY5x48Zht9uZM2cOf/vb3/jpp5+Ij4/3+9yxsbHcddddzJw5k7Vr19K/f/8623/00Ud07tyZXr16ERYWxv79+/n666/ZuHEj3333HaGhod62//jHP1i4cCE9e/bkvvvuo6CggH/84x8kJSX5fX11MWfOHMrLyxk1ahQ6nY45c+YwceJE3n77bZ5//nluuukmrrvuOn799VfmzJlDdHQ048eP9+6/cuVKJk+eTHR0NOPGjcNkMrF06VL+7//+j4yMDCZPnnxOrlNBQUHhYvPggw96+z8PJ0+eZNq0aTX6msrKSsaOHUv79u157LHHyMjI8PYxS5cureY+7g/vv/8+U6dO5c477+SFF17w9r9r165l4sSJNGrUiPvuu4/Q0FC2b9/Ou+++y969e71u1yNGjOCHH35g2bJljB07ttqxFy9e7G1TF59//jn//Oc/iY2N5fbbbycxMZGsrCxWr15NTk5Otf5+7dq1LFiwgNtvv51Ro0axatUqZs2aRWhoKA8++GBA713hykQRPQoXjGnTptXIrjJ06FCmTp1ao21QUBA33ngjzzzzDCkpKTXc4jp16kSnTp28r+12O3fffTcajYbp06dXayuKIh07duSFF17wrktNTeWJJ55g2bJl3HLLLQG/l8LCQqZPn06rVq347LPP0Ol0AIwaNYohQ4b43C8qKorp06cjCAIg+1bffvvtfPHFFzz66KMBXcP48eNZuHAhb775Jn37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tuuvv565c+cybtw4brvtNtRqNevWrePw4cPVUlUDtGzZklGjRvH1119z3333MWjQIPLz81mwYAGtWrVi9+7d1d7XmDFjmDNnDg899JA3e97y5ctrZNU5V2g0Gv7+97/zyCOPMHr0aG655RZMJhPff/89O3bsYOLEiUrmNgUFhSuGv/3tbzidToYMGcLy5curbQsKCmLQoEHn5bxdunRh1qxZ3H///dx11118+umnxMbGYjKZmDJlChMnTmTIkCGMGjWKRo0aUVpaypEjR/jpp59477336N69u/dYkZGR9O3blxUrVmCz2Rg9erRfhUjj4uJ47rnneOmllxg2bBg333wziYmJ5OTksGrVKv773//6He+joACK6FG4xPnXv/7Fv//9b2bMmEFFRQVqtZqhQ4d6AyXPTE8N+BUg6Ys33niD//znPyxevBhJkujSpQtz5szh+eefrxacCTBx4kTMZjNz587ljTfeIDExkQkTJmC329m1a1e9bmnnmqeeeqrW9Ntdu3blnXfeYcaMGUydOhWj0Ujv3r2ZN28et956a432L730EnFxcXz99de8+uqrNGnShJdeeoktW7awe/fuap1VSkoK7733HlOnTuXtt98mPDycESNGMGzYsDqz2J0N1113HbNmzWLGjBnMnDkTl8tF06ZN+e9//8uoUaPOyzkVFBQULgYei/arr75aY1tiYuJ5Ez0AHTp0YPbs2YwbN84rfOLj4+nbty9fffUVH374IUuWLKGoqIiQkBBSUlK49957SUtLq3GsESNGeLODnlmCoi7uuOMOUlJS+Pjjj5k7dy52u52YmBh69uxJXFzcOXuvClcHgnSR7IMbNmxg8eLFbNu2jezsbEJDQ2nXrh2TJk2q9Q9zJidOnODVV19l06ZNiKJIly5deOaZZ2jWrNkFuHoFBd/84x//4PPPP2fDhg0+s65djtx///1s3bqVLVu2XFArloLCxUDpoxQUFBSuLC5aTM9nn31GVlYW9957LzNnzuTZZ58lKyuL0aNHs3379jr3LSgo4I477iAzM5MpU6bw1ltvUVJSwtixY8nOzr5A70DhasdTO6YqOTk5fPfdd7Rq1eqyFTy1va89e/awfv16evbsqQgehasCpY9SUFBQuLK4aJaegoKCahlBQK4XMnDgQHr06MG0adN87vvaa68xb948fvrpJ2JjYwG5EvHA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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "def fhn_derivatives(states, phi=0.08, a=0.7, b=0.8, I=1):\n", + " V, U = states\n", + " vprime = V**3/3 - U + I\n", + " uprime = phi * (V + a - b*U)\n", + " return vprime, uprime\n", + "\n", + "def izh_derivatives(states, a=0.02, b=0.2, c=-65, d=6, I=1):\n", + " V, U = states\n", + " vprime = 0.04*V**2 + 5*V + 140 - U + I\n", + " uprime = a*(b*V - U)\n", + " # Parameters `c` and `d` don't enter the picture\n", + " # until after a spike; we can skip them here\n", + " return vprime, uprime\n", + "\n", + "def stream(name, derivatives, params, vlim, ulim, ax=None):\n", + " vrange = np.linspace(*vlim, 200)\n", + " urange = np.linspace(*ulim, 200)\n", + " v, u = np.meshgrid(vrange, urange)\n", + " vprime, uprime = derivatives([v, u], **params)\n", + " if ax is None:\n", + " ax = plt.gca()\n", + " ax.streamplot(v, u, vprime, uprime)\n", + " ax.set_xlabel('V')\n", + " ax.set_ylabel('U')\n", + " ax.set_title(name)\n", + " \n", + "fig, ax = plt.subplots(1, 2, figsize=(12, 5))\n", + "stream('Fitzhugh-Nagumo', fhn_derivatives, {'phi': 0.08, 'a': 0.7, 'b': 0.08, 'I': 1}, [-2, 2], [0, 2], ax=ax[0])\n", + "stream('Izhikevich', izh_derivatives, {'a': 0.08, 'b': 0.7, 'c': 0.08, 'd': 1}, [-90, -10], [0, 2], ax=ax[1])\n", + "plt.tight_layout()" + ] + } + ], + "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.7.4" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/neuronunit/examples/round_trip_tests_five_param_two.ipynb b/neuronunit/examples/round_trip_tests_five_param_two.ipynb new file mode 100644 index 000000000..c54789a82 --- /dev/null +++ b/neuronunit/examples/round_trip_tests_five_param_two.ipynb @@ -0,0 +1,1367 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import sys\n", + "import os\n", + "nu_russell = os.path.realpath(os.path.join(os.getcwd(),'..','..'))\n", + "sys.path.insert(0,nu_russell)\n", + "import neuronunit\n", + "import dask.bag as db\n", + "from neuronunit.optimization import optimization_management as om\n", + "import pickle\n", + "\n", + " \n", + "from IPython.display import HTML, display\n", + "import seaborn as sns\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OrderedDict([('RS', {'b': -2, 'd': 100, 'a': 0.03, 'vPeak': 35, 'vr': -60, 'c': -50, 'vt': -40, 'k': 0.7, 'C': 100}), ('IB', {'b': 5, 'd': 130, 'a': 0.01, 'vPeak': 50, 'vr': -75, 'c': -56, 'vt': -45, 'k': 1.2, 'C': 150}), ('LTS', {'b': 8, 'd': 20, 'a': 0.03, 'vPeak': 40, 'vr': -56, 'c': -53, 'vt': -42, 'k': 1.0, 'C': 100}), ('TC', {'b': 15, 'd': 10, 'a': 0.01, 'vPeak': 35, 'vr': -60, 'c': -60, 'vt': -50, 'k': 1.6, 'C': 200}), ('TC_burst', {'b': 15, 'd': 10, 'a': 0.01, 'vPeak': 35, 'vr': -60, 'c': -60, 'vt': -50, 'k': 1.6, 'C': 200})])\n" + ] + } + ], + "source": [ + "# http://www.physics.usyd.edu.au/teach_res/mp/mscripts/\n", + "# ns_izh002.m\n", + "import collections\n", + "from collections import OrderedDict\n", + "\n", + "# Fast spiking cannot be reproduced as it requires modifications to the standard Izhi equation,\n", + "# which are expressed in this mod file.\n", + "# https://github.com/OpenSourceBrain/IzhikevichModel/blob/master/NEURON/izhi2007b.mod\n", + "\n", + "reduced2007 = collections.OrderedDict([\n", + " # C k vr vt vpeak a b c d celltype\n", + " ('RS', (100, 0.7, -60, -40, 35, 0.03, -2, -50, 100, 1)),\n", + " ('IB', (150, 1.2, -75, -45, 50, 0.01, 5, -56, 130, 2)),\n", + " ('LTS', (100, 1.0, -56, -42, 40, 0.03, 8, -53, 20, 4)),\n", + " ('TC', (200, 1.6, -60, -50, 35, 0.01, 15, -60, 10, 6)),\n", + " ('TC_burst', (200, 1.6, -60, -50, 35, 0.01, 15, -60, 10, 6)),\n", + " ('RTN', (40, 0.25, -65, -45, 0, 0.015, 10, -55, 50, 7)),\n", + " ('RTN_burst', (40, 0.25, -65, -45, 0, 0.015, 10, -55, 50, 7))])\n", + "\n", + "import numpy as np\n", + "reduced_dict = OrderedDict([(k,[]) for k in ['C','k','vr','vt','vPeak','a','b','c','d']])\n", + "\n", + "#OrderedDict\n", + "for i,k in enumerate(reduced_dict.keys()):\n", + " for v in reduced2007.values():\n", + " reduced_dict[k].append(v[i])\n", + "\n", + "explore_param = {k:(np.min(v),np.max(v)) for k,v in reduced_dict.items()}\n", + "param_ranges = OrderedDict(explore_param)\n", + "\n", + "#IB = mparams[param_dict['IB']]\n", + "RS = {}\n", + "IB = {}\n", + "TC = {}\n", + "CH = {}\n", + "RTN_burst = {}\n", + "cells = OrderedDict([(k,[]) for k in ['RS','IB','CH','LTS','FS','TC','TC_burst','RTN','RTN_busrt']])\n", + "reduced_cells = OrderedDict([(k,[]) for k in ['RS','IB','LTS','TC','TC_burst']])\n", + "\n", + "for index,key in enumerate(reduced_cells.keys()):\n", + " reduced_cells[key] = {}\n", + " for k,v in reduced_dict.items():\n", + " reduced_cells[key][k] = v[index]\n", + "\n", + "print(reduced_cells)\n", + "cells = reduced_cells" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "model = None\n", + "from neuronunit.models.reduced import ReducedModel\n", + "from neuronunit.optimization import get_neab\n", + "\n", + "from neuronunit.optimization.model_parameters import model_params, path_params\n", + "LEMS_MODEL_PATH = path_params['model_path']\n", + "\n", + "\n", + "model = ReducedModel(LEMS_MODEL_PATH,name = str('vanilla'),backend = ('RAW'))\n", + "model.set_attrs(cells['TC'])\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'value': array(74.921875) * pA}\n" + ] + } + ], + "source": [ + "tests_,all_tests, observation,suite = get_neab.get_tests()\n", + "#tests_,all_tests, observation,suite = opt.get_neab.get_tests()\n", + "rheobase = all_tests[0].generate_prediction(model)\n", + "print(rheobase)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[, , , , , , , ]\n" + ] + } + ], + "source": [ + "print(all_tests)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "cnt = 0\n", + "scores = []\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import quantities as pq" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[array(74.921875) * pA, array(74.921875) * pA, array(74.921875) * pA]\n", + "[array(74.921875) * pA, array(-10.0) * pA, array(-10.0) * pA, array(-10.0) * pA, array(-10.0) * pA]\n" + ] + } + ], + "source": [ + "def format_iparams(all_tests,rheobase):\n", + "\n", + " for t in all_tests[1:5]:\n", + " DURATION = 500.0*pq.ms\n", + " DELAY = 200.0*pq.ms\n", + "\n", + " obs = t.observation\n", + " t.params = {}\n", + " t.params['injected_square_current'] = {}\n", + " t.params['injected_square_current']['delay']= DELAY\n", + " t.params['injected_square_current']['duration'] = DURATION\n", + " t.params['injected_square_current']['amplitude'] = -10*pq.pA\n", + " \n", + " \n", + " for t in all_tests[-3::]: \n", + " t.params = {}\n", + " DURATION = 1000.0*pq.ms\n", + " DELAY = 100.0*pq.ms\n", + "\n", + " t.params['injected_square_current'] = {}\n", + " t.params['injected_square_current']['delay']= DELAY\n", + " t.params['injected_square_current']['duration'] = DURATION\n", + " t.params['injected_square_current']['amplitude'] = rheobase['value']\n", + " \n", + " all_tests[0].params = all_tests[-1].params\n", + " \n", + " return all_tests\n", + "\n", + "pt = format_iparams(all_tests,rheobase)\n", + "print([t.params['injected_square_current']['amplitude'] for t in pt[-3::] ])\n", + "print([t.params['injected_square_current']['amplitude'] for t in pt[0:5] ])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "# * Get predictions from models.\n", + "## * Fake NeuroElectro Observations\n", + "## * Do roundtrip testing\n" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[{'value': array(74.921875) * pA}, {'value': array(31739282.99824782) * kg*m**2/(s**3*A**2)}, {'value': array(0.0034631849083750876) * s}, {'value': array(1.0911352057216523e-10) * s**4*A**2/(kg*m**2)}, {'mean': array(-0.060317405613048956) * V, 'std': array(0.00019622743842309925) * V}, {'mean': array(0.000675) * s, 'std': array(0.0) * s, 'n': 1}, {'mean': array(0.05802236247240052) * V, 'std': array(0.0) * V, 'n': 1}, {'mean': array(-0.02302236247240052) * V, 'std': array(0.0) * V, 'n': 1}]\n" + ] + } + ], + "source": [ + "predictions = []\n", + "\n", + "# The rheobase has been obtained seperately and cannot be db mapped.\n", + "# Nested DB mappings dont work (daemons cannot spawn daemonic processes).\n", + "ptbag = db.from_sequence(pt[1::])\n", + "\n", + "def obtain_predictions(t): \n", + " model = None\n", + " model = ReducedModel(LEMS_MODEL_PATH,name = str('vanilla'),backend = ('RAW'))\n", + " model.set_attrs(cells['TC'])\n", + " return t.generate_prediction(model)\n", + "predictions = list(ptbag.map(obtain_predictions).compute())\n", + "predictions.insert(0,rheobase)\n", + "print(predictions) " + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'C': 200,\n", + " 'a': 0.01,\n", + " 'b': 15,\n", + " 'c': -60,\n", + " 'd': 10,\n", + " 'k': 1.6,\n", + " 'vPeak': 35,\n", + " 'vr': -60,\n", + " 'vt': -50}" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "cells['TC']" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[{'value': array(74.921875) * pA}, {'value': array(31739282.99824782) * kg*m**2/(s**3*A**2)}, {'value': array(0.0034631849083750876) * s}, {'value': array(1.0911352057216523e-10) * s**4*A**2/(kg*m**2)}, {'std': array(0.00019622743842309925) * V, 'value': array(-0.060317405613048956) * V}, {'std': array(0.0) * s, 'value': array(0.000675) * s, 'n': 1}, {'std': array(0.0) * V, 'value': array(0.05802236247240052) * V, 'n': 1}, {'std': array(0.0) * V, 'value': array(-0.02302236247240052) * V, 'n': 1}]\n" + ] + } + ], + "source": [ + "# having both means and values in dictionary makes it very irritating to iterate over.\n", + "# It's more harmless to demote means to values, than to elevate values to means.\n", + "# Simply swap key names: means, for values.\n", + "for p in predictions:\n", + " if 'mean' in p.keys():\n", + " p['value'] = p.pop('mean')\n", + "print(predictions)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Make some new tests based on internally generated data \n", + " as opposed to experimental data." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'copy' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0mTC_tests\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mcopy\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcopy\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mall_tests\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 4\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mind\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mt\u001b[0m \u001b[0;32min\u001b[0m \u001b[0menumerate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mTC_tests\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;34m'mean'\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mt\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mobservation\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mkeys\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mNameError\u001b[0m: name 'copy' is not defined" + ] + } + ], + "source": [ + "\n", + "\n", + "TC_tests = copy.copy(all_tests)\n", + "for ind,t in enumerate(TC_tests):\n", + " if 'mean' in t.observation.keys():\n", + " t.observation['value'] = t.observation.pop('mean')\n", + " pred = predictions[ind]['value']\n", + " try:\n", + " pred = pred.rescale(t.units)\n", + " t.observation['value'] = pred\n", + " except: \n", + " t.observation['value'] = pred\n", + " t.observation['mean'] = t.observation['value']\n", + " #t.observation['std'] = 0.0\n", + " \n", + " if float(t.observation['std']) == 0.0:\n", + " print('got here')\n", + " t.observation['std'] = 5.0*t.observation['mean'].units\n", + " \n", + "pickle.dump(TC_tests,open('thalamo_cortical_tests.p','wb')) \n", + "\n", + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false + }, + "source": [ + "\n", + "## Call Genetic Algorithm optimizer\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from neuronunit.optimization import optimization_management as om\n", + "free_params = ['a','b','vr','vt','k'] # this can only be odd numbers.\n", + "#2**3\n", + "hc = {}\n", + "for k,v in cells['TC'].items():\n", + " if k not in free_params:\n", + " hc[k] = v\n", + "#print(hc)\n", + "import pickle\n", + "TC_tests = pickle.load(open('thalamo_cortical_tests.p','rb')) \n", + " #run_ga(model_params, max_ngen, test, free_params = None, hc = None)\n", + " \n", + "#ga_out, DO = om.run_ga(explore_param,10,TC_tests,free_params=free_params,hc = hc, NSGA = False, MU = 10)\n", + " \n", + "#ga_out_nsga, _ = om.run_ga(explore_param,1,TC_tests,free_params=free_params,hc = hc, NSGA = True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from neuronunit.optimization import optimization_management as om\n", + "free_params = ['a','b','vr','vt','k'] # this can only be odd numbers.\n", + "#2**3\n", + "hc = {}\n", + "for k,v in cells['TC'].items():\n", + " if k not in free_params:\n", + " hc[k] = v\n", + "#print(hc)\n", + "import pickle\n", + "try:\n", + " assert 1==2\n", + " ga_out_nsga = pickle.load(open('chatter_ga_out_nsga.p','rb'))\n", + " \n", + "except:\n", + " TC_tests = pickle.load(open('thalamo_cortical_tests.p','rb')) \n", + " ga_out_nsga, _ = om.run_ga(explore_param,25,TC_tests,free_params=free_params,hc = hc, NSGA = True)\n", + " pickle.dump(ga_out_nsga,open('chatter_ga_out_nsga.p','wb'))\n", + "\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "pickle.dump(ga_out_nsga,open('chatter_ga_out_nsga.p','wb'))\n", + "print(ga_out_nsga['hardened'][0].dtc.attrs)\n", + "print(cells['TC'])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "\n", + "for item in ['avg','max','min']:\n", + " plt.plot([x[item] for x in ga_out_nsga['log']],label=item)\n", + "plt.legend()\n", + "plt.show()\n", + "\n", + "history = ga_out_nsga['history']\n", + "hof = ga_out_nsga['hof']\n", + "temp = [ v.dtc for k,v in history.genealogy_history.items() ]\n", + "temp = [ i for i in temp if type(i) is not type(None)]\n", + "temp = [ i for i in temp if len(list(i.attrs.values())) ]\n", + "true_history = [ (v, np.sum(list(v.scores.values()))) for v in temp ]\n", + "plt.plot([i for i,j in enumerate(true_history)],[ i[1] for i in true_history ])\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "true_history = ga_out_nsga['hardened']\n", + "true_mins = sorted(true_history, key=lambda h: h[1])\n", + "\n", + "print(true_mins[0][1], true_mins[0][0])\n", + "try:\n", + " if true_mins[0][1] < np.sum(list(hof[0].dtc.scores.values())):\n", + " #print('history unreliable')\n", + " hof = [i[0] for i in true_mins]\n", + " best = hof[0]\n", + " best_attrs = best.attrs\n", + " else:\n", + " best = ga_out_nsga['dhof'][0]\n", + " best_attrs = ga_out_nsga['dhof'][0].attrs\n", + " ga_out_nsga['dhof'][0].scores\n", + "except:\n", + " best = ga_out_nsga['dhof'][0]\n", + " best_attrs = ga_out_nsga['dhof'][0].attrs\n", + " ga_out_nsga['dhof'][0].scores\n", + "#true_mins " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "best = true_mins[0][0]\n", + "best_attrs = true_mins[0][0].attrs" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "print(best.rheobase)\n", + "\n", + "print('best',best.get_ss())" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false + }, + "source": [ + "# with five parameters, and 20 generations a really good fit can be found" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "fig = plt.figure()\n", + "\n", + "model1 = ReducedModel(LEMS_MODEL_PATH,name = str('vanilla'),backend = ('RAW'))\n", + "model1.attrs.update(best_attrs)\n", + "rheobase = best.rheobase\n", + "iparams['injected_square_current']['amplitude'] = rheobase\n", + "model1.inject_square_current(iparams) # this one is rheobase firing failure.\n", + "\n", + "model2 = ReducedModel(LEMS_MODEL_PATH,name = str('vanilla'),backend = ('RAW'))\n", + "model2.set_attrs(cells['TC'])\n", + "model2.attrs.update(cells['TC'])\n", + "\n", + "\n", + "iparams['injected_square_current']['amplitude'] =75.36800000000001*pq.pA\n", + "model2.inject_square_current(iparams) # this one is rheobase success.\n", + "\n", + "print(model2.attrs)\n", + "\n", + "times = model1.get_membrane_potential().times\n", + "plt.subplot(2, 1, 1)\n", + "\n", + "plt.plot(times,model1.get_membrane_potential(),c='red',label='optimizer result')\n", + "plt.plot(times,model2.get_membrane_potential(),label='ground truth') #fires\n", + "plt.legend()\n", + " #fires\n", + "#plt.show()\n", + "amplitude = iparams['injected_square_current']['amplitude']\n", + "delay = iparams['injected_square_current']['delay']\n", + "duration = iparams['injected_square_current']['duration']\n", + "tMax = float(delay) + float(duration) + 200.0#*pq.ms\n", + "\n", + "dt = 0.025\n", + "N = int(tMax/dt)\n", + "Iext = np.zeros(N)\n", + "delay_ind = int((delay/tMax)*N)\n", + "duration_ind = int((duration/tMax)*N)\n", + "Iext[0:delay_ind-1] = 0.0\n", + "Iext[delay_ind:delay_ind+duration_ind-1] = amplitude\n", + "#print(np.sum(Iext),amplitude*len(Iext[delay_ind:delay_ind+duration_ind-1]))\n", + "Iext[delay_ind+duration_ind::] = 0.0\n", + "plt.subplot(2, 1, 2)\n", + "plt.plot(times,Iext,label='current')\n", + "plt.savefig('high_resolution.png')\n", + "#plt.show()\n", + "#print(first_int,second_int,len(times))\n", + "\n", + "#plt.show(bbox_inches='tight')\n", + "#plt.tight_layout()\n", + "#plt.subplots_adjust(left=0.2,right=0.8)\n", + "#fig.tight_layout()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "fig = plt.figure()\n", + "\n", + "model1 = ReducedModel(LEMS_MODEL_PATH,name = str('vanilla'),backend = ('RAW'))\n", + "model1.attrs.update(best_attrs)\n", + "rheobase = best.rheobase\n", + "iparams['injected_square_current']['amplitude'] = rheobase\n", + "model1.inject_square_current(iparams) # this one is rheobase firing failure.\n", + "\n", + "model2 = ReducedModel(LEMS_MODEL_PATH,name = str('vanilla'),backend = ('RAW'))\n", + "model2.set_attrs(cells['TC'])\n", + "model2.attrs.update(cells['TC'])\n", + "\n", + "\n", + "iparams['injected_square_current']['amplitude'] =75.36800000000001*pq.pA\n", + "model2.inject_square_current(iparams) # this one is rheobase success.\n", + "\n", + "print(model2.attrs)\n", + "\n", + "times = model1.get_membrane_potential().times\n", + "plt.subplot(2, 1, 1)\n", + "\n", + "#plt.plot(times,model1.get_membrane_potential(),label='optimizer result')\n", + "plt.plot(times,model2.get_membrane_potential(),label='ground truth') #fires\n", + "plt.legend()\n", + " #fires\n", + "#plt.show()\n", + "amplitude = iparams['injected_square_current']['amplitude']\n", + "delay = iparams['injected_square_current']['delay']\n", + "duration = iparams['injected_square_current']['duration']\n", + "tMax = float(delay) + float(duration) + 200.0#*pq.ms\n", + "\n", + "dt = 0.025\n", + "N = int(tMax/dt)\n", + "Iext = np.zeros(N)\n", + "delay_ind = int((delay/tMax)*N)\n", + "duration_ind = int((duration/tMax)*N)\n", + "Iext[0:delay_ind-1] = 0.0\n", + "Iext[delay_ind:delay_ind+duration_ind-1] = amplitude\n", + "#print(np.sum(Iext),amplitude*len(Iext[delay_ind:delay_ind+duration_ind-1]))\n", + "Iext[delay_ind+duration_ind::] = 0.0\n", + "plt.subplot(2, 1, 2)\n", + "plt.plot(times,Iext,label='current')\n", + "plt.savefig('high_resolution.png')\n", + "#plt.show()\n", + "#print(first_int,second_int,len(times))\n", + "\n", + "#plt.show(bbox_inches='tight')\n", + "#plt.tight_layout()\n", + "#plt.subplots_adjust(left=0.2,right=0.8)\n", + "#fig.tight_layout()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "abs_max = true_mins[-1][1]\n", + "abs_min = true_mins[0][1]\n", + "pop = ga_out_nsga['pop']\n", + "print(ga_out_nsga['hof'][0].dtc.get_ss())\n", + "ga_out_nsga['hardened'][0]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "\n", + "from mpl_toolkits.mplot3d import Axes3D\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "from matplotlib import cm\n", + "\n", + "from matplotlib import animation, rc\n", + "from IPython.display import HTML\n", + "font = {'family' : 'normal',\n", + " 'weight' : 'bold',\n", + " 'size' : 16}\n", + "import matplotlib\n", + "matplotlib.rc('font', **font)\n", + "plt.rc('xtick', labelsize=5) # fontsize of the tick labels\n", + "plt.rc('ytick', labelsize=5)\n", + "super_set = []\n", + "gen_vs_pop = ga_out_nsga['gen_vs_pop']\n", + "\n", + "length = len(gen_vs_pop)\n", + "\n", + "lims = []\n", + "for k,v in pop[0].dtc.attrs.items():\n", + " lims.append([np.min(explore_param[k]), np.max(explore_param[k])])\n", + "\n", + "x = 0\n", + "y = 1\n", + "z = 2\n", + "plt.clf()\n", + "from collections import OrderedDict\n", + "\n", + "\n", + "from sympy.combinatorics.graycode import GrayCode\n", + "a = GrayCode(5)\n", + "print(a)\n", + "pre_empt = list(a.generate_gray())\n", + "\n", + "for i, pop in enumerate(gen_vs_pop):\n", + " other_points = []\n", + " pf_points = []\n", + " hof_points = [] \n", + " labels = []\n", + " od = OrderedDict(pop[0].dtc.attrs)\n", + " for p in pop:\n", + " \n", + " xyz = []\n", + " for k in od.keys():\n", + " v = p.dtc.attrs[k]\n", + " xyz.append(v)\n", + " labels.append(k)\n", + " other_points.append(xyz)\n", + " best_xyz = []\n", + " \n", + " #for k,v in values(): \n", + " for k in od.keys():\n", + " v = ga_out_nsga['hof'][0].dtc.attrs[k]\n", + " best_xyz.append(v)\n", + " best_error = ga_out_nsga['hof'][0].dtc.get_ss()\n", + "\n", + " fig = plt.figure()\n", + " fig, ax = plt.subplots(1, 1)#, figsize=figsize)\n", + " ax = Axes3D(fig)\n", + " \n", + "\n", + " ax.set_xlim(lims[x])\n", + " ax.set_ylim(lims[y])\n", + " ax.set_zlim(lims[z])\n", + " \n", + " title='Model Sample Evolution in 3D space, frame:' +str(i)#,\n", + " title_fontsize=\"large\"#,\n", + " text_fontsize=\"medium\"\n", + " ax.set_title(title, fontsize=title_fontsize)\n", + "\n", + "\n", + " errors = [ p.dtc.get_ss() for p in pop ]\n", + " xx = [ i[x] for i in other_points ]\n", + " yy = [ i[y] for i in other_points ]\n", + " zz = [ i[z] for i in other_points ]\n", + " if len(super_set) !=0 :\n", + " for ss in super_set:\n", + " ops, ers = ss\n", + " p0 = ax.scatter3D([i[0] for i in ops], [ i[1] for i in ops], [i[2] for i in ops], s=100, alpha=0.0925, c=ers, cmap='jet', marker='o', vmin=abs_min, vmax=abs_max)\n", + "\n", + " p1 = ax.scatter3D(xx, yy, zz, c=errors, cmap='jet', marker='o', s=100, vmin=abs_min, vmax=abs_max)\n", + " if i == length:\n", + " p2 = ax.scatter3D(best_xyz[x], best_xyz[y], best_xyz[z], c=best_error, cmap='jet', marker='o', s=100, vmin=abs_min, vmax=abs_max)\n", + "\n", + " cb = fig.colorbar(p1)\n", + " cb.set_label('summed scores')\n", + "\n", + " ax.set_xlabel(str(labels[x]))\n", + " ax.set_ylabel(str(labels[y]))\n", + " ax.set_zlabel(str(labels[z]))\n", + " for item in ([ax.xaxis.label, ax.yaxis.label, ax.zaxis.label]):\n", + " item.set_fontsize(20)\n", + " \n", + " #for item in ([ax.get_xticklabels() + ax.get_yticklabels() + ax.get_zticklabels()]):\n", + " # item.set_fontsize(10) \n", + " plt.savefig(str(i)+str('.png'))\n", + " super_set.append((other_points,errors)) \n", + " plt.show()\n", + " \n", + "# ls -v *.png >> sorted.txt \n", + "# convert -delay 100 -loop 0 @sorted.txt animation.mp4 \n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "\n", + "from mpl_toolkits.mplot3d import Axes3D\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "from matplotlib import cm\n", + "\n", + "from matplotlib import animation, rc\n", + "from IPython.display import HTML\n", + "\n", + "from matplotlib import animation, rc\n", + "\n", + "from IPython.display import HTML\n", + "font = {'family' : 'normal',\n", + " 'weight' : 'bold',\n", + " 'size' : 16}\n", + "import matplotlib\n", + "matplotlib.rc('font', **font)\n", + "plt.rc('xtick', labelsize=5) # fontsize of the tick labels\n", + "plt.rc('ytick', labelsize=5)\n", + "\n", + "\n", + "super_set = []\n", + "gen_vs_pop = ga_out_nsga['gen_vs_pop']\n", + "\n", + "length = len(gen_vs_pop)\n", + "\n", + "lims = []\n", + "for k,v in pop[0].dtc.attrs.items():\n", + " lims.append([np.min(explore_param[k]), np.max(explore_param[k])])\n", + "\n", + "x = 2\n", + "y = 3\n", + "z = 4\n", + "plt.clf()\n", + "from collections import OrderedDict\n", + "\n", + "\n", + "from sympy.combinatorics.graycode import GrayCode\n", + "a = GrayCode(5)\n", + "print(a)\n", + "pre_empt = list(a.generate_gray())\n", + "\n", + "for i, pop in enumerate(gen_vs_pop):\n", + " other_points = []\n", + " pf_points = []\n", + " hof_points = [] \n", + " labels = []\n", + " od = OrderedDict(pop[0].dtc.attrs)\n", + " for p in pop:\n", + " \n", + " xyz = []\n", + " for k in od.keys():\n", + " v = p.dtc.attrs[k]\n", + " xyz.append(v)\n", + " labels.append(k)\n", + " other_points.append(xyz)\n", + " best_xyz = []\n", + " \n", + " #for k,v in values(): \n", + " for k in od.keys():\n", + " v = ga_out_nsga['hof'][0].dtc.attrs[k]\n", + " best_xyz.append(v)\n", + " best_error = ga_out_nsga['hof'][0].dtc.get_ss()\n", + "\n", + " fig = plt.figure()\n", + " fig, ax = plt.subplots(1, 1)#, figsize=figsize)\n", + " ax = Axes3D(fig)\n", + " \n", + "\n", + " ax.set_xlim(lims[x])\n", + " ax.set_ylim(lims[y])\n", + " ax.set_zlim(lims[z])\n", + " \n", + " title='Particle Movement in 3D space, frame:' +str(i)#,\n", + " title_fontsize=\"large\"#,\n", + " text_fontsize=\"medium\"\n", + " ax.set_title(title, fontsize=title_fontsize)\n", + "\n", + "\n", + " errors = [ p.dtc.get_ss() for p in pop ]\n", + " xx = [ i[x] for i in other_points ]\n", + " yy = [ i[y] for i in other_points ]\n", + " zz = [ i[z] for i in other_points ]\n", + " if len(super_set) !=0 :\n", + " for ss in super_set:\n", + " ops, ers = ss\n", + " print('gets here')\n", + " p0 = ax.scatter3D([i[0] for i in ops], [ i[1] for i in ops], [i[2] for i in ops], s=100, alpha=0.0925, c=ers, cmap='jet', marker='o', vmin=abs_min, vmax=abs_max)\n", + "\n", + " p1 = ax.scatter3D(xx, yy, zz, c=errors, s=100, cmap='jet', marker='o', vmin=abs_min, vmax=abs_max)\n", + " if i == length:\n", + " p2 = ax.scatter3D(best_xyz[x], best_xyz[y], best_xyz[z], c=best_error, cmap='jet', marker='o', s=100, vmin=abs_min, vmax=abs_max)\n", + "\n", + " cb = fig.colorbar(p1)\n", + " cb.set_label('summed scores')\n", + "\n", + " ax.set_xlabel(str(labels[x]))\n", + " ax.set_ylabel(str(labels[y]))\n", + " ax.set_zlabel(str(labels[z]))\n", + " for item in ([ax.xaxis.label, ax.yaxis.label, ax.zaxis.label]):\n", + " item.set_fontsize(20)\n", + " \n", + " plt.savefig(str(i)+str('.png'))\n", + " super_set.append((other_points,errors)) \n", + " plt.show()\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "for item in ['avg','max','min']:\n", + " plt.plot([x[item] for x in ga_out_nsga['log']],label=item)\n", + "plt.legend()\n", + "\n", + "ga_out_nsga.keys()\n", + "\n", + "#ga_out['gen_vs_pop']" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "\n", + "for item in ['avg','std']:\n", + " plt.plot([x['avg']+x['std'] for x in ga_out_nsga['log']],label=item)\n", + " plt.plot([x['avg'] for x in ga_out_nsga['log']],label=item)\n", + " plt.plot([x['avg']-x['std'] for x in ga_out_nsga['log']],label=item)\n", + "plt.legend()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "plt.plot([x['std'] for x in ga_out_nsga['log']],label=item)\n", + "plt.legend()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "#df = df.T\n", + "dfs = []\n", + "for i,dhof in enumerate(true_history):\n", + " agreement = {}\n", + " for k,v in dhof[0].score.items():\n", + " print(k,v['value'])\n", + " agreement[k] = v\n", + "\n", + " dfs.append(agreement)\n", + " \n", + "df = pd.DataFrame(dfs)\n", + "df = df[0:3]\n", + " \n", + "#f, ax = plt.subplots(figsize=(9, 6))\n", + "#sns.heatmap(df, annot=True, linewidths=.5, ax=ax, vmin=0, vmax=1) \n", + "df" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "#df = pd.DataFrame(index=best.scores.keys(),columns=best.scores.keys())\n", + "#df = df.T\n", + "import seaborn as sns\n", + "import matplotlib.pyplot as plt\n", + "sns.set()\n", + "\n", + "\n", + "dfs = []\n", + "for i,dhof in enumerate(true_history):\n", + " new = {}\n", + " #print(dir(dhof[0].score))\n", + " for k,v in dhof[0].scores.items():\n", + " if 'Injected' in str(k):\n", + " #print(k,v)\n", + " #print(v)#.prediction\n", + " #v#.observation\n", + " k = k[15::]\n", + " k = k[0:-4]\n", + " new[k] = v\n", + " #if np.nan(dhof[0].scores)\n", + "\n", + " dfs.append(new)\n", + " \n", + "df = pd.DataFrame(dfs)\n", + "df = df[0:3]\n", + "\n", + "\n", + "\n", + "f, ax = plt.subplots(figsize=(9, 6))\n", + "sns.heatmap(df, annot=True, linewidths=.5, ax=ax, vmin=0, vmax=1) " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "#df = pd.DataFrame(index=best.scores.keys(),columns=best.scores.keys())\n", + "#df = df.T\n", + "gen_vs_pop = ga_out_nsga['gen_vs_pop']\n", + "\n", + "dfs = []\n", + "for i,dhof in enumerate(gen_vs_pop[0]):\n", + " new = {}\n", + " for k,v in dhof.dtc.scores.items():\n", + " if 'Injected' in str(k):\n", + " k = k[15::]\n", + " k = k[0:-4]\n", + " new[k] = v\n", + " #if np.nan(dhof[0].scores)\n", + "\n", + " dfs.append(new)\n", + " \n", + "\n", + "df = pd.DataFrame(dfs)\n", + "df = df[0:3]\n", + "\n", + "\n", + "#dfg = df.reset_index(drop=True)\n", + "\n", + "\n", + "f, ax = plt.subplots(figsize=(9, 6))\n", + "sns.heatmap(df, annot=True, linewidths=.5, ax=ax, vmin=0, vmax=1)\n", + "\n", + "# Set colormap equal to seaborns light green color palette\n", + "#cm = sns.light_palette(\"green\", as_cmap=True)\n", + "#display(dfg.style.background_gradient(cmap=cm))#,subset=['total']))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import seaborn as sns\n", + "sns.set()\n", + "\n", + "# Load the example flights dataset and conver to long-form\n", + "flights_long = sns.load_dataset(\"flights\")\n", + "flights = flights_long.pivot(\"month\", \"year\", \"passengers\")\n", + "\n", + "# Draw a heatmap with the numeric values in each cell\n", + "f, ax = plt.subplots(figsize=(9, 6))\n", + "sns.heatmap(flights, annot=True, fmt=\"d\", linewidths=.5, ax=ax)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "#df = pd.DataFrame(index=best.scores.keys(),columns=best.scores.keys())\n", + "df = df.T\n", + "dfs = []\n", + "for i,dhof in enumerate(true_history):\n", + " new = {}\n", + " for k,v in dhof[0].scores.items():\n", + " if 'Injected' in str(k):\n", + " k = k[15::]\n", + " k = k[0:-4]\n", + " new[k] = v\n", + " #if np.nan(dhof[0].scores)\n", + "\n", + " dfs.append(new)\n", + " \n", + " \n", + "from IPython.display import HTML, display\n", + "import seaborn as sns\n", + "\n", + "df = pd.DataFrame(dfs)\n", + "df = df[-4::]\n", + "\n", + "\n", + "dfg = df.reset_index(drop=True)\n", + "\n", + "# Set colormap equal to seaborns light green color palette\n", + "cm = sns.light_palette(\"green\", as_cmap=True)\n", + "display(dfg.style.background_gradient(cmap=cm))#,subset=['total']))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "\n", + "f, ax = plt.subplots(figsize=(9, 6))\n", + "sns.heatmap(dfg, annot=True, linewidths=.5, ax=ax)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "score = tests_[0].judge(model1)\n", + "score.summarize()\n", + "score.sort_key" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from neuronunit.optimization import optimization_management as om\n", + "free_params = ['a','b','vr'] # this can only be odd numbers.\n", + "2**3\n", + "hc = {}\n", + "for k,v in cells['TC'].items():\n", + " if k not in free_params:\n", + " hc[k] = v\n", + "#print(hc)\n", + "import pickle\n", + "TC_tests = pickle.load(open('thalamo_cortical_tests.p','rb')) \n", + " #run_ga(model_params, max_ngen, test, free_params = None, hc = None)\n", + " \n", + "#ga_out, DO = om.run_ga(explore_param,10,TC_tests,free_params=free_params,hc = hc, NSGA = False, MU = 10)\n", + " \n", + "ga_out_sbest, _ = om.run_ga(explore_param,20,TC_tests,free_params=free_params,hc = hc, NSGA = False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "for item in ['avg','max','min']:\n", + " plt.plot([x[item] for x in ga_out_sbest['log']],label=item)\n", + "plt.legend()\n", + "\n", + "\n", + "best = ga_out['dhof'][0]\n", + "best_attrs = ga_out_sbest['dhof'][0].attrs\n", + "print('best nsga',np.sum(list(ga_out_nsga['dhof'][0].scores.values())))\n", + "print('best BPO select best: ',np.sum(list(ga_out_sbest['dhof'][0].scores.values())))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "plt.plot([x['std'] for x in ga_out_sbest['log']],label=item)\n", + "plt.legend()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def hack_judge(test_and_models):\n", + " (test, attrs) = test_and_models\n", + " model = None\n", + " obs = test.observation\n", + " model = ReducedModel(LEMS_MODEL_PATH,name = str('vanilla'),backend = ('RAW'))\n", + " model.set_attrs(attrs)\n", + " test.generate_prediction(model)\n", + " pred = test.generate_prediction(model)\n", + " score = test.compute_score(obs,pred)\n", + " try:\n", + " print(obs['value'],pred['value'])\n", + " except:\n", + " print(obs['mean'],pred['mean'])\n", + " \n", + " return score\n", + "\n", + "scores = []\n", + "for i,t in enumerate(TC_tests):\n", + " test_and_models = (t,cells['TC'])\n", + " score = hack_judge(test_and_models)\n", + " scores.append(score)\n", + "print(scores[0].norm_score) \n", + "print(scores[0]) \n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "print([s.norm_score for s in scores])\n", + "print([s.score for s in scores])\n", + "\n", + "score = hack_judge((TC_tests[-3],cells['TC']))\n", + "print(score)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "scores = []\n", + "for t in TC_tests:\n", + " test_and_models = (t,cells['RS'])\n", + " score = hack_judge(test_and_models)\n", + " scores.append(score)\n", + "print(scores[0].norm_score) \n", + "print(scores[0])\n", + "print([s.norm_score for s in scores])\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import dask.bag as db\n", + "# The rheobase has been obtained seperately and cannot be db mapped.\n", + "# Nested DB mappings dont work.\n", + "from itertools import repeat\n", + "test_a_models = zip(TC_tests[1::],repeat(cells['RS']))\n", + "tc_bag = db.from_sequence(test_a_models)\n", + "\n", + "scores = list(tc_bag.map(hack_judge).compute())\n", + "scores.insert(0,rheobase)\n", + "print(scores) " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "score = TC_tests[0].judge(model,stop_on_error = False, deep_error = True)\n", + "print(score.prediction)\n", + "#print(model.get_spike_count())" + ] + } + ], + "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.5.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/neuronunit/examples/rtt.ipynb b/neuronunit/examples/rtt.ipynb new file mode 100644 index 000000000..5f5ab003e --- /dev/null +++ b/neuronunit/examples/rtt.ipynb @@ -0,0 +1,969 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OrderedDict([('RS', {'d': 100, 'a': 0.03, 'b': -2, 'k': 0.7, 'c': -50, 'C': 100, 'vPeak': 35, 'vr': -60, 'vt': -40}), ('IB', {'d': 130, 'a': 0.01, 'b': 5, 'k': 1.2, 'c': -56, 'C': 150, 'vPeak': 50, 'vr': -75, 'vt': -45}), ('LTS', {'d': 20, 'a': 0.03, 'b': 8, 'k': 1.0, 'c': -53, 'C': 100, 'vPeak': 40, 'vr': -56, 'vt': -42}), ('TC', {'d': 10, 'a': 0.01, 'b': 15, 'k': 1.6, 'c': -60, 'C': 200, 'vPeak': 35, 'vr': -60, 'vt': -50}), ('TC_burst', {'d': 10, 'a': 0.01, 'b': 15, 'k': 1.6, 'c': -60, 'C': 200, 'vPeak': 35, 'vr': -60, 'vt': -50})])\n" + ] + } + ], + "source": [ + "# http://www.physics.usyd.edu.au/teach_res/mp/mscripts/\n", + "# ns_izh002.m\n", + "import collections\n", + "from collections import OrderedDict\n", + "import numpy as np\n", + "\n", + "# Fast spiking cannot be reproduced as it requires modifications to the standard Izhi equation,\n", + "# which are expressed in this mod file.\n", + "# https://github.com/OpenSourceBrain/IzhikevichModel/blob/master/NEURON/izhi2007b.mod\n", + "\n", + "\n", + "reduced2007 = collections.OrderedDict([\n", + " # C k vr vt vpeak a b c d celltype\n", + " ('RS', (100, 0.7, -60, -40, 35, 0.03, -2, -50, 100, 1)),\n", + " ('IB', (150, 1.2, -75, -45, 50, 0.01, 5, -56, 130, 2)),\n", + " ('LTS', (100, 1.0, -56, -42, 40, 0.03, 8, -53, 20, 4)),\n", + " ('TC', (200, 1.6, -60, -50, 35, 0.01, 15, -60, 10, 6)),\n", + " ('TC_burst', (200, 1.6, -60, -50, 35, 0.01, 15, -60, 10, 6))])\n", + "\n", + "\n", + "type2007 = collections.OrderedDict([\n", + " # C k vr vt vpeak a b c d celltype\n", + " ('RS', (100, 0.7, -60, -40, 35, 0.03, -2, -50, 100, 1)),\n", + " ('IB', (150, 1.2, -75, -45, 50, 0.01, 5, -56, 130, 2)),\n", + " ('LTS', (100, 1.0, -56, -42, 40, 0.03, 8, -53, 20, 4)),\n", + " ('TC', (200, 1.6, -60, -50, 35, 0.01, 15, -60, 10, 6)),\n", + " ('TC_burst', (200, 1.6, -60, -50, 35, 0.01, 15, -60, 10, 6)),\n", + " ('RTN', (40, 0.25, -65, -45, 0, 0.015, 10, -55, 50, 7)),\n", + " ('RTN_burst', (40, 0.25, -65, -45, 0, 0.015, 10, -55, 50, 7)),\n", + " ('CH', (50, 1.5, -60, -40, 25, 0.03, 1, -40, 150, 3)),\n", + " ('FS', (20, 1.0, -55, -40, 25, 0.2, -2, -45, -55, 5))])\n", + "\n", + "reduced_dict = OrderedDict([(k,[]) for k in ['C','k','vr','vt','vPeak','a','b','c','d']])\n", + "\n", + "#OrderedDict\n", + "for i,k in enumerate(reduced_dict.keys()):\n", + " for v in type2007.values():\n", + " reduced_dict[k].append(v[i])\n", + "\n", + "explore_param = {k:(np.min(v),np.max(v)) for k,v in reduced_dict.items()}\n", + "param_ranges = OrderedDict(explore_param)\n", + "\n", + "\n", + "RS = {}\n", + "IB = {}\n", + "TC = {}\n", + "CH = {}\n", + "RTN_burst = {}\n", + "cells = OrderedDict([(k,[]) for k in ['RS','IB','CH','LTS','FS','TC','TC_burst','RTN','RTN_busrt']])\n", + "reduced_cells = OrderedDict([(k,[]) for k in ['RS','IB','LTS','TC','TC_burst']])\n", + "\n", + "for index,key in enumerate(reduced_cells.keys()):\n", + " reduced_cells[key] = {}\n", + " for k,v in reduced_dict.items():\n", + " reduced_cells[key][k] = v[index]\n", + "\n", + "print(reduced_cells)\n", + "cells = reduced_cells" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "model = None\n", + "from neuronunit.models.reduced import ReducedModel\n", + "from neuronunit.optimization import get_neab\n", + "\n", + "from neuronunit.optimization.model_parameters import model_params, path_params\n", + "LEMS_MODEL_PATH = path_params['model_path']\n", + "\n", + "model = ReducedModel(LEMS_MODEL_PATH,name = str('vanilla'),backend = ('RAW'))\n", + "model.set_attrs(cells['TC'])\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'value': array(75.53061224489795) * pA}\n" + ] + } + ], + "source": [ + "\n", + "tests_,all_tests, observation,suite = get_neab.get_tests()\n", + "\n", + "rheobase = all_tests[0].generate_prediction(model)\n", + "print(rheobase)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "\n", + "\n", + "import pickle\n", + "import copy\n", + "import numpy as np\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "\n", + "from neuronunit.models.reduced import ReducedModel\n", + "from neuronunit.optimization.model_parameters import model_params, path_params\n", + "LEMS_MODEL_PATH = path_params['model_path']\n", + "import neuronunit.optimization as opt\n", + "import quantities as pq\n", + "\n", + "from neuronunit.optimization.data_transport_container import DataTC\n", + "model = ReducedModel(LEMS_MODEL_PATH,name = str('vanilla'),backend = ('RAW'))\n", + "model.set_attrs(cells['TC'])\n", + "\n", + "iparams = {}\n", + "iparams['injected_square_current'] = {}\n", + "iparams['injected_square_current']['amplitude'] =75.36800000000001*pq.pA\n", + "#['amplitude'] = dtc.vtest[k]['injected_square_current']['amplitude']\n", + "DELAY = 100.0*pq.ms\n", + "DURATION = 1000.0*pq.ms\n", + "iparams['injected_square_current']['delay'] = DELAY\n", + "iparams['injected_square_current']['duration'] = int(DURATION)\n", + "\n", + "model.inject_square_current(iparams)\n", + "\n", + "plt.plot(model.get_membrane_potential().times,model.get_membrane_potential(),label='RAW')\n", + "plt.legend()\n", + "plt.show()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "\n", + "cnt = 0\n", + "scores = []\n", + "tests_,all_tests, observation,suite = opt.get_neab.get_tests()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[array(75.53061224489795) * pA, array(75.53061224489795) * pA, array(75.53061224489795) * pA]\n", + "[array(75.53061224489795) * pA, array(-10.0) * pA, array(-10.0) * pA, array(-10.0) * pA, array(-10.0) * pA]\n" + ] + } + ], + "source": [ + "\n", + "\n", + "\n", + "def format_iparams(all_tests,rheobase):\n", + "\n", + " for t in all_tests[1:5]:\n", + " DURATION = 500.0*pq.ms\n", + " DELAY = 200.0*pq.ms\n", + "\n", + " obs = t.observation\n", + " t.params = {}\n", + " t.params['injected_square_current'] = {}\n", + " t.params['injected_square_current']['delay']= DELAY\n", + " t.params['injected_square_current']['duration'] = DURATION\n", + " t.params['injected_square_current']['amplitude'] = -10*pq.pA\n", + " \n", + " \n", + " for t in all_tests[-3::]: \n", + " t.params = {}\n", + " DURATION = 1000.0*pq.ms\n", + " DELAY = 100.0*pq.ms\n", + "\n", + " t.params['injected_square_current'] = {}\n", + " t.params['injected_square_current']['delay']= DELAY\n", + " t.params['injected_square_current']['duration'] = DURATION\n", + " t.params['injected_square_current']['amplitude'] = rheobase['value']\n", + " \n", + " all_tests[0].params = all_tests[-1].params\n", + " \n", + " return all_tests\n", + "\n", + "pt = format_iparams(all_tests,rheobase)\n", + "print([t.params['injected_square_current']['amplitude'] for t in pt[-3::] ])\n", + "print([t.params['injected_square_current']['amplitude'] for t in pt[0:5] ])\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##\n", + "# * Get predictions from models.\n", + "## * Fake NeuroElectro Observations\n", + "## * Do roundtrip testing\n", + "##" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "[{'value': array(75.53061224489795) * pA}, {'value': array(31739282.99824782) * kg*m**2/(s**3*A**2)}, {'value': array(0.0034631849083750876) * s}, {'value': array(1.0911352057216523e-10) * s**4*A**2/(kg*m**2)}, {'std': array(0.00019622743842309925) * V, 'mean': array(-0.060317405613048956) * V}, {'std': array(0.0) * s, 'n': 1, 'mean': array(0.0006500000000000001) * s}, {'std': array(0.0) * V, 'n': 1, 'mean': array(0.056127191359642024) * V}, {'std': array(0.0) * V, 'n': 1, 'mean': array(-0.02112719135964202) * V}]\n" + ] + } + ], + "source": [ + "predictions = []\n", + "import dask.bag as db\n", + "# The rheobase has been obtained seperately and cannot be db mapped.\n", + "# Nested DB mappings dont work.\n", + "ptbag = db.from_sequence(pt[1::])\n", + "\n", + "def obtain_predictions(t): \n", + " model = None\n", + " model = ReducedModel(LEMS_MODEL_PATH,name = str('vanilla'),backend = ('RAW'))\n", + " model.set_attrs(cells['TC'])\n", + " return t.generate_prediction(model)\n", + "predictions = list(ptbag.map(obtain_predictions).compute())\n", + "predictions.insert(0,rheobase)\n", + "print(predictions) \n" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[{'value': array(75.53061224489795) * pA}, {'value': array(31739282.99824782) * kg*m**2/(s**3*A**2)}, {'value': array(0.0034631849083750876) * s}, {'value': array(1.0911352057216523e-10) * s**4*A**2/(kg*m**2)}, {'std': array(0.00019622743842309925) * V, 'value': array(-0.060317405613048956) * V}, {'std': array(0.0) * s, 'n': 1, 'value': array(0.0006500000000000001) * s}, {'std': array(0.0) * V, 'n': 1, 'value': array(0.056127191359642024) * V}, {'std': array(0.0) * V, 'n': 1, 'value': array(-0.02112719135964202) * V}]\n" + ] + } + ], + "source": [ + "# having both means and values in dictionary makes it very irritating to iterate over.\n", + "# It's more harmless to demote means to values, than to elevate values to means.\n", + "# Simply swap key names: means, for values.\n", + "for p in predictions:\n", + " if 'mean' in p.keys():\n", + " p['value'] = p.pop('mean')\n", + "print(predictions)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "75.53061224489795 pA\n", + "31.73928299824782 Mohm\n", + "3.4631849083750876 ms\n", + "1.0911352057216523e-10 s**4*A**2/(kg*m**2)\n", + "-60.317405613048955 mV\n", + "0.6500000000000001 ms\n", + "56.127191359642026 mV\n", + "-21.127191359642023 mV\n" + ] + } + ], + "source": [ + "# make some new tests based on internally generated data \n", + "# as opposed to experimental data.\n", + "\n", + "\n", + "TC_tests = copy.copy(all_tests)\n", + "for ind,t in enumerate(TC_tests):\n", + " if 'mean' in t.observation.keys():\n", + " t.observation['value'] = t.observation.pop('mean')\n", + " pred = predictions[ind]['value']\n", + " try:\n", + " pred = pred.rescale(t.units)\n", + " t.observation['value'] = pred\n", + " except: \n", + " t.observation['value'] = pred\n", + " t.observation['mean'] = t.observation['value']\n", + " \n", + " print(t.observation['value'])\n", + " \n", + "pickle.dump(TC_tests,open('thalamo_cortical_tests.p','wb')) \n", + " \n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'d': 10, 'k': 1.6, 'c': -60, 'C': 200, 'vPeak': 35, 'vt': -50}\n", + "not even to test runner getting\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/opt/conda/lib/python3.5/site-packages/sklearn/cross_validation.py:41: DeprecationWarning: This module was deprecated in version 0.18 in favor of the model_selection module into which all the refactored classes and functions are moved. Also note that the interface of the new CV iterators are different from that of this module. This module will be removed in 0.20.\n", + " \"This module will be removed in 0.20.\", DeprecationWarning)\n", + "/opt/conda/lib/python3.5/site-packages/sklearn/grid_search.py:42: DeprecationWarning: This module was deprecated in version 0.18 in favor of the model_selection module into which all the refactored classes and functions are moved. This module will be removed in 0.20.\n", + " DeprecationWarning)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[array(227.1326530612245) * pA, array(6.928571428571429) * pA, array(319.9708454810496) * pA, array(32.33673469387755) * pA, array(226.28571428571428) * pA, array(6.081632653061225) * pA, array(473.7609329446064) * pA, array(83.15306122448979) * pA]\n", + "injected current seen: {'duration': array(1000.0) * ms, 'delay': array(100.0) * ms, 'amplitude': array(227.1326530612245) * pA}\n", + "injected current seen: {'duration': array(1000.0) * ms, 'delay': array(100.0) * ms, 'amplitude': array(6.928571428571429) * pA}\n", + "injected current seen: {'duration': array(1000.0) * ms, 'delay': array(100.0) * ms, 'amplitude': array(32.33673469387755) * pA}\n", + "injected current seen: {'duration': array(1000.0) * ms, 'delay': array(100.0) * ms, 'amplitude': array(319.9708454810496) * pA}\n", + "input resistance score: Z = 3.30\n", + "input resistance score: Z = 0.47\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "input resistance score: Z = -0.11\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "input resistance score: Z = 0.22\n", + "injected current seen: {'duration': array(1000.0) * ms, 'delay': array(100.0) * ms, 'amplitude': array(226.28571428571428) * pA}\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "input resistance score: Z = 1.07\n", + "injected current seen: {'duration': array(1000.0) * ms, 'delay': array(100.0) * ms, 'amplitude': array(473.7609329446064) * pA}\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "injected current seen: {'duration': array(1000.0) * ms, 'delay': array(100.0) * ms, 'amplitude': array(6.081632653061225) * pA}\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "input resistance score: Z = 0.06\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "injected current seen: {'duration': array(1000.0) * ms, 'delay': array(100.0) * ms, 'amplitude': array(83.15306122448979) * pA}\n", + "input resistance score: Z = 3.26\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "input resistance score: Z = 0.65\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "[, , , , , , , ]\n", + "tests, completed, now gene computations\n", + "not even to test runner getting\n", + "[array(227.1326530612245) * pA, array(260.2040816326531) * pA, array(67.90816326530613) * pA, array(219.51020408163265) * pA, array(98.39795918367346) * pA, array(420.5539358600583) * pA]\n", + "injected current seen: {'duration': array(1000.0) * ms, 'delay': array(100.0) * ms, 'amplitude': array(227.1326530612245) * pA}\n", + "injected current seen: {'duration': array(1000.0) * ms, 'delay': array(100.0) * ms, 'amplitude': array(260.2040816326531) * pA}\n", + "injected current seen: {'duration': array(1000.0) * ms, 'delay': array(100.0) * ms, 'amplitude': array(67.90816326530613) * pA}\n", + "injected current seen: {'duration': array(1000.0) * ms, 'delay': array(100.0) * ms, 'amplitude': array(98.39795918367346) * pA}\n", + "injected current seen: {'duration': array(1000.0) * ms, 'delay': array(100.0) * ms, 'amplitude': array(219.51020408163265) * pA}\n", + "injected current seen: {'duration': array(1000.0) * ms, 'delay': array(100.0) * ms, 'amplitude': array(420.5539358600583) * pA}\n", + "input resistance score: Z = -0.09\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "input resistance score: Z = 0.47\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "input resistance score: Z = 1.86\n", + "input resistance score: Z = 1.22\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "input resistance score: Z = 0.16\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "input resistance score: Z = 0.08\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/jovyan/neuronunit/neuronunit/tests/passive.py:83: RuntimeWarning: overflow encountered in exp\n", + " vm_fit[offset:,0] = a * np.exp(-t[offset:]/b) + c\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/jovyan/neuronunit/neuronunit/tests/passive.py:83: RuntimeWarning: overflow encountered in multiply\n", + " vm_fit[offset:,0] = a * np.exp(-t[offset:]/b) + c\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:gen\tnevals\tavg \tstd \tmin \tmax \n", + "1 \t8 \t3.89399\t0.918618\t2.38982\t5.21792\n", + "2 \t6 \t3.82178\t1.18981 \t1.16878\t5.21792\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[, , , , , ]\n", + "tests, completed, now gene computations\n", + "[[0.01, -2.0, -75.0], [0.01, -2, -55], [0.01, 15.0, -72.162359315819316], [0.019842752401015011, 15, -57.837640684180677], [0.20000000000000001, -2, -74.652924436008334], [0.20000000000000001, -2, -55], [0.20000000000000001, 15, -56.279875816303409], [0.20000000000000001, 13.919119365130545, -73.720124183696583]]\n", + "not even to test runner getting\n", + "[array(93.31632653061224) * pA, array(397.23032069970844) * pA, array(416.1807580174927) * pA]\n", + "injected current seen: {'duration': array(1000.0) * ms, 'delay': array(100.0) * ms, 'amplitude': array(93.31632653061224) * pA}\n", + "injected current seen: {'duration': array(1000.0) * ms, 'delay': array(100.0) * ms, 'amplitude': array(397.23032069970844) * pA}\n", + "injected current seen: {'duration': array(1000.0) * ms, 'delay': array(100.0) * ms, 'amplitude': array(416.1807580174927) * pA}\n", + "input resistance score: Z = 1.25\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "input resistance score: Z = 0.10\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "input resistance score: Z = 0.68\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/jovyan/neuronunit/neuronunit/tests/passive.py:83: RuntimeWarning: overflow encountered in exp\n", + " vm_fit[offset:,0] = a * np.exp(-t[offset:]/b) + c\n", + "/home/jovyan/neuronunit/neuronunit/tests/passive.py:83: RuntimeWarning: overflow encountered in multiply\n", + " vm_fit[offset:,0] = a * np.exp(-t[offset:]/b) + c\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:3 \t3 \t3.44631\t0.539488\t2.54048\t3.93815\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[, , ]\n", + "tests, completed, now gene computations\n", + "not even to test runner getting\n", + "[array(93.31632653061224) * pA, array(93.31632653061224) * pA, array(83.15306122448979) * pA, array(101.78571428571428) * pA]\n", + "injected current seen: {'duration': array(1000.0) * ms, 'delay': array(100.0) * ms, 'amplitude': array(93.31632653061224) * pA}\n", + "injected current seen: {'duration': array(1000.0) * ms, 'delay': array(100.0) * ms, 'amplitude': array(93.31632653061224) * pA}\n", + "injected current seen: {'duration': array(1000.0) * ms, 'delay': array(100.0) * ms, 'amplitude': array(83.15306122448979) * pA}\n", + "injected current seen: {'duration': array(1000.0) * ms, 'delay': array(100.0) * ms, 'amplitude': array(101.78571428571428) * pA}\n", + "input resistance score: Z = 0.65\n", + "input resistance score: Z = 0.55\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "input resistance score: Z = 1.25\n", + "input resistance score: Z = 0.19\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:4 \t4 \t2.52815\t0.260893\t2.24113\t2.94118\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[, , , ]\n", + "tests, completed, now gene computations\n", + "[[0.17283487762003827, 13.821996424655216, -56.602532543393217], [0.20000000000000001, 13.821996424655216, -56.602532543393217], [0.20000000000000001, 15.0, -55.0], [0.20000000000000001, 13.002091326490282, -57.788628990542293]]\n", + "not even to test runner getting\n", + "[array(83.15306122448979) * pA, array(95.01020408163265) * pA, array(93.31632653061224) * pA, array(135.66326530612244) * pA]\n", + "injected current seen: {'duration': array(1000.0) * ms, 'delay': array(100.0) * ms, 'amplitude': array(83.15306122448979) * pA}\n", + "injected current seen: {'duration': array(1000.0) * ms, 'delay': array(100.0) * ms, 'amplitude': array(95.01020408163265) * pA}\n", + "injected current seen: {'duration': array(1000.0) * ms, 'delay': array(100.0) * ms, 'amplitude': array(93.31632653061224) * pA}\n", + "injected current seen: {'duration': array(1000.0) * ms, 'delay': array(100.0) * ms, 'amplitude': array(135.66326530612244) * pA}\n", + "input resistance score: Z = 0.65\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "input resistance score: Z = 0.61\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "input resistance score: Z = 0.08\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "input resistance score: Z = 0.64\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:__main__:5 \t4 \t2.47542\t0.34641 \t2.07671\t3.03096\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[, , , ]\n", + "tests, completed, now gene computations\n", + "[[0.20000000000000001, 15.0, -55], [0.20000000000000001, 13.05819718288449, -57.193440475374892], [0.17283487762003827, 13.813518137336468, -56.602532543393217], [0.20000000000000001, 11.735821400014721, -61.046405670263503]]\n" + ] + } + ], + "source": [ + "from neuronunit.optimization import optimization_management as om\n", + "free_params = ['a','b','vr']#,'k','vt'] # this can only be odd numbers.\n", + "\n", + "#\n", + "hc = {}\n", + "for k,v in cells['TC'].items():\n", + " if k not in free_params:\n", + " hc[k] = v\n", + "print(hc)\n", + "import pickle\n", + "TC_tests = pickle.load(open('thalamo_cortical_tests.p','rb')) \n", + "ga_out, DO = om.run_ga(explore_param,5,TC_tests,free_params=free_params,hc = hc)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'a': 0.019842752401015011, 'b': 15, 'vr': -57.837640684180677}\n", + "{'d': 10, 'a': 0.01, 'b': 15, 'k': 1.6, 'c': -60, 'C': 200, 'vPeak': 35, 'vr': -60, 'vt': -50}\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "print(ga_out['dhof'][0].attrs)\n", + "print(cells['TC'])\n", + "\n", + "\n", + "model1 = None\n", + "model1 = ReducedModel(LEMS_MODEL_PATH,name = str('vanilla'),backend = ('RAW'))\n", + "model1.set_attrs(ga_out['dhof'][0].attrs)\n", + "model1.inject_square_current(iparams)\n", + "\n", + "model2 = None\n", + "model2 = ReducedModel(LEMS_MODEL_PATH,name = str('vanilla'),backend = ('RAW'))\n", + "#vanilla = translate(mparams,m2m)\n", + "model2.set_attrs(cells['TC'])\n", + "model2.inject_square_current(iparams)\n", + "\n", + "\n", + "plt.plot(model1.get_membrane_potential().times,model1.get_membrane_potential(),label='optimizer result')\n", + "plt.plot(model2.get_membrane_potential().times,model2.get_membrane_potential(),label='ground truth')\n", + "plt.legend()" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "75.53061224489795 pA 75.53061224489795 pA\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "input resistance score: Z = 0.00\n", + "31.73928299824782 Mohm 31739282.99824782 kg*m**2/(s**3*A**2)\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "3.4631849083750876 ms 0.0034631849083750876 s\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "1.0911352057216523e-10 s**4*A**2/(kg*m**2) 1.0911352057216523e-10 s**4*A**2/(kg*m**2)\n", + "-60.317405613048955 mV -0.060317405613048956 V\n", + "0.6500000000000001 ms 0.0006500000000000001 s\n", + "56.127191359642026 mV 0.056127191359642024 V\n", + "-21.127191359642023 mV -0.02112719135964202 V\n", + "1.0\n", + "Z = 0.00\n" + ] + } + ], + "source": [ + "def hack_judge(test_and_models):\n", + " (test, attrs) = test_and_models\n", + " model = None\n", + " obs = test.observation\n", + " model = ReducedModel(LEMS_MODEL_PATH,name = str('vanilla'),backend = ('RAW'))\n", + " model.set_attrs(attrs)\n", + " test.generate_prediction(model)\n", + " pred = test.generate_prediction(model)\n", + " score = test.compute_score(obs,pred)\n", + " try:\n", + " print(obs['value'],pred['value'])\n", + " except:\n", + " print(obs['mean'],pred['mean'])\n", + " \n", + " return score\n", + "\n", + "scores = []\n", + "for i,t in enumerate(TC_tests):\n", + " test_and_models = (t,cells['TC'])\n", + " score = hack_judge(test_and_models)\n", + " scores.append(score)\n", + "print(scores[0].sort_key) \n", + "print(scores[0]) \n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'CapacitanceTest': 0.3941135778347633,\n", + " 'InjectedCurrentAPAmplitudeTest': 0.056286812623258964,\n", + " 'InjectedCurrentAPThresholdTest': 0.1301810981679412,\n", + " 'InjectedCurrentAPWidthTest': 0.07455112037761191,\n", + " 'InputResistanceTest': 0.12468893958387861,\n", + " 'RestingPotentialTest': 0.2550425415991806,\n", + " 'RheobaseTestP': 0.1269862366342951,\n", + " 'TimeConstantTest': 0.006927779205798235}" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "\n", + "ga_out.keys()\n", + "ga_out['dhof'][0].scores\n" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1.0, 1.0, 1.0, 1.0, 1.0, 0.9999999999999999, 1.0, 0.9999999999999996]\n", + "[0.0, 0.0, 0.0, 0.0, 0.0, -2.029026769368064e-16, 0.0, 6.317017647578838e-16]\n", + "0.6500000000000001 ms 0.0006500000000000001 s\n", + "Z = -0.00\n" + ] + } + ], + "source": [ + "print([s.sort_key for s in scores])\n", + "print([s.score for s in scores])\n", + "\n", + "score = hack_judge((TC_tests[-3],cells['TC']))\n", + "print(score)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "75.53061224489795 pA 51.81632653061225 pA\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "input resistance score: Z = 0.62\n", + "31.73928299824782 Mohm 79634016.52239381 kg*m**2/(s**3*A**2)\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "3.4631849083750876 ms 0.009810667396493434 s\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "1.0911352057216523e-10 s**4*A**2/(kg*m**2) 1.231969430266598e-10 s**4*A**2/(kg*m**2)\n", + "-60.317405613048955 mV -0.06075346627665097 V\n", + "0.6500000000000001 ms 0.0007750000000000001 s\n", + "56.127191359642026 mV 0.05357660263743405 V\n", + "-21.127191359642023 mV -0.018576602637434048 V\n", + "0.6190101302868543\n", + "Z = -0.50\n", + "[0.6190101302868543, 0.5372765850958718, 0.4696921520174828, 0.45016300791078434, 0.946777506200236, 0.81503865531969, 0.841429780079807, 0.6423611420888804]\n" + ] + } + ], + "source": [ + "scores = []\n", + "for t in TC_tests:\n", + " test_and_models = (t,cells['RS'])\n", + " score = hack_judge(test_and_models)\n", + " scores.append(score)\n", + "print(scores[0].sort_key) \n", + "print(scores[0])\n", + "print([s.sort_key for s in scores])\n" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "input resistance score: Z = 0.62\n", + "31.73928299824782 Mohm 79634016.52239381 kg*m**2/(s**3*A**2)\n", + "3.4631849083750876 ms 0.009810667396493434 s\n", + "injected current seen: {'duration': array(500.0) * ms, 'delay': array(200.0) * ms, 'amplitude': array(-10.0) * pA}\n", + "-21.127191359642023 mV -0.018576602637434048 V\n", + "0.6500000000000001 ms 0.0007750000000000001 s\n", + "56.127191359642026 mV 0.05357660263743405 V\n", + "1.0911352057216523e-10 s**4*A**2/(kg*m**2) 1.231969430266598e-10 s**4*A**2/(kg*m**2)\n", + "-60.317405613048955 mV -0.06075346627665097 V\n", + "[{'value': array(75.53061224489795) * pA}, , , , , , , ]\n" + ] + } + ], + "source": [ + "import dask.bag as db\n", + "# The rheobase has been obtained seperately and cannot be db mapped.\n", + "# Nested DB mappings dont work.\n", + "from itertools import repeat\n", + "test_a_models = zip(TC_tests[1::],repeat(cells['RS']))\n", + "tc_bag = db.from_sequence(test_a_models)\n", + "\n", + "scores = list(tc_bag.map(hack_judge).compute())\n", + "scores.insert(0,rheobase)\n", + "print(scores) " + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'value': array(75.53061224489795) * pA}\n" + ] + } + ], + "source": [ + "score = TC_tests[0].judge(model,stop_on_error = False, deep_error = True)\n", + "print(score.prediction)\n", + "#print(model.get_spike_count())" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "ename": "ImportError", + "evalue": "cannot import name 'ReducedModel'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mImportError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0mneuronunit\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0moptimization\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0moptimization_management\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mopt\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0;32mfrom\u001b[0m \u001b[0mneuronunit\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmodels\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mReducedModel\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;31mImportError\u001b[0m: cannot import name 'ReducedModel'" + ] + } + ], + "source": [ + "from neuronunit.optimization import optimization_management as opt\n", + "from neuronunit.models import ReducedModel" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "raw", + "metadata": { + "collapsed": true + }, + "source": [] + } + ], + "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.5.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/neuronunit/examples/serial-vs-parallel-rheobase-test.ipynb b/neuronunit/examples/serial-vs-parallel-rheobase-test.ipynb new file mode 100644 index 000000000..15eb3f32a --- /dev/null +++ b/neuronunit/examples/serial-vs-parallel-rheobase-test.ipynb @@ -0,0 +1,819 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The autoreload extension is already loaded. To reload it, use:\n", + " %reload_ext autoreload\n" + ] + } + ], + "source": [ + "%matplotlib inline\n", + "%load_ext autoreload\n", + "%autoreload 2\n", + "import quantities as pq\n", + "import neuronunit.unit_test.test_tests as x" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Serial rheobase test" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:PyNN:Initializing membrane potential of 0 cells and 0 Populations.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Getting Rheobase data values from neuroelectro.org\n", + "http://neuroelectro.org/api/1/nes/?nlex=nifext_50&e__name=Rheobase\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:PyNN:Initializing membrane potential of 0 cells and 0 Populations.\n", + "INFO:PyNN:Initializing membrane potential of 0 cells and 0 Populations.\n" + ] + }, + { + "ename": "AttributeError", + "evalue": "module 'neuron' has no attribute 'DCSource'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m modelS, testS = x.quick_test_builder(test_class=\"RheobaseTest\", \n\u001b[1;32m 2\u001b[0m backend='HHpyNN')\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0mscoreS\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtestS\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjudge\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmodelS\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 4\u001b[0m \u001b[0mscoreS\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mscoreS\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mprediction\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m/mnt/d/Dropbox (ASU)/dev/scidash/sciunit/sciunit/tests.py\u001b[0m in \u001b[0;36mjudge\u001b[0;34m(self, model, skip_incapable, stop_on_error, deep_error)\u001b[0m\n\u001b[1;32m 247\u001b[0m \u001b[0mscore\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtest\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 248\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mscore\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mErrorScore\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mand\u001b[0m \u001b[0mstop_on_error\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 249\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mscore\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mscore\u001b[0m \u001b[0;31m# An exception.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 250\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mscore\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 251\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m/mnt/d/Dropbox (ASU)/dev/scidash/sciunit/sciunit/tests.py\u001b[0m in \u001b[0;36mjudge\u001b[0;34m(self, model, skip_incapable, stop_on_error, deep_error)\u001b[0m\n\u001b[1;32m 236\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 237\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 238\u001b[0;31m \u001b[0mscore\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_judge\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmodel\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mskip_incapable\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mskip_incapable\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 239\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mCapabilityError\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0me\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 240\u001b[0m \u001b[0mscore\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mNAScore\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mstr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0me\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m/mnt/d/Dropbox (ASU)/dev/scidash/sciunit/sciunit/tests.py\u001b[0m in \u001b[0;36m_judge\u001b[0;34m(self, model, skip_incapable)\u001b[0m\n\u001b[1;32m 176\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 177\u001b[0m \u001b[0;31m# 2.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 178\u001b[0;31m \u001b[0mprediction\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mgenerate_prediction\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmodel\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 179\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcheck_prediction\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mprediction\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 180\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlast_model\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmodel\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m/mnt/d/Dropbox (ASU)/dev/scidash/neuronunit/neuronunit/tests/fi.py\u001b[0m in \u001b[0;36mgenerate_prediction\u001b[0;34m(self, model)\u001b[0m\n\u001b[1;32m 65\u001b[0m \u001b[0munits\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mobservation\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'mean'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0munits\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 66\u001b[0m \u001b[0;31m# begin_rh = time.time()\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 67\u001b[0;31m \u001b[0mlookup\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mthreshold_FI\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmodel\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0munits\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 68\u001b[0m \u001b[0msub\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0marray\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mx\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mx\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mlookup\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mlookup\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0munits\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 69\u001b[0m \u001b[0msupra\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0marray\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mx\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mx\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mlookup\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mlookup\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m>\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0munits\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m/mnt/d/Dropbox (ASU)/dev/scidash/neuronunit/neuronunit/tests/fi.py\u001b[0m in \u001b[0;36mthreshold_FI\u001b[0;34m(self, model, units, guess)\u001b[0m\n\u001b[1;32m 112\u001b[0m \u001b[0msmall\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msmall\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 113\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 114\u001b[0;31m \u001b[0mf\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mhigh\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 115\u001b[0m \u001b[0mi\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 116\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m/mnt/d/Dropbox (ASU)/dev/scidash/neuronunit/neuronunit/tests/fi.py\u001b[0m in \u001b[0;36mf\u001b[0;34m(ampl)\u001b[0m\n\u001b[1;32m 94\u001b[0m \u001b[0mcurrent\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mDEFAULT_INJECTED_SQUARE_CURRENT\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcopy\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 95\u001b[0m \u001b[0mcurrent\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'amplitude'\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mampl\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 96\u001b[0;31m \u001b[0mmodel\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0minject_square_current\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcurrent\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 97\u001b[0m \u001b[0mn_spikes\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmodel\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_spike_count\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 98\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m/mnt/d/Dropbox (ASU)/dev/scidash/neuronunit/neuronunit/models/reduced.py\u001b[0m in \u001b[0;36minject_square_current\u001b[0;34m(self, current)\u001b[0m\n\u001b[1;32m 54\u001b[0m ['amplitude', 'delay', 'duration'])\n\u001b[1;32m 55\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mset_run_params\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0minjected_square_current\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mcurrent\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 56\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_backend\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0minject_square_current\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcurrent\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 57\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 58\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m/mnt/d/Dropbox (ASU)/dev/scidash/neuronunit/neuronunit/models/backends/general_pyNN.py\u001b[0m in \u001b[0;36minject_square_current\u001b[0;34m(self, current)\u001b[0m\n\u001b[1;32m 144\u001b[0m \u001b[0mstart\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mfloat\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'delay'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 145\u001b[0m \u001b[0mamplitude\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mfloat\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'amplitude'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 146\u001b[0;31m \u001b[0melectrode\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mneuron\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mDCSource\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mstart\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mstart\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mstop\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mstop\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mamplitude\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mamplitude\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 147\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 148\u001b[0m \u001b[0melectrode\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0minject_into\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mhhcell\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mAttributeError\u001b[0m: module 'neuron' has no attribute 'DCSource'" + ] + } + ], + "source": [ + "modelS, testS = x.quick_test_builder(test_class=\"RheobaseTest\", \n", + " backend='HHpyNN')\n", + "scoreS = testS.judge(modelS)\n", + "scoreS, scoreS.prediction" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'modelS' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mmodelS\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;31mNameError\u001b[0m: name 'modelS' is not defined" + ] + } + ], + "source": [ + "modelS" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "scoreS.plot_vm()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Getting Rheobase data values from neuroelectro.org\n", + "http://neuroelectro.org/api/1/nes/?nlex=nifext_50&e__name=Rheobase\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:PyNN:Initializing membrane potential of 0 cells and 0 Populations.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "RS_pop[0] { nseg=1 L=10 Ra=35.4\n", + "\t/*location 0 attached to cell 0*/\n", + "\t/* First segment only */\n", + "\tinsert morphology { diam=10}\n", + "\tinsert capacitance { cm=31.831}\n", + "\tinsert RS { v0=-60 k=0.0007 vr=-60 vt=-40 vpeak=35 a=0.03 b=-0.002 c=-50 d=0.1 C=0.0001}\n", + "\tinsert RS_Iext { weight=1 delay=100 duration=1000 amplitude=0.3}\n", + "}\n", + "RS_pop[0] { nseg=1 L=10 Ra=35.4\n", + "\t/*location 0 attached to cell 0*/\n", + "\t/* First segment only */\n", + "\tinsert morphology { diam=10}\n", + "\tinsert capacitance { cm=31.831}\n", + "\tinsert RS { v0=-60 k=0.0007 vr=-60 vt=-40 vpeak=35 a=0.03 b=-0.002 c=-50 d=0.1 C=0.0001}\n", + "\tinsert RS_Iext { weight=1 delay=100 duration=1000 amplitude=0.3}\n", + "}\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:PyNN:Initializing membrane potential of 0 cells and 0 Populations.\n", + "INFO:PyNN:Initializing membrane potential of 0 cells and 0 Populations.\n", + "INFO:PyNN:Initializing membrane potential of 0 cells and 0 Populations.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "RS_pop[0] { nseg=1 L=10 Ra=35.4\n", + "\t/*location 0 attached to cell 0*/\n", + "\t/* First segment only */\n", + "\tinsert morphology { diam=10}\n", + "\tinsert capacitance { cm=31.831}\n", + "\tinsert RS { v0=-60 k=0.0007 vr=-60 vt=-40 vpeak=35 a=0.03 b=-0.002 c=-50 d=0.1 C=0.0001}\n", + "\tinsert RS_Iext { weight=1 delay=100 duration=1000 amplitude=-0}\n", + "}\n", + "RS_pop[0] { nseg=1 L=10 Ra=35.4\n", + "\t/*location 0 attached to cell 0*/\n", + "\t/* First segment only */\n", + "\tinsert morphology { diam=10}\n", + "\tinsert capacitance { cm=31.831}\n", + "\tinsert RS { v0=-60 k=0.0007 vr=-60 vt=-40 vpeak=35 a=0.03 b=-0.002 c=-50 d=0.1 C=0.0001}\n", + "\tinsert RS_Iext { weight=1 delay=100 duration=1000 amplitude=-0}\n", + "}\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:PyNN:Initializing membrane potential of 0 cells and 0 Populations.\n", + "INFO:PyNN:Initializing membrane potential of 0 cells and 0 Populations.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "RS_pop[0] { nseg=1 L=10 Ra=35.4\n", + "\t/*location 0 attached to cell 0*/\n", + "\t/* First segment only */\n", + "\tinsert morphology { diam=10}\n", + "\tinsert capacitance { cm=31.831}\n", + "\tinsert RS { v0=-60 k=0.0007 vr=-60 vt=-40 vpeak=35 a=0.03 b=-0.002 c=-50 d=0.1 C=0.0001}\n", + "\tinsert RS_Iext { weight=1 delay=100 duration=1000 amplitude=0.15}\n", + "}\n", + "RS_pop[0] { nseg=1 L=10 Ra=35.4\n", + "\t/*location 0 attached to cell 0*/\n", + "\t/* First segment only */\n", + "\tinsert morphology { diam=10}\n", + "\tinsert capacitance { cm=31.831}\n", + "\tinsert RS { v0=-60 k=0.0007 vr=-60 vt=-40 vpeak=35 a=0.03 b=-0.002 c=-50 d=0.1 C=0.0001}\n", + "\tinsert RS_Iext { weight=1 delay=100 duration=1000 amplitude=0.15}\n", + "}\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:PyNN:Initializing membrane potential of 0 cells and 0 Populations.\n", + "INFO:PyNN:Initializing membrane potential of 0 cells and 0 Populations.\n", + "INFO:PyNN:Initializing membrane potential of 0 cells and 0 Populations.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "RS_pop[0] { nseg=1 L=10 Ra=35.4\n", + "\t/*location 0 attached to cell 0*/\n", + "\t/* First segment only */\n", + "\tinsert morphology { diam=10}\n", + "\tinsert capacitance { cm=31.831}\n", + "\tinsert RS { v0=-60 k=0.0007 vr=-60 vt=-40 vpeak=35 a=0.03 b=-0.002 c=-50 d=0.1 C=0.0001}\n", + "\tinsert RS_Iext { weight=1 delay=100 duration=1000 amplitude=0.075}\n", + "}\n", + "RS_pop[0] { nseg=1 L=10 Ra=35.4\n", + "\t/*location 0 attached to cell 0*/\n", + "\t/* First segment only */\n", + "\tinsert morphology { diam=10}\n", + "\tinsert capacitance { cm=31.831}\n", + "\tinsert RS { v0=-60 k=0.0007 vr=-60 vt=-40 vpeak=35 a=0.03 b=-0.002 c=-50 d=0.1 C=0.0001}\n", + "\tinsert RS_Iext { weight=1 delay=100 duration=1000 amplitude=0.075}\n", + "}\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:PyNN:Initializing membrane potential of 0 cells and 0 Populations.\n", + "INFO:PyNN:Initializing membrane potential of 0 cells and 0 Populations.\n", + "INFO:PyNN:Initializing membrane potential of 0 cells and 0 Populations.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "RS_pop[0] { nseg=1 L=10 Ra=35.4\n", + "\t/*location 0 attached to cell 0*/\n", + "\t/* First segment only */\n", + "\tinsert morphology { diam=10}\n", + "\tinsert capacitance { cm=31.831}\n", + "\tinsert RS { v0=-60 k=0.0007 vr=-60 vt=-40 vpeak=35 a=0.03 b=-0.002 c=-50 d=0.1 C=0.0001}\n", + "\tinsert RS_Iext { weight=1 delay=100 duration=1000 amplitude=0.0375}\n", + "}\n", + "RS_pop[0] { nseg=1 L=10 Ra=35.4\n", + "\t/*location 0 attached to cell 0*/\n", + "\t/* First segment only */\n", + "\tinsert morphology { diam=10}\n", + "\tinsert capacitance { cm=31.831}\n", + "\tinsert RS { v0=-60 k=0.0007 vr=-60 vt=-40 vpeak=35 a=0.03 b=-0.002 c=-50 d=0.1 C=0.0001}\n", + "\tinsert RS_Iext { weight=1 delay=100 duration=1000 amplitude=0.0375}\n", + "}\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:PyNN:Initializing membrane potential of 0 cells and 0 Populations.\n", + "INFO:PyNN:Initializing membrane potential of 0 cells and 0 Populations.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "RS_pop[0] { nseg=1 L=10 Ra=35.4\n", + "\t/*location 0 attached to cell 0*/\n", + "\t/* First segment only */\n", + "\tinsert morphology { diam=10}\n", + "\tinsert capacitance { cm=31.831}\n", + "\tinsert RS { v0=-60 k=0.0007 vr=-60 vt=-40 vpeak=35 a=0.03 b=-0.002 c=-50 d=0.1 C=0.0001}\n", + "\tinsert RS_Iext { weight=1 delay=100 duration=1000 amplitude=0.05625}\n", + "}\n", + "RS_pop[0] { nseg=1 L=10 Ra=35.4\n", + "\t/*location 0 attached to cell 0*/\n", + "\t/* First segment only */\n", + "\tinsert morphology { diam=10}\n", + "\tinsert capacitance { cm=31.831}\n", + "\tinsert RS { v0=-60 k=0.0007 vr=-60 vt=-40 vpeak=35 a=0.03 b=-0.002 c=-50 d=0.1 C=0.0001}\n", + "\tinsert RS_Iext { weight=1 delay=100 duration=1000 amplitude=0.05625}\n", + "}\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:PyNN:Initializing membrane potential of 0 cells and 0 Populations.\n", + "INFO:PyNN:Initializing membrane potential of 0 cells and 0 Populations.\n", + "INFO:PyNN:Initializing membrane potential of 0 cells and 0 Populations.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "RS_pop[0] { nseg=1 L=10 Ra=35.4\n", + "\t/*location 0 attached to cell 0*/\n", + "\t/* First segment only */\n", + "\tinsert morphology { diam=10}\n", + "\tinsert capacitance { cm=31.831}\n", + "\tinsert RS { v0=-60 k=0.0007 vr=-60 vt=-40 vpeak=35 a=0.03 b=-0.002 c=-50 d=0.1 C=0.0001}\n", + "\tinsert RS_Iext { weight=1 delay=100 duration=1000 amplitude=0.046875}\n", + "}\n", + "RS_pop[0] { nseg=1 L=10 Ra=35.4\n", + "\t/*location 0 attached to cell 0*/\n", + "\t/* First segment only */\n", + "\tinsert morphology { diam=10}\n", + "\tinsert capacitance { cm=31.831}\n", + "\tinsert RS { v0=-60 k=0.0007 vr=-60 vt=-40 vpeak=35 a=0.03 b=-0.002 c=-50 d=0.1 C=0.0001}\n", + "\tinsert RS_Iext { weight=1 delay=100 duration=1000 amplitude=0.046875}\n", + "}\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:PyNN:Initializing membrane potential of 0 cells and 0 Populations.\n", + "INFO:PyNN:Initializing membrane potential of 0 cells and 0 Populations.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "RS_pop[0] { nseg=1 L=10 Ra=35.4\n", + "\t/*location 0 attached to cell 0*/\n", + "\t/* First segment only */\n", + "\tinsert morphology { diam=10}\n", + "\tinsert capacitance { cm=31.831}\n", + "\tinsert RS { v0=-60 k=0.0007 vr=-60 vt=-40 vpeak=35 a=0.03 b=-0.002 c=-50 d=0.1 C=0.0001}\n", + "\tinsert RS_Iext { weight=1 delay=100 duration=1000 amplitude=0.0515625}\n", + "}\n", + "RS_pop[0] { nseg=1 L=10 Ra=35.4\n", + "\t/*location 0 attached to cell 0*/\n", + "\t/* First segment only */\n", + "\tinsert morphology { diam=10}\n", + "\tinsert capacitance { cm=31.831}\n", + "\tinsert RS { v0=-60 k=0.0007 vr=-60 vt=-40 vpeak=35 a=0.03 b=-0.002 c=-50 d=0.1 C=0.0001}\n", + "\tinsert RS_Iext { weight=1 delay=100 duration=1000 amplitude=0.0515625}\n", + "}\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:PyNN:Initializing membrane potential of 0 cells and 0 Populations.\n", + "INFO:PyNN:Initializing membrane potential of 0 cells and 0 Populations.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "RS_pop[0] { nseg=1 L=10 Ra=35.4\n", + "\t/*location 0 attached to cell 0*/\n", + "\t/* First segment only */\n", + "\tinsert morphology { diam=10}\n", + "\tinsert capacitance { cm=31.831}\n", + "\tinsert RS { v0=-60 k=0.0007 vr=-60 vt=-40 vpeak=35 a=0.03 b=-0.002 c=-50 d=0.1 C=0.0001}\n", + "\tinsert RS_Iext { weight=1 delay=100 duration=1000 amplitude=0.0539063}\n", + "}\n", + "RS_pop[0] { nseg=1 L=10 Ra=35.4\n", + "\t/*location 0 attached to cell 0*/\n", + "\t/* First segment only */\n", + "\tinsert morphology { diam=10}\n", + "\tinsert capacitance { cm=31.831}\n", + "\tinsert RS { v0=-60 k=0.0007 vr=-60 vt=-40 vpeak=35 a=0.03 b=-0.002 c=-50 d=0.1 C=0.0001}\n", + "\tinsert RS_Iext { weight=1 delay=100 duration=1000 amplitude=0.0539063}\n", + "}\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:PyNN:Initializing membrane potential of 0 cells and 0 Populations.\n", + "INFO:PyNN:Initializing membrane potential of 0 cells and 0 Populations.\n", + "INFO:PyNN:Initializing membrane potential of 0 cells and 0 Populations.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "RS_pop[0] { nseg=1 L=10 Ra=35.4\n", + "\t/*location 0 attached to cell 0*/\n", + "\t/* First segment only */\n", + "\tinsert morphology { diam=10}\n", + "\tinsert capacitance { cm=31.831}\n", + "\tinsert RS { v0=-60 k=0.0007 vr=-60 vt=-40 vpeak=35 a=0.03 b=-0.002 c=-50 d=0.1 C=0.0001}\n", + "\tinsert RS_Iext { weight=1 delay=100 duration=1000 amplitude=0.0527344}\n", + "}\n", + "RS_pop[0] { nseg=1 L=10 Ra=35.4\n", + "\t/*location 0 attached to cell 0*/\n", + "\t/* First segment only */\n", + "\tinsert morphology { diam=10}\n", + "\tinsert capacitance { cm=31.831}\n", + "\tinsert RS { v0=-60 k=0.0007 vr=-60 vt=-40 vpeak=35 a=0.03 b=-0.002 c=-50 d=0.1 C=0.0001}\n", + "\tinsert RS_Iext { weight=1 delay=100 duration=1000 amplitude=0.0527344}\n", + "}\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:PyNN:Initializing membrane potential of 0 cells and 0 Populations.\n", + "INFO:PyNN:Initializing membrane potential of 0 cells and 0 Populations.\n", + "INFO:PyNN:Initializing membrane potential of 0 cells and 0 Populations.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "RS_pop[0] { nseg=1 L=10 Ra=35.4\n", + "\t/*location 0 attached to cell 0*/\n", + "\t/* First segment only */\n", + "\tinsert morphology { diam=10}\n", + "\tinsert capacitance { cm=31.831}\n", + "\tinsert RS { v0=-60 k=0.0007 vr=-60 vt=-40 vpeak=35 a=0.03 b=-0.002 c=-50 d=0.1 C=0.0001}\n", + "\tinsert RS_Iext { weight=1 delay=100 duration=1000 amplitude=0.0521484}\n", + "}\n", + "RS_pop[0] { nseg=1 L=10 Ra=35.4\n", + "\t/*location 0 attached to cell 0*/\n", + "\t/* First segment only */\n", + "\tinsert morphology { diam=10}\n", + "\tinsert capacitance { cm=31.831}\n", + "\tinsert RS { v0=-60 k=0.0007 vr=-60 vt=-40 vpeak=35 a=0.03 b=-0.002 c=-50 d=0.1 C=0.0001}\n", + "\tinsert RS_Iext { weight=1 delay=100 duration=1000 amplitude=0.0521484}\n", + "}\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:PyNN:Initializing membrane potential of 0 cells and 0 Populations.\n", + "INFO:PyNN:Initializing membrane potential of 0 cells and 0 Populations.\n", + "INFO:PyNN:Initializing membrane potential of 0 cells and 0 Populations.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Highest subthreshold current is 51.5625 pA\n", + "Lowest suprathreshold current is 52.1484375 pA\n" + ] + }, + { + "data": { + "text/plain": [ + "(Ratio = 0.24, {'value': array(52.1484375) * pA})" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "modelSn, testSn = x.quick_test_builder(test_class=\"RheobaseTest\", backend='NEURON')\n", + "scoreSn = testSn.judge(modelSn)\n", + "scoreSn, scoreSn.prediction" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "scoreSn.plot_vm()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Getting Spike Half-Width data values from neuroelectro.org\n", + "http://neuroelectro.org/api/1/nes/?nlex=nifext_50&e__name=Spike+Half-Width\n" + ] + }, + { + "data": { + "text/plain": [ + "(Z = -0.60, {'mean': array(0.0008875) * s, 'std': array(0.) * s, 'n': 4})" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "modelS, testS = x.quick_test_builder(test_class=\"InjectedCurrentAPWidthTest\")\n", + "scoreS = testS.judge(modelS)\n", + "scoreS, scoreS.prediction" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'-0.599226'" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "'%f' % scoreS.score" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYoAAAEKCAYAAAAMzhLIAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMS4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvNQv5yAAAIABJREFUeJzt3XecXHW5+PHPM1uzqaSHZFNIIwFCAhsEARUIiiJN9AoqAqKoIAhiQ/xZruXCtYLYEHKJigLSq5d+qQES0gghhbTdtN1kS7bN7pTn98fM7M4mm9nZyc6e+Z553q9XXpm2M893zpnvc77lfI+oKsYYY8yBBLwOwBhjTG6zRGGMMSYlSxTGGGNSskRhjDEmJUsUxhhjUrJEYYwxJiXPE4WIFIjIMhF5LH5/ioi8LiLrReQeESn2OkZjjMlnnicK4OvAmqT7NwG/UdXpQB1wmSdRGWOMATxOFCIyATgTuD1+X4BTgfviL1kEnOtNdMYYYwAKPf783wLfBgbH748A6lU1HL9fBYzv7g9F5HLgcoCBAwcee/jhh2c5VGOM8ZelS5fuVtVRPb3Os0QhIh8HqlV1qYh8KPFwNy/tdo0RVb0NuA2goqJClyxZkpU4jTHGr0RkSzqv87JFcSJwtoh8DCgFhhBrYQwTkcJ4q2ICsN3DGI0xJu95Nkahqter6gRVnQxcADynqp8Fngc+GX/ZxcDDHoVojDGG3Jj1tK/vAN8QkQ3Exizu8DgeY4zJa14PZgOgqi8AL8RvbwSO8zIeY4wxnXKxRWGMMSaHWKIwxhiTkiUKY4wxKVmiMHlLVbFLARvTM0sUJmPb61uZ/N3HeXSFm6e6TLn+CT71p9e8DuOgXP7XJfzif9/1OoyDUvHTp/nu/Su9DsOkYInCZGzNjr0APLhsm8eRZG7JljqvQzgoT72zi98//57XYRyU3U3t3P1mpddhmBQsUZiMReO9Nt2tu2KM8Q9LFCZjif792KK/xhi/skRhMpZoUQQsTxjja5YoTMY6WxQeB2KMySpLFCZjiYmlYqMUxviaJQqTscQpCAHbi4zxNfuJm4xFE11P1qIwxtcsUZiMdZzTbHnCGF+zRGEyFo1Peyqw0WxjfM0ShclYouupwObHGuNrlihMxuzMbGPygyUKkzE7M9uY/GCJwmQs0fVkPU/G+JslCpOxRNeTjVGYTNn1QNxgicJkLGpdT+YgWZ5wgyUKk7HE9FhrUJhMRS1TOMEShclY5+qxlilMZiKWKJxgicJkTG0w2xwkyxNusERhMtZxHoW1KEyGrOvJDZYoTMY6p8daovCCH2YMRd0vQl6wRGEy1rmEh8eBZCDqgxrKB0WwFoUjHPyJm1zh8mC2HyooP5RBo15HYNJhicJkrKOici9P+GK2jR8ShR/KkA8sUZiMqcMtCj/UT1EfHI1bonCDJQqTMZevRxHxQQe/HypZH2yGvGCJwmSsc4zC2zgy4Y9K1v0y+GHmVj7wLFGISLmIPC8ia0RktYh8Pf74cBF5WkTWx/8/xKsYTWour/Xkj24bryM4eH4oQz7wskURBq5T1VnA8cCVIjIb+C7wrKpOB56N3zc5yOXzKOxoPDf4YTvkA88SharuUNW34rcbgTXAeOAcYFH8ZYuAc72J0PTE5etR+GPWk9cRHDxLFG7IiTEKEZkMzANeB8ao6g6IJRNgtHeRmVQ6Zj05mCn8UEH5ogw+6ALMB54nChEZBNwPXKOqe3vxd5eLyBIRWVJTU5O9AM0BRTrGKDwOJAN+qKD8cXa5+2XIB54mChEpIpYk7lLVB+IP7xKRcfHnxwHV3f2tqt6mqhWqWjFq1Kj+Cdh04fJ5FH6ooHyQJ5ImRHgciEnJy1lPAtwBrFHVXyc99Qhwcfz2xcDD/R2bSY+dR+EtPyU7Fw828kmhh599InARsEpElscf+x5wI3CviFwGbAU+5VF8pgedy4x7G0cm/FHJul8Gu6aJGzxLFKr6MgdeJei0/ozFZMbt6bFeR3DwfJAnOg82XFwwLI94Ppht3OXy0aB1PeUGG6NwgyUKk7Gow9Nj/XCymp+SnYut0nxiicJkzOUlPOyEu9zQOXPO2zhMapYoTMZcPjPbD+dR+KFVZC0KN1iiMBlLVLYuTo91Ockl+KFF4fLMuXxiicJkzOWBSD8cyfprMNvd7ZAPLFGYjCWqKRenNiYGgi1ReCsadb9llw8sUZiMubzWUOeMLW/jOBg+yBMdCbvA5Q2RB2zrmIy5fETrh66nRCXrcBGSEoXHgZiUbPOYjDncoHB6naqExBRfP5Sh0FoUOc22jsmYy+ciuLxEekJHsnO4gz+cGKOwmiin2eYxGUvM41fcSxguX3QpIeyDROGHll0+sERhMubySWt+mPUU8UGi6GxRuFuGfGCJwmTM5a4nPw1mu5woEi2KQofLkA8sUZiMubwonR/OzI74oNsm7IOWXT6wRGEyFnY5USSWH3E4U/iiRaHulyEfWKIwGXP5hLuID7qe/DCYHY5Y15MLLFGYjIXjh+UuDlWoD6bH+qFF0ZGwHS5DPrBEYTLm9qyn2P8utyg6T1ZzuAw+GGfJB5YoTMbCDmeKROyFBe5WUJF4GVw+Go/Y9FgnWKIwGYs42OWU4Ie+8YjD1wNJiPqgVZQPLFGYjEUcblFEOubvu/sTSHz/Lo9RJBK2y2XIB+7+Soznwg43KRIzhlzuevLDrCebHusGSxQmY4mjchfTRUf/vsvdNj44qzlsg9lOsERhMubyEh5hH1WyLg8E22C2GyxRmIw5fcKdD7ptIj5IdjY91g2WKEzGXF7Cww9jFLYCrukvlihMxlxeFDAcScwYcvcn4IfB7I59yN0i5AV3fyXGc75oUThcyfrhCncuj3PlE0sUJmOJisrF33riSNbdKtZnLQqT0yxRmIz5oUXhssQ5CO6miaRE4f7m8DVLFCZjfpj15DI/JDs/bId8YInCZMzlisrls8oTXD7hMcEShRtyNlGIyBkislZENojId72Ox+zP5YFIl9epSvBDJevywUY+yclEISIFwO+BjwKzgQtFZLa3UZl9uVxRhRyOPcHl7z/B5e7LfJKTiQI4DtigqhtVtR24GzjH45jMPjq7Ptz7sUci1m2TC6xF4YZcTRTjgcqk+1XxxzqIyOUiskREltTU1PRrcMZ9fqig/FGG+OV0nU7Z/periaK7GX9d9iRVvU1VK1S1YtSoUf0UlvELf4xRuF8GP0wqyAe5miiqgPKk+xOA7R7FYnzIF0fjie4zh4vSHnE/2eWDXE0UbwLTRWSKiBQDFwCPeByT8RE/9O/7oZIN+6AM+aDQ6wC6o6phEfka8L9AAbBQVVd7HJbxET+0KEI+qGRD1vXkhJxMFACq+gTwhNdxmO6py/0d+KNF4Yf+fT8ku3zQq64nERkYP8fB5LnkI0EXc0aignI54fmh66lzO3gciEkpZaIQkYCIfEZEHheRauBdYIeIrBaRX4jI9P4J0+Qa148E/dCicH0bgD+6APNBTy2K54GpwPXAWFUtV9XRwMnAYuBGEflclmM0Ocj1SsoP3TZhH5w02B52ez/KFz2NUSxQ1dC+D6pqLXA/cL+IFGUlMpPTXO/2aHM8fnA/WYM/ypAPempRPBjvehp4oBd0l0iM/7k+WyXkgyPZdse3AVjXkyt6ShR/Ac4CNonIPSJybvy8BpPnXK9oXW8RgT+Oxl3fj/JFykShqg+r6oXAJOAB4GJgq4gsFJHT+yNAk5uSKykXjwk7Ztt4HMfB8MPJaiGHL6ebT9KaHquqrap6j6qeB3wYmAf8O6uRmZzm+hG5HwZRXe/+A3+0ivJBWolCRMaIyFUi8grwEPAUcGxWIzM5zfVKyg8VlOvJGvwx+ywfpJz1JCJfAi4EZhLrevq2qr7SH4GZ3OZ6RdvmixaFnTRo+kdP02PfD9wIPKOqtkVNB9cHIV1PdOCPo3E/jLPkg5SJQlUvTdwWkTnA5OS/UdUHshaZyWmuHwn6YYzC9W0QiSo2O9YNaS0KKCILgTnAaiCxdyqx7iiTh7qMUTjW9ZFcQTkWegdVdb5V1HXmnKMbIk+ku3rs8ao6O6uRGKe4XEn5oTURiaqzSS7B5X0o36S7euxrImKJwnRw+UfuepcN+OOMZtdnzuWTdFsUi4gli51AG7FrWquqzslaZCanuXxU7nLsCX5Idm3hiNchmDSlmygWAhcBq+gcozB5zOWjQZdbQwltISuD6T/pJoqtqmrXrDYdXK5s/dCi8MPRuB/OZckX6SaKd0XkH8CjxLqeAJsem8+Coc6KyrW2hevrVAEEfXA03mUfcnVD5Il0E8UAYgniw0mP2fTYPOZyReWHI9nkStZVftgO+SKtRJF84p0xAEGHuz5c7jZL8EMl64dkly96umb290VkeIrnTxWRj/d9WCbXufwjd7k1lNDmg24bPyS7fNFTi2IV8KiIBIG3gBqgFJgOzAWeAX6e1QhNTgqGooi4WUm5nOQSEi26woB4HEnmEtvB5TLki57WenoYeFhEpgMnAuOAvcDfgctVtTX7IZpc1BaKUFpYQKuDlW4i5gFFBR5HkrnE1NKSwnTPmc09iRaFy2XIF+mOUawH1mc5FuOQYDjCgOJYonCtVZE4kh1QXODsEt2JFkWpw8kusR1KiwqcnX2WLyyVm4wEQ1FKHT0S9EOLIjHO4nKiSLQoXC5DvnDzl248FwxFnP2Bd1ay7u7+icFsl7ttEi2KEoe3Q76wLWQy4nai6Ox6clUw0b9fVODsEt2JFkVxgVVDuS7d61FMAa5i/wsXnZ2dsEyuC4aizh6Rt7ZHCAgUBtyMH5L7990tQ1s4QklhABGb9ZTr0j0z+yHgDmJLeNjkZ0MwHGFYWZHXYWQkGIowoKgAl+unYChKUYFQ4HAh2kJRp7vO8km6iSKoqrdkNRLjlLZQtGMw2LWZQ62hiNPdThA7Gi8tdLsMre2d3ZeO7UJ5J91EcbOI/BB4iq6LAr6VlahMznN5jKI1FKGksAB3j8Xjlazjya65PcygkkKnt0O+SDdRHEXsehSn0vWa2adm8qEi8gvgLKAdeA+4VFXr489dD1wGRICrVfV/M/kMk13BUMTZ2SptoajzLYrm9giDStL9+eam5rYwZSUFRK0zO+elu6edBxymqu199LlPA9eralhEbgKuB74Tv9zqBcARwKHAMyIyQ1XdO/3Xx6JRdbqiao2PUbisuS3MwBLHy9AeYWBxIY3BsNehmB6ke0i4AhjWVx+qqk+pamLvWAxMiN8+B7hbVdtUdROwATiurz7X9I2W+Iybga4mivaI07OFAJrawpQVx75/V/v3W9rDzu5D+SbdrTSG2MWL3qTrGEVfTI/9AnBP/PZ4YokjoSr+2H5E5HLgcoCJEyf2QRgmXU3xI0BXWxTN7WFGDCymoTXkdSgZa2kPM3pwKY1Bd8vQ3BahbITbraJ8ke4v/Ye9fWMReQYY281TN8QXG0REbgDCwF2JP+vm9d0eL6nqbcBtABUVFY4eU7mpqa1ronDty28Khpk0YiANrSFnj8ab2yIMHFnoeKIIJx1sOLoh8kTKRCEitwL/UNX/6+0bq+qCHt77YuDjwGnaOb+yCihPetkEYHtvP9tkV3M8UbjabdAYr6BcPtGruS3MQMcH5FvaI5QVFzp9Pku+6Kmjdj3wKxHZLCI3icjcvvhQETkD+A5wtqq2JD31CHCBiJTEzwafDrzRF59p+k5zR4vCzYqqKRhmcKmbSS4hNpjtbhlUleZ29wfk80XKRKGqN6vqCcAHgVrgf0RkjYj8QERmHMTn3goMBp4WkeUi8qf4560G7gXeAf4NXGkznnJPk8MtinAkSmvI3Rlb0DnrzMXvPyGxPL3LZcgn6V6PYgtwE3CTiMwDFhIbt8jocEBVp6V47mfAzzJ5X9M/mtvdTRTNbbHjDpcTRWKZ9ETXk4vjLInt4Hr3Wb5Ia46giBSJyFkichfwJLAOOD+rkZmc1eRwZdvYFhv8HeRw11PyGJE4el5zogwDit3dDvmkp8Hs04ELgTOJjRXcTewSqM39EJvJUc37znpy6Ig20W02uGPGlkPBx+0768xFe+OztYYOiC0s6dI+lI962tO+B/wD+Kaq1vZDPMYBzW1hRKDMwW6DjnNASt1dYyhx/keiknVRchls1lPuS5koVPWU/grEuKOhNcSQ0iInuz0afXA0Xp+oZB1d5h06E4WrS9XnG7fXMTCeqGsJcYijP/C98QpqcKmb8QM0tMQrWYdbFPUt7reK8oklCtNr9S3tDC0r9jqMjNQ1x9a1dDXRQez7BxgW3wYujrP4ofssn1iiML3W0Bpy9mi2riWEiNsVVKLraUhpYfeL3jhgb2uIksKAs9c0yTeWKEyv1bW0dzkid+l4tq6lnSGlRRQWxHZ9F2fbNLSGGFxS2FEGF9W3hLqMTzi4GfKKu3ua8UzsR17s5NFsXUuI4QNjXTauzrZpaAk5PZANsWSXaNW5OCki31iiML0SjkRpDIad7bqpa253fqZNfWvI+TIkJwqT+yxRmF5JDEK6Ohhc19LOcEcH4hPqWtqdr2Rrm9s7WnYm91miML1Sl5ia6WhlG2tRuBl7Qk1jG6MGlXTcd3GcpaapjVGDS3p+ockJlihMr9Q0xi5w6OKPXFWp3Wcg3jWqSk1jG6OHlAJODhMRikSpbW5n1KBSr0MxabJEYXqlujEIwJghyUe0bhzSNraFCYaijB7i7tH43mCYtnC0S4vCNXuaYueBJB9suLIP5StLFKZXqvcmWhSlzs0a2tWQSHKJo3HHCkBniy452blm31apa/tRPrJEYXqlujFISWEgdrKXY3bujSWKsUPc7fJItOhc7PpLqGmKlWHkILfHivKJJQrTK9WNbYweUuLk9aZ3xVtDYxxOFB0tCpcThcPjXPnKEoXpleq9bYwZ7GZFuyvRohjqZvyQXMm6W4adDZYoXGOJwvTKrsags/3ju/YGGTqgyOn1harqWhlUUtil68+1YeCquhbGDCmhpNDd7ZBvLFGYtEWjyra6ViYcUuZ1KBnZXh/cb3zCtZVXq+pamHDIgI6uPwd7AKnqZh9yayvkH0sUJm01TW20haOUHzIAcG8O/9baZiaOSKqgXCsAUFnbSvlwNxN1QmU82SU4uBnyjiUKk7bK2hYAJysqVWVrbQuTHIw9QVX3q2RdE45E2dEQpNzRVmm+skRh0rbV4URR3dhGMBRl0siBXoeSsdrmdlraI05Xsjv3BolE1elkl48sUZi0Vda2AjB+mHs/8s27mwGcblG4nKgTNu+OlWGiw2XIR5YoTNo272lm7JBSJ2cNbYlXspNGuFtBra9uAmDa6EFdn3BoJHh9dSMA08YM6uGVJpdYojBpW7erkRljB+/3uAvL9GyobqK4MLBfa8iF2BPW72qkpDDQ5WjctWVI1lc3MXRA0X5rVbm0HfKRJQqTlkhU2VDdxIyko1mXzs5es2MvM8YM6nL5UHeij1m3q4mpowZREHAt8k4bdjUxffSgrvuOQ/tRvrJEYdKytbaFtnC02xaFC9bsaOTwsUO8DuOgrN/VyAyHu2xUlfXVjUx3uAz5yhKFScvanbG+5Zlj3EsUNY1t7G5q43BHkxzErpO9vSHobKIG2NEQpK4l5OQ+lO8sUZi0vLO9gYDg5NHgOzv2AjBrnLstiuVV9QDMnTDM40gyt7wyXoaJh3gciektSxQmLcsq65k5dghlxe4tL750Sx0BgTkThnodSsaWb61HBI7qpgyuLEOyorKe4oIAs8ZZi8I1lihMj6JRZXllPfMmdn80m+sV1dIttcwcO4TBpftfAjW3I++0vLKO6aMH7VcGl8aBl1fWM+vQId0uBujKdshXniYKEfmmiKiIjIzfFxG5RUQ2iMhKETnGy/hMzMbdTTQGw8wr75ooXKijwpEoy7bWUzFp/+4OVyrZSFR5a2s988rd7bJpC0dYUVW/3z4EbuxH+c6zRCEi5cDpwNakhz8KTI//uxz4owehmX28sakOgHkO9i2v3r6XlvYIFZPdiz3h7W0NNLSGeP+0EV6HkrGlm+sIhqKcNG2k16GYDHjZovgN8G26tjrPAf6qMYuBYSIyzpPoTIeX1tcwbmgpU0e5t07S82urEcHpCuql9TWA42XYsJvCgHD8VHeTXT7zJFGIyNnANlVdsc9T44HKpPtV8ce6e4/LRWSJiCypqanJUqQmHIny8obdfGD6KKdOsEt4fm0Nc8uHMWKQmxdbAnhx/W6OHD/E6TK8tL6GeROHMajEvckQJouJQkSeEZG3u/l3DnAD8IPu/qybx7od51LV21S1QlUrRo0a1ZehmyQrquppDIY5eYZ7R7M1jW2srKrnlJmjvQ4lYzWNbSzZXJuyDLm+/EVlbQtvb9vLabPGeB2KyVDW0ruqLujucRE5CpgCrIgfoU4A3hKR44i1IMqTXj4B2J6tGE3P/v32TooKhJOnHTgZ52pF9djK7ajCR44Ye+AX5WjsCU++vYOowsfnHNrt8y408h5ftQOAM486cC+y5upOZAAPup5UdZWqjlbVyao6mVhyOEZVdwKPAJ+Pz346HmhQ1R39HaOJiUaVx1bu4IMzRjG0bP+ppbleST24bBuzxw1h5gHOZnZhQb3HVuxgxphBByyDCx5buZ2jy4cdcHn0XN+PTO6dR/EEsBHYAPwFuMLbcPLb0q117GgIctbR3R/N5rL1uxpZWdXAJ47pdojLCRuqG3ljcy3nzHW3DCur6nl7217Om+vePmQ6eT6yFG9VJG4rcKV30Zhk97xZSVlxgZN9y3e+upniwgDnznO3kv3ba1soLgjw6fnlPb84R/198RbKigv4xLETvA7FHIRca1GYHFHX3M6jK7Zz3rzxzs1UqW1u5/63qjhv7nhGOjpTaG8wxP1vbePMOeOcLcPupjYeXr6dc+eNZ0g3Z8Ubd1iiMN26d0klbeEoF50wyetQeu1/XtlEMBTlspOneB1Kxm5/aRNNbWEuO6nnMuTqMPCf/+89QpFoWmUwuc0ShdlPMBTh9pc3ccJhI9K6hkMuVVTVe4Pc/tImPj5nHDPSWM46F9epqmtuZ+HLmzjjiLEcOd7NhQyr9wb52+ItnDtvPFNHubfisOnKEoXZz12vb6WmsY2vL5ie8nW5OGvo10+vIxyN8q2PzOzxtbk62+ZXT6+lpT3MtafP6PG1ubgNAG588l0iUeXqU1PvQ2BrPbnAEoXpojEY4o8vvMcJh43g+MPcWm5h8cY93P1mJRefMJlJI9xbbgRg2dY67np9Kxe/f7KzU2IXb9zDA8u28eUPTGXySDe3g+nKrVFKk3W3PLuePc1t3P7RCq9D6ZWW9jDfuX8lE4eX8Y0P93wknouCoQjfvX8VYwaXct2He24R5aLGYIjv3L+SCYcM4MpTpnkdjukjlihMh3W7Gln4ymYumF/O3G6Wg85Vqsr1D6xia20L//ji8U5eXAngx4+uZu2uRhZ94TjnZppBbDt8/6G3qaxt4Z4vn8CA4v2vO2HcZF1PBogt/vft+1YyuLSQb33kcK/D6ZU7X93Mw8u3c93pMzjB0dVJ/7Wkkn++UclXPzSVD85wc+2yRfHtcO2CGcyfPNzrcEwfcu+wxWTFrc9vYHllPbd+Zh7DBxb36m+9XKbnyVU7+Mlj77Bg1hiu+FDvuzpyYYmhF9fVcP0Dqzhx2gi+kcYA9r5yYZ2kp1bv5MePvcPps8dwRQZdTjlQBJOCtSgMS7fU8rvnNvCJeeMPuPhcd7yeNfTiuhq+fvdy5k08hFsunEsg0LuAvI4fYtfz/urflzJ9zGD+9LljKSro3U8yF8rw0voarr57GXPGD+WWC+ZR0OvtkAOFMClZoshzVXUtfPlvbzF+2AB+dM4RXoeTtidW7eCLi5Zw2KiBLLx4vpPjEq9u2M1Fd7zOqMEl3Hnp/G6v6Z3rnl9bzWWLljB5xEAWXjLfxiV8yhJFHmsMhrjsziW0hSMsvKTCiWUWVJVFr27myn+8xVEThnL35cd3u7Jtrnt0xXYuufNNyg8p496vnMCYIaVeh9Rr/3xjK19atITpowfxzy8d7/SFlUxq7h2GmT4RjkS56p/L2FDTxKJLj2Pa6Nyfs9/aHuGGh1bxwFvbWDBrNL+78BjnjmCjUeVXT6/l98+/R8WkQ7jt8xW9HhPyWjgS5edPvMvCVzZx8vSR3PqZYxg6wL1kbdJniSJP/fTxNbywtoafn3cUJ03P/avXrdmxl2vvWc7aXY1cs2A6V586vddjEl7b0dDKN/+1glc27OGC+eX85zlHUlzoVqN+654Wrr13OUu31HHJ+yfz/TNnUdjLcRXjHksUeWjRq5u589XNfOnkKXzmfRMzfh+R2L8N1U19GF1XoUiUP77wHr97bj1DBxSx8JL5fXZp04JAgOrGVoKhCKVF2WuZqMYuAPX9h96mPRzlxk8cxafnl/fJIG5BQKhubOuXMty3tIofP/oOAtx8wdw+u05GQUDY0dBKezjqXOLMF7ZV8szz71bz40dXs2DWGL770VkH9V4lhQVc+v4p3P9WFbe/tLGPIuz0/NpqPnrzS/z66XV87KhxPH3tB/v0+tefe99Etta28IOH387aFNPNu5u55H/e5Kp/LmPyiDKe+PrJXHDcxD6b6XPR8ZOoqmvlew+uyloZNlQ3cuFfFvOt+1Yye9wQnrzm5D69mNJFx0/ivZpmfvLYO332nqZvWYsij7y7cy9X/XMZh48dws0XzO31NMbu3HDmLHbubeWnj68hHFW+/IHDDroSXF5Zz6+fXseL62qYMnIgt3++ggWz+/7iSR8+YixXnzqNW57bwMThZXwtjQXs0tXQEuK2l97jLy9torggwA8+PpvPnzCpz7tpTps1hmsXzOA3z6xj4vAyrlnQd8uX7Glq4w8vvMeiVzczsKSQn593FBfML+/zLr+zjj6Ut7c18OcXN1I+fACXf2Bqn76/OXiWKPJEdWOQy+5cwsCSAu64pIKBfbREREFA+M2n5xKQFdz45LtsqmnmB2fN7vX7R6PKi+truO3Fjbz63h6GlRXx/z4+m4uOn5TV7ohrFsygsq6VXz61DoArT5l2UIlubzDEX1/dzJ9f3EhjMMy5cw/l+o/NyuqspqtOnUZlXQu/fWZeV9+jAAAOM0lEQVQ9qnDNgukHVYb6lnbueHkTd7y8iWAowqeOLefbZ8zM6qymb59xOFX1rfz8iXeJKn1ywGH6jiWKPBAMRfjy35ZS29zOv75yAuOGDujT9y8pLOCWC+YxaUQZf3jhPV7duJvrTp/JWUcfmrLVoqqs3dXIk6t2ct/SKrbVtzJ6cAk3fGwWF75vYr+sdxQICL/81NGoKr98ah0bqpv4yblH9vqchk27m1n06mb+taSS5vYIC2aN4boPz2DWuJ6v53GwAgHhpvPnAHDzs+vZUNPEz887qtczkdbubOTOVzfx4LJtBENRzpwzjmsXzGDa6OxfT6IgIPz203OB2BLl63Y18uOzj3Dy3BI/klw4/f9gVVRU6JIlS7wOIyepKtfdu4IHlm3jT587hjOOHJfVz3t94x5++Mhq3t3ZyMhBJSyYNZojxw9lzJBSSgoDNLSG2Fbfyjvb9/Lm5lp2NAQRgZOmjeQ/Ksr58BFjKCns/ymv0ajyhxc28Kun1zFyUAnXLJjOefPGH/BEPlWlqq6VZ9bs4pEV21m2tZ6iAuGsOYdy6YlTOGpC/19wSFW57cWN3PTvdzmkrJivnTqNTx474YCVraqycXczz7yzi0dXbuftbXspKQxw3rzxXHriFE+WOY9Gld89t4HfPruOUYNKuPq02HY4UAtVVVlf3cTanY2cOG2kc1ONvSYiS1W1x6WiLVH43B9feI+b/v0u150+g6tO67s++FSiUeXZd6t5cFkVL6/fzd5geL/XHDq0lKPLh/GhmaP40MzROXPC2YrKen7wyGpWVNYzsLiA46YM5/BxQxgxsJioKnUtITbvbmZlVQPb6lsBmDVuCGcffSjnHzOe0TlQjre3NfCfj77DG5trKSkMMH/ycGaOHcyIQcVEo0ll2NZATWMbAEeXD+OsOeP4xDETcqKyXV5Zz48eWc3yynoGFBVQMfkQDh87mOEDS4hEo+xpbu/YDnua2wE4/5gJ/Oo/jvY4crdYojC89t4ePnP7Ys48ahy/u3CeJ32+qsr2hiC1Te0EwxGGDihizODSnD6bWlVZuqWOB5dtY/HGPWzZ00I4GvudFAaEicPLmDl2MCdMHcH7p47sl66ZTKyorOeh5dtYvLGWzbubaQ1FACgtCjB5xEBmjh3M8YeN4KRpIykfXuZxtPtTVd7aWscjy7fz+qZaNu9pJhiKAjCgqIBJI8qYPW4Ix08dwUPLtrG8sp43b1jQZ+Nv+cASRZ6rbW7noze/yMCSQh792kn24zkI0ajSGAxTUCCUFgacPMFMVQmGohQEhKICcXKgWFVpDUUoDAT2K8PSLbWc/8fX+O9PzuE/Kso9jNIt6SYK9/Z40yNV5dv3raSuOcTvLpxnSeIgBQLC0LIiBpUUOpkkILZC64DiAooLA04mCYiVoay4sNsyHDPxEA4bOZD7llR5FJ2/ubnXm5SeWLWTZ9bs4lsfmckRh/b/oKox/U1EOP/YCbyxuTarKwXkK0sUPtPQGuJHj67myPFDuPTEyV6HY0y/+fT8cooLA9zx8iavQ/EdSxQ+c+tz69nT1MZ/nTfH2W4SYzIxclAJ5x8zgQfeqmJ3U5vX4fiK1SQ+sqOhlUWvbeG8eRM8mcdvjNe+ePIU2sJRa1X0MUsUPnLLs+tRVa5Z0D/nSxiTa6aOGsS5cw9l4cubOs5zMQfPEoVP1DS2cf/SbXx6fnlOzok3pr9864zDAfj5E2s8jsQ/LFH4xF2vb6E9EuULJ07xOhRjPDV+2ACuPGUaj6/cwaMrtnsdji9YovCBUCTK3xdv5ZSZozhsVG6eJWxMf7riQ1M5unwY33twFWt3NnodjvM8SxQicpWIrBWR1SLy30mPXy8iG+LPfcSr+Fzy8vrd7G5q47Pvm+R1KMbkhMKCAL//zDwGFBVw8cI37NyKg+RJohCRU4BzgDmqegTwy/jjs4ELgCOAM4A/iEj/LyXqmIeXb2NYWREfmDHK61CMyRkTDilj0ReOIxyN8ok/vMITq3Zk7SqAfufV2g5fBW5U1TYAVa2OP34OcHf88U0isgE4Dngt1ZtV1bXyrX+tILELJPYFJWmn0C7/ddlh9v+7A7+Gbt674+/2eU73//ikx7r5+30+N3mXThXbG5tqOf/YCXa9YWP2MWvcEB684kS+etdSrrjrLd4/dQSXnTSFk6aP9GQ5++5Eo0p7JEpbOEp7OEp7JEooHCUcjRKJQiSqRFWJRJWIKtFo8m26eSz2f+ffsd9j0agS7UXO9CpRzABOFpGfAUHgm6r6JjAeWJz0uqr4Y/sRkcuBywFKx07llQ27E4/v87r9bwuy/3Od79vlfvKdfV/T9bEDv3dSzPu9d8ff7fP3XR/bvzDJn3v0hGFcfMLk/T/QGEP58DIeuuJE/vraFm57cSOXLVpCWXEBx046hGmjBzFxeBnDyooYOqCIooIAgnT81NrDUdrCEdrCUYKh2P9todhjwfj/icq9LRwlFFHakx5rj1f+bfvcT74d7k2N7ZGsrR4rIs8AY7t56gbgZ8BzwNeB+cA9wGHArcBrqvr3+HvcATyhqven+ixbPdYYk45QJMqL62p4fm01Kyob2FDd1LH8em+JQElhgOKCAMWFBbHbHfe7uV0YoCTFc8UFAUoKAxTFHy8sCFAgQkEAAiIUBIRAQOKPScdjXZ7veCzptgiBAEm348+JMGxgcVqrx2atRaGqCw70nIh8FXhAY1nqDRGJAiOJtSCS1wieANj8NmNMnygqCHDarDGcNmsMQPxCTu00tIZoaA0RjiqqnV27JUWxBFBSGOi4XRr/vzDg5nLtmfCq6+kh4FTgBRGZARQDu4FHgH+IyK+BQ4HpwBsexWiM8blAQBgxqIQRg0q8DiWneZUoFgILReRtoB24ON66WC0i9wLvAGHgSlXNrF1ojDGmT3iSKFS1HfjcAZ77GbExDGOMMTnA5lMaY4xJyRKFMcaYlCxRGGOMSckShTHGmJQsURhjjEnJEoUxxpiUsraER38SkUZgrddxeGwksZMW85WVP7/LD/YdZFL+Sara47LTXp1w19fWprNeiZ+JyJJ8/g6s/PldfrDvIJvlt64nY4wxKVmiMMYYk5JfEsVtXgeQA/L9O7Dym3z/DrJWfl8MZhtjjMkev7QojDHGZIklCmOMMSk5lShE5AwRWSsiG0Tku908XyIi98Sff11EJvd/lNmTRvk/ICJviUhYRD7pRYzZlsZ38A0ReUdEVorIsyIyyYs4syWN8n9FRFaJyHIReVlEZnsRZ7b0VP6k131SRFREfDddNo194BIRqYnvA8tF5IsH/aGq6sQ/oAB4j9i1tYuBFcDsfV5zBfCn+O0LgHu8jrufyz8ZmAP8Ffik1zF79B2cApTFb381D/eBIUm3zwb+7XXc/Vn++OsGAy8Ci4EKr+P2YB+4BLi1Lz/XpRbFccAGVd2osQsf3Q2cs89rzgEWxW/fB5wm/rmobY/lV9XNqroSiHoRYD9I5zt4XlVb4ncXE7vuul+kU/69SXcHAn6arZJOHQDwE+C/gWB/BtdP0v0O+pRLiWI8UJl0vyr+WLevUdUw0ACM6Jfosi+d8vtdb7+Dy4AnsxpR/0qr/CJypYi8R6yyvLqfYusPPZZfROYB5ar6WH8G1o/S/Q2cH+9+vU9Eyg/2Q11KFN21DPY9WkrnNa7yc9nSlfZ3ICKfAyqAX2Q1ov6VVvlV9feqOhX4DvD9rEfVf1KWX0QCwG+A6/otov6Xzj7wKDBZVecAz9DZy5IxlxJFFZCcGScA2w/0GhEpBIYCtf0SXfalU36/S+s7EJEFwA3A2ara1k+x9Yfe7gN3A+dmNaL+1VP5BwNHAi+IyGbgeOARnw1o97gPqOqepP3+L8CxB/uhLiWKN4HpIjJFRIqJDVY/ss9rHgEujt/+JPCcxkd3fCCd8vtdj99BvOvhz8SSRLUHMWZTOuWfnnT3TGB9P8aXbSnLr6oNqjpSVSer6mRiY1Rnq+oSb8LNinT2gXFJd88G1hz0p3o9it/LEf+PAeuIjfrfEH/sP4ntDAClwL+ADcAbwGFex9zP5Z9P7IijGdgDrPY6Zg++g2eAXcDy+L9HvI65n8t/M7A6XvbngSO8jrk/y7/Pa1/AZ7Oe0twH/iu+D6yI7wOHH+xn2hIexhhjUnKp68kYY4wHLFEYY4xJyRKFMcaYlCxRGGOMSckShTHGmJQsURiTRERGJK26uVNEtiXdfzVLnzlPRG5P8fwoEfl3Nj7bmHQUeh2AMblEVfcAcwFE5EdAk6r+Mssf+z3gpyliqhGRHSJyoqq+kuVYjNmPtSiMSZOINMX//5CI/J+I3Csi60TkRhH5rIi8Eb8WxNT460aJyP0i8mb834ndvOdgYI6qrojf/2BSC2ZZ/HmAh4DP9lNRjenCEoUxmTka+DpwFHARMENVjwNuB66Kv+Zm4DeqOh84P/7cviqAt5PufxO4UlXnAicDrfHHl8TvG9PvrOvJmMy8qao7AOJLej8Vf3wVsYsnASwAZiddEmWIiAxW1cak9xkH1CTdfwX4tYjcBTygqlXxx6uBQ/u+GMb0zBKFMZlJXpU2mnQ/SufvKgCcoKqtHFgrsTXKAFDVG0XkcWLr+SwWkQWq+m78Nanex5issa4nY7LnKeBriTsiMreb16wBpiW9ZqqqrlLVm4h1Nx0ef2oGXbuojOk3liiMyZ6rgYr4lcbeAb6y7wvirYWhSYPW14jI2yKyglgLInGFvlOAx/sjaGP2ZavHGuMxEbkWaFTVVOdSvAico6p1/ReZMTHWojDGe3+k65hHFyIyCvi1JQnjFWtRGGOMSclaFMYYY1KyRGGMMSYlSxTGGGNSskRhjDEmJUsUxhhjUvr/XVgCzGRVoJwAAAAASUVORK5CYII=\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "scoreS.plot_vm()" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Getting Spike Half-Width data values from neuroelectro.org\n", + "http://neuroelectro.org/api/1/nes/?nlex=nifext_50&e__name=Spike+Half-Width\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:PyNN:Initializing membrane potential of 0 cells and 0 Populations.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "RS_pop[0] { nseg=1 L=10 Ra=35.4\n", + "\t/*location 0 attached to cell 0*/\n", + "\t/* First segment only */\n", + "\tinsert morphology { diam=10}\n", + "\tinsert capacitance { cm=31.831}\n", + "\tinsert RS { v0=-60 k=0.0007 vr=-60 vt=-40 vpeak=35 a=0.03 b=-0.002 c=-50 d=0.1 C=0.0001}\n", + "\tinsert RS_Iext { weight=1 delay=100 duration=300 amplitude=0.1}\n", + "}\n", + "RS_pop[0] { nseg=1 L=10 Ra=35.4\n", + "\t/*location 0 attached to cell 0*/\n", + "\t/* First segment only */\n", + "\tinsert morphology { diam=10}\n", + "\tinsert capacitance { cm=31.831}\n", + "\tinsert RS { v0=-60 k=0.0007 vr=-60 vt=-40 vpeak=35 a=0.03 b=-0.002 c=-50 d=0.1 C=0.0001}\n", + "\tinsert RS_Iext { weight=1 delay=100 duration=300 amplitude=0.1}\n", + "}\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:PyNN:Initializing membrane potential of 0 cells and 0 Populations.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "RS_pop[0] { nseg=1 L=10 Ra=35.4\n", + "\t/*location 0 attached to cell 0*/\n", + "\t/* First segment only */\n", + "\tinsert morphology { diam=10}\n", + "\tinsert capacitance { cm=31.831}\n", + "\tinsert RS { v0=-60 k=0.0007 vr=-60 vt=-40 vpeak=35 a=0.03 b=-0.002 c=-50 d=0.1 C=0.0001}\n", + "\tinsert RS_Iext { weight=1 delay=100 duration=300 amplitude=0.1}\n", + "}\n", + "RS_pop[0] { nseg=1 L=10 Ra=35.4\n", + "\t/*location 0 attached to cell 0*/\n", + "\t/* First segment only */\n", + "\tinsert morphology { diam=10}\n", + "\tinsert capacitance { cm=31.831}\n", + "\tinsert RS { v0=-60 k=0.0007 vr=-60 vt=-40 vpeak=35 a=0.03 b=-0.002 c=-50 d=0.1 C=0.0001}\n", + "\tinsert RS_Iext { weight=1 delay=100 duration=300 amplitude=0.1}\n", + "}\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:PyNN:Initializing membrane potential of 0 cells and 0 Populations.\n", + "INFO:PyNN:Initializing membrane potential of 0 cells and 0 Populations.\n" + ] + }, + { + "data": { + "text/plain": [ + "(Z = -0.60,\n", + " {'mean': array(0.00088625) * s, 'std': array(1.25e-06) * s, 'n': 4})" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "modelSn, testSn = x.quick_test_builder(test_class=\"InjectedCurrentAPWidthTest\", backend='NEURON')\n", + "scoreSn = testSn.judge(modelSn)\n", + "scoreSn, scoreSn.prediction" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "scoreSn.plot_vm()" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'-0.601565'" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "'%f' % scoreSn.score" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Parallel rheobase test" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Getting Rheobase data values from neuroelectro.org\n", + "http://neuroelectro.org/api/1/nes/?nlex=nifext_50&e__name=Rheobase\n", + "Getting rheobase vm\n" + ] + }, + { + "data": { + "text/plain": [ + "(Ratio = 0.25, {'value': array(53.5234375) * pA})" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "modelP, testP = x.quick_test_builder(test_class=\"RheobaseTestP\")\n", + "scoreP = testP.judge(modelP)\n", + "scoreP, scoreP.prediction" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "scoreP.plot_vm()" + ] + } + ], + "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.6.7" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/neuronunit/examples/upgrading_simulator_implemented_models_to_NU_models.ipynb b/neuronunit/examples/upgrading_simulator_implemented_models_to_NU_models.ipynb new file mode 100644 index 000000000..6319dd3f4 --- /dev/null +++ b/neuronunit/examples/upgrading_simulator_implemented_models_to_NU_models.ipynb @@ -0,0 +1,1560 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "WARNING /opt/conda/lib/python3.5/site-packages/Cython/Compiler/Main.py:367: FutureWarning: Cython directive 'language_level' not set, using 2 for now (Py2). This will change in a later release! File: /home/jovyan/.cache/cython/brian_extensions/_cython_magic_c623251294d243655f5ca77c558577cb.pyx\n", + " tree = Parsing.p_module(s, pxd, full_module_name)\n", + " [py.warnings]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Starting simulation at t=0. s for a duration of 50. ms\n", + "50. ms (100%) simulated in 4s\n", + "Starting simulation at t=53. ms for a duration of 50. ms\n", + "50. ms (100%) simulated in 2s\n", + "Velocity = 12.33 m/s\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "['ALLOW_THREADS',\n", + " 'Annotation',\n", + " 'Arrow',\n", + " 'Artist',\n", + " 'AutoLocator',\n", + " 'Axes',\n", + " 'BUFSIZE',\n", + " 'BinomialFunction',\n", + " 'BrianLogger',\n", + " 'BrianObject',\n", + " 'BrianObjectException',\n", + " 'BrianPreference',\n", + " 'Button',\n", + " 'CLIP',\n", + " 'Circle',\n", + " 'Clock',\n", + " 'CodeRunner',\n", + " 'ComplexWarning',\n", + " 'Cylinder',\n", + " 'CythonCodeObject',\n", + " 'DAILY',\n", + " 'DEFAULT_CONSTANTS',\n", + " 'DEFAULT_FUNCTIONS',\n", + " 'DEFAULT_UNITS',\n", + " 'DataSource',\n", + " 'DateFormatter',\n", + " 'DateLocator',\n", + " 'DayLocator',\n", + " 'DimensionMismatchError',\n", + " 'EK',\n", + " 'ENa',\n", + " 'ERR_CALL',\n", + " 'ERR_DEFAULT',\n", + " 'ERR_IGNORE',\n", + " 'ERR_LOG',\n", + " 'ERR_PRINT',\n", + " 'ERR_RAISE',\n", + " 'ERR_WARN',\n", + " 'El',\n", + " 'Equations',\n", + " 'EventMonitor',\n", + " 'ExplicitStateUpdater',\n", + " 'Expression',\n", + " 'FLOATING_POINT_SUPPORT',\n", + " 'FPE_DIVIDEBYZERO',\n", + " 'FPE_INVALID',\n", + " 'FPE_OVERFLOW',\n", + " 'FPE_UNDERFLOW',\n", + " 'FR',\n", + " 'False_',\n", + " 'Figure',\n", + " 'FigureCanvasBase',\n", + " 'FixedFormatter',\n", + " 'FixedLocator',\n", + " 'FormatStrFormatter',\n", + " 'Formatter',\n", + " 'FuncFormatter',\n", + " 'Function',\n", + " 'Gamp',\n", + " 'Gcoulomb',\n", + " 'Gfarad',\n", + " 'Ggram',\n", + " 'Ggramme',\n", + " 'Ghertz',\n", + " 'Gjoule',\n", + " 'Gliter',\n", + " 'Glitre',\n", + " 'Gmeter',\n", + " 'Gmetre',\n", + " 'Gmolar',\n", + " 'Gmole',\n", + " 'Gohm',\n", + " 'Gpascal',\n", + " 'GridSpec',\n", + " 'Group',\n", + " 'Gsecond',\n", + " 'Gsiemens',\n", + " 'Gvolt',\n", + " 'Gwatt',\n", + " 'HOURLY',\n", + " 'HourLocator',\n", + " 'Hz',\n", + " 'ImportExport',\n", + " 'In',\n", + " 'IndexDateFormatter',\n", + " 'IndexLocator',\n", + " 'Inf',\n", + " 'Infinity',\n", + " 'LinAlgError',\n", + " 'Line2D',\n", + " 'LinearLocator',\n", + " 'Locator',\n", + " 'LogFormatter',\n", + " 'LogFormatterExponent',\n", + " 'LogFormatterMathtext',\n", + " 'LogLocator',\n", + " 'M',\n", + " 'MAXDIMS',\n", + " 'MAY_SHARE_BOUNDS',\n", + " 'MAY_SHARE_EXACT',\n", + " 'MHz',\n", + " 'MINUTELY',\n", + " 'MO',\n", + " 'MONTHLY',\n", + " 'MachAr',\n", + " 'MagicError',\n", + " 'MagicNetwork',\n", + " 'Mamp',\n", + " 'MatplotlibFinder',\n", + " 'MaxNLocator',\n", + " 'Mcoulomb',\n", + " 'Mfarad',\n", + " 'Mgram',\n", + " 'Mgramme',\n", + " 'Mhertz',\n", + " 'MinuteLocator',\n", + " 'Mjoule',\n", + " 'Mliter',\n", + " 'Mlitre',\n", + " 'Mmeter',\n", + " 'Mmetre',\n", + " 'Mmolar',\n", + " 'Mmole',\n", + " 'ModuleDeprecationWarning',\n", + " 'Mohm',\n", + " 'MonthLocator',\n", + " 'Morphology',\n", + " 'Mpascal',\n", + " 'Msecond',\n", + " 'Msiemens',\n", + " 'MultipleLocator',\n", + " 'Mvolt',\n", + " 'Mwatt',\n", + " 'NAN',\n", + " 'NINF',\n", + " 'NZERO',\n", + " 'NaN',\n", + " 'Nameable',\n", + " 'Network',\n", + " 'NetworkOperation',\n", + " 'NeuronGroup',\n", + " 'Normalize',\n", + " 'NullFormatter',\n", + " 'NullLocator',\n", + " 'NumpyCodeObject',\n", + " 'Out',\n", + " 'PINF',\n", + " 'PZERO',\n", + " 'PackageLoader',\n", + " 'PoissonGroup',\n", + " 'PoissonInput',\n", + " 'PolarAxes',\n", + " 'Polygon',\n", + " 'PopulationRateMonitor',\n", + " 'PreferenceError',\n", + " 'Quantity',\n", + " 'RAISE',\n", + " 'RRuleLocator',\n", + " 'RankWarning',\n", + " 'Rectangle',\n", + " 'SA',\n", + " 'SECONDLY',\n", + " 'SHIFT_DIVIDEBYZERO',\n", + " 'SHIFT_INVALID',\n", + " 'SHIFT_OVERFLOW',\n", + " 'SHIFT_UNDERFLOW',\n", + " 'SU',\n", + " 'ScalarFormatter',\n", + " 'ScalarType',\n", + " 'SecondLocator',\n", + " 'Section',\n", + " 'Slider',\n", + " 'Soma',\n", + " 'SpatialNeuron',\n", + " 'SpikeGeneratorGroup',\n", + " 'SpikeMonitor',\n", + " 'SpikeSource',\n", + " 'StateMonitor',\n", + " 'StateUpdateMethod',\n", + " 'Statements',\n", + " 'Subgroup',\n", + " 'Subplot',\n", + " 'SubplotTool',\n", + " 'Synapses',\n", + " 'TH',\n", + " 'TU',\n", + " 'Tamp',\n", + " 'Tcoulomb',\n", + " 'Text',\n", + " 'Tfarad',\n", + " 'Tgram',\n", + " 'Tgramme',\n", + " 'Thertz',\n", + " 'TickHelper',\n", + " 'TimedArray',\n", + " 'Tjoule',\n", + " 'Tliter',\n", + " 'Tlitre',\n", + " 'Tmeter',\n", + " 'Tmetre',\n", + " 'Tmolar',\n", + " 'Tmole',\n", + " 'Tohm',\n", + " 'TooHardError',\n", + " 'Tpascal',\n", + " 'Trackable',\n", + " 'True_',\n", + " 'Tsecond',\n", + " 'Tsiemens',\n", + " 'Tvolt',\n", + " 'Twatt',\n", + " 'UFUNC_BUFSIZE_DEFAULT',\n", + " 'UFUNC_PYVALS_NAME',\n", + " 'Unit',\n", + " 'VariableOwner',\n", + " 'VisibleDeprecationWarning',\n", + " 'WE',\n", + " 'WEEKLY',\n", + " 'WRAP',\n", + " 'WeaveCodeObject',\n", + " 'WeekdayLocator',\n", + " 'Widget',\n", + " 'YEARLY',\n", + " 'YearLocator',\n", + " '_',\n", + " '__',\n", + " '___',\n", + " '__builtin__',\n", + " '__builtins__',\n", + " '__doc__',\n", + " '__loader__',\n", + " '__name__',\n", + " '__package__',\n", + " '__spec__',\n", + " '_dh',\n", + " '_i',\n", + " '_i1',\n", + " '_i2',\n", + " '_ih',\n", + " '_ii',\n", + " '_iii',\n", + " '_oh',\n", + " '_sh',\n", + " 'absolute',\n", + " 'absolute_import',\n", + " 'acorr',\n", + " 'add',\n", + " 'add_docstring',\n", + " 'add_newdoc',\n", + " 'add_newdoc_ufunc',\n", + " 'add_newdocs',\n", + " 'alen',\n", + " 'all',\n", + " 'all_devices',\n", + " 'allclose',\n", + " 'alltrue',\n", + " 'alterdot',\n", + " 'amap',\n", + " 'amax',\n", + " 'amin',\n", + " 'amp',\n", + " 'angle',\n", + " 'angle_spectrum',\n", + " 'annotate',\n", + " 'any',\n", + " 'append',\n", + " 'apply_along_axis',\n", + " 'apply_over_axes',\n", + " 'arange',\n", + " 'arccos',\n", + " 'arccosh',\n", + " 'arcsin',\n", + " 'arcsinh',\n", + " 'arctan',\n", + " 'arctan2',\n", + " 'arctanh',\n", + " 'argmax',\n", + " 'argmin',\n", + " 'argpartition',\n", + " 'argsort',\n", + " 'argwhere',\n", + " 'around',\n", + " 'array',\n", + " 'array2string',\n", + " 'array_equal',\n", + " 'array_equiv',\n", + " 'array_repr',\n", + " 'array_split',\n", + " 'array_str',\n", + " 'arrow',\n", + " 'asanyarray',\n", + " 'asarray',\n", + " 'asarray_chkfinite',\n", + " 'ascontiguousarray',\n", + " 'asfarray',\n", + " 'asfortranarray',\n", + " 'asmatrix',\n", + " 'asscalar',\n", + " 'atleast_1d',\n", + " 'atleast_2d',\n", + " 'atleast_3d',\n", + " 'autoscale',\n", + " 'autumn',\n", + " 'average',\n", + " 'axes',\n", + " 'axhline',\n", + " 'axhspan',\n", + " 'axis',\n", + " 'axvline',\n", + " 'axvspan',\n", + " 'bar',\n", + " 'barbs',\n", + " 'barh',\n", + " 'bartlett',\n", + " 'base_repr',\n", + " 'bench',\n", + " 'beta',\n", + " 'binary_repr',\n", + " 'bincount',\n", + " 'binomial',\n", + " 'bitwise_and',\n", + " 'bitwise_not',\n", + " 'bitwise_or',\n", + " 'bitwise_xor',\n", + " 'bivariate_normal',\n", + " 'blackman',\n", + " 'bmat',\n", + " 'bone',\n", + " 'bool8',\n", + " 'bool_',\n", + " 'box',\n", + " 'boxplot',\n", + " 'brian_prefs',\n", + " 'broadcast',\n", + " 'broadcast_arrays',\n", + " 'broadcast_to',\n", + " 'broken_barh',\n", + " 'busday_count',\n", + " 'busday_offset',\n", + " 'busdaycalendar',\n", + " 'byte',\n", + " 'byte_bounds',\n", + " 'bytes',\n", + " 'bytes0',\n", + " 'bytes_',\n", + " 'c_',\n", + " 'can_cast',\n", + " 'cast',\n", + " 'cbook',\n", + " 'cbrt',\n", + " 'cdouble',\n", + " 'ceil',\n", + " 'center_matrix',\n", + " 'cfloat',\n", + " 'char',\n", + " 'character',\n", + " 'chararray',\n", + " 'check_cache',\n", + " 'check_units',\n", + " 'chisquare',\n", + " 'choice',\n", + " 'cholesky',\n", + " 'choose',\n", + " 'cla',\n", + " 'clabel',\n", + " 'clear_cache',\n", + " 'clf',\n", + " 'clim',\n", + " 'clip',\n", + " 'clongdouble',\n", + " 'clongfloat',\n", + " 'close',\n", + " 'cm',\n", + " 'cm2',\n", + " 'cm3',\n", + " 'cmeter',\n", + " 'cmetre',\n", + " 'codegen',\n", + " 'cohere',\n", + " 'collect',\n", + " 'colorbar',\n", + " 'colormaps',\n", + " 'colors',\n", + " 'column_stack',\n", + " 'common_type',\n", + " 'compare_chararrays',\n", + " 'complex128',\n", + " 'complex256',\n", + " 'complex64',\n", + " 'complex_',\n", + " 'complexfloating',\n", + " 'compress',\n", + " 'concatenate',\n", + " 'cond',\n", + " 'conj',\n", + " 'conjugate',\n", + " 'connect',\n", + " 'contour',\n", + " 'contourf',\n", + " 'convolve',\n", + " 'cool',\n", + " 'copper',\n", + " 'copy',\n", + " 'copysign',\n", + " 'copyto',\n", + " 'core',\n", + " 'corrcoef',\n", + " 'correlate',\n", + " 'cos',\n", + " 'cosh',\n", + " 'coulomb',\n", + " 'count_nonzero',\n", + " 'cov',\n", + " 'cross',\n", + " 'csd',\n", + " 'csingle',\n", + " 'csv2rec',\n", + " 'ctypeslib',\n", + " 'cumprod',\n", + " 'cumproduct',\n", + " 'cumsum',\n", + " 'cycler',\n", + " 'date2num',\n", + " 'datestr2num',\n", + " 'datetime',\n", + " 'datetime64',\n", + " 'datetime_as_string',\n", + " 'datetime_data',\n", + " 'declare_types',\n", + " 'dedent',\n", + " 'defaultclock',\n", + " 'deg2rad',\n", + " 'degrees',\n", + " 'delaxes',\n", + " 'delete',\n", + " 'demean',\n", + " 'deprecate',\n", + " 'deprecate_with_doc',\n", + " 'deprecated',\n", + " 'det',\n", + " 'detrend',\n", + " 'detrend_linear',\n", + " 'detrend_mean',\n", + " 'detrend_none',\n", + " 'device',\n", + " 'devices',\n", + " 'diag',\n", + " 'diag_indices',\n", + " 'diag_indices_from',\n", + " 'diagflat',\n", + " 'diagonal',\n", + " 'diff',\n", + " 'digitize',\n", + " 'dirichlet',\n", + " 'disconnect',\n", + " 'disp',\n", + " 'dist',\n", + " 'dist_point_to_segment',\n", + " 'distances_along_curve',\n", + " 'divide',\n", + " 'division',\n", + " 'docstring',\n", + " 'dot',\n", + " 'double',\n", + " 'drange',\n", + " 'draw',\n", + " 'draw_all',\n", + " 'draw_if_interactive',\n", + " 'dsplit',\n", + " 'dstack',\n", + " 'dtype',\n", + " 'e',\n", + " 'ediff1d',\n", + " 'eig',\n", + " 'eigh',\n", + " 'eigvals',\n", + " 'eigvalsh',\n", + " 'einsum',\n", + " 'einsum_path',\n", + " 'emath',\n", + " 'empty',\n", + " 'empty_like',\n", + " 'entropy',\n", + " 'epoch2num',\n", + " 'eqs',\n", + " 'equal',\n", + " 'equations',\n", + " 'errorbar',\n", + " 'errstate',\n", + " 'euler',\n", + " 'euler_gamma',\n", + " 'eventplot',\n", + " 'exact',\n", + " 'exception_to_str',\n", + " 'exit',\n", + " 'exp',\n", + " 'exp2',\n", + " 'exp_safe',\n", + " 'expand_dims',\n", + " 'expm1',\n", + " 'exponential',\n", + " 'exponential_euler',\n", + " 'extract',\n", + " 'eye',\n", + " 'fabs',\n", + " 'farad',\n", + " 'fastCopyAndTranspose',\n", + " 'fft',\n", + " 'fft2',\n", + " 'fftfreq',\n", + " 'fftn',\n", + " 'fftpack',\n", + " 'fftpack_lite',\n", + " 'fftshift',\n", + " 'fftsurr',\n", + " 'figaspect',\n", + " 'figimage',\n", + " 'figlegend',\n", + " 'fignum_exists',\n", + " 'figtext',\n", + " 'figure',\n", + " 'fill',\n", + " 'fill_between',\n", + " 'fill_betweenx',\n", + " 'fill_diagonal',\n", + " 'find',\n", + " 'find_common_type',\n", + " 'findobj',\n", + " 'finfo',\n", + " 'fix',\n", + " 'flag',\n", + " 'flatiter',\n", + " 'flatnonzero',\n", + " 'flatten',\n", + " 'flexible',\n", + " 'flip',\n", + " 'fliplr',\n", + " 'flipud',\n", + " 'float128',\n", + " 'float16',\n", + " 'float32',\n", + " 'float64',\n", + " 'float_',\n", + " 'float_power',\n", + " 'floating',\n", + " 'floor',\n", + " 'floor_divide',\n", + " 'fmax',\n", + " 'fmin',\n", + " 'fmod',\n", + " 'format_parser',\n", + " 'frange',\n", + " 'frexp',\n", + " 'frombuffer',\n", + " 'fromfile',\n", + " 'fromfunction',\n", + " 'fromiter',\n", + " 'frompyfunc',\n", + " 'fromregex',\n", + " 'fromstring',\n", + " 'full',\n", + " 'full_like',\n", + " 'fv',\n", + " 'gK',\n", + " 'gNa0',\n", + " 'gamma',\n", + " 'gca',\n", + " 'gcf',\n", + " 'gci',\n", + " 'generic',\n", + " 'genfromtxt',\n", + " 'geometric',\n", + " 'geomspace',\n", + " 'get',\n", + " 'get_array_wrap',\n", + " 'get_backend',\n", + " 'get_cmap',\n", + " 'get_current_fig_manager',\n", + " 'get_device',\n", + " 'get_dimensions',\n", + " 'get_figlabels',\n", + " 'get_fignums',\n", + " 'get_include',\n", + " 'get_ipython',\n", + " 'get_local_namespace',\n", + " 'get_logger',\n", + " 'get_or_create_dimension',\n", + " 'get_plot_commands',\n", + " 'get_printoptions',\n", + " 'get_scale_docs',\n", + " 'get_scale_names',\n", + " 'get_sparse_matrix',\n", + " 'get_state',\n", + " 'get_unit',\n", + " 'get_xyz_where',\n", + " 'getbufsize',\n", + " 'geterr',\n", + " 'geterrcall',\n", + " 'geterrobj',\n", + " 'getp',\n", + " 'ginput',\n", + " 'gl',\n", + " 'gradient',\n", + " 'gram',\n", + " 'gramme',\n", + " 'gray',\n", + " 'greater',\n", + " 'greater_equal',\n", + " 'grid',\n", + " 'griddata',\n", + " 'groups',\n", + " 'gsl_rk2',\n", + " 'gsl_rk4',\n", + " 'gsl_rk8pd',\n", + " 'gsl_rkck',\n", + " 'gsl_rkf45',\n", + " 'gumbel',\n", + " 'half',\n", + " 'hamming',\n", + " 'hanning',\n", + " 'have_same_dimensions',\n", + " 'helper',\n", + " 'hertz',\n", + " 'heun',\n", + " 'hexbin',\n", + " 'hfft',\n", + " 'hist',\n", + " 'hist2d',\n", + " 'histogram',\n", + " 'histogram2d',\n", + " 'histogramdd',\n", + " 'hlines',\n", + " 'hold',\n", + " 'hot',\n", + " 'hsplit',\n", + " 'hstack',\n", + " 'hsv',\n", + " 'hypergeometric',\n", + " 'hypot',\n", + " 'i',\n", + " 'i0',\n", + " 'identity',\n", + " 'ifft',\n", + " 'ifft2',\n", + " 'ifftn',\n", + " 'ifftshift',\n", + " 'ihfft',\n", + " 'iinfo',\n", + " 'imag',\n", + " 'implementation',\n", + " 'importexport',\n", + " 'imread',\n", + " 'imsave',\n", + " 'imshow',\n", + " 'in1d',\n", + " 'in_best_unit',\n", + " 'in_unit',\n", + " 'independent',\n", + " 'index_exp',\n", + " 'indices',\n", + " 'inexact',\n", + " 'inf',\n", + " 'inferno',\n", + " 'info',\n", + " 'infty',\n", + " 'inner',\n", + " 'input',\n", + " 'insert',\n", + " 'inside_poly',\n", + " 'install_repl_displayhook',\n", + " 'int0',\n", + " 'int16',\n", + " 'int32',\n", + " 'int64',\n", + " 'int8',\n", + " 'int_',\n", + " 'int_asbuffer',\n", + " 'intc',\n", + " 'integer',\n", + " 'interactive',\n", + " 'intercept',\n", + " 'interp',\n", + " 'intersect1d',\n", + " 'intp',\n", + " 'inv',\n", + " 'invert',\n", + " 'ioff',\n", + " 'ion',\n", + " 'ipmt',\n", + " 'irfft',\n", + " 'irfft2',\n", + " 'irfftn',\n", + " 'irr',\n", + " 'is_busday',\n", + " 'is_closed_polygon',\n", + " 'is_dimensionless',\n", + " 'is_numlike',\n", + " 'is_scalar_type',\n", + " 'is_string_like',\n", + " 'isclose',\n", + " 'iscomplex',\n", + " 'iscomplexobj',\n", + " 'isfinite',\n", + " 'isfortran',\n", + " 'ishold',\n", + " 'isinf',\n", + " 'isinteractive',\n", + " 'isnan',\n", + " 'isneginf',\n", + " 'isposinf',\n", + " 'ispower2',\n", + " 'isreal',\n", + " 'isrealobj',\n", + " 'isscalar',\n", + " 'issctype',\n", + " 'issubclass_',\n", + " 'issubdtype',\n", + " 'issubsctype',\n", + " 'isvector',\n", + " 'iterable',\n", + " 'ix_',\n", + " 'jet',\n", + " 'joule',\n", + " 'kHz',\n", + " 'kaiser',\n", + " 'kamp',\n", + " 'kcoulomb',\n", + " 'kelvin',\n", + " 'kfarad',\n", + " 'kgram',\n", + " 'kgramme',\n", + " 'khertz',\n", + " 'kilogram',\n", + " 'kjoule',\n", + " 'kliter',\n", + " 'klitre',\n", + " 'kmeter',\n", + " 'kmetre',\n", + " 'kmolar',\n", + " 'kmole',\n", + " 'kohm',\n", + " 'kpascal',\n", + " 'kron',\n", + " 'ksecond',\n", + " 'ksiemens',\n", + " 'kvolt',\n", + " 'kwatt',\n", + " 'l1norm',\n", + " 'l2norm',\n", + " 'lapack_lite',\n", + " 'laplace',\n", + " 'ldexp',\n", + " 'left_shift',\n", + " 'legend',\n", + " 'less',\n", + " 'less_equal',\n", + " 'lexsort',\n", + " 'linalg',\n", + " 'linear',\n", + " 'linked_var',\n", + " 'linspace',\n", + " 'liter',\n", + " 'litre',\n", + " 'little_endian',\n", + " 'load',\n", + " 'loads',\n", + " 'loadtxt',\n", + " 'locator_params',\n", + " 'log',\n", + " 'log10',\n", + " 'log1p',\n", + " 'log2',\n", + " 'logaddexp',\n", + " 'logaddexp2',\n", + " 'logger',\n", + " 'logical_and',\n", + " 'logical_not',\n", + " 'logical_or',\n", + " 'logical_xor',\n", + " 'logistic',\n", + " 'loglog',\n", + " 'lognormal',\n", + " 'logseries',\n", + " 'logspace',\n", + " 'long',\n", + " 'longcomplex',\n", + " 'longdouble',\n", + " 'longest_contiguous_ones',\n", + " 'longest_ones',\n", + " 'longfloat',\n", + " 'longlong',\n", + " 'lookfor',\n", + " 'lstsq',\n", + " 'mA',\n", + " 'mM',\n", + " 'mS',\n", + " 'mV',\n", + " 'ma',\n", + " 'mafromtxt',\n", + " 'magic_network',\n", + " 'magma',\n", + " 'magnitude_spectrum',\n", + " 'mamp',\n", + " 'margins',\n", + " 'mask_indices',\n", + " 'mat',\n", + " 'math',\n", + " 'matmul',\n", + " 'matplotlib',\n", + " 'matrix',\n", + " 'matrix_power',\n", + " 'matrix_rank',\n", + " 'matshow',\n", + " 'maximum',\n", + " 'maximum_sctype',\n", + " 'may_share_memory',\n", + " 'mcoulomb',\n", + " 'mean',\n", + " 'median',\n", + " 'memmap',\n", + " 'memory',\n", + " 'meshgrid',\n", + " 'meter',\n", + " 'metre',\n", + " 'mfarad',\n", + " 'mgram',\n", + " 'mgramme',\n", + " 'mgrid',\n", + " 'mhertz',\n", + " 'milstein',\n", + " 'min_scalar_type',\n", + " 'minimum',\n", + " 'minorticks_off',\n", + " 'minorticks_on',\n", + " 'mintypecode',\n", + " 'mirr',\n", + " 'mjoule',\n", + " 'mlab',\n", + " 'mliter',\n", + " 'mlitre',\n", + " 'mm',\n", + " 'mm2',\n", + " 'mm3',\n", + " 'mmeter',\n", + " 'mmetre',\n", + " 'mmolar',\n", + " 'mmole',\n", + " 'mod',\n", + " 'modf',\n", + " 'mohm',\n", + " 'molar',\n", + " 'mole',\n", + " 'monitors',\n", + " 'morpho',\n", + " 'movavg',\n", + " 'moveaxis',\n", + " 'mpascal',\n", + " 'mpl',\n", + " 'ms',\n", + " 'msecond',\n", + " 'msiemens',\n", + " 'msort',\n", + " 'multi_dot',\n", + " 'multinomial',\n", + " 'multiply',\n", + " 'multivariate_normal',\n", + " 'mvolt',\n", + " 'mwatt',\n", + " 'mx2num',\n", + " 'nA',\n", + " 'nF',\n", + " 'nM',\n", + " 'nS',\n", + " 'namp',\n", + " 'nan',\n", + " 'nan_to_num',\n", + " 'nanargmax',\n", + " 'nanargmin',\n", + " 'nancumprod',\n", + " 'nancumsum',\n", + " 'nanmax',\n", + " 'nanmean',\n", + " 'nanmedian',\n", + " 'nanmin',\n", + " 'nanpercentile',\n", + " 'nanprod',\n", + " 'nanstd',\n", + " 'nansum',\n", + " 'nanvar',\n", + " 'nbytes',\n", + " 'ncoulomb',\n", + " 'ndarray',\n", + " 'ndenumerate',\n", + " 'ndfromtxt',\n", + " 'ndim',\n", + " 'ndindex',\n", + " 'nditer',\n", + " 'negative',\n", + " 'negative_binomial',\n", + " 'nested_iters',\n", + " 'network_operation',\n", + " 'neuron',\n", + " 'new_figure_manager',\n", + " 'newaxis',\n", + " 'nextafter',\n", + " 'nfarad',\n", + " 'ngram',\n", + " 'ngramme',\n", + " 'nhertz',\n", + " 'nipy_spectral',\n", + " 'njoule',\n", + " 'nliter',\n", + " 'nlitre',\n", + " 'nmeter',\n", + " 'nmetre',\n", + " 'nmolar',\n", + " 'nmole',\n", + " 'nohm',\n", + " 'noncentral_chisquare',\n", + " 'noncentral_f',\n", + " 'nonzero',\n", + " 'norm',\n", + " 'norm_flat',\n", + " 'normal',\n", + " 'normpdf',\n", + " 'not_equal',\n", + " 'np',\n", + " 'npascal',\n", + " 'nper',\n", + " 'npv',\n", + " 'nsecond',\n", + " 'nsiemens',\n", + " 'num2date',\n", + " 'num2epoch',\n", + " 'number',\n", + " 'numpy',\n", + " 'numpy_',\n", + " 'nvolt',\n", + " 'nwatt',\n", + " 'obj2sctype',\n", + " 'object0',\n", + " 'object_',\n", + " 'ogrid',\n", + " 'ohm',\n", + " 'ones',\n", + " 'ones_like',\n", + " 'only',\n", + " 'outer',\n", + " 'over',\n", + " 'pA',\n", + " 'pF',\n", + " 'p_value',\n", + " 'packbits',\n", + " 'pad',\n", + " 'pamp',\n", + " 'pareto',\n", + " 'parsing',\n", + " 'partition',\n", + " 'pascal',\n", + " 'path_length',\n", + " 'pause',\n", + " 'pcolor',\n", + " 'pcolormesh',\n", + " 'pcoulomb',\n", + " 'percentile',\n", + " 'permutation',\n", + " 'pfarad',\n", + " 'pgram',\n", + " 'pgramme',\n", + " 'phase_spectrum',\n", + " 'phertz',\n", + " 'pi',\n", + " 'pie',\n", + " 'piecewise',\n", + " 'pink',\n", + " 'pinv',\n", + " 'pjoule',\n", + " 'pkgload',\n", + " 'place',\n", + " 'plasma',\n", + " 'pliter',\n", + " 'plitre',\n", + " 'plot',\n", + " 'plot_date',\n", + " 'plotfile',\n", + " 'plotting',\n", + " 'plt',\n", + " 'pmeter',\n", + " 'pmetre',\n", + " 'pmolar',\n", + " 'pmole',\n", + " 'pmt',\n", + " 'pohm',\n", + " 'poisson',\n", + " 'polar',\n", + " 'poly',\n", + " 'poly1d',\n", + " 'poly_below',\n", + " 'poly_between',\n", + " 'polyadd',\n", + " ...]" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dir()" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Requirement already satisfied: lazyarray in /opt/conda/lib/python3.5/site-packages (0.3.2)\n", + "Requirement already satisfied: numpy>=1.8 in /opt/conda/lib/python3.5/site-packages (from lazyarray) (1.12.1)\n", + "\u001b[31mcryptography 2.2.1 requires asn1crypto>=0.21.0, which is not installed.\u001b[0m\n", + "\u001b[31mcffi 1.11.5 requires pycparser, which is not installed.\u001b[0m\n", + "\u001b[31mallensdk 0.14.2 has requirement pandas<0.20.0,>=0.16.2, but you'll have pandas 0.23.1 which is incompatible.\u001b[0m\n", + "\u001b[33mYou are using pip version 10.0.1, however version 18.1 is available.\n", + "You should consider upgrading via the 'pip install --upgrade pip' command.\u001b[0m\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/opt/conda/lib/python3.5/site-packages/pyNN/neuron/__init__.py:14: UserWarning: mpi4py not available\n", + " warnings.warn(\"mpi4py not available\")\n" + ] + } + ], + "source": [ + "from pyNN.neuron import *\n", + "from pyNN.neuron import HH_cond_exp\n", + "from pyNN.neuron import EIF_cond_exp_isfa_ista\n", + "from pyNN.neuron import Izhikevich\n", + "\n", + "from pyNN import neuron\n", + "#\n", + "from pyNN.neuron import simulator as sim\n", + "from pyNN.neuron import setup as setup\n", + "\n", + "from pyNN.neuron import DCSource\n", + "from types import MethodType\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import io\n", + "import math\n", + "import pdb\n", + "#from numba import jit\n", + "from contextlib import redirect_stdout\n", + "import numpy as np\n", + "#from .base import *\n", + "import quantities as qt\n", + "from quantities import mV, ms, s\n", + "import matplotlib.pyplot as plt\n", + "from pyNN.neuron import *\n", + "from pyNN.neuron import HH_cond_exp\n", + "from pyNN.neuron import EIF_cond_exp_isfa_ista\n", + "from pyNN.neuron import Izhikevich\n", + "from elephant.spike_train_generation import threshold_detection\n", + "\n", + "from pyNN import neuron\n", + "#\n", + "#from pyNN.neuron import simulator as sim\n", + "from pyNN.neuron import setup as setup\n", + "#from pyNN.neuron import Izhikevich\n", + "#from pyNN.neuron import Population\n", + "from pyNN.neuron import DCSource\n", + "import numpy as np\n", + "import copy\n", + "from neo import AnalogSignal\n", + "import neuronunit.capabilities.spike_functions as sf\n", + "\n", + "import neuronunit.capabilities as cap\n", + "cap.ReceivesCurrent\n", + "cap.ProducesActionPotentials\n", + "def bind_NU_interface(model):\n", + "\n", + " def load_model(self):\n", + " neuron = None\n", + " from pyNN import neuron\n", + " self.hhcell = neuron.create(EIF_cond_exp_isfa_ista())\n", + " neuron.setup(timestep=self.dt, min_delay=1.0)\n", + "\n", + "\n", + " def init_backend(self, attrs = None, cell_name= 'HH_cond_exp', current_src_name = 'hannah', DTC = None, dt=0.01):\n", + " backend = 'HHpyNN'\n", + " self.current_src_name = current_src_name\n", + " self.cell_name = cell_name\n", + " self.adexp = True\n", + "\n", + " self.DCSource = DCSource\n", + " self.setup = setup\n", + " self.model_path = None\n", + " self.related_data = {}\n", + " self.lookup = {}\n", + " self.attrs = {}\n", + " self.neuron = neuron\n", + " self.model._backend = str('ExternalSim')\n", + " self.backend = self\n", + " self.model.attrs = {}\n", + "\n", + " #self.orig_lems_file_path = 'satisfying'\n", + " #self.model._backend.use_memory_cache = False\n", + " #self.model.unpicklable += ['h','ns','_backend']\n", + " self.dt = dt\n", + " if type(DTC) is not type(None):\n", + " if type(DTC.attrs) is not type(None):\n", + "\n", + " self.set_attrs(**DTC.attrs)\n", + " assert len(self.model.attrs.keys()) > 0\n", + "\n", + " if hasattr(DTC,'current_src_name'):\n", + " self._current_src_name = DTC.current_src_name\n", + "\n", + " if hasattr(DTC,'cell_name'):\n", + " self.cell_name = DTC.cell_name\n", + " \n", + " self.load_model()\n", + "\n", + " def get_membrane_potential(self):\n", + " \"\"\"Must return a neo.core.AnalogSignal.\n", + " And must destroy the hoc vectors that comprise it.\n", + " \"\"\"\n", + " #dt = float(copy.copy(self.neuron.dt))\n", + " data = self.hhcell.get_data().segments[0]\n", + " volts = data.filter(name=\"v\")[0]#/10.0\n", + " #data_block = all_cells.get_data()\n", + "\n", + " vm = AnalogSignal(volts,\n", + " units = mV,\n", + " sampling_period = self.dt *ms)\n", + " #results['vm'] = vm\n", + " return vm#data.filter(name=\"v\")[0]\n", + "\n", + " def _local_run(self):\n", + " '''\n", + " pyNN lazy array demands a minimum population size of 3. Why is that.\n", + " '''\n", + " results = {}\n", + " DURATION = 1000.0\n", + " \n", + " #ctx_cells.celltype.recordable\n", + " \n", + " \n", + " if self.celltype == 'HH_cond_exp':\n", + "\n", + " self.hhcell.record('spikes','v')\n", + "\n", + " else:\n", + " self.neuron.record_v(self.hhcell, \"Results/HH_cond_exp_%s.v\" % str(neuron))\n", + "\n", + " #self.neuron.record_gsyn(self.hhcell, \"Results/HH_cond_exp_%s.gsyn\" % str(neuron))\n", + " self.neuron.run(DURATION)\n", + " data = self.hhcell.get_data().segments[0]\n", + " volts = data.filter(name=\"v\")[0]#/10.0\n", + " #data_block = all_cells.get_data()\n", + "\n", + " vm = AnalogSignal(volts,\n", + " units = mV,\n", + " sampling_period = self.dt *ms)\n", + " results['vm'] = vm\n", + " results['t'] = vm.times # self.times\n", + " results['run_number'] = results.get('run_number',0) + 1\n", + " return results\n", + "\n", + "\n", + "\n", + "\n", + " def set_attrs(self,**attrs):\n", + " self.init_backend()\n", + " self.model.attrs.update(attrs)\n", + " assert type(self.model.attrs) is not type(None)\n", + " self.hhcell[0].set_parameters(**attrs)\n", + " return self\n", + "\n", + "\n", + " def inject_square_current(self,current):\n", + " attrs = copy.copy(self.model.attrs)\n", + " self.init_backend()\n", + " self.set_attrs(**attrs)\n", + " c = copy.copy(current)\n", + " if 'injected_square_current' in c.keys():\n", + " c = current['injected_square_current']\n", + "\n", + " stop = float(c['delay'])+float(c['duration'])\n", + " duration = float(c['duration'])\n", + " start = float(c['delay'])\n", + " amplitude = float(c['amplitude'])\n", + " electrode = self.neuron.DCSource(start=start, stop=stop, amplitude=amplitude)\n", + "\n", + "\n", + " electrode.inject_into(self.hhcell)\n", + " self.results = self._local_run()\n", + " self.vm = self.results['vm']\n", + "\n", + " def get_APs(self,vm):\n", + " vm = self.get_membrane_potential()\n", + " waveforms = sf.get_spike_waveforms(vm,threshold=-45.0*mV)\n", + " return waveforms\n", + "\n", + " def get_spike_train(self,**run_params):\n", + " vm = self.get_membrane_potential()\n", + "\n", + " spike_train = threshold_detection(vm,threshold=-45.0*mV)\n", + "\n", + " return spike_train\n", + " \n", + " def get_spike_count(self,**run_params):\n", + " vm = self.get_membrane_potential()\n", + " return len(threshold_detection(vm,threshold=-45.0*mV))\n", + " \n", + " model.init_backend = MethodType(init_backend,model)\n", + " model.get_spike_count = MethodType(get_spike_count,model)\n", + " model.get_APs = MethodType(get_APs,model)\n", + " model.get_spike_train = MethodType(get_spike_train,model)\n", + " model.set_attrs = MethodType(set_attrs, model) # Bind to the score.\n", + " model.inject_square_current = MethodType(inject_square_current, model) # Bind to the score.\n", + " model.set_attrs = MethodType(set_attrs, model) # Bind to the score.\n", + " model.get_membrane_potential = MethodType(get_membrane_potential,model)\n", + " model.load_model = MethodType(load_model, model) # Bind to the score.\n", + " model._local_run = MethodType(_local_run,model)\n", + " model.init_backend(model)\n", + " #model.load_model() #= MethodType(load_model, model) # Bind to the score.\n", + "\n", + " return model\n", + "HH_cond_exp = bind_NU_interface(HH_cond_exp) \n", + "#HH_cond_exp" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "electro_tests = []\n", + "obs_frame = {}\n", + "test_frame = {}\n", + "import os\n", + "import pickle\n", + "try: \n", + "\n", + " electro_path = str(os.getcwd())+'all_tests.p'\n", + "\n", + " assert os.path.isfile(electro_path) == True\n", + " with open(electro_path,'rb') as f:\n", + " (obs_frame,test_frame) = pickle.load(f)\n", + "\n", + "except:\n", + " for p in pipe:\n", + " p_tests, p_observations = get_neab.get_neuron_criteria(p)\n", + " obs_frame[p[\"name\"]] = p_observations#, p_tests))\n", + " test_frame[p[\"name\"]] = p_tests#, p_tests))\n", + " electro_path = str(os.getcwd())+'all_tests.p'\n", + " with open(electro_path,'wb') as f:\n", + " pickle.dump((obs_frame,test_frame),f)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": true + }, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'test_frame' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0muse_test\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtest_frame\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"Neocortex pyramidal cell layer 5-6\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0muse_test\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mobservation\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;31m#from neuronunit.tests import RheobaseP\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0mneuronunit\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtests\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfi\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mRheobaseTest\u001b[0m\u001b[0;31m# as discovery\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mNameError\u001b[0m: name 'test_frame' is not defined" + ] + } + ], + "source": [ + "use_test = test_frame[\"Neocortex pyramidal cell layer 5-6\"]\n", + "use_test[0].observation\n", + "#from neuronunit.tests import RheobaseP\n", + "from neuronunit.tests.fi import RheobaseTest# as discovery\n", + "\n", + "rtp = RheobaseTest(use_test[0].observation)\n", + "use_test[0] = rtp\n", + "\n", + "\n", + "HH_cond_exp.attrs = HH_cond_exp.simple_parameters(HH_cond_exp)\n", + "#print(HH_cond_exp.attrs)\n", + "HH_cond_exp.scaled_parameters(HH_cond_exp)\n", + "dir(HH_cond_exp)\n", + "HH_cond_exp.default_initial_values\n", + "HH_cond_exp.attrs\n", + "NGEN = 10\n", + "MU = 10\n", + "from neuronunit.optimization import optimization_management as om\n", + "explore_ranges = {'e_rev_Na' : (40,70), 'e_rev_K': (-90.0,-75.0), 'cm' : (0.25,1.5)}\n", + "npcl, DO = om.run_ga(explore_ranges,NGEN,use_test,free_params=explore_ranges.keys(), NSGA = True, MU = MU,model_type=None)\n", + "\n", + "#hc = " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "#dir(HH_cond_exp)\n", + "#HH_cond_exp.get_parameters()\n", + "#hhcell[0].get_parameters()\n", + "#dir(HH_cond_exp)\n", + "HH_cond_exp.attrs = HH_cond_exp.simple_parameters(HH_cond_exp)\n", + "HH_cond_exp.celltype = HH_cond_exp\n", + "iparams = {}\n", + "iparams['injected_square_current'] = {}\n", + "#iparams['injected_square_current']['amplitude'] = 1.98156805*pq.pA\n", + "iparams['injected_square_current']['amplitude'] = 0.68156805*pq.pA\n", + "\n", + "DELAY = 100.0*pq.ms\n", + "DURATION = 1000.0*pq.ms\n", + "iparams['injected_square_current']['delay'] = DELAY\n", + "iparams['injected_square_current']['duration'] = int(DURATION)\n", + "\n", + "HH_cond_exp.inject_square_current(iparams)\n", + "print(HH_cond_exp.get_spike_count())\n", + "\n", + "print(HH_cond_exp.vm)\n", + "\n", + "import matplotlib.pyplot as plt\n", + "plt.plot(HH_cond_exp.vm.times,HH_cond_exp.vm)\n", + "\n", + "\n", + "plt.show()\n", + "iparams['injected_square_current']['amplitude'] = 0.8598156805*pq.pA\n", + "#iparams['injected_square_current']['amplitude'] = 2000.98156805*pq.pA\n", + "\n", + "DELAY = 100.0*pq.ms\n", + "DURATION = 1000.0*pq.ms\n", + "iparams['injected_square_current']['delay'] = DELAY\n", + "iparams['injected_square_current']['duration'] = int(DURATION)\n", + "\n", + "HH_cond_exp.inject_square_current(iparams)\n", + "print(HH_cond_exp.get_spike_count())\n", + "import matplotlib.pyplot as plt\n", + "plt.plot(HH_cond_exp.vm.times,HH_cond_exp.vm)\n", + "\n", + "plt.show()\n", + "pred = use_test[0].generate_prediction(HH_cond_exp)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "#dir(HH_cond_exp)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "print(pred)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "iparams['injected_square_current']['amplitude'] = pred['value']\n", + "#iparams['injected_square_current']['amplitude'] = 2000.98156805*pq.pA\n", + "\n", + "DELAY = 100.0*pq.ms\n", + "DURATION = 1000.0*pq.ms\n", + "iparams['injected_square_current']['delay'] = DELAY\n", + "iparams['injected_square_current']['duration'] = int(DURATION)\n", + "\n", + "HH_cond_exp.inject_square_current(iparams)\n", + "print(HH_cond_exp.get_spike_count())\n", + "import matplotlib.pyplot as plt\n", + "plt.plot(HH_cond_exp.vm.times,HH_cond_exp.vm)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "\n", + "import pyNN\n", + "from pyNN import neuron\n", + "from pyNN.neuron import EIF_cond_exp_isfa_ista\n", + "#neurons = pyNN.Population(N_CX, pyNN.EIF_cond_exp_isfa_ista, RS_parameters)\n", + "\n", + "cell = neuron.create(EIF_cond_exp_isfa_ista())\n", + "#cell[0].set_parameters(**LTS_parameters)\n", + "cell[0].get_parameters()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "\n", + "\n", + "explore_ranges = {'E_Na' : (40,70), 'E_K': (-90.0,-75.0), 'C_m' : (0.25,1.5), 'g_K':(30,40), 'g_Na':(100,140), 'g_L':(0.1,0.5), 'E_L':(-60.0,-45)}\n", + "\n", + "attrs = { 'g_K' : 36.0, 'g_Na' : 120.0, 'g_L' : 0.3, \\\n", + " 'C_m' : 1.0, 'E_L' : -54.387, 'E_K' : -77.0, 'E_Na' : 50.0, 'vr':-65.0 } \n", + "\n", + " \n", + "from neuronunit.optimization import optimization_management as om\n", + "print(test_frame) \n", + "MU = 12\n", + "NGEN = 25\n", + "cnt = 1\n", + "#hc = { 'g_L' : 0.3, 'E_L' : -54.387,\n", + "hc = {'vr':-65.0 } \n", + "\n", + "#npcl, DO = om.run_g\n", + "npcl, DO = om.run_ga(explore_ranges,NGEN,use_test,free_params=explore_ranges.keys(), hc = hc, NSGA = True, MU = MU,model_type='HH')\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + } + ], + "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.5.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/neuronunit/models/NeuroML2/fragments/Izh2007One-no-input-1.net.nml b/neuronunit/models/NeuroML2/fragments/Izh2007One-no-input-1.net.nml new file mode 100644 index 000000000..2176481e4 --- /dev/null +++ b/neuronunit/models/NeuroML2/fragments/Izh2007One-no-input-1.net.nml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + diff --git a/neuronunit/models/NeuroML2/fragments/Izh2007One-no-input-2.net.nml b/neuronunit/models/NeuroML2/fragments/Izh2007One-no-input-2.net.nml new file mode 100644 index 000000000..241d6d530 --- /dev/null +++ b/neuronunit/models/NeuroML2/fragments/Izh2007One-no-input-2.net.nml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + diff --git a/neuronunit/models/NeuroML2/fragments/LEMS_2007One-no-input-1.xml b/neuronunit/models/NeuroML2/fragments/LEMS_2007One-no-input-1.xml new file mode 100644 index 000000000..c61a65f7d --- /dev/null +++ b/neuronunit/models/NeuroML2/fragments/LEMS_2007One-no-input-1.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/neuronunit/models/NeuroML2/fragments/LEMS_2007One-no-input-2.xml b/neuronunit/models/NeuroML2/fragments/LEMS_2007One-no-input-2.xml new file mode 100644 index 000000000..ce4bd399f --- /dev/null +++ b/neuronunit/models/NeuroML2/fragments/LEMS_2007One-no-input-2.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/neuronunit/models/__init__.py b/neuronunit/models/__init__.py index 45feabc08..b4085335a 100644 --- a/neuronunit/models/__init__.py +++ b/neuronunit/models/__init__.py @@ -1,220 +1,11 @@ """Model classes for NeuronUnit""" -import os -from copy import deepcopy +import warnings +from .static import StaticModel, ExternalModel, RandomVmModel try: - from tempfile import TemporaryDirectory + from .lems import LEMSModel + from .channel import ChannelModel except: - from backports.tempfile import TemporaryDirectory -import inspect -import shutil -import random -import pickle - -from lxml import etree -from neo.core import AnalogSignal - -import sciunit -#from sciunit.utils import dict_hash -import neuronunit.capabilities as cap -from pyneuroml import pynml -from .backends import available_backends - - -class StaticModel(sciunit.Model, - cap.ProducesMembranePotential): - """A model which produces a frozen membrane potential waveform""" - - def __init__(self, vm): - """vm is either a neo.core.AnalogSignal or a path to a - pickled neo.core.AnalogSignal""" - - if isinstance(vm,str): - with open(vm,'r') as f: - vm = pickle.load(f) - if not isinstance(vm,AnalogSignal): - raise TypeError('vm must be a neo.core.AnalogSignal') - - self.vm = vm - - def get_membrane_potential(self,**kwargs): - return self.vm - - -class LEMSModel(sciunit.Model, - cap.Runnable, - ): - """A generic LEMS model""" - - def __init__(self, LEMS_file_path, name=None, - backend=None, attrs=None): - - #for base in cls.__bases__: - # sciunit.Model.__init__() - if name is None: - name = os.path.split(LEMS_file_path)[1].split('.')[0] - self.name = name - #sciunit.Modelsuper(LEMSModel,self).__init__(name=name) - self.attrs = attrs if attrs else {} - self.orig_lems_file_path = os.path.abspath(LEMS_file_path) - assert os.path.isfile(self.orig_lems_file_path),\ - "'%s' is not a file" % self.orig_lems_file_path - # Use original path unless create_lems_file is called - self.lems_file_path = self.orig_lems_file_path - self.run_defaults = pynml.DEFAULTS - self.run_defaults['nogui'] = True - self.run_params = {} - self.last_run_params = None - self.skip_run = False - self.rerun = True # Needs to be rerun since it hasn't been run yet! - self.unpicklable = [] - if backend is None: - backend = 'jNeuroML' - self.set_backend(backend) - - def get_backend(self): - return self._backend - - def set_backend(self, backend): - if isinstance(backend,str): - name = backend - args = [] - kwargs = {} - elif isinstance(backend,(tuple,list)): - name = '' - args = [] - kwargs = {} - for i in range(len(backend)): - if i==0: - name = backend[i] - else: - if isinstance(backend[i],dict): - kwargs.update(backend[i]) - else: - args += backend[i] - else: - raise TypeError("Backend must be string, tuple, or list") - if name in available_backends: - self.backend = name - self._backend = available_backends[name]() - elif name is None: - # The base class should not be called. - raise Exception(("A backend (e.g. 'jNeuroML' or 'NEURON') " - "must be selected")) - else: - raise Exception("Backend %s not found in backends.py" \ - % name) - self._backend.model = self - self._backend.init_backend(*args, **kwargs) - - def get_nml_paths(self, lems_tree=None, absolute=True, original=False): - if not lems_tree: - lems_tree = etree.parse(self.lems_file_path) - nml_paths = [x.attrib['file'] for x in \ - lems_tree.xpath("Include[contains(@file, '.nml')]")] - if absolute: # Turn into absolute paths - lems_file_path = self.orig_lems_file_path if original \ - else self.lems_file_path - nml_paths = [os.path.join(os.path.dirname(lems_file_path),x) \ - for x in nml_paths] - return nml_paths - - def create_lems_file_copy(self, name=None, use=True): - """Creates a temporary, writable copy of the original LEMS file so that - e.g. edits can be made to it programatically before simulation - """ - if name is None: - name = self.name - if not hasattr(self,'temp_dir'): - self.temp_dir = TemporaryDirectory() - lems_copy_path = os.path.join(self.temp_dir.name, '%s.xml' % name) - shutil.copy2(self.orig_lems_file_path,lems_copy_path) - nml_paths = self.get_nml_paths(original=True) - for orig_nml_path in nml_paths: - new_nml_path = os.path.join(self.temp_dir.name, - os.path.basename(orig_nml_path)) - shutil.copy2(orig_nml_path,new_nml_path) - if self.attrs: - self.set_lems_attrs(self.attrs, path=lems_copy_path) - if use: - self.lems_file_path = lems_copy_path - return lems_copy_path - - def set_attrs(self,attrs): - self._backend.set_attrs(**attrs) - - def inject_square_current(self,current): - self._backend.inject_square_current(current) - - def set_lems_attrs(self, attrs, path=None): - if path is None: - path = self.lems_file_path - paths = [path] + self.get_nml_paths() - for p in paths: - tree = etree.parse(p) - for key1,value1 in attrs.items(): - nodes = tree.findall(key1) - for node in nodes: - for key2,value2 in value1.items(): - node.attrib[key2] = value2 - tree.write(p) - - def run(self, rerun=None, **run_params): - if rerun is None: - rerun = self.rerun - self.set_run_params(**run_params) - for key,value in self.run_defaults.items(): - if key not in self.run_params: - self.set_run_params(**{key:value}) - #if (not rerun) and hasattr(self,'last_run_params') and \ - # self.run_params == self.last_run_params: - # print("Same run_params; skipping...") - # return - - self.results = self._backend.local_run() - self.last_run_params = deepcopy(self.run_params) - #self.rerun = False - # Reset run parameters so the next test has to pass its own - # run parameters and not use the same ones - self.run_params = {} - - def set_run_params(self, **params): - self._backend.set_run_params(**params) - - def set_lems_run_params(self, verbose=False): - from lxml import etree - from neuroml import nml - lems_tree = etree.parse(self.lems_file_path) - trees = {self.lems_file_path:lems_tree} - - # Edit LEMS files. - nml_paths = self.get_nml_paths(lems_tree=lems_tree) - trees.update({x:nml.nml.parsexml_(x) for x in nml_paths}) - - # Edit NML files. - for file_path,tree in trees.items(): - for key,value in self.run_params.items(): - if key == 'injected_square_current': - pulse_generators = tree.findall('pulseGenerator') - for pg in pulse_generators: - for attr in ['delay', 'duration', 'amplitude']: - if attr in value: - if verbose: - print('Setting %s to %f' % (attr,value[attr])) - pg.attrib[attr] = '%s' % value[attr] - - tree.write(file_path) - - def inject_square_current(self, current): - self._backend.inject_square_current(current) - - @property - def state(self): - return self._state(keys=['name','url', 'attrs','run_params','backend']) - - def __del__(self): - if hasattr(self,'temp_dir'):# is not type(None): - self.temp_dir.cleanup() # Delete the temporary directory - s = super(LEMSModel,self) - if hasattr(s,'__del__'): - s.__del__() \ No newline at end of file + print("neuroml not installed") +from .reduced import ReducedModel +from . import backends # Required to register backends diff --git a/neuronunit/models/backends/__init__.py b/neuronunit/models/backends/__init__.py index 5c91e7d10..9e60d6e18 100644 --- a/neuronunit/models/backends/__init__.py +++ b/neuronunit/models/backends/__init__.py @@ -1,23 +1,56 @@ +"""Neuronunit-specific model backends.""" + +import contextlib +import io +import importlib import inspect +import pathlib +import re +import warnings +import sciunit.models.backends as su_backends +from sciunit.utils import PLATFORM, PYTHON_MAJOR_VERSION from .base import Backend +available_backends = su_backends.available_backends + +backend_paths = ['static.StaticBackend', + 'geppetto.GeppettoBackend', + 'jNeuroML.jNeuroMLBackend', + #'neuron.NeuronBackend', + 'adexp.JIT_ADEXPBackend', + 'izhikevich.JIT_IZHIBackend'] +def check_backend(partial_path): + full_path = 'jithub.models.backends.%s' % partial_path + class_name = full_path.split('.')[-1] + module_path = '.'.join(full_path.split('.')[:-1]) + try: + backend_stdout = io.StringIO() + with contextlib.redirect_stdout(backend_stdout): + module = importlib.import_module(module_path) + backend = getattr(module, class_name) + except Exception as e: + msg = "Import of %s failed due to:" % partial_path + stdout = backend_stdout.read() + if stdout: + msg += '\n%s' % stdout + msg += '\n%s' % e + print(msg) + #warnings.warn(msg) + return (None, None) + else: + return (backend.name, backend) -try: - from .jNeuroML import jNeuroMLBackend -except: - pass +def register_backends(backend_paths): + provided_backends = {} + for partial_path in backend_paths: + name, backend = check_backend(partial_path) + if name is not None: + provided_backends[name] = backend + su_backends.register_backends(provided_backends) -try: - from .neuron import NEURONBackend -except: - pass -try: - from .pyNN import pyNNBackend -except: - pass +register_backends(backend_paths) -available_backends = {x.replace('Backend',''):cls for x, cls \ - in locals().items() \ - if inspect.isclass(cls) and \ - issubclass(cls, Backend)} +#from .adexp import ADEXPBackend +#from .glif import GLIFBackend +#from .l5pcSciUnit import L5PCBackend diff --git a/neuronunit/models/backends/base.py b/neuronunit/models/backends/base.py index 28b17d9e6..de41485ae 100644 --- a/neuronunit/models/backends/base.py +++ b/neuronunit/models/backends/base.py @@ -11,137 +11,15 @@ import subprocess import neuronunit.capabilities as cap -from quantities import ms, mV, nA +import quantities as pq from pyneuroml import pynml -from quantities import ms, mV from neo.core import AnalogSignal import neuronunit.capabilities.spike_functions as sf import sciunit -from sciunit.utils import dict_hash, import_module_from_path -try: - import neuron - from neuron import h - NEURON_SUPPORT = True -except: - NEURON_SUPPORT = False +from sciunit.models.backends import Backend, BackendException +from sciunit.utils import dict_hash, import_module_from_path, \ + TemporaryDirectory - -class Backend(object): - """Base class for simulator backends that implement simulator-specific - details of modifying, running, and reading results from the simulation - """ - - def init_backend(self, *args, **kwargs): - self.model.attrs = {} - - self.use_memory_cache = kwargs.get('use_memory_cache', True) - if self.use_memory_cache: - self.init_memory_cache() - self.use_disk_cache = kwargs.get('use_disk_cache', False) - if self.use_disk_cache: - self.init_disk_cache() - self.load_model() - self.model.unpicklable += ['_backend'] - - # Name of the backend - backend = None - - #The function (e.g. from pynml) that handles running the simulation - f = None - - def init_cache(self): - self.init_memory_cache() - self.init_disk_cache() - - def init_memory_cache(self): - self.memory_cache = {} - - def init_disk_cache(self): - try: - # Cleanup old disk cache files - path = self.disk_cache_location - os.remove(path) - except: - pass - self.disk_cache_location = os.path.join(tempfile.mkdtemp(),'cache') - - def get_memory_cache(self, key): - """Returns result in memory cache for key 'key' or None if it - is not found""" - self._results = self.memory_cache.get(key) - return self._results - - def get_disk_cache(self, key): - """Returns result in disk cache for key 'key' or None if it - is not found""" - if not getattr(self,'disk_cache_location',False): - self.init_disk_cache() - disk_cache = shelve.open(self.disk_cache_location) - self._results = disk_cache.get(key) - disk_cache.close() - return self._results - - def set_memory_cache(self, results, key=None): - """Stores result in memory cache with key - corresponding to model state""" - key = self.model.hash if key is None else key - self.memory_cache[key] = results - - def set_disk_cache(self, results, key=None): - """Stores result in disk cache with key - corresponding to model state""" - if not getattr(self,'disk_cache_location',False): - self.init_disk_cache() - disk_cache = shelve.open(self.disk_cache_location) - key = self.model.hash if key is None else key - disk_cache[key] = results - disk_cache.close() - - def set_attrs(self, **attrs): - """Set model attributes, e.g. input resistance of a cell""" - #If the key is in the dictionary, it updates the key with the new value. - self.model.attrs.update(attrs) - #pass - - def set_run_params(self, **params): - """Set run-time parameters, e.g. the somatic current to inject""" - self.model.run_params.update(params) - self.check_run_params() - #pass - - def check_run_params(self): - """Check to see if the run parameters are reasonable for this model - class. Raise a sciunit.BadParameterValueError if any of them are not. - """ - pass - - def load_model(self): - """Load the model into memory""" - pass - - def local_run(self): - """Checks for cached results in memory and on disk, then runs the model - if needed""" - key = self.model.hash - if self.use_memory_cache and self.get_memory_cache(key): - return self._results - if self.use_disk_cache and self.get_disk_cache(key): - return self._results - results = self._local_run() - if self.use_memory_cache: - self.set_memory_cache(results, key) - if self.use_disk_cache: - self.set_disk_cache(results, key) - return results - - def _local_run(self): - """Runs the model via the backend""" - raise NotImplementedError("Each backend must implement '_local_run'") - - def save_results(self, path='.'): - with open(path,'wb') as f: - pickle.dump(self.results,f) - - -class BackendException(Exception): - pass +# Test for NEURON support in a separate python process +NEURON_SUPPORT = (os.system("python -c 'import neuron' > /dev/null 2>&1") == 0) +PYNN_SUPPORT = (os.system("python -c 'import pyNN' > /dev/null 2>&1") == 0) \ No newline at end of file diff --git a/neuronunit/models/backends/geppetto.py b/neuronunit/models/backends/geppetto.py new file mode 100644 index 000000000..1f9c82c5a --- /dev/null +++ b/neuronunit/models/backends/geppetto.py @@ -0,0 +1,21 @@ +"""jNeuroML Backend.""" + +from .jNeuroML import jNeuroMLBackend + +class GeppettoBackend(jNeuroMLBackend): + """Use for simulation with the Geppetto backend for SciDash.""" + + backend = 'Geppetto' + + def init_backend(self, *args, **kwargs): + """Initialize the Geppetto backend.""" + super(GeppettoBackend, self).init_backend(*args, **kwargs) + + def _backend_run(self): + """Send the simulation to Geppetto to run. + You have two options here. Either: + (1) Run the simulation and return a dictionay of results, as other backends do. + (2) Implement nothing here and never call it, always writing to the backend's cache instead. + """ + results = None + return results \ No newline at end of file diff --git a/neuronunit/models/backends/glif.py b/neuronunit/models/backends/glif.py new file mode 100644 index 000000000..258b84d18 --- /dev/null +++ b/neuronunit/models/backends/glif.py @@ -0,0 +1,200 @@ +from quantities import mV, ms, s, V +import sciunit +from neo import AnalogSignal +import neuronunit.capabilities as cap +import numpy as np +from neuronunit.models.backends import parse_glif +from neuronunit.models.backends.base import Backend +import quantities as qt +import quantities as pq + +from quantities import mV, ms, s +import pickle +import copy +import re + + +import allensdk.core.json_utilities as json_utilities +from allensdk.model.glif.glif_neuron import GlifNeuron +from allensdk.api.queries.cell_types_api import CellTypesApi +# from neuronunit.models.reduced import ReducedModel + +try: + from allensdk.api.queries.glif_api import GlifApi + from allensdk.core.cell_types_cache import CellTypesCache + import allensdk.core.json_utilities as json_utilities + import sciunit +except: + import os + os.system('pip install allensdk') + from allensdk.api.queries.glif_api import GlifApi + from allensdk.core.cell_types_cache import CellTypesCache + import allensdk.core.json_utilities as json_utilities + + os.system('pip install git+https://github.com/scidash/sciunit@dev') + + + +class GLIFBackend(Backend): + def init_backend(self, attrs = None, cell_name = 'alice', current_src_name = 'hannah', DTC = None): + backend = 'GLIF' + super(GLIFBackend,self).init_backend() + + self.model._backend.use_memory_cache = False + self.current_src_name = current_src_name + self.cell_name = cell_name + self.vM = None + self.allen_id = None + self.attrs = attrs + self.nc = None + + self.temp_attrs = None + + + if self.allen_id == None: + try: + self.nc = pickle.load(open(str('allen_id.p'),'rb')) + except: + self.allen_id = 566302806 + glif_api = GlifApi() + + self.nc = glif_api.get_neuron_configs([self.allen_id])[self.allen_id] + pickle.dump(copy.copy(self.nc),open(str('allen_id.p'),'wb')) + + + else: + + try: + self.nc = pickle.load(open(str('allen_id.p'),'rb')) + except: + glif_api = GlifApi() + self.allen_id = allen_id + self.glif = glif_api.get_neuronal_models_by_id([allen_id])[0] + self.nc = glif_api.get_neuron_configs([self.allen_id])[self.allen_id] + pickle.dump(self.nc,open(str('allen_id.p'),'wb')) + + + self.glif = GlifNeuron.from_dict(self.nc) + + + if type(attrs) is not type(None): + self.set_attrs(**attrs) + self.sim_attrs = attrs + + if type(DTC) is not type(None): + if type(DTC.attrs) is not type(None): + + self.set_attrs(**DTC.attrs) + + + if hasattr(DTC,'current_src_name'): + self._current_src_name = DTC.current_src_name + + if hasattr(DTC,'cell_name'): + self.cell_name = DTC.cell_name + + #print(self.internal_params) + def as_lems_model(self, backend=None): + glif_package = [] + glif_package.append(self.metad) + glif_package.append(self.nc) + glif_package.append(self.get_sweeps) + lems_file_path = parse_glif.generate_lems(glif_package) + return ReducedModel(lems_file_path, backend=backend) + + def get_sweeps(self,specimen_id = None): + if specimen_id == None: + self.sweeps = ctc.get_ephys_sweeps(self.glif[self.allen_id], \ + file_name='%d_ephys_sweeps.json' % self.allen_id) + + def get_sweep(self, n,specimen_id = None): + if specimen_id == None: + self.sweeps = ctc.get_ephys_sweeps(self.glif[self.allen_id], \ + file_name='%d_ephys_sweeps.json' % self.allen_id) + sweep_info = self.sweeps[n] + sweep_number = sweep_info['sweep_number'] + sweep = ds.get_sweep(sweep_number) + return sweep + + def get_stimulus(self, n): + sweep = self.get_sweep(n) + return sweep['stimulus'] + + def apply_stimulus(self, n): + self.stimulus = self.get_stimulus(n) + + def get_spike_train(self): + #vms = self.get_membrane_potential() + #from neuronunit.capabilities.spike_functions import get_spike_train + #import numpy as np + spike_times = self.results['interpolated_spike_times'] + return np.array(spike_times) + + def get_membrane_potential(self): + """Must return a neo.core.AnalogSignal. + And must destroy the hoc vectors that comprise it. + """ + threshold = self.results['threshold'] + interpolated_spike_times = self.results['interpolated_spike_times'] + + interpolated_spike_thresholds = self.results['interpolated_spike_threshold'] + grid_spike_indices = self.results['spike_time_steps'] + grid_spike_times = self.results['grid_spike_times'] + after_spike_currents = self.results['AScurrents'] + + vm = self.results['voltage'] + if len(self.results['interpolated_spike_voltage']) > 0: + isv = self.results['interpolated_spike_voltage'].tolist()[0] + vm = list(map(lambda x: isv if np.isnan(x) else x, vm)) + dt = self.glif.dt + self.vM = AnalogSignal(vm,units = mV,sampling_period = dt * ms) + return vms + + def _local_run(self): + #self.results = np.array(self.glif.run(self.stim)) + results = {} + results['vm'] = self.vM + results['t'] = self.vM.times + results['run_number'] = results.get('run_number',0) + 1 + return results + + return self.results + + + def set_attrs(self, **attrs): + self.model.attrs.update(attrs) + #self.nc.update(attrs) + for k,v in attrs.items(): + self.nc[k] = v + self.glif = GlifNeuron.from_dict(self.nc) + return self.glif + + + def set_stop_time(self, stop_time = 650*pq.ms): + """Sets the simulation duration + stopTimeMs: duration in milliseconds + """ + self.tstop = float(stop_time.rescale(pq.ms)) + + def inject_square_current(self, current): + if 'injected_square_current' in current.keys(): + c = current['injected_square_current'] + else: + c = current + stop = float(c['delay'])+float(c['duration']) + start = float(c['delay']) + duration = float(c['duration']) + amplitude = float(c['amplitude'])/1000.0 + self.glif.dt = 0.001 + dt = self.glif.dt + self.stim = [ 0.0 ] * int(start) + [ amplitude ] * int(duration) + [ 0.0 ] * int(stop) + #self.glif.init_voltage = -0.0065 + self.results = self.glif.run(self.stim) + vm = self.results['voltage'] + if len(self.results['interpolated_spike_voltage']) > 0: + isv = self.results['interpolated_spike_voltage'].tolist()[0] + vm = list(map(lambda x: isv if np.isnan(x) else x, vm)) + + vms = AnalogSignal(vm,units = V,sampling_period = dt * s) + self.vM = vms + return vms diff --git a/neuronunit/models/backends/jNeuroML.py b/neuronunit/models/backends/jNeuroML.py index 34c0eeef3..e4e108a64 100644 --- a/neuronunit/models/backends/jNeuroML.py +++ b/neuronunit/models/backends/jNeuroML.py @@ -1,33 +1,70 @@ -from .base import * +"""jNeuroML Backend.""" + +import os +import io +import tempfile + +from pyneuroml import pynml + +from sciunit.utils import redirect_stdout +from .base import Backend +from elephant.spike_train_generation import threshold_detection + + class jNeuroMLBackend(Backend): - """Used for simulation with jNeuroML, a reference simulator for NeuroML""" + """Use for simulation with jNeuroML, a reference simulator for NeuroML.""" - backend = 'jNeuroML' + name = 'jNeuroML' def init_backend(self, *args, **kwargs): - self.model.create_lems_file_copy() - super(jNeuroMLBackend,self).init_backend(*args, **kwargs) + """Initialize the jNeuroML backend.""" + assert hasattr(self.model, 'set_lems_run_params'), \ + "A model using %s must implement `set_lems_run_params`" % \ + self.backend + self.stdout = io.StringIO() + self.model.create_lems_file_copy() # Create a copy of the LEMS file + super(jNeuroMLBackend, self).init_backend(*args, **kwargs) def set_attrs(self, **attrs): - self.model.attrs.update(attrs) - self.model.set_lems_attrs(attrs) + """Set the model attributes, i.e. model parameters.""" + self.model.set_lems_attrs() - def set_run_params(self, **params): - super(jNeuroMLBackend,self).set_run_params(**params) + def set_run_params(self, **run_params): + """Sey the backend runtime parameters, i.e. simulation parameters.""" self.model.set_lems_run_params() def inject_square_current(self, current): - self.set_run_params(injected_square_current=current) + """Inject a square current into the cell.""" + self.model.run_params['injected_square_current'] = current + self.set_run_params() # Doesn't work yet. + self._backend_run() + self.vm + def set_stop_time(self, t_stop): + """Set the stop time of the simulation.""" + self.model.run_params['t_stop'] = t_stop + self.set_run_params() + + def set_time_step(self, dt): + """Set the time step of the simulation.""" + self.model.run_params['dt'] = dt + self.set_run_params() + def get_spike_count(self): + thresh = threshold_detection(self.vm) + return len(thresh) - def _local_run(self): + def _backend_run(self): + """Run the simulation.""" f = pynml.run_lems_with_jneuroml self.exec_in_dir = tempfile.mkdtemp() - results = f(self.model.lems_file_path, - paths_to_include=[os.path.dirname(self.model.orig_lems_file_path)], - skip_run=self.model.skip_run, - nogui=self.model.run_params['nogui'], - load_saved_data=True, plot=False, - exec_in_dir=self.exec_in_dir, - verbose=self.model.run_params['v']) + lems_path = os.path.dirname(self.model.orig_lems_file_path) + with redirect_stdout(self.stdout): + results = f(self.model.lems_file_path, + paths_to_include=[lems_path], + skip_run=self.model.skip_run, + nogui=self.model.run_params['nogui'], + load_saved_data=True, plot=False, + exec_in_dir=self.exec_in_dir, + verbose=self.model.run_params['v']) + self.vm = results['vm'] return results diff --git a/neuronunit/models/backends/neuron.py b/neuronunit/models/backends/neuron.py deleted file mode 100644 index 69718280b..000000000 --- a/neuronunit/models/backends/neuron.py +++ /dev/null @@ -1,349 +0,0 @@ -from .base import * - -class NEURONBackend(Backend): - """Used for simulation with NEURON, a popular simulator - http://www.neuron.yale.edu/neuron/ - Units used by NEURON are sometimes different to quantities/neo - (note nA versus pA) - http://neurosimlab.org/ramcd/pyhelp/modelspec/programmatic/mechanisms/mech.html#IClamp - NEURON's units: - del -- ms - dur -- ms - amp -- nA - i -- nA - """ - - def init_backend(self, attrs = None, cell_name = None, current_src_name = None, DTC = None): - '''Initialize the NEURON backend for neuronunit. - Optional Key Word Arguments: - Arguments: attrs a dictionary of items used to update NEURON model attributes. - cell_name, and _current_src_name should not attain arbitrary values, rather these variable names - may need to have consistency with an underlying jNEUROML model files: - LEMS_2007One_nrn.py LEMS_2007One.xml - cell_name: A string that represents the cell models name in the NEURON HOC space. - current_src_name: A string that represents the current source models name in the NEURON HOC space. - DTC: An object of type Data Transport Container. The data transport container contains a dictionary of model attributes - When the DTC object is provided, it\'s attribute dictionary can be used to update the NEURONBackends model attribute dictionary. - ''' - if not NEURON_SUPPORT: - raise BackendException("The neuron module was not successfully imported") - - - self.neuron = None - self.model_path = None - self.h = h - super(NEURONBackend,self).init_backend() - self.model.unpicklable += ['h','ns','_backend'] - if cell_name: - self._cell_name = cell_name - if current_src_name: - self._current_src_name = current_src_name - if DTC is not None: - if DTC.attrs is not None: - self.set_attrs(**DTC.attrs) - - backend = 'NEURON' - - def reset_neuron(self, neuronVar): - """Arguments: neuronVar, the neuronmodules path in the current python namespace. - Side effects: refreshes the the HOC module, purging it's variable namespace. - - Sets the NEURON h variable, and resets the NEURON h variable. - The NEURON h variable, may benefit from being reset between simulation runs - as a way of insuring that each simulation is freshly initialized. - the reset_neuron method is used to prevent a situation where a new models - initial conditions are erroneously updated from a stale models final state. - """ - self.h = neuronVar.h - self.neuron = neuronVar - # - # TODO it is desirable to over ride set_run_params - # def set_run_params(self, **params): - # super(NEURONBackend,self).set_run_params(**params) - # self.model.set_lems_run_params() - # self.h.dt = params['dt'] - # self.h.tstop = params['stop_time'] - - - def set_stop_time(self, stop_time = 650*ms): - """Sets the simulation duration - stopTimeMs: duration in milliseconds - """ - self.h.tstop = float(stop_time) - - def set_time_step(self, integrationTimeStep = 1/128.0 * ms): - """Sets the simulation itegration fixed time step - integrationTimeStepMs: time step in milliseconds. - Powers of two preferred. Defaults to 1/128.0 - """ - - dt = integrationTimeStep - #dt.units = ms - self.h.dt = float(dt) - - def set_tolerance(self, tolerance = 0.001): - """Sets the variable time step integration method absolute tolerance. - tolerance: absolute tolerance value - """ - - self.h.cvode.atol(tolerance) - - def set_integration_method(self, method = "fixed"): - """Sets the simulation itegration method - method: either "fixed" or "variable". Defaults to fixed. - cvode is used when "variable" """ - - # This line is compatible with the above cvodes - # statements. - self.h.cvode.active(1 if method == "variable" else 0) - - try: - assert self.cvode.active() - except: - self.cvode = self.h.CVode() - self.cvode.active(1 if method == "variable" else 0) - - def get_membrane_potential(self): - """Must return a neo.core.AnalogSignal. - And must destroy the hoc vectors that comprise it. - """ - - if self.h.cvode.active() == 0: - dt = float(copy.copy(self.h.dt)) - fixed_signal = copy.copy(self.vVector.to_python()) - else: - dt = float(copy.copy(self.fixedTimeStep)) - fixed_signal =copy.copy(self.get_variable_step_analog_signal()) - - self.h.dt = dt - self.fixedTimeStep = float(1.0/dt) - return AnalogSignal(fixed_signal, - units = mV, - sampling_period = dt * ms) - - def get_variable_step_analog_signal(self): - """Converts variable dt array values to fixed - dt array by using linear interpolation""" - - # Fixed dt potential - fPots = [] - fDt = self.fixedTimeStep - # Variable dt potential - vPots = self.vVector.to_python() - # Variable dt times - vTimes = self.tVector.to_python() - duration = vTimes[len(vTimes)-1] - # Fixed and Variable dt times - fTime = vTime = vTimes[0] - # Index of variable dt time array - vIndex = 0 - # Advance the fixed dt position - while fTime <= duration: - - # If v and f times are exact, no interpolation needed - if fTime == vTime: - fPots.append(vPots[vIndex]) - - # Interpolate between the two nearest vdt times - else: - - # Increment vdt time until it surpases the fdt time - while fTime > vTime and vIndex < len(vTimes): - vIndex += 1 - vTime = vTimes[vIndex] - - # Once surpassed, use the new vdt time and t-1 for interpolation - vIndexMinus1 = max(0, vIndex-1) - vTimeMinus1 = vTimes[vIndexMinus1] - - fPot = self.linearInterpolate(vTimeMinus1, vTime, \ - vPots[vIndexMinus1], vPots[vIndex], \ - fTime) - - fPots.append(fPot) - - # Go to the next fdt time step - fTime += fDt - - return fPots - - def linearInterpolate(self, tStart, tEnd, vStart, vEnd, tTarget): - tRange = float(tEnd - tStart) - tFractionAlong = (tTarget - tStart)/tRange - vRange = vEnd - vStart - vTarget = vRange*tFractionAlong + vStart - - return vTarget - - def load(self,tstop=650*ms): - nrn_path = os.path.splitext(self.model.orig_lems_file_path)[0]+'_nrn.py' - nrn = import_module_from_path(nrn_path) - self.reset_neuron(nrn.neuron) - modeldirname = os.path.dirname(self.model.orig_lems_file_path) - self.h.tstop = tstop - self.set_stop_time(self.h.tstop) # previously 500ms add on 150ms of recovery - #self.h.tstop - self.ns = nrn.NeuronSimulation(self.h.tstop, dt=0.0025) - - def load_mechanisms(self): - neuron.load_mechanisms(self.neuron_model_dir) - - def load_model(self, verbose=True): - """Inputs: NEURONBackend instance object - Side Effects: Substantially mutates neuronal model stored in self. - Description: Take a declarative model description, and call JneuroML to convert it - into an python/neuron implementation stored in a pyhoc file. - Then import the pyhoc file thus dragging the neuron variables - into memory/python name space. - Since this only happens once outside of the optimization - loop its a tolerable performance hit. - """ - - #Create a pyhoc file using jneuroml to convert from NeuroML to pyhoc. - #import the contents of the file into the current names space. - - #The code block below does not actually function: - #architecture = platform.machine() - assert os.path.isfile(self.model.orig_lems_file_path) - base_name = os.path.splitext(self.model.orig_lems_file_path)[0] - NEURON_file_path ='{0}_nrn.py'.format(base_name) - self.neuron_model_dir = os.path.dirname(self.model.orig_lems_file_path) - assert os.path.isdir(self.neuron_model_dir) - if not os.path.exists(NEURON_file_path): - pynml.run_lems_with_jneuroml_neuron(self.model.orig_lems_file_path, - skip_run=False, - nogui=True, - load_saved_data=False, - only_generate_scripts=True, - plot=False, - show_plot_already=False, - exec_in_dir = self.neuron_model_dir, - verbose=verbose, - exit_on_fail = True) - # use a different process to call NEURONS compiler nrnivmodl in the - # background if the NEURON_file_path does not yet exist. - subprocess.run(["cd %s; nrnivmodl" % self.neuron_model_dir],shell=True) - self.load_mechanisms() - elif os.path.realpath(os.getcwd()) != os.path.realpath(self.neuron_model_dir): - # Load mechanisms unless they've already been loaded - self.load_mechanisms() - - self.load() - - - # Although the above approach successfuly instantiates a LEMS/neuroml model in pyhoc - # the resulting hoc variables for current source and cell name are idiosyncratic (not generic). - # the non generic approach described above makes it hard to create a generalizable code. - # work around involves predicting the hoc variable names from pyneuroml LEMS file that was used to generate them. - more_attributes = pynml.read_lems_file(self.model.orig_lems_file_path, - include_includes=True, - debug=False) - for i in more_attributes.components: - # This code strips out simulation parameters from the xml tree also such as current source name. - # and cell_name - if str('pulseGenerator') in i.type: - self._current_src_name = i.id - if str('Cell') in i.type: - self._cell_name = i.id - more_attributes = None # explitly perform garbage collection on more_attributes since its not needed anymore. - return self - - @property - def cell_name(self): - return getattr(self,'_cell_name','RS') - - @property - def current_src_name(self): - return getattr(self,'_current_src_name','RS') - - def set_attrs(self, **attrs): - self.model.attrs.update(attrs) - #assert type(self.model.attrs) is not type(None) - #assert len(list(self.model.attrs.values())) > 0 - for h_key,h_value in attrs.items(): - self.h('m_{0}_{1}_pop[0].{2} = {3}'\ - .format(self.cell_name,self.cell_name,h_key,h_value)) - - # Below create a list of NEURON experimental recording rig parameters. - # This is where parameters of the artificial neuron experiment are initiated. - # Code is sent to the python interface to neuron by executing strings: - neuron_sim_rig = [] - neuron_sim_rig.append(' { v_time = new Vector() } ') - neuron_sim_rig.append(' { v_time.record(&t) } ') - neuron_sim_rig.append(' { v_v_of0 = new Vector() } ') - neuron_sim_rig.append(' { v_v_of0.record(&RS_pop[0].v(0.5)) } ') - neuron_sim_rig.append(' { v_u_of0 = new Vector() } ') - neuron_sim_rig.append(' { v_u_of0.record(&m_RS_RS_pop[0].u) } ') - - for string in neuron_sim_rig: - # execute hoc code strings in the python interface to neuron. - self.h(string) - - # These two variables have been aliased in the code below: - self.tVector = self.h.v_time - self.vVector = self.h.v_v_of0 - return self - - def inject_square_current(self, current, section = None): - """Inputs: current : a dictionary with exactly three items, whose keys are: 'amplitude', 'delay', 'duration' - Example: current = {'amplitude':float*pq.pA, 'delay':float*pq.ms, 'duration':float*pq.ms}} - where \'pq\' is a physical unit representation, implemented by casting float values to the quanitities \'type\'. - Description: A parameterized means of applying current injection into defined - Currently only single section neuronal models are supported, the neurite section is understood to be simply the soma. - - Implementation: - 1. purge the HOC space, by calling reset_neuron() - 2. Redefine the neuronal model in the HOC namespace, which was recently cleared out. - 3. Strip away quantities representation of physical units. - 4. Translate the dictionary of current injection parameters into executable HOC code. - - - """ - self.h = None - self.neuron = None - - #import neuron - nrn_path = os.path.splitext(self.model.orig_lems_file_path)[0]+'_nrn.py' - nrn = import_module_from_path(nrn_path) - import copy - - ## - # init_backend is the most reliable way to purge existing NEURON simulations. - # however we don't want to purge the model attributes, we only want to purge - # the NEURON model code. - # store the model attributes, in a temp buffer such that they persist throughout the model reinitialization. - ## - temp_attrs = copy.copy(self.model.attrs) - self.init_backend() - self.set_attrs(**temp_attrs) - - c = copy.copy(current) - if 'injected_square_current' in c.keys(): - c = current['injected_square_current'] - - c['delay'] = re.sub('\ ms$', '', str(c['delay'])) # take delay - c['duration'] = re.sub('\ ms$', '', str(c['duration'])) - c['amplitude'] = re.sub('\ pA$', '', str(c['amplitude'])) - # NEURONs default unit multiplier for current injection values is nano amps. - # to make sure that pico amps are not erroneously interpreted as a larger nano amp. - # current injection value, the value is divided by 1000. - amps=float(c['amplitude'])/1000.0 #This is the right scale. - prefix = 'explicitInput_%s%s_pop0.' % (self.current_src_name,self.cell_name) - define_current = [] - define_current.append(prefix+'amplitude=%s'%amps) - define_current.append(prefix+'duration=%s'%c['duration']) - define_current.append(prefix+'delay=%s'%c['delay']) - for string in define_current: - # execute hoc code strings in the python interface to neuron. - self.h(string) - - def _local_run(self): - self.h('run()') - results={} - # Prepare NEURON vectors for quantities/sciunit - # By rescaling voltage to milli volts, and time to milli seconds. - results['vm'] = [float(x/1000.0) for x in copy.copy(self.neuron.h.v_v_of0.to_python())] - results['t'] = [float(x/1000.0) for x in copy.copy(self.neuron.h.v_time.to_python())] - results['run_number'] = results.get('run_number',0) + 1 - - return results diff --git a/neuronunit/models/backends/parse_glif.py b/neuronunit/models/backends/parse_glif.py new file mode 100644 index 000000000..4ddfbd315 --- /dev/null +++ b/neuronunit/models/backends/parse_glif.py @@ -0,0 +1,185 @@ +usage=''' +Provenance: This file is originally from +https://github.com/vrhaynes/AllenInstituteNeuroML +https://github.com/OpenSourceBrain/AllenInstituteNeuroML +It is authored by pgleeson@github.com, vrhaynes@github.com and russelljjarvis@github.com + +This file can be used to generate LEMS components for each of a number of GLIF models + +Usage: + + python parse_glif.py -all + +''' + +import sys +import os +import json + +from pyneuroml import pynml + +def generate_lems(glif_package, curr_pA=None, show_plot=False): + if curr_pA == None: + curr_pA = 10 + glif_dir = os.getcwd() + + model_metadata,neuron_config,ephys_sweeps = glif_package + + template_cell = ''' + + <%s %s/> + + + ''' + + type = '???' + print(model_metadata['name']) + if '(LIF)' in model_metadata['name']: + type = 'glifCell' + if '(LIF-ASC)' in model_metadata['name']: + type = 'glifAscCell' + if '(LIF-R)' in model_metadata['name']: + type = 'glifRCell' + if '(LIF-R-ASC)' in model_metadata['name']: + type = 'glifRAscCell' + if '(LIF-R-ASC-A)' in model_metadata['name']: + type = 'glifRAscATCell' + + cell_id = 'GLIF_%s'%glif_dir + + #model_metadata['name'] + + attributes = "" + + attributes +=' id="%s"'%cell_id + attributes +='\n C="%s F"'%neuron_config["C"] + attributes +='\n leakReversal="%s V"'%neuron_config["El"] + attributes +='\n reset="%s V"'%neuron_config["El"] + attributes +='\n thresh="%s V"'%( float(neuron_config["th_inf"]) * float(neuron_config["coeffs"]["th_inf"])) + attributes +='\n leakConductance="%s S"'%(1/float(neuron_config["R_input"])) + + if 'Asc' in type: + attributes +='\n tau1="%s s"'%neuron_config["asc_tau_array"][0] + attributes +='\n tau2="%s s"'%neuron_config["asc_tau_array"][1] + attributes +='\n amp1="%s A"'% ( float(neuron_config["asc_amp_array"][0]) * float(neuron_config["coeffs"]["asc_amp_array"][0]) ) + attributes +='\n amp2="%s A"'% ( float(neuron_config["asc_amp_array"][1]) * float(neuron_config["coeffs"]["asc_amp_array"][1]) ) + + if 'glifR' in type: + attributes +='\n bs="%s per_s"'%neuron_config["threshold_dynamics_method"]["params"]["b_spike"] + attributes +='\n deltaThresh="%s V"'%neuron_config["threshold_dynamics_method"]["params"]["a_spike"] + attributes +='\n fv="%s"'%neuron_config["voltage_reset_method"]["params"]["a"] + attributes +='\n deltaV="%s V"'%neuron_config["voltage_reset_method"]["params"]["b"] + + if 'glifRAscATCell' in type: + attributes +='\n bv="%s per_s"'%neuron_config["threshold_dynamics_method"]["params"]["b_voltage"] + attributes +='\n a="%s per_s"'%neuron_config["threshold_dynamics_method"]["params"]["a_voltage"] + + + file_contents = template_cell%(type, attributes) + + print(file_contents) + + #cell_file_name = '%s.xml'%(cell_id) + cell_file_name = '{0}{1}.xml'.format(os.getcwd(),str(model_metadata['name'])) + cell_file = open(cell_file_name,'w') + cell_file.write(file_contents) + cell_file.close() + return cell_file_name + + ''' + import opencortex.build as oc + + + nml_doc, network = oc.generate_network("Test_%s"%glif_dir) + + pop = oc.add_single_cell_population(network, + 'pop_%s'%glif_dir, + cell_id) + + + pg = oc.add_pulse_generator(nml_doc, + id="pg0", + delay="100ms", + duration="1000ms", + amplitude="%s pA"%curr_pA) + + + oc.add_inputs_to_population(network, + "Stim0", + pop, + pg.id, + all_cells=True) + + + + nml_file_name = '%s.net.nml'%network.id + oc.save_network(nml_doc, nml_file_name, validate=True) + + + thresh = 'thresh' + if 'glifR' in type: + thresh = 'threshTotal' + + lems_file_name = oc.generate_lems_simulation(nml_doc, + network, + nml_file_name, + include_extra_lems_files = [cell_file_name,'../GLIFs.xml'], + duration = 1200, + dt = 0.01, + gen_saves_for_quantities = {'thresh.dat':['pop_%s/0/GLIF_%s/%s'%(glif_dir,glif_dir,thresh)]}, + gen_plots_for_quantities = {'Threshold':['pop_%s/0/GLIF_%s/%s'%(glif_dir,glif_dir,thresh)]}) + + results = pynml.run_lems_with_jneuroml(lems_file_name, + nogui=True, + load_saved_data=True) + + print("Ran simulation; results reloaded for: %s"%results.keys()) + + info = "Model %s; %spA stimulation"%(glif_dir,curr_pA) + + times = [results['t']] + vs = [results['pop_%s/0/GLIF_%s/v'%(glif_dir,glif_dir)]] + labels = ['LEMS - jNeuroML'] + + original_model_v = 'original.v.dat' + if os.path.isfile(original_model_v): + data, indices = pynml.reload_standard_dat_file(original_model_v) + times.append(data['t']) + vs.append(data[0]) + labels.append('Allen SDK') + + + pynml.generate_plot(times, + vs, + "Membrane potential; %s"%info, + xaxis = "Time (s)", + yaxis = "Voltage (V)", + labels = labels, + grid = True, + show_plot_already=False, + save_figure_to='Comparison_%ipA.png'%(curr_pA)) + + times = [results['t']] + vs = [results['pop_%s/0/GLIF_%s/%s'%(glif_dir,glif_dir,thresh)]] + labels = ['LEMS - jNeuroML'] + + original_model_th = 'original.thresh.dat' + if os.path.isfile(original_model_th): + data, indeces = pynml.reload_standard_dat_file(original_model_th) + times.append(data['t']) + vs.append(data[0]) + labels.append('Allen SDK') + + + pynml.generate_plot(times, + vs, + "Threshold; %s"%info, + xaxis = "Time (s)", + yaxis = "Voltage (V)", + labels = labels, + grid = True, + show_plot_already=show_plot, + save_figure_to='Comparison_Threshold_%ipA.png'%(curr_pA)) + + readme = + ''' diff --git a/neuronunit/models/backends/static.py b/neuronunit/models/backends/static.py new file mode 100644 index 000000000..59d8cf3ec --- /dev/null +++ b/neuronunit/models/backends/static.py @@ -0,0 +1,11 @@ +"""Static Backend.""" + +from .base import Backend + + +class StaticBackend(Backend): + def _backend_run(self): + pass + + def set_stop_time(self, t_stop): + pass diff --git a/neuronunit/models/channel.py b/neuronunit/models/channel.py index 462701bde..758b12e88 100644 --- a/neuronunit/models/channel.py +++ b/neuronunit/models/channel.py @@ -1,67 +1,89 @@ """NeuronUnit model class for ion channels models""" import os +import re -import sciunit import neuronunit.capabilities.channel as cap +from .lems import LEMSModel from pyneuroml.analysis import NML2ChannelAnalysis as ca import quantities as pq -class ChannelModel(sciunit.Model, cap.NML2_Channel_Runnable, - cap.ProducesIVCurve): + +class ChannelModel(LEMSModel, cap.NML2ChannelAnalysis): """A model for ion channels""" - - def __init__(self, channel_file_path, channel_index=0, name=None): + + def __init__(self, channel_file_path_or_url, channel_index=0, name=None, backend='jNeuroML'): """ channel_file_path: Path to NML file. - channel_index: Order of channel in NML file (usually 0 since most files contain one channel). + channel_index: Order of channel in NML file + (usually 0 since most files contain one channel). name: Optional model name. """ - self.nml_file_path = channel_file_path - channels = ca.get_channels_from_channel_file(self.nml_file_path) + if name is None: + base, file_name = os.path.split(channel_file_path_or_url) + name = file_name.split('.')[0] + super(ChannelModel, self).__init__(channel_file_path_or_url, name=name, + backend=backend) + channels = ca.get_channels_from_channel_file(self.orig_lems_file_path) self.channel = channels[channel_index] self.a = None - self.run_defaults = ca.DEFAULTS.copy() # Temperature, clamp parameters, etc. - self.run_defaults.update({'nogui': True}) - self.run_defaults = ca.build_namespace() + # Temperature, clamp parameters, etc. + self.default_params = ca.DEFAULTS.copy() + self.default_params.update({'nogui': True}) - if name is None: - name = os.path.split()[1].split('.')[0] - super(ChannelModel,self).__init__(name=name) - - def NML2_run(self, rerun=False, a=None, verbose=None, **run_params): - if not run_params: - run_params = self.run_defaults - a = ca.build_namespace(a=a,**run_params) # Convert keyword args to a namespace. + """ + DEPRECATED + def NML2_run(self, rerun=False, a=None, verbose=None, **params): + self.params = self.default_params.copy() + self.params.update(params) + # Convert keyword args to a namespace. + a = ca.build_namespace(a=a, **self.params) if verbose is None: verbose = a.v - if self.a is None or a.__dict__ != self.a.__dict__ or rerun: # Only rerun if params have changed. + # Only rerun if params have changed. + if self.a is None or a.__dict__ != self.a.__dict__ or rerun: self.a = a - self.lems_file_path = ca.make_lems_file(self.channel,self.a) # Create a lems file. - self.results = ca.run_lems_file(self.lems_file_path,verbose) # Writes data to disk. + # Force the Channel Analysis module to write files to the + # temporary directory + ca.OUTPUT_DIR = self.temp_dir.name + # Create a lems file. + self.lems_file_path = ca.make_lems_file(self.channel, self.a) + # Writes data to disk. + self.results = ca.run_lems_file(self.lems_file_path, verbose) + """ + - def produce_iv_curve(self, **run_params): - run_params['ivCurve'] = True - self.NML2_run(**run_params) - iv_data = ca.compute_iv_curve(self.channel, self.a, self.results) + + def ca_make_lems_file(self, **params): + # Set params in the SciUnit model instance + self.params = params + # ChannelAnalysis only accepts camelCase parameter names + # This converts snake_case to camelCase + params = {snake_to_camel(key): value for key, value in params.items()} + # Build a namespace for use by ChannelAnalysis + self.ca_namespace = ca.build_namespace(**params) + # Make the new LEMS file + self.lems_file_path = ca.make_lems_file(self.channel, + self.ca_namespace) + + def ca_run_lems_file(self, verbose=True): + self.run(verbose=verbose) + return self.results + + def ca_compute_iv_curve(self, results): + iv_data = ca.compute_iv_curve(self.channel, self.ca_namespace, results) self.iv_data = {} for kind in ['i_peak', 'i_steady']: self.iv_data[kind] = {} - for v,i in iv_data[kind].items(): + for v, i in iv_data[kind].items(): v = float((v * pq.V).rescale(pq.mV)) self.iv_data[kind][v] = (i * pq.A).rescale(pq.pA) self.iv_data['hold_v'] = (iv_data['hold_v'] * pq.V).rescale(pq.mV) return self.iv_data - - def produce_iv_curve_ss(self, **run_params): - self.produce_iv_curve(**run_params) - return {'v':self.iv_data['hold_v'], - 'i':self.iv_data['i_steady']} - - def produce_iv_curve_peak(self, **run_params): - self.produce_iv_curve(**run_params) - return {'v':self.iv_data['hold_v'], - 'i':self.iv_data['i_peak']} - + def plot_iv_curve(self, v, i, *plt_args, **plt_kwargs): - ca.plot_iv_curve(self.a, v, i, *plt_args, **plt_kwargs) \ No newline at end of file + ca.plot_iv_curve(self.a, v, i, *plt_args, **plt_kwargs) + + +def snake_to_camel(string): + return re.sub(r'_([a-z])', lambda x: x.group(1).upper(), string) diff --git a/neuronunit/models/interfaces/glif.py b/neuronunit/models/interfaces/glif.py deleted file mode 100644 index 709472d0e..000000000 --- a/neuronunit/models/interfaces/glif.py +++ /dev/null @@ -1,154 +0,0 @@ -from .base import * - -class glifBackend(Backend): - - backend = 'glif' - try: - from allensdk.api.queries.glif_api import GlifApi - from allensdk.core.cell_types_cache import CellTypesCache - import allensdk.core.json_utilities as json_utilities - except: - import os - os.system('pip install allensdk') - from allensdk.api.queries.glif_api import GlifApi - from allensdk.core.cell_types_cache import CellTypesCache - import allensdk.core.json_utilities as json_utilities - - neuronal_model_id = 566302806 - # download model metadata - glif_api = GlifApi() - nm = glif_api.get_neuronal_models_by_id([neuronal_model_id])[0] - # download the model configuration file - nc = glif_api.get_neuron_configs([neuronal_model_id])[neuronal_model_id] - neuron_config = glif_api.get_neuron_configs([neuronal_model_id]) - json_utilities.write('neuron_config.json', neuron_config) - - # download information about the cell - ctc = CellTypesCache() - ctc.get_ephys_data(nm['specimen_id'], file_name='stimulus.nwb') - ctc.get_ephys_sweeps(nm['specimen_id'], file_name='ephys_sweeps.json') - import allensdk.core.json_utilities as json_utilities - from allensdk.model.glif.glif_neuron import GlifNeuron - - # initialize the neuron - neuron_config = json_utilities.read('neuron_config.json') - neuron_config = neuron_config['566302806'] - - neuron = GlifNeuron.from_dict(neuron_config) - - - def init_backend(self, attrs=None, simulator='neuron', DTC = None): - from pyNN import neuron - self.neuron = neuron - from pyNN.neuron import simulator as sim - from pyNN.neuron import setup as setup - from pyNN.neuron import Izhikevich - from pyNN.neuron import Population - from pyNN.neuron import DCSource - self.Izhikevich = Izhikevich - self.Population = Population - self.DCSource = DCSource - self.setup = setup - self.model_path = None - self.related_data = {} - self.lookup = {} - self.attrs = {} - super(pyNNBackend,self).init_backend()#*args, **kwargs) - if DTC is not None: - - self.set_attrs(**DTC.attrs) - - backend = 'pyNN' - - - def get_membrane_potential(self): - """Must return a neo.core.AnalogSignal. - And must destroy the hoc vectors that comprise it. - """ - dt = float(copy.copy(self.neuron.dt)) - data = self.population.get_data().segments[0] - return data.filter(name="v")[0] - - def _local_run(self): - ''' - pyNN lazy array demands a minimum population size of 3. Why is that. - ''' - import numpy as np - results={} - #self.population.record('v') - #self.population.record('spikes') - # For ome reason you need to record from all three neurons in a population - # In order to get the membrane potential from only the stimulated neuron. - - self.population[0:2].record(('v', 'spikes','u')) - ''' - self.Iz.record('v') - self.Iz.record('spikes') - # For ome reason you need to record from all three neurons in a population - # In order to get the membrane potential from only the stimulated neuron. - - self.Iz.record(('v', 'spikes','u')) - ''' - #self.neuron.run(650.0) - DURATION = 1000.0 - self.neuron.run(DURATION) - - data = self.population.get_data().segments[0] - vm = data.filter(name="v")[0]#/10.0 - results['vm'] = vm - #print(vm) - sample_freq = DURATION/len(vm) - results['t'] = np.arange(0,len(vm),DURATION/len(vm)) - results['run_number'] = results.get('run_number',0) + 1 - return results - - - def load_model(self): - self.Iz = None - self.population = None - self.setup(timestep=0.01, min_delay=1.0) - import pyNN - #i_offset=[0.014, 0.0, 0.0] - pop = self.neuron.Population(3, pyNN.neuron.Izhikevich(a=0.02, b=0.2, c=-65, d=6, i_offset=[0.014, -65.0, 0.0]))#,v=-65)) - self.population = pop - - - - def set_attrs(self, **attrs): - #attrs = copy.copy(self.model.attrs) - self.init_backend() - #self.set_attrs(**attrs) - self.model.attrs.update(attrs) - assert type(self.model.attrs) is not type(None) - attrs['i_offset']=None - attrs_ = {x:attrs[x] for x in ['a','b','c','d','i_offset']} - attrs_['i_offset']=0.014#[0.014,-attrs_['v0'],0.0] - #self.population[0].initialize() - self.population[0].set_parameters(**attrs_) - - print(self.population[0].get_parameters()) - self.neuron.h.psection() - return self - - def inject_square_current(self, current): - import copy - attrs = copy.copy(self.model.attrs) - self.init_backend() - self.set_attrs(**attrs) - c = copy.copy(current) - if 'injected_square_current' in c.keys(): - c = current['injected_square_current'] - - c['delay'] = re.sub('\ ms$', '', str(c['delay'])) # take delay - c['duration'] = re.sub('\ ms$', '', str(c['duration'])) - c['amplitude'] = re.sub('\ pA$', '', str(c['amplitude'])) - stop = float(c['delay'])+float(c['duration']) - start = float(c['delay']) - amplitude = float(c['amplitude'])/1000.0 - #print('amplitude',amplitude) - electrode = self.neuron.DCSource(start=start, stop=stop, amplitude=amplitude) - print(self.population[0]) - print(type(self.population[0])) - print(self.population[0].get_parameters()) - - electrode.inject_into(self.population[0:1]) diff --git a/neuronunit/models/interfaces/pyNN.py b/neuronunit/models/interfaces/pyNN.py deleted file mode 100644 index 7b414927c..000000000 --- a/neuronunit/models/interfaces/pyNN.py +++ /dev/null @@ -1,122 +0,0 @@ -from .base import * -import pyNN - -class pyNNBackend(Backend): - - backend = 'pyNN' - - def init_backend(self, attrs=None, simulator='neuron', DTC = None): - from pyNN import neuron - self.neuron = neuron - from pyNN.neuron import simulator as sim - from pyNN.neuron import setup as setup - from pyNN.neuron import Izhikevich - from pyNN.neuron import Population - from pyNN.neuron import DCSource - self.Izhikevich = Izhikevich - self.Population = Population - self.DCSource = DCSource - self.setup = setup - self.model_path = None - self.related_data = {} - self.lookup = {} - self.attrs = {} - super(pyNNBackend,self).init_backend()#*args, **kwargs) - if DTC is not None: - - self.set_attrs(**DTC.attrs) - - - - - def get_membrane_potential(self): - """Must return a neo.core.AnalogSignal. - And must destroy the hoc vectors that comprise it. - """ - dt = float(copy.copy(self.neuron.dt)) - data = self.population.get_data().segments[0] - return data.filter(name="v")[0] - - def _local_run(self): - ''' - pyNN lazy array demands a minimum population size of 3. Why is that. - ''' - import numpy as np - results={} - #self.population.record('v') - #self.population.record('spikes') - # For ome reason you need to record from all three neurons in a population - # In order to get the membrane potential from only the stimulated neuron. - - self.population[0:2].record(('v', 'spikes','u')) - ''' - self.Iz.record('v') - self.Iz.record('spikes') - # For ome reason you need to record from all three neurons in a population - # In order to get the membrane potential from only the stimulated neuron. - - self.Iz.record(('v', 'spikes','u')) - ''' - #self.neuron.run(650.0) - DURATION = 1000.0 - self.neuron.run(DURATION) - - data = self.population.get_data().segments[0] - vm = data.filter(name="v")[0]#/10.0 - results['vm'] = vm - #print(vm) - sample_freq = DURATION/len(vm) - results['t'] = vm.times #np.arange(0,len(vm),DURATION/len(vm)) - results['run_number'] = results.get('run_number',0) + 1 - return results - - - def load_model(self): - self.Iz = None - self.population = None - self.setup(timestep=0.01, min_delay=1.0) - import pyNN - #i_offset=[0.014, 0.0, 0.0] - pop = self.neuron.Population(3, pyNN.neuron.Izhikevich(a=0.02, b=0.2, c=-65, d=6, i_offset=[0.014, -65.0, 0.0]))#,v=-65)) - self.population = pop - - - - def set_attrs(self, **attrs): - #attrs = copy.copy(self.model.attrs) - self.init_backend() - #self.set_attrs(**attrs) - self.model.attrs.update(attrs) - assert type(self.model.attrs) is not type(None) - attrs['i_offset']=None - attrs_ = {x:attrs[x] for x in ['a','b','c','d','i_offset']} - attrs_['i_offset']=0.014#[0.014,-attrs_['v0'],0.0] - #self.population[0].initialize() - self.population[0].set_parameters(**attrs_) - - print(self.population[0].get_parameters()) - self.neuron.h.psection() - return self - - def inject_square_current(self, current): - import copy - attrs = copy.copy(self.model.attrs) - self.init_backend() - self.set_attrs(**attrs) - c = copy.copy(current) - if 'injected_square_current' in c.keys(): - c = current['injected_square_current'] - - c['delay'] = re.sub('\ ms$', '', str(c['delay'])) # take delay - c['duration'] = re.sub('\ ms$', '', str(c['duration'])) - c['amplitude'] = re.sub('\ pA$', '', str(c['amplitude'])) - stop = float(c['delay'])+float(c['duration']) - start = float(c['delay']) - amplitude = float(c['amplitude'])/1000.0 - #print('amplitude',amplitude) - electrode = self.neuron.DCSource(start=start, stop=stop, amplitude=amplitude) - print(self.population[0]) - print(type(self.population[0])) - print(self.population[0].get_parameters()) - - electrode.inject_into(self.population[0:1]) diff --git a/neuronunit/models/lems.py b/neuronunit/models/lems.py new file mode 100644 index 000000000..636d1e143 --- /dev/null +++ b/neuronunit/models/lems.py @@ -0,0 +1,223 @@ +"""Model classes for NeuronUnit.""" + +import os +import shutil +from urllib.parse import urljoin + +import requests +import validators +import quantities as pq +from lxml import etree +from neuroml import nml + +import neuronunit.capabilities as cap +from pyneuroml import pynml +from sciunit.utils import TemporaryDirectory +from sciunit.models.runnable import RunnableModel + + +class LEMSModel(RunnableModel): + """A generic LEMS model.""" + + extra_capability_checks = { + cap.ReceivesSquareCurrent: 'has_pulse_generator' + } + + def __init__(self, LEMS_file_path_or_url, name=None, + backend=None, attrs=None): + """Instantiate a LEMS model.""" + # If a URL is provided, download and get the path + LEMS_file_path = self.url_to_path(LEMS_file_path_or_url) + + if name is None: + name = os.path.split(LEMS_file_path)[1].split('.')[0] + self.orig_lems_file_path = os.path.abspath(LEMS_file_path) + assert os.path.isfile(self.orig_lems_file_path),\ + "'%s' is not a file" % self.orig_lems_file_path + # Use original path unless create_lems_file is called + self.lems_file_path = self.orig_lems_file_path + + if self.from_url: + nml_paths = self.get_nml_paths(original=True, absolute=False) + for nml_path in nml_paths: + nml_url = urljoin(self.from_url, nml_path) + self.url_to_path(nml_url) + + if backend is None: + backend = 'jNeuroML' + super(LEMSModel, self).__init__(name, backend=backend, attrs=attrs) + self.set_default_run_params(**pynml.DEFAULTS) + self.set_default_run_params(nogui=True) + self.use_default_run_params() + + from_url = None + + def url_to_path(self, possible_url, base=None): + """Check for a URL and download the contents. + + If it is not a URL, just consider it a local path to the contents. + """ + if validators.url(possible_url): + if base is None: + base = os.getcwd() # Location to which to download model files + file_name = os.path.split(possible_url)[1] + download_path = os.path.join(base, file_name) + try: + r = requests.get(possible_url, allow_redirects=True) + if r.status_code != 200: + print("URL %s gave a response code %d" % (possible_url, r.status_code)) + with open(download_path, 'wb') as f: + f.write(r.content) + except requests.ConnectionError: + print("Could not connect to server at %s" % possible_url) + + self.from_url = possible_url + else: + download_path = possible_url + self.from_url = False + return download_path + + def get_nml_paths(self, lems_tree=None, absolute=True, original=False): + """Get all NeuroML file paths associated with the model.""" + if not lems_tree: + lems_tree = etree.parse(self.lems_file_path) + nml_paths = [] + for atrb in ['file', 'href']: + for tag in ['Include', 'include']: + match = "*[contains(@%s, '.nml')][name() = '%s']" % (atrb, tag) + elements = lems_tree.xpath(match) + nml_paths += [x.attrib[atrb] for x in elements] + if absolute: # Turn into absolute paths + lems_file_path = self.orig_lems_file_path if original \ + else self.lems_file_path + nml_paths = [os.path.join(os.path.dirname(lems_file_path), x) + for x in nml_paths] + return nml_paths + + def create_lems_file_copy(self, name=None, use=True): + """Create a temporary, writable copy of the original LEMS file. + + Used so that e.g. edits can be made to it programatically before + simulation. + """ + if name is None: + name = self.name + lems_copy_path = os.path.join(self.temp_dir.name, + '%s.xml' % name) + shutil.copy2(self.orig_lems_file_path, lems_copy_path) + nml_paths = self.get_nml_paths(original=True) + for orig_nml_path in nml_paths: + new_nml_path = os.path.join(self.temp_dir.name, + os.path.basename(orig_nml_path)) + shutil.copy2(orig_nml_path, new_nml_path) + if self.attrs: + self.set_lems_attrs(path=lems_copy_path) + if use: + self.lems_file_path = lems_copy_path + return lems_copy_path + + def get_parsed_trees(self): + """Get a dictionary of parsed XML trees for each model file.""" + lems_tree = etree.parse(self.lems_file_path) + trees = {self.lems_file_path: lems_tree} + nml_paths = self.get_nml_paths(lems_tree=lems_tree) + trees.update({x: nml.nml.parsexml_(x) for x in nml_paths}) + for path, tree in trees.items(): + for elem in tree.getiterator(): + try: + # Set the tag name to the local name (i.e. without the namespace) + elem.tag = etree.QName(elem).localname + except: + # Probably a comment or someting else that has no QName + pass + # Remove unused namespace declarations + etree.cleanup_namespaces(tree) + return trees + + def set_lems_attrs(self, path=None): + """Set attribite equivalents in the LEMS file and write it to disk.""" + if path is None: + path = self.lems_file_path + paths = [path] + self.get_nml_paths() + for p in paths: + tree = etree.parse(p) + for key1, value1 in self.attrs.items(): + nodes = tree.findall(key1) + for node in nodes: + for key2, value2 in value1.items(): + node.attrib[key2] = value2 + tree.write(p) + + def set_lems_run_params(self, verbose=False): + """Set run_param equivalents in the LEMS file and write it to disk.""" + trees = self.get_parsed_trees() + + # NeuronUnit->LEMS attribute mapping + mapping = {'t_stop': 'length', 'dt': 'step'} + # Edit NML files. + for file_path, tree in trees.items(): + for key, value in self.run_params.items(): + if key in ['t_stop', 'dt']: + simulations = tree.findall('Simulation') + for sim in simulations: + value_in_ms = float(value.rescale(pq.ms)) + sim.attrib[mapping[key]] = '%fms' % value_in_ms + elif key == 'injected_square_current': + pulse_generators = tree.findall('pulseGenerator') + for pg in pulse_generators: + for attr in ['delay', 'duration', 'amplitude']: + if attr in value: + if verbose: + print('Setting %s to %f' % + (attr, value[attr])) + pg.attrib[attr] = '%s' % value[attr] + + tree.write(file_path) + + def has_pulse_generator(self, tree=None): + """Return True if this model instance contains a pulse generator. + + It must be a NeuroML implementation of a pulse generator attached to an + explicit input. + """ + if tree is None: + trees = self.get_parsed_trees() + return any([self.has_pulse_generator(tree=tree) + for path, tree in trees.items()]) + else: + try: + pulse_generators = tree.findall('pulseGenerator') + pg_ids = [pg.attrib['id'] for pg in pulse_generators] + all_inputs = [] + explicit_inputs = tree.findall('.//explicitInput') + all_inputs += [ei.attrib['input'] for ei in explicit_inputs] + inputLists = tree.findall('.//inputList') + all_inputs += [il.attrib['component'] for il in inputLists] + if len(set(pg_ids).intersection(all_inputs)): + return True + except Exception as e: + raise e + return False + + @property + def temp_dir(self): + if not hasattr(self, '_temp_dir'): + self._temp_dir = TemporaryDirectory() + return self._temp_dir + + def get_state_variables(self): + """ + Parses LEMS xml file and gets simulation's state variables from + OutputFile element. + + Returns: + Dict of type "string: string" with the format "id: quantity" + + """ + lems_tree = etree.parse(self.lems_file_path) + state_variables = {'t': 't'} + for output_column in lems_tree.iter('OutputColumn'): + id = output_column.attrib['id'] + quantity = output_column.attrib['quantity'] + state_variables[id] = quantity + return state_variables diff --git a/neuronunit/models/morphology.py b/neuronunit/models/morphology.py new file mode 100755 index 000000000..5f642d615 --- /dev/null +++ b/neuronunit/models/morphology.py @@ -0,0 +1,26 @@ +"""NeuronUnit model class for NEURON HOC defined cell models""" + +import os +import sciunit +import neuronunit.capabilities.morphology as cap +import quantities as pq + +class SwcCellModel(sciunit.Model, cap.ProducesSWC): + """A model for cells defined using SWC files. Requires a path to the SWC file.""" + + def __init__(self, swc_path, name=None): + """ + hoc_path: Path to SWC file. + + name: Optional model name. + """ + + self.swc_path = os.path.abspath(swc_path) + + if name is None: + name = os.path.basename(self.swc_path).replace('.swc','') + + super(SwcCellModel,self).__init__(name=name) + + def produce_swc(self): + return os.path.abspath(self.swc_path) diff --git a/neuronunit/models/optimization_model_layer.py b/neuronunit/models/optimization_model_layer.py new file mode 100644 index 000000000..817b21881 --- /dev/null +++ b/neuronunit/models/optimization_model_layer.py @@ -0,0 +1,372 @@ +import numpy as np +import quantities as qt +qt.quantity.PREFERRED = [qt.mV, qt.pA, qt.MOhm, qt.ms, qt.pF, qt.Hz / qt.pA] + +import copy +from collections import OrderedDict +from sciunit import scores +from sciunit.scores.collections import ScoreArray +import sciunit +import pandas as pd +from typing import Any, Dict, List, Optional, Tuple, Type, Union, Text +from jithub.models import model_classes +from bluepyopt.parameters import Parameter + +class OptimizationModel(object): + """ + --Synopsis: OptimizationModel Layer + This Object class serves as a data type for storing rheobase search + attributes and apriori model parameters, + with the distinction that unlike the LEMS model this class + can be cheaply transported across HOSTS/CPUs + """ + + def model_default(self): + if self.backend is not None: + if self.attrs is None: + from neuronunit.optimization.model_parameters import ( + MODEL_PARAMS, + BPO_PARAMS, + ) + + if str("MAT") in self.backend: + self.backend_ref = str("MAT") + if str("IZHI") in self.backend: + self.backend_ref = str("IZHI") + if str("ADEXP") in self.backend: + self.backend_ref = str("ADEXP") + + self.attrs = { + k: np.mean(v) for k, v in MODEL_PARAMS[self.backend_ref].items() + } + else: + self.attrs = self._backend.default_attrs + #print(self.attrs) + return None + + def __init__(self, attrs=None, backend=None, _backend=None): + self.rheobase = None + self.initiated = False + self.backend = backend + #self._backend = _backend + self.lookup = {} + self.name = "NeuronUnitModel" + #super(BPOModel,self).__init__(name=self.name) + self.attrs = attrs + """ + if self.backend is not None: + if ( + str("MAT") in str(self.backend) + or str("IZHI") in str(self.backend) + or str("ADEXP") in str(self.backend) + ): + self.jithub = True + else: + self.jithub = False + """ + if attrs is None: + self.attrs = None + self.model_default() + else: + self.attrs = attrs + #self.set_attrs(self.attrs) + """ + if hasattr(self,'jithub'): + if self.jithub: + if str("MAT") in str(self.backend): + self = model_classes.MATModel() + if str("IZHI") in self.backend: + model = model_classes.IzhiModel() + if str("ADEXP") in self.backend: + model = model_classes.ADEXPModel(name=self.name) + + model.set_attrs(self.attrs) + assert model.attrs == self.attrs + assert model._backend.attrs == self.attrs + #self.params = model.params = self.to_bpo_param(self.attrs) + assert len(self.attrs) + """ + #assert len(model.attrs) + + def to_bpo_param(self, attrs: dict = {}) -> dict: + lop = {} + for k, v in attrs.items(): + p = Parameter(name=k, bounds=v, frozen=False) + lop[k] = p + self.param = lop + return lop + + def attrs_to_params(self): + params = self.attrs + for k, v in params.items(): + if np.round(v, 2) != 0: + params[k] = np.round(v, 2) + if k == "celltype": + params[k] = int(np.round(v, 0)) + return params + + def make_pretty(self, tests:List) -> pd.DataFrame: + + self.tests = tests + self.obs_preds = pd.DataFrame(columns=["observations", "predictions"]) + holding_obs = {t.name: t.observation["mean"] for t in self.tests} + grab_keys = [] + for t in self.tests: + if "value" in t.prediction.keys(): + grab_keys.append("value") + else: + grab_keys.append("mean") + holding_preds = { + t.name: t.prediction[k] + for t, k in zip(self.tests, grab_keys) + } + ## + # This step only partially undoes quantities annoyances. + ## + + for k, v in holding_preds.items(): + if k in holding_obs.keys() and k in holding_preds: + v.rescale_preferred() + v = v.simplified + if np.round(v, 2) != 0: + v = np.round(v, 2) + + for k, v in holding_obs.items(): + if k in holding_obs.keys() and k in holding_preds: + + v.rescale_preferred() + v = v.simplified + if np.round(v, 2) != 0: + v = np.round(v, 2) + + for k, v in holding_preds.items(): + if k in holding_obs.keys() and k in holding_preds: + # units1 = holding_preds[k].units # v.units) + + units1 = holding_preds[k].rescale_preferred().units # v.units) + + holding_preds[k] = holding_preds[k].simplified + holding_preds[k] = holding_preds[k].rescale(units1) + if np.round(holding_preds[k], 2) != 0: + holding_preds[k] = np.round(holding_preds[k], 2) + + holding_obs[k] = holding_obs[k].simplified + holding_obs[k] = holding_obs[k].rescale(units1) + if np.round(holding_obs[k], 2) != 0: + holding_obs[k] = np.round(holding_obs[k], 2) + + temp_obs = pd.DataFrame([holding_obs], index=["observations"]) + temp_preds = pd.DataFrame([holding_preds], index=["predictions"]) + # like a score array but nicer reporting of test name instead of test data type. + try: + scores_ = [] + model = self#.dtc_to_model() + for t in self.tests: + scores_.append(t.judge(model, prediction=t.prediction)) + + not_SA = { + t.name: np.round(score.raw, 2) for t, score in zip(self.tests, scores_) + } + temp_scores = pd.DataFrame([not_SA], index=["Z-Scores"]) + self.obs_preds = pd.concat([temp_obs, temp_preds, temp_scores]) + except: + self.obs_preds = pd.concat([temp_obs, temp_preds]) # , temp_scores]) + self.obs_preds = self.obs_preds.T + return self.obs_preds + + + def add_constant(self): + if self.constants is not None: + self.attrs.update(self.constants) + return # self.attrs + ''' + def dtc_to_model(self): + if ( + str("MAT") in self.backend + or str("IZHI") in self.backend + or str("ADEXP") in self.backend + ): + self.jithub = True + + else: + self.jithub = False + if self.attrs is None: + self.model_default() + if self.jithub: + if str("MAT") in self.backend: + model = model_classes.MATModel() + if str("IZHI") in self.backend: + model = model_classes.IzhiModel() + if str("ADEXP") in self.backend: + model = model_classes.ADEXPModel() + + model.set_attrs(self.attrs) + assert model.attrs == self.attrs + assert model._backend.attrs == self.attrs + assert len(self.attrs) + assert len(model.attrs) + return model + + else: + from neuronunit.models.very_reduced_sans_lems import VeryReducedModel + + model = VeryReducedModel(backend=self.backend, attrs=self.attrs) + + model.set_attrs(self.attrs) + if model.attrs is None: + model.attrs = self.attrs + return model + ''' + ''' + + def dtc_to_model(self): + if self.attrs is None: + self.model_default() + assert self.attrs is not None + return self + def dtc_to_sciunit_model(self): + model = self.dtc_to_model() + sciunit_model = model._backend.as_sciunit_model() + return sciunit_model + ''' + + def to_gene(self, subset_params=None): + """ + These imports probably need to be contained to stop recursive imports + """ + from deap import base + import array + from deap import creator + + creator.create( + "FitnessMin", base.Fitness, weights=tuple(-1.0 for i in range(0, 10)) + ) + creator.create( + "Individual", array.array, typecode="d", fitness=creator.FitnessMin + ) + + # from neuronunit.optimisation.optimization_management import WSListIndividual + # print('warning translation dictionary should be used, to garuntee correct attribute order from random access dictionaries') + if "IZHI" in self.backend: + self.attrs.pop("dt", None) + self.attrs.pop("Iext", None) + if subset_params: + pre_gene = OrderedDict() + for k in subset_params: + pre_gene[k] = self.attrs[k] + else: + pre_gene = OrderedDict(self.attrs) + pre_gene = list(pre_gene.values()) + gene = creator.Individual(pre_gene) + return gene + + def judge_test(self, index=0): + model = self#self.dtc_to_model() + if not hasattr(self, "tests"): + print("warning dtc object does not contain NU-tests yet") + return dtc + + ts = self.tests + # this_test = ts[index] + if not hasattr(self, "preds"): + self.preds = {} + for this_test in self.tests: + this_test.setup_protocol(model) + pred = this_test.extract_features(model, this_test.get_result(model)) + pred1 = this_test.generate_prediction(model) + self.preds[this_test.name] = pred + return self.preds + + """ + def jt_ratio(self,index=0): + from sciunit import scores + model = self.dtc_to_model() + if not hasattr(self,'tests'): + print('warning dtc object does not contain NU-tests yet') + return dtc + + ts = self.tests + #this_test = ts[index] + if not hasattr(self,'preds'): + self.preds = {} + tests = self.format_test() + for this_test in self.tests: + if this_test.passive: + this_test.params['injected_square_current'] = {} + this_test.params['injected_square_current']['amplitude'] = -10*qt.pA + + this_test.params['injected_square_current']['duration'] = 1000*qt.ms + this_test.params['injected_square_current']['delay'] = 200*qt.ms + + this_test.setup_protocol(model) + pred = this_test.extract_features(model,this_test.get_result(model)) + else: + this_test.params['injected_square_current']['amplitude'] = self.rheobase + this_test.params['injected_square_current']['duration'] = 1000*qt.ms + this_test.params['injected_square_current']['delay'] = 200*qt.ms + + pred = this_test.generate_prediction(model) + #self.preds[this_test.name] = pred + ratio_type = scores.RatioScore + temp = copy.copy(this_test.score_type) + this_test.score_type = ratio_type + try: + #print(this_test.name) + self.rscores[rscores.name] = this_test.compute_score(this_test.observation,pred) + + except: + this_test.score_type = temp + #self.rscores = this_test.compute_score(model) + self.rscores[rscores.name] = this_test.compute_score(this_test.observation,pred) + + self.failed = {} + self.failed['pred'] = pred + self.failed['observation'] = this_test.observation + + return self.preds + """ + + def check_params(self): + self.judge_test() + + return self.preds + + def plot_obs(self, ow): + """ + assuming a waveform exists (observed waved-form) plot to terminal with ascii + This is useful for debugging new backends, in bash big/fast command line orientated optimization routines. + """ + + t = [float(f) for f in ow.times] + v = [float(f) for f in ow.magnitude] + fig = apl.figure() + fig.plot( + t, + v, + label=str("observation waveform from inside dtc: "), + width=100, + height=20, + ) + fig.show() + + def iap(self, tests=None): + """ + Inject and plot to terminal with ascii + This is useful for debugging new backends, in bash big/fast command line orientated optimization routines. + """ + from neuronunit.optimisation import optimization_management as om_ + + if tests is not None: + self.tests = tests + + OM = self.dtc_to_opt_man() + self = om_.dtc_to_rheo(self) + + model = self#.dtc_to_model() + pms = uset_t.params + pms["injected_square_current"]["amplitude"] = self.rheobase + model.inject_square_current(pms["injected_square_current"]) + nspike = model.get_spike_train() + self.nspike = nspike + vm = model.get_membrane_potential() + return vm diff --git a/neuronunit/models/reduced.py b/neuronunit/models/reduced.py index 232cebfd0..e00b261eb 100644 --- a/neuronunit/models/reduced.py +++ b/neuronunit/models/reduced.py @@ -1,53 +1,105 @@ -"""NeuronUnit model class for reduced neuron models""" +"""NeuronUnit model class for reduced neuron models.""" import numpy as np from neo.core import AnalogSignal import quantities as pq import neuronunit.capabilities as cap -import neuronunit.models as mod +from .lems import LEMSModel +from .static import ExternalModel import neuronunit.capabilities.spike_functions as sf -from neuronunit.models import backends +from copy import deepcopy +from sciunit.models import RunnableModel -class ReducedModel(mod.LEMSModel, - cap.ReceivesCurrent, +class ReducedModel(LEMSModel, + cap.ReceivesSquareCurrent, cap.ProducesActionPotentials, ): """Base class for reduced models, using LEMS""" def __init__(self, LEMS_file_path, name=None, backend=None, attrs=None): - """ + """Instantiate a reduced model. + LEMS_file_path: Path to LEMS file (an xml file). name: Optional model name. """ - super(ReducedModel,self).__init__(LEMS_file_path,name=name, - backend=backend,attrs=attrs) + + super(ReducedModel, self).__init__(LEMS_file_path, name=name, + backend=backend, attrs=attrs) self.run_number = 0 self.tstop = None - def get_membrane_potential(self, rerun=None, **run_params): - if rerun is None: - rerun = self.rerun - self.run(rerun=rerun, **run_params) + def get_membrane_potential(self, **run_params): + self.run(**run_params) + v = None for rkey in self.results.keys(): if 'v' in rkey or 'vm' in rkey: v = np.array(self.results[rkey]) t = np.array(self.results['t']) - dt = (t[1]-t[0])*pq.s # Time per sample in seconds. - vm = AnalogSignal(v,units=pq.V,sampling_rate=1.0/dt) + dt = (t[1]-t[0])*pq.s # Time per sample in seconds. + vm = AnalogSignal(v, units=pq.V, sampling_rate=1.0/dt) return vm - def get_APs(self, rerun=False, **run_params): - vm = self.get_membrane_potential(rerun=rerun, **run_params) + def get_APs(self, **run_params): + vm = self.get_membrane_potential(**run_params) waveforms = sf.get_spike_waveforms(vm) return waveforms - def get_spike_train(self, rerun=False, **run_params): - vm = self.get_membrane_potential(rerun=rerun, **run_params) + def get_spike_train(self, **run_params): + vm = self.get_membrane_potential(**run_params) spike_train = sf.get_spike_train(vm) return spike_train - #This method must be overwritten in the child class or Derived class - # NEURONbackend but I don't understand how to do that. - #def inject_square_current(self,current): - # self.run_params['injected_square_current'] = current + def inject_square_current(self, current): + assert isinstance(current, dict) + assert all(x in current for x in + ['amplitude', 'delay', 'duration']) + self.set_run_params(injected_square_current=current) + self._backend.inject_square_current(current) + +class VeryReducedModel(RunnableModel, + cap.ReceivesCurrent, + cap.ProducesActionPotentials, + ): + """Base class for reduced models, using LEMS""" + + def __init__(self, name=None, backend=None, attrs=None): + """Instantiate a reduced model. + LEMS_file_path: Path to LEMS file (an xml file). + name: Optional model name. + """ + super(VeryReducedModel,self).__init__(name=name, backend=backend, attrs=attrs) + self.run_number = 0 + self.tstop = None + + def run(self, rerun=None, **run_params): + if rerun is None: + rerun = self.rerun + self.set_run_params(**run_params) + for key,value in self.run_defaults.items(): + if key not in self.run_params: + self.set_run_params(**{key:value}) + #if (not rerun) and hasattr(self,'last_run_params') and \ + # self.run_params == self.last_run_params: + # print("Same run_params; skipping...") + # return + + self.results = self._backend.local_run() + self.last_run_params = deepcopy(self.run_params) + #self.rerun = False + # Reset run parameters so the next test has to pass its own + # run parameters and not use the same ones + self.run_params = {} + + def set_run_params(self, **params): + self._backend.set_run_params(**params) + + # Methods to override using inheritance. + def get_membrane_potential(self, **run_params): + pass + def get_APs(self, **run_params): + pass + def get_spike_train(self, **run_params): + pass + def inject_square_current(self, current): + pass diff --git a/neuronunit/models/section_extension.py b/neuronunit/models/section_extension.py deleted file mode 100644 index f080d2a79..000000000 --- a/neuronunit/models/section_extension.py +++ /dev/null @@ -1,45 +0,0 @@ - -# These classes exist for compatibility with the old neuronunit.neuron module. -import sciunit -# -from neuronunit.models.backends import NEURONBackend -class HasSegment(sciunit.Capability): - """Model has a membrane segment of NEURON simulator""" - - def setSegment(self, section, location = 0.5): - """Sets the target NEURON segment object - section: NEURON Section object - location: 0.0-1.0 value that refers to the location - along the section length. Defaults to 0.5 - """ - - self.section = section - self.location = location - - def getSegment(self): - """Returns the segment at the active section location""" - - return self.section(self.location) - -class SingleCellModel(NEURONBackend): - def __init__(self, \ - neuronVar, \ - section, \ - loc = 0.5, \ - name=None): - super(SingleCellModel,self).__init__()#name=name, hVar=hVar) - hs = HasSegment() - hs.setSegment(section, loc) - self.reset_neuron(neuronVar) - self.section = section - self.loc = loc - self.name = name - #section = soma - #super(SingleCellModel,self).reset_neuron(self, neuronVar) - # Store voltage and time values - self.tVector = self.h.Vector() - self.vVector = self.h.Vector() - self.vVector.record(hs.getSegment()._ref_v) - self.tVector.record(self.h._ref_t) - - return diff --git a/neuronunit/models/static.py b/neuronunit/models/static.py new file mode 100755 index 000000000..0c170f7e1 --- /dev/null +++ b/neuronunit/models/static.py @@ -0,0 +1,86 @@ +from neo.core import AnalogSignal +import neuronunit.capabilities as cap +import neuronunit.capabilities.spike_functions as sf +import numpy as np +import pickle +import quantities as pq +import sciunit +from sciunit.models import RunnableModel +import sciunit.capabilities as scap +from sciunit.models import RunnableModel + + +class StaticModel(RunnableModel, + cap.ReceivesSquareCurrent, + cap.ProducesActionPotentials, + cap.ProducesMembranePotential): + """A model which produces a frozen membrane potential waveform.""" + + def __init__(self, vm): + """Create an instace of a model that produces a static waveform. + + :param vm: either a neo.core.AnalogSignal or a path to a + pickled neo.core.AnalogSignal + """ + if isinstance(vm, str): + with open(vm, 'r') as f: + vm = pickle.load(f) + + if not isinstance(vm, AnalogSignal): + raise TypeError('vm must be a neo.core.AnalogSignal') + + self.vm = vm + self.backend = 'static_model' + def run(self, **kwargs): + pass + + def get_membrane_potential(self, **kwargs): + """Return the Vm passed into the class constructor.""" + return self.vm + + def get_APs(self, **run_params): + """Return the APs, if any, contained in the static waveform.""" + vm = self.get_membrane_potential(**run_params) + waveforms = sf.get_spike_waveforms(vm) + return waveforms + + def inject_square_current(self, current): + """Static model always returns the same waveform. + This method is for compatibility only.""" + pass + + +class ExternalModel(sciunit.models.RunnableModel, + cap.ProducesMembranePotential, + scap.Runnable): + """A model which produces a frozen membrane potential waveform.""" + + def __init__(self, *args, **kwargs): + """Create an instace of a model that produces a static waveform.""" + super(ExternalModel, self).__init__(*args, **kwargs) + + + def set_membrane_potential(self, vm): + self.vm = vm + + def set_model_attrs(self, attrs): + self.attrs = attrs + + def get_membrane_potential(self): + return self.vm + def get_APs(self, **run_params): + """Return the APs, if any, contained in the static waveform.""" + vm = self.get_membrane_potential(**run_params) + waveforms = sf.get_spike_waveforms(vm) + return waveforms + + +class RandomVmModel(RunnableModel, cap.ProducesMembranePotential, cap.ReceivesCurrent): + def get_membrane_potential(self): + # Random membrane potential signal + vm = (np.random.randn(10000)-60)*pq.mV + vm = AnalogSignal(vm, sampling_period=0.1*pq.ms) + return vm + + def inject_square_current(self, current): + pass diff --git a/neuronunit/models/very_reduced.py b/neuronunit/models/very_reduced.py new file mode 100644 index 000000000..20c73f2a1 --- /dev/null +++ b/neuronunit/models/very_reduced.py @@ -0,0 +1,54 @@ +"""NeuronUnit model class for reduced neuron models.""" + +import numpy as np +from neo.core import AnalogSignal +import quantities as pq + +import neuronunit.capabilities as cap +#import neuronunit.models as mod +import neuronunit.capabilities.spike_functions as sf + +from sciunit.models import RunnableModel + +class VeryReducedModel(RunnableModel, + cap.ReceivesCurrent, + cap.ProducesActionPotentials, + ): + """Base class for reduced models, not using LEMS, + and not requiring file paths this is to wrap pyNN models, Brian models, + and other self contained models+model descriptions""" + + def __init__(self, name=None, backend=None, attrs=None): + """Instantiate a reduced model. + + """ + super(VeryReducedModel,self).__init__(name) + self.backend = backend + self.attrs = attrs + self.run_number = 0 + self.tstop = None + + def get_membrane_potential(self, **run_params): + self.run(**run_params) + v = None + for rkey in self.results.keys(): + if 'v' in rkey or 'vm' in rkey: + v = np.array(self.results[rkey]) + t = np.array(self.results['t']) + dt = (t[1]-t[0])*pq.s # Time per sample in seconds. + vm = AnalogSignal(v, units=pq.V, sampling_rate=1.0/dt) + return vm + + def get_APs(self, **run_params): + vm = self.get_membrane_potential(**run_params) + waveforms = sf.get_spike_waveforms(vm) + return waveforms + + def get_spike_train(self, **run_params): + vm = self.get_membrane_potential(**run_params) + spike_train = sf.get_spike_train(vm) + return spike_train + + #def inject_square_current(self, current): + # self.set_run_params(injected_square_current=current) + # self._backend.inject_square_current(current) diff --git a/neuronunit/models/very_reduced_sans_lems.py b/neuronunit/models/very_reduced_sans_lems.py new file mode 100644 index 000000000..5d613201d --- /dev/null +++ b/neuronunit/models/very_reduced_sans_lems.py @@ -0,0 +1,124 @@ +"""NeuronUnit model class for reduced neuron models.""" + +<<<<<<< HEAD +import sciunit +import neuronunit.capabilities as cap +from sciunit.models.runnable import RunnableModel + + +import numpy as np +from neo.core import AnalogSignal +import quantities as pq +from neuronunit.optimization.data_transport_container import DataTC +import copy + +import neuronunit.capabilities.spike_functions as sf +======= +import numpy as np +from neo.core import AnalogSignal +import quantities as pq +import copy + +import sciunit +from sciunit.models.runnable import RunnableModel + +import neuronunit.capabilities.spike_functions as sf +import neuronunit.capabilities as cap + +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f +class VeryReducedModel(RunnableModel, + cap.ReceivesSquareCurrent, + cap.ProducesActionPotentials, + cap.ProducesMembranePotential): + """Base class for reduced models, not using LEMS, + and not requiring file paths this is to wrap pyNN models, Brian models, + and other self contained models+model descriptions""" + + def __init__(self,name='',backend=None, attrs={}): + """Instantiate a reduced model. + + LEMS_file_path: Path to LEMS file (an xml file). + name: Optional model name. + """ + #sciunit.Model() + + super(VeryReducedModel, self).__init__(name=name,backend=backend, attrs=attrs) + self.backend = backend + self.attrs = {} + self.run_number = 0 + self.tstop = None + self.rheobse = None +<<<<<<< HEAD + +======= + ''' +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + def model_test_eval(self,tests): + """ + Take a model and some tests + Evaluate a test suite over them. + """ + from sciunit import TestSuite + if type(tests) is TestSuite: + not_suite = TSD({t.name:t for t in tests.tests}) + OM = OptMan(tests, backend = self._backend) + dtc = DataTC() + dtc.attrs = self.attrs + assert set(self._backend.attrs.keys()) in set(self.attrs.keys()) + dtc.backend = self._backend + dtc.tests = copy.copy(not_suite) + dtc = dtc_to_rheo(dtc) + if dtc.rheobase is not None: + dtc.tests = dtc.format_test() + dtc = list(map(OM.elephant_evaluation,[dtc])) + model = dtc.dtc_to_model() + model.SM = dtc.SM + model.obs_preds = dtc.obs_preds + return dtc[0], model + + def model_to_dtc(self): + dtc = DataTC() + dtc.attrs = self.attrs + try: + dtc.backend = self.get_backend() + except: + dtc.backend = self.backend + if hasattr(self,'rheobase'): + dtc.rheobase = self.rheobase + return dtc +<<<<<<< HEAD + +======= + ''' +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + def inject_square_current(self, current): + #pass + vm = self._backend.inject_square_current(current) + return vm + ''' + def get_membrane_potential(self,**run_params): + #try: + # vm = self._backend.get_membrane_potential() + #except: + #vm = self.get_membrane_potential() + print(run_params) + vm = self.get_membrane_potential(**run_params) + + return vm + ''' + def get_APs(self, **run_params): + vm = self.get_membrane_potential(**run_params) + waveforms = sf.get_spike_waveforms(vm)#,width=10*ms) + return waveforms + + def get_spike_train(self, **run_params): + vm = self._backend.get_membrane_potential(**run_params) + spike_train = sf.get_spike_train(vm) + return spike_train + + def get_spike_count(self, **run_params): + train = self.get_spike_train(**run_params) + return len(train) + + def set_attrs(self,attrs): + self.attrs.update(attrs) diff --git a/neuronunit/neuroconstruct/__init__.py b/neuronunit/neuroconstruct/__init__.py index b7b6cb9e8..165c785bf 100644 --- a/neuronunit/neuroconstruct/__init__.py +++ b/neuronunit/neuroconstruct/__init__.py @@ -1,6 +1,5 @@ """Neuroconstruct classes for use with neurounit""" -from __future__ import absolute_import import os import sys import warnings diff --git a/neuronunit/neuroelectro.py b/neuronunit/neuroelectro.py index 6b1030f93..5ce6457d5 100644 --- a/neuronunit/neuroelectro.py +++ b/neuronunit/neuroelectro.py @@ -1,33 +1,33 @@ -"""NeuronUnit interface to Neuroelectro.org""" +"""NeuronUnit interface to Neuroelectro.org. -# Interface for creating tests using neuroelectro.org as reference data. -# -# Example workflow: +Interface for creating tests using neuroelectro.org as reference data. -# x = NeuroElectroDataMap() -# x.set_neuron(nlex_id='nifext_152') # neurolex.org ID for 'Amygdala basolateral - # nucleus pyramidal neuron'. -# x.set_ephysprop(id=23) # neuroelectro.org ID for 'Spike width'. -# x.set_article(pmid=18667618) # Pubmed ID for Fajardo et al, 2008 (J. Neurosci.) -# x.get_values() # Gets values for spike width from this paper. -# width = x.val # Spike width reported in that paper. - -# t = neurounit.tests.SpikeWidthTest(spike_width=width) -# c = sciunit.Candidate() # Instantiation of your model (or other candidate) -# c.execute = code_that_runs_your_model -# result = sciunit.run(t,m) -# print result.score +Example workflow: + +x = NeuroElectroDataMap() +x.set_neuron(nlex_id='nifext_152') +# neurolex.org ID for 'Amygdala basolateral nucleus pyramidal neuron'. +x.set_ephysprop(id=23) # neuroelectro.org ID for 'Spike width'. +x.set_article(pmid=18667618) # Pubmed ID for Fajardo et al, 2008 (J. Neurosci.) +x.get_values() # Gets values for spike width from this paper. +width = x.val # Spike width reported in that paper. + +t = neurounit.tests.SpikeWidthTest(spike_width=width) +c = sciunit.Candidate() # Instantiation of your model (or other candidate) +c.execute = code_that_runs_your_model +result = sciunit.run(t,m) +print result.score # # OR # -# x = NeuroElectroSummary() -# x.set_neuron(nlex_id='nifext_152') # neurolex.org ID for 'Amygdala basolateral +x = NeuroElectroSummary() +x.set_neuron(nlex_id='nifext_152') # neurolex.org ID for 'Amygdala basolateral # nucleus pyramidal neuron'. -# x.set_ephysprop(id=2) # neuroelectro.org ID for 'Spike width'. -# x.get_values() # Gets values for spike width from this paper. -# width = x.mean # Mean Spike width reported across all matching papers. -# ... - +x.set_ephysprop(id=2) # neuroelectro.org ID for 'Spike width'. +x.get_values() # Gets values for spike width from this paper. +width = x.mean # Mean Spike width reported across all matching papers. +... +""" import json from pprint import pprint @@ -35,12 +35,8 @@ import hashlib import pickle import requests -try: # Python 2 - from urllib import urlencode, urlretrieve - from urllib2 import urlopen, URLError, HTTPError -except ImportError: # Python 3 - from urllib.parse import urlencode - from urllib.request import urlopen, urlretrieve, URLError, HTTPError +from urllib.parse import urlencode +from urllib.request import urlopen, URLError import numpy as np DUMP = True @@ -56,70 +52,90 @@ def is_neuroelectro_up(): + """Check if neuroelectro.org is up.""" url = "http://neuroelectro.org" request = requests.get(url) return request.status_code == 200 - + class NeuroElectroError(Exception): + """Base class for NeuroElectro errors.""" + pass class Neuron: + """Describes a neuron type in NeuroElectro.""" + id = None nlex_id = None name = None class EphysProp: + """Describes an electrophysiolical property in NeuroElectro.""" + id = None nlex_id = None name = None class Article: + """Describes a journal article in NeuroElectro.""" + id = None pmid = None class NeuroElectroData(object): """Abstract class based on neuroelectro.org data using that site's API.""" - def __init__(self, neuron=None, ephysprop=None, \ - get_values=False, cached=True): + + def __init__(self, neuron=None, ephysprop=None, + get_values=False, cached=True): + """Constructor. + + Args: + neuron (dict): Dictionary of information about the neuron + ephys_prop (dict): Dictionary of information about the + electrophysiological property + get_values (bool): Whether to get the values from NeuroElectro + cached (bool): Whether to use a cached value if it is available + """ self.neuron = Neuron() if neuron: - for key,value in neuron.items(): - setattr(self.neuron,key,value) + for key, value in neuron.items(): + setattr(self.neuron, key, value) self.ephysprop = EphysProp() if ephysprop: - for key,value in ephysprop.items(): - setattr(self.ephysprop,key,value) + for key, value in ephysprop.items(): + setattr(self.ephysprop, key, value) self.require_attrs = None - self.get_one_match = True # By default only get the first match + self.get_one_match = True # By default only get the first match self.cached = cached if get_values: self.get_values() - url = API_URL # Base URL. - + url = API_URL # Base URL. + def set_names(self, neuron_name, ephysprop_name): + """Set the names of the neurons (i.e. their types).""" self.set_neuron(name=neuron_name) self.set_ephysprop(name=ephysprop_name) def set_neuron(self, **kwargs): - """Sets the biological neuron lookup attributes.""" - for key,value in kwargs.items(): - if key in ['id','nlex_id','name']: - setattr(self.neuron,key,value) + """Set the biological neuron lookup attributes.""" + for key, value in kwargs.items(): + if key in ['id', 'nlex_id', 'name']: + setattr(self.neuron, key, value) def set_ephysprop(self, **kwargs): - """Sets the electrophysiological property lookup attributes.""" - for key,value in kwargs.items(): - if key in ['id','nlex_id','name']: - setattr(self.ephysprop,key,value) + """Set the electrophysiological property lookup attributes.""" + for key, value in kwargs.items(): + if key in ['id', 'nlex_id', 'name']: + setattr(self.ephysprop, key, value) - def make_url(self,params=None): - """Creates the full URL to the neuroelectro API.""" + def make_url(self, params=None): + """Create the full URL to the neuroelectro API.""" url = self.url+"?" query = {} # Change these for consistency in the neuroelectro.org API. @@ -128,8 +144,8 @@ def make_url(self,params=None): query['n__name'] = self.neuron.name query['e'] = self.ephysprop.id query['e__name'] = self.ephysprop.name - #print(query) - query = {key:value for key,value in query.items() if value is not None} + query = {key: value for key, value in query.items() + if value is not None} if params is not None: for key in params.keys(): @@ -140,15 +156,17 @@ def make_url(self,params=None): return url def get_json(self, params=None, quiet=False): - """Gets JSON data from neuroelectro.org based on the currently - set neuron and ephys property. Use 'params' to constrain the - data returned.""" + """Get JSON data from neuroelectro.org. + + Data is based on the currently set neuron and ephys property. + Use 'params' to constrain the data returned. + """ url = self.make_url(params=params) if not quiet: print(url) try: - url_result = urlopen(url,None,3) # Get the page. - html = url_result.read() # Read out the HTML (actually JSON) + url_result = urlopen(url, None, 3) # Get the page. + html = url_result.read() # Read out the HTML (actually JSON) except URLError as e: try: html = e.read().decode('utf-8') @@ -156,14 +174,9 @@ def get_json(self, params=None, quiet=False): if 'error_message' in self.json_object: raise NeuroElectroError(self.json_object['error_message']) except: - if hasattr(e,'reason'): + if hasattr(e, 'reason'): raise NeuroElectroError(e.reason) raise NeuroElectroError("NeuroElectro.org appears to be down.") - #print "Using fake data for now." - #html = '{"objects":[{"n":{"name":"CA1 Pyramidal Cell"}, - # "e":{"name":"Spike Width"},\ - # "value_mean":0.001, - # "value_sd":0.0003}]}' else: html = html.decode('utf-8') @@ -171,17 +184,19 @@ def get_json(self, params=None, quiet=False): return self.json_object def get_values(self, params=None, quiet=False): - """Gets values from neuroelectro.org. - We will use 'params' in the future to specify metadata (e.g. temperature) - that neuroelectro.org will provide.""" + """Get values from neuroelectro.org. + + We will use 'params' in the future to specify metadata + (e.g. temperature) that neuroelectro.org will provide. + """ db = shelve.open('neuroelectro-cache') if self.cached else {} - contents = (self.__class__,self.neuron,self.ephysprop,params) + contents = (self.__class__, self.neuron, self.ephysprop, params) if DUMP: pickled = pickle.dumps(contents) identifier = hashlib.sha224(pickled).hexdigest() if not quiet: - print("Getting %s%s data values from neuroelectro.org" \ - % ("cached " if identifier in db else "", \ + print("Getting %s%s data values from neuroelectro.org" + % ("cached " if identifier in db else "", self.ephysprop.name)) if identifier in db: print("Using cached value.") @@ -208,38 +223,45 @@ def get_values(self, params=None, quiet=False): return self.api_data def check(self): - """See if the data requested from the server - were obtained successfully.""" + """See if data requested from the server were obtained successfully.""" if self.require_attrs: for attr in self.require_attrs: - if not hasattr(self,attr): + if not hasattr(self, attr): raise AttributeError(("The attribute '%s' was " "not found.") % attr) class NeuroElectroDataMap(NeuroElectroData): """Class for getting single reported values from neuroelectro.org.""" + url = API_URL+'nedm/' article = Article() - require_attrs = ['val','sem'] + require_attrs = ['val', 'sem'] def set_article(self, id_=None, pmid=None): - """Sets the biological neuron using a NeuroLex ID.""" + """Set the biological neuron using a NeuroLex ID.""" self.article.id = id_ self.article.pmid = pmid def make_url(self, params=None): + """Create the full URL to the neuroelectro API.""" url = super(NeuroElectroDataMap, self).make_url(params=params) query = {} query['a'] = self.article.id query['pmid'] = self.article.pmid - query = {key:value for key,value in query.items() if value is not None} + query = {key: value for key, value in query.items() + if value is not None} url += '&'+urlencode(query) return url def get_values(self, params=None, quiet=False): - data = super(NeuroElectroDataMap,self).get_values(params=params, - quiet=quiet) + """Get values from neuroelectro.org. + + We will use 'params' in the future to specify metadata + (e.g. temperature) that neuroelectro.org will provide. + """ + data = super(NeuroElectroDataMap, self).get_values(params=params, + quiet=quiet) if data: self.neuron.name = data['ncm']['n']['name'] # Set the neuron name from the json data. @@ -254,12 +276,18 @@ def get_values(self, params=None, quiet=False): class NeuroElectroSummary(NeuroElectroData): """Class for getting summary values (across reports) from - neuroelectro.org.""" + neuroelectro.org. + """ url = API_URL+'nes/' - require_attrs = ['mean','std'] + require_attrs = ['mean', 'std'] def get_values(self, params=None, quiet=False): + """Get values from neuroelectro.org. + + We will use 'params' in the future to specify metadata + (e.g. temperature) that neuroelectro.org will provide. + """ data = super(NeuroElectroSummary, self).get_values(params=params, quiet=quiet) if data: @@ -274,45 +302,49 @@ def get_values(self, params=None, quiet=False): return data def get_observation(self, params=None, show=False): + """Get the observation from neuroelectro.org.""" values = self.get_values(params=params) if show: pprint(values) - observation = {'mean':self.mean, 'std':self.std} + observation = {'mean': self.mean, 'std': self.std} return observation class NeuroElectroPooledSummary(NeuroElectroDataMap): - """Class for getting summary values by pooling each report's mean and SD from - neuroelectro.org.""" + """Class for getting summary values from neuroelectro.org. - def get_values(self, params=None, quiet=False): + Values are computed by pooling each report's mean and std across reports. + """ - # Get all papers reporting the neuron's property value - self.get_one_match = False # We want all matches + def get_values(self, params=None, quiet=False): + """Get all papers reporting the neuron's property value.""" + self.get_one_match = False # We want all matches if params is None: params = {} params['limit'] = 999 - data = super(NeuroElectroPooledSummary,self).get_values(params=params,\ - quiet=quiet) + data = super(NeuroElectroPooledSummary, self).get_values(params=params, + quiet=quiet) if data: # Ensure data from api matches the requested params - data = [item for item in data \ - if (item['ecm']['e']['name'] == self.ephysprop.name.lower() or \ - item['ecm']['e']['id'] == self.ephysprop.id) \ - and \ - (item['ncm']['n']['nlex_id'] == self.neuron.nlex_id or \ - item['ncm']['n']['id'] == self.neuron.id) \ - ] + data = [item for item in data + if (item['ecm']['e']['name'] == self.ephysprop.name.lower() + or + item['ecm']['e']['id'] == self.ephysprop.id) + and + (item['ncm']['n']['nlex_id'] == self.neuron.nlex_id + or + item['ncm']['n']['id'] == self.neuron.id) + ] # Set the neuron name and prop from the first json data object. self.neuron_name = data[0]['ncm']['n']['name'] self.ephysprop_name = data[0]['ecm']['e']['name'] - # Pool each paper by weighing each mean by the paper's N and SD + # Pool each paper by weighing each mean by the paper's N and std stats = self.get_pooled_stats(data, quiet) self.mean = stats['mean'] @@ -332,21 +364,22 @@ def get_values(self, params=None, quiet=False): return self.items def get_observation(self, params=None, show=False): + """Get the observation from neuroelectro.org.""" values = self.get_values(params=params) if show: pprint(values) - observation = {'mean':self.mean, 'std':self.std, 'n':self.n } + observation = {'mean': self.mean, 'std': self.std, 'n': self.n} return observation - def get_pooled_stats(self, data, quiet = True): - + def get_pooled_stats(self, data, quiet=True): + """Get pooled statistics from the data.""" lines = [] means = [] sems = [] - sds = [] + stds = [] ns = [] sources = [] @@ -356,89 +389,96 @@ def get_pooled_stats(self, data, quiet = True): # Collect raw values for each paper from NeuroElectro for item in data: - err_is_sem = item['error_type'] == "sem" # SEM or SD - err = (item['err_norm'] if item['err_norm'] is not None else item['err']) + err_is_sem = item['error_type'] == "sem" # SEM or std + err = (item['err_norm'] if item['err_norm'] is not None + else item['err']) sem = err if err_is_sem else None - sd = err if not err_is_sem else None - mean = item['val_norm'] if item['val_norm'] is not None else item['val'] + std = err if not err_is_sem else None + mean = (item['val_norm'] if item['val_norm'] is not None + else item['val']) n = item['n'] source = item['source'] means.append(mean) sems.append(sem) - sds.append(sd) + stds.append(std) ns.append(n) sources.append(source) if not quiet: - print({ 'mean': mean, 'std': sd, 'sem': sem, 'n': n }) + print({'mean': mean, 'std': std, 'sem': sem, 'n': n}) # Fill in missing values self.fill_missing_ns(ns) - self.fill_missing_sems_sds(sems, sds, ns) + self.fill_missing_sems_stds(sems, stds, ns) if not quiet: print("---------------------------------------------------") print("Filled in Values (computed or median where missing)") - for i,_ in enumerate(means): - line = { 'mean': means[i], 'sd': sds[i], 'sem': sems[i], 'n': ns[i], "source": sources[i] } + for i, _ in enumerate(means): + line = {'mean': means[i], 'std': stds[i], 'sem': sems[i], + 'n': ns[i], "source": sources[i]} lines.append(line) print(line) # Compute the weighted grand_mean # grand_mean = SUM( N[i]*Mean[i] ) / SUM(N[i]) - # grand_sd = SQRT( SUM( (N[i]-1)*SD[i]^2 ) / SUM(N[i]-1) ) + # grand_std = SQRT( SUM( (N[i]-1)*std[i]^2 ) / SUM(N[i]-1) ) ns_np = np.array(ns) means_np = np.array(means) - sds_np = np.array(sds) + stds_np = np.array(stds) n_sum = ns_np.sum() grand_mean = np.sum(ns_np * means_np) / n_sum - grand_sd = np.sqrt( np.sum( (ns_np-1)*(sds_np**2) ) / np.sum(ns_np-1) ) - grand_sem = grand_sd / np.sqrt(n_sum) + grand_std = np.sqrt(np.sum((ns_np-1)*(stds_np**2))/np.sum(ns_np-1)) + grand_sem = grand_std / np.sqrt(n_sum) - return { 'mean': grand_mean, 'sem': grand_sem, 'std': grand_sd, 'n': n_sum, 'items': lines } + return {'mean': grand_mean, 'sem': grand_sem, 'std': grand_std, + 'n': n_sum, 'items': lines} def fill_missing_ns(self, ns): - # Fill in the missing N's with median N + """Fill in the missing N's with median N.""" none_free_ns = np.array(ns)[ns != np.array(None)] if none_free_ns: n_median = int(np.median(none_free_ns)) else: - n_median = 1 # If no N's reported at all, weigh all means equally + n_median = 1 # If no N's reported at all, weigh all means equally - for i,_ in enumerate(ns): + for i, _ in enumerate(ns): if ns[i] is None: ns[i] = n_median - def fill_missing_sems_sds(self, sems, sds, ns): - # Fill in computable sems/sds - for i,_ in enumerate(sems): + def fill_missing_sems_stds(self, sems, stds, ns): + """Fill in computable sems/stds.""" + for i, _ in enumerate(sems): - # Check if sem or sd is computable - if sems[i] is None and sds[i] is not None: - sems[i] = sds[i] / np.sqrt(ns[i]) + # Check if sem or std is computable + if sems[i] is None and stds[i] is not None: + sems[i] = stds[i] / np.sqrt(ns[i]) - if sds[i] is None and sems[i] is not None: - sds[i] = sems[i] * np.sqrt(ns[i]) + if stds[i] is None and sems[i] is not None: + stds[i] = sems[i] * np.sqrt(ns[i]) - # Fill in the remaining missing using median sd - none_free_sds = np.array(sds)[sds != np.array(None)] + # Fill in the remaining missing using median std + none_free_stds = np.array(stds)[stds != np.array(None)] - if none_free_sds: - sd_median = np.median(none_free_sds) + if none_free_stds: + std_median = np.median(none_free_stds) - else: # If no SDs or SEMs reported at all, raise error + else: # If no stds or SEMs reported at all, raise error - # Perhaps the median SD of all cells for this property could be used - # however, NE API nes interface does not support summary prop values without specifying the neuron id - raise NotImplementedError('No StDevs or SEMs reported for "%s" property "%s"'%(self.neuron_name,self.ephysprop_name)) + # Perhaps the median std of all cells for this property could + # be used. However, NE API nes interface does not support summary + # prop values without specifying the neuron id + msg = 'No StDevs or SEMs reported for "%s" property "%s"' + msg = msg % (self.neuron_name, self.ephysprop_name) + raise NotImplementedError(msg) - for i,_ in enumerate(sds): - if sds[i] is None: - sds[i] = sd_median - sems[i] = sd_median / np.sqrt(ns[i]) + for i, _ in enumerate(stds): + if stds[i] is None: + stds[i] = std_median + sems[i] = std_median / np.sqrt(ns[i]) diff --git a/neuronunit/neuromldb.py b/neuronunit/neuromldb.py new file mode 100755 index 000000000..6b9e83395 --- /dev/null +++ b/neuronunit/neuromldb.py @@ -0,0 +1,157 @@ +from urllib import request +import sys +import json +import zipfile, tempfile +import os +import pathlib + +import quantities +import quantities as pq +from scipy.interpolate import interp1d +import numpy as np +from neo import AnalogSignal +from neuronunit.models.static import StaticModel + +import urllib.request as urllib + +class NeuroMLDBModel: + def __init__(self, model_id = "NMLCL000086"): + self.model_id = model_id + self.api_url = "https://neuroml-db.org/api/" # See docs at: https://neuroml-db.org/api + + self.waveforms = None + + self.waveform_signals = {} + self.url_responses = {} + + def get_files(self): + zip_url = "https://neuroml-db.org/GetModelZip?modelID=%s&version=NeuroML" % self.model_id + location = pathlib.Path(tempfile.mkdtemp()) + zip_location = location / ('%s.zip' % self.model_id) + request.urlretrieve(zip_url, zip_location) + assert zip_location.is_file() + with zipfile.ZipFile(zip_location, 'r') as zip_ref: + zip_ref.extractall(location) + return location + + def read_api_url(self, url): + if url not in self.url_responses: + response = urllib.urlopen(url).read() + response = response.decode("utf-8") + self.url_responses[url] = json.loads(response) + + return self.url_responses[url] + + def fetch_waveform_list(self): + + # Fetch the list of waveforms from the API and cache the result + if not self.waveforms: + data = self.read_api_url(self.api_url + "model?id=" + str(self.model_id)) + self.waveforms = data["waveform_list"] + + return self.waveforms + + def fetch_waveform_as_AnalogSignal(self, waveform_id, resolution_ms = 0.01, units = "mV"): + + # If signal not in cache + if waveform_id not in self.waveform_signals: + # Load api URL into Python + data = self.read_api_url(self.api_url + "waveform?id=" + str(waveform_id)) + + # Get time and signal values (from CSV format) + t = np.array(data["Times"].split(','),float) + signal = np.array(data["Variable_Values"].split(','),float) + + # Interpolate to regularly sampled series (API returns irregularly sampled) + sig = interp1d(t,signal,fill_value="extrapolate") + signal = sig(np.arange(min(t),max(t),resolution_ms)) + + # Convert to neo.AnalogSignal + signal = AnalogSignal(signal,units=units, sampling_period=resolution_ms*quantities.ms) + + starts_from_ss = next(w for w in self.waveforms if w["ID"] == waveform_id)["Starts_From_Steady_State"] == 1 + + if starts_from_ss: + rest_wave = self.get_steady_state_waveform() + + t = np.concatenate((rest_wave.times, signal.times + rest_wave.t_stop)) * quantities.s + v = np.concatenate((np.array(rest_wave), np.array(signal))) * quantities.mV + + signal = AnalogSignal(v, units=units, sampling_period=resolution_ms * quantities.ms) + + self.waveform_signals[waveform_id] = signal + + return self.waveform_signals[waveform_id] + + def get_steady_state_waveform(self): + if not hasattr(self, "steady_state_waveform") or self.steady_state_waveform is None: + for w in self.waveforms: + if w["Protocol_ID"] == "STEADY_STATE" and w["Variable_Name"] == "Voltage": + self.steady_state_waveform = self.fetch_waveform_as_AnalogSignal(w["ID"]) + return self.steady_state_waveform + + raise Exception("Did not find the resting waveform." + + " See " + self.api_url + "model?id=" + self.model_id + + " for the list of available model waveforms.") + + return self.steady_state_waveform + + def get_waveform_by_current(self, amplitude_nA): + for w in self.waveforms: + if w["Variable_Name"] == "Voltage": + wave_amp = self.get_waveform_current_amplitude(w) + if ((amplitude_nA < 0 * pq.nA and w["Protocol_ID"] == "SQUARE") or + (amplitude_nA >= 0 * pq.nA and w["Protocol_ID"] == "LONG_SQUARE")) \ + and amplitude_nA == wave_amp: + return self.fetch_waveform_as_AnalogSignal(w["ID"]) + + raise Exception("Did not find a Voltage waveform with injected " + str(amplitude_nA) + + ". See " + self.api_url + "model?id=" + self.model_id + + " for the list of available model waveforms.") + + def get_druckmann2013_standard_current(self): + currents = [] + for w in self.waveforms: + if w["Protocol_ID"] == "LONG_SQUARE" and w["Variable_Name"] == "Voltage": + currents.append(self.get_waveform_current_amplitude(w)) + + if len(currents) != 4: + raise Exception("The LONG_SQUARE protocol for the model should have 4 waveforms") + + return [currents[-2]] # 2nd to last one is RBx1.5 waveform + + def get_druckmann2013_strong_current(self): + currents = [] + + for w in self.waveforms: + if w["Protocol_ID"] == "LONG_SQUARE" and w["Variable_Name"] == "Voltage": + currents.append(self.get_waveform_current_amplitude(w)) + + if len(currents) != 4: + raise Exception("The LONG_SQUARE protocol for the model should have 4 waveforms") + + return [currents[-1]] # The last one is RBx3 waveform + + def get_druckmann2013_input_resistance_currents(self): + currents = [] + + # Find and return negative square current injections + for w in self.waveforms: + if w["Protocol_ID"] == "SQUARE" and w["Variable_Name"] == "Voltage": + amp = self.get_waveform_current_amplitude(w) + if amp < 0 * pq.nA: + currents.append(amp) + + return currents + + def get_waveform_current_amplitude(self, waveform): + return float(waveform["Waveform_Label"].replace(" nA", "")) * pq.nA + + +class NeuroMLDBStaticModel(StaticModel): + def __init__(self, model_id, **params): + self.nmldb_model = NeuroMLDBModel(model_id) + self.nmldb_model.fetch_waveform_list() + + def inject_square_current(self, current): + self.vm = self.nmldb_model.get_waveform_by_current(current["amplitude"]) diff --git a/neuronunit/optimization/__init__.py b/neuronunit/optimization/__init__.py index f6385ef86..abb13c08d 100644 --- a/neuronunit/optimization/__init__.py +++ b/neuronunit/optimization/__init__.py @@ -1,5 +1,4 @@ - """NeuronUnit Optimization classes""" -#from .evaluate_as_module import * -#from .nsga_parallel import * +# from .evaluate_as_module import * +# from .nsga_parallel import * diff --git a/neuronunit/optimization/algorithms.py b/neuronunit/optimization/algorithms.py new file mode 100644 index 000000000..3b0bfc562 --- /dev/null +++ b/neuronunit/optimization/algorithms.py @@ -0,0 +1,334 @@ +"""Optimisation class""" + +""" +Copyright (c) 2016-2020, EPFL/Blue Brain Project + + This file is part of BluePyOpt + + This library is free software; you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License version 3.0 as published + by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + details. + + You should have received a copy of the GNU Lesser General Public License + along with this library; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +""" + +# pylint: disable=R0914, R0912 + + +import random +import logging +import shutil +import os + +import deap.algorithms +import deap.tools +import pickle +from tqdm.auto import tqdm +from bluepyopt.deapext.stoppingCriteria import MaxNGen +import streamlit as st + +logger = logging.getLogger("__main__") +import numpy as np + + +def _define_fitness(pop, obj_size): + """Re-instanciate the fitness of the individuals for it to matches the + evaluation function. + """ + from .optimisations import WSListIndividual + + new_pop = [] + if pop: + for ind in pop: + new_pop.append(WSListIndividual(list(ind), obj_size=obj_size)) + + return new_pop + + +def _evaluate_invalid_fitness(toolbox, population): + """Evaluate the individuals with an invalid fitness + + Returns the count of individuals with invalid fitness + """ + invalid_ind = [ind for ind in population if not ind.fitness.valid] + fitnesses = toolbox.map(toolbox.evaluate, invalid_ind) + for ind, fit in zip(invalid_ind, fitnesses): + ind.fitness.values = fit + + return len(invalid_ind) + + +def _update_history_and_hof(halloffame, history, population): + """Update the hall of fame with the generated individuals + + Note: History and Hall-of-Fame behave like dictionaries + """ + if halloffame is not None: + halloffame.update(population) + + history.update(population) + + +def _record_stats(stats, logbook, gen, population, invalid_count): + """Update the statistics with the new population""" + record = stats.compile(population) if stats is not None else {} + logbook.record(gen=gen, nevals=invalid_count, **record) + + +from deap import tools + + +def _get_offspring_time_diminishing_eta(parents, toolbox, cxpb, mutpb, gen, ngen): + """return the offspring, use toolbox.variate if possible""" + + BOUND_LOW = [] + BOUND_UP = [] + NDIM = len(parents[0]) + fit_dim = len(parents[0].fitness.values) + for x in range(0, len(parents[0])): + BOUND_LOW.append(toolbox.uniformparams.args[0][x]) + BOUND_UP.append(toolbox.uniformparams.args[1][x]) +<<<<<<< HEAD + ETA = int(30.0 * (1 - (gen / ngen))) + toolbox.register( + "mate", tools.cxSimulatedBinaryBounded, low=BOUND_LOW, up=BOUND_UP, eta=ETA +======= + eta = int(30.0 * (1 - (gen / ngen))) + toolbox.register( + "mate", tools.cxSimulatedBinaryBounded, low=BOUND_LOW, up=BOUND_UP, eta=eta +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + ) + toolbox.register( + "mutate", + tools.mutPolynomialBounded, + low=BOUND_LOW, + up=BOUND_UP, +<<<<<<< HEAD + eta=ETA, +======= + eta=eta, +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + indpb=1.0 / NDIM, + ) + if (1 - (gen / ngen)) / 2.0 > 0.05: + mutpb = (1 - (gen / ngen)) / 2.0 + else: + mutpb = 0.05 + if (1 - (gen / ngen)) > 0.4: + cxpb = (1 - (gen / ngen)) / 1.1 + else: + cxpb = 0.4 +<<<<<<< HEAD +======= + #print(eta,cxpb,mutpb,': Best eta,cxpb,mutpb value?') +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + + if hasattr(toolbox, "variate"): + + return toolbox.variate(parents, toolbox, cxpb, mutpb) + return deap.algorithms.varAnd(parents, toolbox, cxpb, mutpb) + + +def _get_offspring(parents, toolbox, cxpb, mutpb): + """return the offspring, use toolbox.variate if possible""" + + if hasattr(toolbox, "variate"): + return toolbox.variate(parents, toolbox, cxpb, mutpb) + return deap.algorithms.varAnd(parents, toolbox, cxpb, mutpb) + + +def _check_stopping_criteria(criteria, params): + for c in criteria: + c.check(params) + if c.criteria_met: + logger.info("Run stopped because of stopping criteria: " + c.name) + return True + else: + return False + + +def filter_parents(parents): + """ + --synopsis: This method does not work yet. + #Remove the genes that represent the cliff + #edge. Possibly counter productive. + """ + import copy + + parentsc = copy.copy(parents) + orig_len = len(parents) + cnt = 0 + for p in parentsc: + flag = False + for fv in p.fitness.values: + if fv == 1000.0: + flag = True + if flag: + cnt += 1 + while cnt > 0: + for i, p in enumerate(parentsc): + flag = False + for fv in p.fitness.values: + if fv == 1000.0: + flag = True + if flag: + del parentsc[i] + cnt -= 1 + cnt = 0 + while len(parentsc) < orig_len: + parentsc.append(parentsc[cnt]) + cnt += 1 + return parentsc + + +def eaAlphaMuPlusLambdaCheckpoint( + population, + toolbox, + mu, + cxpb, + mutpb, + ngen, + stats=None, + halloffame=None, + cp_frequency=1, + cp_filename=None, + continue_cp=False, +): + r"""This is the :math:`(~\alpha,\mu~,~\lambda)` evolutionary algorithm + + Args: + population(list of deap Individuals) + toolbox(deap Toolbox) + mu(int): Total parent population size of EA + cxpb(float): Crossover probability + mutpb(float): Mutation probability + ngen(int): Total number of generation to run + stats(deap.tools.Statistics): generation of statistics + halloffame(deap.tools.HallOfFame): hall of fame + cp_frequency(int): generations between checkpoints + cp_filename(string): path to checkpoint filename + continue_cp(bool): whether to continue + """ + + if cp_filename: + cp_filename_tmp = cp_filename + ".tmp" + + if continue_cp: + # A file name has been given, then load the data from the file + cp = pickle.load(open(cp_filename, "rb")) + population = cp["population"] + parents = cp["parents"] + start_gen = cp["generation"] + halloffame = cp["halloffame"] + logbook = cp["logbook"] + history = cp["history"] + random.setstate(cp["rndstate"]) + + # Assert that the fitness of the individuals match the evaluator + obj_size = len(population[0].fitness.wvalues) + population = _define_fitness(population, obj_size) + parents = _define_fitness(parents, obj_size) + _evaluate_invalid_fitness(toolbox, parents) + _evaluate_invalid_fitness(toolbox, population) + + else: + prog_bar = st.progress(0) + # Start a new evolution + start_gen = 1 + parents = population[:] + logbook = deap.tools.Logbook() + logbook.header = ["gen", "nevals"] + (stats.fields if stats else []) + history = deap.tools.History() + + invalid_count = _evaluate_invalid_fitness(toolbox, population) + _update_history_and_hof(halloffame, history, population) + _record_stats(stats, logbook, start_gen, population, invalid_count) + logger.info(logbook.stream) + stopping_criteria = [MaxNGen(ngen)] +<<<<<<< HEAD + + # Begin the generational process + gen = start_gen + 1 + stopping_params = {"gen": gen} + pbar = tqdm(total=ngen) + while not (_check_stopping_criteria(stopping_criteria, stopping_params)): + # offspring = _get_offspring(parents, toolbox, cxpb, mutpb) + offspring = _get_offspring_time_diminishing_eta( + parents, toolbox, cxpb, mutpb, gen, ngen + ) + population = parents + offspring + [halloffame[0]] + + stop = _check_stopping_criteria(stopping_criteria, stopping_params) + + invalid_count = _evaluate_invalid_fitness(toolbox, offspring) +======= + + # Begin the generational process + gen = start_gen + 1 + stopping_params = {"gen": gen} + pbar = tqdm(total=ngen) + old = 0 + deltas = [] + while not (_check_stopping_criteria(stopping_criteria, stopping_params)): + offspring1 = _get_offspring(parents, toolbox, cxpb, mutpb) + #offspring1 = _get_offspring_time_diminishing_eta( + # parents, toolbox, cxpb, mutpb, gen, ngen + #) + population = parents + offspring1 + [halloffame[0]] + + stop = _check_stopping_criteria(stopping_criteria, stopping_params) + invalid_count = _evaluate_invalid_fitness(toolbox, offspring1) + #comp1 = np.sum([ind.fitness.values for ind in offspring1]) + #if old: + # delta = np.abs(comp1-old) + # deltas.append((comp1,delta))#,mutpb,cxpb,eta)) + #old = comp1 + #print(deltas) + #comp1 = np.sum([ind.fitness.values for ind in offspring1]) + #print('regular',comp1,'time diminishing')#,comp1) +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + _update_history_and_hof(halloffame, history, population) + _record_stats(stats, logbook, gen, population, invalid_count) + # Select the next generation parents + ## + # was /4 + ## +<<<<<<< HEAD + parents = toolbox.select(population, int(mu / 2.5)) +======= + parents = toolbox.select(population, int(mu / 2)) + parents = filter_parents(parents) + +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + logger.info(logbook.stream) + + if cp_filename and cp_frequency and gen % cp_frequency == 0: + cp = dict( + population=population, + generation=gen, + parents=parents, + halloffame=halloffame, + history=history, + logbook=logbook, + rndstate=random.getstate(), + ) + pickle.dump(cp, open(cp_filename_tmp, "wb")) + if os.path.isfile(cp_filename_tmp): + shutil.copy(cp_filename_tmp, cp_filename) + logger.debug("Wrote checkpoint to %s", cp_filename) + current_prog = gen / ngen + prog_bar.progress(current_prog) + gen += 1 + stopping_params["gen"] = gen + pbar.update(1) + pbar.update(1) + pbar.close() + + return population, halloffame, logbook, history diff --git a/neuronunit/optimization/data_transport_container.py b/neuronunit/optimization/data_transport_container.py index cbf7efe95..e239ce7a5 100644 --- a/neuronunit/optimization/data_transport_container.py +++ b/neuronunit/optimization/data_transport_container.py @@ -1,30 +1,414 @@ +import numpy as np +import quantities as qt +import copy +from collections import OrderedDict +from sciunit import scores +from sciunit.scores.collections import ScoreArray +import sciunit +import pandas as pd +from jithub.models import model_classes +from typing import Any, Dict, List, Optional, Tuple, Type, Union, Text + class DataTC(object): - ''' - Data Transport Vessel + """ + --Synopsis: Data Transport Container + This Object class serves as a data type for storing rheobase search + attributes and apriori model parameters, + with the distinction that unlike the LEMS model this class + can be cheaply transported across HOSTS/CPUs + """ - This Object class serves as a data type for storing rheobase search - attributes and apriori model parameters, - with the distinction that unlike the NEURON model this class - can be cheaply transported across HOSTS/CPUs - ''' - def __init__(self): - self.lookup = {} + def model_default(self): + if self.backend is not None: + if self.attrs is None: + from neuronunit.optimization.model_parameters import ( + MODEL_PARAMS, + BPO_PARAMS, + ) + + if str("MAT") in self.backend: + self.backend_ref = str("MAT") + if str("IZHI") in self.backend: + self.backend_ref = str("IZHI") + if str("ADEXP") in self.backend: + self.backend_ref = str("ADEXP") + + self.attrs = { + k: np.mean(v) for k, v in MODEL_PARAMS[self.backend_ref].items() + } + self = DataTC(backend=self.backend, attrs=self.attrs) + model = self.dtc_to_model() + self.attrs = model._backend.default_attrs + del model + else: + model = self.dtc_to_model() + self.attrs = model._backend.default_attrs + del model + return None + + def __init__(self, attrs=None, backend=None, _backend=None): self.rheobase = None - self.previous = 0 - self.run_number = 0 - self.attrs = None - self.steps = None - self.name = None - self.results = None - self.fitness = None - self.score = None - self.boolean = False + self.attrs = attrs self.initiated = False - self.delta = [] - self.evaluated = False - self.results = {} - self.searched = [] - self.searchedd = {} - self.cached_attrs = {} - self.backend = None + self.backend = backend + self._backend = _backend + self.lookup = {} + if self.backend is not None: + if ( + str("MAT") in self.backend + or str("IZHI") in self.backend + or str("ADEXP") in self.backend + ): + self.jithub = True + else: + self.jithub = False + if attrs is None: + self.attrs = None + self.model_default() + else: + self.attrs = attrs + + def to_bpo_param(self, attrs: dict = {}) -> dict: + from bluepyopt.parameters import Parameter + + lop = {} + for k, v in attrs.items(): + p = Parameter(name=k, bounds=v, frozen=False) + lop[k] = p + self.param = lop + return lop + + def attrs_to_params(self): + params = self.attrs + for k, v in params.items(): + if np.round(v, 2) != 0: + params[k] = np.round(v, 2) + if k == "celltype": + params[k] = int(np.round(v, 0)) + return params + + def make_pretty(self, tests) -> pd.DataFrame: + + self.tests = tests + self.obs_preds = pd.DataFrame(columns=["observations", "predictions"]) + holding_obs = {t.name: t.observation["mean"] for t in self.tests} + grab_keys = [] + for t in self.tests: + if "value" in t.prediction.keys(): + grab_keys.append("value") + else: + grab_keys.append("mean") + holding_preds = { + t.name: t.prediction[k] + for t, k in zip(self.tests, grab_keys) + } + ## + # This step only partially undoes quantities annoyances. + ## + qt.quantity.PREFERRED = [qt.mV, qt.pA, qt.MOhm, qt.ms, qt.pF, qt.Hz / qt.pA] + + for k, v in holding_preds.items(): + if k in holding_obs.keys() and k in holding_preds: + v.rescale_preferred() + v = v.simplified + if np.round(v, 2) != 0: + v = np.round(v, 2) + + for k, v in holding_obs.items(): + if k in holding_obs.keys() and k in holding_preds: + + v.rescale_preferred() + v = v.simplified + if np.round(v, 2) != 0: + v = np.round(v, 2) + + for k, v in holding_preds.items(): + if k in holding_obs.keys() and k in holding_preds: + # units1 = holding_preds[k].units # v.units) + + units1 = holding_preds[k].rescale_preferred().units # v.units) + + holding_preds[k] = holding_preds[k].simplified + holding_preds[k] = holding_preds[k].rescale(units1) + if np.round(holding_preds[k], 2) != 0: + holding_preds[k] = np.round(holding_preds[k], 2) + + holding_obs[k] = holding_obs[k].simplified + holding_obs[k] = holding_obs[k].rescale(units1) + if np.round(holding_obs[k], 2) != 0: + holding_obs[k] = np.round(holding_obs[k], 2) + + temp_obs = pd.DataFrame([holding_obs], index=["observations"]) + temp_preds = pd.DataFrame([holding_preds], index=["predictions"]) + # like a score array but nicer reporting of test name instead of test data type. + try: + scores_ = [] + model = self.dtc_to_model() + for t in self.tests: + scores_.append(t.judge(model, prediction=t.prediction)) + + not_SA = { + t.name: np.round(score.raw, 2) for t, score in zip(self.tests, scores_) + } + temp_scores = pd.DataFrame([not_SA], index=["Z-Scores"]) + self.obs_preds = pd.concat([temp_obs, temp_preds, temp_scores]) + except: + self.obs_preds = pd.concat([temp_obs, temp_preds]) # , temp_scores]) + self.obs_preds = self.obs_preds.T + return self.obs_preds + + """ + def dtc_to_opt_man(self): + from neuronunit.optimization.optimization_management import OptMan + from collections import OrderedDict + from neuronunit.optimization.model_parameters import MODEL_PARAMS + + OM = OptMan(self.tests, self.backend) + OM.boundary_dict = MODEL_PARAMS[self.backend] + OM.backend = self.backend + OM.td = list(OrderedDict(OM.boundary_dict).keys()) + return OM + + def get_agreement(self): + self = self.self_evaluate() + OM = self.dtc_to_opt_man() + self = OM.get_agreement(self) + return self + """ + + def add_constant(self): + if self.constants is not None: + self.attrs.update(self.constants) + return # self.attrs + ''' + def format_test(self): + from neuronunit.optimisation.optimization_management import ( + switch_logic, + active_values, + passive_values, + ) + + # pre format the current injection dictionary based on pre computed + # rheobase values of current injection. + # This is much like the hooked method from the old get neab file. + self.protocols = {} + if not hasattr(self, "tests"): + self.tests = copy.copy(self.tests) + if hasattr(self.tests, "keys"): # is type(dict): + tests = [key for key in self.tests.values()] + self.tests = switch_logic(tests) # ,self.tests.use_rheobase_score) + else: + self.tests = switch_logic(self.tests) + + for v in self.tests: + k = v.name + self.protocols[k] = {} + if hasattr(v, "passive"): + if v.passive == False and v.active == True: + keyed = self.protocols[k] # .params + self.protocols[k] = active_values(keyed, self.rheobase) + + if str("APThresholdTest") in v.name and not self.threshold: + model = self.dtc_to_model() + model.attrs = model._backend.default_attrs + treshold = v.generate_prediction(model) + self.threshold = treshold + + if str("APThresholdTest") in v.name: + v.threshold = None + v.threshold = self.threshold + elif v.passive == True and v.active == False: + keyed = self.protocols[k] # .params + self.protocols[k] = passive_values(keyed) + + if v.name in str("RestingPotentialTest"): + self.protocols[k]["injected_square_current"] = {} + self.protocols[k]["injected_square_current"]["amplitude"] = 0.0 * qt.pA + keyed = v.params["injected_square_current"] + v.params["t_max"] = keyed["delay"] + keyed["duration"] + 200.0 * pq.ms + return self.tests + ''' + def dtc_to_model(self): + if ( + str("MAT") in self.backend + or str("IZHI") in self.backend + or str("ADEXP") in self.backend + ): + self.jithub = True + + else: + self.jithub = False + if self.attrs is None: + self.model_default() + if self.jithub: + if str("MAT") in self.backend: + model = model_classes.MATModel() + if str("IZHI") in self.backend: + model = model_classes.IzhiModel() + if str("ADEXP") in self.backend: + model = model_classes.ADEXPModel() + + model.set_attrs(self.attrs) + assert model.attrs == self.attrs + assert model._backend.attrs == self.attrs + #self.params = model.params = self.to_bpo_param(self.attrs) + assert len(self.attrs) + assert len(model.attrs) + return model + + else: + from neuronunit.models.very_reduced_sans_lems import VeryReducedModel + + model = VeryReducedModel(backend=self.backend, attrs=self.attrs) + + model.set_attrs(self.attrs) + if model.attrs is None: + model.attrs = self.attrs + return model + + def dtc_to_sciunit_model(self): + model = self.dtc_to_model() + sciunit_model = model._backend.as_sciunit_model() + return sciunit_model + + def dtc_to_gene(self, subset_params=None): + """ + These imports probably need to be contained to stop recursive imports + """ + from deap import base + import array + from deap import creator + + creator.create( + "FitnessMin", base.Fitness, weights=tuple(-1.0 for i in range(0, 10)) + ) + creator.create( + "Individual", array.array, typecode="d", fitness=creator.FitnessMin + ) + + # from neuronunit.optimisation.optimization_management import WSListIndividual + # print('warning translation dictionary should be used, to garuntee correct attribute order from random access dictionaries') + if "IZHI" in self.backend: + self.attrs.pop("dt", None) + self.attrs.pop("Iext", None) + if subset_params: + pre_gene = OrderedDict() + for k in subset_params: + pre_gene[k] = self.attrs[k] + else: + pre_gene = OrderedDict(self.attrs) + pre_gene = list(pre_gene.values()) + gene = creator.Individual(pre_gene) + return gene + + def judge_test(self, index=0): + model = self.dtc_to_model() + if not hasattr(self, "tests"): + print("warning dtc object does not contain NU-tests yet") + return dtc + + ts = self.tests + # this_test = ts[index] + if not hasattr(self, "preds"): + self.preds = {} + for this_test in self.tests: + this_test.setup_protocol(model) + pred = this_test.extract_features(model, this_test.get_result(model)) + pred1 = this_test.generate_prediction(model) + self.preds[this_test.name] = pred + return self.preds + + """ + def jt_ratio(self,index=0): + from sciunit import scores + model = self.dtc_to_model() + if not hasattr(self,'tests'): + print('warning dtc object does not contain NU-tests yet') + return dtc + + ts = self.tests + #this_test = ts[index] + if not hasattr(self,'preds'): + self.preds = {} + tests = self.format_test() + for this_test in self.tests: + if this_test.passive: + this_test.params['injected_square_current'] = {} + this_test.params['injected_square_current']['amplitude'] = -10*qt.pA + + this_test.params['injected_square_current']['duration'] = 1000*qt.ms + this_test.params['injected_square_current']['delay'] = 200*qt.ms + + this_test.setup_protocol(model) + pred = this_test.extract_features(model,this_test.get_result(model)) + else: + this_test.params['injected_square_current']['amplitude'] = self.rheobase + this_test.params['injected_square_current']['duration'] = 1000*qt.ms + this_test.params['injected_square_current']['delay'] = 200*qt.ms + + pred = this_test.generate_prediction(model) + #self.preds[this_test.name] = pred + ratio_type = scores.RatioScore + temp = copy.copy(this_test.score_type) + this_test.score_type = ratio_type + try: + #print(this_test.name) + self.rscores[rscores.name] = this_test.compute_score(this_test.observation,pred) + + except: + this_test.score_type = temp + #self.rscores = this_test.compute_score(model) + self.rscores[rscores.name] = this_test.compute_score(this_test.observation,pred) + + self.failed = {} + self.failed['pred'] = pred + self.failed['observation'] = this_test.observation + + return self.preds + """ + + def check_params(self): + self.judge_test() + + return self.preds + + def plot_obs(self, ow): + """ + assuming a waveform exists (observed waved-form) plot to terminal with ascii + This is useful for debugging new backends, in bash big/fast command line orientated optimization routines. + """ + + t = [float(f) for f in ow.times] + v = [float(f) for f in ow.magnitude] + fig = apl.figure() + fig.plot( + t, + v, + label=str("observation waveform from inside dtc: "), + width=100, + height=20, + ) + fig.show() + + def iap(self, tests=None): + """ + Inject and plot to terminal with ascii + This is useful for debugging new backends, in bash big/fast command line orientated optimization routines. + """ + from neuronunit.optimisation import optimization_management as om_ + + if tests is not None: + self.tests = tests + + OM = self.dtc_to_opt_man() + self = om_.dtc_to_rheo(self) + + model = self.dtc_to_model() + pms = uset_t.params + pms["injected_square_current"]["amplitude"] = self.rheobase + model.inject_square_current(pms["injected_square_current"]) + nspike = model.get_spike_train() + self.nspike = nspike + vm = model.get_membrane_potential() + return vm diff --git a/neuronunit/optimization/data_transport_container_orig.py b/neuronunit/optimization/data_transport_container_orig.py new file mode 100644 index 000000000..e239ce7a5 --- /dev/null +++ b/neuronunit/optimization/data_transport_container_orig.py @@ -0,0 +1,414 @@ +import numpy as np +import quantities as qt +import copy +from collections import OrderedDict +from sciunit import scores +from sciunit.scores.collections import ScoreArray +import sciunit +import pandas as pd +from jithub.models import model_classes +from typing import Any, Dict, List, Optional, Tuple, Type, Union, Text + + +class DataTC(object): + """ + --Synopsis: Data Transport Container + This Object class serves as a data type for storing rheobase search + attributes and apriori model parameters, + with the distinction that unlike the LEMS model this class + can be cheaply transported across HOSTS/CPUs + """ + + def model_default(self): + if self.backend is not None: + if self.attrs is None: + from neuronunit.optimization.model_parameters import ( + MODEL_PARAMS, + BPO_PARAMS, + ) + + if str("MAT") in self.backend: + self.backend_ref = str("MAT") + if str("IZHI") in self.backend: + self.backend_ref = str("IZHI") + if str("ADEXP") in self.backend: + self.backend_ref = str("ADEXP") + + self.attrs = { + k: np.mean(v) for k, v in MODEL_PARAMS[self.backend_ref].items() + } + self = DataTC(backend=self.backend, attrs=self.attrs) + model = self.dtc_to_model() + self.attrs = model._backend.default_attrs + del model + else: + model = self.dtc_to_model() + self.attrs = model._backend.default_attrs + del model + return None + + def __init__(self, attrs=None, backend=None, _backend=None): + self.rheobase = None + self.attrs = attrs + self.initiated = False + self.backend = backend + self._backend = _backend + self.lookup = {} + if self.backend is not None: + if ( + str("MAT") in self.backend + or str("IZHI") in self.backend + or str("ADEXP") in self.backend + ): + self.jithub = True + else: + self.jithub = False + if attrs is None: + self.attrs = None + self.model_default() + else: + self.attrs = attrs + + def to_bpo_param(self, attrs: dict = {}) -> dict: + from bluepyopt.parameters import Parameter + + lop = {} + for k, v in attrs.items(): + p = Parameter(name=k, bounds=v, frozen=False) + lop[k] = p + self.param = lop + return lop + + def attrs_to_params(self): + params = self.attrs + for k, v in params.items(): + if np.round(v, 2) != 0: + params[k] = np.round(v, 2) + if k == "celltype": + params[k] = int(np.round(v, 0)) + return params + + def make_pretty(self, tests) -> pd.DataFrame: + + self.tests = tests + self.obs_preds = pd.DataFrame(columns=["observations", "predictions"]) + holding_obs = {t.name: t.observation["mean"] for t in self.tests} + grab_keys = [] + for t in self.tests: + if "value" in t.prediction.keys(): + grab_keys.append("value") + else: + grab_keys.append("mean") + holding_preds = { + t.name: t.prediction[k] + for t, k in zip(self.tests, grab_keys) + } + ## + # This step only partially undoes quantities annoyances. + ## + qt.quantity.PREFERRED = [qt.mV, qt.pA, qt.MOhm, qt.ms, qt.pF, qt.Hz / qt.pA] + + for k, v in holding_preds.items(): + if k in holding_obs.keys() and k in holding_preds: + v.rescale_preferred() + v = v.simplified + if np.round(v, 2) != 0: + v = np.round(v, 2) + + for k, v in holding_obs.items(): + if k in holding_obs.keys() and k in holding_preds: + + v.rescale_preferred() + v = v.simplified + if np.round(v, 2) != 0: + v = np.round(v, 2) + + for k, v in holding_preds.items(): + if k in holding_obs.keys() and k in holding_preds: + # units1 = holding_preds[k].units # v.units) + + units1 = holding_preds[k].rescale_preferred().units # v.units) + + holding_preds[k] = holding_preds[k].simplified + holding_preds[k] = holding_preds[k].rescale(units1) + if np.round(holding_preds[k], 2) != 0: + holding_preds[k] = np.round(holding_preds[k], 2) + + holding_obs[k] = holding_obs[k].simplified + holding_obs[k] = holding_obs[k].rescale(units1) + if np.round(holding_obs[k], 2) != 0: + holding_obs[k] = np.round(holding_obs[k], 2) + + temp_obs = pd.DataFrame([holding_obs], index=["observations"]) + temp_preds = pd.DataFrame([holding_preds], index=["predictions"]) + # like a score array but nicer reporting of test name instead of test data type. + try: + scores_ = [] + model = self.dtc_to_model() + for t in self.tests: + scores_.append(t.judge(model, prediction=t.prediction)) + + not_SA = { + t.name: np.round(score.raw, 2) for t, score in zip(self.tests, scores_) + } + temp_scores = pd.DataFrame([not_SA], index=["Z-Scores"]) + self.obs_preds = pd.concat([temp_obs, temp_preds, temp_scores]) + except: + self.obs_preds = pd.concat([temp_obs, temp_preds]) # , temp_scores]) + self.obs_preds = self.obs_preds.T + return self.obs_preds + + """ + def dtc_to_opt_man(self): + from neuronunit.optimization.optimization_management import OptMan + from collections import OrderedDict + from neuronunit.optimization.model_parameters import MODEL_PARAMS + + OM = OptMan(self.tests, self.backend) + OM.boundary_dict = MODEL_PARAMS[self.backend] + OM.backend = self.backend + OM.td = list(OrderedDict(OM.boundary_dict).keys()) + return OM + + def get_agreement(self): + self = self.self_evaluate() + OM = self.dtc_to_opt_man() + self = OM.get_agreement(self) + return self + """ + + def add_constant(self): + if self.constants is not None: + self.attrs.update(self.constants) + return # self.attrs + ''' + def format_test(self): + from neuronunit.optimisation.optimization_management import ( + switch_logic, + active_values, + passive_values, + ) + + # pre format the current injection dictionary based on pre computed + # rheobase values of current injection. + # This is much like the hooked method from the old get neab file. + self.protocols = {} + if not hasattr(self, "tests"): + self.tests = copy.copy(self.tests) + if hasattr(self.tests, "keys"): # is type(dict): + tests = [key for key in self.tests.values()] + self.tests = switch_logic(tests) # ,self.tests.use_rheobase_score) + else: + self.tests = switch_logic(self.tests) + + for v in self.tests: + k = v.name + self.protocols[k] = {} + if hasattr(v, "passive"): + if v.passive == False and v.active == True: + keyed = self.protocols[k] # .params + self.protocols[k] = active_values(keyed, self.rheobase) + + if str("APThresholdTest") in v.name and not self.threshold: + model = self.dtc_to_model() + model.attrs = model._backend.default_attrs + treshold = v.generate_prediction(model) + self.threshold = treshold + + if str("APThresholdTest") in v.name: + v.threshold = None + v.threshold = self.threshold + elif v.passive == True and v.active == False: + keyed = self.protocols[k] # .params + self.protocols[k] = passive_values(keyed) + + if v.name in str("RestingPotentialTest"): + self.protocols[k]["injected_square_current"] = {} + self.protocols[k]["injected_square_current"]["amplitude"] = 0.0 * qt.pA + keyed = v.params["injected_square_current"] + v.params["t_max"] = keyed["delay"] + keyed["duration"] + 200.0 * pq.ms + return self.tests + ''' + def dtc_to_model(self): + if ( + str("MAT") in self.backend + or str("IZHI") in self.backend + or str("ADEXP") in self.backend + ): + self.jithub = True + + else: + self.jithub = False + if self.attrs is None: + self.model_default() + if self.jithub: + if str("MAT") in self.backend: + model = model_classes.MATModel() + if str("IZHI") in self.backend: + model = model_classes.IzhiModel() + if str("ADEXP") in self.backend: + model = model_classes.ADEXPModel() + + model.set_attrs(self.attrs) + assert model.attrs == self.attrs + assert model._backend.attrs == self.attrs + #self.params = model.params = self.to_bpo_param(self.attrs) + assert len(self.attrs) + assert len(model.attrs) + return model + + else: + from neuronunit.models.very_reduced_sans_lems import VeryReducedModel + + model = VeryReducedModel(backend=self.backend, attrs=self.attrs) + + model.set_attrs(self.attrs) + if model.attrs is None: + model.attrs = self.attrs + return model + + def dtc_to_sciunit_model(self): + model = self.dtc_to_model() + sciunit_model = model._backend.as_sciunit_model() + return sciunit_model + + def dtc_to_gene(self, subset_params=None): + """ + These imports probably need to be contained to stop recursive imports + """ + from deap import base + import array + from deap import creator + + creator.create( + "FitnessMin", base.Fitness, weights=tuple(-1.0 for i in range(0, 10)) + ) + creator.create( + "Individual", array.array, typecode="d", fitness=creator.FitnessMin + ) + + # from neuronunit.optimisation.optimization_management import WSListIndividual + # print('warning translation dictionary should be used, to garuntee correct attribute order from random access dictionaries') + if "IZHI" in self.backend: + self.attrs.pop("dt", None) + self.attrs.pop("Iext", None) + if subset_params: + pre_gene = OrderedDict() + for k in subset_params: + pre_gene[k] = self.attrs[k] + else: + pre_gene = OrderedDict(self.attrs) + pre_gene = list(pre_gene.values()) + gene = creator.Individual(pre_gene) + return gene + + def judge_test(self, index=0): + model = self.dtc_to_model() + if not hasattr(self, "tests"): + print("warning dtc object does not contain NU-tests yet") + return dtc + + ts = self.tests + # this_test = ts[index] + if not hasattr(self, "preds"): + self.preds = {} + for this_test in self.tests: + this_test.setup_protocol(model) + pred = this_test.extract_features(model, this_test.get_result(model)) + pred1 = this_test.generate_prediction(model) + self.preds[this_test.name] = pred + return self.preds + + """ + def jt_ratio(self,index=0): + from sciunit import scores + model = self.dtc_to_model() + if not hasattr(self,'tests'): + print('warning dtc object does not contain NU-tests yet') + return dtc + + ts = self.tests + #this_test = ts[index] + if not hasattr(self,'preds'): + self.preds = {} + tests = self.format_test() + for this_test in self.tests: + if this_test.passive: + this_test.params['injected_square_current'] = {} + this_test.params['injected_square_current']['amplitude'] = -10*qt.pA + + this_test.params['injected_square_current']['duration'] = 1000*qt.ms + this_test.params['injected_square_current']['delay'] = 200*qt.ms + + this_test.setup_protocol(model) + pred = this_test.extract_features(model,this_test.get_result(model)) + else: + this_test.params['injected_square_current']['amplitude'] = self.rheobase + this_test.params['injected_square_current']['duration'] = 1000*qt.ms + this_test.params['injected_square_current']['delay'] = 200*qt.ms + + pred = this_test.generate_prediction(model) + #self.preds[this_test.name] = pred + ratio_type = scores.RatioScore + temp = copy.copy(this_test.score_type) + this_test.score_type = ratio_type + try: + #print(this_test.name) + self.rscores[rscores.name] = this_test.compute_score(this_test.observation,pred) + + except: + this_test.score_type = temp + #self.rscores = this_test.compute_score(model) + self.rscores[rscores.name] = this_test.compute_score(this_test.observation,pred) + + self.failed = {} + self.failed['pred'] = pred + self.failed['observation'] = this_test.observation + + return self.preds + """ + + def check_params(self): + self.judge_test() + + return self.preds + + def plot_obs(self, ow): + """ + assuming a waveform exists (observed waved-form) plot to terminal with ascii + This is useful for debugging new backends, in bash big/fast command line orientated optimization routines. + """ + + t = [float(f) for f in ow.times] + v = [float(f) for f in ow.magnitude] + fig = apl.figure() + fig.plot( + t, + v, + label=str("observation waveform from inside dtc: "), + width=100, + height=20, + ) + fig.show() + + def iap(self, tests=None): + """ + Inject and plot to terminal with ascii + This is useful for debugging new backends, in bash big/fast command line orientated optimization routines. + """ + from neuronunit.optimisation import optimization_management as om_ + + if tests is not None: + self.tests = tests + + OM = self.dtc_to_opt_man() + self = om_.dtc_to_rheo(self) + + model = self.dtc_to_model() + pms = uset_t.params + pms["injected_square_current"]["amplitude"] = self.rheobase + model.inject_square_current(pms["injected_square_current"]) + nspike = model.get_spike_train() + self.nspike = nspike + vm = model.get_membrane_potential() + return vm diff --git a/neuronunit/optimization/exhaustive_search.py b/neuronunit/optimization/exhaustive_search.py deleted file mode 100644 index e7a09ae9c..000000000 --- a/neuronunit/optimization/exhaustive_search.py +++ /dev/null @@ -1,133 +0,0 @@ - -from neuronunit.optimization import get_neab -tests = get_neab.tests - - -def sample_points(iter_dict, npoints=3): - import numpy as np - replacement={} - for k,v in iter_dict.items(): - sample_points = list(np.linspace(v.max(),v.min(),npoints)) - replacement[k] = sample_points - return replacement - - -def create_refined_grid(best_point,point1,point2): - ''' - Can be used for creating a second pass fine grained grid - ''' - - # This function reports on the deltas brute force obtained versus the GA found attributes. - #from neuronunit.optimization import model_parameters as modelp - from sklearn.grid_search import ParameterGrid - #mp = modelp.model_params - new_search_interval = {} - for k,v in point1.attrs.items(): - higher = max(float(point1.attrs[k]),float(v), point2.attrs[k]) - lower = min(float(point1.attrs[k]),float(v), point2.attrs[k]) - temp = list(np.linspace(lower,higher,10)) - new_search_interval[k] = temp[1:-2] # take only the middle two points - # discard edge points, as they are already well searched/represented. - grid = list(ParameterGrid(new_search_interval)) - return grid - -def update_dtc_grid(item_of_iter_list): - from neuronunit.optimization import data_transport_container - import copy - dtc = data_transport_container.DataTC() - from copy import deepcopy - dtc.attrs = deepcopy(item_of_iter_list) - dtc.scores = {} - dtc.rheobase = None - dtc.evaluated = False - dtc.backend = 'NEURON' - return dtc - -def create_grid(npoints=3,nparams=7,provided_keys=None): - ''' - Description, create a grid of evenly spaced samples in model parameters to search over. - Inputs: npoints, type: Integer: number of sample points per parameter - nparams, type: Integer: number of parameters to use, conflicts, with next argument. - nparams, iterates through a list of parameters, and assigns the nparams to use via stupid counting. - provided keys: explicitly define state the model parameters that are used to create the grid of samples, by - keying into an existing of parameters. - - This method needs the user of the method to declare a dictionary of model parameters in a path: - neuronunit.optimization.model_parameters. - - Miscallenous, once grid created by this function - has been evaluated using neuronunit it can be used for informing a more refined second pass fine grained grid - ''' - - from neuronunit.optimization import model_parameters as modelp - from sklearn.grid_search import ParameterGrid - mp = modelp.model_params - # smaller is a dictionary thats not necessarily as big - # as the grid defined in the model_params file. Its not necessarily - # a smaller dictionary, if it is smaller it is reduced by reducing sampling - # points. - smaller = {} - smaller = sample_points(mp, npoints=npoints) - - if type(provided_keys) is type(None): - - key_list = list(smaller.keys()) - reduced_key_list = key_list[0:nparams] - else: - reduced_key_list = list(provided_keys) - - # subset is reduced, by reducing parameter keys. - subset = { k:smaller[k] for k in reduced_key_list } - grid = list(ParameterGrid(subset)) - return grid - -''' -def dtc_to_rheo(dtc): - from neuronunit.optimization import get_neab - import copy - dtc = copy.copy(dtc) - dtc.scores = {} - from neuronunit.models.reduced import ReducedModel - model = ReducedModel(get_neab.LEMS_MODEL_PATH,name=str('vanilla'),backend=('NEURON',{'DTC':dtc})) - model.set_attrs(dtc.attrs) - model.backend = dtc.backend - model.rheobase = None - rbt = get_neab.tests[0] - score = rbt.judge(model,stop_on_error = False, deep_error = True) - dtc.scores[str(rbt)] = score.sort_key - dtc.rheobase = score.prediction - return dtc - -def update_dtc_pop(item_of_iter_list): - from neuronunit.optimization import data_transport_container - import copy - dtc = data_transport_container.DataTC() - from copy import deepcopy - dtc.attrs = deepcopy(item_of_iter_list) - dtc.scores = {} - dtc.rheobase = None - dtc.evaluated = False - dtc.backend = 'NEURON' - return dtc -''' -def run_grid(npoints,nparams,provided_keys=None): - # not all models will produce scores, since models with rheobase <0 are filtered out. - from neuronunit.optimization.optimization_management import nunit_evaluation - from neuronunit.optimization.optimization_management import update_dtc_pop - - grid_points = create_grid(npoints = npoints,nparams = nparams,vprovided_keys = provided_keys ) - import dask.bag as db - b = db.bag(grid_points) - dtcpop = list(db.map(update_dtc_pop,b).compute()) - print(dtcpop) - # The mapping of rheobase search needs to be serial mapping for now, since embedded in it's functionality is a - # probably this can be bypassed in the future by using zeromq's Client (by using ipyparallel's core module/code base more directly) - dtcpop = list(map(dtc_to_rheo,dtcpop)) - print(dtcpop) - - filtered_dtcpop = list(filter(lambda dtc: dtc.rheobase['value'] > 0.0 , dtcpop)) - dtcpop = list(db.map(nunit_evaluation,filtered_dtcpop).compute()) - dtcpop = list(dtcpop) - dtcpop = list(filter(lambda dtc: type(dtc.scores['RheobaseTestP']) is not type(None), dtcpop)) - - return dtcpop diff --git a/neuronunit/optimization/get_neab.py b/neuronunit/optimization/get_neab.py deleted file mode 100644 index 17857bab2..000000000 --- a/neuronunit/optimization/get_neab.py +++ /dev/null @@ -1,64 +0,0 @@ -import os -import sciunit -import neuronunit -from neuronunit import aibs -import pickle -from neuronunit import tests as _, neuroelectro -#from neuronunit import tests as nu_tests, neuroelectro -from neuronunit.tests import passive, waveform, fi - -def update_amplitude(test,tests,score): - rheobase = score.prediction['value'] - for i in [4,5,6]: - tests[i].params['injected_square_current']['amplitude'] = rheobase*1.01 # I feel that 1.01 may lead to more than one spike - return - -def impute_criteria(observations_donar,observations_acceptor): - # - # - - for index,oa in observations_acceptor.items(): - for k,v in oa.items(): - if k == 'std' and v == 0.0: - oa[k] = observations_donar[index][k] - return observations_acceptor - - -def get_neuron_criteria(cell_id,file_name = None,observation = None): - # Use neuroelectro experimental obsevations to find test - # criterion that will be used to inform scientific unit testing. - # some times observations are not sourced from neuroelectro, - # but they are imputated or borrowed from other TestSuite - # if that happens make test objections using observations external - # to this method, and provided as a method argument. - tests = [] - observations = {} - test_classes = [fi.RheobaseTestP, - passive.InputResistanceTest, - passive.TimeConstantTest, - passive.CapacitanceTest, - passive.RestingPotentialTest, - waveform.InjectedCurrentAPWidthTest, - waveform.InjectedCurrentAPAmplitudeTest, - waveform.InjectedCurrentAPThresholdTest]#, - if observation is not None: - for index, t in enumerate(test_classes): - obs = observation[t.ephysprop_name] - tests.append(t(obs)) - observations[t.ephysprop_name] = obs - else: - for index, t in enumerate(test_classes): - obs = t.neuroelectro_summary_observation(cell_id) - tests.append(t(obs)) - observations[t.ephysprop_name] = obs - - hooks = {tests[0]:{'f':update_amplitude}} #This is a trick to dynamically insert the method - #update amplitude at the location in sciunit thats its passed to, without any loss of generality. - #suite = sciunit.TestSuite("vm_suite",tests) - - if file_name is not None: - file_name = file_name +'.p' - with open(file_name, 'wb') as f: - pickle.dump(tests, f) - - return tests,observations diff --git a/neuronunit/optimization/model_parameters.py b/neuronunit/optimization/model_parameters.py index 6ff7cbb62..2cff2f482 100644 --- a/neuronunit/optimization/model_parameters.py +++ b/neuronunit/optimization/model_parameters.py @@ -1,34 +1,191 @@ import numpy as np +import os +from collections import OrderedDict +import collections +import numpy as np + +# import pickle + + +def to_bpo_param(attrs): + from bluepyopt.parameters import Parameter + + lop = {} + for k, v in attrs.items(): + temp = tuple(sorted(v)) + p = Parameter(name=k, bounds=temp[:], frozen=False) + lop[k] = p + return lop + + +def check_if_param_stradles_boundary(opt, model_type): + for k, v in MODEL_PARAMS[model_type].items(): + print(v, opt.attrs[k], k) + + +MODEL_PARAMS = {} +THIS_DIR = os.path.dirname(os.path.realpath(__file__)) +path_params = {} +path_params["model_path"] = os.path.realpath( + os.path.join(THIS_DIR, "..", "models", "NeuroML2", "LEMS_2007One.xml") +) +# Which Parameters +# https://www.izhikevich.org/publications/spikes.htm +type2007 = collections.OrderedDict( + [ + # C k vr vt vpeak a b c d celltype + ("RS", (100, 0.7, -60, -40, 35, 0.01, -2, -50, 100, 3)), + ("IB", (150, 1.2, -75, -45, 50, 0.1, 5, -56, 130, 3)), + ("TC", (200, 1.6, -60, -50, 35, 0.1, 15, -60, 10, 6)), + ("TC_burst", (200, 1.6, -60, -50, 35, 0.1, 15, -60, 10, 6)), + ("LTS", (100, 1.0, -56, -42, 40, 0.01, 8, -53, 20, 4)), + ("RTN", (40, 0.25, -65, -45, 0, 0.015, 10, -55, 50, 7)), + ("FS", (20, 1, -55, -40, 25, 0.2, -2, -45, -55, 5)), + ("CH", (50, 1.5, -60, -40, 25, 0.01, 1, -40, 150, 3)), + ] +) +temp = {k: [] for k in ["C", "k", "vr", "vt", "vPeak", "a", "b", "c", "d", "celltype"]} +for i, k in enumerate(temp.keys()): + for v in type2007.values(): + temp[k].append(v[i]) +explore_param = {k: (np.min(v), np.max(v)) for k, v in temp.items()} +IZHI_PARAMS = {k: sorted(v) for k, v in explore_param.items()} + +IZHI_PARAMS = OrderedDict(IZHI_PARAMS) +MODEL_PARAMS["IZHI"] = IZHI_PARAMS +# Fast spiking cannot be reproduced as it requires modifications to the standard Izhi equation, +# which are expressed in this mod file. +# https://github.com/OpenSourceBrain/IzhikevichModel/blob/master/NEURON/izhi2007b.mod +""" +depricated +trans_dict = OrderedDict([(k,[]) for k in ['C','k','vr','vt','vPeak','a','b','c','d']]) +for i,k in enumerate(trans_dict.keys()): + for v in type2007.values(): + trans_dict[k].append(v[i]) +reduced_cells = OrderedDict([(k,[]) for k in ['RS','IB','LTS','TC','TC_burst']]) +for index,key in enumerate(reduced_cells.keys()): + reduced_cells[key] = {} + for k,v in trans_dict.items(): + reduced_cells[key][k] = v[index] +""" + +# AdExp Model paramaters +BAE1 = {} +BAE1 = {} +BAE1["cm"] = 0.281 +BAE1["v_spike"] = -40.0 +BAE1["v_reset"] = -70.6 +BAE1["v_rest"] = -70.6 +BAE1["tau_m"] = 9.3667 +BAE1["a"] = 4.0 +BAE1["b"] = 0.0805 +BAE1["delta_T"] = 2.0 +BAE1["tau_w"] = 144.0 +BAE1["v_thresh"] = -50.4 +BAE1["spike_delta"] = 30 +# general range rule: +BAE1 = { + k: (np.mean(v) - np.abs(np.mean(v) * 2.5), np.mean(v) + np.mean(v) * 2.5) + for k, v in BAE1.items() +} +BAE1 = {k: sorted(v) for k, v in BAE1.items()} +# specific ad hoc adjustments: +# BAE1['v_spike']=[-70.0,-20] +# BAE1['v_reset'] = [1, 983.5] +# BAE1['v_rest'] = [-100, -35] +BAE1["v_thresh"] = [-65, -15] +BAE1["spike_delta"] = [1.25, 135] +BAE1["b"] = [0.01, 20] +BAE1["a"] = [0.01, 20] +BAE1["tau_w"] = [0.05, 354] # Tau_w 0, means very low adaption +BAE1["cm"] = [1, 983.5] +BAE1["v_spike"] = [-70.0, -20] +# BAE1['v_reset'] = [1, 983.5] +BAE1["v_reset"] = [-100, -25] +BAE1["v_rest"] = [-100, -35] +BAE1["v_thresh"] = [-65, -15] +BAE1["delta_T"] = [1, 10] +BAE1["tau_m"] = [0.01, 62.78345] +for v in BAE1.values(): + assert v[1] - v[0] != 0 +MODEL_PARAMS["ADEXP"] = BAE1 -model_params={} -model_params['vr'] = np.linspace(-75.0,-50.0,9) +# Multi TimeScale Adaptive Neuron +MATNEURON = { + "vr": -65.0, + "vt": -55.0, + "a1": 10, + "a2": 2, + "b": 0.001, + "w": 5, + "R": 10, + "tm": 10, + "t1": 10, + "t2": 200, + "tv": 5, + "tref": 2, +} +MATNEURON = { + k: ( + np.mean(v) - np.abs(np.mean(v) * 0.125), + np.mean(v) + np.abs(np.mean(v)) * 0.125, + ) + for k, v in MATNEURON.items() +} +MATNEURON["b"] = [0.0000001, 0.003] +MATNEURON["R"] = [2.5, 200] +MATNEURON["vr"] = [-85, -45] +MATNEURON["vt"] = [-60, -35] +MATNEURON["w"] = [0.125, 25] +MATNEURON["tm"] = [5, 250] +MATNEURON["tref"] = [0.5, 50] +MATNEURON["a1"] = [9, 55] +MATNEURON["a2"] = [0.5, 4] +MATNEURON["t1"] = [5, 15] +MATNEURON["t2"] = [150, 2089] +MATNEURON["tv"] = [5, 255] -model_params['a'] = np.linspace(0.0,0.945,9) -model_params['b'] = np.linspace(-5*10E-10,-0.25*10E-9,9) -model_params['vpeak'] =np.linspace(30.0,40.0,9) +MATNEURON = {k: sorted(v) for k, v in MATNEURON.items()} +MODEL_PARAMS["MAT"] = MATNEURON +for k, v in MATNEURON.items(): + assert v[1] - v[0] != 0 +GLIF_RANGE = { + "El_reference": [-0.08569469261169435, -0.05463626766204832], + "C": [3.5071610042390286e-13, 10 * 7.630189223327981e-10], + "init_threshold": [0.009908733642683513, 0.06939040414685865], + "th_inf": [0.009908733642683513, 0.04939040414685865], + "init_AScurrents": [0.0, 0.0], + "init_voltage": [-0.09, -0.01], + "spike_cut_length": [0.25, 94], + "El": [-0.08569469261169435, -0.05463626766204832], + "asc_tau_array": [[0.01, 0.0033333333333333335], [0.3333333333333333, 0.1]], + "R_input": [17743752.593817078, 10 * 1792774179.3647704], +} +GLIF_RANGE["th_adapt"] = [0.01, 1] # 0.1983063518904063] +GLIF_RANGE["C"] = [0, 10] +GLIF_RANGE.pop("init_AScurrents", None) +GLIF_RANGE.pop("dt", None) +GLIF_RANGE.pop("asc_tau_array", None) +GLIF_RANGE.pop("El", None) +GLIF_RANGE = {k: sorted(v) for k, v in GLIF_RANGE.items()} +MODEL_PARAMS["GLIF"] = GLIF_RANGE +BPO_PARAMS = {} +for k, v in MODEL_PARAMS.items(): + BPO_PARAMS[k] = to_bpo_param(v) -model_params['k'] = np.linspace(7.0E-4-+7.0E-5,7.0E-4+70E-5,9) -model_params['C'] = np.linspace(1.00000005E-4-1.00000005E-5,1.00000005E-4+1.00000005E-5,9) -model_params['c'] = np.linspace(-55,-60,9) -model_params['d'] = np.linspace(0.050,0.2,9) -model_params['v0'] = np.linspace(-75.0,-45.0,9) -model_params['vt'] = np.linspace(-50.0,-30.0,9) -''' -+model_params['vr'] = np.linspace(-95.0,-30.0,9) +""" +Depricated +l5_pc_keys = ['gNaTs2_tbar_NaTs2_t.apical', 'gSKv3_1bar_SKv3_1.apical', 'gImbar_Im.apical', 'gNaTa_tbar_NaTa_t.axonal', 'gNap_Et2bar_Nap_Et2.axonal', 'gK_Pstbar_K_Pst.axonal', 'gK_Tstbar_K_Tst.axonal', 'gSK_E2bar_SK_E2.axonal', 'gSKv3_1bar_SKv3_1.axonal', 'gCa_HVAbar_Ca_HVA.axonal', 'gCa_LVAstbar_Ca_LVAst.axonal', 'gamma_CaDynamics_E2.axonal', 'decay_CaDynamics_E2.axonal', 'gNaTs2_tbar_NaTs2_t.somatic', 'gSKv3_1bar_SKv3_1.somatic', 'gSK_E2bar_SK_E2.somatic', 'gCa_HVAbar_Ca_HVA.somatic', 'gCa_LVAstbar_Ca_LVAst.somatic', 'gamma_CaDynamics_E2.somatic', 'decay_CaDynamics_E2.somatic'] +l5_pc_values = [0.0009012730575340265, 0.024287352056036934, 0.0008315987398062784, 1.7100532387472567, 0.7671786030824507, 0.47339571930108143, 0.0025715065622581644, 0.024862299158354962, 0.7754822886266044, 0.0005560440082771592, 0.0020639185209852568, 0.013376906273759268, 207.56154268835758, 0.5154365543590191, 0.2565961138691978, 0.0024100296151316754, 0.0007416593834676707, 0.006240529502225737, 0.028595343511797353, 226.7501580822364] -+model_params['a'] = np.linspace(0.0,0.945,9) -+model_params['b'] = np.linspace(-3.5*10E-10,-0.5*10E-9,9) -+model_params['vpeak'] =np.linspace(0.0,80.0,9) +L5PC = OrderedDict() +for k,v in zip(l5_pc_keys,l5_pc_values): + L5PC[k] = sorted((v-0.1*v,v+0.1*v)) -+model_params['k'] = np.linspace(7.0E-4-+7.0E-5,7.0E-4+70E-5,9) -+model_params['C'] = np.linspace(1.00000005E-4-1.00000005E-5,1.00000005E-4+1.00000005E-5,9) -+model_params['c'] = np.linspace(-55,-60,9) -+model_params['d'] = np.linspace(0.050,0.2,9) -+model_params['v0'] = np.linspace(-85.0,-15.0,9) -+model_params['vt'] = np.linspace(-70.0,0.0,9) -''' +MODEL_PARAMS['L5PC'] = L5PC +""" diff --git a/neuronunit/optimization/neuronunit_to_bpo.py b/neuronunit/optimization/neuronunit_to_bpo.py new file mode 100644 index 000000000..d4b324b73 --- /dev/null +++ b/neuronunit/optimization/neuronunit_to_bpo.py @@ -0,0 +1,10 @@ +def make_passive_protocol(): + pass +def make_zero_current_protocol(): + pass +def make_unknown_rheobase_protocol(): + pass +def make_unknown_multispike_protocol(): + pass +def neuronunit_tests_to_bpo_protocols(): + pass diff --git a/neuronunit/optimization/optimization_management.py b/neuronunit/optimization/optimization_management.py index 41f637971..730c4168e 100644 --- a/neuronunit/optimization/optimization_management.py +++ b/neuronunit/optimization/optimization_management.py @@ -1,260 +1,1722 @@ -#import matplotlib # Its not that this file is responsible for doing plotting, but it calls many modules that are, such that it needs to pre-empt -# setting of an appropriate backend. -#matplotlib.use('agg') +# Its not that this file is responsible for doing plotting, +# but it calls many modules that are, such that it needs to pre-empt +import warnings +SILENT = True +if SILENT: + warnings.filterwarnings("ignore", message="H5pyDeprecationWarning") + warnings.filterwarnings("ignore") + + +# import time +from typing import Any, Dict, List, Optional, Tuple, Type, Union, Text +import dask +from tqdm import tqdm +import warnings +from neo import AnalogSignal +from elephant.spike_train_generation import threshold_detection import numpy as np -import dask.bag as db +import cython import pandas as pd -# Import get_neab has to happen exactly here. It has to be called only on -from neuronunit import tests -from neuronunit.optimization import get_neab -from neuronunit.models.reduced import ReducedModel -from neuronunit.optimization.model_parameters import model_params -from neuronunit.optimization.model_parameters import path_params +from collections import OrderedDict +from collections.abc import Iterable +import math +import numpy +import deap +from deap import creator +from deap import base +import array +import copy +from frozendict import frozendict +from itertools import repeat +import random +<<<<<<< HEAD -from neuronunit.optimization import get_neab -from pyneuroml import pynml -def write_opt_to_nml(path,param_dict): - ''' - Write optimimal simulation parameters back to NeuroML. - ''' - orig_lems_file_path = path_params['model_path'] - more_attributes = pynml.read_lems_file(orig_lems_file_path, - include_includes=True, - debug=False) +import quantities as pq + +pq.quantity.PREFERRED = [pq.mV, pq.pA, pq.MOhm, pq.ms, pq.pF, pq.Hz / pq.pA] + +import efel +import bluepyopt as bpop +import bluepyopt.ephys as ephys +from bluepyopt.parameters import Parameter + +import sciunit +from sciunit import TestSuite +from sciunit import scores +from sciunit.scores import RelativeDifferenceScore +from sciunit.utils import config_set +======= +import matplotlib.pyplot as plt +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + +config_set("PREVALIDATE", False) + +<<<<<<< HEAD +from jithub.models import model_classes + +from neuronunit.optimization.data_transport_container import DataTC +======= +import quantities as pq +pq.quantity.PREFERRED = [pq.mV, pq.pA, pq.MOhm, pq.ms, pq.pF, pq.Hz / pq.pA] + +import efel +import bluepyopt as bpop +import bluepyopt.ephys as ephys +from bluepyopt.parameters import Parameter + +import sciunit +from sciunit import TestSuite +from sciunit import scores +from sciunit.scores import RelativeDifferenceScore +from sciunit.utils import config_set +from sciunit.models import RunnableModel + +config_set("PREVALIDATE", False) + +from jithub.models import model_classes + +from neuronunit.models.optimization_model_layer import OptimizationModel +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f +from neuronunit.tests.base import AMPL, DELAY, DURATION +from neuronunit.tests.target_spike_current import ( + SpikeCountSearch, + SpikeCountRangeSearch, +) +import neuronunit.capabilities.spike_functions as sf +from neuronunit.optimization.model_parameters import MODEL_PARAMS, BPO_PARAMS +from neuronunit.tests.fi import RheobaseTest +from neuronunit.capabilities.spike_functions import get_spike_waveforms, spikes2widths +from neuronunit.tests import VmTest + +PASSIVE_DURATION = 500.0 * pq.ms +PASSIVE_DELAY = 200.0 * pq.ms +ALLEN_DURATION = 2000 * pq.ms +ALLEN_DELAY = 1000 * pq.ms + + +class TSD(dict): + """ + -- Synopsis: + Test Suite Dictionary class + A container for sciunit tests, Indexable by dictionary keys. + Contains a method called optimize. + """ + + def __init__(self, tests={}): + self.backend = None + if type(tests) is TestSuite: + tests = OrderedDict({t.name: t for t in tests.tests}) + if type(tests) is type(dict()): + pass + if type(tests) is type(list()): + tests = OrderedDict({t.name: t for t in tests}) + super(TSD, self).__init__() + self.update(tests) + + def display(self): + from IPython.display import display + + if hasattr(self, "ga_out"): +<<<<<<< HEAD + return display(self.ga_out["pf"][0].dtc.obs_preds) +======= + return display(self.ga_out["pf"][0].model.obs_preds) +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + else: + return None + + def to_pickleable_dict(self): + """ + -- Synopsis: + # A pickleable version of instance object. + # https://joblib.readthedocs.io/en/latest/ + + # This might work joblib.dump(self, filename + '.compressed', compress=True) + # somewhere in job lib there are tools for pickling more complex objects + # including simulation results. + """ + return {k: v for k, v in self.items()} + + +@cython.boundscheck(False) +@cython.wraparound(False) +def random_p(model_type: str = "") -> dict: + """ + -- Synopsis: generate random parameter sets. + This is used to create varied and unpredictible + simulated ephysiological data, which is used to test + robustness of optimization algorithms. + + --params: string specifying model type. + --output: a dictionary of parameters. + """ + ranges = MODEL_PARAMS[model_type] + date_int = int(time.time()) + numpy.random.seed(date_int) + random_param1 = {} # randomly sample a point in the viable parameter space. + for k in ranges.keys(): + sample = random.uniform(ranges[k][0], ranges[k][1]) + random_param1[k] = sample + return random_param1 + + +@cython.boundscheck(False) +@cython.wraparound(False) +def process_rparam( + model_type: str = "", free_parameters: dict = None +<<<<<<< HEAD +) -> Union[DataTC, dict]: +======= +) -> Union[OptimizationModel, dict]: +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + """ + -- Synopsis: generate random parameter sets. + This is used to create varied and unpredictible + simulated ephysiological data, which is used to test + robustness of optimization algorithms. + + --params: string specifying model type. +<<<<<<< HEAD + --output: a DataTC semi-model type instantiated +======= + --output: a OptimizationModel semi-model type instantiated +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + with the random model parameters. + """ + random_param = random_p(model_type) + random_param.pop("Iext", None) + if free_parameters is not None: + reduced_parameter_set = {} + for k in free_parameters: + reduced_parameter_set[k] = rp[k] + random_param = reduced_parameter_set +<<<<<<< HEAD + dsolution = DataTC(backend=backend, attrs=rp) + temp_model = dsolution.dtc_to_model() +======= + dsolution = OptimizationModel(backend=backend, attrs=rp) + temp_model = dsolution.model_to_model() +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + dsolution.attrs = temp_model.default_attrs + dsolution.attrs.update(rp) + return dsolution, random_param + + +<<<<<<< HEAD +def write_models_for_nml_db(dtc: DataTC): + with open(str(list(dtc.attrs.values())) + ".csv", "w") as writeFile: + df = pd.DataFrame([dtc.attrs]) +======= +def write_models_for_nml_db(model: RunnableModel): + with open(str(list(model.attrs.values())) + ".csv", "w") as writeFile: + df = pd.DataFrame([model.attrs]) +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + writer = csv.writer(writeFile) + writer.writerows(df) + + +def write_opt_to_nml(path: str, param_dict: dict): + """ + -- Inputs: desired file path, model parameters to encode in NeuroML2 + -- Outputs: NeuroML2 file. + -- Synopsis: Write optimimal simulation parameters back to NeuroML2. + """ + more_attributes = pynml.read_lems_file( + orig_lems_file_path, include_includes=True, debug=False + ) for i in more_attributes.components: new = {} - if str('izhikevich2007Cell') in i.type: - for k,v in i.parameters.items(): + if str("izhikevich2007Cell") in i.type: + for k, v in i.parameters.items(): units = v.split() if len(units) == 2: units = units[1] else: - units = 'mV' - new[k] = str(param_dict[k]) + str(' ') + str(units) + units = "mV" + new[k] = str(param_dict[k]) + str(" ") + str(units) i.parameters = new - more_attributes.export_to_file(path+'.nml') + fopen = open(path + ".nml", "w") + more_attributes.export_to_file(fopen) + fopen.close() return -def map_wrapper(function_item,list_items,other_args=None): - from dask.distributed import Client - import dask.bag as db - c = Client() - NCORES = len(c.ncores().values())-2 - b0 = db.from_sequence(list_items, npartitions=NCORES) - if other_args is not None: - list_items = list(db.map(function_item,b0,other_args).compute()) - else: - list_items = list(db.map(function_item,b0).compute()) - return list_items - -def dtc_to_rheo(xargs): - dtc,rtest = xargs - dtc.model_path = path_params['model_path'] - LEMS_MODEL_PATH = path_params['model_path'] - model = ReducedModel(LEMS_MODEL_PATH,name=str('vanilla'),backend='NEURON') - model.set_attrs(dtc.attrs) - dtc.scores = {} - dtc.score = {} - score = rtest.judge(model,stop_on_error = False, deep_error = True) - #if bool(model.get_spike_count() == 1 or model.get_spike_count() == 0) - if score.sort_key is not None: - dtc.scores[str(rtest)] = 1 - score.sort_key #pd.DataFrame([ ]) - dtc.rheobase = score.prediction - #assert dtc.rheobase is not None + + +<<<<<<< HEAD +#Should be Depricated, but is not. +def get_rh(dtc: DataTC, rtest_class: RheobaseTest, bind_vm: bool = False) -> DataTC: +======= +def get_rh(model: RunnableModel, rtest_class: RheobaseTest, bind_vm: bool = False) -> RunnableModel: +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + ''' + --Synpopsis: This approach should be redundant, but for + some reason this method works when others fail. + --args: +<<<<<<< HEAD + :param object dtc: + :param object Rheobase Test Class: + :-- returns: object dtc: +======= + :param object model: + :param object Rheobase Test Class: + :-- returns: object model: +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + -- Synopsis: This is used to recover/produce + a rheobase test class instance, + given unknown experimental observations. + ''' + place_holder = {"mean": None * pq.pA} +<<<<<<< HEAD + #backend_ = dtc.backend + rtest = RheobaseTest(observation=place_holder, name="RheobaseTest") + rtest.score_type = RelativeDifferenceScore + assert len(dtc.attrs) + model = dtc.dtc_to_model() +======= + #backend_ = model.backend + rtest = RheobaseTest(observation=place_holder, name="RheobaseTest") + rtest.score_type = RelativeDifferenceScore + assert len(model.attrs) + model = model.model_to_model() +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + rtest.params["injected_square_current"] = {} + rtest.params["injected_square_current"]["delay"] = DELAY + rtest.params["injected_square_current"]["duration"] = DURATION + #return rtest + +<<<<<<< HEAD + dtc.rheobase = rtest.generate_prediction(model)["value"] + if bind_vm: + temp_vm = model.get_membrane_potential() + dtc.vmrh = temp_vm + if np.isnan(np.min(temp_vm)): + # rheobase exists but the waveform is nuts. + # this is the fastest way to filter out a gene + dtc.rheobase = None return dtc -def nunit_evaluation(dtc,error_criterion): - # Inputs single data transport container modules, and neuroelectro observations that - # inform test error error_criterion - # Outputs Neuron Unit evaluation scores over error criterion - - dtc.model_path = path_params['model_path'] - LEMS_MODEL_PATH = path_params['model_path'] - assert dtc.rheobase is not None - from neuronunit.models.reduced import ReducedModel - #from neuronunit.optimization import get_neab - tests = error_criterion - model = ReducedModel(LEMS_MODEL_PATH,name=str('vanilla'),backend=('NEURON',{'DTC':dtc})) - model.set_attrs(dtc.attrs) - tests[0].prediction = dtc.rheobase - model.rheobase = dtc.rheobase['value'] - from dask import dataframe as dd - if dtc.score is None: - dtc.score = {} - - for k,t in enumerate(tests[1:-1]): - t.params = dtc.vtest[k] - print(t.params) - score = None - score = t.judge(model,stop_on_error = False, deep_error = False) - if score.sort_key is not None: - # dtc.scores.get(str(t), score.sort_key) - # dtc.score.get(str(t), score.sort_key-1) - dtc.scores[str(t)] = 1.0 - score.sort_key - print(str(t),score.sort_key) - if not hasattr(dtc,'score'): - dtc.score = {} - dtc.score[str(t)] = score.sort_key + + +def eval_rh(dtc: DataTC, rtest_class: RheobaseTest, bind_vm: bool = False) -> DataTC: +======= + model.rheobase = rtest.generate_prediction(model)["value"] + if bind_vm: + temp_vm = model.get_membrane_potential() + model.vmrh = temp_vm + if np.isnan(np.min(temp_vm)): + # rheobase exists but the waveform is nuts. + # this is the fastest way to filter out a gene + model.rheobase = None + return model + + + +def eval_rh(model: RunnableModel, rtest_class: RheobaseTest, bind_vm: bool = False) -> RunnableModel: +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + """ + --Synpopsis: This approach should be redundant, but for + some reason this method works when others fail. + --args: +<<<<<<< HEAD + :param object dtc: + :param object Rheobase Test Class: + :-- returns: object dtc: with rheobase solution stored as an updated + dtc object attribute +======= + :param object model: + :param object Rheobase Test Class: + :-- returns: object model: with rheobase solution stored as an updated + model object attribute +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + -- Synopsis: This is used to recover/produce + a rheobase test class instance, + given unknown experimental observations. + """ +<<<<<<< HEAD + assert len(dtc.attrs) + model = dtc.dtc_to_model() + rtest.params["injected_square_current"] = {} + rtest.params["injected_square_current"]["delay"] = DELAY + rtest.params["injected_square_current"]["duration"] = DURATION + dtc.rheobase = rtest.generate_prediction(model)["value"] + if bind_vm: + temp_vm = model.get_membrane_potential() + dtc.vmrh = temp_vm + if np.isnan(np.min(temp_vm)): + # rheobase exists but the waveform is nuts. + # this is the fastest way to filter out a gene + dtc.rheobase = None + return dtc + + +def get_new_rtest(dtc: DataTC) -> RheobaseTest: +======= + assert len(model.attrs) + model = model.model_to_model() + rtest.params["injected_square_current"] = {} + rtest.params["injected_square_current"]["delay"] = DELAY + rtest.params["injected_square_current"]["duration"] = DURATION + model.rheobase = rtest.generate_prediction(model)["value"] + if bind_vm: + temp_vm = model.get_membrane_potential() + model.vmrh = temp_vm + if np.isnan(np.min(temp_vm)): + # rheobase exists but the waveform is nuts. + # this is the fastest way to filter out a gene + model.rheobase = None + return model + + +def get_new_rtest(model: RunnableModel) -> RheobaseTest: +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + place_holder = {"mean": 10 * pq.pA} + f = RheobaseTest + rtest = f(observation=place_holder, name="RheobaseTest") + rtest.score_type = RelativeDifferenceScore + return rtest + + +<<<<<<< HEAD +def get_rtest(dtc: DataTC) -> RheobaseTest: + if not hasattr(dtc, "tests"): + rtest = get_new_rtest(dtc) + else: + if type(dtc.tests) is type(list()): + rtests = [t for t in dtc.tests if "rheo" in t.name.lower()] else: - pass + rtests = [v for k, v in dtc.tests.items() if "rheo" in str(k).lower()] + if len(rtests): + rtest = rtests[0] + else: + rtest = get_new_rtest(dtc) + return rtest + + +def dtc_to_rheo(dtc: DataTC, bind_vm: bool = False) -> DataTC: + """ + --Synopsis: If test taking data, and objects are present (observations etc). + Take the rheobase test and store it in the data transport container. + """ + if hasattr(dtc, "tests"): + if type(dtc.tests) is type({}) and str("RheobaseTest") in dtc.tests.keys(): + rtest = dtc.tests["RheobaseTest"] + else: + rtest = get_rtest(dtc) + else: + rtest = get_rtest(dtc) +======= +def get_rtest(model: RunnableModel) -> RheobaseTest: + if not hasattr(model, "tests"): + rtest = get_new_rtest(model) + else: + if type(model.tests) is type(list()): + rtests = [t for t in model.tests if "rheo" in t.name.lower()] + else: + rtests = [v for k, v in model.tests.items() if "rheo" in str(k).lower()] + if len(rtests): + rtest = rtests[0] + else: + rtest = get_new_rtest(model) + return rtest +def dtc_to_rheo(model: RunnableModel, bind_vm: bool = False) -> RunnableModel: + #--Synopsis: If test taking data, and objects are present (observations etc). + #Take the rheobase test and store it in the data transport container. + if hasattr(model, "tests"): + if type(model.tests) is type({}) and str("RheobaseTest") in model.tests.keys(): + rtest = model.tests["RheobaseTest"] + else: + rtest = get_rtest(model) + else: + rtest = get_rtest(model) + if rtest is None:# + # If neuronunit models are run without first + # defining neuronunit tests, we run only + # generate_prediction/extract_features methods. + # but still need to construct tests somehow + # test construction requires an observation. + model = get_rh(model,rtest) + model = eval_rh(model,rtest) + + if rtest is not None: + if isinstance(rtest, Iterable): + rtest = rtest[0] + model.rheobase = rtest.generate_prediction(model)["value"] + temp_vm = model.get_membrane_potential() + min = np.min(temp_vm) + if np.isnan(temp_vm.any()): + model.rheobase = None + if bind_vm: + model.vmrh = temp_vm + if rtest is None: + raise Exception("rheobase test is still None despite efforts") + # rheobase does exist but lets filter out this bad gene. + return model + +def model_to_rheo(model: RunnableModel, bind_vm: bool = False) -> RunnableModel: + """ + --Synopsis: If test taking data, and objects are present (observations etc). + Take the rheobase test and store it in the data transport container. + """ + if hasattr(model, "tests"): + if type(model.tests) is type({}) and str("RheobaseTest") in model.tests.keys(): + rtest = model.tests["RheobaseTest"] + else: + rtest = get_rtest(model) + else: + rtest = get_rtest(model) +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + if rtest is None:# + # If neuronunit models are run without first + # defining neuronunit tests, we run only + # generate_prediction/extract_features methods. + # but still need to construct tests somehow + # test construction requires an observation. +<<<<<<< HEAD + #if dtc.rheobase is None: + #dtc = get_rh(dtc,rtest) + dtc = get_rh(dtc,rtest) + dtc = eval_rh(dtc,rtest) + + if rtest is not None: + model = dtc.dtc_to_model() + if dtc.attrs is not None: + model.attrs = dtc.attrs + if isinstance(rtest, Iterable): + rtest = rtest[0] + dtc.rheobase = rtest.generate_prediction(model)["value"] + temp_vm = model.get_membrane_potential() + min = np.min(temp_vm) + if np.isnan(temp_vm.any()): + dtc.rheobase = None + if bind_vm: + dtc.vmrh = temp_vm + if rtest is None: + raise Exception("rheobase test is still None despite efforts") + # rheobase does exist but lets filter out this bad gene. return dtc + # else: + # otherwise, if no observation is available, or if rheobase test score is not desired. + # Just generate rheobase predictions, giving the models the freedom of rheobase + # discovery without test taking. + # dtc = get_rh(dtc, rtest, bind_vm=bind_vm) + # if bind_vm: + # dtc.vmrh = temp_vm + # return dtc +======= + model = get_rh(model,rtest) + model = eval_rh(model,rtest) + if rtest is not None: + if isinstance(rtest, Iterable): + rtest = rtest[0] + model.rheobase = rtest.generate_prediction(model)["value"] + temp_vm = model.get_membrane_potential() + min = np.min(temp_vm) + if np.isnan(temp_vm.any()): + model.rheobase = None + if bind_vm: + model.vmrh = temp_vm + if rtest is None: + raise Exception("rheobase test is still None despite efforts") + # rheobase does exist but lets filter out this bad gene. + return model -def evaluate(dtc): - fitness = [ 1.0 for i in range(0,len(dtc.scores.keys())) ] - for k,t in enumerate(dtc.scores.keys()): - fitness[k] = dtc.scores[str(t)]#.sort_key - return fitness[0],fitness[1],\ - fitness[2],fitness[3],\ - fitness[4],fitness[5],\ - fitness[6],#fitness[7], +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f -def get_trans_list(param_dict): - trans_list = [] - for i,k in enumerate(list(param_dict.keys())): - trans_list.append(k) - return trans_list -def format_test(xargs): - ''' - pre format the current injection dictionary based on pre computed - rheobase values of current injection. - This is much like the hooked method from the old get neab file. - ''' - dtc,tests = xargs - #import copy - import quantities as pq - #import copy - dtc.vtest = None - dtc.vtest = {} - #from neuronunit.optimization import get_neab - #tests = get_neab.tests - for k,v in enumerate(tests): - dtc.vtest[k] = {} - #dtc.vtest.get(k,{}) - dtc.vtest[k]['injected_square_current'] = {} - for k,v in enumerate(tests): - if k == 1 or k == 2 or k == 3: - # Negative square pulse current. - dtc.vtest[k]['injected_square_current']['duration'] = 100 * pq.ms - dtc.vtest[k]['injected_square_current']['amplitude'] = -10 *pq.pA - dtc.vtest[k]['injected_square_current']['delay'] = 30 * pq.ms - - if k == 0 or k == 4 or k == 5 or k == 6 or k == 7: - # Threshold current. - dtc.vtest[k]['injected_square_current']['duration'] = 1000 * pq.ms - dtc.vtest[k]['injected_square_current']['amplitude'] = dtc.rheobase['value'] - dtc.vtest[k]['injected_square_current']['delay'] = 250 * pq.ms # + 150 +def basic_expVar(trace1, trace2): + """ + https://github.com/AllenInstitute/GLIF_Teeter_et_al_2018/blob/master/query_biophys/query_biophys_expVar.py + --Synopsis: This is the fundamental calculation that is used in all different types of explained variation. + At a basic level, the explained variance is calculated between two traces. These traces can be PSTH's + or single spike trains that have been convolved with a kernel (in this case always a Gaussian) + --Args: + trace 1 & 2: 1D numpy array containing values of the trace. (This function requires numpy array + to ensure that this is not a multidemensional list.) + --Returns: + expVar: float value of explained variance + """ + + var_trace1 = np.var(trace1) + var_trace2 = np.var(trace2) + var_trace1_minus_trace2 = np.var(trace1 - trace2) + # Traces are the same in variance + if var_trace1_minus_trace2 == 0.0: + return 1.0 + else: + return (var_trace1 + var_trace2 - var_trace1_minus_trace2) / ( + var_trace1 + var_trace2 + ) + + +<<<<<<< HEAD +def train_length(dtc: DataTC) -> DataTC: + if not hasattr(dtc, "efel"): + dtc.efel = [{}] + vm = dtc.vm_soma + train_len = float(len(sf.get_spike_train(vm))) + dtc.efel["Spikecount"] = train_len return dtc +def multi_spiking_feature_extraction( + dtc: DataTC, solve_for_current: bool = None, efel_filter_iterable: List = None +) -> DataTC: +======= +def train_length(model: RunnableModel) -> RunnableModel: + if not hasattr(model, "efel"): + model.efel = [{}] + vm = model.vm_soma + train_len = float(len(sf.get_spike_train(vm))) + model.efel["Spikecount"] = train_len + return model -def update_dtc_pop(pop, td = None, backend = None): - ''' - inputs a population of genes/alleles, the population size MU, and an optional argument of a rheobase value guess - outputs a population of genes/alleles, a population of individual object shells, ie a pickleable container for gene attributes. - Rationale, not every gene value will result in a model for which rheobase is found, in which case that gene is discarded, however to - compensate for losses in gene population size, more gene samples must be tested for a successful return from a rheobase search. - If the tests return are successful these new sampled individuals are appended to the population, and then their attributes are mapped onto - corresponding virtual model objects. - ''' +def multi_spiking_feature_extraction( + model: RunnableModel, solve_for_current: bool = None, efel_filter_iterable: List = None +) -> RunnableModel: +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + """ + Perform multi spiking feature extraction + via EFEL because its fast + """ + if solve_for_current is None: +<<<<<<< HEAD + dtc = inject_model_soma(dtc) + if dtc.vm_soma is None: # or dtc.exclude is True: + return dtc + dtc = efel_evaluation(dtc, efel_filter_iterable) + dtc.vm_soma = None + else: + dtc = inject_model_soma(dtc, solve_for_current=solve_for_current) + + if dtc.vm_soma is None: # or dtc.exclude is True: + dtc.efel = None + return dtc + + dtc = efel_evaluation(dtc, efel_filter_iterable) + if hasattr(dtc, "efel"): + if dtc.efel is not None: + dtc = train_length(dtc) + + else: + dtc.efel = None + + return dtc + + +def constrain_ahp(vm_used: Any = object()) -> dict: + efel.reset() + efel.setThreshold(0) + trace3 = { + "T": [float(t) * 1000.0 for t in vm_used.times], + "V": [float(v) for v in vm_used.magnitude], + } + DURATION = 1100 * pq.ms + DELAY = 100 * pq.ms + trace3["stim_end"] = [float(DELAY) + float(DURATION)] + trace3["stim_start"] = [float(DELAY)] + simple_ahp_constraint_list = ["AHP_depth", "AHP_depth_abs", "AHP_depth_last"] + results = efel.getMeanFeatureValues( + [trace3], simple_ahp_constraint_list, raise_warnings=False + ) + return results + + +def exclude_non_viable_deflections(responses: dict = {}) -> float: + """ + Synopsis: reject waveforms that would otherwise score well but have + unrealistically huge AHP + """ + if responses["response"] is not None: + vm = responses["response"] + results = constrain_ahp(vm) + results = results[0] + if results["AHP_depth"] is None or np.abs(results["AHP_depth_abs"]) >= 80: + return 1000.0 + if np.abs(results["AHP_depth"]) >= 105: + return 1000.0 + if np.max(vm) >= 0: + snippets = get_spike_waveforms(vm) + widths = spikes2widths(snippets) + spike_train = threshold_detection(vm, threshold=0 * pq.mV) + if not len(spike_train): + return 1000.0 + + if (spike_train[0] + 2.5 * pq.ms) > vm.times[-1]: + too_long = True + return 1000.0 + if isinstance(widths, Iterable): + for w in widths: + if w >= 3.5 * pq.ms: + return 1000.0 + else: + width = widths + if width >= 2.0 * pq.ms: + return 1000.0 + if float(vm[-1]) == np.nan or np.isnan(vm[-1]): + return 1000.0 + if float(vm[-1]) >= 0.0: + return 1000.0 + assert vm[-1] < 0 * pq.mV + return 0 + + +class NUFeature_standard_suite(object): + def __init__(self, test, model): + self.test = test + print(self.test) + self.model = model + self.score_array = None + + def calculate_score(self, responses: dict = {}) -> float: + dtc = responses["dtc"] + model = dtc.dtc_to_model() + model.attrs = responses["params"] + self.test = initialise_test(self.test) + if self.test.active and responses["dtc"].rheobase is not None: + result = exclude_non_viable_deflections(responses) + if result != 0: + return result + self.test.prediction = self.test.generate_prediction(model) + if responses["rheobase"] is not None: + if self.test.prediction is not None: + score_gene = self.test.judge( + model, prediction=self.test.prediction, deep_error=True + ) + else: + return 1000.0 + else: + return 1000.0 + if not isinstance(type(score_gene), type(None)): + if not isinstance(score_gene, sciunit.scores.InsufficientDataScore): + try: + if not isinstance(type(score_gene.raw), type(None)): + lns = np.abs(np.float(score_gene.raw)) + else: + if not isinstance(type(score_gene.raw), type(None)): + # works 1/2 time that log_norm_score does not work + # more informative than nominal bad score 100 + lns = np.abs(np.float(score_gene.raw)) + # works 1/2 time that log_norm_score does not work + # more informative than nominal bad score 100 + + except: + lns = 1000 + else: + lns = 1000 + else: + lns = 1000 + if lns == np.inf or lns == np.nan: + lns = 1000 + + return lns + + +def make_evaluator( + nu_tests: Iterable, + PARAMS: Iterable, + experiment: str = str("Neocortex pyramidal cell layer 5-6"), + model_type: str = str("IZHI"), + score_type: Any = RelativeDifferenceScore, +) -> Union[Any, Any, Any, List[Any]]: + """ + --Synopsis: make a BluePyOpt genetic algorithm evaluator + ie an object that has a model attribute, + and a fitness calculation method. + Using these attributes the evaluator + can update parameters on the model, + and then can calculate appropriate objective + fitness/scores. The genetic algorithm can thus use this + object to evolve genes, but also the human user of the + GA can use the evaluator to find the fitness of any + particular model parameterization. + """ + + if type(nu_tests) is type(dict()): + nu_tests = list(nu_tests.values()) + if model_type == "IZHI": + simple_cell = model_classes.IzhiModel() + if model_type == "MAT": + simple_cell = model_classes.MATModel() + if model_type == "ADEXP": + simple_cell = model_classes.ADEXPModel() + simple_cell.params = PARAMS[model_type] + simple_cell.NU = True + simple_cell.name = model_type + experiment + objectives = [] + for tt in nu_tests: + feature_name = tt.name + tt.score_type = score_type + ft = NUFeature_standard_suite(tt, simple_cell) + objective = ephys.objectives.SingletonObjective(feature_name, ft) + objectives.append(objective) + + score_calc = ephys.objectivescalculators.ObjectivesCalculator(objectives) + sweep_protocols = [] + protocol = ephys.protocols.NeuronUnitAllenStepProtocol("onestep", [None], [None]) + sweep_protocols.append(protocol) + onestep_protocol = ephys.protocols.SequenceProtocol( + "onestep", protocols=sweep_protocols + ) + cell_evaluator = ephys.evaluators.CellEvaluator( + cell_model=simple_cell, + param_names=list(copy.copy(BPO_PARAMS)[model_type].keys()), + fitness_protocols={onestep_protocol.name: onestep_protocol}, + fitness_calculator=score_calc, + sim="euler", + ) + simple_cell.params_by_names(copy.copy(BPO_PARAMS)[model_type].keys()) + return (cell_evaluator, simple_cell, score_calc, [tt.name for tt in nu_tests]) + + +def get_binary_file_downloader_html(bin_file_path, file_label="File"): + """ + --Synopsis: Intended to be used with streamlit/frontend. + Allows someone to download a pickle version of + python models that are output from the optimization process. + """ + with open(bin_file_path, "rb") as f: + data = f.read() + bin_str = base64.b64encode(data).decode() + href = f'Download {file_label}' + return href + + +def rescale(v: pq): + """ + --Synopsis: default rescaling quantities SI units are often not desirable. + this method helps circumnavigates quantities defaults, instead + the method tries to apply neuroscience relevant units as a first preference. + A constant "rescale preffered was defined as a CONSTANT at the top of this file" + --params: v is a qauntities type object (often a float multiplied by a si unit) + --returns rescaled units + """ + v.rescale_preferred() + v = v.simplified + if np.round(v, 2) != 0: + v = np.round(v, 2) + return v + + +def _opt_( + constraints, + PARAMS, + test_key, + model_value, + MU, + NGEN, + diversity, + use_streamlit=False, + score_type=RelativeDifferenceScore, +): + """ + --Synopsis: A private interface for optimizing with neuron unit ephys type tests + """ - import copy - import numpy as np - from deap import base - toolbox = base.Toolbox() - pop = [toolbox.clone(i) for i in pop ] - def transform(ind): - import dask.bag as db - from neuronunit.optimization.data_transport_container import DataTC - dtc = DataTC() - import neuronunit - LEMS_MODEL_PATH = str(neuronunit.__path__[0])+str('/models/NeuroML2/LEMS_2007One.xml') - if backend is not None: - dtc.backend = backend + if type(constraints) is not type(list()): + constraints = list(constraints.values()) + cell_evaluator, simple_cell, score_calc, test_names = make_evaluator( + constraints, PARAMS, test_key, model_type=model_value, score_type=score_type + ) + model_type = str("_best_fit_") + str(model_value) + "_" + str(test_key) + "_.p" + mut = 0.125 + cxp = 0.6125 + optimization = bpop.optimisations.DEAPOptimisation( + evaluator=cell_evaluator, + offspring_size=MU, + map_function=map, + selector_name=diversity, + mutpb=mut, + cxpb=cxp, + neuronunit=True, + ) + + final_pop, hall_of_fame, logs, hist = optimization.run(max_ngen=NGEN) + + best_ind = hall_of_fame[0] + best_ind_dict = cell_evaluator.param_dict(best_ind) + model = cell_evaluator.cell_model + cell_evaluator.param_dict(best_ind) + tests = constraints + obs_preds = [] + scores = [] + +======= + model = inject_model_soma(model) + if model.vm_soma is None: # or model.exclude is True: + return model + model = efel_evaluation(model, efel_filter_iterable) + model.vm_soma = None + else: + model = inject_model_soma(model, solve_for_current=solve_for_current) + + if model.vm_soma is None: # or model.exclude is True: + model.efel = None + return model + + model = efel_evaluation(model, efel_filter_iterable) + if hasattr(model, "efel"): + if model.efel is not None: + model = train_length(model) + + else: + model.efel = None + + return model + + +def constrain_ahp(vm_used: Any = object()) -> dict: + efel.reset() + efel.setThreshold(0) + trace3 = { + "T": [float(t) * 1000.0 for t in vm_used.times], + "V": [float(v) for v in vm_used.magnitude], + } + DURATION = 1100 * pq.ms + DELAY = 100 * pq.ms + trace3["stim_end"] = [float(DELAY) + float(DURATION)] + trace3["stim_start"] = [float(DELAY)] + simple_ahp_constraint_list = ["AHP_depth", "AHP_depth_abs", "AHP_depth_last"] + results = efel.getMeanFeatureValues( + [trace3], simple_ahp_constraint_list, raise_warnings=False + ) + return results + + +def exclude_non_viable_deflections(responses: dict = {}) -> float: + """ + Synopsis: reject waveforms that would otherwise score well but have + unrealistically huge AHP + """ + if responses["response"] is not None: + vm = responses["response"] + results = constrain_ahp(vm) + results = results[0] + if results["AHP_depth"] is None or np.abs(results["AHP_depth_abs"]) >= 80: + return 1000.0 + if np.abs(results["AHP_depth"]) >= 105: + return 1000.0 + if np.max(vm) >= 0: + snippets = get_spike_waveforms(vm) + widths = spikes2widths(snippets) + spike_train = threshold_detection(vm, threshold=0 * pq.mV) + if not len(spike_train): + return 1000.0 + + if (spike_train[0] + 2.5 * pq.ms) > vm.times[-1]: + too_long = True + return 1000.0 + if isinstance(widths, Iterable): + for w in widths: + if w >= 3.5 * pq.ms: + return 1000.0 + else: + width = widths + if width >= 2.0 * pq.ms: + return 1000.0 + if float(vm[-1]) == np.nan or np.isnan(vm[-1]): + return 1000.0 + if float(vm[-1]) >= 0.0: + return 1000.0 + assert vm[-1] < 0 * pq.mV + return 0 + + +class NUFeature_standard_suite(object): + def __init__(self, test, model): + self.test = test + print(self.test) + self.model = model + self.score_array = None + + def calculate_score(self, responses: dict = {}) -> float: + model = responses["model"] + #model = model.model_to_model() + model.attrs = responses["params"] + self.test = initialise_test(self.test) + if self.test.active and responses["model"].rheobase is not None: + result = exclude_non_viable_deflections(responses) + if result != 0: + return result + if str("RheobaseTest") in self.test.name: + self.test.target_number_spikes = None + self.test.target_number_spikes = 1 + + self.test.prediction = self.test.generate_prediction(model) + if responses["rheobase"] is not None: + + if self.test.prediction is not None: + score_gene = self.test.judge( + model, prediction=self.test.prediction, deep_error=True + ) + else: + return 1000.0 + else: + return 1000.0 + if not isinstance(type(score_gene), type(None)): + if not isinstance(score_gene, sciunit.scores.InsufficientDataScore): + try: + if not isinstance(type(score_gene.raw), type(None)): + lns = np.abs(np.float(score_gene.raw)) + else: + if not isinstance(type(score_gene.raw), type(None)): + # works 1/2 time that log_norm_score does not work + # more informative than nominal bad score 100 + lns = np.abs(np.float(score_gene.raw)) + # works 1/2 time that log_norm_score does not work + # more informative than nominal bad score 100 + + except: + lns = 1000 + else: + lns = 1000 + else: + lns = 1000 + if lns == np.inf or lns == np.nan: + lns = 1000 + + return lns + + +def make_evaluator( + nu_tests: Iterable, + PARAMS: Iterable, + experiment: str = str("Neocortex pyramidal cell layer 5-6"), + model_type: str = str("IZHI"), + score_type: Any = RelativeDifferenceScore, +) -> Union[Any, Any, Any, List[Any]]: + """ + --Synopsis: make a BluePyOpt genetic algorithm evaluator + ie an object that has a model attribute, + and a fitness calculation method. + Using these attributes the evaluator + can update parameters on the model, + and then can calculate appropriate objective + fitness/scores. The genetic algorithm can thus use this + object to evolve genes, but also the human user of the + GA can use the evaluator to find the fitness of any + particular model parameterization. + """ + + if type(nu_tests) is type(dict()): + nu_tests = list(nu_tests.values()) + if model_type == "IZHI": + simple_cell = model_classes.IzhiModel() + if model_type == "MAT": + simple_cell = model_classes.MATModel() + if model_type == "ADEXP": + simple_cell = model_classes.ADEXPModel() + simple_cell.params = PARAMS[model_type] + simple_cell.NU = True + simple_cell.name = model_type + experiment + objectives = [] + for tt in nu_tests: + feature_name = tt.name + tt.score_type = score_type + ft = NUFeature_standard_suite(tt, simple_cell) + objective = ephys.objectives.SingletonObjective(feature_name, ft) + objectives.append(objective) + + score_calc = ephys.objectivescalculators.ObjectivesCalculator(objectives) + sweep_protocols = [] + protocol = ephys.protocols.NeuronUnitAllenStepProtocol("onestep", [None], [None]) + sweep_protocols.append(protocol) + onestep_protocol = ephys.protocols.SequenceProtocol( + "onestep", protocols=sweep_protocols + ) + cell_evaluator = ephys.evaluators.CellEvaluator( + cell_model=simple_cell, + param_names=list(copy.copy(BPO_PARAMS)[model_type].keys()), + fitness_protocols={onestep_protocol.name: onestep_protocol}, + fitness_calculator=score_calc, + sim="euler", + ) + simple_cell.params_by_names(copy.copy(BPO_PARAMS)[model_type].keys()) + return (cell_evaluator, simple_cell, score_calc, [tt.name for tt in nu_tests]) + + +def get_binary_file_downloader_html(bin_file_path, file_label="File"): + """ + --Synopsis: Intended to be used with streamlit/frontend. + Allows someone to download a pickle version of + python models that are output from the optimization process. + """ + with open(bin_file_path, "rb") as f: + data = f.read() + bin_str = base64.b64encode(data).decode() + href = f'Download {file_label}' + return href + + +def rescale(v: pq): + """ + --Synopsis: default rescaling quantities SI units are often not desirable. + this method helps circumnavigates quantities defaults, instead + the method tries to apply neuroscience relevant units as a first preference. + A constant "rescale preffered was defined as a CONSTANT at the top of this file" + --params: v is a qauntities type object (often a float multiplied by a si unit) + --returns rescaled units + """ + v.rescale_preferred() + v = v.simplified + if np.round(v, 2) != 0: + v = np.round(v, 2) + return v + + +def _opt_( + constraints, + PARAMS, + test_key, + model_value, + MU, + NGEN, + diversity, + use_streamlit=False, + score_type=RelativeDifferenceScore, +): + """ + --Synopsis: A private interface for optimizing with neuron unit ephys type tests + """ + + if type(constraints) is not type(list()): + constraints = list(constraints.values()) + cell_evaluator, simple_cell, score_calc, test_names = make_evaluator( + constraints, PARAMS, test_key, model_type=model_value, score_type=score_type + ) + model_type = str("_best_fit_") + str(model_value) + "_" + str(test_key) + "_.p" + mut = 0.125 + cxp = 0.6125 + optimization = bpop.optimisations.DEAPOptimisation( + evaluator=cell_evaluator, + offspring_size=MU, + map_function=map, + selector_name=diversity, + mutpb=mut, + cxpb=cxp, + neuronunit=True, + ) + + final_pop, hall_of_fame, logs, hist = optimization.run(max_ngen=NGEN) + + best_ind = hall_of_fame[0] + best_ind_dict = cell_evaluator.param_dict(best_ind) + model = cell_evaluator.cell_model + cell_evaluator.param_dict(best_ind) + tests = constraints + obs_preds = [] + scores = [] + +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + for t in tests: + scores.append(t.judge(model, prediction=t.prediction)) + if "mean" in t.observation.keys(): + t.observation["mean"] = rescale(t.observation["mean"]) + + if "mean" in t.prediction.keys(): + t.prediction["mean"] = rescale(t.prediction["mean"]) + + obs_preds.append( + (t.name, t.observation["mean"], t.prediction["mean"], scores[-1]) + ) + if "value" in t.prediction.keys(): + t.prediction["value"] = rescale(t.prediction["value"]) + + obs_preds.append( + (t.name, t.observation["mean"], t.prediction["value"], scores[-1]) + ) else: - dtc.backend = 'NEURON' + obs_preds.append((t.name, t.observation, t.prediction, scores[-1])) + + df = pd.DataFrame(obs_preds) + + model.attrs = { + str(k): float(v) for k, v in cell_evaluator.param_dict(best_ind).items() + } +<<<<<<< HEAD + opt = model.model_to_dtc() +======= + opt = model#.model_to_model() +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + opt.attrs = { + str(k): float(v) for k, v in cell_evaluator.param_dict(best_ind).items() + } + # try: + # df = opt.make_pretty(tests=tests) + # except: + # df = pd.DataFrame(obs_preds) + + best_fit_val = best_ind.fitness.values + return ( + final_pop, + hall_of_fame, + logs, + hist, + best_ind, + best_fit_val, + opt, + obs_preds, + df, + ) + + +def public_opt( + constraints, + PARAMS, + test_key, + model_value, + MU, + NGEN, + diversity, + score_type=RelativeDifferenceScore, +): + """ + --Synopsis: A public interface for optimizing with neuron unit ephys type tests + calls _opt_ + """ + _, _, _, _, _, _, opt, obs_preds, df = _opt_( + constraints=constraints, + PARAMS=PARAMS, + test_key=test_key, + model_value=model_value, + MU=MU, + NGEN=NGEN, + diversity=diversity, + score_type=score_type, + ) + return opt, obs_preds, df + + +ALLEN_DELAY = 1000.0 * pq.ms +ALLEN_DURATION = 2000.0 * pq.ms +from neuronunit.tests.target_spike_current import SpikeCountSearch + + +def inject_model_soma( +<<<<<<< HEAD + dtc: DataTC, +======= + model: RunnableModel, +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + figname=None, + solve_for_current=None, + fixed: bool = False, + final_run=False, +<<<<<<< HEAD +) -> DataTC: + + """ + -- Synpopsis: this method changes the DataTC object (side effects) + -- args: dtc: containing spike number attribute. +======= +) -> RunnableModel: + + """ + -- Synpopsis: this method changes the OptimizationModel object (side effects) + -- args: model: containing spike number attribute. +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + the spike number attribute signifies the number of spikes wanted. + if solve_for_current is True find the current that causes the + wanted spike number. If solve_for_current is False + find vm at various multiples or rheobase. + + -- outputs: voltage that causes a desired number of spikes, + current Injection Parameters, +<<<<<<< HEAD + dtc +======= + model +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + -- Synopsis: + produce an rheobase injection value + produce an object of class Neuronunit runnable Model + with known attributes and known rheobase current injection value. + """ + if type(solve_for_current) is not type(None): + observation_range = {} +<<<<<<< HEAD + model = dtc.dtc_to_model() + model._backend.attrs = dtc.attrs + + if not fixed: + observation_range["value"] = dtc.spk_count +======= + + if not fixed: + observation_range["value"] = model.spk_count +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + scs = SpikeCountSearch(observation_range) + target_current = scs.generate_prediction(model) + if type(target_current) is not type(None): + solve_for_current = target_current["value"] +<<<<<<< HEAD + dtc.solve_for_current = solve_for_current +======= + model.solve_for_current = solve_for_current +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f - dtc.attrs = {} - for i,j in enumerate(ind): - dtc.attrs[str(td[i])] = j - dtc.evaluated = False + uc = { + "amplitude": solve_for_current, + "duration": ALLEN_DURATION, + "delay": ALLEN_DELAY, + "padding": 342.85 * pq.ms, + } +<<<<<<< HEAD + # model = dtc.dtc_to_model() + # temp = model.attrs + model.inject_square_current(**uc) + n_spikes = model.get_spike_count() + if n_spikes != dtc.spk_count: + dtc.exclude = True + else: + dtc.exclude = False + # if hasattr(dtc, "spikes"): + # spikes = model._backend.spikes + # assert model._backend.spikes == n_spikes + # dtc.spikes = n_spikes + vm_soma = model.get_membrane_potential() + dtc.vm_soma = vm_soma +======= + model.inject_square_current(**uc) + n_spikes = model.get_spike_count() + if n_spikes != model.spk_count: + model.exclude = True + else: + model.exclude = False + vm_soma = model.get_membrane_potential() + model.vm_soma = vm_soma +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + ## + # Refactor somewhere else, this simulation takes time. + # the rmp calculation somewhere else. + ## +<<<<<<< HEAD return dtc - if len(pop) > 1: - b = db.from_sequence(pop, npartitions=8) - dtcpop = list(db.map(transform,b).compute()) +======= + return model +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + +def still_more_features( + instance_obj: Any, results: List, vm_used: AnalogSignal, target_vm: None +) -> Any: + """ + -- Synopsis: Measure resting membrane potential and variance explained + as features, so that they can be used + as features to optimize with. + """ +<<<<<<< HEAD + if hasattr(instance_obj, "dtc_to_model"): + model = instance_obj + model = dtc.dtc_to_model() + model._backend.attrs = dtc.attrs + else: + dtc = instance_obj +======= + if hasattr(instance_obj, "model_to_model"): + model = instance_obj + model = model.model_to_model() + model._backend.attrs = model.attrs else: - # In this case pop is not really a population but an individual - # but parsimony of naming variables - # suggests not to change the variable name to reflect this. - dtcpop = list(transform(pop)) - return dtcpop + model = instance_obj +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + uc["amplitude"] = 0 * pq.pA + model.inject_square_current(**uc) + vr = model.get_membrane_potential() + vmr = np.mean(vr) +<<<<<<< HEAD + dtc.vmr = None + dtc.vmr = vmr +======= + model.vmr = None + model.vmr = vmr +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + del model + if target_vm is not None: + results[0]["var_expl"] = basic_expVar(vm_used, target_vm) + if vm_used[-1] > 0: + spikes_ = spikes[0:-2] + spikes = spikes_ + results[0]["vr_"] = instance_obj.vmr -def update_deap_pop(pop,error_criterion,td): - ''' - Inputs a population of genes (pop). - Returned neuronunit scored DTCs (dtcpop). - This method converts a population of genes to a population of Data Transport Containers, - Which act as communicatable data types for storing model attributes. - Rheobase values are found on the DTCs - DTCs for which a rheobase value of x (pA)<=0 are filtered out - DTCs are then scored by neuronunit, using neuronunit models that act in place. - ''' - orig_MU = len(pop) - import numpy - import dask.bag as db - from neuronunit.optimization import model_parameters as modelp - from itertools import repeat - # given the wrong attributes, and they don't have rheobase values. - def proc(pop): - dtcpop = list(update_dtc_pop(pop, td)) - rheobase_test = error_criterion[0] - xargs = zip(dtcpop,repeat(rheobase_test)) - dtcpop = list(map(dtc_to_rheo,xargs)) - dtcpop = list(filter(lambda dtc: dtc.rheobase['value'] > 0.0 , dtcpop)) - #while len(dtcpop) < len(pop): - # dtcpop.append(dtcpop[0]) - xargs = zip(dtcpop,repeat(error_criterion)) - dtcpop = list(map(format_test,xargs)) - #b = db.from_sequence(dtcpop, npartitions=8) - dtcpop = map_wrapper(nunit_evaluation,dtcpop,other_args = error_criterion) - #dtcpop = list(db.map(nunit_evaluation,b,error_criterion).compute()) - return dtcpop - - def kull(dtcpop): - dtcpop = list(filter(lambda dtc: not isinstance(dtc.scores['RheobaseTestP'],type(None)), dtcpop)) - dtcpop = list(filter(lambda dtc: not type(None) in (list(dtc.scores.values())), dtcpop)) - # This call deletes everything - #dtcpop = list(filter(lambda dtc: not (numpy.isinf(x) for x in list(dtc.scores.values())), dtcpop)) - - return dtcpop, len(dtcpop) - dtcpop = proc(pop) - dtcpop,length = kull(dtcpop) - while len(dtcpop) < len(pop): - dtcpop.append(dtcpop[0]) - for i,d in enumerate(dtcpop): - pop[i].rheobase = d.rheobase - return_package = zip(dtcpop, pop) - return return_package - - -def create_subset(nparams=10, provided_keys=None): - from neuronunit.optimization import model_parameters as modelp - import numpy as np - mp = modelp.model_params - - key_list = list(mp.keys()) - - if type(provided_keys) is type(None): - key_list = list(mp.keys()) - reduced_key_list = key_list[0:nparams] + +def spike_time_errors( + instance_obj: Any, results: List, vm_used: AnalogSignal, target_vm: None +) -> Any: + """ + -- Synopsis: Generate simple errors simply based on spike times. + """ +<<<<<<< HEAD + if hasattr(instance_obj, "spikes"): + spikes = instance_obj.spikes else: - reduced_key_list = provided_keys + spikes = threshold_detection(vm_used) + for index, tc in enumerate(spikes): + results[0]["spike_" + str(index)] = float(tc) + return instance_obj, results - subset = { k:mp[k] for k in reduced_key_list } - return subset + +def efel_evaluation( + instance_obj: Any, efel_filter_iterable: Iterable = None, current: float = None +) -> Any: + """ + -- Synopsis: evaluate efel feature extraction criteria against on + reduced cell models and probably efel data. + -- Args: efel_filter_iterable an Iterable that can be a list or a dictionary. + If it is a dictionary, then the keys are the features that efel should extract, + and the values are the SI units that belong to that feature (often units are None). + This method works on both sciunit runnable models + and DataTC objects. + efel_filter_iterable: is the list of efel features to extract + it can be a list or a dictionary, if it is a list, the keys should be feature units + current is the value of current amplitude to evaluate the model features at. + """ + if isinstance(efel_filter_iterable, type(list())): + efel_filter_list = efel_filter_iterable + if isinstance(efel_filter_iterable, type(dict())): + efel_filter_list = list(efel_filter_iterable.keys()) + if "extra_tests" in efel_filter_iterable.keys(): + if "var_expl" in efel_filter_iterable["extra_tests"].keys(): + target_vm = efel_filter_iterable["extra_tests"]["var_expl"] + else: + target_vm = None + else: +======= + #if hasattr(instance_obj, "spikes"): + # spikes = instance_obj.spikes + #else: + spikes = threshold_detection(vm_used) + for index, tc in enumerate(spikes): + results[0]["spike_" + str(index)] = float(tc) + return instance_obj, results + + +def efel_evaluation( + instance_obj: Any, efel_filter_iterable: Iterable = None, current: float = None +) -> Any: + """ + -- Synopsis: evaluate efel feature extraction criteria against on + reduced cell models and probably efel data. + -- Args: efel_filter_iterable an Iterable that can be a list or a dictionary. + If it is a dictionary, then the keys are the features that efel should extract, + and the values are the SI units that belong to that feature (often units are None). + This method works on both sciunit runnable models + and OptimizationModel objects. + efel_filter_iterable: is the list of efel features to extract + it can be a list or a dictionary, if it is a list, the keys should be feature units + current is the value of current amplitude to evaluate the model features at. + """ + if isinstance(efel_filter_iterable, type(list())): + efel_filter_list = efel_filter_iterable + if isinstance(efel_filter_iterable, type(dict())): + efel_filter_list = list(efel_filter_iterable.keys()) + if "extra_tests" in efel_filter_iterable.keys(): + if "var_expl" in efel_filter_iterable["extra_tests"].keys(): + target_vm = efel_filter_iterable["extra_tests"]["var_expl"] + else: + target_vm = None + else: +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + target_vm = None + + vm_used = instance_obj.vm_soma + try: + efel.reset() + except: + pass + efel.setThreshold(0) + if current is None: + if hasattr(instance_obj, "solve_for_current"): + current = instance_obj.solve_for_current + trace3 = { + "T": [float(t) * 1000.0 for t in vm_used.times], + "V": [float(v) for v in vm_used.magnitude], + "stimulus_current": [current], + } + trace3["stim_end"] = [float(ALLEN_DELAY) + float(ALLEN_DURATION)] + trace3["stim_start"] = [float(ALLEN_DELAY)] + + if np.min(vm_used.magnitude) < 0: + if not np.max(vm_used.magnitude) > 0: + vm_used_mag = [v + np.mean([0, float(np.max(v))]) * pq.mV for v in vm_used] + if not np.max(vm_used_mag) > 0: + instance_obj.efel = None + return instance_obj + + trace3["V"] = vm_used_mag + if efel_filter_iterable is None: + + default_efel_filter_iterable = { + "burst_ISI_indices": None, + "burst_mean_freq": pq.Hz, + "burst_number": None, + "single_burst_ratio": None, + "ISI_log_slope": None, + "mean_frequency": pq.Hz, + "adaptation_index2": None, + "first_isi": pq.ms, + "ISI_CV": None, + "median_isi": pq.ms, + "Spikecount": None, + "all_ISI_values": pq.ms, + "ISI_values": pq.ms, + "time_to_first_spike": pq.ms, + "time_to_last_spike": pq.ms, + "time_to_second_spike": pq.ms, + } + efel_filter_list = list(default_efel_filter_iterable.keys()) + # print(len(efel_filter_list)) + results = efel.getMeanFeatureValues( + [trace3], efel_filter_list, raise_warnings=False + ) + + efel.reset() + # instance_obj = apply_units_to_efel(instance_obj, + # efel_filter_iterable) + instance_obj, results = spike_time_errors( + instance_obj, results, vm_used, target_vm=target_vm + ) + + instance_obj.efel = None + instance_obj.efel = results[0] + try: + assert hasattr(instance_obj, "efel") + except: + raise Exception("efel object has no efel results list attribute") + return instance_obj + + +def generic_nu_tests_to_bpo_protocols(multi_spiking=None): + pass + + +def apply_units_to_efel(instance_obj, efel_filter_iterable): + if isinstance(efel_filter_iterable, type(dict())): + for k, v in instance_obj.efel.items(): + units = efel_filter_iterable[k] + if units is not None and v is not None: + instance_obj.efel[k] = v * units + return instance_obj + + +def inject_and_plot_model( +<<<<<<< HEAD + dtc: DataTC, figname=None, plotly=True, verbose=False +======= + model: RunnableModel, figname=None, plotly=True, verbose=False +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f +) -> Union[Any, Any, Any]: + """ + -- Synopsis: produce rheobase injection value + produce an object of class sciunit Runnable Model + with known attributes + and known rheobase current injection value. + """ +<<<<<<< HEAD + dtc = dtc_to_rheo(dtc) + model = dtc.dtc_to_model() + uc = {"amplitude": dtc.rheobase, "duration": DURATION, "delay": DELAY} + if dtc.jithub or "NEURON" in str(dtc.backend): + vm = model._backend.inject_square_current(**uc) + else: + vm = model.inject_square_current(uc) + vm = model.get_membrane_potential() +======= + model = model_to_rheo(model) + uc = {"amplitude": model.rheobase, "duration": DURATION, "delay": DELAY} + vm = model.inject_square_current(**uc) +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + if verbose: + if vm is not None: + print(vm[-1], vm[-1] < 0 * pq.mV) + if vm is None: + return [None, None, None] + if not plotly: +<<<<<<< HEAD + import matplotlib.pyplot as plt + + plt.clf() + plt.figure() + if dtc.backend in str("HH"): +======= + + plt.clf() + plt.figure() + if str(model.backend) in str("HH"): +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + plt.title("Conductance based model membrane potential plot") + else: + plt.title("Membrane potential plot") + plt.plot(vm.times, vm.magnitude, "k") + plt.ylabel("V (mV)") + plt.xlabel("Time (s)") + + if figname is not None: + plt.savefig(str(figname) + str(".png")) + plt.plot(vm.times, vm.magnitude) + + if plotly: + fig = px.line(x=vm.times, y=vm.magnitude, labels={"x": "t (s)", "y": "V (mV)"}) + if figname is not None: + fig.write_image(str(figname) + str(".png")) + else: +<<<<<<< HEAD + return vm, fig, dtc + return [vm, plt, dtc] +======= + return vm, fig, model + return [vm, plt, model] +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + + +def switch_logic(xtests: Any = None) -> List: + """ + Logic to apply current injection stimulation protocols + """ + try: + atsd = TSD() + except: + atsd = neuronunit.optimization.optimization_management.TSD() + + if type(xtests) is type(atsd): + xtests = list(xtests.values()) + for t in xtests: + if str("FITest") == t.name: + t.active = True + t.passive = False + + if str("RheobaseTest") == t.name: + t.active = True + t.passive = False + elif str("InjectedCurrentAPWidthTest") == t.name: + t.active = True + t.passive = False + elif str("InjectedCurrentAPAmplitudeTest") == t.name: + t.active = True + t.passive = False + elif str("InjectedCurrentAPThresholdTest") == t.name: + t.active = True + t.passive = False + elif str("RestingPotentialTest") == t.name: + t.passive = False + t.active = False + elif str("InputResistanceTest") == t.name: + t.passive = True + t.active = False + elif str("TimeConstantTest") == t.name: + t.passive = True + t.active = False + elif str("CapacitanceTest") == t.name: + t.passive = True + t.active = False + else: + t.passive = False + t.active = False + return xtests + + +def active_values(keyed: dict, rheobase, square: dict = None) -> dict: + keyed["injected_square_current"] = {} + if square is None: + if isinstance(rheobase, type(dict())): + keyed["injected_square_current"]["amplitude"] = ( + float(rheobase["value"]) * pq.pA + ) + else: + keyed["injected_square_current"]["amplitude"] = rheobase + return keyed +<<<<<<< HEAD + + +def passive_values(keyed: dict = {}) -> dict: + PASSIVE_DURATION = 500.0 * pq.ms + PASSIVE_DELAY = 200.0 * pq.ms + keyed["injected_square_current"] = {} + keyed["injected_square_current"]["delay"] = PASSIVE_DELAY + keyed["injected_square_current"]["duration"] = PASSIVE_DURATION + keyed["injected_square_current"]["amplitude"] = -10 * pq.pA + return keyed +======= + + +def passive_values(keyed: dict = {}) -> dict: + PASSIVE_DURATION = 500.0 * pq.ms + PASSIVE_DELAY = 200.0 * pq.ms + keyed["injected_square_current"] = {} + keyed["injected_square_current"]["delay"] = PASSIVE_DELAY + keyed["injected_square_current"]["duration"] = PASSIVE_DURATION + keyed["injected_square_current"]["amplitude"] = -10 * pq.pA + return keyed + +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + +def neutral_values(keyed: dict = {}) -> dict: + PASSIVE_DURATION = 500.0 * pq.ms + PASSIVE_DELAY = 200.0 * pq.ms + keyed["injected_square_current"] = {} + keyed["injected_square_current"]["delay"] = PASSIVE_DELAY + keyed["injected_square_current"]["duration"] = PASSIVE_DURATION + keyed["injected_square_current"]["amplitude"] = 0 * pq.pA + return keyed + +<<<<<<< HEAD +def neutral_values(keyed: dict = {}) -> dict: + PASSIVE_DURATION = 500.0 * pq.ms + PASSIVE_DELAY = 200.0 * pq.ms + keyed["injected_square_current"] = {} + keyed["injected_square_current"]["delay"] = PASSIVE_DELAY + keyed["injected_square_current"]["duration"] = PASSIVE_DURATION + keyed["injected_square_current"]["amplitude"] = 0 * pq.pA + return keyed + +======= +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + +def initialise_test(v: Any, rheobase: Any = None) -> dict: + """ + -- Synpopsis: + Create appropriate square wave stimulus for various + Different square pulse current injection protocols. + ### + # TODO move this to BPO/ephys/protocols.py + # unify it with BPO protocol code. + # It is already a bit similar. + ### + """ + if not isinstance(v, Iterable): + v = [v] + v = switch_logic(v) + v = v[0] + k = v.name + if not hasattr(v, "params"): + v.params = {} + if not "injected_square_current" in v.params.keys(): + v.params["injected_square_current"] = {} + if v.passive == False and v.active == True: + keyed = v.params["injected_square_current"] + v.params = active_values(keyed, rheobase) + v.params["injected_square_current"]["delay"] = DELAY + v.params["injected_square_current"]["duration"] = DURATION + if v.passive == True and v.active == False: + + v.params["injected_square_current"]["amplitude"] = -10 * pq.pA + v.params["injected_square_current"]["delay"] = PASSIVE_DELAY + v.params["injected_square_current"]["duration"] = PASSIVE_DURATION + + if v.name in str("RestingPotentialTest"): + v.params["injected_square_current"]["delay"] = PASSIVE_DELAY + v.params["injected_square_current"]["duration"] = PASSIVE_DURATION + v.params["injected_square_current"]["amplitude"] = 0.0 * pq.pA + v.params = v.params + if "delay" in v.params["injected_square_current"].keys(): + v.params["tmax"] = ( + v.params["injected_square_current"]["delay"] + + v.params["injected_square_current"]["duration"] + ) + else: + v.params["tmax"] = DELAY + DURATION + return v diff --git a/neuronunit/plotting/plot_utils.py b/neuronunit/plotting/plot_utils.py new file mode 100644 index 000000000..4d4f480b0 --- /dev/null +++ b/neuronunit/plotting/plot_utils.py @@ -0,0 +1,410 @@ +import os + +def plot_as_normal(dtc): + import streamlit as st + collect_z_offset = [] + for i,t in enumerate(dtc.tests): + t.score_type = ZScore + model = dtc.dtc_to_model() + score = t.judge(model) + x1 = -1.01 + x2 = 1.0 + sigma = 1.0 + mu = 0 + x = np.arange(-sigma, sigma, 0.001) # range of x in spec + x_all = np.arange(-sigma, sigma, 0.001) + y_point = norm.pdf(mu+float(score.raw),0,1) + y2 = norm.pdf(x_all,0,1) + + y = norm.pdf(x,0,1) + y2 = norm.pdf(x_all,0,1) + + + + x_point = mu+float(score.raw) + collect_z_offset.append(score.raw) + name = t.name.split('Test')[0] + title = str(name)+str(' ')+str(t.observation['mean'].units) + + zplot(x_point,y_point,title) + st.pyplot() +def plot_as_normal_all(dtc,random): + import streamlit as st + collect_z_offset = [] + collect_z_offset_random = [] + for i,t in enumerate(dtc.tests): + #ax = axes.flat[i] + t.score_type = ZScore + model = dtc.dtc_to_model() + score = t.judge(model) + collect_z_offset.append(np.abs(float(score.raw))) + + for i,t in enumerate(random.tests): + #ax = axes.flat[i] + t.score_type = ZScore + model = dtc.dtc_to_model() + score = t.judge(model) + collect_z_offset_random.append(np.abs(float(score.raw))) + + + x1 = -1.01 + x2 = 1.0 + sigma = 1.0 + mu = 0 + x = np.arange(-sigma, sigma, 0.001) # range of x in spec + x_all = np.arange(-sigma, sigma, 0.001) + y_point = norm.pdf(mu+float(np.mean(collect_z_offset)),0,1) + y2 = norm.pdf(x_all,0,1) + + y = norm.pdf(x,0,1) + y2 = norm.pdf(x_all,0,1) + x_point = mu+float(np.mean(collect_z_offset)) + + x_point_random = mu+float(np.mean(collect_z_offset_random)) + y_point_random = norm.pdf(mu+float(np.mean(collect_z_offset_random)),0,1) + best_random = [x_point_random,y_point_random] + + zplot(x_point,y_point,'all_tests',more=best_random) + + +def zplot(x_point,y_point,title,area=0.68, two_tailed=True, align_right=False, more=None): + """Plots a z distribution with common annotations + Example: + zplot(area=0.95) + zplot(area=0.80, two_tailed=False, align_right=True) + Parameters: + area (float): The area under the standard normal distribution curve. + align (str): The area under the curve can be aligned to the center + (default) or to the left. + Returns: + None: A plot of the normal distribution with annotations showing the + area under the curve and the boundaries of the area. + """ + # create plot object + fig = plt.figure(figsize=(12, 6)) + ax = fig.subplots() + # create normal distribution + norm = scs.norm() + # create data points to plot + x = np.linspace(-5, 5, 1000) + y = norm.pdf(x) + + ax.plot(x, y) + ax.scatter(x_point,y_point,c='g',s=150,marker='o') + + if more is not None: + ax.scatter(more[0],more[1],c='b',s=150,marker='o') + + + # code to fill areas for two-tailed tests + if two_tailed: + left = norm.ppf(0.5 - area / 2) + right = norm.ppf(0.5 + area / 2) + ax.vlines(right, 0, norm.pdf(right), color='grey', linestyle='--') + ax.vlines(left, 0, norm.pdf(left), color='grey', linestyle='--') + + ax.fill_between(x, 0, y, color='grey', alpha='0.25', + where=(x > left) & (x < right)) + plt.xlabel('z') + plt.ylabel('PDF') + plt.text(left, norm.pdf(left), "z = {0:.3f}".format(left), fontsize=12, + rotation=90, va="bottom", ha="right") + plt.text(right, norm.pdf(right), "z = {0:.3f}".format(right), + fontsize=12, rotation=90, va="bottom", ha="left") + # for one-tailed tests + else: + # align the area to the right + if align_right: + left = norm.ppf(1-area) + ax.vlines(left, 0, norm.pdf(left), color='grey', linestyle='--') + ax.fill_between(x, 0, y, color='grey', alpha='0.25', + where=x > left) + plt.text(left, norm.pdf(left), "z = {0:.3f}".format(left), + fontsize=12, rotation=90, va="bottom", ha="right") + # align the area to the left + else: + right = norm.ppf(area) + ax.vlines(right, 0, norm.pdf(right), color='grey', linestyle='--') + ax.fill_between(x, 0, y, color='grey', alpha='0.25', + where=x < right) + plt.text(right, norm.pdf(right), "z = {0:.3f}".format(right), + fontsize=12, rotation=90, va="bottom", ha="left") + + # annotate the shaded area + plt.text(0, 0.1, "shaded area = {0:.3f}".format(area), fontsize=12, + ha='center') + # axis labels + plt.xlabel('z') + plt.ylabel('PDF') + plt.title(title) + plt.show() + +try: + import plotly.offline as py +except: + warnings.warn("plotly") +try: + import plotly + + plotly.io.orca.config.executable = "/usr/bin/orca" +except: + print("silently fail on plotly") +try: + import seaborn as sns +except: + warnings.warn("Seaborne plotting sub library not available, consider installing") + +def check_bin_vm_soma(target,opt): + import matplotlib + import matplotlib.pyplot as plt + import plotly.graph_objects as go + import quantities as qt + + plt.plot(target.vm_soma.times,target.vm_soma.magnitude,label='Allen Experiment') + plt.plot(opt.vm_soma.times,opt.vm_soma.magnitude,label='Optimized Model') + signal = target.vm_soma + plt.xlabel(qt.s) + plt.ylabel(signal.dimensionality) + plt.legend() + plt.show() + +def display_fitting_data(): + cells = pickle.load(open("processed_multicellular_constraints.p","rb")) + + purk = TSD(cells['Cerebellum Purkinje cell'])#.tests + purk_vr = purk["RestingPotentialTest"].observation['mean'] + + ncl5 = TSD(cells["Neocortex pyramidal cell layer 5-6"]) + ncl5.name = str("Neocortex pyramidal cell layer 5-6") + ncl5_vr = ncl5["RestingPotentialTest"].observation['mean'] + + ca1 = TSD(cells['Hippocampus CA1 pyramidal cell']) + ca1_vr = ca1["RestingPotentialTest"].observation['mean'] + + + olf = TSD(pickle.load(open("olf_tests.p","rb"))) + olf.use_rheobase_score = False + cells.pop('Olfactory bulb (main) mitral cell',None) + cells['Olfactory bulb (main) mitral cell'] = olf + + + list_of_dicts = [] + for k,v in cells.items(): + observations = {} + for k1 in ca1.keys(): + vsd = TSD(v) + if k1 in vsd.keys(): + vsd[k1].observation['mean'] + observations[k1] = float(vsd[k1].observation['mean'])##,2) + + observations[k1] = np.round(vsd[k1].observation['mean'],2) + observations['name'] = k + list_of_dicts.append(observations) + df = pd.DataFrame(list_of_dicts) + df = df.set_index('name').T + return df + +def inject_and_plot_passive_model(pre_model,second=None,figname=None,plotly=True): + model = pre_model.dtc_to_model() + uc = {'amplitude':-10*pq.pA,'duration':500*pq.ms,'delay':100*pq.ms} + model.inject_square_current(uc) + vm = model.get_membrane_potential() + + + if second is not None: + model2 = second.dtc_to_model() + uc = {'amplitude':-10*pq.pA,'duration':500*pq.ms,'delay':100*pq.ms} + model2.inject_square_current(uc) + vm2 = model2.get_membrane_potential() + if plotly and second is None: + fig = px.line(x=vm.times, y=vm.magnitude, labels={'x':'t (ms)', 'y':'V (mV)'}) + return fig + if plotly and second is not None: + import plotly.graph_objects as go + from plotly.subplots import make_subplots + # Create figure with secondary y-axis + fig = make_subplots(specs=[[{"secondary_y": True}]]) + # Add traces + fig.add_trace( + go.Scatter(x=[float(i) for i in vm.times[0:-1]], y=[float(i) for i in vm.magnitude[0:-1]], name="yaxis data"), + secondary_y=False, + ) + fig.add_trace( + go.Scatter(x=[float(i) for i in vm2.times[0:-1]], y=[float(i) for i in vm2.magnitude[0:-1]], name="yaxis2 data"), + secondary_y=True, + ) + # Add figure title + fig.update_layout( + title_text="Compare traces" + ) + # Set x-axis title + fig.update_xaxes(title_text="time (ms)") + # Set y-axes titles + fig.update_yaxes(title_text="Vm (mv) model 1", secondary_y=False) + fig.update_yaxes(title_text="Vm (mv) model 2", secondary_y=True) + return fig + if not plotly: + matplotlib.rcParams.update({'font.size': 15}) + plt.figure() + if pre_model.backend in str("HH"): + plt.title('Hodgkin-Huxley Neuron') + else: + plt.title('Membrane Potential') + plt.plot(vm.times, vm.magnitude, c='b')#+str(model.attrs['a'])) + + plt.plot(vm2.times, vm2.magnitude, c='g') + plt.ylabel('Time (sec)') + + plt.ylabel('V (mV)') + plt.legend(loc="upper left") + + if figname is not None: + plt.savefig('thesis_simulated_data_match.png') + return vm,plt + +def inject_and_not_plot_model(pre_model,known_rh=None): + + # get rheobase injection value + # get an object of class ReducedModel with known attributes and known rheobase current injection value. + model = pre_model.dtc_to_model() + + if known_rh is None: + pre_model = dtc_to_rheo(pre_model) + if type(model.rheobase) is type(dict()): + uc = {'amplitude':model.rheobase['value'],'duration':DURATION,'delay':DELAY} + else: + uc = {'amplitude':model.rheobase,'duration':DURATION,'delay':DELAY} + + else: + if type(known_rh) is type(dict()): + uc = {'amplitude':known_rh['value'],'duration':DURATION,'delay':DELAY} + else: + uc = {'amplitude':known_rh,'duration':DURATION,'delay':DELAY} + model.inject_square_current(uc) + vm = model.get_membrane_potential() + return vm + +def plotly_version(vm0,vm1,figname=None,snippets=False): + + import plotly.graph_objects as go + if snippets: + snippets1 = get_spike_waveforms(vm1) + snippets0 = get_spike_waveforms(vm0) + + import plotly.graph_objects as go + from plotly.subplots import make_subplots + + # Create figure with secondary y-axis + fig = make_subplots(specs=[[{"secondary_y": True}]]) + # Add traces + fig.add_trace( + go.Scatter(x=[float(i) for i in snippets0.times[0:-1]], y=[float(i) for i in snippets0.magnitude[0:-1]], name="yaxis data"), + secondary_y=False, + ) + + fig.add_trace( + go.Scatter(x=[float(i) for i in snippets1.times[0:-1]], y=[float(i) for i in snippets1.magnitude[0:-1]], name="yaxis2 data"), + secondary_y=True, + ) + + # Add figure title + fig.update_layout( + title_text="Double Y Axis Example" + ) + + # Set x-axis title + fig.update_xaxes(title_text="xaxis title") + + # Set y-axes titles + fig.update_yaxes(title_text="primary yaxis title", secondary_y=False) + fig.update_yaxes(title_text="secondary yaxis title", secondary_y=True) + + fig.show() + if figname is not None: + fig.write_image(str(figname)+str('.png')) + else: + fig.show() + + else: + import plotly.graph_objects as go + from plotly.subplots import make_subplots + + # Create figure with secondary y-axis + fig = make_subplots(specs=[[{"secondary_y": True}]]) + # Add traces + fig.add_trace( + go.Scatter(x=[float(i) for i in vm0.times[0:-1]], y=[float(i) for i in vm0.magnitude[0:-1]], name="yaxis data"), + secondary_y=False, + ) + + fig.add_trace( + go.Scatter(x=[float(i) for i in vm1.times[0:-1]], y=[float(i) for i in vm1.magnitude[0:-1]], name="yaxis2 data"), + secondary_y=True, + ) + + # Add figure title + + # Set x-axis title + fig.update_xaxes(title_text="xaxis title") + + # Set y-axes titles + fig.update_yaxes(title_text="Vm (mv) model 1", secondary_y=False) + fig.update_yaxes(title_text="Vm (mv) model 2", secondary_y=True) + + if figname is not None: + fig.write_image(str(figname)+str('.png')) + else: + fig.show() + +def model_trace(pre_model): + from neuronunit.tests.base import AMPL, DELAY, DURATION + + # get rheobase injection value + # get an object of class ReducedModel with known attributes and known rheobase current injection value. + pre_model = dtc_to_rheo(pre_model) + model = pre_model.dtc_to_model() + uc = {'amplitude':model.rheobase,'duration':DURATION,'delay':DELAY} + model.inject_square_current(uc) + vm = model.get_membrane_potential() + return vm +def check_binary_match(dtc0,dtc1,figname=None,snippets=True,plotly=True): + + vm0 = model_trace(dtc0) + vm1 = model_trace(dtc1) + + if plotly: + plotly_version(vm0,vm1,figname,snippets) + else: + matplotlib.rcParams.update({'font.size': 8}) + + plt.figure() + + if snippets: + plt.figure() + + snippets1 = get_spike_waveforms(vm1) + snippets0 = get_spike_waveforms(vm0) + plt.plot(snippets0.times,snippets0.magnitude,label=str('model type: '))#+label)#,label='ground truth') + plt.plot(snippets1.times,snippets1.magnitude,label=str('model type: '))#+label)#,label='ground truth') + if dtc0.backend in str("HH"): + plt.title('Check for waveform Alignment variance exp: {0}'.format(basic_expVar(snippets1, snippets0))) + else: + plt.title('membrane potential: variance exp: {0}'.format(basic_expVar(snippets1, snippets0))) + plt.ylabel('V (mV)') + plt.legend(loc="upper left") + + if figname is not None: + plt.savefig(figname) + + else: + if dtc0.backend in str("HH"): + plt.title('Check for waveform Alignment') + else: + plt.title('membrane potential plot') + plt.plot(vm0.times, vm0.magnitude,label="target") + plt.plot(vm1.times, vm1.magnitude,label="solutions") + plt.ylabel('V (mV)') + plt.legend(loc="upper left") + + if figname is not None: + plt.savefig(figname) diff --git a/neuronunit/plottools.py b/neuronunit/plottools.py index ce393f7a8..497d7ff89 100644 --- a/neuronunit/plottools.py +++ b/neuronunit/plottools.py @@ -10,10 +10,7 @@ import sys KERNEL = ('ipykernel' in sys.modules) -try: - from io import StringIO -except ImportError: - from StringIO import StringIO +from io import StringIO import numpy as np import pandas as pd @@ -157,53 +154,113 @@ def tiled_figure(figname='', frames=1, columns=2, return axs -def plot_surface(model_param0,model_param1,td,history): + + +import numpy as np +import matplotlib +matplotlib.rcParams.update({'font.size':16}) +import matplotlib.pyplot as plt +import numpy as np +import scipy.spatial +import pylab + +def plot_surface(fig_trip,ax_trip,model_param0,model_param1,history): ''' Move this method back to plottools Inputs should be keys, that are parameters see new function definition below ''' - - import numpy as np - import matplotlib - matplotlib.rcParams.update({'font.size':16}) - import matplotlib.pyplot as plt - - + td = list(history.genealogy_history[1].dtc.attrs.keys()) x = [ i for i,j in enumerate(td) if str(model_param0) == j ][0] y = [ i for i,j in enumerate(td) if str(model_param1) == j ][0] + z = [ i for i,j in enumerate(td) if str(model_param1) == j ][0] + all_inds = history.genealogy_history.values() sums = np.array([np.sum(ind.fitness.values) for ind in all_inds]) - #quads = [] - ''' - for k in range(1,9): - for i,j in enumerate(td): - if i+k < 10: - quads.append((td[i],td[i+k],i,i+k)) - all_inds1 = list(history.genealogy_history.values()) - ''' - #import pdb; pdb.set_trace() - #print(all_inds1[x]) - - #ab = [ (all_inds1[x],all_inds1[y]) for y in all_inds1 ] xs = np.array([ind[x] for ind in all_inds]) ys = np.array([ind[y] for ind in all_inds]) + zs = np.array([ind[z] for ind in all_inds]) + min_ys = ys[np.where(sums == np.min(sums))] min_xs = xs[np.where(sums == np.min(sums))] - plt.clf() - fig_trip, ax_trip = plt.subplots(1, figsize=(10, 5), facecolor='white') + min_zs = xs[np.where(sums == np.min(sums))] + + data = np.zeros((len(xs),3)) + data[:,0] = xs + data[:,1] = ys + data[:,2] = zs + + + #data = np.random.random((12,3)) # arbitrary 3D data set + #tri = scipy.spatial.Delaunay( data[:,:2] ) # take the first two dimensions + + #pylab.triplot( data[:,0], data[:,1], tri.simplices.copy() ) + #pylab.plot( data[:,0], data[:,1], 'ro' ) ; + + #fig_trip, ax_trip = plt.subplots(1, figsize=(10, 5), facecolor='white') trip_axis = ax_trip.tripcolor(xs,ys,sums,20,norm=matplotlib.colors.LogNorm()) plot_axis = ax_trip.plot(list(min_xs), list(min_ys), 'o', color='lightblue',label='global minima') - fig_trip.colorbar(trip_axis, label='Sum of Objective Errors ') - ax_trip.set_xlabel('Parameter '+str((td[x]))) - ax_trip.set_ylabel('Parameter '+str((td[y]))) + #plot_axis.colorbar(trip_axis, label='Sum of Objective Errors ') + if type(td) is not type(None): + ax_trip.set_xlabel('Parameter '+str((td[x]))) + ax_trip.set_ylabel('Parameter '+str((td[y]))) plot_axis = ax_trip.plot(list(min_xs), list(min_ys), 'o', color='lightblue') - fig_trip.tight_layout() - if not KERNEL: - plt.savefig('surface'+str((td[z])+str('.png'))) - else: - plt.show() + #plot_axis.tight_layout() + return ax_trip,plot_axis + +def plot_vm(hof,ax,key): + ax.cla() + ax.set_title(' {0} vs $V_{M}$'.format(key[0])) + best_dtc = hof[0].dtc + best_rh = hof[0].dtc.rheobase + neuron = None + model = ReducedModel(path_params['model_path'],name = str('regular_spiking'),backend =('NEURON',{'DTC':best_dtc})) + params = {'injected_square_current': + {'amplitude': best_rh, 'delay':DELAY, 'duration':DURATION}} + results = modelrs.inject_square_current(params) + vm = model.get_membrane_potential() + times = vm.times + ax.plot(times,vm) + return ax + +def plotss(matrix,hof): + dim = np.shape(matrix)[0] + print(dim) + cnt = 0 + fig,ax = plt.subplots(dim,dim,figsize=(10,10)) + flat_iter = [] + for i,k in enumerate(matrix): + for j,r in enumerate(k): + keys = list(r[0]) + gr = r[1] + if i==j: + ax[i,j] = plot_vm(hof,ax[i,j],keys) + if i>j: + ax[i,j] = plot_surface(gr,ax[i,j],keys,imshow=False) + if i < j: + ax[i,j] = plot_scatter(hof,ax[i,j],keys) + print(i,j) + plt.savefig(str('surface_and_vm.png')) + return None + +def scatter_surface(fig_trip,ax_trip,model_param0,model_param1,history): + ''' + + Move this method back to plottools + Inputs should be keys, that are parameters see new function definition below + ''' + td = list(history.genealogy_history[1].dtc.attrs.keys()) + x = [ i for i,j in enumerate(td) if str(model_param0) == j ][0] + y = [ i for i,j in enumerate(td) if str(model_param1) == j ][0] + + all_inds = history.genealogy_history.values() + z = np.array([np.sum(ind.fitness.values) for ind in all_inds]) + + xs = np.array([ind[x] for ind in all_inds]) + ys = np.array([ind[y] for ind in all_inds]) + + return xs,ys,z def shadow(dtcpop,best_vm):#This method must be pickle-able for ipyparallel to work. ''' @@ -504,96 +561,6 @@ def plot_log(log): #logbook else: fig.show() -def try_hard_coded0(): - params0 = {'C': '0.000107322241995', - 'a': '0.177922330376', - 'b': '-5e-09', - 'c': '-59.5280130394', - 'd': '0.153178745992', - 'k': '0.000879131572692', - 'v0': '-73.3255584633', - 'vpeak': '34.5214177196', - 'vr': '-71.0211905343', - 'vt': '-46.6016774842'} - #rheobase = {'value': array(131.34765625) * pA} - return params0 - - - -def try_hard_coded1(): - params1 = {'C': '0.000106983591242', - 'a': '0.480856799107', - 'b': '-5e-09', - 'c': '-57.4022276619', - 'd': '0.0818117582621', - 'k': '0.00114004749537', - 'v0': '-58.4899756601', - 'vpeak': '36.6769758895', - 'vr': '-63.4080852004', - 'vt': '-44.1074682812'} - #rheobase = {'value': array(106.4453125) * pA}131.34765625 - return params1 - - - -def plot_suspicious(dtc): - import matplotlib.pyplot as plt - import numpy as np - plt.clf() - plt.style.use('ggplot') - from neuronunit.models.reduced import ReducedModel - from neuronunit.optimization.get_neab import tests as T - from neuronunit.optimization import get_neab - - from neuronunit.optimization import evaluate_as_module - from neuronunit.optimization.evaluate_as_module import pre_format - - param_list = [try_hard_coded0(), try_hard_coded1()] - dtc.vm0 = None - dtc.vm1 = None - for p in param_list: - dtc.attrs = p - model = ReducedModel(get_neab.LEMS_MODEL_PATH,name=str('vanilla'),backend='NEURON') - model.set_attrs(dtc.attrs) - model.rheobase = None - score = T[0].judge(model,stop_on_error = False, deep_error = True) - dtc.rheobase = score.prediction - dtc.vm0 = list(model.results['vm']) - - model = ReducedModel(get_neab.LEMS_MODEL_PATH,name=str('vanilla'),backend='NEURON') - model.set_attrs(dtc.attrs) - dtc.rheobase = score.prediction - dtc = pre_format(dtc) - model.rheobase = dtc.rheobase - print(model) - print(model.rheobase) - - model.inject_square_current(dtc.vtest[0]) - model._backend.local_run() - assert model.get_spike_count() == 1 - print(model.get_spike_count(),bool(model.get_spike_count() == 1)) - - model = None - model = ReducedModel(get_neab.LEMS_MODEL_PATH,name=str('vanilla'),backend='NEURON') - model.set_attrs(dtc.attrs) - model.inject_square_current(dtc.vtest[-1]) - dtc.vm1 = list(model.results['vm']) - dtc.tvec = list(model.results['t']) - - plt.clf() - plt.style.use('ggplot') - fig, axes = plt.subplots(figsize=(10, 10), facecolor='white') - plt.plot(dtc.tvec,dtc.vm0,linewidth=1, color='red',label='suspc rheobase') - plt.plot(dtc.tvec,dtc.vm1,linewidth=1, color='blue',label='suspc spike width') - plt.legend() - plt.ylabel('$V_{m}$ mV') - plt.xlabel('ms') - if not KERNEL: - plt.savefig(str('suspicious_rheobase')+str(p)+'vm_versus_t.png', format='png', dpi=1200) - else: - plt.show() - - def dtc_to_plotting(dtc): dtc.vm0 = None dtc.vm1 = None @@ -617,11 +584,6 @@ def dtc_to_plotting(dtc): model.inject_square_current(parameter_list[0]) model._backend.local_run() - print('\n\n\n\n\n\n') - print(type(model.get_spike_count()), ' < type') - print(model.get_spike_count(),bool(model.get_spike_count() == 1)) - print('\n\n\n\n\n\n') - assert model.get_spike_count() == 1 or model.get_spike_count() == 0 dtc.vm0 = list(model.results['vm']) @@ -925,7 +887,7 @@ def pca(best_worst,vmpop,fitnesses,td): py.sign_in('RussellJarvis','FoyVbw7Ry3u4N2kCY4LE') py.iplot(fig, filename='improved_names.svg',image='svg') -''' + def plot_evaluate(vms_best,vms_worst,names=['best','worst']):#This method must be pickle-able for ipyparallel to work. #A method to plot the best and worst candidate solution waveforms side by side @@ -981,11 +943,7 @@ def plot_evaluate(vms_best,vms_worst,names=['best','worst']):#This method must b sf_best = pd.DataFrame(sc_for_frame_best) sf_worst = pd.DataFrame(sc_for_frame_worst) -''' - -''' -def sp_spike_width(best_worst):#This method must be pickle-able for ipyparallel to work. - +def sp_spike_width(best_worst): #A method to plot the best and worst candidate solution waveforms side by side #Inputs: An individual gene from the population that has compound parameters, and a tuple iterator that #is a virtual model object containing an appropriate parameter set, zipped togethor with an appropriate rheobase @@ -1318,7 +1276,7 @@ def pandas_rh_search(vmoffspring): s4 = df4.style.background_gradient(cmap = shrunk_cmap) return df0,df1,df2,df3,df4 -''' + def not_just_mean(log,hypervolumes): ''' https://github.com/BlueBrain/BluePyOpt/blob/master/examples/graupnerbrunelstdp/run_fit.py @@ -1464,7 +1422,7 @@ def bar_chart(vms,name=None): delta.append(unit_delta) print('observation {0} versus prediction {1}'.format(unit_observations,unit_predictions)) print('unit delta', unit_delta) - sv = score.sort_key + sv = score.norm_score test_dic[str(v)] = (float(unit_observations), float(unit_predictions), unit_delta) diff --git a/neuronunit/tests/.gitignore b/neuronunit/tests/.gitignore index cad9988f8..568981a29 100644 --- a/neuronunit/tests/.gitignore +++ b/neuronunit/tests/.gitignore @@ -1,125 +1,6 @@ -['0.00010581171343', '0.0286630390232', '0.000738851984475', '23.7372062366', '-3.45765600042e-08', '0.0650094377801', '-46.9155818137', '-49.3916483139', '-71.6526341773', '-57.3449523609'].p -['0.000108058907722', '0.00123168405057', '-2.97933342022e-08', '-67.0822868305', '0.0262322313747', '27.6388314312', '0.139747071457', '-59.3653705856', '-37.0054771982', '-69.9737651608'].p -['0.000644266586273', '-34.990216783', '9.24543574228e-05', '-2.96089671798e-08', '0.0349604514291', '-70.542224896', '-47.3553887544', '-58.3391877071', '0.162080075012', '26.3613909403'].p -['0.00065619923945', '0.000101394096684', '-1.91327363663e-08', '-38.5078050775', '0.173589733066', '-50.7656552884', '-59.8338624044', '-64.4395723949', '0.0376994164137', '29.1650904208'].p -['0.000694991327685', '0.033692142323', '-2.96646706485e-08', '-71.0332257726', '23.9015125789', '9.18381517089e-05', '-57.0940500988', '-74.211497896', '-32.4941478525', '0.116371852627'].p -['0.000707261314543', '-72.9554250545', '29.1826006985', '-1.70008189824e-08', '-50.7277459648', '-40.0179172876', '-58.3655037909', '0.120849414355', '0.0384577586778', '9.45603108233e-05'].p -['0.000814050163136', '-46.4930611719', '-58.7776907778', '-37.431610777', '0.000105804230565', '-69.404629391', '-1.21048173416e-08', '0.0975136676411', '26.2126724737', '0.0247729500602'].p -['0.000935265310826', '-61.9296251238', '24.6427056843', '-1.66041519983e-08', '-54.8134018405', '-36.5910080631', '-59.0345080453', '0.150587357314', '0.0242315645349', '0.000100847269783'].p -['0.00102330720878', '-50.403641138', '-56.8227910956', '-32.2193665177', '0.000105475690358', '-62.0947366664', '-2.03721819429e-08', '0.0879159536002', '21.6105823288', '0.0368649418672'].p -['0.00122347774468', '9.9399861357e-05', '-1.67595806391e-08', '-43.2286523552', '0.0725204875037', '-74.6338697705', '-56.4255398679', '-50.7744048818', '0.018023799389', '24.0346130317'].p -0.015-3.5e-09-75.030.0.png -['0.026131530028', '9.84028489735e-05', '-62.6318702851', '0.00118885453926', '0.197406280323', '-30.3449477522', '-6.09370167836e-09', '20.3082920568', '-57.0434215129', '-55.3477713034'].p -['0.0441716916229', '-55.8879309435', '-35.2092259408', '9.94537002013e-05', '-65.1738529019', '-47.5516875184', '0.0501842932103', '-1.84321603536e-08', '27.4871325957', '0.00137949965215'].p -['0.15496380304', '-56.162323833', '0.0445592245166', '-60.7274677001', '-40.3255534379', '0.000107798567271', '-62.6799833288', '0.00134869307745', '-9.98955330247e-09', '29.7103671775'].p -['0.189518405125', '-59.1532142881', '0.0318353969674', '-68.3988091186', '-33.9760035853', '9.10928494763e-05', '-56.3717026326', '0.000902675387163', '-2.30199720239e-08', '29.6104871808'].p -['21.47301235', '0.00119067864711', '0.153743283815', '-7.63655189419e-09', '-64.2267948602', '-55.6125311089', '0.000105797148029', '-64.8879195162', '0.0424448705647', '-45.8314679441'].p -['22.3634672286', '-74.4269112091', '0.0163008052772', '-59.777993522', '9.71386885542e-05', '-52.7557185546', '-2.32991309297e-08', '0.146750735181', '0.000651831622932', '-37.9123278099'].p -['-34.1949864915', '0.022443999819', '-68.1413271917', '-56.9253034348', '-47.8147303237', '0.00133774365778', '0.111091353283', '0.000104151028393', '25.821670253', '-1.01378730193e-08'].p -['-36.3764265645', '0.0353723695736', '0.000822398369401', '-62.3801067855', '0.121232649954', '-45.9381501772', '-57.8903478346', '-3.29236652041e-08', '0.000104446203784', '26.9373154238'].p -['-37.3588509885', '0.031366633366', '-64.9189638931', '-57.4500841706', '-63.6452863234', '0.00129731731455', '0.101995788612', '0.000106130849849', '22.0017944865', '-1.09749483949e-08'].p -['-40.0179172876', '0.000707261314543', '29.1826006985', '0.120849414355', '0.0384577586778', '-50.7277459648', '9.45603108233e-05', '-1.70008189824e-08', '-72.9554250545', '-58.3655037909'].p -['-40.7424455693', '0.00136179289865', '-3.29340565713e-08', '0.15537226049', '0.0426253962343', '0.000102576464087', '-52.7149666922', '-70.8603226384', '20.5155113255', '-58.7749281976'].p -['-43.9549344003', '-52.7682215346', '-59.2725595637', '20.1509336868', '0.0400167388679', '9.21495554551e-05', '0.000833279701518', '0.120446383065', '-7.35637676602e-09', '-59.275130925'].p -['-48.3088390049', '0.000889888276231', '26.7338468671', '0.0705454814528', '0.0279563462945', '-55.786781638', '9.02951972096e-05', '-1.14154492411e-08', '-50.0847131045', '-57.6318539711'].p -['-55.0144677225', '28.2853005216', '-65.8443590361', '9.20541786191e-05', '0.0265706689577', '-60.4809785644', '0.186827068905', '-1.9477508505e-08', '0.000968076096567', '-38.6668043391'].p -['-55.5593661455', '0.00130422010005', '-55.9522282881', '24.0514115865', '0.0403086718766', '0.000106833737768', '-34.5151466157', '-1.25791841978e-08', '0.0745578007662', '-54.3592508272'].p -['-55.6573589571', '-44.600582006', '0.0430986422216', '-65.8848018627', '22.5758235858', '-55.1214693996', '0.000109127684431', '0.00067857243185', '0.114994025542', '-1.39559290044e-08'].p -['-56.3954782422', '20.8488338911', '-61.0170063367', '9.24426934221e-05', '0.0430113435941', '-64.9085546796', '0.0533371888819', '-1.99128280237e-08', '0.00133577204394', '-31.8367886023'].p -['-56.8839942188', '-60.9461869862', '-3.2070744604e-08', '23.1438536958', '0.000857540678347', '-31.7278528472', '0.16538139034', '-59.2729182678', '0.000104029516523', '0.03721716907'].p -['-56.921375678', '0.174911507349', '-36.5006896685', '26.5772685101', '0.00116480689381', '-52.8630203019', '-2.6282714584e-08', '-58.6023462513', '9.84798260658e-05', '0.0404120238906'].p -['-58.4568943341', '0.00132863953691', '-60.7099538365', '28.3215824413', '0.0278410420942', '9.93449460065e-05', '-43.0373046683', '-1.89486160264e-08', '0.144502641965', '-65.0947155862'].p -['-58.939186174', '0.106498942945', '-35.7491040661', '28.0205057388', '0.000900826967952', '-67.8239416237', '-1.46899042757e-08', '-70.0277024721', '9.80770311607e-05', '0.0382388816027'].p -['-59.3173180813', '-72.1611034273', '0.0162486146481', '24.9765353004', '9.6010454915e-05', '0.00124628287819', '-64.9722092509', '0.196060379126', '-2.9978573026e-08', '-44.4469035652'].p -['-73.6010186818', '0.0248749523925', '-58.9857510141', '0.141904397373', '-35.20235989', '0.000101273301957', '-53.101285725', '24.0919412078', '-1.37247331456e-08', '0.000722274177676'].p -['-74.6286667128', '0.0221657243119', '0.0501559517154', '0.000103781172884', '24.3956867746', '-53.475141', '0.000816453096408', '-59.847074198', '-44.0289429678', '-2.25357280357e-08'].p -['9.55626095267e-05', '20.2685306725', '-50.5485552515', '0.196213890943', '-52.9778421529', '-57.1252804249', '-31.2094750767', '0.0010813512771', '0.016604690105', '-2.62557133151e-08'].p -avg_error_versus_gen.png -CapacitanceTestmax_one.png -CapacitanceTestmax_two.png -CapacitanceTestmin_one.png -CapacitanceTestmin_two.png -CapacitanceTest.p -channel.py -Dockerfile.orig -dynamics.py -exhaustive_search_BACKUP_20625.py -exhaustive_search_BASE_20625.py -exhaustive_search_LOCAL_20625.py -exhaustive_search.pyc -exhaustive_search.py.orig -exhaustive_search_REMOTE_20625.py -finish_time.txt -gbevaluator.py -get_neab.pyc -get_neab.py.orig -html_score_matrix.html -InjectedCurrentAPAmplitudeTestmax_one.png -InjectedCurrentAPAmplitudeTestmax_two.png -InjectedCurrentAPAmplitudeTestmin_one.png -InjectedCurrentAPAmplitudeTestmin_two.png -InjectedCurrentAPAmplitudeTest.p -InjectedCurrentAPThresholdTestmax_one.png -InjectedCurrentAPThresholdTestmax_two.png -InjectedCurrentAPThresholdTestmin_one.png -InjectedCurrentAPThresholdTestmin_two.png -InjectedCurrentAPThresholdTest.p -InjectedCurrentAPWidthTestmax_one.png -InjectedCurrentAPWidthTestmax_two.png -InjectedCurrentAPWidthTestmin_one.png -InjectedCurrentAPWidthTestmin_two.png -InjectedCurrentAPWidthTest.p -InputResistanceTestmax_one.png -InputResistanceTestmax_two.png -InputResistanceTestmin_one.png -InputResistanceTestmin_two.png -InputResistanceTest.p -jneuroml_call_time.txt -max_one.png -max_two.png -mean_call_length_spiking.txt -min_one.png -min_two.png -minumum_and_maximum_values.pickle -model_parameters -neuroelectro2.pickle -NeuroML2 -nsga3.pyc -nsga6.pyc -nsga.pyc -other_nrn_count_invokations_run_time_metric.txt -pickle_test.pickle +*.png +*.p +*.pyc +*.orig +*.pickle __pycache__ -RestingPotentialTestmax_one.png -RestingPotentialTestmax_two.png -RestingPotentialTestmin_one.png -RestingPotentialTestmin_two.png -RestingPotentialTest.p -rheobase_old2.py -rheobase_old3.py -rheobase_old.py -rheobase_only.py -rheobase_only.pyc -RheobaseTestmax_one.png -RheobaseTestmax_two.png -RheobaseTestmin_one.png -RheobaseTestmin_two.png -RheobaseTest.p -save.p -score_matrix.pickle -score_matrix_serial.pickle -snap_shot_at_1.png -snap_shot_at_2.png -snap_shot_at_3.png -snap_shot_at_gen_1.png -snap_shot_at_gen_2.png -snap_shot_at_gen_3.png -stats_summart.txt -stdputil.py -test_all.py -testsrh -TimeConstantTestmax_one.png -TimeConstantTestmax_two.png -TimeConstantTestmin_one.png -TimeConstantTestmin_two.png -TimeConstantTest.p diff --git a/neuronunit/tests/__init__.py b/neuronunit/tests/__init__.py index 129991611..5b629a52c 100644 --- a/neuronunit/tests/__init__.py +++ b/neuronunit/tests/__init__.py @@ -1,7 +1,33 @@ - -"""NeuronUnit Test classes""" +"""NeuronUnit Test classes.""" from .passive import * from .waveform import * +from .dynamics import FITest + from .dynamics import * from .fi import * + +from sciunit import scores, errors + +from sciunit.errors import CapabilityError, InvalidScoreError + + +class FakeTest(sciunit.Test): + + # from sciunit.errors import CapabilityError, InvalidScoreError + + # score_type = scores.RatioScore + score_type = sciunit.scores.ZScore + + def generate_prediction(self, model): + self.key_param = self.name.split("_")[1] + self.prediction = model.attrs[self.key_param] + return self.prediction + + def compute_score(self, observation, prediction): + mean = observation[0] + std = observation[1] + z = (prediction - mean) / std + # self.prediction = prediction + # print(scores.ZScore(z)) + return scores.ZScore(z) diff --git a/neuronunit/tests/base.py b/neuronunit/tests/base.py index 9b416c7bf..8820d01bc 100644 --- a/neuronunit/tests/base.py +++ b/neuronunit/tests/base.py @@ -1,177 +1,231 @@ -"""Base classes and attributes for many neuronunit tests. No classes here meant -for direct use in testing""" +"""Base classes and attributes for many neuronunit tests. + +No classes here meant for direct use in testing. +""" from types import MethodType -import quantities as pq import numpy as np +import quantities as pq import sciunit +from sciunit.tests import ProtocolToFeaturesTest import sciunit.scores as scores -from sciunit.errors import ObservationError -import neuronunit.capabilities as cap +import neuronunit.capabilities as ncap +import sciunit.capabilities as scap from neuronunit import neuroelectro +import pickle -AMPL = 0.0*pq.pA -DELAY = 100.0*pq.ms -DURATION = 300.0*pq.ms +AMPL = 100.0 * pq.pA +DELAY = 100.0 * pq.ms +DURATION = 1000.0 * pq.ms +PASSIVE_AMPL = -10.0 * pq.pA +PASSIVE_DELAY = 100.0 * pq.ms +PASSIVE_DURATION = 300.0 * pq.ms -class VmTest(sciunit.Test): +class VmTest(ProtocolToFeaturesTest): """Base class for tests involving the membrane potential of a model.""" - def __init__(self, - observation={'mean':None,'std':None}, - name=None, - **params): - - super(VmTest,self).__init__(observation,name,**params) + def __init__(self, observation={"mean": None, "std": None}, name=None, **params): + super(VmTest, self).__init__(observation, name, **params) cap = [] for cls in self.__class__.__bases__: cap += cls.required_capabilities self.required_capabilities += tuple(cap) self._extra() - required_capabilities = (cap.ProducesMembranePotential,) + required_capabilities = ( + scap.Runnable, + ncap.ProducesMembranePotential, + ) - name = '' + name = "" units = pq.Dimensionless - ephysprop_name = '' - - # Observation values with units. - united_observation_keys = ['value','mean','std'] - - # Observation values without units. - nonunited_observation_keys = [] + ephysprop_name = "" + + observation_schema = [ + ( + "Mean, Standard Deviation, N", + { + "mean": {"units": True, "required": True}, + "std": {"units": True, "min": 0, "required": True}, + "n": {"type": "integer", "min": 1}, + }, + ), + ( + "Mean, Standard Error, N", + { + "mean": {"units": True, "required": True}, + "sem": {"units": True, "min": 0, "required": True}, + "n": {"type": "integer", "min": 1, "required": True}, + }, + ), + ] + + default_params = { + "amplitude": 0.0 * pq.pA, + "delay": 100.0 * pq.ms, + "duration": 300.0 * pq.ms, + "dt": 0.025 * pq.ms, + "padding": 200 * pq.ms, + } + + params_schema = { + "dt": {"type": "time", "min": 0, "required": False}, + "tmax": {"type": "time", "min": 0, "required": False}, + "delay": {"type": "time", "min": 0, "required": False}, + "duration": {"type": "time", "min": 0, "required": False}, + "amplitude": {"type": "current", "required": False}, + "padding": {"type": "time", "min": 0, "required": False}, + } def _extra(self): pass - def validate_observation(self, observation, - united_keys=None, - nonunited_keys=None): - if united_keys is None: - united_keys = self.united_observation_keys - if nonunited_keys is None: - nonunited_keys = self.nonunited_observation_keys - try: - assert type(observation) is dict - assert any([key in observation for key in united_keys]) \ - or len(nonunited_keys) - for key in united_keys: - if key in observation: - assert type(observation[key]) is pq.quantity.Quantity - for key in nonunited_keys: - if key in observation: - assert type(observation[key]) is not pq.quantity.Quantity \ - or observation[key].units == pq.Dimensionless - except Exception as e: - key_str = 'and/or a '.join(['%s key' % key for key in united_keys]) - msg = ("Observation must be a dictionary with a %s and each key " - "must have units from the quantities package." % key_str) - raise ObservationError(msg) - for key in united_keys: - if key in observation: - provided = observation[key].simplified.units - if not isinstance(self.units,pq.Dimensionless): - required = self.units.simplified.units - else: - required = self.units - if provided != required: # Units don't match spec. - msg = ("Units of %s are required for %s but units of %s " - "were provided" % (required.dimensionality.__str__(), - key, - provided.dimensionality.__str__()) - ) - raise ObservationError(msg) - if 'std' not in observation: - if all([x in observation for x in ['sem','n']]): - observation['std'] = observation['sem'] * np.sqrt(observation['n']) - elif 'mean' in observation: - raise ObservationError(("Observation must have an 'std' key " - "or both 'sem' and 'n' keys.")) + def compute_params(self): + self.params["tmax"] = ( + self.params["delay"] + self.params["duration"] + self.params["padding"] + ) + + def validate_observation(self, observation): + super(VmTest, self).validate_observation(observation) + # Catch another case that is trickier + if "std" not in observation: + observation["std"] = observation["sem"] * np.sqrt(observation["n"]) return observation - def bind_score(self, score, model, observation, prediction): - score.related_data['vm'] = model.get_membrane_potential() - score.related_data['model_name'] = '%s_%s' % (model.name,self.name) + def condition_model(self, model): + model.set_run_params(t_stop=self.params["tmax"]) - def plot_vm(self, ax=None, ylim=(None,None)): + def bind_score(self, score, model, observation, prediction): + try: + score.related_data["vm"] = model.get_membrane_potential() + except: + try: + score.related_data["vm"] = model._backend.get_membrane_potential() + print(score.related_data["vm"]) + except: + score.related_data["vm"] = None + score.related_data["model_name"] = "%s_%s" % (model.name, self.name) + + def plot_vm(self, ax=None, ylim=(None, None)): """A plot method the score can use for convenience.""" import matplotlib.pyplot as plt + if ax is None: ax = plt.gca() - vm = score.related_data['vm'].rescale('mV') - ax.plot(vm.times,vm) - y_min = float(vm.min()-5.0*pq.mV) if ylim[0] is None else ylim[0] - y_max = float(vm.max()+5.0*pq.mV) if ylim[1] is None else ylim[1] - ax.set_xlim(vm.times.min(),vm.times.max()) - ax.set_ylim(y_min,y_max) - ax.set_xlabel('Time (s)') - ax.set_ylabel('Vm (mV)') - score.plot_vm = MethodType(plot_vm, score) # Bind to the score. - score.unpicklable.append('plot_vm') + vm = score.related_data["vm"].rescale("mV") + ax.plot(vm.times, vm) + y_min = float(vm.min() - 5.0 * pq.mV) if ylim[0] is None else ylim[0] + y_max = float(vm.max() + 5.0 * pq.mV) if ylim[1] is None else ylim[1] + ax.set_xlim(vm.times.min(), vm.times.max()) + ax.set_ylim(y_min, y_max) + ax.set_xlabel("Time (s)") + ax.set_ylabel("Vm (mV)") + + score.plot_vm = MethodType(plot_vm, score) # Bind to the score. + score.unpicklable.append("plot_vm") @classmethod def neuroelectro_summary_observation(cls, neuron, cached=False): reference_data = neuroelectro.NeuroElectroSummary( - neuron = neuron, # Neuron type lookup using the NeuroLex ID. - ephysprop = {'name': cls.ephysprop_name}, # Ephys property name in - # NeuroElectro ontology. - cached = cached - ) - reference_data.get_values(quiet=not cls.verbose) # Get and verify summary data - # from neuroelectro.org. - - - observation = {'mean': reference_data.mean*cls.units, - 'std': reference_data.std*cls.units, - 'n': reference_data.n} + neuron=neuron, # Neuron type lookup using the NeuroLex ID. + ephysprop={"name": cls.ephysprop_name}, # Ephys property name in + # NeuroElectro ontology. + cached=cached, + ) + + # Get and verify summary data from neuroelectro.org. + reference_data.get_values(quiet=not cls.verbose) + + if hasattr(reference_data, "mean"): + observation = { + "mean": reference_data.mean * cls.units, + "std": reference_data.std * cls.units, + "n": reference_data.n, + } + else: + observation = None + return observation @classmethod def neuroelectro_pooled_observation(cls, neuron, cached=False, quiet=True): reference_data = neuroelectro.NeuroElectroPooledSummary( - neuron = neuron, # Neuron type lookup using the NeuroLex ID. - ephysprop = {'name': cls.ephysprop_name}, # Ephys property name in - # NeuroElectro ontology. - cached = cached - ) - reference_data.get_values(quiet=quiet) # Get and verify summary data - # from neuroelectro.org. - observation = {'mean': reference_data.mean*cls.units, - 'std': reference_data.std*cls.units, - 'n': reference_data.n} + neuron=neuron, # Neuron type lookup using the NeuroLex ID. + # Ephys property name in NeuroElectro ontology. + ephysprop={"name": cls.ephysprop_name}, + cached=cached, + ) + # Get and verify summary data from neuroelectro.org. + reference_data.get_values(quiet=quiet) + observation = { + "mean": reference_data.mean * cls.units, + "std": reference_data.std * cls.units, + "n": reference_data.n, + } return observation - def sanity_check(self,rheobase,model): - #model.inject_square_current(self.params['injected_square_current']) - self.params['injected_square_current']['delay'] = DELAY - self.params['injected_square_current']['duration'] = DURATION - self.params['injected_square_current']['amplitude'] = rheobase - model.inject_square_current(self.params['injected_square_current']) - - mp = model.results['vm'] - import math - for i in mp: - if math.isnan(i): - return False - - sws=cap.spike_functions.get_spike_waveforms(model.get_membrane_potential()) - - for i,s in enumerate(sws): + def sanity_check(self, rheobase, model): + self.params["injected_square_current"]["delay"] = self.params["delay"] + self.params["injected_square_current"]["duration"] = self.params["duration"] + self.params["injected_square_current"]["amplitude"] = rheobase + model.inject_square_current(self.params["injected_square_current"]) + + mp = model.results["vm"] + if np.any(np.isnan(mp)) or np.any(np.isinf(mp)): + return False + + sws = ncap.spike_functions.get_spike_waveforms(model.get_membrane_potential()) + + for i, s in enumerate(sws): s = np.array(s) dvdt = np.diff(s) - import math for j in dvdt: - if math.isnan(j): + if np.isnan(j): return False return True + @classmethod + def get_default_injected_square_current(cls): + current = { + key: cls.default_params[key] for key in ["duration", "delay", "amplitude"] + } + return current + + def get_injected_square_current(self): + current = { + key: self.default_params[key] for key in ["duration", "delay", "amplitude"] + } + return current + @property def state(self): - state = super(VmTest,self).state - return self._state(state=state, exclude=['unpicklable','verbose']) \ No newline at end of file + state = super(VmTest, self).state + return self._state(state=state, exclude=["unpicklable", "verbose"]) + + +class FakeTest(sciunit.Test): + """Fake test class. + + Just computes agreement between an observation key and a model attribute. + e.g. + observation = {'a':[0.8,0.3], 'b':[0.5,0.1], 'vr':[-70*pq.mV,5*pq.mV]} + fake_test_a = FakeTest("test_a",observation=observation) + fake_test_b = FakeTest("test_b",observation=observation) + fake_test_vr = FakeTest("test_vr",observation=observation) + """ + + def generate_prediction(self, model): + self.key_param = self.name.split("_")[1] + return model.attrs[self.key_param] + + def compute_score(self, observation, prediction): + mean = observation[self.key_param][0] + std = observation[self.key_param][1] + z = (prediction - mean) / std + return scores.ZScore(z) diff --git a/neuronunit/tests/channel.py b/neuronunit/tests/channel.py index 59b9b80c8..77e899607 100644 --- a/neuronunit/tests/channel.py +++ b/neuronunit/tests/channel.py @@ -1,145 +1,207 @@ """NeuronUnit Test classes for ion channel models""" -from types import MethodType - import numpy as np from scipy.interpolate import interp1d from scipy.optimize import minimize import quantities as pq -import sciunit -from sciunit.scores import BooleanScore,FloatScore +from sciunit.tests import ProtocolToFeaturesTest +from sciunit.scores import BooleanScore, FloatScore from sciunit.converters import AtMostToBoolean -from neuronunit.capabilities.channel import * - -class _IVCurveTest(sciunit.Test): - """Test the agreement between channel IV curves produced by models and those - observed in experiments""" - - def __init__(self, observation, name='IV Curve Test', scale=False, - **params): +from neuronunit.capabilities.channel import NML2ChannelAnalysis +import pyneuroml.analysis.NML2ChannelAnalysis as ca + + +class _IVCurveTest(ProtocolToFeaturesTest): + """Test the agreement between channel IV curves produced by models and + those observed in experiments""" + + def __init__(self, observation, name="IV Curve Test", scale=False, **params): self.validate_observation(observation) - for key,value in observation.items(): + for key, value in observation.items(): setattr(self, key, value) - self.scale = scale # Whether to scale the predicted IV curve to - # minimize distance from the observed IV curve - self.converter = AtMostToBoolean(pq.Quantity(1.0,'pA**2')) - super(_IVCurveTest,self).__init__(observation, name=name, **params) - - required_capabilities = (ProducesIVCurve,) + # Whether to scale the predicted IV curve to + # minimize distance from the observed IV curve + self.scale = scale + self.converter = AtMostToBoolean(pq.Quantity(1.0, "pA**2")) + super(_IVCurveTest, self).__init__(observation, name=name, **params) + + required_capabilities = (NML2ChannelAnalysis,) + + units = {"v": pq.V, "i": pq.pA} + score_type = BooleanScore - + + observation_schema = [ + ( + "Current Array, Voltage Array", + { + "i": {"units": True, "iterable": True, "required": True}, + "v": {"units": True, "iterable": True, "required": True}, + }, + ), + ] + + default_params = { + "v_min": -80.0 * pq.mV, + "v_step": 20.0 * pq.mV, + "v_max": 60.0 * pq.mV, + "dt": 0.025 * pq.ms, + "tmax": 100 * pq.ms, + } + + params_schema = { + "v_min": {"type": "voltage", "required": True}, + "v_step": {"type": "voltage", "min": 1e-3, "required": True}, + "v_max": {"type": "voltage", "required": True}, + "dt": {"type": "time", "min": 0, "required": False}, + "tmax": {"type": "time", "min": 0, "required": False}, + } + def validate_observation(self, observation): - assert type(observation) is dict - for item in ['v', 'i']: - assert item in observation - assert type(observation[item]) in [list,tuple] \ - or isinstance(observation[item],np.ndarray) - if hasattr(observation['v'],'units'): - observation['v'] = observation['v'].rescale(pq.V) # Rescale to V - if hasattr(observation['i'],'units'): - observation['i'] = observation['i'].rescale(pq.pA) # Rescale to pA - - def generate_prediction(self, model): - raise Exception(("This is a meta-class for tests; use tests derived " - "from this class instead")) - + super(_IVCurveTest, self).validate_observation(observation) + if hasattr(observation["v"], "units"): + observation["v"] = observation["v"].rescale(pq.V) # Rescale to V + if hasattr(observation["i"], "units"): + observation["i"] = observation["i"].rescale(pq.pA) # Rescale to pA + + def validate_params(self, params): + params = super(_IVCurveTest, self).validate_params(params) + assert params["v_max"] > params["v_min"], "v_max must be greater than v_min" + return params + + def condition_model(self, model): + # Start with the model defaults + params = model.default_params.copy() + # Required parameter by ChannelAnalysis to commpute an IV curve + params.update(**{"ivCurve": True}) + # Use test parameters as well to build the new LEMS file + params.update(self.params) + # Make a new LEMS file with these parameters + model.ca_make_lems_file(**params) + + def setup_protocol(self, model): + """Implement sciunit.tests.ProtocolToFeatureTest.setup_protocol.""" + self.condition_model(model) + + def get_result(self, model): + results = model.ca_run_lems_file(verbose=True) + return results + + def extract_features(self, model, result): + """Implemented in the subclasses""" + return NotImplementedError() + def interp_IV_curves(self, v_obs, i_obs, v_pred, i_pred): """ - Interpolate IV curve along a larger and evenly spaced range of holding - potentials. Ensures that test is restricted to ranges present in both + Interpolate IV curve along a larger and evenly spaced range of holding + potentials. Ensures that test is restricted to ranges present in both predicted and observed data. - v_pred: The array of holding potentials in model. - i_pred: The array or dict of resulting currents (single values) + v_pred: The array of holding potentials in model. + i_pred: The array or dict of resulting currents (single values) from model . - v_obs: The array of holding potentials from the experiments. - i_obs: The array or dict of resulting currents (single values) + v_obs: The array of holding potentials from the experiments. + i_obs: The array or dict of resulting currents (single values) from the experiment . """ - - if type(i_obs)==dict: - # Convert dict to numpy array. + + if isinstance(i_obs, dict): + # Convert dict to numpy array. units = list(i_obs.values())[0].units i_obs = np.array([i_obs[float(key)] for key in v_obs]) * units - if type(i_pred)==dict: + if isinstance(i_pred, dict): # Convert dict to numpy array. units = list(i_pred.values())[0].units i_pred = np.array([i_pred[float(key)] for key in v_pred]) * units - - # Removing units temporarily due to + + # Removing units temporarily due to # https://github.com/python-quantities/python-quantities/issues/105 v_obs = v_obs.rescale(pq.mV).magnitude i_obs = i_obs.rescale(pq.pA).magnitude v_pred = v_pred.rescale(pq.mV).magnitude i_pred = i_pred.rescale(pq.pA).magnitude - - f_obs = interp1d(v_obs, i_obs, kind='cubic') - f_pred = interp1d(v_pred, i_pred, kind='cubic') - start = max(v_obs[0],v_pred[0]) # Min voltage in mV - stop = min(v_obs[-1],v_pred[-1]) # Max voltage in mV - - v_new = np.linspace(start, stop, 100) # 1 mV interpolation - i_obs_interp = f_obs(v_new)*pq.pA # Interpolated current from data - i_pred_interp = f_pred(v_new)*pq.pA # Interpolated current from model - - return {'v':v_new*pq.mV, 'i_pred':i_pred_interp, 'i_obs':i_obs_interp} - + + f_obs = interp1d(v_obs, i_obs, kind="cubic") + f_pred = interp1d(v_pred, i_pred, kind="cubic") + start = max(v_obs[0], v_pred[0]) # Min voltage in mV + stop = min(v_obs[-1], v_pred[-1]) # Max voltage in mV + + v_new = np.linspace(start, stop, 100) # 1 mV interpolation + i_obs_interp = f_obs(v_new) * pq.pA # Interpolated current from data + i_pred_interp = f_pred(v_new) * pq.pA # Interpolated current from model + + return {"v": v_new * pq.mV, "i_pred": i_pred_interp, "i_obs": i_obs_interp} + def compute_score(self, observation, prediction): # Sum of the difference between the curves. o = observation p = prediction - interped = self.interp_IV_curves(o['v'], o['i'], p['v'], p['i']) - + interped = self.interp_IV_curves(o["v"], o["i"], p["v"], p["i"]) + if self.scale: + def f(sf): - score = FloatScore.compute_ssd(interped['i_obs'], - (10**sf)*interped['i_pred']) + score = FloatScore.compute_ssd( + interped["i_obs"], (10 ** sf) * interped["i_pred"] + ) return score.score.magnitude - result = minimize(f,0.0) - scale_factor = 10**result.x - interped['i_pred'] *= scale_factor + + result = minimize(f, 0.0) + scale_factor = 10 ** result.x + interped["i_pred"] *= scale_factor else: scale_factor = 1 - - score = FloatScore.compute_ssd(interped['i_obs'],interped['i_pred']) - score.related_data['scale_factor'] = scale_factor + + score = FloatScore.compute_ssd(interped["i_obs"], interped["i_pred"]) + score.related_data["scale_factor"] = scale_factor self.interped = interped return score - def bind_score(self,score,model,observation,prediction): - score.description = ("The sum-squared difference in the observed and " - "predicted current values over the range of the " - "tested holding potentials.") - # Observation and prediction are automatically stored in score, - # but since that is before interpolation, we store the interpolated - # versions as well. + def bind_score(self, score, model, observation, prediction): + score.description = ( + "The sum-squared difference in the observed and " + "predicted current values over the range of the " + "tested holding potentials." + ) + # Observation and prediction are automatically stored in score, + # but since that is before interpolation, we store the interpolated + # versions as well. score.related_data.update(self.interped) def plot(score): import matplotlib.pyplot as plt + rd = score.related_data - rd['v'] = rd['v'].rescale('V') - rd['i_obs'] = rd['i_obs'].rescale('A') - rd['i_pred'] = rd['i_pred'].rescale('A') - score.test.last_model.plot_iv_curve(rd['v'],rd['i_obs'], - color='k',label='Observed (data)') - score.test.last_model.plot_iv_curve(rd['v'],rd['i_pred'], - color='r',same_fig=True,label='Predicted (model)') - plt.title('%s on %s: %s' % (score.model,score.test,score)) - - score.plot = plot.__get__(score) # Binds this method to 'score'. + rd["v"] = rd["v"].rescale("V") + rd["i_obs"] = rd["i_obs"].rescale("A") + rd["i_pred"] = rd["i_pred"].rescale("A") + score.test.last_model.plot_iv_curve( + rd["v"], rd["i_obs"], color="k", label="Observed (data)" + ) + score.test.last_model.plot_iv_curve( + rd["v"], + rd["i_pred"], + color="r", + same_fig=True, + label="Predicted (model)", + ) + plt.title("%s on %s: %s" % (score.model, score.test, score)) + + score.plot = plot.__get__(score) # Binds this method to 'score'. return score - + class IVCurveSSTest(_IVCurveTest): """Test IV curves using steady-state curent""" - - def generate_prediction(self, model): - return model.produce_iv_curve_ss(**self.params) - - + + def extract_features(self, model, results): + iv_data = model.ca_compute_iv_curve(results) + return {"v": iv_data["hold_v"], "i": iv_data["i_steady"]} + + class IVCurvePeakTest(_IVCurveTest): """Test IV curves using steady-state curent""" - - def generate_prediction(self, model): - return model.produce_iv_curve_peak(**self.params) \ No newline at end of file + + def extract_features(self, model, results): + iv_data = model.ca_compute_iv_curve(results) + return {"v": iv_data["hold_v"], "i": iv_data["i_peak"]} diff --git a/neuronunit/tests/druckmann2013.py b/neuronunit/tests/druckmann2013.py new file mode 100644 index 000000000..d42b5ab7a --- /dev/null +++ b/neuronunit/tests/druckmann2013.py @@ -0,0 +1,1481 @@ +""" +Tests of features described in Druckmann et. al. 2013 (https://academic.oup.com/cercor/article/23/12/2994/470476) + +AP analysis details (from suplementary info): https://github.com/scidash/neuronunit/files/2295064/bhs290supp.pdf + +Numbers in class names refer to the numbers in the publication table +""" + +from elephant.spike_train_generation import threshold_detection +from neo import AnalogSignal +from numba import jit +from .base import np, pq, ncap, VmTest, scores + + +# How this file is different to the original. +# Some big functions broken into smaller ones, for greater modularity +# use of numba jit where possible. +# Different inheritance designed to work with optimizer. + +per_ms = pq.UnitQuantity('per_ms',1.0/pq.ms,symbol='per_ms') + +none_score = { + 'mean': None, + 'std': None, + 'n': 0 + } + +debug = False #True + + + + +@jit +def get_diff_spikes(vm): + differentiated = np.diff(vm) + spikes = len([np.any(differentiated) > 0.000143667327364]) + return spikes + + +@jit +def get_diff(vm,axis=None): + if axis is not None: + differentiated = np.diff(vm,axis=axis) + else: + differentiated = np.diff(vm) + return differentiated + + +class Druckmann2013AP: + """ + This is a helper class that computes/finds aspects of APs as defined in Druckmann 2013 + """ + + def __init__(self, waveform, begin_time): + self.waveform = waveform + self.begin_time = begin_time + + self.begin_time.units = pq.ms + + def get_beginning(self): + """ + The beginning of a spike was then determined by a crossing of a threshold on the derivative of the voltage (12mV/msec). + + :return: the voltage and time of the AP beginning + """ + begining_time = self.begin_time + beginning_voltage = self.waveform[0] + + return beginning_voltage, begining_time + + def get_amplitude(self): + """ + The amplitude of a spike is given by the difference between the voltage at the beginning and peak of the spike. + + :return: the amplitude value + """ + v_begin, _ = self.get_beginning() + v_peak, _ = self.get_peak() + + return (v_peak - v_begin) + + def get_halfwidth(self): + """ + Amount of time in between the first crossing (in the upwards direction) of the + half-height voltage value and the second crossing (in the downwards direction) of + this value, for the first AP. Half-height voltage is the voltage at the beginning + of the AP plus half the AP amplitude. + + :return: + """ + v_begin, _ = self.get_beginning() + amp = self.get_amplitude() + half_v = v_begin + amp / 2.0 + + above_half_v = np.where(self.waveform.magnitude > half_v)[0] + + half_start = self.waveform.times[above_half_v[0]] + half_end = self.waveform.times[above_half_v[-1]] + + half_width = half_end - half_start + half_width.units = pq.ms + + return half_width + + + def get_peak(self): + """ + The peak point of the spike is the maximum in between the beginning and the end. + + :return: the voltage and time of the peak + """ + if not hasattr(self, 'peak'): + value = self.waveform.max() + time = self.begin_time + self.waveform.times[np.where(self.waveform.magnitude == value)[0]] + time.units = pq.ms + self.peak = { 'value': value, 'time': time } + + return self.peak['value'], self.peak['time'] + + def get_trough(self): + peak_v, peak_t = self.get_peak() + + post_peak_waveform = self.waveform.magnitude[np.where(self.waveform.times > (peak_t - self.begin_time))] + post_peak_waveform = AnalogSignal(post_peak_waveform, units=self.waveform.units, sampling_period=self.waveform.sampling_period) + + + value = post_peak_waveform.min() + time = peak_t + post_peak_waveform.times[np.where(post_peak_waveform.magnitude == value)[0]] + time = time[0] + time.units = pq.ms + + return value, time +def isolate_code_block(threshold_crosses,start_time,dvdt_threshold_crosses,dvdt_zero_crosses,vm): + ''' + The introduction of this function is was not syntactically necissated. The reason for this functions existence + is to support code modularity. + ''' + threshold_crosses = threshold_crosses[np.where(threshold_crosses > start_time)] + dvdt_threshold_crosses = dvdt_threshold_crosses[np.where(dvdt_threshold_crosses > start_time)] + dvdt_zero_crosses = dvdt_zero_crosses[np.where(dvdt_zero_crosses > start_time)] + + # Normally, there should be at least as many dvdt threshold crosses as there are v threshold crosses + if len(dvdt_threshold_crosses) < len(threshold_crosses): + dvdt_threshold_crosses = threshold_crosses # for slowly rising APs (e.g. muscle) use the vm threshold as the beginning + + ap_beginnings = [] + prev_beginning = start_time + prev_threshold = start_time + vm_chopped = 0 + for ti, curr_thresh in enumerate(threshold_crosses): + prev_dvdt_zero = dvdt_zero_crosses[np.where(dvdt_zero_crosses < curr_thresh)] + + if len(prev_dvdt_zero) == 0: + prev_dvdt_zero = start_time + else: + prev_dvdt_zero = prev_dvdt_zero[-1] + + earliest_dvdt_thresh_since_prev_ap = dvdt_threshold_crosses[ + np.where((dvdt_threshold_crosses > prev_beginning) & (dvdt_threshold_crosses > prev_threshold) & (dvdt_threshold_crosses > prev_dvdt_zero)) + ] + + if len(earliest_dvdt_thresh_since_prev_ap) != 0: + earliest_dvdt_thresh_since_prev_ap = earliest_dvdt_thresh_since_prev_ap[0] + else: + if ti == 0: + earliest_dvdt_thresh_since_prev_ap = prev_beginning + else: + raise Exception("Did not find a dvdt threshold crossing since previous AP") + + ap_beginnings.append(earliest_dvdt_thresh_since_prev_ap) + + prev_beginning = earliest_dvdt_thresh_since_prev_ap + prev_threshold = curr_thresh + + # The number of ap beginnings should match the number aps detected + assert len(np.unique(ap_beginnings)) == len(threshold_crosses) + vm_mag = vm.magnitude + vm_times = vm.times + vm_chopped = np.split(vm_mag, np.isin(vm_times, ap_beginnings).nonzero()[0]) + # The waveform should be cut into APs+1 pieces (1st waveform is steady state) + assert len(vm_chopped) == len(threshold_crosses)+1 + return vm_chopped, threshold_crosses, ap_beginnings, vm_mag, vm_times + + +class Druckmann2013Test(VmTest): + """ + All tests inheriting from this class assume that the subject model: + 1. Is at steady state at time 0 (i.e. resume from SS) + 2. Starting at t=0, will have a 2s step current injected into soma, at least once + """ + required_capabilities = (ncap.ProducesActionPotentials,) + score_type = scores.ZScore + + def __init__(self, current_amplitude, **params): + super(Druckmann2013Test, self).__init__(**params) + + self.params = { + 'injected_square_current': { + 'delay': 1000 * pq.ms, + 'duration': 2000 * pq.ms, + 'amplitude': current_amplitude + }, + 'threshold': -20 * pq.mV, + 'beginning_threshold': 12.0 * pq.mV/pq.ms, + 'ap_window': 10 * pq.ms, + 'repetitions': 1, + } + + # This will be an array that stores DruckmannAPs + self.APs = None + + def generate_prediction(self, model): + results = [] + + reps = self.params['repetitions'] + + for rep in range(reps): + pred = self.generate_repetition_prediction(model) + results.append(pred) + + if reps > 1: + return self.aggregate_repetitions(results) + else: + return results[0] + + def generate_repetition_prediction(self, model): + raise NotImplementedError() + + def aggregate_repetitions(self, results): + values = [rep['mean'] for rep in results if rep['mean'] is not None] + + units = values[0].units if len(values) > 0 else self.units + + if len(values) > 0: + return { + 'mean': np.mean(values) * units, + 'std': np.std(values) * units, + 'n': len(results) + } + + return none_score + + def current_length(self): + return self.params['injected_square_current']['duration'] + + def get_APs(self, model): + """ + Spikes were detected by a crossing of a voltage threshold (-20 mV). + + :param model: model which provides the waveform to analyse + :return: a list of Druckman2013APs + """ + + vm = model.get_membrane_potential() + + vm_times = vm.times + start_time = self.params['injected_square_current']['delay'].rescale('sec') + end_time = start_time + self.params['injected_square_current']['duration'].rescale('sec') + vm = AnalogSignal(vm.magnitude[np.where(vm_times <= end_time)], sampling_period=vm.sampling_period, units=vm.units) + try: + dvdt = np.array(np.append([0], get_diff(vm, axis=0))) * pq.mV / vm.sampling_period + except: + dvdt = np.array(np.append([0], get_diff(vm))) * pq.mV / vm.sampling_period + dvdt = AnalogSignal(dvdt, sampling_period=vm.sampling_period) + + threshold_crosses = threshold_detection(vm,threshold=self.params['threshold']) + dvdt_threshold_crosses = threshold_detection(dvdt,threshold=self.params['beginning_threshold']) + dvdt_zero_crosses = threshold_detection(dvdt, threshold=0 * pq.mV/pq.ms) + + vm_chopped, threshold_crosses, ap_beginnings, vm_mag, vm_times = isolate_code_block( + threshold_crosses, \ + start_time,dvdt_threshold_crosses,dvdt_zero_crosses,vm \ + ) + ap_waveforms = [] + for i, b in enumerate(ap_beginnings): + if i != len(ap_beginnings)-1: + waveform = vm_chopped[i+1] + else: + # Keep up to 100ms of the last AP + waveform = vm_mag[np.where((vm_times >= b) & (vm_times < b + 100.0*pq.ms))] + + waveform = AnalogSignal(waveform, units=vm.units, sampling_rate=vm.sampling_rate) + + ap_waveforms.append(waveform) + + # Pass in the AP waveforms and the times when they occured + self.APs = [] + for i, b in enumerate(ap_beginnings): + self.APs.append(Druckmann2013AP(ap_waveforms[i], ap_beginnings[i])) + + return self.APs + + def get_ISIs(self, model=None): + aps = self.get_APs(model) + + ap_times = np.array([ap.get_beginning()[1] for ap in aps]) + + isis = get_diff(ap_times)# np.diff(ap_times) + + return isis + + +class AP12AmplitudeDropTest(Druckmann2013Test): + """ + 1. Drop in AP amplitude (amp.) from first to second spike (mV) + + Difference in the voltage value between the amplitude of the first and second AP. + + Negative values indicate 2nd AP amplitude > 1st + """ + + name = "Drop in AP amplitude from 1st to 2nd AP" + description = "Difference in the voltage value between the amplitude of the first and second AP" + + units = pq.mV + def generate_prediction(self, model): + model.inject_square_current(self.params['injected_square_current']) + + aps = self.get_APs(model) + + if len(aps) >= 2: + + if debug: + from matplotlib import pyplot as plt + plt.plot(aps[0].waveform) + plt.plot(aps[1].waveform) + plt.show() + + return { + 'mean': aps[0].get_amplitude() - aps[1].get_amplitude(), + 'std': 0, + 'n': 1 + } + + else: + return none_score + + +class AP1SSAmplitudeChangeTest(Druckmann2013Test): + """ + 2. AP amplitude change from first spike to steady-state (mV) + + Steady state AP amplitude is calculated as the mean amplitude of the set of APs + that occurred during the latter third of the current step. + """ + + name = "AP amplitude change from 1st AP to steady-state" + description = """Steady state AP amplitude is calculated as the mean amplitude of the set of APs + that occurred during the latter third of the current step.""" + + units = pq.mV + def generate_prediction(self, model): + current_start = self.params['injected_square_current']['delay'] + + start_latter_3rd = current_start + self.current_length() * 2.0 / 3.0 + end_latter_3rd = current_start + self.current_length() + + aps = self.get_APs(model) + amps = np.array([ap.get_amplitude() for ap in aps]) * pq.mV + ap_times = np.array([ap.get_beginning()[1] for ap in aps]) * pq.ms + + ss_aps = np.where( + (ap_times >= start_latter_3rd) & + (ap_times <= end_latter_3rd)) + + ss_amps = amps[ss_aps] + + if len(aps) > 0 and len(ss_amps) > 0: + + if debug: + from matplotlib import pyplot as plt + plt.plot(aps[0].waveform) + for i in ss_aps[0]: + plt.plot(aps[i].waveform) + plt.show() + + return { + 'mean': amps[0] - ss_amps.mean(), + 'std': ss_amps.std(), + 'n': len(ss_amps) + } + + return none_score + +class AP1AmplitudeTest(Druckmann2013Test): + """ + 3. AP 1 amplitude (mV) + + Amplitude of the first AP. + """ + + name = "First AP amplitude" + description = "Amplitude of the first AP" + + units = pq.mV + + def generate_prediction(self, model, ap_index=0): + + model.inject_square_current(self.params['injected_square_current']) + + aps = self.get_APs(model) + + if len(aps) > ap_index: + amp = aps[ap_index].get_amplitude() + + assert 0 * self.units < amp < 200 * self.units + + return { + 'mean': amp, + 'std': 0, + 'n': 1 + } + + else: + return none_score + +class AP1WidthHalfHeightTest(Druckmann2013Test): + """ + 4. AP 1 width at half height (ms) + """ + + name = "First AP width at its half height" + description = """Amount of time in between the first crossing (in the upwards direction) of the + half-height voltage value and the second crossing (in the downwards direction) of + this value, for the first AP. Half-height voltage is the voltage at the beginning of + the AP plus half the AP amplitude.""" + + units = pq.ms + + def generate_prediction(self, model, ap_index=0): + + model.inject_square_current(self.params['injected_square_current']) + + aps = self.get_APs(model) + + if len(aps) > ap_index: + + hw = aps[ap_index].get_halfwidth() + + assert 0 * self.units < hw < 100 * self.units + + return { + 'mean': hw, + 'std': 0, + 'n': 1 + } + + return none_score + +class AP1WidthPeakToTroughTest(Druckmann2013Test): + """ + 5. AP 1 peak to trough time (ms) + + Amount of time between the peak of the first AP and the trough, i.e., the + minimum of the AHP. + """ + + name = "AP 1 peak to trough time" + description = """Amount of time between the peak of the first AP and the trough, i.e., the minimum of the AHP""" + + units = pq.ms + + def generate_prediction(self, model, ap_index=0): + + model.inject_square_current(self.params['injected_square_current']) + + aps = self.get_APs(model) + + if len(aps) > ap_index: + ap = aps[ap_index] + + _, peak_t = ap.get_peak() + _, trough_t = ap.get_trough() + + width = trough_t - peak_t + + if debug: + from matplotlib import pyplot as plt + plt.plot(aps[0].waveform) + plt.xlim(0, 1000) + plt.show() + + assert 0 * self.units <= width < 100 * self.units + + return { + 'mean': width, + 'std': 0, + 'n': 1 + } + + return none_score + + +class AP1RateOfChangePeakToTroughTest(Druckmann2013Test): + """ + 6. AP 1 peak to trough rate of change (mV/ms) + + Difference in voltage value between peak and trough divided by the amount of time in + between the peak and trough. + """ + + name = "AP 1 peak to trough rate of change" + description = """Difference in voltage value between peak and trough over the amount of time in between the peak and trough.""" + + units = pq.mV/pq.ms + + def generate_prediction(self, model, ap_index=0): + + model.inject_square_current(self.params['injected_square_current']) + + aps = self.get_APs(model) + + if len(aps) > ap_index: + ap = aps[ap_index] + + peak_v, peak_t = ap.get_peak() + trough_v, trough_t = ap.get_trough() + + width = trough_t - peak_t + + if width == 0 * pq.ms: + width = ap.waveform.sampling_period + + change = (trough_v - peak_v) / width + + assert change < 0 * self.units + + return { + 'mean': change, + 'std': 0, + 'n': 1 + } + + return none_score + +class AP1AHPDepthTest(Druckmann2013Test): + """ + 7. AP 1 Fast AHP depth (mV) + + Difference between the minimum of voltage at the trough and the voltage value at + the beginning of the AP. + """ + + name = "AP 1 Fast AHP depth" + description = """Difference between the minimum of voltage at the trough and the voltage value at + the beginning of the AP.""" + + units = pq.mV + + def generate_prediction(self, model, ap_index=0): + + model.inject_square_current(self.params['injected_square_current']) + + aps = self.get_APs(model) + + if len(aps) > ap_index: + ap = aps[ap_index] + + begin_v, _ = ap.get_beginning() + trough_v, _ = ap.get_trough() + + change = begin_v - trough_v + + + if debug: + from matplotlib import pyplot as plt + plt.plot(aps[0].waveform) + plt.xlim(0, 1000) + plt.show() + + return { + 'mean': change, + 'std': 0, + 'n': 1 + } + + else: + return none_score + + + +class AP2AmplitudeTest(AP1AmplitudeTest): + """ + 8. AP 2 amplitude (mV) + + Same as :any:`AP1AmplitudeTest` but for second AP + """ + + name = "AP 2 amplitude" + description = """Same as :any:`AP1AmplitudeTest` but for second AP""" + + def generate_prediction(self, model): + return super(AP2AmplitudeTest, self).generate_prediction(model, ap_index=1) + +class AP2WidthHalfHeightTest(AP1WidthHalfHeightTest): + """ + 9. AP 2 width at half height (ms) + + Same as :any:`AP1WidthHalfHeightTest` but for second AP + """ + + name = "AP 2 width at half height" + description = """Same as :any:`AP1WidthHalfHeightTest` but for second AP""" + + def generate_prediction(self, model): + return super(AP2WidthHalfHeightTest, self).generate_prediction(model, ap_index=1) + +class AP2WidthPeakToTroughTest(AP1WidthPeakToTroughTest): + """ + 10. AP 2 peak to trough time (ms) + + Same as :any:`AP1WidthPeakToTroughTest` but for second AP + """ + + name = "AP 2 peak to trough time" + description = """Same as :any:`AP1WidthPeakToTroughTest` but for second AP""" + + def generate_prediction(self, model): + return super(AP2WidthPeakToTroughTest, self).generate_prediction(model, ap_index=1) + +class AP2RateOfChangePeakToTroughTest(AP1RateOfChangePeakToTroughTest): + """ + 11. AP 2 peak to trough rate of change (mV/ms) + + Same as :any:`AP1RateOfChangePeakToTroughTest` but for second AP + """ + + name = "AP 2 peak to trough rate of change" + description = """Same as :any:`AP1RateOfChangePeakToTroughTest` but for second AP""" + + def generate_prediction(self, model): + return super(AP2RateOfChangePeakToTroughTest, self).generate_prediction(model, ap_index=1) + +class AP2AHPDepthTest(AP1AHPDepthTest): + """ + 12. AP 2 Fast AHP depth (mV) + + Same as :any:`AP1AHPDepthTest` but for second AP + """ + + name = "AP 2 Fast AHP depth" + description = """Same as :any:`AP1AHPDepthTest` but for second AP""" + + def generate_prediction(self, model): + return super(AP2AHPDepthTest, self).generate_prediction(model, ap_index=1) + +class AP12AmplitudeChangePercentTest(Druckmann2013Test): + """ + 13. Percent change in AP amplitude, first to second spike (%) + + Difference in AP amplitude between first and second AP divided by the first AP + amplitude. + """ + + name = "Percent change in AP amplitude, first to second spike " + description = """Difference in AP amplitude between first and second AP divided by the first AP + amplitude.""" + + units = pq.dimensionless + + def generate_prediction(self, model): + + model.inject_square_current(self.params['injected_square_current']) + + aps = self.get_APs(model) + + if len(aps) >= 2: + + amp = self.params['injected_square_current']['amplitude'] + + amp1 = AP1AmplitudeTest(amp).generate_prediction(model)["mean"] + amp2 = AP2AmplitudeTest(amp).generate_prediction(model)["mean"] + + change = (amp2-amp1)/amp1 * 100.0; + + return { + 'mean': change, + 'std': 0, + 'n': 1 + } + + else: + return none_score + +class AP12HalfWidthChangePercentTest(Druckmann2013Test): + """ + 14. Percent change in AP width at half height, first to second spike (%) + + Difference in AP width at half-height between first and second AP divided by the + first AP width at half-height. + """ + + name = "Percent change in AP width at half height, first to second spike" + description = """Difference in AP width at half-height between first and second AP divided by the + first AP width at half-height.""" + + units = pq.dimensionless + + def generate_prediction(self, model): + + model.inject_square_current(self.params['injected_square_current']) + + aps = self.get_APs(model) + + if len(aps) >= 2: + + amp = self.params['injected_square_current']['amplitude'] + + width1 = AP1WidthHalfHeightTest(amp).generate_prediction(model)["mean"] + width2 = AP2WidthHalfHeightTest(amp).generate_prediction(model)["mean"] + + change = (width2-width1)/width1 * 100.0; + + return { + 'mean': change, + 'std': 0, + 'n': 1 + } + + else: + return none_score + +class AP12RateOfChangePeakToTroughPercentChangeTest(Druckmann2013Test): + """ + 15. Percent change in AP peak to trough rate of change, first to second spike (%) + + Difference in peak to trough rate of change between first and second AP divided + by the first AP peak to trough rate of change. + """ + + name = "Percent change in AP peak to trough rate of change, first to second spike" + description = """Difference in peak to trough rate of change between first and second AP divided + by the first AP peak to trough rate of change.""" + + units = pq.dimensionless + + def generate_prediction(self, model): + + model.inject_square_current(self.params['injected_square_current']) + + aps = self.get_APs(model) + + if len(aps) >= 2: + + amp = self.params['injected_square_current']['amplitude'] + + roc1 = AP1RateOfChangePeakToTroughTest(amp).generate_prediction(model)["mean"] + roc2 = AP2RateOfChangePeakToTroughTest(amp).generate_prediction(model)["mean"] + + change = (roc2-roc1)/roc1 * 100.0; + + return { + 'mean': change, + 'std': 0, + 'n': 1 + } + + else: + return none_score + +class AP12AHPDepthPercentChangeTest(Druckmann2013Test): + """ + 16 Percent change in AP fast AHP depth, first to second spike (%) + + Difference in depth of fast AHP between first and second AP divided by the first + AP depth of fast AHP. + """ + + name = "Percent change in AP fast AHP depth, first to second spike" + description = """Difference in depth of fast AHP between first and second AP divided by the first + AP depth of fast AHP.""" + + units = pq.dimensionless + + def generate_prediction(self, model): + + model.inject_square_current(self.params['injected_square_current']) + + aps = self.get_APs(model) + + if len(aps) >= 2: + + amp = self.params['injected_square_current']['amplitude'] + + ap1 = AP1AHPDepthTest(amp).generate_prediction(model)["mean"] + ap2 = AP2AHPDepthTest(amp).generate_prediction(model)["mean"] + + change = (ap2-ap1)/ap1 * 100.0; + + return { + 'mean': change, + 'std': 0, + 'n': 1 + } + + else: + return none_score + +class InputResistanceTest(Druckmann2013Test): + """ + 17 Input resistance for steady-state current (MOhm) + + Input resistance calculated by injecting weak subthreshold hyperpolarizing and + depolarizing step currents. Input resistance was taken as linear fit of current to + voltage difference. + """ + + name = "Input resistance for steady-state current" + description = """Input resistance calculated by injecting weak subthreshold hyperpolarizing and + depolarizing step currents. Input resistance was taken as linear fit of current to + voltage difference""" + + units = pq.Quantity(1,'MOhm') + + def __init__(self, injection_currents=np.array([])*pq.nA, **params): + super(InputResistanceTest, self).__init__(current_amplitude=None, **params) + + if not injection_currents or len(injection_currents) < 1: + raise Exception("Test requires at least one current injection") + + for i in injection_currents: + if i.units != pq.nA: + i.units = pq.nA + + self.injection_currents = injection_currents + #@jit + def generate_prediction(self, model): + voltages = [] + + # Loop through the injection currents + for i in self.injection_currents: + + # Set the current amplitude + self.params['injected_square_current']['amplitude'] = i + + # Inject current + model.inject_square_current(self.params['injected_square_current']) + + # Get the voltage waveform + vm = model.get_membrane_potential() + + # The voltage at final 1ms of current step is assumed to be steady state + ss_voltage = np.median(vm.magnitude[np.where((vm.times >= 1999*pq.ms) & (vm.times <= 2000*pq.ms))]) * pq.mV + + voltages.append(ss_voltage) + + if debug: + from matplotlib import pyplot as plt + plt.plot(vm) + + if debug: + plt.show() + + # Rescale units + amps = [i.rescale('A') for i in self.injection_currents] + volts = [v.rescale('V') for v in voltages] + + # If there is only one injection current available, use the resting voltage as 0 Amp current response + if len(self.injection_currents) < 2: + amps.append(0 * pq.A) + + resting_voltage = np.median(vm.magnitude[np.where((vm.times >= 999*pq.ms) & (vm.times <= 1000*pq.ms))]) * pq.mV + resting_voltage.units = pq.V + volts.append(resting_voltage) + + + # v = ir -> r is slope of v(i) curve + slope, _ = np.polyfit(amps, volts, 1) + slope *= pq.Ohm + slope.units = pq.Quantity(1,'MOhm') + + if debug: + from matplotlib import pyplot as plt + plt.plot(amps, volts) + plt.show() + + assert slope > -0.001 * self.units + + return { + 'mean': slope, + 'std': 0, + 'n': 1 + } + + + +class AP1DelayMeanTest(Druckmann2013Test): + """ + 18 Average delay to AP 1 (ms) + + Mean of the delay to beginning of first AP over experimental repetitions of step + currents. + """ + + name = "First AP delay mean" + description = "Mean delay to the first AP" + + units = pq.ms + + def __init__(self, current_amplitude, repetitions=7, **params): + super(AP1DelayMeanTest, self).__init__(current_amplitude, **params) + + self.params['repetitions'] = repetitions + + def generate_repetition_prediction(self, model, ap_index=0): + + model.inject_square_current(self.params['injected_square_current']) + + aps = self.get_APs(model) + + if len(aps) > ap_index: + delay = self.params['injected_square_current']['delay'] + + if debug: + from matplotlib import pyplot as plt + vm = model.get_membrane_potential() + plt.plot(vm.times.magnitude, vm.magnitude) + plt.xlim(1, aps[ap_index].get_beginning()[1].rescale('sec').magnitude + 0.1) + plt.show() + + ap_delay = aps[ap_index].get_beginning()[1] - delay + + assert ap_delay > -1 * self.units + + return { + 'mean': ap_delay, + 'std': 0, + 'n': 1 + } + + return none_score + +class AP1DelaySDTest(AP1DelayMeanTest): + """ + 19 SD of delay to AP 1 (ms) + + Standard deviation of the delay to beginning of first AP over experimental + repetitions of step currents. + """ + + name = "First AP delay standard deviation" + description = "Standard deviation of delay to the first AP" + + units = pq.ms + + def aggregate_repetitions(self, results): + aggregate = super(AP1DelaySDTest, self).aggregate_repetitions(results) + + if aggregate['mean'] is not None: + aggregate['mean'] = aggregate['std'] + aggregate['std'] = 0 * self.units + + assert aggregate['mean'] >= 0 * self.units + + return aggregate + + return none_score + +class AP2DelayMeanTest(AP1DelayMeanTest): + """ + 20 Average delay to AP 2 (ms) + + Same as :any:`AP1DelayMeanTest` but for 2nd AP + """ + + name = "Second AP delay mean" + description = "Mean of delay to the second AP" + + def generate_repetition_prediction(self, model, ap_index=0): + return super(AP2DelayMeanTest, self).generate_repetition_prediction(model, ap_index=1) + +class AP2DelaySDTest(AP1DelaySDTest): + """ + 21 SD of delay to AP 2 (ms) + + Same as :any:`AP1DelaySDTest` but for 2nd AP + + Only stochastic models will have a non-zero value for this test + """ + + name = "Second AP delay standard deviation" + description = "Standard deviation of delay to the second AP" + + def generate_repetition_prediction(self, model, ap_index=0): + return super(AP2DelaySDTest, self).generate_repetition_prediction(model, ap_index=1) + +class Burst1ISIMeanTest(Druckmann2013Test): + """ + 22 Average initial burst interval (ms) + + Initial burst interval is defined as the average of the first two ISIs, i.e., the average + of the time differences between the first and second AP and the second and third + AP. This feature is the average the initial burst interval across experimental + repetitions. + """ + + name = "Initial burst interval mean" + description = "Mean of the initial burst interval" + + units = pq.ms + + def __init__(self, current_amplitude, repetitions=7, **params): + super(Burst1ISIMeanTest, self).__init__(current_amplitude, **params) + + self.params['repetitions'] = repetitions + def generate_repetition_prediction(self, model): + + model.inject_square_current(self.params['injected_square_current']) + + aps = self.get_APs(model) + + if len(aps) >= 3: + t1 = aps[0].get_beginning()[1] + t2 = aps[1].get_beginning()[1] + t3 = aps[2].get_beginning()[1] + + isi1 = t2 - t1 + isi2 = t3 - t2 + + if debug: + print("first 3 aps: %s, %s, %s"%(t1,t2,t3)) + print("2 isis: %s, %s" % (isi1, isi2)) + + isi_mean = (isi1 + isi2) / 2.0 + + assert isi_mean > 0 * self.units + + return { + 'mean': isi_mean, + 'std': 0, + 'n': 1 + } + + return none_score + +class Burst1ISISDTest(Burst1ISIMeanTest): + """ + 23 SD of average initial burst interval (ms) + + The standard deviation of the initial burst interval across experimental repetitions. + """ + + name = "Initial burst interval std" + description = "StDev of the initial burst interval" + + def aggregate_repetitions(self, results): + aggregate = super(Burst1ISISDTest, self).aggregate_repetitions(results) + + if aggregate['mean'] is not None: + aggregate['mean'] = aggregate['std'] + aggregate['std'] = 0 * self.units + + assert aggregate['mean'] >= 0 * self.units + + return aggregate + + return none_score + +class InitialAccommodationMeanTest(Druckmann2013Test): + """ + 24 Average initial accommodation (%) + + Initial accommodation is defined as the percent difference between the spiking rate of the + *first* fifth of the step current and the *third* fifth of the step current. + """ + + name = "Initial accomodation mean" + description = "Mean of the initial accomodation" + + units = pq.dimensionless + + def generate_prediction(self, model): + + model.inject_square_current(self.params['injected_square_current']) + + current_start = self.params['injected_square_current']['delay'] + + start_1st_5th = current_start + end_1st_5th = current_start + self.current_length() * 1/5.0 + + start_3rd_5th = current_start + self.current_length() * 2/5.0 + end_3rd_5th = current_start + self.current_length() * 3/5.0 + + aps = self.get_APs(model) + ap_times = np.array([ap.get_beginning()[1] for ap in aps]) + + ap_count15 = np.where((ap_times >= start_1st_5th) & (ap_times <= end_1st_5th))[0] + ap_count35 = np.where((ap_times >= start_3rd_5th) & (ap_times <= end_3rd_5th))[0] + + if debug: + print("aps in 1st 5th: %s" % (len(ap_count15))) + print("aps in 3rd 5th: %s" % (len(ap_count35))) + + if len(ap_count15) > 0: + percent_diff = (len(ap_count35) - len(ap_count15)) / float(len(ap_count15)) * 100.0 + + return { + 'mean': percent_diff, + 'std': 0, + 'n': 1 + } + + return none_score + +class SSAccommodationMeanTest(Druckmann2013Test): + """ + 25 Average steady-state accommodation (%) + + Steady-state accommodation is defined as the percent difference between the spiking rate + of the *first* fifth of the step current and the *last* fifth of the step current. + """ + + name = "Steady state accomodation mean" + description = "Mean of the steady state accomodation" + + units = pq.dimensionless + + def generate_prediction(self, model): + + model.inject_square_current(self.params['injected_square_current']) + + current_start = self.params['injected_square_current']['delay'] + + start_1st_5th = current_start + end_1st_5th = current_start + self.current_length() * 1/5.0 + + start_last_5th = current_start + self.current_length() * 4/5.0 + end_last_5th = current_start + self.current_length() + + aps = self.get_APs(model) + ap_times = np.array([ap.get_beginning()[1] for ap in aps]) + + ap_count15 = np.where((ap_times >= start_1st_5th) & (ap_times <= end_1st_5th))[0] + ap_count55 = np.where((ap_times >= start_last_5th) & (ap_times <= end_last_5th))[0] + + if len(ap_count15) > 0: + percent_diff = (len(ap_count55) - len(ap_count15)) / float(len(ap_count15)) * 100.0 + + if debug: + print("aps in 1st 5th: %s" % (len(ap_count15))) + print("aps in last 5th: %s" % (len(ap_count55))) + + return { + 'mean': percent_diff, + 'std': 0, + 'n': 1 + } + + return none_score + + +class AccommodationRateToSSTest(Druckmann2013Test): + """ + 26 Rate of accommodation to steady-state (percent/ms) + + The percent difference between the spiking rate of the *first* fifth of the step current and + *final* fifth of the step current divided by the time taken to first reach the rate of + steady state accommodation. + + Note: It's not clear what is meant by "time taken to first reach the rate of steady state + accommodation". Here, it's computed as smallest t of an ISI which is longer than 0.95 of the + mean ISIs in the final fifth of the current step. + """ + + name = "ISI Accomodation Rate" + description = "Rate of ISI Accomodation" + + units = per_ms + + def generate_prediction(self, model): + model.inject_square_current(self.params['injected_square_current']) + + current_start = self.params['injected_square_current']['delay'] + + start_1st_5th = current_start + end_1st_5th = current_start + self.current_length() * 1/5.0 + + start_last_5th = current_start + self.current_length() * 4/5.0 + end_last_5th = current_start + self.current_length() + + aps = self.get_APs(model) + ap_times = np.array([ap.get_beginning()[1] for ap in aps]) + + aps_15 = np.where((ap_times >= start_1st_5th) & (ap_times <= end_1st_5th))[0] + aps_55 = np.where((ap_times >= start_last_5th) & (ap_times <= end_last_5th))[0] + + if len(aps_15) > 0 and len(aps_55) >= 2: + percent_diff = (len(aps_55) - len(aps_15)) / float(len(aps_15)) * 100.0 + + if debug: + print("aps in 1st 5th vs last 5th, percent change: %s" % (percent_diff)) + + isis = get_diff(ap_times) + isi_times = ap_times[1:] + + isis_55 = isis[np.where((isi_times >= start_last_5th) & (isi_times <= end_last_5th))] + + ss_isi = np.mean(isis_55) + + if debug: + print("mean ISI at SS: %s" % (ss_isi)) + + nearly_ss_isis = np.where(isis >= 0.95*ss_isi)[0] + + if len(nearly_ss_isis) > 0: + + first_nearly_ss_isi_time = isi_times[nearly_ss_isis[0]] * pq.ms + + if debug: + print("time of first nearly mean SS ISI: %s" % (first_nearly_ss_isi_time)) + + if first_nearly_ss_isi_time > 0: + return { + 'mean': percent_diff / (first_nearly_ss_isi_time - self.params['injected_square_current']['delay']), + 'std': 0, + 'n': 1 + } + + return none_score + +class AccommodationAtSSMeanTest(Druckmann2013Test): + """ + 27 Average accommodation at steady-state (%) + + Accommodation analysis based on a fit of the ISIs to an exponential function: + ISI = A+B*exp(-t/tau). This feature gives the relative size of the constant term (A) to + the term before the exponent (B). + """ + + name = "ISI Steady state accomodation mean" + description = "Mean of the ISI steady state accomodation" + + units = pq.dimensionless + + def generate_prediction(self, model): + + model.inject_square_current(self.params['injected_square_current']) + + aps = self.get_APs(model) + ap_times = np.array([ap.get_beginning()[1] for ap in aps]) + + if len(aps) >= 4: + isis = get_diff(ap_times) + isi_delays = ap_times[1:] - self.params['injected_square_current']['delay'].rescale('ms').magnitude + isi_delays = isi_delays - isi_delays[0] + + def isi_func(t, A, B, tau): + return A + B * np.exp(-t/(1.0*tau)) + + from lmfit import Model + + model = Model(isi_func) + params = model.make_params(A=isis[-1], B=-1.0, tau=10.0) + params['A'].min = 0 + params['B'].max = 0 + params['tau'].min = 0 + + result = model.fit(isis, t=isi_delays, params=params) + + A = result.best_values["A"] + B = result.best_values["B"] + tau = result.best_values["tau"] + + + if debug: + from matplotlib import pyplot as plt + print(result.fit_report()) + + plt.plot(isi_delays, isis, 'bo') + plt.plot(isi_delays, result.best_fit, 'r-') + plt.show() + + return { + 'mean': self.get_final_result(A, B, tau), + 'std': 0, + 'n': 1 + } + + return none_score + + @jit + def get_final_result(self, A, B, tau): + return B / float(A) * 100.0 + +class AccommodationRateMeanAtSSTest(AccommodationAtSSMeanTest): + """ + 28 Average rate of accommodation during steady-state + + Accommodation analysis based on a fit of the ISIs to an exponential function. + This feature is the time constant (tau) of the exponent. + """ + + name = "ISI accomodation time constant" + description = "Time constant of the ISI accomodation" + + units = pq.ms + + def get_final_result(self, A, B, tau): + return tau * pq.ms + +class ISICVTest(Druckmann2013Test): + """ + 29 Average inter-spike interval (ISI) coefficient of variation (CV) (unitless) + + Coefficient of variation (mean divided by standard deviation) of the distribution + of ISIs. + """ + + name = "ISI CV" + description = "ISI Coefficient of Variation" + + units = pq.dimensionless + + def generate_prediction(self, model): + + model.inject_square_current(self.params['injected_square_current']) + + isis = self.get_ISIs(model) + + if len(isis) >= 2: + + mean = np.mean(isis) + std = np.std(isis) + + if debug: + print("isi mean: %s std: %s"%(mean, std)) + + if std > 1e-5: + + assert mean > 0 * self.units + + return { + 'mean': mean / std, + 'std': 0, + 'n': 1 + } + + return none_score + + +class ISIMedianTest(Druckmann2013Test): + """ + 30 Median of the distribution of ISIs (ms) + + Median of the distribution of ISIs. + """ + + name = "ISI Median" + description = "ISI Median" + + units = pq.ms + + def generate_prediction(self, model): + + model.inject_square_current(self.params['injected_square_current']) + + isis = self.get_ISIs(model) + + if len(isis) >= 1: + + med = np.median(isis) * self.units + + assert med > 0 * self.units + + return { + 'mean': med, + 'std': 0, + 'n': 1 + } + + else: + return none_score + +class ISIBurstMeanChangeTest(Druckmann2013Test): + """ + 31 Average change in ISIs during a burst (%) + + Difference between the first and second ISI divided by the value of the first ISI. + """ + + name = "ISI Burst Mean Change" + description = "ISI Burst Mean Change" + + units = pq.dimensionless + + def generate_prediction(self, model): + + model.inject_square_current(self.params['injected_square_current']) + + isis = self.get_ISIs(model) + + if len(isis) >= 2: + + if debug: + print("ISI1: %s ISI2: %s Change: %s"%(isis[0],isis[1],isis[1] - isis[0])) + + if isis[0] > 1e-5: + return { + 'mean': (isis[1] - isis[0])/isis[0] * 100.0, + 'std': 0, + 'n': 1 + } + + return none_score + +class SpikeRateStrongStimTest(Druckmann2013Test): + """ + 32 Average rate, strong stimulus (Hz) + + Firing rate of strong stimulus. + """ + + name = "Strong Stimulus Firing Rate" + description = "Strong Stimulus Firing Rate" + + units = pq.Hz + + def generate_prediction(self, model): + + model.inject_square_current(self.params['injected_square_current']) + + aps = self.get_APs(model) + + duration = self.current_length() + + spike_rate = len(aps) / duration + spike_rate.units = pq.Hz + + if debug: + print("APs: %s Duration: %s"%(len(aps), duration)) + + assert 400 * self.units > spike_rate > 0 * self.units + + return { + 'mean': spike_rate, + 'std': 0, + 'n': 1 + } + +class AP1DelayMeanStrongStimTest(AP1DelayMeanTest): + """ + 33 Average delay to AP 1, strong stimulus (ms) + + Same as :any:`AP1DelayMeanTest` but for strong stimulus + """ + +class AP1DelaySDStrongStimTest(AP1DelaySDTest): + """ + 34 SD of delay to AP 1, strong stimulus (ms) + + Same as :any:`AP1DelaySDTest` but for strong stimulus + """ + +class AP2DelayMeanStrongStimTest(AP2DelayMeanTest): + """ + 35 Average delay to AP 2, strong stimulus (ms) + + + Same as :any:`AP2DelayMeanTest` but for strong stimulus + """ + +class AP2DelaySDStrongStimTest(AP2DelaySDTest): + """ + 36 SD of delay to AP 2, strong stimulus (ms) + + Same as :any:`AP2DelaySDTest` but for strong stimulus + """ + +class Burst1ISIMeanStrongStimTest(Burst1ISIMeanTest): + """ + 37 Average initial burst ISI, strong stimulus (ms) + + Same as :any:`Burst1ISIMeanTest` but for strong stimulus + """ + +class Burst1ISISDStrongStimTest(Burst1ISISDTest): + """ + 38 SD of average initial burst ISI, strong stimulus (ms) + + Same as :any:`Burst1ISISDTest` but for strong stimulus + """ diff --git a/neuronunit/tests/dynamics.py b/neuronunit/tests/dynamics.py index 9188463c0..c195b9fae 100644 --- a/neuronunit/tests/dynamics.py +++ b/neuronunit/tests/dynamics.py @@ -4,73 +4,80 @@ from elephant.statistics import isi from elephant.statistics import cv from elephant.statistics import lv +from elephant.spike_train_generation import threshold_detection + from neuronunit.capabilities.channel import * -from .base import np, pq, cap, VmTest, scores, AMPL, DELAY, DURATION +from .base import np, pq, ncap, VmTest, scores, AMPL, DELAY, DURATION from .waveform import InjectedCurrentAPWidthTest -from .fi import RheobaseTestP, RheobaseTest +from .fi import RheobaseTest + + +import matplotlib.pyplot as plt +import numpy as np +from sklearn.linear_model import LinearRegression class TFRTypeTest(RheobaseTest): - """Tests whether a model has particular threshold firing rate dynamics, + """Test whether a model has particular threshold firing rate dynamics, i.e. type 1 or type 2.""" def __init__(self, *args, **kwargs): - super(TFRTypeTest,self).__init__(*args,**kwargs) + super(TFRTypeTest, self).__init__(*args, **kwargs) if self.name == self.__class__.name: - self.name = "Firing Rate Type %d test" % self.observation['type'] + self.name = "Firing Rate Type %d test" % self.observation["type"] name = "Firing Rate Type test" - description = (("A test of the instantaneous firing rate dynamics, i.e. " - "type 1 or type 2")) + description = ( + "A test of the instantaneous firing rate dynamics, i.e. " "type 1 or type 2" + ) score_type = scores.BooleanScore - + def validate_observation(self, observation): - super(TFRTypeTest,self).validate_observation(observation, - united_keys=['rheobase'], - nonunited_keys=['type']) - assert ('type' in observation) and (observation['type'] in [1,2]), \ - ("observation['type'] must be either 1 or 2, corresponding to " - "type 1 or type 2 threshold firing rate dynamics.") + super(TFRTypeTest, self).validate_observation( + observation, united_keys=["rheobase"], nonunited_keys=["type"] + ) + assert ("type" in observation) and (observation["type"] in [1, 2]), ( + "observation['type'] must be either 1 or 2, corresponding to " + "type 1 or type 2 threshold firing rate dynamics." + ) def generate_prediction(self, model): - """Implementation of sciunit.Test.generate_prediction.""" + """Implement sciunit.Test.generate_prediction.""" model.rerun = True - if 'rheobase' in self.observation: - guess = self.observation['rheobase'] + if "rheobase" in self.observation: + guess = self.observation["rheobase"] else: - guess = 100.0*pq.pA + guess = 100.0 * pq.pA lookup = self.threshold_FI(model, self.units, guess=guess) - sub = np.array([x for x in lookup if lookup[x]==0])*self.units - supra = np.array([x for x in lookup if lookup[x]>0])*self.units + sub = np.array([x for x in lookup if lookup[x] == 0]) * self.units + supra = np.array([x for x in lookup if lookup[x] > 0]) * self.units if self.verbose: if len(sub): - print("Highest subthreshold current is %s" \ - % float(sub.max().round(2))) + print("Highest subthreshold current is %s" % float(sub.max().round(2))) else: print("No subthreshold current was tested.") if len(supra): - print("Lowest suprathreshold current is %s" \ - % supra.min().round(2)) + print("Lowest suprathreshold current is %s" % supra.min().round(2)) else: print("No suprathreshold current was tested.") prediction = None if len(sub) and len(supra): - supra = np.array([x for x in lookup if lookup[x]>0]) # No units + supra = np.array([x for x in lookup if lookup[x] > 0]) # No units thresh_i = supra.min() n_spikes_at_thresh = lookup[thresh_i] if n_spikes_at_thresh == 1: - prediction = 1 # Type 1 dynamics. + prediction = 1 # Type 1 dynamics. elif n_spikes_at_thresh > 1: - prediction = 2 # Type 2 dynamics. + prediction = 2 # Type 2 dynamics. return prediction def compute_score(self, observation, prediction): - """Implementation of sciunit.Test.score_prediction.""" + """Implement sciunit.Test.score_prediction.""" if prediction is None: score = scores.InsufficientDataScore(None) else: @@ -79,168 +86,274 @@ def compute_score(self, observation, prediction): class BurstinessTest(InjectedCurrentAPWidthTest): - """Tests whether a model exhibits the observed burstiness""" + """Test whether a model exhibits the observed burstiness""" name = "Burstiness test" - description = (("A test of AP bursting at the provided current")) + description = "A test of AP bursting at the provided current" score_type = scores.RatioScore units = pq.dimensionless - nonunited_observation_keys = ['cv'] + nonunited_observation_keys = ["cv"] cv_threshold = 1.0 def generate_prediction(self, model): - model.inject_square_current(observation['current']) + model.inject_square_current(self.run_params["current"]) spike_train = model.get_spike_train() if len(spike_train) >= 3: - cv(spike_train)*pq.dimensionless - #isis = isi(spike_train) - #cv_old = np.std(isis) / np.mean(isis) + value = cv(spike_train) * pq.dimensionless else: - cv = None - return {'cv':cv} + value = None + return {"cv": value} def compute_score(self, observation, prediction): - """Implementation of sciunit.Test.score_prediction.""" + """Implement sciunit.Test.score_prediction.""" if prediction is None: score = scores.InsufficientDataScore(None) else: - score = self.score_type.compute(observation,prediction,key='cv') + score = self.score_type.compute(observation, prediction, key="cv") return score class ISICVTest(VmTest): - """Tests whether a model exhibits the observed burstiness""" + """Test whether a model exhibits the observed burstiness""" name = "ISI Coefficient of Variation Test" - description = (("For neurons and muscle cells check the Coefficient of " - "Variation on a list of Interval Between Spikes given a " - "spike train recording.")) + description = ( + "For neurons and muscle cells check the Coefficient of " + "Variation on a list of Interval Between Spikes given a " + "spike train recording." + ) score_type = scores.RatioScore units = pq.dimensionless united_observation_keys = [] - nonunited_observation_keys = ['cv'] - - def generate_prediction(self, model = None): + nonunited_observation_keys = ["cv"] + + def generate_prediction(self, model=None): st = model.get_spike_train() - #prediction = abs(cv(st)) if len(st) >= 3: - value = abs(cv(st))*pq.dimensionless + value = abs(cv(st)) * pq.dimensionless else: value = None - prediction = {'cv':value} + prediction = {"cv": value} return prediction def compute_score(self, observation, prediction): - """Implementation of sciunit.Test.score_prediction.""" + """Implement sciunit.Test.score_prediction.""" if prediction is None: score = scores.InsufficientDataScore(None) else: - print(observation,prediction) - score = self.score_type.compute(observation,prediction,key='cv') + # print(observation, prediction) + score = self.score_type.compute(observation, prediction, key="cv") return score class ISITest(VmTest): - """Tests whether a model exhibits the observed Inter Spike Intervals""" + """Test whether a model exhibits the observed Inter Spike Intervals""" - def __init__(self, observation={'isi_mean':None,'isi_std':None}, - name=None, - params=None): + def __init__( + self, observation={"isi_mean": None, "isi_std": None}, name=None, params=None + ): pass name = "Inter Spike Interval Tests" - description = (("For neurons and muscle cells check the mean Interval " - "Between Spikes given a spike train recording.")) + description = ( + "For neurons and muscle cells check the mean Interval " + "Between Spikes given a spike train recording." + ) score_type = scores.ZScore units = pq.ms def __init__(self, *args, **kwargs): - super(ISITest,self).__init__(*args,**kwargs) + super(ISITest, self).__init__(*args, **kwargs) if self.name == self.__class__.name: - self.name = "Inter Spike Interval Test %d test" % \ - self.observation['type'] + self.name = "Inter Spike Interval Test %d test" % self.observation["type"] - def generate_prediction(self, model = None): + def generate_prediction(self, model=None): st = model.get_spike_train() isis = isi(st) - value = float(np.mean(isis))*1000.0*pq.ms - prediction = {'value':value} + value = float(np.mean(isis)) * 1000.0 * pq.ms + prediction = {"value": value} return prediction def compute_score(self, observation, prediction): - """Implementation of sciunit.Test.score_prediction.""" + """Implement sciunit.Test.score_prediction.""" if prediction is None: score = scores.InsufficientDataScore(None) else: - score = self.score_type.compute(observation,prediction) + score = self.score_type.compute(observation, prediction) + return score + + +class InjectedCurrent: + """Metaclass to mixin with InjectedCurrent tests.""" + + def __init__(self, amp): + self.amp = amp + default_params = dict(VmTest.default_params) + default_params.update({"amplitude": 100 * pq.pA}) + default_params.update({"delay": 100 * pq.ms}) + default_params.update({"duration": 1000 * pq.ms}) + self.default_params = default_params + + required_capabilities = (ncap.ReceivesSquareCurrent,) + + def get_params(self): + self.verbose = False + self.params = {} + self.params["injected_square_current"] = self.default_params + self.params["injected_square_current"]["amplitude"] = self.amp + return self.params + + +def get_firing_rate(model, input_current): + + # inject a test current into the neuron and call it's run() function. + # get the spike times using spike_tools.get_spike_times + # from the spike times, calculate the firing rate f + IC = InjectedCurrent(amp=input_current * pq.pA) + params = IC.get_params() + if "injected_square_current" in params.keys(): + params = params["injected_square_current"] + if "dt" in params.keys(): + params.pop("dt", None) + if "padding" in params.keys(): + params.pop("padding", None) + + model.inject_square_current(**params) + vm = model.get_membrane_potential() + spikes = threshold_detection(vm, threshold=0 * pq.mV) + + if len(spikes): + isi_easy = isi(spikes) + rate = 1.0 / np.mean(isi_easy) + # print(rate,spikes) + + if rate == np.nan or np.isnan(rate): + rate = 0 + rate = rate * pq.Hz + else: + rate = 0 * pq.Hz + # print(rate,spikes) + return rate + + +class FITest(VmTest): + name = "FITest" + """ + file:///home/user/Downloads/CellTypes_Ephys_Overview.pdf + For all long square responses, the average firing rate and the stimulus amplitude were combined to estimate + the curve of frequency response of the cell versus stimulus intensity (“f-I curve”). The suprathreshold part of this + curve was fit to a straight line, and the slope of this line was recorded as a cell-wide feature (Figure 6C). + """ + + def generate_prediction(self, model, plot=False): + I = np.arange(-5, 200, 10.0) # a range of current inputs + + fr = [] + # loop over current values + supra_thresh_I = [] + for I_amp in I: + firing_rate = get_firing_rate(model, I_amp) + if firing_rate > 0: + supra_thresh_I.append(I_amp) + fr.append(firing_rate) + model = LinearRegression() + x = np.array(supra_thresh_I).reshape(-1, 1) + y = np.array(fr) + if len(x): + model.fit(x, y) + m = model.coef_ * (pq.Hz / pq.pA) + if type(m) is type(list()): + m = m[0] + self.prediction = {"value": m} + if plot: + c = model.intercept_ + y = supra_thresh_I * float(m) + c + plt.figure() # new figure + plt.plot(supra_thresh_I, fr) + plt.plot(supra_thresh_I, y) + plt.xlabel("Amplitude of Injecting step current (pA)") + plt.ylabel("Firing rate (Hz)") + plt.grid() + plt.show() + else: + self.prediction = None # {'value':m} + return self.prediction + + def compute_score(self, observation, prediction): + """Implement sciunit.Test.score_prediction.""" + if prediction is None: + score = scores.InsufficientDataScore(None) + else: + score = self.score_type.compute(observation, prediction) return score class FiringRateTest(RheobaseTest): - """Tests whether a model exhibits the observed burstiness""" + """Test whether a model exhibits the observed burstiness""" def __init__(self, *args, **kwargs): - super(LocalVariationTest,self).__init__(*args,**kwargs) + super(LocalVariationTest, self).__init__(*args, **kwargs) if self.name == self.__class__.name: - self.name = "Firing Rate Type %d test" % self.observation['type'] + self.name = "Firing Rate Type %d test" % self.observation["type"] name = "Firing Rate Test" - description = (("Spikes Per Second.")) + description = "Spikes Per Second." score_type = scores.RatioScore units = pq.dimensionless - def generate_prediction(self, model = None): - """Implementation of sciunit.Test.generate_prediction.""" + def generate_prediction(self, model=None): + """Implements sciunit.Test.generate_prediction.""" ass = model.get_membrane_potential() spike_count = model.get_spike_count() - window = ass.t_stop-ass.t_start - prediction['sps_mean'] = float(spike_count/window) + window = ass.t_stop - ass.t_start + prediction = {"value": float(spike_count / window)} # prediction should be number of spikes divided by time. - return predicted_rate + return prediction def compute_score(self, observation, prediction): - """Implementation of sciunit.Test.score_prediction.""" + """Implement sciunit.Test.score_prediction.""" if prediction is None: score = scores.InsufficientDataScore(None) else: - score = self.score_type.compute(observation,prediction,key='sps_mean') + score = self.score_type.compute(observation, prediction, key="sps_mean") return score class LocalVariationTest(VmTest): """Tests whether a model exhibits the observed burstiness""" - def __init__(self, observation={'cv_mean':None,'cv_std':None}, - name=None, - params=None): + def __init__( + self, observation={"cv_mean": None, "cv_std": None}, name=None, params=None + ): pass - required_capabilities = (cap.ReceivesSquareCurrent, - cap.ProducesSpikes) + required_capabilities = (ncap.ReceivesSquareCurrent, ncap.ProducesSpikes) name = "Local Variation test" - description = (("For neurons and muscle cells with slower non firing " - "dynamics like CElegans neurons check to see how much " - "variation is in the continuous membrane potential.")) + description = ( + "For neurons and muscle cells with slower non firing " + "dynamics like CElegans neurons check to see how much " + "variation is in the continuous membrane potential." + ) score_type = scores.RatioScore units = pq.dimensionless - local_variation = 0.0 # 1.0 + local_variation = 0.0 # 1.0 def __init__(self, *args, **kwargs): - super(LocalVariationTest,self).__init__(*args,**kwargs) + super(LocalVariationTest, self).__init__(*args, **kwargs) if self.name == self.__class__.name: - self.name = "Firing Rate Type %d test" % self.observation['type'] + self.name = "Firing Rate Type %d test" % self.observation["type"] - def generate_prediction(self, model = None): + def generate_prediction(self, model=None): prediction = lv(model.get_membrane_potential()) return prediction @@ -251,5 +364,5 @@ def compute_score(self, observation, prediction): if prediction is None: score = scores.InsufficientDataScore(None) else: - score = self.score_type.compute(observation,prediction,key='lv') + score = self.score_type.compute(observation, prediction, key="lv") return score diff --git a/neuronunit/tests/fi.py b/neuronunit/tests/fi.py index 9d1beee74..18a5095da 100644 --- a/neuronunit/tests/fi.py +++ b/neuronunit/tests/fi.py @@ -1,192 +1,383 @@ -"""F/I neuronunit tests, e.g. investigating firing rates and patterns as a -function of input current""" +"""F/I neuronunit tests. + +For example, investigating firing rates and patterns as a +function of input current. +""" import os +import multiprocessing + +global cpucount +npartitions = cpucount = multiprocessing.cpu_count() +from .base import np, pq, ncap, VmTest, scores, AMPL, DELAY, DURATION +import quantities +import neuronunit + + +import dask.bag as db +import quantities as pq +import numpy as np +import copy +import time +import copy +import dask +from neuronunit.capabilities.spike_functions import ( + get_spike_waveforms, + spikes2amplitudes, + threshold_detection, +) + +tolerance = 0.0 + -from .base import np, pq, cap, VmTest, scores, AMPL, DELAY, DURATION -from .. import optimization -from neuronunit.optimization.data_transport_container import DataTC class RheobaseTest(VmTest): """ - Tests the full widths of APs at their half-maximum - under current injection. + --Synopsis: + Serial implementation of a binary search to test the rheobase. + + Strengths: this algorithm is faster than the parallel class, present in + this file under important and limited circumstances: this serial algorithm + is faster than parallel for model backends that are able to implement + numba jit optimization. + + """ + def _extra(self): - self.prediction = None - self.high = 300*pq.pA - self.small = 0*pq.pA + self.prediction = {} + self.high = 900 * pq.pA + self.small = 0 * pq.pA self.rheobase_vm = None + self.verbose = False + + def __init__( + self, + observation=None, + prediction=None, + target_number_spikes=1, + name="RheobaseTest", + ): + self._extra() + super(RheobaseTest, self).__init__(observation=observation, name=name) + self.prediction = prediction + self.target_number_spikes = target_number_spikes - required_capabilities = (cap.ReceivesSquareCurrent, - cap.ProducesSpikes) - - params = {'injected_square_current': - {'amplitude':100.0*pq.pA, 'delay':DELAY, 'duration':DURATION}} + required_capabilities = (ncap.ReceivesSquareCurrent, ncap.ProducesSpikes) name = "Rheobase test" - - description = ("A test of the rheobase, i.e. the minimum injected current " - "needed to evoke at least one spike.") - + description = ( + "A test of the rheobase, i.e. the minimum injected current " + "needed to evoke at least one spike." + ) units = pq.pA - - ephysprop_name = 'Rheobase' - - score_type = scores.RatioScore + ephysprop_name = "Rheobase" + default_params = dict(VmTest.default_params) + default_params.update( + { + "amplitude": 100 * pq.pA, + "duration": DURATION, + "delay": DELAY, + "tolerance": 1.0 * pq.pA, + } + ) + + params_schema = dict(VmTest.params_schema) + params_schema.update( + {"tolerance": {"type": "current", "min": 1, "required": False}} + ) + + def condition_model(self, model): + if not "t_max" in self.params: + self.params["t_max"] = 2000.0 * pq.ms + else: + model.set_run_params(t_stop=self.params["t_max"]) def generate_prediction(self, model): - """Implementation of sciunit.Test.generate_prediction.""" + """Implement sciunit.Test.generate_prediction.""" # Method implementation guaranteed by # ProducesActionPotentials capability. - prediction = {'value': None} + + ## + # Handle case that the + # Test constructor __init__ originated from + # a file that is older than this source code + # because it was recovered from pickle. + ## + # if not hasattr(self,'target_number_spikes'): + # self.target_number_spikes=1 + + self.condition_model(model) + prediction = {"value": None} model.rerun = True - try: - units = self.observation['value'].units - except KeyError: - units = self.observation['mean'].units - import time - begin_rh=time.time() + + if self.observation is not None: + try: + units = self.observation["value"].units + except KeyError: + units = self.observation["mean"].units + else: + units = pq.pA lookup = self.threshold_FI(model, units) - sub = np.array([x for x in lookup if lookup[x]==0])*units - supra = np.array([x for x in lookup if lookup[x]>0])*units - #self.verbose=True - if self.verbose: + ## + # New code + ## + temp = [v for v in lookup.values() if v > self.target_number_spikes] + if len(temp): + too_many_spikes = np.min(temp) + else: + too_many_spikes = 0 + + spikes_one = sorted( + [(k, v) for k, v in lookup.items() if v == self.target_number_spikes] + ) + + if len(spikes_one) >= 3: + prediction["value"] = np.abs(spikes_one[0][0] * units) + return prediction + + single_spike_found = bool(len(spikes_one)) + sub = np.array([x for x in lookup if lookup[x] == 0]) * units + supra = np.array([x for x in lookup if lookup[x] > 0]) * units + if self.verbose > 1: if len(sub): - print("Highest subthreshold current is %s" \ - % (float(sub.max().round(2))*units)) + print("Highest subthreshold current is %s" % (float(sub.max()) * units)) else: print("No subthreshold current was tested.") if len(supra): - print("Lowest suprathreshold current is %s" \ - % supra.min().round(2)) + print("Lowest suprathreshold current is %s" % supra.min()) else: print("No suprathreshold current was tested.") - if len(sub) and len(supra): + if len(sub) and len(supra) and single_spike_found: rheobase = supra.min() + elif too_many_spikes >= 5: + rheobase = None else: rheobase = None - prediction['value'] = rheobase + prediction["value"] = rheobase + + if len(supra) and single_spike_found and str("BHH") in str(model._backend.name): + prediction["value"] = sorted(supra)[1] + if len(supra) and single_spike_found and str("HH") in str(model._backend.name): + prediction["value"] = sorted(supra)[0] + self.prediction = prediction + return prediction + def extract_features(self, model): + prediction = self.generate_prediction(model) self.prediction = prediction - return self.prediction + return prediction def threshold_FI(self, model, units, guess=None): - lookup = {} # A lookup table global to the function below. + """Use binary search to generate an FI curve including rheobase.""" + lookup = {} # A lookup table global to the function below. def f(ampl): if float(ampl) not in lookup: - current = self.params.copy()['injected_square_current'] - #This does not do what you would expect. - #due to syntax I don't understand. - #updating the dictionary keys with new values doesn't work. - - uc = {'amplitude':ampl} - current.update(uc) - model.inject_square_current(current) - n_spikes = model.get_spike_count() - if self.verbose: - print("Injected %s current and got %d spikes" % \ - (ampl,n_spikes)) + if False: + uc = {"amplitude": ampl, "duration": DURATION, "delay": DELAY} + + model.inject_square_current(uc) + n_spikes = model._backend.get_spike_count() + assert n_spikes == model.get_spike_count() + + current = self.get_injected_square_current() + + current["amplitude"] = ampl * pq.pA +<<<<<<< HEAD + if "JIT_" in model.backend: + try: + model.inject_square_current(**current) + except: + model._backend.inject_square_current(**current) +======= + if "JIT_" in str(model.backend): + #try: + model.inject_square_current(**current) + #except: + #model._backend.inject_square_current(**current) +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + + n_spikes = model.get_spike_count() + assert n_spikes == model.get_spike_count() + + else: + +<<<<<<< HEAD + model._backend.inject_square_current(**current) + n_spikes = model._backend.get_spike_count() +======= + model.inject_square_current(**current) + n_spikes = model.get_spike_count() +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + + # if self.target_num_spikes == 1: + # ie this is rheobase search + # vm = model.get_membrane_potential() + # if vm[-1]>0 and n_spikes==1: + # this means current was not strong enough + # to evoke an early spike. + # the voltage deflection did not come back down below zero. + # treat this as zero spikes because a slightly higher + # spike will give a cleaner rheobase waveform. + if n_spikes == self.target_number_spikes: + + self.n_spikes = n_spikes + if self.verbose >= 2: + print("Injected %s current and got %d spikes" % (ampl, n_spikes)) lookup[float(ampl)] = n_spikes - spike_counts = np.array([n for x,n in lookup.items() if n>0]) + spike_counts = np.array([n for x, n in lookup.items() if n > 0]) if n_spikes and n_spikes <= spike_counts.min(): - self.rheobase_vm = model.get_membrane_potential() + self.rheobase_vm = model._backend.get_membrane_potential() - max_iters = 10 + max_iters = 40 - #evaluate once with a current injection at 0pA - high=self.high - small=self.small + # evaluate once with a current injection at 0pA + high = self.high + small = self.small f(high) i = 0 while True: - #sub means below threshold, or no spikes - sub = np.array([x for x in lookup if lookup[x]==0])*units - #supra means above threshold, but possibly too high above threshold. - supra = np.array([x for x in lookup if lookup[x]>0])*units - #The actual part of the Rheobase test that is - #computation intensive and therefore - #a target for parellelization. + # sub means below threshold, or no spikes + sub = np.array([x for x in lookup if lookup[x] == 0]) * units + # supra means above threshold, + # but possibly too high above threshold. + + supra = np.array([x for x in lookup if lookup[x] > 0]) * units + # The actual part of the Rheobase test that is + # computation intensive and therefore + temp_ = [v for v in lookup.values() if v == self.target_number_spikes] + + if len(supra) and len(sub): + delta = float(supra.min()) - float(sub.max()) + temp = [v for v in lookup.values() if v > self.target_number_spikes] + if len(temp): + too_many_spikes = np.min(temp) + else: + too_many_spikes = 0 + if "tolerance" not in self.params.keys(): + tolerance = 0.0000001 * pq.pA + else: + tolerance = float(self.params["tolerance"].rescale(pq.pA)) + + if str(supra.min()) == str(sub.max()): + + break if i >= max_iters: break - #Its this part that should be like an evaluate function that is passed to futures map. + # Its this part that should be like an evaluate function + # that is passed to futures map. if len(sub) and len(supra): - f((supra.min() + sub.max())/2) + f((supra.min() + sub.max()) / 2) elif len(sub): - f(max(small,sub.max()*2)) + f(max(small, sub.max() * 10)) elif len(supra): - f(min(-small,supra.min()*2)) + f(min(-small, supra.min() * 2)) i += 1 return lookup def compute_score(self, observation, prediction): - """Implementation of sciunit.Test.score_prediction.""" - #print("%s: Observation = %s, Prediction = %s" % \ - # (self.name,str(observation),str(prediction))) - if prediction['value'] is None: + """Implement sciunit.Test.score_prediction.""" + # from sciunit.scores import BooleanScore + # + # if type(self.score_type) == BooleanScore: + # print('warning using unusual score type') + if prediction is None or ( + isinstance(prediction, dict) and prediction["value"] is None + ): score = scores.InsufficientDataScore(None) else: - score = super(RheobaseTest,self).\ - compute_score(observation, prediction) - #self.bind_score(score,None,observation,prediction) + + score = super(RheobaseTest, self).compute_score( + observation, prediction + ) # max return score def bind_score(self, score, model, observation, prediction): - super(RheobaseTest,self).bind_score(score, model, - observation, prediction) + """Bind additional attributes to the test score.""" + super(RheobaseTest, self).bind_score(score, model, observation, prediction) if self.rheobase_vm is not None: - score.related_data['vm'] = self.rheobase_vm - - -class RheobaseTestP(VmTest): - """ - A parallel version of test Rheobase. - Tests the full widths of APs at their half-maximum - under current injection. - - """ + score.related_data["vm"] = self.rheobase_vm - required_capabilities = (cap.ReceivesSquareCurrent, - cap.ProducesSpikes) +class RheobaseTestP(RheobaseTest): + """Parallel implementation of a binary search to test the rheobase. - DELAY = 100.0*pq.ms - DURATION = 1000.0*pq.ms - params = {'injected_square_current': - {'amplitude':100.0*pq.pA, 'delay':DELAY, 'duration':DURATION}} - - name = "Rheobase test" - - description = ("A test of the rheobase, i.e. the minimum injected current " - "needed to evoke at least one spike.") - - units = pq.pA + Strengths: this algorithm is faster than the serial class, present in this + file for model backends that are not able to implement numba jit + optimization, which actually happens to be typical of a signifcant number + of backends. + """ - ephysprop_name = 'Rheobase' + def _extra(self, target_number_spikes=1): + self.verbose = False + self.target_number_spikes = 1 - score_type = scores.RatioScore + def __init__( + self, observation=None, prediction=None, target_number_spikes=1, name="" + ): + self._extra() + super(RheobaseTest, self).__init__(observation=observation, name=name) + self.prediction = prediction + self.target_number_spikes = target_number_spikes - def generate_prediction(self, model): + required_capabilities = (ncap.ReceivesSquareCurrent, ncap.ProducesSpikes) - ''' - inputs a population of genes/alleles, the population size MU, and an optional argument of a rheobase value guess - outputs a population of genes/alleles, a population of individual object shells, ie a pickleable container for gene attributes. - Rationale, not every gene value will result in a model for which rheobase is found, in which case that gene is discarded, however to - compensate for losses in gene population size, more gene samples must be tested for a successful return from a rheobase search. - If the tests return are successful these new sampled individuals are appended to the population, and then their attributes are mapped onto - corresponding virtual model objects. - ''' + name = "Rheobase test" + description = ( + "A test of the rheobase, i.e. the minimum injected current " + "needed to evoke at least one spike." + ) + units = pq.pA + ephysprop_name = "Rheobase" + + get_rheobase_vm = True + default_params = dict(VmTest.default_params) + default_params.update( + { + "amplitude": 100 * pq.pA, + "duration": DURATION, + "delay": DELAY, + "tolerance": 1.0 * pq.pA, + } + ) + + params_schema = dict(VmTest.params_schema) + params_schema.update( + {"tolerance": {"type": "current", "min": 1, "required": False}} + ) + + def condition_model(self, model): + if not "t_max" in self.params: + self.params["t_max"] = 2000.0 * pq.ms + else: + model.set_run_params(t_stop=self.params["t_max"]) + + default_params = dict(VmTest.default_params) + default_params.update( + { + "amplitude": 100 * pq.pA, + "duration": DURATION, + "duration": DELAY, + "tolerance": 1.0 * pq.pA, + } + ) + params_schema = dict(VmTest.params_schema) + params_schema.update( + {"tolerance": {"type": "current", "min": 1, "required": False}} + ) + units = pq.pA + def generate_prediction(self, model): def check_fix_range(dtc): - ''' + """ Inputs: lookup, A dictionary of previous current injection values used to search rheobase Outputs: A boolean to indicate if the correct rheobase current was found @@ -195,179 +386,250 @@ def check_fix_range(dtc): instead logical True, and the rheobase current is returned. given a dictionary of rheobase search values, use that dictionary as input for a subsequent search. - ''' - import numpy as np - import quantities as pq - import copy - sub=[] - supra=[] - steps=[] - - dtc.rheobase = 0.0 - for k,v in dtc.lookup.items(): - - if v == 1: - #A logical flag is returned to indicate that rheobase was found. - dtc.rheobase = float(k) - dtc.current_steps = 0.0 - dtc.boolean = True - return dtc - elif v == 0: - sub.append(k) - elif v > 0: - supra.append(k) - - sub = np.array(sub) - supra = np.array(supra) - if 0. in supra and len(sub) == 0: - dtc.boolean = True - dtc.rheobase = -1 - #score = scores.InsufficientDataScore(None) - - return dtc - - - if len(sub)!=0 and len(supra)!=0: - #this assertion would only be occur if there was a bug - assert sub.max()<=supra.min() - if len(sub) and len(supra): - center = list(np.linspace(sub.max(),supra.min(),9.0)) - center = [ i for i in center if not i == sub.max() ] - center = [ i for i in center if not i == supra.min() ] - center[int(len(center)/2)+1]=(sub.max()+supra.min())/2.0 - steps = [ i*pq.pA for i in center ] + """ + steps = [] + dtc.rheobase = None + sub, supra = get_sub_supra(dtc.lookup) + + # if 0. in supra and len(sub) == 0: + # dtc.boolean = True + # dtc.rheobase = None + # return dtc + if (len(sub) + len(supra)) == 0: + # This assertion would only be occur if there was a bug + assert sub.max() <= supra.min() + elif len(sub) and len(supra): + # Termination criterion + + steps = np.linspace(sub.max(), supra.min(), cpucount) * pq.pA + steps = steps[1:-1] elif len(sub): - steps = list(np.linspace(sub.max(),2*sub.max(),9.0)) - steps = [ i for i in steps if not i == sub.max() ] - steps = [ i*pq.pA for i in steps ] + steps = np.linspace(sub.max(), 10 * sub.max(), cpucount) * pq.pA + steps = steps[1:-1] elif len(supra): - step = list(np.linspace(-2*(supra.min()),supra.min(),9.0)) - steps = [ i for i in steps if not i == supra.min() ] - steps = [ i*pq.pA for i in steps ] + steps = np.linspace(supra.min() - 100, supra.min(), cpucount) * pq.pA + steps = steps[1:-1] dtc.current_steps = steps - dtc.rheobase = None - return copy.copy(dtc) + return dtc - def check_current(ampl,dtc): - ''' + def get_sub_supra(lookup): + sub, supra = [], [] + for current, n_spikes in lookup.items(): + if n_spikes == 0: + sub.append(current) + elif n_spikes > 0: + supra.append(current) + + sub = np.array(sorted(list(set(sub)))) + supra = np.array(sorted(list(set(supra)))) + return sub, supra + + # @dask.delayed + def check_current(dtc): + """ Inputs are an amplitude to test and a virtual model output is an virtual model with an updated dictionary. - ''' - import copy - import os - import quantities - from neuronunit.models.reduced import ReducedModel - import neuronunit - LEMS_MODEL_PATH = str(neuronunit.__path__[0])+str('/models/NeuroML2/LEMS_2007One.xml') - dtc.model_path = LEMS_MODEL_PATH - model = ReducedModel(dtc.model_path,name='vanilla', backend=(str(dtc.backend), {'DTC':dtc})) - - import copy - model.set_attrs(dtc.attrs) - - DELAY = 100.0*pq.ms - DURATION = 1000.0*pq.ms - params = {'injected_square_current': - {'amplitude':100.0*pq.pA, 'delay':DELAY, 'duration':DURATION}} - - dtc = copy.copy(dtc) - ampl = float(ampl) - #print(dtc.lookup) - if ampl not in dtc.lookup or len(dtc.lookup) == 0: - current = params.copy()['injected_square_current'] - uc = {'amplitude':ampl} - current.update(uc) - current = {'injected_square_current':current} - dtc.run_number += 1 - model.set_attrs(dtc.attrs) + """ + dtc.boolean = False + + model = dtc.dtc_to_model() + self.condition_model(model) + + ampl = dtc.ampl + if float(ampl) not in dtc.lookup or len(dtc.lookup) == 0: + + current = {"amplitude": ampl, "duration": DURATION, "delay": DELAY} + float(current["delay"]) > 100 + current["amplitude"] = ampl model.inject_square_current(current) - dtc.previous = ampl n_spikes = model.get_spike_count() - dtc.lookup[float(ampl)] = n_spikes - if n_spikes == 1: - dtc.rheobase = float(ampl) + + dtc.previous = ampl + dtc.rheobase = {} + if n_spikes == self.target_number_spikes: + dtc.lookup[float(ampl)] = self.target_number_spikes + dtc.rheobase["value"] = ampl dtc.boolean = True + return dtc - return dtc - if float(ampl) in dtc.lookup: - return dtc + dtc.lookup[float(ampl)] = n_spikes + return dtc def init_dtc(dtc): - import numpy as np - import copy - + """ + Exploit memory of last model in genes. + # Exploit memory of the genes to inform searchable range. + # if this model has lineage, assume it didn't mutate that far away from it's ancestor. + # using that assumption, on first pass, consult a very narrow range, of test current injection samples: + # only slightly displaced away from the ancestors rheobase value. + + + if type(dtc.current_steps) is type(float): + dtc.current_steps = [ 0.80 * dtc.current_steps, 1.20 * dtc.current_steps ] + elif type(dtc.current_steps) is type(list): + dtc.current_steps = [ s * 1.25 for s in dtc.current_steps ] + dtc.initiated = True # logically unnecessary but included for readibility + + """ + # check for memory and exploit it. if dtc.initiated == True: - # expand values in the range to accomodate for mutation. - # but otherwise exploit memory of this range. - - if type(dtc.current_steps) is type(float): - dtc.current_steps = [ 0.75 * dtc.current_steps, 1.25 * dtc.current_steps ] - elif type(dtc.current_steps) is type(list): - dtc.current_steps = [ s * 1.25 for s in dtc.current_steps ] - dtc.initiated = True # logically unnecessary but included for readibility - + dtc = check_current(dtc) + if dtc.boolean: + return dtc if dtc.initiated == False: - import quantities as pq - import numpy as np dtc.boolean = False - steps = np.linspace(0,250,7.0) - steps_current = [ i*pq.pA for i in steps ] + steps = np.linspace(0.0, 550.0, cpucount) + steps_current = [i * pq.pA for i in steps] dtc.current_steps = steps_current dtc.initiated = True return dtc - def find_rheobase(self, dtc): - import dask.bag as db - cnt = 0 - assert os.path.isfile(dtc.model_path), "%s is not a file" % dtc.model_path + def find_rheobase(self, global_dtc): + units = pq.pA + # If this it not the first pass/ first generation # then assume the rheobase value found before mutation still holds until proven otherwise. # dtc = check_current(model.rheobase,dtc) # If its not true enter a search, with ranges informed by memory cnt = 0 - while dtc.boolean == False: - dtc_clones = [ dtc for s in dtc.current_steps ] - b0 = db.from_sequence(dtc.current_steps, npartitions=8) - b1 = db.from_sequence(dtc_clones, npartitions=8) - dtcpop = list(db.map(check_current,b0,b1).compute()) - for dtc_clone in dtcpop: - dtc.lookup.update(dtc_clone.lookup) - dtc = check_fix_range(dtc) + sub = np.array([0, 0]) + supra = np.array([0, 0]) + + big = 20 + + while global_dtc.boolean == False and cnt < big: + + if len(sub): + if sub.max() < -1.0: + pass + + dtc_clones = [ + global_dtc for i in range(0, len(global_dtc.current_steps)) + ] + for i, s in enumerate(global_dtc.current_steps): + dtc_clones[i] = copy.copy(dtc_clones[i]) + dtc_clones[i].ampl = copy.copy(global_dtc.current_steps[i]) + dtc_clones = [d for d in dtc_clones if not np.isnan(d.ampl)] + set_clones = set([float(d.ampl) for d in dtc_clones]) + dtc_clone = [] + + for dtc_local, sc in zip(dtc_clones, set_clones): + dtc_local = copy.copy(dtc_local) + dtc_local.ampl = sc * pq.pA + dtc_clone.append(dtc_local) + bag = db.from_sequence(dtc_clone, npartitions=8) + dtc_clone = list(bag.map(check_current).compute()) + spikes_one = sorted( + [(dtc.ampl, dtc) for dtc in dtc_clone if dtc.boolean == True] + ) + if len(spikes_one) >= 2: + return spikes_one[0][0] + + for d in dtc_clone: + global_dtc.lookup.update(d.lookup) + dtc = check_fix_range(global_dtc) + sub, supra = get_sub_supra(dtc.lookup) + if len(supra) and len(sub): + + delta = float(supra.min()) - float(sub.max()) + + tolerance = 0.0 + if delta < tolerance or (str(supra.min()) == str(sub.max())): + if self.verbose >= 2: + print( + delta, + "a neuron, close to the edge! Multi spiking rheobase. # spikes: ", + len(supra), + ) + too_many_spikes = np.min( + [ + v + for v in dtc.lookup.values() + if v > self.target_number_spikes + ] + ) + if too_many_spikes > 10: + + dtc.rheobase = {} + dtc.rheobase["value"] = None + dtc.boolean = True + dtc.lookup[float(supra.min())] = len(supra) + + else: + if len(supra) <= 10: + + dtc.rheobase = float(supra.min()) * units + dtc.boolean = True + dtc.lookup[float(supra.min())] = len(supra) + else: + dtc.rheobase = float(supra.min()) + dtc.boolean = True + dtc.lookup[float(supra.min())] = len(supra) * units + # print(dtc.rheobase) + return dtc.rheobase + + if self.verbose >= 2: + print( + "Try %d: SubMax = %s; SupraMin = %s" + % ( + cnt, + sub.max() if len(sub) else None, + supra.min() if len(supra) else None, + ) + ) cnt += 1 return dtc + from neuronunit.optimisation.data_transport_container import DataTC + dtc = DataTC() dtc.attrs = {} - for k,v in model.attrs.items(): + for k, v in model.attrs.items(): dtc.attrs[k] = v - dtc = init_dtc(dtc) - dtc.model_path = model.orig_lems_file_path + + # this is not a perservering assignment, of value, + # but rather a multi statement assertion that will be checked. dtc.backend = model.backend - assert os.path.isfile(dtc.model_path), "%s is not a file" % dtc.model_path + + dtc = init_dtc(dtc) prediction = {} - prediction['value'] = find_rheobase(self,dtc).rheobase * pq.pA + temp = find_rheobase(self, dtc) # .rheobase + if type(temp) is type(pq.pA): + prediction["value"] = temp + return prediction + if type(temp) is not type(None): + if hasattr(temp, "rheobase"): + temp = temp.rheobase + if type(temp) is type(dict()): + if temp["value"] is None: + prediction["value"] = None + else: + prediction["value"] = float(temp["value"]) * pq.pA + else: + prediction["value"] = temp # float(temp)* pq.pA + else: + prediction["value"] = None + self.prediction = prediction return prediction - def bind_score(self, score, model, observation, prediction): - super(RheobaseTestP,self).bind_score(score, model, - observation, prediction) + def extract_features(self, model): + prediction = self.generate_prediction(model) + return prediction - def compute_score(self, observation, prediction): - """Implementation of sciunit.Test.score_prediction.""" - score = None - #if type(prediction['value']) is type(None): - # prediction['value'] = -1 * pq.pA - # return scores.InsufficientDataScore(None) + ''' - if float(prediction['value']) <= 0.0: - # if rheobase is negative discard the model essentially. - prediction['value'] = -1 * pq.pA - return scores.InsufficientDataScore(None) + def bind_score(self, score, model, observation, prediction): + super(RheobaseTestP,self).bind_score(score, model, + observation, prediction) + def compute_score(self, observation, prediction): + """Implementation of sciunit.Test.score_prediction.""" + score = None - score = super(RheobaseTestP,self).\ + score = super(RheobaseTestP,self).\ compute_score(observation, prediction) - return score + return score + ''' diff --git a/neuronunit/tests/make_allen_tests.py b/neuronunit/tests/make_allen_tests.py new file mode 100644 index 000000000..21e51f421 --- /dev/null +++ b/neuronunit/tests/make_allen_tests.py @@ -0,0 +1,37 @@ +from neuronunit.tests.base import VmTest +import pickle +import numpy as np +from allensdk.core.cell_types_cache import CellTypesCache + + +class AllenTest(VmTest): + def __init__( + self, + observation={"mean": None, "std": None}, + name="generic_allen", + prediction={"mean": None, "std": None}, + **params + ): + super(AllenTest, self).__init__(observation, name, **params) + + name = "" + aliases = "" + + def compute_params(self): + self.params["t_max"] = ( + self.params["delay"] + self.params["duration"] + self.params["padding"] + ) + + def set_observation(self, observation): + self.observation = {} + self.observation["mean"] = observation + self.observation["std"] = observation + + def set_prediction(self, prediction): + self.prediction = {} + self.prediction["mean"] = prediction + self.prediction["std"] = prediction + + +ctc = CellTypesCache(manifest_file="manifest.json") +features = ctc.get_ephys_features() diff --git a/neuronunit/tests/morphology.py b/neuronunit/tests/morphology.py new file mode 100755 index 000000000..115b9f6df --- /dev/null +++ b/neuronunit/tests/morphology.py @@ -0,0 +1,467 @@ +# -*- coding: utf-8 -*- +"""NeuronUnit Test classes for cell models with morphology""" + +import quantities as pq +from pylmeasure import * + +from neuronunit.capabilities.morphology import * +from sciunit.scores import ZScore + + +class MorphologyTest(sciunit.Test): + """ + An abstract class to hold common elements among the morphology tests below + """ + + required_capabilities = (ProducesSWC,) + score_type = ZScore + specificity = "Type > 1" + pca = False + + def get_lmeasure(self, model_swc, measure, stat): + """ + Computes the specified L-Measure measure and selects one of the statistics + + :param model_swc: A model that has ProducesSWC capability + :param measure: One of the functions from the list: http://cng.gmu.edu:8080/Lm/help/index.htm + :param stat: One of: Average, Maximum, Minimum, StdDev, TotalSum + :return: The computed measure statistic + """ + + swc_path = model_swc.produce_swc() + value = getOneMeasure(measure, swc_path, self.pca, self.specificity)[stat] + return value + + +class SomaSurfaceAreaTest(MorphologyTest): + """Test the agreement between soma surface area observed in the model and experiments""" + + name = "Soma Surface Area Test" + units = pq.um ** 2 + + def generate_prediction(self, model): + self.specificity = "Type == 1" + + value = self.get_lmeasure(model, "Surface", "TotalSum") + + return {"mean": value * self.units, "std": 0 * self.units, "n": 1} + + +class NumberofStemsTest(MorphologyTest): + """Test the agreement between number of stems observed in the model and experiments""" + + name = "Number of Stems Test" + units = pq.dimensionless + + def generate_prediction(self, model): + self.specificity = "Type > 0" + + value = self.get_lmeasure(model, "N_stems", "TotalSum") + + return {"mean": value * self.units, "std": 0 * self.units, "n": 1} + + +class NumberofBifurcationsTest(MorphologyTest): + """Test the agreement between number of bifurcations observed in the model and experiments""" + + name = "Number of Bifurcations Test" + units = pq.dimensionless + + def generate_prediction(self, model): + value = self.get_lmeasure(model, "N_bifs", "TotalSum") + + return {"mean": value * self.units, "std": 0 * self.units, "n": 1} + + +class BasalDendriteNumberofBifurcationsTest(NumberofBifurcationsTest): + """Test the agreement between number of basal dendrite bifurcations observed in the model and experiments""" + + name = "Number of Basal Dendrite Bifurcations Test" + specificity = "Type==3" + + +class ApicalDendriteNumberofBifurcationsTest(NumberofBifurcationsTest): + """Test the agreement between number of apical dendrite bifurcations observed in the model and experiments""" + + name = "Number of Apical Dendrite Bifurcations Test" + specificity = "Type==4" + + +class NumberofBranchesTest(MorphologyTest): + """Test the agreement between number of branches observed in the model and experiments""" + + name = "Number of Branches Test" + units = pq.dimensionless + + def generate_prediction(self, model): + value = self.get_lmeasure(model, "N_branch", "TotalSum") + + return {"mean": value * self.units, "std": 0 * self.units, "n": 1} + + +class BasalDendriteNumberofBranchesTest(NumberofBranchesTest): + name = "Basal Dendrite Number of Branches Test" + specificity = "Type==3" + + +class ApicalDendriteNumberofBranchesTest(NumberofBranchesTest): + name = "Apical Dendrite Number of Branches Test" + specificity = "Type==4" + + +class OverallWidthTest(MorphologyTest): + """Test the agreement between overall width observed in the model and experiments""" + + name = "Overall Width Test" + units = pq.um + pca = True + + def generate_prediction(self, model): + value = self.get_lmeasure(model, "Width", "Maximum") + + return {"mean": value * self.units, "std": 0 * self.units, "n": 1} + + +class BasalDendriteOverallWidthTest(OverallWidthTest): + name = "Basal Dendrite Overall Width Test" + specificity = "Type==3" + + +class ApicalDendriteOverallWidthTest(OverallWidthTest): + name = "Apical Dendrite Overall Width Test" + specificity = "Type==4" + + +class OverallHeightTest(MorphologyTest): + """Test the agreement between overall height observed in the model and experiments""" + + name = "Overall Height Test" + units = pq.um + pca = True + + def generate_prediction(self, model): + value = self.get_lmeasure(model, "Height", "Maximum") + + return {"mean": value * self.units, "std": 0 * self.units, "n": 1} + + +class BasalDendriteOverallHeightTest(OverallHeightTest): + name = "Basal Dendrite Overall Height Test" + specificity = "Type==3" + + +class ApicalDendriteOverallHeightTest(OverallHeightTest): + name = "Apical Dendrite Overall Height Test" + specificity = "Type==4" + + +class OverallDepthTest(MorphologyTest): + """Test the agreement between overall depth observed in the model and experiments""" + + name = "Overall Depth Test" + units = pq.um + pca = True + + def generate_prediction(self, model): + value = self.get_lmeasure(model, "Depth", "Maximum") + + return {"mean": value * self.units, "std": 0 * self.units, "n": 1} + + +class BasalDendriteOverallDepthTest(OverallDepthTest): + name = "Basal Dendrite Overall Depth Test" + specificity = "Type==3" + + +class ApicalDendriteOverallDepthTest(OverallDepthTest): + name = "Apical Dendrite Overall Depth Test" + specificity = "Type==4" + + +class AverageDiameterTest(MorphologyTest): + """Test the agreement between average diameter observed in the model and experiments""" + + name = "Average Diameter Test" + units = pq.um + pca = True + + def generate_prediction(self, model): + value = self.get_lmeasure(model, "Diameter", "Average") + + return {"mean": value * self.units, "std": 0 * self.units, "n": 1} + + +class BasalDendriteAverageDiameterTest(AverageDiameterTest): + name = "Basal Dendrite Average Diameter Test" + specificity = "Type==3" + + +class ApicalDendriteAverageDiameterTest(AverageDiameterTest): + name = "Apical Dendrite Average Diameter Test" + specificity = "Type==4" + + +class TotalLengthTest(MorphologyTest): + """Test the agreement between total length observed in the model and experiments""" + + name = "Total Length Test" + units = pq.um + + def generate_prediction(self, model): + value = self.get_lmeasure(model, "Length", "TotalSum") + + return {"mean": value * self.units, "std": 0 * self.units, "n": 1} + + +class BasalDendriteTotalLengthTest(TotalLengthTest): + name = "Basal Dendrite Total Length Test" + specificity = "Type==3" + + +class ApicalDendriteTotalLengthTest(TotalLengthTest): + name = "Apical Dendrite Total Length Test" + specificity = "Type==4" + + +class TotalSurfaceTest(MorphologyTest): + """Test the agreement between total surface observed in the model and experiments""" + + name = "Total Surface Test" + units = pq.um ** 2 + + def generate_prediction(self, model): + value = self.get_lmeasure(model, "Surface", "TotalSum") + + return {"mean": value * self.units, "std": 0 * self.units, "n": 1} + + +class BasalDendriteTotalSurfaceTest(TotalSurfaceTest): + name = "Basal Dendrite Total Surface Test" + specificity = "Type==3" + + +class ApicalDendriteTotalSurfaceTest(TotalSurfaceTest): + name = "Apical Dendrite Total Surface Test" + specificity = "Type==4" + + +class TotalVolumeTest(MorphologyTest): + """Test the agreement between total volume (excl. soma) observed in the model and experiments""" + + name = "Total Volume Test" + units = pq.um ** 3 + + def generate_prediction(self, model): + value = self.get_lmeasure(model, "Volume", "TotalSum") + + return {"mean": value * self.units, "std": 0 * self.units, "n": 1} + + +class BasalDendriteTotalVolumeTest(TotalVolumeTest): + name = "Basal Dendrite Total Volume Test" + specificity = "Type==3" + + +class ApicalDendriteTotalVolumeTest(TotalVolumeTest): + name = "Apical Dendrite Total Volume Test" + specificity = "Type==4" + + +class MaxEuclideanDistanceTest(MorphologyTest): + """Test the agreement between max euclidean distance observed in the model and experiments""" + + name = "Max Euclidean Distance Test" + units = pq.um + + def generate_prediction(self, model): + value = self.get_lmeasure(model, "EucDistance", "Maximum") + + return {"mean": value * self.units, "std": 0 * self.units, "n": 1} + + +class BasalDendriteMaxEuclideanDistanceTest(MaxEuclideanDistanceTest): + name = "Basal Dendrite Max Euclidean Distance Test" + specificity = "Type==3" + + +class ApicalDendriteMaxEuclideanDistanceTest(MaxEuclideanDistanceTest): + name = "Apical Dendrite Max Euclidean Distance Test" + specificity = "Type==4" + + +class MaxPathDistanceTest(MorphologyTest): + """Test the agreement between max path distance observed in the model and experiments""" + + name = "Max Path Distance Test" + units = pq.um + + def generate_prediction(self, model): + value = self.get_lmeasure(model, "PathDistance", "Maximum") + + return {"mean": value * self.units, "std": 0 * self.units, "n": 1} + + +class BasalDendriteMaxPathDistanceTest(MaxPathDistanceTest): + name = "Basal Dendrite Max Path Distance Test" + specificity = "Type==3" + + +class ApicalDendriteMaxPathDistanceTest(MaxPathDistanceTest): + name = "Apical Dendrite Max Path Distance Test" + specificity = "Type==4" + + +class MaxBranchOrderTest(MorphologyTest): + """Test the agreement between max branch order observed in the model and experiments""" + + name = "Max Branch Order Test" + units = pq.dimensionless + + def generate_prediction(self, model): + value = self.get_lmeasure(model, "Branch_Order", "Maximum") + + return {"mean": value * self.units, "std": 0 * self.units, "n": 1} + + +class BasalDendriteMaxBranchOrderTest(MaxBranchOrderTest): + name = "Basal Dendrite Max Branch Order Test" + specificity = "Type==3" + + +class ApicalDendriteMaxBranchOrderTest(MaxBranchOrderTest): + name = "Apical Dendrite Max Branch Order Test" + specificity = "Type==4" + + +class AverageContractionTest(MorphologyTest): + """Test the agreement between average contraction observed in the model and experiments""" + + name = "Average Contraction Test" + units = pq.dimensionless + + def generate_prediction(self, model): + value = self.get_lmeasure(model, "Contraction", "Average") + + return {"mean": value * self.units, "std": 0 * self.units, "n": 1} + + +class BasalDendriteAverageContractionTest(AverageContractionTest): + name = "Basal Dendrite Average Contraction Test" + specificity = "Type==3" + + +class ApicalDendriteAverageContractionTest(AverageContractionTest): + name = "Apical Dendrite Average Contraction Test" + specificity = "Type==4" + + +class PartitionAsymmetryTest(MorphologyTest): + """Test the agreement between partition asymmetry observed in the model and experiments""" + + name = "Partition Asymmetry Test" + units = pq.dimensionless + + def generate_prediction(self, model): + value = self.get_lmeasure(model, "Partition_asymmetry", "Average") + + return {"mean": value * self.units, "std": 0 * self.units, "n": 1} + + +class BasalDendritePartitionAsymmetryTest(PartitionAsymmetryTest): + name = "Basal Dendrite Partition Asymmetry Test" + specificity = "Type==3" + + +class ApicalDendritePartitionAsymmetryTest(PartitionAsymmetryTest): + name = "Apical Dendrite Partition Asymmetry Test" + specificity = "Type==4" + + +class AverageRallsRatioTest(MorphologyTest): + """Test the agreement between average Rall's ratio observed in the model and experiments""" + + name = "Average Ralls Ratio Test" + units = pq.dimensionless + + def generate_prediction(self, model): + value = self.get_lmeasure(model, "Pk_classic", "Average") + + return {"mean": value * self.units, "std": 0 * self.units, "n": 1} + + +class BasalDendriteAverageRallsRatioTest(AverageRallsRatioTest): + name = "Basal Dendrite Average Ralls Ratio Test" + specificity = "Type==3" + + +class ApicalDendriteAverageRallsRatioTest(AverageRallsRatioTest): + name = "Apical Dendrite Average Ralls Ratio Test" + specificity = "Type==4" + + +class AverageBifurcationAngleLocalTest(MorphologyTest): + """Test the agreement between average bifurcation angle local observed in the model and experiments""" + + name = "Average Bifurcation Angle Local Test" + units = pq.dimensionless + + def generate_prediction(self, model): + value = self.get_lmeasure(model, "Bif_ampl_local", "Average") + + return {"mean": value * self.units, "std": 0 * self.units, "n": 1} + + +class BasalDendriteAverageBifurcationAngleLocalTest(AverageBifurcationAngleLocalTest): + name = "Basal Dendrite Average Bifurcation Angle Local Test" + specificity = "Type==3" + + +class ApicalDendriteAverageBifurcationAngleLocalTest(AverageBifurcationAngleLocalTest): + name = "Apical Dendrite Average Bifurcation Angle Local Test" + specificity = "Type==4" + + +class AverageBifurcationAngleRemoteTest(MorphologyTest): + """Test the agreement between average bifurcation angle remote observed in the model and experiments""" + + name = "Average Bifurcation Angle Remote Test" + units = pq.dimensionless + + def generate_prediction(self, model): + value = self.get_lmeasure(model, "Bif_ampl_remote", "Average") + + return {"mean": value * self.units, "std": 0 * self.units, "n": 1} + + +class BasalDendriteAverageBifurcationAngleRemoteTest(AverageBifurcationAngleRemoteTest): + name = "Basal Dendrite Average Bifurcation Angle Remote Test" + specificity = "Type==3" + + +class ApicalDendriteAverageBifurcationAngleRemoteTest( + AverageBifurcationAngleRemoteTest +): + name = "Apical Dendrite Average Bifurcation Angle Remote Test" + specificity = "Type==4" + + +class FractalDimensionTest(MorphologyTest): + """Test the agreement between fractal dimension observed in the model and experiments""" + + name = "Fractal Dimension Test" + units = pq.dimensionless + + def generate_prediction(self, model): + value = self.get_lmeasure(model, "Fractal_Dim", "Average") + + return {"mean": value * self.units, "std": 0 * self.units, "n": 1} + + +class BasalDendriteFractalDimensionTest(FractalDimensionTest): + name = "Basal Dendrite Fractal Dimension Test" + specificity = "Type==3" + + +class ApicalDendriteFractalDimensionTest(FractalDimensionTest): + name = "Apical Dendrite Fractal Dimension Test" + specificity = "Type==4" diff --git a/neuronunit/tests/passive.py b/neuronunit/tests/passive.py index 5fab2107c..7ea8f8dc8 100644 --- a/neuronunit/tests/passive.py +++ b/neuronunit/tests/passive.py @@ -1,142 +1,185 @@ -"""Passive neuronunit tests, e.g. requiring no active conductances or spiking""" +"""Passive neuronunit tests, requiring no active conductances or spiking.""" -from .base import np, pq, sciunit, cap, VmTest, scores, AMPL, DELAY, DURATION +from .base import np, pq, ncap, VmTest, scores from scipy.optimize import curve_fit +DURATION = 500.0 * pq.ms +DELAY = 200.0 * pq.ms + +import warnings + +warnings.filterwarnings("ignore") + class TestPulseTest(VmTest): - """A base class for tests that use a square test pulse""" + """A base class for tests that use a square test pulse.""" + + def __init__(self, *args, **kwargs): + super(TestPulseTest, self).__init__(*args, **kwargs) - required_capabilities = (cap.ReceivesSquareCurrent,) + default_params = dict(VmTest.default_params) + default_params["amplitude"] = -10.0 * pq.pA - name = '' + required_capabilities = (ncap.ReceivesSquareCurrent,) + + name = "" score_type = scores.ZScore - params = {'injected_square_current': - {'amplitude':-10.0*pq.pA, 'delay':DELAY, 'duration':DURATION}} + def compute_params(self): + super(TestPulseTest, self).compute_params() + self.params["injected_square_current"] = self.get_injected_square_current() + + def condition_model(self, model): + t_stop = self.params["tmax"] + model.get_backend().set_stop_time(t_stop) - def generate_prediction(self, model): - """Implementation of sciunit.Test.generate_prediction.""" - model.inject_square_current(self.params['injected_square_current']) + def setup_protocol(self, model): + """Implement sciunit.tests.ProtocolToFeatureTest.setup_protocol.""" + self.condition_model(model) + model.inject_square_current(**self.params["injected_square_current"]) + + def get_result(self, model): vm = model.get_membrane_potential() - i = self.params['injected_square_current'] - return (i,vm) + return vm + + def extract_features(self, model, vm): + i = self.params["injected_square_current"] + if np.any(np.isnan(vm)) or np.any(np.isinf(vm)): + return None + + return (i, vm) @classmethod def get_segment(cls, vm, start, finish): - start = int((start/vm.sampling_period).simplified) - finish = int((finish/vm.sampling_period).simplified) + start = int((start / vm.sampling_period).simplified) + finish = int((finish / vm.sampling_period).simplified) return vm[start:finish] @classmethod def get_rin(cls, vm, i): - start, stop = -11*pq.ms, -1*pq.ms - before = cls.get_segment(vm,start+i['delay'], - stop+i['delay']) - after = cls.get_segment(vm,start+i['delay']+i['duration'], - stop+i['delay']+i['duration']) - r_in = (after.mean()-before.mean())/i['amplitude'] + start, stop = -11 * pq.ms, -1 * pq.ms + before = cls.get_segment(vm, start + i["delay"], stop + i["delay"]) + after = cls.get_segment( + vm, start + i["delay"] + i["duration"], stop + i["delay"] + i["duration"] + ) + r_in = (after.mean() - before.mean()) / i["amplitude"] return r_in.simplified @classmethod def get_tau(cls, vm, i): # 10 ms before pulse start or halfway between sweep start # and pulse start, whichever is longer - start = max(i['delay']-10*pq.ms,i['delay']/2) - stop = i['duration']+i['delay']-1*pq.ms # 1 ms before pulse end - region = cls.get_segment(vm,start,stop) - amplitude,tau,y0 = cls.exponential_fit(region, i['delay']) + start = max(i["delay"] - 10 * pq.ms, i["delay"] / 2) + stop = i["duration"] + i["delay"] - 1 * pq.ms # 1 ms before pulse end + region = cls.get_segment(vm, start, stop) + amplitude, tau, y0 = cls.exponential_fit(region, i["delay"]) return tau @classmethod def exponential_fit(cls, segment, offset): - t = segment.times.rescale('ms') + t = segment.times.rescale("ms") start = t[0] - offset = offset-start - t = t-start + offset = offset - start + t = t - start t = t.magnitude - vm = segment.rescale('mV').magnitude + vm = segment.rescale("mV").magnitude offset = (offset * segment.sampling_rate).simplified assert offset.dimensionality == pq.dimensionless offset = int(offset) - guesses = [vm.min(), # amplitude (mV) - 10, # time constant (ms) - vm.max()] # y0 (mV) + guesses = [ + vm.min(), # amplitude (mV) + 10, # time constant (ms) + vm.max(), + ] # y0 (mV) vm_fit = vm.copy() def func(x, a, b, c): - ''' - This function is simply the shape of exponential decay which must be differenced, its basically an ideal template - An exp decay equation derived from experiments. - For the model to compare against. - ''' + """Produce an exponential function. + + Given function parameters a, b, and c, returns the exponential + decay function for those parameters. + """ vm_fit[:offset] = c - vm_fit[offset:,0] = a * np.exp(-t[offset:]/b) + c + shaped = len(np.shape(vm_fit)) + if shaped > 1: + vm_fit[offset:, 0] = a * np.exp(-t[offset:] / b) + c + elif shaped == 1: + vm_fit[offset:] = a * np.exp(-t[offset:] / b) + c + return vm_fit.squeeze() - popt, pcov = curve_fit(func, t, vm.squeeze(), p0=guesses) # Estimate starting values for better convergence - amplitude = popt[0]*pq.mV - tau = popt[1]*pq.ms - y0 = popt[2]*pq.mV - return amplitude,tau,y0 + # Estimate starting values for better convergence + popt, pcov = curve_fit(func, t, vm.squeeze(), p0=guesses) + amplitude = popt[0] * pq.mV + tau = popt[1] * pq.ms + y0 = popt[2] * pq.mV + return amplitude, tau, y0 + def compute_score(self, observation, prediction): + """Implement sciunit.Test.score_prediction.""" + if prediction is None: + return None # scores.InsufficientDataScore(None) -class InputResistanceTest(TestPulseTest): - """Tests the input resistance of a cell.""" + else: + score = super(TestPulseTest, self).compute_score(observation, prediction) + return score - name = "Input resistance test" - description = ("A test of the input resistance of a cell.") +class InputResistanceTest(TestPulseTest): + """Test the input resistance of a cell.""" - units = pq.ohm*1e6 + name = "Input resistance test" - ephysprop_name = 'Input Resistance' + description = "A test of the input resistance of a cell." - def generate_prediction(self, model): - """Implementation of sciunit.Test.generate_prediction.""" - i,vm = super(InputResistanceTest,self).\ - generate_prediction(model) - i['duration'] = 100 * pq.ms + units = pq.UnitQuantity("megaohm", pq.ohm * 1e6, symbol="Mohm") # Megaohms - r_in = self.__class__.get_rin(vm, i) - r_in = r_in.simplified - # Put prediction in a form that compute_score() can use. - prediction = {'value':r_in} + ephysprop_name = "Input Resistance" - return prediction + def extract_features(self, model, result): + features = super(InputResistanceTest, self).extract_features(model, result) + if features is not None: + i, vm = features + r_in = self.__class__.get_rin(vm, i) + r_in = r_in.simplified + # Put prediction in a form that compute_score() can use. + features = {"value": r_in} + return features class TimeConstantTest(TestPulseTest): - """Tests the input resistance of a cell.""" + """Test the input resistance of a cell.""" name = "Time constant test" - description = ("A test of membrane time constant of a cell.") + description = "A test of membrane time constant of a cell." units = pq.ms - ephysprop_name = 'Membrane Time Constant' + ephysprop_name = "Membrane Time Constant" - def generate_prediction(self, model): - """Implementation of sciunit.Test.generate_prediction.""" - i,vm = super(TimeConstantTest,self).generate_prediction(model) - tau = self.__class__.get_tau(vm, i) - tau = tau.simplified - # Put prediction in a form that compute_score() can use. - prediction = {'value':tau} - return prediction + def extract_features(self, model, result): + features = super(TimeConstantTest, self).extract_features(model, result) + if features is not None: + i, vm = features + tau = self.__class__.get_tau(vm, i) + tau = tau.simplified + # Put prediction in a form that compute_score() can use. + features = {"value": tau} + return features def compute_score(self, observation, prediction): - """Implementation of sciunit.Test.score_prediction.""" + """Implement sciunit.Test.score_prediction.""" + if prediction is None: + return None # scores.InsufficientDataScore(None) - if 'n' in prediction.keys(): - if prediction['n'] == 0: + if "n" in prediction.keys(): + if prediction["n"] == 0: # if prediction is None: score = scores.InsufficientDataScore(None) else: - prediction['value']=prediction['value'] - score = super(TimeConstantTest,self).compute_score(observation, - prediction) + prediction["value"] = prediction["value"] + score = super(TimeConstantTest, self).compute_score(observation, prediction) return score @@ -146,85 +189,71 @@ class CapacitanceTest(TestPulseTest): name = "Capacitance test" - description = ("A test of the membrane capacitance of a cell.") + description = "A test of the membrane capacitance of a cell." - units = pq.F*1e-12 + units = pq.pF - ephysprop_name = 'Cell Capacitance' + ephysprop_name = "Cell Capacitance" - def generate_prediction(self, model): - """Implementation of sciunit.Test.generate_prediction.""" - i,vm = super(CapacitanceTest,self).generate_prediction(model) - r_in = self.__class__.get_rin(vm, i) - tau = self.__class__.get_tau(vm, i) - c = (tau/r_in).simplified - # Put prediction in a form that compute_score() can use. - prediction = {'value':c} - - return prediction + def extract_features(self, model, result): + features = super(CapacitanceTest, self).extract_features(model, result) + if features is not None: + i, vm = features + r_in = self.__class__.get_rin(vm, i) + tau = self.__class__.get_tau(vm, i) + c = (tau / r_in).simplified + # Put prediction in a form that compute_score() can use. + features = {"value": c} + return features def compute_score(self, observation, prediction): - """Implementation of sciunit.Test.score_prediction.""" + """Implement sciunit.Test.score_prediction.""" + if prediction is None: + return None # scores.InsufficientDataScore(None) - if 'n' in prediction.keys(): - if prediction['n'] == 0: + if "n" in prediction.keys(): + if prediction["n"] == 0: score = scores.InsufficientDataScore(None) else: - score = super(CapacitanceTest,self).compute_score(observation, - prediction) + score = super(CapacitanceTest, self).compute_score(observation, prediction) return score -class RestingPotentialTest(VmTest): +class RestingPotentialTest(TestPulseTest): """Tests the resting potential under zero current injection.""" - required_capabilities = (cap.ReceivesSquareCurrent,) - - params = {'injected_square_current': - {'amplitude':0.0*pq.pA, 'delay':DELAY, 'duration':DURATION}} + default_params = dict(TestPulseTest.default_params) + default_params["amplitude"] = 0.0 * pq.pA name = "Resting potential test" - description = ("A test of the resting potential of a cell " - "where injected current is set to zero.") + description = ( + "A test of the resting potential of a cell " + "where injected current is set to zero." + ) score_type = scores.ZScore - units = pq.mV - ephysprop_name = 'Resting membrane potential' - - def generate_prediction(self, model): - """Implementation of sciunit.Test.generate_prediction.""" - - model.rerun = True + ephysprop_name = "Resting membrane potential" - model.inject_square_current(self.params['injected_square_current']) - - median = model.get_median_vm() # Use median for robustness. - std = model.get_std_vm() - prediction = {'mean':median, 'std':std} - - mp=model.get_membrane_potential() - import math - for i in mp: - if math.isnan(i): - return None - prediction = {'mean':median, 'std':std} - self.prediction = prediction - return prediction + def extract_features(self, model, result): + features = super(RestingPotentialTest, self).extract_features(model, result) + if features is not None: + median = model.get_median_vm() # Use median for robustness. + std = model.get_std_vm() + features = {"mean": median, "std": std} + return features def compute_score(self, observation, prediction): - """Implementation of sciunit.Test.score_prediction.""" - #print("%s: Observation = %s, Prediction = %s" % \ - # (self.name,str(observation),str(prediction))) + """Implement sciunit.Test.score_prediction.""" if prediction is None: - score = scores.InsufficientDataScore(None) - #score = scores.ErrorScore(None) - + return None # scores.InsufficientDataScore(None) else: - score = super(RestingPotentialTest,self).\ - compute_score(observation, prediction) - #self.bind_score(score,None,observation,prediction) + # print(observation,prediction) + # print(type(observation),type(prediction)) + score = super(RestingPotentialTest, self).compute_score( + observation, prediction + ) return score diff --git a/neuronunit/tests/target_spike_current.py b/neuronunit/tests/target_spike_current.py new file mode 100644 index 000000000..9d3f2c710 --- /dev/null +++ b/neuronunit/tests/target_spike_current.py @@ -0,0 +1,646 @@ +"""F/I neuronunit tests, e.g. investigating firing rates and patterns as a +function of input current""" + +import os +import multiprocessing + +global cpucount +cpucount = multiprocessing.cpu_count() +from .base import np, pq, ncap, VmTest, scores, AMPL + +DURATION = 2000 +DELAY = 1000 + +<<<<<<< HEAD +from neuronunit.optimization.data_transport_container import DataTC +======= +#from neuronunit.optimization.data_transport_container import DataTC +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f +import os +import quantities +import neuronunit + +<<<<<<< HEAD +import dask.bag as db +======= +#import dask.bag as db +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f +import quantities as pq +import numpy as np +import copy +import pdb +<<<<<<< HEAD +from numba import jit +import time +import numba +======= +#from numba import jit +import time +#import numba +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f +import copy + +import matplotlib as mpl + +# mpl.use('Agg') +import matplotlib.pyplot as plt +from neuronunit.capabilities.spike_functions import ( + get_spike_waveforms, + spikes2amplitudes, + threshold_detection, +) + +# +# When using differentiation based spike detection is used this is faster. + + +class SpikeCountSearch(VmTest): + """ + A parallel Implementation of a Binary search algorithm, + which finds a rheobase prediction. + + Strengths: this algorithm is faster than the serial class present in this file for model backends that are not able to + implement numba jit optimisation. + Failure to implement JIT happens to be typical of a signifcant number of backends + """ + + def _extra(self): + self.verbose = 1 + + required_capabilities = (ncap.ReceivesSquareCurrent, ncap.ProducesSpikes) + params = { + "injected_square_current": { + "amplitude": 100.0 * pq.pA, + "delay": DELAY, + "duration": DURATION, + } + } + name = "Rheobase test" + description = ( + "A test of the rheobase, i.e. the minimum injected current " + "needed to evoke at least one spike." + ) + units = pq.pA + ephysprop_name = "Rheobase" + score_type = scores.ZScore + + def generate_prediction(self, model): + def check_fix_range(dtc): + """ + Inputs: lookup, A dictionary of previous current injection values + used to search rheobase + Outputs: A boolean to indicate if the correct rheobase current was found + and a dictionary containing the range of values used. + If rheobase was actually found then rather returning a boolean and a dictionary, + instead logical True, and the rheobase current is returned. + given a dictionary of rheobase search values, use that + dictionary as input for a subsequent search. + """ + + steps = [] + dtc.rheobase = {} + dtc.rheobase["value"] = None + sub, supra = get_sub_supra(dtc.lookup) + if (len(sub) + len(supra)) == 0: + # This assertion would only be occur if there was a bug + assert sub.max() <= supra.min() + elif len(sub) and len(supra): + # Termination criterion + + steps = np.linspace(sub.max(), supra.min(), cpucount + 1) * pq.pA + steps = steps[1:-1] * pq.pA + elif len(sub): + steps = np.linspace(sub.max(), 2 * sub.max(), cpucount + 1) * pq.pA + steps = steps[1:-1] * pq.pA + elif len(supra): + steps = ( + np.linspace(supra.min() - 100, supra.min(), cpucount + 1) * pq.pA + ) + steps = steps[1:-1] * pq.pA + + dtc.current_steps = steps + return dtc + + def get_sub_supra(lookup): + sub, supra = [], [] + for current, n_spikes in lookup.items(): + if n_spikes < self.observation["value"]: + sub.append(current) + elif n_spikes > self.observation["value"]: + supra.append(current) + delta = n_spikes - self.observation["value"] + # print(delta,'difference \n\n\n\nn') + sub = np.array(sorted(list(set(sub)))) + supra = np.array(sorted(list(set(supra)))) + return sub, supra + + def check_current(dtc): + """ + Inputs are an amplitude to test and a virtual model + output is an virtual model with an updated dictionary. + """ + dtc.boolean = False + +<<<<<<< HEAD + model = dtc.dtc_to_model() + model._backend.attrs = dtc.attrs +======= + model = dtc#.dtc_to_model() + #print(type(model)) + #model._backend.attrs = dtc.attrs +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + params = { + "injected_square_current": { + "amplitude": None, + "delay": DELAY, + "duration": DURATION, + } + } + + ampl = float(dtc.ampl) + if ampl not in dtc.lookup or len(dtc.lookup) == 0: + uc = {"amplitude": ampl * pq.pA, "duration": DURATION, "delay": DELAY} +<<<<<<< HEAD + + if str("JIT_") in model.backend: + _ = model.inject_square_current(**uc) + n_spikes = model.get_spike_count() + # _ = model._backend.inject_square_current(**uc) + # n_spikes_b = model._backend.get_spike_count() + # assert n_spikes == n_spikes_b +======= + #print(model._backend) + vm = dtc.inject_square_current(**uc) + n_spikes = dtc.get_spike_count() + # _ = model._backend.inject_square_current(**uc) + # n_spikes_b = model._backend.get_spike_count() + # assert n_spikes == n_spikes_b +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + if float(ampl) < -10.0: + dtc.rheobase = {} + dtc.rheobase["value"] = None + dtc.boolean = True + return dtc + + target_spk_count = self.observation["value"] + if n_spikes == target_spk_count: + + dtc.lookup[float(ampl)] = n_spikes + dtc.rheobase = {} + dtc.rheobase["value"] = float(ampl) * pq.pA + dtc.target_spk_count = None + dtc.target_spk_count = dtc.rheobase["value"] + dtc.boolean = True + if self.verbose > 2: + print( + "gets here", + n_spikes, + target_spk_count, + n_spikes == target_spk_count, + ) + return dtc + + dtc.lookup[float(ampl)] = n_spikes + return dtc + + def init_dtc(dtc): + """ + Exploit memory of last model in genes. + """ + if dtc.initiated == True: + + dtc = check_current(dtc) + if dtc.boolean: + + return dtc + + else: + + if type(dtc.current_steps) is type(float): + dtc.current_steps = [ + 0.75 * dtc.current_steps, + 1.25 * dtc.current_steps, + ] + elif type(dtc.current_steps) is type(list): + dtc.current_steps = [s * 1.25 for s in dtc.current_steps] + dtc.initiated = ( + True # logically unnecessary but included for readibility + ) + if dtc.initiated == False: + dtc.boolean = False + # steps = np.linspace(-12,67,int(8)) + ### + # These values are important for who knows what reason + steps = np.linspace(-10, 65, int(7)) + ### + steps_current = [i * pq.pA for i in steps] + dtc.current_steps = steps_current + dtc.initiated = True + return dtc + + def find_target_current(self, dtc): + # This line should not be necessary: + # a class, VeryReducedModel has been made to circumvent this. +<<<<<<< HEAD + # if hasattr(dtc,'model_path'): + # assert os.path.isfile(dtc.model_path), "%s is not a file" % dtc.model_path + # If this it not the first pass/ first generation + # then assume the rheobase value found before mutation still holds until proven otherwise. + # dtc = check_current(model.rheobase,dtc) + # If its not true enter a search, with ranges informed by memory +======= +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + cnt = 0 + sub = np.array([0, 0]) + supra = np.array([0, 0]) + ## + # + ## Important number +<<<<<<< HEAD + # big = 100 +======= +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + big = 250 + while dtc.boolean == False and cnt < big: + + # negeative spiker + if len(sub): + if sub.max() < -1.0: + pass + dtc_clones = [dtc for i in range(0, len(dtc.current_steps))] + for i, s in enumerate(dtc.current_steps): + dtc_clones[i] = copy.copy(dtc_clones[i]) + dtc_clones[i].ampl = copy.copy(dtc.current_steps[i]) + dtc_clones[i].backend = copy.copy(dtc.backend) + + dtc_clones = [d for d in dtc_clones if not np.isnan(d.ampl)] + set_clones = set([float(d.ampl) for d in dtc_clones]) + dtc_clone = [] + for dtc, sc in zip(dtc_clones, set_clones): + dtc = copy.copy(dtc) + dtc.ampl = sc * pq.pA + dtc = check_current(dtc) + dtc_clone.append(dtc) + + for dtc in dtc_clone: + if dtc.boolean == True: + return dtc + + for d in dtc_clone: + dtc.lookup.update(d.lookup) + dtc = check_fix_range(dtc) + + sub, supra = get_sub_supra(dtc.lookup) + if len(supra) and len(sub): + delta = float(supra.min()) - float(sub.max()) + # tolerance = 0.0 + + if self.verbose >= 2: + print("not rheobase alg") + print( + "Try %d: SubMax = %s; SupraMin = %s" + % ( + cnt, + sub.max() if len(sub) else None, + supra.min() if len(supra) else None, + ) + ) + cnt += 1 + reversed = {v: k for k, v in dtc.lookup.items()} + if cnt == big: + if self.verbose >= 2: + print("counted out and thus wrong spike count") + return dtc + +<<<<<<< HEAD + dtc = DataTC() + dtc.attrs = {} + for k, v in model.attrs.items(): + dtc.attrs[k] = v + +======= + #dtc = DataTC() + #dtc.attrs = {} + #for k, v in model.attrs.items(): + # dtc.attrs[k] = v + dtc = model +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + # this is not a perservering assignment, of value, + # but rather a multi statement assertion that will be checked. + dtc.backend = model.backend + dtc = init_dtc(dtc) + prediction = {} + temp = find_target_current(self, dtc).rheobase + + if type(temp) is not type(None) and not type(dict): + prediction["value"] = float(temp) * pq.pA + elif type(temp) is not type(None) and type(dict): + if temp["value"] is not None: + prediction["value"] = float(temp["value"]) * pq.pA + else: + prediction = None + else: + prediction = None + # print(prediction,'prediction') + return prediction + + +class SpikeCountRangeSearch(VmTest): + """ + A parallel Implementation of a Binary search algorithm, + which finds a rheobase prediction. + + Strengths: this algorithm is faster than the serial class, present in this file for model backends that are not able to + implement numba jit optimisation, which actually happens to be typical of a signifcant number of backends + + Weaknesses this serial class is significantly slower, for many backend implementations including raw NEURON + NEURON via PyNN, and possibly GLIF. + + """ + + def _extra(self): + self.verbose = 1 + + # def __init__(self,other_current=None): + # self.other_current = other_current + + required_capabilities = (ncap.ReceivesSquareCurrent, ncap.ProducesSpikes) + # DELAY = 100.0*pq.ms + # DURATION = 1000.0*pq.ms + params = { + "injected_square_current": { + "amplitude": 100.0 * pq.pA, + "delay": DELAY, + "duration": DURATION, + } + } + name = "Rheobase test" + description = ( + "A test of the rheobase, i.e. the minimum injected current " + "needed to evoke at least one spike." + ) + units = pq.pA + # tolerance # Rheobase search tolerance in `self.units`. + ephysprop_name = "Rheobase" + score_type = scores.ZScore + + def generate_prediction(self, model): + def check_fix_range(dtc): + """ + Inputs: lookup, A dictionary of previous current injection values + used to search rheobase + Outputs: A boolean to indicate if the correct rheobase current was found + and a dictionary containing the range of values used. + If rheobase was actually found then rather returning a boolean and a dictionary, + instead logical True, and the rheobase current is returned. + given a dictionary of rheobase search values, use that + dictionary as input for a subsequent search. + """ + + steps = [] + dtc.rheobase = None + sub, supra = get_sub_supra(dtc.lookup) + + if (len(sub) + len(supra)) == 0: + # This assertion would only be occur if there was a bug + assert sub.max() <= supra.min() + elif len(sub) and len(supra): + # Termination criterion + + steps = np.linspace(sub.max(), supra.min(), cpucount + 1) * pq.pA + steps = steps[1:-1] * pq.pA + elif len(sub): + steps = np.linspace(sub.max(), 2 * sub.max(), cpucount + 1) * pq.pA + steps = steps[1:-1] * pq.pA + elif len(supra): + steps = ( + np.linspace(supra.min() - 100, supra.min(), cpucount + 1) * pq.pA + ) + steps = steps[1:-1] * pq.pA + + dtc.current_steps = steps + return dtc + + def get_sub_supra(lookup): + sub, supra = [], [] + for current, n_spikes in lookup.items(): + if n_spikes < self.observation["range"][0]: + sub.append(current) + elif n_spikes > self.observation["range"][1]: + supra.append(current) + + sub = np.array(sorted(list(set(sub)))) + supra = np.array(sorted(list(set(supra)))) + return sub, supra + + """ + def check_current(dtc): + + #Inputs are an amplitude to test and a virtual model + #output is an virtual model with an updated dictionary. + + dtc.boolean = False + + model = dtc.dtc_to_model() + + params = {'injected_square_current': + {'amplitude':100.0*pq.pA, 'delay':DELAY, 'duration':DURATION}} + + ampl = float(dtc.ampl) + if ampl not in dtc.lookup or len(dtc.lookup) == 0: + uc = {'amplitude':ampl*pq.pA,'duration':DURATION,'delay':DELAY} + + dtc.run_number += 1 + model.set_attrs(dtc.attrs) + model.inject_square_current(uc) + n_spikes = model.get_spike_count() + + if float(ampl) < -1.0: + dtc.rheobase = None + dtc.boolean = True + return dtc + + #target_spk_count = self.observation['range'] + if self.observation['range'][0] <= n_spikes <= self.observation['range'][1]: + dtc.lookup[float(ampl)] = n_spikes + dtc.rheobase = {} + dtc.rheobase['value'] = float(ampl)*pq.pA + dtc.target_spk_count = None + dtc.target_spk_count = dtc.rheobase['value'] + dtc.boolean = True + if self.verbose >2: + print('gets here',n_spikes,target_spk_count,n_spikes == target_spk_count) + return dtc + + dtc.lookup[float(ampl)] = n_spikes + return dtc + """ + + def init_dtc(dtc): + """ + Exploit memory of last model in genes. + """ + # check for memory and exploit it. + if dtc.initiated == True: + + dtc = check_current(dtc) + if dtc.boolean: + + return dtc + + else: + # Exploit memory of the genes to inform searchable range. +<<<<<<< HEAD + +======= +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + # if this model has lineage, assume it didn't mutate that far away from it's ancestor. + # using that assumption, on first pass, consult a very narrow range, of test current injection samples: + # only slightly displaced away from the ancestors rheobase value. + + if type(dtc.current_steps) is type(float): + dtc.current_steps = [ + 0.75 * dtc.current_steps, + 1.25 * dtc.current_steps, + ] + elif type(dtc.current_steps) is type(list): + dtc.current_steps = [s * 1.25 for s in dtc.current_steps] + dtc.initiated = ( + True # logically unnecessary but included for readibility + ) + if dtc.initiated == False: + + dtc.boolean = False + + steps = np.linspace(0, 85.0, int(8)) + + steps_current = [i * pq.pA for i in steps] + dtc.current_steps = steps_current + dtc.initiated = True + return dtc + + def find_target_current(self, dtc): + cnt = 0 + sub = np.array([0, 0]) + supra = np.array([0, 0]) + # if dtc.backend is 'GLIF': + # big = 100 + # else: + big = 255 + + while dtc.boolean == False and cnt < big: + + # negeative spiker + if len(sub): + if sub.max() < -1.0: + pass + # use_diff = True # differentiate the wave to look for spikes + + # be = dtc.backend + dtc_clones = [dtc for i in range(0, len(dtc.current_steps))] + for i, s in enumerate(dtc.current_steps): + dtc_clones[i] = copy.copy(dtc_clones[i]) + dtc_clones[i].ampl = copy.copy(dtc.current_steps[i]) + dtc_clones[i].backend = copy.copy(dtc.backend) + + dtc_clones = [d for d in dtc_clones if not np.isnan(d.ampl)] + # try: + # b0 = db.from_sequence(dtc_clones, npartitions=npartitions) + # dtc_clone = list(b0.map(check_current).compute()) + # except: + set_clones = set([float(d.ampl) for d in dtc_clones]) + dtc_clone = [] + for dtc, sc in zip(dtc_clones, set_clones): + dtc = copy.copy(dtc) + dtc.ampl = sc * pq.pA + dtc = check_current(dtc) + dtc_clone.append(dtc) + + for dtc in dtc_clone: + if dtc.boolean == True: + return dtc + + for d in dtc_clone: + dtc.lookup.update(d.lookup) + dtc = check_fix_range(dtc) + + sub, supra = get_sub_supra(dtc.lookup) + if len(supra) and len(sub): + delta = float(supra.min()) - float(sub.max()) + # if str("GLIF") in dtc.backend: + # tolerance = 0.0 + # else: + # tolerance = 0.0 + # tolerance = tolerance + if str(supra.min()) == str(sub.max()): + if self.verbose >= 2: + print( + delta, + "a neuron, close to the edge! Multi spiking rheobase. # spikes: ", + len(supra), + ) + if len(supra) < 100: + dtc.rheobase["value"] = float(supra.min()) + dtc.boolean = True + dtc.lookup[float(supra.min())] = len(supra) + else: + if type(dtc.rheobase) is type(None): + dtc.rheobase = {} + dtc.rheobase["value"] = None + dtc.boolean = False + dtc.lookup[float(supra.min())] = len(supra) + + return dtc + + if self.verbose >= 2: + print("not rheobase alg") + print( + "Try %d: SubMax = %s; SupraMin = %s" + % ( + cnt, + sub.max() if len(sub) else None, + supra.min() if len(supra) else None, + ) + ) + cnt += 1 + return dtc + +<<<<<<< HEAD + dtc = DataTC() + dtc.attrs = {} + for k, v in model.attrs.items(): + dtc.attrs[k] = v + + # this is not a perservering assignment, of value, + # but rather a multi statement assertion that will be checked. + dtc.backend = model.backend +======= + #dtc = DataTC() + dtc = model + #dtc.attrs = {} + #for k, v in model.attrs.items(): + # dtc.attrs[k] = v + + # this is not a perservering assignment, of value, + # but rather a multi statement assertion that will be checked. + #dtc.backend = model.backend +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + dtc = init_dtc(dtc) + + prediction = {} + + temp = find_target_current(self, dtc).rheobase + if type(temp) is not type(None): + if type(temp) is not type(dict()): + prediction["value"] = float(temp) * pq.pA + elif type(temp) is type(dict()): + if type(temp["value"]) is not type(None): + prediction["value"] = float(temp["value"]) * pq.pA + else: + prediction["value"] = None + elif type(temp) is type(None): + prediction["value"] = None # float(temp['value'])* pq.pA + + else: + prediction = None + return prediction diff --git a/neuronunit/tests/waveform.py b/neuronunit/tests/waveform.py index 4787e2661..6a8bffea1 100644 --- a/neuronunit/tests/waveform.py +++ b/neuronunit/tests/waveform.py @@ -1,197 +1,207 @@ """Waveform neuronunit tests, e.g. testing AP waveform properties""" -from .base import np, pq, cap, VmTest, scores, AMPL, DELAY, DURATION +from .base import np, pq, ncap, VmTest, scores + + +class InjectedCurrent: + """Metaclass to mixin with InjectedCurrent tests.""" + + required_capabilities = (ncap.ReceivesSquareCurrent,) + + default_params = dict(VmTest.default_params) + default_params.update({"amplitude": 100 * pq.pA}) + + def compute_params(self): + self.params["injected_square_current"] = self.get_injected_square_current() + self.params["injected_square_current"]["amplitude"] = self.params["amplitude"] class APWidthTest(VmTest): - """Tests the full widths of action potentials at their half-maximum.""" + """Test the full widths of action potentials at their half-maximum.""" - required_capabilities = (cap.ProducesActionPotentials,) + required_capabilities = (ncap.ProducesActionPotentials,) name = "AP width test" - description = ("A test of the widths of action potentials " - "at half of their maximum height.") + description = ( + "A test of the widths of action potentials " "at half of their maximum height." + ) - score_type = scores.ZScore + score_type = scores.RatioScore units = pq.ms - ephysprop_name = 'Spike Half-Width' + ephysprop_name = "Spike Half-Width" def generate_prediction(self, model): - """Implementation of sciunit.Test.generate_prediction.""" + """Implement sciunit.Test.generate_prediction.""" # Method implementation guaranteed by # ProducesActionPotentials capability. # if get_spike_count is zero, then widths will be None # len of None returns an exception that is not handled - model.rerun = True + model.inject_square_current(**self.params["injected_square_current"]) widths = model.get_AP_widths() # Put prediction in a form that compute_score() can use. - prediction = {'mean':np.mean(widths) if len(widths) else None, - 'std':np.std(widths) if len(widths) else None, - 'n':len(widths)} + prediction = { + "mean": np.mean(widths) if len(widths) else None, + "std": np.std(widths) if len(widths) else None, + "n": len(widths), + } return prediction def compute_score(self, observation, prediction): - """Implementation of sciunit.Test.score_prediction.""" - if type(prediction) is type(None): + """Implement sciunit.Test.score_prediction.""" + if isinstance(prediction, type(None)): score = scores.InsufficientDataScore(None) - - elif prediction['n'] == 0: - #sciunit.NoneScore: + elif prediction["n"] == 0: score = scores.InsufficientDataScore(None) - else: - score = super(APWidthTest,self).compute_score(observation, - prediction) + score = super(APWidthTest, self).compute_score(observation, prediction) return score -class InjectedCurrentAPWidthTest(APWidthTest): - """ - Tests the full widths of APs at their half-maximum +class InjectedCurrentAPWidthTest(InjectedCurrent, APWidthTest): + """Tests the full widths of APs at their half-maximum under current injection. """ - required_capabilities = (cap.ReceivesSquareCurrent,) - params = {'injected_square_current': - {'amplitude':100.0*pq.pA, 'delay':DELAY, 'duration':DURATION}} + score_type = scores.ZScore + + units = pq.ms name = "Injected current AP width test" - description = ("A test of the widths of action potentials " - "at half of their maximum height when current " - "is injected into cell.") + description = ( + "A test of the widths of action potentials " + "at half of their maximum height when current " + "is injected into cell." + ) def generate_prediction(self, model): - model.inject_square_current(self.params['injected_square_current']) - prediction = super(InjectedCurrentAPWidthTest,self).generate_prediction(model) + print(self.params["injected_square_current"]) + model.inject_square_current(**self.params["injected_square_current"]) + prediction = super(InjectedCurrentAPWidthTest, self).generate_prediction(model) return prediction class APAmplitudeTest(VmTest): - """Tests the heights (peak amplitude) of action potentials.""" + """Test the heights (peak amplitude) of action potentials.""" - required_capabilities = (cap.ProducesActionPotentials,) + required_capabilities = (ncap.ProducesActionPotentials,) name = "AP amplitude test" - description = ("A test of the amplitude (peak minus threshold) of " - "action potentials.") + description = ( + "A test of the amplitude (peak minus threshold) of " "action potentials." + ) score_type = scores.ZScore units = pq.mV - ephysprop_name = 'Spike Amplitude' + ephysprop_name = "Spike Amplitude" def generate_prediction(self, model): - """Implementation of sciunit.Test.generate_prediction.""" + """Implement sciunit.Test.generate_prediction.""" # Method implementation guaranteed by # ProducesActionPotentials capability. - model.rerun = True + model.inject_square_current(**self.params["injected_square_current"]) + heights = model.get_AP_amplitudes() - model.get_AP_thresholds() # Put prediction in a form that compute_score() can use. - prediction = {'mean':np.mean(heights) if len(heights) else None, - 'std':np.std(heights) if len(heights) else None, - 'n':len(heights)} + prediction = { + "mean": np.mean(heights) if len(heights) else None, + "std": np.std(heights) if len(heights) else None, + "n": len(heights), + } return prediction def compute_score(self, observation, prediction): - """Implementation of sciunit.Test.score_prediction.""" - if prediction['n'] == 0: + """Implementat sciunit.Test.score_prediction.""" + if prediction["n"] == 0: score = scores.InsufficientDataScore(None) else: - score = super(APAmplitudeTest,self).compute_score(observation, - prediction) + score = super(APAmplitudeTest, self).compute_score(observation, prediction) return score -class InjectedCurrentAPAmplitudeTest(APAmplitudeTest): - """ - Tests the heights (peak amplitude) of action potentials - under current injection. - """ - +class InjectedCurrentAPAmplitudeTest(InjectedCurrent, APAmplitudeTest): + """Test the heights (peak amplitude) of action potentials. - required_capabilities = (cap.ReceivesSquareCurrent,) - - params = {'injected_square_current': - {'amplitude':100.0*pq.pA, 'delay':DELAY, 'duration':DURATION}} + Uses current injection. + """ name = "Injected current AP amplitude test" - description = ("A test of the heights (peak amplitudes) of " - "action potentials when current " - "is injected into cell.") + description = ( + "A test of the heights (peak amplitudes) of " + "action potentials when current " + "is injected into cell." + ) def generate_prediction(self, model): - model.inject_square_current(self.params['injected_square_current']) - prediction = super(InjectedCurrentAPAmplitudeTest,self).\ - generate_prediction(model) + model.inject_square_current(**self.params["injected_square_current"]) + prediction = super(InjectedCurrentAPAmplitudeTest, self).generate_prediction( + model + ) return prediction class APThresholdTest(VmTest): - """Tests the full widths of action potentials at their half-maximum.""" + """Test the full widths of action potentials at their half-maximum.""" - required_capabilities = (cap.ProducesActionPotentials,) + required_capabilities = (ncap.ProducesActionPotentials,) name = "AP threshold test" - description = ("A test of the membrane potential threshold at which " - "action potentials are produced.") + description = ( + "A test of the membrane potential threshold at which " + "action potentials are produced." + ) score_type = scores.ZScore units = pq.mV - ephysprop_name = 'Spike Threshold' - + ephysprop_name = "Spike Threshold" def generate_prediction(self, model): - """Implementation of sciunit.Test.generate_prediction.""" + """Implement sciunit.Test.generate_prediction.""" # Method implementation guaranteed by # ProducesActionPotentials capability. - model.rerun = True + model.inject_square_current(**self.params["injected_square_current"]) + threshes = model.get_AP_thresholds() # Put prediction in a form that compute_score() can use. - prediction = {'mean':np.mean(threshes) if len(threshes) else None, - 'std':np.std(threshes) if len(threshes) else None, - 'n':len(threshes)} + prediction = { + "mean": np.mean(threshes) if len(threshes) else None, + "std": np.std(threshes) if len(threshes) else None, + "n": len(threshes), + } return prediction def compute_score(self, observation, prediction): - """Implementation of sciunit.Test.score_prediction.""" - if prediction['n'] == 0: + """Implement sciunit.Test.score_prediction.""" + if prediction["n"] == 0: score = scores.InsufficientDataScore(None) else: - score = super(APThresholdTest,self).compute_score(observation, - prediction) + score = super(APThresholdTest, self).compute_score(observation, prediction) return score -class InjectedCurrentAPThresholdTest(APThresholdTest): - """ - Tests the thresholds of action potentials - under current injection. - """ - - required_capabilities = (cap.ReceivesSquareCurrent,) - - params = {'injected_square_current': - {'amplitude':100.0*pq.pA, 'delay':DELAY, 'duration':DURATION}} +class InjectedCurrentAPThresholdTest(InjectedCurrent, APThresholdTest): + """Test the thresholds of action potentials under current injection.""" name = "Injected current AP threshold test" - description = ("A test of the membrane potential threshold at which " - "action potentials are produced under current injection.") + description = ( + "A test of the membrane potential threshold at which " + "action potentials are produced under current injection." + ) def generate_prediction(self, model): - model.inject_square_current(self.params['injected_square_current']) - return super(InjectedCurrentAPThresholdTest,self).\ - generate_prediction(model) + model.inject_square_current(self.params["injected_square_current"]) + return super(InjectedCurrentAPThresholdTest, self).generate_prediction(model) diff --git a/neuronunit/unit_test/__init__.py b/neuronunit/unit_test/__init__.py old mode 100644 new mode 100755 index 35c756d32..36d27410b --- a/neuronunit/unit_test/__init__.py +++ b/neuronunit/unit_test/__init__.py @@ -1,16 +1,69 @@ """Unit testing module for NeuronUnit""" - -import unittest - + +import warnings + +import matplotlib as mpl + +mpl.use("Agg") # Needed for headless testing +warnings.filterwarnings("ignore") # Suppress all warning messages + from .base import * from .import_tests import ImportTestCase from .doc_tests import DocumentationTestCase -from .resource_tests import NeuroElectroTestCase,BlueBrainTestCase,AIBSTestCase -from .model_tests import ReducedModelTestCase -from .test_tests import TestsPassiveTestCase,TestsWaveformTestCase,\ - TestsFITestCase,TestsDynamicsTestCase,\ - TestsChannelTestCase +from .resource_tests import NeuroElectroTestCase, BlueBrainTestCase, AIBSTestCase +from .model_tests import ( + ReducedModelTestCase, + ExtraCapabilitiesTestCase, + HasSegmentTestCase, + GeppettoBackendTestCase, + VeryReducedModelTestCase, + StaticExternalTestCase, +) + +# from .observation_tests import ObservationsTestCase +""" +from .test_tests import ( + TestsPassiveTestCase, + TestsWaveformTestCase, + TestsFITestCase, + TestsDynamicsTestCase, + TestsChannelTestCase, + FakeTestCase, +) +""" from .misc_tests import EphysPropertiesTestCase -from .sciunit_tests import SciUnitTestCase -#from .evaluate_as_module import * -#from .nsga_parallel import * + +# from .cache_tests import BackendCacheTestCase +<<<<<<< HEAD +from .opt_ephys_properties import testOptimizationEphysCase +from .scores_unit_test import testOptimizationAllenMultiSpike +======= +#from .opt_ephys_properties import testOptimizationEphysCase +#from .scores_unit_test import testOptimizationAllenMultiSpike +from .rheobase_model_test import testModelRheobase +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f + +# from .adexp_opt import * +""" +from .capabilities_tests import * + +from .test_druckmann2013 import ( + Model1TestCase, + Model2TestCase, + Model3TestCase, + Model4TestCase, + Model5TestCase, + Model6TestCase, + Model7TestCase, + Model8TestCase, + Model9TestCase, + Model10TestCase, + Model11TestCase, + OthersTestCase, +) +""" +# from .test_morphology import MorphologyTestCase +# from .test_optimization import DataTCTestCase +# from .sciunit_tests import SciUnitTestCase + +# from .adexp_opt import * diff --git a/neuronunit/unit_test/__main__.py b/neuronunit/unit_test/__main__.py index 4ca3b97ad..85a90fb15 100644 --- a/neuronunit/unit_test/__main__.py +++ b/neuronunit/unit_test/__main__.py @@ -2,13 +2,14 @@ import sys import unittest -from . import * # Import all the tests from the unit_test module +from . import * # Import all the tests from the unit_test module + def main(): - buffer = 'buffer' in sys.argv - sys.argv = sys.argv[:1] # Args need to be removed for __main__ to work. + buffer = "buffer" in sys.argv + sys.argv = sys.argv[:1] # Args need to be removed for __main__ to work. unittest.main(buffer=buffer) -if __name__ == '__main__': + +if __name__ == "__main__": main() - \ No newline at end of file diff --git a/neuronunit/unit_test/adexp_opt.py b/neuronunit/unit_test/adexp_opt.py new file mode 100644 index 000000000..5a8c2928f --- /dev/null +++ b/neuronunit/unit_test/adexp_opt.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python +# coding: utf-8 +SILENT = True +import warnings + +if SILENT: + warnings.filterwarnings("ignore") + +import unittest +import numpy as np +import efel +import matplotlib.pyplot as plt +import quantities as qt + +import matplotlib + +matplotlib.use("Agg") +from neuronunit.allenapi.allen_data_efel_features_opt import ( + opt_to_model, + opt_setup, + opt_exec, +) +from neuronunit.allenapi.allen_data_efel_features_opt import opt_to_model +from neuronunit.allenapi.utils import dask_map_function + +from neuronunit.optimization.model_parameters import ( + MODEL_PARAMS, + BPO_PARAMS, + to_bpo_param, +) +from neuronunit.optimization.optimization_management import inject_model_soma +<<<<<<< HEAD +from neuronunit.optimization.data_transport_container import DataTC +======= +>>>>>>> 9fb0c2e613a1bf059f38eeeae80582d0cfb11f2f +from jithub.models import model_classes + +from sciunit.scores import RelativeDifferenceScore + + +class testOptimization(unittest.TestCase): + def setUp(self): + self.ids = [ + 324257146, + 325479788, + 476053392, + 623893177, + 623960880, + 482493761, + 471819401, + ] + + def test_opt_1(self): + specimen_id = self.ids[1] + cellmodel = "ADEXP" + + if cellmodel == "IZHI": + model = model_classes.IzhiModel() + if cellmodel == "MAT": + model = model_classes.MATModel() + if cellmodel == "ADEXP": + model = model_classes.ADEXPModel() + + target_num_spikes = 9 + + efel_filter_iterable = [ + "ISI_log_slope", + "mean_frequency", + "adaptation_index2", + "first_isi", + "ISI_CV", + "median_isi", + "Spikecount", + "all_ISI_values", + "ISI_values", + "time_to_first_spike", + "time_to_last_spike", + "time_to_second_spike", + ] + [suite, target_current, spk_count, cell_evaluator, simple_cell] = opt_setup( + specimen_id, + cellmodel, + target_num_spikes, + template_model=model, + fixed_current=False, + cached=False, + score_type=RelativeDifferenceScore, + efel_filter_iterable=efel_filter_iterable, + ) + + NGEN = 55 + MU = 35 + + mapping_funct = dask_map_function + final_pop, hall_of_fame, logs, hist = opt_exec( + MU, NGEN, mapping_funct, cell_evaluator, cxpb=0.4, mutpb=0.01 + ) + opt, target, scores, obs_preds, df = opt_to_model( + hall_of_fame, cell_evaluator, suite, target_current, spk_count + ) + best_ind = hall_of_fame[0] + fitnesses = cell_evaluator.evaluate_with_lists(best_ind) + assert np.sum(fitnesses) < 10.7 + self.assertGreater(10.7, np.sum(fitnesses)) + + +if __name__ == "__main__": + unittest.main() diff --git a/neuronunit/unit_test/base.py b/neuronunit/unit_test/base.py index 84aaeb0be..5e92a8432 100644 --- a/neuronunit/unit_test/base.py +++ b/neuronunit/unit_test/base.py @@ -5,15 +5,18 @@ import os import warnings import pdb -try: # Python 2 - from urllib import urlretrieve -except ImportError: # Python 3 - from urllib.request import urlretrieve +from urllib.request import urlretrieve import matplotlib as mpl -mpl.use('Agg') # Avoid any problems with Macs or headless displays. -from sciunit.utils import NotebookTools,import_all_modules -from neuronunit import neuroelectro,bbp,aibs +OSX = sys.platform == "darwin" +if OSX or "Qt" in mpl.rcParams["backend"]: + mpl.use("Agg") # Avoid any problems with Macs or headless displays. -OSX = sys.platform == 'darwin' \ No newline at end of file +from sciunit.utils import NotebookTools, import_all_modules +import neuronunit +from neuronunit.models import ReducedModel +from neuronunit import neuroelectro, bbp, aibs, tests as nu_tests + +NU_BACKEND = os.environ.get("NU_BACKEND", "jNeuroML") +NU_HOME = neuronunit.__path__[0] diff --git a/neuronunit/unit_test/bbp.ipynb b/neuronunit/unit_test/bbp.ipynb index 1ebc58e3f..1d7c720a6 100644 --- a/neuronunit/unit_test/bbp.ipynb +++ b/neuronunit/unit_test/bbp.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": 3, "metadata": { "collapsed": true, "deletable": true, @@ -10,822 +10,59 @@ }, "outputs": [], "source": [ - "%matplotlib notebook\n", - "import matplotlib.pyplot as plt" + "%matplotlib inline\n", + "import matplotlib.pyplot as plt\n", + "\n", + "from neuronunit import bbp # The Blue Brain project module" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 4, "metadata": { "collapsed": false, "deletable": true, - "editable": true + "editable": true, + "tags": [] }, "outputs": [ { - "name": "stdout", "output_type": "stream", - "text": [ - "Getting data from http://microcircuits.epfl.ch/data/released_data/B95.zip\n" - ] - }, - { - "data": { - "application/javascript": [ - "/* Put everything inside the global mpl namespace */\n", - "window.mpl = {};\n", - "\n", - "\n", - "mpl.get_websocket_type = function() {\n", - " if (typeof(WebSocket) !== 'undefined') {\n", - " return WebSocket;\n", - " } else if (typeof(MozWebSocket) !== 'undefined') {\n", - " return MozWebSocket;\n", - " } else {\n", - " alert('Your browser does not have WebSocket support.' +\n", - " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", - " 'Firefox 4 and 5 are also supported but you ' +\n", - " 'have to enable WebSockets in about:config.');\n", - " };\n", - "}\n", - "\n", - "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", - " this.id = figure_id;\n", - "\n", - " this.ws = websocket;\n", - "\n", - " this.supports_binary = (this.ws.binaryType != undefined);\n", - "\n", - " if (!this.supports_binary) {\n", - " var warnings = document.getElementById(\"mpl-warnings\");\n", - " if (warnings) {\n", - " warnings.style.display = 'block';\n", - " warnings.textContent = (\n", - " \"This browser does not support binary websocket messages. \" +\n", - " \"Performance may be slow.\");\n", - " }\n", - " }\n", - "\n", - " this.imageObj = new Image();\n", - "\n", - " this.context = undefined;\n", - " this.message = undefined;\n", - " this.canvas = undefined;\n", - " this.rubberband_canvas = undefined;\n", - " this.rubberband_context = undefined;\n", - " this.format_dropdown = undefined;\n", - "\n", - " this.image_mode = 'full';\n", - "\n", - " this.root = $('
');\n", - " this._root_extra_style(this.root)\n", - " this.root.attr('style', 'display: inline-block');\n", - "\n", - " $(parent_element).append(this.root);\n", - "\n", - " this._init_header(this);\n", - " this._init_canvas(this);\n", - " this._init_toolbar(this);\n", - "\n", - " var fig = this;\n", - "\n", - " this.waiting = false;\n", - "\n", - " this.ws.onopen = function () {\n", - " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", - " fig.send_message(\"send_image_mode\", {});\n", - " if (mpl.ratio != 1) {\n", - " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", - " }\n", - " fig.send_message(\"refresh\", {});\n", - " }\n", - "\n", - " this.imageObj.onload = function() {\n", - " if (fig.image_mode == 'full') {\n", - " // Full images could contain transparency (where diff images\n", - " // almost always do), so we need to clear the canvas so that\n", - " // there is no ghosting.\n", - " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", - " }\n", - " fig.context.drawImage(fig.imageObj, 0, 0);\n", - " };\n", - "\n", - " this.imageObj.onunload = function() {\n", - " this.ws.close();\n", - " }\n", - "\n", - " this.ws.onmessage = this._make_on_message_function(this);\n", - "\n", - " this.ondownload = ondownload;\n", - "}\n", - "\n", - "mpl.figure.prototype._init_header = function() {\n", - " var titlebar = $(\n", - " '
');\n", - " var titletext = $(\n", - " '
');\n", - " titlebar.append(titletext)\n", - " this.root.append(titlebar);\n", - " this.header = titletext[0];\n", - "}\n", - "\n", - "\n", - "\n", - "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", - "\n", - "}\n", - "\n", - "\n", - "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", - "\n", - "}\n", - "\n", - "mpl.figure.prototype._init_canvas = function() {\n", - " var fig = this;\n", - "\n", - " var canvas_div = $('
');\n", - "\n", - " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", - "\n", - " function canvas_keyboard_event(event) {\n", - " return fig.key_event(event, event['data']);\n", - " }\n", - "\n", - " canvas_div.keydown('key_press', canvas_keyboard_event);\n", - " canvas_div.keyup('key_release', canvas_keyboard_event);\n", - " this.canvas_div = canvas_div\n", - " this._canvas_extra_style(canvas_div)\n", - " this.root.append(canvas_div);\n", - "\n", - " var canvas = $('');\n", - " canvas.addClass('mpl-canvas');\n", - " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", - "\n", - " this.canvas = canvas[0];\n", - " this.context = canvas[0].getContext(\"2d\");\n", - "\n", - " var backingStore = this.context.backingStorePixelRatio ||\n", - "\tthis.context.webkitBackingStorePixelRatio ||\n", - "\tthis.context.mozBackingStorePixelRatio ||\n", - "\tthis.context.msBackingStorePixelRatio ||\n", - "\tthis.context.oBackingStorePixelRatio ||\n", - "\tthis.context.backingStorePixelRatio || 1;\n", - "\n", - " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", - "\n", - " var rubberband = $('');\n", - " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", - "\n", - " var pass_mouse_events = true;\n", - "\n", - " canvas_div.resizable({\n", - " start: function(event, ui) {\n", - " pass_mouse_events = false;\n", - " },\n", - " resize: function(event, ui) {\n", - " fig.request_resize(ui.size.width, ui.size.height);\n", - " },\n", - " stop: function(event, ui) {\n", - " pass_mouse_events = true;\n", - " fig.request_resize(ui.size.width, ui.size.height);\n", - " },\n", - " });\n", - "\n", - " function mouse_event_fn(event) {\n", - " if (pass_mouse_events)\n", - " return fig.mouse_event(event, event['data']);\n", - " }\n", - "\n", - " rubberband.mousedown('button_press', mouse_event_fn);\n", - " rubberband.mouseup('button_release', mouse_event_fn);\n", - " // Throttle sequential mouse events to 1 every 20ms.\n", - " rubberband.mousemove('motion_notify', mouse_event_fn);\n", - "\n", - " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", - " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", - "\n", - " canvas_div.on(\"wheel\", function (event) {\n", - " event = event.originalEvent;\n", - " event['data'] = 'scroll'\n", - " if (event.deltaY < 0) {\n", - " event.step = 1;\n", - " } else {\n", - " event.step = -1;\n", - " }\n", - " mouse_event_fn(event);\n", - " });\n", - "\n", - " canvas_div.append(canvas);\n", - " canvas_div.append(rubberband);\n", - "\n", - " this.rubberband = rubberband;\n", - " this.rubberband_canvas = rubberband[0];\n", - " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", - " this.rubberband_context.strokeStyle = \"#000000\";\n", - "\n", - " this._resize_canvas = function(width, height) {\n", - " // Keep the size of the canvas, canvas container, and rubber band\n", - " // canvas in synch.\n", - " canvas_div.css('width', width)\n", - " canvas_div.css('height', height)\n", - "\n", - " canvas.attr('width', width * mpl.ratio);\n", - " canvas.attr('height', height * mpl.ratio);\n", - " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", - "\n", - " rubberband.attr('width', width);\n", - " rubberband.attr('height', height);\n", - " }\n", - "\n", - " // Set the figure to an initial 600x600px, this will subsequently be updated\n", - " // upon first draw.\n", - " this._resize_canvas(600, 600);\n", - "\n", - " // Disable right mouse context menu.\n", - " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", - " return false;\n", - " });\n", - "\n", - " function set_focus () {\n", - " canvas.focus();\n", - " canvas_div.focus();\n", - " }\n", - "\n", - " window.setTimeout(set_focus, 100);\n", - "}\n", - "\n", - "mpl.figure.prototype._init_toolbar = function() {\n", - " var fig = this;\n", - "\n", - " var nav_element = $('
')\n", - " nav_element.attr('style', 'width: 100%');\n", - " this.root.append(nav_element);\n", - "\n", - " // Define a callback function for later on.\n", - " function toolbar_event(event) {\n", - " return fig.toolbar_button_onclick(event['data']);\n", - " }\n", - " function toolbar_mouse_event(event) {\n", - " return fig.toolbar_button_onmouseover(event['data']);\n", - " }\n", - "\n", - " for(var toolbar_ind in mpl.toolbar_items) {\n", - " var name = mpl.toolbar_items[toolbar_ind][0];\n", - " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", - " var image = mpl.toolbar_items[toolbar_ind][2];\n", - " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", - "\n", - " if (!name) {\n", - " // put a spacer in here.\n", - " continue;\n", - " }\n", - " var button = $('