From 0e57abf35d620f5c09149d72ba12493184135611 Mon Sep 17 00:00:00 2001 From: "Haoyu (Daniel)" Date: Mon, 15 Apr 2024 16:26:12 +0800 Subject: [PATCH] `pyright` fixes for `ext/io/phonon/symmetry/transformations/util/vis/dev_scripts` and improve `io.lobster` (#3757) * Add a CI run that only tests non-optional deps (optimistically) - Hopefully all tests have been written in such a way that they will be skipped if the underlying required package is not found * Install dev deps at least * Try out pyright config for unbound vars only * Add pyright to CI * pyright fix * pyright fix in vis.structure_vtk * fix `utils` * fix `transformations` * fix `symmetry.kpath` * finish fixing `symmetry` * update `pyright` * suppress `pyright` reportMissingModuleSource warning * fix `phonon` * some fixes in `io` * format tweaks of `io.vasp.outputs` * fix unit test for `symmetry.kpath` * fix io.vasp * fix unit test for io.vasp.inputs * fix io.qchem * fix io.pwscf, need input * replace `match.group(i)` with `match[i]` * fix missing ) * fix typo * suppress `reportInvalidTypeForm` * fix io.nwchem and packmol * fix io.lobster.inputs and use snake_case * fix lobsterenv * fix io.icet * fix io.lammps * revert to try/except for speed * set default coords_are_cartesian for pwscf * fix io.gaussian * fix io.fiesta * fix io.cif * fix io.babel * fix io.ase * fix io.cif tests * fix io.feff * fix io.exciting * fix io.abinit * fix io.cp2k * fix io.lobster.outputs * take some (not all) code rabbit suggestion * fix ext * fix dev_scripts * fix logic error * simplify checking * remove some weird constructions in lobster io * pre-commit auto-fixes * resolve code rabbit suggestions * fix more new key issues * fix new_key problems with code simplification * make same logic for del * remove unnecessary `int` before `math.floor/ceil` * Fix lower vs other * tweak error type in io.lobster * fix error type in lobster test * fix nested if * pre-commit auto-fixes * remove DEBUG tag * comment out pyright until all chunks finished * replace runtime error with value error * use walrus op for if match := re.match(...) * remove unused pyright code until ready to enable * rename single-letter m->match * remove unused raw --------- Signed-off-by: Matthew Evans <7916000+ml-evs@users.noreply.github.com> Co-authored-by: Matthew Evans Co-authored-by: Matthew Evans <7916000+ml-evs@users.noreply.github.com> Co-authored-by: JaGeo Co-authored-by: Janosh Riebesell --- dev_scripts/chemenv/equivalent_indices.py | 8 +- .../multi_weights_strategy_parameters.py | 17 +- dev_scripts/chemenv/view_environment.py | 1 + dev_scripts/update_pt_data.py | 5 +- pymatgen/analysis/ewald.py | 3 +- pymatgen/analysis/local_env.py | 8 +- pymatgen/command_line/enumlib_caller.py | 4 +- pymatgen/command_line/vampire_caller.py | 54 ++-- pymatgen/core/ion.py | 19 +- pymatgen/core/periodic_table.py | 37 ++- pymatgen/electronic_structure/boltztrap.py | 4 +- pymatgen/ext/matproj_legacy.py | 12 +- pymatgen/ext/optimade.py | 5 + pymatgen/io/abinit/abiobjects.py | 6 +- pymatgen/io/abinit/inputs.py | 1 + pymatgen/io/abinit/pseudos.py | 45 ++- pymatgen/io/ase.py | 11 +- pymatgen/io/babel.py | 2 +- pymatgen/io/cif.py | 29 +- pymatgen/io/cp2k/outputs.py | 4 + pymatgen/io/cp2k/sets.py | 2 +- pymatgen/io/exciting/inputs.py | 24 +- pymatgen/io/feff/inputs.py | 52 ++-- pymatgen/io/fiesta.py | 105 ++++--- pymatgen/io/gaussian.py | 100 +++---- pymatgen/io/icet.py | 4 +- pymatgen/io/lammps/data.py | 15 +- pymatgen/io/lobster/inputs.py | 271 +++++++++--------- pymatgen/io/lobster/lobsterenv.py | 8 +- pymatgen/io/lobster/outputs.py | 132 +++++---- pymatgen/io/nwchem.py | 2 + pymatgen/io/packmol.py | 3 +- pymatgen/io/pwscf.py | 58 ++-- pymatgen/io/qchem/outputs.py | 31 +- pymatgen/io/vasp/inputs.py | 74 +++-- pymatgen/io/vasp/outputs.py | 174 ++++++----- pymatgen/io/vasp/sets.py | 8 +- pymatgen/io/xr.py | 9 +- pymatgen/io/xtb/outputs.py | 8 +- pymatgen/io/zeopp.py | 25 +- pymatgen/phonon/bandstructure.py | 1 + pymatgen/phonon/gruneisen.py | 1 + pymatgen/phonon/thermal_displacements.py | 8 +- pymatgen/symmetry/analyzer.py | 4 + pymatgen/symmetry/bandstructure.py | 2 + pymatgen/symmetry/groups.py | 19 +- pymatgen/symmetry/kpath.py | 214 ++++---------- pymatgen/symmetry/maggroups.py | 8 +- .../advanced_transformations.py | 13 +- pymatgen/util/coord.py | 4 +- pymatgen/util/coord_cython.pyx | 6 +- pymatgen/util/provenance.py | 8 +- pymatgen/util/string.py | 11 +- pymatgen/vis/structure_chemview.py | 7 +- pymatgen/vis/structure_vtk.py | 39 +-- pyproject.toml | 9 + tests/files/cohp/lobsterin.1 | 2 +- tests/files/cohp/lobsterin.3 | 4 +- tests/io/lobster/test_inputs.py | 18 +- 59 files changed, 866 insertions(+), 892 deletions(-) diff --git a/dev_scripts/chemenv/equivalent_indices.py b/dev_scripts/chemenv/equivalent_indices.py index 4e1250b58e8..1d30564b05f 100644 --- a/dev_scripts/chemenv/equivalent_indices.py +++ b/dev_scripts/chemenv/equivalent_indices.py @@ -99,9 +99,9 @@ # 0. any point for i0 in range(8): # 1. point opposite to point 0. in the square face - if i0 in [0, 2]: + if i0 in {0, 2}: i1 = i0 + 1 - elif i0 in [1, 3]: + elif i0 in {1, 3}: i1 = i0 - 1 elif i0 == 4: i1 = 7 @@ -111,10 +111,14 @@ i1 = 5 elif i0 == 7: i1 = 4 + else: + raise RuntimeError("Cannot determine point.") + # 2. one of the two last points in the square face sfleft = list(sf1) if i0 in sf1 else list(sf2) sfleft.remove(i0) sfleft.remove(i1) + i2 = 0 for i2 in sfleft: sfleft2 = list(sfleft) sfleft2.remove(i2) diff --git a/dev_scripts/chemenv/strategies/multi_weights_strategy_parameters.py b/dev_scripts/chemenv/strategies/multi_weights_strategy_parameters.py index 7b1f40218bd..06e5841d155 100644 --- a/dev_scripts/chemenv/strategies/multi_weights_strategy_parameters.py +++ b/dev_scripts/chemenv/strategies/multi_weights_strategy_parameters.py @@ -151,17 +151,18 @@ def get_structure(self, morphing_factor): coords = copy.deepcopy(self.abstract_geometry.points_wcs_ctwcc()) bare_points = self.abstract_geometry.bare_points_with_centre + origin = None for morphing in self.morphing_description: - if morphing["site_type"] == "neighbor": - i_site = morphing["ineighbor"] + 1 - if morphing["expansion_origin"] == "central_site": - origin = bare_points[0] - vector = bare_points[i_site] - origin - coords[i_site] += vector * (morphing_factor - 1.0) - else: + if morphing["site_type"] != "neighbor": raise ValueError(f"Key \"site_type\" is {morphing['site_type']} while it can only be neighbor") + i_site = morphing["ineighbor"] + 1 + if morphing["expansion_origin"] == "central_site": + origin = bare_points[0] + vector = bare_points[i_site] - origin + coords[i_site] += vector * (morphing_factor - 1.0) + return Structure(lattice=lattice, species=species, coords=coords, coords_are_cartesian=True) def estimate_parameters(self, dist_factor_min, dist_factor_max, symmetry_measure_type="csm_wcs_ctwcc"): @@ -269,7 +270,7 @@ def get_weights(self, weights_options): "+-------------------------------------------------------------+\n" ) - with open("ce_pairs.json") as file: + with open("ce_pairs.json", encoding="utf-8") as file: ce_pairs = json.load(file) self_weight_max_csms: dict[str, list[float]] = {} self_weight_max_csms_per_cn: dict[str, list[float]] = {} diff --git a/dev_scripts/chemenv/view_environment.py b/dev_scripts/chemenv/view_environment.py index 17a1df52428..2caa22e9f34 100644 --- a/dev_scripts/chemenv/view_environment.py +++ b/dev_scripts/chemenv/view_environment.py @@ -52,6 +52,7 @@ print() # Visualize the separation plane of a given algorithm sep_plane = False + algo = None if any(algo.algorithm_type == SEPARATION_PLANE for algo in cg.algorithms): test = input("Enter index of the algorithm for which you want to visualize the plane : ") if test != "": diff --git a/dev_scripts/update_pt_data.py b/dev_scripts/update_pt_data.py index f7f77166980..178c2ce394b 100644 --- a/dev_scripts/update_pt_data.py +++ b/dev_scripts/update_pt_data.py @@ -150,9 +150,10 @@ def parse_shannon_radii(): from openpyxl import load_workbook wb = load_workbook("Shannon Radii.xlsx") - print(wb.get_sheet_names()) + print(wb.sheetnames()) sheet = wb["Sheet1"] i = 2 + el = charge = cn = None radii = collections.defaultdict(dict) while sheet[f"E{i}"].value: if sheet[f"A{i}"].value: @@ -235,6 +236,7 @@ def add_electron_affinities(): req = requests.get("https://wikipedia.org/wiki/Electron_affinity_(data_page)") soup = BeautifulSoup(req.text, "html.parser") + table = None for table in soup.find_all("table"): if "Hydrogen" in table.text: break @@ -271,6 +273,7 @@ def add_ionization_energies(): with open("NIST Atomic Ionization Energies Output.html") as file: soup = BeautifulSoup(file.read(), "html.parser") + table = None for table in soup.find_all("table"): if "Hydrogen" in table.text: break diff --git a/pymatgen/analysis/ewald.py b/pymatgen/analysis/ewald.py index 22c7afd18f0..092f45266a5 100644 --- a/pymatgen/analysis/ewald.py +++ b/pymatgen/analysis/ewald.py @@ -344,8 +344,7 @@ def _calc_recip(self): for g, g2, gr, exp_val, s_real, s_imag in zip(gs, g2s, grs, exp_vals, s_reals, s_imags): # Uses the identity sin(x)+cos(x) = 2**0.5 sin(x + pi/4) - m = (gr[None, :] + pi / 4) - gr[:, None] - np.sin(m, m) + m = np.sin((gr[None, :] + pi / 4) - gr[:, None]) m *= exp_val / g2 e_recip += m diff --git a/pymatgen/analysis/local_env.py b/pymatgen/analysis/local_env.py index fac782b0866..6ec084015ca 100644 --- a/pymatgen/analysis/local_env.py +++ b/pymatgen/analysis/local_env.py @@ -4164,10 +4164,10 @@ def _get_radius(site): return el.ionic_radii[oxi] # e.g., oxi = 2.667, average together 2+ and 3+ radii - if int(math.floor(oxi)) in el.ionic_radii and int(math.ceil(oxi)) in el.ionic_radii: - oxi_low = el.ionic_radii[int(math.floor(oxi))] - oxi_high = el.ionic_radii[int(math.ceil(oxi))] - x = oxi - int(math.floor(oxi)) + if math.floor(oxi) in el.ionic_radii and math.ceil(oxi) in el.ionic_radii: + oxi_low = el.ionic_radii[math.floor(oxi)] + oxi_high = el.ionic_radii[math.ceil(oxi)] + x = oxi - math.floor(oxi) return (1 - x) * oxi_low + x * oxi_high if oxi > 0 and el.average_cationic_radius > 0: diff --git a/pymatgen/command_line/enumlib_caller.py b/pymatgen/command_line/enumlib_caller.py index ae09c8a6770..d8df42362c7 100644 --- a/pymatgen/command_line/enumlib_caller.py +++ b/pymatgen/command_line/enumlib_caller.py @@ -256,7 +256,7 @@ def get_sg_info(ss): # enumeration. See Cu7Te5.cif test file. base *= 10 - # base = n_disordered # 10 ** int(math.ceil(math.log10(n_disordered))) + # base = n_disordered # 10 ** math.ceil(math.log10(n_disordered)) # To get a reasonable number of structures, we fix concentrations to the # range expected in the original structure. total_amounts = sum(index_amounts) @@ -266,7 +266,7 @@ def get_sg_info(ss): if abs(conc * base - round(conc * base)) < 1e-5: output.append(f"{int(round(conc * base))} {int(round(conc * base))} {base}") else: - min_conc = int(math.floor(conc * base)) + min_conc = math.floor(conc * base) output.append(f"{min_conc - 1} {min_conc + 1} {base}") output.append("") logger.debug("Generated input file:\n" + "\n".join(output)) diff --git a/pymatgen/command_line/vampire_caller.py b/pymatgen/command_line/vampire_caller.py index 46caba678fb..cfd4153f678 100644 --- a/pymatgen/command_line/vampire_caller.py +++ b/pymatgen/command_line/vampire_caller.py @@ -135,9 +135,9 @@ def __init__( stdout = stdout.decode() if stderr: - vanhelsing = stderr.decode() - if len(vanhelsing) > 27: # Suppress blank warning msg - logging.warning(vanhelsing) + van_helsing = stderr.decode() + if len(van_helsing) > 27: # Suppress blank warning msg + logging.warning(van_helsing) if process.returncode != 0: raise RuntimeError(f"Vampire exited with return code {process.returncode}.") @@ -146,9 +146,9 @@ def __init__( self._stderr = stderr # Process output - nmats = max(self.mat_id_dict.values()) - parsed_out, critical_temp = VampireCaller.parse_stdout("output", nmats) - self.output = VampireOutput(parsed_out, nmats, critical_temp) + n_mats = max(self.mat_id_dict.values()) + parsed_out, critical_temp = VampireCaller.parse_stdout("output", n_mats) + self.output = VampireOutput(parsed_out, n_mats, critical_temp) def _create_mat(self): structure = self.structure @@ -158,43 +158,41 @@ def _create_mat(self): # Maps sites to material id for vampire inputs mat_id_dict = {} - nmats = 0 + n_mats = 0 for key in self.unique_site_ids: spin_up, spin_down = False, False - nmats += 1 # at least 1 mat for each unique site + n_mats += 1 # at least 1 mat for each unique site # Check which spin sublattices exist for this site id for site in key: - m = magmoms[site] - if m > 0: + if magmoms[site] > 0: spin_up = True - if m < 0: + if magmoms[site] < 0: spin_down = True # Assign material id for each site for site in key: - m = magmoms[site] if spin_up and not spin_down: - mat_id_dict[site] = nmats + mat_id_dict[site] = n_mats if spin_down and not spin_up: - mat_id_dict[site] = nmats + mat_id_dict[site] = n_mats if spin_up and spin_down: # Check if spin up or down shows up first m0 = magmoms[key[0]] - if m > 0 and m0 > 0: - mat_id_dict[site] = nmats - if m < 0 and m0 < 0: - mat_id_dict[site] = nmats - if m > 0 > m0: - mat_id_dict[site] = nmats + 1 - if m < 0 < m0: - mat_id_dict[site] = nmats + 1 + if magmoms[site] > 0 and m0 > 0: + mat_id_dict[site] = n_mats + if magmoms[site] < 0 and m0 < 0: + mat_id_dict[site] = n_mats + if magmoms[site] > 0 > m0: + mat_id_dict[site] = n_mats + 1 + if magmoms[site] < 0 < m0: + mat_id_dict[site] = n_mats + 1 # Increment index if two sublattices if spin_up and spin_down: - nmats += 1 + n_mats += 1 - mat_file = [f"material:num-materials={nmats}"] + mat_file = [f"material:num-materials={n_mats}"] for key in self.unique_site_ids: i = self.unique_site_ids[key] # unique site id @@ -230,7 +228,7 @@ def _create_mat(self): def _create_input(self): structure = self.structure - mcbs = self.mc_box_size + mc_box_size = self.mc_box_size equil_timesteps = self.equil_timesteps mc_timesteps = self.mc_timesteps mat_name = self.mat_name @@ -255,9 +253,9 @@ def _create_input(self): # System size in nm input_script += [ - f"dimensions:system-size-x = {mcbs:.1f} !nm", - f"dimensions:system-size-y = {mcbs:.1f} !nm", - f"dimensions:system-size-z = {mcbs:.1f} !nm", + f"dimensions:system-size-x = {mc_box_size:.1f} !nm", + f"dimensions:system-size-y = {mc_box_size:.1f} !nm", + f"dimensions:system-size-z = {mc_box_size:.1f} !nm", ] # Critical temperature Monte Carlo calculation diff --git a/pymatgen/core/ion.py b/pymatgen/core/ion.py index 6fd41b174b7..0af620e4176 100644 --- a/pymatgen/core/ion.py +++ b/pymatgen/core/ion.py @@ -45,15 +45,12 @@ def from_formula(cls, formula: str) -> Self: Ion """ charge = 0.0 - f = formula # strip (aq), if present - m = re.search(r"\(aq\)", f) - if m: - f = f.replace(m.group(), "", 1) + if match := re.search(r"\(aq\)", formula): + formula = formula.replace(match.group(), "", 1) # check for charge in brackets - m = re.search(r"\[([^\[\]]+)\]", f) - if m: - m_chg = re.search(r"([\.\d]*)([+-]*)([\.\d]*)", m.group(1)) + if match := re.search(r"\[([^\[\]]+)\]", formula): + m_chg = re.search(r"([\.\d]*)([+-]*)([\.\d]*)", match.group(1)) if m_chg: if m_chg.group(1) != "": if m_chg.group(3) != "": @@ -65,18 +62,18 @@ def from_formula(cls, formula: str) -> Self: for i in re.findall("[+-]", m_chg.group(2)): charge += float(i + "1") - f = f.replace(m.group(), "", 1) + formula = formula.replace(match.group(), "", 1) # if no brackets, parse trailing +/- - for m_chg in re.finditer(r"([+-])([\.\d]*)", f): + for m_chg in re.finditer(r"([+-])([\.\d]*)", formula): sign = m_chg.group(1) sgn = float(str(sign + "1")) if m_chg.group(2).strip() != "": charge += float(m_chg.group(2)) * sgn else: charge += sgn - f = f.replace(m_chg.group(), "", 1) - composition = Composition(f) + formula = formula.replace(m_chg.group(), "", 1) + composition = Composition(formula) return cls(composition, charge) @property diff --git a/pymatgen/core/periodic_table.py b/pymatgen/core/periodic_table.py index 0e321e0cc66..cb69b88a928 100644 --- a/pymatgen/core/periodic_table.py +++ b/pymatgen/core/periodic_table.py @@ -246,14 +246,15 @@ def __getattr__(self, item: str) -> Any: except ValueError: # Ignore error. val will just remain a string. pass - if item in ("refractive_index", "melting_point") and isinstance(val, str): - # Final attempt to parse a float. - m = re.findall(r"[\.\d]+", val) - if m: - warnings.warn( - f"Ambiguous values ({val}) for {item} of {self.symbol}. Returning first float value." - ) - return float(m[0]) + if ( + item in ("refractive_index", "melting_point") + and isinstance(val, str) + and (match := re.findall(r"[\.\d]+", val)) + ): + warnings.warn( + f"Ambiguous values ({val}) for {item} of {self.symbol}. Returning first float value." + ) + return float(match[0]) return val raise AttributeError(f"Element has no attribute {item}!") @@ -385,9 +386,8 @@ def full_electronic_structure(self) -> list[tuple[int, str, int]]: e_str = self.electronic_structure def parse_orbital(orb_str): - m = re.match(r"(\d+)([spdfg]+)(\d+)", orb_str) - if m: - return int(m.group(1)), m.group(2), int(m.group(3)) + if match := re.match(r"(\d+)([spdfg]+)(\d+)", orb_str): + return int(match.group(1)), match.group(2), int(match.group(3)) return orb_str data = [parse_orbital(s) for s in e_str.split(".")] @@ -1382,17 +1382,16 @@ def from_str(cls, species_string: str) -> Self: Raises: ValueError if species_string cannot be interpreted. """ - m = re.search(r"([A-ZAa-z]*)([0-9.]*)([+\-]*)(.*)", species_string) - if m: - sym = m.group(1) - if m.group(2) == m.group(3) == "": + if match := re.search(r"([A-ZAa-z]*)([0-9.]*)([+\-]*)(.*)", species_string): + sym = match.group(1) + if match.group(2) == match.group(3) == "": oxi = 0.0 else: - oxi = 1.0 if m.group(2) == "" else float(m.group(2)) - oxi = -oxi if m.group(3) == "-" else oxi + oxi = 1.0 if match.group(2) == "" else float(match.group(2)) + oxi = -oxi if match.group(3) == "-" else oxi properties = {} - if m.group(4): # has Spin property - tokens = m.group(4).split("=") + if match.group(4): # has Spin property + tokens = match.group(4).split("=") properties = {tokens[0]: float(tokens[1])} return cls(sym, oxi, **properties) raise ValueError("Invalid DummySpecies String") diff --git a/pymatgen/electronic_structure/boltztrap.py b/pymatgen/electronic_structure/boltztrap.py index 859bafbdb87..d2582cb4689 100644 --- a/pymatgen/electronic_structure/boltztrap.py +++ b/pymatgen/electronic_structure/boltztrap.py @@ -284,7 +284,7 @@ def write_energy(self, output_file) -> None: # use 90% of bottom bands since highest eigenvalues # are usually incorrect # ask Geoffroy Hautier for more details - nb_bands = int(math.floor(self._bs.nb_bands * (1 - self.cb_cut))) + nb_bands = math.floor(self._bs.nb_bands * (1 - self.cb_cut)) for j in range(nb_bands): eigs.append( Energy( @@ -384,7 +384,7 @@ def write_proj(self, output_file_proj: str, output_file_def: str) -> None: file.write(str(len(self._bs.kpoints)) + "\n") for i, kpt in enumerate(self._bs.kpoints): tmp_proj = [] - for j in range(int(math.floor(self._bs.nb_bands * (1 - self.cb_cut)))): + for j in range(math.floor(self._bs.nb_bands * (1 - self.cb_cut))): tmp_proj.append(self._bs.projections[Spin(self.spin)][j][i][oi][site_nb]) # TODO deal with the sorting going on at # the energy level!!! diff --git a/pymatgen/ext/matproj_legacy.py b/pymatgen/ext/matproj_legacy.py index 78b6a475e92..84ed591e560 100644 --- a/pymatgen/ext/matproj_legacy.py +++ b/pymatgen/ext/matproj_legacy.py @@ -1604,9 +1604,9 @@ def parse_criteria(criteria_string): def parse_sym(sym): if sym == "*": return [el.symbol for el in Element] - m = re.match(r"\{(.*)\}", sym) - if m: - return [s.strip() for s in m.group(1).split(",")] + + if match := re.match(r"\{(.*)\}", sym): + return [s.strip() for s in match.group(1).split(",")] return [sym] def parse_tok(t): @@ -1628,8 +1628,8 @@ def parse_tok(t): if ("*" in sym) or ("{" in sym): wild_card_els.append(sym) else: - m = re.match(r"([A-Z][a-z]*)[\.\d]*", sym) - explicit_els.append(m.group(1)) + match = re.match(r"([A-Z][a-z]*)[\.\d]*", sym) + explicit_els.append(match[1]) n_elements = len(wild_card_els) + len(set(explicit_els)) parts = re.split(r"(\*|\{.*\})", t) parts = [parse_sym(s) for s in parts if s != ""] @@ -1660,5 +1660,5 @@ def get_chunks(sequence: Sequence[Any], size=1): Returns: list[Sequence[Any]]: input sequence in chunks of length size. """ - chunks = int(math.ceil(len(sequence) / float(size))) + chunks = math.ceil(len(sequence) / float(size)) return [sequence[i * size : (i + 1) * size] for i in range(chunks)] diff --git a/pymatgen/ext/optimade.py b/pymatgen/ext/optimade.py index 5401b8a1701..92eecf715e6 100644 --- a/pymatgen/ext/optimade.py +++ b/pymatgen/ext/optimade.py @@ -464,6 +464,8 @@ def is_url(url) -> bool: _logger.warning(f"An invalid url was supplied: {provider_url}") return None + url = None + try: url = urljoin(provider_url, "v1/info") provider_info_json = self._get_json(url) @@ -504,6 +506,9 @@ def _parse_provider(self, provider: str, provider_url: str) -> dict[str, Provide # Add trailing slash to all URLs if missing; prevents urljoin from scrubbing if urlparse(provider_url).path is not None and not provider_url.endswith("/"): provider_url += "/" + + url = None + try: url = urljoin(provider_url, "v1/links") provider_link_json = self._get_json(url) diff --git a/pymatgen/io/abinit/abiobjects.py b/pymatgen/io/abinit/abiobjects.py index a28910ead19..1929a5ded26 100644 --- a/pymatgen/io/abinit/abiobjects.py +++ b/pymatgen/io/abinit/abiobjects.py @@ -722,11 +722,11 @@ def __init__( if use_symmetries and use_time_reversal: kptopt = 1 - if not use_symmetries and use_time_reversal: + elif not use_symmetries and use_time_reversal: kptopt = 2 - if not use_symmetries and not use_time_reversal: + elif not use_symmetries and not use_time_reversal: kptopt = 3 - if use_symmetries and not use_time_reversal: + else: # use_symmetries and not use_time_reversal kptopt = 4 abivars.update( diff --git a/pymatgen/io/abinit/inputs.py b/pymatgen/io/abinit/inputs.py index cdbeda3c60a..ba1150d40ac 100644 --- a/pymatgen/io/abinit/inputs.py +++ b/pymatgen/io/abinit/inputs.py @@ -179,6 +179,7 @@ def _stopping_criterion(run_level, accuracy): def _find_ecut_pawecutdg(ecut, pawecutdg, pseudos, accuracy): """Return a |AttrDict| with the value of ecut and pawecutdg.""" # Get ecut and pawecutdg from the pseudo hints. + has_hints = False if ecut is None or (pawecutdg is None and any(p.ispaw for p in pseudos)): has_hints = all(p.has_hints for p in pseudos) diff --git a/pymatgen/io/abinit/pseudos.py b/pymatgen/io/abinit/pseudos.py index 7052baf77ac..cdbfa50c2f0 100644 --- a/pymatgen/io/abinit/pseudos.py +++ b/pymatgen/io/abinit/pseudos.py @@ -62,11 +62,11 @@ def _read_nlines(filename: str, n_lines: int) -> list[str]: If nlines is < 0, the entire file is read. """ if n_lines < 0: - with open(filename) as file: + with open(filename, encoding="utf-8") as file: return file.readlines() lines = [] - with open(filename) as file: + with open(filename, encoding="utf-8") as file: for lineno, line in enumerate(file): if lineno == n_lines: break @@ -229,7 +229,7 @@ def md5(self): def compute_md5(self): """Compute and return MD5 hash value.""" - with open(self.path) as file: + with open(self.path, encoding="utf-8") as file: text = file.read() # usedforsecurity=False needed in FIPS mode (Federal Information Processing Standards) # https://github.com/materialsproject/pymatgen/issues/2804 @@ -289,7 +289,7 @@ def as_tmpfile(self, tmpdir=None): # Copy dojo report file if present. root, _ext = os.path.splitext(self.filepath) - dj_report = root + ".djrepo" + dj_report = f"{root}.djrepo" if os.path.isfile(dj_report): shutil.copy(dj_report, os.path.join(tmpdir, os.path.basename(dj_report))) @@ -311,7 +311,7 @@ def djrepo_path(self): """The path of the djrepo file. None if file does not exist.""" root, _ext = os.path.splitext(self.filepath) - return root + ".djrepo" + return f"{root}.djrepo" # if os.path.isfile(path): return path # return None @@ -473,7 +473,7 @@ def __init__(self, path, header): value = header.get(attr_name) # Hide these attributes since one should always use the public interface. - setattr(self, "_" + attr_name, value) + setattr(self, f"_{attr_name}", value) @property def summary(self): @@ -824,11 +824,12 @@ def tm_header(filename, ppdesc): """ lines = _read_nlines(filename, -1) header = [] + lmax = None for lineno, line in enumerate(lines): header.append(line) if lineno == 2: - # Read lmax. + # Read lmax tokens = line.split() _pspcod, _pspxc, lmax, _lloc = map(int, tokens[:4]) _mmax, _r2well = map(float, tokens[4:6]) @@ -843,13 +844,15 @@ def tm_header(filename, ppdesc): # 0 4.085 6.246 0 2.8786493 l,e99.0,e99.9,nproj,rcpsp # .00000000 .0000000000 .0000000000 .00000000 rms,ekb1,ekb2,epsatm projectors = {} + proj_info = [] + idx = None for idx in range(2 * (lmax + 1)): line = lines[idx] if idx % 2 == 0: proj_info = [ line, ] - if idx % 2 == 1: + else: proj_info.append(line) d = _dict_from_lines(proj_info, [5, 4]) projectors[int(d["l"])] = d @@ -1049,7 +1052,7 @@ def scan_directory(self, dirname, exclude_exts=(), exclude_fnames=()): """ for i, ext in enumerate(exclude_exts): if not ext.strip().startswith("."): - exclude_exts[i] = "." + ext.strip() + exclude_exts[i] = f".{ext.strip()}" # Exclude files depending on the extension. paths = [] @@ -1443,10 +1446,10 @@ def plot_waves(self, ax: plt.Axes = None, fontsize=12, **kwargs): # ax.annotate("$r_c$", xy=(self.paw_radius + 0.1, 0.1)) for state, rfunc in self.pseudo_partial_waves.items(): - ax.plot(rfunc.mesh, rfunc.mesh * rfunc.values, lw=2, label="PS-WAVE: " + state) # noqa: PD011 + ax.plot(rfunc.mesh, rfunc.mesh * rfunc.values, lw=2, label=f"PS-WAVE: {state}") # noqa: PD011 for state, rfunc in self.ae_partial_waves.items(): - ax.plot(rfunc.mesh, rfunc.mesh * rfunc.values, lw=2, label="AE-WAVE: " + state) # noqa: PD011 + ax.plot(rfunc.mesh, rfunc.mesh * rfunc.values, lw=2, label=f"AE-WAVE: {state}") # noqa: PD011 ax.legend(loc="best", shadow=True, fontsize=fontsize) @@ -1473,7 +1476,7 @@ def plot_projectors(self, ax: plt.Axes = None, fontsize=12, **kwargs): # ax.annotate("$r_c$", xy=(self.paw_radius + 0.1, 0.1)) for state, rfunc in self.projector_functions.items(): - ax.plot(rfunc.mesh, rfunc.mesh * rfunc.values, label="TPROJ: " + state) # noqa: PD011 + ax.plot(rfunc.mesh, rfunc.mesh * rfunc.values, label=f"TPROJ: {state}") # noqa: PD011 ax.legend(loc="best", shadow=True, fontsize=fontsize) @@ -1539,9 +1542,7 @@ class PseudoTable(collections.abc.Sequence, MSONable): @classmethod def as_table(cls, items): """Return an instance of PseudoTable from the iterable items.""" - if isinstance(items, cls): - return items - return cls(items) + return items if isinstance(items, cls) else cls(items) @classmethod def from_dir(cls, top, exts=None, exclude_dirs="_*") -> Self | None: @@ -1673,7 +1674,7 @@ def as_dict(self, **kwargs): while k in dct: k += f"{k.split('#')[0]}#{count}" count += 1 - dct.update({k: p.as_dict()}) + dct[k] = p.as_dict() dct["@module"] = type(self).__module__ dct["@class"] = type(self).__name__ return dct @@ -1724,9 +1725,7 @@ def pseudo_with_symbol(self, symbol, allow_multi=False): if not pseudos or (len(pseudos) > 1 and not allow_multi): raise ValueError(f"Found {len(pseudos)} occurrences of {symbol=}") - if not allow_multi: - return pseudos[0] - return pseudos + return pseudos if allow_multi else pseudos[0] def pseudos_with_symbols(self, symbols): """ @@ -1737,13 +1736,11 @@ def pseudos_with_symbols(self, symbols): """ pseudos = self.select_symbols(symbols, ret_list=True) found_symbols = [p.symbol for p in pseudos] - duplicated_elements = [s for s, o in collections.Counter(found_symbols).items() if o > 1] - if duplicated_elements: + if duplicated_elements := [s for s, o in collections.Counter(found_symbols).items() if o > 1]: raise ValueError(f"Found multiple occurrences of symbol(s) {', '.join(duplicated_elements)}") - missing_symbols = [s for s in symbols if s not in found_symbols] - if missing_symbols: + if missing_symbols := [s for s in symbols if s not in found_symbols]: raise ValueError(f"Missing data for symbol(s) {', '.join(missing_symbols)}") return pseudos @@ -1871,4 +1868,4 @@ def select_rows(self, rows): def select_family(self, family): """Return PseudoTable with element belonging to the specified family, e.g. family="alkaline".""" # e.g element.is_alkaline - return type(self)([p for p in self if getattr(p.element, "is_" + family)]) + return type(self)([p for p in self if getattr(p.element, f"is_{family}")]) diff --git a/pymatgen/io/ase.py b/pymatgen/io/ase.py index 7981c845fe1..8cc59cb78ca 100644 --- a/pymatgen/io/ase.py +++ b/pymatgen/io/ase.py @@ -22,13 +22,14 @@ from ase.io.jsonio import decode, encode from ase.spacegroup import Spacegroup - no_ase_err = None + NO_ASE_ERR = None except ImportError: - no_ase_err = PackageNotFoundError("AseAtomsAdaptor requires the ASE package. Use `pip install ase`") + NO_ASE_ERR = PackageNotFoundError("AseAtomsAdaptor requires the ASE package. Use `pip install ase`") + encode = decode = FixAtoms = SinglePointDFTCalculator = Spacegroup = None class Atoms: # type: ignore[no-redef] def __init__(self, *args, **kwargs): - raise no_ase_err + raise NO_ASE_ERR if TYPE_CHECKING: @@ -94,8 +95,8 @@ def get_atoms(structure: SiteCollection, msonable: bool = True, **kwargs) -> MSO Returns: Atoms: ASE Atoms object """ - if no_ase_err: - raise no_ase_err + if NO_ASE_ERR: + raise NO_ASE_ERR if not structure.is_ordered: raise ValueError("ASE Atoms only supports ordered structures") diff --git a/pymatgen/io/babel.py b/pymatgen/io/babel.py index 26f48d81fff..f6f862bc386 100644 --- a/pymatgen/io/babel.py +++ b/pymatgen/io/babel.py @@ -17,7 +17,7 @@ try: from openbabel import openbabel, pybel except Exception: - openbabel = None + openbabel = pybel = None if TYPE_CHECKING: from typing_extensions import Self diff --git a/pymatgen/io/cif.py b/pymatgen/io/cif.py index 9de1fb77f5c..4d8006702f4 100644 --- a/pymatgen/io/cif.py +++ b/pymatgen/io/cif.py @@ -108,7 +108,7 @@ def _loop_to_str(self, loop): out += line + "\n" + val line = "\n" elif len(line) + len(val) + 2 < self.max_len: - line += " " + val + line += f" {val}" else: out += line line = "\n " + val @@ -119,8 +119,9 @@ def _format_field(self, val) -> str: val = str(val).strip() if len(val) > self.max_len: return f";\n{textwrap.fill(val, self.max_len)}\n;" + # add quotes if necessary - if val == "": + if not val: return '""' if ( (" " in val or val[0] == "_") @@ -667,8 +668,8 @@ def get_lattice_no_exception( Returns: Lattice object """ - lengths = [str2float(data["_cell_length_" + i]) for i in length_strings] - angles = [str2float(data["_cell_angle_" + i]) for i in angle_strings] + lengths = [str2float(data[f"_cell_length_{i}"]) for i in length_strings] + angles = [str2float(data[f"_cell_angle_{i}"]) for i in angle_strings] if not lattice_type: return Lattice.from_parameters(*lengths, *angles) return getattr(Lattice, lattice_type)(*(lengths + angles)) @@ -854,7 +855,8 @@ def parse_oxi_states(data): def parse_magmoms(data, lattice=None): """Parse atomic magnetic moments from data dictionary.""" if lattice is None: - raise Exception("Magmoms given in terms of crystal axes in magCIF spec.") + raise ValueError("Magmoms given in terms of crystal axes in magCIF spec.") + try: magmoms = { data["_atom_site_moment_label"][i]: np.array( @@ -903,10 +905,8 @@ def _parse_symbol(self, sym): parsed_sym = sym[:2].title() elif Element.is_valid_symbol(sym[0].upper()): parsed_sym = sym[0].upper() - else: - m = re.match(r"w?[A-Z][a-z]*", sym) - if m: - parsed_sym = m.group() + elif match := re.match(r"w?[A-Z][a-z]*", sym): + parsed_sym = match.group() if parsed_sym is not None and (m_sp or not re.match(rf"{parsed_sym}\d*", sym)): msg = f"{sym} parsed as {parsed_sym}" @@ -1022,6 +1022,7 @@ def get_matching_coord(coord): self.warnings.append(msg) all_species = [] + all_species_noedit = [] all_coords = [] all_magmoms = [] all_hydrogens = [] @@ -1097,7 +1098,7 @@ def get_matching_coord(coord): if self.feature_flags["magcif"]: site_properties["magmom"] = all_magmoms - if len(site_properties) == 0: + if not site_properties: site_properties = None # type: ignore[assignment] if any(all_labels): @@ -1210,8 +1211,7 @@ def parse_structures( structures = [] for idx, dct in enumerate(self._cif.data.values()): try: - struct = self._get_structure(dct, primitive, symmetrized, check_occu=check_occu) - if struct: + if struct := self._get_structure(dct, primitive, symmetrized, check_occu=check_occu): structures.append(struct) except (KeyError, ValueError) as exc: # A user reported a problem with cif files produced by Avogadro @@ -1467,9 +1467,9 @@ def __init__( if symprec is None: block["_symmetry_equiv_pos_site_id"] = ["1"] block["_symmetry_equiv_pos_as_xyz"] = ["x, y, z"] + else: spg_analyzer = SpacegroupAnalyzer(struct, symprec) - symm_ops: list[SymmOp] = [] for op in spg_analyzer.get_symmetry_operations(): v = op.translation_vector @@ -1537,6 +1537,7 @@ def __init__( atom_site_properties[key].append(format_str.format(val)) count += 1 + else: # The following just presents a deterministic ordering. unique_sites = [ @@ -1544,7 +1545,7 @@ def __init__( sorted(sites, key=lambda s: tuple(abs(x) for x in s.frac_coords))[0], len(sites), ) - for sites in spg_analyzer.get_symmetrized_structure().equivalent_sites + for sites in spg_analyzer.get_symmetrized_structure().equivalent_sites # type: ignore[reportPossiblyUnboundVariable] ] for site, mult in sorted( unique_sites, diff --git a/pymatgen/io/cp2k/outputs.py b/pymatgen/io/cp2k/outputs.py index 43ab0028c2a..d4309022a80 100644 --- a/pymatgen/io/cp2k/outputs.py +++ b/pymatgen/io/cp2k/outputs.py @@ -1672,12 +1672,15 @@ def parse_dos(dos_file=None): data = np.loadtxt(dos_file) data[:, 0] *= Ha_to_eV energies = data[:, 0] + vbm_top = None for idx, val in enumerate(data[:, 1]): if val == 0: break vbm_top = idx + efermi = energies[vbm_top] + 1e-6 densities = {Spin.up: data[:, 1]} + if data.shape[1] > 3: densities[Spin.down] = data[:, 3] return Dos(efermi=efermi, energies=energies, densities=densities) @@ -1755,6 +1758,7 @@ def cp2k_to_pmg_labels(label: str) -> str: data = np.delete(data, 1, 1) data[:, 0] *= Ha_to_eV energies = data[:, 0] + vbm_top = None for idx, occu in enumerate(occupations): if occu == 0: break diff --git a/pymatgen/io/cp2k/sets.py b/pymatgen/io/cp2k/sets.py index d686690ae31..60c1c4d21ac 100644 --- a/pymatgen/io/cp2k/sets.py +++ b/pymatgen/io/cp2k/sets.py @@ -399,7 +399,7 @@ def get_basis_and_potential(structure, basis_and_potential): for el in structure.symbol_set: possible_basis_sets = [] possible_potentials = [] - basis, aux_basis, potential = None, None, None + basis, aux_basis, potential, DATA = None, None, None, None desired_basis, desired_aux_basis, desired_potential = None, None, None have_element_file = os.path.isfile(os.path.join(SETTINGS.get("PMG_CP2K_DATA_DIR", "."), el)) diff --git a/pymatgen/io/exciting/inputs.py b/pymatgen/io/exciting/inputs.py index 3896f1ff6ed..706a012d8c9 100644 --- a/pymatgen/io/exciting/inputs.py +++ b/pymatgen/io/exciting/inputs.py @@ -2,6 +2,7 @@ from __future__ import annotations +import itertools import xml.etree.ElementTree as ET from typing import TYPE_CHECKING @@ -121,14 +122,11 @@ def from_str(cls, data: str) -> Self: lockxyz.append(lxyz) # check the atomic positions type - if "cartesian" in struct.attrib: - if struct.attrib["cartesian"]: - cartesian = True - for p in positions: - for j in range(3): - p[j] = p[j] * ExcitingInput.bohr2ang - else: - cartesian = False + cartesian = False + if struct.attrib.get("cartesian"): + cartesian = True + for p, j in itertools.product(positions, range(3)): + p[j] = p[j] * ExcitingInput.bohr2ang _crystal = struct.find("crystal") assert _crystal is not None, "crystal cannot be None." @@ -233,7 +231,7 @@ def write_etree(self, celltype, cartesian=False, bandstr=False, symprec: float = # write atomic positions for each species index = 0 for elem in sorted(new_struct.types_of_species, key=lambda el: el.X): - species = ET.SubElement(structure, "species", speciesfile=elem.symbol + ".xml") + species = ET.SubElement(structure, "species", speciesfile=f"{elem.symbol}.xml") sites = new_struct.indices_from_symbol(elem.symbol) for j in sites: @@ -254,8 +252,12 @@ def write_etree(self, celltype, cartesian=False, bandstr=False, symprec: float = # write atomic positions index = index + 1 _ = ET.SubElement(species, "atom", coord=coord) + # write bandstructure if needed - if bandstr and celltype == "primitive": + if bandstr: + if celltype != "primitive": + raise ValueError("Bandstructure is only implemented for the standard primitive unit cell!") + kpath = HighSymmKpath(new_struct, symprec=symprec, angle_tolerance=angle_tolerance) prop = ET.SubElement(root, "properties") band_struct = ET.SubElement(prop, "bandstructure") @@ -269,8 +271,6 @@ def write_etree(self, celltype, cartesian=False, bandstr=False, symprec: float = symbol_map = {"\\Gamma": "GAMMA", "\\Sigma": "SIGMA", "\\Delta": "DELTA", "\\Lambda": "LAMBDA"} symbol = symbol_map.get(symbol, symbol) _ = ET.SubElement(path, "point", coord=coord, label=symbol) - elif bandstr and celltype != "primitive": - raise ValueError("Bandstructure is only implemented for the standard primitive unit cell!") # write extra parameters from kwargs if provided self._dicttoxml(kwargs, root) diff --git a/pymatgen/io/feff/inputs.py b/pymatgen/io/feff/inputs.py index 51f3fe2bcd0..4d62241142e 100644 --- a/pymatgen/io/feff/inputs.py +++ b/pymatgen/io/feff/inputs.py @@ -273,7 +273,7 @@ def header_string_from_file(filename: str = "feff.inp"): # source end = 0 for line in f: - if (line[0] == "*" or line[0] == "T") and end == 0: + if line[0] in {"*", "T"} and end == 0: feff_header_str.append(line.replace("\r", "")) else: end = 1 @@ -360,6 +360,8 @@ def __str__(self): coords = [f"{j:0.6f}".rjust(12) for j in site.frac_coords] elif isinstance(self.struct, Molecule): coords = [f"{j:0.6f}".rjust(12) for j in site.coords] + else: + raise TypeError("Unsupported type. Expect Structure or Molecule.") output.append(f"* {idx} {site.species_string} {' '.join(coords)}") return "\n".join(output) @@ -370,7 +372,7 @@ def write_file(self, filename="HEADER"): Args: filename: Filename and path for file to be written to disk """ - with open(filename, mode="w") as file: + with open(filename, mode="w", encoding="utf-8") as file: file.write(str(self) + "\n") @@ -546,7 +548,7 @@ def __setitem__(self, key, val): value: value associated with key in dictionary """ if key.strip().upper() not in VALID_FEFF_TAGS: - warnings.warn(key.strip() + " not in VALID_FEFF_TAGS list") + warnings.warn(f"{key.strip()} not in VALID_FEFF_TAGS list") super().__setitem__( key.strip(), Tags.proc_val(key.strip(), val.strip()) if isinstance(val, str) else val, @@ -644,13 +646,13 @@ def write_file(self, filename="PARAMETERS"): @classmethod def from_file(cls, filename: str = "feff.inp") -> Self: """ - Creates a Feff_tag dictionary from a PARAMETER or feff.inp file. + Creates a Tags dictionary from a PARAMETER or feff.inp file. Args: filename: Filename for either PARAMETER or feff.inp file Returns: - Feff_tag object + Tags """ with zopen(filename, mode="rt") as file: lines = list(clean_lines(file.readlines())) @@ -659,10 +661,9 @@ def from_file(cls, filename: str = "feff.inp") -> Self: ieels = -1 ieels_max = -1 for idx, line in enumerate(lines): - m = re.match(r"([A-Z]+\d*\d*)\s*(.*)", line) - if m: - key = m.group(1).strip() - val = m.group(2).strip() + if match := re.match(r"([A-Z]+\d*\d*)\s*(.*)", line): + key = match[1].strip() + val = match[2].strip() val = Tags.proc_val(key, val) if key not in ("ATOMS", "POTENTIALS", "END", "TITLE"): if key in ["ELNES", "EXELFS"]: @@ -709,32 +710,29 @@ def proc_val(key, val): boolean_type_keys = () float_type_keys = ("S02", "EXAFS", "RPATH") - def smart_int_or_float(numstr): - if numstr.find(".") != -1 or numstr.lower().find("e") != -1: - return float(numstr) - return int(numstr) + def smart_int_or_float(num_str): + if num_str.find(".") != -1 or num_str.lower().find("e") != -1: + return float(num_str) + return int(num_str) try: if key.lower() == "cif": - m = re.search(r"\w+.cif", val) - return m.group(0) + return re.search(r"\w+.cif", val)[0] if key in list_type_keys: output = [] tokens = re.split(r"\s+", val) for tok in tokens: - m = re.match(r"(\d+)\*([\d\.\-\+]+)", tok) - if m: - output.extend([smart_int_or_float(m.group(2))] * int(m.group(1))) + if match := re.match(r"(\d+)\*([\d\.\-\+]+)", tok): + output.extend([smart_int_or_float(match[2])] * int(match[1])) else: output.append(smart_int_or_float(tok)) return output if key in boolean_type_keys: - m = re.search(r"^\W+([TtFf])", val) - if m: - return m.group(1) in ["T", "t"] - raise ValueError(key + " should be a boolean type!") + if match := re.search(r"^\W+([TtFf])", val): + return match[1] in {"T", "t"} + raise ValueError(f"{key} should be a boolean type!") if key in float_type_keys: return float(val) @@ -794,13 +792,13 @@ def __init__(self, struct, absorbing_atom): struct (Structure): Structure object. absorbing_atom (str/int): Absorbing atom symbol or site index. """ - if struct.is_ordered: - self.struct = struct - atom_sym = get_absorbing_atom_symbol_index(absorbing_atom, struct)[0] - self.pot_dict = get_atom_map(struct, atom_sym) - else: + if not struct.is_ordered: raise ValueError("Structure with partial occupancies cannot be converted into atomic coordinates!") + self.struct = struct + atom_sym = get_absorbing_atom_symbol_index(absorbing_atom, struct)[0] + self.pot_dict = get_atom_map(struct, atom_sym) + self.absorbing_atom, _ = get_absorbing_atom_symbol_index(absorbing_atom, struct) @staticmethod diff --git a/pymatgen/io/fiesta.py b/pymatgen/io/fiesta.py index e13563b8f64..d016a57a5bd 100644 --- a/pymatgen/io/fiesta.py +++ b/pymatgen/io/fiesta.py @@ -56,9 +56,9 @@ def __init__(self, folder, filename="nwchem", log_file="log_n2f"): self.log_file = log_file self._NWCHEM2FIESTA_cmd = "NWCHEM2FIESTA" - self._nwcheminput_fn = filename + ".nw" - self._nwchemoutput_fn = filename + ".nwout" - self._nwchemmovecs_fn = filename + ".movecs" + self._nwcheminput_fn = f"{filename}.nw" + self._nwchemoutput_fn = f"{filename}.nwout" + self._nwchemmovecs_fn = f"{filename}.movecs" def run(self): """Performs actual NWCHEM2FIESTA run.""" @@ -133,8 +133,8 @@ def run(self): def _gw_run(self): """Performs FIESTA (gw) run.""" - if self.folder != os.getcwd(): - init_folder = os.getcwd() + init_folder = os.getcwd() + if self.folder != init_folder: os.chdir(self.folder) with zopen(self.log_file, mode="w") as fout: @@ -151,13 +151,13 @@ def _gw_run(self): stdout=fout, ) - if self.folder != os.getcwd(): + if self.folder != init_folder: os.chdir(init_folder) def bse_run(self): """Performs BSE run.""" - if self.folder != os.getcwd(): - init_folder = os.getcwd() + init_folder = os.getcwd() + if self.folder != init_folder: os.chdir(self.folder) with zopen(self.log_file, mode="w") as fout: @@ -173,7 +173,7 @@ def bse_run(self): stdout=fout, ) - if self.folder != os.getcwd(): + if self.folder != init_folder: os.chdir(init_folder) def as_dict(self): @@ -221,7 +221,7 @@ def __init__(self, filename): self.data.update(n_nlmo=self.set_n_nlmo()) @staticmethod - def _parse_file(input): + def _parse_file(lines): lmax_nnlo_patt = re.compile(r"\s* (\d+) \s+ (\d+) \s+ \# .* ", re.VERBOSE) nl_orbital_patt = re.compile(r"\s* (\d+) \s+ (\d+) \s+ (\d+) \s+ \# .* ", re.VERBOSE) @@ -235,25 +235,25 @@ def _parse_file(input): parse_nl_orbital = False nnlo = None lmax = None + l_angular = zeta = ng = None - for line in input.split("\n"): + for line in lines.split("\n"): if parse_nl_orbital: match_orb = nl_orbital_patt.search(line) match_alpha = coef_alpha_patt.search(line) if match_orb: - l_angular = match_orb.group(1) - zeta = match_orb.group(2) - ng = match_orb.group(3) + l_angular = match_orb[1] + zeta = match_orb[2] + ng = match_orb[3] basis_set[f"{l_angular}_{zeta}_{ng}"] = [] elif match_alpha: - alpha = match_alpha.group(1) - coef = match_alpha.group(2) + alpha = match_alpha[1] + coef = match_alpha[2] basis_set[f"{l_angular}_{zeta}_{ng}"].append((alpha, coef)) elif parse_lmax_nnlo: - match_orb = lmax_nnlo_patt.search(line) - if match_orb: - lmax = match_orb.group(1) - nnlo = match_orb.group(2) + if match_orb := lmax_nnlo_patt.search(line): + lmax = match_orb[1] + nnlo = match_orb[2] parse_lmax_nnlo = False parse_nl_orbital = True elif parse_preamble: @@ -352,7 +352,7 @@ def set_auxiliary_basis_set(self, folder, auxiliary_folder, auxiliary_basis_set_ for specie in self._mol.symbol_set: for file in list_files: - if file.upper().find(specie.upper() + "2") != -1 and file.lower().find(auxiliary_basis_set_type) != -1: + if file.upper().find(f"{specie.upper()}2") != -1 and file.lower().find(auxiliary_basis_set_type) != -1: shutil.copyfile(f"{auxiliary_folder}/{file}", f"{folder}/{specie}2.ion") def set_gw_options(self, nv_band=10, nc_band=10, n_iteration=5, n_grid=6, dE_grid=0.5): @@ -392,7 +392,7 @@ def set_bse_options(self, n_excitations=10, nit_bse=200): def dump_bse_data_in_gw_run(self, BSE_dump=True): """ Args: - BSE_dump: boolean + BSE_dump: bool Returns: set the "do_bse" variable to one in cell.in @@ -405,7 +405,7 @@ def dump_bse_data_in_gw_run(self, BSE_dump=True): def dump_tddft_data_in_gw_run(self, tddft_dump: bool = True): """ Args: - TDDFT_dump: boolean + TDDFT_dump: bool Returns: set the do_tddft variable to one in cell.in @@ -472,9 +472,7 @@ def molecule(self): def __str__(self): symbols = list(self._mol.symbol_set) - geometry = [] - for site in self._mol: - geometry.append(f" {site.x} {site.y} {site.z} {int(symbols.index(site.specie.symbol)) + 1}") + geometry = [f" {site.x} {site.y} {site.z} {symbols.index(site.specie.symbol) + 1}" for site in self._mol] t = Template( """# number of atoms and species @@ -775,13 +773,11 @@ def _parse_job(output): for line in output.split("\n"): if parse_total_time: - m = end_patt.search(line) - if m: + if match := end_patt.search(line): GW_results.update(end_normally=True) - m = total_time_patt.search(line) - if m: - GW_results.update(total_time=m.group(1)) + if match := total_time_patt.search(line): + GW_results.update(total_time=match[1]) if parse_gw_results: if line.find("Dumping eigen energies") != -1: @@ -789,29 +785,27 @@ def _parse_job(output): parse_gw_results = False continue - m = GW_BANDS_results_patt.search(line) - if m: + if match := GW_BANDS_results_patt.search(line): dct = {} dct.update( - band=m.group(1).strip(), - eKS=m.group(2), - eXX=m.group(3), - eQP_old=m.group(4), - z=m.group(5), - sigma_c_Linear=m.group(6), - eQP_Linear=m.group(7), - sigma_c_SCF=m.group(8), - eQP_SCF=m.group(9), + band=match[1].strip(), + eKS=match[2], + eXX=match[3], + eQP_old=match[4], + z=match[5], + sigma_c_Linear=match[6], + eQP_Linear=match[7], + sigma_c_SCF=match[8], + eQP_SCF=match[9], ) - GW_results[m.group(1).strip()] = dct + GW_results[match[1].strip()] = dct - n = GW_GAPS_results_patt.search(line) - if n: + if n := GW_GAPS_results_patt.search(line): dct = {} dct.update( - Egap_KS=n.group(1), - Egap_QP_Linear=n.group(2), - Egap_QP_SCF=n.group(3), + Egap_KS=n[1], + Egap_QP_Linear=n[2], + Egap_QP_SCF=n[3], ) GW_results["Gaps"] = dct @@ -858,13 +852,11 @@ def _parse_job(output): for line in output.split("\n"): if parse_total_time: - m = end_patt.search(line) - if m: + if match := end_patt.search(line): BSE_results.update(end_normally=True) - m = total_time_patt.search(line) - if m: - BSE_results.update(total_time=m.group(1)) + if match := total_time_patt.search(line): + BSE_results.update(total_time=match[1]) if parse_BSE_results: if line.find("FULL BSE main valence -> conduction transitions weight:") != -1: @@ -872,11 +864,10 @@ def _parse_job(output): parse_BSE_results = False continue - m = BSE_exitons_patt.search(line) - if m: + if match := BSE_exitons_patt.search(line): dct = {} - dct.update(bse_eig=m.group(2), osc_strength=m.group(3)) - BSE_results[str(m.group(1).strip())] = dct + dct.update(bse_eig=match[2], osc_strength=match[3]) + BSE_results[str(match[1].strip())] = dct if line.find("FULL BSE eig.(eV), osc. strength and dipoles:") != -1: parse_BSE_results = True diff --git a/pymatgen/io/gaussian.py b/pymatgen/io/gaussian.py index 9ac91c1efd4..98593b1815c 100644 --- a/pymatgen/io/gaussian.py +++ b/pymatgen/io/gaussian.py @@ -61,23 +61,21 @@ def read_route_line(route): route = route.replace(tok, "") for tok in route.split(): - if m := scrf_patt.match(tok): - route_params[m.group(1)] = m.group(2) + if match := scrf_patt.match(tok): + route_params[match.group(1)] = match.group(2) elif tok.upper() in ["#", "#N", "#P", "#T"]: # does not store # in route to avoid error in input dieze_tag = "#N" if tok == "#" else tok continue + elif match := re.match(multi_params_patt, tok.strip("#")): + pars = {} + for par in match.group(2).split(","): + p = par.split("=") + pars[p[0]] = None if len(p) == 1 else p[1] + route_params[match.group(1)] = pars else: - m = re.match(multi_params_patt, tok.strip("#")) - if m: - pars = {} - for par in m.group(2).split(","): - p = par.split("=") - pars[p[0]] = None if len(p) == 1 else p[1] - route_params[m.group(1)] = pars - else: - d = tok.strip("#").split("=") - route_params[d[0]] = None if len(d) == 1 else d[1] + d = tok.strip("#").split("=") + route_params[d[0]] = None if len(d) == 1 else d[1] return functional, basis_set, route_params, dieze_tag @@ -667,6 +665,7 @@ def _parse(self, filename): std_structures = [] geom_orientation = None opt_structures = [] + route_lower = {} with zopen(filename, mode="rt") as file: for line in file: @@ -674,8 +673,8 @@ def _parse(self, filename): if start_patt.search(line): parse_stage = 1 elif link0_patt.match(line): - m = link0_patt.match(line) - self.link0[m.group(1)] = m.group(2) + match = link0_patt.match(line) + self.link0[match.group(1)] = match.group(2) elif route_patt.search(line) or route_line != "": if set(line.strip()) == {"-"}: params = read_route_line(route_line) @@ -694,21 +693,21 @@ def _parse(self, filename): elif self.title == "": self.title = line.strip() elif charge_mul_patt.search(line): - m = charge_mul_patt.search(line) - self.charge = int(m.group(1)) - self.spin_multiplicity = int(m.group(2)) + match = charge_mul_patt.search(line) + self.charge = int(match.group(1)) + self.spin_multiplicity = int(match.group(2)) parse_stage = 2 elif parse_stage == 2: if self.is_pcm: self._check_pcm(line) if "freq" in route_lower and thermo_patt.search(line): - m = thermo_patt.search(line) - if m.group(1) == "Zero-point": - self.corrections["Zero-point"] = float(m.group(3)) + match = thermo_patt.search(line) + if match.group(1) == "Zero-point": + self.corrections["Zero-point"] = float(match.group(3)) else: - key = m.group(2).replace(" to ", "") - self.corrections[key] = float(m.group(3)) + key = match.group(2).replace(" to ", "") + self.corrections[key] = float(match.group(3)) if read_coord: [file.readline() for i in range(3)] @@ -728,8 +727,8 @@ def _parse(self, filename): std_structures.append(Molecule(sp, coords)) if parse_forces: - if m := forces_patt.search(line): - forces.extend([float(_v) for _v in m.groups()[2:5]]) + if match := forces_patt.search(line): + forces.extend([float(_v) for _v in match.groups()[2:5]]) elif forces_off_patt.search(line): self.cart_forces.append(forces) forces = [] @@ -737,7 +736,7 @@ def _parse(self, filename): # read molecular orbital eigenvalues if read_eigen: - if m := orbital_patt.search(line): + if match := orbital_patt.search(line): eigen_txt.append(line) else: read_eigen = False @@ -753,8 +752,8 @@ def _parse(self, filename): # read molecular orbital coefficients if (not num_basis_found) and num_basis_func_patt.search(line): - m = num_basis_func_patt.search(line) - self.num_basis_func = int(m.group(1)) + match = num_basis_func_patt.search(line) + self.num_basis_func = int(match.group(1)) num_basis_found = True elif read_mo: # build a matrix with all coefficients @@ -767,6 +766,8 @@ def _parse(self, filename): mat_mo[spin] = np.zeros((self.num_basis_func, self.num_basis_func)) nMO = 0 end_mo = False + atom_idx = None + coeffs = [] while nMO < self.num_basis_func and not end_mo: file.readline() file.readline() @@ -775,13 +776,13 @@ def _parse(self, filename): line = file.readline() # identify atom and OA labels - m = mo_coeff_name_patt.search(line) - if m.group(1).strip() != "": - atom_idx = int(m.group(2)) - 1 + match = mo_coeff_name_patt.search(line) + if match.group(1).strip() != "": + atom_idx = int(match.group(2)) - 1 # atname = m.group(3) - self.atom_basis_labels.append([m.group(4)]) + self.atom_basis_labels.append([match.group(4)]) else: - self.atom_basis_labels[atom_idx].append(m.group(4)) + self.atom_basis_labels[atom_idx].append(match.group(4)) # MO coefficients coeffs = [float(c) for c in float_patt.findall(line)] @@ -892,8 +893,8 @@ def _parse(self, filename): parse_bond_order = False elif termination_patt.search(line): - m = termination_patt.search(line) - if m.group(1) == "Normal": + match = termination_patt.search(line) + if match.group(1) == "Normal": self.properly_terminated = True terminated = True elif error_patt.search(line): @@ -901,25 +902,25 @@ def _parse(self, filename): "! Non-Optimized Parameters !": "Optimization error", "Convergence failure": "SCF convergence error", } - m = error_patt.search(line) - self.errors.append(error_defs[m.group(1)]) + match = error_patt.search(line) + self.errors.append(error_defs[match.group(1)]) elif num_elec_patt.search(line): - m = num_elec_patt.search(line) - self.electrons = (int(m.group(1)), int(m.group(2))) + match = num_elec_patt.search(line) + self.electrons = (int(match.group(1)), int(match.group(2))) elif (not self.is_pcm) and pcm_patt.search(line): self.is_pcm = True self.pcm = {} elif "freq" in route_lower and "opt" in route_lower and stat_type_patt.search(line): self.stationary_type = "Saddle" elif mp2_patt.search(line): - m = mp2_patt.search(line) - self.energies.append(float(m.group(1).replace("D", "E"))) + match = mp2_patt.search(line) + self.energies.append(float(match.group(1).replace("D", "E"))) elif oniom_patt.search(line): - m = oniom_patt.matcher(line) - self.energies.append(float(m.group(1))) + match = oniom_patt.matcher(line) + self.energies.append(float(match.group(1))) elif scf_patt.search(line): - m = scf_patt.search(line) - self.energies.append(float(m.group(1))) + match = scf_patt.search(line) + self.energies.append(float(match.group(1))) elif std_orientation_patt.search(line): standard_orientation = True geom_orientation = "standard" @@ -941,13 +942,12 @@ def _parse(self, filename): eigen_txt.append(line) read_eigen = True elif mulliken_patt.search(line): - mulliken_txt = [] read_mulliken = True elif not parse_forces and forces_on_patt.search(line): parse_forces = True elif freq_on_patt.search(line): parse_freq = True - [file.readline() for i in range(3)] + _ = [file.readline() for _i in range(3)] elif mo_coeff_patt.search(line): if "Alpha" in line: self.is_spin = True @@ -969,15 +969,16 @@ def _parse(self, filename): parse_bond_order = True if read_mulliken: + mulliken_txt = [] if not end_mulliken_patt.search(line): mulliken_txt.append(line) else: - m = end_mulliken_patt.search(line) + match = end_mulliken_patt.search(line) mulliken_charges = {} for line in mulliken_txt: if mulliken_charge_patt.search(line): - m = mulliken_charge_patt.search(line) - dic = {int(m.group(1)): [m.group(2), float(m.group(3))]} + match = mulliken_charge_patt.search(line) + dic = {int(match.group(1)): [match.group(2), float(match.group(3))]} mulliken_charges.update(dic) read_mulliken = False self.Mulliken_charges = mulliken_charges @@ -1012,6 +1013,7 @@ def _parse_hessian(self, file, structure): self.hessian = np.zeros((ndf, ndf)) j_indices = range(5) ndf_idx = 0 + vals = None while ndf_idx < ndf: for i in range(ndf_idx, ndf): line = file.readline() diff --git a/pymatgen/io/icet.py b/pymatgen/io/icet.py index 697ee19d0a1..ea153a511cd 100644 --- a/pymatgen/io/icet.py +++ b/pymatgen/io/icet.py @@ -16,7 +16,9 @@ from icet.tools.structure_generation import _get_sqs_cluster_vector, _validate_concentrations, generate_sqs from mchammer.calculators import compare_cluster_vectors except ImportError: - ClusterSpace = None + ClusterSpace = _validate_concentrations = _get_sqs_cluster_vector = compare_cluster_vectors = generate_sqs = ( + enumerate_structures + ) = None if TYPE_CHECKING: diff --git a/pymatgen/io/lammps/data.py b/pymatgen/io/lammps/data.py index ae4653f074c..0a045e4f455 100644 --- a/pymatgen/io/lammps/data.py +++ b/pymatgen/io/lammps/data.py @@ -658,16 +658,17 @@ def from_file(cls, filename: str, atom_style: str = "full", sort_id: bool = Fals bounds = {} for line in clean_lines(parts[0][1:]): # skip the 1st line match = None - for k, v in header_pattern.items(): # noqa: B007 - match = re.match(v, line) + key = None + for key, val in header_pattern.items(): # noqa: B007 + match = re.match(val, line) if match: break - if match and k in ["counts", "types"]: - header[k][match.group(2)] = int(match.group(1)) - elif match and k == "bounds": + if match and key in {"counts", "types"}: + header[key][match[2]] = int(match[1]) + elif match and key == "bounds": g = match.groups() bounds[g[2]] = [float(i) for i in g[:2]] - elif match and k == "tilt": + elif match and key == "tilt": header["tilt"] = [float(i) for i in match.groups()] header["bounds"] = [bounds.get(i, [-0.5, 0.5]) for i in "xyz"] box = LammpsBox(header["bounds"], header.get("tilt")) @@ -997,8 +998,8 @@ def from_bonding( dests, freq = np.unique(bond_list, return_counts=True) hubs = dests[np.where(freq > 1)].tolist() bond_arr = np.array(bond_list) + hub_spokes = {} if len(hubs) > 0: - hub_spokes = {} for hub in hubs: ix = np.any(np.isin(bond_arr, hub), axis=1) bonds = np.unique(bond_arr[ix]).tolist() diff --git a/pymatgen/io/lobster/inputs.py b/pymatgen/io/lobster/inputs.py index 495a28521d6..f0ec0d41ae1 100644 --- a/pymatgen/io/lobster/inputs.py +++ b/pymatgen/io/lobster/inputs.py @@ -131,7 +131,7 @@ def __init__(self, settingsdict: dict): # check for duplicates keys = [key.lower() for key in settingsdict] if len(keys) != len(set(keys)): - raise OSError("There are duplicates for the keywords! The program will stop here.") + raise KeyError("There are duplicates for the keywords!") self.update(settingsdict) def __setitem__(self, key, val): @@ -141,32 +141,26 @@ def __setitem__(self, key, val): leading and trailing white spaces. Similar to INCAR class. """ # due to the missing case sensitivity of lobster, the following code is necessary - found = False - for key_here in self: - if key.strip().lower() == key_here.lower(): - new_key = key_here - found = True - if not found: - new_key = key + new_key = next((key_here for key_here in self if key.strip().lower() == key_here.lower()), key) + if new_key.lower() not in [element.lower() for element in Lobsterin.AVAILABLE_KEYWORDS]: - raise ValueError("Key is currently not available") + raise KeyError("Key is currently not available") super().__setitem__(new_key, val.strip() if isinstance(val, str) else val) def __getitem__(self, item): """Implements getitem from dict to avoid problems with cases.""" - found = False - for key_here in self: - if item.strip().lower() == key_here.lower(): - new_key = key_here - found = True - if not found: - new_key = item + new_item = next((key_here for key_here in self if item.strip().lower() == key_here.lower()), item) + + if new_item.lower() not in [element.lower() for element in Lobsterin.AVAILABLE_KEYWORDS]: + raise KeyError("Key is currently not available") - return super().__getitem__(new_key) + return super().__getitem__(new_item) def __delitem__(self, key): - del self.data[key.lower()] + new_key = next((key_here for key_here in self if key.strip().lower() == key_here.lower()), key) + + del self.data[new_key] def diff(self, other): """ @@ -184,50 +178,47 @@ def diff(self, other): key_list_others = [element.lower() for element in other] for k1, v1 in self.items(): - k1lower = k1.lower() - if k1lower not in key_list_others: - different_param[k1.upper()] = {"lobsterin1": v1, "lobsterin2": None} - else: - for key_here in other: - if k1.lower() == key_here.lower(): - new_key = key_here - - if isinstance(v1, str): - if v1.strip().lower() != other[new_key].strip().lower(): - different_param[k1.upper()] = { - "lobsterin1": v1, - "lobsterin2": other[new_key], - } - else: - similar_param[k1.upper()] = v1 - elif isinstance(v1, list): - new_set1 = {element.strip().lower() for element in v1} - new_set2 = {element.strip().lower() for element in other[new_key]} - if new_set1 != new_set2: - different_param[k1.upper()] = { - "lobsterin1": v1, - "lobsterin2": other[new_key], - } - elif v1 != other[new_key]: - different_param[k1.upper()] = { + k1_lower = k1.lower() + k1_in_other = next((key_here for key_here in other if key_here.lower() == k1_lower), k1_lower) + if k1_lower not in key_list_others: + different_param[k1.lower()] = {"lobsterin1": v1, "lobsterin2": None} + elif isinstance(v1, str): + if v1.strip().lower() != other[k1_lower].strip().lower(): + different_param[k1.lower()] = { "lobsterin1": v1, - "lobsterin2": other[new_key], + "lobsterin2": other[k1_in_other], } else: - similar_param[k1.upper()] = v1 + similar_param[k1.lower()] = v1 + elif isinstance(v1, list): + new_set1 = {element.strip().lower() for element in v1} + new_set2 = {element.strip().lower() for element in other[k1_in_other]} + if new_set1 != new_set2: + different_param[k1.lower()] = { + "lobsterin1": v1, + "lobsterin2": other[k1_in_other], + } + elif v1 != other[k1_lower]: + different_param[k1.lower()] = { + "lobsterin1": v1, + "lobsterin2": other[k1_in_other], + } + else: + similar_param[k1.lower()] = v1 for k2, v2 in other.items(): - if k2.upper() not in similar_param and k2.upper() not in different_param: - for key_here in self: - new_key = key_here if k2.lower() == key_here.lower() else k2 - if new_key not in self: - different_param[k2.upper()] = {"lobsterin1": None, "lobsterin2": v2} + if ( + k2.lower() not in similar_param + and k2.lower() not in different_param + and k2.lower() not in [key.lower() for key in self] + ): + different_param[k2.lower()] = {"lobsterin1": None, "lobsterin2": v2} return {"Same": similar_param, "Different": different_param} def _get_nbands(self, structure: Structure): """Get number of bands.""" if self.get("basisfunctions") is None: - raise OSError("No basis functions are provided. The program cannot calculate nbands.") + raise ValueError("No basis functions are provided. The program cannot calculate nbands.") basis_functions: list[str] = [] for string_basis in self["basisfunctions"]: @@ -263,13 +254,10 @@ def write_lobsterin(self, path="lobsterin", overwritedict=None): # has to search first if entry is already in Lobsterindict (due to case insensitivity) if overwritedict is not None: for key, entry in overwritedict.items(): - found = False + self[key] = entry for key2 in self: if key.lower() == key2.lower(): self[key2] = entry - found = True - if not found: - self[key] = entry filename = path @@ -282,9 +270,7 @@ def write_lobsterin(self, path="lobsterin", overwritedict=None): # checks if entry is True or False for key_here in self: if key.lower() == key_here.lower(): - new_key = key_here - if self.get(new_key): - file.write(key + "\n") + file.write(key + "\n") elif key.lower() in [element.lower() for element in Lobsterin.STRING_KEYWORDS]: file.write(f"{key} {self.get(key)}\n") elif key.lower() in [element.lower() for element in Lobsterin.LISTKEYWORDS]: @@ -367,18 +353,18 @@ def get_basis( atom_types_potcar = [name.split("_")[0] for name in potcar_names] if set(structure.symbol_set) != set(atom_types_potcar): - raise OSError("Your POSCAR does not correspond to your POTCAR!") - BASIS = loadfn(address_basis_file)["BASIS"] + raise ValueError("Your POSCAR does not correspond to your POTCAR!") + basis = loadfn(address_basis_file)["BASIS"] basis_functions = [] list_forin = [] - for idx, basis in enumerate(potcar_names): - if basis not in BASIS: + for idx, name in enumerate(potcar_names): + if name not in basis: raise ValueError( - f"You have to provide the basis for {basis} manually. We don't have any information on this POTCAR." + f"Missing basis information for POTCAR symbol: {name}. Please provide the basis manually." ) - basis_functions.append(BASIS[basis].split()) - list_forin.append(f"{atom_types_potcar[idx]} {BASIS[basis]}") + basis_functions.append(basis[name].split()) + list_forin.append(f"{atom_types_potcar[idx]} {basis[name]}") return list_forin @staticmethod @@ -551,10 +537,9 @@ def write_KPOINTS( kpts.append(f) weights.append(0.0) all_labels.append(labels[k]) - ISYM = isym - comment = f"{ISYM=}, grid: {mesh} plus kpoint path" if line_mode else f"{ISYM=}, grid: {mesh}" + comment = f"{isym=}, grid: {mesh} plus kpoint path" if line_mode else f"{isym=}, grid: {mesh}" - KpointObject = Kpoints( + kpoint_object = Kpoints( comment=comment, style=Kpoints.supported_modes.Reciprocal, num_kpts=len(kpts), @@ -563,7 +548,7 @@ def write_KPOINTS( labels=all_labels, ) - KpointObject.write_file(filename=KPOINTS_output) + kpoint_object.write_file(filename=KPOINTS_output) @classmethod def from_file(cls, lobsterin: str) -> Self: @@ -577,8 +562,8 @@ def from_file(cls, lobsterin: str) -> Self: with zopen(lobsterin, mode="rt") as file: data = file.read().split("\n") if len(data) == 0: - raise OSError("lobsterin file contains no data.") - Lobsterindict: dict[str, Any] = {} + raise RuntimeError("lobsterin file contains no data.") + lobsterin_dict: dict[str, Any] = {} for datum in data: # Remove all comments @@ -591,22 +576,22 @@ def from_file(cls, lobsterin: str) -> Self: # check which type of keyword this is, handle accordingly if key_word[0].lower() not in [datum2.lower() for datum2 in Lobsterin.LISTKEYWORDS]: if key_word[0].lower() not in [datum2.lower() for datum2 in Lobsterin.FLOAT_KEYWORDS]: - if key_word[0].lower() not in Lobsterindict: - Lobsterindict[key_word[0].lower()] = " ".join(key_word[1:]) + if key_word[0].lower() not in lobsterin_dict: + lobsterin_dict[key_word[0].lower()] = " ".join(key_word[1:]) else: raise ValueError(f"Same keyword {key_word[0].lower()} twice!") - elif key_word[0].lower() not in Lobsterindict: - Lobsterindict[key_word[0].lower()] = float(key_word[1]) + elif key_word[0].lower() not in lobsterin_dict: + lobsterin_dict[key_word[0].lower()] = float(key_word[1]) else: raise ValueError(f"Same keyword {key_word[0].lower()} twice!") - elif key_word[0].lower() not in Lobsterindict: - Lobsterindict[key_word[0].lower()] = [" ".join(key_word[1:])] + elif key_word[0].lower() not in lobsterin_dict: + lobsterin_dict[key_word[0].lower()] = [" ".join(key_word[1:])] else: - Lobsterindict[key_word[0].lower()].append(" ".join(key_word[1:])) + lobsterin_dict[key_word[0].lower()].append(" ".join(key_word[1:])) elif len(key_word) > 0: - Lobsterindict[key_word[0].lower()] = True + lobsterin_dict[key_word[0].lower()] = True - return cls(Lobsterindict) + return cls(lobsterin_dict) @staticmethod def _get_potcar_symbols(POTCAR_input: str) -> list: @@ -622,7 +607,7 @@ def _get_potcar_symbols(POTCAR_input: str) -> list: potcar = Potcar.from_file(POTCAR_input) for pot in potcar: if pot.potential_type != "PAW": - raise OSError("Lobster only works with PAW! Use different POTCARs") + raise ValueError("Lobster only works with PAW! Use different POTCARs") # Warning about a bug in lobster-4.1.0 with zopen(POTCAR_input, mode="r") as file: @@ -639,7 +624,7 @@ def _get_potcar_symbols(POTCAR_input: str) -> list: ) if potcar.functional != "PBE": - raise OSError("We only have BASIS options for PBE so far") + raise RuntimeError("We only have BASIS options for PBE so far") return [name["symbol"] for name in potcar.spec] @@ -698,7 +683,7 @@ def standard_calculations_from_vasp_files( ]: raise ValueError("The option is not valid!") - Lobsterindict: dict[str, Any] = { + lobsterin_dict: dict[str, Any] = { # this basis set covers most elements "basisSet": "pbeVaspFit2015", # energies around e-fermi @@ -717,95 +702,95 @@ def standard_calculations_from_vasp_files( "standard_with_fatband", }: # every interaction with a distance of 6.0 is checked - Lobsterindict["cohpGenerator"] = "from 0.1 to 6.0 orbitalwise" + lobsterin_dict["cohpGenerator"] = "from 0.1 to 6.0 orbitalwise" # the projection is saved - Lobsterindict["saveProjectionToFile"] = True + lobsterin_dict["saveProjectionToFile"] = True if option == "standard_from_projection": - Lobsterindict["cohpGenerator"] = "from 0.1 to 6.0 orbitalwise" - Lobsterindict["loadProjectionFromFile"] = True + lobsterin_dict["cohpGenerator"] = "from 0.1 to 6.0 orbitalwise" + lobsterin_dict["loadProjectionFromFile"] = True if option == "standard_with_energy_range_from_vasprun": Vr = Vasprun(Vasprun_output) - Lobsterindict["COHPstartEnergy"] = round(min(Vr.complete_dos.energies - Vr.complete_dos.efermi), 4) - Lobsterindict["COHPendEnergy"] = round(max(Vr.complete_dos.energies - Vr.complete_dos.efermi), 4) - Lobsterindict["COHPSteps"] = len(Vr.complete_dos.energies) + lobsterin_dict["COHPstartEnergy"] = round(min(Vr.complete_dos.energies - Vr.complete_dos.efermi), 4) + lobsterin_dict["COHPendEnergy"] = round(max(Vr.complete_dos.energies - Vr.complete_dos.efermi), 4) + lobsterin_dict["COHPSteps"] = len(Vr.complete_dos.energies) # TODO: add cobi here! might be relevant lobster version if option == "onlycohp": - Lobsterindict["skipdos"] = True - Lobsterindict["skipcoop"] = True - Lobsterindict["skipPopulationAnalysis"] = True - Lobsterindict["skipGrossPopulation"] = True + lobsterin_dict["skipdos"] = True + lobsterin_dict["skipcoop"] = True + lobsterin_dict["skipPopulationAnalysis"] = True + lobsterin_dict["skipGrossPopulation"] = True # lobster-4.1.0 - Lobsterindict["skipcobi"] = True - Lobsterindict["skipMadelungEnergy"] = True + lobsterin_dict["skipcobi"] = True + lobsterin_dict["skipMadelungEnergy"] = True if option == "onlycoop": - Lobsterindict["skipdos"] = True - Lobsterindict["skipcohp"] = True - Lobsterindict["skipPopulationAnalysis"] = True - Lobsterindict["skipGrossPopulation"] = True + lobsterin_dict["skipdos"] = True + lobsterin_dict["skipcohp"] = True + lobsterin_dict["skipPopulationAnalysis"] = True + lobsterin_dict["skipGrossPopulation"] = True # lobster-4.1.0 - Lobsterindict["skipcobi"] = True - Lobsterindict["skipMadelungEnergy"] = True + lobsterin_dict["skipcobi"] = True + lobsterin_dict["skipMadelungEnergy"] = True if option == "onlycohpcoop": - Lobsterindict["skipdos"] = True - Lobsterindict["skipPopulationAnalysis"] = True - Lobsterindict["skipGrossPopulation"] = True + lobsterin_dict["skipdos"] = True + lobsterin_dict["skipPopulationAnalysis"] = True + lobsterin_dict["skipGrossPopulation"] = True # lobster-4.1.0 - Lobsterindict["skipcobi"] = True - Lobsterindict["skipMadelungEnergy"] = True + lobsterin_dict["skipcobi"] = True + lobsterin_dict["skipMadelungEnergy"] = True if option == "onlycohpcoopcobi": - Lobsterindict["skipdos"] = True - Lobsterindict["skipPopulationAnalysis"] = True - Lobsterindict["skipGrossPopulation"] = True - Lobsterindict["skipMadelungEnergy"] = True + lobsterin_dict["skipdos"] = True + lobsterin_dict["skipPopulationAnalysis"] = True + lobsterin_dict["skipGrossPopulation"] = True + lobsterin_dict["skipMadelungEnergy"] = True if option == "onlydos": - Lobsterindict["skipcohp"] = True - Lobsterindict["skipcoop"] = True - Lobsterindict["skipPopulationAnalysis"] = True - Lobsterindict["skipGrossPopulation"] = True + lobsterin_dict["skipcohp"] = True + lobsterin_dict["skipcoop"] = True + lobsterin_dict["skipPopulationAnalysis"] = True + lobsterin_dict["skipGrossPopulation"] = True # lobster-4.1.0 - Lobsterindict["skipcobi"] = True - Lobsterindict["skipMadelungEnergy"] = True + lobsterin_dict["skipcobi"] = True + lobsterin_dict["skipMadelungEnergy"] = True if option == "onlyprojection": - Lobsterindict["skipdos"] = True - Lobsterindict["skipcohp"] = True - Lobsterindict["skipcoop"] = True - Lobsterindict["skipPopulationAnalysis"] = True - Lobsterindict["skipGrossPopulation"] = True - Lobsterindict["saveProjectionToFile"] = True + lobsterin_dict["skipdos"] = True + lobsterin_dict["skipcohp"] = True + lobsterin_dict["skipcoop"] = True + lobsterin_dict["skipPopulationAnalysis"] = True + lobsterin_dict["skipGrossPopulation"] = True + lobsterin_dict["saveProjectionToFile"] = True # lobster-4.1.0 - Lobsterindict["skipcobi"] = True - Lobsterindict["skipMadelungEnergy"] = True + lobsterin_dict["skipcobi"] = True + lobsterin_dict["skipMadelungEnergy"] = True if option == "onlycobi": - Lobsterindict["skipdos"] = True - Lobsterindict["skipcohp"] = True - Lobsterindict["skipPopulationAnalysis"] = True - Lobsterindict["skipGrossPopulation"] = True + lobsterin_dict["skipdos"] = True + lobsterin_dict["skipcohp"] = True + lobsterin_dict["skipPopulationAnalysis"] = True + lobsterin_dict["skipGrossPopulation"] = True # lobster-4.1.0 - Lobsterindict["skipcobi"] = True - Lobsterindict["skipMadelungEnergy"] = True + lobsterin_dict["skipcobi"] = True + lobsterin_dict["skipMadelungEnergy"] = True if option == "onlymadelung": - Lobsterindict["skipdos"] = True - Lobsterindict["skipcohp"] = True - Lobsterindict["skipcoop"] = True - Lobsterindict["skipPopulationAnalysis"] = True - Lobsterindict["skipGrossPopulation"] = True - Lobsterindict["saveProjectionToFile"] = True + lobsterin_dict["skipdos"] = True + lobsterin_dict["skipcohp"] = True + lobsterin_dict["skipcoop"] = True + lobsterin_dict["skipPopulationAnalysis"] = True + lobsterin_dict["skipGrossPopulation"] = True + lobsterin_dict["saveProjectionToFile"] = True # lobster-4.1.0 - Lobsterindict["skipcobi"] = True + lobsterin_dict["skipcobi"] = True incar = Incar.from_file(INCAR_input) if incar["ISMEAR"] == 0: - Lobsterindict["gaussianSmearingWidth"] = incar["SIGMA"] + lobsterin_dict["gaussianSmearingWidth"] = incar["SIGMA"] if incar["ISMEAR"] != 0 and option == "standard_with_fatband": raise ValueError("ISMEAR has to be 0 for a fatband calculation with Lobster") if dict_for_basis is not None: @@ -819,11 +804,11 @@ def standard_calculations_from_vasp_files( basis = Lobsterin.get_basis(structure=Structure.from_file(POSCAR_input), potcar_symbols=potcar_names) else: raise ValueError("basis cannot be generated") - Lobsterindict["basisfunctions"] = basis + lobsterin_dict["basisfunctions"] = basis if option == "standard_with_fatband": - Lobsterindict["createFatband"] = basis + lobsterin_dict["createFatband"] = basis - return cls(Lobsterindict) + return cls(lobsterin_dict) def get_all_possible_basis_combinations(min_basis: list, max_basis: list) -> list: diff --git a/pymatgen/io/lobster/lobsterenv.py b/pymatgen/io/lobster/lobsterenv.py index 1c2c124162f..342b7bc0917 100644 --- a/pymatgen/io/lobster/lobsterenv.py +++ b/pymatgen/io/lobster/lobsterenv.py @@ -162,6 +162,8 @@ def __init__( elif self.id_blist_sg2.lower() == "icobi": are_coops_id2 = False are_cobis_id2 = True + else: + raise ValueError("only icoops and icobis can be added") self.bonding_list_2 = Icohplist( filename=self.filename_blist_sg2, @@ -845,7 +847,7 @@ def _find_environments(self, additional_condition, lowerlimit, upperlimit, only_ list_icohps = [] list_lengths = [] list_keys = [] - for idx in range(len(self.structure)): + for idx, site in enumerate(self.structure): icohps = self._get_icohps( icohpcollection=self.Icohpcollection, isite=idx, @@ -858,7 +860,7 @@ def _find_environments(self, additional_condition, lowerlimit, upperlimit, only_ keys_from_ICOHPs, lengths_from_ICOHPs, neighbors_from_ICOHPs, selected_ICOHPs = additional_conds if len(neighbors_from_ICOHPs) > 0: - centralsite = self.structure[idx] + centralsite = site neighbors_by_distance_start = self.structure.get_sites_in_sphere( pt=centralsite.coords, @@ -960,6 +962,7 @@ def _find_relevant_atoms_additional_condition(self, isite, icohps, additional_co atomnr2 = self._get_atomnumber(icohp._atom2) # test additional conditions + val1 = val2 = None if additional_condition in (1, 3, 5, 6): val1 = self.valences[atomnr1] val2 = self.valences[atomnr2] @@ -1168,6 +1171,7 @@ def _get_limit_from_extremum( tuple[float, float]: [-inf, min(strongest_icohp*0.15,-noise_cutoff)] / [max(strongest_icohp*0.15, noise_cutoff), inf] """ + extremum_based = None if not adapt_extremum_to_add_cond or additional_condition == 0: extremum_based = icohpcollection.extremum_icohpvalue(summed_spin_channels=True) * percentage diff --git a/pymatgen/io/lobster/outputs.py b/pymatgen/io/lobster/outputs.py index 29b09b55bb3..202854d64fa 100644 --- a/pymatgen/io/lobster/outputs.py +++ b/pymatgen/io/lobster/outputs.py @@ -12,6 +12,7 @@ import collections import fnmatch +import itertools import os import re import warnings @@ -124,7 +125,6 @@ def __init__( if not self.are_multi_center_cobis: # The COHP data start in row num_bonds + 3 data = np.array([np.array(row.split(), dtype=float) for row in contents[num_bonds + 3 :]]).transpose() - self.energies = data[0] cohp_data = { "average": { "COHP": {spin: data[1 + 2 * s * (num_bonds + 1)] for s, spin in enumerate(spins)}, @@ -134,7 +134,8 @@ def __init__( else: # The COBI data start in row num_bonds + 3 if multi-center cobis exist data = np.array([np.array(row.split(), dtype=float) for row in contents[num_bonds + 3 :]]).transpose() - self.energies = data[0] + + self.energies = data[0] orb_cohp: dict[str, Any] = {} # present for Lobster versions older than Lobster 2.2.0 @@ -385,7 +386,7 @@ def __init__( with zopen(filename, mode="rt") as file: data = file.read().split("\n")[1:-1] if len(data) == 0: - raise OSError("ICOHPLIST file contains no data.") + raise RuntimeError("ICOHPLIST file contains no data.") # Which Lobster version? if len(data[0].split()) == 8: @@ -422,7 +423,7 @@ def __init__( # TODO: adapt this for orbital-wise stuff n_bonds = len(data_without_orbitals) // 2 if n_bonds == 0: - raise OSError("ICOHPLIST file contains no data.") + raise RuntimeError("ICOHPLIST file contains no data.") else: n_bonds = len(data_without_orbitals) @@ -549,7 +550,7 @@ def __init__(self, filename: str | None = "NcICOBILIST.lobster"): # LOBSTER < 4 with zopen(filename, mode="rt") as file: # type:ignore data = file.read().split("\n")[1:-1] if len(data) == 0: - raise OSError("NcICOBILIST file contains no data.") + raise RuntimeError("NcICOBILIST file contains no data.") # If the calculation is spin-polarized, the line in the middle # of the file will be another header line. @@ -579,7 +580,7 @@ def __init__(self, filename: str | None = "NcICOBILIST.lobster"): # LOBSTER < 4 # TODO: adapt this for orbitalwise case n_bonds = len(data_without_orbitals) // 2 if n_bonds == 0: - raise OSError("NcICOBILIST file contains no data.") + raise RuntimeError("NcICOBILIST file contains no data.") else: n_bonds = len(data_without_orbitals) @@ -820,7 +821,7 @@ def __init__( with zopen(filename, mode="rt") as file: data = file.read().split("\n")[3:-3] if len(data) == 0: - raise OSError("CHARGES file contains no data.") + raise RuntimeError("CHARGES file contains no data.") self.num_atoms = len(data) for atom in range(self.num_atoms): @@ -943,7 +944,7 @@ def __init__(self, filename: str | None, **kwargs) -> None: with zopen(filename, mode="rt") as file: # read in file data = file.read().split("\n") if len(data) == 0: - raise OSError("lobsterout does not contain any data") + raise RuntimeError("lobsterout does not contain any data") # check if Lobster starts from a projection self.is_restart_from_projection = "loading projection from projectionData.lobster..." in data @@ -964,11 +965,11 @@ def __init__(self, filename: str | None, **kwargs) -> None: self.basis_functions = basisfunctions wall_time, user_time, sys_time = self._get_timing(data=data) - timing = {} - timing["wall_time"] = wall_time - timing["user_time"] = user_time - timing["sys_time"] = sys_time - self.timing = timing + self.timing = { + "wall_time": wall_time, + "user_time": user_time, + "sys_time": sys_time, + } warninglines = self._get_all_warning_lines(data=data) self.warning_lines = warninglines @@ -1084,15 +1085,13 @@ def _get_dft_program(data): @staticmethod def _get_number_of_spins(data): - if "spillings for spin channel 2" in data: - return 2 - return 1 + return 2 if "spillings for spin channel 2" in data else 1 @staticmethod def _get_threads(data): for row in data: splitrow = row.split() - if len(splitrow) > 11 and ((splitrow[11]) == "threads" or (splitrow[11] == "thread")): + if len(splitrow) > 11 and splitrow[11] in {"threads", "thread"}: return splitrow[10] raise ValueError("Threads not found.") @@ -1157,9 +1156,9 @@ def _get_timing(data): if "wall" in splitrow: wall_time = splitrow[2:10] if "user" in splitrow: - user_time = splitrow[0:8] + user_time = splitrow[:8] if "sys" in splitrow: - sys_time = splitrow[0:8] + sys_time = splitrow[:8] wall_time_dict = {"h": wall_time[0], "min": wall_time[2], "s": wall_time[4], "ms": wall_time[6]} user_time_dict = {"h": user_time[0], "min": user_time[2], "s": user_time[4], "ms": user_time[6]} @@ -1267,6 +1266,7 @@ def __init__( atom_type = [] atom_names = [] orbital_names = [] + parameters = [] if not isinstance(filenames, list) or filenames is None: filenames_new = [] @@ -1308,7 +1308,9 @@ def __init__( "present" ) - kpoints_array = [] + kpoints_array: list = [] + eigenvals: dict = {} + p_eigenvals: dict = {} for ifilename, filename in enumerate(filenames): with zopen(filename, mode="rt") as file: contents = file.read().split("\n") @@ -1331,7 +1333,7 @@ def __init__( self.is_spinpolarized = len(linenumbers) == 2 if ifilename == 0: - eigenvals = {} # type: dict + eigenvals = {} eigenvals[Spin.up] = [ [collections.defaultdict(float) for _ in range(self.number_kpts)] for _ in range(self.nbands) ] @@ -1340,7 +1342,7 @@ def __init__( [collections.defaultdict(float) for _ in range(self.number_kpts)] for _ in range(self.nbands) ] - p_eigenvals = {} # type: dict + p_eigenvals = {} p_eigenvals[Spin.up] = [ [ { @@ -1366,6 +1368,7 @@ def __init__( idx_kpt = -1 linenumber = 0 + iband = 0 for line in contents[1:-1]: if line.split()[0] == "#": KPOINT = np.array( @@ -1486,7 +1489,7 @@ def _read(self, contents: list, spin_numbers: list): kpoint = line.split(" ") kpoint_array = [] for kpointel in kpoint: - if kpointel not in ["at", "k-point", ""]: + if kpointel not in {"at", "k-point", ""}: kpoint_array += [float(kpointel)] elif "maxDeviation" in line: @@ -1559,15 +1562,16 @@ def has_good_quality_check_occupied_bands( for matrix in self.band_overlaps_dict[Spin.down]["matrices"]: for iband1, band1 in enumerate(matrix): for iband2, band2 in enumerate(band1): - if number_occ_bands_spin_down is not None: - if iband1 < number_occ_bands_spin_down and iband2 < number_occ_bands_spin_down: - if iband1 == iband2: - if abs(band2 - 1.0).all() > limit_deviation: - return False - elif band2.all() > limit_deviation: - return False - else: + if number_occ_bands_spin_down is None: raise ValueError("number_occ_bands_spin_down has to be specified") + + if iband1 < number_occ_bands_spin_down and iband2 < number_occ_bands_spin_down: + if iband1 == iband2: + if abs(band2 - 1.0).all() > limit_deviation: + return False + elif band2.all() > limit_deviation: + return False + return True @property @@ -1686,10 +1690,9 @@ def _parse_file(filename): real += [float(splitline[4])] imaginary += [float(splitline[5])] - if not len(real) == grid[0] * grid[1] * grid[2]: - raise ValueError("Something went wrong while reading the file") - if not len(imaginary) == grid[0] * grid[1] * grid[2]: + if len(real) != grid[0] * grid[1] * grid[2] or len(imaginary) != grid[0] * grid[1] * grid[2]: raise ValueError("Something went wrong while reading the file") + return grid, points, real, imaginary, distance def set_volumetric_data(self, grid, structure): @@ -1713,36 +1716,31 @@ def set_volumetric_data(self, grid, structure): new_imaginary = [] new_density = [] - runner = 0 - for x in range(Nx + 1): - for y in range(Ny + 1): - for z in range(Nz + 1): - x_here = x / float(Nx) * a[0] + y / float(Ny) * b[0] + z / float(Nz) * c[0] - y_here = x / float(Nx) * a[1] + y / float(Ny) * b[1] + z / float(Nz) * c[1] - z_here = x / float(Nx) * a[2] + y / float(Ny) * b[2] + z / float(Nz) * c[2] - - if x != Nx and y != Ny and z != Nz: - if ( - not np.isclose(self.points[runner][0], x_here, 1e-3) - and not np.isclose(self.points[runner][1], y_here, 1e-3) - and not np.isclose(self.points[runner][2], z_here, 1e-3) - ): - raise ValueError( - "The provided wavefunction from Lobster does not contain all relevant" - " points. " - "Please use a line similar to: printLCAORealSpaceWavefunction kpoint 1 " - "coordinates 0.0 0.0 0.0 coordinates 1.0 1.0 1.0 box bandlist 1 " - ) - - new_x += [x_here] - new_y += [y_here] - new_z += [z_here] - - new_real += [self.real[runner]] - new_imaginary += [self.imaginary[runner]] - new_density += [self.real[runner] ** 2 + self.imaginary[runner] ** 2] - - runner += 1 + for runner, (x, y, z) in enumerate(itertools.product(range(Nx + 1), range(Ny + 1), range(Nz + 1))): + x_here = x / float(Nx) * a[0] + y / float(Ny) * b[0] + z / float(Nz) * c[0] + y_here = x / float(Nx) * a[1] + y / float(Ny) * b[1] + z / float(Nz) * c[1] + z_here = x / float(Nx) * a[2] + y / float(Ny) * b[2] + z / float(Nz) * c[2] + + if x != Nx and y != Ny and z != Nz: + if ( + not np.isclose(self.points[runner][0], x_here, 1e-3) + and not np.isclose(self.points[runner][1], y_here, 1e-3) + and not np.isclose(self.points[runner][2], z_here, 1e-3) + ): + raise ValueError( + "The provided wavefunction from Lobster does not contain all relevant" + " points. " + "Please use a line similar to: printLCAORealSpaceWavefunction kpoint 1 " + "coordinates 0.0 0.0 0.0 coordinates 1.0 1.0 1.0 box bandlist 1 " + ) + + new_x += [x_here] + new_y += [y_here] + new_z += [z_here] + + new_real += [self.real[runner]] + new_imaginary += [self.imaginary[runner]] + new_density += [self.real[runner] ** 2 + self.imaginary[runner] ** 2] self.final_real = np.reshape(new_real, [Nx, Ny, Nz]) self.final_imaginary = np.reshape(new_imaginary, [Nx, Ny, Nz]) @@ -1844,7 +1842,7 @@ def __init__( with zopen(filename, mode="rt") as file: data = file.read().split("\n")[5] if len(data) == 0: - raise OSError("MadelungEnergies file contains no data.") + raise RuntimeError("MadelungEnergies file contains no data.") line = data.split() self._filename = filename self.ewald_splitting = float(line[0]) @@ -1924,7 +1922,7 @@ def __init__( with zopen(filename, mode="rt") as file: data = file.read().split("\n") if len(data) == 0: - raise OSError("SitePotentials file contains no data.") + raise RuntimeError("SitePotentials file contains no data.") self._filename = filename self.ewald_splitting = float(data[0].split()[9]) @@ -2088,7 +2086,7 @@ def __init__(self, e_fermi=None, filename: str = "hamiltonMatrices.lobster"): with zopen(self._filename, mode="rt") as file: file_data = file.readlines() if len(file_data) == 0: - raise OSError("Please check provided input file, it seems to be empty") + raise RuntimeError("Please check provided input file, it seems to be empty") pattern_coeff_hamil_trans = r"(\d+)\s+kpoint\s+(\d+)" # regex pattern to extract spin and k-point number pattern_overlap = r"kpoint\s+(\d+)" # regex pattern to extract k-point number diff --git a/pymatgen/io/nwchem.py b/pymatgen/io/nwchem.py index 848d2a4c190..8d7921800d7 100644 --- a/pymatgen/io/nwchem.py +++ b/pymatgen/io/nwchem.py @@ -438,8 +438,10 @@ def from_str(cls, string_input: str) -> Self: tasks = [] charge = spin_multiplicity = title = basis_set = None basis_set_option = None + mol = None theory_directives: dict[str, dict[str, str]] = {} geom_options = symmetry_options = memory_options = None + lines = string_input.strip().split("\n") while len(lines) > 0: line = lines.pop(0).strip() diff --git a/pymatgen/io/packmol.py b/pymatgen/io/packmol.py index 7c535346207..7bd24d85df1 100644 --- a/pymatgen/io/packmol.py +++ b/pymatgen/io/packmol.py @@ -199,6 +199,7 @@ def get_input_set( # type: ignore box_list = f"0.0 0.0 0.0 {box_length:.1f} {box_length:.1f} {box_length:.1f}" for d in molecules: + mol = None if isinstance(d["coords"], str): mol = Molecule.from_file(d["coords"]) elif isinstance(d["coords"], Path): @@ -210,7 +211,7 @@ def get_input_set( # type: ignore raise ValueError("Molecule cannot be None.") fname = f"packmol_{d['name']}.xyz" - mapping.update({fname: mol.to(fmt="xyz")}) + mapping[fname] = mol.to(fmt="xyz") if " " in str(fname): # NOTE - double quotes are deliberately used inside the f-string here, do not change # fmt: off diff --git a/pymatgen/io/pwscf.py b/pymatgen/io/pwscf.py index 124614f2a67..b68d7df1648 100644 --- a/pymatgen/io/pwscf.py +++ b/pymatgen/io/pwscf.py @@ -139,7 +139,7 @@ def to_str(v): out.append("ATOMIC_SPECIES") for k, v in sorted(site_descriptions.items(), key=lambda i: i[0]): - e = re.match(r"[A-Z][a-z]?", k).group(0) + e = re.match(r"[A-Z][a-z]?", k)[0] p = v if self.pseudo is not None else v["pseudo"] out.append(f" {k} {Element(e).atomic_mass:.4f} {p}") @@ -220,7 +220,7 @@ def write_file(self, filename): Args: filename (str): The string filename to output to. """ - with open(filename, mode="w") as file: + with open(filename, mode="w", encoding="utf-8") as file: file.write(str(self)) @classmethod @@ -279,17 +279,21 @@ def input_mode(line): structure = None site_properties: dict[str, list] = {"pseudo": []} mode = None + kpoints_mode = None + kpoints_grid = (1, 1, 1) + kpoints_shift = (0, 0, 0) + coords_are_cartesian = False + for line in lines: mode = input_mode(line) if mode is None: pass elif mode[0] == "sections": section = mode[1] - m = re.match(r"(\w+)\(?(\d*?)\)?\s*=\s*(.*)", line) - if m: - key = m.group(1).strip() - key_ = m.group(2).strip() - val = m.group(3).strip() + if match := re.match(r"(\w+)\(?(\d*?)\)?\s*=\s*(.*)", line): + key = match[1].strip() + key_ = match[2].strip() + val = match[3].strip() if key_ != "": if sections[section].get(key) is None: val_ = [0.0] * 20 # MAX NTYP DEFINITION @@ -303,37 +307,34 @@ def input_mode(line): sections[section][key] = PWInput.proc_val(key, val) elif mode[0] == "pseudo": - m = re.match(r"(\w+)\s+(\d*.\d*)\s+(.*)", line) - if m: - pseudo[m.group(1).strip()] = m.group(3).strip() + if match := re.match(r"(\w+)\s+(\d*.\d*)\s+(.*)", line): + pseudo[match[1].strip()] = match[3].strip() + elif mode[0] == "kpoints": - m = re.match(r"(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)", line) - if m: - kpoints_grid = (int(m.group(1)), int(m.group(2)), int(m.group(3))) - kpoints_shift = (int(m.group(4)), int(m.group(5)), int(m.group(6))) + if match := re.match(r"(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)", line): + kpoints_grid = (int(match[1]), int(match[2]), int(match[3])) + kpoints_shift = (int(match[4]), int(match[5]), int(match[6])) else: kpoints_mode = mode[1] - kpoints_grid = (1, 1, 1) - kpoints_shift = (0, 0, 0) elif mode[0] == "structure": m_l = re.match(r"(-?\d+\.?\d*)\s+(-?\d+\.?\d*)\s+(-?\d+\.?\d*)", line) m_p = re.match(r"(\w+)\s+(-?\d+\.\d*)\s+(-?\d+\.?\d*)\s+(-?\d+\.?\d*)", line) if m_l: lattice += [ - float(m_l.group(1)), - float(m_l.group(2)), - float(m_l.group(3)), + float(m_l[1]), + float(m_l[2]), + float(m_l[3]), ] + elif m_p: - site_properties["pseudo"].append(pseudo[m_p.group(1)]) - species.append(m_p.group(1)) - coords += [[float(m_p.group(2)), float(m_p.group(3)), float(m_p.group(4))]] + site_properties["pseudo"].append(pseudo[m_p[1]]) + species.append(m_p[1]) + coords += [[float(m_p[2]), float(m_p[3]), float(m_p[4])]] if mode[1] == "angstrom": coords_are_cartesian = True - elif mode[1] == "crystal": - coords_are_cartesian = False + structure = Structure( Lattice(lattice), species, @@ -472,10 +473,10 @@ def smart_int_or_float(numstr): raise ValueError(key + " should be a boolean type!") if key in float_keys: - return float(re.search(r"^-?\d*\.?\d*d?-?\d*", val.lower()).group(0).replace("d", "e")) + return float(re.search(r"^-?\d*\.?\d*d?-?\d*", val.lower())[0].replace("d", "e")) if key in int_keys: - return int(re.match(r"^-?[0-9]+", val).group(0)) + return int(re.match(r"^-?[0-9]+", val)[0]) except ValueError: pass @@ -490,9 +491,8 @@ def smart_int_or_float(numstr): if "false" in val.lower(): return False - m = re.match(r"^[\"|'](.+)[\"|']$", val) - if m: - return m.group(1) + if match := re.match(r"^[\"|'](.+)[\"|']$", val): + return match[1] return None diff --git a/pymatgen/io/qchem/outputs.py b/pymatgen/io/qchem/outputs.py index 6fb9c35dbfa..9d61aa1039e 100644 --- a/pymatgen/io/qchem/outputs.py +++ b/pymatgen/io/qchem/outputs.py @@ -677,15 +677,12 @@ def _read_eigenvalues(self): if spin_unrestricted: header_pattern = r"Final Beta MO Eigenvalues" footer_pattern = r"Final Alpha MO Coefficients+\s*" - beta_eigenvalues = read_matrix_pattern( + self.data["beta_eigenvalues"] = read_matrix_pattern( header_pattern, footer_pattern, elements_pattern, self.text, postprocess=float ) self.data["alpha_eigenvalues"] = alpha_eigenvalues - if spin_unrestricted: - self.data["beta_eigenvalues"] = beta_eigenvalues - def _read_fock_matrix(self): """Parses the Fock matrix. The matrix is read in whole from the output file and then transformed into the right dimensions. @@ -705,13 +702,6 @@ def _read_fock_matrix(self): alpha_fock_matrix = read_matrix_pattern( header_pattern, footer_pattern, elements_pattern, self.text, postprocess=float ) - # The beta Fock matrix is only present if this is a spin-unrestricted calculation. - if spin_unrestricted: - header_pattern = r"Final Beta Fock Matrix" - footer_pattern = "SCF time:" - beta_fock_matrix = read_matrix_pattern( - header_pattern, footer_pattern, elements_pattern, self.text, postprocess=float - ) # Convert the matrices to the right dimension. Right now they are simply # one massive list of numbers, but we need to split them into a matrix. The @@ -720,7 +710,14 @@ def _read_fock_matrix(self): alpha_fock_matrix = process_parsed_fock_matrix(alpha_fock_matrix) self.data["alpha_fock_matrix"] = alpha_fock_matrix + # The beta Fock matrix is only present if this is a spin-unrestricted calculation. if spin_unrestricted: + header_pattern = r"Final Beta Fock Matrix" + footer_pattern = "SCF time:" + beta_fock_matrix = read_matrix_pattern( + header_pattern, footer_pattern, elements_pattern, self.text, postprocess=float + ) + # Perform the same transformation for the beta Fock matrix. beta_fock_matrix = process_parsed_fock_matrix(beta_fock_matrix) self.data["beta_fock_matrix"] = beta_fock_matrix @@ -744,12 +741,6 @@ def _read_coefficient_matrix(self): alpha_coeff_matrix = read_matrix_pattern( header_pattern, footer_pattern, elements_pattern, self.text, postprocess=float ) - if spin_unrestricted: - header_pattern = r"Final Beta MO Coefficients" - footer_pattern = "Final Alpha Density Matrix" - beta_coeff_matrix = read_matrix_pattern( - header_pattern, footer_pattern, elements_pattern, self.text, postprocess=float - ) # Convert the matrices to the right dimension. Right now they are simply # one massive list of numbers, but we need to split them into a matrix. The @@ -759,6 +750,12 @@ def _read_coefficient_matrix(self): self.data["alpha_coeff_matrix"] = alpha_coeff_matrix if spin_unrestricted: + header_pattern = r"Final Beta MO Coefficients" + footer_pattern = "Final Alpha Density Matrix" + beta_coeff_matrix = read_matrix_pattern( + header_pattern, footer_pattern, elements_pattern, self.text, postprocess=float + ) + # Perform the same transformation for the beta Fock matrix. beta_coeff_matrix = process_parsed_fock_matrix(beta_coeff_matrix) self.data["beta_coeff_matrix"] = beta_coeff_matrix diff --git a/pymatgen/io/vasp/inputs.py b/pymatgen/io/vasp/inputs.py index 66cd67cabb8..2efd0704c40 100644 --- a/pymatgen/io/vasp/inputs.py +++ b/pymatgen/io/vasp/inputs.py @@ -6,7 +6,6 @@ from __future__ import annotations import codecs -import contextlib import hashlib import itertools import json @@ -328,9 +327,12 @@ def from_str(cls, data, default_names=None, read_velocities=True) -> Self: lattice *= scale vasp5_symbols = False + atomic_symbols = [] + try: n_atoms = [int(i) for i in lines[5].split()] ipos = 6 + except ValueError: vasp5_symbols = True symbols = [symbol.split("/")[0] for symbol in lines[5].split()] @@ -352,9 +354,11 @@ def from_str(cls, data, default_names=None, read_velocities=True) -> Self: # ... n_lines_symbols = 1 for n_lines_symbols in range(1, 11): - with contextlib.suppress(ValueError): + try: int(lines[5 + n_lines_symbols].split()[0]) break + except ValueError: + pass for i_line_symbols in range(6, 5 + n_lines_symbols): symbols.extend(lines[i_line_symbols].split()) @@ -362,7 +366,7 @@ def from_str(cls, data, default_names=None, read_velocities=True) -> Self: iline_natoms_start = 5 + n_lines_symbols for iline_natoms in range(iline_natoms_start, iline_natoms_start + n_lines_symbols): n_atoms.extend([int(i) for i in lines[iline_natoms].split()]) - atomic_symbols = [] + for i, nat in enumerate(n_atoms): atomic_symbols.extend([symbols[i]] * nat) ipos = 5 + 2 * n_lines_symbols @@ -383,11 +387,13 @@ def from_str(cls, data, default_names=None, read_velocities=True) -> Self: # them. This is in line with VASP's parsing order that the POTCAR # specified is the default used. if default_names: - with contextlib.suppress(IndexError): + try: atomic_symbols = [] for i, nat in enumerate(n_atoms): atomic_symbols.extend([default_names[i]] * nat) vasp5_symbols = True + except IndexError: + pass if not vasp5_symbols: ind = 6 if has_selective_dynamics else 3 @@ -398,6 +404,7 @@ def from_str(cls, data, default_names=None, read_velocities=True) -> Self: if not all(Element.is_valid_symbol(sym) for sym in atomic_symbols): raise ValueError("Non-valid symbols detected.") vasp5_symbols = True + except (ValueError, IndexError): # Defaulting to false names. atomic_symbols = [] @@ -810,9 +817,9 @@ def from_str(cls, string: str) -> Self: params = {} for line in lines: for sline in line.split(";"): - if m := re.match(r"(\w+)\s*=\s*(.*)", sline.strip()): - key = m.group(1).strip() - val = m.group(2).strip() + if match := re.match(r"(\w+)\s*=\s*(.*)", sline.strip()): + key = match.group(1).strip() + val = match.group(2).strip() val = Incar.proc_val(key, val) params[key] = val return cls(params) @@ -868,7 +875,7 @@ def smart_int_or_float(num_str): return float(num_str) return int(num_str) - with contextlib.suppress(ValueError): + try: if key in list_keys: output = [] tokens = re.findall(r"(-?\d+\.?\d*)\*?(-?\d+\.?\d*)?\*?(-?\d+\.?\d*)?", val) @@ -881,8 +888,8 @@ def smart_int_or_float(num_str): output.append(smart_int_or_float(tok[0])) return output if key in bool_keys: - if m := re.match(r"^\.?([T|F|t|f])[A-Za-z]*\.?", val): - return m.group(1).lower() == "t" + if match := re.match(r"^\.?([T|F|t|f])[A-Za-z]*\.?", val): + return match.group(1).lower() == "t" raise ValueError(f"{key} should be a boolean type!") @@ -895,12 +902,19 @@ def smart_int_or_float(num_str): if key in lower_str_keys: return val.strip().lower() + except ValueError: + pass + # Not in standard keys. We will try a hierarchy of conversions. - with contextlib.suppress(ValueError): + try: return int(val) + except ValueError: + pass - with contextlib.suppress(ValueError): + try: return float(val) + except ValueError: + pass if "true" in val.lower(): return True @@ -1193,7 +1207,7 @@ def automatic_density(cls, structure: Structure, kppa: float, force_gamma: bool ngrid = kppa / len(structure) mult = (ngrid * lengths[0] * lengths[1] * lengths[2]) ** (1 / 3) - num_div = [int(math.floor(max(mult / length, 1))) for length in lengths] + num_div = [math.floor(max(mult / length, 1)) for length in lengths] is_hexagonal = lattice.is_hexagonal() is_face_centered = structure.get_space_group_info()[0][0] == "F" @@ -1389,8 +1403,11 @@ def from_str(cls, string: str) -> Self: kpts_shift: tuple[float, float, float] = (0, 0, 0) if len(lines) > 4 and coord_pattern.match(lines[4]): - with contextlib.suppress(ValueError): + try: _kpts_shift = tuple(float(i) for i in lines[4].split()) + except ValueError: + _kpts_shift = (0, 0, 0) + if len(_kpts_shift) == 3: kpts_shift = _kpts_shift @@ -1400,8 +1417,7 @@ def from_str(cls, string: str) -> Self: if num_kpts <= 0: _style = cls.supported_modes.Cartesian if style in "ck" else cls.supported_modes.Reciprocal _kpts_shift = tuple(float(i) for i in lines[6].split()) - if len(_kpts_shift) == 3: - kpts_shift = _kpts_shift + kpts_shift = _kpts_shift if len(_kpts_shift) == 3 else (0, 0, 0) return cls( comment=comment, @@ -1420,9 +1436,9 @@ def from_str(cls, string: str) -> Self: patt = re.compile(r"([e0-9.\-]+)\s+([e0-9.\-]+)\s+([e0-9.\-]+)\s*!*\s*(.*)") for idx in range(4, len(lines)): line = lines[idx] - if m := patt.match(line): - _kpts.append([float(m.group(1)), float(m.group(2)), float(m.group(3))]) - labels.append(m.group(4).strip()) + if match := patt.match(line): + _kpts.append([float(match.group(1)), float(match.group(2)), float(match.group(3))]) + labels.append(match.group(4).strip()) return cls( comment=comment, num_kpts=num_kpts, @@ -1566,22 +1582,22 @@ def from_dict(cls, dct: dict) -> Self: ) -def _parse_bool(s): - if m := re.match(r"^\.?([TFtf])[A-Za-z]*\.?", s): - return m[1] in ["T", "t"] - raise ValueError(f"{s} should be a boolean type!") +def _parse_bool(string): + if match := re.match(r"^\.?([TFtf])[A-Za-z]*\.?", string): + return match[1] in {"T", "t"} + raise ValueError(f"{string} should be a boolean type!") -def _parse_float(s): - return float(re.search(r"^-?\d*\.?\d*[eE]?-?\d*", s).group(0)) +def _parse_float(string): + return float(re.search(r"^-?\d*\.?\d*[eE]?-?\d*", string).group(0)) -def _parse_int(s): - return int(re.match(r"^-?[0-9]+", s).group(0)) +def _parse_int(string): + return int(re.match(r"^-?[0-9]+", string).group(0)) -def _parse_list(s): - return [float(y) for y in re.split(r"\s+", s.strip()) if not y.isalpha()] +def _parse_list(string): + return [float(y) for y in re.split(r"\s+", string.strip()) if not y.isalpha()] Orbital = namedtuple("Orbital", ["n", "l", "j", "E", "occ"]) diff --git a/pymatgen/io/vasp/outputs.py b/pymatgen/io/vasp/outputs.py index dcccb50c8bb..deacb1231c2 100644 --- a/pymatgen/io/vasp/outputs.py +++ b/pymatgen/io/vasp/outputs.py @@ -89,23 +89,23 @@ def _parse_v_parameters(val_type, val, filename, param_name): elif val_type == "int": try: val = [int(i) for i in val.split()] - except ValueError: + except ValueError as exc: # Fix for stupid error in vasprun sometimes which displays # LDAUL/J as 2**** val = _parse_from_incar(filename, param_name) if val is None: - raise err + raise err from exc elif val_type == "string": val = val.split() else: try: val = [float(i) for i in val.split()] - except ValueError: + except ValueError as exc: # Fix for stupid error in vasprun sometimes which displays # MAGMOM as 2**** val = _parse_from_incar(filename, param_name) if val is None: - raise err + raise err from exc return val @@ -120,7 +120,7 @@ def _parse_from_incar(filename: str, key: str) -> str | None: dirname = os.path.dirname(filename) for filename in os.listdir(dirname): if re.search(r"INCAR", filename): - warnings.warn("INCAR found. Using " + key + " from INCAR.") + warnings.warn(f"INCAR found. Using {key} from INCAR.") incar = Incar.from_file(os.path.join(dirname, filename)) if key in incar: return incar[key] @@ -555,7 +555,7 @@ def dielectric(self): return self.dielectric_data["density"] @property - def optical_absorption_coeff(self) -> list[float]: + def optical_absorption_coeff(self) -> list[float] | None: """ Calculate the optical absorption coefficient from the dielectric constants. Note that this method is only @@ -566,11 +566,11 @@ def optical_absorption_coeff(self) -> list[float]: """ if self.dielectric_data["density"]: real_avg = [ - sum(self.dielectric_data["density"][1][i][0:3]) / 3 + sum(self.dielectric_data["density"][1][i][:3]) / 3 for i in range(len(self.dielectric_data["density"][0])) ] imag_avg = [ - sum(self.dielectric_data["density"][2][i][0:3]) / 3 + sum(self.dielectric_data["density"][2][i][:3]) / 3 for i in range(len(self.dielectric_data["density"][0])) ] @@ -582,10 +582,10 @@ def optical_absorb_coeff(freq, real, imag): hc = 1.23984 * 1e-4 # plank constant times speed of light, in the unit of eV*cm return 2 * 3.14159 * np.sqrt(np.sqrt(real**2 + imag**2) - real) * np.sqrt(2) / hc * freq - absorption_coeff = list( + return list( itertools.starmap(optical_absorb_coeff, zip(self.dielectric_data["density"][0], real_avg, imag_avg)) ) - return absorption_coeff + return None @property def converged_electronic(self) -> bool: @@ -886,7 +886,7 @@ def get_band_structure( if not kpoints_filename: kpts_path = os.path.join(os.path.dirname(self.filename), "KPOINTS_OPT" if use_kpoints_opt else "KPOINTS") kpoints_filename = zpath(kpts_path) - if kpoints_filename and not os.path.isfile(kpoints_filename) and line_mode is True: + if kpoints_filename and not os.path.isfile(kpoints_filename) and line_mode: name = "KPOINTS_OPT" if use_kpoints_opt else "KPOINTS" raise VaspParseError(f"{name} not found but needed to obtain band structure along symmetry lines.") @@ -1325,6 +1325,9 @@ def _parse_params(self, elem): @staticmethod def _parse_atominfo(elem): + atomic_symbols = [] + potcar_symbols = [] + for a in elem.findall("array"): if a.attrib["name"] == "atoms": atomic_symbols = [rc.find("c").text.strip() for rc in a.find("set")] @@ -1353,6 +1356,7 @@ def _parse_kpoints(elem): e = elem.find("generation") k = Kpoints("Kpoints from vasprun.xml") k.style = Kpoints.supported_modes.from_str(e.attrib.get("param", "Reciprocal")) + for v in e.findall("v"): name = v.attrib.get("name") tokens = v.text.split() @@ -1362,6 +1366,9 @@ def _parse_kpoints(elem): k.kpts_shift = [float(i) for i in tokens] elif name in {"genvec1", "genvec2", "genvec3", "shift"}: setattr(k, name, [float(i) for i in tokens]) + + actual_kpoints = [] + weights = [] for va in elem.findall("varray"): name = va.attrib["name"] if name == "kpointlist": @@ -1369,6 +1376,7 @@ def _parse_kpoints(elem): elif name == "weights": weights = [i[0] for i in _parse_vasp_array(va)] elem.clear() + if k.style == Kpoints.supported_modes.Reciprocal: k = Kpoints( comment="Kpoints from vasprun.xml", @@ -1406,9 +1414,11 @@ def _parse_optical_transition(elem): for va in elem.findall("varray"): if va.attrib.get("name") == "opticaltransitions": # optical transitions array contains oscillator strength and probability of transition - oscillator_strength = np.array(_parse_vasp_array(va))[0:] - probability_transition = np.array(_parse_vasp_array(va))[0:, 1] - return oscillator_strength, probability_transition + oscillator_strength = np.array(_parse_vasp_array(va))[:] + probability_transition = np.array(_parse_vasp_array(va))[:, 1] + + return oscillator_strength, probability_transition + return None def _parse_chemical_shielding_calculation(self, elem): calculation = [] @@ -1860,36 +1870,29 @@ def __init__(self, filename): except ValueError: run_stats[tok[0].strip()] = None continue - m = efermi_patt.search(clean) - if m: + + if match := efermi_patt.search(clean): try: # try-catch because VASP sometimes prints # 'E-fermi: ******** XC(G=0): -6.1327 # alpha+bet : -1.8238' - efermi = float(m.group(1)) + efermi = float(match.group(1)) continue except ValueError: efermi = None continue - m = nelect_patt.search(clean) - if m: - nelect = float(m.group(1)) - m = mag_patt.search(clean) - if m: - total_mag = float(m.group(1)) - - if e_fr_energy is None: - m = e_fr_energy_pattern.search(clean) - if m: - e_fr_energy = float(m.group(1)) - if e_wo_entrp is None: - m = e_wo_entrp_pattern.search(clean) - if m: - e_wo_entrp = float(m.group(1)) - if e0 is None: - m = e0_pattern.search(clean) - if m: - e0 = float(m.group(1)) + if match := nelect_patt.search(clean): + nelect = float(match.group(1)) + + if match := mag_patt.search(clean): + total_mag = float(match.group(1)) + + if e_fr_energy is None and (match := e_fr_energy_pattern.search(clean)): + e_fr_energy = float(match.group(1)) + if e_wo_entrp is None and (match := e_wo_entrp_pattern.search(clean)): + e_wo_entrp = float(match.group(1)) + if e0 is None and (match := e0_pattern.search(clean)): + e0 = float(match.group(1)) if all([nelect, total_mag is not None, efermi is not None, run_stats]): break @@ -1905,24 +1908,22 @@ def __init__(self, filename): if clean.startswith("# of ion"): header = re.split(r"\s{2,}", clean.strip()) header.pop(0) - else: - m = re.match(r"\s*(\d+)\s+(([\d\.\-]+)\s+)+", clean) - if m: - tokens = [float(i) for i in re.findall(r"[\d\.\-]+", clean)] - tokens.pop(0) - if read_charge: - charge.append(dict(zip(header, tokens))) - elif read_mag_x: - mag_x.append(dict(zip(header, tokens))) - elif read_mag_y: - mag_y.append(dict(zip(header, tokens))) - elif read_mag_z: - mag_z.append(dict(zip(header, tokens))) - elif clean.startswith("tot"): - read_charge = False - read_mag_x = False - read_mag_y = False - read_mag_z = False + elif match := re.match(r"\s*(\d+)\s+(([\d\.\-]+)\s+)+", clean): + tokens = [float(i) for i in re.findall(r"[\d\.\-]+", clean)] + tokens.pop(0) + if read_charge: + charge.append(dict(zip(header, tokens))) + elif read_mag_x: + mag_x.append(dict(zip(header, tokens))) + elif read_mag_y: + mag_y.append(dict(zip(header, tokens))) + elif read_mag_z: + mag_z.append(dict(zip(header, tokens))) + elif clean.startswith("tot"): + read_charge = False + read_mag_x = False + read_mag_y = False + read_mag_z = False if clean == "total charge": charge = [] read_charge = True @@ -2260,9 +2261,8 @@ def _parse_sci_notation(line): Returns: list[float]: numbers if found, empty ist if not """ - m = re.findall(r"[\.\-\d]+E[\+\-]\d{2}", line) - if m: - return [float(t) for t in m] + if match := re.findall(r"[\.\-\d]+E[\+\-]\d{2}", line): + return [float(t) for t in match] return [] def read_freq_dielectric(self): @@ -3193,7 +3193,7 @@ def zvals(results, match): zval_dict = {} for x, y in zip(self.atom_symbols, self.zvals): - zval_dict.update({x: y}) + zval_dict[x] = y self.zval_dict = zval_dict # Clean-up @@ -3217,11 +3217,15 @@ def read_core_state_eigen(self): """ with zopen(self.filename, mode="rt") as foutcar: line = foutcar.readline() + cl = [] + while line != "": line = foutcar.readline() + if "NIONS =" in line: natom = int(line.split("NIONS =")[1]) - cl = [defaultdict(list) for i in range(natom)] + cl = [defaultdict(list) for _ in range(natom)] + if "the core state eigen" in line: iat = -1 while line != "": @@ -3600,12 +3604,13 @@ def write_spin(data_type): file.write("".join(data)) write_spin("total") - if self.is_spin_polarized and self.is_soc: - write_spin("diff_x") - write_spin("diff_y") - write_spin("diff_z") - elif self.is_spin_polarized: - write_spin("diff") + if self.is_spin_polarized: + if self.is_soc: + write_spin("diff_x") + write_spin("diff_y") + write_spin("diff_z") + else: + write_spin("diff") class Locpot(VolumetricData): @@ -3653,6 +3658,8 @@ def __init__(self, poscar, data, data_aug=None): struct = poscar self.poscar = Poscar(poscar) self.name = None + else: + raise TypeError("Unsupported POSCAR type.") super().__init__(struct, data, data_aug=data_aug) self._distance_matrix = {} @@ -3673,9 +3680,7 @@ def from_file(cls, filename: str) -> Self: @property def net_magnetization(self): """Net magnetization from Chgcar""" - if self.is_spin_polarized: - return np.sum(self.data["diff"]) - return None + return np.sum(self.data["diff"]) if self.is_spin_polarized else None class Elfcar(VolumetricData): @@ -3701,6 +3706,8 @@ def __init__(self, poscar, data): elif isinstance(poscar, Structure): tmp_struct = poscar self.poscar = Poscar(poscar) + else: + raise TypeError("Unsupported POSCAR type.") super().__init__(tmp_struct, data) # TODO: modify VolumetricData so that the correct keys can be used. @@ -3767,7 +3774,15 @@ def __init__(self, filename): current_band = 0 done = False spin = Spin.down + weights = None + n_kpoints = None + n_bands = None + n_ions = None + weights = [] + headers = None + data = None + phase_factors = None for line in file_handle: line = line.strip() @@ -3839,7 +3854,7 @@ def get_projection_on_elements(self, structure: Structure): """ dico: dict[Spin, list] = {} for spin in self.data: - dico[spin] = [[defaultdict(float) for i in range(self.nkpoints)] for j in range(self.nbands)] + dico[spin] = [[defaultdict(float) for _ in range(self.nkpoints)] for _ in range(self.nbands)] for iat in range(self.nions): name = structure.species[iat].symbol @@ -4040,11 +4055,12 @@ def __init__(self, filename, ionicstep_start=1, ionicstep_end=None, comment=None preamble_done = False if ionicstep_start < 1: raise ValueError("Start ionic step cannot be less than 1") - if ionicstep_end is not None and ionicstep_start < 1: + if ionicstep_end is not None and ionicstep_end < 1: raise ValueError("End ionic step cannot be less than 1") ionicstep_cnt = 1 with zopen(filename, mode="rt") as file: + title = None for line in file: line = line.strip() if preamble is None: @@ -4134,7 +4150,7 @@ def concatenate(self, filename, ionicstep_start=1, ionicstep_end=None): preamble_done = False if ionicstep_start < 1: raise ValueError("Start ionic step cannot be less than 1") - if ionicstep_end is not None and ionicstep_start < 1: + if ionicstep_end is not None and ionicstep_end < 1: raise ValueError("End ionic step cannot be less than 1") ionicstep_cnt = 1 @@ -4410,9 +4426,9 @@ def __init__(self, filename="WAVECAR", verbose=False, precision="normal", vasp_t values are ['std', 'gam', 'ncl'] (only first letter is required) """ self.filename = filename - valid_types = ["std", "gam", "ncl"] + valid_types = {"std", "gam", "ncl"} initials = {x[0] for x in valid_types} - if not (vasp_type is None or vasp_type.lower()[0] in initials): + if vasp_type is not None and vasp_type.lower()[0] not in initials: raise ValueError( f"invalid {vasp_type=}, must be one of {valid_types} (we only check the first letter {initials})" ) @@ -4485,7 +4501,7 @@ def __init__(self, filename="WAVECAR", verbose=False, precision="normal", vasp_t self.Gpoints = [None for _ in range(self.nk)] self.kpoints = [] if spin == 2: - self.coeffs = [[[None for i in range(self.nb)] for j in range(self.nk)] for _ in range(spin)] + self.coeffs = [[[None for _ in range(self.nb)] for _ in range(self.nk)] for _ in range(spin)] self.band_energy = [[] for _ in range(spin)] else: self.coeffs = [[None for i in range(self.nb)] for j in range(self.nk)] @@ -4556,6 +4572,8 @@ def __init__(self, filename="WAVECAR", verbose=False, precision="normal", vasp_t # but I don't have a WAVECAR to test it with data = np.fromfile(file, dtype=np.complex128, count=nplane) np.fromfile(file, dtype=np.float64, count=recl8 - 2 * nplane) + else: + raise RuntimeError("Invalid rtag value.") extra_coeffs = [] if len(extra_coeff_inds) > 0: @@ -4732,9 +4750,7 @@ def fft_mesh(self, kpoint: int, band: int, spin: int = 0, spinor: int = 0, shift t = tuple(gp.astype(int) + (self.ng / 2).astype(int)) mesh[t] = coeff - if shift: - return np.fft.ifftshift(mesh) - return mesh + return np.fft.ifftshift(mesh) if shift else mesh def get_parchg( self, @@ -4816,7 +4832,7 @@ def get_parchg( den = np.abs(np.conj(wfr) * wfr) den += np.abs(np.conj(wfr_t) * wfr_t) - if phase and not (self.vasp_type.lower()[0] == "n" and spinor is None): + if phase and (self.vasp_type.lower()[0] != "n" or spinor is not None): den = np.sign(np.real(wfr)) * den data["total"] = den @@ -4853,7 +4869,7 @@ def write_unks(self, directory: str) -> None: for ib in range(self.nb): data[ib, 0, :, :, :] = np.fft.ifftn(self.fft_mesh(ik, ib, spinor=0)) * N data[ib, 1, :, :, :] = np.fft.ifftn(self.fft_mesh(ik, ib, spinor=1)) * N - Unk(ik + 1, data).write_file(str(out_dir / (fname + "NC"))) + Unk(ik + 1, data).write_file(str(out_dir / f"{fname}NC")) else: data = np.empty((self.nb, *self.ng), dtype=np.complex128) for ispin in range(self.spin): diff --git a/pymatgen/io/vasp/sets.py b/pymatgen/io/vasp/sets.py index 384214ea960..3f1c4dba468 100644 --- a/pymatgen/io/vasp/sets.py +++ b/pymatgen/io/vasp/sets.py @@ -176,6 +176,7 @@ def write_input( same name as the InputSet (e.g., MPStaticSet.zip) """ if potcar_spec: + vasp_input = None if make_dir_if_not_present: os.makedirs(output_dir, exist_ok=True) @@ -191,19 +192,20 @@ def write_input( vasp_input.write_input(output_dir, make_dir_if_not_present=make_dir_if_not_present) cif_name = "" - if include_cif: + if include_cif and vasp_input is not None: struct = vasp_input["POSCAR"].structure cif_name = f"{output_dir}/{struct.formula.replace(' ', '')}.cif" struct.to(filename=cif_name) if zip_output: - filename = type(self).__name__ + ".zip" + filename = f"{type(self).__name__}.zip" with ZipFile(os.path.join(output_dir, filename), mode="w") as zip_file: for file in ["INCAR", "POSCAR", "KPOINTS", "POTCAR", "POTCAR.spec", cif_name]: try: zip_file.write(os.path.join(output_dir, file), arcname=file) except FileNotFoundError: pass + try: os.remove(os.path.join(output_dir, file)) except (FileNotFoundError, PermissionError, IsADirectoryError): @@ -2709,6 +2711,8 @@ def incar_updates(self) -> dict: if atom_type not in self.user_supplied_basis: raise ValueError(f"There are no basis functions for the atom type {atom_type}") basis = [f"{key} {value}" for key, value in self.user_supplied_basis.items()] + else: + basis = None lobsterin = Lobsterin(settingsdict={"basisfunctions": basis}) nbands = lobsterin._get_nbands(structure=self.structure) # type: ignore diff --git a/pymatgen/io/xr.py b/pymatgen/io/xr.py index 922a7e31d61..789651e66e4 100644 --- a/pymatgen/io/xr.py +++ b/pymatgen/io/xr.py @@ -124,12 +124,11 @@ def from_str(cls, string: str, use_cores: bool = True, thresh: float = 1.0e-4) - sp = [] coords = [] for j in range(n_sites): - m = re.match( + if match := re.match( r"\d+\s+(\w+)\s+([0-9\-\.]+)\s+([0-9\-\.]+)\s+([0-9\-\.]+)", lines[4 + j].strip(), - ) - if m: - tmp_sp = m.group(1) + ): + tmp_sp = match.group(1) if use_cores and tmp_sp[len(tmp_sp) - 2 :] == "_s": continue if not use_cores and tmp_sp[len(tmp_sp) - 2 :] == "_c": @@ -138,7 +137,7 @@ def from_str(cls, string: str, use_cores: bool = True, thresh: float = 1.0e-4) - sp.append(tmp_sp[0 : len(tmp_sp) - 2]) else: sp.append(tmp_sp) - coords.append([float(m.group(i)) for i in range(2, 5)]) + coords.append([float(match.group(i)) for i in range(2, 5)]) return cls(Structure(lattice, sp, coords, coords_are_cartesian=True)) @classmethod diff --git a/pymatgen/io/xtb/outputs.py b/pymatgen/io/xtb/outputs.py index 2c8429ab3f1..1a6132f496b 100644 --- a/pymatgen/io/xtb/outputs.py +++ b/pymatgen/io/xtb/outputs.py @@ -56,7 +56,7 @@ def _parse_crest_output(self): # Get CREST command crest_cmd = None - with open(output_filepath) as xtbout_file: + with open(output_filepath, encoding="utf-8") as xtbout_file: for line in xtbout_file: if "> crest" in line: crest_cmd = line.strip()[8:] @@ -112,7 +112,7 @@ def _parse_crest_output(self): ) conformer_degeneracies = [] energies = [] - with open(output_filepath) as xtbout_file: + with open(output_filepath, encoding="utf-8") as xtbout_file: for line in xtbout_file: conformer_match = conformer_pattern.match(line) rotamer_match = rotamer_pattern.match(line) @@ -131,8 +131,8 @@ def _parse_crest_output(self): if "crest_rotamers" in filename: n_rot_file = int(os.path.splitext(filename)[0].split("_")[2]) n_rot_files.append(n_rot_file) - if len(n_rot_files) > 0: - final_rotamer_filename = f"crest_rotamers_{max(n_rot_files)}.xyz" + final_rotamer_filename = f"crest_rotamers_{max(n_rot_files)}.xyz" if len(n_rot_files) > 0 else "" + try: rotamers_path = os.path.join(self.path, final_rotamer_filename) rotamer_structures = XYZ.from_file(rotamers_path).all_molecules diff --git a/pymatgen/io/zeopp.py b/pymatgen/io/zeopp.py index d27f61488bc..364eb8c71f7 100644 --- a/pymatgen/io/zeopp.py +++ b/pymatgen/io/zeopp.py @@ -44,6 +44,7 @@ zeo_found = True except ImportError: zeo_found = False + AtomNetwork = prune_voronoi_network_close_node = None if TYPE_CHECKING: from pathlib import Path @@ -241,22 +242,22 @@ def get_voronoi_nodes(structure, rad_dict=None, probe_rad=0.1): """ with ScratchDir("."): name = "temp_zeo1" - zeo_inp_filename = name + ".cssr" + zeo_inp_filename = f"{name}.cssr" ZeoCssr(structure).write_file(zeo_inp_filename) rad_file = None rad_flag = False if rad_dict: - rad_file = name + ".rad" + rad_file = f"{name}.rad" rad_flag = True - with open(rad_file, "w+") as file: + with open(rad_file, "w+", encoding="utf-8") as file: for el in rad_dict: file.write(f"{el} {rad_dict[el].real}\n") atom_net = AtomNetwork.read_from_CSSR(zeo_inp_filename, rad_flag=rad_flag, rad_file=rad_file) vor_net, vor_edge_centers, vor_face_centers = atom_net.perform_voronoi_decomposition() vor_net.analyze_writeto_XYZ(name, probe_rad, atom_net) - voro_out_filename = name + "_voro.xyz" + voro_out_filename = f"{name}_voro.xyz" voro_node_mol = ZeoVoronoiXYZ.from_file(voro_out_filename).molecule species = ["X"] * len(voro_node_mol) @@ -331,8 +332,8 @@ def get_high_accuracy_voronoi_nodes(structure, rad_dict, probe_rad=0.1): zeo_inp_filename = f"{name}.cssr" ZeoCssr(structure).write_file(zeo_inp_filename) rad_flag = True - rad_file = name + ".rad" - with open(rad_file, "w+") as file: + rad_file = f"{name}.rad" + with open(rad_file, "w+", encoding="utf-8") as file: for el in rad_dict: print(f"{el} {rad_dict[el].real}", file=file) @@ -390,15 +391,15 @@ def get_free_sphere_params(structure, rad_dict=None, probe_rad=0.1): """ with ScratchDir("."): name = "temp_zeo1" - zeo_inp_filename = name + ".cssr" + zeo_inp_filename = f"{name}.cssr" ZeoCssr(structure).write_file(zeo_inp_filename) rad_file = None rad_flag = False if rad_dict: - rad_file = name + ".rad" + rad_file = f"{name}.rad" rad_flag = True - with open(rad_file, "w+") as file: + with open(rad_file, "w+", encoding="utf-8") as file: for el in rad_dict: file.write(f"{el} {rad_dict[el].real}\n") @@ -406,16 +407,16 @@ def get_free_sphere_params(structure, rad_dict=None, probe_rad=0.1): out_file = "temp.res" atom_net.calculate_free_sphere_parameters(out_file) if os.path.isfile(out_file) and os.path.getsize(out_file) > 0: - with open(out_file) as file: + with open(out_file, encoding="utf-8") as file: output = file.readline() else: output = "" fields = [val.strip() for val in output.split()][1:4] if len(fields) == 3: fields = [float(field) for field in fields] - free_sphere_params = { + return { "inc_sph_max_dia": fields[0], "free_sph_max_dia": fields[1], "inc_sph_along_free_sph_path_max_dia": fields[2], } - return free_sphere_params + return None diff --git a/pymatgen/phonon/bandstructure.py b/pymatgen/phonon/bandstructure.py index 9fdb73347cc..2b51d497ced 100644 --- a/pymatgen/phonon/bandstructure.py +++ b/pymatgen/phonon/bandstructure.py @@ -45,6 +45,7 @@ def estimate_band_connection(prev_eigvecs, eigvecs, prev_band_order) -> list[int connection_order = [] for overlaps in metric: max_val = 0 + max_idx = 0 for idx in reversed(range(len(metric))): val = overlaps[idx] if idx in connection_order: diff --git a/pymatgen/phonon/gruneisen.py b/pymatgen/phonon/gruneisen.py index 447bf5a1ccb..baabee17cd6 100644 --- a/pymatgen/phonon/gruneisen.py +++ b/pymatgen/phonon/gruneisen.py @@ -21,6 +21,7 @@ from phonopy.phonon.dos import TotalDos except ImportError: phonopy = None + TotalDos = None if TYPE_CHECKING: from collections.abc import Sequence diff --git a/pymatgen/phonon/thermal_displacements.py b/pymatgen/phonon/thermal_displacements.py index 5d4316e05ea..566e1945db7 100644 --- a/pymatgen/phonon/thermal_displacements.py +++ b/pymatgen/phonon/thermal_displacements.py @@ -4,7 +4,7 @@ import re from functools import partial -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Literal import numpy as np from monty.json import MSONable @@ -306,7 +306,7 @@ def visualize_directionality_quality_criterion( self, other: ThermalDisplacementMatrices, filename: str | PathLike = "visualization.vesta", - which_structure: int = 0, + which_structure: Literal[0, 1] = 0, ) -> None: """Will create a VESTA file for visualization of the directionality criterion. @@ -328,8 +328,10 @@ def visualize_directionality_quality_criterion( structure = self.structure elif which_structure == 1: structure = other.structure + else: + raise ValueError("Illegal which_structure value.") - with open(filename, mode="w") as file: + with open(filename, mode="w", encoding="utf-8") as file: # file.write("#VESTA_FORMAT_VERSION 3.5.4\n \n \n") file.write("CRYSTAL\n\n") diff --git a/pymatgen/symmetry/analyzer.py b/pymatgen/symmetry/analyzer.py index 715f9ed29d4..7b1a082d001 100644 --- a/pymatgen/symmetry/analyzer.py +++ b/pymatgen/symmetry/analyzer.py @@ -581,6 +581,7 @@ def get_conventional_standard_structure(self, international_monoclinic=True, kee The structure in a conventional standardized cell """ tol = 1e-5 + transf = None struct = self.get_refined_structure(keep_site_properties=keep_site_properties) lattice = struct.lattice latt_type = self.get_lattice_type() @@ -718,6 +719,7 @@ def get_conventional_standard_structure(self, international_monoclinic=True, kee # keep the ones with the non-90 angle=alpha # and b None: # TODO: Support different origin choices. enc = list(data["enc"]) inversion = int(enc.pop(0)) - ngen = int(enc.pop(0)) + n_gen = int(enc.pop(0)) symm_ops = [np.eye(4)] if inversion: symm_ops.append(np.array([[-1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 1]])) - for _ in range(ngen): - m = np.eye(4) - m[:3, :3] = SpaceGroup.gen_matrices[enc.pop(0)] - m[0, 3] = SpaceGroup.translations[enc.pop(0)] - m[1, 3] = SpaceGroup.translations[enc.pop(0)] - m[2, 3] = SpaceGroup.translations[enc.pop(0)] - symm_ops.append(m) + for _ in range(n_gen): + matrix = np.eye(4) + matrix[:3, :3] = SpaceGroup.gen_matrices[enc.pop(0)] + matrix[0, 3] = SpaceGroup.translations[enc.pop(0)] + matrix[1, 3] = SpaceGroup.translations[enc.pop(0)] + matrix[2, 3] = SpaceGroup.translations[enc.pop(0)] + symm_ops.append(matrix) self.generators = symm_ops self.full_symbol = data["full_symbol"] self.point_group = data["point_group"] @@ -293,12 +293,15 @@ def get_settings(cls, int_symbol: str) -> set[str]: set[str]: All possible settings for the given international symbol. """ symbols = [] + int_number = None if int_symbol in SpaceGroup.abbrev_sg_mapping: symbols.append(SpaceGroup.abbrev_sg_mapping[int_symbol]) int_number = SpaceGroup.sg_encoding[int_symbol]["int_number"] + elif int_symbol in SpaceGroup.full_sg_mapping: symbols.append(SpaceGroup.full_sg_mapping[int_symbol]) int_number = SpaceGroup.sg_encoding[int_symbol]["int_number"] + else: for spg in SpaceGroup.SYMM_OPS: if int_symbol in [ diff --git a/pymatgen/symmetry/kpath.py b/pymatgen/symmetry/kpath.py index 4d46e7c921d..fc26429fe43 100644 --- a/pymatgen/symmetry/kpath.py +++ b/pymatgen/symmetry/kpath.py @@ -12,7 +12,6 @@ import numpy as np import spglib from monty.dev import requires -from scipy.linalg import sqrtm from pymatgen.core.lattice import Lattice from pymatgen.core.operations import MagSymmOp, SymmOp @@ -1849,160 +1848,54 @@ def _get_IRBZ(self, recip_point_group, W, key_points, face_center_inds, atol): used_axes = [] + def find_face_center(name: str, IRBZ_points): + for rotn in rpgdict["rotations"][name]: + ax = rotn["axis"] + op = rotn["op"] + rot_boundaries = None + + if not np.any([np.allclose(ax, usedax, atol) for usedax in used_axes]) and self._op_maps_IRBZ_to_self( + op, IRBZ_points, atol + ): + face_center_found = False + for point in IRBZ_points: + if point[0] in face_center_inds: + cross = D * np.dot(g_inv, np.cross(ax, point[1])) + if not np.allclose(cross, 0, atol=atol): + rot_boundaries = [cross, -1 * np.dot(op, cross)] + face_center_found = True + used_axes.append(ax) + break + + if not face_center_found: + print("face center not found") + for point in IRBZ_points: + cross = D * np.dot(g_inv, np.cross(ax, point[1])) + if not np.allclose(cross, 0, atol=atol): + rot_boundaries = [cross, -1 * np.dot(op, cross)] + used_axes.append(ax) + break + + if rot_boundaries is None: + raise RuntimeError("Failed to find rotation boundaries.") + + return self._reduce_IRBZ(IRBZ_points, rot_boundaries, g, atol) + return IRBZ_points + return IRBZ_points + # six-fold rotoinversion always comes with horizontal mirror so don't # need to check - for rotn in rpgdict["rotations"]["six-fold"]: - ax = rotn["axis"] - op = rotn["op"] - if not np.any([np.allclose(ax, usedax, atol) for usedax in used_axes]) and self._op_maps_IRBZ_to_self( - op, IRBZ_points, atol - ): - face_center_found = False - for point in IRBZ_points: - if point[0] in face_center_inds: - cross = D * np.dot(g_inv, np.cross(ax, point[1])) - if not np.allclose(cross, 0, atol=atol): - rot_boundaries = [cross, -1 * np.dot(op, cross)] - face_center_found = True - used_axes.append(ax) - break - if not face_center_found: - print("face center not found") - for point in IRBZ_points: - cross = D * np.dot(g_inv, np.cross(ax, point[1])) - if not np.allclose(cross, 0, atol=atol): - rot_boundaries = [cross, -1 * np.dot(op, cross)] - used_axes.append(ax) - break - IRBZ_points = self._reduce_IRBZ(IRBZ_points, rot_boundaries, g, atol) - - for rotn in rpgdict["rotations"]["rotoinv-four-fold"]: - ax = rotn["axis"] - op = rotn["op"] - if not np.any([np.allclose(ax, usedax, atol) for usedax in used_axes]) and self._op_maps_IRBZ_to_self( - op, IRBZ_points, atol - ): - face_center_found = False - for point in IRBZ_points: - if point[0] in face_center_inds: - cross = D * np.dot(g_inv, np.cross(ax, point[1])) - if not np.allclose(cross, 0, atol=atol): - rot_boundaries = [cross, np.dot(op, cross)] - face_center_found = True - used_axes.append(ax) - break - if not face_center_found: - print("face center not found") - for point in IRBZ_points: - cross = D * np.dot(g_inv, np.cross(ax, point[1])) - if not np.allclose(cross, 0, atol=atol): - rot_boundaries = [cross, -1 * np.dot(op, cross)] - used_axes.append(ax) - break - IRBZ_points = self._reduce_IRBZ(IRBZ_points, rot_boundaries, g, atol) - - for rotn in rpgdict["rotations"]["four-fold"]: - ax = rotn["axis"] - op = rotn["op"] - if not np.any([np.allclose(ax, usedax, atol) for usedax in used_axes]) and self._op_maps_IRBZ_to_self( - op, IRBZ_points, atol - ): - face_center_found = False - for point in IRBZ_points: - if point[0] in face_center_inds: - cross = D * np.dot(g_inv, np.cross(ax, point[1])) - if not np.allclose(cross, 0, atol=atol): - rot_boundaries = [cross, -1 * np.dot(op, cross)] - face_center_found = True - used_axes.append(ax) - break - if not face_center_found: - print("face center not found") - for point in IRBZ_points: - cross = D * np.dot(g_inv, np.cross(ax, point[1])) - if not np.allclose(cross, 0, atol=atol): - rot_boundaries = [cross, -1 * np.dot(op, cross)] - used_axes.append(ax) - break - IRBZ_points = self._reduce_IRBZ(IRBZ_points, rot_boundaries, g, atol) - - for rotn in rpgdict["rotations"]["rotoinv-three-fold"]: - ax = rotn["axis"] - op = rotn["op"] - if not np.any([np.allclose(ax, usedax, atol) for usedax in used_axes]) and self._op_maps_IRBZ_to_self( - op, IRBZ_points, atol - ): - face_center_found = False - for point in IRBZ_points: - if point[0] in face_center_inds: - cross = D * np.dot(g_inv, np.cross(ax, point[1])) - if not np.allclose(cross, 0, atol=atol): - rot_boundaries = [ - cross, - -1 * np.dot(sqrtm(-1 * op), cross), - ] - face_center_found = True - used_axes.append(ax) - break - if not face_center_found: - print("face center not found") - for point in IRBZ_points: - cross = D * np.dot(g_inv, np.cross(ax, point[1])) - if not np.allclose(cross, 0, atol=atol): - rot_boundaries = [cross, -1 * np.dot(op, cross)] - used_axes.append(ax) - break - IRBZ_points = self._reduce_IRBZ(IRBZ_points, rot_boundaries, g, atol) - - for rotn in rpgdict["rotations"]["three-fold"]: - ax = rotn["axis"] - op = rotn["op"] - if not np.any([np.allclose(ax, usedax, atol) for usedax in used_axes]) and self._op_maps_IRBZ_to_self( - op, IRBZ_points, atol - ): - face_center_found = False - for point in IRBZ_points: - if point[0] in face_center_inds: - cross = D * np.dot(g_inv, np.cross(ax, point[1])) - if not np.allclose(cross, 0, atol=atol): - rot_boundaries = [cross, -1 * np.dot(op, cross)] - face_center_found = True - used_axes.append(ax) - break - if not face_center_found: - print("face center not found") - for point in IRBZ_points: - cross = D * np.dot(g_inv, np.cross(ax, point[1])) - if not np.allclose(cross, 0, atol=atol): - rot_boundaries = [cross, -1 * np.dot(op, cross)] - used_axes.append(ax) - break - IRBZ_points = self._reduce_IRBZ(IRBZ_points, rot_boundaries, g, atol) - - for rotn in rpgdict["rotations"]["two-fold"]: - ax = rotn["axis"] - op = rotn["op"] - if not np.any([np.allclose(ax, usedax, atol) for usedax in used_axes]) and self._op_maps_IRBZ_to_self( - op, IRBZ_points, atol - ): - face_center_found = False - for point in IRBZ_points: - if point[0] in face_center_inds: - cross = D * np.dot(g_inv, np.cross(ax, point[1])) - if not np.allclose(cross, 0, atol=atol): - rot_boundaries = [cross, -1 * np.dot(op, cross)] - face_center_found = True - used_axes.append(ax) - break - if not face_center_found: - print("face center not found") - for point in IRBZ_points: - cross = D * np.dot(g_inv, np.cross(ax, point[1])) - if not np.allclose(cross, 0, atol=atol): - rot_boundaries = [cross, -1 * np.dot(op, cross)] - used_axes.append(ax) - break - IRBZ_points = self._reduce_IRBZ(IRBZ_points, rot_boundaries, g, atol) + IRBZ_points = find_face_center("six-fold", IRBZ_points) + + IRBZ_points = find_face_center("rotoinv-four-fold", IRBZ_points) + + IRBZ_points = find_face_center("four-fold", IRBZ_points) + + IRBZ_points = find_face_center("rotoinv-three-fold", IRBZ_points) + + IRBZ_points = find_face_center("three-fold", IRBZ_points) + + IRBZ_points = find_face_center("two-fold", IRBZ_points) return [point[0] for point in IRBZ_points] @@ -2035,22 +1928,29 @@ def _get_reciprocal_point_group_dict(recip_point_group, atol): if np.isclose(tr, 3, atol=atol): continue if np.isclose(tr, -1, atol=atol): # two-fold rotation + ax = None for j in range(3): if np.isclose(evals[j], 1, atol=atol): ax = evects[:, j] dct["rotations"]["two-fold"].append({"ind": idx, "axis": ax, "op": op}) + elif np.isclose(tr, 0, atol=atol): # three-fold rotation + ax = None for j in range(3): if np.isreal(evals[j]) and np.isclose(np.absolute(evals[j]), 1, atol=atol): ax = evects[:, j] dct["rotations"]["three-fold"].append({"ind": idx, "axis": ax, "op": op}) + # four-fold rotation elif np.isclose(tr, 1, atol=atol): + ax = None for j in range(3): if np.isreal(evals[j]) and np.isclose(np.absolute(evals[j]), 1, atol=atol): ax = evects[:, j] dct["rotations"]["four-fold"].append({"ind": idx, "axis": ax, "op": op}) + elif np.isclose(tr, 2, atol=atol): # six-fold rotation + ax = None for j in range(3): if np.isreal(evals[j]) and np.isclose(np.absolute(evals[j]), 1, atol=atol): ax = evects[:, j] @@ -2060,24 +1960,32 @@ def _get_reciprocal_point_group_dict(recip_point_group, atol): if np.isclose(det, -1, atol=atol): if np.isclose(tr, -3, atol=atol): dct["inversion"].append({"ind": idx, "op": PAR}) + elif np.isclose(tr, 1, atol=atol): # two-fold rotation + norm = None for j in range(3): if np.isclose(evals[j], -1, atol=atol): norm = evects[:, j] dct["reflections"].append({"ind": idx, "normal": norm, "op": op}) + elif np.isclose(tr, 0, atol=atol): # three-fold rotoinversion + ax = None for j in range(3): if np.isreal(evals[j]) and np.isclose(np.absolute(evals[j]), 1, atol=atol): ax = evects[:, j] dct["rotations"]["rotoinv-three-fold"].append({"ind": idx, "axis": ax, "op": op}) + # four-fold rotoinversion elif np.isclose(tr, -1, atol=atol): + ax = None for j in range(3): if np.isreal(evals[j]) and np.isclose(np.absolute(evals[j]), 1, atol=atol): ax = evects[:, j] dct["rotations"]["rotoinv-four-fold"].append({"ind": idx, "axis": ax, "op": op}) + # six-fold rotoinversion elif np.isclose(tr, -2, atol=atol): + ax = None for j in range(3): if np.isreal(evals[j]) and np.isclose(np.absolute(evals[j]), 1, atol=atol): ax = evects[:, j] diff --git a/pymatgen/symmetry/maggroups.py b/pymatgen/symmetry/maggroups.py index aa2460be385..e0c2e0afadf 100644 --- a/pymatgen/symmetry/maggroups.py +++ b/pymatgen/symmetry/maggroups.py @@ -193,10 +193,10 @@ def get_label(idx): n = 1 # nth Wyckoff site num_wyckoff = b[0] while len(wyckoff_sites) < num_wyckoff: - m = b[1 + o] # multiplicity - label = str(b[2 + o] * m) + get_label(num_wyckoff - n) + multiplicity = b[1 + o] + label = str(b[2 + o] * multiplicity) + get_label(num_wyckoff - n) sites = [] - for j in range(m): + for j in range(multiplicity): s = b[3 + o + (j * 22) : 3 + o + (j * 22) + 22] # data corresponding to specific Wyckoff position translation_vec = [s[0] / s[3], s[1] / s[3], s[2] / s[3]] matrix = [ @@ -227,7 +227,7 @@ def get_label(idx): # could do something else with these in future wyckoff_sites.append({"label": label, "str": " ".join(s["str"] for s in sites)}) n += 1 - o += m * 22 + 2 + o += multiplicity * 22 + 2 return wyckoff_sites diff --git a/pymatgen/transformations/advanced_transformations.py b/pymatgen/transformations/advanced_transformations.py index 5299f70fcf8..338387283c3 100644 --- a/pymatgen/transformations/advanced_transformations.py +++ b/pymatgen/transformations/advanced_transformations.py @@ -386,7 +386,7 @@ def apply_transformation( raise ValueError(f"Too many disordered sites! ({n_disordered} > {self.max_disordered_sites})") max_cell_sizes: Iterable[int] = range( self.min_cell_size, - int(math.floor(self.max_disordered_sites / n_disordered)) + 1, + math.floor(self.max_disordered_sites / n_disordered) + 1, ) else: max_cell_sizes = [self.max_cell_size] @@ -416,6 +416,8 @@ def apply_transformation( original_latt = structure.lattice inv_latt = np.linalg.inv(original_latt.matrix) ewald_matrices = {} + m3gnet_model = None + if not callable(self.sort_criteria) and self.sort_criteria.startswith("m3gnet"): import matgl from matgl.ext.ase import M3GNetCalculator, Relaxer @@ -452,13 +454,15 @@ def _get_stats(struct): relax_results = m3gnet_model.relax(struct) energy = float(relax_results["trajectory"].energies[-1]) struct = relax_results["final_structure"] - else: - from pymatgen.io.ase import AseAtomsAdaptor + elif self.sort_criteria == "m3gnet": atoms = AseAtomsAdaptor().get_atoms(struct) m3gnet_model.calculate(atoms) energy = float(m3gnet_model.results["energy"]) + else: + raise ValueError("Unsupported sort criteria.") + return { "num_sites": len(struct), "energy": energy, @@ -2076,6 +2080,9 @@ def apply_transformation(self, structure: Structure, return_ranked_list: bool | sqs_kwargs=self.icet_sqs_kwargs, ).run() + else: + raise RuntimeError(f"Unsupported SQS method {self.sqs_method}.") + return self._get_unique_best_sqs_structs( sqs, best_only=self.best_only, diff --git a/pymatgen/util/coord.py b/pymatgen/util/coord.py index 8af3059ebfb..a9c26f3405a 100644 --- a/pymatgen/util/coord.py +++ b/pymatgen/util/coord.py @@ -265,9 +265,9 @@ def is_coord_subset_pbc(subset, superset, atol: float = 1e-8, mask=None, pbc: Pb """ c1 = np.array(subset, dtype=np.float64) c2 = np.array(superset, dtype=np.float64) - m = np.array(mask, dtype=int) if mask is not None else np.zeros((len(subset), len(superset)), dtype=int) + mask_arr = np.array(mask, dtype=int) if mask is not None else np.zeros((len(subset), len(superset)), dtype=int) atol = np.zeros(3, dtype=np.float64) + atol - return coord_cython.is_coord_subset_pbc(c1, c2, atol, m, pbc) + return coord_cython.is_coord_subset_pbc(c1, c2, atol, mask_arr, pbc) def lattice_points_in_supercell(supercell_matrix): diff --git a/pymatgen/util/coord_cython.pyx b/pymatgen/util/coord_cython.pyx index 428c6e92b10..f3a853d2e97 100644 --- a/pymatgen/util/coord_cython.pyx +++ b/pymatgen/util/coord_cython.pyx @@ -126,9 +126,9 @@ def pbc_shortest_vectors(lattice, fcoords1, fcoords2, mask=None, return_d2=False cdef np.float_t[:, ::1] cart_im = malloc(3 * n_pbc_im * sizeof(np.float_t)) cdef bint has_mask = mask is not None - cdef np.int_t[:, :] m + cdef np.int_t[:, :] mask_arr if has_mask: - m = np.array(mask, dtype=np.int_, copy=False, order="C") + mask_arr = np.array(mask, dtype=np.int_, copy=False, order="C") cdef bint has_ftol = (lll_frac_tol is not None) cdef np.float_t[:] ftol @@ -151,7 +151,7 @@ def pbc_shortest_vectors(lattice, fcoords1, fcoords2, mask=None, return_d2=False for i in range(I): for j in range(J): within_frac = False - if (not has_mask) or (m[i, j] == 0): + if (not has_mask) or (mask_arr[i, j] == 0): within_frac = True if has_ftol: for l in range(3): diff --git a/pymatgen/util/provenance.py b/pymatgen/util/provenance.py index 4941cfbd093..438bc06ef19 100644 --- a/pymatgen/util/provenance.py +++ b/pymatgen/util/provenance.py @@ -18,7 +18,7 @@ from pybtex import errors from pybtex.database.input import bibtex except ImportError: - pybtex = bibtex = None + pybtex = bibtex = errors = None if TYPE_CHECKING: from collections.abc import Sequence @@ -146,10 +146,10 @@ def parse_author(cls, author) -> Self: if isinstance(author, str): # Regex looks for whitespace, (any name), whitespace, <, (email), # >, whitespace - m = re.match(r"\s*(.*?)\s*<(.*?@.*?)>\s*", author) - if not m or m.start() != 0 or m.end() != len(author): + match = re.match(r"\s*(.*?)\s*<(.*?@.*?)>\s*", author) + if not match or match.start() != 0 or match.end() != len(author): raise ValueError(f"Invalid author format! {author}") - return cls(m.groups()[0], m.groups()[1]) + return cls(match.groups()[0], match.groups()[1]) if isinstance(author, dict): return cls.from_dict(author) if len(author) != 2: diff --git a/pymatgen/util/string.py b/pymatgen/util/string.py index bc6f3b9b99a..cbd7da1c5a2 100644 --- a/pymatgen/util/string.py +++ b/pymatgen/util/string.py @@ -298,11 +298,11 @@ def transformation_to_string(matrix, translation_vec=(0, 0, 0), components=("x", parts = [] for idx in range(3): string = "" - m = matrix[idx] + mat = matrix[idx] offset = translation_vec[idx] for j, dim in enumerate(components): - if m[j] != 0: - f = Fraction(m[j]).limit_denominator() + if mat[j] != 0: + f = Fraction(mat[j]).limit_denominator() if string != "" and f >= 0: string += "+" if abs(f.numerator) != 1: @@ -398,7 +398,10 @@ def disordered_formula(disordered_struct, symbols=("x", "y", "z"), fmt="plain"): elif fmt == "HTML": sub_start = "" sub_end = "" - elif fmt != "plain": + elif fmt == "plain": + sub_start = "" + sub_end = "" + else: raise ValueError("Unsupported output format, choose from: LaTeX, HTML, plain") disordered_formula = [] diff --git a/pymatgen/vis/structure_chemview.py b/pymatgen/vis/structure_chemview.py index 4a62a215c5c..9007bb9446f 100644 --- a/pymatgen/vis/structure_chemview.py +++ b/pymatgen/vis/structure_chemview.py @@ -3,7 +3,6 @@ from __future__ import annotations import numpy as np -from monty.dev import requires from pymatgen.analysis.molecule_structure_comparator import CovalentRadius from pymatgen.symmetry.analyzer import SpacegroupAnalyzer @@ -15,9 +14,9 @@ chemview_loaded = True except ImportError: chemview_loaded = False + MolecularViewer = get_atom_color = None -@requires(chemview_loaded, "To use quick_view, you need to have chemview installed.") def quick_view( structure, bonds=True, @@ -43,6 +42,10 @@ def quick_view( Returns: A chemview.MolecularViewer object """ + # Ensure MolecularViewer is loaded + if not chemview_loaded: + raise RuntimeError("MolecularViewer not loaded.") + struct = structure.copy() if conventional: struct = SpacegroupAnalyzer(struct).get_conventional_standard_structure() diff --git a/pymatgen/vis/structure_vtk.py b/pymatgen/vis/structure_vtk.py index 0a8c6b46e2e..cfad254aae4 100644 --- a/pymatgen/vis/structure_vtk.py +++ b/pymatgen/vis/structure_vtk.py @@ -227,11 +227,9 @@ def set_structure(self, structure: Structure, reset_camera=True, to_unit_cell=Tr labels = ["a", "b", "c"] colors = [(1, 0, 0), (0, 1, 0), (0, 0, 1)] - if has_lattice: - matrix = struct.lattice.matrix + matrix = struct.lattice.matrix if has_lattice else None if self.show_unit_cell and has_lattice: - # matrix = s.lattice.matrix self.add_text([0, 0, 0], "o") for vec in matrix: self.add_line((0, 0, 0), vec, colors[count]) @@ -340,10 +338,8 @@ def add_site(self, site): vis_radius = 0.2 + 0.002 * radius for specie, occu in site.species.items(): - if not specie: - color = (1, 1, 1) - elif specie.symbol in self.el_color_mapping: - color = [i / 255 for i in self.el_color_mapping[specie.symbol]] + color = [i / 255 for i in self.el_color_mapping.get(specie.symbol, (255, 255, 255))] + mapper = self.add_partial_sphere(site.coords, vis_radius, color, start_angle, start_angle + 360 * occu) self.mapper_map[mapper] = [site] start_angle += 360 * occu @@ -487,14 +483,16 @@ def add_polyhedron( # If partial occupations are involved, the color of the specie with # the highest occupation is used max_occu = 0.0 - for specie, occu in center.species.items(): + max_species = next(iter(center.species), None) + for species, occu in center.species.items(): if occu > max_occu: - max_specie = specie + max_species = species max_occu = occu - color = [i / 255 for i in self.el_color_mapping[max_specie.symbol]] + color = [i / 255 for i in self.el_color_mapping[max_species.symbol]] ac.GetProperty().SetColor(color) else: ac.GetProperty().SetColor(color) + if draw_edges: ac.GetProperty().SetEdgeColor(edges_color) ac.GetProperty().SetLineWidth(edges_linewidth) @@ -551,11 +549,12 @@ def add_triangle( # If partial occupations are involved, the color of the specie with # the highest occupation is used max_occu = 0.0 - for specie, occu in center.species.items(): + max_species = next(iter(center.species), None) + for species, occu in center.species.items(): if occu > max_occu: - max_specie = specie + max_species = species max_occu = occu - color = [i / 255 for i in self.el_color_mapping[max_specie.symbol]] + color = [i / 255 for i in self.el_color_mapping[max_species.symbol]] ac.GetProperty().SetColor(color) else: ac.GetProperty().SetColor(color) @@ -868,7 +867,7 @@ def make_movie(structures, output_filename="movie.mp4", zoom=1.0, fps=20, bitrat vis.show_help = False vis.redraw() vis.zoom(zoom) - sig_fig = int(math.floor(math.log10(len(structures))) + 1) + sig_fig = math.floor(math.log10(len(structures))) + 1 filename = f"image{{0:0{sig_fig}d}}.png" for idx, site in enumerate(structures): vis.set_structure(site) @@ -957,17 +956,21 @@ def set_structures(self, structures: Sequence[Structure], tags=None): struct_vis_radii = [] for site in struct: radius = 0 - for specie, occu in site.species.items(): + vis_radius = 0.2 + for species, occu in site.species.items(): radius += occu * ( - specie.ionic_radius - if isinstance(specie, Species) and specie.ionic_radius - else specie.average_ionic_radius + species.ionic_radius + if isinstance(species, Species) and species.ionic_radius + else species.average_ionic_radius ) vis_radius = 0.2 + 0.002 * radius + struct_radii.append(radius) struct_vis_radii.append(vis_radius) + self.all_radii.append(struct_radii) self.all_vis_radii.append(struct_vis_radii) + self.set_structure(self.current_structure, reset_camera=True, to_unit_cell=False) def set_structure(self, structure: Structure, reset_camera=True, to_unit_cell=False): diff --git a/pyproject.toml b/pyproject.toml index 44f184029cc..5bcb0f6abfc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -143,3 +143,12 @@ rute,reson,titels,ges,scalr,strat,struc,hda,nin,ons,pres,kno,loos,lamda,lew,atom """ skip = "pymatgen/analysis/aflow_prototypes.json" check-filenames = true + +[tool.pyright] +typeCheckingMode = "off" +reportPossiblyUnboundVariable = true +reportUnboundVariable = true +reportMissingImports = false +reportMissingModuleSource = false +reportInvalidTypeForm = false +exclude = ["**/tests"] diff --git a/tests/files/cohp/lobsterin.1 b/tests/files/cohp/lobsterin.1 index 046868f0691..8b92bf4613b 100644 --- a/tests/files/cohp/lobsterin.1 +++ b/tests/files/cohp/lobsterin.1 @@ -4,7 +4,7 @@ basisSet pbeVaspFit2015 gaussianSmearingWidth 0.1 basisfunctions Fe 3d 4p 4s basisfunctions Co 3d 4p 4s -skipdos +skipDOS skipcohp skipcoop skipPopulationAnalysis diff --git a/tests/files/cohp/lobsterin.3 b/tests/files/cohp/lobsterin.3 index e0ca783e1d8..e39e8e9230e 100644 --- a/tests/files/cohp/lobsterin.3 +++ b/tests/files/cohp/lobsterin.3 @@ -3,4 +3,6 @@ COHPendEnergy 5.0 basisSet pbeVaspFit2015 gaussianSmearingWidth 0.1 basisfunctions Fe 3d 4p 4s -basisfunctions Co 3d 4p 4s \ No newline at end of file +basisfunctions Co 3d 4p 4s +skipcoBI +SKIPDOS \ No newline at end of file diff --git a/tests/io/lobster/test_inputs.py b/tests/io/lobster/test_inputs.py index 6e1c87fcefe..1a0be9ab9dc 100644 --- a/tests/io/lobster/test_inputs.py +++ b/tests/io/lobster/test_inputs.py @@ -693,7 +693,7 @@ def setUp(self): self.DOSCAR_spin_pol = Doscar(doscar=doscar, structure_file=poscar) self.DOSCAR_nonspin_pol = Doscar(doscar=doscar2, structure_file=poscar2) - with open(f"{TEST_FILES_DIR}/structure_KF.json") as file: + with open(f"{TEST_FILES_DIR}/structure_KF.json", encoding="utf-8") as file: data = json.load(file) self.structure = Structure.from_dict(data) @@ -1612,11 +1612,11 @@ def test_initialize_from_dict(self): assert lobsterin["basisfunctions"][0] == "Fe 3d 4p 4s" assert lobsterin["basisfunctions"][1] == "Co 3d 4p 4s" assert {*lobsterin} >= {"skipdos", "skipcohp", "skipcoop", "skippopulationanalysis", "skipgrosspopulation"} - with pytest.raises(IOError, match="There are duplicates for the keywords! The program will stop here."): + with pytest.raises(KeyError, match="There are duplicates for the keywords!"): lobsterin2 = Lobsterin({"cohpstartenergy": -15.0, "cohpstartEnergy": -20.0}) lobsterin2 = Lobsterin({"cohpstartenergy": -15.0}) # can only calculate nbands if basis functions are provided - with pytest.raises(IOError, match="No basis functions are provided. The program cannot calculate nbands"): + with pytest.raises(ValueError, match="No basis functions are provided. The program cannot calculate nbands"): lobsterin2._get_nbands(structure=Structure.from_file(f"{VASP_IN_DIR}/POSCAR_Fe3O4")) def test_standard_settings(self): @@ -1752,7 +1752,7 @@ def test_standard_with_energy_range_from_vasprun(self): def test_diff(self): # test diff assert self.Lobsterinfromfile.diff(self.Lobsterinfromfile2)["Different"] == {} - assert self.Lobsterinfromfile.diff(self.Lobsterinfromfile2)["Same"]["COHPSTARTENERGY"] == approx(-15.0) + assert self.Lobsterinfromfile.diff(self.Lobsterinfromfile2)["Same"]["cohpstartenergy"] == approx(-15.0) # test diff in both directions for entry in self.Lobsterinfromfile.diff(self.Lobsterinfromfile3)["Same"]: @@ -1765,8 +1765,8 @@ def test_diff(self): assert entry in self.Lobsterinfromfile.diff(self.Lobsterinfromfile3)["Different"] assert ( - self.Lobsterinfromfile.diff(self.Lobsterinfromfile3)["Different"]["SKIPCOHP"]["lobsterin1"] - == self.Lobsterinfromfile3.diff(self.Lobsterinfromfile)["Different"]["SKIPCOHP"]["lobsterin2"] + self.Lobsterinfromfile.diff(self.Lobsterinfromfile3)["Different"]["skipcohp"]["lobsterin1"] + == self.Lobsterinfromfile3.diff(self.Lobsterinfromfile)["Different"]["skipcohp"]["lobsterin2"] ) def test_dict_functionality(self): @@ -1793,8 +1793,6 @@ def test_read_write_lobsterin(self): lobsterin2 = Lobsterin.from_file(outfile_path) assert lobsterin1.diff(lobsterin2)["Different"] == {} - # TODO: will integer vs float break cohpsteps? - def test_get_basis(self): # get basis functions lobsterin1 = Lobsterin({}) @@ -2590,7 +2588,7 @@ def test_raises(self): self.hamilton_matrices = LobsterMatrices(filename=f"{TEST_FILES_DIR}/cohp/Na_hamiltonMatrices.lobster.gz") with pytest.raises( - OSError, - match=r"Please check provided input file, it seems to be empty", + RuntimeError, + match="Please check provided input file, it seems to be empty", ): self.hamilton_matrices = LobsterMatrices(filename=f"{TEST_FILES_DIR}/cohp/hamiltonMatrices.lobster")