Commit 84b3546d authored by Alvin Noe Ladines's avatar Alvin Noe Ladines
Browse files

Implemented fixes and tests to workflow

parent a77d1e1f
Pipeline #82332 passed with stages
in 22 minutes and 47 seconds
Subproject commit 2b9ee49d74cf89bb07e2b798e6e391fd5ac724c6 Subproject commit c17dbecac0f395b48fc65b0c7dcc2ba4de1be271
Subproject commit 84602c2c62462b93274dab91fce38a72b249cb49 Subproject commit e1f873f104d4ce64815636251c8e30971a9fcbda
...@@ -5646,20 +5646,20 @@ class section_XC_functionals(MSection): ...@@ -5646,20 +5646,20 @@ class section_XC_functionals(MSection):
a_legacy=LegacyDefinition(name='XC_functional_weight')) a_legacy=LegacyDefinition(name='XC_functional_weight'))
class Relaxation(MSection): class GeometryOptimization(MSection):
''' '''
Section containing the results of a relaxation workflow. Section containing the results of a geometry_optimization workflow.
''' '''
m_def = Section(validate=False, a_legacy=LegacyDefinition(name='section_relaxation')) m_def = Section(validate=False, a_legacy=LegacyDefinition(name='section_geometry_optimization'))
relaxation_type = Quantity( geometry_optimization_type = Quantity(
type=str, type=str,
shape=[], shape=[],
description=''' description='''
The type of relaxation ionic, cell_shape, cell_volume. The type of geometry optimization can either be ionic, cell_shape, cell_volume.
''', ''',
a_legacy=LegacyDefinition(name='relaxation_type') a_legacy=LegacyDefinition(name='geometry_optimization_type')
) )
input_energy_difference_tolerance = Quantity( input_energy_difference_tolerance = Quantity(
...@@ -5685,7 +5685,7 @@ class Relaxation(MSection): ...@@ -5685,7 +5685,7 @@ class Relaxation(MSection):
shape=[], shape=[],
unit='joule', unit='joule',
description=''' description='''
The difference in the energy between the last two steps during relaxation. The difference in the energy between the last two steps during optimization.
''', ''',
a_legacy=LegacyDefinition(name='final_energy_difference'), a_legacy=LegacyDefinition(name='final_energy_difference'),
a_search=Search()) a_search=Search())
...@@ -5695,35 +5695,17 @@ class Relaxation(MSection): ...@@ -5695,35 +5695,17 @@ class Relaxation(MSection):
shape=[], shape=[],
unit='newton', unit='newton',
description=''' description='''
The maximum net force in the last relaxation step. The maximum net force in the last optimization step.
''', ''',
a_legacy=LegacyDefinition(name='final_force_maximum')) a_legacy=LegacyDefinition(name='final_force_maximum'))
final_calculation_ref = Quantity( optimization_steps = Quantity(
type=Reference(SectionProxy('section_single_configuration_calculation')),
categories=[fast_access],
shape=[],
description='''
Reference to last calculation step.
''',
a_legacy=LegacyDefinition(name='final_calculation_ref'))
n_relaxation_steps = Quantity(
type=int, type=int,
shape=[], shape=[],
description=''' description='''
Number of relaxation steps. Number of optimization steps.
''', ''',
a_legacy=LegacyDefinition(name='n_relaxation_steps')) a_legacy=LegacyDefinition(name='optimization_steps'))
calculations_ref = Quantity(
type=Reference(SectionProxy('section_single_configuration_calculation')),
shape=['n_relaxation_steps'],
description='''
List of references to each section_single_configuration_calculation corresponding
to each step in the relaxation.
''',
a_legacy=LegacyDefinition(name='calculations_ref'))
class Phonon(MSection): class Phonon(MSection):
...@@ -5794,6 +5776,14 @@ class Elastic(MSection): ...@@ -5794,6 +5776,14 @@ class Elastic(MSection):
m_def = Section(validate=False, a_legacy=LegacyDefinition(name='section_elastic')) m_def = Section(validate=False, a_legacy=LegacyDefinition(name='section_elastic'))
energy_stress_calculator = Quantity(
type=str,
shape=[],
description='''
Name of program used to calculate energy or stress.
''',
a_legacy=LegacyDefinition(name='energy_stress_calculator'))
elastic_calculation_method = Quantity( elastic_calculation_method = Quantity(
type=str, type=str,
shape=[], shape=[],
...@@ -5886,15 +5876,35 @@ class Workflow(MSection): ...@@ -5886,15 +5876,35 @@ class Workflow(MSection):
type=str, type=str,
shape=[], shape=[],
description=''' description='''
The type of calculation workflow. Can be one of relaxation, elastic, phonon, The type of calculation workflow. Can be one of geometry_optimization, elastic,
molecular dynamics. phonon, molecular_dynamics.
''', ''',
a_legacy=LegacyDefinition(name='workflow_type'), a_legacy=LegacyDefinition(name='workflow_type'),
a_search=Search(statistic_size=4, statistic_order='_count')) a_search=Search(statistic_size=4, statistic_order='_count'))
section_relaxation = SubSection( calculation_result_ref = Quantity(
sub_section=SectionProxy('Relaxation'), categories=[fast_access], type=Reference(SectionProxy('section_single_configuration_calculation')),
a_legacy=LegacyDefinition(name='section_relaxation')) categories=[fast_access],
shape=[],
description='''
Reference to calculation result. In the case of geometry_optimization and
molecular dynamics, this corresponds to the final step in the simulation. For the
rest of the workflow types, it refers to the original system.
''',
a_legacy=LegacyDefinition(name='calculation_result_ref'))
calculations_ref = Quantity(
type=Reference(SectionProxy('section_single_configuration_calculation')),
shape=['optimization_steps'],
description='''
List of references to each section_single_configuration_calculation in the
simulation.
''',
a_legacy=LegacyDefinition(name='calculations_ref'))
section_geometry_optimization = SubSection(
sub_section=SectionProxy('GeometryOptimization'), categories=[fast_access],
a_legacy=LegacyDefinition(name='section_geometry_optimization'))
section_phonon = SubSection( section_phonon = SubSection(
sub_section=SectionProxy('Phonon'), categories=[fast_access], sub_section=SectionProxy('Phonon'), categories=[fast_access],
......
...@@ -15,17 +15,18 @@ ...@@ -15,17 +15,18 @@
import numpy as np import numpy as np
from nomad.normalizing.normalizer import Normalizer from nomad.normalizing.normalizer import Normalizer
from nomad.datamodel.metainfo.public import Workflow, Relaxation, Phonon, Elastic from nomad.datamodel.metainfo.public import Workflow, GeometryOptimization, Phonon, Elastic,\
MolecularDynamics
class RelaxationNormalizer(Normalizer): class GeometryOptimizationNormalizer(Normalizer):
def __init__(self, entry_archive): def __init__(self, entry_archive):
super().__init__(entry_archive) super().__init__(entry_archive)
def _to_numpy_array(self, quantity): def _to_numpy_array(self, quantity):
return np.array(quantity.m if quantity is not None else quantity) return np.array(quantity.m if quantity is not None else quantity)
def _get_relaxation_type(self): def _get_geometry_optimization_type(self):
sec_system = self.section_run.section_system sec_system = self.section_run.section_system
if not sec_system: if not sec_system:
return return
...@@ -45,8 +46,15 @@ class RelaxationNormalizer(Normalizer): ...@@ -45,8 +46,15 @@ class RelaxationNormalizer(Normalizer):
return 'static' return 'static'
else: else:
cell_init = self._to_numpy_array(sec_system[0].lattice_vectors) cell_init = sec_system[0].lattice_vectors
cell_final = self._to_numpy_array(sec_system[-1].lattice_vectors) cell_final = sec_system[-1].lattice_vectors
if cell_init is None:
cell_init = sec_system[0].simulation_cell
if cell_final is None:
cell_final = sec_system[-1].simulation_cell
cell_init = self._to_numpy_array(cell_init)
cell_final = self._to_numpy_array(cell_final)
cell_relaxation = compare_cell(cell_init, cell_final) cell_relaxation = compare_cell(cell_init, cell_final)
...@@ -62,23 +70,28 @@ class RelaxationNormalizer(Normalizer): ...@@ -62,23 +70,28 @@ class RelaxationNormalizer(Normalizer):
return 'ionic' return 'ionic'
def normalize(self): def normalize(self):
self.section = self.entry_archive.section_workflow.section_relaxation self.section = self.entry_archive.section_workflow.section_geometry_optimization
if not self.section: if not self.section:
self.section = self.entry_archive.section_workflow.m_create(Relaxation) self.section = self.entry_archive.section_workflow.m_create(GeometryOptimization)
if not self.section.relaxation_type: if not self.section.geometry_optimization_type:
self.section.relaxation_type = self._get_relaxation_type() try:
geometry_optimization_type = self._get_geometry_optimization_type()
self.section.geometry_optimization_type = geometry_optimization_type
except Exception:
pass
scc = self.section_run.section_single_configuration_calculation scc = self.section_run.section_single_configuration_calculation
if not self.section.final_calculation_ref: if not self.entry_archive.section_workflow.calculation_result_ref:
if scc: if scc:
self.section.final_calculation_ref = scc[-1] self.entry_archive.section_workflow.calculation_result_ref = scc[-1]
if not self.section.n_relaxation_steps: if not self.entry_archive.section_workflow.calculations_ref:
self.section.n_relaxation_steps = len(scc) if scc:
self.entry_archive.section_workflow.calculations_ref = scc
if not self.section.calculations_ref: if not self.section.optimization_steps:
self.section.calculations_ref = scc self.section.optimization_steps = len(scc)
if not self.section.final_energy_difference: if not self.section.final_energy_difference:
energies = [] energies = []
...@@ -115,7 +128,7 @@ class PhononNormalizer(Normalizer): ...@@ -115,7 +128,7 @@ class PhononNormalizer(Normalizer):
super().__init__(entry_archive) super().__init__(entry_archive)
def _get_n_imaginary_frequencies(self): def _get_n_imaginary_frequencies(self):
scc = self.entry_archive.section_run[0].section_single_configuration_calculation scc = self.section_run.section_single_configuration_calculation
sec_band = scc[0].section_k_band sec_band = scc[0].section_k_band
result = 0 result = 0
for band_segment in sec_band[0].section_k_band_segment: for band_segment in sec_band[0].section_k_band_segment:
...@@ -125,6 +138,16 @@ class PhononNormalizer(Normalizer): ...@@ -125,6 +138,16 @@ class PhononNormalizer(Normalizer):
def normalize(self): def normalize(self):
self.section = self.entry_archive.section_workflow.section_phonon self.section = self.entry_archive.section_workflow.section_phonon
scc = self.section_run.section_single_configuration_calculation
if not self.entry_archive.section_workflow.calculation_result_ref:
if scc:
self.entry_archive.section_workflow.calculation_result_ref = scc[-1]
if not self.entry_archive.section_workflow.calculations_ref:
if scc:
self.entry_archive.section_workflow.calculations_ref = scc
if not self.section: if not self.section:
self.section = self.entry_archive.section_workflow.m_create(Phonon) self.section = self.entry_archive.section_workflow.m_create(Phonon)
...@@ -144,9 +167,12 @@ class ElasticNormalizer(Normalizer): ...@@ -144,9 +167,12 @@ class ElasticNormalizer(Normalizer):
if spacegroup is None or order != 2: if spacegroup is None or order != 2:
return return
scc = self.entry_archive.section_run[-1].section_single_configuration_calculation[-1] scc = self.section_run.section_single_configuration_calculation[-1]
c = scc.x_elastic_2nd_order_constants_matrix c = scc.x_elastic_2nd_order_constants_matrix
if c is None:
return
# see Phys. Rev B 90, 224104 (2014) # see Phys. Rev B 90, 224104 (2014)
res = False res = False
if spacegroup <= 2: # Triclinic if spacegroup <= 2: # Triclinic
...@@ -189,7 +215,7 @@ class ElasticNormalizer(Normalizer): ...@@ -189,7 +215,7 @@ class ElasticNormalizer(Normalizer):
return res return res
def _get_maximum_fit_error(self): def _get_maximum_fit_error(self):
scc = self.entry_archive.section_run[-1].section_single_configuration_calculation[-1] scc = self.section_run.section_single_configuration_calculation[-1]
max_error = 0.0 max_error = 0.0
for diagram in scc.x_elastic_section_strain_diagrams: for diagram in scc.x_elastic_section_strain_diagrams:
...@@ -201,14 +227,66 @@ class ElasticNormalizer(Normalizer): ...@@ -201,14 +227,66 @@ class ElasticNormalizer(Normalizer):
def normalize(self): def normalize(self):
self.section = self.entry_archive.section_workflow.section_elastic self.section = self.entry_archive.section_workflow.section_elastic
scc = self.section_run.section_single_configuration_calculation
if not self.entry_archive.section_workflow.calculation_result_ref:
if scc:
self.entry_archive.section_workflow.calculation_result_ref = scc[-1]
if not self.entry_archive.section_workflow.calculations_ref:
if scc:
self.entry_archive.section_workflow.calculations_ref = scc
if not self.section: if not self.section:
self.section = self.entry_archive.section_workflow.m_create(Elastic) self.section = self.entry_archive.section_workflow.m_create(Elastic)
if self.section.is_mechanically_stable is None:
self.section.is_mechanically_stable = bool(self._resolve_mechanical_stability()) self.section.is_mechanically_stable = bool(self._resolve_mechanical_stability())
if self.section.fitting_error_maximum is None:
self.section.fitting_error_maximum = self._get_maximum_fit_error() self.section.fitting_error_maximum = self._get_maximum_fit_error()
class MolecularDynamicsNormalizer(Normalizer):
def __init__(self, entry_archive):
super().__init__(entry_archive)
def _is_with_thermodynamics(self):
scc = self.section_run.section_single_configuration_calculation
res = False
if scc:
res = scc[-1].temperature is not None
return res
def _is_with_trajectory(self):
sec_system = self.section_run.section_system
res = False
if sec_system:
res = sec_system[-1].atom_positions is not None
return res
def normalize(self):
self.section = self.entry_archive.section_workflow.section_molecular_dynamics
scc = self.section_run.section_single_configuration_calculation
if not self.entry_archive.section_workflow.calculation_result_ref:
if scc:
self.entry_archive.section_workflow.calculation_result_ref = scc[-1]
if not self.entry_archive.section_workflow.calculations_ref:
if scc:
self.entry_archive.section_workflow.calculations_ref = scc
if not self.section:
self.section = self.entry_archive.section_workflow.m_create(MolecularDynamics)
if self.section.with_thermodynamics is None:
self.section.with_thermodynamics = self._is_with_thermodynamics()
if self.section.with_trajectory is None:
self.section.with_trajectory = self._is_with_trajectory()
class WorkflowNormalizer(Normalizer): class WorkflowNormalizer(Normalizer):
''' '''
This normalizer performs all produces a section all data necessary for the Optimade API. This normalizer performs all produces a section all data necessary for the Optimade API.
...@@ -216,6 +294,43 @@ class WorkflowNormalizer(Normalizer): ...@@ -216,6 +294,43 @@ class WorkflowNormalizer(Normalizer):
''' '''
def __init__(self, entry_archive): def __init__(self, entry_archive):
super().__init__(entry_archive) super().__init__(entry_archive)
self._elastic_programs = ['elastic']
self._phonon_programs = ['phonopy']
def _resolve_workflow_type_vasp(self):
ibrion = self.section_run.section_method[0].x_vasp_incarOut_IBRION
if ibrion == 0:
workflow_type = "molecular_dynamics"
else:
workflow_type = "geometry_optimization"
return workflow_type
def _resolve_workflow_type(self):
# first get it from section_sampling_method
workflow_type = None
sec_sampling_method = self.section_run.section_sampling_method
if sec_sampling_method:
workflow_type = sec_sampling_method[-1].sampling_method
# resolve it from parser
if not workflow_type:
program_name = self.section_run.program_name
if program_name:
program_name = program_name.lower()
if program_name == 'vasp':
workflow_type = self._resolve_workflow_type_vasp()
elif program_name == 'elastic':
workflow_type = 'elastic'
elif program_name == 'lammps':
workflow_type = 'molecular_dynamics'
elif program_name == 'phonopy':
workflow_type = 'phonon'
return workflow_type
def normalize(self, logger=None) -> None: def normalize(self, logger=None) -> None:
# Setup logger # Setup logger
...@@ -226,31 +341,30 @@ class WorkflowNormalizer(Normalizer): ...@@ -226,31 +341,30 @@ class WorkflowNormalizer(Normalizer):
if self.section_run is None: if self.section_run is None:
return return
workflow = self.entry_archive.section_workflow
if not workflow:
workflow = self.entry_archive.m_create(Workflow)
workflow_type = None workflow_type = None
if self.entry_archive.section_workflow: if self.entry_archive.section_workflow:
workflow_type = self.entry_archive.section_workflow.workflow_type workflow_type = self.entry_archive.section_workflow.workflow_type
if not workflow_type: if not workflow_type:
sec_sampling_method = self.section_run.section_sampling_method workflow_type = self._resolve_workflow_type()
if sec_sampling_method:
# TODO imho geometry_optimization is not an appropriate name
# if workflow_type == 'geometry_optimization':
# workflow_type = 'relaxation'
workflow_type = sec_sampling_method[-1].sampling_method
if not workflow_type: if not workflow_type:
return return
workflow = self.entry_archive.section_workflow
if not workflow:
workflow = self.entry_archive.m_create(Workflow)
workflow.workflow_type = workflow_type workflow.workflow_type = workflow_type
if workflow.workflow_type in ['geometry_optimization', 'relaxation']: if workflow.workflow_type == 'geometry_optimization':
RelaxationNormalizer(self.entry_archive).normalize() GeometryOptimizationNormalizer(self.entry_archive).normalize()
elif workflow.workflow_type == 'phonon': elif workflow.workflow_type == 'phonon':
PhononNormalizer(self.entry_archive).normalize() PhononNormalizer(self.entry_archive).normalize()
elif workflow.workflow_type == 'elastic': elif workflow.workflow_type == 'elastic':
ElasticNormalizer(self.entry_archive).normalize() ElasticNormalizer(self.entry_archive).normalize()
elif workflow.workflow_type == 'molecular_dynamics':
MolecularDynamicsNormalizer(self.entry_archive).normalize()
...@@ -28,7 +28,7 @@ from hashlib import md5 ...@@ -28,7 +28,7 @@ from hashlib import md5
from nomad.app.common import rfc3339DateTime from nomad.app.common import rfc3339DateTime
from nomad.app.api.auth import generate_upload_token from nomad.app.api.auth import generate_upload_token
from nomad import search, parsing, files, config, utils, infrastructure from nomad import search, files, config, utils, infrastructure
from nomad.metainfo import search_extension from nomad.metainfo import search_extension
from nomad.files import UploadFiles, PublicUploadFiles from nomad.files import UploadFiles, PublicUploadFiles
from nomad.processing import Upload, Calc, SUCCESS from nomad.processing import Upload, Calc, SUCCESS
...@@ -781,7 +781,7 @@ class TestArchive(UploadFilesBasedTests): ...@@ -781,7 +781,7 @@ class TestArchive(UploadFilesBasedTests):
'$and': [ '$and': [
{'dft.code_name': 'VASP'}, {'dft.code_name': 'VASP'},
{'$gte': {'n_atoms': 3}}, {'$gte': {'n_atoms': 3}},
{'$lte': {'dft.workflow.section_relaxation.final_energy_difference': 1e-24}} {'$lte': {'dft.workflow.section_geometry_optimization.final_energy_difference': 1e-24}}
]}, 0, id='client-example') ]}, 0, id='client-example')
]) ])
def test_post_archive_query(self, api, example_upload, query_expression, nresults): def test_post_archive_query(self, api, example_upload, query_expression, nresults):
...@@ -848,7 +848,7 @@ class TestMetainfo(): ...@@ -848,7 +848,7 @@ class TestMetainfo():
class TestRepo(): class TestRepo():
@pytest.fixture(scope='class') @pytest.fixture(scope='class')
def example_elastic_calcs( def example_elastic_calcs(
self, elastic_infra, raw_files_infra, normalized: parsing.Backend, self, elastic_infra, raw_files_infra, normalized,
test_user: User, other_test_user: User): test_user: User, other_test_user: User):
clear_elastic(elastic_infra) clear_elastic(elastic_infra)
......
Dst01, Lagrangian strain = ( eta, eta, eta, 0.0, 0.0, 0.0)
Dst01_01, eta = -0.05
V1 --=> 0.4743416490258656 0.4743416490258656 0.0000000000000000
V2 --=> 0.4743416490258656 0.0000000000000000 0.4743416490258656
V3 --=> 0.0000000000000000 0.4743416490258656 0.4743416490258656
Dst01_02, eta = -0.04
V1 --=> 0.4795831523313500 0.4795831523313500 0.0000000000000000
V2 --=> 0.4795831523313500 0.0000000000000000 0.4795831523313500
V3 --=> 0.0000000000000000 0.4795831523313500 0.4795831523313500
Dst01_03, eta = -0.03
V1 --=> 0.4847679857418169 0.4847679857418169 0.0000000000000000
V2 --=> 0.4847679857418169 0.0000000000000000 0.4847679857418169
V3 --=> 0.0000000000000000 0.4847679857418169 0.4847679857418169
Dst01_04, eta = -0.02
V1 --=> 0.4898979485569774 0.4898979485569774 0.0000000000000000
V2 --=> 0.4898979485569774 0.0000000000000000 0.4898979485569774
V3 --=> 0.0000000000000000 0.4898979485569774 0.4898979485569774
Dst01_05, eta = -0.01
V1 --=> 0.4949747468308403 0.4949747468308403 0.0000000000000000
V2 --=> 0.4949747468308403 0.0000000000000000 0.4949747468308403
V3 --=> 0.0000000000000000 0.4949747468308403 0.4949747468308403
Dst01_06, eta = 0.0001
V1 --=> 0.5000499975002500 0.5000499975002500 0.0000000000000000
V2 --=> 0.5000499975002500 0.0000000000000000 0.5000499975002500
V3 --=> 0.0000000000000000 0.5000499975002500 0.5000499975002500
Dst01_07, eta = 0.01
V1 --=> 0.50