From 41e6d99f0b6a4ea9935aff34f1c7415ff661155d Mon Sep 17 00:00:00 2001 From: Janosh Riebesell Date: Sat, 3 Aug 2024 13:27:41 -0400 Subject: [PATCH] refactor dict.update() to use |= operator --- .../coordination_geometry_finder.py | 4 +- src/pymatgen/analysis/chempot_diagram.py | 6 +- .../analysis/ferroelectricity/polarization.py | 5 +- src/pymatgen/analysis/graphs.py | 4 +- src/pymatgen/analysis/local_env.py | 10 +- src/pymatgen/analysis/phase_diagram.py | 408 +++++++++--------- src/pymatgen/analysis/pourbaix_diagram.py | 10 +- .../apps/battery/insertion_battery.py | 26 +- src/pymatgen/core/structure.py | 10 +- src/pymatgen/entries/computed_entries.py | 16 +- src/pymatgen/ext/matproj.py | 2 +- src/pymatgen/ext/matproj_legacy.py | 8 +- src/pymatgen/io/abinit/abiobjects.py | 64 +-- src/pymatgen/io/aims/parsers.py | 62 +-- src/pymatgen/io/core.py | 8 + src/pymatgen/io/cp2k/inputs.py | 29 +- src/pymatgen/io/cp2k/sets.py | 62 ++- src/pymatgen/io/feff/sets.py | 2 +- src/pymatgen/io/lammps/data.py | 6 +- src/pymatgen/io/lammps/inputs.py | 4 +- src/pymatgen/io/lammps/outputs.py | 8 +- src/pymatgen/io/lobster/outputs.py | 42 +- src/pymatgen/io/nwchem.py | 40 +- src/pymatgen/io/pwmat/inputs.py | 12 +- src/pymatgen/io/pwmat/outputs.py | 16 +- src/pymatgen/io/vasp/inputs.py | 2 +- src/pymatgen/io/vasp/sets.py | 2 +- tests/io/cp2k/test_sets.py | 2 +- 28 files changed, 394 insertions(+), 476 deletions(-) diff --git a/src/pymatgen/analysis/chemenv/coordination_environments/coordination_geometry_finder.py b/src/pymatgen/analysis/chemenv/coordination_environments/coordination_geometry_finder.py index 2e2b91e0066..20aa39158fc 100644 --- a/src/pymatgen/analysis/chemenv/coordination_environments/coordination_geometry_finder.py +++ b/src/pymatgen/analysis/chemenv/coordination_environments/coordination_geometry_finder.py @@ -613,8 +613,8 @@ def compute_structure_environments( optimization: optimization algorithm Returns: - The StructureEnvironments object containing all the information about the coordination - environments in the structure. + StructureEnvironments: contains all the information about the coordination + environments in the structure. """ time_init = time.process_time() if info is None: diff --git a/src/pymatgen/analysis/chempot_diagram.py b/src/pymatgen/analysis/chempot_diagram.py index a2545066477..775775cf8b9 100644 --- a/src/pymatgen/analysis/chempot_diagram.py +++ b/src/pymatgen/analysis/chempot_diagram.py @@ -295,7 +295,7 @@ def _get_2d_plot(self, elements: list[Element], label_stable: bool | None, eleme draw_domains[formula] = pts_2d layout = plotly_layouts["default_layout_2d"].copy() - layout.update(self._get_axis_layout_dict(elements)) + layout |= self._get_axis_layout_dict(elements) if label_stable: layout["annotations"] = annotations @@ -366,7 +366,7 @@ def _get_3d_plot( domain_simplexes[formula] = simplexes layout = plotly_layouts["default_layout_3d"].copy() - layout["scene"].update(self._get_axis_layout_dict(elements)) + layout["scene"] |= self._get_axis_layout_dict(elements) layout["scene"]["annotations"] = None if label_stable: @@ -559,7 +559,7 @@ def _get_annotation(ann_loc: np.ndarray, formula: str) -> dict[str, str | float] """Get a Plotly annotation dict given a formula and location.""" formula = htmlify(formula) annotation = plotly_layouts["default_annotation_layout"].copy() - annotation.update({"x": ann_loc[0], "y": ann_loc[1], "text": formula}) + annotation |= {"x": ann_loc[0], "y": ann_loc[1], "text": formula} if len(ann_loc) == 3: annotation["z"] = ann_loc[2] return annotation diff --git a/src/pymatgen/analysis/ferroelectricity/polarization.py b/src/pymatgen/analysis/ferroelectricity/polarization.py index 0ec0e7e1043..7ce1cd2e4d8 100644 --- a/src/pymatgen/analysis/ferroelectricity/polarization.py +++ b/src/pymatgen/analysis/ferroelectricity/polarization.py @@ -75,10 +75,7 @@ def zval_dict_from_potcar(potcar) -> dict[str, float]: potcar: Potcar object """ - zval_dict = {} - for p in potcar: - zval_dict.update({p.element: p.ZVAL}) - return zval_dict + return {p.element: p.ZVAL for p in potcar} def calc_ionic(site: PeriodicSite, structure: Structure, zval: float) -> np.ndarray: diff --git a/src/pymatgen/analysis/graphs.py b/src/pymatgen/analysis/graphs.py index a23ce49ef36..eda21ac92ef 100644 --- a/src/pymatgen/analysis/graphs.py +++ b/src/pymatgen/analysis/graphs.py @@ -933,7 +933,7 @@ def draw_graph_to_file( d["label"] = f"{d['weight']:.2f} {units}" # update edge with our new style attributes - g.edges[u, v, k].update(d) + g.edges[u, v, k] |= d # optionally remove periodic image edges, # these can be confusing due to periodic boundaries @@ -2603,7 +2603,7 @@ def draw_graph_to_file( d["label"] = f"{d['weight']:.2f} {units}" # update edge with our new style attributes - g.edges[u, v, k].update(d) + g.edges[u, v, k] |= d # optionally remove periodic image edges, # these can be confusing due to periodic boundaries diff --git a/src/pymatgen/analysis/local_env.py b/src/pymatgen/analysis/local_env.py index 42319b7c6b4..6f6701f1ce8 100644 --- a/src/pymatgen/analysis/local_env.py +++ b/src/pymatgen/analysis/local_env.py @@ -1218,7 +1218,7 @@ def __init__( # Update any user preference elemental radii if el_radius_updates: - self.el_radius.update(el_radius_updates) + self.el_radius |= el_radius_updates @property def structures_allowed(self) -> bool: @@ -2755,7 +2755,7 @@ def get_type(self, index): raise ValueError("Index for getting order parameter type out-of-bounds!") return self._types[index] - def get_parameters(self, index): + def get_parameters(self, index: int) -> list[float]: """Get list of floats that represents the parameters associated with calculation of the order @@ -2764,12 +2764,10 @@ def get_parameters(self, index): inputted because of processing out of efficiency reasons. Args: - index (int): - index of order parameter for which associated parameters - are to be returned. + index (int): index of order parameter for which to return associated params. Returns: - [float]: parameters of a given OP. + list[float]: parameters of a given OP. """ if index < 0 or index >= len(self._types): raise ValueError("Index for getting parameters associated with order parameter calculation out-of-bounds!") diff --git a/src/pymatgen/analysis/phase_diagram.py b/src/pymatgen/analysis/phase_diagram.py index ec8af0b94b8..520691bb9f3 100644 --- a/src/pymatgen/analysis/phase_diagram.py +++ b/src/pymatgen/analysis/phase_diagram.py @@ -96,9 +96,7 @@ def energy(self) -> float: def as_dict(self): """Get MSONable dict representation of PDEntry.""" - return_dict = super().as_dict() - return_dict.update({"name": self.name, "attribute": self.attribute}) - return return_dict + return super().as_dict() | {"name": self.name, "attribute": self.attribute} @classmethod def from_dict(cls, dct: dict) -> Self: @@ -1903,7 +1901,7 @@ def __init__(self, entry1, entry2, all_entries, tol: float = 1e-4, float_fmt="%. """ elem_set = set() for entry in [entry1, entry2]: - elem_set.update([el.symbol for el in entry.elements]) + elem_set |= {el.symbol for el in entry.elements} elements = tuple(elem_set) # Fix elements to ensure order. @@ -2948,7 +2946,7 @@ def _create_plotly_element_annotations(self): for d in ["xref", "yref"]: annotation.pop(d) # Scatter3d cannot contain xref, yref if self._dim == 3: - annotation.update({"x": y, "y": x}) + annotation.update(x=x, y=y) if entry.composition.is_element: z = 0.9 * self._min_energy # place label 10% above base @@ -3096,26 +3094,24 @@ def get_marker_props(coords, entries): if highlight_entries: highlight_markers = plotly_layouts["default_unary_marker_settings"].copy() highlight_markers.update( - { - "x": [0] * len(highlight_props["y"]), - "y": list(highlight_props["x"]), - "name": "Highlighted", - "marker": { - "color": "mediumvioletred", - "size": 22, - "line": {"color": "black", "width": 2}, - "symbol": "square", - }, - "opacity": 0.9, - "hovertext": highlight_props["texts"], - "error_y": { - "array": list(highlight_props["uncertainties"]), - "type": "data", - "color": "gray", - "thickness": 2.5, - "width": 5, - }, - } + x=[0] * len(highlight_props["y"]), + y=list(highlight_props["x"]), + name="Highlighted", + marker={ + "color": "mediumvioletred", + "size": 22, + "line": {"color": "black", "width": 2}, + "symbol": "square", + }, + opacity=0.9, + hovertext=highlight_props["texts"], + error_y={ + "array": list(highlight_props["uncertainties"]), + "type": "data", + "color": "gray", + "thickness": 2.5, + "width": 5, + }, ) if self._dim == 2: @@ -3137,22 +3133,20 @@ def get_marker_props(coords, entries): "width": 5, }, ) - unstable_markers.update( - { - "x": list(unstable_props["x"]), - "y": list(unstable_props["y"]), - "name": "Above Hull", - "marker": { - "color": unstable_props["energies"], - "colorscale": plotly_layouts["unstable_colorscale"], - "size": 7, - "symbol": "diamond", - "line": {"color": "black", "width": 1}, - "opacity": 0.8, - }, - "hovertext": unstable_props["texts"], - } - ) + unstable_markers |= { + "x": list(unstable_props["x"]), + "y": list(unstable_props["y"]), + "name": "Above Hull", + "marker": { + "color": unstable_props["energies"], + "colorscale": plotly_layouts["unstable_colorscale"], + "size": 7, + "symbol": "diamond", + "line": {"color": "black", "width": 1}, + "opacity": 0.8, + }, + "hovertext": unstable_props["texts"], + } if highlight_entries: highlight_markers = plotly_layouts["default_binary_marker_settings"].copy() highlight_markers.update( @@ -3180,209 +3174,191 @@ def get_marker_props(coords, entries): stable_markers = plotly_layouts["default_ternary_2d_marker_settings"].copy() unstable_markers = plotly_layouts["default_ternary_2d_marker_settings"].copy() - stable_markers.update( - { - "a": list(stable_props["x"]), - "b": list(stable_props["y"]), - "c": list(stable_props["z"]), - "name": "Stable", - "hovertext": stable_props["texts"], - "marker": { - "color": "green", - "line": {"width": 2.0, "color": "black"}, - "symbol": "circle", - "size": 15, + stable_markers |= { + "a": list(stable_props["x"]), + "b": list(stable_props["y"]), + "c": list(stable_props["z"]), + "name": "Stable", + "hovertext": stable_props["texts"], + "marker": { + "color": "green", + "line": {"width": 2.0, "color": "black"}, + "symbol": "circle", + "size": 15, + }, + } + unstable_markers |= { + "a": unstable_props["x"], + "b": unstable_props["y"], + "c": unstable_props["z"], + "name": "Above Hull", + "hovertext": unstable_props["texts"], + "marker": { + "color": unstable_props["energies"], + "opacity": 0.8, + "colorscale": plotly_layouts["unstable_colorscale"], + "line": {"width": 1, "color": "black"}, + "size": 7, + "symbol": "diamond", + "colorbar": { + "title": "Energy Above Hull
(eV/atom)", + "x": 0, + "y": 1, + "yanchor": "top", + "xpad": 0, + "ypad": 0, + "thickness": 0.02, + "thicknessmode": "fraction", + "len": 0.5, }, - } - ) - unstable_markers.update( - { - "a": unstable_props["x"], - "b": unstable_props["y"], - "c": unstable_props["z"], - "name": "Above Hull", - "hovertext": unstable_props["texts"], + }, + } + if highlight_entries: + highlight_markers = plotly_layouts["default_ternary_2d_marker_settings"].copy() + highlight_markers |= { + "a": list(highlight_props["x"]), + "b": list(highlight_props["y"]), + "c": list(highlight_props["z"]), + "name": "Highlighted", + "hovertext": highlight_props["texts"], "marker": { - "color": unstable_props["energies"], - "opacity": 0.8, - "colorscale": plotly_layouts["unstable_colorscale"], - "line": {"width": 1, "color": "black"}, - "size": 7, - "symbol": "diamond", - "colorbar": { - "title": "Energy Above Hull
(eV/atom)", - "x": 0, - "y": 1, - "yanchor": "top", - "xpad": 0, - "ypad": 0, - "thickness": 0.02, - "thicknessmode": "fraction", - "len": 0.5, - }, + "color": "mediumvioletred", + "line": {"width": 2.0, "color": "black"}, + "symbol": "square", + "size": 16, }, } - ) - if highlight_entries: - highlight_markers = plotly_layouts["default_ternary_2d_marker_settings"].copy() - highlight_markers.update( - { - "a": list(highlight_props["x"]), - "b": list(highlight_props["y"]), - "c": list(highlight_props["z"]), - "name": "Highlighted", - "hovertext": highlight_props["texts"], - "marker": { - "color": "mediumvioletred", - "line": {"width": 2.0, "color": "black"}, - "symbol": "square", - "size": 16, - }, - } - ) elif self._dim == 3 and self.ternary_style == "3d": stable_markers = plotly_layouts["default_ternary_3d_marker_settings"].copy() unstable_markers = plotly_layouts["default_ternary_3d_marker_settings"].copy() - stable_markers.update( - { - "x": list(stable_props["y"]), - "y": list(stable_props["x"]), - "z": list(stable_props["z"]), - "name": "Stable", + stable_markers |= { + "x": list(stable_props["y"]), + "y": list(stable_props["x"]), + "z": list(stable_props["z"]), + "name": "Stable", + "marker": { + "color": "#1e1e1f", + "size": 11, + "opacity": 0.99, + }, + "hovertext": stable_props["texts"], + "error_z": { + "array": list(stable_props["uncertainties"]), + "type": "data", + "color": "darkgray", + "width": 10, + "thickness": 5, + }, + } + unstable_markers |= { + "x": unstable_props["y"], + "y": unstable_props["x"], + "z": unstable_props["z"], + "name": "Above Hull", + "hovertext": unstable_props["texts"], + "marker": { + "color": unstable_props["energies"], + "colorscale": plotly_layouts["unstable_colorscale"], + "size": 5, + "line": {"color": "black", "width": 1}, + "symbol": "diamond", + "opacity": 0.7, + "colorbar": { + "title": "Energy Above Hull
(eV/atom)", + "x": 0, + "y": 1, + "yanchor": "top", + "xpad": 0, + "ypad": 0, + "thickness": 0.02, + "thicknessmode": "fraction", + "len": 0.5, + }, + }, + } + if highlight_entries: + highlight_markers = plotly_layouts["default_ternary_3d_marker_settings"].copy() + highlight_markers |= { + "x": list(highlight_props["y"]), + "y": list(highlight_props["x"]), + "z": list(highlight_props["z"]), + "name": "Highlighted", "marker": { - "color": "#1e1e1f", - "size": 11, + "size": 12, "opacity": 0.99, + "symbol": "square", + "color": "mediumvioletred", }, - "hovertext": stable_props["texts"], + "hovertext": highlight_props["texts"], "error_z": { - "array": list(stable_props["uncertainties"]), + "array": list(highlight_props["uncertainties"]), "type": "data", "color": "darkgray", "width": 10, "thickness": 5, }, } - ) - unstable_markers.update( - { - "x": unstable_props["y"], - "y": unstable_props["x"], - "z": unstable_props["z"], - "name": "Above Hull", - "hovertext": unstable_props["texts"], - "marker": { - "color": unstable_props["energies"], - "colorscale": plotly_layouts["unstable_colorscale"], - "size": 5, - "line": {"color": "black", "width": 1}, - "symbol": "diamond", - "opacity": 0.7, - "colorbar": { - "title": "Energy Above Hull
(eV/atom)", - "x": 0, - "y": 1, - "yanchor": "top", - "xpad": 0, - "ypad": 0, - "thickness": 0.02, - "thicknessmode": "fraction", - "len": 0.5, - }, - }, - } - ) - if highlight_entries: - highlight_markers = plotly_layouts["default_ternary_3d_marker_settings"].copy() - highlight_markers.update( - { - "x": list(highlight_props["y"]), - "y": list(highlight_props["x"]), - "z": list(highlight_props["z"]), - "name": "Highlighted", - "marker": { - "size": 12, - "opacity": 0.99, - "symbol": "square", - "color": "mediumvioletred", - }, - "hovertext": highlight_props["texts"], - "error_z": { - "array": list(highlight_props["uncertainties"]), - "type": "data", - "color": "darkgray", - "width": 10, - "thickness": 5, - }, - } - ) elif self._dim == 4: stable_markers = plotly_layouts["default_quaternary_marker_settings"].copy() unstable_markers = plotly_layouts["default_quaternary_marker_settings"].copy() - stable_markers.update( - { - "x": stable_props["x"], - "y": stable_props["y"], - "z": stable_props["z"], - "name": "Stable", - "marker": { - "size": 7, - "opacity": 0.99, - "color": "darkgreen", - "line": {"color": "black", "width": 1}, + stable_markers |= { + "x": stable_props["x"], + "y": stable_props["y"], + "z": stable_props["z"], + "name": "Stable", + "marker": { + "size": 7, + "opacity": 0.99, + "color": "darkgreen", + "line": {"color": "black", "width": 1}, + }, + "hovertext": stable_props["texts"], + } + unstable_markers |= { + "x": unstable_props["x"], + "y": unstable_props["y"], + "z": unstable_props["z"], + "name": "Above Hull", + "marker": { + "color": unstable_props["energies"], + "colorscale": plotly_layouts["unstable_colorscale"], + "size": 5, + "symbol": "diamond", + "line": {"color": "black", "width": 1}, + "colorbar": { + "title": "Energy Above Hull
(eV/atom)", + "x": 0, + "y": 1, + "yanchor": "top", + "xpad": 0, + "ypad": 0, + "thickness": 0.02, + "thicknessmode": "fraction", + "len": 0.5, }, - "hovertext": stable_props["texts"], - } - ) - unstable_markers.update( - { - "x": unstable_props["x"], - "y": unstable_props["y"], - "z": unstable_props["z"], - "name": "Above Hull", + }, + "hovertext": unstable_props["texts"], + "visible": "legendonly", + } + if highlight_entries: + highlight_markers = plotly_layouts["default_quaternary_marker_settings"].copy() + highlight_markers |= { + "x": highlight_props["x"], + "y": highlight_props["y"], + "z": highlight_props["z"], + "name": "Highlighted", "marker": { - "color": unstable_props["energies"], - "colorscale": plotly_layouts["unstable_colorscale"], - "size": 5, - "symbol": "diamond", + "size": 9, + "opacity": 0.99, + "symbol": "square", + "color": "mediumvioletred", "line": {"color": "black", "width": 1}, - "colorbar": { - "title": "Energy Above Hull
(eV/atom)", - "x": 0, - "y": 1, - "yanchor": "top", - "xpad": 0, - "ypad": 0, - "thickness": 0.02, - "thicknessmode": "fraction", - "len": 0.5, - }, }, - "hovertext": unstable_props["texts"], - "visible": "legendonly", + "hovertext": highlight_props["texts"], } - ) - if highlight_entries: - highlight_markers = plotly_layouts["default_quaternary_marker_settings"].copy() - highlight_markers.update( - { - "x": highlight_props["x"], - "y": highlight_props["y"], - "z": highlight_props["z"], - "name": "Highlighted", - "marker": { - "size": 9, - "opacity": 0.99, - "symbol": "square", - "color": "mediumvioletred", - "line": {"color": "black", "width": 1}, - }, - "hovertext": highlight_props["texts"], - } - ) highlight_marker_plot = None diff --git a/src/pymatgen/analysis/pourbaix_diagram.py b/src/pymatgen/analysis/pourbaix_diagram.py index 74fa89f3d22..c850855564e 100644 --- a/src/pymatgen/analysis/pourbaix_diagram.py +++ b/src/pymatgen/analysis/pourbaix_diagram.py @@ -795,8 +795,8 @@ def get_decomposition_energy(self, entry, pH, V): Args: entry (PourbaixEntry): PourbaixEntry corresponding to compound to find the decomposition for - pH (float, [float]): pH at which to find the decomposition - V (float, [float]): voltage at which to find the decomposition + pH (float, list[float]): pH at which to find the decomposition + V (float, list[float]): voltage at which to find the decomposition Returns: Decomposition energy for the entry, i. e. the energy above @@ -818,13 +818,13 @@ def get_decomposition_energy(self, entry, pH, V): decomposition_energy /= entry.composition.num_atoms return decomposition_energy - def get_hull_energy(self, pH, V): + def get_hull_energy(self, pH: float | list[float], V: float | list[float]) -> np.ndarray: """Get the minimum energy of the Pourbaix "basin" that is formed from the stable Pourbaix planes. Vectorized. Args: - pH (float or [float]): pH at which to find the hull energy - V (float or [float]): V at which to find the hull energy + pH (float | list[float]): pH at which to find the hull energy + V (float | list[float]): V at which to find the hull energy Returns: np.array: minimum Pourbaix energy at conditions diff --git a/src/pymatgen/apps/battery/insertion_battery.py b/src/pymatgen/apps/battery/insertion_battery.py index 76631f53368..09e53128606 100644 --- a/src/pymatgen/apps/battery/insertion_battery.py +++ b/src/pymatgen/apps/battery/insertion_battery.py @@ -320,19 +320,17 @@ def get_summary_dict(self, print_subelectrodes=True) -> dict: chg_comp = self.fully_charged_entry.composition dischg_comp = self.fully_discharged_entry.composition - dct.update( - { - "id_charge": self.fully_charged_entry.entry_id, - "formula_charge": chg_comp.reduced_formula, - "id_discharge": self.fully_discharged_entry.entry_id, - "formula_discharge": dischg_comp.reduced_formula, - "max_instability": self.get_max_instability(), - "min_instability": self.get_min_instability(), - "material_ids": [itr_ent.entry_id for itr_ent in self.get_all_entries()], - "stable_material_ids": [itr_ent.entry_id for itr_ent in self.get_stable_entries()], - "unstable_material_ids": [itr_ent.entry_id for itr_ent in self.get_unstable_entries()], - } - ) + dct |= { + "id_charge": self.fully_charged_entry.entry_id, + "formula_charge": chg_comp.reduced_formula, + "id_discharge": self.fully_discharged_entry.entry_id, + "formula_discharge": dischg_comp.reduced_formula, + "max_instability": self.get_max_instability(), + "min_instability": self.get_min_instability(), + "material_ids": [itr_ent.entry_id for itr_ent in self.get_all_entries()], + "stable_material_ids": [itr_ent.entry_id for itr_ent in self.get_stable_entries()], + "unstable_material_ids": [itr_ent.entry_id for itr_ent in self.get_unstable_entries()], + } if all("decomposition_energy" in itr_ent.data for itr_ent in self.get_all_entries()): dct.update( stability_charge=self.fully_charged_entry.data["decomposition_energy"], @@ -343,7 +341,7 @@ def get_summary_dict(self, print_subelectrodes=True) -> dict: ) if all("muO2" in itr_ent.data for itr_ent in self.get_all_entries()): - dct.update({"muO2_data": {itr_ent.entry_id: itr_ent.data["muO2"] for itr_ent in self.get_all_entries()}}) + dct |= {"muO2_data": {itr_ent.entry_id: itr_ent.data["muO2"] for itr_ent in self.get_all_entries()}} return dct diff --git a/src/pymatgen/core/structure.py b/src/pymatgen/core/structure.py index 56a6ddfcd2b..5b9b2fba660 100644 --- a/src/pymatgen/core/structure.py +++ b/src/pymatgen/core/structure.py @@ -3941,9 +3941,8 @@ def __setitem__( # type: ignore[override] """Modify a site in the structure. Args: - idx (int, [int], slice, Species-like): Indices to change. You can - specify these as an int, a list of int, or a species-like - string. + idx (int, list[int], slice, Species-like): Indices to change. You can + specify these as an int, a list of int, or a species-like string. site (PeriodicSite | Species | dict[SpeciesLike, float] | Sequence): 4 options exist. You can provide a PeriodicSite directly (lattice will be checked). Or more conveniently, you can provide a species-like object (or a dict mapping SpeciesLike to occupancy floats) @@ -4773,9 +4772,8 @@ def __setitem__( # type: ignore[override] """Modify a site in the molecule. Args: - idx (int, [int], slice, Species-like): Indices to change. You can - specify these as an int, a list of int, or a species-like - string. + idx (int, list[int], slice, Species-like): Indices to change. You can + specify these as an int, a list of int, or a species-like string. site (PeriodicSite/Species/Sequence): Three options exist. You can provide a Site directly, or for convenience, you can provide simply a Species-like string/object, or finally a (Species, diff --git a/src/pymatgen/entries/computed_entries.py b/src/pymatgen/entries/computed_entries.py index d17eb36bbe6..c1d89545828 100644 --- a/src/pymatgen/entries/computed_entries.py +++ b/src/pymatgen/entries/computed_entries.py @@ -508,15 +508,13 @@ def from_dict(cls, dct: dict) -> Self: def as_dict(self) -> dict: """MSONable dict.""" return_dict = super().as_dict() - return_dict.update( - { - "entry_id": self.entry_id, - "correction": self.correction, - "energy_adjustments": json.loads(json.dumps(self.energy_adjustments, cls=MontyEncoder)), - "parameters": json.loads(json.dumps(self.parameters, cls=MontyEncoder)), - "data": json.loads(json.dumps(self.data, cls=MontyEncoder)), - } - ) + return_dict |= { + "entry_id": self.entry_id, + "correction": self.correction, + "energy_adjustments": json.loads(json.dumps(self.energy_adjustments, cls=MontyEncoder)), + "parameters": json.loads(json.dumps(self.parameters, cls=MontyEncoder)), + "data": json.loads(json.dumps(self.data, cls=MontyEncoder)), + } return return_dict def __hash__(self) -> int: diff --git a/src/pymatgen/ext/matproj.py b/src/pymatgen/ext/matproj.py index 90093bcec60..25f7229a3e5 100644 --- a/src/pymatgen/ext/matproj.py +++ b/src/pymatgen/ext/matproj.py @@ -334,7 +334,7 @@ def get_entries_in_chemsys(self, elements, *args, **kwargs): Li, Fe and O phases. Extremely useful for creating phase diagrams of entire chemical systems. Args: - elements (str or [str]): Chemical system string comprising element + elements (str | list[str]): Chemical system string comprising element symbols separated by dashes, e.g. "Li-Fe-O" or List of element symbols, e.g. ["Li", "Fe", "O"]. *args: Pass-through to get_entries. diff --git a/src/pymatgen/ext/matproj_legacy.py b/src/pymatgen/ext/matproj_legacy.py index babddf7d9b2..f256207d521 100644 --- a/src/pymatgen/ext/matproj_legacy.py +++ b/src/pymatgen/ext/matproj_legacy.py @@ -535,7 +535,7 @@ def get_entries( ] data = {"oxide_type": d["oxide_type"]} if property_data: - data.update({k: d[k] for k in property_data}) + data |= {k: d[k] for k in property_data} if not inc_structure: e = ComputedEntry( d["unit_cell_formula"], @@ -575,7 +575,7 @@ def get_pourbaix_entries(self, chemsys, solid_compat="MaterialsProject2020Compat a Pourbaix diagram from the rest interface. Args: - chemsys (str or [str]): Chemical system string comprising element + chemsys (str | list[str]): Chemical system string comprising element symbols separated by dashes, e.g. "Li-Fe-O" or List of element symbols, e.g. ["Li", "Fe", "O"]. solid_compat: Compatibility scheme used to pre-process solid DFT energies prior to applying aqueous @@ -836,7 +836,7 @@ def get_entries_in_chemsys( phases. Extremely useful for creating phase diagrams of entire chemical systems. Args: - elements (str or [str]): Chemical system string comprising element + elements (str | list[str]): Chemical system string comprising element symbols separated by dashes, e.g. "Li-Fe-O" or List of element symbols, e.g. ["Li", "Fe", "O"]. compatible_only (bool): Whether to return only "compatible" @@ -1001,7 +1001,7 @@ def query( progress_bar = tqdm(total=len(mids), disable=not show_progress_bar) for chunk in chunks: chunk_criteria = criteria.copy() - chunk_criteria.update({"material_id": {"$in": chunk}}) + chunk_criteria |= {"material_id": {"$in": chunk}} n_tries = 0 while n_tries < max_tries_per_chunk: try: diff --git a/src/pymatgen/io/abinit/abiobjects.py b/src/pymatgen/io/abinit/abiobjects.py index 2057f79543f..76d99e289fa 100644 --- a/src/pymatgen/io/abinit/abiobjects.py +++ b/src/pymatgen/io/abinit/abiobjects.py @@ -266,10 +266,7 @@ def structure_to_abivars( # One should make sure that the orientation is preserved (see Curtarolo's settings) if geo_mode == "rprim": - dct.update( - acell=3 * [1.0], - rprim=r_prim, - ) + dct.update(acell=3 * [1.0], rprim=r_prim) elif geo_mode == "angdeg": dct.update( @@ -376,7 +373,7 @@ def to_abivars(self): def as_dict(self): """JSON-friendly dict representation of SpinMode.""" out = {k: getattr(self, k) for k in self._fields} - out.update({"@module": type(self).__module__, "@class": type(self).__name__}) + out |= {"@module": type(self).__module__, "@class": type(self).__name__} return out @classmethod @@ -385,7 +382,6 @@ def from_dict(cls, dct: dict) -> Self: return cls(**{key: dct[key] for key in dct if key in cls._fields}) -# An handy Multiton _mode_to_spin_vars = { "unpolarized": SpinMode("unpolarized", 1, 1, 1), "polarized": SpinMode("polarized", 2, 1, 2), @@ -632,13 +628,7 @@ def to_abivars(self): """Return dictionary with Abinit variables.""" abivars = self.spin_mode.to_abivars() - abivars.update( - { - "nband": self.nband, - "fband": self.fband, - "charge": self.charge, - } - ) + abivars |= {"nband": self.nband, "fband": self.fband, "charge": self.charge} if self.smearing: abivars.update(self.smearing.to_abivars()) @@ -741,15 +731,13 @@ def __init__( else: # use_symmetries and not use_time_reversal kptopt = 4 - abivars.update( - { - "ngkpt": ngkpt, - "shiftk": shiftk, - "nshiftk": len(shiftk), - "kptopt": kptopt, - "chksymbreak": chksymbreak, - } - ) + abivars |= { + "ngkpt": ngkpt, + "shiftk": shiftk, + "nshiftk": len(shiftk), + "kptopt": kptopt, + "chksymbreak": chksymbreak, + } elif mode == KSamplingModes.path: if num_kpts <= 0: @@ -757,29 +745,21 @@ def __init__( kptbounds = np.reshape(kpts, (-1, 3)) - abivars.update( - { - "ndivsm": num_kpts, - "kptbounds": kptbounds, - "kptopt": -len(kptbounds) + 1, - } - ) + abivars |= {"ndivsm": num_kpts, "kptbounds": kptbounds, "kptopt": -len(kptbounds) + 1} elif mode == KSamplingModes.automatic: kpts = np.reshape(kpts, (-1, 3)) if len(kpts) != num_kpts: raise ValueError("For Automatic mode, num_kpts must be specified.") - abivars.update( - { - "kptopt": 0, - "kpt": kpts, - "nkpt": num_kpts, - "kptnrm": np.ones(num_kpts), - "wtk": kpts_weights, # for iscf/=-2, wtk. - "chksymbreak": chksymbreak, - } - ) + abivars |= { + "kptopt": 0, + "kpt": kpts, + "nkpt": num_kpts, + "kptnrm": np.ones(num_kpts), + "wtk": kpts_weights, # for iscf/=-2, wtk. + "chksymbreak": chksymbreak, + } else: raise ValueError(f"Unknown {mode=}") @@ -1116,11 +1096,7 @@ def to_abivars(self): # Atom relaxation. if self.move_atoms: - out_vars.update( - { - "tolmxf": self.abivars.tolmxf, - } - ) + out_vars["tolmxf"] = self.abivars.tolmxf if self.abivars.atoms_constraints: # Add input variables for constrained relaxation. diff --git a/src/pymatgen/io/aims/parsers.py b/src/pymatgen/io/aims/parsers.py index 74ed02a7312..288d735682a 100644 --- a/src/pymatgen/io/aims/parsers.py +++ b/src/pymatgen/io/aims/parsers.py @@ -320,24 +320,14 @@ def _parse_k_points(self) -> None: """Parse the list of k-points used in the calculation.""" n_kpts = self.parse_scalar("n_kpts") if n_kpts is None: - self._cache.update( - { - "k_points": None, - "k_point_weights": None, - } - ) + self._cache |= {"k_points": None, "k_point_weights": None} return n_kpts = int(n_kpts) line_start = self.reverse_search_for(["| K-points in task"]) line_end = self.reverse_search_for(["| k-point:"]) if LINE_NOT_FOUND in {line_start, line_end} or (line_end - line_start != n_kpts): - self._cache.update( - { - "k_points": None, - "k_point_weights": None, - } - ) + self._cache |= {"k_points": None, "k_point_weights": None} return k_points = np.zeros((n_kpts, 3)) @@ -346,12 +336,7 @@ def _parse_k_points(self) -> None: k_points[kk] = [float(inp) for inp in line.split()[4:7]] k_point_weights[kk] = float(line.split()[-1]) - self._cache.update( - { - "k_points": k_points, - "k_point_weights": k_point_weights, - } - ) + self._cache |= {"k_points": k_points, "k_point_weights": k_point_weights} @property def n_atoms(self) -> int: @@ -612,7 +597,7 @@ def lattice(self) -> Lattice: return self._cache["lattice"] @property - def forces(self) -> np.array[Vector3D] | None: + def forces(self) -> np.ndarray | None: """The forces from the aims.out file.""" line_start = self.reverse_search_for(["Total atomic forces"]) if line_start == LINE_NOT_FOUND: @@ -625,8 +610,8 @@ def forces(self) -> np.array[Vector3D] | None: ) @property - def stresses(self) -> np.array[Matrix3D] | None: - """The stresses from the aims.out file and convert to kbar.""" + def stresses(self) -> np.ndarray | None: + """The stresses from the aims.out file and convert to kBar.""" line_start = self.reverse_search_for(["Per atom stress (eV) used for heat flux calculation"]) if line_start == LINE_NOT_FOUND: return None @@ -640,12 +625,9 @@ def stresses(self) -> np.array[Matrix3D] | None: @property def stress(self) -> Matrix3D | None: - """The stress from the aims.out file and convert to kbar.""" + """The stress from the aims.out file and convert to kBar.""" line_start = self.reverse_search_for( - [ - "Analytical stress tensor - Symmetrized", - "Numerical stress tensor", - ] + ["Analytical stress tensor - Symmetrized", "Numerical stress tensor"] ) # Offset to relevant lines if line_start == LINE_NOT_FOUND: return None @@ -739,14 +721,12 @@ def _parse_hirshfeld( """Parse the Hirshfled charges volumes, and dipole moments.""" line_start = self.reverse_search_for(["Performing Hirshfeld analysis of fragment charges and moments."]) if line_start == LINE_NOT_FOUND: - self._cache.update( - { - "hirshfeld_charges": None, - "hirshfeld_volumes": None, - "hirshfeld_atomic_dipoles": None, - "hirshfeld_dipole": None, - } - ) + self._cache |= { + "hirshfeld_charges": None, + "hirshfeld_volumes": None, + "hirshfeld_atomic_dipoles": None, + "hirshfeld_dipole": None, + } return line_inds = self.search_for_all("Hirshfeld charge", line_start, -1) @@ -768,14 +748,12 @@ def _parse_hirshfeld( else: hirshfeld_dipole = None - self._cache.update( - { - "hirshfeld_charges": hirshfeld_charges, - "hirshfeld_volumes": hirshfeld_volumes, - "hirshfeld_atomic_dipoles": hirshfeld_atomic_dipoles, - "hirshfeld_dipole": hirshfeld_dipole, - } - ) + self._cache |= { + "hirshfeld_charges": hirshfeld_charges, + "hirshfeld_volumes": hirshfeld_volumes, + "hirshfeld_atomic_dipoles": hirshfeld_atomic_dipoles, + "hirshfeld_dipole": hirshfeld_dipole, + } @property def structure(self) -> Structure | Molecule: diff --git a/src/pymatgen/io/core.py b/src/pymatgen/io/core.py index 1392e8a564e..39dacde1347 100644 --- a/src/pymatgen/io/core.py +++ b/src/pymatgen/io/core.py @@ -177,6 +177,14 @@ def __setitem__(self, key: PathLike, value: str | InputFile) -> None: def __delitem__(self, key: PathLike) -> None: del self.inputs[key] + # enable dict merge + def __or__(self, other: dict | Self) -> Self: + if isinstance(other, dict): + other = type(self)(other) + if not isinstance(other, type(self)): + return NotImplemented + return type(self)({**self.inputs, **other.inputs}, **self._kwargs) + def write_input( self, directory: PathLike, diff --git a/src/pymatgen/io/cp2k/inputs.py b/src/pymatgen/io/cp2k/inputs.py index 8076e51d09c..ae0c020c2a2 100644 --- a/src/pymatgen/io/cp2k/inputs.py +++ b/src/pymatgen/io/cp2k/inputs.py @@ -334,6 +334,10 @@ def __add__(self, other) -> Section: def __setitem__(self, key, value): self.setitem(key, value) + # dict merge + def __or__(self, other: dict) -> Section: + return self.update(other) + def setitem(self, key, value, strict=False): """ Helper function for setting items. Kept separate from the double-underscore function so that @@ -418,7 +422,7 @@ def get_keyword(self, d, default=None): return v return default - def update(self, dct: dict, strict=False): + def update(self, dct: dict, strict=False) -> Section: """ Update the Section according to a dictionary argument. This is most useful for providing user-override settings to default parameters. As you pass a @@ -443,6 +447,7 @@ def update(self, dct: dict, strict=False): new sections and keywords. Default: False """ Section._update(self, dct, strict=strict) + return self @staticmethod def _update(d1, d2, strict=False): @@ -1973,18 +1978,16 @@ def __init__( else: raise ValueError("No k-points provided!") - keywords.update( - { - "SCHEME": Keyword("SCHEME", scheme), - "EPS_GEO": Keyword("EPS_GEO", eps_geo), - "FULL_GRID": Keyword("FULL_GRID", full_grid), - "PARALLEL_GROUP_SIZE": Keyword("PARALLEL_GROUP_SIZE", parallel_group_size), - "SYMMETRY": Keyword("SYMMETRY", symmetry), - "UNITS": Keyword("UNITS", units), - "VERBOSE": Keyword("VERBOSE", verbose), - "WAVEFUNCTIONS": Keyword("WAVEFUNCTIONS", wavefunctions), - } - ) + keywords |= { + "SCHEME": Keyword("SCHEME", scheme), + "EPS_GEO": Keyword("EPS_GEO", eps_geo), + "FULL_GRID": Keyword("FULL_GRID", full_grid), + "PARALLEL_GROUP_SIZE": Keyword("PARALLEL_GROUP_SIZE", parallel_group_size), + "SYMMETRY": Keyword("SYMMETRY", symmetry), + "UNITS": Keyword("UNITS", units), + "VERBOSE": Keyword("VERBOSE", verbose), + "WAVEFUNCTIONS": Keyword("WAVEFUNCTIONS", wavefunctions), + } super().__init__( name="KPOINTS", diff --git a/src/pymatgen/io/cp2k/sets.py b/src/pymatgen/io/cp2k/sets.py index d6fc950a583..16a79842efa 100644 --- a/src/pymatgen/io/cp2k/sets.py +++ b/src/pymatgen/io/cp2k/sets.py @@ -829,7 +829,7 @@ def activate_hybrid( " distance between atoms. I hope you know what you're doing." ) - ip_keywords = {} + ip_keywords: dict[str, Keyword] = {} if hybrid_functional == "HSE06": pbe = PBE("ORIG", scale_c=1, scale_x=0) xc_functional = XCFunctional(functionals=[], subsections={"PBE": pbe}) @@ -846,13 +846,11 @@ def activate_hybrid( }, ) ) - ip_keywords.update( - { - "POTENTIAL_TYPE": Keyword("POTENTIAL_TYPE", potential_type), - "OMEGA": Keyword("OMEGA", 0.11), - "CUTOFF_RADIUS": Keyword("CUTOFF_RADIUS", cutoff_radius), - } - ) + ip_keywords |= { + "POTENTIAL_TYPE": Keyword("POTENTIAL_TYPE", potential_type), + "OMEGA": Keyword("OMEGA", 0.11), + "CUTOFF_RADIUS": Keyword("CUTOFF_RADIUS", cutoff_radius), + } elif hybrid_functional == "PBE0": pbe = PBE("ORIG", scale_c=1, scale_x=1 - hf_fraction) xc_functional = XCFunctional(functionals=[], subsections={"PBE": pbe}) @@ -885,16 +883,14 @@ def activate_hybrid( potential_type = potential_type or "MIX_CL_TRUNC" hf_fraction = 1 - ip_keywords.update( - { - "POTENTIAL_TYPE": Keyword("POTENTIAL_TYPE", potential_type), - "CUTOFF_RADIUS": Keyword("CUTOFF_RADIUS", cutoff_radius), - "T_C_G_DATA": Keyword("T_C_G_DATA", "t_c_g.dat"), - "OMEGA": Keyword("OMEGA", omega), - "SCALE_COULOMB": Keyword("SCALE_COULOMB", scale_coulomb), - "SCALE_LONGRANGE": Keyword("SCALE_LONGRANGE", scale_longrange - scale_coulomb), - } - ) + ip_keywords |= { + "POTENTIAL_TYPE": Keyword("POTENTIAL_TYPE", potential_type), + "CUTOFF_RADIUS": Keyword("CUTOFF_RADIUS", cutoff_radius), + "T_C_G_DATA": Keyword("T_C_G_DATA", "t_c_g.dat"), + "OMEGA": Keyword("OMEGA", omega), + "SCALE_COULOMB": Keyword("SCALE_COULOMB", scale_coulomb), + "SCALE_LONGRANGE": Keyword("SCALE_LONGRANGE", scale_longrange - scale_coulomb), + } xc_functional.insert( Section( "XWPBE", @@ -924,17 +920,15 @@ def activate_hybrid( pbe = PBE("ORIG", scale_c=gga_c_fraction, scale_x=gga_x_fraction) xc_functional = XCFunctional(functionals=[], subsections={"PBE": pbe}) - ip_keywords.update( - { - "POTENTIAL_TYPE": Keyword("POTENTIAL_TYPE", potential_type), - "CUTOFF_RADIUS": Keyword("CUTOFF_RADIUS", cutoff_radius), - "T_C_G_DATA": Keyword("T_C_G_DATA", "t_c_g.dat"), - "SCALE_COULOMB": Keyword("SCALE_COULOMB", scale_coulomb), - "SCALE_GAUSSIAN": Keyword("SCALE_GAUSSIAN", scale_gaussian), - "SCALE_LONGRANGE": Keyword("SCALE_LONGRANGE", scale_longrange), - "OMEGA": Keyword("OMEGA", omega), - } - ) + ip_keywords |= { + "POTENTIAL_TYPE": Keyword("POTENTIAL_TYPE", potential_type), + "CUTOFF_RADIUS": Keyword("CUTOFF_RADIUS", cutoff_radius), + "T_C_G_DATA": Keyword("T_C_G_DATA", "t_c_g.dat"), + "SCALE_COULOMB": Keyword("SCALE_COULOMB", scale_coulomb), + "SCALE_GAUSSIAN": Keyword("SCALE_GAUSSIAN", scale_gaussian), + "SCALE_LONGRANGE": Keyword("SCALE_LONGRANGE", scale_longrange), + "OMEGA": Keyword("OMEGA", omega), + } interaction_potential = Section("INTERACTION_POTENTIAL", subsections={}, keywords=ip_keywords) @@ -1098,14 +1092,14 @@ def activate_epr(self, **kwargs) -> None: if not self.check("force_eval/properties/linres/localize"): self.activate_localize() self["FORCE_EVAL"]["PROPERTIES"]["LINRES"].insert(Section("EPR", **kwargs)) - self["FORCE_EVAL"]["PROPERTIES"]["LINRES"]["EPR"].update({"PRINT": {"G_TENSOR": {}}}) + self["FORCE_EVAL"]["PROPERTIES"]["LINRES"]["EPR"] |= {"PRINT": {"G_TENSOR": {}}} def activate_nmr(self, **kwargs) -> None: """Calculate nmr shifts. Requires localize. Suggested with GAPW.""" if not self.check("force_eval/properties/linres/localize"): self.activate_localize() self["FORCE_EVAL"]["PROPERTIES"]["LINRES"].insert(Section("NMR", **kwargs)) - self["FORCE_EVAL"]["PROPERTIES"]["LINRES"]["NMR"].update({"PRINT": {"CHI_TENSOR": {}, "SHIELDING_TENSOR": {}}}) + self["FORCE_EVAL"]["PROPERTIES"]["LINRES"]["NMR"] |= {"PRINT": {"CHI_TENSOR": {}, "SHIELDING_TENSOR": {}}} def activate_spinspin(self, **kwargs) -> None: """Calculate spin-spin coupling tensor. Requires localize.""" @@ -1186,7 +1180,7 @@ def activate_fast_minimization(self, on) -> None: algorithm="IRAC", linesearch="2PNT", ) - self.update({"FORCE_EVAL": {"DFT": {"SCF": {"OT": ot}}}}) + self |= {"FORCE_EVAL": {"DFT": {"SCF": {"OT": ot}}}} # type: ignore[assignment] def activate_robust_minimization(self) -> None: """Modify the set to use more robust SCF minimization technique.""" @@ -1196,7 +1190,7 @@ def activate_robust_minimization(self) -> None: algorithm="STRICT", linesearch="3PNT", ) - self.update({"FORCE_EVAL": {"DFT": {"SCF": {"OT": ot}}}}) + self |= {"FORCE_EVAL": {"DFT": {"SCF": {"OT": ot}}}} # type: ignore[assignment] def activate_very_strict_minimization(self) -> None: """Method to modify the set to use very strict SCF minimization scheme.""" @@ -1206,7 +1200,7 @@ def activate_very_strict_minimization(self) -> None: algorithm="STRICT", linesearch="GOLD", ) - self.update({"FORCE_EVAL": {"DFT": {"SCF": {"OT": ot}}}}) + self |= {"FORCE_EVAL": {"DFT": {"SCF": {"OT": ot}}}} # type: ignore[assignment] def activate_nonperiodic(self, solver="ANALYTIC") -> None: """ diff --git a/src/pymatgen/io/feff/sets.py b/src/pymatgen/io/feff/sets.py index 55ddd7fd588..6dca1577562 100644 --- a/src/pymatgen/io/feff/sets.py +++ b/src/pymatgen/io/feff/sets.py @@ -81,7 +81,7 @@ def all_input(self): dct = {"HEADER": self.header(), "PARAMETERS": self.tags} if "RECIPROCAL" not in self.tags: - dct.update({"POTENTIALS": self.potential, "ATOMS": self.atoms}) + dct |= {"POTENTIALS": self.potential, "ATOMS": self.atoms} return dct diff --git a/src/pymatgen/io/lammps/data.py b/src/pymatgen/io/lammps/data.py index 69d9d834853..5fb50b053d3 100644 --- a/src/pymatgen/io/lammps/data.py +++ b/src/pymatgen/io/lammps/data.py @@ -555,7 +555,7 @@ def disassemble( for t in ff_df.itertuples(index=True, name=None): coeffs_dict = {"coeffs": list(t[1:]), "types": []} if class2_coeffs: - coeffs_dict.update({k: list(v[t[0] - 1]) for k, v in class2_coeffs.items()}) + coeffs_dict |= {k: list(v[t[0] - 1]) for k, v in class2_coeffs.items()} topo_coeffs[kw].append(coeffs_dict) if self.topology: @@ -799,7 +799,7 @@ def from_ff_and_topologies( topology[key] = df[SECTION_HEADERS[key]] topology = {key: values for key, values in topology.items() if not values.empty} - items.update({"atoms": atoms, "velocities": velocities, "topology": topology}) + items |= {"atoms": atoms, "velocities": velocities, "topology": topology} return cls(**items) @classmethod @@ -1162,7 +1162,7 @@ def process_data(data) -> pd.DataFrame: all_data = {kw: process_data(main_data)} if class2_data: - all_data.update({k: process_data(v) for k, v in class2_data.items()}) + all_data |= {k: process_data(v) for k, v in class2_data.items()} return all_data, {f"{kw[:-7]}s": mapper} def to_file(self, filename: str) -> None: diff --git a/src/pymatgen/io/lammps/inputs.py b/src/pymatgen/io/lammps/inputs.py index 565feea80bf..953f0978b90 100644 --- a/src/pymatgen/io/lammps/inputs.py +++ b/src/pymatgen/io/lammps/inputs.py @@ -911,7 +911,7 @@ def md( with open(template_path, encoding="utf-8") as file: script_template = file.read() settings = other_settings.copy() if other_settings else {} - settings.update({"force_field": force_field, "temperature": temperature, "nsteps": nsteps}) + settings |= {"force_field": force_field, "temperature": temperature, "nsteps": nsteps} script_filename = "in.md" return cls( script_template=script_template, @@ -959,7 +959,7 @@ def get_input_set( # type: ignore[override] input_set = super().get_input_set(template=script_template, variables=settings, filename=script_filename) if data: - input_set.update({data_filename: data}) + input_set |= {data_filename: data} return input_set diff --git a/src/pymatgen/io/lammps/outputs.py b/src/pymatgen/io/lammps/outputs.py index 033a7d58b4a..0c1b182ed27 100644 --- a/src/pymatgen/io/lammps/outputs.py +++ b/src/pymatgen/io/lammps/outputs.py @@ -164,19 +164,19 @@ def _parse_thermo(lines: list[str]) -> pd.DataFrame: # multi line thermo data if re.match(multi_pattern, lines[0]): timestep_marks = [idx for idx, line in enumerate(lines) if re.match(multi_pattern, line)] - timesteps = np.split(lines, timestep_marks)[1:] + time_steps = np.split(lines, timestep_marks)[1:] dicts = [] kv_pattern = r"([0-9A-Za-z_\[\]]+)\s+=\s+([0-9eE\.+-]+)" - for ts in timesteps: + for ts in time_steps: data = {} step = re.match(multi_pattern, ts[0]) assert step is not None data["Step"] = int(step[1]) - data.update({k: float(v) for k, v in re.findall(kv_pattern, "".join(ts[1:]))}) + data |= {k: float(v) for k, v in re.findall(kv_pattern, "".join(ts[1:]))} dicts.append(data) df = pd.DataFrame(dicts) # rearrange the sequence of columns - columns = ["Step"] + [k for k, v in re.findall(kv_pattern, "".join(timesteps[0][1:]))] + columns = ["Step"] + [k for k, v in re.findall(kv_pattern, "".join(time_steps[0][1:]))] df = df[columns] # one line thermo data else: diff --git a/src/pymatgen/io/lobster/outputs.py b/src/pymatgen/io/lobster/outputs.py index 2a59f6a3215..322fcbf261f 100644 --- a/src/pymatgen/io/lobster/outputs.py +++ b/src/pymatgen/io/lobster/outputs.py @@ -166,18 +166,16 @@ def __init__( } elif label in orb_cohp: - orb_cohp[label].update( - { - bond_data["orb_label"]: { - "COHP": cohp, - "ICOHP": icohp, - "orbitals": orbs, - "length": bond_data["length"], - "sites": bond_data["sites"], - "cells": bond_data["cells"], - } + orb_cohp[label] |= { + bond_data["orb_label"]: { + "COHP": cohp, + "ICOHP": icohp, + "orbitals": orbs, + "length": bond_data["length"], + "sites": bond_data["sites"], + "cells": bond_data["cells"], } - ) + } else: # present for Lobster versions older than Lobster 2.2.0 if bond_num == 0: @@ -219,17 +217,15 @@ def __init__( } elif label in orb_cohp: - orb_cohp[label].update( - { - bond_data["orb_label"]: { - "COHP": cohp, - "ICOHP": icohp, - "orbitals": orbs, - "length": bond_data["length"], - "sites": bond_data["sites"], - } + orb_cohp[label] |= { + bond_data["orb_label"]: { + "COHP": cohp, + "ICOHP": icohp, + "orbitals": orbs, + "length": bond_data["length"], + "sites": bond_data["sites"], } - ) + } else: # present for Lobster versions older than Lobster 2.2.0 if bond_num == 0: @@ -2136,10 +2132,10 @@ def _parse_matrix(file_data, pattern, e_fermi): if matches and len(matches.groups()) == 2: spin = Spin.up if matches[1] == "1" else Spin.down k_point = matches[2] - complex_matrices[k_point].update({spin: comp_matrix}) + complex_matrices[k_point] |= {spin: comp_matrix} elif matches and len(matches.groups()) == 1: k_point = matches[1] - complex_matrices.update({k_point: comp_matrix}) + complex_matrices |= {k_point: comp_matrix} matrix_diagonal_values.append(comp_matrix.real.diagonal() - e_fermi) # extract elements basis functions as list diff --git a/src/pymatgen/io/nwchem.py b/src/pymatgen/io/nwchem.py index 04686e878f7..d3ddddbc440 100644 --- a/src/pymatgen/io/nwchem.py +++ b/src/pymatgen/io/nwchem.py @@ -310,7 +310,7 @@ def dft_task(cls, mol, xc="b3lyp", **kwargs): theory is always "dft" for a dft task. """ t = NwTask.from_molecule(mol, theory="dft", **kwargs) - t.theory_directives.update({"xc": xc, "mult": t.spin_multiplicity}) + t.theory_directives |= {"xc": xc, "mult": t.spin_multiplicity} return t @classmethod @@ -844,10 +844,10 @@ def isfloatstring(in_str): cosmo_scf_energy = energies[-1] energies[-1] = {} energies[-1]["cosmo scf"] = cosmo_scf_energy - energies[-1].update({"gas phase": Energy(match[1], "Ha").to("eV")}) + energies[-1] |= {"gas phase": Energy(match[1], "Ha").to("eV")} if match := energy_sol_patt.search(line): - energies[-1].update({"sol phase": Energy(match[1], "Ha").to("eV")}) + energies[-1] |= {"sol phase": Energy(match[1], "Ha").to("eV")} if match := preamble_patt.search(line): try: @@ -911,23 +911,21 @@ def isfloatstring(in_str): for jj in range(ii + 1, len_hess): projected_hessian[ii].append(projected_hessian[jj][ii]) - data.update( - { - "job_type": job_type, - "energies": energies, - "corrections": corrections, - "molecules": molecules, - "structures": structures, - "basis_set": basis_set, - "errors": errors, - "has_error": len(errors) > 0, - "frequencies": frequencies, - "normal_frequencies": normal_frequencies, - "hessian": hessian, - "projected_hessian": projected_hessian, - "forces": all_forces, - "task_time": time, - } - ) + data |= { + "job_type": job_type, + "energies": energies, + "corrections": corrections, + "molecules": molecules, + "structures": structures, + "basis_set": basis_set, + "errors": errors, + "has_error": len(errors) > 0, + "frequencies": frequencies, + "normal_frequencies": normal_frequencies, + "hessian": hessian, + "projected_hessian": projected_hessian, + "forces": all_forces, + "task_time": time, + } return data diff --git a/src/pymatgen/io/pwmat/inputs.py b/src/pymatgen/io/pwmat/inputs.py index a0330270d00..bd143572216 100644 --- a/src/pymatgen/io/pwmat/inputs.py +++ b/src/pymatgen/io/pwmat/inputs.py @@ -399,7 +399,7 @@ def from_str(cls, data: str, mag: bool = False) -> Self: if mag: magmoms = ac_extractor.get_magmoms() for idx, tmp_site in enumerate(structure): - tmp_site.properties.update({"magmom": magmoms[idx]}) + tmp_site.properties |= {"magmom": magmoms[idx]} return cls(structure) @@ -498,8 +498,8 @@ def __init__( """ self.reciprocal_lattice: np.ndarray = reciprocal_lattice self.kpath: dict = {} - self.kpath.update({"kpoints": kpoints}) - self.kpath.update({"path": path}) + self.kpath |= {"kpoints": kpoints} + self.kpath |= {"path": path} self.density = density @classmethod @@ -517,7 +517,7 @@ def from_structure(cls, structure: Structure, dim: int, density: float = 0.01) - kpts_2d: dict[str, np.ndarray] = {} for tmp_name, tmp_kpt in kpath_set.kpath["kpoints"].items(): if (tmp_kpt[2]) == 0: - kpts_2d.update({tmp_name: tmp_kpt}) + kpts_2d |= {tmp_name: tmp_kpt} path_2d: list[list[str]] = [] for tmp_path in kpath_set.kpath["path"]: @@ -600,8 +600,8 @@ def __init__(self, reciprocal_lattice: np.ndarray, kpts: dict[str, list], path: """ self.reciprocal_lattice: np.ndarray = reciprocal_lattice self.kpath: dict = {} - self.kpath.update({"kpoints": kpts}) - self.kpath.update({"path": path}) + self.kpath |= {"kpoints": kpts} + self.kpath |= {"path": path} self.density = density @classmethod diff --git a/src/pymatgen/io/pwmat/outputs.py b/src/pymatgen/io/pwmat/outputs.py index 3daf32b57c0..1d2bbef2256 100644 --- a/src/pymatgen/io/pwmat/outputs.py +++ b/src/pymatgen/io/pwmat/outputs.py @@ -136,17 +136,17 @@ def _parse_sefv(self) -> list[dict]: tmp_chunk: str = "" for _ in range(self.chunk_sizes[ii]): tmp_chunk += mvt.readline() - tmp_step.update({"atom_config": AtomConfig.from_str(tmp_chunk)}) - tmp_step.update({"e_tot": ACstrExtractor(tmp_chunk).get_e_tot()[0]}) - tmp_step.update({"atom_forces": ACstrExtractor(tmp_chunk).get_atom_forces().reshape(-1, 3)}) + tmp_step["atom_config"] = AtomConfig.from_str(tmp_chunk) + tmp_step["e_tot"] = ACstrExtractor(tmp_chunk).get_e_tot()[0] + tmp_step["atom_forces"] = ACstrExtractor(tmp_chunk).get_atom_forces().reshape(-1, 3) e_atoms: np.ndarray | None = ACstrExtractor(tmp_chunk).get_atom_forces() if e_atoms is not None: - tmp_step.update({"atom_energies": ACstrExtractor(tmp_chunk).get_atom_energies()}) + tmp_step["atom_energies"] = ACstrExtractor(tmp_chunk).get_atom_energies() else: print(f"Ionic step #{ii} : Energy deposition is turn down.") virial: np.ndarray | None = ACstrExtractor(tmp_chunk).get_virial() if virial is not None: - tmp_step.update({"virial": virial.reshape(3, 3)}) + tmp_step["virial"] = virial.reshape(3, 3) else: print(f"Ionic step #{ii} : No virial information.") ionic_steps.append(tmp_step) @@ -262,9 +262,9 @@ def _parse_kpt(self) -> tuple[np.ndarray, np.ndarray, dict[str, np.ndarray]]: kpts_weight[ii] = float(tmp_row_lst[3].strip()) if len(tmp_row_lst) == 5: - hsps.update( - {tmp_row_lst[4]: np.array([float(tmp_row_lst[0]), float(tmp_row_lst[1]), float(tmp_row_lst[2])])} - ) + hsps |= { + tmp_row_lst[4]: np.array([float(tmp_row_lst[0]), float(tmp_row_lst[1]), float(tmp_row_lst[2])]) + } return kpts, kpts_weight, hsps @property diff --git a/src/pymatgen/io/vasp/inputs.py b/src/pymatgen/io/vasp/inputs.py index fec858874cd..5ae331e11af 100644 --- a/src/pymatgen/io/vasp/inputs.py +++ b/src/pymatgen/io/vasp/inputs.py @@ -2753,7 +2753,7 @@ def __init__( """ super().__init__(**kwargs) self._potcar_filename = "POTCAR" + (".spec" if potcar_spec else "") - self.update({"INCAR": incar, "KPOINTS": kpoints, "POSCAR": poscar, self._potcar_filename: potcar}) + self |= {"INCAR": incar, "KPOINTS": kpoints, "POSCAR": poscar, self._potcar_filename: potcar} if optional_files is not None: self.update(optional_files) diff --git a/src/pymatgen/io/vasp/sets.py b/src/pymatgen/io/vasp/sets.py index c2715b52a2c..d333056b4ce 100644 --- a/src/pymatgen/io/vasp/sets.py +++ b/src/pymatgen/io/vasp/sets.py @@ -1490,7 +1490,7 @@ def __post_init__(self) -> None: ) if self.xc_functional.upper() == "R2SCAN": - self._config_dict["INCAR"].update({"METAGGA": "R2SCAN", "ALGO": "ALL", "GGA": None}) + self._config_dict["INCAR"] |= {"METAGGA": "R2SCAN", "ALGO": "ALL", "GGA": None} if self.xc_functional.upper().endswith("+U"): self._config_dict["INCAR"]["LDAU"] = True diff --git a/tests/io/cp2k/test_sets.py b/tests/io/cp2k/test_sets.py index ecb644a82a3..161faf95117 100644 --- a/tests/io/cp2k/test_sets.py +++ b/tests/io/cp2k/test_sets.py @@ -103,7 +103,7 @@ def test_dft_set(self): assert dft_set.check("force_eval/dft/auxiliary_density_matrix_method") # Validator will trip for kpoints + hfx - dft_set.update({"force_eval": {"dft": {"kpoints": {}}}}) + dft_set |= {"force_eval": {"dft": {"kpoints": {}}}} with pytest.raises(Cp2kValidationError, match="CP2K v2022.1: Does not support hartree fock with kpoints"): dft_set.validate()