From 692c77db30f85aa451cfc40dc7557128202ce340 Mon Sep 17 00:00:00 2001 From: JosePizarro3 Date: Mon, 8 Apr 2024 17:13:35 +0200 Subject: [PATCH] Added testing for PhysicalProperty --- src/nomad_simulations/physical_property.py | 28 ++-- tests/conftest.py | 18 +++ tests/test_outputs.py | 2 +- tests/test_physical_properties.py | 143 +++++++++++++++++++++ tests/test_template.py | 39 ------ 5 files changed, 178 insertions(+), 52 deletions(-) create mode 100644 tests/test_physical_properties.py delete mode 100644 tests/test_template.py diff --git a/src/nomad_simulations/physical_property.py b/src/nomad_simulations/physical_property.py index 444b967c..b40189e2 100644 --- a/src/nomad_simulations/physical_property.py +++ b/src/nomad_simulations/physical_property.py @@ -16,8 +16,9 @@ # limitations under the License. # -from typing import Any +from typing import Any, Optional +from nomad import utils from nomad.datamodel.data import ArchiveSection from nomad.metainfo import ( Quantity, @@ -35,6 +36,10 @@ from .numerical_settings import SelfConsistency +# We add `logger` for the `PhysicalProperty.variables_shape` method +logger = utils.get_logger(__name__) + + class PhysicalProperty(ArchiveSection): """ A base section used to define the physical properties obtained in a simulation, experiment, or in a post-processing @@ -133,7 +138,7 @@ class PhysicalProperty(ArchiveSection): ) @property - def get_variables_shape(self) -> list: + def variables_shape(self) -> Optional[list]: """ Shape of the variables over which the physical property varies. This is extracted from `Variables.n_grid_points` and appended in a list. @@ -144,15 +149,17 @@ def get_variables_shape(self) -> list: Returns: (list): The shape of the variables over which the physical property varies. """ - return [v.n_grid_points for v in self.variables] + if self.variables is not None: + return [v.get_n_grid_points(v.grid_points, logger) for v in self.variables] + return [] @property - def get_full_shape(self) -> list: + def full_shape(self) -> list: """ Full shape of the physical property. This quantity is calculated as: `full_shape = variables_shape + shape` where `shape` is passed as an attribute of the `PhysicalProperty` and is related with the order of - the tensor of `value`, and `variables_shape` is obtained from `get_variables_shape` and is + the tensor of `value`, and `variables_shape` is obtained from `variables_shape` and is related with the shapes of the `variables` over which the physical property varies. Example: a physical property which is a 3D vector and varies with `variables=[Temperature, ElectricField]` @@ -162,7 +169,7 @@ def get_full_shape(self) -> list: Returns: (list): The full shape of the physical property. """ - return self.get_variables_shape + self.shape + return self.variables_shape + self.shape def __init__(self, m_def: Section = None, m_context: Context = None, **kwargs): super().__init__(m_def, m_context, **kwargs) @@ -182,21 +189,18 @@ def __init__(self, m_def: Section = None, m_context: Context = None, **kwargs): def __setattr__(self, name: str, val: Any) -> None: # For the special case of `value`, its `shape` needs to be defined from `_full_shape` if name == 'value': - # * This setattr logic for `value` only works if `variables` and `shape` have been stored BEFORE the `value` is set - _full_shape = self.get_full_shape - # non-scalar or scalar `val` try: value_shape = list(val.shape) except AttributeError: value_shape = [] - if value_shape != _full_shape: + if value_shape != self.full_shape: raise ValueError( - f'The shape of the stored `value` {value_shape} does not match the full shape {_full_shape} ' + f'The shape of the stored `value` {value_shape} does not match the full shape {self.full_shape} ' f'extracted from the variables `n_grid_points` and the `shape` defined in `PhysicalProperty`.' ) - self._new_value.shape = _full_shape + self._new_value.shape = self.full_shape self._new_value = val.magnitude * val.u return super().__setattr__(name, self._new_value) return super().__setattr__(name, val) diff --git a/tests/conftest.py b/tests/conftest.py index 9738ba39..1b16c8cc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,21 @@ +# +# Copyright The NOMAD Authors. +# +# This file is part of NOMAD. See https://nomad-lab.eu for further info. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + import os import pytest diff --git a/tests/test_outputs.py b/tests/test_outputs.py index 735b8569..8c6f5005 100644 --- a/tests/test_outputs.py +++ b/tests/test_outputs.py @@ -40,7 +40,7 @@ def test_normalize(self, outputs_ref, result): Test the `normalize` and `resolve_is_derived` methods. """ # dummy test until we implement the unit testing with the new schema - assert True == True + assert True # outputs = Outputs() # assert outputs.resolve_is_derived(outputs_ref) == result # outputs.outputs_ref = outputs_ref diff --git a/tests/test_physical_properties.py b/tests/test_physical_properties.py new file mode 100644 index 00000000..33fa83cf --- /dev/null +++ b/tests/test_physical_properties.py @@ -0,0 +1,143 @@ +# +# Copyright The NOMAD Authors. +# +# This file is part of NOMAD. See https://nomad-lab.eu for further info. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import numpy as np +import pytest + +from . import logger + +from nomad.units import ureg +from nomad.metainfo import Quantity + +from nomad_simulations.variables import Variables +from nomad_simulations.physical_property import PhysicalProperty + + +class DummyPhysicalProperty(PhysicalProperty): + value = Quantity( + type=np.float64, + unit='eV', + description=""" + This value is defined in order to test the `__setattr__` method in `PhysicalProperty`. + """, + ) + + +class TestPhysicalProperty: + """ + Test the `PhysicalProperty` class defined in `physical_property.py`. + """ + + @pytest.mark.parametrize( + 'shape, variables, result_variables_shape, result_full_shape', + [ + ([], [], [], []), + ([3], [], [], [3]), + ([3, 3], [], [], [3, 3]), + ([], [Variables(n_grid_points=4)], [4], [4]), + ([3], [Variables(n_grid_points=4)], [4], [4, 3]), + ([3, 3], [Variables(n_grid_points=4)], [4], [4, 3, 3]), + ( + [], + [Variables(n_grid_points=4), Variables(n_grid_points=10)], + [4, 10], + [4, 10], + ), + ( + [3], + [Variables(n_grid_points=4), Variables(n_grid_points=10)], + [4, 10], + [4, 10, 3], + ), + ( + [3, 3], + [Variables(n_grid_points=4), Variables(n_grid_points=10)], + [4, 10], + [4, 10, 3, 3], + ), + ], + ) + def test_static_properties( + self, + shape: list, + variables: list, + result_variables_shape: list, + result_full_shape: list, + ): + """ + Test the static properties of the `PhysicalProperty` class, `variables_shape` and `full_shape`. + """ + physical_property = PhysicalProperty( + source='simulation', + shape=shape, + variables=variables, + ) + assert physical_property.variables_shape == result_variables_shape + assert physical_property.full_shape == result_full_shape + + def test_setattr_value(self): + """ + Test the `__setattr__` method when setting the `value` quantity of a physical property. + """ + physical_property = DummyPhysicalProperty( + source='simulation', + shape=[3, 3], + variables=[Variables(n_grid_points=4), Variables(n_grid_points=10)], + ) + # `physical_property.value` must have full_shape=[4, 10, 3, 3] + value = np.ones((4, 10, 3, 3)) * ureg.eV + assert physical_property.full_shape == list(value.shape) + physical_property.value = value + assert np.all(physical_property.value == value) + + def test_setattr_value_wrong_shape(self): + """ + Test the `__setattr__` method when the `value` has a wrong shape. + """ + physical_property = PhysicalProperty( + source='simulation', + shape=[], + variables=[], + ) + # `physical_property.value` must have shape=[] + value = np.ones((3, 3)) + wrong_shape = list(value.shape) + with pytest.raises(ValueError) as exc_info: + physical_property.value = value + assert ( + str(exc_info.value) + == f'The shape of the stored `value` {wrong_shape} does not match the full shape {physical_property.full_shape} extracted from the variables `n_grid_points` and the `shape` defined in `PhysicalProperty`.' + ) + + def test_is_derived(self): + """ + Test the `normalize` and `_is_derived` methods. + """ + # Testing a directly parsed physical property + not_derived_physical_property = PhysicalProperty(source='simulation') + assert not_derived_physical_property._is_derived() is False + not_derived_physical_property.normalize(None, logger) + assert not_derived_physical_property.is_derived is False + # Testing a derived physical property + derived_physical_property = PhysicalProperty( + source='analysis', + physical_property_ref=not_derived_physical_property, + ) + assert derived_physical_property._is_derived() is True + derived_physical_property.normalize(None, logger) + assert derived_physical_property.is_derived is True diff --git a/tests/test_template.py b/tests/test_template.py deleted file mode 100644 index 4d56c854..00000000 --- a/tests/test_template.py +++ /dev/null @@ -1,39 +0,0 @@ -# -# Copyright The NOMAD Authors. -# -# This file is part of NOMAD. See https://nomad-lab.eu for further info. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -import pytest - -from nomad.utils import get_logger -from nomad.datamodel import EntryArchive, EntryMetadata - - -def approx(value, abs=0, rel=1e-6): - return pytest.approx(value, abs=abs, rel=rel) - - -LOGGER = get_logger(__name__) - - -def run_parsing(parser_class, filepath): - archive = EntryArchive(metadata=EntryMetadata()) - parser_class().parse(filepath, archive, LOGGER) - return archive - - -def test_dummy(): - assert True