Skip to content

Commit

Permalink
- Add PhysicalPropertyDecomposition to standardize contributions
Browse files Browse the repository at this point in the history
- Rework energies, forces, thermodynamics
  • Loading branch information
ndaelman committed Jan 14, 2025
1 parent 430bfb8 commit 17cf3bb
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 478 deletions.
39 changes: 39 additions & 0 deletions src/nomad_simulations/schema_packages/physical_property.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
SubSection,
)
from nomad.metainfo.metainfo import Dimension, DirectQuantity, _placeholder_quantity
from nomad.metainfo.datasets import (
ValuesTemplate,
DatasetTemplate,
)

if TYPE_CHECKING:
from nomad.datamodel.datamodel import EntryArchive
Expand Down Expand Up @@ -331,3 +335,38 @@ def normalize(self, archive, logger) -> None:
super().normalize(archive, logger)
if not self.name:
self.name = self.get('model_method_ref').get('name')


class PhysicalPropertyDecomposition:
"""
Generator class to convert a `values_template: ValuesTemplate` to a `DatasetTemplate`
with `mandatory_fields = [values_template, list[value_template], kind, reference]`.
"""

def __init__(
self,
value_template: 'ValuesTemplate',
kind: Optional['ValuesTemplate'] = None,
reference_type: Optional['ValuesTemplate'] = None,
) -> None:
self.value_template = value_template
self.kind = kind
self.reference_type = reference_type

def _generate_repeating_value(self) -> 'ValuesTemplate':
"""
Generate a repeating `ValuesTemplate` for the `value` quantity.
"""
repeating_value = self.value.m_copy()
repeating_value.m_def.shape = repeating_value.shape + ['*']
return repeating_value

def __call__(self, *args, **kwargs) -> 'DatasetTemplate':
return DatasetTemplate(
mandatory_fields=[
self.value_template,
self._generate_repeating_value(),
self.kind,
self.reference_type,
],
)
49 changes: 18 additions & 31 deletions src/nomad_simulations/schema_packages/properties/energies.py
Original file line number Diff line number Diff line change
@@ -1,45 +1,32 @@
from typing import TYPE_CHECKING

import numpy as np
from nomad.metainfo import MEnum, Quantity, Reference
from nomad.metainfo.dataset import MDataset, Dataset
from nomad.datamodel.metainfo.model import ModelMethod

from ..physical_property import PhysicalPropertyDecomposition
from ..variables import Energy, EnergyType, MethodReference

if TYPE_CHECKING:
from nomad.datamodel.datamodel import EntryArchive
from structlog.stdlib import BoundLogger

EnergyTemplateGenerator = PhysicalPropertyDecomposition(
Energy,
reference_type=MethodReference,
)

class Energy(MDataset):
m_def = Dataset(
type=np.float64,
unit='joule',
description="""A base section used to define basic quantities for the `TotalEnergy` property.""",
default_variables=['Energy'], # ? does this require variables of this shape
)

# ? origin_reference

kind = Quantity(
type=MEnum('kinetic', 'potential', 'total'),
)
class ModelEnergySection('ArchiveSection'):
energy = EnergyTemplateGenerator()() # ? suggest energy_origin or kind

method_reference = Quantity(
type=Reference(ModelMethod),
description="""
Reference to a `ModelMethod` definition, according to which the energy was calculated.
""",
)
type = EnergyType()

def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None:
super().normalize(archive, logger)

energy_sums = np.sum(
[var.data for var in self.variables if isinstance(var, Energy)], axis=0
)
if self.data is None or self.data == []:
self.data = energy_sums
elif not np.allclose(self.data, energy_sums):
logger.warning(
f'The sum of the energies in the variables is different from the total energy: {energy_sums} != {self.data}'
)
# check that the contributions do not outgrow the total energy
for field in self.energy.fields:
if (total_energy := field[0]) < (energy_contributions := np.sum(field[1])):
logger.warning(
f'The contributions outweigh the total energy',
energy_contributions,
total_energy,
)
82 changes: 12 additions & 70 deletions src/nomad_simulations/schema_packages/properties/forces.py
Original file line number Diff line number Diff line change
@@ -1,79 +1,21 @@
from typing import TYPE_CHECKING

import numpy as np
from nomad.metainfo import Context, Quantity, Section, SubSection
from ..physical_property import PhysicalPropertyDecomposition
from ..variables import Force, MethodReference
from nomad.metainfo.datasets import DatasetTemplate

if TYPE_CHECKING:
from nomad.datamodel.datamodel import EntryArchive
from nomad.metainfo import Context, Section
from structlog.stdlib import BoundLogger

from nomad_simulations.schema_packages.physical_property import (
PhysicalProperty,
PropertyContribution,
ForceTemplateGenerator = PhysicalPropertyDecomposition(
Force,
reference_type=MethodReference,
)

##################
# Abstract classes
##################


class BaseForce(PhysicalProperty):
"""
Abstract class used to define a common `value` quantity with the appropriate units
for different types of forces, which avoids repeating the definitions for each
force class.
"""

value = Quantity(
type=np.dtype(np.float64),
unit='newton',
description="""
""",
)

def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None:
super().normalize(archive, logger)


class ForceContribution(BaseForce, PropertyContribution):
"""
Abstract class for incorporating specific force contributions to the `TotalForce`.
The inheritance from `PropertyContribution` allows to link this contribution to a
specific component (of class `BaseModelMethod`) of the over `ModelMethod` using the
`model_method_ref` quantity.
For example, for a force field calculation, the `model_method_ref` may point to a
particular potential type (e.g., a Lennard-Jones potential between atom types X and Y),
while for a DFT calculation, it may point to a particular electronic interaction term
(e.g., 'XC' for the exchange-correlation term, or 'Hartree' for the Hartree term).
Then, the contribution will be named according to this model component and the `value`
quantity will contain the force contribution from this component evaluated over all
relevant atoms or electrons or as a function of them.
"""

def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None:
super().normalize(archive, logger)


###################################
# List of specific force properties
###################################


class TotalForce(BaseForce):
"""
The total force on a system. `contributions` specify individual force
contributions to the `TotalForce`.
"""
class ModelForceSection('ArchiveSection'):
force = ForceTemplateGenerator()()

contributions = SubSection(sub_section=ForceContribution.m_def, repeats=True)

def __init__(
self, m_def: 'Section' = None, m_context: 'Context' = None, **kwargs
) -> None:
super().__init__(m_def, m_context, **kwargs)
self.name = self.m_def.name
# OR

def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None:
super().normalize(archive, logger)
ModelForceSection = DatasetTemplate(
mandatory_fields=[Force],
)()
Loading

0 comments on commit 17cf3bb

Please sign in to comment.