Skip to content

Commit

Permalink
Updated emmocheck to ahead to latest formulation of units (#809)
Browse files Browse the repository at this point in the history
# Description
Updated emmocheck to ahead to latest formulation of units

**Note**: this PR is already in use when testing the development version
of EMMO. When merging this PR, please update the pip install EMMOntoPy
statement in
https://github.com/emmo-repo/EMMO/blob/master/.github/workflows/ci_emmocheck.yml

---------

Co-authored-by: Francesca L. Bleken <[email protected]>
  • Loading branch information
jesper-friis and francescalb authored Feb 4, 2025
1 parent a64ffe3 commit 9a69c93
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 7 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci_workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]

steps:
- name: Checkout repository
Expand Down
29 changes: 27 additions & 2 deletions docs/tools-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,40 @@ optional arguments:
```
<!-- (Missing example with local and path) -->

### Example configuration file

Example of YAML configuration file provided with the `--configfile` option that will omit `myunits.MyUnitCategory1` and `myunits.MyUnitCategory1` from the *unit dimensions test*.
### Configuration file
The `--configfile` options expects a YAML configuration file that specifies what tests to skip or enable.

The following keywords are recognised in the YAML file:

- `skip`: List of tests to skip
- `enable`: List of tests to enable
- `<test_name>`: A name of a test. Recognised nested keywords are:
- `exceptions`: List of entities in the ontology to skip. Should be written
as `<ns0>.<name>`, where `<ns0>` is the last component of the base IRI
and `<name>` is the name of the entity.
- `skipmodules`: List of module names to skip the test for. The module
names may be written either as the full module IRI or as the last
component of the module IRI.

Example configuration file:

```console
test_description:
skipmodules:
- manufacturing
- conformityassessment

test_unit_dimensions:
exceptions:
- myunits.MyUnitCategory1
- myunits.MyUnitCategory2

skip:
- name_of_test_to_skip

enable:
- name_of_test_to_enable
```

---
Expand Down
113 changes: 112 additions & 1 deletion emmopy/emmocheck.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,29 @@
# -*- coding: utf-8 -*-
# pylint: disable=too-many-lines,invalid-name
"""
A module for testing an ontology against conventions defined for EMMO.
A YAML file can be provided with additional test configurations.
Toplevel keywords in the YAML file:
- `skip`: List of tests to skip
- `enable`: List of tests to enable
- `<test_name>`: A name of a test. Recognised nested keywords are:
- `exceptions`: List of entities in the ontology to skip. Should be written
as `<ns0>.<name>`, where `<ns0>` is the last component of the base IRI
and `<name>` is the name of the entity.
- `skipmodules`: List of module names to skip the test for. The module
names may be written either as the full module IRI or as the last
component of the module IRI.
Example configuration file:
test_description:
skipmodules:
- manufacturing
- conformityassessment
test_unit_dimensions:
exceptions:
- myunits.MyUnitCategory1
Expand Down Expand Up @@ -194,6 +212,12 @@ def test_description(self):
Exceptions include entities from standard w3c vocabularies.
"""
# pylint: disable=invalid-name
MeasurementUnit = (
self.onto.MeasurementUnit
if "MeasurementUnit" in self.onto
else None
)
exceptions = set()
exceptions.update(self.get_config("test_description.exceptions", ()))
props = self.onto.world._props # pylint: disable=protected-access
Expand All @@ -214,6 +238,23 @@ def test_description(self):
if r in exceptions or any(r.startswith(v) for v in vocabs):
continue

# Skip units subclasses with a physical dimension
if (
MeasurementUnit
and issubclass(entity, MeasurementUnit)
and any(
str(r.property.prefLabel.first()) == "hasDimensionString"
for r in entity.get_indirect_is_a()
if hasattr(r, "property")
and hasattr(r.property, "prefLabel")
)
):
continue

# Check skipmodules
if skipmodule(self, "test_description", entity):
continue

label = str(get_label(entity))
with self.subTest(entity=entity, label=label):
self.assertTrue(
Expand Down Expand Up @@ -314,6 +355,10 @@ def test_unit_dimension(self):
"emmo.SIBaseUnit",
"emmo.SIUnitSymbol",
"emmo.SIUnit",
"emmo.SIAcceptedDerivedUnit",
"emmo.SIDerivedUnit",
"emmo.SIAcceptedPrefixedUnit",
"emmo.CGSUnit",
)
)
if not hasattr(self.onto, "MeasurementUnit"):
Expand Down Expand Up @@ -378,6 +423,11 @@ def test_unit_dimension_rc1(self):
"emmo.SIBaseUnit",
"emmo.SIUnitSymbol",
"emmo.SIUnit",
"emmo.SIDerivedUnit",
"emmo.SIAcceptedPrefixedUnit",
"emmo.SIAcceptedDerivedUnit",
"emmo.SIMetricPrefixedUnit",
"emmo.CGSUnit",
)
)
if not hasattr(self.onto, "MeasurementUnit"):
Expand Down Expand Up @@ -496,6 +546,7 @@ def test_quantity_dimension(self):
"metrology.ExactConstant",
"metrology.MeasuredConstant",
"metrology.DerivedQuantity",
"metrology.PhysicalQuantiyByDefinition",
"isq.ISQBaseQuantity",
"isq.InternationalSystemOfQuantity",
"isq.ISQDerivedQuantity",
Expand Down Expand Up @@ -531,6 +582,7 @@ def test_quantity_dimension(self):
"emmo.Intensive",
"emmo.Extensive",
"emmo.Concentration",
"emmo.PhysicalQuantiyByDefinition",
)
)
if not hasattr(self.onto, "PhysicalQuantity"):
Expand Down Expand Up @@ -565,7 +617,7 @@ def test_quantity_dimension(self):
issubclass(cls, self.onto.ISQDimensionlessQuantity)
)

def test_dimensional_unit(self):
def test_dimensional_unit_rc2(self):
"""Check correct syntax of dimension string of dimensional units."""

# This test requires that the ontology has imported SIDimensionalUnit
Expand All @@ -585,6 +637,38 @@ def test_dimensional_unit(self):
self.assertIsInstance(r, owlready2.Restriction)
self.assertRegex(r.value, regex)

def test_dimensional_unit(self):
"""Check correct syntax of dimension string of dimensional units."""

# This test requires that the ontology has imported SIDimensionalUnit
if "SIDimensionalUnit" not in self.onto:
self.skipTest("SIDimensionalUnit is not imported")

# pylint: disable=invalid-name
regex = re.compile(
"^T([+-][1-9][0-9]*|0) L([+-][1-9]|0) M([+-][1-9]|0) "
"I([+-][1-9]|0) (H|Θ)([+-][1-9]|0) N([+-][1-9]|0) "
"J([+-][1-9]|0)$"
)
for cls in self.onto.SIDimensionalUnit.__subclasses__():
with self.subTest(cls=cls, label=get_label(cls)):
dimstr = [
r.value
for r in cls.is_a
if isinstance(r, owlready2.Restriction)
and repr(r.property) == "emmo.hasDimensionString"
]
self.assertEqual(
len(dimstr),
1,
msg="expect one emmo:hasDimensionString value restriction",
)
self.assertRegex(
dimstr[0],
regex,
msg=f"invalid dimension string: '{dimstr[0]}'",
)

def test_physical_quantity_dimension(self):
"""Check that all physical quantities have `hasPhysicalDimension`.
Expand Down Expand Up @@ -734,6 +818,32 @@ def checker(onto, ignore_namespace):
checker(self.onto, self.ignore_namespace)


def skipmodule(testobj, testname, entity):
"""Return true if `entity` is in a module that should be skipped."""
skipmodules = testobj.get_config(f"{testname}.skipmodules")

if not skipmodules:
return False

# Infer base iri
if entity.namespace.ontology.base_iri != "https://w3id.org/emmo#":
base_iri = entity.namespace.ontology.base_iri.rstrip("/#")
elif hasattr(entity, "isDefinedBy") and entity.isDefinedBy:
base_iri = entity.isDefinedBy.first().rstrip("/#")
else:
base_iri = entity.namespace.ontology.base_iri.rstrip("/#")

for module in skipmodules:
module = module.rstrip("/#")
if "/" in module:
if module == base_iri:
return True
elif module == base_iri.rsplit("/", 1)[-1]:
return True

return False


def main(
argv: list = None,
): # pylint: disable=too-many-locals,too-many-branches,too-many-statements
Expand Down Expand Up @@ -938,6 +1048,7 @@ def main(
"test_physical_quantity_dimension_annotation",
"test_quantity_dimension_beta3",
"test_physical_quantity_dimension",
"test_dimensional_unit_rc2",
]
)
msg = {name: "skipped by default" for name in skipped}
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,11 @@ def fglob(patt):
"License :: OSI Approved :: BSD License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Scientific/Engineering",
"Topic :: Scientific/Engineering :: Information Analysis",
"Topic :: Scientific/Engineering :: Visualization",
Expand Down
4 changes: 2 additions & 2 deletions tests/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class H2O(emmo.Molecule):
emmo.hasSpatialDirectPart.exactly(2, onto.Hydrogen)
emmo.hasSpatialDirectPart.exactly(1, Oxygen)

# Create some
# Create some individuals
H1 = onto.Hydrogen()
H2 = onto.Hydrogen()
O = Oxygen()
Expand All @@ -59,7 +59,7 @@ class H2O(emmo.Molecule):
name_prefix = "myonto_"
onto.sync_attributes(name_policy="sequential", name_prefix=name_prefix)
assert f"{onto.base_iri}{name_prefix}0" in onto
assert f"{onto.base_iri}{name_prefix}6" in onto
assert f"{onto.base_iri}{name_prefix}3" in onto

name_prefix = "onto_"
onto.sync_attributes(name_policy="uuid", name_prefix=name_prefix)
Expand Down

0 comments on commit 9a69c93

Please sign in to comment.