From 7b860838172ea39a4d0bf144a2868f3c2aad0f8f Mon Sep 17 00:00:00 2001 From: jrudz Date: Wed, 9 Apr 2025 14:37:15 +0200 Subject: [PATCH 01/25] connect to calculations and systems through tasks --- simulationworkflowschema/equation_of_state.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/simulationworkflowschema/equation_of_state.py b/simulationworkflowschema/equation_of_state.py index edda544..c0444bf 100644 --- a/simulationworkflowschema/equation_of_state.py +++ b/simulationworkflowschema/equation_of_state.py @@ -159,7 +159,20 @@ def normalize(self, archive, logger): self.outputs.append(Link(name=WORKFLOW_RESULTS_NAME, section=self.results)) if not self._calculations: - return + # try to get calculations from tasks (in case of instantiation from workflow yaml) + try: + self._calculations = [ + task.task.results.calculations_ref[0] for task in self.tasks + ] + except Exception: + pass + + if not self._systems: + # try to get systems from tasks (in case of instantiation from workflow yaml) + try: + self._systems = [calc.system_ref for calc in self._calculations] + except Exception: + pass if self.results.energies is None: try: From 0e91b8eb9bb2de6fab6b4b2ba9d92d6396c6bbf6 Mon Sep 17 00:00:00 2001 From: Alvin Noe Ladines Date: Tue, 29 Apr 2025 13:07:16 +0200 Subject: [PATCH 02/25] Create section run --- simulationworkflowschema/equation_of_state.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/simulationworkflowschema/equation_of_state.py b/simulationworkflowschema/equation_of_state.py index c0444bf..62849aa 100644 --- a/simulationworkflowschema/equation_of_state.py +++ b/simulationworkflowschema/equation_of_state.py @@ -30,6 +30,8 @@ WORKFLOW_METHOD_NAME, WORKFLOW_RESULTS_NAME, ) +from runschema.run import Run, Program +from runschema.system import System class EquationOfStateMethod(SimulationWorkflowMethod): @@ -224,3 +226,9 @@ def normalize(self, archive, logger): self.results.eos_fit.append(eos_fit) except Exception: self.logger.warning('EOS fit not succesful.') + + # necessary to trigger results normalization + if not archive.run: + run = Run(program=Program()) + run.system.append(System(systems_ref=self._systems)) + archive.run.append(run) From 57e491fc9ab2cdeb263d20950a5fb2b4a024be73 Mon Sep 17 00:00:00 2001 From: jrudz Date: Wed, 30 Apr 2025 16:25:07 +0200 Subject: [PATCH 03/25] added calculation and method --- simulationworkflowschema/equation_of_state.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/simulationworkflowschema/equation_of_state.py b/simulationworkflowschema/equation_of_state.py index 62849aa..51dff19 100644 --- a/simulationworkflowschema/equation_of_state.py +++ b/simulationworkflowschema/equation_of_state.py @@ -30,7 +30,7 @@ WORKFLOW_METHOD_NAME, WORKFLOW_RESULTS_NAME, ) -from runschema.run import Run, Program +from runschema.run import Run, Program, Method from runschema.system import System @@ -225,10 +225,14 @@ def normalize(self, archive, logger): ) self.results.eos_fit.append(eos_fit) except Exception: - self.logger.warning('EOS fit not succesful.') + logger.warning('EOS fit not succesful.') # necessary to trigger results normalization if not archive.run: run = Run(program=Program()) - run.system.append(System(systems_ref=self._systems)) + # run.system.append(System(systems_ref=[self._systems[0]])) + run.system.append(self._systems[0]) + run.calculation.append(self._calculations[0]) + logger.warning(f'adding method {self.tasks[0].task.method}') + run.method.append(self.tasks[0].task.method) archive.run.append(run) From 657d6803ea2e69a3b5a9f6035baa35f543364e80 Mon Sep 17 00:00:00 2001 From: jrudz Date: Wed, 30 Apr 2025 16:26:08 +0200 Subject: [PATCH 04/25] remove logger debug warning --- simulationworkflowschema/equation_of_state.py | 1 - 1 file changed, 1 deletion(-) diff --git a/simulationworkflowschema/equation_of_state.py b/simulationworkflowschema/equation_of_state.py index 51dff19..686e8d3 100644 --- a/simulationworkflowschema/equation_of_state.py +++ b/simulationworkflowschema/equation_of_state.py @@ -233,6 +233,5 @@ def normalize(self, archive, logger): # run.system.append(System(systems_ref=[self._systems[0]])) run.system.append(self._systems[0]) run.calculation.append(self._calculations[0]) - logger.warning(f'adding method {self.tasks[0].task.method}') run.method.append(self.tasks[0].task.method) archive.run.append(run) From c27fd1d67984461d314a5eecdf4af197c7ceb923 Mon Sep 17 00:00:00 2001 From: Alvin Noe Ladines Date: Tue, 6 May 2025 15:25:05 +0200 Subject: [PATCH 05/25] Add run sub-sections --- simulationworkflowschema/equation_of_state.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/simulationworkflowschema/equation_of_state.py b/simulationworkflowschema/equation_of_state.py index 686e8d3..6c706ab 100644 --- a/simulationworkflowschema/equation_of_state.py +++ b/simulationworkflowschema/equation_of_state.py @@ -229,9 +229,9 @@ def normalize(self, archive, logger): # necessary to trigger results normalization if not archive.run: + task0_archive = self.tasks[0].task.m_root() run = Run(program=Program()) - # run.system.append(System(systems_ref=[self._systems[0]])) - run.system.append(self._systems[0]) - run.calculation.append(self._calculations[0]) - run.method.append(self.tasks[0].task.method) + run.system.extend(task0_archive.run[0].system) + run.method.extend(task0_archive.run[0].method) + run.calculation.extend(task0_archive.run[0].calculation) archive.run.append(run) From 1f6d7617290e103b3f2921f800f518a77b7bad4b Mon Sep 17 00:00:00 2001 From: jrudz Date: Wed, 7 May 2025 15:17:21 +0200 Subject: [PATCH 06/25] check for geom-opt --- simulationworkflowschema/equation_of_state.py | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/simulationworkflowschema/equation_of_state.py b/simulationworkflowschema/equation_of_state.py index 6c706ab..f5c0f90 100644 --- a/simulationworkflowschema/equation_of_state.py +++ b/simulationworkflowschema/equation_of_state.py @@ -30,6 +30,7 @@ WORKFLOW_METHOD_NAME, WORKFLOW_RESULTS_NAME, ) +from .geometry_optimization import GeometryOptimization from runschema.run import Run, Program, Method from runschema.system import System @@ -160,17 +161,24 @@ def normalize(self, archive, logger): self.results = EquationOfStateResults() self.outputs.append(Link(name=WORKFLOW_RESULTS_NAME, section=self.results)) + task0_archive = self.tasks[0].task.m_root() + tasks = self.tasks[:] + if task0_archive.workflow2: + # remove the first task if it is a GeometryOptimization (not part of the EOS) + if isinstance(task0_archive.workflow2, GeometryOptimization): + tasks.pop(0) + if not self._calculations: # try to get calculations from tasks (in case of instantiation from workflow yaml) try: self._calculations = [ - task.task.results.calculations_ref[0] for task in self.tasks + task.task.results.calculations_ref[0] for task in tasks ] except Exception: pass if not self._systems: - # try to get systems from tasks (in case of instantiation from workflow yaml) + # try to get systems from calculations (in case of instantiation from workflow yaml) try: self._systems = [calc.system_ref for calc in self._calculations] except Exception: @@ -229,9 +237,13 @@ def normalize(self, archive, logger): # necessary to trigger results normalization if not archive.run: - task0_archive = self.tasks[0].task.m_root() run = Run(program=Program()) - run.system.extend(task0_archive.run[0].system) - run.method.extend(task0_archive.run[0].method) - run.calculation.extend(task0_archive.run[0].calculation) + try: + # assuming the final structure is the relevant one, e.g., from a GO + run.system.extend([task0_archive.run[0].system[-1]]) + run.method.extend(task0_archive.run[0].method) + run.calculation.extend([task0_archive.run[0].calculation[-1]]) + except Exception: + logger.warning('Failed to link structure from first task archive. ') + return archive.run.append(run) From 88bca74c09035295e2184a96982f4b291576ea7f Mon Sep 17 00:00:00 2001 From: jrudz Date: Thu, 8 May 2025 21:21:27 +0200 Subject: [PATCH 07/25] debugging tests --- tests/test_simulationworkflowschema.py | 1790 ++++++++++++------------ 1 file changed, 896 insertions(+), 894 deletions(-) diff --git a/tests/test_simulationworkflowschema.py b/tests/test_simulationworkflowschema.py index cb6005a..307dec4 100644 --- a/tests/test_simulationworkflowschema.py +++ b/tests/test_simulationworkflowschema.py @@ -68,639 +68,639 @@ def test_no_workflow(): assert not entry_archive.workflow2.results.calculations_ref -def test_single_point_workflow(): - entry_archive = load_archive('tests/data/single_point.archive.json') - sec_workflow = entry_archive.workflow2 - assert sec_workflow.method.method == 'DFT' - assert sec_workflow.results.n_scf_steps == 9 - assert sec_workflow.results.final_scf_energy_difference > 0 - assert sec_workflow.results.dos is not None - assert sec_workflow.results.band_structure is None - assert sec_workflow.results.eigenvalues is not None - assert sec_workflow.results.density_charge is None - assert sec_workflow.results.spectra is None - assert sec_workflow.results.is_converged - - -# TODO do not skip nomad-lab>=3.15 -@pytest.mark.skip -def test_gw_workflow(gw_workflow): - """Testing GW workflow (DFT+GW) entry""" - workflow = gw_workflow.workflow2 - assert workflow.name == 'DFT+GW' - assert workflow.method.gw_method_ref.type == 'G0W0' - assert workflow.method.electrons_representation.type == 'plane waves' - assert workflow.method.starting_point.name == 'GGA_X_PBE' - results = gw_workflow.results - assert results.method.method_name == 'GW' - assert results.method.workflow_name == 'DFT+GW' - assert results.method.simulation.program_name == 'VASP' - assert results.method.simulation.program_version == '4.6.35' - assert results.method.simulation.gw.type == 'G0W0' - assert results.method.simulation.gw.starting_point_type == 'GGA' - assert results.method.simulation.gw.starting_point_names == ['GGA_X_PBE'] - assert results.method.simulation.gw.basis_set_type == 'plane waves' - assert not results.properties.electronic.band_gap - assert not results.properties.electronic.greens_functions_electronic - assert len(results.properties.electronic.dos_electronic_new) == 2 - assert len(results.properties.electronic.band_structure_electronic) == 2 - assert results.properties.electronic.dos_electronic_new[0].label == 'DFT' - assert results.properties.electronic.dos_electronic_new[1].label == 'GW' - - -# TODO do not skip nomad-lab>=3.15 -@pytest.mark.skip -def test_dmft_workflow(dmft_workflow): - """Testing DMFT workflow entry""" - workflow = dmft_workflow.workflow2 - assert workflow.name == 'TB+DMFT' - assert not workflow.method.tb_method_ref.wannier.is_maximally_localized - assert workflow.method.dmft_method_ref.n_impurities == 1 - assert workflow.method.dmft_method_ref.n_correlated_orbitals[0] == 3 - assert workflow.method.dmft_method_ref.n_electrons[0] == 1.0 - assert workflow.method.dmft_method_ref.inverse_temperature.magnitude == 60.0 - assert workflow.method.dmft_method_ref.magnetic_state == 'paramagnetic' - assert workflow.method.dmft_method_ref.impurity_solver == 'CT-HYB' - results = dmft_workflow.results - assert results.method.method_name == 'DMFT' - assert results.method.workflow_name == 'TB+DMFT' - assert results.method.simulation.program_name == 'w2dynamics' - assert results.method.simulation.dmft.impurity_solver_type == 'CT-HYB' - assert results.method.simulation.dmft.inverse_temperature.magnitude == 60.0 - assert results.method.simulation.dmft.magnetic_state == 'paramagnetic' - assert results.method.simulation.dmft.u.magnitude == 4.0e-19 - assert results.method.simulation.dmft.jh.magnitude == 0.6e-19 - assert results.m_xpath('properties.electronic.band_gap') - assert len(results.properties.electronic.band_gap) == 1 - assert results.properties.electronic.band_gap[0].label == 'TB' - assert results.m_xpath('properties.electronic.band_structure_electronic') - assert len(results.properties.electronic.band_structure_electronic) == 1 - # TODO check why this testing is not passing - # * conftest seems to not be able to normalize the archive_dmft for the Greens functions, despite self_energy_iw is defined. - # assert results.m_xpath('properties.electronic.greens_function_electronic') - - -# TODO do not skip nomad-lab>=3.15 -@pytest.mark.skip -def test_maxent_workflow(maxent_workflow): - """Testing MaxEnt workflow entry""" - workflow = maxent_workflow.workflow2 - assert workflow.name == 'DMFT+MaxEnt' - assert workflow.method.dmft_method_ref.n_impurities == 1 - assert workflow.method.dmft_method_ref.n_correlated_orbitals[0] == 3 - assert workflow.method.dmft_method_ref.n_electrons[0] == 1.0 - assert workflow.method.dmft_method_ref.inverse_temperature.magnitude == 60.0 - assert workflow.method.dmft_method_ref.magnetic_state == 'paramagnetic' - assert workflow.method.dmft_method_ref.impurity_solver == 'CT-HYB' - assert workflow.method.maxent_method_ref - results = maxent_workflow.results - assert results.method.method_name == 'DMFT' - assert results.method.workflow_name == 'DMFT+MaxEnt' - assert results.method.simulation.program_name == 'w2dynamics' - assert results.method.simulation.dmft.impurity_solver_type == 'CT-HYB' - assert results.method.simulation.dmft.inverse_temperature.magnitude == 60.0 - assert results.method.simulation.dmft.magnetic_state == 'paramagnetic' - assert results.method.simulation.dmft.u.magnitude == 4.0e-19 - assert results.method.simulation.dmft.jh.magnitude == 0.6e-19 - assert results.method.simulation.dmft.analytical_continuation == 'MaxEnt' - assert results.m_xpath('properties.electronic.dos_electronic_new') - assert len(results.properties.electronic.dos_electronic_new) == 1 - assert results.m_xpath('properties.electronic.greens_functions_electronic') - assert len(results.properties.electronic.greens_functions_electronic) == 2 - assert results.properties.electronic.greens_functions_electronic[0].label == 'DMFT' - assert ( - results.properties.electronic.greens_functions_electronic[1].label == 'MaxEnt' - ) - - -def test_bse_workflow(bse_workflow): - """Testing BSE workflow (Photon1+Photon2) entry""" - workflow = bse_workflow.workflow2 - assert workflow.name == 'BSE' - assert len(workflow.inputs) == 2 - assert workflow.inputs[0].name == 'Input structure' - assert workflow.inputs[1].name == 'Input BSE methodology' - assert ( - len(workflow.outputs) == 2 - and len(workflow.outputs) == workflow.results.n_polarizations - ) - assert len(workflow.tasks) == 2 - assert workflow.method.bse_method_ref.type == 'Singlet' - assert workflow.method.bse_method_ref.solver == 'Lanczos-Haydock' - results = bse_workflow.results - assert results.method.method_name == 'BSE' - assert results.method.workflow_name == 'BSE' - assert results.method.simulation.program_name == 'VASP' - assert results.method.simulation.program_version == '4.6.35' - assert results.method.simulation.bse.type == 'Singlet' - assert results.method.simulation.bse.solver == 'Lanczos-Haydock' - assert results.properties.spectroscopic - spectra = results.properties.spectroscopic.spectra - assert len(spectra) == 2 - assert spectra[0].type == 'XAS' - assert spectra[0].label == 'computation' - assert spectra[0].n_energies == 11 - assert spectra[0].energies[3].to('eV').magnitude == approx(3.0) - assert spectra[0].intensities[3] == approx(130.0) - assert spectra[0].intensities_units == 'F/m' - assert spectra[0].provenance and spectra[1].provenance - assert spectra[0].provenance != spectra[1].provenance - - -# TODO do not skip nomad-lab>=3.15 -@pytest.mark.skip -def test_xs_workflow(xs_workflow): - """Testing XS workflow (DFT+BSEworkflow) entry""" - workflow = xs_workflow.workflow2 - assert workflow.name == 'XS' - assert len(workflow.inputs) == 1 - assert workflow.inputs[0].name == 'Input structure' - assert len(workflow.outputs) == 2 - assert len(workflow.tasks) == 2 - assert workflow.tasks[0].name == 'DFT' and workflow.tasks[1].name == 'BSE 1' - assert ( - workflow.results.dft_outputs.dos - and workflow.results.dft_outputs.band_structure - and workflow.results.spectra - ) - results = xs_workflow.results - assert results.method.method_name == 'BSE' - assert results.method.workflow_name == 'XS' - assert results.method.simulation.program_name == 'VASP' - assert results.method.simulation.program_version == '4.6.35' - assert results.method.simulation.bse.type == 'Singlet' - assert results.method.simulation.bse.solver == 'Lanczos-Haydock' - assert results.method.simulation.bse.starting_point_type == 'GGA' - assert results.method.simulation.bse.starting_point_names == ['GGA_X_PBE'] - assert results.method.simulation.bse.basis_set_type == 'plane waves' - assert results.properties.electronic and results.properties.spectroscopic - assert results.properties.electronic.dos_electronic_new[0].label == 'DFT' - assert len(results.properties.spectroscopic.spectra) == 2 - assert ( - results.properties.spectroscopic.spectra[0].provenance - != results.properties.spectroscopic.spectra[1].provenance - ) - - -def test_geometry_optimization_workflow(): - entry_archive = load_archive('tests/data/geometry_optimization.archive.json') - sec_workflow = entry_archive.workflow2 - assert sec_workflow.method.type == 'cell_shape' - assert sec_workflow.results.calculation_result_ref.m_def.name == 'Calculation' - assert sec_workflow.results.final_energy_difference.to('eV').magnitude == approx( - 0.00012532 - ) - assert sec_workflow.results.optimization_steps == 3 - assert sec_workflow.results.final_force_maximum > 0.0 - assert sec_workflow.results.is_converged_geometry - - tasks = sec_workflow.tasks - assert len(tasks) == len(entry_archive.run[0].calculation) - assert ( - tasks[0].inputs[0].section.m_proxy_resolve() == entry_archive.run[0].method[0] - ) - assert tasks[-1].outputs[0].section == entry_archive.run[0].calculation[-1] - - -def test_elastic_workflow(): - entry_archive = load_archive('tests/data/elastic.archive.json') - sec_workflow = entry_archive.workflow2 - sec_workflow.results.calculation_result_ref.m_def.name == 'Calculation' - sec_workflow.method.calculation_method == 'energy' - sec_workflow.method.elastic_constants_order == 2 - sec_workflow.results.is_mechanically_stable - sec_workflow.method.fitting_error_maximum > 0.0 - sec_workflow.method.strain_maximum > 0.0 - - -def test_phonon_workflow(): - entry_archive = load_archive('tests/data/phonon.archive.json') - - sec_workflow = entry_archive.workflow2 - assert sec_workflow.results.calculation_result_ref.m_def.name == 'Calculation' - assert sec_workflow.method.force_calculator == 'fhi-aims' - assert sec_workflow.method.mesh_density > 0.0 - assert sec_workflow.results.n_imaginary_frequencies > 0 - assert not sec_workflow.method.random_displacements - assert not sec_workflow.method.with_non_analytic_correction - assert not sec_workflow.method.with_grueneisen_parameters - - -def test_molecular_dynamics_workflow(): - entry_archive = load_archive('tests/data/molecular_dynamics.archive.json') - sec_workflow = entry_archive.workflow2 - sec_workflow.results.calculation_result_ref.m_def.name == 'Calculation' - assert sec_workflow.results.finished_normally - assert sec_workflow.results.trajectory - - -def test_rdf_and_msd(): - entry_archive = load_archive('tests/data/rdf_and_msd.archive.json') - - sec_workflow = entry_archive.workflow2 - section_md = sec_workflow.results - - assert section_md.radial_distribution_functions[0].type == 'molecular' - assert section_md.radial_distribution_functions[0].n_smooth == 2 - assert section_md.radial_distribution_functions[0].variables_name[0] == 'distance' - - assert ( - section_md.radial_distribution_functions[0] - .radial_distribution_function_values[0] - .label - == '0-0' - ) - assert ( - section_md.radial_distribution_functions[0] - .radial_distribution_function_values[0] - .n_bins - == 198 - ) - assert section_md.radial_distribution_functions[ - 0 - ].radial_distribution_function_values[0].bins[122].magnitude == approx( - 6.923255643844605 * 10 ** (-10) - ) - assert ( - section_md.radial_distribution_functions[0] - .radial_distribution_function_values[0] - .bins[122] - .units - == 'meter' - ) - assert section_md.radial_distribution_functions[ - 0 - ].radial_distribution_function_values[0].value[96] == approx(0.0) - assert ( - section_md.radial_distribution_functions[0] - .radial_distribution_function_values[0] - .frame_start - == 0 - ) - assert ( - section_md.radial_distribution_functions[0] - .radial_distribution_function_values[0] - .frame_end - == 40 - ) - - assert ( - section_md.radial_distribution_functions[0] - .radial_distribution_function_values[3] - .label - == '0-0' - ) - assert ( - section_md.radial_distribution_functions[0] - .radial_distribution_function_values[3] - .n_bins - == 198 - ) - assert section_md.radial_distribution_functions[ - 0 - ].radial_distribution_function_values[3].bins[65].magnitude == approx( - 3.727906885147095 * 10 ** (-10) - ) - assert ( - section_md.radial_distribution_functions[0] - .radial_distribution_function_values[3] - .bins[65] - .units - == 'meter' - ) - assert section_md.radial_distribution_functions[ - 0 - ].radial_distribution_function_values[3].value[52] == approx(0.0) - assert ( - section_md.radial_distribution_functions[0] - .radial_distribution_function_values[3] - .frame_start - == 120 - ) - assert ( - section_md.radial_distribution_functions[0] - .radial_distribution_function_values[3] - .frame_end - == 201 - ) - - assert ( - section_md.radial_distribution_functions[0] - .radial_distribution_function_values[5] - .label - == '1-0' - ) - assert ( - section_md.radial_distribution_functions[0] - .radial_distribution_function_values[5] - .n_bins - == 198 - ) - assert section_md.radial_distribution_functions[ - 0 - ].radial_distribution_function_values[5].bins[102].magnitude == approx( - 5.802080640792847 * 10 ** (-10) - ) - assert ( - section_md.radial_distribution_functions[0] - .radial_distribution_function_values[5] - .bins[102] - .units - == 'meter' - ) - assert section_md.radial_distribution_functions[ - 0 - ].radial_distribution_function_values[5].value[55] == approx(0.0) - assert ( - section_md.radial_distribution_functions[0] - .radial_distribution_function_values[5] - .frame_start - == 40 - ) - assert ( - section_md.radial_distribution_functions[0] - .radial_distribution_function_values[5] - .frame_end - == 201 - ) - - assert ( - section_md.radial_distribution_functions[0] - .radial_distribution_function_values[10] - .label - == '1-1' - ) - assert ( - section_md.radial_distribution_functions[0] - .radial_distribution_function_values[10] - .n_bins - == 198 - ) - assert section_md.radial_distribution_functions[ - 0 - ].radial_distribution_function_values[10].bins[44].magnitude == approx( - 2.550673131942749 * 10 ** (-10) - ) - assert ( - section_md.radial_distribution_functions[0] - .radial_distribution_function_values[10] - .bins[44] - .units - == 'meter' - ) - assert section_md.radial_distribution_functions[ - 0 - ].radial_distribution_function_values[10].value[101] == approx(1.4750986777470825) - assert ( - section_md.radial_distribution_functions[0] - .radial_distribution_function_values[10] - .frame_start - == 80 - ) - assert ( - section_md.radial_distribution_functions[0] - .radial_distribution_function_values[10] - .frame_end - == 201 - ) - - assert section_md.mean_squared_displacements[0].type == 'molecular' - assert section_md.mean_squared_displacements[0].direction == 'xyz' - - assert ( - section_md.mean_squared_displacements[0] - .mean_squared_displacement_values[0] - .label - == '0' - ) - assert ( - section_md.mean_squared_displacements[0] - .mean_squared_displacement_values[0] - .n_times - == 54 - ) - assert section_md.mean_squared_displacements[0].mean_squared_displacement_values[ - 0 - ].times[13].magnitude == approx(1.3 * 10 ** (-12)) - assert ( - section_md.mean_squared_displacements[0] - .mean_squared_displacement_values[0] - .times[13] - .units - == 'second' - ) - assert section_md.mean_squared_displacements[0].mean_squared_displacement_values[ - 0 - ].value[32].magnitude == approx(8.98473539965496 * 10 ** (-19)) - assert ( - section_md.mean_squared_displacements[0] - .mean_squared_displacement_values[0] - .value[32] - .units - == 'meter^2' - ) - assert section_md.mean_squared_displacements[0].mean_squared_displacement_values[ - 0 - ].diffusion_constant.value.magnitude == approx(6.09812270414572 * 10 ** (-8)) - assert ( - section_md.mean_squared_displacements[0] - .mean_squared_displacement_values[0] - .diffusion_constant.value.units - == 'meter^2/second' - ) - assert ( - section_md.mean_squared_displacements[0] - .mean_squared_displacement_values[0] - .diffusion_constant.error_type - == 'Pearson correlation coefficient' - ) - assert section_md.mean_squared_displacements[0].mean_squared_displacement_values[ - 0 - ].diffusion_constant.errors == approx(0.9924847048341159) - - assert ( - section_md.mean_squared_displacements[0] - .mean_squared_displacement_values[1] - .label - == '1' - ) - assert ( - section_md.mean_squared_displacements[0] - .mean_squared_displacement_values[1] - .n_times - == 54 - ) - assert section_md.mean_squared_displacements[0].mean_squared_displacement_values[ - 1 - ].times[13].magnitude == approx(1.3 * 10 ** (-12)) - assert ( - section_md.mean_squared_displacements[0] - .mean_squared_displacement_values[1] - .times[13] - .units - == 'second' - ) - assert section_md.mean_squared_displacements[0].mean_squared_displacement_values[ - 1 - ].value[32].magnitude == approx(8.448369705677565 * 10 ** (-19)) - assert ( - section_md.mean_squared_displacements[0] - .mean_squared_displacement_values[1] - .value[32] - .units - == 'meter^2' - ) - assert section_md.mean_squared_displacements[0].mean_squared_displacement_values[ - 1 - ].diffusion_constant.value.magnitude == approx(5.094072039759048 * 10 ** (-8)) - assert ( - section_md.mean_squared_displacements[0] - .mean_squared_displacement_values[1] - .diffusion_constant.value.units - == 'meter^2/second' - ) - assert ( - section_md.mean_squared_displacements[0] - .mean_squared_displacement_values[1] - .diffusion_constant.error_type - == 'Pearson correlation coefficient' - ) - assert section_md.mean_squared_displacements[0].mean_squared_displacement_values[ - 1 - ].diffusion_constant.errors == approx(0.9965870174917716) - - -def test_rdf_2(): - entry_archive = load_archive('tests/data/rdf_2.archive.json') - - sec_workflow = entry_archive.workflow2 - section_md = sec_workflow.results - - assert section_md.radial_distribution_functions[0].type == 'molecular' - assert section_md.radial_distribution_functions[0].n_smooth == 2 - assert section_md.radial_distribution_functions[0].variables_name[0] == 'distance' - - assert ( - section_md.radial_distribution_functions[0] - .radial_distribution_function_values[0] - .label - == 'SOL-Protein' - ) - assert ( - section_md.radial_distribution_functions[0] - .radial_distribution_function_values[0] - .n_bins - == 198 - ) - assert section_md.radial_distribution_functions[ - 0 - ].radial_distribution_function_values[0].bins[122].magnitude == approx( - 7.624056451320648 * 10 ** (-10) - ) - assert ( - section_md.radial_distribution_functions[0] - .radial_distribution_function_values[0] - .bins[122] - .units - == 'meter' - ) - assert section_md.radial_distribution_functions[ - 0 - ].radial_distribution_function_values[0].value[96] == approx(1.093694948374587) - assert ( - section_md.radial_distribution_functions[0] - .radial_distribution_function_values[0] - .frame_start - == 0 - ) - assert ( - section_md.radial_distribution_functions[0] - .radial_distribution_function_values[0] - .frame_end - == 2 - ) - - assert ( - section_md.radial_distribution_functions[0] - .radial_distribution_function_values[1] - .label - == 'SOL-SOL' - ) - assert ( - section_md.radial_distribution_functions[0] - .radial_distribution_function_values[1] - .n_bins - == 198 - ) - assert section_md.radial_distribution_functions[ - 0 - ].radial_distribution_function_values[1].bins[102].magnitude == approx( - 6.389391438961029 * 10 ** (-10) - ) - assert ( - section_md.radial_distribution_functions[0] - .radial_distribution_function_values[1] - .bins[102] - .units - == 'meter' - ) - assert section_md.radial_distribution_functions[ - 0 - ].radial_distribution_function_values[1].value[55] == approx(0.8368052672121375) - assert ( - section_md.radial_distribution_functions[0] - .radial_distribution_function_values[1] - .frame_start - == 0 - ) - assert ( - section_md.radial_distribution_functions[0] - .radial_distribution_function_values[1] - .frame_end - == 2 - ) - - -def test_radius_of_gyration(): - entry_archive = load_archive('tests/data/radius_of_gyration.archive.json') - - sec_calc = entry_archive.run[0].calculation[4] - sec_rg = sec_calc.radius_of_gyration[0] - sec_rgvals = sec_rg.radius_of_gyration_values[0] - - assert sec_rg.kind == 'molecular' - - assert sec_rgvals.label == 'Protein_chain_X-index_0' - assert sec_rgvals.value.magnitude == approx(5.081165959952965e-10) - assert sec_rgvals.value.units == 'meter' - - sec_calc = entry_archive.run[0].calculation[1] - sec_rg = sec_calc.radius_of_gyration[0] - sec_rgvals = sec_rg.radius_of_gyration_values[0] - - assert sec_rg.kind == 'molecular' - assert sec_rgvals.label == 'Protein_chain_X-index_0' - assert sec_rgvals.value.magnitude == approx(5.036762961380965e-10) - assert sec_rgvals.value.units == 'meter' - - sec_workflow = entry_archive.workflow2 - sec_rg = sec_workflow.results.radius_of_gyration[0] - frame = 4 - - assert sec_rg.type == 'molecular' - - assert sec_rg.label == 'Protein_chain_X-index_0' - assert sec_rg.value[frame].magnitude == approx(5.081165959952965e-10) - assert sec_rg.value[frame].units == 'meter' - - frame = 1 - sec_rg = sec_workflow.results.radius_of_gyration[0] - sec_calc = entry_archive.run[0].calculation[1] - - assert sec_rg.type == 'molecular' - assert sec_rg.label == 'Protein_chain_X-index_0' - assert sec_rg.value[frame].magnitude == approx(5.036762961380965e-10) - assert sec_rg.value[frame].units == 'meter' +# def test_single_point_workflow(): +# entry_archive = load_archive('tests/data/single_point.archive.json') +# sec_workflow = entry_archive.workflow2 +# assert sec_workflow.method.method == 'DFT' +# assert sec_workflow.results.n_scf_steps == 9 +# assert sec_workflow.results.final_scf_energy_difference > 0 +# assert sec_workflow.results.dos is not None +# assert sec_workflow.results.band_structure is None +# assert sec_workflow.results.eigenvalues is not None +# assert sec_workflow.results.density_charge is None +# assert sec_workflow.results.spectra is None +# assert sec_workflow.results.is_converged + + +# # TODO do not skip nomad-lab>=3.15 +# @pytest.mark.skip +# def test_gw_workflow(gw_workflow): +# """Testing GW workflow (DFT+GW) entry""" +# workflow = gw_workflow.workflow2 +# assert workflow.name == 'DFT+GW' +# assert workflow.method.gw_method_ref.type == 'G0W0' +# assert workflow.method.electrons_representation.type == 'plane waves' +# assert workflow.method.starting_point.name == 'GGA_X_PBE' +# results = gw_workflow.results +# assert results.method.method_name == 'GW' +# assert results.method.workflow_name == 'DFT+GW' +# assert results.method.simulation.program_name == 'VASP' +# assert results.method.simulation.program_version == '4.6.35' +# assert results.method.simulation.gw.type == 'G0W0' +# assert results.method.simulation.gw.starting_point_type == 'GGA' +# assert results.method.simulation.gw.starting_point_names == ['GGA_X_PBE'] +# assert results.method.simulation.gw.basis_set_type == 'plane waves' +# assert not results.properties.electronic.band_gap +# assert not results.properties.electronic.greens_functions_electronic +# assert len(results.properties.electronic.dos_electronic_new) == 2 +# assert len(results.properties.electronic.band_structure_electronic) == 2 +# assert results.properties.electronic.dos_electronic_new[0].label == 'DFT' +# assert results.properties.electronic.dos_electronic_new[1].label == 'GW' + + +# # TODO do not skip nomad-lab>=3.15 +# @pytest.mark.skip +# def test_dmft_workflow(dmft_workflow): +# """Testing DMFT workflow entry""" +# workflow = dmft_workflow.workflow2 +# assert workflow.name == 'TB+DMFT' +# assert not workflow.method.tb_method_ref.wannier.is_maximally_localized +# assert workflow.method.dmft_method_ref.n_impurities == 1 +# assert workflow.method.dmft_method_ref.n_correlated_orbitals[0] == 3 +# assert workflow.method.dmft_method_ref.n_electrons[0] == 1.0 +# assert workflow.method.dmft_method_ref.inverse_temperature.magnitude == 60.0 +# assert workflow.method.dmft_method_ref.magnetic_state == 'paramagnetic' +# assert workflow.method.dmft_method_ref.impurity_solver == 'CT-HYB' +# results = dmft_workflow.results +# assert results.method.method_name == 'DMFT' +# assert results.method.workflow_name == 'TB+DMFT' +# assert results.method.simulation.program_name == 'w2dynamics' +# assert results.method.simulation.dmft.impurity_solver_type == 'CT-HYB' +# assert results.method.simulation.dmft.inverse_temperature.magnitude == 60.0 +# assert results.method.simulation.dmft.magnetic_state == 'paramagnetic' +# assert results.method.simulation.dmft.u.magnitude == 4.0e-19 +# assert results.method.simulation.dmft.jh.magnitude == 0.6e-19 +# assert results.m_xpath('properties.electronic.band_gap') +# assert len(results.properties.electronic.band_gap) == 1 +# assert results.properties.electronic.band_gap[0].label == 'TB' +# assert results.m_xpath('properties.electronic.band_structure_electronic') +# assert len(results.properties.electronic.band_structure_electronic) == 1 +# # TODO check why this testing is not passing +# # * conftest seems to not be able to normalize the archive_dmft for the Greens functions, despite self_energy_iw is defined. +# # assert results.m_xpath('properties.electronic.greens_function_electronic') + + +# # TODO do not skip nomad-lab>=3.15 +# @pytest.mark.skip +# def test_maxent_workflow(maxent_workflow): +# """Testing MaxEnt workflow entry""" +# workflow = maxent_workflow.workflow2 +# assert workflow.name == 'DMFT+MaxEnt' +# assert workflow.method.dmft_method_ref.n_impurities == 1 +# assert workflow.method.dmft_method_ref.n_correlated_orbitals[0] == 3 +# assert workflow.method.dmft_method_ref.n_electrons[0] == 1.0 +# assert workflow.method.dmft_method_ref.inverse_temperature.magnitude == 60.0 +# assert workflow.method.dmft_method_ref.magnetic_state == 'paramagnetic' +# assert workflow.method.dmft_method_ref.impurity_solver == 'CT-HYB' +# assert workflow.method.maxent_method_ref +# results = maxent_workflow.results +# assert results.method.method_name == 'DMFT' +# assert results.method.workflow_name == 'DMFT+MaxEnt' +# assert results.method.simulation.program_name == 'w2dynamics' +# assert results.method.simulation.dmft.impurity_solver_type == 'CT-HYB' +# assert results.method.simulation.dmft.inverse_temperature.magnitude == 60.0 +# assert results.method.simulation.dmft.magnetic_state == 'paramagnetic' +# assert results.method.simulation.dmft.u.magnitude == 4.0e-19 +# assert results.method.simulation.dmft.jh.magnitude == 0.6e-19 +# assert results.method.simulation.dmft.analytical_continuation == 'MaxEnt' +# assert results.m_xpath('properties.electronic.dos_electronic_new') +# assert len(results.properties.electronic.dos_electronic_new) == 1 +# assert results.m_xpath('properties.electronic.greens_functions_electronic') +# assert len(results.properties.electronic.greens_functions_electronic) == 2 +# assert results.properties.electronic.greens_functions_electronic[0].label == 'DMFT' +# assert ( +# results.properties.electronic.greens_functions_electronic[1].label == 'MaxEnt' +# ) + + +# def test_bse_workflow(bse_workflow): +# """Testing BSE workflow (Photon1+Photon2) entry""" +# workflow = bse_workflow.workflow2 +# assert workflow.name == 'BSE' +# assert len(workflow.inputs) == 2 +# assert workflow.inputs[0].name == 'Input structure' +# assert workflow.inputs[1].name == 'Input BSE methodology' +# assert ( +# len(workflow.outputs) == 2 +# and len(workflow.outputs) == workflow.results.n_polarizations +# ) +# assert len(workflow.tasks) == 2 +# assert workflow.method.bse_method_ref.type == 'Singlet' +# assert workflow.method.bse_method_ref.solver == 'Lanczos-Haydock' +# results = bse_workflow.results +# assert results.method.method_name == 'BSE' +# assert results.method.workflow_name == 'BSE' +# assert results.method.simulation.program_name == 'VASP' +# assert results.method.simulation.program_version == '4.6.35' +# assert results.method.simulation.bse.type == 'Singlet' +# assert results.method.simulation.bse.solver == 'Lanczos-Haydock' +# assert results.properties.spectroscopic +# spectra = results.properties.spectroscopic.spectra +# assert len(spectra) == 2 +# assert spectra[0].type == 'XAS' +# assert spectra[0].label == 'computation' +# assert spectra[0].n_energies == 11 +# assert spectra[0].energies[3].to('eV').magnitude == approx(3.0) +# assert spectra[0].intensities[3] == approx(130.0) +# assert spectra[0].intensities_units == 'F/m' +# assert spectra[0].provenance and spectra[1].provenance +# assert spectra[0].provenance != spectra[1].provenance + + +# # TODO do not skip nomad-lab>=3.15 +# @pytest.mark.skip +# def test_xs_workflow(xs_workflow): +# """Testing XS workflow (DFT+BSEworkflow) entry""" +# workflow = xs_workflow.workflow2 +# assert workflow.name == 'XS' +# assert len(workflow.inputs) == 1 +# assert workflow.inputs[0].name == 'Input structure' +# assert len(workflow.outputs) == 2 +# assert len(workflow.tasks) == 2 +# assert workflow.tasks[0].name == 'DFT' and workflow.tasks[1].name == 'BSE 1' +# assert ( +# workflow.results.dft_outputs.dos +# and workflow.results.dft_outputs.band_structure +# and workflow.results.spectra +# ) +# results = xs_workflow.results +# assert results.method.method_name == 'BSE' +# assert results.method.workflow_name == 'XS' +# assert results.method.simulation.program_name == 'VASP' +# assert results.method.simulation.program_version == '4.6.35' +# assert results.method.simulation.bse.type == 'Singlet' +# assert results.method.simulation.bse.solver == 'Lanczos-Haydock' +# assert results.method.simulation.bse.starting_point_type == 'GGA' +# assert results.method.simulation.bse.starting_point_names == ['GGA_X_PBE'] +# assert results.method.simulation.bse.basis_set_type == 'plane waves' +# assert results.properties.electronic and results.properties.spectroscopic +# assert results.properties.electronic.dos_electronic_new[0].label == 'DFT' +# assert len(results.properties.spectroscopic.spectra) == 2 +# assert ( +# results.properties.spectroscopic.spectra[0].provenance +# != results.properties.spectroscopic.spectra[1].provenance +# ) + + +# def test_geometry_optimization_workflow(): +# entry_archive = load_archive('tests/data/geometry_optimization.archive.json') +# sec_workflow = entry_archive.workflow2 +# assert sec_workflow.method.type == 'cell_shape' +# assert sec_workflow.results.calculation_result_ref.m_def.name == 'Calculation' +# assert sec_workflow.results.final_energy_difference.to('eV').magnitude == approx( +# 0.00012532 +# ) +# assert sec_workflow.results.optimization_steps == 3 +# assert sec_workflow.results.final_force_maximum > 0.0 +# assert sec_workflow.results.is_converged_geometry + +# tasks = sec_workflow.tasks +# assert len(tasks) == len(entry_archive.run[0].calculation) +# assert ( +# tasks[0].inputs[0].section.m_proxy_resolve() == entry_archive.run[0].method[0] +# ) +# assert tasks[-1].outputs[0].section == entry_archive.run[0].calculation[-1] + + +# def test_elastic_workflow(): +# entry_archive = load_archive('tests/data/elastic.archive.json') +# sec_workflow = entry_archive.workflow2 +# sec_workflow.results.calculation_result_ref.m_def.name == 'Calculation' +# sec_workflow.method.calculation_method == 'energy' +# sec_workflow.method.elastic_constants_order == 2 +# sec_workflow.results.is_mechanically_stable +# sec_workflow.method.fitting_error_maximum > 0.0 +# sec_workflow.method.strain_maximum > 0.0 + + +# def test_phonon_workflow(): +# entry_archive = load_archive('tests/data/phonon.archive.json') + +# sec_workflow = entry_archive.workflow2 +# assert sec_workflow.results.calculation_result_ref.m_def.name == 'Calculation' +# assert sec_workflow.method.force_calculator == 'fhi-aims' +# assert sec_workflow.method.mesh_density > 0.0 +# assert sec_workflow.results.n_imaginary_frequencies > 0 +# assert not sec_workflow.method.random_displacements +# assert not sec_workflow.method.with_non_analytic_correction +# assert not sec_workflow.method.with_grueneisen_parameters + + +# def test_molecular_dynamics_workflow(): +# entry_archive = load_archive('tests/data/molecular_dynamics.archive.json') +# sec_workflow = entry_archive.workflow2 +# sec_workflow.results.calculation_result_ref.m_def.name == 'Calculation' +# assert sec_workflow.results.finished_normally +# assert sec_workflow.results.trajectory + + +# def test_rdf_and_msd(): +# entry_archive = load_archive('tests/data/rdf_and_msd.archive.json') + +# sec_workflow = entry_archive.workflow2 +# section_md = sec_workflow.results + +# assert section_md.radial_distribution_functions[0].type == 'molecular' +# assert section_md.radial_distribution_functions[0].n_smooth == 2 +# assert section_md.radial_distribution_functions[0].variables_name[0] == 'distance' + +# assert ( +# section_md.radial_distribution_functions[0] +# .radial_distribution_function_values[0] +# .label +# == '0-0' +# ) +# assert ( +# section_md.radial_distribution_functions[0] +# .radial_distribution_function_values[0] +# .n_bins +# == 198 +# ) +# assert section_md.radial_distribution_functions[ +# 0 +# ].radial_distribution_function_values[0].bins[122].magnitude == approx( +# 6.923255643844605 * 10 ** (-10) +# ) +# assert ( +# section_md.radial_distribution_functions[0] +# .radial_distribution_function_values[0] +# .bins[122] +# .units +# == 'meter' +# ) +# assert section_md.radial_distribution_functions[ +# 0 +# ].radial_distribution_function_values[0].value[96] == approx(0.0) +# assert ( +# section_md.radial_distribution_functions[0] +# .radial_distribution_function_values[0] +# .frame_start +# == 0 +# ) +# assert ( +# section_md.radial_distribution_functions[0] +# .radial_distribution_function_values[0] +# .frame_end +# == 40 +# ) + +# assert ( +# section_md.radial_distribution_functions[0] +# .radial_distribution_function_values[3] +# .label +# == '0-0' +# ) +# assert ( +# section_md.radial_distribution_functions[0] +# .radial_distribution_function_values[3] +# .n_bins +# == 198 +# ) +# assert section_md.radial_distribution_functions[ +# 0 +# ].radial_distribution_function_values[3].bins[65].magnitude == approx( +# 3.727906885147095 * 10 ** (-10) +# ) +# assert ( +# section_md.radial_distribution_functions[0] +# .radial_distribution_function_values[3] +# .bins[65] +# .units +# == 'meter' +# ) +# assert section_md.radial_distribution_functions[ +# 0 +# ].radial_distribution_function_values[3].value[52] == approx(0.0) +# assert ( +# section_md.radial_distribution_functions[0] +# .radial_distribution_function_values[3] +# .frame_start +# == 120 +# ) +# assert ( +# section_md.radial_distribution_functions[0] +# .radial_distribution_function_values[3] +# .frame_end +# == 201 +# ) + +# assert ( +# section_md.radial_distribution_functions[0] +# .radial_distribution_function_values[5] +# .label +# == '1-0' +# ) +# assert ( +# section_md.radial_distribution_functions[0] +# .radial_distribution_function_values[5] +# .n_bins +# == 198 +# ) +# assert section_md.radial_distribution_functions[ +# 0 +# ].radial_distribution_function_values[5].bins[102].magnitude == approx( +# 5.802080640792847 * 10 ** (-10) +# ) +# assert ( +# section_md.radial_distribution_functions[0] +# .radial_distribution_function_values[5] +# .bins[102] +# .units +# == 'meter' +# ) +# assert section_md.radial_distribution_functions[ +# 0 +# ].radial_distribution_function_values[5].value[55] == approx(0.0) +# assert ( +# section_md.radial_distribution_functions[0] +# .radial_distribution_function_values[5] +# .frame_start +# == 40 +# ) +# assert ( +# section_md.radial_distribution_functions[0] +# .radial_distribution_function_values[5] +# .frame_end +# == 201 +# ) + +# assert ( +# section_md.radial_distribution_functions[0] +# .radial_distribution_function_values[10] +# .label +# == '1-1' +# ) +# assert ( +# section_md.radial_distribution_functions[0] +# .radial_distribution_function_values[10] +# .n_bins +# == 198 +# ) +# assert section_md.radial_distribution_functions[ +# 0 +# ].radial_distribution_function_values[10].bins[44].magnitude == approx( +# 2.550673131942749 * 10 ** (-10) +# ) +# assert ( +# section_md.radial_distribution_functions[0] +# .radial_distribution_function_values[10] +# .bins[44] +# .units +# == 'meter' +# ) +# assert section_md.radial_distribution_functions[ +# 0 +# ].radial_distribution_function_values[10].value[101] == approx(1.4750986777470825) +# assert ( +# section_md.radial_distribution_functions[0] +# .radial_distribution_function_values[10] +# .frame_start +# == 80 +# ) +# assert ( +# section_md.radial_distribution_functions[0] +# .radial_distribution_function_values[10] +# .frame_end +# == 201 +# ) + +# assert section_md.mean_squared_displacements[0].type == 'molecular' +# assert section_md.mean_squared_displacements[0].direction == 'xyz' + +# assert ( +# section_md.mean_squared_displacements[0] +# .mean_squared_displacement_values[0] +# .label +# == '0' +# ) +# assert ( +# section_md.mean_squared_displacements[0] +# .mean_squared_displacement_values[0] +# .n_times +# == 54 +# ) +# assert section_md.mean_squared_displacements[0].mean_squared_displacement_values[ +# 0 +# ].times[13].magnitude == approx(1.3 * 10 ** (-12)) +# assert ( +# section_md.mean_squared_displacements[0] +# .mean_squared_displacement_values[0] +# .times[13] +# .units +# == 'second' +# ) +# assert section_md.mean_squared_displacements[0].mean_squared_displacement_values[ +# 0 +# ].value[32].magnitude == approx(8.98473539965496 * 10 ** (-19)) +# assert ( +# section_md.mean_squared_displacements[0] +# .mean_squared_displacement_values[0] +# .value[32] +# .units +# == 'meter^2' +# ) +# assert section_md.mean_squared_displacements[0].mean_squared_displacement_values[ +# 0 +# ].diffusion_constant.value.magnitude == approx(6.09812270414572 * 10 ** (-8)) +# assert ( +# section_md.mean_squared_displacements[0] +# .mean_squared_displacement_values[0] +# .diffusion_constant.value.units +# == 'meter^2/second' +# ) +# assert ( +# section_md.mean_squared_displacements[0] +# .mean_squared_displacement_values[0] +# .diffusion_constant.error_type +# == 'Pearson correlation coefficient' +# ) +# assert section_md.mean_squared_displacements[0].mean_squared_displacement_values[ +# 0 +# ].diffusion_constant.errors == approx(0.9924847048341159) + +# assert ( +# section_md.mean_squared_displacements[0] +# .mean_squared_displacement_values[1] +# .label +# == '1' +# ) +# assert ( +# section_md.mean_squared_displacements[0] +# .mean_squared_displacement_values[1] +# .n_times +# == 54 +# ) +# assert section_md.mean_squared_displacements[0].mean_squared_displacement_values[ +# 1 +# ].times[13].magnitude == approx(1.3 * 10 ** (-12)) +# assert ( +# section_md.mean_squared_displacements[0] +# .mean_squared_displacement_values[1] +# .times[13] +# .units +# == 'second' +# ) +# assert section_md.mean_squared_displacements[0].mean_squared_displacement_values[ +# 1 +# ].value[32].magnitude == approx(8.448369705677565 * 10 ** (-19)) +# assert ( +# section_md.mean_squared_displacements[0] +# .mean_squared_displacement_values[1] +# .value[32] +# .units +# == 'meter^2' +# ) +# assert section_md.mean_squared_displacements[0].mean_squared_displacement_values[ +# 1 +# ].diffusion_constant.value.magnitude == approx(5.094072039759048 * 10 ** (-8)) +# assert ( +# section_md.mean_squared_displacements[0] +# .mean_squared_displacement_values[1] +# .diffusion_constant.value.units +# == 'meter^2/second' +# ) +# assert ( +# section_md.mean_squared_displacements[0] +# .mean_squared_displacement_values[1] +# .diffusion_constant.error_type +# == 'Pearson correlation coefficient' +# ) +# assert section_md.mean_squared_displacements[0].mean_squared_displacement_values[ +# 1 +# ].diffusion_constant.errors == approx(0.9965870174917716) + + +# def test_rdf_2(): +# entry_archive = load_archive('tests/data/rdf_2.archive.json') + +# sec_workflow = entry_archive.workflow2 +# section_md = sec_workflow.results + +# assert section_md.radial_distribution_functions[0].type == 'molecular' +# assert section_md.radial_distribution_functions[0].n_smooth == 2 +# assert section_md.radial_distribution_functions[0].variables_name[0] == 'distance' + +# assert ( +# section_md.radial_distribution_functions[0] +# .radial_distribution_function_values[0] +# .label +# == 'SOL-Protein' +# ) +# assert ( +# section_md.radial_distribution_functions[0] +# .radial_distribution_function_values[0] +# .n_bins +# == 198 +# ) +# assert section_md.radial_distribution_functions[ +# 0 +# ].radial_distribution_function_values[0].bins[122].magnitude == approx( +# 7.624056451320648 * 10 ** (-10) +# ) +# assert ( +# section_md.radial_distribution_functions[0] +# .radial_distribution_function_values[0] +# .bins[122] +# .units +# == 'meter' +# ) +# assert section_md.radial_distribution_functions[ +# 0 +# ].radial_distribution_function_values[0].value[96] == approx(1.093694948374587) +# assert ( +# section_md.radial_distribution_functions[0] +# .radial_distribution_function_values[0] +# .frame_start +# == 0 +# ) +# assert ( +# section_md.radial_distribution_functions[0] +# .radial_distribution_function_values[0] +# .frame_end +# == 2 +# ) + +# assert ( +# section_md.radial_distribution_functions[0] +# .radial_distribution_function_values[1] +# .label +# == 'SOL-SOL' +# ) +# assert ( +# section_md.radial_distribution_functions[0] +# .radial_distribution_function_values[1] +# .n_bins +# == 198 +# ) +# assert section_md.radial_distribution_functions[ +# 0 +# ].radial_distribution_function_values[1].bins[102].magnitude == approx( +# 6.389391438961029 * 10 ** (-10) +# ) +# assert ( +# section_md.radial_distribution_functions[0] +# .radial_distribution_function_values[1] +# .bins[102] +# .units +# == 'meter' +# ) +# assert section_md.radial_distribution_functions[ +# 0 +# ].radial_distribution_function_values[1].value[55] == approx(0.8368052672121375) +# assert ( +# section_md.radial_distribution_functions[0] +# .radial_distribution_function_values[1] +# .frame_start +# == 0 +# ) +# assert ( +# section_md.radial_distribution_functions[0] +# .radial_distribution_function_values[1] +# .frame_end +# == 2 +# ) + + +# def test_radius_of_gyration(): +# entry_archive = load_archive('tests/data/radius_of_gyration.archive.json') + +# sec_calc = entry_archive.run[0].calculation[4] +# sec_rg = sec_calc.radius_of_gyration[0] +# sec_rgvals = sec_rg.radius_of_gyration_values[0] + +# assert sec_rg.kind == 'molecular' + +# assert sec_rgvals.label == 'Protein_chain_X-index_0' +# assert sec_rgvals.value.magnitude == approx(5.081165959952965e-10) +# assert sec_rgvals.value.units == 'meter' + +# sec_calc = entry_archive.run[0].calculation[1] +# sec_rg = sec_calc.radius_of_gyration[0] +# sec_rgvals = sec_rg.radius_of_gyration_values[0] + +# assert sec_rg.kind == 'molecular' +# assert sec_rgvals.label == 'Protein_chain_X-index_0' +# assert sec_rgvals.value.magnitude == approx(5.036762961380965e-10) +# assert sec_rgvals.value.units == 'meter' + +# sec_workflow = entry_archive.workflow2 +# sec_rg = sec_workflow.results.radius_of_gyration[0] +# frame = 4 + +# assert sec_rg.type == 'molecular' + +# assert sec_rg.label == 'Protein_chain_X-index_0' +# assert sec_rg.value[frame].magnitude == approx(5.081165959952965e-10) +# assert sec_rg.value[frame].units == 'meter' + +# frame = 1 +# sec_rg = sec_workflow.results.radius_of_gyration[0] +# sec_calc = entry_archive.run[0].calculation[1] + +# assert sec_rg.type == 'molecular' +# assert sec_rg.label == 'Protein_chain_X-index_0' +# assert sec_rg.value[frame].magnitude == approx(5.036762961380965e-10) +# assert sec_rg.value[frame].units == 'meter' def parse_trajectory(filename): @@ -738,6 +738,8 @@ def parse_trajectory(filename): def test_eos_workflow(): archive = parse_trajectory('tests/data/ase/Cu.traj') + print(archive.workflow2.tasks) + print(archive.workflow2.results.eos_fit) eos_fit = archive.workflow2.results.eos_fit assert len(eos_fit) == 5 assert eos_fit[0].fitted_energies[1].to('eV').magnitude == approx(-0.00636507) @@ -751,264 +753,264 @@ def test_eos_workflow(): assert eos_fit[4].rms_error == approx(1.408202378222592e-07) -class TestSimulationWorkflow: - """ - Tests for the base simulation workflow class. - """ - - n_calc = 10 - - @pytest.fixture(autouse=True) - def serial_simulation(self) -> EntryArchive: - """ - Simulation with calculations done in serial. - """ - archive = EntryArchive() - archive.metadata = EntryMetadata(entry_type='Workflow') - archive.workflow2 = SimulationWorkflow( - method=SimulationWorkflowMethod(), results=SimulationWorkflowResults() - ) - - archive.run.append( - Run( - calculation=[ - Calculation(time_physical=t, time_calculation=1) - for t in range(1, self.n_calc + 1) - ] - ) - ) - return archive - - def test_tasks_serial(self, serial_simulation): - """ - Test tasks creation of purely serial calculation. - """ - workflow = serial_simulation.workflow2 - workflow.normalize(serial_simulation, LOGGER) - assert len(workflow.tasks) == self.n_calc - - assert workflow.inputs[0].section == workflow.tasks[0].inputs[0].section - for n, task in enumerate(workflow.tasks[:-1]): - assert task.name == f'Step {n + 1}' - assert len(task.inputs) == 1 - assert len(task.outputs) == 1 - assert task.outputs[0].section == workflow.tasks[n + 1].inputs[0].section - assert workflow.outputs[0].section == workflow.tasks[-1].outputs[0].section - - def test_tasks_defined(self, serial_simulation): - """ - Test tasks creation skipped if tasks are predefined - """ - workflow = SimulationWorkflow(tasks=[Task(name='1')]) - serial_simulation.workflow2 = workflow - workflow.normalize(serial_simulation, LOGGER) - - assert len(serial_simulation.workflow2.tasks) == 1 - assert serial_simulation.workflow2.tasks[0].name == '1' - - def test_tasks_no_time(self, serial_simulation): - """ - Test tasks creation skipped if at least one calculation has no time info. - """ - - for key in ['time_physical', 'time_calculation']: - calc = serial_simulation.run[0].calculation[ - random.randint(0, self.n_calc - 1) - ] - calc.m_set(calc.m_get_quantity_definition(key), None) - serial_simulation.workflow2.normalize(serial_simulation, LOGGER) - assert not serial_simulation.workflow2.tasks - - @pytest.mark.parametrize( - 'calculation_indices', - [ - # parallel (0 to 3), 4, 5, parallel (6 to 9) - [[0, 1, 2, 3], [4], [5], [6, 7, 8, 9]], - # 0, parallel (1 to 2), 4, 5, 6, 7, 8, 8 - [[0], [1, 2], [3], [4], [5], [6], [7], [8], [9]], - # parallel (0 to 8), 9 - [[0, 1, 2, 3, 4, 5, 6, 7, 8], [9]], - ], - ) - def test_task_not_serial(self, serial_simulation, calculation_indices): - """ - Test creation for mixed serial and parallel tasks. - """ - - def _create_times(indices, start_time=0): - times = [] - for n in indices: - if not isinstance(n, int): - times.extend( - sorted( - _create_times(n, start_time=times[-1][1] if times else 0), - key=lambda x: x[1], - ) - ) - else: - calc_time = random.random() - dt = random.random() * 0.1 # small perturbation - times.append([n, calc_time + start_time + dt, calc_time]) - return times - - for n, time_physical, time_calculation in _create_times(calculation_indices): - serial_simulation.run[-1].calculation[n].time_physical = time_physical - serial_simulation.run[-1].calculation[n].time_calculation = time_calculation - - workflow = serial_simulation.workflow2 - workflow.normalize(serial_simulation, LOGGER) - assert len(workflow.tasks) == 10 - - # workflow inputs as inputs to first parallel tasks - for n in calculation_indices[0]: - assert workflow.tasks[n].name == 'Step 1' - assert workflow.tasks[n].inputs[0].section == workflow.inputs[0].section - - # outputs of previous tasks are inputs of succeeding tasks in series - for i in range(1, len(calculation_indices)): - for n1 in calculation_indices[i]: - assert workflow.tasks[n1].name == f'Step {i + 1}' - inputs = [input.section for input in workflow.tasks[n1].inputs] - for n0 in calculation_indices[i - 1]: - assert workflow.tasks[n0].outputs[-1].section in inputs - - # last parallel tasks oututs as workflow outputs - for n in calculation_indices[-1]: - assert workflow.tasks[n].outputs[0].section in [ - output.section for output in workflow.outputs - ] - - -class TestChemicalReactionWorkflow: - """ - Contains tests for the matinfo defintion and normalization of the chemical reaction - workflow. - """ - - @pytest.fixture(autouse=True, scope='class') - def dft_archives(self): - """ - Parse all relevant dft calculations. - """ - test_dir = 'tests/data/chemical_reaction' - - archives = {} - for root, _, names in os.walk(test_dir): - for filename in names: - if filename != 'run.archive.json': - continue - archives[os.path.basename(root)] = load_archive( - os.path.join(root, filename) - ) - return archives - - @pytest.fixture(autouse=True) - def segregation_workflow_archive(self, dft_archives): - """ - Constructs a chemical reaction workflow archive describing the segregation of H - from RhCu_CH4 into RhCu_CH3 and RhCu_H through a transition state RhCu_CH3_H. - """ - formula_type = [ - ['RhCu_CH4', 'reactant'], - ['RhCu', 'reactant'], - ['RhCu_CH3_H', 'transition state'], - ['RhCu_CH3', 'product'], - ['RhCu_xHfcc', 'product'], - ] - workflow = ChemicalReaction() - for formula, type in formula_type: - archive = dft_archives[formula] - workflow.inputs.append( - Link(name=f'{formula} {type}', section=archive.run[0].calculation[-1]) - ) - # add also slab to transition state to preserve mass balance - if formula == 'RhCu': - workflow.inputs.append( - Link( - name=f'transition state {formula}', - section=archive.run[0].calculation[-1], - ) - ) - - return EntryArchive( - metadata=EntryMetadata(entry_type='Workflow'), workflow2=workflow - ) - - @pytest.fixture(autouse=True) - def adsorption_workflow_archive(self, dft_archives): - """ - Constructs a chemical reaction workflow archive describing the adsorption of N - in PdAg. - """ - - formula_type = [ - ['N', 'reactant'], - ['PdAg', 'reactant'], - ['NPdAg', 'product'], - ] - workflow = ChemicalReaction() - for formula, type in formula_type: - archive = dft_archives[formula] - workflow.inputs.append( - Link(name=f'{formula} {type}', section=archive.run[0].calculation[-1]) - ) - - return EntryArchive( - metadata=EntryMetadata(entry_type='Workflow'), workflow2=workflow - ) - - @pytest.mark.parametrize( - 'workflow_archive, reaction_energy, activation_energy', - [ - pytest.param( - 'segregation_workflow_archive', - 4.41467915e-20, - 1.02994872e-19, - id='segregation', - ), - pytest.param( - 'adsorption_workflow_archive', -3.04029682e-19, None, id='adsorption' - ), - ], - ) - def test_reaction_energy( - self, request, workflow_archive, reaction_energy, activation_energy - ): - """ - Test the calculation of reaction and activation energy. - """ - workflow_archive = request.getfixturevalue(workflow_archive) - workflow = workflow_archive.workflow2 - workflow.normalize(workflow_archive, LOGGER) - - assert np.isclose( - workflow.results.reaction_energy.magnitude, - reaction_energy, - atol=0, - rtol=1e6, - ) - if activation_energy: - assert np.isclose( - workflow.results.activation_energy.magnitude, - activation_energy, - atol=0, - rtol=1e6, - ) - assert len(workflow.tasks) == 1 - - def test_system_checks(self, segregation_workflow_archive): - """ - Test the checks for the consistency of the system from reactants - """ - workflow = segregation_workflow_archive.workflow2 - # change the system size in an input to make them inconsistent - lattice = np.array(workflow.inputs[-1].section.system_ref.atoms.lattice_vectors) - workflow.inputs[-1].section.system_ref.atoms.lattice_vectors = np.ones((3, 3)) - workflow.normalize(segregation_workflow_archive, LOGGER) - assert workflow.results.reaction_energy is None - - # change the chemical composition in an input to make them inconsistent - workflow.inputs[-1].section.system_ref.atoms.lattice_vectoprs = lattice - workflow.inputs[0].section.system_ref.atoms.labels = ['C'] - workflow.normalize(segregation_workflow_archive, LOGGER) - assert workflow.results.reaction_energy is None +# class TestSimulationWorkflow: +# """ +# Tests for the base simulation workflow class. +# """ + +# n_calc = 10 + +# @pytest.fixture(autouse=True) +# def serial_simulation(self) -> EntryArchive: +# """ +# Simulation with calculations done in serial. +# """ +# archive = EntryArchive() +# archive.metadata = EntryMetadata(entry_type='Workflow') +# archive.workflow2 = SimulationWorkflow( +# method=SimulationWorkflowMethod(), results=SimulationWorkflowResults() +# ) + +# archive.run.append( +# Run( +# calculation=[ +# Calculation(time_physical=t, time_calculation=1) +# for t in range(1, self.n_calc + 1) +# ] +# ) +# ) +# return archive + +# def test_tasks_serial(self, serial_simulation): +# """ +# Test tasks creation of purely serial calculation. +# """ +# workflow = serial_simulation.workflow2 +# workflow.normalize(serial_simulation, LOGGER) +# assert len(workflow.tasks) == self.n_calc + +# assert workflow.inputs[0].section == workflow.tasks[0].inputs[0].section +# for n, task in enumerate(workflow.tasks[:-1]): +# assert task.name == f'Step {n + 1}' +# assert len(task.inputs) == 1 +# assert len(task.outputs) == 1 +# assert task.outputs[0].section == workflow.tasks[n + 1].inputs[0].section +# assert workflow.outputs[0].section == workflow.tasks[-1].outputs[0].section + +# def test_tasks_defined(self, serial_simulation): +# """ +# Test tasks creation skipped if tasks are predefined +# """ +# workflow = SimulationWorkflow(tasks=[Task(name='1')]) +# serial_simulation.workflow2 = workflow +# workflow.normalize(serial_simulation, LOGGER) + +# assert len(serial_simulation.workflow2.tasks) == 1 +# assert serial_simulation.workflow2.tasks[0].name == '1' + +# def test_tasks_no_time(self, serial_simulation): +# """ +# Test tasks creation skipped if at least one calculation has no time info. +# """ + +# for key in ['time_physical', 'time_calculation']: +# calc = serial_simulation.run[0].calculation[ +# random.randint(0, self.n_calc - 1) +# ] +# calc.m_set(calc.m_get_quantity_definition(key), None) +# serial_simulation.workflow2.normalize(serial_simulation, LOGGER) +# assert not serial_simulation.workflow2.tasks + +# @pytest.mark.parametrize( +# 'calculation_indices', +# [ +# # parallel (0 to 3), 4, 5, parallel (6 to 9) +# [[0, 1, 2, 3], [4], [5], [6, 7, 8, 9]], +# # 0, parallel (1 to 2), 4, 5, 6, 7, 8, 8 +# [[0], [1, 2], [3], [4], [5], [6], [7], [8], [9]], +# # parallel (0 to 8), 9 +# [[0, 1, 2, 3, 4, 5, 6, 7, 8], [9]], +# ], +# ) +# def test_task_not_serial(self, serial_simulation, calculation_indices): +# """ +# Test creation for mixed serial and parallel tasks. +# """ + +# def _create_times(indices, start_time=0): +# times = [] +# for n in indices: +# if not isinstance(n, int): +# times.extend( +# sorted( +# _create_times(n, start_time=times[-1][1] if times else 0), +# key=lambda x: x[1], +# ) +# ) +# else: +# calc_time = random.random() +# dt = random.random() * 0.1 # small perturbation +# times.append([n, calc_time + start_time + dt, calc_time]) +# return times + +# for n, time_physical, time_calculation in _create_times(calculation_indices): +# serial_simulation.run[-1].calculation[n].time_physical = time_physical +# serial_simulation.run[-1].calculation[n].time_calculation = time_calculation + +# workflow = serial_simulation.workflow2 +# workflow.normalize(serial_simulation, LOGGER) +# assert len(workflow.tasks) == 10 + +# # workflow inputs as inputs to first parallel tasks +# for n in calculation_indices[0]: +# assert workflow.tasks[n].name == 'Step 1' +# assert workflow.tasks[n].inputs[0].section == workflow.inputs[0].section + +# # outputs of previous tasks are inputs of succeeding tasks in series +# for i in range(1, len(calculation_indices)): +# for n1 in calculation_indices[i]: +# assert workflow.tasks[n1].name == f'Step {i + 1}' +# inputs = [input.section for input in workflow.tasks[n1].inputs] +# for n0 in calculation_indices[i - 1]: +# assert workflow.tasks[n0].outputs[-1].section in inputs + +# # last parallel tasks oututs as workflow outputs +# for n in calculation_indices[-1]: +# assert workflow.tasks[n].outputs[0].section in [ +# output.section for output in workflow.outputs +# ] + + +# class TestChemicalReactionWorkflow: +# """ +# Contains tests for the matinfo defintion and normalization of the chemical reaction +# workflow. +# """ + +# @pytest.fixture(autouse=True, scope='class') +# def dft_archives(self): +# """ +# Parse all relevant dft calculations. +# """ +# test_dir = 'tests/data/chemical_reaction' + +# archives = {} +# for root, _, names in os.walk(test_dir): +# for filename in names: +# if filename != 'run.archive.json': +# continue +# archives[os.path.basename(root)] = load_archive( +# os.path.join(root, filename) +# ) +# return archives + +# @pytest.fixture(autouse=True) +# def segregation_workflow_archive(self, dft_archives): +# """ +# Constructs a chemical reaction workflow archive describing the segregation of H +# from RhCu_CH4 into RhCu_CH3 and RhCu_H through a transition state RhCu_CH3_H. +# """ +# formula_type = [ +# ['RhCu_CH4', 'reactant'], +# ['RhCu', 'reactant'], +# ['RhCu_CH3_H', 'transition state'], +# ['RhCu_CH3', 'product'], +# ['RhCu_xHfcc', 'product'], +# ] +# workflow = ChemicalReaction() +# for formula, type in formula_type: +# archive = dft_archives[formula] +# workflow.inputs.append( +# Link(name=f'{formula} {type}', section=archive.run[0].calculation[-1]) +# ) +# # add also slab to transition state to preserve mass balance +# if formula == 'RhCu': +# workflow.inputs.append( +# Link( +# name=f'transition state {formula}', +# section=archive.run[0].calculation[-1], +# ) +# ) + +# return EntryArchive( +# metadata=EntryMetadata(entry_type='Workflow'), workflow2=workflow +# ) + +# @pytest.fixture(autouse=True) +# def adsorption_workflow_archive(self, dft_archives): +# """ +# Constructs a chemical reaction workflow archive describing the adsorption of N +# in PdAg. +# """ + +# formula_type = [ +# ['N', 'reactant'], +# ['PdAg', 'reactant'], +# ['NPdAg', 'product'], +# ] +# workflow = ChemicalReaction() +# for formula, type in formula_type: +# archive = dft_archives[formula] +# workflow.inputs.append( +# Link(name=f'{formula} {type}', section=archive.run[0].calculation[-1]) +# ) + +# return EntryArchive( +# metadata=EntryMetadata(entry_type='Workflow'), workflow2=workflow +# ) + +# @pytest.mark.parametrize( +# 'workflow_archive, reaction_energy, activation_energy', +# [ +# pytest.param( +# 'segregation_workflow_archive', +# 4.41467915e-20, +# 1.02994872e-19, +# id='segregation', +# ), +# pytest.param( +# 'adsorption_workflow_archive', -3.04029682e-19, None, id='adsorption' +# ), +# ], +# ) +# def test_reaction_energy( +# self, request, workflow_archive, reaction_energy, activation_energy +# ): +# """ +# Test the calculation of reaction and activation energy. +# """ +# workflow_archive = request.getfixturevalue(workflow_archive) +# workflow = workflow_archive.workflow2 +# workflow.normalize(workflow_archive, LOGGER) + +# assert np.isclose( +# workflow.results.reaction_energy.magnitude, +# reaction_energy, +# atol=0, +# rtol=1e6, +# ) +# if activation_energy: +# assert np.isclose( +# workflow.results.activation_energy.magnitude, +# activation_energy, +# atol=0, +# rtol=1e6, +# ) +# assert len(workflow.tasks) == 1 + +# def test_system_checks(self, segregation_workflow_archive): +# """ +# Test the checks for the consistency of the system from reactants +# """ +# workflow = segregation_workflow_archive.workflow2 +# # change the system size in an input to make them inconsistent +# lattice = np.array(workflow.inputs[-1].section.system_ref.atoms.lattice_vectors) +# workflow.inputs[-1].section.system_ref.atoms.lattice_vectors = np.ones((3, 3)) +# workflow.normalize(segregation_workflow_archive, LOGGER) +# assert workflow.results.reaction_energy is None + +# # change the chemical composition in an input to make them inconsistent +# workflow.inputs[-1].section.system_ref.atoms.lattice_vectoprs = lattice +# workflow.inputs[0].section.system_ref.atoms.labels = ['C'] +# workflow.normalize(segregation_workflow_archive, LOGGER) +# assert workflow.results.reaction_energy is None From 9ea8ebd0b66040255e60eaf1c12aad3734bee4b8 Mon Sep 17 00:00:00 2001 From: jrudz Date: Thu, 8 May 2025 22:00:27 +0200 Subject: [PATCH 08/25] fixed test, filter for SinglePoints --- simulationworkflowschema/equation_of_state.py | 30 +- tests/test_simulationworkflowschema.py | 1790 ++++++++--------- 2 files changed, 911 insertions(+), 909 deletions(-) diff --git a/simulationworkflowschema/equation_of_state.py b/simulationworkflowschema/equation_of_state.py index f5c0f90..fa79455 100644 --- a/simulationworkflowschema/equation_of_state.py +++ b/simulationworkflowschema/equation_of_state.py @@ -30,9 +30,8 @@ WORKFLOW_METHOD_NAME, WORKFLOW_RESULTS_NAME, ) -from .geometry_optimization import GeometryOptimization -from runschema.run import Run, Program, Method -from runschema.system import System +from .single_point import SinglePoint +from runschema.run import Run, Program class EquationOfStateMethod(SimulationWorkflowMethod): @@ -161,12 +160,17 @@ def normalize(self, archive, logger): self.results = EquationOfStateResults() self.outputs.append(Link(name=WORKFLOW_RESULTS_NAME, section=self.results)) - task0_archive = self.tasks[0].task.m_root() - tasks = self.tasks[:] - if task0_archive.workflow2: - # remove the first task if it is a GeometryOptimization (not part of the EOS) - if isinstance(task0_archive.workflow2, GeometryOptimization): - tasks.pop(0) + try: + task_archives = [task.task.m_root() for task in self.tasks] + tasks = [ + task + for i_task, task in enumerate(self.tasks) + if isinstance(task_archives[i_task], SinglePoint) + ] + except Exception: + logger.warning( + 'Failed to get task archives. Cannot filter for SinglePoint tasks.' + ) if not self._calculations: # try to get calculations from tasks (in case of instantiation from workflow yaml) @@ -240,10 +244,10 @@ def normalize(self, archive, logger): run = Run(program=Program()) try: # assuming the final structure is the relevant one, e.g., from a GO - run.system.extend([task0_archive.run[0].system[-1]]) - run.method.extend(task0_archive.run[0].method) - run.calculation.extend([task0_archive.run[0].calculation[-1]]) + run.system.extend([task_archives[0].run[0].system[-1]]) + run.method.extend(task_archives[0].run[0].method) + run.calculation.extend([task_archives[0].run[0].calculation[-1]]) except Exception: logger.warning('Failed to link structure from first task archive. ') - return + archive.run.append(run) diff --git a/tests/test_simulationworkflowschema.py b/tests/test_simulationworkflowschema.py index 307dec4..cb6005a 100644 --- a/tests/test_simulationworkflowschema.py +++ b/tests/test_simulationworkflowschema.py @@ -68,639 +68,639 @@ def test_no_workflow(): assert not entry_archive.workflow2.results.calculations_ref -# def test_single_point_workflow(): -# entry_archive = load_archive('tests/data/single_point.archive.json') -# sec_workflow = entry_archive.workflow2 -# assert sec_workflow.method.method == 'DFT' -# assert sec_workflow.results.n_scf_steps == 9 -# assert sec_workflow.results.final_scf_energy_difference > 0 -# assert sec_workflow.results.dos is not None -# assert sec_workflow.results.band_structure is None -# assert sec_workflow.results.eigenvalues is not None -# assert sec_workflow.results.density_charge is None -# assert sec_workflow.results.spectra is None -# assert sec_workflow.results.is_converged - - -# # TODO do not skip nomad-lab>=3.15 -# @pytest.mark.skip -# def test_gw_workflow(gw_workflow): -# """Testing GW workflow (DFT+GW) entry""" -# workflow = gw_workflow.workflow2 -# assert workflow.name == 'DFT+GW' -# assert workflow.method.gw_method_ref.type == 'G0W0' -# assert workflow.method.electrons_representation.type == 'plane waves' -# assert workflow.method.starting_point.name == 'GGA_X_PBE' -# results = gw_workflow.results -# assert results.method.method_name == 'GW' -# assert results.method.workflow_name == 'DFT+GW' -# assert results.method.simulation.program_name == 'VASP' -# assert results.method.simulation.program_version == '4.6.35' -# assert results.method.simulation.gw.type == 'G0W0' -# assert results.method.simulation.gw.starting_point_type == 'GGA' -# assert results.method.simulation.gw.starting_point_names == ['GGA_X_PBE'] -# assert results.method.simulation.gw.basis_set_type == 'plane waves' -# assert not results.properties.electronic.band_gap -# assert not results.properties.electronic.greens_functions_electronic -# assert len(results.properties.electronic.dos_electronic_new) == 2 -# assert len(results.properties.electronic.band_structure_electronic) == 2 -# assert results.properties.electronic.dos_electronic_new[0].label == 'DFT' -# assert results.properties.electronic.dos_electronic_new[1].label == 'GW' - - -# # TODO do not skip nomad-lab>=3.15 -# @pytest.mark.skip -# def test_dmft_workflow(dmft_workflow): -# """Testing DMFT workflow entry""" -# workflow = dmft_workflow.workflow2 -# assert workflow.name == 'TB+DMFT' -# assert not workflow.method.tb_method_ref.wannier.is_maximally_localized -# assert workflow.method.dmft_method_ref.n_impurities == 1 -# assert workflow.method.dmft_method_ref.n_correlated_orbitals[0] == 3 -# assert workflow.method.dmft_method_ref.n_electrons[0] == 1.0 -# assert workflow.method.dmft_method_ref.inverse_temperature.magnitude == 60.0 -# assert workflow.method.dmft_method_ref.magnetic_state == 'paramagnetic' -# assert workflow.method.dmft_method_ref.impurity_solver == 'CT-HYB' -# results = dmft_workflow.results -# assert results.method.method_name == 'DMFT' -# assert results.method.workflow_name == 'TB+DMFT' -# assert results.method.simulation.program_name == 'w2dynamics' -# assert results.method.simulation.dmft.impurity_solver_type == 'CT-HYB' -# assert results.method.simulation.dmft.inverse_temperature.magnitude == 60.0 -# assert results.method.simulation.dmft.magnetic_state == 'paramagnetic' -# assert results.method.simulation.dmft.u.magnitude == 4.0e-19 -# assert results.method.simulation.dmft.jh.magnitude == 0.6e-19 -# assert results.m_xpath('properties.electronic.band_gap') -# assert len(results.properties.electronic.band_gap) == 1 -# assert results.properties.electronic.band_gap[0].label == 'TB' -# assert results.m_xpath('properties.electronic.band_structure_electronic') -# assert len(results.properties.electronic.band_structure_electronic) == 1 -# # TODO check why this testing is not passing -# # * conftest seems to not be able to normalize the archive_dmft for the Greens functions, despite self_energy_iw is defined. -# # assert results.m_xpath('properties.electronic.greens_function_electronic') - - -# # TODO do not skip nomad-lab>=3.15 -# @pytest.mark.skip -# def test_maxent_workflow(maxent_workflow): -# """Testing MaxEnt workflow entry""" -# workflow = maxent_workflow.workflow2 -# assert workflow.name == 'DMFT+MaxEnt' -# assert workflow.method.dmft_method_ref.n_impurities == 1 -# assert workflow.method.dmft_method_ref.n_correlated_orbitals[0] == 3 -# assert workflow.method.dmft_method_ref.n_electrons[0] == 1.0 -# assert workflow.method.dmft_method_ref.inverse_temperature.magnitude == 60.0 -# assert workflow.method.dmft_method_ref.magnetic_state == 'paramagnetic' -# assert workflow.method.dmft_method_ref.impurity_solver == 'CT-HYB' -# assert workflow.method.maxent_method_ref -# results = maxent_workflow.results -# assert results.method.method_name == 'DMFT' -# assert results.method.workflow_name == 'DMFT+MaxEnt' -# assert results.method.simulation.program_name == 'w2dynamics' -# assert results.method.simulation.dmft.impurity_solver_type == 'CT-HYB' -# assert results.method.simulation.dmft.inverse_temperature.magnitude == 60.0 -# assert results.method.simulation.dmft.magnetic_state == 'paramagnetic' -# assert results.method.simulation.dmft.u.magnitude == 4.0e-19 -# assert results.method.simulation.dmft.jh.magnitude == 0.6e-19 -# assert results.method.simulation.dmft.analytical_continuation == 'MaxEnt' -# assert results.m_xpath('properties.electronic.dos_electronic_new') -# assert len(results.properties.electronic.dos_electronic_new) == 1 -# assert results.m_xpath('properties.electronic.greens_functions_electronic') -# assert len(results.properties.electronic.greens_functions_electronic) == 2 -# assert results.properties.electronic.greens_functions_electronic[0].label == 'DMFT' -# assert ( -# results.properties.electronic.greens_functions_electronic[1].label == 'MaxEnt' -# ) - - -# def test_bse_workflow(bse_workflow): -# """Testing BSE workflow (Photon1+Photon2) entry""" -# workflow = bse_workflow.workflow2 -# assert workflow.name == 'BSE' -# assert len(workflow.inputs) == 2 -# assert workflow.inputs[0].name == 'Input structure' -# assert workflow.inputs[1].name == 'Input BSE methodology' -# assert ( -# len(workflow.outputs) == 2 -# and len(workflow.outputs) == workflow.results.n_polarizations -# ) -# assert len(workflow.tasks) == 2 -# assert workflow.method.bse_method_ref.type == 'Singlet' -# assert workflow.method.bse_method_ref.solver == 'Lanczos-Haydock' -# results = bse_workflow.results -# assert results.method.method_name == 'BSE' -# assert results.method.workflow_name == 'BSE' -# assert results.method.simulation.program_name == 'VASP' -# assert results.method.simulation.program_version == '4.6.35' -# assert results.method.simulation.bse.type == 'Singlet' -# assert results.method.simulation.bse.solver == 'Lanczos-Haydock' -# assert results.properties.spectroscopic -# spectra = results.properties.spectroscopic.spectra -# assert len(spectra) == 2 -# assert spectra[0].type == 'XAS' -# assert spectra[0].label == 'computation' -# assert spectra[0].n_energies == 11 -# assert spectra[0].energies[3].to('eV').magnitude == approx(3.0) -# assert spectra[0].intensities[3] == approx(130.0) -# assert spectra[0].intensities_units == 'F/m' -# assert spectra[0].provenance and spectra[1].provenance -# assert spectra[0].provenance != spectra[1].provenance - - -# # TODO do not skip nomad-lab>=3.15 -# @pytest.mark.skip -# def test_xs_workflow(xs_workflow): -# """Testing XS workflow (DFT+BSEworkflow) entry""" -# workflow = xs_workflow.workflow2 -# assert workflow.name == 'XS' -# assert len(workflow.inputs) == 1 -# assert workflow.inputs[0].name == 'Input structure' -# assert len(workflow.outputs) == 2 -# assert len(workflow.tasks) == 2 -# assert workflow.tasks[0].name == 'DFT' and workflow.tasks[1].name == 'BSE 1' -# assert ( -# workflow.results.dft_outputs.dos -# and workflow.results.dft_outputs.band_structure -# and workflow.results.spectra -# ) -# results = xs_workflow.results -# assert results.method.method_name == 'BSE' -# assert results.method.workflow_name == 'XS' -# assert results.method.simulation.program_name == 'VASP' -# assert results.method.simulation.program_version == '4.6.35' -# assert results.method.simulation.bse.type == 'Singlet' -# assert results.method.simulation.bse.solver == 'Lanczos-Haydock' -# assert results.method.simulation.bse.starting_point_type == 'GGA' -# assert results.method.simulation.bse.starting_point_names == ['GGA_X_PBE'] -# assert results.method.simulation.bse.basis_set_type == 'plane waves' -# assert results.properties.electronic and results.properties.spectroscopic -# assert results.properties.electronic.dos_electronic_new[0].label == 'DFT' -# assert len(results.properties.spectroscopic.spectra) == 2 -# assert ( -# results.properties.spectroscopic.spectra[0].provenance -# != results.properties.spectroscopic.spectra[1].provenance -# ) - - -# def test_geometry_optimization_workflow(): -# entry_archive = load_archive('tests/data/geometry_optimization.archive.json') -# sec_workflow = entry_archive.workflow2 -# assert sec_workflow.method.type == 'cell_shape' -# assert sec_workflow.results.calculation_result_ref.m_def.name == 'Calculation' -# assert sec_workflow.results.final_energy_difference.to('eV').magnitude == approx( -# 0.00012532 -# ) -# assert sec_workflow.results.optimization_steps == 3 -# assert sec_workflow.results.final_force_maximum > 0.0 -# assert sec_workflow.results.is_converged_geometry - -# tasks = sec_workflow.tasks -# assert len(tasks) == len(entry_archive.run[0].calculation) -# assert ( -# tasks[0].inputs[0].section.m_proxy_resolve() == entry_archive.run[0].method[0] -# ) -# assert tasks[-1].outputs[0].section == entry_archive.run[0].calculation[-1] - - -# def test_elastic_workflow(): -# entry_archive = load_archive('tests/data/elastic.archive.json') -# sec_workflow = entry_archive.workflow2 -# sec_workflow.results.calculation_result_ref.m_def.name == 'Calculation' -# sec_workflow.method.calculation_method == 'energy' -# sec_workflow.method.elastic_constants_order == 2 -# sec_workflow.results.is_mechanically_stable -# sec_workflow.method.fitting_error_maximum > 0.0 -# sec_workflow.method.strain_maximum > 0.0 - - -# def test_phonon_workflow(): -# entry_archive = load_archive('tests/data/phonon.archive.json') - -# sec_workflow = entry_archive.workflow2 -# assert sec_workflow.results.calculation_result_ref.m_def.name == 'Calculation' -# assert sec_workflow.method.force_calculator == 'fhi-aims' -# assert sec_workflow.method.mesh_density > 0.0 -# assert sec_workflow.results.n_imaginary_frequencies > 0 -# assert not sec_workflow.method.random_displacements -# assert not sec_workflow.method.with_non_analytic_correction -# assert not sec_workflow.method.with_grueneisen_parameters - - -# def test_molecular_dynamics_workflow(): -# entry_archive = load_archive('tests/data/molecular_dynamics.archive.json') -# sec_workflow = entry_archive.workflow2 -# sec_workflow.results.calculation_result_ref.m_def.name == 'Calculation' -# assert sec_workflow.results.finished_normally -# assert sec_workflow.results.trajectory - - -# def test_rdf_and_msd(): -# entry_archive = load_archive('tests/data/rdf_and_msd.archive.json') - -# sec_workflow = entry_archive.workflow2 -# section_md = sec_workflow.results - -# assert section_md.radial_distribution_functions[0].type == 'molecular' -# assert section_md.radial_distribution_functions[0].n_smooth == 2 -# assert section_md.radial_distribution_functions[0].variables_name[0] == 'distance' - -# assert ( -# section_md.radial_distribution_functions[0] -# .radial_distribution_function_values[0] -# .label -# == '0-0' -# ) -# assert ( -# section_md.radial_distribution_functions[0] -# .radial_distribution_function_values[0] -# .n_bins -# == 198 -# ) -# assert section_md.radial_distribution_functions[ -# 0 -# ].radial_distribution_function_values[0].bins[122].magnitude == approx( -# 6.923255643844605 * 10 ** (-10) -# ) -# assert ( -# section_md.radial_distribution_functions[0] -# .radial_distribution_function_values[0] -# .bins[122] -# .units -# == 'meter' -# ) -# assert section_md.radial_distribution_functions[ -# 0 -# ].radial_distribution_function_values[0].value[96] == approx(0.0) -# assert ( -# section_md.radial_distribution_functions[0] -# .radial_distribution_function_values[0] -# .frame_start -# == 0 -# ) -# assert ( -# section_md.radial_distribution_functions[0] -# .radial_distribution_function_values[0] -# .frame_end -# == 40 -# ) - -# assert ( -# section_md.radial_distribution_functions[0] -# .radial_distribution_function_values[3] -# .label -# == '0-0' -# ) -# assert ( -# section_md.radial_distribution_functions[0] -# .radial_distribution_function_values[3] -# .n_bins -# == 198 -# ) -# assert section_md.radial_distribution_functions[ -# 0 -# ].radial_distribution_function_values[3].bins[65].magnitude == approx( -# 3.727906885147095 * 10 ** (-10) -# ) -# assert ( -# section_md.radial_distribution_functions[0] -# .radial_distribution_function_values[3] -# .bins[65] -# .units -# == 'meter' -# ) -# assert section_md.radial_distribution_functions[ -# 0 -# ].radial_distribution_function_values[3].value[52] == approx(0.0) -# assert ( -# section_md.radial_distribution_functions[0] -# .radial_distribution_function_values[3] -# .frame_start -# == 120 -# ) -# assert ( -# section_md.radial_distribution_functions[0] -# .radial_distribution_function_values[3] -# .frame_end -# == 201 -# ) - -# assert ( -# section_md.radial_distribution_functions[0] -# .radial_distribution_function_values[5] -# .label -# == '1-0' -# ) -# assert ( -# section_md.radial_distribution_functions[0] -# .radial_distribution_function_values[5] -# .n_bins -# == 198 -# ) -# assert section_md.radial_distribution_functions[ -# 0 -# ].radial_distribution_function_values[5].bins[102].magnitude == approx( -# 5.802080640792847 * 10 ** (-10) -# ) -# assert ( -# section_md.radial_distribution_functions[0] -# .radial_distribution_function_values[5] -# .bins[102] -# .units -# == 'meter' -# ) -# assert section_md.radial_distribution_functions[ -# 0 -# ].radial_distribution_function_values[5].value[55] == approx(0.0) -# assert ( -# section_md.radial_distribution_functions[0] -# .radial_distribution_function_values[5] -# .frame_start -# == 40 -# ) -# assert ( -# section_md.radial_distribution_functions[0] -# .radial_distribution_function_values[5] -# .frame_end -# == 201 -# ) - -# assert ( -# section_md.radial_distribution_functions[0] -# .radial_distribution_function_values[10] -# .label -# == '1-1' -# ) -# assert ( -# section_md.radial_distribution_functions[0] -# .radial_distribution_function_values[10] -# .n_bins -# == 198 -# ) -# assert section_md.radial_distribution_functions[ -# 0 -# ].radial_distribution_function_values[10].bins[44].magnitude == approx( -# 2.550673131942749 * 10 ** (-10) -# ) -# assert ( -# section_md.radial_distribution_functions[0] -# .radial_distribution_function_values[10] -# .bins[44] -# .units -# == 'meter' -# ) -# assert section_md.radial_distribution_functions[ -# 0 -# ].radial_distribution_function_values[10].value[101] == approx(1.4750986777470825) -# assert ( -# section_md.radial_distribution_functions[0] -# .radial_distribution_function_values[10] -# .frame_start -# == 80 -# ) -# assert ( -# section_md.radial_distribution_functions[0] -# .radial_distribution_function_values[10] -# .frame_end -# == 201 -# ) - -# assert section_md.mean_squared_displacements[0].type == 'molecular' -# assert section_md.mean_squared_displacements[0].direction == 'xyz' - -# assert ( -# section_md.mean_squared_displacements[0] -# .mean_squared_displacement_values[0] -# .label -# == '0' -# ) -# assert ( -# section_md.mean_squared_displacements[0] -# .mean_squared_displacement_values[0] -# .n_times -# == 54 -# ) -# assert section_md.mean_squared_displacements[0].mean_squared_displacement_values[ -# 0 -# ].times[13].magnitude == approx(1.3 * 10 ** (-12)) -# assert ( -# section_md.mean_squared_displacements[0] -# .mean_squared_displacement_values[0] -# .times[13] -# .units -# == 'second' -# ) -# assert section_md.mean_squared_displacements[0].mean_squared_displacement_values[ -# 0 -# ].value[32].magnitude == approx(8.98473539965496 * 10 ** (-19)) -# assert ( -# section_md.mean_squared_displacements[0] -# .mean_squared_displacement_values[0] -# .value[32] -# .units -# == 'meter^2' -# ) -# assert section_md.mean_squared_displacements[0].mean_squared_displacement_values[ -# 0 -# ].diffusion_constant.value.magnitude == approx(6.09812270414572 * 10 ** (-8)) -# assert ( -# section_md.mean_squared_displacements[0] -# .mean_squared_displacement_values[0] -# .diffusion_constant.value.units -# == 'meter^2/second' -# ) -# assert ( -# section_md.mean_squared_displacements[0] -# .mean_squared_displacement_values[0] -# .diffusion_constant.error_type -# == 'Pearson correlation coefficient' -# ) -# assert section_md.mean_squared_displacements[0].mean_squared_displacement_values[ -# 0 -# ].diffusion_constant.errors == approx(0.9924847048341159) - -# assert ( -# section_md.mean_squared_displacements[0] -# .mean_squared_displacement_values[1] -# .label -# == '1' -# ) -# assert ( -# section_md.mean_squared_displacements[0] -# .mean_squared_displacement_values[1] -# .n_times -# == 54 -# ) -# assert section_md.mean_squared_displacements[0].mean_squared_displacement_values[ -# 1 -# ].times[13].magnitude == approx(1.3 * 10 ** (-12)) -# assert ( -# section_md.mean_squared_displacements[0] -# .mean_squared_displacement_values[1] -# .times[13] -# .units -# == 'second' -# ) -# assert section_md.mean_squared_displacements[0].mean_squared_displacement_values[ -# 1 -# ].value[32].magnitude == approx(8.448369705677565 * 10 ** (-19)) -# assert ( -# section_md.mean_squared_displacements[0] -# .mean_squared_displacement_values[1] -# .value[32] -# .units -# == 'meter^2' -# ) -# assert section_md.mean_squared_displacements[0].mean_squared_displacement_values[ -# 1 -# ].diffusion_constant.value.magnitude == approx(5.094072039759048 * 10 ** (-8)) -# assert ( -# section_md.mean_squared_displacements[0] -# .mean_squared_displacement_values[1] -# .diffusion_constant.value.units -# == 'meter^2/second' -# ) -# assert ( -# section_md.mean_squared_displacements[0] -# .mean_squared_displacement_values[1] -# .diffusion_constant.error_type -# == 'Pearson correlation coefficient' -# ) -# assert section_md.mean_squared_displacements[0].mean_squared_displacement_values[ -# 1 -# ].diffusion_constant.errors == approx(0.9965870174917716) - - -# def test_rdf_2(): -# entry_archive = load_archive('tests/data/rdf_2.archive.json') - -# sec_workflow = entry_archive.workflow2 -# section_md = sec_workflow.results - -# assert section_md.radial_distribution_functions[0].type == 'molecular' -# assert section_md.radial_distribution_functions[0].n_smooth == 2 -# assert section_md.radial_distribution_functions[0].variables_name[0] == 'distance' - -# assert ( -# section_md.radial_distribution_functions[0] -# .radial_distribution_function_values[0] -# .label -# == 'SOL-Protein' -# ) -# assert ( -# section_md.radial_distribution_functions[0] -# .radial_distribution_function_values[0] -# .n_bins -# == 198 -# ) -# assert section_md.radial_distribution_functions[ -# 0 -# ].radial_distribution_function_values[0].bins[122].magnitude == approx( -# 7.624056451320648 * 10 ** (-10) -# ) -# assert ( -# section_md.radial_distribution_functions[0] -# .radial_distribution_function_values[0] -# .bins[122] -# .units -# == 'meter' -# ) -# assert section_md.radial_distribution_functions[ -# 0 -# ].radial_distribution_function_values[0].value[96] == approx(1.093694948374587) -# assert ( -# section_md.radial_distribution_functions[0] -# .radial_distribution_function_values[0] -# .frame_start -# == 0 -# ) -# assert ( -# section_md.radial_distribution_functions[0] -# .radial_distribution_function_values[0] -# .frame_end -# == 2 -# ) - -# assert ( -# section_md.radial_distribution_functions[0] -# .radial_distribution_function_values[1] -# .label -# == 'SOL-SOL' -# ) -# assert ( -# section_md.radial_distribution_functions[0] -# .radial_distribution_function_values[1] -# .n_bins -# == 198 -# ) -# assert section_md.radial_distribution_functions[ -# 0 -# ].radial_distribution_function_values[1].bins[102].magnitude == approx( -# 6.389391438961029 * 10 ** (-10) -# ) -# assert ( -# section_md.radial_distribution_functions[0] -# .radial_distribution_function_values[1] -# .bins[102] -# .units -# == 'meter' -# ) -# assert section_md.radial_distribution_functions[ -# 0 -# ].radial_distribution_function_values[1].value[55] == approx(0.8368052672121375) -# assert ( -# section_md.radial_distribution_functions[0] -# .radial_distribution_function_values[1] -# .frame_start -# == 0 -# ) -# assert ( -# section_md.radial_distribution_functions[0] -# .radial_distribution_function_values[1] -# .frame_end -# == 2 -# ) - - -# def test_radius_of_gyration(): -# entry_archive = load_archive('tests/data/radius_of_gyration.archive.json') - -# sec_calc = entry_archive.run[0].calculation[4] -# sec_rg = sec_calc.radius_of_gyration[0] -# sec_rgvals = sec_rg.radius_of_gyration_values[0] - -# assert sec_rg.kind == 'molecular' - -# assert sec_rgvals.label == 'Protein_chain_X-index_0' -# assert sec_rgvals.value.magnitude == approx(5.081165959952965e-10) -# assert sec_rgvals.value.units == 'meter' - -# sec_calc = entry_archive.run[0].calculation[1] -# sec_rg = sec_calc.radius_of_gyration[0] -# sec_rgvals = sec_rg.radius_of_gyration_values[0] - -# assert sec_rg.kind == 'molecular' -# assert sec_rgvals.label == 'Protein_chain_X-index_0' -# assert sec_rgvals.value.magnitude == approx(5.036762961380965e-10) -# assert sec_rgvals.value.units == 'meter' - -# sec_workflow = entry_archive.workflow2 -# sec_rg = sec_workflow.results.radius_of_gyration[0] -# frame = 4 - -# assert sec_rg.type == 'molecular' - -# assert sec_rg.label == 'Protein_chain_X-index_0' -# assert sec_rg.value[frame].magnitude == approx(5.081165959952965e-10) -# assert sec_rg.value[frame].units == 'meter' - -# frame = 1 -# sec_rg = sec_workflow.results.radius_of_gyration[0] -# sec_calc = entry_archive.run[0].calculation[1] - -# assert sec_rg.type == 'molecular' -# assert sec_rg.label == 'Protein_chain_X-index_0' -# assert sec_rg.value[frame].magnitude == approx(5.036762961380965e-10) -# assert sec_rg.value[frame].units == 'meter' +def test_single_point_workflow(): + entry_archive = load_archive('tests/data/single_point.archive.json') + sec_workflow = entry_archive.workflow2 + assert sec_workflow.method.method == 'DFT' + assert sec_workflow.results.n_scf_steps == 9 + assert sec_workflow.results.final_scf_energy_difference > 0 + assert sec_workflow.results.dos is not None + assert sec_workflow.results.band_structure is None + assert sec_workflow.results.eigenvalues is not None + assert sec_workflow.results.density_charge is None + assert sec_workflow.results.spectra is None + assert sec_workflow.results.is_converged + + +# TODO do not skip nomad-lab>=3.15 +@pytest.mark.skip +def test_gw_workflow(gw_workflow): + """Testing GW workflow (DFT+GW) entry""" + workflow = gw_workflow.workflow2 + assert workflow.name == 'DFT+GW' + assert workflow.method.gw_method_ref.type == 'G0W0' + assert workflow.method.electrons_representation.type == 'plane waves' + assert workflow.method.starting_point.name == 'GGA_X_PBE' + results = gw_workflow.results + assert results.method.method_name == 'GW' + assert results.method.workflow_name == 'DFT+GW' + assert results.method.simulation.program_name == 'VASP' + assert results.method.simulation.program_version == '4.6.35' + assert results.method.simulation.gw.type == 'G0W0' + assert results.method.simulation.gw.starting_point_type == 'GGA' + assert results.method.simulation.gw.starting_point_names == ['GGA_X_PBE'] + assert results.method.simulation.gw.basis_set_type == 'plane waves' + assert not results.properties.electronic.band_gap + assert not results.properties.electronic.greens_functions_electronic + assert len(results.properties.electronic.dos_electronic_new) == 2 + assert len(results.properties.electronic.band_structure_electronic) == 2 + assert results.properties.electronic.dos_electronic_new[0].label == 'DFT' + assert results.properties.electronic.dos_electronic_new[1].label == 'GW' + + +# TODO do not skip nomad-lab>=3.15 +@pytest.mark.skip +def test_dmft_workflow(dmft_workflow): + """Testing DMFT workflow entry""" + workflow = dmft_workflow.workflow2 + assert workflow.name == 'TB+DMFT' + assert not workflow.method.tb_method_ref.wannier.is_maximally_localized + assert workflow.method.dmft_method_ref.n_impurities == 1 + assert workflow.method.dmft_method_ref.n_correlated_orbitals[0] == 3 + assert workflow.method.dmft_method_ref.n_electrons[0] == 1.0 + assert workflow.method.dmft_method_ref.inverse_temperature.magnitude == 60.0 + assert workflow.method.dmft_method_ref.magnetic_state == 'paramagnetic' + assert workflow.method.dmft_method_ref.impurity_solver == 'CT-HYB' + results = dmft_workflow.results + assert results.method.method_name == 'DMFT' + assert results.method.workflow_name == 'TB+DMFT' + assert results.method.simulation.program_name == 'w2dynamics' + assert results.method.simulation.dmft.impurity_solver_type == 'CT-HYB' + assert results.method.simulation.dmft.inverse_temperature.magnitude == 60.0 + assert results.method.simulation.dmft.magnetic_state == 'paramagnetic' + assert results.method.simulation.dmft.u.magnitude == 4.0e-19 + assert results.method.simulation.dmft.jh.magnitude == 0.6e-19 + assert results.m_xpath('properties.electronic.band_gap') + assert len(results.properties.electronic.band_gap) == 1 + assert results.properties.electronic.band_gap[0].label == 'TB' + assert results.m_xpath('properties.electronic.band_structure_electronic') + assert len(results.properties.electronic.band_structure_electronic) == 1 + # TODO check why this testing is not passing + # * conftest seems to not be able to normalize the archive_dmft for the Greens functions, despite self_energy_iw is defined. + # assert results.m_xpath('properties.electronic.greens_function_electronic') + + +# TODO do not skip nomad-lab>=3.15 +@pytest.mark.skip +def test_maxent_workflow(maxent_workflow): + """Testing MaxEnt workflow entry""" + workflow = maxent_workflow.workflow2 + assert workflow.name == 'DMFT+MaxEnt' + assert workflow.method.dmft_method_ref.n_impurities == 1 + assert workflow.method.dmft_method_ref.n_correlated_orbitals[0] == 3 + assert workflow.method.dmft_method_ref.n_electrons[0] == 1.0 + assert workflow.method.dmft_method_ref.inverse_temperature.magnitude == 60.0 + assert workflow.method.dmft_method_ref.magnetic_state == 'paramagnetic' + assert workflow.method.dmft_method_ref.impurity_solver == 'CT-HYB' + assert workflow.method.maxent_method_ref + results = maxent_workflow.results + assert results.method.method_name == 'DMFT' + assert results.method.workflow_name == 'DMFT+MaxEnt' + assert results.method.simulation.program_name == 'w2dynamics' + assert results.method.simulation.dmft.impurity_solver_type == 'CT-HYB' + assert results.method.simulation.dmft.inverse_temperature.magnitude == 60.0 + assert results.method.simulation.dmft.magnetic_state == 'paramagnetic' + assert results.method.simulation.dmft.u.magnitude == 4.0e-19 + assert results.method.simulation.dmft.jh.magnitude == 0.6e-19 + assert results.method.simulation.dmft.analytical_continuation == 'MaxEnt' + assert results.m_xpath('properties.electronic.dos_electronic_new') + assert len(results.properties.electronic.dos_electronic_new) == 1 + assert results.m_xpath('properties.electronic.greens_functions_electronic') + assert len(results.properties.electronic.greens_functions_electronic) == 2 + assert results.properties.electronic.greens_functions_electronic[0].label == 'DMFT' + assert ( + results.properties.electronic.greens_functions_electronic[1].label == 'MaxEnt' + ) + + +def test_bse_workflow(bse_workflow): + """Testing BSE workflow (Photon1+Photon2) entry""" + workflow = bse_workflow.workflow2 + assert workflow.name == 'BSE' + assert len(workflow.inputs) == 2 + assert workflow.inputs[0].name == 'Input structure' + assert workflow.inputs[1].name == 'Input BSE methodology' + assert ( + len(workflow.outputs) == 2 + and len(workflow.outputs) == workflow.results.n_polarizations + ) + assert len(workflow.tasks) == 2 + assert workflow.method.bse_method_ref.type == 'Singlet' + assert workflow.method.bse_method_ref.solver == 'Lanczos-Haydock' + results = bse_workflow.results + assert results.method.method_name == 'BSE' + assert results.method.workflow_name == 'BSE' + assert results.method.simulation.program_name == 'VASP' + assert results.method.simulation.program_version == '4.6.35' + assert results.method.simulation.bse.type == 'Singlet' + assert results.method.simulation.bse.solver == 'Lanczos-Haydock' + assert results.properties.spectroscopic + spectra = results.properties.spectroscopic.spectra + assert len(spectra) == 2 + assert spectra[0].type == 'XAS' + assert spectra[0].label == 'computation' + assert spectra[0].n_energies == 11 + assert spectra[0].energies[3].to('eV').magnitude == approx(3.0) + assert spectra[0].intensities[3] == approx(130.0) + assert spectra[0].intensities_units == 'F/m' + assert spectra[0].provenance and spectra[1].provenance + assert spectra[0].provenance != spectra[1].provenance + + +# TODO do not skip nomad-lab>=3.15 +@pytest.mark.skip +def test_xs_workflow(xs_workflow): + """Testing XS workflow (DFT+BSEworkflow) entry""" + workflow = xs_workflow.workflow2 + assert workflow.name == 'XS' + assert len(workflow.inputs) == 1 + assert workflow.inputs[0].name == 'Input structure' + assert len(workflow.outputs) == 2 + assert len(workflow.tasks) == 2 + assert workflow.tasks[0].name == 'DFT' and workflow.tasks[1].name == 'BSE 1' + assert ( + workflow.results.dft_outputs.dos + and workflow.results.dft_outputs.band_structure + and workflow.results.spectra + ) + results = xs_workflow.results + assert results.method.method_name == 'BSE' + assert results.method.workflow_name == 'XS' + assert results.method.simulation.program_name == 'VASP' + assert results.method.simulation.program_version == '4.6.35' + assert results.method.simulation.bse.type == 'Singlet' + assert results.method.simulation.bse.solver == 'Lanczos-Haydock' + assert results.method.simulation.bse.starting_point_type == 'GGA' + assert results.method.simulation.bse.starting_point_names == ['GGA_X_PBE'] + assert results.method.simulation.bse.basis_set_type == 'plane waves' + assert results.properties.electronic and results.properties.spectroscopic + assert results.properties.electronic.dos_electronic_new[0].label == 'DFT' + assert len(results.properties.spectroscopic.spectra) == 2 + assert ( + results.properties.spectroscopic.spectra[0].provenance + != results.properties.spectroscopic.spectra[1].provenance + ) + + +def test_geometry_optimization_workflow(): + entry_archive = load_archive('tests/data/geometry_optimization.archive.json') + sec_workflow = entry_archive.workflow2 + assert sec_workflow.method.type == 'cell_shape' + assert sec_workflow.results.calculation_result_ref.m_def.name == 'Calculation' + assert sec_workflow.results.final_energy_difference.to('eV').magnitude == approx( + 0.00012532 + ) + assert sec_workflow.results.optimization_steps == 3 + assert sec_workflow.results.final_force_maximum > 0.0 + assert sec_workflow.results.is_converged_geometry + + tasks = sec_workflow.tasks + assert len(tasks) == len(entry_archive.run[0].calculation) + assert ( + tasks[0].inputs[0].section.m_proxy_resolve() == entry_archive.run[0].method[0] + ) + assert tasks[-1].outputs[0].section == entry_archive.run[0].calculation[-1] + + +def test_elastic_workflow(): + entry_archive = load_archive('tests/data/elastic.archive.json') + sec_workflow = entry_archive.workflow2 + sec_workflow.results.calculation_result_ref.m_def.name == 'Calculation' + sec_workflow.method.calculation_method == 'energy' + sec_workflow.method.elastic_constants_order == 2 + sec_workflow.results.is_mechanically_stable + sec_workflow.method.fitting_error_maximum > 0.0 + sec_workflow.method.strain_maximum > 0.0 + + +def test_phonon_workflow(): + entry_archive = load_archive('tests/data/phonon.archive.json') + + sec_workflow = entry_archive.workflow2 + assert sec_workflow.results.calculation_result_ref.m_def.name == 'Calculation' + assert sec_workflow.method.force_calculator == 'fhi-aims' + assert sec_workflow.method.mesh_density > 0.0 + assert sec_workflow.results.n_imaginary_frequencies > 0 + assert not sec_workflow.method.random_displacements + assert not sec_workflow.method.with_non_analytic_correction + assert not sec_workflow.method.with_grueneisen_parameters + + +def test_molecular_dynamics_workflow(): + entry_archive = load_archive('tests/data/molecular_dynamics.archive.json') + sec_workflow = entry_archive.workflow2 + sec_workflow.results.calculation_result_ref.m_def.name == 'Calculation' + assert sec_workflow.results.finished_normally + assert sec_workflow.results.trajectory + + +def test_rdf_and_msd(): + entry_archive = load_archive('tests/data/rdf_and_msd.archive.json') + + sec_workflow = entry_archive.workflow2 + section_md = sec_workflow.results + + assert section_md.radial_distribution_functions[0].type == 'molecular' + assert section_md.radial_distribution_functions[0].n_smooth == 2 + assert section_md.radial_distribution_functions[0].variables_name[0] == 'distance' + + assert ( + section_md.radial_distribution_functions[0] + .radial_distribution_function_values[0] + .label + == '0-0' + ) + assert ( + section_md.radial_distribution_functions[0] + .radial_distribution_function_values[0] + .n_bins + == 198 + ) + assert section_md.radial_distribution_functions[ + 0 + ].radial_distribution_function_values[0].bins[122].magnitude == approx( + 6.923255643844605 * 10 ** (-10) + ) + assert ( + section_md.radial_distribution_functions[0] + .radial_distribution_function_values[0] + .bins[122] + .units + == 'meter' + ) + assert section_md.radial_distribution_functions[ + 0 + ].radial_distribution_function_values[0].value[96] == approx(0.0) + assert ( + section_md.radial_distribution_functions[0] + .radial_distribution_function_values[0] + .frame_start + == 0 + ) + assert ( + section_md.radial_distribution_functions[0] + .radial_distribution_function_values[0] + .frame_end + == 40 + ) + + assert ( + section_md.radial_distribution_functions[0] + .radial_distribution_function_values[3] + .label + == '0-0' + ) + assert ( + section_md.radial_distribution_functions[0] + .radial_distribution_function_values[3] + .n_bins + == 198 + ) + assert section_md.radial_distribution_functions[ + 0 + ].radial_distribution_function_values[3].bins[65].magnitude == approx( + 3.727906885147095 * 10 ** (-10) + ) + assert ( + section_md.radial_distribution_functions[0] + .radial_distribution_function_values[3] + .bins[65] + .units + == 'meter' + ) + assert section_md.radial_distribution_functions[ + 0 + ].radial_distribution_function_values[3].value[52] == approx(0.0) + assert ( + section_md.radial_distribution_functions[0] + .radial_distribution_function_values[3] + .frame_start + == 120 + ) + assert ( + section_md.radial_distribution_functions[0] + .radial_distribution_function_values[3] + .frame_end + == 201 + ) + + assert ( + section_md.radial_distribution_functions[0] + .radial_distribution_function_values[5] + .label + == '1-0' + ) + assert ( + section_md.radial_distribution_functions[0] + .radial_distribution_function_values[5] + .n_bins + == 198 + ) + assert section_md.radial_distribution_functions[ + 0 + ].radial_distribution_function_values[5].bins[102].magnitude == approx( + 5.802080640792847 * 10 ** (-10) + ) + assert ( + section_md.radial_distribution_functions[0] + .radial_distribution_function_values[5] + .bins[102] + .units + == 'meter' + ) + assert section_md.radial_distribution_functions[ + 0 + ].radial_distribution_function_values[5].value[55] == approx(0.0) + assert ( + section_md.radial_distribution_functions[0] + .radial_distribution_function_values[5] + .frame_start + == 40 + ) + assert ( + section_md.radial_distribution_functions[0] + .radial_distribution_function_values[5] + .frame_end + == 201 + ) + + assert ( + section_md.radial_distribution_functions[0] + .radial_distribution_function_values[10] + .label + == '1-1' + ) + assert ( + section_md.radial_distribution_functions[0] + .radial_distribution_function_values[10] + .n_bins + == 198 + ) + assert section_md.radial_distribution_functions[ + 0 + ].radial_distribution_function_values[10].bins[44].magnitude == approx( + 2.550673131942749 * 10 ** (-10) + ) + assert ( + section_md.radial_distribution_functions[0] + .radial_distribution_function_values[10] + .bins[44] + .units + == 'meter' + ) + assert section_md.radial_distribution_functions[ + 0 + ].radial_distribution_function_values[10].value[101] == approx(1.4750986777470825) + assert ( + section_md.radial_distribution_functions[0] + .radial_distribution_function_values[10] + .frame_start + == 80 + ) + assert ( + section_md.radial_distribution_functions[0] + .radial_distribution_function_values[10] + .frame_end + == 201 + ) + + assert section_md.mean_squared_displacements[0].type == 'molecular' + assert section_md.mean_squared_displacements[0].direction == 'xyz' + + assert ( + section_md.mean_squared_displacements[0] + .mean_squared_displacement_values[0] + .label + == '0' + ) + assert ( + section_md.mean_squared_displacements[0] + .mean_squared_displacement_values[0] + .n_times + == 54 + ) + assert section_md.mean_squared_displacements[0].mean_squared_displacement_values[ + 0 + ].times[13].magnitude == approx(1.3 * 10 ** (-12)) + assert ( + section_md.mean_squared_displacements[0] + .mean_squared_displacement_values[0] + .times[13] + .units + == 'second' + ) + assert section_md.mean_squared_displacements[0].mean_squared_displacement_values[ + 0 + ].value[32].magnitude == approx(8.98473539965496 * 10 ** (-19)) + assert ( + section_md.mean_squared_displacements[0] + .mean_squared_displacement_values[0] + .value[32] + .units + == 'meter^2' + ) + assert section_md.mean_squared_displacements[0].mean_squared_displacement_values[ + 0 + ].diffusion_constant.value.magnitude == approx(6.09812270414572 * 10 ** (-8)) + assert ( + section_md.mean_squared_displacements[0] + .mean_squared_displacement_values[0] + .diffusion_constant.value.units + == 'meter^2/second' + ) + assert ( + section_md.mean_squared_displacements[0] + .mean_squared_displacement_values[0] + .diffusion_constant.error_type + == 'Pearson correlation coefficient' + ) + assert section_md.mean_squared_displacements[0].mean_squared_displacement_values[ + 0 + ].diffusion_constant.errors == approx(0.9924847048341159) + + assert ( + section_md.mean_squared_displacements[0] + .mean_squared_displacement_values[1] + .label + == '1' + ) + assert ( + section_md.mean_squared_displacements[0] + .mean_squared_displacement_values[1] + .n_times + == 54 + ) + assert section_md.mean_squared_displacements[0].mean_squared_displacement_values[ + 1 + ].times[13].magnitude == approx(1.3 * 10 ** (-12)) + assert ( + section_md.mean_squared_displacements[0] + .mean_squared_displacement_values[1] + .times[13] + .units + == 'second' + ) + assert section_md.mean_squared_displacements[0].mean_squared_displacement_values[ + 1 + ].value[32].magnitude == approx(8.448369705677565 * 10 ** (-19)) + assert ( + section_md.mean_squared_displacements[0] + .mean_squared_displacement_values[1] + .value[32] + .units + == 'meter^2' + ) + assert section_md.mean_squared_displacements[0].mean_squared_displacement_values[ + 1 + ].diffusion_constant.value.magnitude == approx(5.094072039759048 * 10 ** (-8)) + assert ( + section_md.mean_squared_displacements[0] + .mean_squared_displacement_values[1] + .diffusion_constant.value.units + == 'meter^2/second' + ) + assert ( + section_md.mean_squared_displacements[0] + .mean_squared_displacement_values[1] + .diffusion_constant.error_type + == 'Pearson correlation coefficient' + ) + assert section_md.mean_squared_displacements[0].mean_squared_displacement_values[ + 1 + ].diffusion_constant.errors == approx(0.9965870174917716) + + +def test_rdf_2(): + entry_archive = load_archive('tests/data/rdf_2.archive.json') + + sec_workflow = entry_archive.workflow2 + section_md = sec_workflow.results + + assert section_md.radial_distribution_functions[0].type == 'molecular' + assert section_md.radial_distribution_functions[0].n_smooth == 2 + assert section_md.radial_distribution_functions[0].variables_name[0] == 'distance' + + assert ( + section_md.radial_distribution_functions[0] + .radial_distribution_function_values[0] + .label + == 'SOL-Protein' + ) + assert ( + section_md.radial_distribution_functions[0] + .radial_distribution_function_values[0] + .n_bins + == 198 + ) + assert section_md.radial_distribution_functions[ + 0 + ].radial_distribution_function_values[0].bins[122].magnitude == approx( + 7.624056451320648 * 10 ** (-10) + ) + assert ( + section_md.radial_distribution_functions[0] + .radial_distribution_function_values[0] + .bins[122] + .units + == 'meter' + ) + assert section_md.radial_distribution_functions[ + 0 + ].radial_distribution_function_values[0].value[96] == approx(1.093694948374587) + assert ( + section_md.radial_distribution_functions[0] + .radial_distribution_function_values[0] + .frame_start + == 0 + ) + assert ( + section_md.radial_distribution_functions[0] + .radial_distribution_function_values[0] + .frame_end + == 2 + ) + + assert ( + section_md.radial_distribution_functions[0] + .radial_distribution_function_values[1] + .label + == 'SOL-SOL' + ) + assert ( + section_md.radial_distribution_functions[0] + .radial_distribution_function_values[1] + .n_bins + == 198 + ) + assert section_md.radial_distribution_functions[ + 0 + ].radial_distribution_function_values[1].bins[102].magnitude == approx( + 6.389391438961029 * 10 ** (-10) + ) + assert ( + section_md.radial_distribution_functions[0] + .radial_distribution_function_values[1] + .bins[102] + .units + == 'meter' + ) + assert section_md.radial_distribution_functions[ + 0 + ].radial_distribution_function_values[1].value[55] == approx(0.8368052672121375) + assert ( + section_md.radial_distribution_functions[0] + .radial_distribution_function_values[1] + .frame_start + == 0 + ) + assert ( + section_md.radial_distribution_functions[0] + .radial_distribution_function_values[1] + .frame_end + == 2 + ) + + +def test_radius_of_gyration(): + entry_archive = load_archive('tests/data/radius_of_gyration.archive.json') + + sec_calc = entry_archive.run[0].calculation[4] + sec_rg = sec_calc.radius_of_gyration[0] + sec_rgvals = sec_rg.radius_of_gyration_values[0] + + assert sec_rg.kind == 'molecular' + + assert sec_rgvals.label == 'Protein_chain_X-index_0' + assert sec_rgvals.value.magnitude == approx(5.081165959952965e-10) + assert sec_rgvals.value.units == 'meter' + + sec_calc = entry_archive.run[0].calculation[1] + sec_rg = sec_calc.radius_of_gyration[0] + sec_rgvals = sec_rg.radius_of_gyration_values[0] + + assert sec_rg.kind == 'molecular' + assert sec_rgvals.label == 'Protein_chain_X-index_0' + assert sec_rgvals.value.magnitude == approx(5.036762961380965e-10) + assert sec_rgvals.value.units == 'meter' + + sec_workflow = entry_archive.workflow2 + sec_rg = sec_workflow.results.radius_of_gyration[0] + frame = 4 + + assert sec_rg.type == 'molecular' + + assert sec_rg.label == 'Protein_chain_X-index_0' + assert sec_rg.value[frame].magnitude == approx(5.081165959952965e-10) + assert sec_rg.value[frame].units == 'meter' + + frame = 1 + sec_rg = sec_workflow.results.radius_of_gyration[0] + sec_calc = entry_archive.run[0].calculation[1] + + assert sec_rg.type == 'molecular' + assert sec_rg.label == 'Protein_chain_X-index_0' + assert sec_rg.value[frame].magnitude == approx(5.036762961380965e-10) + assert sec_rg.value[frame].units == 'meter' def parse_trajectory(filename): @@ -738,8 +738,6 @@ def parse_trajectory(filename): def test_eos_workflow(): archive = parse_trajectory('tests/data/ase/Cu.traj') - print(archive.workflow2.tasks) - print(archive.workflow2.results.eos_fit) eos_fit = archive.workflow2.results.eos_fit assert len(eos_fit) == 5 assert eos_fit[0].fitted_energies[1].to('eV').magnitude == approx(-0.00636507) @@ -753,264 +751,264 @@ def test_eos_workflow(): assert eos_fit[4].rms_error == approx(1.408202378222592e-07) -# class TestSimulationWorkflow: -# """ -# Tests for the base simulation workflow class. -# """ - -# n_calc = 10 - -# @pytest.fixture(autouse=True) -# def serial_simulation(self) -> EntryArchive: -# """ -# Simulation with calculations done in serial. -# """ -# archive = EntryArchive() -# archive.metadata = EntryMetadata(entry_type='Workflow') -# archive.workflow2 = SimulationWorkflow( -# method=SimulationWorkflowMethod(), results=SimulationWorkflowResults() -# ) - -# archive.run.append( -# Run( -# calculation=[ -# Calculation(time_physical=t, time_calculation=1) -# for t in range(1, self.n_calc + 1) -# ] -# ) -# ) -# return archive - -# def test_tasks_serial(self, serial_simulation): -# """ -# Test tasks creation of purely serial calculation. -# """ -# workflow = serial_simulation.workflow2 -# workflow.normalize(serial_simulation, LOGGER) -# assert len(workflow.tasks) == self.n_calc - -# assert workflow.inputs[0].section == workflow.tasks[0].inputs[0].section -# for n, task in enumerate(workflow.tasks[:-1]): -# assert task.name == f'Step {n + 1}' -# assert len(task.inputs) == 1 -# assert len(task.outputs) == 1 -# assert task.outputs[0].section == workflow.tasks[n + 1].inputs[0].section -# assert workflow.outputs[0].section == workflow.tasks[-1].outputs[0].section - -# def test_tasks_defined(self, serial_simulation): -# """ -# Test tasks creation skipped if tasks are predefined -# """ -# workflow = SimulationWorkflow(tasks=[Task(name='1')]) -# serial_simulation.workflow2 = workflow -# workflow.normalize(serial_simulation, LOGGER) - -# assert len(serial_simulation.workflow2.tasks) == 1 -# assert serial_simulation.workflow2.tasks[0].name == '1' - -# def test_tasks_no_time(self, serial_simulation): -# """ -# Test tasks creation skipped if at least one calculation has no time info. -# """ - -# for key in ['time_physical', 'time_calculation']: -# calc = serial_simulation.run[0].calculation[ -# random.randint(0, self.n_calc - 1) -# ] -# calc.m_set(calc.m_get_quantity_definition(key), None) -# serial_simulation.workflow2.normalize(serial_simulation, LOGGER) -# assert not serial_simulation.workflow2.tasks - -# @pytest.mark.parametrize( -# 'calculation_indices', -# [ -# # parallel (0 to 3), 4, 5, parallel (6 to 9) -# [[0, 1, 2, 3], [4], [5], [6, 7, 8, 9]], -# # 0, parallel (1 to 2), 4, 5, 6, 7, 8, 8 -# [[0], [1, 2], [3], [4], [5], [6], [7], [8], [9]], -# # parallel (0 to 8), 9 -# [[0, 1, 2, 3, 4, 5, 6, 7, 8], [9]], -# ], -# ) -# def test_task_not_serial(self, serial_simulation, calculation_indices): -# """ -# Test creation for mixed serial and parallel tasks. -# """ - -# def _create_times(indices, start_time=0): -# times = [] -# for n in indices: -# if not isinstance(n, int): -# times.extend( -# sorted( -# _create_times(n, start_time=times[-1][1] if times else 0), -# key=lambda x: x[1], -# ) -# ) -# else: -# calc_time = random.random() -# dt = random.random() * 0.1 # small perturbation -# times.append([n, calc_time + start_time + dt, calc_time]) -# return times - -# for n, time_physical, time_calculation in _create_times(calculation_indices): -# serial_simulation.run[-1].calculation[n].time_physical = time_physical -# serial_simulation.run[-1].calculation[n].time_calculation = time_calculation - -# workflow = serial_simulation.workflow2 -# workflow.normalize(serial_simulation, LOGGER) -# assert len(workflow.tasks) == 10 - -# # workflow inputs as inputs to first parallel tasks -# for n in calculation_indices[0]: -# assert workflow.tasks[n].name == 'Step 1' -# assert workflow.tasks[n].inputs[0].section == workflow.inputs[0].section - -# # outputs of previous tasks are inputs of succeeding tasks in series -# for i in range(1, len(calculation_indices)): -# for n1 in calculation_indices[i]: -# assert workflow.tasks[n1].name == f'Step {i + 1}' -# inputs = [input.section for input in workflow.tasks[n1].inputs] -# for n0 in calculation_indices[i - 1]: -# assert workflow.tasks[n0].outputs[-1].section in inputs - -# # last parallel tasks oututs as workflow outputs -# for n in calculation_indices[-1]: -# assert workflow.tasks[n].outputs[0].section in [ -# output.section for output in workflow.outputs -# ] - - -# class TestChemicalReactionWorkflow: -# """ -# Contains tests for the matinfo defintion and normalization of the chemical reaction -# workflow. -# """ - -# @pytest.fixture(autouse=True, scope='class') -# def dft_archives(self): -# """ -# Parse all relevant dft calculations. -# """ -# test_dir = 'tests/data/chemical_reaction' - -# archives = {} -# for root, _, names in os.walk(test_dir): -# for filename in names: -# if filename != 'run.archive.json': -# continue -# archives[os.path.basename(root)] = load_archive( -# os.path.join(root, filename) -# ) -# return archives - -# @pytest.fixture(autouse=True) -# def segregation_workflow_archive(self, dft_archives): -# """ -# Constructs a chemical reaction workflow archive describing the segregation of H -# from RhCu_CH4 into RhCu_CH3 and RhCu_H through a transition state RhCu_CH3_H. -# """ -# formula_type = [ -# ['RhCu_CH4', 'reactant'], -# ['RhCu', 'reactant'], -# ['RhCu_CH3_H', 'transition state'], -# ['RhCu_CH3', 'product'], -# ['RhCu_xHfcc', 'product'], -# ] -# workflow = ChemicalReaction() -# for formula, type in formula_type: -# archive = dft_archives[formula] -# workflow.inputs.append( -# Link(name=f'{formula} {type}', section=archive.run[0].calculation[-1]) -# ) -# # add also slab to transition state to preserve mass balance -# if formula == 'RhCu': -# workflow.inputs.append( -# Link( -# name=f'transition state {formula}', -# section=archive.run[0].calculation[-1], -# ) -# ) - -# return EntryArchive( -# metadata=EntryMetadata(entry_type='Workflow'), workflow2=workflow -# ) - -# @pytest.fixture(autouse=True) -# def adsorption_workflow_archive(self, dft_archives): -# """ -# Constructs a chemical reaction workflow archive describing the adsorption of N -# in PdAg. -# """ - -# formula_type = [ -# ['N', 'reactant'], -# ['PdAg', 'reactant'], -# ['NPdAg', 'product'], -# ] -# workflow = ChemicalReaction() -# for formula, type in formula_type: -# archive = dft_archives[formula] -# workflow.inputs.append( -# Link(name=f'{formula} {type}', section=archive.run[0].calculation[-1]) -# ) - -# return EntryArchive( -# metadata=EntryMetadata(entry_type='Workflow'), workflow2=workflow -# ) - -# @pytest.mark.parametrize( -# 'workflow_archive, reaction_energy, activation_energy', -# [ -# pytest.param( -# 'segregation_workflow_archive', -# 4.41467915e-20, -# 1.02994872e-19, -# id='segregation', -# ), -# pytest.param( -# 'adsorption_workflow_archive', -3.04029682e-19, None, id='adsorption' -# ), -# ], -# ) -# def test_reaction_energy( -# self, request, workflow_archive, reaction_energy, activation_energy -# ): -# """ -# Test the calculation of reaction and activation energy. -# """ -# workflow_archive = request.getfixturevalue(workflow_archive) -# workflow = workflow_archive.workflow2 -# workflow.normalize(workflow_archive, LOGGER) - -# assert np.isclose( -# workflow.results.reaction_energy.magnitude, -# reaction_energy, -# atol=0, -# rtol=1e6, -# ) -# if activation_energy: -# assert np.isclose( -# workflow.results.activation_energy.magnitude, -# activation_energy, -# atol=0, -# rtol=1e6, -# ) -# assert len(workflow.tasks) == 1 - -# def test_system_checks(self, segregation_workflow_archive): -# """ -# Test the checks for the consistency of the system from reactants -# """ -# workflow = segregation_workflow_archive.workflow2 -# # change the system size in an input to make them inconsistent -# lattice = np.array(workflow.inputs[-1].section.system_ref.atoms.lattice_vectors) -# workflow.inputs[-1].section.system_ref.atoms.lattice_vectors = np.ones((3, 3)) -# workflow.normalize(segregation_workflow_archive, LOGGER) -# assert workflow.results.reaction_energy is None - -# # change the chemical composition in an input to make them inconsistent -# workflow.inputs[-1].section.system_ref.atoms.lattice_vectoprs = lattice -# workflow.inputs[0].section.system_ref.atoms.labels = ['C'] -# workflow.normalize(segregation_workflow_archive, LOGGER) -# assert workflow.results.reaction_energy is None +class TestSimulationWorkflow: + """ + Tests for the base simulation workflow class. + """ + + n_calc = 10 + + @pytest.fixture(autouse=True) + def serial_simulation(self) -> EntryArchive: + """ + Simulation with calculations done in serial. + """ + archive = EntryArchive() + archive.metadata = EntryMetadata(entry_type='Workflow') + archive.workflow2 = SimulationWorkflow( + method=SimulationWorkflowMethod(), results=SimulationWorkflowResults() + ) + + archive.run.append( + Run( + calculation=[ + Calculation(time_physical=t, time_calculation=1) + for t in range(1, self.n_calc + 1) + ] + ) + ) + return archive + + def test_tasks_serial(self, serial_simulation): + """ + Test tasks creation of purely serial calculation. + """ + workflow = serial_simulation.workflow2 + workflow.normalize(serial_simulation, LOGGER) + assert len(workflow.tasks) == self.n_calc + + assert workflow.inputs[0].section == workflow.tasks[0].inputs[0].section + for n, task in enumerate(workflow.tasks[:-1]): + assert task.name == f'Step {n + 1}' + assert len(task.inputs) == 1 + assert len(task.outputs) == 1 + assert task.outputs[0].section == workflow.tasks[n + 1].inputs[0].section + assert workflow.outputs[0].section == workflow.tasks[-1].outputs[0].section + + def test_tasks_defined(self, serial_simulation): + """ + Test tasks creation skipped if tasks are predefined + """ + workflow = SimulationWorkflow(tasks=[Task(name='1')]) + serial_simulation.workflow2 = workflow + workflow.normalize(serial_simulation, LOGGER) + + assert len(serial_simulation.workflow2.tasks) == 1 + assert serial_simulation.workflow2.tasks[0].name == '1' + + def test_tasks_no_time(self, serial_simulation): + """ + Test tasks creation skipped if at least one calculation has no time info. + """ + + for key in ['time_physical', 'time_calculation']: + calc = serial_simulation.run[0].calculation[ + random.randint(0, self.n_calc - 1) + ] + calc.m_set(calc.m_get_quantity_definition(key), None) + serial_simulation.workflow2.normalize(serial_simulation, LOGGER) + assert not serial_simulation.workflow2.tasks + + @pytest.mark.parametrize( + 'calculation_indices', + [ + # parallel (0 to 3), 4, 5, parallel (6 to 9) + [[0, 1, 2, 3], [4], [5], [6, 7, 8, 9]], + # 0, parallel (1 to 2), 4, 5, 6, 7, 8, 8 + [[0], [1, 2], [3], [4], [5], [6], [7], [8], [9]], + # parallel (0 to 8), 9 + [[0, 1, 2, 3, 4, 5, 6, 7, 8], [9]], + ], + ) + def test_task_not_serial(self, serial_simulation, calculation_indices): + """ + Test creation for mixed serial and parallel tasks. + """ + + def _create_times(indices, start_time=0): + times = [] + for n in indices: + if not isinstance(n, int): + times.extend( + sorted( + _create_times(n, start_time=times[-1][1] if times else 0), + key=lambda x: x[1], + ) + ) + else: + calc_time = random.random() + dt = random.random() * 0.1 # small perturbation + times.append([n, calc_time + start_time + dt, calc_time]) + return times + + for n, time_physical, time_calculation in _create_times(calculation_indices): + serial_simulation.run[-1].calculation[n].time_physical = time_physical + serial_simulation.run[-1].calculation[n].time_calculation = time_calculation + + workflow = serial_simulation.workflow2 + workflow.normalize(serial_simulation, LOGGER) + assert len(workflow.tasks) == 10 + + # workflow inputs as inputs to first parallel tasks + for n in calculation_indices[0]: + assert workflow.tasks[n].name == 'Step 1' + assert workflow.tasks[n].inputs[0].section == workflow.inputs[0].section + + # outputs of previous tasks are inputs of succeeding tasks in series + for i in range(1, len(calculation_indices)): + for n1 in calculation_indices[i]: + assert workflow.tasks[n1].name == f'Step {i + 1}' + inputs = [input.section for input in workflow.tasks[n1].inputs] + for n0 in calculation_indices[i - 1]: + assert workflow.tasks[n0].outputs[-1].section in inputs + + # last parallel tasks oututs as workflow outputs + for n in calculation_indices[-1]: + assert workflow.tasks[n].outputs[0].section in [ + output.section for output in workflow.outputs + ] + + +class TestChemicalReactionWorkflow: + """ + Contains tests for the matinfo defintion and normalization of the chemical reaction + workflow. + """ + + @pytest.fixture(autouse=True, scope='class') + def dft_archives(self): + """ + Parse all relevant dft calculations. + """ + test_dir = 'tests/data/chemical_reaction' + + archives = {} + for root, _, names in os.walk(test_dir): + for filename in names: + if filename != 'run.archive.json': + continue + archives[os.path.basename(root)] = load_archive( + os.path.join(root, filename) + ) + return archives + + @pytest.fixture(autouse=True) + def segregation_workflow_archive(self, dft_archives): + """ + Constructs a chemical reaction workflow archive describing the segregation of H + from RhCu_CH4 into RhCu_CH3 and RhCu_H through a transition state RhCu_CH3_H. + """ + formula_type = [ + ['RhCu_CH4', 'reactant'], + ['RhCu', 'reactant'], + ['RhCu_CH3_H', 'transition state'], + ['RhCu_CH3', 'product'], + ['RhCu_xHfcc', 'product'], + ] + workflow = ChemicalReaction() + for formula, type in formula_type: + archive = dft_archives[formula] + workflow.inputs.append( + Link(name=f'{formula} {type}', section=archive.run[0].calculation[-1]) + ) + # add also slab to transition state to preserve mass balance + if formula == 'RhCu': + workflow.inputs.append( + Link( + name=f'transition state {formula}', + section=archive.run[0].calculation[-1], + ) + ) + + return EntryArchive( + metadata=EntryMetadata(entry_type='Workflow'), workflow2=workflow + ) + + @pytest.fixture(autouse=True) + def adsorption_workflow_archive(self, dft_archives): + """ + Constructs a chemical reaction workflow archive describing the adsorption of N + in PdAg. + """ + + formula_type = [ + ['N', 'reactant'], + ['PdAg', 'reactant'], + ['NPdAg', 'product'], + ] + workflow = ChemicalReaction() + for formula, type in formula_type: + archive = dft_archives[formula] + workflow.inputs.append( + Link(name=f'{formula} {type}', section=archive.run[0].calculation[-1]) + ) + + return EntryArchive( + metadata=EntryMetadata(entry_type='Workflow'), workflow2=workflow + ) + + @pytest.mark.parametrize( + 'workflow_archive, reaction_energy, activation_energy', + [ + pytest.param( + 'segregation_workflow_archive', + 4.41467915e-20, + 1.02994872e-19, + id='segregation', + ), + pytest.param( + 'adsorption_workflow_archive', -3.04029682e-19, None, id='adsorption' + ), + ], + ) + def test_reaction_energy( + self, request, workflow_archive, reaction_energy, activation_energy + ): + """ + Test the calculation of reaction and activation energy. + """ + workflow_archive = request.getfixturevalue(workflow_archive) + workflow = workflow_archive.workflow2 + workflow.normalize(workflow_archive, LOGGER) + + assert np.isclose( + workflow.results.reaction_energy.magnitude, + reaction_energy, + atol=0, + rtol=1e6, + ) + if activation_energy: + assert np.isclose( + workflow.results.activation_energy.magnitude, + activation_energy, + atol=0, + rtol=1e6, + ) + assert len(workflow.tasks) == 1 + + def test_system_checks(self, segregation_workflow_archive): + """ + Test the checks for the consistency of the system from reactants + """ + workflow = segregation_workflow_archive.workflow2 + # change the system size in an input to make them inconsistent + lattice = np.array(workflow.inputs[-1].section.system_ref.atoms.lattice_vectors) + workflow.inputs[-1].section.system_ref.atoms.lattice_vectors = np.ones((3, 3)) + workflow.normalize(segregation_workflow_archive, LOGGER) + assert workflow.results.reaction_energy is None + + # change the chemical composition in an input to make them inconsistent + workflow.inputs[-1].section.system_ref.atoms.lattice_vectoprs = lattice + workflow.inputs[0].section.system_ref.atoms.labels = ['C'] + workflow.normalize(segregation_workflow_archive, LOGGER) + assert workflow.results.reaction_energy is None From 0edcc01aa6758debe1a87eef5c0fd70532b36b69 Mon Sep 17 00:00:00 2001 From: jrudz Date: Thu, 8 May 2025 22:18:34 +0200 Subject: [PATCH 09/25] fixed missing workflow2 section --- simulationworkflowschema/equation_of_state.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simulationworkflowschema/equation_of_state.py b/simulationworkflowschema/equation_of_state.py index fa79455..84a47c2 100644 --- a/simulationworkflowschema/equation_of_state.py +++ b/simulationworkflowschema/equation_of_state.py @@ -165,7 +165,7 @@ def normalize(self, archive, logger): tasks = [ task for i_task, task in enumerate(self.tasks) - if isinstance(task_archives[i_task], SinglePoint) + if isinstance(task_archives[i_task].workflow2, SinglePoint) ] except Exception: logger.warning( From 17fc14c8e47109d8d3653c9d2427bdf2ca972720 Mon Sep 17 00:00:00 2001 From: jrudz Date: Wed, 21 May 2025 08:12:41 +0200 Subject: [PATCH 10/25] new structure with GO as input --- simulationworkflowschema/equation_of_state.py | 73 ++++++++++++++++--- 1 file changed, 61 insertions(+), 12 deletions(-) diff --git a/simulationworkflowschema/equation_of_state.py b/simulationworkflowschema/equation_of_state.py index 84a47c2..8269ec9 100644 --- a/simulationworkflowschema/equation_of_state.py +++ b/simulationworkflowschema/equation_of_state.py @@ -32,6 +32,7 @@ ) from .single_point import SinglePoint from runschema.run import Run, Program +from runschema.system import System class EquationOfStateMethod(SimulationWorkflowMethod): @@ -149,9 +150,57 @@ class EquationOfState(ParallelSimulation): results = SubSection(sub_section=EquationOfStateResults) + def extract_indices_from_proxy_value(self, path: str): + """ + Extracts run_index and system_index from a path string that contains '/run//system/'. + + Args: + path (str): The input path string. + + Returns: + tuple: (run_index, system_index) as integers, or (None, None) if not found or parsing fails. + """ + parts = path.split('/run/') + if len(parts) > 1: + try: + indices = parts[1].split('/system/') + run_index = int(indices[0]) + system_index = int(indices[1]) + return run_index, system_index + except (IndexError, ValueError): + return None, None + else: + return None, None + def normalize(self, archive, logger): super().normalize(archive, logger) + # find and verify the input structure + if self.inputs: + input_structure = {} + for input_item in self.inputs: + if isinstance(input_item.section.m_proxy_resolve(), System): + if input_structure: + logger.warning( + 'Multiple input structures found. Using the first one.' + ) + continue + input_structure['system'] = input_item.section.m_proxy_resolved + run_index, system_index = self.extract_indices_from_proxy_value( + input_item.section.m_proxy_value + ) + input_archive = input_item.section.m_root() + if input_archive: + input_structure['method'] = input_archive.run[run_index].method + if system_index == -1: + system_index = len(input_archive.run[run_index].system) - 1 + for calc in input_archive.run[run_index].calculation: + if calc.system_ref.m_parent_index == system_index: + input_structure['calculation'] = calc + break + if not input_structure: + logger.warning('No input structure found in EOS workflow normalizer.') + if not self.method: self.method = EquationOfStateMethod() self.inputs.append(Link(name=WORKFLOW_METHOD_NAME, section=self.method)) @@ -162,21 +211,22 @@ def normalize(self, archive, logger): try: task_archives = [task.task.m_root() for task in self.tasks] - tasks = [ - task - for i_task, task in enumerate(self.tasks) - if isinstance(task_archives[i_task].workflow2, SinglePoint) - ] + assert all( + isinstance(task_archive.workflow2, SinglePoint) + for task_archive in task_archives + ) except Exception: logger.warning( - 'Failed to get task archives. Cannot filter for SinglePoint tasks.' + 'Not all tasks are SinglePoints or failed to retrieve task archives. EOS workflow may be incomplete or incorrect.' ) + # TODO - overwrite IOs of tasks to match the given input structure + if not self._calculations: # try to get calculations from tasks (in case of instantiation from workflow yaml) try: self._calculations = [ - task.task.results.calculations_ref[0] for task in tasks + task.task.results.calculations_ref[0] for task in self.tasks ] except Exception: pass @@ -243,11 +293,10 @@ def normalize(self, archive, logger): if not archive.run: run = Run(program=Program()) try: - # assuming the final structure is the relevant one, e.g., from a GO - run.system.extend([task_archives[0].run[0].system[-1]]) - run.method.extend(task_archives[0].run[0].method) - run.calculation.extend([task_archives[0].run[0].calculation[-1]]) + run.system.extend([input_structure['system']]) + run.method.extend(input_structure['method']) + run.calculation.extend([input_structure['calculation']]) except Exception: - logger.warning('Failed to link structure from first task archive. ') + logger.warning('Failed to create run section from input structure. ') archive.run.append(run) From cb3dc2b6c495803ba557fc1344cc9adf3b300beb Mon Sep 17 00:00:00 2001 From: jrudz Date: Wed, 21 May 2025 08:51:54 +0200 Subject: [PATCH 11/25] check and fill task inputs --- simulationworkflowschema/equation_of_state.py | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/simulationworkflowschema/equation_of_state.py b/simulationworkflowschema/equation_of_state.py index 8269ec9..791201d 100644 --- a/simulationworkflowschema/equation_of_state.py +++ b/simulationworkflowschema/equation_of_state.py @@ -185,6 +185,8 @@ def normalize(self, archive, logger): 'Multiple input structures found. Using the first one.' ) continue + input_structure['name'] = input_item.name + input_structure['section'] = input_item.section input_structure['system'] = input_item.section.m_proxy_resolved run_index, system_index = self.extract_indices_from_proxy_value( input_item.section.m_proxy_value @@ -220,8 +222,25 @@ def normalize(self, archive, logger): 'Not all tasks are SinglePoints or failed to retrieve task archives. EOS workflow may be incomplete or incorrect.' ) - # TODO - overwrite IOs of tasks to match the given input structure - + for task in self.tasks: + flag_input_structure = False + for input in task.inputs: + if ( + input.section.m_proxy_value + == input_structure['section'].m_proxy_value + ): + # overwrite the name of the task input to match the global input + input.name = input_structure['name'] + flag_input_structure = True + break + if not flag_input_structure: + # TODO - Test this! + # add the global input structure to each task if not already present + task.inputs.append( + Link( + name=input_structure['name'], section=input_structure['section'] + ) + ) if not self._calculations: # try to get calculations from tasks (in case of instantiation from workflow yaml) try: @@ -290,6 +309,7 @@ def normalize(self, archive, logger): logger.warning('EOS fit not succesful.') # necessary to trigger results normalization + # TODO - test moving this above and/or removing super.normalize if not archive.run: run = Run(program=Program()) try: From d9a05c6fe59381ddea871bfaf1475acb29c06395 Mon Sep 17 00:00:00 2001 From: jrudz Date: Wed, 21 May 2025 14:03:45 +0200 Subject: [PATCH 12/25] added proxy vs. non-proxy support for system section, debugging comments still present --- simulationworkflowschema/equation_of_state.py | 128 ++++++++++++------ 1 file changed, 85 insertions(+), 43 deletions(-) diff --git a/simulationworkflowschema/equation_of_state.py b/simulationworkflowschema/equation_of_state.py index 791201d..9c41d4d 100644 --- a/simulationworkflowschema/equation_of_state.py +++ b/simulationworkflowschema/equation_of_state.py @@ -21,7 +21,7 @@ from nomad.atomutils import get_volume from nomad.datamodel.data import ArchiveSection from nomad.units import ureg -from nomad.metainfo import SubSection, Section, Quantity +from nomad.metainfo import SubSection, Section, Quantity, MProxy from nomad.datamodel.metainfo.workflow import Link from .general import ( SimulationWorkflowMethod, @@ -150,48 +150,76 @@ class EquationOfState(ParallelSimulation): results = SubSection(sub_section=EquationOfStateResults) - def extract_indices_from_proxy_value(self, path: str): - """ - Extracts run_index and system_index from a path string that contains '/run//system/'. - - Args: - path (str): The input path string. - - Returns: - tuple: (run_index, system_index) as integers, or (None, None) if not found or parsing fails. - """ - parts = path.split('/run/') - if len(parts) > 1: - try: - indices = parts[1].split('/system/') - run_index = int(indices[0]) - system_index = int(indices[1]) - return run_index, system_index - except (IndexError, ValueError): - return None, None - else: - return None, None + # def extract_indices_from_proxy_value(self, path: str): + # """ + # Extracts run_index and system_index from a path string that contains '/run//system/'. + + # Args: + # path (str): The input path string. + + # Returns: + # tuple: (run_index, system_index) as integers, or (None, None) if not found or parsing fails. + # """ + # parts = path.split('/run/') + # if len(parts) > 1: + # try: + # indices = parts[1].split('/system/') + # run_index = int(indices[0]) + # system_index = int(indices[1]) + # return run_index, system_index + # except (IndexError, ValueError): + # return None, None + # else: + # return None, None def normalize(self, archive, logger): super().normalize(archive, logger) + print('in eos workflow normalizer') # find and verify the input structure if self.inputs: input_structure = {} for input_item in self.inputs: - if isinstance(input_item.section.m_proxy_resolve(), System): + # self.m_proxy_value = m_proxy_value + # self.m_proxy_section = m_proxy_section + # self.m_proxy_resolved = None + # self.m_proxy_type = m_proxy_type + # self.m_proxy_context = m_proxy_context + # print(input_item) + # logger.warning(f'input_item: {input_item}') + # print(f'input_item.m_proxy_value: {input_item.section}') + # logger.warning(f'input_item.m_proxy_value: {input_item.section}') + if isinstance(input_item.section, MProxy): + input_section = input_item.section.m_proxy_resolve() + input_proxy = input_item.section + # input_system = input_item.section.m_proxy_resolved + # run_index, system_index = self.extract_indices_from_proxy_value( + # input_item.section.m_proxy_value + # ) + else: + input_section = input_item.section + input_proxy = None + # input_system = input_item.section + system_index = input_section.m_parent_index + run_section = input_section.m_parent + run_index = run_section.m_parent_index + # logger.warning( + # f'input_system: {input_system}, system_index: {system_index}' + # ) + # logger.warning(f'run_section: {run_section}, run_index: {run_index}') + # run_index, system_index = ...? + if isinstance(input_section, System): if input_structure: logger.warning( 'Multiple input structures found. Using the first one.' ) continue input_structure['name'] = input_item.name - input_structure['section'] = input_item.section - input_structure['system'] = input_item.section.m_proxy_resolved - run_index, system_index = self.extract_indices_from_proxy_value( - input_item.section.m_proxy_value - ) - input_archive = input_item.section.m_root() + # input_structure['section'] = input_section + input_structure['system'] = input_section + input_archive = ( + input_section.m_root() + ) # input_item.section.m_root() if input_archive: input_structure['method'] = input_archive.run[run_index].method if system_index == -1: @@ -203,6 +231,19 @@ def normalize(self, archive, logger): if not input_structure: logger.warning('No input structure found in EOS workflow normalizer.') + # print('input structure:', input_structure) + # logger.warning(f'input structure: {input_structure}') + if not archive.run and input_structure: + run = Run(program=Program()) + try: + run.system.extend([input_structure['system']]) + run.method.extend(input_structure['method']) + run.calculation.extend([input_structure['calculation']]) + except Exception: + logger.warning('Failed to create run section from input structure. ') + + archive.run.append(run) + if not self.method: self.method = EquationOfStateMethod() self.inputs.append(Link(name=WORKFLOW_METHOD_NAME, section=self.method)) @@ -225,10 +266,11 @@ def normalize(self, archive, logger): for task in self.tasks: flag_input_structure = False for input in task.inputs: - if ( - input.section.m_proxy_value - == input_structure['section'].m_proxy_value - ): + # logger.warning( + # f'input_structure.m_def: {input_structure["section"].m_def}' + # ) + # logger.warning(f'input.section.m_def: {input.section.m_def}') + if input.section.m_proxy_value == input_proxy.m_proxy_value: # overwrite the name of the task input to match the global input input.name = input_structure['name'] flag_input_structure = True @@ -310,13 +352,13 @@ def normalize(self, archive, logger): # necessary to trigger results normalization # TODO - test moving this above and/or removing super.normalize - if not archive.run: - run = Run(program=Program()) - try: - run.system.extend([input_structure['system']]) - run.method.extend(input_structure['method']) - run.calculation.extend([input_structure['calculation']]) - except Exception: - logger.warning('Failed to create run section from input structure. ') - - archive.run.append(run) + # if not archive.run: + # run = Run(program=Program()) + # try: + # run.system.extend([input_structure['system']]) + # run.method.extend(input_structure['method']) + # run.calculation.extend([input_structure['calculation']]) + # except Exception: + # logger.warning('Failed to create run section from input structure. ') + + # archive.run.append(run) From 085ef615596cd23a040c0b68a8a389418d823627 Mon Sep 17 00:00:00 2001 From: jrudz Date: Wed, 21 May 2025 14:15:49 +0200 Subject: [PATCH 13/25] improved comparison of m_proxy_value in case of no proxy --- simulationworkflowschema/equation_of_state.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/simulationworkflowschema/equation_of_state.py b/simulationworkflowschema/equation_of_state.py index 9c41d4d..99706eb 100644 --- a/simulationworkflowschema/equation_of_state.py +++ b/simulationworkflowschema/equation_of_state.py @@ -191,14 +191,14 @@ def normalize(self, archive, logger): # logger.warning(f'input_item.m_proxy_value: {input_item.section}') if isinstance(input_item.section, MProxy): input_section = input_item.section.m_proxy_resolve() - input_proxy = input_item.section + input_proxy_value = input_item.section.m_proxy_value # input_system = input_item.section.m_proxy_resolved # run_index, system_index = self.extract_indices_from_proxy_value( # input_item.section.m_proxy_value # ) else: input_section = input_item.section - input_proxy = None + input_proxy_value = '' # input_system = input_item.section system_index = input_section.m_parent_index run_section = input_section.m_parent @@ -270,7 +270,7 @@ def normalize(self, archive, logger): # f'input_structure.m_def: {input_structure["section"].m_def}' # ) # logger.warning(f'input.section.m_def: {input.section.m_def}') - if input.section.m_proxy_value == input_proxy.m_proxy_value: + if input.section.m_proxy_value == input_proxy_value: # overwrite the name of the task input to match the global input input.name = input_structure['name'] flag_input_structure = True From c8cf76d854b368f8d9dde5f439c0142382f76a19 Mon Sep 17 00:00:00 2001 From: jrudz Date: Wed, 21 May 2025 20:39:28 +0200 Subject: [PATCH 14/25] some reorg and simplification --- simulationworkflowschema/equation_of_state.py | 181 +++++++----------- tests/test_simulationworkflowschema.py | 30 +-- 2 files changed, 85 insertions(+), 126 deletions(-) diff --git a/simulationworkflowschema/equation_of_state.py b/simulationworkflowschema/equation_of_state.py index 99706eb..4a42a46 100644 --- a/simulationworkflowschema/equation_of_state.py +++ b/simulationworkflowschema/equation_of_state.py @@ -21,7 +21,7 @@ from nomad.atomutils import get_volume from nomad.datamodel.data import ArchiveSection from nomad.units import ureg -from nomad.metainfo import SubSection, Section, Quantity, MProxy +from nomad.metainfo import SubSection, Section, Quantity from nomad.datamodel.metainfo.workflow import Link from .general import ( SimulationWorkflowMethod, @@ -150,99 +150,51 @@ class EquationOfState(ParallelSimulation): results = SubSection(sub_section=EquationOfStateResults) - # def extract_indices_from_proxy_value(self, path: str): - # """ - # Extracts run_index and system_index from a path string that contains '/run//system/'. - - # Args: - # path (str): The input path string. - - # Returns: - # tuple: (run_index, system_index) as integers, or (None, None) if not found or parsing fails. - # """ - # parts = path.split('/run/') - # if len(parts) > 1: - # try: - # indices = parts[1].split('/system/') - # run_index = int(indices[0]) - # system_index = int(indices[1]) - # return run_index, system_index - # except (IndexError, ValueError): - # return None, None - # else: - # return None, None - def normalize(self, archive, logger): super().normalize(archive, logger) - print('in eos workflow normalizer') - # find and verify the input structure + # TODO - Check beyond the failing old test for example from a parser + + # find the input structure if self.inputs: - input_structure = {} + flag_input_structure = False + input_proxy_value = '' for input_item in self.inputs: - # self.m_proxy_value = m_proxy_value - # self.m_proxy_section = m_proxy_section - # self.m_proxy_resolved = None - # self.m_proxy_type = m_proxy_type - # self.m_proxy_context = m_proxy_context - # print(input_item) - # logger.warning(f'input_item: {input_item}') - # print(f'input_item.m_proxy_value: {input_item.section}') - # logger.warning(f'input_item.m_proxy_value: {input_item.section}') - if isinstance(input_item.section, MProxy): - input_section = input_item.section.m_proxy_resolve() - input_proxy_value = input_item.section.m_proxy_value - # input_system = input_item.section.m_proxy_resolved - # run_index, system_index = self.extract_indices_from_proxy_value( - # input_item.section.m_proxy_value - # ) - else: - input_section = input_item.section - input_proxy_value = '' - # input_system = input_item.section + section = input_item.section.m_resolved() + if not isinstance(section, System): + continue + + flag_input_structure = True + input_section = input_item.section.m_proxy_resolved system_index = input_section.m_parent_index run_section = input_section.m_parent run_index = run_section.m_parent_index - # logger.warning( - # f'input_system: {input_system}, system_index: {system_index}' - # ) - # logger.warning(f'run_section: {run_section}, run_index: {run_index}') - # run_index, system_index = ...? - if isinstance(input_section, System): - if input_structure: - logger.warning( - 'Multiple input structures found. Using the first one.' - ) - continue - input_structure['name'] = input_item.name - # input_structure['section'] = input_section - input_structure['system'] = input_section - input_archive = ( - input_section.m_root() - ) # input_item.section.m_root() - if input_archive: - input_structure['method'] = input_archive.run[run_index].method - if system_index == -1: - system_index = len(input_archive.run[run_index].system) - 1 + input_proxy_value = input_item.section.m_proxy_value + input_name = input_item.name + input_archive = input_section.m_root() + if input_archive: + if system_index == -1: + system_index = len(input_archive.run[run_index].system) - 1 + if not archive.run: + run = Run(program=Program()) + try: + run.system.extend([input_section]) + run.method.extend(input_archive.run[run_index].method) for calc in input_archive.run[run_index].calculation: if calc.system_ref.m_parent_index == system_index: - input_structure['calculation'] = calc + run.calculation.extend([calc]) break - if not input_structure: - logger.warning('No input structure found in EOS workflow normalizer.') + except Exception: + logger.warning( + 'Failed to create run section from input structure. ' + ) - # print('input structure:', input_structure) - # logger.warning(f'input structure: {input_structure}') - if not archive.run and input_structure: - run = Run(program=Program()) - try: - run.system.extend([input_structure['system']]) - run.method.extend(input_structure['method']) - run.calculation.extend([input_structure['calculation']]) - except Exception: - logger.warning('Failed to create run section from input structure. ') + archive.run.append(run) - archive.run.append(run) + break + + if not flag_input_structure: + logger.warning('No input structure found in EOS workflow normalizer.') if not self.method: self.method = EquationOfStateMethod() @@ -262,27 +214,45 @@ def normalize(self, archive, logger): logger.warning( 'Not all tasks are SinglePoints or failed to retrieve task archives. EOS workflow may be incomplete or incorrect.' ) + return for task in self.tasks: - flag_input_structure = False - for input in task.inputs: - # logger.warning( - # f'input_structure.m_def: {input_structure["section"].m_def}' - # ) - # logger.warning(f'input.section.m_def: {input.section.m_def}') - if input.section.m_proxy_value == input_proxy_value: - # overwrite the name of the task input to match the global input - input.name = input_structure['name'] - flag_input_structure = True - break - if not flag_input_structure: + # ALVIN's Suggestion + # sections = [input.section for input in task.inputs] + # if input_section in sections: # ! Does not work! + # task.inputs[sections.index(input_section)].name = input_section.name + # else: + # # add the input structure to each task if not already present + # task.inputs.append(Link(name=input_name, section=input_section)) + + # NEW TRY + proxy_values = [input.section.m_proxy_value for input in task.inputs] + if input_proxy_value in proxy_values: + # get the index of the match + index = proxy_values.index(input_proxy_value) + task.inputs[index].name = input_name + else: # TODO - Test this! - # add the global input structure to each task if not already present - task.inputs.append( - Link( - name=input_structure['name'], section=input_structure['section'] - ) - ) + # add the input structure to each task if not already present + task.inputs.append(Link(name=input_name, section=input_section)) + + # OLD IMPLEMENTATION + # flag_input_structure = False + # for input in task.inputs: + # if input.section.m_proxy_value == input_proxy_value: + # # overwrite the name of the task input to match the global input + # input.name = input_structure['name'] + # flag_input_structure = True + # break + # if not flag_input_structure: + # # TODO - Test this! + # # add the global input structure to each task if not already present + # task.inputs.append( + # Link( + # name=input_structure['name'], section=input_structure['system'] + # ) + # ) + if not self._calculations: # try to get calculations from tasks (in case of instantiation from workflow yaml) try: @@ -349,16 +319,3 @@ def normalize(self, archive, logger): self.results.eos_fit.append(eos_fit) except Exception: logger.warning('EOS fit not succesful.') - - # necessary to trigger results normalization - # TODO - test moving this above and/or removing super.normalize - # if not archive.run: - # run = Run(program=Program()) - # try: - # run.system.extend([input_structure['system']]) - # run.method.extend(input_structure['method']) - # run.calculation.extend([input_structure['calculation']]) - # except Exception: - # logger.warning('Failed to create run section from input structure. ') - - # archive.run.append(run) diff --git a/tests/test_simulationworkflowschema.py b/tests/test_simulationworkflowschema.py index cb6005a..9b6c069 100644 --- a/tests/test_simulationworkflowschema.py +++ b/tests/test_simulationworkflowschema.py @@ -735,20 +735,22 @@ def parse_trajectory(filename): return archive -def test_eos_workflow(): - archive = parse_trajectory('tests/data/ase/Cu.traj') - - eos_fit = archive.workflow2.results.eos_fit - assert len(eos_fit) == 5 - assert eos_fit[0].fitted_energies[1].to('eV').magnitude == approx(-0.00636507) - assert eos_fit[1].function_name == 'pourier_tarantola' - assert eos_fit[2].equilibrium_volume.to('angstrom**3').magnitude == approx( - 11.565388081047471 - ) - assert eos_fit[3].equilibrium_energy.to('eV').magnitude == approx( - -0.007035923370513912 - ) - assert eos_fit[4].rms_error == approx(1.408202378222592e-07) +# ! This parser seems to has very strange behavior, none of the basic m_xxx +# ! attributes are defined for the section refs of the workflow +# def test_eos_workflow(): +# archive = parse_trajectory('tests/data/ase/Cu.traj') + +# eos_fit = archive.workflow2.results.eos_fit +# assert len(eos_fit) == 5 +# assert eos_fit[0].fitted_energies[1].to('eV').magnitude == approx(-0.00636507) +# assert eos_fit[1].function_name == 'pourier_tarantola' +# assert eos_fit[2].equilibrium_volume.to('angstrom**3').magnitude == approx( +# 11.565388081047471 +# ) +# assert eos_fit[3].equilibrium_energy.to('eV').magnitude == approx( +# -0.007035923370513912 +# ) +# assert eos_fit[4].rms_error == approx(1.408202378222592e-07) class TestSimulationWorkflow: From 868f62bac610056f4512ecd91273b580f4fb9a37 Mon Sep 17 00:00:00 2001 From: jrudz Date: Fri, 30 May 2025 15:31:24 +0200 Subject: [PATCH 15/25] save before refactor for non-proxy --- simulationworkflowschema/equation_of_state.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simulationworkflowschema/equation_of_state.py b/simulationworkflowschema/equation_of_state.py index 4a42a46..a6421b7 100644 --- a/simulationworkflowschema/equation_of_state.py +++ b/simulationworkflowschema/equation_of_state.py @@ -21,7 +21,7 @@ from nomad.atomutils import get_volume from nomad.datamodel.data import ArchiveSection from nomad.units import ureg -from nomad.metainfo import SubSection, Section, Quantity +from nomad.metainfo import SubSection, Section, Quantity, MProxy from nomad.datamodel.metainfo.workflow import Link from .general import ( SimulationWorkflowMethod, From cb233fafe3f07d6105f8be69d83245a73776a261 Mon Sep 17 00:00:00 2001 From: jrudz Date: Fri, 30 May 2025 15:59:44 +0200 Subject: [PATCH 16/25] refactored for non-proxy, some unit test, have not tested yet --- simulationworkflowschema/equation_of_state.py | 69 ++++++++++--------- tests/test_simulationworkflowschema.py | 52 ++++++++++++++ 2 files changed, 89 insertions(+), 32 deletions(-) diff --git a/simulationworkflowschema/equation_of_state.py b/simulationworkflowschema/equation_of_state.py index a6421b7..e71c18b 100644 --- a/simulationworkflowschema/equation_of_state.py +++ b/simulationworkflowschema/equation_of_state.py @@ -158,18 +158,26 @@ def normalize(self, archive, logger): # find the input structure if self.inputs: flag_input_structure = False - input_proxy_value = '' for input_item in self.inputs: - section = input_item.section.m_resolved() + # Always resolve proxies to their actual objects + section = ( + input_item.section.m_resolved() + if isinstance(input_item.section, MProxy) + else input_item.section + ) if not isinstance(section, System): continue flag_input_structure = True - input_section = input_item.section.m_proxy_resolved + # Use resolved section for all further logic + input_section = ( + input_item.section.m_proxy_resolved + if isinstance(input_item.section, MProxy) + else input_item.section + ) system_index = input_section.m_parent_index run_section = input_section.m_parent run_index = run_section.m_parent_index - input_proxy_value = input_item.section.m_proxy_value input_name = input_item.name input_archive = input_section.m_root() if input_archive: @@ -217,42 +225,39 @@ def normalize(self, archive, logger): return for task in self.tasks: - # ALVIN's Suggestion - # sections = [input.section for input in task.inputs] - # if input_section in sections: # ! Does not work! - # task.inputs[sections.index(input_section)].name = input_section.name + # --- BEGIN OLD CODE --- + # proxy_values = [input.section.m_proxy_value for input in task.inputs] + # if input_proxy_value in proxy_values: + # # get the index of the match + # index = proxy_values.index(input_proxy_value) + # task.inputs[index].name = input_name # else: + # # TODO - Test this! # # add the input structure to each task if not already present # task.inputs.append(Link(name=input_name, section=input_section)) + # --- END OLD CODE --- + + # Refactored: Always resolve proxies to their actual objects and use id() for comparison + task_input_ids = [ + id( + inp.section.m_resolved() + if isinstance(inp.section, MProxy) + else inp.section + ) + for inp in task.inputs + ] + input_id = id( + input_section.m_resolved() + if isinstance(input_section, MProxy) + else input_section + ) - # NEW TRY - proxy_values = [input.section.m_proxy_value for input in task.inputs] - if input_proxy_value in proxy_values: - # get the index of the match - index = proxy_values.index(input_proxy_value) + if input_id in task_input_ids: + index = task_input_ids.index(input_id) task.inputs[index].name = input_name else: - # TODO - Test this! - # add the input structure to each task if not already present task.inputs.append(Link(name=input_name, section=input_section)) - # OLD IMPLEMENTATION - # flag_input_structure = False - # for input in task.inputs: - # if input.section.m_proxy_value == input_proxy_value: - # # overwrite the name of the task input to match the global input - # input.name = input_structure['name'] - # flag_input_structure = True - # break - # if not flag_input_structure: - # # TODO - Test this! - # # add the global input structure to each task if not already present - # task.inputs.append( - # Link( - # name=input_structure['name'], section=input_structure['system'] - # ) - # ) - if not self._calculations: # try to get calculations from tasks (in case of instantiation from workflow yaml) try: diff --git a/tests/test_simulationworkflowschema.py b/tests/test_simulationworkflowschema.py index 9b6c069..8749217 100644 --- a/tests/test_simulationworkflowschema.py +++ b/tests/test_simulationworkflowschema.py @@ -753,6 +753,58 @@ def parse_trajectory(filename): # assert eos_fit[4].rms_error == approx(1.408202378222592e-07) +def test_eos_workflow_minimal(): + """ + Minimal synthetic test for EOS normalization and fit logic. + """ + from nomad.datamodel import EntryArchive + from simulationworkflowschema.equation_of_state import EquationOfState + import numpy as np + + # Create minimal synthetic archive with 5 points + archive = EntryArchive() + workflow = EquationOfState() + archive.workflow2 = workflow + + # Volumes and energies (parabola with minimum at v=2.0) + volumes = np.linspace(1.8, 2.2, 5) + energies = 0.5 * (volumes - 2.0) ** 2 - 1.0 + + # Attach as workflow inputs (simulate calculation sections) + class DummyCalc: + pass + + workflow.inputs = [] + for v, e in zip(volumes, energies): + calc = DummyCalc() + calc.energy_total = type( + 'E', (), {'to': lambda self, unit: type('V', (), {'magnitude': float(e)})()} + )() + calc.system_ref = type( + 'S', (), {'atoms': type('A', (), {'get_volume': lambda self: float(v)})()} + )() + workflow.inputs.append(type('L', (), {'section': calc})()) + + # Run normalization + workflow.normalize(archive, LOGGER) + + # Check fit results + assert hasattr(workflow.results, 'eos_fit') + fits = workflow.results.eos_fit + assert isinstance(fits, list) + assert len(fits) > 0 + for fit in fits: + assert hasattr(fit, 'function_name') + assert hasattr(fit, 'fitted_energies') + assert hasattr(fit, 'equilibrium_volume') + assert hasattr(fit, 'equilibrium_energy') + assert hasattr(fit, 'rms_error') + assert len(fit.fitted_energies) == len(volumes) + assert fit.equilibrium_volume is not None + assert fit.equilibrium_energy is not None + assert fit.rms_error >= 0 + + class TestSimulationWorkflow: """ Tests for the base simulation workflow class. From 393f26f5c804afc89f996a754e5b18abbac5f8f8 Mon Sep 17 00:00:00 2001 From: jrudz Date: Tue, 3 Jun 2025 14:30:29 +0200 Subject: [PATCH 17/25] extend for non-proxy sections --- simulationworkflowschema/equation_of_state.py | 22 +++----- tests/test_simulationworkflowschema.py | 52 ------------------- 2 files changed, 7 insertions(+), 67 deletions(-) diff --git a/simulationworkflowschema/equation_of_state.py b/simulationworkflowschema/equation_of_state.py index e71c18b..f6a9ee0 100644 --- a/simulationworkflowschema/equation_of_state.py +++ b/simulationworkflowschema/equation_of_state.py @@ -159,7 +159,13 @@ def normalize(self, archive, logger): if self.inputs: flag_input_structure = False for input_item in self.inputs: - # Always resolve proxies to their actual objects + # DEBUGGING: resolve input_item.section to its actual object + # in order to test non-proxy case with proxy data + # TODO - Remove this after proper testing is included + # logger.warning(f'Is proxy: {isinstance(input_item.section, MProxy)}') ## DEBUG + # input_item.section.m_resolved() ## DEBUG + # logger.warning(f'Is proxy: {isinstance(input_item.section, MProxy)}') ## DEBUG + section = ( input_item.section.m_resolved() if isinstance(input_item.section, MProxy) @@ -169,7 +175,6 @@ def normalize(self, archive, logger): continue flag_input_structure = True - # Use resolved section for all further logic input_section = ( input_item.section.m_proxy_resolved if isinstance(input_item.section, MProxy) @@ -225,19 +230,6 @@ def normalize(self, archive, logger): return for task in self.tasks: - # --- BEGIN OLD CODE --- - # proxy_values = [input.section.m_proxy_value for input in task.inputs] - # if input_proxy_value in proxy_values: - # # get the index of the match - # index = proxy_values.index(input_proxy_value) - # task.inputs[index].name = input_name - # else: - # # TODO - Test this! - # # add the input structure to each task if not already present - # task.inputs.append(Link(name=input_name, section=input_section)) - # --- END OLD CODE --- - - # Refactored: Always resolve proxies to their actual objects and use id() for comparison task_input_ids = [ id( inp.section.m_resolved() diff --git a/tests/test_simulationworkflowschema.py b/tests/test_simulationworkflowschema.py index 8749217..9b6c069 100644 --- a/tests/test_simulationworkflowschema.py +++ b/tests/test_simulationworkflowschema.py @@ -753,58 +753,6 @@ def parse_trajectory(filename): # assert eos_fit[4].rms_error == approx(1.408202378222592e-07) -def test_eos_workflow_minimal(): - """ - Minimal synthetic test for EOS normalization and fit logic. - """ - from nomad.datamodel import EntryArchive - from simulationworkflowschema.equation_of_state import EquationOfState - import numpy as np - - # Create minimal synthetic archive with 5 points - archive = EntryArchive() - workflow = EquationOfState() - archive.workflow2 = workflow - - # Volumes and energies (parabola with minimum at v=2.0) - volumes = np.linspace(1.8, 2.2, 5) - energies = 0.5 * (volumes - 2.0) ** 2 - 1.0 - - # Attach as workflow inputs (simulate calculation sections) - class DummyCalc: - pass - - workflow.inputs = [] - for v, e in zip(volumes, energies): - calc = DummyCalc() - calc.energy_total = type( - 'E', (), {'to': lambda self, unit: type('V', (), {'magnitude': float(e)})()} - )() - calc.system_ref = type( - 'S', (), {'atoms': type('A', (), {'get_volume': lambda self: float(v)})()} - )() - workflow.inputs.append(type('L', (), {'section': calc})()) - - # Run normalization - workflow.normalize(archive, LOGGER) - - # Check fit results - assert hasattr(workflow.results, 'eos_fit') - fits = workflow.results.eos_fit - assert isinstance(fits, list) - assert len(fits) > 0 - for fit in fits: - assert hasattr(fit, 'function_name') - assert hasattr(fit, 'fitted_energies') - assert hasattr(fit, 'equilibrium_volume') - assert hasattr(fit, 'equilibrium_energy') - assert hasattr(fit, 'rms_error') - assert len(fit.fitted_energies) == len(volumes) - assert fit.equilibrium_volume is not None - assert fit.equilibrium_energy is not None - assert fit.rms_error >= 0 - - class TestSimulationWorkflow: """ Tests for the base simulation workflow class. From cee83c5acce992fd7a2a0d5e763034179ddaffa8 Mon Sep 17 00:00:00 2001 From: jrudz Date: Tue, 3 Jun 2025 21:55:29 +0200 Subject: [PATCH 18/25] non-proxy compatibility for identifying system, but not comparing task inputs --- simulationworkflowschema/equation_of_state.py | 57 +++++++++++++------ 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/simulationworkflowschema/equation_of_state.py b/simulationworkflowschema/equation_of_state.py index f6a9ee0..6ff537c 100644 --- a/simulationworkflowschema/equation_of_state.py +++ b/simulationworkflowschema/equation_of_state.py @@ -166,6 +166,11 @@ def normalize(self, archive, logger): # input_item.section.m_resolved() ## DEBUG # logger.warning(f'Is proxy: {isinstance(input_item.section, MProxy)}') ## DEBUG + # Old way to compare sections + input_proxy_value = '' + if isinstance(input_item.section, MProxy): + input_proxy_value = input_item.section.m_proxy_value + section = ( input_item.section.m_resolved() if isinstance(input_item.section, MProxy) @@ -230,26 +235,46 @@ def normalize(self, archive, logger): return for task in self.tasks: - task_input_ids = [ - id( - inp.section.m_resolved() - if isinstance(inp.section, MProxy) - else inp.section - ) - for inp in task.inputs - ] - input_id = id( - input_section.m_resolved() - if isinstance(input_section, MProxy) - else input_section - ) - - if input_id in task_input_ids: - index = task_input_ids.index(input_id) + # DEBUGGING: resolve task.inputs their its actual object + # in order to test non-proxy case with proxy data + # TODO - Remove this after proper testing is included + # task_input_ids = [ + # id( + # inp.section.m_resolved() + # if isinstance(inp.section, MProxy) + # else inp.section + # ) + # for inp in task.inputs + # ] + # Check for matches by proxy value + input_proxy_values = [input.section.m_proxy_value for input in task.inputs] + if input_proxy_value in input_proxy_values: + # get the index of the match + index = input_proxy_values.index(input_proxy_value) task.inputs[index].name = input_name else: + # TODO - Test this! + # add the input structure to each task if not already present task.inputs.append(Link(name=input_name, section=input_section)) + # ! Does not work since I want to match simply the archive path of the section + # Check for matches by section id + # task_input_ids = [ + # id( + # inp.section.m_resolved() + # if isinstance(inp.section, MProxy) + # else inp.section + # ) + # for inp in task.inputs + # ] + # input_id = id(input_section) + + # if input_id in task_input_ids: + # index = task_input_ids.index(input_id) + # task.inputs[index].name = input_name + # else: + # task.inputs.append(Link(name=input_name, section=input_section)) + if not self._calculations: # try to get calculations from tasks (in case of instantiation from workflow yaml) try: From f840c0fa228fc38d8fd5e513dd76cda0f1b23f47 Mon Sep 17 00:00:00 2001 From: jrudz Date: Wed, 4 Jun 2025 10:03:59 +0200 Subject: [PATCH 19/25] simplified approach + clean --- simulationworkflowschema/equation_of_state.py | 62 ++----------------- 1 file changed, 4 insertions(+), 58 deletions(-) diff --git a/simulationworkflowschema/equation_of_state.py b/simulationworkflowschema/equation_of_state.py index 6ff537c..831913e 100644 --- a/simulationworkflowschema/equation_of_state.py +++ b/simulationworkflowschema/equation_of_state.py @@ -153,38 +153,17 @@ class EquationOfState(ParallelSimulation): def normalize(self, archive, logger): super().normalize(archive, logger) - # TODO - Check beyond the failing old test for example from a parser - # find the input structure if self.inputs: flag_input_structure = False + input_proxy_value = '' for input_item in self.inputs: - # DEBUGGING: resolve input_item.section to its actual object - # in order to test non-proxy case with proxy data - # TODO - Remove this after proper testing is included - # logger.warning(f'Is proxy: {isinstance(input_item.section, MProxy)}') ## DEBUG - # input_item.section.m_resolved() ## DEBUG - # logger.warning(f'Is proxy: {isinstance(input_item.section, MProxy)}') ## DEBUG - - # Old way to compare sections - input_proxy_value = '' - if isinstance(input_item.section, MProxy): - input_proxy_value = input_item.section.m_proxy_value - - section = ( - input_item.section.m_resolved() - if isinstance(input_item.section, MProxy) - else input_item.section - ) - if not isinstance(section, System): + input_proxy_value = input_item.section.m_proxy_value + input_section = input_item.section.m_resolved() + if not isinstance(input_section, System): continue flag_input_structure = True - input_section = ( - input_item.section.m_proxy_resolved - if isinstance(input_item.section, MProxy) - else input_item.section - ) system_index = input_section.m_parent_index run_section = input_section.m_parent run_index = run_section.m_parent_index @@ -235,46 +214,13 @@ def normalize(self, archive, logger): return for task in self.tasks: - # DEBUGGING: resolve task.inputs their its actual object - # in order to test non-proxy case with proxy data - # TODO - Remove this after proper testing is included - # task_input_ids = [ - # id( - # inp.section.m_resolved() - # if isinstance(inp.section, MProxy) - # else inp.section - # ) - # for inp in task.inputs - # ] - # Check for matches by proxy value input_proxy_values = [input.section.m_proxy_value for input in task.inputs] if input_proxy_value in input_proxy_values: - # get the index of the match index = input_proxy_values.index(input_proxy_value) task.inputs[index].name = input_name else: - # TODO - Test this! - # add the input structure to each task if not already present task.inputs.append(Link(name=input_name, section=input_section)) - # ! Does not work since I want to match simply the archive path of the section - # Check for matches by section id - # task_input_ids = [ - # id( - # inp.section.m_resolved() - # if isinstance(inp.section, MProxy) - # else inp.section - # ) - # for inp in task.inputs - # ] - # input_id = id(input_section) - - # if input_id in task_input_ids: - # index = task_input_ids.index(input_id) - # task.inputs[index].name = input_name - # else: - # task.inputs.append(Link(name=input_name, section=input_section)) - if not self._calculations: # try to get calculations from tasks (in case of instantiation from workflow yaml) try: From 7c7dde67345288d88c719515566bf6234d414b87 Mon Sep 17 00:00:00 2001 From: jrudz Date: Fri, 6 Jun 2025 16:15:16 +0200 Subject: [PATCH 20/25] revert to full paths in yaml --- simulationworkflowschema/equation_of_state.py | 82 ++++++++++++++++++- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/simulationworkflowschema/equation_of_state.py b/simulationworkflowschema/equation_of_state.py index 831913e..3165921 100644 --- a/simulationworkflowschema/equation_of_state.py +++ b/simulationworkflowschema/equation_of_state.py @@ -21,7 +21,7 @@ from nomad.atomutils import get_volume from nomad.datamodel.data import ArchiveSection from nomad.units import ureg -from nomad.metainfo import SubSection, Section, Quantity, MProxy +from nomad.metainfo import SubSection, Section, Quantity from nomad.datamodel.metainfo.workflow import Link from .general import ( SimulationWorkflowMethod, @@ -150,20 +150,59 @@ class EquationOfState(ParallelSimulation): results = SubSection(sub_section=EquationOfStateResults) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.default_archive_paths = { + 'input': 'run/0/system/-1', + 'task': 'workflow2', + } + + def get_default_archive_path(self, raw_proxy_value, section_type='') -> str: + """ + Returns a certain archive path if the raw proxy value points to the root of the archive. + """ + if raw_proxy_value is None: + return '' + + if '#/' in raw_proxy_value: + _, after = raw_proxy_value.split('#/', 1) + if after: + return '' + else: + return self.default_archive_paths.get(section_type, '') + else: + return '' + def normalize(self, archive, logger): super().normalize(archive, logger) + logger.warning(f'self.tasks: {self.tasks}') + logger.warning(f'self.inputs: {self.inputs}') + # find the input structure if self.inputs: flag_input_structure = False input_proxy_value = '' for input_item in self.inputs: - input_proxy_value = input_item.section.m_proxy_value input_section = input_item.section.m_resolved() + # TODO - I need an alternative method to get the full input section path + # ! m_proxy_value is not available for "noraml sections" + raw_proxy_value = input_item.section.m_proxy_value + logger.warning(f'raw_proxy_value: {raw_proxy_value}') + default_path = self.get_default_archive_path( + raw_proxy_value, section_type='input' + ) + logger.warning(f'default_path: {default_path}') + if default_path != '': + archive_root = archive.m_context.resolve_archive(raw_proxy_value) + input_section = archive_root.m_resolve(default_path) if not isinstance(input_section, System): continue + logger.warning(f'self.tasks: {self.tasks}') + flag_input_structure = True + input_proxy_value = raw_proxy_value + default_path system_index = input_section.m_parent_index run_section = input_section.m_parent run_index = run_section.m_parent_index @@ -190,6 +229,8 @@ def normalize(self, archive, logger): break + logger.warning(f'self.tasks: {self.tasks}') + if not flag_input_structure: logger.warning('No input structure found in EOS workflow normalizer.') @@ -214,6 +255,22 @@ def normalize(self, archive, logger): return for task in self.tasks: + # TODO - I need an alternative method to get the full input section path + # ! m_proxy_value is not available for "noraml sections" + # logger.warning(f'task: {task.task}') + # logger.warning(f'task.section: {task.task.section}') + # raw_proxy_value = task.section.m_proxy_value + # default_path = self.get_default_archive_path( + # raw_proxy_value, section_type='task' + # ) + # if default_path: + # # replace the task section with the default for tasks + # # task.section = task.section.m_xpath(default_path) + # archive_root = archive.m_context.resolve_archive(raw_proxy_value) + # task.section = archive_root.m_resolve(default_path) + + # TODO - I need an alternative method to get the full input section path + # ! m_proxy_value is not available for "noraml sections" input_proxy_values = [input.section.m_proxy_value for input in task.inputs] if input_proxy_value in input_proxy_values: index = input_proxy_values.index(input_proxy_value) @@ -287,3 +344,24 @@ def normalize(self, archive, logger): self.results.eos_fit.append(eos_fit) except Exception: logger.warning('EOS fit not succesful.') + + # @staticmethod + # def archive_path_to_jmespath(path: str) -> str: + # """ + # Converts an archive path like 'run/0/system/-1' to a jmespath like 'run[0].system[-1]'. + # """ + # if not path: + # return '' + # parts = path.strip('/').split('/') + # jmes = [] + # i = 0 + # while i < len(parts): + # part = parts[i] + # # If next part is an integer, treat as index + # if i + 1 < len(parts) and parts[i + 1].lstrip('-').isdigit(): + # jmes.append(f"{part}[{parts[i + 1]}]") + # i += 2 + # else: + # jmes.append(part) + # i += 1 + # return '.'.join(jmes) From 3771792ca431bf5a11bde6b6983bc858c37f2417 Mon Sep 17 00:00:00 2001 From: jrudz Date: Fri, 6 Jun 2025 16:23:20 +0200 Subject: [PATCH 21/25] reinsert old test --- simulationworkflowschema/equation_of_state.py | 4 +++ tests/test_simulationworkflowschema.py | 28 +++++++++---------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/simulationworkflowschema/equation_of_state.py b/simulationworkflowschema/equation_of_state.py index 3165921..9dee703 100644 --- a/simulationworkflowschema/equation_of_state.py +++ b/simulationworkflowschema/equation_of_state.py @@ -187,6 +187,8 @@ def normalize(self, archive, logger): input_section = input_item.section.m_resolved() # TODO - I need an alternative method to get the full input section path # ! m_proxy_value is not available for "noraml sections" + print(f'input_item: {input_item}') + print(f'input_item.section: {input_item.section}') raw_proxy_value = input_item.section.m_proxy_value logger.warning(f'raw_proxy_value: {raw_proxy_value}') default_path = self.get_default_archive_path( @@ -269,6 +271,8 @@ def normalize(self, archive, logger): # archive_root = archive.m_context.resolve_archive(raw_proxy_value) # task.section = archive_root.m_resolve(default_path) + # TODO - Add global output to each task output? + # TODO - I need an alternative method to get the full input section path # ! m_proxy_value is not available for "noraml sections" input_proxy_values = [input.section.m_proxy_value for input in task.inputs] diff --git a/tests/test_simulationworkflowschema.py b/tests/test_simulationworkflowschema.py index 9b6c069..d170d5a 100644 --- a/tests/test_simulationworkflowschema.py +++ b/tests/test_simulationworkflowschema.py @@ -737,20 +737,20 @@ def parse_trajectory(filename): # ! This parser seems to has very strange behavior, none of the basic m_xxx # ! attributes are defined for the section refs of the workflow -# def test_eos_workflow(): -# archive = parse_trajectory('tests/data/ase/Cu.traj') - -# eos_fit = archive.workflow2.results.eos_fit -# assert len(eos_fit) == 5 -# assert eos_fit[0].fitted_energies[1].to('eV').magnitude == approx(-0.00636507) -# assert eos_fit[1].function_name == 'pourier_tarantola' -# assert eos_fit[2].equilibrium_volume.to('angstrom**3').magnitude == approx( -# 11.565388081047471 -# ) -# assert eos_fit[3].equilibrium_energy.to('eV').magnitude == approx( -# -0.007035923370513912 -# ) -# assert eos_fit[4].rms_error == approx(1.408202378222592e-07) +def test_eos_workflow(): + archive = parse_trajectory('tests/data/ase/Cu.traj') + + eos_fit = archive.workflow2.results.eos_fit + assert len(eos_fit) == 5 + assert eos_fit[0].fitted_energies[1].to('eV').magnitude == approx(-0.00636507) + assert eos_fit[1].function_name == 'pourier_tarantola' + assert eos_fit[2].equilibrium_volume.to('angstrom**3').magnitude == approx( + 11.565388081047471 + ) + assert eos_fit[3].equilibrium_energy.to('eV').magnitude == approx( + -0.007035923370513912 + ) + assert eos_fit[4].rms_error == approx(1.408202378222592e-07) class TestSimulationWorkflow: From 28a1b0b775a20edc4e68f5f398c9902d755c6db9 Mon Sep 17 00:00:00 2001 From: jrudz Date: Mon, 9 Jun 2025 16:04:44 +0200 Subject: [PATCH 22/25] revert shortcut sections implementation and add safetly guards for root archives without metadata --- simulationworkflowschema/equation_of_state.py | 125 ++++++++++-------- 1 file changed, 69 insertions(+), 56 deletions(-) diff --git a/simulationworkflowschema/equation_of_state.py b/simulationworkflowschema/equation_of_state.py index 9dee703..70ea9db 100644 --- a/simulationworkflowschema/equation_of_state.py +++ b/simulationworkflowschema/equation_of_state.py @@ -21,7 +21,7 @@ from nomad.atomutils import get_volume from nomad.datamodel.data import ArchiveSection from nomad.units import ureg -from nomad.metainfo import SubSection, Section, Quantity +from nomad.metainfo import SubSection, Section, Quantity, MProxy from nomad.datamodel.metainfo.workflow import Link from .general import ( SimulationWorkflowMethod, @@ -157,21 +157,21 @@ def __init__(self, *args, **kwargs): 'task': 'workflow2', } - def get_default_archive_path(self, raw_proxy_value, section_type='') -> str: - """ - Returns a certain archive path if the raw proxy value points to the root of the archive. - """ - if raw_proxy_value is None: - return '' - - if '#/' in raw_proxy_value: - _, after = raw_proxy_value.split('#/', 1) - if after: - return '' - else: - return self.default_archive_paths.get(section_type, '') - else: - return '' + # def get_default_archive_path(self, raw_proxy_value, section_type='') -> str: + # """ + # Returns a certain archive path if the raw proxy value points to the root of the archive. + # """ + # if raw_proxy_value is None: + # return '' + + # if '#/' in raw_proxy_value: + # _, after = raw_proxy_value.split('#/', 1) + # if after: + # return '' + # else: + # return self.default_archive_paths.get(section_type, '') + # else: + # return '' def normalize(self, archive, logger): super().normalize(archive, logger) @@ -179,55 +179,65 @@ def normalize(self, archive, logger): logger.warning(f'self.tasks: {self.tasks}') logger.warning(f'self.inputs: {self.inputs}') + flag_input_structure = False + input_path_global = '' + archive_root = None # find the input structure if self.inputs: - flag_input_structure = False - input_proxy_value = '' for input_item in self.inputs: input_section = input_item.section.m_resolved() # TODO - I need an alternative method to get the full input section path + print(f'is MProxy: {isinstance(input_section, MProxy)}') # ! m_proxy_value is not available for "noraml sections" - print(f'input_item: {input_item}') - print(f'input_item.section: {input_item.section}') - raw_proxy_value = input_item.section.m_proxy_value - logger.warning(f'raw_proxy_value: {raw_proxy_value}') - default_path = self.get_default_archive_path( - raw_proxy_value, section_type='input' - ) - logger.warning(f'default_path: {default_path}') - if default_path != '': - archive_root = archive.m_context.resolve_archive(raw_proxy_value) - input_section = archive_root.m_resolve(default_path) + archive_root = archive.m_root() + archive_metadata = archive_root.metadata if archive_root else None + input_path_global = '' + if isinstance(input_section, MProxy): + input_path_global = input_section.m_proxy_value + elif archive_metadata: + upload_id = archive_metadata.upload_id + entry_id = archive_metadata.entry_id + input_path = input_section.m_path() + input_path_global = ( + f'../{upload_id}/archive/{entry_id}#/{input_path}' + if upload_id and entry_id and input_path + else '' + ) + + # default_path = self.get_default_archive_path( + # input_path_global, section_type='input' + # ) + # logger.warning(f'default_path: {default_path}') + # if default_path != '': + # archive_root = archive.m_context.resolve_archive(input_path_global) + # input_section = archive_root.m_resolve(default_path) if not isinstance(input_section, System): continue - logger.warning(f'self.tasks: {self.tasks}') - flag_input_structure = True - input_proxy_value = raw_proxy_value + default_path + # input_proxy_value = input_path_global + default_path system_index = input_section.m_parent_index run_section = input_section.m_parent run_index = run_section.m_parent_index input_name = input_item.name - input_archive = input_section.m_root() - if input_archive: + if archive_root: if system_index == -1: - system_index = len(input_archive.run[run_index].system) - 1 - if not archive.run: - run = Run(program=Program()) - try: - run.system.extend([input_section]) - run.method.extend(input_archive.run[run_index].method) - for calc in input_archive.run[run_index].calculation: - if calc.system_ref.m_parent_index == system_index: - run.calculation.extend([calc]) - break - except Exception: - logger.warning( - 'Failed to create run section from input structure. ' - ) - - archive.run.append(run) + system_index = len(archive_root.run[run_index].system) - 1 + # if not archive.run: + # run = Run(program=Program()) + # try: + # run.system.extend([input_section]) + # run.method.extend(archive_root.run[run_index].method) + # for calc in archive_root.run[run_index].calculation: + # if calc.system_ref.m_parent_index == system_index: + # run.calculation.extend([calc]) + # break + # except Exception: + # logger.warning( + # 'Failed to create run section from input structure. ' + # ) + + # archive.run.append(run) break @@ -275,12 +285,15 @@ def normalize(self, archive, logger): # TODO - I need an alternative method to get the full input section path # ! m_proxy_value is not available for "noraml sections" - input_proxy_values = [input.section.m_proxy_value for input in task.inputs] - if input_proxy_value in input_proxy_values: - index = input_proxy_values.index(input_proxy_value) - task.inputs[index].name = input_name - else: - task.inputs.append(Link(name=input_name, section=input_section)) + if input_path_global: + input_proxy_values = [ + input.section.m_proxy_value for input in task.inputs + ] + if input_path_global in input_proxy_values: + index = input_proxy_values.index(input_path_global) + task.inputs[index].name = input_name + else: + task.inputs.append(Link(name=input_name, section=input_section)) if not self._calculations: # try to get calculations from tasks (in case of instantiation from workflow yaml) From a6d126fa493cfe9ec31737b083106bd0faef7979 Mon Sep 17 00:00:00 2001 From: jrudz Date: Mon, 9 Jun 2025 16:32:37 +0200 Subject: [PATCH 23/25] working test without check for SinglePoints --- simulationworkflowschema/equation_of_state.py | 55 +++++++++---------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/simulationworkflowschema/equation_of_state.py b/simulationworkflowschema/equation_of_state.py index 70ea9db..2e27527 100644 --- a/simulationworkflowschema/equation_of_state.py +++ b/simulationworkflowschema/equation_of_state.py @@ -223,25 +223,23 @@ def normalize(self, archive, logger): if archive_root: if system_index == -1: system_index = len(archive_root.run[run_index].system) - 1 - # if not archive.run: - # run = Run(program=Program()) - # try: - # run.system.extend([input_section]) - # run.method.extend(archive_root.run[run_index].method) - # for calc in archive_root.run[run_index].calculation: - # if calc.system_ref.m_parent_index == system_index: - # run.calculation.extend([calc]) - # break - # except Exception: - # logger.warning( - # 'Failed to create run section from input structure. ' - # ) - - # archive.run.append(run) + if not archive.run: + run = Run(program=Program()) + try: + run.system.extend([input_section]) + run.method.extend(archive_root.run[run_index].method) + for calc in archive_root.run[run_index].calculation: + if calc.system_ref.m_parent_index == system_index: + run.calculation.extend([calc]) + break + except Exception: + logger.warning( + 'Failed to create run section from input structure. ' + ) - break + archive.run.append(run) - logger.warning(f'self.tasks: {self.tasks}') + break if not flag_input_structure: logger.warning('No input structure found in EOS workflow normalizer.') @@ -254,17 +252,18 @@ def normalize(self, archive, logger): self.results = EquationOfStateResults() self.outputs.append(Link(name=WORKFLOW_RESULTS_NAME, section=self.results)) - try: - task_archives = [task.task.m_root() for task in self.tasks] - assert all( - isinstance(task_archive.workflow2, SinglePoint) - for task_archive in task_archives - ) - except Exception: - logger.warning( - 'Not all tasks are SinglePoints or failed to retrieve task archives. EOS workflow may be incomplete or incorrect.' - ) - return + #! Causing test to fail + # try: + # task_archives = [task.task.m_root() for task in self.tasks] + # assert all( + # isinstance(task_archive.workflow2, SinglePoint) + # for task_archive in task_archives + # ) + # except Exception: + # logger.warning( + # 'Not all tasks are SinglePoints or failed to retrieve task archives. EOS workflow may be incomplete or incorrect.' + # ) + # return for task in self.tasks: # TODO - I need an alternative method to get the full input section path From 37a6af16e6a802c95169086a8f0fc4575fd888e6 Mon Sep 17 00:00:00 2001 From: jrudz Date: Mon, 9 Jun 2025 16:37:13 +0200 Subject: [PATCH 24/25] add back failing code --- simulationworkflowschema/equation_of_state.py | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/simulationworkflowschema/equation_of_state.py b/simulationworkflowschema/equation_of_state.py index 2e27527..817ecc7 100644 --- a/simulationworkflowschema/equation_of_state.py +++ b/simulationworkflowschema/equation_of_state.py @@ -253,17 +253,17 @@ def normalize(self, archive, logger): self.outputs.append(Link(name=WORKFLOW_RESULTS_NAME, section=self.results)) #! Causing test to fail - # try: - # task_archives = [task.task.m_root() for task in self.tasks] - # assert all( - # isinstance(task_archive.workflow2, SinglePoint) - # for task_archive in task_archives - # ) - # except Exception: - # logger.warning( - # 'Not all tasks are SinglePoints or failed to retrieve task archives. EOS workflow may be incomplete or incorrect.' - # ) - # return + try: + task_archives = [task.task.m_root() for task in self.tasks] + assert all( + isinstance(task_archive.workflow2, SinglePoint) + for task_archive in task_archives + ) + except Exception: + logger.warning( + 'Not all tasks are SinglePoints or failed to retrieve task archives. EOS workflow may be incomplete or incorrect.' + ) + return for task in self.tasks: # TODO - I need an alternative method to get the full input section path From bc0d77fcb860278b3aad2e9ef85ac63f1b2356e4 Mon Sep 17 00:00:00 2001 From: jrudz Date: Tue, 10 Jun 2025 14:24:48 +0200 Subject: [PATCH 25/25] working version with helper function for global path from proxy/non-proxy --- simulationworkflowschema/equation_of_state.py | 105 ++++++++++-------- 1 file changed, 58 insertions(+), 47 deletions(-) diff --git a/simulationworkflowschema/equation_of_state.py b/simulationworkflowschema/equation_of_state.py index 817ecc7..8f6b15e 100644 --- a/simulationworkflowschema/equation_of_state.py +++ b/simulationworkflowschema/equation_of_state.py @@ -150,12 +150,13 @@ class EquationOfState(ParallelSimulation): results = SubSection(sub_section=EquationOfStateResults) - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.default_archive_paths = { - 'input': 'run/0/system/-1', - 'task': 'workflow2', - } + # ! For default path code in normalize + # def __init__(self, *args, **kwargs): + # super().__init__(*args, **kwargs) + # self.default_archive_paths = { + # 'input': 'run/0/system/-1', + # 'task': 'workflow2', + # } # def get_default_archive_path(self, raw_proxy_value, section_type='') -> str: # """ @@ -173,37 +174,39 @@ def __init__(self, *args, **kwargs): # else: # return '' + def get_section_global_path(self, section: ArchiveSection) -> str: + """ + Returns the global path of a section in the archive. + """ + if isinstance(section, MProxy): + return section.m_proxy_value + else: + archive_root = section.m_root() + archive_metadata = ( + archive_root.metadata if archive_root is not None else None + ) + if not archive_metadata: + return None + + entry_id = archive_metadata.entry_id + path = section.m_path() + return f'../upload/archive/{entry_id}#{path}' if entry_id and path else None + def normalize(self, archive, logger): super().normalize(archive, logger) - logger.warning(f'self.tasks: {self.tasks}') - logger.warning(f'self.inputs: {self.inputs}') - flag_input_structure = False input_path_global = '' - archive_root = None + input_archive_root = None # find the input structure if self.inputs: for input_item in self.inputs: + # if isinstance(input_item.section, MProxy): + # input_path_global = input_item.section.m_proxy_value + input_path_global = self.get_section_global_path(input_item.section) input_section = input_item.section.m_resolved() - # TODO - I need an alternative method to get the full input section path - print(f'is MProxy: {isinstance(input_section, MProxy)}') - # ! m_proxy_value is not available for "noraml sections" - archive_root = archive.m_root() - archive_metadata = archive_root.metadata if archive_root else None - input_path_global = '' - if isinstance(input_section, MProxy): - input_path_global = input_section.m_proxy_value - elif archive_metadata: - upload_id = archive_metadata.upload_id - entry_id = archive_metadata.entry_id - input_path = input_section.m_path() - input_path_global = ( - f'../{upload_id}/archive/{entry_id}#/{input_path}' - if upload_id and entry_id and input_path - else '' - ) + # ! For replacing short-hand sections with standardized paths # default_path = self.get_default_archive_path( # input_path_global, section_type='input' # ) @@ -215,29 +218,36 @@ def normalize(self, archive, logger): continue flag_input_structure = True + # ! No longer needed + # archive_root = ( + # archive.m_context.resolve_archive(input_path_global) + # if input_path_global + # else archive_root + # ) + # ! Goes with default path code above # input_proxy_value = input_path_global + default_path system_index = input_section.m_parent_index run_section = input_section.m_parent run_index = run_section.m_parent_index input_name = input_item.name - if archive_root: + if input_archive_root: if system_index == -1: - system_index = len(archive_root.run[run_index].system) - 1 - if not archive.run: - run = Run(program=Program()) - try: - run.system.extend([input_section]) - run.method.extend(archive_root.run[run_index].method) - for calc in archive_root.run[run_index].calculation: - if calc.system_ref.m_parent_index == system_index: - run.calculation.extend([calc]) - break - except Exception: - logger.warning( - 'Failed to create run section from input structure. ' - ) - - archive.run.append(run) + system_index = len(input_archive_root.run[run_index].system) - 1 + if not archive.run: + run = Run(program=Program()) + try: + run.system.extend([input_section]) + run.method.extend(input_archive_root.run[run_index].method) + for calc in input_archive_root.run[run_index].calculation: + if calc.system_ref.m_parent_index == system_index: + run.calculation.extend([calc]) + break + except Exception: + logger.warning( + 'Failed to create run section from input structure. ' + ) + + archive.run.append(run) break @@ -282,11 +292,12 @@ def normalize(self, archive, logger): # TODO - Add global output to each task output? - # TODO - I need an alternative method to get the full input section path - # ! m_proxy_value is not available for "noraml sections" if input_path_global: + # input_proxy_values = [ + # input.section.m_proxy_value for input in task.inputs + # ] input_proxy_values = [ - input.section.m_proxy_value for input in task.inputs + self.get_section_global_path(input.section) for input in task.inputs ] if input_path_global in input_proxy_values: index = input_proxy_values.index(input_path_global)