Skip to content

Commit

Permalink
create base structure for energies (#91)
Browse files Browse the repository at this point in the history
* transfered changes from 28 to new structure

* ruff

* reorg energy and force structure

* updated outputs list

* added default tests for energies forces and thermodynamics

* removed mechanics

* expanded thermodynamics

* ruff

* updated to BaseModelMethod for contribution ref

* standardized normalization function syntax

* more normalization functions

* fixed error

* improved descriptions, addressed most other review items

* ruff

* fixed import, remove value descriptions

---------

Co-authored-by: jrudz <[email protected]>
  • Loading branch information
JFRudzinski and jrudz authored Jul 8, 2024
1 parent 50fc8bd commit 0e5707f
Show file tree
Hide file tree
Showing 9 changed files with 934 additions and 36 deletions.
66 changes: 65 additions & 1 deletion src/nomad_simulations/schema_packages/outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
# limitations under the License.
#

import numpy as np
from typing import TYPE_CHECKING, List, Optional

from nomad.datamodel.data import ArchiveSection
Expand Down Expand Up @@ -43,6 +44,11 @@
HoppingMatrix,
Permittivity,
XASSpectrum,
TotalEnergy,
KineticEnergy,
PotentialEnergy,
TotalForce,
Temperature,
)


Expand Down Expand Up @@ -70,7 +76,8 @@ class Outputs(ArchiveSection):
model_method_ref = Quantity(
type=ModelMethod,
description="""
Reference to the `ModelMethod` section in which the output physical properties were calculated.
Reference to the `ModelMethod` section containing the details of the mathematical
model with which the output physical properties were calculated.
""",
a_eln=ELNAnnotation(component='ReferenceEditQuantity'),
)
Expand Down Expand Up @@ -111,6 +118,16 @@ class Outputs(ArchiveSection):

xas_spectra = SubSection(sub_section=XASSpectrum.m_def, repeats=True)

total_energy = SubSection(sub_section=TotalEnergy.m_def, repeats=True)

kinetic_energy = SubSection(sub_section=KineticEnergy.m_def, repeats=True)

potential_energy = SubSection(sub_section=PotentialEnergy.m_def, repeats=True)

total_force = SubSection(sub_section=TotalForce.m_def, repeats=True)

temperature = SubSection(sub_section=Temperature.m_def, repeats=True)

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

Expand Down Expand Up @@ -310,3 +327,50 @@ def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None:
physical_property=phys_property,
logger=logger,
)


class WorkflowOutputs(Outputs):
"""
This section contains output properties that depend on a single system, but were
calculated as part of a workflow (e.g., the energies from a geometry optimization),
and thus may include step information.
"""

step = Quantity(
type=np.int32,
description="""
The step number with respect to the workflow.
""",
)

# TODO add this in when we link to nomad-simulations-workflow schema
# ? check potential circular imports problems when the nomad-simulations-workflow schema is transferred here
# workflow_ref = Quantity(
# type=SimulationWorkflow,
# description="""
# Reference to the `SelfConsistency` section that defines the numerical settings to converge the
# output property.
# """,
# )

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


class TrajectoryOutputs(WorkflowOutputs):
"""
This section contains output properties that depend on a single system, but were
calculated as part of a trajectory (e.g., temperatures from a molecular dynamics
simulation), and thus may include time information.
"""

time = Quantity(
type=np.float64,
unit='ps',
description="""
The elapsed simulated physical time since the start of the trajectory.
""",
)

def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None:
super().normalize(archive, logger)
26 changes: 26 additions & 0 deletions src/nomad_simulations/schema_packages/physical_property.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from nomad import utils
from nomad.datamodel.data import ArchiveSection
from nomad.datamodel.metainfo.basesections import Entity
from nomad.datamodel.metainfo.annotations import ELNAnnotation
from nomad.metainfo import (
URL,
MEnum,
Expand All @@ -41,6 +42,7 @@

from nomad_simulations.schema_packages.numerical_settings import SelfConsistency
from nomad_simulations.schema_packages.variables import Variables
from nomad_simulations.schema_packages.model_method import BaseModelMethod

# We add `logger` for the `validate_quantity_wrt_value` decorator
logger = utils.get_logger(__name__)
Expand Down Expand Up @@ -324,3 +326,27 @@ 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()


class PropertyContribution(PhysicalProperty):
"""
Abstract physical property section linking a property contribution to a contribution
from some method.
Abstract class for incorporating specific contributions of a physical property, while
linking this contribution to a specific component (of class `BaseModelMethod`) of the
over `ModelMethod` using the `model_method_ref` quantity.
"""

model_method_ref = Quantity(
type=BaseModelMethod,
description="""
Reference to the `ModelMethod` section to which the property is linked to.
""",
a_eln=ELNAnnotation(component='ReferenceEditQuantity'),
)

def normalize(self, archive, logger) -> None:
super().normalize(archive, logger)
if not self.name:
self.name = self.get('model_method_ref').get('name')
26 changes: 25 additions & 1 deletion src/nomad_simulations/schema_packages/properties/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,31 @@

from .band_gap import ElectronicBandGap
from .band_structure import ElectronicBandStructure, ElectronicEigenvalues
from .energies import ChemicalPotential, FermiLevel
from .energies import (
FermiLevel,
EnergyContribution,
TotalEnergy,
KineticEnergy,
PotentialEnergy,
)
from .forces import BaseForce, ForceContribution, TotalForce
from .thermodynamics import (
Pressure,
Volume,
Temperature,
Heat,
Work,
InternalEnergy,
Enthalpy,
Entropy,
GibbsFreeEnergy,
HelmholtzFreeEnergy,
ChemicalPotential,
HeatCapacity,
VirialTensor,
MassDensity,
Hessian,
)
from .fermi_surface import FermiSurface
from .hopping_matrix import CrystalFieldSplitting, HoppingMatrix
from .permittivity import Permittivity
Expand Down
101 changes: 78 additions & 23 deletions src/nomad_simulations/schema_packages/properties/energies.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,33 +20,76 @@

import numpy as np

from nomad.metainfo import Quantity
from nomad.metainfo import Quantity, Section, Context, SubSection

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

from nomad_simulations.schema_packages.physical_property import PhysicalProperty
from nomad_simulations.schema_packages.physical_property import (
PhysicalProperty,
PropertyContribution,
)

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

class FermiLevel(PhysicalProperty):

class BaseEnergy(PhysicalProperty):
"""
Energy required to add or extract a charge from a material at zero temperature. It can be also defined as the chemical potential at zero temperature.
Abstract class used to define a common `value` quantity with the appropriate units
for different types of energies, which avoids repeating the definitions for each
energy class.
"""

# ! implement `iri` and `rank` as part of `m_def = Section()`

iri = 'http://fairmat-nfdi.eu/taxonomy/FermiLevel'

value = Quantity(
type=np.float64,
unit='joule',
description="""
The value of the Fermi level.
""",
)

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


class EnergyContribution(BaseEnergy, PropertyContribution):
"""
Abstract class for incorporating specific energy contributions to the `TotalEnergy`.
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 energy contribution from this component evaluated over all
relevant atoms or electrons or as a function of them.
"""

# TODO address the dual parent normalization explicity
def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None:
super().normalize(archive, logger)


####################################
# List of specific energy properties
####################################


class FermiLevel(BaseEnergy):
"""
Energy required to add or extract a charge from a material at zero temperature. It can be also defined as the chemical potential at zero temperature.
"""

# ! implement `iri` and `rank` as part of `m_def = Section()`

iri = 'http://fairmat-nfdi.eu/taxonomy/FermiLevel'

def __init__(
self, m_def: 'Section' = None, m_context: 'Context' = None, **kwargs
) -> None:
Expand All @@ -58,29 +101,41 @@ def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None:
super().normalize(archive, logger)


class ChemicalPotential(PhysicalProperty):
#! The only issue with this structure is that total energy will never be a sum of its contributions,
#! since kinetic energy lives separately, but I think maybe this is ok?
class TotalEnergy(BaseEnergy):
"""
Free energy cost of adding or extracting a particle from a thermodynamic system.
The total energy of a system. `contributions` specify individual energetic
contributions to the `TotalEnergy`.
"""

# ! implement `iri` and `rank` as part of `m_def = Section()`

iri = 'http://fairmat-nfdi.eu/taxonomy/ChemicalPotential'

value = Quantity(
type=np.float64,
unit='joule',
description="""
The value of the chemical potential.
""",
)
# ? add a generic contributions quantity to PhysicalProperty
contributions = SubSection(sub_section=EnergyContribution.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.rank = []
self.name = self.m_def.name

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


# ? Separate quantities for nuclear and electronic KEs?
class KineticEnergy(BaseEnergy):
"""
Physical property section describing the kinetic energy of a (sub)system.
"""

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


class PotentialEnergy(BaseEnergy):
"""
Physical property section describing the potential energy of a (sub)system.
"""

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

2 comments on commit 0e5707f

@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__.py14286%53–55
   atoms_state.py1902189%32–34, 220–223, 247, 302–303, 371–372, 374, 556, 568–569, 630–634, 649–653, 660
   general.py75791%30–31, 99, 163, 273–274, 284
   model_method.py2657771%29–31, 190–193, 196–203, 295–296, 316, 337–356, 372–398, 401–418, 772, 783, 825–832, 870, 889, 969, 1026, 1101, 1215
   model_system.py2612292%45–47, 514–517, 565–572, 746–747, 969–973, 979–980, 988–989, 994, 1017
   numerical_settings.py2636575%32–34, 166, 236, 238–239, 242–245, 249–250, 257–260, 269–272, 276–279, 281–284, 289–292, 298–301, 472–499, 574, 609–612, 636, 639, 684, 686–689, 693, 697, 744, 748–769, 824–825, 892, 901–903, 906
   outputs.py1151091%27–28, 247–250, 290–293, 318, 320, 357, 376
   physical_property.py102793%39–41, 221, 350–352
   variables.py641183%27–29, 115, 138, 158–159, 162, 184, 207, 227
src/nomad_simulations/schema_packages/properties
   band_gap.py51590%27–29, 154–155
   band_structure.py1112280%28–30, 250–283, 296, 303, 339–340, 343
   energies.py36975%26–28, 55, 76, 101, 122, 132, 141
   fermi_surface.py17476%26–28, 59
   forces.py22673%27–29, 56, 76, 99
   hopping_matrix.py29583%26–28, 76, 111
   permittivity.py48883%26–28, 116–124
   spectral_profile.py25612452%28–30, 76–79, 114–117, 218–319, 375–387, 412–415, 435, 440–443, 485–521, 545, 592–595, 611–612, 617–623
   thermodynamics.py751876%26–28, 54, 75, 91, 100, 109, 120, 129, 156, 166, 176, 196, 212, 237, 253, 278
src/nomad_simulations/schema_packages/utils
   utils.py681479%26–28, 78–87, 96–97, 102, 105
TOTAL208744179% 

Tests Skipped Failures Errors Time
321 0 💤 19 ❌ 0 🔥 2.668s ⏱️

@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__.py14286%53–55
   atoms_state.py1902189%32–34, 220–223, 247, 302–303, 371–372, 374, 556, 568–569, 630–634, 649–653, 660
   general.py75791%30–31, 99, 163, 273–274, 284
   model_method.py2657771%29–31, 190–193, 196–203, 295–296, 316, 337–356, 372–398, 401–418, 772, 783, 825–832, 870, 889, 969, 1026, 1101, 1215
   model_system.py2612292%45–47, 514–517, 565–572, 746–747, 969–973, 979–980, 988–989, 994, 1017
   numerical_settings.py2636575%32–34, 166, 236, 238–239, 242–245, 249–250, 257–260, 269–272, 276–279, 281–284, 289–292, 298–301, 472–499, 574, 609–612, 636, 639, 684, 686–689, 693, 697, 744, 748–769, 824–825, 892, 901–903, 906
   outputs.py1151091%27–28, 247–250, 290–293, 318, 320, 357, 376
   physical_property.py102793%39–41, 221, 350–352
   variables.py641183%27–29, 115, 138, 158–159, 162, 184, 207, 227
src/nomad_simulations/schema_packages/properties
   band_gap.py51590%27–29, 154–155
   band_structure.py1112280%28–30, 250–283, 296, 303, 339–340, 343
   energies.py36975%26–28, 55, 76, 101, 122, 132, 141
   fermi_surface.py17476%26–28, 59
   forces.py22673%27–29, 56, 76, 99
   hopping_matrix.py29583%26–28, 76, 111
   permittivity.py48883%26–28, 116–124
   spectral_profile.py25612452%28–30, 76–79, 114–117, 218–319, 375–387, 412–415, 435, 440–443, 485–521, 545, 592–595, 611–612, 617–623
   thermodynamics.py751876%26–28, 54, 75, 91, 100, 109, 120, 129, 156, 166, 176, 196, 212, 237, 253, 278
src/nomad_simulations/schema_packages/utils
   utils.py681479%26–28, 78–87, 96–97, 102, 105
TOTAL208744179% 

Tests Skipped Failures Errors Time
321 0 💤 19 ❌ 0 🔥 2.723s ⏱️

Please sign in to comment.