diff --git a/src/nomad_simulations/schema_packages/model_method.py b/src/nomad_simulations/schema_packages/model_method.py index 7dfaf2f4..e2c9ac79 100644 --- a/src/nomad_simulations/schema_packages/model_method.py +++ b/src/nomad_simulations/schema_packages/model_method.py @@ -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 @@ -36,11 +36,25 @@ 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])]` + + 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 @@ -48,9 +62,8 @@ class ModelMethod(ArchiveSection): 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'), ) @@ -58,7 +71,7 @@ class ModelMethod(ArchiveSection): 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. """, @@ -66,7 +79,7 @@ class ModelMethod(ArchiveSection): ) external_reference = Quantity( - type=str, + type=URL, description=""" External reference to the model e.g. DOI, URL. """, @@ -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 @@ -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( @@ -399,7 +432,8 @@ 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 | | --------- | ----------------------- | @@ -407,6 +441,7 @@ class TB(ModelMethodElectronic): | `'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'), ) @@ -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=""" @@ -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 @@ -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, @@ -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', diff --git a/src/nomad_simulations/schema_packages/outputs.py b/src/nomad_simulations/schema_packages/outputs.py index 2578aba2..462d3a12 100644 --- a/src/nomad_simulations/schema_packages/outputs.py +++ b/src/nomad_simulations/schema_packages/outputs.py @@ -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 ( @@ -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'), ) @@ -130,11 +138,11 @@ 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 @@ -142,13 +150,31 @@ def set_model_system_ref(self) -> Optional[ModelSystem]: 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): """ diff --git a/tests/test_outputs.py b/tests/test_outputs.py index 172e8f3d..7b06486d 100644 --- a/tests/test_outputs.py +++ b/tests/test_outputs.py @@ -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 @@ -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: