diff --git a/src/nomad_simulations/schema_packages/common.py b/src/nomad_simulations/schema_packages/common.py new file mode 100644 index 00000000..3f68c443 --- /dev/null +++ b/src/nomad_simulations/schema_packages/common.py @@ -0,0 +1,54 @@ +import numpy as np +from nomad.datamodel.data import ArchiveSection +from nomad.datamodel.metainfo.annotations import ELNAnnotation +from nomad.metainfo import Datetime, Quantity + + +class Time(ArchiveSection): + """ + Contains time-related quantities. + """ + + datetime_end = Quantity( + type=Datetime, + description=""" + The date and time when this computation ended. + """, + a_eln=ELNAnnotation(component='DateTimeEditQuantity'), + ) + + cpu1_start = Quantity( + type=np.float64, + unit='second', + description=""" + The starting time of the computation on the (first) CPU 1. + """, + a_eln=ELNAnnotation(component='NumberEditQuantity'), + ) + + cpu1_end = Quantity( + type=np.float64, + unit='second', + description=""" + The end time of the computation on the (first) CPU 1. + """, + a_eln=ELNAnnotation(component='NumberEditQuantity'), + ) + + wall_start = Quantity( + type=np.float64, + unit='second', + description=""" + The internal wall-clock time from the starting of the computation. + """, + a_eln=ELNAnnotation(component='NumberEditQuantity'), + ) + + wall_end = Quantity( + type=np.float64, + unit='second', + description=""" + The internal wall-clock time from the end of the computation. + """, + a_eln=ELNAnnotation(component='NumberEditQuantity'), + ) diff --git a/src/nomad_simulations/schema_packages/general.py b/src/nomad_simulations/schema_packages/general.py index 9a2d48f0..3835b52b 100644 --- a/src/nomad_simulations/schema_packages/general.py +++ b/src/nomad_simulations/schema_packages/general.py @@ -11,7 +11,7 @@ from nomad.datamodel.data import Schema from nomad.datamodel.metainfo.annotations import ELNAnnotation from nomad.datamodel.metainfo.basesections import Activity, Entity -from nomad.metainfo import Datetime, Quantity, SchemaPackage, Section, SubSection +from nomad.metainfo import Quantity, SchemaPackage, Section, SubSection from nomad_simulations.schema_packages.model_method import ModelMethod from nomad_simulations.schema_packages.model_system import ModelSystem @@ -21,6 +21,8 @@ is_not_representative, ) +from .common import Time + configuration = config.get_plugin_entry_point( 'nomad_simulations.schema_packages:nomad_simulations_plugin' ) @@ -121,7 +123,7 @@ def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None: pass -class BaseSimulation(Activity): +class BaseSimulation(Activity, Time): """ A computational simulation that produces output data from a given input model system and input methodological parameters. @@ -135,50 +137,6 @@ class BaseSimulation(Activity): links=['https://liusemweb.github.io/mdo/core/1.1/index.html#Calculation'] ) - datetime_end = Quantity( - type=Datetime, - description=""" - The date and time when this computation ended. - """, - a_eln=ELNAnnotation(component='DateTimeEditQuantity'), - ) - - cpu1_start = Quantity( - type=np.float64, - unit='second', - description=""" - The starting time of the computation on the (first) CPU 1. - """, - a_eln=ELNAnnotation(component='NumberEditQuantity'), - ) - - cpu1_end = Quantity( - type=np.float64, - unit='second', - description=""" - The end time of the computation on the (first) CPU 1. - """, - a_eln=ELNAnnotation(component='NumberEditQuantity'), - ) - - wall_start = Quantity( - type=np.float64, - unit='second', - description=""" - The internal wall-clock time from the starting of the computation. - """, - a_eln=ELNAnnotation(component='NumberEditQuantity'), - ) - - wall_end = Quantity( - type=np.float64, - unit='second', - description=""" - The internal wall-clock time from the end of the computation. - """, - a_eln=ELNAnnotation(component='NumberEditQuantity'), - ) - program = SubSection(sub_section=Program.m_def, repeats=False) def normalize(self, archive: 'EntryArchive', logger: 'BoundLogger') -> None: diff --git a/src/nomad_simulations/schema_packages/outputs.py b/src/nomad_simulations/schema_packages/outputs.py index 1491fda3..6592b7af 100644 --- a/src/nomad_simulations/schema_packages/outputs.py +++ b/src/nomad_simulations/schema_packages/outputs.py @@ -1,7 +1,6 @@ from typing import TYPE_CHECKING, Optional import numpy as np -from nomad.datamodel.data import ArchiveSection from nomad.datamodel.metainfo.annotations import ELNAnnotation from nomad.metainfo import Quantity, SubSection @@ -38,8 +37,10 @@ XASSpectrum, ) +from .common import Time -class Outputs(ArchiveSection): + +class Outputs(Time): """ Output properties of a simulation. This base class can be used for inheritance in any of the output properties defined in this schema. diff --git a/src/nomad_simulations/schema_packages/physical_property.py b/src/nomad_simulations/schema_packages/physical_property.py index 4ce63115..446d97d1 100644 --- a/src/nomad_simulations/schema_packages/physical_property.py +++ b/src/nomad_simulations/schema_packages/physical_property.py @@ -6,14 +6,7 @@ from nomad.datamodel.data import ArchiveSection from nomad.datamodel.metainfo.annotations import ELNAnnotation from nomad.datamodel.metainfo.basesections import Entity -from nomad.metainfo import ( - URL, - MEnum, - Quantity, - Reference, - SectionProxy, - SubSection, -) +from nomad.metainfo import URL, MEnum, Quantity, Reference, SectionProxy, SubSection from nomad.metainfo.metainfo import Dimension if TYPE_CHECKING: @@ -244,7 +237,10 @@ def _new_value(self) -> Quantity: def __init__( self, m_def: 'Section' = None, m_context: 'Context' = None, **kwargs ) -> None: + value = kwargs.pop('value', None) super().__init__(m_def, m_context, **kwargs) + if value is not None: + self.value = value # Checking if IRI is defined if self.iri is None: @@ -252,44 +248,14 @@ 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.' ) - # Checking if the quantities `n_` are defined, as this are used to calculate `rank` - for quantity, _ in self.m_def.all_quantities.items(): - if quantity.startswith('n_') and getattr(self, quantity) is None: - raise ValueError( - f'`{quantity}` is not defined during initialization of the class.' - ) - - def __setattr__(self, name: str, val: Any) -> None: - # For the special case of `value`, its `shape` needs to be defined from `_full_shape` + def __setattr__(self, name, value): 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) + value = np.array(value) + self.__class__.value.shape = ['*'] * value.ndim + except Exception: + pass + return super().__setattr__(name, value) def _is_derived(self) -> bool: """ diff --git a/src/nomad_simulations/schema_packages/workflow/__init__.py b/src/nomad_simulations/schema_packages/workflow/__init__.py new file mode 100644 index 00000000..bf08ad75 --- /dev/null +++ b/src/nomad_simulations/schema_packages/workflow/__init__.py @@ -0,0 +1,7 @@ +from .beyond_dft import BeyondDFTResults, DFTGWWorkflow, DFTTBWorkflow +from .general import SerialWorkflow, SimulationWorkflow +from .geometry_optimization import GeometryOptimization +from .molecular_dynamics import MolecularDynamics +from .phonon import Phonon +from .single_point import SinglePoint +from .thermodynamics import Thermodynamics diff --git a/src/nomad_simulations/schema_packages/workflow/beyond_dft.py b/src/nomad_simulations/schema_packages/workflow/beyond_dft.py new file mode 100644 index 00000000..e7e738c3 --- /dev/null +++ b/src/nomad_simulations/schema_packages/workflow/beyond_dft.py @@ -0,0 +1,116 @@ +from nomad.datamodel import EntryArchive +from nomad.metainfo import SchemaPackage, SubSection +from structlog.stdlib import BoundLogger + +from .general import ( + INCORRECT_N_TASKS, + ElectronicStructureResults, + SerialWorkflow, + SimulationWorkflowModel, + SimulationWorkflowResults, +) + +m_package = SchemaPackage() + + +class BeyondDFTModel(SimulationWorkflowModel): + label = 'DFT+ workflow parameters' + + +class BeyondDFTResults(SimulationWorkflowResults): + """ + Contains reference to DFT outputs. + """ + + label = 'DFT+ workflow results' + + dft = SubSection(sub_section=ElectronicStructureResults) + + ext = SubSection(sub_section=ElectronicStructureResults) + + +class BeyondDFTWorkflow(SerialWorkflow): + """ + Definitions for workflows based on DFT. + """ + + def map_inputs(self, archive: EntryArchive, logger: BoundLogger) -> None: + if not self.model: + self.model = BeyondDFTModel() + super().map_inputs(archive, logger) + + def map_outputs(self, archive: EntryArchive, logger: BoundLogger) -> None: + if not self.results: + self.results = BeyondDFTResults() + super().map_outputs(archive, logger) + + def normalize(self, archive: EntryArchive, logger: BoundLogger) -> None: + """ + Link the DFT and the extended single point workflow. + """ + super().normalize(archive, logger) + + if len(self.tasks) != 2: + logger.error(INCORRECT_N_TASKS) + return + + if not self.name: + self.name: str = self.m_def.name + + if not self.tasks[0].name: + self.tasks[0].name = 'DFT' + + +class DFTGWModel(BeyondDFTModel): + label = 'DFT+GW workflow parameters' + + +class DFTGWResults(BeyondDFTResults): + label = 'DFT+GW workflow results' + + +class DFTGWWorkflow(BeyondDFTWorkflow): + """ + Definitions for GW calculations based on DFT. + """ + + def normalize(self, archive: EntryArchive, logger: BoundLogger) -> None: + if not self.model: + self.model = DFTGWModel() + + if not self.results: + self.results = DFTGWResults() + + super().normalize(archive, logger) + + if self.task and not self.task[-1].name: + self.task[-1].name = 'GW' + + +class DFTTBModel(BeyondDFTModel): + label = 'DFT+TB workflow parameters' + + +class DFTTBResults(BeyondDFTResults): + label = 'DFT+TB worklfow results' + + +class DFTTBWorkflow(BeyondDFTWorkflow): + """ + Definitions for TB calculations based on DFT. + """ + + def normalize(self, archive: EntryArchive, logger: BoundLogger) -> None: + if not self.model: + self.model = DFTTBModel() + + if not self.results: + self.results = DFTTBResults() + + super().normalize(archive, logger) + + if self.tasks and not self.tasks[-1].name: + self.tasks[-1].name = 'TB' + + +m_package.__init_metainfo__() diff --git a/src/nomad_simulations/schema_packages/workflow/general.py b/src/nomad_simulations/schema_packages/workflow/general.py new file mode 100644 index 00000000..543a036b --- /dev/null +++ b/src/nomad_simulations/schema_packages/workflow/general.py @@ -0,0 +1,197 @@ +from nomad.datamodel import ArchiveSection, EntryArchive +from nomad.datamodel.metainfo.workflow import Link, Task, TaskReference, Workflow +from nomad.metainfo import Quantity, SchemaPackage, SubSection +from structlog.stdlib import BoundLogger + +from nomad_simulations.schema_packages.model_method import ModelMethod +from nomad_simulations.schema_packages.model_system import ModelSystem +from nomad_simulations.schema_packages.outputs import Outputs +from nomad_simulations.schema_packages.properties import ElectronicDensityOfStates + +INCORRECT_N_TASKS = 'Incorrect number of tasks found.' + +m_package = SchemaPackage() + + +class SimulationTask(Task): + pass + + +class SimulationWorkflowModel(ArchiveSection): + """ + Base class for simulation workflow model sub-section definition. + """ + + label = 'Input model' + + initial_system = Quantity( + type=ModelSystem, + description=""" + Reference to the input model_system. + """, + ) + + initial_method = Quantity( + type=ModelMethod, + description=""" + Reference to the input model_method. + """, + ) + + def normalize(self, archive: EntryArchive, logger: BoundLogger) -> None: + if not archive.data: + return + + if not self.initial_system and archive.data.model_system: + self.initial_system = archive.data.model_system[0] + if not self.initial_method and archive.data.model_method: + self.initial_method = archive.data.model_method[0] + + +class SimulationWorkflowResults(ArchiveSection): + """ + Base class for simulation workflow results sub-section definition. + """ + + label = 'Output results' + + final_outputs = Quantity( + type=Outputs, + description=""" + Reference to the final outputs. + """, + ) + + def normalize(self, archive: EntryArchive, logger: BoundLogger) -> None: + if not archive.data or not archive.data.outputs: + return + + if not self.final_outputs: + self.final_outputs = archive.data.outputs[-1] + + +class SimulationTaskReference(TaskReference, SimulationTask): + pass + + +class SimulationWorkflow(Workflow, SimulationTask): + """ + Base class for simulation workflows. + + It contains sub-sections model and results which are included in inputs and + outputs, respectively. + """ + + task_label = 'Task' + + model = SubSection(sub_section=SimulationWorkflowModel.m_def) + + results = SubSection(sub_section=SimulationWorkflowResults.m_def) + + def map_inputs(self, archive: EntryArchive, logger: BoundLogger) -> None: + if self.model: + self.model.normalize(archive, logger) + # add method to inputs + self.inputs.append(Link(name=self.model.label, section=self.model)) + + def map_outputs(self, archive: EntryArchive, logger: BoundLogger) -> None: + if self.results: + self.results.normalize(archive, logger) + # add results to outputs + self.outputs.append(Link(name=self.results.label, section=self.results)) + + def map_tasks(self, archive: EntryArchive, logger: BoundLogger) -> None: + """ + Generate tasks from archive data outputs. Tasks are ordered and linked based + on the execution time of the calculation corresponding to the output. + """ + if not archive.data or not archive.data.outputs: + return + + tasks = [] + for outputs in archive.data.outputs: + tasks.append( + SimulationTask( + outputs=[ + Link( + name='Outputs', + section=outputs, + ) + ] + ) + ) + + self.tasks.extend(tasks) + + def normalize(self, archive: EntryArchive, logger: BoundLogger): + """ + Link tasks based on start and end times. + """ + if not self.name: + self.name = self.m_def.name + + if not self.inputs: + self.map_inputs(archive, logger) + + if not self.outputs: + self.map_outputs(archive, logger) + + if not self.tasks: + self.map_tasks(archive, logger) + + +class SerialWorkflow(SimulationWorkflow): + """ + Base class for workflows where tasks are executed sequentially. + """ + + def map_tasks(self, archive: EntryArchive, logger: BoundLogger) -> None: + super().map_tasks(archive, logger) + for n, task in enumerate(self.tasks): + if not task.name: + task.name = f'{self.task_label} {n}' + + def normalize(self, archive: EntryArchive, logger: BoundLogger) -> None: + super().normalize(archive, logger) + + if not self.tasks: + logger.error(INCORRECT_N_TASKS) + return + + # link tasks sequentially + for n, task in enumerate(self.tasks): + if n == 0: + inputs = self.inputs + else: + previous_task = self.tasks[n - 1] + inputs = [ + Link( + name='Linked task', + section=previous_task.task + if isinstance(previous_task, TaskReference) + else previous_task, + ) + ] + + task.inputs.extend([inp for inp in inputs if inp not in task.inputs]) + + # add oututs of last task to outputs + self.outputs.extend( + [out for out in self.tasks[-1].outputs if out not in self.outputs] + ) + + +class ElectronicStructureResults(SimulationWorkflowResults): + """ + Contains definitions for results of an electronic structure simulation. + """ + + dos = Quantity( + type=ElectronicDensityOfStates, + description=""" + Reference to the electronic density of states output. + """, + ) + + +m_package.__init_metainfo__() diff --git a/src/nomad_simulations/schema_packages/workflow/geometry_optimization.py b/src/nomad_simulations/schema_packages/workflow/geometry_optimization.py new file mode 100644 index 00000000..f4b6f0fc --- /dev/null +++ b/src/nomad_simulations/schema_packages/workflow/geometry_optimization.py @@ -0,0 +1,205 @@ +import numpy as np +from nomad.datamodel import EntryArchive +from nomad.metainfo import MEnum, Quantity, SchemaPackage +from structlog.stdlib import BoundLogger + +from nomad_simulations.schema_packages.properties.energies import BaseEnergy + +from .general import SerialWorkflow, SimulationWorkflowModel, SimulationWorkflowResults + +m_package = SchemaPackage() + + +class GeometryOptimizationModel(SimulationWorkflowModel): + label = 'Geometry optimization parameters' + + optimization_type = Quantity( + type=MEnum('static', 'atomic', 'cell_shape', 'cell_volume'), + shape=[], + description=""" + The type of geometry optimization, which denotes what is being optimized. + + Allowed values are: + + | Type | Description | + + | ---------------------- | ----------------------------------------- | + + | `"static"` | no optimization | + + | `"atomic"` | the atomic coordinates alone are updated | + + | `"cell_volume"` | `"atomic"` + cell lattice paramters are updated isotropically | + + | `"cell_shape"` | `"cell_volume"` but without the isotropic constraint: all cell parameters are updated | + + """, + ) + + optimization_method = Quantity( + type=str, + shape=[], + description=""" + The method used for geometry optimization. Some known possible values are: + `"steepest_descent"`, `"conjugant_gradient"`, `"low_memory_broyden_fletcher_goldfarb_shanno"`. + """, + ) + + convergence_tolerance_energy_difference = Quantity( + type=float, + shape=[], + unit='joule', + description=""" + The input energy difference tolerance criterion. + """, + ) + + convergence_tolerance_force_maximum = Quantity( + type=float, + shape=[], + unit='newton', + description=""" + The input maximum net force tolerance criterion. + """, + ) + + convergence_tolerance_stress_maximum = Quantity( + type=float, + shape=[], + unit='pascal', + description=""" + The input maximum stress tolerance criterion. + """, + ) + + convergence_tolerance_displacement_maximum = Quantity( + type=float, + shape=[], + unit='meter', + description=""" + The input maximum displacement tolerance criterion. + """, + ) + + n_steps_maximum = Quantity( + type=int, + shape=[], + description=""" + Maximum number of optimization steps. + """, + ) + + sampling_frequency = Quantity( + type=int, + shape=[], + description=""" + The number of optimization steps between saved outputs. + """, + ) + + +class GeometryOptimizationResults(SimulationWorkflowResults): + label = 'Geometry optimiztation results' + + n_steps = Quantity( + type=int, + shape=[], + description=""" + Number of saved optimization steps. + """, + ) + + energies = Quantity( + type=np.float64, + unit='joule', + shape=['n_steps'], + description=""" + List of energy_total values gathered from the single configuration + calculations that are a part of the optimization trajectory. + """, + ) + + steps = Quantity( + type=np.int32, + shape=['n_steps'], + description=""" + The step index corresponding to each saved configuration. + """, + ) + + final_energy_difference = Quantity( + type=np.float64, + shape=[], + unit='joule', + description=""" + The difference in the energy_total between the last two steps during + optimization. + """, + ) + + final_force_maximum = Quantity( + type=np.float64, + shape=[], + unit='newton', + description=""" + The maximum net force in the last optimization step. + """, + ) + + final_displacement_maximum = Quantity( + type=np.float64, + shape=[], + unit='meter', + description=""" + The maximum displacement in the last optimization step with respect to previous. + """, + ) + + is_converged_geometry = Quantity( + type=bool, + shape=[], + description=""" + Indicates if the geometry convergence criteria were fulfilled. + """, + ) + + def normalize(self, archive: EntryArchive, logger: BoundLogger) -> None: + if not self.n_steps: + self.n_steps = len(archive.data.outputs) + + if not self.energies: + energies = [] + for outputs in archive.data.outputs: + try: + energies.append(outputs.total_energies[-1].value.magnitude) + except Exception: + logger.error('Energy not found in outputs.') + break + if energies: + energies = np.array(energies) + self.energies = energies * BaseEnergy.value.unit + denergies = energies[1:] - energies[: len(energies) - 1] + self.final_energy_difference = ( + denergies[denergies.nonzero()[0][-1]] * BaseEnergy.value.unit + ) + + +class GeometryOptimization(SerialWorkflow): + """ + Definitions for geometry optimization workflow. + """ + + task_label = 'Step' + + def map_inputs(self, archive: EntryArchive, logger: BoundLogger) -> None: + if not self.model: + self.model = GeometryOptimizationModel() + super().map_inputs(archive, logger) + + def map_outputs(self, archive: EntryArchive, logger: BoundLogger) -> None: + if not self.results: + self.results = GeometryOptimizationResults() + super().map_outputs(archive, logger) + + +m_package.__init_metainfo__() diff --git a/src/nomad_simulations/schema_packages/workflow/molecular_dynamics.py b/src/nomad_simulations/schema_packages/workflow/molecular_dynamics.py new file mode 100644 index 00000000..22a1a280 --- /dev/null +++ b/src/nomad_simulations/schema_packages/workflow/molecular_dynamics.py @@ -0,0 +1,145 @@ +from nomad.datamodel import EntryArchive +from nomad.metainfo import MEnum, Quantity +from structlog.stdlib import BoundLogger + +from .general import SerialWorkflow, SimulationWorkflowModel, SimulationWorkflowResults +from .thermodynamics import ThermodynamicsResults + + +class MolecularDynamicsModel(SimulationWorkflowModel): + label = 'MD parameters' + + thermodynamic_ensemble = Quantity( + type=MEnum('NVE', 'NVT', 'NPT', 'NPH'), + shape=[], + description=""" + The type of thermodynamic ensemble that was simulated. + + Allowed values are: + + | Thermodynamic Ensemble | Description | + + | ---------------------- | ----------------------------------------- | + + | `"NVE"` | Constant number of particles, volume, and energy | + + | `"NVT"` | Constant number of particles, volume, and temperature | + + | `"NPT"` | Constant number of particles, pressure, and temperature | + + | `"NPH"` | Constant number of particles, pressure, and enthalpy | + """, + ) + + integrator_type = Quantity( + type=MEnum( + 'brownian', + 'conjugant_gradient', + 'langevin_goga', + 'langevin_schneider', + 'leap_frog', + 'rRESPA_multitimescale', + 'velocity_verlet', + 'langevin_leap_frog', + ), + shape=[], + description=""" + Name of the integrator. + + Allowed values are: + + | Integrator Name | Description | + + | ---------------------- | ----------------------------------------- | + + | `"langevin_goga"` | N. Goga, A. J. Rzepiela, A. H. de Vries, + S. J. Marrink, and H. J. C. Berendsen, [J. Chem. Theory Comput. **8**, 3637 (2012)] + (https://doi.org/10.1021/ct3000876) | + + | `"langevin_schneider"` | T. Schneider and E. Stoll, + [Phys. Rev. B **17**, 1302](https://doi.org/10.1103/PhysRevB.17.1302) | + + | `"leap_frog"` | R.W. Hockney, S.P. Goel, and J. Eastwood, + [J. Comp. Phys. **14**, 148 (1974)](https://doi.org/10.1016/0021-9991(74)90010-2) | + + | `"velocity_verlet"` | W.C. Swope, H.C. Andersen, P.H. Berens, and K.R. Wilson, + [J. Chem. Phys. **76**, 637 (1982)](https://doi.org/10.1063/1.442716) | + + | `"rRESPA_multitimescale"` | M. Tuckerman, B. J. Berne, and G. J. Martyna + [J. Chem. Phys. **97**, 1990 (1992)](https://doi.org/10.1063/1.463137) | + + | `"langevin_leap_frog"` | J.A. Izaguirre, C.R. Sweet, and V.S. Pande + [Pac Symp Biocomput. **15**, 240-251 (2010)](https://doi.org/10.1142/9789814295291_0026) | + """, + ) + + integration_timestep = Quantity( + type=float, + shape=[], + unit='s', + description=""" + The timestep at which the numerical integration is performed. + """, + ) + + n_steps = Quantity( + type=int, + shape=[], + description=""" + Number of timesteps performed. + """, + ) + + coordinate_save_frequency = Quantity( + type=int, + shape=[], + description=""" + The number of timesteps between saving the coordinates. + """, + ) + + velocity_save_frequency = Quantity( + type=int, + shape=[], + description=""" + The number of timesteps between saving the velocities. + """, + ) + + force_save_frequency = Quantity( + type=int, + shape=[], + description=""" + The number of timesteps between saving the forces. + """, + ) + + thermodynamics_save_frequency = Quantity( + type=int, + shape=[], + description=""" + The number of timesteps between saving the thermodynamic quantities. + """, + ) + + +class MolecularDynamicsResults(ThermodynamicsResults): + """ + Contains defintions for the results of a molecular dynamics calculation. + """ + + label = 'MD results' + + +class MolecularDynamics(SerialWorkflow): + task_label = 'Step' + + def map_inputs(self, archive: EntryArchive, logger: BoundLogger) -> None: + if not self.model: + self.model = MolecularDynamicsModel() + super().map_inputs(archive, logger) + + def map_outputs(self, archive: EntryArchive, logger: BoundLogger) -> None: + if not self.results: + self.results = MolecularDynamicsResults() + super().map_outputs(archive, logger) diff --git a/src/nomad_simulations/schema_packages/workflow/phonon.py b/src/nomad_simulations/schema_packages/workflow/phonon.py new file mode 100644 index 00000000..d143bf86 --- /dev/null +++ b/src/nomad_simulations/schema_packages/workflow/phonon.py @@ -0,0 +1,186 @@ +from nomad.datamodel import EntryArchive +from nomad.datamodel.metainfo.workflow import Link, TaskReference +from nomad.metainfo import Quantity +from structlog.stdlib import BoundLogger + +from nomad_simulations.schema_packages.properties.spectral_profile import DOSProfile + +from .general import ( + INCORRECT_N_TASKS, + SimulationWorkflow, + SimulationWorkflowModel, + SimulationWorkflowResults, +) + + +class PhononModel(SimulationWorkflowModel): + label = 'Phonon calculation parameters' + + force_calculator = Quantity( + type=str, + shape=[], + description=""" + Name of the program used to calculate the forces. + """, + ) + + phonon_calculator = Quantity( + type=str, + shape=[], + description=""" + Name of the program used to perform phonon calculation. + """, + ) + + mesh_density = Quantity( + type=float, + shape=[], + unit='1 / meter ** 3', + description=""" + Density of the k-mesh for sampling. + """, + ) + + random_displacements = Quantity( + type=bool, + shape=[], + description=""" + Identifies if displacements are made randomly. + """, + ) + + with_non_analytic_correction = Quantity( + type=bool, + shape=[], + description=""" + Identifies if non-analytical term corrections are applied to dynamical matrix. + """, + ) + + with_grueneisen_parameters = Quantity( + type=bool, + shape=[], + description=""" + Identifies if Grueneisen parameters are calculated. + """, + ) + + +class PhononResults(SimulationWorkflowResults): + label = 'Phonon results' + + n_imaginary_frequencies = Quantity( + type=int, + shape=[], + description=""" + Number of modes with imaginary frequencies. + """, + ) + + n_bands = Quantity( + type=int, + shape=[], + description=""" + Number of phonon bands. + """, + ) + + n_qpoints = Quantity( + type=int, + shape=[], + description=""" + Number of q points for which phonon properties are evaluated. + """, + ) + + qpoints = Quantity( + type=float, + shape=['n_qpoints', 3], + description=""" + Value of the qpoints. + """, + ) + + group_velocity = Quantity( + type=float, + shape=['n_qpoints', 'n_bands', 3], + unit='meter / second', + description=""" + Calculated value of the group velocity at each qpoint. + """, + ) + + n_displacements = Quantity( + type=int, + shape=[], + description=""" + Number of independent displacements. + """, + ) + + n_atoms = Quantity( + type=int, + shape=[], + description=""" + Number of atoms in the simulation cell. + """, + ) + + displacements = Quantity( + type=float, + shape=['n_displacements', 'n_atoms', 3], + unit='meter', + description=""" + Value of the displacements applied to each atom in the simulation cell. + """, + ) + + # TODO add band dos and bandstructure + + +class Phonon(SimulationWorkflow): + """ + Definitions for a phonon workflow. + """ + + task_label = 'Force calculation' + + def map_inputs(self, archive: EntryArchive, logger: BoundLogger) -> None: + if not self.model: + self.model = PhononModel() + super().map_inputs(archive, logger) + + def map_outputs(self, archive: EntryArchive, logger: BoundLogger) -> None: + if not self.results: + self.results = PhononResults() + super().map_outputs(archive, logger) + + def normalize(self, archive: EntryArchive, logger: BoundLogger) -> None: + super().normalize(archive, logger) + + if len(self.tasks) < 2: + logger.error(INCORRECT_N_TASKS) + return + + # assign inputs to force calculations + for n, task in enumerate(self.tasks[:-1]): + if not task.name: + task.name = f'Force calculation for supercell {n}' + task.inputs.extend([inp for inp in self.inputs if inp not in task.inputs]) + + # assign outputs of force calculation as input to phonon task + self.tasks[-1].inputs = [ + Link( + name='Linked task', + section=task.task if isinstance(task, TaskReference) else task, + ) + for task in self.tasks[:-1] + ] + + # add phonon task oututs to outputs + self.outputs.extend( + [out for out in self.tasks[-1].outputs if out not in self.outputs] + ) + + if not self.tasks[-1].name: + self.tasks[-1].name = 'Phonon calculation' diff --git a/src/nomad_simulations/schema_packages/workflow/single_point.py b/src/nomad_simulations/schema_packages/workflow/single_point.py new file mode 100644 index 00000000..26d5b92d --- /dev/null +++ b/src/nomad_simulations/schema_packages/workflow/single_point.py @@ -0,0 +1,70 @@ +from nomad.datamodel import EntryArchive +from nomad.datamodel.metainfo.workflow import Link, Task +from nomad.metainfo import SchemaPackage +from structlog.stdlib import BoundLogger + +from .general import ( + INCORRECT_N_TASKS, + SimulationWorkflow, + SimulationWorkflowModel, + SimulationWorkflowResults, +) + +m_package = SchemaPackage() + + +class SinglePointModel(SimulationWorkflowModel): + """ + Contains definitions for the input model of a single point workflow. + """ + + label = 'Single point model' + + +class SinglePointResults(SimulationWorkflowResults): + """ + Contains defintions for the results of a single point workflow. + """ + + label = 'Single point results' + + +class SinglePoint(SimulationWorkflow): + """ + Definitions for single point workflow. + """ + + task_label = 'Calculation' + + def map_inputs(self, archive: EntryArchive, logger: BoundLogger): + super().map_inputs(archive, logger) + if archive.data: + if archive.data.model_method: + self.inputs.append( + Link(name='Input method', section=archive.data.model_method[0]) + ) + + if archive.data.model_system: + self.inputs.append( + Link(name='Input system', section=archive.data.model_system[0]) + ) + + def normalize(self, archive: EntryArchive, logger: BoundLogger) -> None: + super().normalize(archive, logger) + if len(self.tasks) != 1: + logger.error(INCORRECT_N_TASKS) + return + self.tasks[0].name = self.task_label + + # add inputs to calculation inputs + self.tasks[0].inputs.extend( + [inp for inp in self.inputs if inp not in self.tasks[0].inputs] + ) + + # add outputs of calculation to outputs + self.outputs.extend( + [out for out in self.tasks[0].outputs if out not in self.outputs] + ) + + +m_package.__init_metainfo__() diff --git a/src/nomad_simulations/schema_packages/workflow/thermodynamics.py b/src/nomad_simulations/schema_packages/workflow/thermodynamics.py new file mode 100644 index 00000000..563eaa62 --- /dev/null +++ b/src/nomad_simulations/schema_packages/workflow/thermodynamics.py @@ -0,0 +1,63 @@ +import numpy as np +from nomad.datamodel import EntryArchive +from nomad.metainfo import Quantity, SchemaPackage +from structlog.stdlib import BoundLogger + +from .general import ( + SimulationWorkflow, + SimulationWorkflowModel, + SimulationWorkflowResults, +) + +m_package = SchemaPackage() + + +class ThermodynamicsModel(SimulationWorkflowModel): + label = 'Thermodynamics model' + + +class ThermodynamicsResults(SimulationWorkflowResults): + label = 'Thermodynamics results' + + n_values = Quantity( + type=int, + shape=[], + description=""" + Number of thermodynamics property evaluations. + """, + ) + + temperature = Quantity( + type=np.float64, + shape=['n_values'], + unit='kelvin', + description=""" + Specifies the temperatures at which properties such as the Helmholtz free energy + are calculated. + """, + ) + + pressure = Quantity( + type=np.float64, + shape=['n_values'], + unit='pascal', + description=""" + Array containing the values of the pressure (one third of the trace of the stress + tensor) corresponding to each property evaluation. + """, + ) + + +class Thermodynamics(SimulationWorkflow): + def map_inputs(self, archive: EntryArchive, logger: BoundLogger) -> None: + if not self.model: + self.model = ThermodynamicsModel() + super().map_inputs(archive, logger) + + def map_outputs(self, archive: EntryArchive, logger: BoundLogger) -> None: + if not self.results: + self.results = ThermodynamicsResults() + super().map_outputs(archive, logger) + + +m_package.__init_metainfo__()