{"id":47423552,"url":"https://github.com/scidash/neuronunit","last_synced_at":"2026-04-05T01:00:47.862Z","repository":{"id":51162979,"uuid":"6494521","full_name":"scidash/neuronunit","owner":"scidash","description":"A package for data-driven validation of neuron and ion channel models using SciUnit","archived":false,"fork":false,"pushed_at":"2021-07-18T07:39:17.000Z","size":45419,"stargazers_count":41,"open_issues_count":62,"forks_count":26,"subscribers_count":7,"default_branch":"dev","last_synced_at":"2025-10-30T22:42:20.570Z","etag":null,"topics":["computational-neuroscience","neuroinformatics","neuron-model","neuroscience","python","unit-testing"],"latest_commit_sha":null,"homepage":"http://neuronunit.scidash.org","language":"Jupyter Notebook","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/scidash.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2012-11-01T18:23:07.000Z","updated_at":"2025-09-23T22:57:18.000Z","dependencies_parsed_at":"2022-09-26T16:31:16.533Z","dependency_job_id":null,"html_url":"https://github.com/scidash/neuronunit","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/scidash/neuronunit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scidash%2Fneuronunit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scidash%2Fneuronunit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scidash%2Fneuronunit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scidash%2Fneuronunit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/scidash","download_url":"https://codeload.github.com/scidash/neuronunit/tar.gz/refs/heads/dev","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scidash%2Fneuronunit/sbom","scorecard":{"id":804584,"data":{"date":"2025-08-11","repo":{"name":"github.com/scidash/neuronunit","commit":"835235e4ca58545e36d1914571e5adc314d81fef"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":1.3,"checks":[{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Code-Review","score":1,"reason":"Found 3/28 approved changesets -- score normalized to 1","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"License","score":0,"reason":"license file not detected","details":["Warn: project does not have a license file"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'dev'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Info: Possibly incomplete results: error parsing shell code: \u003c\u003c\u003c must be followed by a word: build.sh:0","Warn: containerImage not pinned by hash: Dockerfile:1: pin your Docker image by updating scidash/neuronunit-optimization to scidash/neuronunit-optimization@sha256:fb6dd0f917ebfeb06f4ac290059a5eeae6cd2c08e2da462a846e42b757086914","Warn: pipCommand not pinned by hash: Dockerfile:3","Warn: pipCommand not pinned by hash: Dockerfile:5","Warn: pipCommand not pinned by hash: Dockerfile:6","Warn: pipCommand not pinned by hash: Dockerfile:8","Warn: pipCommand not pinned by hash: Dockerfile:10","Warn: pipCommand not pinned by hash: Dockerfile:12","Warn: pipCommand not pinned by hash: Dockerfile:20","Warn: pipCommand not pinned by hash: Dockerfile:41","Warn: pipCommand not pinned by hash: Dockerfile:44","Warn: pipCommand not pinned by hash: Dockerfile:45","Warn: pipCommand not pinned by hash: Dockerfile:47","Info:   0 out of   1 containerImage dependencies pinned","Info:   0 out of  11 pipCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Vulnerabilities","score":0,"reason":"15 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: PYSEC-2021-387 / GHSA-j8fq-86c5-5v2r","Warn: Project is vulnerable to: PYSEC-2022-42991 / GHSA-v3c5-jqr6-7qm8","Warn: Project is vulnerable to: GHSA-cpwx-vrp4-4pq7","Warn: Project is vulnerable to: PYSEC-2021-66 / GHSA-g3rq-g295-4j3m","Warn: Project is vulnerable to: GHSA-h5c8-rqwp-cp95","Warn: Project is vulnerable to: GHSA-h75v-3vvj-5mfj","Warn: Project is vulnerable to: GHSA-q2x7-8rv6-6q7h","Warn: Project is vulnerable to: GHSA-55x5-fj6c-h6m8","Warn: Project is vulnerable to: PYSEC-2021-19 / GHSA-jq4v-f5q6-mjqq","Warn: Project is vulnerable to: PYSEC-2020-62 / GHSA-pgww-xf46-h92r","Warn: Project is vulnerable to: PYSEC-2022-230 / GHSA-wrxv-2j5q-m38w","Warn: Project is vulnerable to: PYSEC-2024-110 / GHSA-jw8x-6495-233v","Warn: Project is vulnerable to: PYSEC-2020-108 / GHSA-jxfp-4rvq-9h9m","Warn: Project is vulnerable to: PYSEC-2024-153 / GHSA-rxff-vr5r-8cj5","Warn: Project is vulnerable to: GHSA-g7vv-2v7x-gj9p"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 6 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-23T11:26:01.450Z","repository_id":51162979,"created_at":"2025-08-23T11:26:01.456Z","updated_at":"2025-08-23T11:26:01.456Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31420789,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-05T00:25:07.052Z","status":"ssl_error","status_checked_at":"2026-04-05T00:25:05.923Z","response_time":60,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["computational-neuroscience","neuroinformatics","neuron-model","neuroscience","python","unit-testing"],"created_at":"2026-03-21T20:00:25.398Z","updated_at":"2026-04-05T01:00:47.856Z","avatar_url":"https://github.com/scidash.png","language":"Jupyter Notebook","funding_links":[],"categories":["Open Code"],"sub_categories":["Analysis Software"],"readme":"### Circle CI russelljjarvis/optimization build:\n[![Build Status](https://circleci.com/gh/russelljjarvis/neuronunit/tree/optimization.svg?style=svg)](https://app.circleci.com/pipelines/github/russelljjarvis/neuronunit/)\n### Travis CI scidash/optimization build:\n[![Travis](https://travis-ci.org/scidash/neuronunit.svg?branch=optimization)](https://travis-ci.org/scidash/neuronunit?branch=optimization)  \n| Master  | Dev |\n| ------------- | ------------- |\n| [![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)  |\n| [![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) |\n| [![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) |\n| [![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) |\n| [![Binder](https://mybinder.org/badge.svg)](https://mybinder.org/v2/gh/scidash/neuronunit/master) |\n\n\n\n![NeuronUnit Logo](https://raw.githubusercontent.com/scidash/assets/master/logos/neuronunit/NeuronUnitBlack2.png)\n\n# Concept:  \nNeuronUnit uses the [SciUnit](http://github.com/scidash/sciunit)-framework to test models of ion channels, neurons, and neuronal networks.  \nhttps://github.com/rgerkin/papers/blob/master/neuronunit_frontiers/Paper.pdf\n\n# Documentation:\n(See SciUnit documentation [here](http://github.com/scidash/sciunit/blob/master/docs/chapter1.ipynb) first)\n- [Chapter 1](http://github.com/scidash/neuronunit/blob/master/docs/chapter1.ipynb)\n- [Chapter 2](http://github.com/scidash/neuronunit/blob/master/docs/chapter2.ipynb)\n- [Chapter 3](http://github.com/scidash/neuronunit/blob/master/docs/chapter3.ipynb)\n\n# Presentations:  \n\nINCF Meeting (August, 2014) (Less code)\nhttps://github.com/scidash/assets/blob/master/presentations/SciUnit%20INCF%20Talk.pdf?raw=true\n\nOpenWorm Journal Club (August, 2014) (More code)\nhttps://github.com/scidash/assets/blob/master/presentations/SciUnit%20OpenWorm%20Journal%20Club.pdf?raw=true\n\n# Examples:\n### (Example 1) Validating an ion channel model's IV curve against data from a published experiment\n```python\nfrom channelworm.ion_channel.models import GraphData\nfrom neuronunit.tests.channel import IVCurvePeakTest\nfrom neuronunit.models.channel import ChannelModel\n\n# Instantiate the model\nchannel_model_name = 'EGL-19.channel' # Name of a NeuroML channel model\nchannel_id = 'ca_boyle'\nchannel_file_path = os.path.join('path', 'to', 'models', '%s.nml' % channel_model_name)\nmodel = ChannelModel(channel_file_path, channel_index=0, name=channel_model_name)\n\n# Get the experiment data from ChannelWorm and instantiate the test\ndoi = '10.1083/jcb.200203055'\nfig = '2B'\nsample_data = GraphData.objects.get(graph__experiment__reference__doi=doi,\n                                    graph__figure_ref_address=fig)\nvoltage, current_per_farad = sample_data.asunitedarray()\npatch_capacitance = pq.Quantity(1e-13,'F') # Assume recorded patch had this capacitance;\n                                           # an arbitrary scaling factor.  \ncurrent = current_per_farad * patch_capacitance\nobservation = {'v':voltage,\n               'i':current}\ntest = IVCurvePeakTest(observation)\n\n# Judge the model output against the experimental data\nscore = test.judge(model)\nrd = score.related_data\nscore.plot(rd['v'],rd['i_obs'],color='k',label='Observed (data)')\nscore.plot(rd['v'],rd['i_pred'],same_fig=True,color='r',label='Predicted (model)')\n```\n![png](https://raw.githubusercontent.com/scidash/assets/master/figures/SCU_IVCurve_Model_6_0.png)\n\n```\nscore.summarize()\n\"\"\" OUTPUT:\nModel EGL-19.channel (ChannelModel) achieved score Fail on test 'IV Curve Test (IVCurvePeakTest)'. ===\n\"\"\"\nscore.describe()\n\"\"\" OUTPUT:\nThe score was computed according to 'The sum-squared difference in the observed and predicted current values over the range of the tested holding potentials.' with raw value 3.151 pA^2\n\"\"\"\n```\n\n### (Example 2) Testing the membrane potential and action potential widths of several spiking neuron models against experimental data found at http://neuroelectro.org.  \n\n```\nimport sciunit\nfrom neuronunit import neuroelectro,tests,capabilities\nimport neuronunit.neuroconstruct.models as nc_models\nfrom pythonnC.utils.putils import OSB_MODELS\n\n# We will test cerebellar granule cell models.  \nbrain_area = 'cerebellum'\nneuron_type = 'cerebellar_granule_cell'\npath = os.path.join(OSB_MODELS,brain_area,neuron_type)\nneurolex_id = 'nifext_128' # Cerebellar Granule Cell\n\n# Specify reference data for a test of resting potential for a granule cell.  \nreference_data = neuroelectro.NeuroElectroSummary(\n    neuron = {'nlex_id':neurolex_id}, # Neuron type.  \n    ephysprop = {'name':'Resting Membrane Potential'}) # Electrophysiological property name.\n# Get and verify summary data for the combination above from neuroelectro.org.\nreference_data.get_values()\nvm_test = tests.RestingPotentialTest(\n                observation = {'mean':reference_data.mean,\n                               'sd':reference_data.std},\n                name = 'Resting Potential')\n\n# Specify reference data for a test of action potential width.  \nreference_data = neuroelectro.NeuroElectroSummary(\n    neuron = {'nlex_id':neurolex_id}, # Neuron type.  \n    ephysprop = {'name':'Spike Half-Width'}) # Electrophysiological property name.\n# Get and verify summary data for the combination above from neuroelectro.org.\nreference_data.get_values()\nspikewidth_test = tests.InjectedCurrentAPWidthTest(\n                observation = {'mean':reference_data.mean,\n                               'sd':reference_data.std},\n                name = 'Spike Width',\n                params={'injected_square_current':{'amplitude':5.3*pq.pA,\n                                                   'delay':50.0*pq.ms,\n                                                   'duration':500.0*pq.ms}})\n                # 5.3 pA of injected current in a 500 ms square pulse.  \n\n# Create a test suite from these two tests.  \nsuite = sciunit.TestSuite('Neuron Tests',(spikewidth_test,vm_test))\n\nmodels = []\nfor model_name in model_names # Iterate through a list of models downloaded from http://opensourcebrain.org\n    model_info = (brain_area,neuron_type,model_name)\n    model = nc_models.OSBModel(*model_info)\n    models.append(model) # Add to the list of models to be tested.  \n\nscore_matrix = suite.judge(models,stop_on_error=True)\nscore_matrix.view()\n```\n### Score Matrix for Test Suite 'Neuron Tests'\n| Model                   | Spike Width                     | Resting Potential      |\n|-------------------------|:-------------------------------:|:----------------------:|\n|                         | (InjectedCurrentSpikeWidthTest) | (RestingPotentialTest) |\n| cereb_grc_mc (OSBModel) |\tZ = nan\t                        | Z = 4.92               |\n| GranuleCell (OSBModel)  |\tZ = -3.56\t                    | Z = 0.88               |\n\n\n```\nimport matplotlib.pyplot as plt\nax1 = plt.subplot2grid((1,3), (0,0), colspan=2)\nax2 = plt.subplot2grid((1,3), (0,2), colspan=1)\nrd = score.related_data\nscore_matrix[0,0].plot(rd['t'],rd['v_pred'],ax=ax1[0])\nscore_matrix[0,1].plot(rd['t'],rd['v_pred'],ax=ax2[0])\nax2.set_xlim(283,284.7)\n````\n![png](https://raw.githubusercontent.com/scidash/assets/master/figures/spike_width_test.png)\n![png](https://raw.githubusercontent.com/scidash/assets/master/figures/spike_width_test2.png)\n\n# Tutorial:\nNeuronUnit is based on [SciUnit](http://github.com/scidash/sciunit), a discipline-agnostic framework for data-driven unit testing of scientific models.  Any test script will do the following things in sequence.  Most of these will be abstracted away in SciUnit or NeuronUnit modules that make things easier:  \n\n1. Instantiate a model(s) from a model class, with parameters of interest to build a specific model.    \n1. Instantiate a test(s) from a test class, with parameters of interest to build a specific test.  \n2. Check that the model has the capabilities required to take the test.     \n1. Make the model take the test.  \n2. Generate a score from that test run.  \n1. Bind the score to the specific model/test combination and any related data from test execution.\n1. Visualize the score (i.e. print or display the result of the test).  \n\nHere, 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.  \n\nHere is a toy model class:  \n```python\nclass ToyNeuronModel(sciunit.Model,\n                     neuronunit.capabilities.ProducesMembranePotential):\n    \"\"\"A toy neuron model that is always at its resting potential\"\"\"\n    def __init__(self, v_rest, name=None):\n        self.v_rest = v_rest\n        sciunit.Model.__init__(self, name=name)\n\n    def get_membrane_potential(self):\n        array = np.ones(10000) * self.v_rest\n        dt = 1*ms # Time per sample in milliseconds.  \n        vm = AnalogSignal(array,units=mV,sampling_rate=1.0/dt)\n        return vm\n```\n\nThe `ToyNeuronModel` class inherits from `sciunit.Model` (as do all NeuronUnit models), and also from  `ProducesMembranePotential`, which is a subclass of `sciunit.Capability`.  Inheriting from a SciUnit `Capability` is a how a SciUnit `Model` lets tests know what it can and cannot do.  It tells tests that the model will be implementing any method stubbed out in the definition of that `Capability`.   \n\nLet's see what the `neuronunit.capabilities.ProducesMembranePotential` capability looks like:  \n```python\nclass ProducesMembranePotential(Capability):\n\t\"\"\"Indicates that the model produces a somatic membrane potential.\"\"\"\n\n\tdef get_membrane_potential(self):\n\t\t\"\"\"Must return a neo.core.AnalogSignal.\"\"\"\n\t\traise NotImplementedError()\n\n\tdef get_median_vm(self):\n\t\tvm = self.get_membrane_potential()\n\t\treturn np.median(vm)\n```\n\n`ProducesMembranePotential` has two methods.  The first, `get_membrane_potential` is unimplemented by design.  Since there is no way to know how each model will generate and return a membrane potential time series, the `get_membrane_potential` method in this capability is left unimplemented, while the docstring describes what the model must implement in order to satisfy that capability.  In the `ToyNeuronModel` above, we see that the model implements it by simply creating a long array of resting potential values, and returning it as a `neo.core.AnalogSignal` object.  \n\nThe second, `get_median_vm` is implemented, which means there is no need for the model to implement it again.  For its implementation to work, however, the implementation of `get_membrane_potential` must be complete.  Pre-implemented capability methods such as these allow the developer to focus on implementing only a few core interactions with the model, and then getting a lot of extra functionality for free.  In the example above, once we know that the membrane potential is being returned as a `neo.core.AnalogSignal`, we can simply take the median using numpy.  We know that the membrane potential isn't being returned as a list or a tuple or some other object on which numpy's median function won't necessarily work.  \n\nLet's construct a single instance of this model, by choosing a value for the single membrane potential argument.  This toy model will now have a 60 mV membrane potential at all times:  \n\n```python\nfrom quantities import mV\nmy_neuron_model = ToyNeuronModel(-60.0*mV, name='my_neuron_model')\n```\n\nNow we can then construct a simple test to use on this model or any other test that expresses the appropriate capabilities:  \n\n```python\nclass ToyAveragePotentialTest(sciunit.Test):\n\t\"\"\"Tests the average membrane potential of a neuron.\"\"\"\n\n\tdef __init__(self,\n\t\t\t     observation={'mean':None,'sd':None},\n\t\t\t     name=\"Average potential test\"):\n\t\t\"\"\"Takes the mean and standard deviation of reference membrane potentials.\"\"\"\n\n\t\tsciunit.Test.__init__(self,observation,name) # Call the base constructor.  \n\t\tself.required_capabilities += (neuronunit.capabilities.ProducesMembranePotential,)\n\t\t\t\t\t\t\t  \t\t   # This test will require a model to express the above capabilities\n\n\tdescription = \"A test of the average membrane potential of a cell.\"\n\tscore_type = sciunit.scores.ZScore # The test will return this kind of score.  \n\n\tdef validate_observation(self, observation):\n\t    \"\"\"An optional method that makes sure an observation to be used as\n\t    reference data has the right form\"\"\"\n\t\ttry:\n\t\t\tassert type(observation['mean']) is quantities.Quantity # From the 'quantities' package\n\t\t\tassert type(observation['sd']) is quantities.Quantity\n\t\texcept Exception as e:\n\t\t\traise sciunit.ObservationError((\"Observation must be of the form \"\n\t\t\t\t\t\t\t\t\t\"{'mean':float*mV,'sd':float*mV}\"))\n\n\tdef generate_prediction(self, model):\n\t\t\"\"\"Implementation of sciunit.Test.generate_prediction.\"\"\"\n\t\tvm = model.get_median_vm() # If the model has the capability 'ProducesMembranePotential',\n\t\t                           # then it implements this method\n\t\tprediction = {'mean':vm}\n\t\treturn prediction\n\n\tdef compute_score(self, observation, prediction):\n\t\t\"\"\"Implementation of sciunit.Test.score_prediction.\"\"\"\n\t\tscore = sciunit.comparators.zscore(observation,prediction)\t# Computes a decimal Z score.  \n\t\tscore = sciunit.scores.ZScore(score) # Wraps it in a sciunit.Score type.  \n\t\tscore.related_data['mean_vm'] = prediction['mean'] # Binds some related data about the test run.  \n\t\treturn score\n```\n\nThe test constructor takes an observation to parameterize the test, e.g.:  \n\n```python\nfrom quantities import mV\nmy_observation = {'mean':-60.0*mV,\n                  'sd':3.5*mV}\nmy_average_potential_test = ToyAveragePotentialTest(my_observation, name='my_average_potential_test')\n```\n\nA few things happen upon test instantiation, including validation of the observation. Since the observation has the correct format (for this test class), `ToyAveragePotentialTest.validate_observation` will complete successfully and the test will be ready for use.  \n\n```python\nscore = my_average_potential_test.judge(my_neuron_model)\n```\n\nThe `sciunit.Test.judge` method does several things.  \n-- First, it checks to makes sure that my_neuron_model expresses the capabilities required to take the test.  It doesn't check to see if they are implemented correctly (how could it know?) but it does check to make sure the model at least claims (through inheritance) to express these capabilities.  The required capabilities are none other than those in the test's `required_capabilities` attribute.  Since `ProducesMembranePotential` is the only required capability, and the `ToyNeuronModel` class inherits from this capability class, that check passes.  \n-- Second, it calls the test's `generate_prediction` method, which uses the model's capabilities to make the model return some quantity of interest, in this case the median membrane potential.  \n-- Third, it calls the test's `compute_score` method, which compares the observation the test was instantiated with against the prediction returned in the previous step.  This comparison of quantities is cast into a `score` (in this case, a Z Score), bounds to some model output of interest (in this case, the model's mean membrane potential), and that `score` object is returned.  \n-- Fourth, the score returned is checked to make sure it is of the type promised in the class definition, i.e. that a Z Score is returned if a Z Score is listed in the test's `score_type` attribute.  \n-- Fifth, the score is bound to the test that returned it, the model that took the test, and the prediction and observation that were used to compute it.  \n\nIf all these checks pass (and there are no runtime errors) then we will get back a `score` object.  Usually this will be the kind of score we can use to evaluate model/data agreement.  If one of the capabilities required by the test is not expressed by the model, `judge` returns a special `NAScore` score type, which can be thought of as a blank.  It's not an error -- it just means that the model, as written, is not capable of taking that test.  If there are runtime errors, they will be raised during test execution; however if the optional `stop_on_error` keyword argument is set to `False` when we call `judge`, then it we will return a special `ErrorScore` score type, encoding the error that was raised.  This is useful when batch testing many model and test combinations, so that the whole script doesn't get halted.  One can always check the scores at the end and then fix and re-run the subset of model/test combinations that returned an `ErrorScore`.\n\nFor any score, we can summarize it like so:  \n```python\nscore.summarize()\n''' OUTPUT:\n=== Model my_neuron_model (ToyAveragePotentialModel) achieved score 1.0 on test my_average_potential_test (ToyAveragePotentialTest)'. ===\n'''\n```\n\nand we can get more information about the score:  \n\n```python\nscore.describe()\n''' OUTPUT:\nThe score was computed according to 'the difference of the predicted and observed means divided by the observed standard deviation' with raw value 1.0\n'''\n```\n\n## Reproducible Research ID\nRRID:[SCR_015634](https://scicrunch.org/scicrunch/Resources/record/nlx_144509-1/c70f9dfd-0fc6-5052-9d90-a571c2ebea2e/search)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscidash%2Fneuronunit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fscidash%2Fneuronunit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscidash%2Fneuronunit/lists"}