From 2f264537d3f71bd01087bbc4cba416ee33d79996 Mon Sep 17 00:00:00 2001 From: "Jose M. Pizarro" <112697669+JosePizarro3@users.noreply.github.com> Date: Wed, 7 Feb 2024 09:27:27 +0100 Subject: [PATCH] 2 general base classes for workflows (#3) * Added base classes for DFTMethod and DFTOutputs, and for GWOutputs * Reformatted and using inheritance gw.py * Added TBOutputs and DMFTOutputs to general.py Reformatted dmft.py * Added BeyondDFT2Tasks base class and MaxEntOutputs in general.py Reformatted and inheritance in maxent.py * Reformatted xs.py and photon_polarization.py * Changed inheritance by composition Reformat tb.py * Added composition to XS workflow * Defining ElectronicStructureOutputs for generic complex workflows * Fix bug name * Fixing XS and tests * Changed name to electronic workflows to add Plus * Fix testing * Generalize method in BeyondDFT class * Improved extraction of workflow_name * Adding nmr workflow (#6) * Added MagneticOutputs in general.py * Added more imports for usage in __init__ * Fix testing --- simulationworkflowschema/__init__.py | 18 +- simulationworkflowschema/dmft.py | 132 +--- simulationworkflowschema/general.py | 179 ++++- simulationworkflowschema/gw.py | 114 +-- simulationworkflowschema/max_ent.py | 82 +-- simulationworkflowschema/tb.py | 95 +-- simulationworkflowschema/xs.py | 85 +-- tests/conftest.py | 938 +++++++++++++++---------- tests/test_simulationworkflowschema.py | 18 +- 9 files changed, 891 insertions(+), 770 deletions(-) diff --git a/simulationworkflowschema/__init__.py b/simulationworkflowschema/__init__.py index 3c4a667..dd0b831 100644 --- a/simulationworkflowschema/__init__.py +++ b/simulationworkflowschema/__init__.py @@ -20,6 +20,12 @@ SimulationWorkflow, SimulationWorkflowMethod, SimulationWorkflowResults, + ParallelSimulation, + SerialSimulation, + BeyondDFT, + DFTMethod, + ElectronicStructureOutputs, + MagneticOutputs, ) from .single_point import SinglePoint, SinglePointMethod, SinglePointResults from .geometry_optimization import ( @@ -44,11 +50,15 @@ ChemicalReactionResults, ) from .elastic import Elastic, ElasticMethod, ElasticResults -from .tb import TB, TBMethod, TBResults -from .gw import GW, GWMethod, GWResults +from .tb import ( + FirstPrinciplesPlusTB, + FirstPrinciplesPlusTBMethod, + FirstPrinciplesPlusTBResults, +) +from .gw import DFTPlusGW, DFTPlusGWMethod, DFTPlusGWResults from .xs import XS, XSMethod, XSResults -from .dmft import DMFT, DMFTMethod, DMFTResults -from .max_ent import MaxEnt, MaxEntMethod, MaxEntResults +from .dmft import DFTPlusTBPlusDMFT, DFTPlusTBPlusDMFTMethod, DFTPlusTBPlusDMFTResults +from .max_ent import DMFTPlusMaxEnt, DMFTPlusMaxEntMethod, DMFTPlusMaxEntResults from .photon_polarization import ( PhotonPolarization, PhotonPolarizationMethod, diff --git a/simulationworkflowschema/dmft.py b/simulationworkflowschema/dmft.py index 5f769f3..9b50e0a 100644 --- a/simulationworkflowschema/dmft.py +++ b/simulationworkflowschema/dmft.py @@ -17,109 +17,41 @@ # from nomad.metainfo import SubSection, Quantity, Reference from runschema.method import ( - XCFunctional, - BasisSetContainer, TB as TBMethodology, DMFT as DMFTMethodology, ) -from runschema.calculation import BandGap, Dos, BandStructure, GreensFunctions from .general import ( SimulationWorkflowResults, - SimulationWorkflowMethod, - SerialSimulation, + ElectronicStructureOutputs, + DFTMethod, + BeyondDFT, ) -class DMFTResults(SimulationWorkflowResults): - """Groups DFT, TB and DMFT outputs: band gaps (all), DOS (DFT, TB), band +class DFTPlusTBPlusDMFTResults(SimulationWorkflowResults): + """ + Groups DFT, TB and DMFT outputs: band gaps (all), DOS (DFT, TB), band structures (DFT, TB), Greens functions (DMFT). The ResultsNormalizer takes care of adding a label 'DFT', 'PROJECTION, or 'DMFT' in the method `get_dmft_workflow_properties`. """ - band_gap_dft = Quantity( - type=Reference(BandGap), - shape=["*"], - description=""" - DFT band gap. - """, - ) - - band_gap_tb = Quantity( - type=Reference(BandGap), - shape=["*"], - description=""" - TB band gap. - """, - ) - - band_gap_dmft = Quantity( - type=Reference(BandGap), - shape=["*"], - description=""" - DMFT band gap. - """, - ) - - band_structure_dft = Quantity( - type=Reference(BandStructure), - shape=["*"], - description=""" - Ref to the DFT band structure. - """, - ) - - dos_dft = Quantity( - type=Reference(Dos), - shape=["*"], - description=""" - Ref to the DFT density of states. - """, - ) - - band_structure_tb = Quantity( - type=Reference(BandStructure), - shape=["*"], - description=""" - Ref to the TB band structure. - """, + dft_outputs = SubSection( + sub_section=ElectronicStructureOutputs.m_def, repeats=False ) - dos_tb = Quantity( - type=Reference(Dos), - shape=["*"], - description=""" - Ref to the TB density of states. - """, - ) + tb_outputs = SubSection(sub_section=ElectronicStructureOutputs.m_def, repeats=False) - greens_functions_dmft = Quantity( - type=Reference(GreensFunctions), - shape=["*"], - description=""" - Ref to the DMFT Greens functions. - """, + dmft_outputs = SubSection( + sub_section=ElectronicStructureOutputs.m_def, repeats=False ) -class DMFTMethod(SimulationWorkflowMethod): - """Groups DFT, TB and DMFT input methodologies: starting XC functional, electrons +class DFTPlusTBPlusDMFTMethod(DFTMethod): + """ + Specifies all DFT, TB and DMFT input methodologies: starting XC functional, electrons representation (basis set), TB method reference, DMFT method reference. """ - starting_point = Quantity( - type=Reference(XCFunctional), - description=""" - Starting point (XC functional or HF) used. - """, - ) - - electrons_representation = Quantity( - type=Reference(BasisSetContainer), - description=""" - Basis set used. - """, - ) - tb_method_ref = Quantity( type=Reference(TBMethodology), description=""" @@ -135,38 +67,18 @@ class DMFTMethod(SimulationWorkflowMethod): ) -class DMFT(SerialSimulation): - """The DMFT workflow is generated in an extra EntryArchive IF both the TB SinglePoint +class DFTPlusTBPlusDMFT(BeyondDFT): # TODO implement connection with DFT task + """ + The DMFT workflow is generated in an extra EntryArchive IF both the TB SinglePoint and the DMFT SinglePoint EntryArchives are present in the upload. """ - # TODO extend to reference a DFT SinglePoint. - - method = SubSection(sub_section=DMFTMethod) + method = SubSection(sub_section=DFTPlusTBPlusDMFTMethod) - results = SubSection(sub_section=DMFTResults) + results = SubSection(sub_section=DFTPlusTBPlusDMFTResults) def normalize(self, archive, logger): - super().normalize(archive, logger) - - if len(self.tasks) != 2: - logger.error("Expected two tasks: TB and DMFT SinglePoint tasks") - return - - proj_task = self.tasks[0] - dmft_task = self.tasks[1] + if not self.results: # creates Results section if not present + self.results = DFTPlusTBPlusDMFTResults() - if not self.results: - self.results = DMFTResults() - - for name, section in self.results.m_def.all_quantities.items(): - calc_name = "_".join(name.split("_")[:-1]) - if calc_name in ["dos", "band_structure"]: - calc_name = f"{calc_name}_electronic" - calc_section = [] - if "tb" in name: - calc_section = getattr(proj_task.outputs[-1].section, calc_name) - elif "dmft" in name: - calc_section = getattr(dmft_task.outputs[-1].section, calc_name) - if calc_section: - self.results.m_set(section, calc_section) + super().normalize(archive, logger) diff --git a/simulationworkflowschema/general.py b/simulationworkflowschema/general.py index 600455a..4af7e60 100644 --- a/simulationworkflowschema/general.py +++ b/simulationworkflowschema/general.py @@ -21,10 +21,24 @@ from nomad.datamodel.data import ArchiveSection from nomad.metainfo import SubSection, Section, Quantity, Reference from nomad.datamodel.metainfo.common import FastAccess -from nomad.datamodel.metainfo.workflow import Workflow, Link, Task -from runschema.method import Method +from nomad.datamodel.metainfo.workflow import Workflow, Link, Task, TaskReference +from runschema.method import ( + Method, + XCFunctional, + BasisSetContainer, +) from runschema.system import System -from runschema.calculation import Calculation +from runschema.calculation import ( + Calculation, + BandGap, + Dos, + BandStructure, + GreensFunctions, + MagneticShielding, + ElectricFieldGradient, + SpinSpinCoupling, + MagneticSusceptibility, +) def resolve_difference(values): @@ -324,3 +338,162 @@ def normalize(self, archive, logger): self.tasks.append( Task(name=f"Step {n}", inputs=inputs, outputs=outputs) ) + + +class BeyondDFT(SerialSimulation): + """ + Base class used to normalize standard workflows beyond DFT containing two specific + SinglePoint tasks (GWWorkflow = DFT + GW, DMFTWorkflow = DFT + DMFT, + MaxEntWorkflow = DMFT + MaxEnt, and so on) and store the outputs in the self.results + section. + """ + + def _resolve_outputs_section(self, output_section, task: TaskReference) -> None: + """ + Resolves the output_section of a task and stores the results in the output_section. + + Args: + task (TaskReference): The task from which the outputs are got. + """ + for name, section in output_section.m_def.all_quantities.items(): + name = f"{name}_electronic" if name in ["dos", "band_structure"] else name + try: + calc_section = getattr(task.outputs[-1].section, name) + if calc_section: + output_section.m_set(section, calc_section) + except Exception: + continue + + def get_electronic_structure_workflow_results(self, task_map: dict) -> None: + """ + Gets the standard electronic structure workflow results section by resolving the + outputs specified in the `task_map`. + + Args: + task_map (dict): The dictionary used to resolve the outputs sections. + """ + for method, task in task_map.items(): + outputs = ElectronicStructureOutputs() + self._resolve_outputs_section(outputs, task) + setattr(self.results, f"{method}_outputs", outputs) + + def normalize(self, archive, logger): + super().normalize(archive, logger) + + if len(self.tasks) != 2: + logger.error("Expected two tasks.") + return + + # We extract the workflow name from the tasks names + self.name = "+".join([task.name for task in self.tasks if task.name]) + task_map = { + task.name.lower(): self.tasks[n] for n, task in enumerate(self.tasks) + } + # Resolve workflow2.results for each standard BeyondDFT workflow + if self.name == "DFT+GW": + self.get_electronic_structure_workflow_results(task_map) + elif self.name == "TB+DMFT": # TODO extend for DFT tasks + self.get_electronic_structure_workflow_results(task_map) + elif self.name == "DMFT+MaxEnt": + self.get_electronic_structure_workflow_results(task_map) + elif self.name == "FirstPrinciples+TB": + task_map["first_principles"] = task_map.pop("firstprinciples") + self.get_electronic_structure_workflow_results(task_map) + + +class DFTMethod(SimulationWorkflowMethod): + """ + Base class defining the DFT input methodologies: starting XC functional and electrons + representation (basis set). + """ + + starting_point = Quantity( + type=Reference(XCFunctional), + description=""" + Reference to the starting point (XC functional or HF) used. + """, + ) + + electrons_representation = Quantity( + type=Reference(BasisSetContainer), + description=""" + Reference to the basis set used. + """, + ) + + +class ElectronicStructureOutputs(SimulationWorkflowResults): + """ + Base class defining the typical output properties of any electronic structure + SinglePoint calculation: DFT, TB, DMFT, GW, MaxEnt, XS. + """ + + band_gap = Quantity( + type=Reference(BandGap), + shape=["*"], + description=""" + Reference to the band gap section. + """, + ) + + dos = Quantity( + type=Reference(Dos), + shape=["*"], + description=""" + Reference to the density of states section. + """, + ) + + band_structure = Quantity( + type=Reference(BandStructure), + shape=["*"], + description=""" + Reference to the band structure section. + """, + ) + + greens_functions = Quantity( + type=Reference(GreensFunctions), + shape=["*"], + description=""" + Ref to the Green functions section. + """, + ) + + +class MagneticOutputs(SimulationWorkflowResults): + """ + Base class defining the typical output properties of magnetic SinglePoint calculations. + """ + + magnetic_shielding = Quantity( + type=Reference(MagneticShielding), + shape=["*"], + description=""" + Reference to the magnetic shielding tensors. + """, + ) + + electric_field_gradient = Quantity( + type=Reference(ElectricFieldGradient), + shape=["*"], + description=""" + Reference to the electric field gradient tensors. + """, + ) + + spin_spin_coupling = Quantity( + type=Reference(SpinSpinCoupling), + shape=["*"], + description=""" + Reference to the spin-spin coupling tensors. + """, + ) + + magnetic_susceptibility_nmr = Quantity( + type=Reference(MagneticSusceptibility), + shape=["*"], + description=""" + Reference to the magnetic susceptibility tensors. + """, + ) diff --git a/simulationworkflowschema/gw.py b/simulationworkflowschema/gw.py index 9a595f1..5b298f1 100644 --- a/simulationworkflowschema/gw.py +++ b/simulationworkflowschema/gw.py @@ -16,88 +16,34 @@ # limitations under the License. # from nomad.metainfo import SubSection, Quantity, Reference -from runschema.method import XCFunctional, BasisSetContainer, GW as GWMethodology -from runschema.calculation import BandGap, Dos, BandStructure +from runschema.method import GW as GWMethodology from .general import ( SimulationWorkflowResults, - SimulationWorkflowMethod, - SerialSimulation, + ElectronicStructureOutputs, + DFTMethod, + BeyondDFT, ) -class GWResults(SimulationWorkflowResults): - """Groups DFT and GW outputs: band gaps, DOS, band structures. The ResultsNormalizer +class DFTPlusGWResults(SimulationWorkflowResults): + """ + Groups DFT and GW outputs: band gaps, DOS, band structures. The ResultsNormalizer takes care of adding a label 'DFT' or 'GW' in the method `get_gw_workflow_properties`. """ - band_gap_dft = Quantity( - type=Reference(BandGap), - shape=["*"], - description=""" - Reference to the DFT band gap. - """, + dft_outputs = SubSection( + sub_section=ElectronicStructureOutputs.m_def, repeats=False ) - band_gap_gw = Quantity( - type=Reference(BandGap), - shape=["*"], - description=""" - Reference to the GW band gap. - """, - ) + gw_outputs = SubSection(sub_section=ElectronicStructureOutputs.m_def, repeats=False) - dos_dft = Quantity( - type=Reference(Dos), - shape=["*"], - description=""" - Reference to the DFT density of states. - """, - ) - dos_gw = Quantity( - type=Reference(Dos), - shape=["*"], - description=""" - Reference to the GW density of states. - """, - ) - - band_structure_dft = Quantity( - type=Reference(BandStructure), - shape=["*"], - description=""" - Reference to the DFT band structure. - """, - ) - - band_structure_gw = Quantity( - type=Reference(BandStructure), - shape=["*"], - description=""" - Reference to the GW band structure. - """, - ) - - -class GWMethod(SimulationWorkflowMethod): - """Groups DFT and GW input methodologies: starting XC functional, electrons +class DFTPlusGWMethod(DFTMethod): + """ + Specifies both DFT and GW input methodologies: starting XC functional, electrons representation (basis set), GW method reference. """ - starting_point = Quantity( - type=Reference(XCFunctional), - description=""" - Reference to the starting point (XC functional or HF) used. - """, - ) - - electrons_representation = Quantity( - type=Reference(BasisSetContainer), - description=""" - Reference to the basis set used. - """, - ) - gw_method_ref = Quantity( type=Reference(GWMethodology), description=""" @@ -106,36 +52,18 @@ class GWMethod(SimulationWorkflowMethod): ) -class GW(SerialSimulation): - """The GW workflow is generated in an extra EntryArchive IF both the DFT SinglePoint +class DFTPlusGW(BeyondDFT): + """ + The GW workflow is generated in an extra EntryArchive IF both the DFT SinglePoint and the GW SinglePoint EntryArchives are present in the upload. """ - method = SubSection(sub_section=GWMethod) + method = SubSection(sub_section=DFTPlusGWMethod) - results = SubSection(sub_section=GWResults) + results = SubSection(sub_section=DFTPlusGWResults) def normalize(self, archive, logger): - super().normalize(archive, logger) + if not self.results: # creates Results section if not present + self.results = DFTPlusGWResults() - if len(self.tasks) != 2: - logger.error("Expected two tasks.") - return - - dft_task = self.tasks[0] - gw_task = self.tasks[1] - - if not self.results: - self.results = GWResults() - - for name, section in self.results.m_def.all_quantities.items(): - calc_name = "_".join(name.split("_")[:-1]) - if calc_name in ["dos", "band_structure"]: - calc_name = f"{calc_name}_electronic" - calc_section = [] - if "dft" in name: - calc_section = getattr(dft_task.outputs[-1].section, calc_name) - elif "gw" in name: - calc_section = getattr(gw_task.outputs[-1].section, calc_name) - if calc_section: - self.results.m_set(section, calc_section) + super().normalize(archive, logger) diff --git a/simulationworkflowschema/max_ent.py b/simulationworkflowschema/max_ent.py index 8b7e164..c7768d8 100644 --- a/simulationworkflowschema/max_ent.py +++ b/simulationworkflowschema/max_ent.py @@ -17,55 +17,35 @@ # from nomad.metainfo import SubSection, Quantity, Reference from runschema.method import Method, DMFT as DMFTMethodology -from runschema.calculation import BandGap, Dos, GreensFunctions from .general import ( SimulationWorkflowResults, + ElectronicStructureOutputs, SimulationWorkflowMethod, - SerialSimulation, + BeyondDFT, ) -class MaxEntResults(SimulationWorkflowResults): - """Groups DMFT and MaxEnt outputs: greens functions (DMFT, MaxEnt), band gaps (MaxEnt), +class DMFTPlusMaxEntResults(SimulationWorkflowResults): + """ + Groups DMFT and MaxEnt outputs: greens functions (DMFT, MaxEnt), band gaps (MaxEnt), DOS (MaxEnt), band structures (MaxEnt). The ResultsNormalizer takes care of adding a label 'DMFT' or 'MaxEnt' in the method `get_maxent_workflow_properties`. """ - greens_functions_dmft = Quantity( - type=Reference(GreensFunctions), - shape=["*"], - description=""" - Ref to the DMFT Greens functions. - """, + dmft_outputs = SubSection( + sub_section=ElectronicStructureOutputs.m_def, repeats=False ) - band_gap_maxent = Quantity( - type=Reference(BandGap), - shape=["*"], - description=""" - MaxEnt band gap. - """, + maxent_outputs = SubSection( + sub_section=ElectronicStructureOutputs.m_def, repeats=False ) - dos_maxent = Quantity( - type=Reference(Dos), - shape=["*"], - description=""" - Ref to the MaxEnt density of states (also called spectral function). - """, - ) - greens_functions_maxent = Quantity( - type=Reference(GreensFunctions), - shape=["*"], - description=""" - Ref to the MaxEnt Greens functions. - """, - ) - - -class MaxEntMethod(SimulationWorkflowMethod): - """Groups DMFT and MaxEnt input methodologies: DMFT method references, MaxEnt method reference.""" +class DMFTPlusMaxEntMethod(SimulationWorkflowMethod): + """ + Specifies both DMFT and MaxEnt input methodologies: DMFT method references, MaxEnt method + reference. + """ dmft_method_ref = Quantity( type=Reference(DMFTMethodology), @@ -83,36 +63,18 @@ class MaxEntMethod(SimulationWorkflowMethod): ) -class MaxEnt(SerialSimulation): - """The MaxEnt (Maximum Entropy) workflow is generated in an extra EntryArchive IF both +class DMFTPlusMaxEnt(BeyondDFT): + """ + The MaxEnt (Maximum Entropy) workflow is generated in an extra EntryArchive IF both the DMFT SinglePoint and the MaxEnt SinglePoint EntryArchives are present in the upload. """ - method = SubSection(sub_section=MaxEntMethod) + method = SubSection(sub_section=DMFTPlusMaxEntMethod) - results = SubSection(sub_section=MaxEntResults) + results = SubSection(sub_section=DMFTPlusMaxEntResults) def normalize(self, archive, logger): - super().normalize(archive, logger) - - if len(self.tasks) != 2: - logger.error("Expected two tasks: DMFT and MaxEnt SinglePoint tasks") - return - - dmft_task = self.tasks[0] - maxent_task = self.tasks[1] + if not self.results: # creates Results section if not present + self.results = DMFTPlusMaxEntResults() - if not self.results: - self.results = MaxEntResults() - - for name, section in self.results.m_def.all_quantities.items(): - calc_name = "_".join(name.split("_")[:-1]) - if calc_name in ["dos", "band_structure"]: - calc_name = f"{calc_name}_electronic" - calc_section = [] - if "dmft" in name: - calc_section = getattr(dmft_task.outputs[-1].section, calc_name) - elif "maxent" in name: - calc_section = getattr(maxent_task.outputs[-1].section, calc_name) - if calc_section: - self.results.m_set(section, calc_section) + super().normalize(archive, logger) diff --git a/simulationworkflowschema/tb.py b/simulationworkflowschema/tb.py index 5163a28..0da230a 100644 --- a/simulationworkflowschema/tb.py +++ b/simulationworkflowschema/tb.py @@ -16,91 +16,62 @@ # limitations under the License. # from nomad.metainfo import SubSection, Quantity, Reference -from runschema.method import TB as TBMethodology -from runschema.calculation import BandGap, BandStructure +from runschema.method import TB as TBMethodology, Method from .general import ( SimulationWorkflowResults, + ElectronicStructureOutputs, SimulationWorkflowMethod, SerialSimulation, ) -class TBResults(SimulationWorkflowResults): - band_gap_first_principles = Quantity( - type=Reference(BandGap), - shape=["*"], - description=""" - Reference to the First-principles band gap. - """, - ) +class FirstPrinciplesPlusTBResults(SimulationWorkflowResults): + """ + Groups first principles and TB outputs: band gaps, DOS, band structures. The + ResultsNormalizer takes care of adding a label 'FirstPrinciples' or 'TB' in the method + `get_tb_workflow_properties`. + """ - band_gap_tb = Quantity( - type=Reference(BandGap), - shape=["*"], - description=""" - Reference to the TB band gap. - """, + first_principles_outputs = SubSection( + sub_section=ElectronicStructureOutputs.m_def, repeats=False ) - band_structure_first_principles = Quantity( - type=Reference(BandStructure), - shape=["*"], - description=""" - Reference to the first-principles band structure. - """, - ) + tb_outputs = SubSection(sub_section=ElectronicStructureOutputs.m_def, repeats=False) + + +class FirstPrinciplesPlusTBMethod(SimulationWorkflowMethod): + """ + Specifies both the first principles and the TB input methodologies. + """ - band_structure_tb = Quantity( - type=Reference(BandStructure), - shape=["*"], + # TODO refine this referencing + first_principles_method_ref = Quantity( + type=Reference(Method), description=""" - Reference to the tight-Binding band structure. + First principles methodology reference. """, ) - -class TBMethod(SimulationWorkflowMethod): tb_method_ref = Quantity( type=Reference(TBMethodology), description=""" - Reference to the tight-Binding methodology. + TB methodology reference. """, ) -class TB(SerialSimulation): - method = SubSection(sub_section=TBMethod) - - results = SubSection(sub_section=TBResults) - - def normalize(self, archive, logger): - super().normalize(archive, logger) - - if not self.method: - self.method = TBMethod() +class FirstPrinciplesPlusTB(SerialSimulation): + """ + The TB (tight-binding) workflow is generated in an extra EntryArchive IF both + the first principles SinglePoint and the TB SinglePoint EntryArchives are present in the upload. + """ - if not self.results: - self.results = TBResults() + method = SubSection(sub_section=FirstPrinciplesPlusTBMethod) - if len(self.tasks) != 2: - logger.error("Expected two tasks.") - return + results = SubSection(sub_section=FirstPrinciplesPlusTBResults) - first_principles_task = self.tasks[0] - tb_task = self.tasks[1] + def normalize(self, archive, logger): + if not self.results: # creates Results section if not present + self.results = FirstPrinciplesPlusTBResults() - for name, section in self.results.m_def.all_quantities.items(): - calc_name = "_".join(name.split("_")[:-1]) - if name.endswith("first_principles"): - calc_name = "_".join(name.split("_")[:-2]) - if calc_name in ["band_structure"]: - calc_name = f"{calc_name}_electronic" - calc_section = [] - if "first_principles" in name: - calc_section = getattr( - first_principles_task.outputs[-1].section, calc_name - ) - elif "tb" in name: - calc_section = getattr(tb_task.outputs[-1].section, calc_name) - if calc_section: - self.results.m_set(section, calc_section) + super().normalize(archive, logger) diff --git a/simulationworkflowschema/xs.py b/simulationworkflowschema/xs.py index c9c75f7..37299c8 100644 --- a/simulationworkflowschema/xs.py +++ b/simulationworkflowschema/xs.py @@ -15,75 +15,32 @@ # See the License for the specific language governing permissions and # limitations under the License. # -from nomad.metainfo import SubSection, Quantity, Reference +from nomad.metainfo import SubSection from runschema.calculation import ( - BandGap, - Dos, - BandStructure, Spectra, ElectronicStructureProvenance, ) from .general import ( SimulationWorkflowResults, + ElectronicStructureOutputs, SimulationWorkflowMethod, - SerialSimulation, + BeyondDFT, ) from .photon_polarization import PhotonPolarizationResults class XSResults(SimulationWorkflowResults): - """Groups DFT, GW and PhotonPolarization outputs: band gaps (DFT, GW), DOS (DFT, GW), + """ + Groups DFT, GW and PhotonPolarization outputs: band gaps (DFT, GW), DOS (DFT, GW), band structures (DFT, GW), spectra (PhotonPolarization). The ResultsNormalizer takes care of adding a label 'DFT' or 'GW' in the method `get_xs_workflow_properties`. """ - band_gap_dft = Quantity( - type=Reference(BandGap), - shape=["*"], - description=""" - Reference to the DFT band gap. - """, - ) - - band_gap_gw = Quantity( - type=Reference(BandGap), - shape=["*"], - description=""" - Reference to the GW band gap. - """, + dft_outputs = SubSection( + sub_section=ElectronicStructureOutputs.m_def, repeats=False ) - band_structure_dft = Quantity( - type=Reference(BandStructure), - shape=["*"], - description=""" - Reference to the DFT density of states. - """, - ) - - band_structure_gw = Quantity( - type=Reference(BandStructure), - shape=["*"], - description=""" - Reference to the GW density of states. - """, - ) - - dos_dft = Quantity( - type=Reference(Dos), - shape=["*"], - description=""" - Reference to the DFT band structure. - """, - ) - - dos_gw = Quantity( - type=Reference(Dos), - shape=["*"], - description=""" - Reference to the GW band structure. - """, - ) + gw_outputs = SubSection(sub_section=ElectronicStructureOutputs.m_def, repeats=False) spectra = SubSection(sub_section=PhotonPolarizationResults, repeats=True) @@ -92,8 +49,9 @@ class XSMethod(SimulationWorkflowMethod): pass -class XS(SerialSimulation): - """The XS workflow is generated in an extra EntryArchive IF both the DFT SinglePoint +class XS(BeyondDFT): + """ + The XS workflow is generated in an extra EntryArchive IF both the DFT SinglePoint and the PhotonPolarization EntryArchives are present in the upload. """ @@ -104,8 +62,6 @@ class XS(SerialSimulation): results = SubSection(sub_section=XSResults) def normalize(self, archive, logger): - super().normalize(archive, logger) - if len(self.tasks) < 2: logger.error( "Expected more than one task: DFT+PhotonPolarization or DFT+GW+PhotonPolarization." @@ -125,19 +81,12 @@ def normalize(self, archive, logger): if not self.results: self.results = XSResults() - for name, section in self.results.m_def.all_quantities.items(): - calc_name = "_".join(name.split("_")[:-1]) - if calc_name in ["dos", "band_structure"]: - calc_name = f"{calc_name}_electronic" - calc_section = [] - if "dft" in name: - calc_section = getattr(dft_task.outputs[-1].section, calc_name) - elif "gw" in name and gw_task: - calc_section = getattr(gw_task.outputs[-1].section, calc_name) - elif name == "spectra": - pass - if calc_section: - self.results.m_set(section, calc_section) + task_map = { + "dft": dft_task, + "gw": gw_task, + } + self.get_electronic_structure_workflow_results(task_map) + for xs in xs_tasks: if xs.m_xpath("task.results"): photon_results = xs.task.results diff --git a/tests/conftest.py b/tests/conftest.py index ef2a7c6..b3ef1af 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -28,20 +28,53 @@ from nomad.datamodel import EntryArchive, EntryMetadata from runschema.run import Run, Program from runschema.method import ( - Method, BasisSetContainer, BasisSet, Electronic, DFT, XCFunctional, Functional, - Electronic, Smearing, Scf, GW, Photon, BSE, DMFT, AtomParameters, TB, Wannier, - LatticeModelHamiltonian, HubbardKanamoriModel) + Method, + BasisSetContainer, + BasisSet, + Electronic, + DFT, + XCFunctional, + Functional, + Electronic, + Smearing, + Scf, + GW, + Photon, + BSE, + DMFT, + AtomParameters, + TB, + Wannier, + LatticeModelHamiltonian, + HubbardKanamoriModel, +) from runschema.calculation import ( - Calculation, Energy, EnergyEntry, Dos, DosValues, BandStructure, BandEnergies, - RadiusOfGyration, RadiusOfGyrationValues, GreensFunctions, Spectra, ElectronicStructureProvenance) + Calculation, + Energy, + EnergyEntry, + Dos, + DosValues, + BandStructure, + BandEnergies, + RadiusOfGyration, + RadiusOfGyrationValues, + GreensFunctions, + Spectra, + ElectronicStructureProvenance, +) from runschema.method import ( - Method, BasisSetContainer, BasisSet, Electronic, XCFunctional, Functional, DFT, GW + Method, + BasisSetContainer, + BasisSet, + Electronic, + XCFunctional, + Functional, + DFT, + GW, ) from runschema.system import AtomsGroup, System, Atoms -from nomad.datamodel.metainfo.workflow import ( - Link, TaskReference -) +from nomad.datamodel.metainfo.workflow import Link, TaskReference from simulationworkflowschema.molecular_dynamics import ( DiffusionConstantValues, MeanSquaredDisplacement, @@ -61,14 +94,22 @@ MolecularDynamics, EquationOfState, EquationOfStateResults, - GWMethod, GW as GWworkflow, DMFTMethod, DMFT as DMFTworkflow, PhotonPolarization, - PhotonPolarizationMethod, PhotonPolarizationResults, XS as XSworkflow, MaxEntMethod, - MaxEnt as MaxEntworkflow + DFTPlusGWMethod, + DFTPlusGW as GWworkflow, + DFTPlusTBPlusDMFTMethod, + DFTPlusTBPlusDMFT as DMFTworkflow, + PhotonPolarization, + PhotonPolarizationMethod, + PhotonPolarizationResults, + XS as XSworkflow, + DMFTPlusMaxEntMethod, + DMFTPlusMaxEnt as MaxEntworkflow, ) LOGGER = get_logger(__name__) + def run_normalize(entry_archive: EntryArchive) -> EntryArchive: for normalizer_class in normalizers: normalizer = normalizer_class(entry_archive) @@ -77,18 +118,17 @@ def run_normalize(entry_archive: EntryArchive) -> EntryArchive: def get_template_computation() -> EntryArchive: - '''Returns a basic archive template for a computational calculation - ''' + """Returns a basic archive template for a computational calculation""" template = EntryArchive() run = Run() template.run.append(run) - run.program = Program(name='VASP', version='4.6.35') + run.program = Program(name="VASP", version="4.6.35") system = run.m_create(System) system.atoms = Atoms( lattice_vectors=[ [5.76372622e-10, 0.0, 0.0], [0.0, 5.76372622e-10, 0.0], - [0.0, 0.0, 4.0755698899999997e-10] + [0.0, 0.0, 4.0755698899999997e-10], ], positions=[ [2.88186311e-10, 0.0, 2.0377849449999999e-10], @@ -96,33 +136,38 @@ def get_template_computation() -> EntryArchive: [0.0, 0.0, 0.0], [2.88186311e-10, 2.88186311e-10, 0.0], ], - labels=['Br', 'K', 'Si', 'Si'], - periodic=[True, True, True]) + labels=["Br", "K", "Si", "Si"], + periodic=[True, True, True], + ) scc = run.m_create(Calculation) scc.system_ref = system scc.energy = Energy( free=EnergyEntry(value=-1.5936767191492225e-18), total=EnergyEntry(value=-1.5935696296699573e-18), - total_t0=EnergyEntry(value=-3.2126683561907e-22)) + total_t0=EnergyEntry(value=-3.2126683561907e-22), + ) return template def get_template_dft() -> EntryArchive: - '''Returns a basic archive template for a DFT calculation. - ''' + """Returns a basic archive template for a DFT calculation.""" template = get_template_computation() run = template.run[-1] method = run.m_create(Method) - method.electrons_representation = [BasisSetContainer( - type='plane waves', - scope=['wavefunction'], - basis_set=[BasisSet( - type='plane waves', - scope=['valence'], - )] - )] - method.electronic = Electronic(method='DFT') - xc_functional = XCFunctional(exchange=[Functional(name='GGA_X_PBE')]) + method.electrons_representation = [ + BasisSetContainer( + type="plane waves", + scope=["wavefunction"], + basis_set=[ + BasisSet( + type="plane waves", + scope=["valence"], + ) + ], + ) + ] + method.electronic = Electronic(method="DFT") + xc_functional = XCFunctional(exchange=[Functional(name="GGA_X_PBE")]) method.dft = DFT(xc_functional=xc_functional) scc = run.calculation[-1] scc.method_ref = method @@ -131,18 +176,17 @@ def get_template_dft() -> EntryArchive: def get_template_excited(type: str) -> EntryArchive: - '''Returns a basic archive template for a ExcitedState calculation. - ''' + """Returns a basic archive template for a ExcitedState calculation.""" template = get_template_computation() run = template.run[-1] method = run.m_create(Method) - if type == 'GW': - method.gw = GW(type='G0W0') - elif type == 'Photon': - photon = Photon(multipole_type='dipole') + if type == "GW": + method.gw = GW(type="G0W0") + elif type == "Photon": + photon = Photon(multipole_type="dipole") method.m_add_sub_section(Method.photon, photon) - elif type == 'BSE': - method.bse = BSE(type='Singlet', solver='Lanczos-Haydock') + elif type == "BSE": + method.bse = BSE(type="Singlet", solver="Lanczos-Haydock") scc = run.calculation[-1] scc.method_ref = method template.workflow2 = SinglePoint() @@ -150,24 +194,26 @@ def get_template_excited(type: str) -> EntryArchive: def get_template_tb_wannier() -> EntryArchive: - '''Returns a basic archive template for a TB calculation. - ''' + """Returns a basic archive template for a TB calculation.""" template = get_template_computation() run = template.run[-1] - run.program = Program(name='Wannier90', version='3.1.0') + run.program = Program(name="Wannier90", version="3.1.0") method = run.m_create(Method) method_tb = method.m_create(TB) - method_tb.name = 'Wannier' + method_tb.name = "Wannier" method_tb.wannier = Wannier(is_maximally_localized=False) system = run.system[-1] - system.m_add_sub_section(System.atoms_group, AtomsGroup( - label='Br', - type='projection', - index=0, - is_molecule=False, - n_atoms=1, - atom_indices=np.array([0]) - )) + system.m_add_sub_section( + System.atoms_group, + AtomsGroup( + label="Br", + type="projection", + index=0, + is_molecule=False, + n_atoms=1, + atom_indices=np.array([0]), + ), + ) scc = run.calculation[-1] scc.method_ref = method template.workflow2 = SinglePoint() @@ -175,18 +221,24 @@ def get_template_tb_wannier() -> EntryArchive: def get_template_dmft() -> EntryArchive: - '''Returns a basic archive template for a DMFT calculation. - ''' + """Returns a basic archive template for a DMFT calculation.""" template = get_template_computation() run = template.run[-1] - run.program = Program(name='w2dynamics') + run.program = Program(name="w2dynamics") input_method = run.m_create(Method) input_model = input_method.m_create(LatticeModelHamiltonian) - input_model.hubbard_kanamori_model.append(HubbardKanamoriModel(orbital='d', u=4.0e-19, jh=0.6e-19)) + input_model.hubbard_kanamori_model.append( + HubbardKanamoriModel(orbital="d", u=4.0e-19, jh=0.6e-19) + ) method_dmft = run.m_create(Method) method_dmft.dmft = DMFT( - impurity_solver='CT-HYB', n_impurities=1, n_electrons=[1.0], n_correlated_orbitals=[3.0], - inverse_temperature=60.0, magnetic_state='paramagnetic') + impurity_solver="CT-HYB", + n_impurities=1, + n_electrons=[1.0], + n_correlated_orbitals=[3.0], + inverse_temperature=60.0, + magnetic_state="paramagnetic", + ) method_dmft.starting_method_ref = input_method scc = run.calculation[-1] scc.method_ref = method_dmft @@ -195,12 +247,11 @@ def get_template_dmft() -> EntryArchive: def get_template_maxent() -> EntryArchive: - '''Returns a basic archive template for a MaxEnt analytical continuation calculation. - ''' + """Returns a basic archive template for a MaxEnt analytical continuation calculation.""" # TODO update when MaxEnt methodology is defined template = get_template_computation() run = template.run[-1] - run.program = Program(name='w2dynamics') + run.program = Program(name="w2dynamics") method = run.m_create(Method) scc = run.calculation[-1] scc.method_ref = method @@ -230,22 +281,24 @@ def get_template_for_structure(atoms: aseAtoms) -> EntryArchive: def get_section_system(atoms: aseAtoms): system = System() system.atoms = aseAtoms( - positions=atoms.get_positions() * 1E-10, + positions=atoms.get_positions() * 1e-10, labels=atoms.get_chemical_symbols(), - lattice_vectors=atoms.get_cell() * 1E-10, - periodic=atoms.get_pbc()) + lattice_vectors=atoms.get_cell() * 1e-10, + periodic=atoms.get_pbc(), + ) return system def add_template_dos( - template: EntryArchive, - fill: List = [[[0, 1], [2, 3]]], - energy_reference_fermi: Union[float, None] = None, - energy_reference_highest_occupied: Union[float, None] = None, - energy_reference_lowest_unoccupied: Union[float, None] = None, - n_values: int = 101, - type: str = 'electronic') -> EntryArchive: - '''Used to create a test data for DOS. + template: EntryArchive, + fill: List = [[[0, 1], [2, 3]]], + energy_reference_fermi: Union[float, None] = None, + energy_reference_highest_occupied: Union[float, None] = None, + energy_reference_lowest_unoccupied: Union[float, None] = None, + n_values: int = 101, + type: str = "electronic", +) -> EntryArchive: + """Used to create a test data for DOS. Args: fill: List containing the energy ranges (eV) that should be filled with @@ -256,15 +309,17 @@ def add_template_dos( energy_reference_lowest_unoccupied: Lowest unoccupied energy (eV) as given by a parser. type: 'electronic' or 'vibrational' has_references: Whether the DOS has energy references or not. - ''' - if len(fill) > 1 and type != 'electronic': - raise ValueError('Cannot create spin polarized DOS for non-electronic data.') + """ + if len(fill) > 1 and type != "electronic": + raise ValueError("Cannot create spin polarized DOS for non-electronic data.") scc = template.run[0].calculation[0] - dos_type = Calculation.dos_electronic if type == 'electronic' else Calculation.dos_phonon + dos_type = ( + Calculation.dos_electronic if type == "electronic" else Calculation.dos_phonon + ) energies = np.linspace(-5, 5, n_values) for i, range_list in enumerate(fill): dos = scc.m_create(Dos, dos_type) - dos.spin_channel = i if (len(fill) == 2 and type == 'electronic') else None + dos.spin_channel = i if (len(fill) == 2 and type == "electronic") else None dos.energies = energies * ureg.electron_volt dos_total = dos.m_create(DosValues, Dos.total) dos_value = np.zeros(n_values) @@ -283,36 +338,43 @@ def add_template_dos( scc.energy = Energy( fermi=energy_reference_fermi, highest_occupied=energy_reference_highest_occupied, - lowest_unoccupied=energy_reference_lowest_unoccupied) + lowest_unoccupied=energy_reference_lowest_unoccupied, + ) return template def get_template_dos( - fill: List = [[[0, 1], [2, 3]]], - energy_reference_fermi: Union[float, None] = None, - energy_reference_highest_occupied: Union[float, None] = None, - energy_reference_lowest_unoccupied: Union[float, None] = None, - n_values: int = 101, - type: str = 'electronic', - normalize: bool = True) -> EntryArchive: - + fill: List = [[[0, 1], [2, 3]]], + energy_reference_fermi: Union[float, None] = None, + energy_reference_highest_occupied: Union[float, None] = None, + energy_reference_lowest_unoccupied: Union[float, None] = None, + n_values: int = 101, + type: str = "electronic", + normalize: bool = True, +) -> EntryArchive: archive = get_template_dft() - archive = add_template_dos(archive, fill, energy_reference_fermi, - energy_reference_highest_occupied, - energy_reference_lowest_unoccupied, - n_values, type) + archive = add_template_dos( + archive, + fill, + energy_reference_fermi, + energy_reference_highest_occupied, + energy_reference_lowest_unoccupied, + n_values, + type, + ) if normalize: archive = run_normalize(archive) return archive def add_template_band_structure( - template: EntryArchive, - band_gaps: List = None, - type: str = 'electronic', - has_references: bool = True, - has_reciprocal_cell: bool = True) -> EntryArchive: - '''Used to create a test data for band structures. + template: EntryArchive, + band_gaps: List = None, + type: str = "electronic", + has_references: bool = True, + has_reciprocal_cell: bool = True, +) -> EntryArchive: + """Used to create a test data for band structures. Args: band_gaps: List containing the band gap value and band gap type as a @@ -322,13 +384,13 @@ def add_template_band_structure( type: 'electronic' or 'vibrational' has_references: Whether the band structure has energy references or not. has_reciprocal_cell: Whether the reciprocal cell is available or not. - ''' + """ if band_gaps is None: band_gaps = [None] if not has_reciprocal_cell: template.run[0].system[0].atoms = None scc = template.run[0].calculation[0] - if type == 'electronic': + if type == "electronic": bs = scc.m_create(BandStructure, Calculation.band_structure_electronic) n_spin_channels = len(band_gaps) fermi: List[float] = [] @@ -354,7 +416,12 @@ def add_template_band_structure( n_segments = 2 full_space = np.linspace(0, 2 * np.pi, 200) k, m = divmod(len(full_space), n_segments) - space = list((full_space[i * k + min(i, m):(i + 1) * k + min(i + 1, m)] for i in range(n_segments))) + space = list( + ( + full_space[i * k + min(i, m) : (i + 1) * k + min(i + 1, m)] + for i in range(n_segments) + ) + ) for i_seg in range(n_segments): krange = space[i_seg] n_points = len(krange) @@ -362,17 +429,17 @@ def add_template_band_structure( energies = np.zeros((n_spin_channels, n_points, 2)) k_points = np.zeros((n_points, 3)) k_points[:, 0] = np.linspace(0, 1, n_points) - if type == 'electronic': + if type == "electronic": for i_spin in range(n_spin_channels): if band_gaps[i_spin] is not None: - if band_gaps[i_spin][1] == 'direct': + if band_gaps[i_spin][1] == "direct": energies[i_spin, :, 0] = -np.cos(krange) energies[i_spin, :, 1] = np.cos(krange) - elif band_gaps[i_spin][1] == 'indirect': + elif band_gaps[i_spin][1] == "indirect": energies[i_spin, :, 0] = -np.cos(krange) energies[i_spin, :, 1] = np.sin(krange) else: - raise ValueError('Invalid band gap type') + raise ValueError("Invalid band gap type") energies[i_spin, :, 1] += 2 + band_gaps[i_spin][0] else: energies[i_spin, :, 0] = -np.cos(krange) @@ -386,23 +453,23 @@ def add_template_band_structure( def get_template_band_structure( - band_gaps: List = None, - type: str = 'electronic', - has_references: bool = True, - has_reciprocal_cell: bool = True, - normalize: bool = True) -> EntryArchive: - + band_gaps: List = None, + type: str = "electronic", + has_references: bool = True, + has_reciprocal_cell: bool = True, + normalize: bool = True, +) -> EntryArchive: archive = get_template_dft() - archive = add_template_band_structure(archive, band_gaps, type, - has_references, has_reciprocal_cell) + archive = add_template_band_structure( + archive, band_gaps, type, has_references, has_reciprocal_cell + ) if normalize: archive = run_normalize(archive) return archive def add_template_greens_functions(template: EntryArchive) -> EntryArchive: - '''Used to create a test data for Greens functions. - ''' + """Used to create a test data for Greens functions.""" scc = template.run[0].calculation[0] sec_gfs = scc.m_create(GreensFunctions) sec_gfs.matsubara_freq = np.array([-2.0, -1.0, 0.0, 1.0, 2.0]) @@ -411,18 +478,24 @@ def add_template_greens_functions(template: EntryArchive) -> EntryArchive: n_spin = 2 n_orbitals = 3 n_iw = len(sec_gfs.matsubara_freq) - self_energy_iw = [[[[w * 1j + o + s + a for w in range(n_iw)] for o in range(n_orbitals)] for s in range(n_spin)] for a in range(n_atoms)] + self_energy_iw = [ + [ + [[w * 1j + o + s + a for w in range(n_iw)] for o in range(n_orbitals)] + for s in range(n_spin) + ] + for a in range(n_atoms) + ] sec_gfs.self_energy_iw = self_energy_iw sec_gfs.greens_function_tau = self_energy_iw return template def get_template_gw_workflow() -> EntryArchive: - '''Returns a basic archive template for a GW workflow entry, composed of two main tasks: - DFT GeometryOptimization and GW SinglePoint.''' + """Returns a basic archive template for a GW workflow entry, composed of two main tasks: + DFT GeometryOptimization and GW SinglePoint.""" # Defining DFT and GW SinglePoint archives and adding band_structure and dos to them. archive_dft = get_template_dft() - archive_gw = get_template_excited(type='GW') + archive_gw = get_template_excited(type="GW") archive_dft = add_template_band_structure(archive_dft) archive_gw = add_template_band_structure(archive_gw) archive_dft = add_template_dos(archive_dft) @@ -432,13 +505,21 @@ def get_template_gw_workflow() -> EntryArchive: run_normalize(archive_gw) # Defining DFT and GW tasks for later the GW workflow task_dft = TaskReference(task=archive_dft.workflow2) - task_dft.name = 'DFT' - task_dft.inputs = [Link(name='Input structure', section=archive_dft.run[-1].system[-1])] - task_dft.outputs = [Link(name='Output DFT calculation', section=archive_dft.run[-1].calculation[-1])] + task_dft.name = "DFT" + task_dft.inputs = [ + Link(name="Input structure", section=archive_dft.run[-1].system[-1]) + ] + task_dft.outputs = [ + Link(name="Output DFT calculation", section=archive_dft.run[-1].calculation[-1]) + ] task_gw = TaskReference(task=archive_gw.workflow2) - task_gw.name = 'GW' - task_gw.inputs = [Link(name='Output DFT calculation', section=archive_dft.run[-1].calculation[-1])] - task_gw.outputs = [Link(name='Output GW calculation', section=archive_gw.run[-1].calculation[-1])] + task_gw.name = "GW" + task_gw.inputs = [ + Link(name="Output DFT calculation", section=archive_dft.run[-1].calculation[-1]) + ] + task_gw.outputs = [ + Link(name="Output GW calculation", section=archive_gw.run[-1].calculation[-1]) + ] # GW workflow entry (no need of creating Method nor Calculation) template = EntryArchive() run = Run() @@ -446,14 +527,22 @@ def get_template_gw_workflow() -> EntryArchive: run.program = archive_dft.run[-1].program run.system = archive_dft.run[-1].system workflow = GWworkflow() - workflow.name = 'GW' - workflow_method = GWMethod( + workflow_method = DFTPlusGWMethod( gw_method_ref=archive_gw.run[-1].method[-1].gw, starting_point=archive_dft.run[-1].method[-1].dft.xc_functional, - electrons_representation=archive_dft.run[-1].method[-1].electrons_representation[-1]) + electrons_representation=archive_dft.run[-1] + .method[-1] + .electrons_representation[-1], + ) workflow.m_add_sub_section(GWworkflow.method, workflow_method) - workflow.m_add_sub_section(GWworkflow.inputs, Link(name='Input structure', section=archive_dft.run[-1].system[-1])) - workflow.m_add_sub_section(GWworkflow.outputs, Link(name='Output GW calculation', section=archive_gw.run[-1].calculation[-1])) + workflow.m_add_sub_section( + GWworkflow.inputs, + Link(name="Input structure", section=archive_dft.run[-1].system[-1]), + ) + workflow.m_add_sub_section( + GWworkflow.outputs, + Link(name="Output GW calculation", section=archive_gw.run[-1].calculation[-1]), + ) workflow.m_add_sub_section(GWworkflow.tasks, task_dft) workflow.m_add_sub_section(GWworkflow.tasks, task_gw) template.workflow2 = workflow @@ -471,13 +560,23 @@ def get_template_dmft_workflow() -> EntryArchive: run_normalize(archive_dmft) # Defining Projection and DMFT tasks for later the DMFT workflow task_proj = TaskReference(task=archive_tb.workflow2) - task_proj.name = 'Projection' - task_proj.inputs = [Link(name='Input structure', section=archive_tb.run[-1].system[-1])] - task_proj.outputs = [Link(name='Output TB calculation', section=archive_tb.run[-1].calculation[-1])] + task_proj.name = "TB" + task_proj.inputs = [ + Link(name="Input structure", section=archive_tb.run[-1].system[-1]) + ] + task_proj.outputs = [ + Link(name="Output TB calculation", section=archive_tb.run[-1].calculation[-1]) + ] task_dmft = TaskReference(task=archive_dmft.workflow2) - task_dmft.name = 'DMFT' - task_dmft.inputs = [Link(name='Output TB calculation', section=archive_tb.run[-1].calculation[-1])] - task_dmft.outputs = [Link(name='Output DMFT calculation', section=archive_dmft.run[-1].calculation[-1])] + task_dmft.name = "DMFT" + task_dmft.inputs = [ + Link(name="Output TB calculation", section=archive_tb.run[-1].calculation[-1]) + ] + task_dmft.outputs = [ + Link( + name="Output DMFT calculation", section=archive_dmft.run[-1].calculation[-1] + ) + ] # DMFT workflow entry (no need of creating Method nor Calculation) template = EntryArchive() run = Run() @@ -485,13 +584,21 @@ def get_template_dmft_workflow() -> EntryArchive: run.program = archive_dmft.run[-1].program run.system = archive_tb.run[-1].system workflow = DMFTworkflow() - workflow.name = 'DMFT' - workflow_method = DMFTMethod( + workflow_method = DFTPlusTBPlusDMFTMethod( tb_method_ref=archive_tb.run[-1].method[-1].tb, - dmft_method_ref=archive_dmft.run[-1].method[-1].dmft) + dmft_method_ref=archive_dmft.run[-1].method[-1].dmft, + ) workflow.m_add_sub_section(DMFTworkflow.method, workflow_method) - workflow.m_add_sub_section(DMFTworkflow.inputs, Link(name='Input structure', section=archive_tb.run[-1].system[-1])) - workflow.m_add_sub_section(DMFTworkflow.outputs, Link(name='Output DMFT calculation', section=archive_dmft.run[-1].calculation[-1])) + workflow.m_add_sub_section( + DMFTworkflow.inputs, + Link(name="Input structure", section=archive_tb.run[-1].system[-1]), + ) + workflow.m_add_sub_section( + DMFTworkflow.outputs, + Link( + name="Output DMFT calculation", section=archive_dmft.run[-1].calculation[-1] + ), + ) workflow.m_add_sub_section(DMFTworkflow.tasks, task_proj) workflow.m_add_sub_section(DMFTworkflow.tasks, task_dmft) template.workflow2 = workflow @@ -509,13 +616,28 @@ def get_template_maxent_workflow() -> EntryArchive: run_normalize(archive_maxent) # Defining Projection and DMFT tasks for later the DMFT workflow task_dmft = TaskReference(task=archive_dmft.workflow2) - task_dmft.name = 'DMFT' - task_dmft.inputs = [Link(name='Input structure', section=archive_dmft.run[-1].system[-1])] - task_dmft.outputs = [Link(name='Output DMFT calculation', section=archive_dmft.run[-1].calculation[-1])] + task_dmft.name = "DMFT" + task_dmft.inputs = [ + Link(name="Input structure", section=archive_dmft.run[-1].system[-1]) + ] + task_dmft.outputs = [ + Link( + name="Output DMFT calculation", section=archive_dmft.run[-1].calculation[-1] + ) + ] task_maxent = TaskReference(task=archive_dmft.workflow2) - task_maxent.name = 'MaxEnt Sigma' - task_maxent.inputs = [Link(name='Output DMFT calculation', section=archive_dmft.run[-1].calculation[-1])] - task_maxent.outputs = [Link(name='Output MaxEnt Sigma calculation', section=archive_maxent.run[-1].calculation[-1])] + task_maxent.name = "MaxEnt" + task_maxent.inputs = [ + Link( + name="Output DMFT calculation", section=archive_dmft.run[-1].calculation[-1] + ) + ] + task_maxent.outputs = [ + Link( + name="Output MaxEnt calculation", + section=archive_maxent.run[-1].calculation[-1], + ) + ] # DMFT workflow entry (no need of creating Method) template = EntryArchive() run = Run() @@ -526,15 +648,24 @@ def get_template_maxent_workflow() -> EntryArchive: scc.system_ref = run.system[-1] template = add_template_dos(template) workflow = MaxEntworkflow() - workflow.name = 'MaxEnt' - workflow_method = MaxEntMethod( + workflow_method = DMFTPlusMaxEntMethod( dmft_method_ref=archive_dmft.run[-1].method[-1].dmft, - maxent_method_ref=archive_maxent.run[-1].method[-1]) + maxent_method_ref=archive_maxent.run[-1].method[-1], + ) workflow.m_add_sub_section(MaxEntworkflow.method, workflow_method) - workflow.m_add_sub_section(MaxEntworkflow.inputs, Link(name='Input structure', section=archive_dmft.run[-1].system[-1])) + workflow.m_add_sub_section( + MaxEntworkflow.inputs, + Link(name="Input structure", section=archive_dmft.run[-1].system[-1]), + ) outputs = [ - Link(name='Output MaxEnt Sigma calculation', section=archive_dmft.run[-1].calculation[-1]), - Link(name='Output MaxEnt calculation', section=template.run[-1].calculation[-1])] + Link( + name="Output MaxEnt calculation", + section=archive_dmft.run[-1].calculation[-1], + ), + Link( + name="Output MaxEnt calculation", section=template.run[-1].calculation[-1] + ), + ] workflow.outputs = outputs workflow.m_add_sub_section(MaxEntworkflow.tasks, task_dmft) workflow.m_add_sub_section(MaxEntworkflow.tasks, task_maxent) @@ -543,50 +674,66 @@ def get_template_maxent_workflow() -> EntryArchive: def get_template_bse_workflow() -> EntryArchive: - '''Returns a basic archive template for a BSE workflow entry, composed of two tasks: - PhotonPolarization SinglePoint number 1 and PhotonPolarization SinglePoint number 2.''' + """Returns a basic archive template for a BSE workflow entry, composed of two tasks: + PhotonPolarization SinglePoint number 1 and PhotonPolarization SinglePoint number 2.""" # Adding two spectras for both photon polarizations - archive_photon_1 = get_template_excited(type='Photon') - archive_photon_2 = get_template_excited(type='Photon') + archive_photon_1 = get_template_excited(type="Photon") + archive_photon_2 = get_template_excited(type="Photon") n_energies = 11 spectra_1 = Spectra( - type='XAS', + type="XAS", n_energies=n_energies, excitation_energies=np.linspace(0, 10, n_energies) * ureg.eV, intensities=np.linspace(100, 200, n_energies), - intensities_units='F/m' + intensities_units="F/m", ) provenance_1 = ElectronicStructureProvenance( - label='photon', - methodology=archive_photon_1.run[-1].method[-1] + label="photon", methodology=archive_photon_1.run[-1].method[-1] ) spectra_1.m_add_sub_section(Spectra.provenance, provenance_1) - archive_photon_1.run[-1].calculation[-1].m_add_sub_section(Calculation.spectra, spectra_1) + archive_photon_1.run[-1].calculation[-1].m_add_sub_section( + Calculation.spectra, spectra_1 + ) spectra_2 = Spectra( - type='XAS', + type="XAS", n_energies=n_energies, excitation_energies=np.linspace(0, 10, n_energies) * ureg.eV, intensities=np.linspace(200, 300, n_energies), - intensities_units='F/m' + intensities_units="F/m", ) provenance_2 = ElectronicStructureProvenance( - label='photon', - methodology=archive_photon_2.run[-1].method[-1] + label="photon", methodology=archive_photon_2.run[-1].method[-1] ) spectra_2.m_add_sub_section(Spectra.provenance, provenance_2) - archive_photon_2.run[-1].calculation[-1].m_add_sub_section(Calculation.spectra, spectra_2) + archive_photon_2.run[-1].calculation[-1].m_add_sub_section( + Calculation.spectra, spectra_2 + ) # Normalizing SinglePoint archives BEFORE defining the BSE workflow entry run_normalize(archive_photon_1) run_normalize(archive_photon_2) # Defining Photon1 and Photon2 tasks for later the BSE workflow task_photon_1 = TaskReference(task=archive_photon_1.workflow2) - task_photon_1.name = 'Photon 1' - task_photon_1.inputs = [Link(name='Input structure', section=archive_photon_1.run[-1].system[-1])] - task_photon_1.outputs = [Link(name='Output polarization 1', section=archive_photon_1.run[-1].calculation[-1])] + task_photon_1.name = "Photon 1" + task_photon_1.inputs = [ + Link(name="Input structure", section=archive_photon_1.run[-1].system[-1]) + ] + task_photon_1.outputs = [ + Link( + name="Output polarization 1", + section=archive_photon_1.run[-1].calculation[-1], + ) + ] task_photon_2 = TaskReference(task=archive_photon_2.workflow2) - task_photon_2.name = 'Photon 2' - task_photon_2.inputs = [Link(name='Input structure', section=archive_photon_1.run[-1].system[-1])] - task_photon_2.outputs = [Link(name='Output polarization 2', section=archive_photon_2.run[-1].calculation[-1])] + task_photon_2.name = "Photon 2" + task_photon_2.inputs = [ + Link(name="Input structure", section=archive_photon_1.run[-1].system[-1]) + ] + task_photon_2.outputs = [ + Link( + name="Output polarization 2", + section=archive_photon_2.run[-1].calculation[-1], + ) + ] # BSE workflow entry (no need of creating Calculation). We need to define BSE method. template = EntryArchive() run = Run() @@ -594,21 +741,40 @@ def get_template_bse_workflow() -> EntryArchive: run.program = archive_photon_1.run[-1].program run.system = archive_photon_1.run[-1].system method = run.m_create(Method) - method.bse = BSE(type='Singlet', solver='Lanczos-Haydock') + method.bse = BSE(type="Singlet", solver="Lanczos-Haydock") workflow = PhotonPolarization() - workflow.name = 'BSE' - workflow_method = PhotonPolarizationMethod(bse_method_ref=template.run[-1].method[-1].bse) + workflow.name = "BSE" + workflow_method = PhotonPolarizationMethod( + bse_method_ref=template.run[-1].method[-1].bse + ) workflow.m_add_sub_section(PhotonPolarization.method, workflow_method) spectras = [spectra_1, spectra_2] workflow_results = PhotonPolarizationResults( - n_polarizations=2, - spectrum_polarization=spectras + n_polarizations=2, spectrum_polarization=spectras ) workflow.m_add_sub_section(PhotonPolarization.results, workflow_results) - workflow.m_add_sub_section(PhotonPolarization.inputs, Link(name='Input structure', section=archive_photon_1.run[-1].system[-1])) - workflow.m_add_sub_section(PhotonPolarization.inputs, Link(name='Input BSE methodology', section=template.run[-1].method[-1])) - workflow.m_add_sub_section(PhotonPolarization.outputs, Link(name='Output polarization 1', section=archive_photon_1.run[-1].calculation[-1])) - workflow.m_add_sub_section(PhotonPolarization.outputs, Link(name='Output polarization 2', section=archive_photon_2.run[-1].calculation[-1])) + workflow.m_add_sub_section( + PhotonPolarization.inputs, + Link(name="Input structure", section=archive_photon_1.run[-1].system[-1]), + ) + workflow.m_add_sub_section( + PhotonPolarization.inputs, + Link(name="Input BSE methodology", section=template.run[-1].method[-1]), + ) + workflow.m_add_sub_section( + PhotonPolarization.outputs, + Link( + name="Output polarization 1", + section=archive_photon_1.run[-1].calculation[-1], + ), + ) + workflow.m_add_sub_section( + PhotonPolarization.outputs, + Link( + name="Output polarization 2", + section=archive_photon_2.run[-1].calculation[-1], + ), + ) workflow.m_add_sub_section(PhotonPolarization.tasks, task_photon_1) workflow.m_add_sub_section(PhotonPolarization.tasks, task_photon_2) template.workflow2 = workflow @@ -616,9 +782,9 @@ def get_template_bse_workflow() -> EntryArchive: def get_template_xs_workflow() -> EntryArchive: - '''Returns a basic archive template for a XS workflow entry, composed of two main tasks: + """Returns a basic archive template for a XS workflow entry, composed of two main tasks: DFT GeometryOptimization and BSE workflow. The BSE workflow archive contains one - PhotonPolarization SinglePoint task.''' + PhotonPolarization SinglePoint task.""" # Defining DFT and GW SinglePoint archives and adding band_structure and dos to them. archive_dft = get_template_dft() archive_dft = add_template_band_structure(archive_dft) @@ -629,15 +795,22 @@ def get_template_xs_workflow() -> EntryArchive: run_normalize(archive_bse) # Defining DFT and BSE tasks for later the BS workflow task_dft = TaskReference(task=archive_dft.workflow2) - task_dft.name = 'DFT' - task_dft.inputs = [Link(name='Input structure', section=archive_dft.run[-1].system[-1])] - task_dft.outputs = [Link(name='Output DFT calculation', section=archive_dft.run[-1].calculation[-1])] + task_dft.name = "DFT" + task_dft.inputs = [ + Link(name="Input structure", section=archive_dft.run[-1].system[-1]) + ] + task_dft.outputs = [ + Link(name="Output DFT calculation", section=archive_dft.run[-1].calculation[-1]) + ] task_bse = TaskReference(task=archive_bse.workflow2) - task_bse.name = 'BSE 1' - task_bse.inputs = [Link(name='Output DFT calculation', section=archive_dft.run[-1].calculation[-1])] + task_bse.name = "BSE 1" + task_bse.inputs = [ + Link(name="Output DFT calculation", section=archive_dft.run[-1].calculation[-1]) + ] task_bse.outputs = [ - Link(name='Polarization 1', section=archive_bse.workflow2.outputs[0].section), - Link(name='Polarization 2', section=archive_bse.workflow2.outputs[1].section)] + Link(name="Polarization 1", section=archive_bse.workflow2.outputs[0].section), + Link(name="Polarization 2", section=archive_bse.workflow2.outputs[1].section), + ] # XS (BSE) workflow entry (no need of creating Method nor Calculation) template = EntryArchive() run = Run() @@ -645,10 +818,19 @@ def get_template_xs_workflow() -> EntryArchive: run.program = archive_dft.run[-1].program run.system = archive_dft.run[-1].system workflow = XSworkflow() - workflow.name = 'XS' - workflow.m_add_sub_section(XSworkflow.inputs, Link(name='Input structure', section=archive_dft.run[-1].system[-1])) - workflow.m_add_sub_section(XSworkflow.outputs, Link(name='Polarization 1', section=archive_bse.workflow2.outputs[0].section)) - workflow.m_add_sub_section(XSworkflow.outputs, Link(name='Polarization 2', section=archive_bse.workflow2.outputs[1].section)) + workflow.name = "XS" + workflow.m_add_sub_section( + XSworkflow.inputs, + Link(name="Input structure", section=archive_dft.run[-1].system[-1]), + ) + workflow.m_add_sub_section( + XSworkflow.outputs, + Link(name="Polarization 1", section=archive_bse.workflow2.outputs[0].section), + ) + workflow.m_add_sub_section( + XSworkflow.outputs, + Link(name="Polarization 2", section=archive_bse.workflow2.outputs[1].section), + ) workflow.m_add_sub_section(XSworkflow.tasks, task_dft) workflow.m_add_sub_section(XSworkflow.tasks, task_bse) template.workflow2 = workflow @@ -656,231 +838,262 @@ def get_template_xs_workflow() -> EntryArchive: def set_dft_values(xc_functional_names: list) -> EntryArchive: - '''''' + """""" template = get_template_dft() template.run[0].method = None run = template.run[0] method_dft = run.m_create(Method) - method_dft.electrons_representation = [BasisSetContainer( - type='plane waves', - scope=['wavefunction'], - basis_set=[BasisSet( - type='plane waves', - scope=['valence'], - cutoff=500, - )] - )] + method_dft.electrons_representation = [ + BasisSetContainer( + type="plane waves", + scope=["wavefunction"], + basis_set=[ + BasisSet( + type="plane waves", + scope=["valence"], + cutoff=500, + ) + ], + ) + ] method_dft.dft = DFT() method_dft.electronic = Electronic( - method='DFT', - smearing=Smearing(kind='gaussian', width=1e-20), + method="DFT", + smearing=Smearing(kind="gaussian", width=1e-20), n_spin_channels=2, - van_der_waals_method='G06', - relativity_method='scalar_relativistic') + van_der_waals_method="G06", + relativity_method="scalar_relativistic", + ) method_dft.scf = Scf(threshold_energy_change=1e-24) method_dft.dft.xc_functional = XCFunctional() xc = method_dft.dft.xc_functional for xc_functional_name in xc_functional_names: - if re.search('^HYB_', xc_functional_name): + if re.search("^HYB_", xc_functional_name): xc.hybrid.append(Functional(name=xc_functional_name, weight=1.0)) continue - if re.search('_X?C_', xc_functional_name): + if re.search("_X?C_", xc_functional_name): xc.correlation.append(Functional(name=xc_functional_name, weight=1.0)) - if re.search('_XC?_', xc_functional_name): + if re.search("_XC?_", xc_functional_name): xc.exchange.append(Functional(name=xc_functional_name, weight=1.0)) xc.correlation.append(Functional(name=xc_functional_name, weight=1.0)) return template -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def dft() -> EntryArchive: - '''DFT calculation.''' - template = set_dft_values(['GGA_C_PBE', 'GGA_X_PBE']) + """DFT calculation.""" + template = set_dft_values(["GGA_C_PBE", "GGA_X_PBE"]) return run_normalize(template) -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def dft_method_referenced() -> EntryArchive: - '''DFT calculation with two methods: one referencing the other.''' + """DFT calculation with two methods: one referencing the other.""" template = get_template_dft() template.run[0].method = None run = template.run[0] method_dft = run.m_create(Method) - method_dft.electrons_representation = [BasisSetContainer( - type='plane waves', - scope=['wavefunction'], - basis_set=[BasisSet( - type='plane waves', - scope=['valence'], - )] - )] + method_dft.electrons_representation = [ + BasisSetContainer( + type="plane waves", + scope=["wavefunction"], + basis_set=[ + BasisSet( + type="plane waves", + scope=["valence"], + ) + ], + ) + ] method_dft.electronic = Electronic( - smearing=Smearing(kind='gaussian', width=1e-20), - n_spin_channels=2, van_der_waals_method='G06', - relativity_method='scalar_relativistic') + smearing=Smearing(kind="gaussian", width=1e-20), + n_spin_channels=2, + van_der_waals_method="G06", + relativity_method="scalar_relativistic", + ) method_dft.scf = Scf(threshold_energy_change=1e-24) method_dft.dft = DFT(xc_functional=XCFunctional()) - method_dft.dft.xc_functional.correlation.append(Functional(name='GGA_C_PBE', weight=1.0)) - method_dft.dft.xc_functional.exchange.append(Functional(name='GGA_X_PBE', weight=1.0)) + method_dft.dft.xc_functional.correlation.append( + Functional(name="GGA_C_PBE", weight=1.0) + ) + method_dft.dft.xc_functional.exchange.append( + Functional(name="GGA_X_PBE", weight=1.0) + ) method_ref = run.m_create(Method) - method_ref.electrons_representation = [BasisSetContainer( - type='plane waves', - scope=['wavefunction'], - basis_set=[BasisSet( - type='plane waves', - scope=['valence'], - )] - )] - method_ref.electronic = Electronic(method='DFT') + method_ref.electrons_representation = [ + BasisSetContainer( + type="plane waves", + scope=["wavefunction"], + basis_set=[ + BasisSet( + type="plane waves", + scope=["valence"], + ) + ], + ) + ] + method_ref.electronic = Electronic(method="DFT") method_ref.core_method_ref = method_dft run.calculation[0].method_ref = method_ref return run_normalize(template) -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def dft_exact_exchange() -> EntryArchive: - '''Add exact exchange explicitely to a PBE calculation.''' - template = set_dft_values(['GGA_C_PBE', 'GGA_X_PBE']) + """Add exact exchange explicitely to a PBE calculation.""" + template = set_dft_values(["GGA_C_PBE", "GGA_X_PBE"]) template.run[0].method[0].dft.xc_functional.hybrid.append(Functional()) - template.run[0].method[0].dft.xc_functional.hybrid[0].parameters = {'exact_exchange_mixing_factor': .25} - template.run[0].method[0].dft.xc_functional.hybrid[0].name = '+alpha' + template.run[0].method[0].dft.xc_functional.hybrid[0].parameters = { + "exact_exchange_mixing_factor": 0.25 + } + template.run[0].method[0].dft.xc_functional.hybrid[0].name = "+alpha" return run_normalize(template) -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def dft_empty() -> EntryArchive: template = set_dft_values([]) return run_normalize(template) -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def dft_wrong() -> EntryArchive: - template = set_dft_values(['FOO_X_FIGHTERS', 'BAR_C_EXAM']) + template = set_dft_values(["FOO_X_FIGHTERS", "BAR_C_EXAM"]) return run_normalize(template) -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def dft_pw() -> EntryArchive: - template = set_dft_values(['LDA_X', 'LDA_C_PW']) + template = set_dft_values(["LDA_X", "LDA_C_PW"]) return run_normalize(template) -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def dft_m06() -> EntryArchive: - template = set_dft_values(['MGGA_X_M06', 'MGGA_C_M06']) + template = set_dft_values(["MGGA_X_M06", "MGGA_C_M06"]) return run_normalize(template) -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def dft_b3lyp() -> EntryArchive: - template = set_dft_values(['HYB_GGA_XC_B3LYP']) + template = set_dft_values(["HYB_GGA_XC_B3LYP"]) return run_normalize(template) -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def dft_pbeh() -> EntryArchive: - template = set_dft_values(['HYB_GGA_XC_PBEH']) + template = set_dft_values(["HYB_GGA_XC_PBEH"]) return run_normalize(template) -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def dft_m05() -> EntryArchive: - template = set_dft_values(['MGGA_C_M05', 'HYB_MGGA_X_M05']) + template = set_dft_values(["MGGA_C_M05", "HYB_MGGA_X_M05"]) return run_normalize(template) -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def dft_pbe0_13() -> EntryArchive: - template = set_dft_values(['HYB_GGA_XC_PBE0_13']) + template = set_dft_values(["HYB_GGA_XC_PBE0_13"]) return run_normalize(template) -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def dft_pbe38() -> EntryArchive: - template = set_dft_values(['HYB_GGA_XC_PBE38']) + template = set_dft_values(["HYB_GGA_XC_PBE38"]) return run_normalize(template) -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def dft_pbe50() -> EntryArchive: - template = set_dft_values(['HYB_GGA_XC_PBE50']) + template = set_dft_values(["HYB_GGA_XC_PBE50"]) return run_normalize(template) -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def dft_m06_2x() -> EntryArchive: - template = set_dft_values(['HYB_MGGA_X_M06_2X']) + template = set_dft_values(["HYB_MGGA_X_M06_2X"]) return run_normalize(template) -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def dft_m05_2x() -> EntryArchive: - template = set_dft_values(['MGGA_C_M05_2X', 'HYB_MGGA_X_M05_2X']) + template = set_dft_values(["MGGA_C_M05_2X", "HYB_MGGA_X_M05_2X"]) return run_normalize(template) -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def dft_plus_u() -> EntryArchive: - '''DFT+U calculation with a Hubbard model.''' + """DFT+U calculation with a Hubbard model.""" template = get_template_dft() template.run[0].method = None run = template.run[0] method_dft = run.m_create(Method) - method_dft.electrons_representation = [BasisSetContainer( - type='plane waves', - scope=['wavefunction'], - basis_set=[BasisSet( - type='plane waves', - scope=['valence'], - )] - )] + method_dft.electrons_representation = [ + BasisSetContainer( + type="plane waves", + scope=["wavefunction"], + basis_set=[ + BasisSet( + type="plane waves", + scope=["valence"], + ) + ], + ) + ] method_dft.electronic = Electronic( - method='DFT+U', - smearing=Smearing(kind='gaussian', width=1e-20), - n_spin_channels=2, van_der_waals_method='G06', - relativity_method='scalar_relativistic') + method="DFT+U", + smearing=Smearing(kind="gaussian", width=1e-20), + n_spin_channels=2, + van_der_waals_method="G06", + relativity_method="scalar_relativistic", + ) method_dft.scf = Scf(threshold_energy_change=1e-24) method_dft.dft = DFT(xc_functional=XCFunctional()) - method_dft.dft.xc_functional.correlation.append(Functional(name='GGA_C_PBE', weight=1.0)) - method_dft.dft.xc_functional.exchange.append(Functional(name='GGA_X_PBE', weight=1.0)) - method_dft.atom_parameters.append(AtomParameters(label='Ti')) + method_dft.dft.xc_functional.correlation.append( + Functional(name="GGA_C_PBE", weight=1.0) + ) + method_dft.dft.xc_functional.exchange.append( + Functional(name="GGA_X_PBE", weight=1.0) + ) + method_dft.atom_parameters.append(AtomParameters(label="Ti")) method_dft.atom_parameters[0].hubbard_kanamori_model = HubbardKanamoriModel( - orbital='3d', u=4.5e-19, j=1.0e-19, double_counting_correction='Dudarev') + orbital="3d", u=4.5e-19, j=1.0e-19, double_counting_correction="Dudarev" + ) return run_normalize(template) -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def tb_wannier() -> EntryArchive: - '''Wannier TB calculation.''' + """Wannier TB calculation.""" template = get_template_tb_wannier() return run_normalize(template) -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def gw() -> EntryArchive: - '''GW calculation.''' - template = get_template_excited(type='GW') + """GW calculation.""" + template = get_template_excited(type="GW") return run_normalize(template) -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def bse() -> EntryArchive: - '''BSE calculation.''' - template = get_template_excited(type='BSE') + """BSE calculation.""" + template = get_template_excited(type="BSE") return run_normalize(template) -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def dmft() -> EntryArchive: - '''DMFT calculation.''' + """DMFT calculation.""" template = get_template_dmft() return run_normalize(template) - -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def mechanical_elastic() -> EntryArchive: - '''Entry with mechanical properties.''' + """Entry with mechanical properties.""" template = get_template_dft() # Elastic workflow @@ -894,83 +1107,86 @@ def mechanical_elastic() -> EntryArchive: return run_normalize(template) -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def mechanical_eos() -> EntryArchive: - '''Entry with mechanical properties.''' + """Entry with mechanical properties.""" template = get_template_dft() # EOS workflow template.workflow2 = EquationOfState() template.workflow2.results = EquationOfStateResults( - volumes=np.linspace(0, 10, 10) * ureg.angstrom ** 3, + volumes=np.linspace(0, 10, 10) * ureg.angstrom**3, energies=np.linspace(0, 10, 10) * ureg.electron_volt, ) eos_fit = template.workflow2.results.m_create(EOSFit) - eos_fit.function_name = 'murnaghan' + eos_fit.function_name = "murnaghan" eos_fit.fitted_energies = np.linspace(0, 10, 10) * ureg.electron_volt eos_fit.bulk_modulus = 10000 return run_normalize(template) - -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def single_point() -> EntryArchive: - '''Single point calculation.''' + """Single point calculation.""" template = get_template_dft() return run_normalize(template) -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def gw_workflow() -> EntryArchive: - '''GW workflow (DFT+GW) EntryArchive.''' + """GW workflow (DFT+GW) EntryArchive.""" template = get_template_gw_workflow() return run_normalize(template) -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def dmft_workflow() -> EntryArchive: - '''DMFT workflow (Projection+DMFT) EntryArchive.''' + """DMFT workflow (Projection+DMFT) EntryArchive.""" template = get_template_dmft_workflow() return run_normalize(template) -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def maxent_workflow() -> EntryArchive: - '''MaxEnt workflow (DMFT+MaxEnt Sigma) EntryArchive.''' + """MaxEnt workflow (DMFT+MaxEnt Sigma) EntryArchive.""" template = get_template_maxent_workflow() return run_normalize(template) -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def bse_workflow() -> EntryArchive: - '''BSE workflow (Photon1+Photon2) EntryArchive''' + """BSE workflow (Photon1+Photon2) EntryArchive""" template = get_template_bse_workflow() return run_normalize(template) -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def xs_workflow() -> EntryArchive: - '''XS workflow (DFT+BSEworkflow) EntryArchive.''' + """XS workflow (DFT+BSEworkflow) EntryArchive.""" template = get_template_xs_workflow() return run_normalize(template) -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def geometry_optimization() -> EntryArchive: template = get_template_dft() template.run[0].system = None template.run[0].calculation = None run = template.run[0] - atoms1 = ase.build.bulk('Si', 'diamond', cubic=True, a=5.431) - atoms2 = ase.build.bulk('Si', 'diamond', cubic=True, a=5.431) + atoms1 = ase.build.bulk("Si", "diamond", cubic=True, a=5.431) + atoms2 = ase.build.bulk("Si", "diamond", cubic=True, a=5.431) atoms2.translate([0.01, 0, 0]) sys1 = get_section_system(atoms1) sys2 = get_section_system(atoms2) scc1 = run.m_create(Calculation) scc2 = run.m_create(Calculation) - scc1.energy = Energy(total=EnergyEntry(value=1e-19), total_t0=EnergyEntry(value=1e-19)) - scc2.energy = Energy(total=EnergyEntry(value=0.5e-19), total_t0=EnergyEntry(value=0.5e-19)) + scc1.energy = Energy( + total=EnergyEntry(value=1e-19), total_t0=EnergyEntry(value=1e-19) + ) + scc2.energy = Energy( + total=EnergyEntry(value=0.5e-19), total_t0=EnergyEntry(value=0.5e-19) + ) scc1.system_ref = sys1 scc2.system_ref = sys2 scc1.method_ref = run.method[0] @@ -982,13 +1198,15 @@ def geometry_optimization() -> EntryArchive: convergence_tolerance_energy_difference=1e-3 * ureg.electron_volt, convergence_tolerance_force_maximum=1e-11 * ureg.newton, convergence_tolerance_displacement_maximum=1e-3 * ureg.angstrom, - method='bfgs')) + method="bfgs", + ) + ) return run_normalize(template) -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def molecular_dynamics() -> EntryArchive: - '''Molecular dynamics calculation.''' + """Molecular dynamics calculation.""" template = get_template_dft() run = template.run[0] @@ -1009,35 +1227,35 @@ def molecular_dynamics() -> EntryArchive: potential=EnergyEntry(value=step), ) rg_values = RadiusOfGyrationValues( - value=step, - label='MOL', - atomsgroup_ref=system + value=step, label="MOL", atomsgroup_ref=system ) - calc.radius_of_gyration = [RadiusOfGyration( - kind='molecular', - radius_of_gyration_values=[rg_values], - )] + calc.radius_of_gyration = [ + RadiusOfGyration( + kind="molecular", + radius_of_gyration_values=[rg_values], + ) + ] calcs.append(calc) run.m_add_sub_section(Run.calculation, calc) # Create workflow diff_values = DiffusionConstantValues( value=2.1, - error_type='Pearson correlation coefficient', + error_type="Pearson correlation coefficient", errors=0.98, ) msd_values = MeanSquaredDisplacementValues( times=[0, 1, 2], n_times=3, value=[0, 1, 2], - label='MOL', + label="MOL", errors=[0, 1, 2], diffusion_constant=diff_values, ) msd = MeanSquaredDisplacement( - type='molecular', - direction='xyz', - error_type='bootstrapping', + type="molecular", + direction="xyz", + error_type="bootstrapping", mean_squared_displacement_values=[msd_values], ) rdf_values = RadialDistributionFunctionValues( @@ -1046,10 +1264,10 @@ def molecular_dynamics() -> EntryArchive: value=[0, 1, 2], frame_start=0, frame_end=100, - label='MOL-MOL', + label="MOL-MOL", ) rdf = RadialDistributionFunction( - type='molecular', + type="molecular", radial_distribution_function_values=[rdf_values], ) results = MolecularDynamicsResults( @@ -1057,12 +1275,10 @@ def molecular_dynamics() -> EntryArchive: mean_squared_displacements=[msd], ) method = MolecularDynamicsMethod( - thermodynamic_ensemble='NVT', - integration_timestep=0.5 * ureg('fs'), - ) - md = MolecularDynamics( - results=results, method=method + thermodynamic_ensemble="NVT", + integration_timestep=0.5 * ureg("fs"), ) + md = MolecularDynamics(results=results, method=method) results.calculation_result_ref = calcs[-1] results.calculations_ref = calcs template.workflow2 = md @@ -1076,8 +1292,8 @@ def get_template_topology(pbc=False) -> EntryArchive: del run.system[0] # System - water1 = ase.build.molecule('H2O') - water2 = ase.build.molecule('H2O') + water1 = ase.build.molecule("H2O") + water2 = ase.build.molecule("H2O") water2.translate([5, 0, 0]) sys = water1 + water2 sys.set_cell([10, 10, 10]) @@ -1087,48 +1303,48 @@ def get_template_topology(pbc=False) -> EntryArchive: # Topology molecule_group = AtomsGroup( - label='MOL_GROUP', - type='molecule_group', + label="MOL_GROUP", + type="molecule_group", index=0, - composition_formula='H(4)O(2)', + composition_formula="H(4)O(2)", n_atoms=6, - atom_indices=[0, 1, 2, 3, 4, 5] + atom_indices=[0, 1, 2, 3, 4, 5], ) system.m_add_sub_section(System.atoms_group, molecule_group) molecule1 = AtomsGroup( - label='MOL', - type='molecule', + label="MOL", + type="molecule", index=0, - composition_formula='H(2)O(1)', + composition_formula="H(2)O(1)", n_atoms=3, - atom_indices=[0, 1, 2] + atom_indices=[0, 1, 2], ) molecule_group.m_add_sub_section(AtomsGroup.atoms_group, molecule1) molecule2 = AtomsGroup( - label='MOL', - type='molecule', + label="MOL", + type="molecule", index=0, - composition_formula='H(2)O(1)', + composition_formula="H(2)O(1)", n_atoms=3, - atom_indices=[3, 4, 5] + atom_indices=[3, 4, 5], ) molecule_group.m_add_sub_section(AtomsGroup.atoms_group, molecule2) monomer_group = AtomsGroup( - label='MON_GROUP', - type='monomer_group', + label="MON_GROUP", + type="monomer_group", index=0, - composition_formula='H(2)', + composition_formula="H(2)", n_atoms=2, - atom_indices=[1, 2] + atom_indices=[1, 2], ) molecule1.m_add_sub_section(AtomsGroup.atoms_group, monomer_group) monomer = AtomsGroup( - label='MON', - type='monomer', + label="MON", + type="monomer", index=0, - composition_formula='H(2)', + composition_formula="H(2)", n_atoms=2, - atom_indices=[1, 2] + atom_indices=[1, 2], ) monomer_group.m_add_sub_section(AtomsGroup.atoms_group, monomer) diff --git a/tests/test_simulationworkflowschema.py b/tests/test_simulationworkflowschema.py index 5e2faa8..9e5446d 100644 --- a/tests/test_simulationworkflowschema.py +++ b/tests/test_simulationworkflowschema.py @@ -85,13 +85,13 @@ def test_single_point_workflow(): def test_gw_workflow(gw_workflow): """Testing GW workflow (DFT+GW) entry""" workflow = gw_workflow.workflow2 - assert workflow.name == "GW" + 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 == "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" @@ -109,7 +109,7 @@ def test_gw_workflow(gw_workflow): def test_dmft_workflow(dmft_workflow): """Testing DMFT workflow entry""" workflow = dmft_workflow.workflow2 - assert workflow.name == "DMFT" + 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 @@ -119,7 +119,7 @@ def test_dmft_workflow(dmft_workflow): 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 == "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 @@ -139,7 +139,7 @@ def test_dmft_workflow(dmft_workflow): def test_maxent_workflow(maxent_workflow): """Testing MaxEnt workflow entry""" workflow = maxent_workflow.workflow2 - assert workflow.name == "MaxEnt" + 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 @@ -149,7 +149,7 @@ def test_maxent_workflow(maxent_workflow): assert workflow.method.maxent_method_ref results = maxent_workflow.results assert results.method.method_name == "DMFT" - assert results.method.workflow_name == "MaxEnt" + 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 @@ -183,7 +183,7 @@ def test_bse_workflow(bse_workflow): assert workflow.method.bse_method_ref.solver == "Lanczos-Haydock" results = bse_workflow.results assert results.method.method_name == "BSE" - assert results.method.workflow_name == "PhotonPolarization" + 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" @@ -211,8 +211,8 @@ def test_xs_workflow(xs_workflow): assert len(workflow.tasks) == 2 assert workflow.tasks[0].name == "DFT" and workflow.tasks[1].name == "BSE 1" assert ( - workflow.results.dos_dft - and workflow.results.band_structure_dft + workflow.results.dft_outputs.dos + and workflow.results.dft_outputs.band_structure and workflow.results.spectra ) results = xs_workflow.results