From 3f1d53c8cbd447704574d085c5c6ce3cb86799d0 Mon Sep 17 00:00:00 2001 From: Padraig Gleeson Date: Fri, 5 Jun 2026 09:02:02 +0100 Subject: [PATCH 1/4] To v0.4.0 --- .github/workflows/ci_versions.yml | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci_versions.yml b/.github/workflows/ci_versions.yml index 5c168d0..506d9de 100644 --- a/.github/workflows/ci_versions.yml +++ b/.github/workflows/ci_versions.yml @@ -44,6 +44,13 @@ jobs: pip install . pip list + + - name: Run pytest + run: | + pip install pytest + cd omv/test + pytest -vs + - name: Run simple OMV tests run: | omv all -V --engine=jNeuroML_NEURON diff --git a/setup.cfg b/setup.cfg index 8281b2a..a8e82f3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = OSBModelValidation -version = 0.3.11 +version = 0.4.0 author = Boris Marin, Padraig Gleeson author_email = borismarin@gmail.com url = https://github.com/OpenSourceBrain/osb-model-validation From fcd10bddfb4ace97e8326703fe27ab4c6f8a0840 Mon Sep 17 00:00:00 2001 From: Padraig Gleeson Date: Fri, 5 Jun 2026 09:02:48 +0100 Subject: [PATCH 2/4] Remove all brian1 features - not supported on python 3 --- omv/engines/__init__.py | 2 - omv/engines/brian1.py | 59 ---------------------- omv/engines/getbrian2.py | 2 - omv/engines/jneuromlbrian.py | 65 ------------------------- omv/engines/pynnbrian1.py | 59 ---------------------- omv/omv_util.py | 9 ---- utilities/tests/test_brian1.py | 3 -- utilities/tests/test_pynn_simulators.py | 7 ++- 8 files changed, 6 insertions(+), 200 deletions(-) delete mode 100644 omv/engines/brian1.py delete mode 100644 omv/engines/jneuromlbrian.py delete mode 100644 omv/engines/pynnbrian1.py delete mode 100644 utilities/tests/test_brian1.py diff --git a/omv/engines/__init__.py b/omv/engines/__init__.py index 31dbc34..efe25f3 100644 --- a/omv/engines/__init__.py +++ b/omv/engines/__init__.py @@ -21,7 +21,6 @@ from omv.engines.pylemsnml2 import PyLemsNeuroML2Engine from omv.engines.genesis import GenesisEngine -# from omv.engines.brian1 import Brian1Engine from omv.engines.brian2_ import Brian2Engine from omv.engines.arbor_ import ArborEngine from omv.engines.xpp import XppEngine @@ -33,7 +32,6 @@ from omv.engines.pynnneuron import PyNNNRNEngine from omv.engines.pyneuron import PyNRNEngine -# from omv.engines.pynnbrian1 import PyNNBrian1Engine from omv.engines.pynnbrian2 import PyNNBrian2Engine from omv.engines.pynnnest import PyNNNestEngine from omv.engines.pynnneuroml import PyNNNeuroMLEngine diff --git a/omv/engines/brian1.py b/omv/engines/brian1.py deleted file mode 100644 index 1b3505f..0000000 --- a/omv/engines/brian1.py +++ /dev/null @@ -1,59 +0,0 @@ -import os -import subprocess as sp - -from omv.common.inout import inform, trim_path, check_output, is_verbose -from omv.engines.engine import OMVEngine, EngineExecutionError - - -class Brian1Engine(OMVEngine): - name = "Brian" - - python3_compatible = False - - @staticmethod - def is_installed(): - ret = False - try: - import brian - - if is_verbose(): - inform( - "Brian version %s is correctly installed..." % brian.__version__, - indent=2, - ) - - ret = "v%s" % brian.__version__ - - except Exception as err: - inform("Couldn't import Brian into Python: ", err, indent=1) - ret = False - return ret - - @staticmethod - def install(version): - from omv.engines.getbrian1 import install_brian - - home = os.environ["HOME"] - inform("Will fetch and install the latest Brian (version 1.x)", indent=2) - install_brian() - inform("Done...", indent=2) - - def run(self): - try: - inform( - "Running file %s with %s" % (trim_path(self.modelpath), self.name), - indent=1, - ) - self.stdout = check_output( - ["python", self.modelpath, "-nogui"], - cwd=os.path.dirname(self.modelpath), - ) - self.returncode = 0 - except sp.CalledProcessError as err: - self.returncode = err.returncode - self.stdout = err.output - raise EngineExecutionError - except Exception as err: - inform("Another error with running %s: " % self.name, err, indent=1) - self.returncode = -1 - self.stdout = "???" diff --git a/omv/engines/getbrian2.py b/omv/engines/getbrian2.py index 6b98ad2..8f55b6e 100644 --- a/omv/engines/getbrian2.py +++ b/omv/engines/getbrian2.py @@ -1,7 +1,5 @@ from omv.common.inout import pip_install -from omv.engines.getbrian1 import check_scipy_dev - def install_brian2(version): if not version: diff --git a/omv/engines/jneuromlbrian.py b/omv/engines/jneuromlbrian.py deleted file mode 100644 index 333743b..0000000 --- a/omv/engines/jneuromlbrian.py +++ /dev/null @@ -1,65 +0,0 @@ -import os -import subprocess as sp - -from omv.engines.jneuroml import JNeuroMLEngine -from omv.engines.brian1 import Brian1Engine -from omv.common.inout import inform, trim_path, check_output, is_verbose -from omv.engines.engine import EngineExecutionError - - -class JNeuroMLBrianEngine(JNeuroMLEngine): - name = "jNeuroML_Brian" - - python3_compatible = Brian1Engine.python3_compatible - - @staticmethod - def is_installed(): - if is_verbose(): - inform( - "Checking whether %s is installed..." % JNeuroMLBrianEngine.name, - indent=1, - ) - return JNeuroMLEngine.is_installed() and Brian1Engine.is_installed() - - @staticmethod - def install(version): - if not JNeuroMLEngine.is_installed(): - JNeuroMLEngine.install(None) - if not Brian1Engine.is_installed(): - Brian1Engine.install(version) - - JNeuroMLBrianEngine.path = JNeuroMLEngine.path + ":" + Brian1Engine.path - JNeuroMLBrianEngine.environment_vars = {} - JNeuroMLBrianEngine.environment_vars.update(JNeuroMLEngine.environment_vars) - JNeuroMLBrianEngine.environment_vars.update(Brian1Engine.environment_vars) - inform("PATH: " + JNeuroMLBrianEngine.path) - inform("Env vars: %s" % JNeuroMLBrianEngine.environment_vars) - - def run(self): - try: - inform( - "Running file %s with %s" - % (trim_path(self.modelpath), JNeuroMLBrianEngine.name), - indent=1, - ) - - from omv.engines.jneuroml import JNeuroMLEngine - - jnml = JNeuroMLEngine.get_executable() - - self.stdout = check_output( - [jnml, self.modelpath, "-brian"], - cwd=os.path.dirname(self.modelpath), - env=JNeuroMLEngine.get_environment(), - ) - self.stdout += check_output( - ["python", self.modelpath.replace(".xml", "_brian.py"), "-nogui"], - cwd=os.path.dirname(self.modelpath), - ) - inform("Success with running ", JNeuroMLBrianEngine.name, indent=1) - self.returncode = 0 - except sp.CalledProcessError as err: - inform("Error with ", JNeuroMLBrianEngine.name, indent=1) - self.returncode = err.returncode - self.stdout = err.output - raise EngineExecutionError diff --git a/omv/engines/pynnbrian1.py b/omv/engines/pynnbrian1.py deleted file mode 100644 index cb2270e..0000000 --- a/omv/engines/pynnbrian1.py +++ /dev/null @@ -1,59 +0,0 @@ -import os -import subprocess as sp - -from omv.engines.brian1 import Brian1Engine -from omv.engines.pynn import PyNNEngine - -from omv.common.inout import inform, trim_path, check_output, is_verbose -from omv.engines.engine import EngineExecutionError - - -class PyNNBrian1Engine(PyNNEngine): - name = "PyNN_Brian1" - python3_compatible = Brian1Engine.python3_compatible - - @staticmethod - def is_installed(): - if is_verbose(): - inform( - "Checking whether %s is installed..." % PyNNBrian1Engine.name, indent=1 - ) - return PyNNEngine.is_installed() and Brian1Engine.is_installed() - - @staticmethod - def install(version): - if not Brian1Engine.is_installed(): - Brian1Engine.install(version) # interpret version as version of Brian! - inform( - "%s installed Brian..." % PyNNBrian1Engine.name, indent=2, verbosity=1 - ) - if not PyNNEngine.is_installed(): - PyNNEngine.install(None) - inform( - "%s installed PyNN..." % PyNNBrian1Engine.name, indent=2, verbosity=1 - ) - - PyNNBrian1Engine.path = PyNNEngine.path + ":" + Brian1Engine.path - PyNNBrian1Engine.environment_vars = {} - PyNNBrian1Engine.environment_vars.update(PyNNEngine.environment_vars) - PyNNBrian1Engine.environment_vars.update(Brian1Engine.environment_vars) - inform("PATH: " + PyNNBrian1Engine.path) - - def run(self): - try: - inform( - "Running file %s with %s" % (trim_path(self.modelpath), self.name), - indent=1, - ) - self.stdout = check_output( - ["python", self.modelpath, "brian"], cwd=os.path.dirname(self.modelpath) - ) - self.returncode = 0 - except sp.CalledProcessError as err: - self.returncode = err.returncode - self.stdout = err.output - raise EngineExecutionError - except Exception as err: - inform("Another error with running %s: " % self.name, err, indent=1) - self.returncode = -1 - self.stdout = "???" diff --git a/omv/omv_util.py b/omv/omv_util.py index 8faefca..a4ff9d1 100644 --- a/omv/omv_util.py +++ b/omv/omv_util.py @@ -364,15 +364,6 @@ def _install_engine(eng): install_netpyne(engine_version) - elif eng.lower() == "Brian".lower(): - if engine_version is not None: - raise Exception( - "Currently, cannot install a specific version of engine %s" % eng - ) - from omv.engines.getbrian1 import install_brian - - install_brian() - elif eng.lower() == "Brian2".lower(): from omv.engines.brian2_ import Brian2Engine as ee diff --git a/utilities/tests/test_brian1.py b/utilities/tests/test_brian1.py deleted file mode 100644 index 8f23d30..0000000 --- a/utilities/tests/test_brian1.py +++ /dev/null @@ -1,3 +0,0 @@ -import brian - -print("Brain version: %s present" % brian.__version__) diff --git a/utilities/tests/test_pynn_simulators.py b/utilities/tests/test_pynn_simulators.py index 3f4b5dd..b1b08e8 100644 --- a/utilities/tests/test_pynn_simulators.py +++ b/utilities/tests/test_pynn_simulators.py @@ -1,6 +1,11 @@ from pyNN.utility import get_script_args +import sys -simulator = get_script_args(1)[0] +if len(sys.argv) > 1 and sys.argv[1].lower() in ["nest", "neuron", "brian2"]: + simulator = get_script_args(1)[0] +else: + simulator = "neuron" + print("Importing pyNN.%s..." % simulator) exec("import pyNN.%s as simulator" % simulator) From e8b2c4fbb66a7546dca84a001a4e3d1cf14924d6 Mon Sep 17 00:00:00 2001 From: Padraig Gleeson Date: Fri, 5 Jun 2026 09:12:21 +0100 Subject: [PATCH 3/4] Fix up readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index eb6aa24..66f21bb 100644 --- a/README.md +++ b/README.md @@ -9,14 +9,14 @@ # OSB Model Validation -Tools for automated model validation in [Open Source Brain](http://www.opensourcebrain.org) projects, which can also be used for testing model behaviour on many [simulation engines](https://github.com/OpenSourceBrain/osb-model-validation/tree/master/omv/engines) both: +Tools for automated model validation in [Open Source Brain](https://www.opensourcebrain.org) projects, which can also be used for testing model behaviour on many [simulation engines](https://github.com/OpenSourceBrain/osb-model-validation/tree/master/omv/engines) both: - on your local machine when developing models - on [GitHub Actions](https://github.com/features/actions), to ensure tests pass on every commit. To see this framework in action, click on some of the green buttons below: -| OSB project | Tests on GitHub Actions | Test workflow script | +| OSBv1 project | Tests on GitHub Actions | Test workflow script | |----------|:-------------:|:------:| |[FitzHugh Nagumo](http://www.opensourcebrain.org/projects/fitzhugh-nagumo-fitzhugh-1969) | [![Continuous build using OMV](https://github.com/OpenSourceBrain/FitzHugh-Nagumo/actions/workflows/omv-ci.yml/badge.svg)](https://github.com/OpenSourceBrain/FitzHugh-Nagumo/actions/workflows/omv-ci.yml) | [omv-ci.yml](https://github.com/OpenSourceBrain/FitzHugh-Nagumo/blob/master/.github/workflows/omv-ci.yml) | |[Auditory cortex network](http://www.opensourcebrain.org/projects/acnet2)| [![Continuous build using OMV](https://github.com/OpenSourceBrain/ACnet2/actions/workflows/omv-ci.yml/badge.svg)](https://github.com/OpenSourceBrain/ACnet2/actions/workflows/omv-ci.yml) | [omv-ci.yml](https://github.com/OpenSourceBrain/ACnet2/blob/master/.github/workflows/omv-ci.yml) | @@ -54,7 +54,7 @@ Setting up validation for a model and simulation written in NeuroML2/LEMS requir ### Write MEP files Depending on the size of your model, you can run validation on the full fledged model, or you can create smaller stripped down versions that test particular aspects of the model. -Here is an example LEMS file for the FitzHugh-Nagumo model on Open Source Brain: [LEMS_FitzHugNagamo.xml](https://github.com/OpenSourceBrain/FitzHugh-Nagumo/blob/master/NeuroML2/LEMS_FitzHughNagumo.xml). +Here is an example LEMS file for the FitzHugh-Nagumo model on Open Source Brain: [LEMS_FitzHughNagumo.xml](https://github.com/OpenSourceBrain/FitzHugh-Nagumo/blob/master/NeuroML2/LEMS_FitzHughNagumo.xml). ``` yaml # Script for running automated tests on OSBrain, see https://github.com/OpenSourceBrain/osb-model-validation From d2d017076a27e95ced006ec3fcadf5d7f640a5e7 Mon Sep 17 00:00:00 2001 From: Padraig Gleeson Date: Fri, 5 Jun 2026 09:32:07 +0100 Subject: [PATCH 4/4] Add option to not return zero with argument --exit-zero --- .github/workflows/ci_versions.yml | 4 +++ omv/omv_util.py | 18 ++++++++----- .../brokentest/.test.ex9.jnml.omt.broken | 26 +++++++++++++++++++ utilities/tests/brokentest/.test.ex9.mep | 9 +++++++ utilities/tests/test_pynn_simulators.py | 4 +-- 5 files changed, 53 insertions(+), 8 deletions(-) create mode 100644 utilities/tests/brokentest/.test.ex9.jnml.omt.broken create mode 100644 utilities/tests/brokentest/.test.ex9.mep diff --git a/.github/workflows/ci_versions.yml b/.github/workflows/ci_versions.yml index 506d9de..a91a9ad 100644 --- a/.github/workflows/ci_versions.yml +++ b/.github/workflows/ci_versions.yml @@ -56,6 +56,10 @@ jobs: omv all -V --engine=jNeuroML_NEURON omv test -V utilities/tests/.test.exIzh.jnmlnetpyne.omt + - name: Run broken test with flag --exit-zero + run: | + # This is known to fail, but should not cause the CI to fail due to the --exit-zero flag + omv test -V utilities/tests/brokentest/.test.ex9.jnml.omt.broken --exit-zero - name: OMV final version info run: | diff --git a/omv/omv_util.py b/omv/omv_util.py index a4ff9d1..576f234 100644 --- a/omv/omv_util.py +++ b/omv/omv_util.py @@ -2,9 +2,9 @@ ============================================ Usage: - omv all [-V | --verbose] [--engine=engine] [--ignore-non-py3] - omv all_ [-V | --verbose] [--engine=engine] - omv test [-V | --verbose] + omv all [-V | --verbose] [--engine=engine] [--ignore-non-py3] [--exit-zero] + omv all_ [-V | --verbose] [--engine=engine] [--ignore-non-py3] [--exit-zero] + omv test [-V | --verbose] [--exit-zero] omv autogen [options] omv install omv find @@ -20,6 +20,7 @@ -V --verbose Display additional diagnosis messages [default: False]. --version Show version. --ignore-non-py3 If Python 3, ignore tests on non Py3 compatible engines [default: False] + --exit-zero Run tests as normal and report failures, but always exit with code 0 [default: False] -y Auto-select default options (non-interactive mode) """ @@ -46,12 +47,15 @@ def main(): if arguments["--verbose"]: set_verbosity(1) + exit_zero = arguments["--exit-zero"] + if arguments["test"]: try: test_one(arguments[""]) except AssertionError: inform("Failed due to non passing tests") - exit(1) + if not exit_zero: + exit(1) elif arguments["all"]: try: @@ -74,7 +78,8 @@ def main(): ) except AssertionError: inform("Failed due to non passing tests") - exit(1) + if not exit_zero: + exit(1) # Includes *.omt_, i.e. temporary test files elif arguments["all_"]: @@ -86,7 +91,8 @@ def main(): ) except AssertionError: inform("Failed due to non passing tests") - exit(1) + if not exit_zero: + exit(1) elif arguments["find"]: try: diff --git a/utilities/tests/brokentest/.test.ex9.jnml.omt.broken b/utilities/tests/brokentest/.test.ex9.jnml.omt.broken new file mode 100644 index 0000000..47bd8e0 --- /dev/null +++ b/utilities/tests/brokentest/.test.ex9.jnml.omt.broken @@ -0,0 +1,26 @@ +# Script for running automated tests on OSB using Travis-CI, see https://github.com/OpenSourceBrain/osb-model-validation + +target: ../LEMS_NML2_Ex9_FN.xml +engine: jNeuroML +mep: .test.ex9.mep +experiments: + V: + observables: + spike times: + file: + path: ../ex9.dat + columns: [0,1] + scaling: [1000, 1000] + spike detection: + method: derivative + tolerance: 0.004099 + W: + observables: + spike times: + file: + path: ../ex9.dat + columns: [0,2] + scaling: [1000, 1] + spike detection: + method: derivative + tolerance: 0.000616 diff --git a/utilities/tests/brokentest/.test.ex9.mep b/utilities/tests/brokentest/.test.ex9.mep new file mode 100644 index 0000000..d5f8dbb --- /dev/null +++ b/utilities/tests/brokentest/.test.ex9.mep @@ -0,0 +1,9 @@ +system: Test Ex9 + +experiments: + V: + expected: + spike times: [2440.0] + W: + expected: + spike times: [16260, 52790] diff --git a/utilities/tests/test_pynn_simulators.py b/utilities/tests/test_pynn_simulators.py index b1b08e8..7409f53 100644 --- a/utilities/tests/test_pynn_simulators.py +++ b/utilities/tests/test_pynn_simulators.py @@ -4,8 +4,8 @@ if len(sys.argv) > 1 and sys.argv[1].lower() in ["nest", "neuron", "brian2"]: simulator = get_script_args(1)[0] else: - simulator = "neuron" - + simulator = "neuroml" + print("Importing pyNN.%s..." % simulator) exec("import pyNN.%s as simulator" % simulator)