Skip to content

Commit

Permalink
- Set property shape constraints to normalization time, instead of du…
Browse files Browse the repository at this point in the history
…ring quantity setting

- NOTE: code will still fail when assigning `value`
  • Loading branch information
ndaelman committed Nov 4, 2024
1 parent b776565 commit 7bdf7ff
Showing 1 changed file with 33 additions and 55 deletions.
88 changes: 33 additions & 55 deletions src/nomad_simulations/schema_packages/physical_property.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
logger = utils.get_logger(__name__)


def validate_quantity_wrt_value(name: str = ''):
def validate_quantity_wrt_value(name: str):
"""
Decorator to validate the existence of a quantity and its shape with respect to the `PhysicalProperty.value`
before calling a method. An example can be found in the module `properties/band_structure.py` for the method
Expand All @@ -48,17 +48,6 @@ def wrapper(self, *args, **kwargs):
logger.warning(f'The quantity `{name}` is not defined.')
return False

# Checks if `value` exists and has the same shape as `quantity`
value = getattr(self, 'value', None)
if value is None:
logger.warning('The quantity `value` is not defined.')
return False
if value is not None and value.shape != quantity.shape:
logger.warning(
f'The shape of the quantity `{name}` does not match the shape of the `value`.'
)
return False

return func(self, *args, **kwargs)

return wrapper
Expand Down Expand Up @@ -175,7 +164,7 @@ class PhysicalProperty(ArchiveSection):
Flag indicating whether the physical property is converged or not after a SCF process. This quantity is connected
with `SelfConsistency` defined in the `numerical_settings.py` module.
""",
)
) # ? typically, a calculation is converged as a whole, at least at the level of SCF

self_consistency_ref = Quantity(
type=SelfConsistency,
Expand Down Expand Up @@ -204,14 +193,8 @@ def variables_shape(self) -> Optional[list]:
@property
def full_shape(self) -> list: # TODO: add support for N-dim variables
"""
Full shape of the physical property. This quantity is calculated as a concatenation of the `variables_shape`
and `rank`:
`full_shape = variables_shape + rank`
where `rank` 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 the property-decorated function `variables_shape()`
and is related with the shapes of the `variables` over which the physical property varies.
Full shape of the physical property, defined as the concatenation of the `variables_shape`
and `rank`.
Example: a physical property which is a 3D vector and varies with `variables=[Temperature, ElectricField]`
will have `rank=[3]`, `variables_shape=[n_temperatures, n_electric_fields]`, and thus
Expand All @@ -233,11 +216,10 @@ def _new_value(self) -> Quantity:
(Quantity): The new `Quantity` object for setting the `value` quantity.
"""
value_quantity = self.m_def.all_quantities.get('value')
if value_quantity is None:
return None
return Quantity(
type=value_quantity.type,
unit=value_quantity.unit, # ? this can be moved to __setattr__
shape=self.full_shape,
unit=value_quantity.unit,
description=value_quantity.description,
)

Expand All @@ -252,37 +234,24 @@ def __init__(
'The used property is not defined in the FAIRmat taxonomy (https://fairmat-nfdi.github.io/fairmat-taxonomy/). You can contribute there if you want to extend the list of available materials properties.'
)

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':
if val is None:
raise ValueError(
f'The value of the physical property {self.name} is None. Please provide a finite valid value.'
)
_new_value = self._new_value

# patch for when `val` does not have units and it is passed as a list (instead of np.array)
if isinstance(val, list):
val = np.array(val)

# non-scalar or scalar `val`
try:
value_shape = list(val.shape)
except AttributeError:
value_shape = []

if value_shape != self.full_shape:
raise ValueError(
f'The shape of the stored `value` {value_shape} does not match the full shape {self.full_shape} '
f'extracted from the variables `n_points` and the `shape` defined in `PhysicalProperty`.'
)
_new_value.shape = self.full_shape
if hasattr(val, 'magnitude'):
_new_value = val.magnitude * val.u
else:
_new_value = val
return super().__setattr__(name, _new_value)
return super().__setattr__(name, val)
@property
def _check_value_shape(self) -> int:
"""Provide a code of how the `value` conforms tol `full_shape`."""

if self.value is None:
return 0

if hasattr(self.value, 'shape'):
normalized_shape = list(self.value.shape)
if normalized_shape == [0]:
normalized_shape = []
else:
normalized_shape = []

if normalized_shape != self.full_shape:
return -1

return 1

def _is_derived(self) -> bool:
"""
Expand All @@ -301,6 +270,15 @@ def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None:
# Resolve if the physical property `is_derived` or not from another physical property.
self.is_derived = self._is_derived()

if (check_value_shape := self._check_value_shape()) == 0:
raise ValueError('`PhysicalProperty.value` is unset.')
elif check_value_shape == -1:
raise ValueError(
f'The shape of the stored `value` does not match the full shape {self.full_shape} '
f'extracted from the variables `n_points` and the `shape` defined in `PhysicalProperty`.'
)

self.m_def.value = self._new_value

class PropertyContribution(PhysicalProperty):
"""
Expand Down

1 comment on commit 7bdf7ff

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage

Coverage Report
FileStmtsMissCoverMissing
src/nomad_simulations
   __init__.py4250%3–4
   _version.py11282%5–6
src/nomad_simulations/schema_packages
   __init__.py15287%39–41
   atoms_state.py1902189%13–15, 201–204, 228, 283–284, 352–353, 355, 537, 549–550, 611–615, 630–634, 641
   basis_set.py2402888%8–9, 122–133, 172–185, 208, 391–395, 417–418, 462–465, 584, 615, 617
   general.py89891%4–7, 121, 185, 295–296, 306
   model_method.py2697871%10–12, 171–174, 177–184, 276–277, 297, 318–339, 355–381, 384–401, 587, 780, 791, 833–840, 878, 897, 977, 1034, 1109, 1223
   model_system.py3172592%25–27, 378, 410–411, 621–624, 671–678, 852–853, 1074–1078, 1084–1085, 1093–1094, 1099, 1122
   numerical_settings.py2596176%12–14, 217, 219–220, 223–226, 230–231, 238–241, 250–253, 257–260, 262–265, 270–273, 279–282, 469–496, 571, 606–609, 633, 636, 681, 683–686, 690, 694, 741, 745–766, 821–822, 889
   outputs.py1201092%9–10, 252–255, 295–298, 323, 325, 362, 381
   physical_property.py881781%20–22, 191, 218–219, 247–249, 252, 264, 274–281, 302–304
   variables.py861286%8–10, 98, 121, 145, 167, 189, 211, 233, 256, 276
src/nomad_simulations/schema_packages/properties
   band_gap.py511080%8–10, 132–144
   band_structure.py1256846%9–11, 64, 153–165, 181–205, 217–224, 238–271, 280–292, 295–314, 327–328, 331, 378–379, 384
   energies.py42979%7–9, 36, 57, 82, 103, 119, 134
   fermi_surface.py17476%7–9, 40
   forces.py22673%7–9, 36, 56, 79
   greens_function.py993664%7–9, 184–189, 210–211, 214, 235–236, 239, 260–261, 264, 365–367, 376–384, 387–403
   hopping_matrix.py29583%7–9, 58, 94
   permittivity.py481862%7–9, 73–76, 85–94, 97–105
   spectral_profile.py26015640%9–11, 56–60, 95–98, 103, 112, 199–300, 356–368, 388–400, 416, 421–424, 428–463, 466–502, 526, 573–576, 592–593, 598–604
   thermodynamics.py752764%7–9, 35, 56, 72, 81, 90, 101, 110, 137, 147, 157, 172–174, 177, 193, 213–215, 218, 234, 254–256, 259
src/nomad_simulations/schema_packages/utils
   utils.py701480%8–11, 65–74, 83–84, 89, 92
TOTAL253761976% 

Tests Skipped Failures Errors Time
409 0 💤 71 ❌ 0 🔥 17.258s ⏱️

Please sign in to comment.