Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Further abstraction in ModelMethod #93

Merged
merged 4 commits into from
Jun 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 52 additions & 23 deletions src/nomad_simulations/schema_packages/model_method.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@

from nomad.datamodel.data import ArchiveSection
from nomad.datamodel.metainfo.annotations import ELNAnnotation
from nomad.metainfo import MEnum, Quantity, SubSection
from nomad.metainfo import MEnum, Quantity, SubSection, URL, Section

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

Expand All @@ -36,37 +36,50 @@
from nomad_simulations.schema_packages.utils import is_not_representative


class ModelMethod(ArchiveSection):
class BaseModelMethod(ArchiveSection):
"""
Model method input parameters and numerical settings used to simulate materials properties.

# ! add more description once section is finished
A base section used to define the abstract class of a Hamiltonian section. This section is an
abstraction of the `ModelMethod` section, which contains the settings and parameters used in
the mathematical model solved in a simulation. This abstraction is needed in order to allow
`ModelMethod` to be divided into specific `terms`, so that the total Hamiltonian is specified in
`ModelMethod`, while its contributions are defined in `ModelMethod.terms`.

Example: a custom model Hamiltonian containing two terms:
$H = H_{V_{1}(r)}+H_{V_{2}(r)}$
where $H_{V_{1}(r)}$ and $H_{V_{2}(r)}$ are the contributions of two different potentials written in
real space coordinates $r$. These potentials could be defined in terms of a combination of parameters
$(a_{1}, b_{1}, c_{1}...)$ for $V_{1}(r)$ and $(a_{2}, b_{2}, c_{2}...)$ for $V_{2}(r)$. If we name the
total Hamiltonian as `'FF1'`:
`ModelMethod.name = 'FF1'`
`ModelMethod.contributions = [BaseModelMethod(name='V1', parameters=[a1, b1, c1]), BaseModelMethod(name='V2', parameters=[a2, b2, c2])]`
JosePizarro3 marked this conversation as resolved.
Show resolved Hide resolved

Note: quantities such as `name`, `type`, `external_reference` should be descriptive enough so that the
total Hamiltonian model or each of the terms or contributions can be identified.
"""

normalizer_level = 1

name = Quantity(
type=str,
description="""
Name of the model method. This is typically used to easy identification of the `ModelMethod` section.

Suggested values: 'DFT', 'TB', 'GE', 'BSE', 'DMFT', 'NMR', 'kMC'.
Name of the mathematical model. This is typically used to identify the model Hamiltonian used in the
simulation. Typical standard names: 'DFT', 'TB', 'GW', 'BSE', 'DMFT', 'NMR', 'kMC'.
""",
a_eln=ELNAnnotation(component='StringEditQuantity'),
)

type = Quantity(
type=str,
description="""
Identifier used to further specify the type of model Hamiltonian. Example: a TB
Identifier used to further specify the kind or sub-type of model Hamiltonian. Example: a TB
model can be 'Wannier', 'DFTB', 'xTB' or 'Slater-Koster'. This quantity should be
rewritten to a MEnum when inheriting from this class.
""",
a_eln=ELNAnnotation(component='StringEditQuantity'),
)

external_reference = Quantity(
type=str,
type=URL,
description="""
External reference to the model e.g. DOI, URL.
""",
Expand All @@ -79,6 +92,25 @@ def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None:
super().normalize(archive, logger)


class ModelMethod(BaseModelMethod):
"""
A base section containing the mathematical model parameters. These are both the parameters of
the model and the settings used in the simulation. Optionally, this section can be decomposed
in a series of contributions by storing them under the `contributions` quantity.
"""

contributions = SubSection(
sub_section=BaseModelMethod.m_def,
repeats=True,
description="""
Contribution or sub-term of the total model Hamiltonian.
""",
)

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


class ModelMethodElectronic(ModelMethod):
"""
A base section used to define the parameters of a model Hamiltonian used in electronic structure
Expand Down Expand Up @@ -199,6 +231,7 @@ class DFT(ModelMethodElectronic):
""",
)

# ? This could be moved under `contributions`, @ndaelman-hu
xc_functionals = SubSection(sub_section=XCFunctional.m_def, repeats=True)

exact_exchange_mixing_factor = Quantity(
Expand Down Expand Up @@ -399,14 +432,16 @@ class TB(ModelMethodElectronic):
type=MEnum('DFTB', 'xTB', 'Wannier', 'SlaterKoster', 'unavailable'),
default='unavailable',
description="""
Tight-binding model type.
Tight-binding model Hamiltonian type. The default is set to `'unavailable'` in case none of the
standard types can be recognized. These can be:

| Value | Reference |
| --------- | ----------------------- |
| `'DFTB'` | https://en.wikipedia.org/wiki/DFTB |
| `'xTB'` | https://xtb-docs.readthedocs.io/en/latest/ |
| `'Wannier'` | https://www.wanniertools.org/theory/tight-binding-model/ |
| `'SlaterKoster'` | https://journals.aps.org/pr/abstract/10.1103/PhysRev.94.1498 |
| `'unavailable'` | - |
""",
a_eln=ELNAnnotation(component='EnumEditQuantity'),
)
Expand Down Expand Up @@ -806,14 +841,6 @@ class ExcitedStateMethodology(ModelMethodElectronic):
DFT Hamiltonian. These are: GW, TDDFT, BSE.
"""

type = Quantity(
type=str,
description="""
Identifier used to further specify the type of model Hamiltonian.
""",
a_eln=ELNAnnotation(component='StringEditQuantity'),
)

n_states = Quantity(
type=np.int32,
description="""
Expand Down Expand Up @@ -951,12 +978,10 @@ class BSE(ExcitedStateMethodology):
type = Quantity(
type=MEnum('Singlet', 'Triplet', 'IP', 'RPA'),
description="""
Type of BSE hamiltonian solved:
Type of the BSE Hamiltonian solved:

H_BSE = H_diagonal + 2 * gx * Hx - gc * Hc

where gx, gc specifies the type.

Online resources for the theory:
- http://exciting.wikidot.com/carbon-excited-states-from-bse#toc1
- https://www.vasp.at/wiki/index.php/Bethe-Salpeter-equations_calculations
Expand Down Expand Up @@ -1008,6 +1033,8 @@ class CoreHoleSpectra(ModelMethodElectronic):
also contains reference to the specific methodological section (DFT, BSE) used to obtain the core-hole spectra.
"""

m_def = Section(a_eln={'hide': ['type']})

# # TODO add examples
# solver = Quantity(
# type=str,
Expand Down Expand Up @@ -1079,6 +1106,8 @@ class DMFT(ModelMethodElectronic):
A base section used to define the parameters of a DMFT calculation.
"""

m_def = Section(a_eln={'hide': ['type']})

impurity_solver = Quantity(
type=MEnum(
'CT-INT',
Expand Down
38 changes: 32 additions & 6 deletions src/nomad_simulations/schema_packages/outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from structlog.stdlib import BoundLogger

from nomad_simulations.schema_packages.model_system import ModelSystem
from nomad_simulations.schema_packages.model_method import ModelMethod
from nomad_simulations.schema_packages.numerical_settings import SelfConsistency
from nomad_simulations.schema_packages.physical_property import PhysicalProperty
from nomad_simulations.schema_packages.properties import (
Expand Down Expand Up @@ -61,8 +62,15 @@ class Outputs(ArchiveSection):
model_system_ref = Quantity(
type=ModelSystem,
description="""
Reference to the `ModelSystem` section to which the output property references to and on
on which the simulation is performed.
Reference to the `ModelSystem` section in which the output physical properties were calculated.
""",
a_eln=ELNAnnotation(component='ReferenceEditQuantity'),
)

model_method_ref = Quantity(
type=ModelMethod,
description="""
Reference to the `ModelMethod` section in which the output physical properties were calculated.
""",
a_eln=ELNAnnotation(component='ReferenceEditQuantity'),
)
Expand Down Expand Up @@ -130,25 +138,43 @@ def extract_spin_polarized_property(

def set_model_system_ref(self) -> Optional[ModelSystem]:
"""
Set the reference to the last ModelSystem if this is not set in the output. This is only
valid if there is only one ModelSystem in the parent section.
Set the reference to the last `ModelSystem` if this is not set in the output. This is only
valid if there is only one `ModelSystem` in the parent section.

Returns:
(Optional[ModelSystem]): The reference to the last ModelSystem.
(Optional[ModelSystem]): The reference to the last `ModelSystem`.
"""
if self.m_parent is not None:
model_systems = self.m_parent.model_system
if model_systems is not None and len(model_systems) == 1:
return model_systems[-1]
return None

def set_model_method_ref(self) -> Optional[ModelMethod]:
"""
Set the reference to the last `ModelMethod` if this is not set in the output. This is only
valid if there is only one `ModelMethod` in the parent section.

Returns:
(Optional[ModelMethod]): The reference to the last `ModelMethod`.
"""
if self.m_parent is not None:
model_methods = self.m_parent.model_method
if model_methods is not None and len(model_methods) == 1:
return model_methods[-1]
return None

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

# Set ref to the last ModelSystem if this is not set in the output
# Set ref to the last `ModelSystem` if this is not set in the output
if self.model_system_ref is None:
self.model_system_ref = self.set_model_system_ref()

# Set ref to the last `ModelMethod` if this is not set in the output
if self.model_method_ref is None:
self.model_method_ref = self.set_model_method_ref()


class SCFOutputs(Outputs):
"""
Expand Down
48 changes: 43 additions & 5 deletions tests/test_outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from nomad.datamodel import EntryArchive

from nomad_simulations.schema_packages.model_system import ModelSystem
from nomad_simulations.schema_packages.model_method import ModelMethod
from nomad_simulations.schema_packages.numerical_settings import SelfConsistency
from nomad_simulations.schema_packages.outputs import Outputs, SCFOutputs
from nomad_simulations.schema_packages.properties import ElectronicBandGap
Expand Down Expand Up @@ -135,25 +136,62 @@ def test_set_model_system_ref(self, model_system: Optional[ModelSystem]):
assert model_system_ref is None

@pytest.mark.parametrize(
'model_system',
[(None), (ModelSystem(name='example'))],
'model_method',
[(None), (ModelMethod(name='example'))],
)
def test_normalize(self, model_system: Optional[ModelSystem]):
def test_set_model_method_ref(self, model_method: Optional[ModelMethod]):
"""
Test the `set_model_method_ref` method.

Args:
model_method (Optional[ModelMethod]): The `ModelMethod` to be tested for the `model_method_ref` reference
stored in `Outputs`.
"""
outputs = Outputs()
simulation = generate_simulation(model_method=model_method, outputs=outputs)
model_method_ref = outputs.set_model_method_ref()
if model_method is not None:
assert model_method_ref == simulation.model_method[-1]
assert model_method_ref.name == 'example'
else:
assert model_method_ref is None

@pytest.mark.parametrize(
'model_system, model_method',
[
(None, None),
(ModelSystem(name='example system'), None),
(None, ModelMethod(name='example method')),
(ModelSystem(name='example system'), ModelMethod(name='example method')),
],
)
def test_normalize(
self, model_system: Optional[ModelSystem], model_method: Optional[ModelMethod]
):
"""
Test the `normalize` method.

Args:
model_system (Optional[ModelSystem]): The expected `model_system_ref` obtained after normalization and
initially stored under `Simulation.model_system[0]`.
model_method (Optional[ModelMethod]): The expected `model_method_ref` obtained after normalization and
initially stored under `Simulation.model_method[0]`.
"""
outputs = Outputs()
simulation = generate_simulation(model_system=model_system, outputs=outputs)
simulation = generate_simulation(
model_system=model_system, model_method=model_method, outputs=outputs
)
outputs.normalize(archive=EntryArchive(), logger=logger)
if model_system is not None:
assert outputs.model_system_ref == simulation.model_system[-1]
assert outputs.model_system_ref.name == 'example'
assert outputs.model_system_ref.name == 'example system'
else:
assert outputs.model_system_ref is None
if model_method is not None:
assert outputs.model_method_ref == simulation.model_method[-1]
assert outputs.model_method_ref.name == 'example method'
else:
assert outputs.model_method_ref is None


class TestSCFOutputs:
Expand Down