Skip to content

Commit

Permalink
Added testing for PhysicalProperty
Browse files Browse the repository at this point in the history
  • Loading branch information
JosePizarro3 committed Apr 8, 2024
1 parent f694cba commit 692c77d
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 52 deletions.
28 changes: 16 additions & 12 deletions src/nomad_simulations/physical_property.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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]`
Expand All @@ -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)
Expand All @@ -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)
Expand Down
18 changes: 18 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down
2 changes: 1 addition & 1 deletion tests/test_outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
143 changes: 143 additions & 0 deletions tests/test_physical_properties.py
Original file line number Diff line number Diff line change
@@ -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
39 changes: 0 additions & 39 deletions tests/test_template.py

This file was deleted.

0 comments on commit 692c77d

Please sign in to comment.