diff --git a/pyiron_atomistics/atomistics/job/atomistic.py b/pyiron_atomistics/atomistics/job/atomistic.py index e1d80e085..0eb2e799a 100644 --- a/pyiron_atomistics/atomistics/job/atomistic.py +++ b/pyiron_atomistics/atomistics/job/atomistic.py @@ -259,15 +259,15 @@ def calc_md( self._generic_input["temperature"] = temperature self._generic_input["n_ionic_steps"] = n_ionic_steps self._generic_input["n_print"] = n_print - self._generic_input[ - "temperature_damping_timescale" - ] = temperature_damping_timescale + self._generic_input["temperature_damping_timescale"] = ( + temperature_damping_timescale + ) if pressure is not None: self._generic_input["pressure"] = pressure if pressure_damping_timescale is not None: - self._generic_input[ - "pressure_damping_timescale" - ] = pressure_damping_timescale + self._generic_input["pressure_damping_timescale"] = ( + pressure_damping_timescale + ) if time_step is not None: self._generic_input["time_step"] = time_step self._generic_input.remove_keys(["max_iter"]) diff --git a/pyiron_atomistics/atomistics/master/phonopy.py b/pyiron_atomistics/atomistics/master/phonopy.py index 3724dc50d..e164f076a 100644 --- a/pyiron_atomistics/atomistics/master/phonopy.py +++ b/pyiron_atomistics/atomistics/master/phonopy.py @@ -277,9 +277,9 @@ def collect_output(self): hdf5_out["qpoints"] = mesh_dict["qpoints"] hdf5_out["supercell_matrix"] = self._phonopy_supercell_matrix() hdf5_out["displacement_dataset"] = self.phonopy.get_displacement_dataset() - hdf5_out[ - "dynamical_matrix" - ] = self.phonopy.dynamical_matrix.get_dynamical_matrix() + hdf5_out["dynamical_matrix"] = ( + self.phonopy.dynamical_matrix.get_dynamical_matrix() + ) hdf5_out["force_constants"] = self.phonopy.force_constants def write_phonopy_force_constants(self, file_name="FORCE_CONSTANTS", cwd=None): diff --git a/pyiron_atomistics/atomistics/structure/atoms.py b/pyiron_atomistics/atomistics/structure/atoms.py index 9b24b481a..8e46a93b1 100644 --- a/pyiron_atomistics/atomistics/structure/atoms.py +++ b/pyiron_atomistics/atomistics/structure/atoms.py @@ -2446,15 +2446,17 @@ def get_initial_magnetic_moments(self): ): return np.array( [ - [ - float(spin_dir) - for spin_dir in spin.replace("[", "") - .replace("]", "") - .replace(",", "") - .split() - ] - if spin - else [0.0, 0.0, 0.0] + ( + [ + float(spin_dir) + for spin_dir in spin.replace("[", "") + .replace("]", "") + .replace(",", "") + .split() + ] + if spin + else [0.0, 0.0, 0.0] + ) for spin in spin_lst ] ) @@ -3133,9 +3135,9 @@ def ase_to_pyiron(ase_obj): elif constraint_dict["name"] == "FixScaled": if "selective_dynamics" not in pyiron_atoms.arrays.keys(): pyiron_atoms.add_tag(selective_dynamics=[True, True, True]) - pyiron_atoms.selective_dynamics[ - constraint_dict["kwargs"]["a"] - ] = constraint_dict["kwargs"]["mask"] + pyiron_atoms.selective_dynamics[constraint_dict["kwargs"]["a"]] = ( + constraint_dict["kwargs"]["mask"] + ) else: warnings.warn("Unsupported ASE constraint: " + constraint_dict["name"]) return pyiron_atoms @@ -3404,7 +3406,6 @@ def default(data, dflt): class Symbols(ASESymbols): - """ Derived from the ase symbols class which has the following docs: diff --git a/pyiron_atomistics/dft/bader.py b/pyiron_atomistics/dft/bader.py index 0b125648c..c7260f8fd 100644 --- a/pyiron_atomistics/dft/bader.py +++ b/pyiron_atomistics/dft/bader.py @@ -19,7 +19,6 @@ class Bader: - """ Module to apply the Bader charge partitioning scheme to finished DFT jobs. This module is interfaced with the `Bader code`_ from the Greame Henkelmann group. diff --git a/pyiron_atomistics/dft/waves/dos.py b/pyiron_atomistics/dft/waves/dos.py index f4022991f..69adb87c2 100644 --- a/pyiron_atomistics/dft/waves/dos.py +++ b/pyiron_atomistics/dft/waves/dos.py @@ -18,7 +18,6 @@ class Dos(object): - """ The DOS class stores all information to store and retrieve the total and resolved density of states from an electronic structure calculation. diff --git a/pyiron_atomistics/dft/waves/electronic.py b/pyiron_atomistics/dft/waves/electronic.py index 2f9ac59ba..fb3e94af3 100644 --- a/pyiron_atomistics/dft/waves/electronic.py +++ b/pyiron_atomistics/dft/waves/electronic.py @@ -462,9 +462,9 @@ def grand_dos_matrix(self): for spin in range(self.n_spins): for i, kpt in enumerate(self.kpoints): for j, band in enumerate(kpt.bands): - self._grand_dos_matrix[ - spin, i, j, :, : - ] = band.resolved_dos_matrix + self._grand_dos_matrix[spin, i, j, :, :] = ( + band.resolved_dos_matrix + ) return self._grand_dos_matrix @grand_dos_matrix.setter @@ -624,9 +624,9 @@ def generate_from_matrices(self): occ = self.occupancy_matrix[spin][i][j] self.kpoints[-1].add_band(eigenvalue=val, occupancy=occ, spin=spin) if self._grand_dos_matrix is not None: - self.kpoints[-1].bands[spin][ - -1 - ].resolved_dos_matrix = self.grand_dos_matrix[spin, i, j, :, :] + self.kpoints[-1].bands[spin][-1].resolved_dos_matrix = ( + self.grand_dos_matrix[spin, i, j, :, :] + ) def get_spin_resolved_dos(self, spin_indices=0): """ @@ -751,7 +751,6 @@ def __repr__(self): class Kpoint(object): - """ All data related to a single k-point is stored in this module diff --git a/pyiron_atomistics/interactive/quasi_newton.py b/pyiron_atomistics/interactive/quasi_newton.py index 1d456ffb5..b07392dab 100644 --- a/pyiron_atomistics/interactive/quasi_newton.py +++ b/pyiron_atomistics/interactive/quasi_newton.py @@ -91,8 +91,7 @@ def inv_hessian(self): return np.einsum( "ik,k,jk->ij", self.eigenvectors, - self.eigenvalues - / (self.eigenvalues**2 + np.exp(self.regularization)), + self.eigenvalues / (self.eigenvalues**2 + np.exp(self.regularization)), self.eigenvectors, ) else: @@ -158,8 +157,7 @@ def _get_PSB(dx, dg, H_tmp): dH = (dH + dH.T) / dxdx return ( dH - - np.einsum("i,i,j,k->jk", dx, H_tmp, dx, dx, optimize="optimal") - / dxdx**2 + - np.einsum("i,i,j,k->jk", dx, H_tmp, dx, dx, optimize="optimal") / dxdx**2 ) @staticmethod @@ -255,7 +253,6 @@ def run_qn( class QuasiNewton(InteractiveWrapper): - """ Structure optimization scheme via Quasi-Newton algorithm. diff --git a/pyiron_atomistics/lammps/base.py b/pyiron_atomistics/lammps/base.py index 9b3738eaa..982f2ad1b 100644 --- a/pyiron_atomistics/lammps/base.py +++ b/pyiron_atomistics/lammps/base.py @@ -366,9 +366,9 @@ def enable_h5md(self): """ del self.input.control["dump_modify___1"] del self.input.control["dump___1"] - self.input.control[ - "dump___1" - ] = "all h5md ${dumptime} dump.h5 position force create_group yes" + self.input.control["dump___1"] = ( + "all h5md ${dumptime} dump.h5 position force create_group yes" + ) def write_input(self): """ @@ -902,79 +902,79 @@ def _set_selective_dynamics(self): self.input.control["group___constraintxyz"] = "id " + " ".join( [str(ind) for ind in constraint_xyz] ) - self.input.control[ - "fix___constraintxyz" - ] = "constraintxyz setforce 0.0 0.0 0.0" + self.input.control["fix___constraintxyz"] = ( + "constraintxyz setforce 0.0 0.0 0.0" + ) if self._generic_input["calc_mode"] == "md": - self.input.control[ - "velocity___constraintxyz" - ] = "set 0.0 0.0 0.0" + self.input.control["velocity___constraintxyz"] = ( + "set 0.0 0.0 0.0" + ) if len(constraint_xy) > 0: self.input.control["group___constraintxy"] = "id " + " ".join( [str(ind) for ind in constraint_xy] ) - self.input.control[ - "fix___constraintxy" - ] = "constraintxy setforce 0.0 0.0 NULL" + self.input.control["fix___constraintxy"] = ( + "constraintxy setforce 0.0 0.0 NULL" + ) if self._generic_input["calc_mode"] == "md": - self.input.control[ - "velocity___constraintxy" - ] = "set 0.0 0.0 NULL" + self.input.control["velocity___constraintxy"] = ( + "set 0.0 0.0 NULL" + ) if len(constraint_yz) > 0: self.input.control["group___constraintyz"] = "id " + " ".join( [str(ind) for ind in constraint_yz] ) - self.input.control[ - "fix___constraintyz" - ] = "constraintyz setforce NULL 0.0 0.0" + self.input.control["fix___constraintyz"] = ( + "constraintyz setforce NULL 0.0 0.0" + ) if self._generic_input["calc_mode"] == "md": - self.input.control[ - "velocity___constraintyz" - ] = "set NULL 0.0 0.0" + self.input.control["velocity___constraintyz"] = ( + "set NULL 0.0 0.0" + ) if len(constraint_zx) > 0: self.input.control["group___constraintxz"] = "id " + " ".join( [str(ind) for ind in constraint_zx] ) - self.input.control[ - "fix___constraintxz" - ] = "constraintxz setforce 0.0 NULL 0.0" + self.input.control["fix___constraintxz"] = ( + "constraintxz setforce 0.0 NULL 0.0" + ) if self._generic_input["calc_mode"] == "md": - self.input.control[ - "velocity___constraintxz" - ] = "set 0.0 NULL 0.0" + self.input.control["velocity___constraintxz"] = ( + "set 0.0 NULL 0.0" + ) if len(constraint_x) > 0: self.input.control["group___constraintx"] = "id " + " ".join( [str(ind) for ind in constraint_x] ) - self.input.control[ - "fix___constraintx" - ] = "constraintx setforce 0.0 NULL NULL" + self.input.control["fix___constraintx"] = ( + "constraintx setforce 0.0 NULL NULL" + ) if self._generic_input["calc_mode"] == "md": - self.input.control[ - "velocity___constraintx" - ] = "set 0.0 NULL NULL" + self.input.control["velocity___constraintx"] = ( + "set 0.0 NULL NULL" + ) if len(constraint_y) > 0: self.input.control["group___constrainty"] = "id " + " ".join( [str(ind) for ind in constraint_y] ) - self.input.control[ - "fix___constrainty" - ] = "constrainty setforce NULL 0.0 NULL" + self.input.control["fix___constrainty"] = ( + "constrainty setforce NULL 0.0 NULL" + ) if self._generic_input["calc_mode"] == "md": - self.input.control[ - "velocity___constrainty" - ] = "set NULL 0.0 NULL" + self.input.control["velocity___constrainty"] = ( + "set NULL 0.0 NULL" + ) if len(constraint_z) > 0: self.input.control["group___constraintz"] = "id " + " ".join( [str(ind) for ind in constraint_z] ) - self.input.control[ - "fix___constraintz" - ] = "constraintz setforce NULL NULL 0.0" + self.input.control["fix___constraintz"] = ( + "constraintz setforce NULL NULL 0.0" + ) if self._generic_input["calc_mode"] == "md": - self.input.control[ - "velocity___constraintz" - ] = "set NULL NULL 0.0" + self.input.control["velocity___constraintz"] = ( + "set NULL NULL 0.0" + ) @staticmethod def _modify_structure_to_allow_requested_deformation( diff --git a/pyiron_atomistics/lammps/control.py b/pyiron_atomistics/lammps/control.py index b6f750a0e..5462ac53a 100644 --- a/pyiron_atomistics/lammps/control.py +++ b/pyiron_atomistics/lammps/control.py @@ -816,9 +816,9 @@ def _fix_with_three_vector(self, ids, vector, fix_string, conversion): vector[i] = str(v * conversion) name = str(hash(tuple(ids))).replace("-", "m") # A unique name for the group self._set_group_by_id(name, ids) - self[ - "fix___{}_{}".format(fix_string.replace(" ", "_"), name) - ] = "{} {} {}".format(name, fix_string, " ".join(vector)) + self["fix___{}_{}".format(fix_string.replace(" ", "_"), name)] = ( + "{} {} {}".format(name, fix_string, " ".join(vector)) + ) def fix_move_linear_by_id(self, ids, velocity): """ diff --git a/pyiron_atomistics/lammps/interactive.py b/pyiron_atomistics/lammps/interactive.py index abeffc7b8..99dde397a 100644 --- a/pyiron_atomistics/lammps/interactive.py +++ b/pyiron_atomistics/lammps/interactive.py @@ -274,9 +274,9 @@ def function(ptr, timestep, nlocal, ids, x, fexternal): if not self.server.run_mode.interactive: raise AssertionError("Callback works only in interactive mode") self._user_fix_external = _FixExternal(function) - self.input.control[ - "fix___fix_external" - ] = "all external pf/callback {} {}".format(n_call, n_apply) + self.input.control["fix___fix_external"] = ( + "all external pf/callback {} {}".format(n_call, n_apply) + ) if overload_internal_fix_external: self._user_fix_external.fix_external = function diff --git a/pyiron_atomistics/lammps/potential.py b/pyiron_atomistics/lammps/potential.py index 01b55856b..b33beb04e 100644 --- a/pyiron_atomistics/lammps/potential.py +++ b/pyiron_atomistics/lammps/potential.py @@ -26,7 +26,6 @@ class LammpsPotential(GenericParameters): - """ This module helps write commands which help in the control of parameters related to the potential used in LAMMPS simulations diff --git a/pyiron_atomistics/sphinx/base.py b/pyiron_atomistics/sphinx/base.py index 28a9c55aa..af3b50776 100644 --- a/pyiron_atomistics/sphinx/base.py +++ b/pyiron_atomistics/sphinx/base.py @@ -2017,9 +2017,11 @@ def write_spin_constraints(self, file_name="spins.in", cwd=None, spins_list=None if self.structure.has("initial_magmoms"): if any( [ - True - if isinstance(spin, list) or isinstance(spin, np.ndarray) - else False + ( + True + if isinstance(spin, list) or isinstance(spin, np.ndarray) + else False + ) for spin in self.structure.get_initial_magnetic_moments() ] ): diff --git a/pyiron_atomistics/testing/randomatomistic.py b/pyiron_atomistics/testing/randomatomistic.py index fead998b1..fd213bc7a 100644 --- a/pyiron_atomistics/testing/randomatomistic.py +++ b/pyiron_atomistics/testing/randomatomistic.py @@ -467,9 +467,7 @@ def _s_r(self): return self.input["sigma"] / self.neigh.flattened.distances def interactive_energy_pot_getter(self): - return self.input["epsilon"] * ( - np.sum(self._s_r**12) - np.sum(self._s_r**6) - ) + return self.input["epsilon"] * (np.sum(self._s_r**12) - np.sum(self._s_r**6)) def interactive_energy_tot_getter(self): return ( @@ -479,13 +477,7 @@ def interactive_energy_tot_getter(self): def interadtive_energy_kin_getter(self): v = np.einsum("ni,n->", self._velocity**2, self.structure.get_masses()) / 2 return ( - ( - v - * self._unit.angstrom**2 - / self._unit.second**2 - / 1e-30 - * self._unit.amu - ) + (v * self._unit.angstrom**2 / self._unit.second**2 / 1e-30 * self._unit.amu) .to("eV") .magnitude ) @@ -529,9 +521,7 @@ def interactive_pressures_getter(self): * self._unit.amu ) return ( - (pot_part + kin_part) - / self.structure.get_volume() - / self._unit.angstrom**3 + (pot_part + kin_part) / self.structure.get_volume() / self._unit.angstrom**3 ).to(self._unit.pascal).magnitude / 1e9 def interactive_stress_getter(self): diff --git a/pyiron_atomistics/vasp/base.py b/pyiron_atomistics/vasp/base.py index f4fb61e27..f7494d5ea 100644 --- a/pyiron_atomistics/vasp/base.py +++ b/pyiron_atomistics/vasp/base.py @@ -458,9 +458,9 @@ def collect_output_parser(self, cwd): self._output_parser.generic_output.dft_log_dict["bader_charges"] = ( valence_charges - charges ) - self._output_parser.generic_output.dft_log_dict[ - "bader_volumes" - ] = volumes + self._output_parser.generic_output.dft_log_dict["bader_volumes"] = ( + volumes + ) return self._output_parser.to_dict() # define routines that collect all output files @@ -839,9 +839,9 @@ def get_final_structure_from_file(self, cwd, filename="CONTCAR"): species_list=input_structure.get_parent_symbols(), ) input_structure.cell = output_structure.cell.copy() - input_structure.positions[ - self.sorted_indices - ] = output_structure.positions + input_structure.positions[self.sorted_indices] = ( + output_structure.positions + ) except (IndexError, ValueError, IOError): raise IOError("Unable to read output structure") return input_structure @@ -856,9 +856,11 @@ def write_magmoms(self): if self.input.incar["ISPIN"] != 1: final_cmd = " ".join( [ - " ".join([str(spinmom) for spinmom in spin]) - if isinstance(spin, (list, np.ndarray)) - else str(spin) + ( + " ".join([str(spinmom) for spinmom in spin]) + if isinstance(spin, (list, np.ndarray)) + else str(spin) + ) for spin in self.structure.get_initial_magnetic_moments()[ self.sorted_indices ] @@ -2053,12 +2055,12 @@ def collect(self, directory=os.getcwd(), sorted_indices=None): final_magmoms[:, sorted_indices, :] = final_magmoms.copy() else: final_magmoms[:, sorted_indices] = final_magmoms.copy() - self.generic_output.dft_log_dict[ - "magnetization" - ] = magnetization.tolist() - self.generic_output.dft_log_dict[ - "final_magmoms" - ] = final_magmoms.tolist() + self.generic_output.dft_log_dict["magnetization"] = ( + magnetization.tolist() + ) + self.generic_output.dft_log_dict["final_magmoms"] = ( + final_magmoms.tolist() + ) self.generic_output.dft_log_dict["e_fermi_list"] = self.outcar.parse_dict[ "e_fermi_list" ] @@ -2120,9 +2122,9 @@ def collect(self, directory=os.getcwd(), sorted_indices=None): ] = self.electronic_structure.resolved_densities[:, :, :, :].copy() self.structure.positions = log_dict["positions"][-1] self.structure.set_cell(log_dict["cells"][-1]) - self.generic_output.dft_log_dict[ - "potentiostat_output" - ] = self.vp_new.get_potentiostat_output() + self.generic_output.dft_log_dict["potentiostat_output"] = ( + self.vp_new.get_potentiostat_output() + ) valence_charges_orig = self.vp_new.get_valence_electrons_per_atom() valence_charges = valence_charges_orig.copy() valence_charges[sorted_indices] = valence_charges_orig @@ -2154,9 +2156,9 @@ def collect(self, directory=os.getcwd(), sorted_indices=None): log_dict["volume"] = np.array( [np.linalg.det(cell) for cell in self.outcar.parse_dict["cells"]] ) - self.generic_output.dft_log_dict[ - "scf_energy_free" - ] = self.outcar.parse_dict["scf_energies"] + self.generic_output.dft_log_dict["scf_energy_free"] = ( + self.outcar.parse_dict["scf_energies"] + ) self.generic_output.dft_log_dict["scf_dipole_mom"] = self.outcar.parse_dict[ "scf_dipole_moments" ] @@ -2198,9 +2200,9 @@ def collect(self, directory=os.getcwd(), sorted_indices=None): self.generic_output.log_dict = log_dict if vasprun_working: # self.dft_output.log_dict["parameters"] = self.vp_new.vasprun_dict["parameters"] - self.generic_output.dft_log_dict[ - "scf_dipole_mom" - ] = self.vp_new.vasprun_dict["scf_dipole_moments"] + self.generic_output.dft_log_dict["scf_dipole_mom"] = ( + self.vp_new.vasprun_dict["scf_dipole_moments"] + ) if len(self.generic_output.dft_log_dict["scf_dipole_mom"][0]) > 0: total_dipole_moments = np.array( [ @@ -2209,15 +2211,15 @@ def collect(self, directory=os.getcwd(), sorted_indices=None): ] ) self.generic_output.dft_log_dict["dipole_mom"] = total_dipole_moments - self.generic_output.dft_log_dict[ - "scf_energy_int" - ] = self.vp_new.vasprun_dict["scf_energies"] - self.generic_output.dft_log_dict[ - "scf_energy_free" - ] = self.vp_new.vasprun_dict["scf_fr_energies"] - self.generic_output.dft_log_dict[ - "scf_energy_zero" - ] = self.vp_new.vasprun_dict["scf_0_energies"] + self.generic_output.dft_log_dict["scf_energy_int"] = ( + self.vp_new.vasprun_dict["scf_energies"] + ) + self.generic_output.dft_log_dict["scf_energy_free"] = ( + self.vp_new.vasprun_dict["scf_fr_energies"] + ) + self.generic_output.dft_log_dict["scf_energy_zero"] = ( + self.vp_new.vasprun_dict["scf_0_energies"] + ) self.generic_output.dft_log_dict["energy_int"] = np.array( [ e_int[-1] @@ -2236,9 +2238,9 @@ def collect(self, directory=os.getcwd(), sorted_indices=None): self.generic_output.dft_log_dict["energy_free"], np.round(self.oszicar.parse_dict["energy_pot"], 8), ): - self.generic_output.dft_log_dict[ - "energy_free" - ] = self.oszicar.parse_dict["energy_pot"] + self.generic_output.dft_log_dict["energy_free"] = ( + self.oszicar.parse_dict["energy_pot"] + ) self.generic_output.dft_log_dict["energy_zero"] = np.array( [ e_zero[-1] @@ -2250,12 +2252,12 @@ def collect(self, directory=os.getcwd(), sorted_indices=None): ) if "kinetic_energies" in self.vp_new.vasprun_dict.keys(): # scf_energy_kin is for backwards compatibility - self.generic_output.dft_log_dict[ - "scf_energy_kin" - ] = self.vp_new.vasprun_dict["kinetic_energies"] - self.generic_output.dft_log_dict[ - "energy_kin" - ] = self.vp_new.vasprun_dict["kinetic_energies"] + self.generic_output.dft_log_dict["scf_energy_kin"] = ( + self.vp_new.vasprun_dict["kinetic_energies"] + ) + self.generic_output.dft_log_dict["energy_kin"] = ( + self.vp_new.vasprun_dict["kinetic_energies"] + ) if ( "LOCPOT" in files_present @@ -2283,9 +2285,9 @@ def to_dict(self): hdf5_output["structure"] = self.structure.to_dict() if self.electrostatic_potential.total_data is not None: - hdf5_output[ - "electrostatic_potential" - ] = self.electrostatic_potential.to_dict() + hdf5_output["electrostatic_potential"] = ( + self.electrostatic_potential.to_dict() + ) if self.charge_density.total_data is not None: hdf5_output["charge_density"] = self.charge_density.to_dict() diff --git a/pyiron_atomistics/vasp/parser/outcar.py b/pyiron_atomistics/vasp/parser/outcar.py index cd4619428..ee3760c7c 100644 --- a/pyiron_atomistics/vasp/parser/outcar.py +++ b/pyiron_atomistics/vasp/parser/outcar.py @@ -1150,12 +1150,14 @@ def get_energy_components(filename="OUTCAR", lines=None): [ np.hstack( [ - float(lines[ind + i].split()[-1]) - if i != 7 - else [ - float(lines[ind_lst[-1] + 7].split()[-2]), - float(lines[ind_lst[-1] + 7].split()[-1]), - ] + ( + float(lines[ind + i].split()[-1]) + if i != 7 + else [ + float(lines[ind_lst[-1] + 7].split()[-2]), + float(lines[ind_lst[-1] + 7].split()[-1]), + ] + ) for i in range(2, 12) ] ) @@ -1205,9 +1207,11 @@ def _split_indices(ind_ionic_lst, ind_elec_lst): """ ind_elec_array = np.array(ind_elec_lst) return [ - ind_elec_array[(ind_elec_array < j2) & (j1 < ind_elec_array)] - if j1 < j2 - else ind_elec_array[(ind_elec_array < j2)] + ( + ind_elec_array[(ind_elec_array < j2) & (j1 < ind_elec_array)] + if j1 < j2 + else ind_elec_array[(ind_elec_array < j2)] + ) for j1, j2 in zip(np.roll(ind_ionic_lst, 1), ind_ionic_lst) ] diff --git a/pyiron_atomistics/vasp/vasprun.py b/pyiron_atomistics/vasp/vasprun.py index a42d8483b..8c03e8d73 100644 --- a/pyiron_atomistics/vasp/vasprun.py +++ b/pyiron_atomistics/vasp/vasprun.py @@ -29,7 +29,6 @@ class Vasprun(object): - """ This module is used to parse vasprun.xml files and store the data consistent with the pyiron input/output storage formats. @@ -57,6 +56,7 @@ def from_file(self, filename="vasprun.xml"): """ if not (os.path.isfile(filename)): raise AssertionError() + self.filename = filename try: self.parse_root_to_dict(filename) except ParseError: @@ -83,9 +83,7 @@ def parse_root_to_dict(self, filename): d["stress_tensors"] = list() for _, leaf in ETree.iterparse(filename): if leaf.tag in ["generator", "incar"]: - d[leaf.tag] = dict() - for items in leaf: - d[leaf.tag] = self.parse_item_to_dict(items, d[leaf.tag]) + d[leaf.tag] = self._parse_INCAR_and_generator(leaf) if leaf.tag in ["kpoints"]: d[leaf.tag] = dict() self.parse_kpoints_to_dict(leaf, d[leaf.tag]) @@ -229,9 +227,9 @@ def parse_atom_information_to_dict(self, node, d): species_dict[special_element]["n_atoms"] = int( elements[0].text ) - species_dict[special_element][ - "valence" - ] = float(elements[3].text) + species_dict[special_element]["valence"] = ( + float(elements[3].text) + ) else: species_key = elements[1].text species_dict[species_key] = dict() @@ -539,17 +537,112 @@ def parse_structure_to_dict(self, node, d): if leaf.tag == "varray" and leaf.attrib["name"] == "selective": d["selective_dynamics"] = self._parse_2d_matrix(leaf, vec_type=bool) + def _parse_INCAR_generator_parameters(self, val_type, val): + """ + Helper function to convert a Vasprun parameter into the proper type. + Boolean, int and float types are converted. + + Args: + val_type: Value type parsed from vasprun.xml. + val: Actual string value parsed for vasprun.xml. + """ + if val_type == "logical": + return val == "T" + if val_type == "int": + return int(val) + if val_type == "string": + return val.strip() + return float(val) + + def _parse_INCAR_generator_vector_parameters( + self, val_type, val, filename, param_name + ): + """ + Helper function to convert a Vasprun array-type parameter into the proper + type. Boolean, int and float types are converted. + + Args: + val_type: Value type parsed from vasprun.xml. + val: Actual string value parsed for vasprun.xml. + filename: Fullpath of vasprun.xml. Used for robust error handling. + E.g., if vasprun.xml contains *** for some Incar parameters, + the code will try to read from an INCAR file present in the same + directory. + param_name: Name of parameter. + + Returns: + Parsed value. + """ + err = ValueError("Error in parsing vasprun.xml") + if val_type == "logical": + val = [i == "T" for i in val.split()] + elif val_type == "int": + try: + val = [int(i) for i in val.split()] + except ValueError: + # Fix for stupid error in vasprun sometimes which displays + # LDAUL/J as 2**** + val = self._parse_from_incar(filename, param_name) + if val is None: + raise err + elif val_type == "string": + val = val.split() + else: + try: + val = [float(i) for i in val.split()] + except ValueError: + # Fix for stupid error in vasprun sometimes which displays + # MAGMOM as 2**** + # val = self._parse_from_incar(filename, param_name) + va + if val is None: + raise err + return val + + def _parse_INCAR_and_generator(self, elem): + params = {} + for c in elem: + name = c.attrib.get("name") + if c.tag not in ("i", "v"): + p = self._parse_INCAR_and_generator(c) + if name == "response functions": + # Delete duplicate fields from "response functions", + # which overrides the values in the root params. + p = {k: v for k, v in p.items() if k not in params} + params.update(p) + else: + ptype = c.attrib.get("type") + val = c.text.strip() if c.text else "" + try: + if c.tag == "i": + params[name] = self._parse_INCAR_generator_parameters( + ptype, val + ) + else: + params[name] = self._parse_INCAR_generator_vector_parameters( + ptype, val, self.filename, name + ) + except Exception as exc: + if name == "RANDOM_SEED": + # Handles the case where RANDOM SEED > 99999, which results in ***** + params[name] = None + else: + raise exc + elem.clear() + return params + @staticmethod def parse_item_to_dict(node, d): """ - Parses values from an item to a dictionary + Parses an XML element with the tag 'i' into a dictionary. The method handles different + data types specified in the 'type' attribute of the element (e.g., string, float, int, logical). Args: - node (etree.Element instance): Node to be parsed - d (dict): The dictionary to which data is to be parsed + node (xml.etree.Element): An XML element with the tag 'i', containing data to be parsed. Returns: - d (dictionary) + dict: A dictionary with a single key-value pair. The key is the 'name' attribute of the element, + and the value is the parsed data, converted to the appropriate Python data type. """ type_dict = {"string": str, "float": float, "int": int, "logical": bool} logical_dict = {"T": True, "F": False} @@ -562,6 +655,27 @@ def parse_item_to_dict(node, d): d[node.attrib["name"]] = node.text return d + def _parse_from_incar(self, param_name, val_type, val): + """ + Placeholder method for reading and parsing parameter values from an INCAR file. This method is intended + to be used for handling special cases where values in the VASP XML file are not correctly formatted. + + Args: + filename (str): The path to the INCAR file. + param_name (str): The name of the parameter to be read. + val_type (str): The expected data type of the parameter ('string', 'float', 'int', 'logical'). + val (str): The original value from the XML file that needs special handling. + + Returns: + The correct value read from the INCAR file, or None if the value cannot be determined. The return + type should match the expected `val_type`. + """ + pass + # Placeholder for special handling logic + # Adapt this method based on specific handling logic for irregular values + # For example, read from an INCAR file or set to a default value + return None + def parse_parameters(self, node, d): """ Parses parameter data from a node to a dictionary diff --git a/tests/vasp/test_vasprun.py b/tests/vasp/test_vasprun.py index 24612b8ef..5fde8de8f 100644 --- a/tests/vasp/test_vasprun.py +++ b/tests/vasp/test_vasprun.py @@ -48,6 +48,89 @@ def test_get_potentiostat_output(self): self.assertIsNone(vp.get_potentiostat_output()) def test_parse_generator(self): + vasprun_data = { + 'vasprun_1.xml': { + 'program': 'vasp', + 'version': '4.6.28', + 'subversion': '25Jul05 complex parallel', + 'platform': 'LinuxIFC', + 'date': '2016 04 10', + 'time': '17:29:20' + }, + 'vasprun_2.xml': { + 'program': 'vasp', + 'version': '5.3.5', + 'subversion': '31Mar14 (build Apr 17 2014 15:55:21) complex parallel', + 'platform': 'LinuxIFC', + 'date': '2016 06 22', + 'time': '20:08:33' + }, + 'vasprun_3.xml': { + 'program': 'vasp', + 'version': '5.3.5', + 'subversion': '31Mar14 (build Dec 16 2016 13:46:59) complex parallel', + 'platform': 'LinuxIFC', + 'date': '2017 04 25', + 'time': '19:09:22' + }, + 'vasprun_4.xml': { + 'program': 'vasp', + 'version': '5.3.5', + 'subversion': '31Mar14 (build Dec 16 2016 13:46:59) complex parallel', + 'platform': 'LinuxIFC', + 'date': '2017 04 25', + 'time': '19:47:01' + }, + 'vasprun_5.xml': { + 'program': 'vasp', + 'version': '5.3.5', + 'subversion': '31Mar14 (build Dec 16 2016 13:46:59) complex parallel', + 'platform': 'LinuxIFC', + 'date': '2017 04 26', + 'time': '12:55:54' + }, + 'vasprun_6.xml': { + 'program': 'vasp', + 'version': '5.4.1', + 'subversion': '24Jun15 (build Dec 18 2016 09:48:16) gamma-only parallel', + 'platform': 'IFC91_ompi', + 'date': '2017 11 21', + 'time': '15:01:21' + }, + 'vasprun_7.xml': { + 'program': 'vasp', + 'version': '5.4.4.18Apr17-6-g9f103f2a35', + 'subversion': '(build Oct 10 2019 11:41:53) complex parallel', + 'platform': 'LinuxIFC', + 'date': '2019 12 02', + 'time': '06:30:52' + }, + 'vasprun_8.xml': { + 'program': 'vasp', + 'version': '5.4.4.18Apr17-6-g9f103f2a35', + 'subversion': '(build Oct 10 2019 11:41:53) complex parallel', + 'platform': 'LinuxIFC', + 'date': '2019 12 02', + 'time': '06:30:52' + }, + 'vasprun_9.xml': { + 'program': 'vasp', + 'version': '5.4.4.18Apr17-6-g9f103f2a35', + 'subversion': '(build Mar 04 2021 11:38:41) gamma-only parallel', + 'platform': 'LinuxIFC', + 'date': '2021 03 04', + 'time': '11:39:31' + }, + 'vasprun_line.xml': { + 'program': 'vasp', + 'version': '5.4.1', + 'subversion': '24Jun15 (build Dec 18 2016 09:36:42) complex parallel', + 'platform': 'IFC91_ompi', + 'date': '2019 03 19', + 'time': '16:54:22' + } + } + for vp in self.vp_list: self.assertIsInstance(vp.vasprun_dict["generator"], dict) self.assertTrue("program" in vp.vasprun_dict["generator"].keys()) @@ -56,10 +139,29 @@ def test_parse_generator(self): self.assertTrue("subversion" in vp.vasprun_dict["generator"].keys()) self.assertTrue("platform" in vp.vasprun_dict["generator"].keys()) self.assertTrue("date" in vp.vasprun_dict["generator"].keys()) + self.assertEqual(vp.vasprun_dict["generator"], vasprun_data[os.path.basename(vp.filename)]) + def test_parse_incar(self): - for vp in self.vp_list: - self.assertIsInstance(vp.vasprun_dict["incar"], dict) + expected_incar_outputs = { + 'vasprun_1.xml': {'SYSTEM': 'passivated_AlN_vacuum_118.0', 'ISTART': 0, 'PREC': 'accurate', 'ALGO': 'Fast', 'ICHARG': 2, 'NELM': 40, 'NELMDL': -5, 'NELMIN': 8, 'EDIFF': 1e-05, 'EDIFFG': 0.001, 'IBRION': 1, 'NSW': 10, 'ISIF': 2, 'ENCUT': 500.0, 'LREAL': False, 'ISMEAR': 2, 'SIGMA': 0.1, 'LWAVE': False, 'LCHARG': False, 'LVTOT': False, 'REF_TYPE': 'none'}, + 'vasprun_2.xml': {'SYSTEM': 'ToDo', 'ISTART': 0, 'PREC': 'accurate', 'ALGO': 'Fast', 'NELM': 300, 'IBRION': -1, 'ENCUT': 400.0, 'LREAL': False, 'SIGMA': 0.1, 'LCHARG': True, 'LVTOT': True, 'LORBIT': False, 'LDIPOL': True, 'IDIPOL': 3, 'DIP%EFIELD': -0.01}, + 'vasprun_3.xml': {'SYSTEM': 'ToDo', 'PREC': 'accurate', 'ALGO': 'Fast', 'NELM': 60, 'IBRION': -1, 'ENCUT': 250.0, 'LREAL': False, 'LWAVE': False, 'LORBIT': False}, + 'vasprun_4.xml': {'SYSTEM': 'ToDo', 'PREC': 'accurate', 'ALGO': 'Fast', 'NELM': 60, 'IBRION': -1, 'ENCUT': 250.0, 'LREAL': False, 'LWAVE': False, 'LORBIT': False}, + 'vasprun_5.xml': {'SYSTEM': 'ToDo', 'PREC': 'accurate', 'ALGO': 'Fast', 'NELM': 60, 'IBRION': -1, 'ENCUT': 250.0, 'LREAL': False, 'LWAVE': False, 'LORBIT': False}, + 'vasprun_6.xml': {'SYSTEM': 'ToDo', 'ISTART': 1, 'PREC': 'accurate', 'ALGO': 'Fast', 'NELM': 200, 'IBRION': -1, 'EDIFF': 1e-06, 'ISYM': 0, 'ENCUT': 500.0, 'LREAL': 'Auto', 'ISMEAR': -1, 'SIGMA': 0.1, 'LWAVE': True, 'LCHARG': True, 'LVTOT': True, 'LVHAR': True, 'LORBIT': False, 'KPOINT_BSE': [-1, 0, 0, 0], 'LDIPOL': True, 'IDIPOL': 3, 'DIPOL': [0.0, 0.0, 0.31870518]}, + 'vasprun_7.xml': {'SYSTEM': 'pseudo_H', 'PREC': 'accurate', 'ALGO': 'Fast', 'NELM': 400, 'IBRION': -1, 'LREAL': False, 'LWAVE': False, 'LORBIT': 0, 'KPOINT_BSE': [-1, 0, 0, 0]}, + 'vasprun_8.xml': {'SYSTEM': 'pseudo_H', 'PREC': 'accurate', 'ALGO': 'Fast', 'NELM': 400, 'IBRION': -1, 'LREAL': False, 'LWAVE': False, 'LORBIT': 0, 'KPOINT_BSE': [-1, 0, 0, 0]}, + 'vasprun_9.xml': {'SYSTEM': 'Ge.water', 'PREC': 'normal', 'ALGO': 'FAST', 'NELM': 100, 'IBRION': 0, 'EDIFF': 1e-05, 'EDIFFG': -0.01, 'NSW': 2, 'IWAVPR': 10, 'ISYM': 0, 'ENCUT': 400.0, 'POTIM': 0.5, 'TEBEG': 400.0, 'SMASS': -3.0, 'LREAL': 'A', 'ISMEAR': 0, 'SIGMA': 0.05, 'LWAVE': False, 'LCHARG': False, 'LVTOT': True, 'LVHAR': True, 'MDALGO': 5, 'CSVRTAU': 400.0, 'ICCE': 4, 'CCETYP': 3, 'POTTAU': 100.0, 'UBEG': 0.0, 'POTCAP': 0.03339719, 'ILOC': 3, 'KPOINT_BSE': [-1, 0, 0, 0], 'LDIPOL': True, 'IDIPOL': 3, 'DIPOL': [0.0, 0.0, 0.337978]}, + 'vasprun_line.xml': {'SYSTEM': 'Test2', 'ISTART': 0, 'PREC': 'high', 'ALGO': 'FAST', 'ICHARG': 11, 'NELM': 400, 'EDIFF': 1e-07, 'ENCUT': 450.0, 'LREAL': False, 'ISMEAR': 1, 'SIGMA': 0.1, 'LWAVE': False, 'LCHARG': True, 'LORBIT': False, 'KPOINT_BSE': [-1, 0, 0, 0]} + } + + for _, vp in enumerate(self.vp_list): + filename = os.path.basename(vp.filename) # Extract filename + self.assertIn(filename, expected_incar_outputs) # Ensure expected output is defined for this file + expected_incar = expected_incar_outputs[filename] + actual_incar = vp.vasprun_dict["incar"] + self.assertEqual(actual_incar, expected_incar) def test_parse_kpoints(self): for vp in self.vp_list: