-
Notifications
You must be signed in to change notification settings - Fork 871
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 3 commits
644c12d
87da226
bc6645b
5b4b0ae
c2ccd92
13e58d6
dd1b338
2289d54
9618ddc
467b840
658b33a
99988ef
702b80b
091c911
ecc2d05
78e880b
8c1fb79
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 | ||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -4717,43 +4717,53 @@ 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. | ||||||||||||||||||||||||||||||||
"""Merges sites (by adding occupancies) within tolerance and optionally 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" 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. | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
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"}: | ||||||||||||||||||||||||||||||||
warnings.warn( | ||||||||||||||||||||||||||||||||
"mode would only allow full name sum/delete/average after 2025-12-01", DeprecationWarning, stacklevel=2 | ||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
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 | ||||||||||||||||||||||||||||||||
if mode.lower()[0] == "s": | ||||||||||||||||||||||||||||||||
species += sp | ||||||||||||||||||||||||||||||||
offset = self[i].frac_coords - coords | ||||||||||||||||||||||||||||||||
coords += ((offset - np.round(offset)) / (n + 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 props[key] is not None and not np.array_equal(self[i].properties[key], props[key]): | ||||||||||||||||||||||||||||||||
if mode.lower()[0] == "a" and isinstance(props[key], float): | ||||||||||||||||||||||||||||||||
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. I guess only allowing pymatgen/tests/core/test_structure.py Lines 1677 to 1679 in 31f1e1f
Also the docstring would be clarified to explain this behaviour. |
||||||||||||||||||||||||||||||||
# update a running total | ||||||||||||||||||||||||||||||||
props[key] = props[key] * (n + 1) / (n + 2) + self[i].properties[key] / (n + 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. So 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.
Want to hear more opinions on this, I guess it's beneficial to allow only full name (
"sum", "delete", "average"
) instead of checking first letter only to make use of the IDE auto-complete feature and facilitate typingAlso using the full name would be more readable:
mode="sum"
instead ofmode="s"