-
Notifications
You must be signed in to change notification settings - Fork 867
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix array comparison in core.Structure.merge_sites
, also allow int
property to be merged instead of float
alone, mode
only allow full name
#4198
base: master
Are you sure you want to change the base?
Changes from 13 commits
644c12d
87da226
bc6645b
5b4b0ae
c2ccd92
13e58d6
dd1b338
2289d54
9618ddc
467b840
658b33a
99988ef
702b80b
091c911
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -48,7 +48,7 @@ | |||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
if TYPE_CHECKING: | ||||||||||||||||||||||||||||||||
from collections.abc import Callable, Iterable, Iterator, Sequence | ||||||||||||||||||||||||||||||||
from typing import Any, SupportsIndex | ||||||||||||||||||||||||||||||||
from typing import Any, ClassVar, SupportsIndex, TypeAlias | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
import pandas as pd | ||||||||||||||||||||||||||||||||
from ase import Atoms | ||||||||||||||||||||||||||||||||
|
@@ -61,7 +61,7 @@ | |||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
from pymatgen.util.typing import CompositionLike, MillerIndex, PathLike, PbcLike, SpeciesLike | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
FileFormats = Literal[ | ||||||||||||||||||||||||||||||||
FileFormats: TypeAlias = Literal[ | ||||||||||||||||||||||||||||||||
"cif", | ||||||||||||||||||||||||||||||||
"poscar", | ||||||||||||||||||||||||||||||||
"cssr", | ||||||||||||||||||||||||||||||||
|
@@ -75,7 +75,7 @@ | |||||||||||||||||||||||||||||||
"aims", | ||||||||||||||||||||||||||||||||
"", | ||||||||||||||||||||||||||||||||
] | ||||||||||||||||||||||||||||||||
StructureSources = Literal["Materials Project", "COD"] | ||||||||||||||||||||||||||||||||
StructureSources: TypeAlias = Literal["Materials Project", "COD"] | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
class Neighbor(Site): | ||||||||||||||||||||||||||||||||
|
@@ -216,7 +216,7 @@ class SiteCollection(collections.abc.Sequence, ABC): | |||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
# Tolerance in Angstrom for determining if sites are too close | ||||||||||||||||||||||||||||||||
DISTANCE_TOLERANCE = 0.5 | ||||||||||||||||||||||||||||||||
DISTANCE_TOLERANCE: ClassVar[float] = 0.5 | ||||||||||||||||||||||||||||||||
_properties: dict | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
def __contains__(self, site: object) -> bool: | ||||||||||||||||||||||||||||||||
|
@@ -4716,44 +4716,64 @@ def scale_lattice(self, volume: float) -> Self: | |||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
return self | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
def merge_sites(self, tol: float = 0.01, mode: Literal["sum", "delete", "average"] = "sum") -> Self: | ||||||||||||||||||||||||||||||||
"""Merges sites (adding occupancies) within tol of each other. | ||||||||||||||||||||||||||||||||
Removes site properties. | ||||||||||||||||||||||||||||||||
def merge_sites( | ||||||||||||||||||||||||||||||||
self, | ||||||||||||||||||||||||||||||||
tol: float = 0.01, | ||||||||||||||||||||||||||||||||
mode: Literal["sum", "delete", "average"] = "sum", | ||||||||||||||||||||||||||||||||
) -> Self: | ||||||||||||||||||||||||||||||||
"""Merges sites (by adding occupancies) within tolerance and removes | ||||||||||||||||||||||||||||||||
site properties in "sum/delete" modes. | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
Args: | ||||||||||||||||||||||||||||||||
tol (float): Tolerance for distance to merge sites. | ||||||||||||||||||||||||||||||||
mode ("sum" | "delete" | "average"): "delete" means duplicate sites are | ||||||||||||||||||||||||||||||||
deleted. "sum" means the occupancies are summed for the sites. | ||||||||||||||||||||||||||||||||
"average" means that the site is deleted but the properties are averaged | ||||||||||||||||||||||||||||||||
Only first letter is considered. | ||||||||||||||||||||||||||||||||
mode ("sum" | "delete" | "average"): Only first letter is considered at this moment. | ||||||||||||||||||||||||||||||||
- "delete": delete duplicate sites. | ||||||||||||||||||||||||||||||||
- "sum": sum the occupancies for the sites. | ||||||||||||||||||||||||||||||||
- "average": delete the site but average the properties if it's numerical. | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
Returns: | ||||||||||||||||||||||||||||||||
Structure: self with merged sites. | ||||||||||||||||||||||||||||||||
Structure: Structure with merged sites. | ||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||
dist_mat = self.distance_matrix | ||||||||||||||||||||||||||||||||
# TODO: change the code the allow full name after 2025-12-01 | ||||||||||||||||||||||||||||||||
# TODO2: add a test for mode value, currently it only checks if first letter is "s/a" | ||||||||||||||||||||||||||||||||
if mode.lower() not in {"sum", "delete", "average"} and mode.lower()[0] in {"s", "d", "a"}: | ||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Want to hear more opinions on this, I guess it's beneficial to allow only full name ( Also using the full name would be more readable: |
||||||||||||||||||||||||||||||||
warnings.warn( | ||||||||||||||||||||||||||||||||
"mode would only allow full name sum/delete/average after 2025-12-01", DeprecationWarning, stacklevel=2 | ||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
if mode.lower()[0] not in {"s", "d", "a"}: | ||||||||||||||||||||||||||||||||
raise ValueError(f"Illegal {mode=}, should start with a/d/s.") | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
dist_mat: NDArray = self.distance_matrix | ||||||||||||||||||||||||||||||||
np.fill_diagonal(dist_mat, 0) | ||||||||||||||||||||||||||||||||
clusters = fcluster(linkage(squareform((dist_mat + dist_mat.T) / 2)), tol, "distance") | ||||||||||||||||||||||||||||||||
sites = [] | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
sites: list[PeriodicSite] = [] | ||||||||||||||||||||||||||||||||
for cluster in np.unique(clusters): | ||||||||||||||||||||||||||||||||
inds = np.where(clusters == cluster)[0] | ||||||||||||||||||||||||||||||||
species = self[inds[0]].species | ||||||||||||||||||||||||||||||||
coords = self[inds[0]].frac_coords | ||||||||||||||||||||||||||||||||
props = self[inds[0]].properties | ||||||||||||||||||||||||||||||||
for n, i in enumerate(inds[1:]): | ||||||||||||||||||||||||||||||||
sp = self[i].species | ||||||||||||||||||||||||||||||||
indexes = np.where(clusters == cluster)[0] | ||||||||||||||||||||||||||||||||
species: Composition = self[indexes[0]].species | ||||||||||||||||||||||||||||||||
coords: NDArray = self[indexes[0]].frac_coords | ||||||||||||||||||||||||||||||||
props: dict = self[indexes[0]].properties | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
for site_idx, clust_idx in enumerate(indexes[1:]): | ||||||||||||||||||||||||||||||||
# Sum occupancies in "sum" mode | ||||||||||||||||||||||||||||||||
if mode.lower()[0] == "s": | ||||||||||||||||||||||||||||||||
species += sp | ||||||||||||||||||||||||||||||||
offset = self[i].frac_coords - coords | ||||||||||||||||||||||||||||||||
coords += ((offset - np.round(offset)) / (n + 2)).astype(coords.dtype) | ||||||||||||||||||||||||||||||||
species += self[clust_idx].species | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
offset = self[clust_idx].frac_coords - coords | ||||||||||||||||||||||||||||||||
coords += ((offset - np.round(offset)) / (site_idx + 2)).astype(coords.dtype) | ||||||||||||||||||||||||||||||||
for key in props: | ||||||||||||||||||||||||||||||||
if props[key] is not None and self[i].properties[key] != props[key]: | ||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TODO tag: Using TODO2: test failure pymatgen/tests/core/test_structure.py Lines 1667 to 1681 in 31f1e1f
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Property can actually be anything, including value supplied by user. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thanks for stepping in, I'm here adding a note for myself to work on later. Yes in this case using But I assume we would not be able to use |
||||||||||||||||||||||||||||||||
if mode.lower()[0] == "a" and isinstance(props[key], float): | ||||||||||||||||||||||||||||||||
if props[key] is not None and not np.array_equal(self[clust_idx].properties[key], props[key]): | ||||||||||||||||||||||||||||||||
if mode.lower()[0] == "a" and isinstance(props[key], float | int): | ||||||||||||||||||||||||||||||||
# update a running total | ||||||||||||||||||||||||||||||||
props[key] = props[key] * (n + 1) / (n + 2) + self[i].properties[key] / (n + 2) | ||||||||||||||||||||||||||||||||
props[key] = props[key] * (site_idx + 1) / (site_idx + 2) + self[clust_idx].properties[ | ||||||||||||||||||||||||||||||||
key | ||||||||||||||||||||||||||||||||
] / (site_idx + 2) | ||||||||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||||||
props[key] = None | ||||||||||||||||||||||||||||||||
warnings.warn( | ||||||||||||||||||||||||||||||||
f"Sites with different site property {key} are merged. So property is set to none" | ||||||||||||||||||||||||||||||||
f"Sites with different site property {key} are merged. But property is set to None", | ||||||||||||||||||||||||||||||||
stacklevel=2, | ||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||
sites.append(PeriodicSite(species, coords, self.lattice, properties=props)) | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NEED CONFIRM on 091c911: Is
_properties
misplaced? an instance variable maybe?pymatgen/src/pymatgen/core/structure.py
Lines 211 to 220 in 31f1e1f