Skip to content

Commit

Permalink
incorporating validation for CellML 2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
jonrkarr committed May 23, 2021
1 parent 529aa4e commit ace2d20
Show file tree
Hide file tree
Showing 15 changed files with 172 additions and 7 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ pip install git+https://github.com/biosimulators/Biosimulators_utils.git#biosimu

### Installation optional features

To use BioSimulators utils to validate CellML models, install BioSimulators utils with the `cellml` option:
```
pip install biosimulators-utils[cellml]
```

To use BioSimulators utils to validate NeuroML models, install BioSimulators utils with the `neuroml` option:
```
pip install biosimulators-utils[neuroml]
Expand Down
Empty file.
61 changes: 61 additions & 0 deletions biosimulators_utils/cellml/validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
""" Utilities for validating CellML models
:Author: Jonathan Karr <[email protected]>
:Date: 2021-05-10
:Copyright: 2021, Center for Reproducible Biomedical Modeling
:License: MIT
"""

import libcellml
import os


def validate_model(filename, name=None):
""" Check that a model is valid
Args:
filename (:obj:`str`): path to model
name (:obj:`str`, optional): name of model for use in error messages
Returns:
:obj:`tuple`:
* nested :obj:`list` of :obj:`str`: nested list of errors (e.g., required ids missing or ids not unique)
* nested :obj:`list` of :obj:`str`: nested list of errors (e.g., required ids missing or ids not unique)
"""
errors = []
warnings = []

if not os.path.isfile(filename):
errors.append(['`{}` is not a file.'.format(filename)])
return (errors, warnings)

# read model
parser = libcellml.Parser()
with open(filename, 'r') as file:
model = parser.parseModel(file.read())

for i_error in range(parser.errorCount()):
error = parser.error(i_error)
errors.append([error.description()])

for i_warning in range(parser.warningCount()):
warning = parser.warning(i_warning)
warnings.append([warning.description()])

if errors:
return (errors, warnings)

# validate model
validator = libcellml.Validator()
validator.validateModel(model)

for i_error in range(validator.errorCount()):
error = validator.error(i_error)
errors.append([error.description()])

for i_warning in range(validator.warningCount()):
warning = validator.warning(i_warning)
warnings.append([warning.description()])

return (errors, warnings)
5 changes: 4 additions & 1 deletion biosimulators_utils/sedml/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -723,7 +723,10 @@ def validate_model_with_language(source, language, name=None):
errors = []
warnings = []

if language and re.match(ModelLanguagePattern.NeuroML, language):
if language and re.match(ModelLanguagePattern.CellML, language):
from ..cellml.validation import validate_model

elif language and re.match(ModelLanguagePattern.NeuroML, language):
from ..neuroml.validation import validate_model

elif language and re.match(ModelLanguagePattern.SBML, language):
Expand Down
6 changes: 6 additions & 0 deletions docs-src/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ After installing `Python <https://www.python.org/downloads/>`_ (>= 3.7) and `pip
Installing the optional features
--------------------------------

To use BioSimulators utils to validate models encoded in CellML, install BioSimulators utils with the ``cellml`` option:

.. code-block:: text
pip install biosimulators-utils[cellml]
To use BioSimulators utils to validate models encoded in NeuroML, install BioSimulators utils with the ``neuroml`` option:

.. code-block:: text
Expand Down
4 changes: 2 additions & 2 deletions requirements.optional.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# [cellml]
# libcellml
[cellml]
libcellml

[neuroml]
libneuroml
Expand Down
25 changes: 25 additions & 0 deletions tests/cellml/test_cellml_validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from biosimulators_utils.cellml.validation import validate_model
from biosimulators_utils.utils.core import flatten_nested_list_of_strings
import os
import unittest


class CellMlValidationTestCase(unittest.TestCase):
FIXTURE_DIR = os.path.join(os.path.dirname(__file__), '..', 'fixtures', 'cellml')

def test(self):
errors, warnings = validate_model(os.path.join(self.FIXTURE_DIR, 'level2.xml'))
self.assertEqual(errors, [])
self.assertEqual(warnings, [])

errors, warnings = validate_model(os.path.join(self.FIXTURE_DIR, 'not_a_path.xml'))
self.assertIn("is not a file", flatten_nested_list_of_strings(errors))
self.assertEqual(warnings, [])

errors, warnings = validate_model(os.path.join(self.FIXTURE_DIR, 'invalid_cellml_2.0.xml'))
self.assertIn("Start tag expected", flatten_nested_list_of_strings(errors))
self.assertEqual(warnings, [])

errors, warnings = validate_model(os.path.join(self.FIXTURE_DIR, 'missing-attribute.xml'))
self.assertIn("does not have a valid name attribute", flatten_nested_list_of_strings(errors))
self.assertEqual(warnings, [])
1 change: 1 addition & 0 deletions tests/fixtures/cellml/invalid_cellml_2.0.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This is not a CellML file.
26 changes: 26 additions & 0 deletions tests/fixtures/cellml/level2.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?xml version="1.0"?>
<model xmlns="http://www.cellml.org/cellml/2.0#" xmlns:cellml="http://www.cellml.org/cellml/2.0#" xmlns:xlink="http://www.w3.org/1999/xlink" name="level2_model">
<component name="level2_component">
<variable name="time" units="dimensionless" interface="public"/>
<variable name="parameter" units="dimensionless" interface="public"/>
<variable name="cosine" units="dimensionless" interface="public" initial_value="0"/>
<math xmlns="http://www.w3.org/1998/Math/MathML">
<apply><eq/>
<apply><diff/>
<bvar><ci>time</ci></bvar>
<ci>cosine</ci>
</apply>
<apply><sin/>
<apply><times/>
<ci>parameter</ci>
<ci>time</ci>
</apply>
</apply>
</apply>
</math>
</component>
<component name="isnt_imported"/>
<import xlink:href="this_file_should_not_exist..._ever!.xml">
<component name="also_never_imported" component_ref="not_sure_what_to_look_for"/>
</import>
</model>
26 changes: 26 additions & 0 deletions tests/fixtures/cellml/missing-attribute.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?xml version="1.0"?>
<model xmlns="http://www.cellml.org/cellml/2.0#" xmlns:cellml="http://www.cellml.org/cellml/2.0#" xmlns:xlink="http://www.w3.org/1999/xlink" name="level2_model">
<component>
<variable name="time" units="dimensionless" interface="public"/>
<variable name="parameter" units="dimensionless" interface="public"/>
<variable name="cosine" units="dimensionless" interface="public" initial_value="0"/>
<math xmlns="http://www.w3.org/1998/Math/MathML">
<apply><eq/>
<apply><diff/>
<bvar><ci>time</ci></bvar>
<ci>cosine</ci>
</apply>
<apply><sin/>
<apply><times/>
<ci>parameter</ci>
<ci>time</ci>
</apply>
</apply>
</apply>
</math>
</component>
<component name="isnt_imported"/>
<import xlink:href="this_file_should_not_exist..._ever!.xml">
<component name="also_never_imported" component_ref="not_sure_what_to_look_for"/>
</import>
</model>
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion tests/neuroml/test_neuroml_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@


class NeuroMlValidationTestCase(unittest.TestCase):
FIXTURE_DIR = os.path.join(os.path.dirname(__file__), '..', 'fixtures')
FIXTURE_DIR = os.path.join(os.path.dirname(__file__), '..', 'fixtures', 'neuroml')

def test(self):
errors, warnings = validate_model(os.path.join(self.FIXTURE_DIR, 'IM.channel.nml'))
Expand Down
2 changes: 1 addition & 1 deletion tests/sedml/test_sedml_exec.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def test_successful(self):
doc.models.append(data_model.Model(
id='model2',
source='https://models.edu/model1.xml',
language=data_model.ModelLanguage.CellML.value,
language=data_model.ModelLanguage.VCML.value,
))

doc.simulations.append(data_model.SteadyStateSimulation(
Expand Down
16 changes: 14 additions & 2 deletions tests/sedml/test_sedml_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -719,12 +719,24 @@ def test_validate_model_with_language(self):
self.assertEqual(errors, [])
self.assertIn('No validation is available for', flatten_nested_list_of_strings(warnings))

filename = os.path.join(os.path.dirname(__file__), '..', 'fixtures', 'IM.channel.nml')
# CellML
filename = os.path.join(os.path.dirname(__file__), '..', 'fixtures', 'cellml', 'level2.xml')
errors, warnings = validation.validate_model_with_language(filename, data_model.ModelLanguage.CellML)
self.assertEqual(errors, [])
self.assertEqual(warnings, [])

filename = os.path.join(os.path.dirname(__file__), '..', 'fixtures', 'cellml', 'missing-attribute.xml')
errors, warnings = validation.validate_model_with_language(filename, data_model.ModelLanguage.CellML)
self.assertNotEqual(errors, [])
self.assertEqual(warnings, [])

# NeuroML
filename = os.path.join(os.path.dirname(__file__), '..', 'fixtures', 'neuroml', 'IM.channel.nml')
errors, warnings = validation.validate_model_with_language(filename, data_model.ModelLanguage.NeuroML)
self.assertEqual(errors, [])
self.assertEqual(warnings, [])

filename = os.path.join(os.path.dirname(__file__), '..', 'fixtures', 'invalid-model.nml')
filename = os.path.join(os.path.dirname(__file__), '..', 'fixtures', 'neuroml', 'invalid-model.nml')
errors, warnings = validation.validate_model_with_language(filename, data_model.ModelLanguage.NeuroML)
self.assertIn("Not a valid NeuroML 2 doc", flatten_nested_list_of_strings(errors))
self.assertEqual(warnings, [])
Expand Down

0 comments on commit ace2d20

Please sign in to comment.