From ae3d0d6b676bf8805b3526b31053427377c37ac0 Mon Sep 17 00:00:00 2001 From: Aakash Ashok Naik <91958822+naik-aakash@users.noreply.github.com> Date: Sat, 11 May 2024 21:36:59 +0200 Subject: [PATCH] Update LobsterTaskDoc (#723) * decouple file pymatgen file parsing and post-processing (lobsterpy) * attempt temp fix to by pass pydantic error * revert allow_arbitrary type * update pymatgen version * update schema, lobster job and tests * update monty version * Update pyproject.toml * Update pyproject.toml * removed redundant are_cobis/are_coops in strongestbonds, add lobsterpy kwargs to taskdoc * Update pyproject.toml * move typically small objects like charge, madelung back to mongostore * restructure strongest bonds field * fix vasp/flows/test_electrode.py::test_electrode_makers - TypeError: Lattice.get_all_distances() got an unexpected keyword argument 'fcoords1' https://github.com/materialsproject/pymatgen-analysis-defects/releases/tag/v2024.5.11 * pin nequip==0.5.6 https://github.com/mir-group/nequip/issues/431 --------- Co-authored-by: J. George Co-authored-by: Janosh Riebesell --- .github/workflows/testing.yml | 2 +- pyproject.toml | 12 +- src/atomate2/lobster/jobs.py | 12 +- src/atomate2/lobster/schemas.py | 345 +++++++++++---------- tests/vasp/lobster/flows/test_lobster.py | 9 +- tests/vasp/lobster/schemas/test_lobster.py | 199 +++++------- 6 files changed, 293 insertions(+), 286 deletions(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index c8fbffcae6..99e65127a9 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -57,7 +57,7 @@ jobs: pip install git+https://gitlab.com/ase/ase pip install .[strict,tests,abinit] pip install torch-runstats - pip install --no-deps nequip + pip install --no-deps nequip==0.5.6 - name: Test env: diff --git a/pyproject.toml b/pyproject.toml index ead926f965..c88e7b19b4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,7 @@ dependencies = [ "numpy", "pydantic-settings>=2.0.3", "pydantic>=2.0.1", - "pymatgen>=2024.3.1", + "pymatgen>=2024.4.13", ] [project.optional-dependencies] @@ -43,10 +43,10 @@ amset = ["amset>=0.4.15", "pydash"] cclib = ["cclib"] mp = ["mp-api>=0.37.5"] phonons = ["phonopy>=1.10.8", "seekpath"] -lobster = ["ijson>=3.2.2", "lobsterpy>=0.3.7"] +lobster = ["ijson>=3.2.2", "lobsterpy>=0.3.8"] defects = [ "dscribe>=1.2.0", - "pymatgen-analysis-defects>=2022.11.30", + "pymatgen-analysis-defects>=2024.5.11", "python-ulid", ] forcefields = [ @@ -89,7 +89,7 @@ strict = [ "emmet-core==0.82.2", "ijson==3.2.3", "jobflow==0.1.17", - "lobsterpy==0.3.8", + "lobsterpy==0.4.0", "mace-torch>=0.3.3", "matgl==1.1.1", "monty==2024.3.31", @@ -98,8 +98,8 @@ strict = [ "phonopy==2.22.1", "pydantic-settings==2.2.1", "pydantic==2.7.1", - "pymatgen-analysis-defects==2024.4.23", - "pymatgen==2024.3.1", + "pymatgen-analysis-defects==2024.5.11", + "pymatgen==2024.5.1", "python-ulid==2.5.0", "quippy-ase==0.9.14", "seekpath==2.1.0", diff --git a/src/atomate2/lobster/jobs.py b/src/atomate2/lobster/jobs.py index af4aac1856..9d5bac7f27 100644 --- a/src/atomate2/lobster/jobs.py +++ b/src/atomate2/lobster/jobs.py @@ -9,7 +9,7 @@ from jobflow import Maker, job from pymatgen.electronic_structure.cohp import CompleteCohp from pymatgen.electronic_structure.dos import LobsterCompleteDos -from pymatgen.io.lobster import Lobsterin +from pymatgen.io.lobster import Bandoverlaps, Icohplist, Lobsterin from atomate2 import SETTINGS from atomate2.common.files import gzip_output_folder @@ -56,7 +56,15 @@ class LobsterMaker(Maker): run_lobster_kwargs: dict = field(default_factory=dict) calculation_type: str = "standard" - @job(output_schema=LobsterTaskDocument, data=[CompleteCohp, LobsterCompleteDos]) + @job( + output_schema=LobsterTaskDocument, + data=[ + CompleteCohp, + LobsterCompleteDos, + Bandoverlaps, + Icohplist, + ], + ) def make( self, wavefunction_dir: str | Path = None, diff --git a/src/atomate2/lobster/schemas.py b/src/atomate2/lobster/schemas.py index 67087ac028..5afecfc84d 100644 --- a/src/atomate2/lobster/schemas.py +++ b/src/atomate2/lobster/schemas.py @@ -298,6 +298,7 @@ def from_directory( cls, dir_name: Union[str, Path], save_cohp_plots: bool = True, + lobsterpy_kwargs: dict = None, plot_kwargs: dict = None, which_bonds: str = "all", ) -> tuple: @@ -311,12 +312,15 @@ def from_directory( save_cohp_plots : bool. Bool to indicate whether automatic cohp plots and jsons from lobsterpy will be generated. + lobsterpy_kwargs : dict. + kwargs to change default lobsterpy automatic analysis parameters. plot_kwargs : dict. kwargs to change plotting options in lobsterpy. which_bonds: str. mode for condensed bonding analysis: "cation-anion" and "all". """ plot_kwargs = plot_kwargs or {} + lobsterpy_kwargs = lobsterpy_kwargs or {} dir_name = Path(dir_name) cohpcar_path = Path(zpath(dir_name / "COHPCAR.lobster")) charge_path = Path(zpath(dir_name / "CHARGE.lobster")) @@ -325,6 +329,20 @@ def from_directory( icobilist_path = Path(zpath(dir_name / "ICOBILIST.lobster")) icooplist_path = Path(zpath(dir_name / "ICOOPLIST.lobster")) + # Update lobsterpy analysis parameters with user supplied parameters + lobsterpy_kwargs_updated = { + "are_cobis": False, + "are_coops": False, + "cutoff_icohp": 0.10, + "noise_cutoff": 0.1, + "orbital_cutoff": 0.05, + "orbital_resolved": False, + "start": None, + "summed_spins": False, # we will always use spin polarization here + "type_charge": None, + **lobsterpy_kwargs, + } + try: start = time.time() analyse = Analysis( @@ -332,9 +350,8 @@ def from_directory( path_to_icohplist=icohplist_path, path_to_cohpcar=cohpcar_path, path_to_charge=charge_path, - summed_spins=False, # we will always use spin polarization here - cutoff_icohp=0.10, which_bonds=which_bonds, + **lobsterpy_kwargs_updated, ) cba_run_time = time.time() - start # initialize lobsterpy condensed bonding analysis @@ -368,7 +385,7 @@ def from_directory( type_charges=analyse.type_charge, cohp_plot_data=CohpPlotData(data=cba_cohp_plot_data), cutoff_icohp=analyse.cutoff_icohp, - summed_spins=False, + summed_spins=lobsterpy_kwargs_updated.get("summed_spins"), which_bonds=analyse.which_bonds, final_dict_bonds=DictBonds(data=analyse.final_dict_bonds), final_dict_ions=DictIons(data=analyse.final_dict_ions), @@ -391,16 +408,17 @@ def from_directory( fp.write(f"{line}\n") # Read in strongest icohp values - sb_icohp, sb_icobi, sb_icoop = _identify_strongest_bonds( + sb = _identify_strongest_bonds( analyse=analyse, icobilist_path=icobilist_path, icohplist_path=icohplist_path, icooplist_path=icooplist_path, ) + except ValueError: - return None, None, None, None, None + return None, None, None else: - return condensed_bonding_analysis, describe, sb_icobi, sb_icohp, sb_icoop + return condensed_bonding_analysis, describe, sb class DosComparisons(BaseModel): @@ -544,6 +562,7 @@ def from_directory( A task document summarizing quality of the lobster calculation. """ dir_name = Path(dir_name) + calc_quality_kwargs = calc_quality_kwargs or {} band_overlaps_path = Path(zpath(dir_name / "bandOverlaps.lobster")) charge_path = Path(zpath(dir_name / "CHARGE.lobster")) doscar_path = Path( @@ -563,7 +582,14 @@ def from_directory( structure_path = Path(zpath(dir_name / "POSCAR")) vasprun_path = Path(zpath(dir_name / "vasprun.xml")) - calc_quality_kwargs = {} if calc_quality_kwargs is None else calc_quality_kwargs + # Update calc quality kwargs supplied by user + calc_quality_kwargs_updated = { + "e_range": [-20, 0], + "dos_comparison": True, + "n_bins": 256, + "bva_comp": True, + **calc_quality_kwargs, + } cal_quality_dict = Analysis.get_lobster_calc_quality_summary( path_to_poscar=structure_path, path_to_vasprun=vasprun_path, @@ -573,7 +599,7 @@ def from_directory( path_to_lobsterin=lobsterin_path, path_to_lobsterout=lobsterout_path, path_to_bandoverlaps=band_overlaps_path, - **calc_quality_kwargs, + **calc_quality_kwargs_updated, ) return CalcQualitySummary(**cal_quality_dict) @@ -589,11 +615,17 @@ class StrongestBonds(BaseModel): description="Denotes whether the information " "is for cation-anion pairs or all bonds", ) - are_coops: bool = Field(description="Denotes whether the file consists of ICOOPs") - are_cobis: bool = Field(escription="Denotes whether the file consists of ICOBIs") - strongest_bonds: Optional[dict] = Field( + strongest_bonds_icoop: Optional[dict] = Field( + None, + description="Dict with infos on bond strength and bond length based on ICOOP.", + ) + strongest_bonds_icohp: Optional[dict] = Field( + None, + description="Dict with infos on bond strength and bond length based on ICOHP.", + ) + strongest_bonds_icobi: Optional[dict] = Field( None, - description="Dict with infos on bond strength and bond length,.", + description="Dict with infos on bond strength and bond length based on ICOBI.", ) @@ -608,35 +640,31 @@ class LobsterTaskDocument(StructureMetadata, extra="allow"): # type: ignore[cal default_factory=datetime_str, description="Timestamp for this task document was last updated", ) - charges: Optional[dict] = Field( + charges: Optional[Charge] = Field( None, - description="Atomic charges dict from LOBSTER" - " based on Mulliken and Loewdin charge analysis", + description="pymatgen Charge obj. Contains atomic charges based on Mulliken " + "and Loewdin charge analysis", ) lobsterout: LobsteroutModel = Field(description="Lobster out data") lobsterin: LobsterinModel = Field(description="Lobster calculation inputs") - lobsterpy_data: CondensedBondingAnalysis = Field( - description="Model describing the LobsterPy data" + lobsterpy_data: Optional[CondensedBondingAnalysis] = Field( + None, description="Model describing the LobsterPy data" ) - lobsterpy_text: str = Field( - description="Stores LobsterPy automatic analysis summary text" + lobsterpy_text: Optional[str] = Field( + None, description="Stores LobsterPy automatic analysis summary text" ) - calc_quality_summary: CalcQualitySummary = Field( + calc_quality_summary: Optional[CalcQualitySummary] = Field( + None, description="Model summarizing results of lobster runs like charge spillings, " "band overlaps, DOS comparisons with VASP runs and quantum chemical LOBSTER " - "charge sign comparisons with BVA method" - ) - calc_quality_text: str = Field( - description="Stores calculation quality analysis summary text" + "charge sign comparisons with BVA method", ) - strongest_bonds_icohp: Optional[StrongestBonds] = Field( - None, description="Describes the strongest cation-anion ICOHP bonds" + calc_quality_text: Optional[str] = Field( + None, description="Stores calculation quality analysis summary text" ) - strongest_bonds_icoop: Optional[StrongestBonds] = Field( - None, description="Describes the strongest cation-anion ICOOP bonds" - ) - strongest_bonds_icobi: Optional[StrongestBonds] = Field( - None, description="Describes the strongest cation-anion ICOBI bonds" + strongest_bonds: Optional[StrongestBonds] = Field( + None, + description="Describes the strongest cation-anion ICOOP, ICOBI and ICOHP bonds", ) lobsterpy_data_cation_anion: Optional[CondensedBondingAnalysis] = Field( None, description="Model describing the LobsterPy data" @@ -645,14 +673,9 @@ class LobsterTaskDocument(StructureMetadata, extra="allow"): # type: ignore[cal None, description="Stores LobsterPy automatic analysis summary text", ) - strongest_bonds_icohp_cation_anion: Optional[StrongestBonds] = Field( - None, description="Describes the strongest cation-anion ICOHP bonds" - ) - strongest_bonds_icoop_cation_anion: Optional[StrongestBonds] = Field( - None, description="Describes the strongest cation-anion ICOOP bonds" - ) - strongest_bonds_icobi_cation_anion: Optional[StrongestBonds] = Field( - None, description="Describes the strongest cation-anion ICOBI bonds" + strongest_bonds_cation_anion: Optional[StrongestBonds] = Field( + None, + description="Describes the strongest cation-anion ICOOP, ICOBI and ICOHP bonds", ) dos: Optional[LobsterCompleteDos] = Field( None, description="pymatgen pymatgen.io.lobster.Doscar.completedos data" @@ -660,25 +683,24 @@ class LobsterTaskDocument(StructureMetadata, extra="allow"): # type: ignore[cal lso_dos: Optional[LobsterCompleteDos] = Field( None, description="pymatgen pymatgen.io.lobster.Doscar.completedos data" ) - madelung_energies: Optional[dict] = Field( + madelung_energies: Optional[MadelungEnergies] = Field( None, - description="Madelung energies dict from" - " LOBSTER based on Mulliken and Loewdin charges", + description="pymatgen Madelung energies obj. Contains madelung energies" + "based on Mulliken and Loewdin charges", ) - site_potentials: Optional[dict] = Field( + site_potentials: Optional[SitePotential] = Field( None, - description="Site potentials dict from" - " LOBSTER based on Mulliken and Loewdin charges", + description="pymatgen Site potentials obj. Contains site potentials " + "based on Mulliken and Loewdin charges", ) - gross_populations: Optional[dict] = Field( + gross_populations: Optional[Grosspop] = Field( None, - description="Gross populations dict from" - " LOBSTER based on Mulliken and Loewdin charges with" - "each site as a key and the gross population as a value.", + description="pymatgen Grosspopulations obj. Contains gross populations " + " based on Mulliken and Loewdin charges ", ) - band_overlaps: Optional[dict] = Field( + band_overlaps: Optional[Bandoverlaps] = Field( None, - description="Band overlaps data for each k-point from" + description="pymatgen Bandoverlaps obj for each k-point from" " bandOverlaps.lobster file if it exists", ) cohp_data: Optional[CompleteCohp] = Field( @@ -690,6 +712,15 @@ class LobsterTaskDocument(StructureMetadata, extra="allow"): # type: ignore[cal cobi_data: Optional[CompleteCohp] = Field( None, description="pymatgen CompleteCohp object with COBI data" ) + icohp_list: Optional[Icohplist] = Field( + None, description="pymatgen Icohplist object with ICOHP data" + ) + icoop_list: Optional[Icohplist] = Field( + None, description="pymatgen Icohplist object with ICOOP data" + ) + icobi_list: Optional[Icohplist] = Field( + None, description="pymatgen Icohplist object with ICOBI data" + ) schema: str = Field( __version__, description="Version of atomate2 used to create the document" @@ -705,13 +736,15 @@ def from_directory( cls, dir_name: Union[Path, str], additional_fields: dict = None, + add_coxxcar_to_task_document: bool = False, + analyze_outputs: bool = True, + calc_quality_kwargs: dict = None, + lobsterpy_kwargs: dict = None, + plot_kwargs: dict = None, store_lso_dos: bool = False, save_cohp_plots: bool = True, - plot_kwargs: dict = None, - calc_quality_kwargs: dict = None, save_cba_jsons: bool = True, - add_coxxcar_to_task_document: bool = True, - save_computational_data_jsons: bool = True, + save_computational_data_jsons: bool = False, ) -> "LobsterTaskDocument": """ Create a task document from a directory containing LOBSTER files. @@ -722,22 +755,26 @@ def from_directory( The path to the folder containing the calculation outputs. additional_fields : dict. Dictionary of additional fields to add to output document. + add_coxxcar_to_task_document : bool. + Bool to indicate whether to add COHPCAR, COOPCAR, COBICAR data objects + to the task document. + analyze_outputs: bool. + If True, will enable lobsterpy analysis. + calc_quality_kwargs : dict. + kwargs to change calc quality summary options in lobsterpy. + lobsterpy_kwargs : dict. + kwargs to change default lobsterpy automatic analysis parameters. + plot_kwargs : dict. + kwargs to change plotting options in lobsterpy. store_lso_dos : bool. Whether to store the LSO DOS. save_cohp_plots : bool. Bool to indicate whether automatic cohp plots and jsons from lobsterpy will be generated. - plot_kwargs : dict. - kwargs to change plotting options in lobsterpy. - calc_quality_kwargs : dict. - kwargs to change calc quality summary options in lobsterpy. save_cba_jsons : bool. Bool to indicate whether condensed bonding analysis jsons should be saved, consists of outputs from lobsterpy analysis, calculation quality summary, lobster dos, charges and madelung energies - add_coxxcar_to_task_document : bool. - Bool to indicate whether to add COHPCAR, COOPCAR, COBICAR data objects - to the task document save_computational_data_jsons : bool. Bool to indicate whether computational data jsons should be saved @@ -758,6 +795,8 @@ def from_directory( ) icohplist_path = Path(zpath(dir_name / "ICOHPLIST.lobster")) + icooplist_path = Path(zpath(dir_name / "ICOOPLIST.lobster")) + icobilist_path = Path(zpath(dir_name / "ICOBILIST.lobster")) cohpcar_path = Path(zpath(dir_name / "COHPCAR.lobster")) charge_path = Path(zpath(dir_name / "CHARGE.lobster")) cobicar_path = Path(zpath(dir_name / "COBICAR.lobster")) @@ -769,60 +808,69 @@ def from_directory( gross_populations_path = Path(zpath(dir_name / "GROSSPOP.lobster")) band_overlaps_path = Path(zpath(dir_name / "bandOverlaps.lobster")) + icohp_list = icoop_list = icobi_list = None + if icohplist_path.exists(): + icohp_list = Icohplist(filename=icohplist_path) + if icooplist_path.exists(): + icoop_list = Icohplist(filename=icooplist_path, are_coops=True) + if icobilist_path.exists(): + icobi_list = Icohplist(filename=icobilist_path, are_cobis=True) + # Do automatic bonding analysis with LobsterPy - condensed_bonding_analysis = None - sb_icobi = sb_icohp = sb_icoop = describe = None struct = Structure.from_file(structure_path) # will perform two condensed bonding analysis computations - if icohplist_path.exists() and cohpcar_path.exists() and charge_path.exists(): - ( - condensed_bonding_analysis, - describe, - sb_icobi, - sb_icohp, - sb_icoop, - ) = CondensedBondingAnalysis.from_directory( - dir_name, - save_cohp_plots=save_cohp_plots, - plot_kwargs=plot_kwargs, - which_bonds="all", - ) - ( - condensed_bonding_analysis_ionic, - describe_ionic, - sb_icobi_ionic, - sb_icohp_ionic, - sb_icoop_ionic, - ) = CondensedBondingAnalysis.from_directory( + condensed_bonding_analysis = None + condensed_bonding_analysis_ionic = None + sb_all = None + sb_ionic = None + calc_quality_summary = None + calc_quality_text = None + describe = None + describe_ionic = None + if analyze_outputs: + if ( + icohplist_path.exists() + and cohpcar_path.exists() + and charge_path.exists() + ): + ( + condensed_bonding_analysis, + describe, + sb_all, + ) = CondensedBondingAnalysis.from_directory( + dir_name, + save_cohp_plots=save_cohp_plots, + plot_kwargs=plot_kwargs, + lobsterpy_kwargs=lobsterpy_kwargs, + which_bonds="all", + ) + ( + condensed_bonding_analysis_ionic, + describe_ionic, + sb_ionic, + ) = CondensedBondingAnalysis.from_directory( + dir_name, + save_cohp_plots=save_cohp_plots, + plot_kwargs=plot_kwargs, + lobsterpy_kwargs=lobsterpy_kwargs, + which_bonds="cation-anion", + ) + # Get lobster calculation quality summary data + + calc_quality_summary = CalcQualitySummary.from_directory( dir_name, - save_cohp_plots=save_cohp_plots, - plot_kwargs=plot_kwargs, - which_bonds="cation-anion", + calc_quality_kwargs=calc_quality_kwargs, ) - # Get lobster calculation quality summary data - calc_quality_kwargs_default = { - "e_range": [-20, 0], - "dos_comparison": True, - "n_bins": 256, - "bva_comp": True, - **(calc_quality_kwargs or {}), - } - - calc_quality_summary = CalcQualitySummary.from_directory( - dir_name, - calc_quality_kwargs=calc_quality_kwargs_default, - ) - calc_quality_text = Description.get_calc_quality_description( - calc_quality_summary.model_dump() - ) + calc_quality_text = Description.get_calc_quality_description( + calc_quality_summary.model_dump() + ) # Read in charges charges = None if charge_path.exists(): - charge = Charge(charge_path) - charges = {"Mulliken": charge.Mulliken, "Loewdin": charge.Loewdin} + charges = Charge(filename=charge_path) # Read in DOS dos = None @@ -842,44 +890,22 @@ def from_directory( # Read in Madelung energies madelung_energies = None if madelung_energies_path.exists(): - madelung_obj = MadelungEnergies(filename=madelung_energies_path) - - madelung_energies = { - "Mulliken": madelung_obj.madelungenergies_Mulliken, - "Loewdin": madelung_obj.madelungenergies_Loewdin, - "Ewald_splitting": madelung_obj.ewald_splitting, - } + madelung_energies = MadelungEnergies(filename=madelung_energies_path) # Read in Site Potentials site_potentials = None if site_potentials_path.exists(): - site_potentials_obj = SitePotential(filename=site_potentials_path) - - site_potentials = { - "Mulliken": site_potentials_obj.sitepotentials_Mulliken, - "Loewdin": site_potentials_obj.sitepotentials_Loewdin, - "Ewald_splitting": site_potentials_obj.ewald_splitting, - } + site_potentials = SitePotential(filename=site_potentials_path) # Read in Gross Populations gross_populations = None if gross_populations_path.exists(): - gross_populations_obj = Grosspop(filename=gross_populations_path) - - gross_populations = {} - for atom_index, gross_pop in enumerate( - gross_populations_obj.list_dict_grosspop - ): - gross_populations[atom_index] = gross_pop + gross_populations = Grosspop(filename=gross_populations_path) # Read in Band overlaps band_overlaps = None if band_overlaps_path.exists(): - band_overlaps_obj = Bandoverlaps(filename=band_overlaps_path) - - band_overlaps = {} - for spin, value in band_overlaps_obj.bandoverlapsdict.items(): - band_overlaps[str(spin.value)] = value + band_overlaps = Bandoverlaps(filename=band_overlaps_path) # Read in COHPCAR, COBICAR, COOPCAR cohp_obj = None @@ -923,18 +949,16 @@ def from_directory( # include additional fields for cation-anion lobsterpy_data=condensed_bonding_analysis, lobsterpy_text=" ".join(describe.text) if describe is not None else None, - strongest_bonds_icohp=sb_icohp, - strongest_bonds_icoop=sb_icoop, - strongest_bonds_icobi=sb_icobi, + strongest_bonds=sb_all, lobsterpy_data_cation_anion=condensed_bonding_analysis_ionic, lobsterpy_text_cation_anion=" ".join(describe_ionic.text) if describe_ionic is not None else None, - strongest_bonds_icohp_cation_anion=sb_icohp_ionic, - strongest_bonds_icoop_cation_anion=sb_icoop_ionic, - strongest_bonds_icobi_cation_anion=sb_icobi_ionic, + strongest_bonds_cation_anion=sb_ionic, calc_quality_summary=calc_quality_summary, - calc_quality_text=" ".join(calc_quality_text), + calc_quality_text=" ".join(calc_quality_text) + if calc_quality_text is not None + else None, dos=dos, lso_dos=lso_dos, charges=charges, @@ -946,9 +970,12 @@ def from_directory( cohp_data=cohp_obj, coop_data=coop_obj, cobi_data=cobi_obj, + icohp_list=icohp_list, + icoop_list=icoop_list, + icobi_list=icobi_list, ) - if save_cba_jsons: + if save_cba_jsons and analyze_outputs: cba_json_save_dir = dir_name / "cba.json.gz" with gzip.open(cba_json_save_dir, "wt", encoding="UTF-8") as file: # Write the json in iterable format @@ -966,9 +993,7 @@ def from_directory( "lobsterpy_text": [ "".join(doc.lobsterpy_text_cation_anion) ], - "sb_icobi": doc.strongest_bonds_icobi_cation_anion, - "sb_icohp": doc.strongest_bonds_icohp_cation_anion, - "sb_icoop": doc.strongest_bonds_icoop_cation_anion, + "strongest_bonds": doc.strongest_bonds_cation_anion, } } else: @@ -984,9 +1009,7 @@ def from_directory( f"{lobsterpy_analysis_type}_bonds": { "lobsterpy_data": doc.lobsterpy_data, "lobsterpy_text": ["".join(doc.lobsterpy_text)], - "sb_icobi": doc.strongest_bonds_icobi, - "sb_icohp": doc.strongest_bonds_icohp, - "sb_icoop": doc.strongest_bonds_icoop, + "strongest_bonds": doc.strongest_bonds, } } monty_encoded_json_doc = jsanitize( @@ -1158,7 +1181,7 @@ def _identify_strongest_bonds( icobilist_path: Path, icohplist_path: Path, icooplist_path: Path, -) -> list[StrongestBonds]: +) -> StrongestBonds: """ Identify the strongest bonds and convert them into StrongestBonds objects. @@ -1175,16 +1198,16 @@ def _identify_strongest_bonds( Returns ------- - list[StrongestBonds] - List of StrongestBonds + StrongestBonds """ data = [ - (icohplist_path, False, False), - (icobilist_path, True, False), - (icooplist_path, False, True), + (icohplist_path, False, False, "icohp"), + (icobilist_path, True, False, "icobi"), + (icooplist_path, False, True, "icoop"), ] output = [] - for file, are_cobis, are_coops in data: + model_data = {"which_bonds": analyse.which_bonds} + for file, are_cobis, are_coops, prop in data: if file.exists(): icohplist = Icohplist( filename=file, @@ -1197,17 +1220,17 @@ def _identify_strongest_bonds( are_cobis=are_cobis, are_coops=are_coops, ) + model_data[f"strongest_bonds_{prop}"] = bond_dict output.append( StrongestBonds( - are_cobis=are_cobis, - are_coops=are_coops, strongest_bonds=bond_dict, which_bonds=analyse.which_bonds, ) ) else: + model_data[f"strongest_bonds_{prop}"] = {} output.append(None) - return output + return StrongestBonds(**model_data) # Don't we have this in pymatgen somewhere? @@ -1257,11 +1280,11 @@ def _get_strong_bonds( sep_lengths[idx].append(lengths[j]) if are_cobis and not are_coops: - prop = "ICOBI" + prop = "icobi" elif not are_cobis and are_coops: - prop = "ICOOP" + prop = "icoop" else: - prop = "ICOHP" + prop = "icohp" bond_dict = {} for idx, lab in enumerate(bond_labels_unique): @@ -1271,12 +1294,12 @@ def _get_strong_bonds( rel_bnd_list = rel_bnd.split("-") rel_bnd_list.sort() if label == rel_bnd_list: - if prop == "ICOHP": + if prop == "icohp": index = np.argmin(sep_icohp[idx]) bond_dict.update( { rel_bnd: { - prop: min(sep_icohp[idx]), + "bond_strength": min(sep_icohp[idx]), "length": sep_lengths[idx][index], } } @@ -1286,7 +1309,7 @@ def _get_strong_bonds( bond_dict.update( { rel_bnd: { - prop: max(sep_icohp[idx]), + "bond_strength": max(sep_icohp[idx]), "length": sep_lengths[idx][index], } } diff --git a/tests/vasp/lobster/flows/test_lobster.py b/tests/vasp/lobster/flows/test_lobster.py index 35ccf1fb23..23ee752ab2 100644 --- a/tests/vasp/lobster/flows/test_lobster.py +++ b/tests/vasp/lobster/flows/test_lobster.py @@ -68,7 +68,8 @@ def test_lobster_uniform_maker( job = VaspLobsterMaker( lobster_maker=LobsterMaker( task_document_kwargs={ - "calc_quality_kwargs": {"potcar_symbols": ["Si"], "n_bins": 10} + "calc_quality_kwargs": {"potcar_symbols": ["Si"], "n_bins": 10}, + "add_coxxcar_to_task_document": True, }, user_lobsterin_settings={ "COHPstartEnergy": -5.0, @@ -143,7 +144,8 @@ def test_lobstermaker( lobster_static_maker=LobsterStaticMaker(), lobster_maker=LobsterMaker( task_document_kwargs={ - "calc_quality_kwargs": {"potcar_symbols": ["Si"], "n_bins": 10} + "calc_quality_kwargs": {"potcar_symbols": ["Si"], "n_bins": 10}, + "add_coxxcar_to_task_document": True, }, user_lobsterin_settings={ "COHPstartEnergy": -5.0, @@ -219,7 +221,7 @@ def test_lobstermaker_delete( lobster_static_maker=LobsterStaticMaker(), lobster_maker=LobsterMaker( task_document_kwargs={ - "calc_quality_kwargs": {"potcar_symbols": ["Si"], "n_bins": 10} + "calc_quality_kwargs": {"potcar_symbols": ["Si"], "n_bins": 10}, }, user_lobsterin_settings={ "COHPstartEnergy": -5.0, @@ -303,6 +305,7 @@ def test_mp_vasp_lobstermaker( "calc_quality_kwargs": {"potcar_symbols": ["Fe_pv"], "n_bins": 10}, "save_computational_data_jsons": False, "save_cba_jsons": False, + "add_coxxcar_to_task_document": False, }, user_lobsterin_settings={ "COHPstartEnergy": -5.0, diff --git a/tests/vasp/lobster/schemas/test_lobster.py b/tests/vasp/lobster/schemas/test_lobster.py index 6aadd0c0d6..6af63c7468 100644 --- a/tests/vasp/lobster/schemas/test_lobster.py +++ b/tests/vasp/lobster/schemas/test_lobster.py @@ -4,6 +4,14 @@ from pymatgen.core.structure import Structure from pymatgen.electronic_structure.cohp import Cohp, CompleteCohp from pymatgen.electronic_structure.dos import LobsterCompleteDos +from pymatgen.io.lobster import ( + Bandoverlaps, + Charge, + Grosspop, + Icohplist, + MadelungEnergies, + SitePotential, +) from atomate2.common.files import copy_files, gunzip_files from atomate2.lobster.schemas import ( @@ -26,8 +34,10 @@ def test_lobster_task_document(lobster_test_dir): dir_name=lobster_test_dir / "lobsteroutputs/mp-2534", save_cohp_plots=False, calc_quality_kwargs={"n_bins": 100, "potcar_symbols": ["Ga_d", "As"]}, + lobsterpy_kwargs={"cutoff_icohp": 0.10, "noise_cutoff": 0.1}, save_cba_jsons=False, save_computational_data_jsons=False, + add_coxxcar_to_task_document=True, ) assert isinstance(doc.structure, Structure) assert isinstance(doc.lobsterout, LobsteroutModel) @@ -35,55 +45,57 @@ def test_lobster_task_document(lobster_test_dir): assert isinstance(doc.lobsterin, LobsterinModel) assert_allclose(doc.lobsterin.cohpstartenergy, -5) - assert isinstance(doc.strongest_bonds_icohp, StrongestBonds) + assert isinstance(doc.strongest_bonds, StrongestBonds) assert_allclose( - doc.strongest_bonds_icohp.strongest_bonds["As-Ga"]["ICOHP"], -4.32971 + doc.strongest_bonds.strongest_bonds_icohp["As-Ga"]["bond_strength"], -4.32971 ) assert_allclose( - doc.strongest_bonds_icobi.strongest_bonds["As-Ga"]["ICOBI"], 0.82707 + doc.strongest_bonds.strongest_bonds_icobi["As-Ga"]["bond_strength"], 0.82707 ) assert_allclose( - doc.strongest_bonds_icoop.strongest_bonds["As-Ga"]["ICOOP"], 0.31405 + doc.strongest_bonds.strongest_bonds_icoop["As-Ga"]["bond_strength"], 0.31405 ) assert_allclose( - doc.strongest_bonds_icohp.strongest_bonds["As-Ga"]["length"], 2.4899 + doc.strongest_bonds.strongest_bonds_icohp["As-Ga"]["length"], 2.4899 ) assert_allclose( - doc.strongest_bonds_icobi.strongest_bonds["As-Ga"]["length"], 2.4899 + doc.strongest_bonds.strongest_bonds_icobi["As-Ga"]["length"], 2.4899 ) assert_allclose( - doc.strongest_bonds_icoop.strongest_bonds["As-Ga"]["length"], 2.4899 + doc.strongest_bonds.strongest_bonds_icoop["As-Ga"]["length"], 2.4899 ) - assert doc.strongest_bonds_icoop.which_bonds == "all" - assert doc.strongest_bonds_icohp.which_bonds == "all" - assert doc.strongest_bonds_icobi.which_bonds == "all" + assert doc.strongest_bonds.which_bonds == "all" assert_allclose( - doc.strongest_bonds_icohp_cation_anion.strongest_bonds["As-Ga"]["ICOHP"], + doc.strongest_bonds_cation_anion.strongest_bonds_icohp["As-Ga"][ + "bond_strength" + ], -4.32971, ) assert_allclose( - doc.strongest_bonds_icobi_cation_anion.strongest_bonds["As-Ga"]["ICOBI"], + doc.strongest_bonds_cation_anion.strongest_bonds_icobi["As-Ga"][ + "bond_strength" + ], 0.82707, ) assert_allclose( - doc.strongest_bonds_icoop_cation_anion.strongest_bonds["As-Ga"]["ICOOP"], + doc.strongest_bonds_cation_anion.strongest_bonds_icoop["As-Ga"][ + "bond_strength" + ], 0.31405, ) assert_allclose( - doc.strongest_bonds_icohp_cation_anion.strongest_bonds["As-Ga"]["length"], + doc.strongest_bonds_cation_anion.strongest_bonds_icohp["As-Ga"]["length"], 2.4899, ) assert_allclose( - doc.strongest_bonds_icobi_cation_anion.strongest_bonds["As-Ga"]["length"], + doc.strongest_bonds_cation_anion.strongest_bonds_icobi["As-Ga"]["length"], 2.4899, ) assert_allclose( - doc.strongest_bonds_icoop_cation_anion.strongest_bonds["As-Ga"]["length"], + doc.strongest_bonds_cation_anion.strongest_bonds_icoop["As-Ga"]["length"], 2.4899, ) - assert doc.strongest_bonds_icoop_cation_anion.which_bonds == "cation-anion" - assert doc.strongest_bonds_icohp_cation_anion.which_bonds == "cation-anion" - assert doc.strongest_bonds_icobi_cation_anion.which_bonds == "cation-anion" + assert doc.strongest_bonds_cation_anion.which_bonds == "cation-anion" assert isinstance(doc.lobsterpy_data.cohp_plot_data.data["Ga1: 4 x As-Ga"], Cohp) assert doc.lobsterpy_data.which_bonds == "all" assert doc.lobsterpy_data_cation_anion.which_bonds == "cation-anion" @@ -98,31 +110,14 @@ def test_lobster_task_document(lobster_test_dir): assert isinstance(doc.cobi_data, CompleteCohp) assert isinstance(doc.coop_data, CompleteCohp) assert isinstance(doc.dos, LobsterCompleteDos) - assert_allclose(doc.madelung_energies["Mulliken"], -0.68) - assert_allclose( - doc.site_potentials["Mulliken"], - [-1.26, -1.27, -1.26, -1.27, 1.27, 1.27, 1.26, 1.26], - rtol=1e-2, - ) - assert_allclose(doc.site_potentials["Ewald_splitting"], 3.14) - assert len(doc.gross_populations) == 8 - assert doc.gross_populations[5]["element"] == "As" - expected_gross_pop = { - "4s": 1.38, - "4p_y": 1.18, - "4p_z": 1.18, - "4p_x": 1.18, - "total": 4.93, - } - gross_pop_here = doc.gross_populations[5]["Loewdin GP"] - assert expected_gross_pop == gross_pop_here - assert_allclose( - doc.charges["Mulliken"], - [0.13, 0.13, 0.13, 0.13, -0.13, -0.13, -0.13, -0.13], - rtol=1e-2, - ) - assert len(doc.band_overlaps["1"]) + len(doc.band_overlaps["-1"]) == 12 - + assert isinstance(doc.charges, Charge) + assert isinstance(doc.madelung_energies, MadelungEnergies) + assert isinstance(doc.site_potentials, SitePotential) + assert isinstance(doc.band_overlaps, Bandoverlaps) + assert isinstance(doc.icohp_list, Icohplist) + assert isinstance(doc.icobi_list, Icohplist) + assert isinstance(doc.icoop_list, Icohplist) + assert isinstance(doc.gross_populations, Grosspop) assert doc.chemsys == "As-Ga" doc2 = LobsterTaskDocument.from_directory( @@ -131,31 +126,22 @@ def test_lobster_task_document(lobster_test_dir): calc_quality_kwargs={"n_bins": 100, "potcar_symbols": ["Ba_sv", "O", "F"]}, save_cba_jsons=False, save_computational_data_jsons=False, + add_coxxcar_to_task_document=True, ) assert_allclose( - doc2.strongest_bonds_icohp.strongest_bonds["Ba-O"]["ICOHP"], -0.55689 + doc2.strongest_bonds.strongest_bonds_icohp["Ba-O"]["bond_strength"], -0.55689 ) assert_allclose( - doc2.strongest_bonds_icohp.strongest_bonds["Ba-F"]["ICOHP"], -0.44806 + doc2.strongest_bonds.strongest_bonds_icohp["Ba-F"]["bond_strength"], -0.44806 ) - assert len(doc2.band_overlaps["1"]) + len(doc2.band_overlaps["-1"]) == 2 - assert_allclose( - doc2.site_potentials["Loewdin"], - [*[-15.09] * 8, 14.78, 14.78, *[8.14, 8.14, 8.48, 8.48, 8.14, 8.14] * 2], - rtol=1e-2, - ) - assert_allclose(doc2.site_potentials["Ewald_splitting"], 3.14) - assert len(doc2.gross_populations) == 22 - assert doc2.gross_populations[10]["element"] == "F" - expected_gross_pop = { - "2s": 1.98, - "2p_y": 1.97, - "2p_z": 1.97, - "2p_x": 1.97, - "total": 7.88, - } - gross_popp_here = doc2.gross_populations[10]["Mulliken GP"] - assert expected_gross_pop == gross_popp_here + assert isinstance(doc2.charges, Charge) + assert isinstance(doc2.madelung_energies, MadelungEnergies) + assert isinstance(doc2.site_potentials, SitePotential) + assert isinstance(doc2.band_overlaps, Bandoverlaps) + assert isinstance(doc2.icohp_list, Icohplist) + assert isinstance(doc2.icobi_list, Icohplist) + assert isinstance(doc2.icoop_list, Icohplist) + assert isinstance(doc2.gross_populations, Grosspop) def test_lobster_task_document_non_gzip(lobster_test_dir, tmp_path): @@ -173,8 +159,10 @@ def test_lobster_task_document_non_gzip(lobster_test_dir, tmp_path): dir_name=tmp_path, # lobster_test_dir / "lobsteroutputs/mp-2534", save_cohp_plots=False, calc_quality_kwargs={"n_bins": 100, "potcar_symbols": ["Ga_d", "As"]}, + lobsterpy_kwargs={"cutoff_icohp": 0.10, "noise_cutoff": 0.01}, save_cba_jsons=False, save_computational_data_jsons=False, + add_coxxcar_to_task_document=True, ) assert isinstance(doc.structure, Structure) assert isinstance(doc.lobsterout, LobsteroutModel) @@ -182,55 +170,57 @@ def test_lobster_task_document_non_gzip(lobster_test_dir, tmp_path): assert isinstance(doc.lobsterin, LobsterinModel) assert_allclose(doc.lobsterin.cohpstartenergy, -5) - assert isinstance(doc.strongest_bonds_icohp, StrongestBonds) + assert isinstance(doc.strongest_bonds, StrongestBonds) assert_allclose( - doc.strongest_bonds_icohp.strongest_bonds["As-Ga"]["ICOHP"], -4.32971 + doc.strongest_bonds.strongest_bonds_icohp["As-Ga"]["bond_strength"], -4.32971 ) assert_allclose( - doc.strongest_bonds_icobi.strongest_bonds["As-Ga"]["ICOBI"], 0.82707 + doc.strongest_bonds.strongest_bonds_icobi["As-Ga"]["bond_strength"], 0.82707 ) assert_allclose( - doc.strongest_bonds_icoop.strongest_bonds["As-Ga"]["ICOOP"], 0.31405 + doc.strongest_bonds.strongest_bonds_icoop["As-Ga"]["bond_strength"], 0.31405 ) assert_allclose( - doc.strongest_bonds_icohp.strongest_bonds["As-Ga"]["length"], 2.4899 + doc.strongest_bonds.strongest_bonds_icohp["As-Ga"]["length"], 2.4899 ) assert_allclose( - doc.strongest_bonds_icobi.strongest_bonds["As-Ga"]["length"], 2.4899 + doc.strongest_bonds.strongest_bonds_icobi["As-Ga"]["length"], 2.4899 ) assert_allclose( - doc.strongest_bonds_icoop.strongest_bonds["As-Ga"]["length"], 2.4899 + doc.strongest_bonds.strongest_bonds_icoop["As-Ga"]["length"], 2.4899 ) - assert doc.strongest_bonds_icoop.which_bonds == "all" - assert doc.strongest_bonds_icohp.which_bonds == "all" - assert doc.strongest_bonds_icobi.which_bonds == "all" + assert doc.strongest_bonds.which_bonds == "all" assert_allclose( - doc.strongest_bonds_icohp_cation_anion.strongest_bonds["As-Ga"]["ICOHP"], + doc.strongest_bonds_cation_anion.strongest_bonds_icohp["As-Ga"][ + "bond_strength" + ], -4.32971, ) assert_allclose( - doc.strongest_bonds_icobi_cation_anion.strongest_bonds["As-Ga"]["ICOBI"], + doc.strongest_bonds_cation_anion.strongest_bonds_icobi["As-Ga"][ + "bond_strength" + ], 0.82707, ) assert_allclose( - doc.strongest_bonds_icoop_cation_anion.strongest_bonds["As-Ga"]["ICOOP"], + doc.strongest_bonds_cation_anion.strongest_bonds_icoop["As-Ga"][ + "bond_strength" + ], 0.31405, ) assert_allclose( - doc.strongest_bonds_icohp_cation_anion.strongest_bonds["As-Ga"]["length"], + doc.strongest_bonds_cation_anion.strongest_bonds_icohp["As-Ga"]["length"], 2.4899, ) assert_allclose( - doc.strongest_bonds_icobi_cation_anion.strongest_bonds["As-Ga"]["length"], + doc.strongest_bonds_cation_anion.strongest_bonds_icobi["As-Ga"]["length"], 2.4899, ) assert_allclose( - doc.strongest_bonds_icoop_cation_anion.strongest_bonds["As-Ga"]["length"], + doc.strongest_bonds_cation_anion.strongest_bonds_icoop["As-Ga"]["length"], 2.4899, ) - assert doc.strongest_bonds_icoop_cation_anion.which_bonds == "cation-anion" - assert doc.strongest_bonds_icohp_cation_anion.which_bonds == "cation-anion" - assert doc.strongest_bonds_icobi_cation_anion.which_bonds == "cation-anion" + assert doc.strongest_bonds_cation_anion.which_bonds == "cation-anion" assert isinstance(doc.lobsterpy_data.cohp_plot_data.data["Ga1: 4 x As-Ga"], Cohp) assert doc.lobsterpy_data.which_bonds == "all" assert doc.lobsterpy_data_cation_anion.which_bonds == "cation-anion" @@ -243,30 +233,14 @@ def test_lobster_task_document_non_gzip(lobster_test_dir, tmp_path): assert isinstance(doc.cobi_data, CompleteCohp) assert isinstance(doc.coop_data, CompleteCohp) assert isinstance(doc.dos, LobsterCompleteDos) - assert_allclose(doc.madelung_energies["Mulliken"], -0.68) - assert_allclose( - doc.site_potentials["Mulliken"], - [-1.26, -1.27, -1.26, -1.27, 1.27, 1.27, 1.26, 1.26], - rtol=1e-2, - ) - assert_allclose(doc.site_potentials["Ewald_splitting"], 3.14) - assert len(doc.gross_populations) == 8 - assert doc.gross_populations[5]["element"] == "As" - expected_gross_pop = { - "4s": 1.38, - "4p_y": 1.18, - "4p_z": 1.18, - "4p_x": 1.18, - "total": 4.93, - } - gross_pop_here = doc.gross_populations[5]["Loewdin GP"] - assert expected_gross_pop == gross_pop_here - assert_allclose( - doc.charges["Mulliken"], - [0.13, 0.13, 0.13, 0.13, -0.13, -0.13, -0.13, -0.13], - rtol=1e-2, - ) - assert len(doc.band_overlaps["1"]) + len(doc.band_overlaps["-1"]) == 12 + assert isinstance(doc.charges, Charge) + assert isinstance(doc.madelung_energies, MadelungEnergies) + assert isinstance(doc.site_potentials, SitePotential) + assert isinstance(doc.band_overlaps, Bandoverlaps) + assert isinstance(doc.icohp_list, Icohplist) + assert isinstance(doc.icobi_list, Icohplist) + assert isinstance(doc.icoop_list, Icohplist) + assert isinstance(doc.gross_populations, Grosspop) assert doc.chemsys == "As-Ga" @@ -363,14 +337,10 @@ def test_lobster_task_doc_saved_jsons(lobster_test_dir): "lobsterpy_text", "calc_quality_summary", "calc_quality_text", - "strongest_bonds_icohp", - "strongest_bonds_icoop", - "strongest_bonds_icobi", + "strongest_bonds", "lobsterpy_data_cation_anion", "lobsterpy_text_cation_anion", - "strongest_bonds_icohp_cation_anion", - "strongest_bonds_icoop_cation_anion", - "strongest_bonds_icobi_cation_anion", + "strongest_bonds_cation_anion", "dos", "lso_dos", "madelung_energies", @@ -380,6 +350,9 @@ def test_lobster_task_doc_saved_jsons(lobster_test_dir): "cohp_data", "coop_data", "cobi_data", + "icobi_list", + "icoop_list", + "icohp_list", ] # Read the data from saved computational data json as pymatgen objects