From 4b84d781cc5beced4df81333aab063997e9b0a08 Mon Sep 17 00:00:00 2001 From: Janosh Riebesell Date: Mon, 25 Nov 2024 16:52:45 -0500 Subject: [PATCH] fix minor code formatting issues + typos (#1070) --- docs/user/codes/vasp.md | 7 +- src/atomate2/common/flows/anharmonicity.py | 2 +- src/atomate2/common/flows/gruneisen.py | 2 +- src/atomate2/common/flows/phonons.py | 2 +- src/atomate2/common/flows/qha.py | 27 +++---- src/atomate2/common/jobs/anharmonicity.py | 18 ++--- src/atomate2/common/jobs/gruneisen.py | 8 +- src/atomate2/common/jobs/qha.py | 4 +- src/atomate2/common/schemas/phonons.py | 13 ++-- tests/vasp/flows/test_phonons.py | 88 +++++++--------------- 10 files changed, 65 insertions(+), 106 deletions(-) diff --git a/docs/user/codes/vasp.md b/docs/user/codes/vasp.md index e0b63ab51e..5e1e299b4b 100644 --- a/docs/user/codes/vasp.md +++ b/docs/user/codes/vasp.md @@ -1,4 +1,5 @@ (codes.vasp)= + # VASP At present, most workflows in atomate2 use the Vienna *ab initio* simulation package @@ -260,21 +261,19 @@ With the help of phonopy, these forces are then converted into a dynamical matri The dynamical matrices of three structures are then used as an input to the phonopy Grueneisen api to compute mode-dependent Grueneisen parameters. - ### Quasi-harmonic Workflow + Uses the quasi-harmonic approximation with the help of Phonopy to compute thermodynamic properties. First, a tight relaxation is performed. Subsequently, several optimizations at different constant volumes are performed. At each of the volumes, an additional phonon run is performed as well. Afterwards, equation of state fits are performed with phonopy. - - ### Equation of State Workflow + An equation of state workflow is implemented. First, a tight relaxation is performed. Subsequently, several optimizations at different constant volumes are performed. Additional static calculations might be performed afterwards to arrive at more accurate energies. Then, an equation of state fit is performed with pymatgen. - ### LOBSTER Perform bonding analysis with [LOBSTER](http://cohp.de/) and [LobsterPy](https://github.com/jageo/lobsterpy) diff --git a/src/atomate2/common/flows/anharmonicity.py b/src/atomate2/common/flows/anharmonicity.py index 5d9067bc4f..e2d7534592 100644 --- a/src/atomate2/common/flows/anharmonicity.py +++ b/src/atomate2/common/flows/anharmonicity.py @@ -81,7 +81,7 @@ def make( A previous calculation directory to use for copying outputs. Default is None. born: Optional[list[Matrix3D]] - Instead of recomputing born charges and epsilon, these values can also be + Instead of recomputing Born charges and epsilon, these values can also be provided manually. If born and epsilon_static are provided, the born run will be skipped it can be provided in the VASP convention with information for every atom in unit cell. Please be careful when converting structures diff --git a/src/atomate2/common/flows/gruneisen.py b/src/atomate2/common/flows/gruneisen.py index 700aa0b793..0977f9ebc6 100644 --- a/src/atomate2/common/flows/gruneisen.py +++ b/src/atomate2/common/flows/gruneisen.py @@ -40,7 +40,7 @@ class BaseGruneisenMaker(Maker, ABC): generated for all the three structures (ground state, expanded and shrunk volume) and accurate forces are computed for these structures. With the help of phonopy, these forces are then converted into a dynamical matrix. This dynamical matrix of - three structures is then used as an input for the phonopy Grueneisen api + three structures is then used as an input for the phonopy Grueneisen API to compute Grueneisen parameters. diff --git a/src/atomate2/common/flows/phonons.py b/src/atomate2/common/flows/phonons.py index 2604a08dd5..95d65e5f01 100644 --- a/src/atomate2/common/flows/phonons.py +++ b/src/atomate2/common/flows/phonons.py @@ -187,7 +187,7 @@ def make( prev_dir : str or Path or None A previous calculation directory to use for copying outputs. born: Matrix3D - Instead of recomputing born charges and epsilon, these values can also be + Instead of recomputing Born charges and epsilon, these values can also be provided manually. If born and epsilon_static are provided, the born run will be skipped it can be provided in the VASP convention with information for every atom in unit cell. Please be careful when converting structures diff --git a/src/atomate2/common/flows/qha.py b/src/atomate2/common/flows/qha.py index 6c5aa64bec..74c33209c3 100644 --- a/src/atomate2/common/flows/qha.py +++ b/src/atomate2/common/flows/qha.py @@ -25,28 +25,24 @@ from atomate2.common.flows.phonons import BasePhononMaker from atomate2.forcefields.jobs import ForceFieldRelaxMaker from atomate2.vasp.jobs.core import BaseVaspMaker + supported_eos = frozenset(("vinet", "birch_murnaghan", "murnaghan")) @dataclass class CommonQhaMaker(Maker, ABC): - """ - Use the quasi-harmonic approximation. + """Use the quasi-harmonic approximation. - First relax a structure. - Then we scale the relaxed structure, and - then compute harmonic phonons for each scaled - structure with Phonopy. - Finally, we compute the Gibbs free energy and - other thermodynamic properties available from - the quasi-harmonic approximation. + First relax a structure. Then we scale the relaxed structure, and then compute + harmonic phonons for each scaled structure with Phonopy. Finally, we compute the + Gibbs free energy and other thermodynamic properties available from the + quasi-harmonic approximation. Note: We do not consider electronic free energies so far. This might be problematic for metals (see e.g., Wolverton and Zunger, Phys. Rev. B, 52, 8813 (1994).) - Note: Magnetic Materials have never been computed with - this workflow. + Note: Magnetic Materials have never been computed with this workflow. Parameters ---------- @@ -84,7 +80,6 @@ class CommonQhaMaker(Maker, ABC): with 3 90 degree angles get_supercell_size_kwargs: dict kwargs that will be passed to get_supercell_size to determine supercell size - """ name: str = "QHA Maker" @@ -128,10 +123,7 @@ def make( .Flow, a QHA flow """ if self.eos_type not in supported_eos: - raise ValueError( - "EOS not supported.", - "Please choose 'vinet', 'birch_murnaghan', 'murnaghan'", - ) + raise ValueError(f"EOS not supported. Choose one of {set(supported_eos)}") qha_jobs = [] @@ -148,7 +140,7 @@ def make( eos_job = self.eos.make(structure) qha_jobs.append(eos_job) - # implement a supercell job to get matrix for just the equillibrium structure + # implement a supercell job to get matrix for just the equilibrium structure if supercell_matrix is None: supercell = get_supercell_size( eos_output=eos_job.output, @@ -162,7 +154,6 @@ def make( supercell_matrix = supercell.output # pass the matrix to the phonon_jobs, allow to set a consistent matrix instead - phonon_jobs = get_phonon_jobs( phonon_maker=self.phonon_maker, eos_output=eos_job.output, diff --git a/src/atomate2/common/jobs/anharmonicity.py b/src/atomate2/common/jobs/anharmonicity.py index e44aa7f863..5ebace0e9e 100644 --- a/src/atomate2/common/jobs/anharmonicity.py +++ b/src/atomate2/common/jobs/anharmonicity.py @@ -44,15 +44,16 @@ class ImaginaryModeError(Exception): def __init__(self, largest_mode: float) -> None: self.largest_mode = largest_mode - self.message = f"""Structure has imaginary modes: the largest optical - eigenmode {largest_mode} < 0.0001""" + self.message = ( + f"Structure has imaginary modes: the largest optical eigenmode " + f"{largest_mode} < 0.0001" + ) super().__init__(self.message) @job def get_phonon_supercell( - structure: Structure, - supercell_matrix: np.ndarray, + structure: Structure, supercell_matrix: np.ndarray ) -> Structure: """Get the phonon supercell of a structure. @@ -68,12 +69,9 @@ def get_phonon_supercell( Structure The phonopy structure """ - cell = get_phonopy_structure(structure) - phonon = Phonopy( - cell, - supercell_matrix, - ) - return get_pmg_structure(phonon.supercell) + unit_cell = get_phonopy_structure(structure) + phonopy = Phonopy(unit_cell, supercell_matrix) + return get_pmg_structure(phonopy.supercell) def get_sigma_per_site( diff --git a/src/atomate2/common/jobs/gruneisen.py b/src/atomate2/common/jobs/gruneisen.py index 8e1fa14c2e..d78fceb636 100644 --- a/src/atomate2/common/jobs/gruneisen.py +++ b/src/atomate2/common/jobs/gruneisen.py @@ -17,6 +17,8 @@ from atomate2.common.schemas.phonons import PhononBSDOSDoc if TYPE_CHECKING: + from pathlib import Path + from pymatgen.core.structure import Structure from atomate2.common.flows.phonons import BasePhononMaker @@ -128,11 +130,11 @@ def run_phonon_jobs( ) def compute_gruneisen_param( code: str, - phonopy_yaml_paths_dict: dict, - phonon_imaginary_modes_info: dict, + phonopy_yaml_paths_dict: dict[str, Path], + phonon_imaginary_modes_info: dict[str, bool], kpath_scheme: str, symprec: float, - mesh: tuple | float = (20, 20, 20), + mesh: tuple[int, int, int] | float = (20, 20, 20), structure: Structure = None, **compute_gruneisen_param_kwargs, ) -> GruneisenParameterDocument: diff --git a/src/atomate2/common/jobs/qha.py b/src/atomate2/common/jobs/qha.py index 3610867729..51f657cd7c 100644 --- a/src/atomate2/common/jobs/qha.py +++ b/src/atomate2/common/jobs/qha.py @@ -56,9 +56,7 @@ def get_supercell_size( ) -@job( - data=[PhononBSDOSDoc], -) +@job(data=[PhononBSDOSDoc]) def get_phonon_jobs( phonon_maker: BasePhononMaker, eos_output: dict, supercell_matrix: list[list[float]] ) -> Flow: diff --git a/src/atomate2/common/schemas/phonons.py b/src/atomate2/common/schemas/phonons.py index 73b53c9e26..99e95c4c10 100644 --- a/src/atomate2/common/schemas/phonons.py +++ b/src/atomate2/common/schemas/phonons.py @@ -201,7 +201,7 @@ class PhononBSDOSDoc(StructureMetadata, extra="allow"): # type: ignore[call-arg born: Optional[list[Matrix3D]] = Field( None, - description="born charges as computed from phonopy. Only for symmetrically " + description="Born charges as computed from phonopy. Only for symmetrically " "different atoms", ) @@ -280,7 +280,7 @@ def from_forces_born( epsilon_static: Matrix3D The high-frequency dielectric constant born: Matrix3D - born charges + Born charges **kwargs: additional arguments """ @@ -329,7 +329,7 @@ def from_forces_born( ) else: raise ValueError( - "Number of born charges does not agree with number of atoms" + "Number of Born charges does not agree with number of atoms" ) if code == "vasp" and not np.all(np.isclose(borns, 0.0)): phonon.nac_params = { @@ -595,7 +595,8 @@ def get_kpath( **kpath_kwargs: additional parameters that can be passed to this method as a dict """ - if kpath_scheme in ("setyawan_curtarolo", "latimer_munro", "hinuma"): + valid_schemes = {"setyawan_curtarolo", "latimer_munro", "hinuma", "seekpath"} + if kpath_scheme in (valid_schemes - {"seekpath"}): high_symm_kpath = HighSymmKpath( structure, path_type=kpath_scheme, symprec=symprec, **kpath_kwargs ) @@ -604,7 +605,9 @@ def get_kpath( high_symm_kpath = KPathSeek(structure, symprec=symprec, **kpath_kwargs) kpath = high_symm_kpath._kpath # noqa: SLF001 else: - raise ValueError(f"Unexpected {kpath_scheme=}") + raise ValueError( + f"Unexpected {kpath_scheme=}, must be one of {valid_schemes}" + ) path = copy.deepcopy(kpath["path"]) diff --git a/tests/vasp/flows/test_phonons.py b/tests/vasp/flows/test_phonons.py index ddb6ac14d4..4348dbdbad 100644 --- a/tests/vasp/flows/test_phonons.py +++ b/tests/vasp/flows/test_phonons.py @@ -265,66 +265,41 @@ def test_phonon_wf_vasp_only_displacements_kpath( responses = run_locally(job, create_folders=True, ensure_success=True) # validate the outputs - # print(type(responses)) - assert isinstance(responses[job.jobs[-1].uuid][1].output, PhononBSDOSDoc) + ph_doc = responses[job.jobs[-1].uuid][1].output + assert isinstance(ph_doc, PhononBSDOSDoc) assert_allclose( - responses[job.jobs[-1].uuid][1].output.free_energies, + ph_doc.free_energies, [5776.14995034, 5617.74737777, 4725.50269363, 3043.81827626, 694.49078355], atol=1e-3, ) + assert isinstance(ph_doc.phonon_bandstructure, PhononBandStructureSymmLine) + assert isinstance(ph_doc.phonon_dos, PhononDos) + assert isinstance(ph_doc.thermal_displacement_data, ThermalDisplacementData) + assert isinstance(ph_doc.structure, Structure) + assert_allclose(ph_doc.temperatures, [0, 100, 200, 300, 400]) + assert ph_doc.has_imaginary_modes is False + force_const = ph_doc.force_constants.force_constants[0][0][0][0] + assert force_const == pytest.approx(13.032324) + assert isinstance(ph_doc.jobdirs, PhononJobDirs) + assert isinstance(ph_doc.uuids, PhononUUIDs) + assert ph_doc.total_dft_energy is None + assert ph_doc.born is None + assert ph_doc.epsilon_static is None + assert ph_doc.supercell_matrix == ((-1, 1, 1), (1, -1, 1), (1, 1, -1)) + assert ph_doc.primitive_matrix == ((1, 0, 0), (0, 1, 0), (0, 0, 1)) + assert ph_doc.code == "vasp" assert isinstance( - responses[job.jobs[-1].uuid][1].output.phonon_bandstructure, - PhononBandStructureSymmLine, - ) - assert isinstance(responses[job.jobs[-1].uuid][1].output.phonon_dos, PhononDos) - assert isinstance( - responses[job.jobs[-1].uuid][1].output.thermal_displacement_data, - ThermalDisplacementData, - ) - assert isinstance(responses[job.jobs[-1].uuid][1].output.structure, Structure) - assert_allclose( - responses[job.jobs[-1].uuid][1].output.temperatures, [0, 100, 200, 300, 400] - ) - assert responses[job.jobs[-1].uuid][1].output.has_imaginary_modes is False - assert_allclose( - responses[job.jobs[-1].uuid][1].output.force_constants.force_constants[0][0][0][ - 0 - ], - 13.032324, - ) - assert isinstance(responses[job.jobs[-1].uuid][1].output.jobdirs, PhononJobDirs) - assert isinstance(responses[job.jobs[-1].uuid][1].output.uuids, PhononUUIDs) - assert responses[job.jobs[-1].uuid][1].output.total_dft_energy is None - assert responses[job.jobs[-1].uuid][1].output.born is None - assert responses[job.jobs[-1].uuid][1].output.epsilon_static is None - assert_allclose( - responses[job.jobs[-1].uuid][1].output.supercell_matrix, - [[-1.0, 1.0, 1.0], [1.0, -1.0, 1.0], [1.0, 1.0, -1.0]], - ) - assert_allclose( - responses[job.jobs[-1].uuid][1].output.primitive_matrix, - [[1, 0, 0], [0, 1, 0], [0, 0, 1]], - atol=1e-8, - ) - assert responses[job.jobs[-1].uuid][1].output.code == "vasp" - assert isinstance( - responses[job.jobs[-1].uuid][1].output.phonopy_settings, + ph_doc.phonopy_settings, PhononComputationalSettings, ) - assert responses[job.jobs[-1].uuid][1].output.phonopy_settings.npoints_band == 101 - assert ( - responses[job.jobs[-1].uuid][1].output.phonopy_settings.kpath_scheme - == kpath_scheme - ) - assert ( - responses[job.jobs[-1].uuid][1].output.phonopy_settings.kpoint_density_dos - == 7_000 - ) + assert ph_doc.phonopy_settings.npoints_band == 101 + assert ph_doc.phonopy_settings.kpath_scheme == kpath_scheme + assert ph_doc.phonopy_settings.kpoint_density_dos == 7_000 -# test supply of born charges, epsilon, DFT energy, supercell +# test supply of Born charges, epsilon, DFT energy, supercell def test_phonon_wf_vasp_only_displacements_add_inputs_raises( mock_vasp, clean_dir, si_structure: Structure ): @@ -337,16 +312,9 @@ def test_phonon_wf_vasp_only_displacements_add_inputs_raises( # automatically use fake VASP and write POTCAR.spec during the test mock_vasp(ref_paths, fake_run_vasp_kwargs) - born = [ - [[0, 0, 0], [0, 0, 0], [0, 0, 0]], - [[0, 0, 0], [0, 0, 0], [0, 0, 0]], - [[0, 0, 0], [0, 0, 0], [0, 0, 0.1]], - ] - epsilon_static = [ - [5.25, 0, 0], - [0, 5.25, 0], - [0, 0, 5.25], - ] + born = np.zeros((3, 3)) + born[-1, -1] = 0.1 + epsilon_static = 5.25 * np.eye(3) total_dft_energy_per_formula_unit = -5 job = PhononMaker( @@ -368,7 +336,7 @@ def test_phonon_wf_vasp_only_displacements_add_inputs_raises( run_locally(job, create_folders=True, ensure_success=True) -# test supply of born charges, epsilon, DFT energy, supercell +# test supply of Born charges, epsilon, DFT energy, supercell def test_phonon_wf_vasp_only_displacements_add_inputs( mock_vasp, clean_dir, si_structure: Structure ):